From f2ae88b4e97fdd24971bb6ed7af6e396ffdef0b2 Mon Sep 17 00:00:00 2001 From: lim39869-create Date: Mon, 28 Sep 2026 11:49:29 +0800 Subject: [PATCH] PayGateway: epay-compatible Go payment gateway with plugin channels and verifiable ledger --- .env.example | 141 ++ .github/workflows/ci.yml | 38 + .gitignore | 46 + CONTRIBUTING.md | 14 + LICENSE | 21 + Makefile | 34 + README.md | 263 +++ SECURITY.md | 12 + assets/logo/README.md | 18 + assets/logo/favicon.svg | 5 + assets/logo/paygateway-mark-light.svg | 11 + assets/logo/paygateway-mark.svg | 12 + assets/logo/paygateway-wordmark.svg | 8 + cmd/e2e_ledger/main.go | 67 + cmd/gateway/alloc.go | 263 +++ cmd/gateway/channels.go | 169 ++ cmd/gateway/check.go | 164 ++ cmd/gateway/close_time_test.go | 59 + cmd/gateway/config.go | 297 ++++ cmd/gateway/devices.go | 38 + cmd/gateway/e2e_lifecycle_test.go | 263 +++ cmd/gateway/handlers_epay.go | 363 ++++ cmd/gateway/handlers_qr.go | 250 +++ cmd/gateway/ledger.go | 448 +++++ cmd/gateway/ledger_match_test.go | 804 +++++++++ cmd/gateway/main.go | 918 ++++++++++ cmd/gateway/notify.go | 192 +++ cmd/gateway/return_url.go | 72 + cmd/gateway/return_url_test.go | 59 + cmd/gateway/risk.go | 104 ++ cmd/gateway/risk_test.go | 68 + cmd/tunnel/main.go | 86 + current_callback.example.txt | 5 + deploy/Dockerfile | 38 + deploy/backup.sh | 54 + deploy/docker-compose.yml | 30 + deploy/paygateway.service | 44 + docs/ARCHITECTURE.md | 560 ++++++ docs/DESIGN-DEVICE-ACTOR.md | 25 + docs/DESIGN-FAIR-SCHEDULER.md | 20 + docs/DESIGN-NOTIFY-RETRY-LATEST-URL.md | 224 +++ docs/DESIGN-REUSE-UPDATE-AMOUNT.md | 86 + docs/MIANQIAN.md | 39 + docs/OPERATIONS.md | 53 + docs/PLAN-DYNAMIC-CLOSE-ALLOC.md | 388 +++++ docs/RESILIENCE.md | 173 ++ go.mod | 55 + go.sum | 154 ++ internal/account/crypto.go | 112 ++ internal/account/model.go | 100 ++ internal/account/pool.go | 492 ++++++ internal/account/qrcode.go | 34 + internal/account/qrcode_test.go | 143 ++ internal/adbmgr/adbmgr.go | 809 +++++++++ internal/adbmgr/adbmgr_screen_test.go | 50 + internal/adbmgr/keepalive.go | 113 ++ internal/adbmgr/keepalive_test.go | 75 + internal/admin/accounts.go | 861 ++++++++++ internal/admin/admin.go | 1213 +++++++++++++ internal/admin/capture/alipay.go | 356 ++++ internal/admin/capture/alipay_ledger.go | 258 +++ internal/admin/capture/alipay_ledger_test.go | 58 + internal/admin/capture/alipay_lock.go | 183 ++ internal/admin/capture/alipay_note.go | 85 + internal/admin/capture/alipay_note_test.go | 44 + internal/admin/capture/common.go | 138 ++ .../capture/testdata/alipay_detail_note.xml | 13 + .../capture/testdata/alipay_ledger_ref.xml | 1 + internal/admin/capture/wait.go | 30 + internal/admin/capture/wechat.go | 387 +++++ internal/admin/capture/wechat_ledger.go | 164 ++ internal/admin/capture/wechat_ledger_test.go | 240 +++ internal/admin/deviceconsole.go | 415 +++++ internal/admin/loginlimiter.go | 185 ++ internal/admin/personal.go | 721 ++++++++ internal/admin/qrcode.go | 44 + internal/admin/qrcode_alipay.go | 145 ++ internal/admin/qrcode_renew.go | 247 +++ internal/admin/qrcode_test.go | 72 + internal/admin/qrcode_wechat.go | 128 ++ internal/admin/reconciler.go | 347 ++++ .../admin/static/assets/index-BT-lxyOz.css | 1 + .../admin/static/assets/index-Bdo7HwZH.js | 127 ++ internal/admin/static/index.html | 14 + internal/admin/upstreams.go | 453 +++++ internal/admin/wizard.go | 377 +++++ internal/admin/wizard_test.go | 39 + internal/alert/alert.go | 179 ++ internal/channel/alipay/alipay.go | 447 +++++ internal/channel/alipay/alipay_test.go | 161 ++ internal/channel/interface.go | 157 ++ internal/channel/personal/personal.go | 279 +++ internal/channel/registry.go | 139 ++ internal/channel/wechat/wechat.go | 353 ++++ internal/epay/sign.go | 85 + internal/ledger/ledger.go | 224 +++ internal/ledger/ledger_test.go | 369 ++++ internal/ledgermon/detector.go | 178 ++ internal/ledgermon/detector_test.go | 129 ++ internal/ledgermon/device_actor.go | 577 +++++++ internal/ledgermon/monitor.go | 652 +++++++ internal/ledgermon/monitor_test.go | 278 +++ internal/ledgermon/reader_alipay.go | 233 +++ internal/ledgermon/reader_wechat.go | 264 +++ internal/ledgermon/scheduler.go | 423 +++++ internal/ledgermon/scheduler_test.go | 233 +++ internal/logger/logger.go | 300 ++++ internal/model/exception.go | 63 + internal/model/order.go | 134 ++ internal/notify/urlguard.go | 139 ++ internal/notify/urlguard_test.go | 62 + internal/notify/worker.go | 181 ++ internal/notifywatcher/hash.go | 12 + internal/notifywatcher/notifywatcher.go | 221 +++ internal/notifywatcher/notifywatcher_test.go | 195 +++ internal/notifywatcher/parse.go | 226 +++ internal/notifywatcher/parse_test.go | 235 +++ internal/order/service.go | 1310 +++++++++++++++ internal/order/service_test.go | 1497 +++++++++++++++++ internal/personal/model.go | 191 +++ internal/personal/notecode.go | 108 ++ internal/personal/notecode_test.go | 82 + internal/personal/ratelimit.go | 93 + internal/personal/ratelimit_test.go | 65 + internal/personal/sign.go | 79 + internal/personal/sign_test.go | 113 ++ internal/personal/token.go | 127 ++ internal/personal/token_test.go | 96 ++ internal/pool/waitpay_pool.go | 800 +++++++++ internal/pool/waitpay_pool_test.go | 671 ++++++++ internal/ratelimit/ratelimit.go | 108 ++ internal/reconciler/dailyreport.go | 166 ++ internal/reconciler/devicewatcher.go | 363 ++++ internal/reconciler/devicewatcher_test.go | 265 +++ internal/reconciler/scheduler.go | 441 +++++ internal/scrcpy/bridge.go | 361 ++++ internal/scrcpy/scrcpy-server.jar | Bin 0 -> 733706 bytes internal/scrcpy/scrcpy.go | 286 ++++ internal/scrcpy/scrcpy_test.go | 62 + internal/scrcpy/session.go | 218 +++ internal/store/accountdb.go | 303 ++++ internal/store/ledgerdb.go | 237 +++ internal/store/logdb.go | 133 ++ internal/store/notify_tasks_test.go | 69 + internal/store/personalstore.go | 231 +++ .../store/personalstore_device_accounts.go | 102 ++ internal/store/personalstore_devices.go | 147 ++ internal/store/personalstore_ledger.go | 70 + .../store/personalstore_migration_test.go | 53 + internal/store/personalstore_pushes.go | 146 ++ internal/store/personalstore_qrcodes.go | 194 +++ internal/store/sqlite.go | 387 +++++ internal/store/sqlite_exception.go | 183 ++ internal/store/sqlite_rest.go | 697 ++++++++ internal/store/store.go | 164 ++ internal/store/upstreamdb.go | 246 +++ internal/tunnel/cloudflared.go | 22 + internal/tunnel/config.go | 114 ++ internal/tunnel/tunnel.go | 348 ++++ internal/upstream/model.go | 57 + internal/upstream/pool.go | 120 ++ internal/web/note_test.go | 66 + internal/web/pages.go | 483 ++++++ run.sh | 32 + scripts/adb-soak.sh | 65 + scripts/download-cloudflared.sh | 78 + scripts/download-scrcpy-server.sh | 25 + scripts/e2e_usb_realdevice.sh | 150 ++ scripts/setup-domain.sh | 242 +++ start.sh | 45 + status.sh | 20 + stop.sh | 41 + web/admin/DESIGN.md | 132 ++ web/admin/index.html | 13 + web/admin/package-lock.json | 1443 ++++++++++++++++ web/admin/package.json | 21 + web/admin/public/assets/favicon.svg | 5 + .../public/assets/paygateway-mark-light.svg | 11 + web/admin/public/assets/paygateway-mark.svg | 12 + web/admin/src/App.vue | 398 +++++ web/admin/src/DESIGN.md | 232 +++ web/admin/src/api.js | 214 +++ web/admin/src/components/DeviceStream.vue | 265 +++ web/admin/src/main.js | 18 + web/admin/src/store.js | 9 + web/admin/src/theme.css | 645 +++++++ web/admin/src/views/Accounts.vue | 468 ++++++ web/admin/src/views/Channels.vue | 296 ++++ web/admin/src/views/Dashboard.vue | 621 +++++++ web/admin/src/views/Ledger.vue | 360 ++++ web/admin/src/views/Login.vue | 269 +++ web/admin/src/views/Logs.vue | 196 +++ web/admin/src/views/NotifyTasks.vue | 176 ++ web/admin/src/views/Orders.vue | 242 +++ web/admin/src/views/PersonalDevices.vue | 1051 ++++++++++++ web/admin/src/views/Qrcodes.vue | 240 +++ web/admin/src/views/Reconciler.vue | 330 ++++ web/admin/src/views/Settings.vue | 255 +++ web/admin/src/views/Unmatched.vue | 177 ++ web/admin/src/views/Upstreams.vue | 350 ++++ web/admin/vite.config.js | 20 + 201 files changed, 44898 insertions(+) create mode 100644 .env.example create mode 100644 .github/workflows/ci.yml create mode 100644 .gitignore create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE create mode 100644 Makefile create mode 100755 README.md create mode 100644 SECURITY.md create mode 100644 assets/logo/README.md create mode 100644 assets/logo/favicon.svg create mode 100644 assets/logo/paygateway-mark-light.svg create mode 100644 assets/logo/paygateway-mark.svg create mode 100644 assets/logo/paygateway-wordmark.svg create mode 100644 cmd/e2e_ledger/main.go create mode 100644 cmd/gateway/alloc.go create mode 100644 cmd/gateway/channels.go create mode 100755 cmd/gateway/check.go create mode 100644 cmd/gateway/close_time_test.go create mode 100644 cmd/gateway/config.go create mode 100644 cmd/gateway/devices.go create mode 100644 cmd/gateway/e2e_lifecycle_test.go create mode 100644 cmd/gateway/handlers_epay.go create mode 100644 cmd/gateway/handlers_qr.go create mode 100644 cmd/gateway/ledger.go create mode 100644 cmd/gateway/ledger_match_test.go create mode 100755 cmd/gateway/main.go create mode 100644 cmd/gateway/notify.go create mode 100644 cmd/gateway/return_url.go create mode 100644 cmd/gateway/return_url_test.go create mode 100644 cmd/gateway/risk.go create mode 100644 cmd/gateway/risk_test.go create mode 100755 cmd/tunnel/main.go create mode 100644 current_callback.example.txt create mode 100644 deploy/Dockerfile create mode 100755 deploy/backup.sh create mode 100644 deploy/docker-compose.yml create mode 100644 deploy/paygateway.service create mode 100755 docs/ARCHITECTURE.md create mode 100644 docs/DESIGN-DEVICE-ACTOR.md create mode 100644 docs/DESIGN-FAIR-SCHEDULER.md create mode 100644 docs/DESIGN-NOTIFY-RETRY-LATEST-URL.md create mode 100644 docs/DESIGN-REUSE-UPDATE-AMOUNT.md create mode 100644 docs/MIANQIAN.md create mode 100644 docs/OPERATIONS.md create mode 100644 docs/PLAN-DYNAMIC-CLOSE-ALLOC.md create mode 100755 docs/RESILIENCE.md create mode 100755 go.mod create mode 100755 go.sum create mode 100755 internal/account/crypto.go create mode 100755 internal/account/model.go create mode 100755 internal/account/pool.go create mode 100644 internal/account/qrcode.go create mode 100644 internal/account/qrcode_test.go create mode 100644 internal/adbmgr/adbmgr.go create mode 100644 internal/adbmgr/adbmgr_screen_test.go create mode 100644 internal/adbmgr/keepalive.go create mode 100644 internal/adbmgr/keepalive_test.go create mode 100755 internal/admin/accounts.go create mode 100755 internal/admin/admin.go create mode 100644 internal/admin/capture/alipay.go create mode 100644 internal/admin/capture/alipay_ledger.go create mode 100644 internal/admin/capture/alipay_ledger_test.go create mode 100644 internal/admin/capture/alipay_lock.go create mode 100644 internal/admin/capture/alipay_note.go create mode 100644 internal/admin/capture/alipay_note_test.go create mode 100644 internal/admin/capture/common.go create mode 100644 internal/admin/capture/testdata/alipay_detail_note.xml create mode 100644 internal/admin/capture/testdata/alipay_ledger_ref.xml create mode 100644 internal/admin/capture/wait.go create mode 100644 internal/admin/capture/wechat.go create mode 100644 internal/admin/capture/wechat_ledger.go create mode 100644 internal/admin/capture/wechat_ledger_test.go create mode 100644 internal/admin/deviceconsole.go create mode 100644 internal/admin/loginlimiter.go create mode 100755 internal/admin/personal.go create mode 100755 internal/admin/qrcode.go create mode 100644 internal/admin/qrcode_alipay.go create mode 100644 internal/admin/qrcode_renew.go create mode 100755 internal/admin/qrcode_test.go create mode 100644 internal/admin/qrcode_wechat.go create mode 100755 internal/admin/reconciler.go create mode 100644 internal/admin/static/assets/index-BT-lxyOz.css create mode 100644 internal/admin/static/assets/index-Bdo7HwZH.js create mode 100644 internal/admin/static/index.html create mode 100755 internal/admin/upstreams.go create mode 100644 internal/admin/wizard.go create mode 100644 internal/admin/wizard_test.go create mode 100755 internal/alert/alert.go create mode 100755 internal/channel/alipay/alipay.go create mode 100755 internal/channel/alipay/alipay_test.go create mode 100755 internal/channel/interface.go create mode 100755 internal/channel/personal/personal.go create mode 100755 internal/channel/registry.go create mode 100755 internal/channel/wechat/wechat.go create mode 100755 internal/epay/sign.go create mode 100755 internal/ledger/ledger.go create mode 100755 internal/ledger/ledger_test.go create mode 100644 internal/ledgermon/detector.go create mode 100644 internal/ledgermon/detector_test.go create mode 100644 internal/ledgermon/device_actor.go create mode 100644 internal/ledgermon/monitor.go create mode 100644 internal/ledgermon/monitor_test.go create mode 100644 internal/ledgermon/reader_alipay.go create mode 100644 internal/ledgermon/reader_wechat.go create mode 100644 internal/ledgermon/scheduler.go create mode 100644 internal/ledgermon/scheduler_test.go create mode 100755 internal/logger/logger.go create mode 100755 internal/model/exception.go create mode 100755 internal/model/order.go create mode 100644 internal/notify/urlguard.go create mode 100644 internal/notify/urlguard_test.go create mode 100755 internal/notify/worker.go create mode 100644 internal/notifywatcher/hash.go create mode 100644 internal/notifywatcher/notifywatcher.go create mode 100644 internal/notifywatcher/notifywatcher_test.go create mode 100644 internal/notifywatcher/parse.go create mode 100644 internal/notifywatcher/parse_test.go create mode 100755 internal/order/service.go create mode 100755 internal/order/service_test.go create mode 100755 internal/personal/model.go create mode 100755 internal/personal/notecode.go create mode 100755 internal/personal/notecode_test.go create mode 100755 internal/personal/ratelimit.go create mode 100755 internal/personal/ratelimit_test.go create mode 100755 internal/personal/sign.go create mode 100755 internal/personal/sign_test.go create mode 100755 internal/personal/token.go create mode 100755 internal/personal/token_test.go create mode 100644 internal/pool/waitpay_pool.go create mode 100644 internal/pool/waitpay_pool_test.go create mode 100644 internal/ratelimit/ratelimit.go create mode 100755 internal/reconciler/dailyreport.go create mode 100755 internal/reconciler/devicewatcher.go create mode 100755 internal/reconciler/devicewatcher_test.go create mode 100755 internal/reconciler/scheduler.go create mode 100644 internal/scrcpy/bridge.go create mode 100644 internal/scrcpy/scrcpy-server.jar create mode 100644 internal/scrcpy/scrcpy.go create mode 100644 internal/scrcpy/scrcpy_test.go create mode 100644 internal/scrcpy/session.go create mode 100755 internal/store/accountdb.go create mode 100755 internal/store/ledgerdb.go create mode 100644 internal/store/logdb.go create mode 100644 internal/store/notify_tasks_test.go create mode 100755 internal/store/personalstore.go create mode 100644 internal/store/personalstore_device_accounts.go create mode 100755 internal/store/personalstore_devices.go create mode 100644 internal/store/personalstore_ledger.go create mode 100644 internal/store/personalstore_migration_test.go create mode 100755 internal/store/personalstore_pushes.go create mode 100644 internal/store/personalstore_qrcodes.go create mode 100755 internal/store/sqlite.go create mode 100755 internal/store/sqlite_exception.go create mode 100755 internal/store/sqlite_rest.go create mode 100755 internal/store/store.go create mode 100755 internal/store/upstreamdb.go create mode 100644 internal/tunnel/cloudflared.go create mode 100755 internal/tunnel/config.go create mode 100755 internal/tunnel/tunnel.go create mode 100755 internal/upstream/model.go create mode 100755 internal/upstream/pool.go create mode 100644 internal/web/note_test.go create mode 100755 internal/web/pages.go create mode 100755 run.sh create mode 100644 scripts/adb-soak.sh create mode 100644 scripts/download-cloudflared.sh create mode 100644 scripts/download-scrcpy-server.sh create mode 100755 scripts/e2e_usb_realdevice.sh create mode 100755 scripts/setup-domain.sh create mode 100755 start.sh create mode 100755 status.sh create mode 100755 stop.sh create mode 100644 web/admin/DESIGN.md create mode 100755 web/admin/index.html create mode 100755 web/admin/package-lock.json create mode 100755 web/admin/package.json create mode 100644 web/admin/public/assets/favicon.svg create mode 100644 web/admin/public/assets/paygateway-mark-light.svg create mode 100644 web/admin/public/assets/paygateway-mark.svg create mode 100755 web/admin/src/App.vue create mode 100644 web/admin/src/DESIGN.md create mode 100755 web/admin/src/api.js create mode 100644 web/admin/src/components/DeviceStream.vue create mode 100755 web/admin/src/main.js create mode 100755 web/admin/src/store.js create mode 100755 web/admin/src/theme.css create mode 100755 web/admin/src/views/Accounts.vue create mode 100755 web/admin/src/views/Channels.vue create mode 100755 web/admin/src/views/Dashboard.vue create mode 100755 web/admin/src/views/Ledger.vue create mode 100755 web/admin/src/views/Login.vue create mode 100755 web/admin/src/views/Logs.vue create mode 100755 web/admin/src/views/NotifyTasks.vue create mode 100755 web/admin/src/views/Orders.vue create mode 100755 web/admin/src/views/PersonalDevices.vue create mode 100644 web/admin/src/views/Qrcodes.vue create mode 100755 web/admin/src/views/Reconciler.vue create mode 100755 web/admin/src/views/Settings.vue create mode 100755 web/admin/src/views/Unmatched.vue create mode 100755 web/admin/src/views/Upstreams.vue create mode 100755 web/admin/vite.config.js diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..a2a14ca --- /dev/null +++ b/.env.example @@ -0,0 +1,141 @@ +# PayGateway 配置示例 —— 复制为 .env 后修改 +# cp .env.example .env && vim .env +# +# 配置加载顺序:.env 文件(本文件) → 运行时设置(settings 表,可由管理后台改) +# 同名键后者覆盖前者;密钥类一律只从 .env 读取(不进 settings)。 +# +# ⚠️ 重要:以下密钥更换会导致数据/会话不可恢复 +# - ACCOUNT_MASTER_KEY:accounts.db / upstreams.db 全部密文无法解密 +# - LEDGER_PRIVATE_KEY:旧账本条目验签失败 +# - QR_TOKEN_SECRET:旧二维码套码 token 失效,需重生成 +# - JWT_SECRET:所有登录会话失效(用户被踢下线,重新登录即可,无数据风险) + +# ===== 网关监听 ===== +# 网关业务端口(易支付 /mq / 套码 /qr 等),对外暴露用 +GATEWAY_ADDR=:8080 +# 管理后台端口(Vue3 SPA + REST API + WebSocket) +# 生产建议改为 127.0.0.1:8081 配合 nginx 反代,或不暴露 +ADMIN_ADDR=:8081 + +# ===== 对外地址 ===== +# 网关公网地址(用于向支付平台回调注册等) +# 同机填 http://127.0.0.1:8080;公网填隧道/域名,留空自动推断为 http://127.0.0.1:8080 +EPAY_PUBLIC_BASE_URL=http://127.0.0.1:8080 +# 可选设备事件适配器的服务端可达地址。 +# PERSONAL_BASE_URL= +# 通道回调地址(一般无需手动设;启动时由隧道动态更新) +# ALIPAY_NOTIFY_URL=http://localhost:8080/alipay/notify +# WECHAT_NOTIFY_URL=http://localhost:8080/wechat/notify + +# ===== 上游对接(已废弃 .env 方式!)===== +# 每个上游独立 pid + 密钥,AES-256-GCM 加密存于独立 upstreams.db。 +# 网关不再读取 EPAY_PID / EPAY_KEY,未登记的上游一律拒绝下单。 +# 配置入口:http://127.0.0.1:8081 → 上游对接 → 新增上游 + +# ===== 支付账户密钥(已废弃 .env 方式!)===== +# 全部走管理后台 → 账户池 → 新增账户 配置;AES-256-GCM 加密存 accounts.db +# 以下仅为历史字段注释,均不再读取: +# ALIPAY_APP_ID / ALIPAY_PRIVATE_KEY_FILE / ALIPAY_PUBLIC_KEY_FILE +# WECHAT_APP_ID / WECHAT_MCH_ID / WECHAT_API_KEY / WECHAT_IS_PROD + +# ===== 订单自动关闭(动态关单窗口,docs/PLAN-DYNAMIC-CLOSE-ALLOC.md)===== +# 【商家通道】未支付订单自动关闭时间(分钟,0=不自动关闭)。 +# 商家通道走官方 API、可主动查单、无监听延迟问题 → 固定窗口即可(默认 30min)。 +# 关单前必查单:已支付→补账;查单失败→跳过留待调度器/人工池;确认未支付→才关闭。 +ORDER_CLOSE_MINUTES=30 +# 【个人免签通道】动态窗口由设备负载驱动,clamp 在 [MIN, MAX] 之间(仅影响 personal.* 通道)。 +# 窗口 = clamp(设备一轮耗时(活跃账户数×配额×poll成本)×1.5 + 60s, MIN, MAX) +# - 低负载(1~2 活跃账户)→ 逼近 MIN:确认快,尽早释放池配额 +# - 高负载(多账户积压)→ 逼近 MAX:给设备更多确认时间,防误关已付款订单 +# 0 或 MIN>=MAX → 个人免签退化为 ORDER_CLOSE_MINUTES 静态窗口 +# 注意:窗口只影响「未支付订单」何时关,已付款订单始终秒级入账,不受影响 +# 动态窗口下限(分钟,默认 5):下限锚点 = 用户扫码→支付操作时间(30s~2min) + 一轮完整确认 +ORDER_CLOSE_MIN=5 +# 动态窗口上限(分钟,默认 10):上限锚点 = 用户体验红线;超过转人工异常池升级处理,不是无限挂起 +ORDER_CLOSE_MAX=10 +# 关闭扫描间隔秒数(动态窗口下建议 30s 起,跟随 MAX 窗口粒度) +ORDER_CLOSE_INTERVAL=30 + +# ===== 设备负载均衡(docs/PLAN-DYNAMIC-CLOSE-ALLOC.md §4.5/§5)===== +# 单设备 WAIT_PAY 订单承载上限(0=按公式自动算:(MAX-60s)/(单次poll 4s)≈135) +# 设备超限 → 均衡分配器跳过该设备,新单路由到其他健康设备 +DEVICE_LOAD_CAP=0 +# 设备降权阈值(占 DEVICE_LOAD_CAP 比例,默认 0.8):超过后该设备在分配中降权(缓冲带) +DEVICE_OVERLOAD_RATIO=0.8 +# 单次 poll 成本估算(ms,动态窗口/设备能力计算用;埋点校准后可留 0) +# DEVICE_POLL_COST_MS=4000 + +# ===== 存储 / 日志 ===== +# 业务库 SQLite 路径(订单/异常池/回调队列/设置) +ORDER_DB=data/paygateway.db +# 日志文件路径(slog JSON + 按天分文件:{date} 自动替换为当天日期,如 paygateway-2026-08-18.log) +# 每文件 50MB 封顶、保留 10 份 × 30 天,超过自动清理;日志同时写入独立日志库 data/logs.db(长期保存) +LOG_FILE=logs/paygateway-{date}.log +# 日志级别:debug / info / warn / error +LOG_LEVEL=info + +# ===== 管理后台鉴权 ===== +# 管理员凭据已迁移到主数据库(settings 表,bcrypt 哈希),不再使用环境变量。 +# 首次启动(DB 无管理员时)访问管理后台自动进入注册页;注册后入口自动关闭。 +# 重置管理员:删除 paygateway.db settings 表中 admin_username / admin_password_hash 两行后重启。 +# JWT 签名密钥:留空 = 每次重启随机生成(重启后需重新登录) +# 持久化方法:固定为 64 位十六进制(openssl rand -hex 32);更换会踢所有已登录用户下线 +# JWT_SECRET= + +# ===== 加密 / 签名密钥(前 3 个缺失=服务拒绝启动;后 3 个可空)===== +# 【必备·固定】账户加密主密钥:accounts.db / upstreams.db 全部密文用此加解密 +# 首次部署生成:openssl rand -hex 32 +# ⚠️ 一旦部署,永不能改 —— 改了等于丢掉所有已配置账户/上游密钥 +ACCOUNT_MASTER_KEY=REPLACE_WITH_openssl_rand_hex_32 + +# 【可选·推荐固定】审计账本签名私钥(ECDSA P-256 PEM 格式) +# 留空 = 首次启动自动生成并持久化到 settings 表(重启复用) +# 显式提供可跨实例迁移账本;更换则旧账本条目验签失败 +# LEDGER_PRIVATE_KEY= + +# 【可选·推荐固定】个人免签套码 token 签名密钥 +# 留空 = 首次启动自动生成并持久化到 settings 表(重启复用) +# 更换则旧"用户扫码 → /mq/{token}"链接全部失效,需在管理后台重新生成套码 +# QR_TOKEN_SECRET= + +# ===== 个人免签风控(金额单位均为分)===== +# 单笔限额(默认 50000 = 500 元;0 = 不限) +# PERSONAL_MAX_AMOUNT=50000 +# 大额告警阈值(默认 20000 = 200 元;超此触发 Bark / Webhook 告警) +# PERSONAL_BIG_AMOUNT=20000 +# 单设备每分钟收款推送上限(默认 30;超过则限流,防止监听端异常刷消息) +# PERSONAL_MAX_PER_MIN=30 + +# ===== 告警(RESILIENCE.md P1-3)===== +# Bark 推送 URL(https://api.day.app/),空=禁用 +# ALERT_BARK_URL= +# 通用 Webhook URL(POST JSON:掉线 / 账本异常 / 风控文案检测) +# ALERT_WEBHOOK_URL= + +# ===== 公网接口 IP 限流(防刷 / 防 DoS)===== +# 窗口内单 IP 最大请求数(仅作用于 /submit.php /api.php /qr/refresh /mq/:token,不影响 /alipay/notify /wechat/notify /personal/* 签名回调),0=禁用 +# PUBLIC_RATE_MAX=60 +# 滑动窗口秒数,0=禁用 +# PUBLIC_RATE_WINDOW_SEC=60 + +# ===== 可选二维码适配器 ===== +# 具体模式由授权的业务适配器定义。 +PERSONAL_QR_MODE=direct + +# ===== Cloudflare 命名隧道(固定域名,生产环境推荐)===== +# 非空 = 命名隧道模式(cloudflared tunnel run --config,公网固定域名) +# 空 = 快速隧道(trycloudflare.com 随机域名,仅测试用,重启后域名变化) +CF_TUNNEL_HOSTNAME=your-domain.example.com +# 隧道凭据 JSON 的 base64 编码(含 TunnelID/AccountTag/TunnelSecret) +# 生成方法:base64 -w0 < ~/.cloudflared/paygateway.json +CF_TUNNEL_CREDENTIALS=your_base64_encoded_tunnel_credentials +# Cloudflare 账户邮箱(setup-domain.sh 需要) +CF_EMAIL=your-email@example.com +# Cloudflare Global API Key(setup-domain.sh 自动配置 zone/DNS/SSL) +CF_GLOBAL_KEY=your_global_api_key + +# ===== DNSHe API(de5.net 免费子域名需要;setup-domain.sh 全自动处理)===== +# 脚本能力:子域名不存在时自动注册 + 自动把子域 NS 委派给 Cloudflare +DNSHE_API_KEY=your_dnshe_api_key +DNSHE_API_SECRET=your_dnshe_api_secret +DNSHE_API_BASE=https://api005.dnshe.com diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9054d04 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,38 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + test: + name: build / vet / test / race / fmt + runs-on: ubuntu-latest + timeout-minutes: 15 + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: '1.25' + cache: true + + - name: gofmt 门禁(未格式化即失败) + run: | + unformatted=$(gofmt -l cmd/ internal/) + if [ -n "$unformatted" ]; then + echo "以下文件未通过 gofmt:" + echo "$unformatted" + exit 1 + fi + + - name: Build + run: go build ./... + + - name: Vet + run: go vet ./... + + - name: Test(含竞态检测) + run: go test -race ./... -count=1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b7b8d41 --- /dev/null +++ b/.gitignore @@ -0,0 +1,46 @@ +# ---- 环境与密钥 ---- +.env +*.pem +*.key + +# ---- 运行时数据 / 数据库 ---- +data/ +backup/ +paygateway.db +personal.db +accounts.db +*.db-shm +*.db-wal +logs/ +.gateway.pid +*.pid + +# ---- 构建产物 ---- +/gateway +internal/tunnel/cloudflared +*.test +*.out +vendor/ +__pycache__/ +*.pyc + +# ---- Removed Android listener artifacts ---- +listener-app/ +*.apk +*.aab +*.keystore +*.jks + +# ---- Web 前端 ---- +web/admin/node_modules/ +web/admin/dist/ +web/admin/.vite/ + +# ---- 系统 / IDE ---- +.DS_Store +Thumbs.db +.idea/ +*.iml +.vscode/ +*.swp +*.swo diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..869e758 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,14 @@ +# Contributing + +Bug reports, design feedback, tests, and documentation improvements are +welcome. Include a reproducible test or explanation when changing order, +ledger, callback, or channel behavior. + +Before opening a pull request, run: + +```bash +make check +``` + +Never include credentials, real payment data, production URLs, device +identifiers, or private logs in a contribution. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..7216ead --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 PayGateway contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..59be5fd --- /dev/null +++ b/Makefile @@ -0,0 +1,34 @@ +# PayGateway 开发门禁 —— 与 .github/workflows/ci.yml 保持一致 +# 用法: +# make check # 本地全量检查(fmt/build/vet/test/race),提交前必跑 +# make test # 仅单测 +# make race # 竞态检测 + +GO ?= go +PKG ?= ./... + +.PHONY: fmt-check build vet test race check + +# gofmt 门禁:未格式化即失败 +fmt-check: + @unformatted=$$(gofmt -l cmd/ internal/); \ + if [ -n "$$unformatted" ]; then \ + echo "❌ 以下文件未通过 gofmt:"; echo "$$unformatted"; exit 1; \ + fi + @echo "✅ gofmt 通过" + +build: + $(GO) build ./... + +vet: + $(GO) vet ./... + +test: + $(GO) test $(PKG) -count=1 + +race: + $(GO) test -race $(PKG) -count=1 + +# 提交前全量检查(CI 同一套) +check: fmt-check build vet race + @echo "✅ 全部检查通过,可以提交" diff --git a/README.md b/README.md new file mode 100755 index 0000000..a0d83ec --- /dev/null +++ b/README.md @@ -0,0 +1,263 @@ +

+ PayGateway +

+ +

可扩展、可验证、面向授权业务集成的 Go 支付网关

+ +# PayGateway 支付网关 + +一个面向学习和授权业务集成的 Go 支付网关参考实现,兼容易支付(epay)协议,支持插件化商家通道、订单编排、可验证账本、异常对账和弹性调度。仓库同时保留设备自动化相关的通用工程实现,便于研究设备访问、事件处理和故障恢复。 + +> 详细设计见 [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md)(架构)、[`docs/MIANQIAN.md`](docs/MIANQIAN.md)(设备自动化概览)、[`docs/RESILIENCE.md`](docs/RESILIENCE.md)(弹性/对账)和 [`docs/PLAN-DYNAMIC-CLOSE-ALLOC.md`](docs/PLAN-DYNAMIC-CLOSE-ALLOC.md)(动态调度)。 + +## 项目定位 + +`paygateway` 不是一个只展示接口调用的 Demo,而是一个经过真实业务验证的支付与账务基础设施参考实现。项目重点不在于堆叠支付渠道数量,而在于把以下问题放进同一套可审查的工程模型中: + +- 支付通道可插拔,核心订单流程不依赖单一渠道; +- 订单状态、回调、补账和异常处理具备明确的状态边界; +- 资金事件进入可验证的追加式账本,历史记录的静默修改可以被发现; +- 管理后台、日志、备份、告警和测试构成完整的运行闭环; +- 设备自动化适配器与订单、账本和异常池隔离,结果不明确时默认人工核查。 + +项目当前已验证支付宝、微信相关商家通道和设备事件适配能力。国际支付、数字资产支付及其他地区通道属于可扩展方向,尚不代表仓库已经提供对应的生产适配器。 + +## 品牌资源 + +Logo 和 favicon 位于 [`assets/logo/`](assets/logo/),可用于项目主页、技术博客、后台界面和社区头像。标识的视觉语义是“插件化通道、统一编排和可验证账本链”。 + +## 生产参考部署 + +`paygateway` 当前用于作者运营的 [FlashXAPI](https://flashxapi.com) 服务,承载真实的充值、订单、余额和账务流程。FlashXAPI 是作者自己的生产参考部署,不是独立第三方测评,也不是使用本项目的前置条件。 + +这意味着仓库中的订单、回调、账本和异常处理设计并非只在示例测试中存在,而是在实际运行环境中持续验证。生产部署中的用户数据、支付凭证、渠道信息和运行日志不会随仓库公开。 + +需要模型 API 的开发者可以单独了解 FlashXAPI;只希望研究或自部署支付网关的用户可以完全忽略该服务,直接使用本项目的开源代码。 + +## 账务审计模型 + +项目的账本使用哈希链、ECDSA 签名、独立存储和数据库层约束,目标是让资金事件可追溯,并在历史记录被静默修改或删除时提供检测依据。 + +这里的“防篡改”是技术语义:正常业务流程下账本只追加,历史记录的异常变化可被检测。它不等同于绝对不可攻击,也不替代会计凭证、税务申报、司法鉴定或专业审计意见。签名密钥、运行主机、备份和访问权限仍需由部署者独立保护。 + +## 为什么持续维护需要收入 + +本项目由个人维护,代码免费开源,不设置付费墙。持续维护、测试、生产运行和问题响应都需要时间与基础设施成本。作者通过自己运营的 FlashXAPI 服务获得维护收入,而不是向开源用户募捐或对源码设置获取门槛。 + +FlashXAPI 的生产使用关系会在项目发布、博客文章和技术实践记录中保持透明。服务可用性、模型能力和价格属于独立的服务承诺,不应被理解为本仓库对任何第三方渠道的保证。 + +## 社区与实践记录 + +项目后续会通过配套博客发布架构设计、支付异常复盘、账务审计实践和版本更新记录;社区频道用于维护公告、问题交流和使用反馈。公开内容会优先讨论可复用的工程问题,不公开生产密钥、真实订单、用户请求、渠道来源或未经授权的账户操作细节。 + +- **Telegram 频道**:[@paygtway](https://t.me/paygtway) — 项目公告、版本更新和实践文章 +- **Telegram 群组**:[加入群组](https://t.me/+TyBkhTb5HT0xNDFl) — 使用交流、问题反馈和社区讨论 + +如果你在实际部署中发现回调、账务、并发、备份或文档问题,欢迎通过 GitHub Issue 提供最小复现。请先阅读 [`SECURITY.md`](SECURITY.md),不要在公开 Issue 中提交密钥、支付凭证、个人数据或生产地址。 + +--- + +## ✨ 功能特性 + +### 商家通道(官方 API) +- **支付宝**:PC/手机网站支付、当面付扫码(gopay v1.5.119) +- **微信支付**:Native/JSAPI(预留扩展) +- 多商户(上游池):每上游独立 `pid + 密钥`,下单/回调按 pid 路由验签 +- 回调验签(RSA2 / HMAC-SHA256)、查单补账、超时关单 + +### 设备自动化实验适配器(可选) +- 设备访问、事件读取、状态机、调度和健康检查采用独立包封装 +- 事件经来源认证、时间窗口、金额、状态和幂等校验后才可影响订单 +- 设备离线、解析失败和状态冲突进入异常池,不自动确认资金结果 +- 具体平台适配和授权边界见 [`docs/MIANQIAN.md`](docs/MIANQIAN.md);文档不提供第三方账户操作或绕过安全控制的步骤 + +### 基础设施 +- **账户池**:多账户轮询调度、日限额、连续失败熔断、手动/自动冻结,密钥 AES-256-GCM 加密入库 +- **审计账本**:哈希链 + ECDSA 签名(`ledger.db`),每日快照,防篡改可验证 +- **异常池**:金额不匹配 / 监听端离线 / 状态冲突 → 人工核查,绝不自动入账 +- **不漏单保护**:个人通道关单前必查池内动态 `CloseAt` + 监听端离线检查——离线时订单进异常池人工核实(`LISTEN_OFFLINE`),绝不直接关单;商家通道关单前必查单(已支付→补账,查单失败→跳过) +- **管理后台**:Vue3 + Element Plus 单页(embed 进二进制),仪表盘/通道/订单/账户/上游/对账/账本/日志 +- **告警**:Bark / Webhook(掉线、账本异常、风控) + +--- + +## 🚀 快速开始 + +### 1. 编译 + +```bash +# Go 1.25+;隧道功能运行时需要系统 cloudflared +go build -o gateway ./cmd/gateway/ + +# 可选:重新构建前端(需要 node 18+) +cd web/admin && npm install && npm run build +cd ../.. && go build -o gateway ./cmd/gateway/ +``` + +### 2. 配置 `.env` + +```bash +cp .env.example .env && vim .env +``` + +`ACCOUNT_MASTER_KEY` 必须替换(`openssl rand -hex 32` 生成)。管理员账号不再配置在 .env——首次启动访问管理后台会自动进入注册页,凭据 bcrypt 哈希存主数据库 settings 表。 + +| 变量 | 必填 | 默认 | 说明 | +|------|------|------|------| +| `GATEWAY_ADDR` | 是 | `:8080` | 网关监听地址(易支付 /mq、套码 /qr 等) | +| `ADMIN_ADDR` | 否 | `:8081` | 管理后台监听(Vue3 SPA + REST + WS) | +| `JWT_SECRET` | 建议 | 空 | 留空 = 每次重启随机生成(重启后全员被踢下线);管理员凭据存主数据库 settings 表(bcrypt),首次启动自动引导注册 | +| `ACCOUNT_MASTER_KEY` | 必填 | 空 | 账户 / 上游密钥加密主密钥。**缺失则服务拒绝启动**;首次用 `openssl rand -hex 32` 生成;**永不能改**(改 = accounts.db / upstreams.db 全部密文无法解密) | +| `LEDGER_PRIVATE_KEY` | 建议 | 空 | 账本 ECDSA 私钥(PEM);留空 = 首次启动自动生成并持久化到 settings;更改则旧账本条目验签失败 | +| `QR_TOKEN_SECRET` | 个人免签建议 | 空 | 套码 token 签名密钥;留空 = 首次启动自动生成并持久化到 settings;更改则旧套码失效 | +| `ORDER_DB` | 否 | `data/paygateway.db` | 业务库路径(订单/异常池/回调队列/settings) | +| `LOG_FILE` | 否 | `logs/paygateway.log` | 日志文件(slog JSON + lumberjack:50MB×10 份×30 天 压缩) | +| `LOG_LEVEL` | 否 | `info` | `debug` / `info` / `warn` / `error` | +| `ORDER_CLOSE_MINUTES` | 否 | `30` | **商家通道**未支付订单自动关闭分钟数(0=不自动关);商家走官方 API 可查单,固定窗口即可 | +| `ORDER_CLOSE_MIN` | 否 | `5` | 个人免签**动态关单窗口下限**(分钟);0 或 ≥MAX → 退化为静态窗口 | +| `ORDER_CLOSE_MAX` | 否 | `10` | 个人免签**动态关单窗口上限**(分钟);超过转人工异常池升级处理,不无限挂起 | +| `ORDER_CLOSE_INTERVAL` | 否 | `30` | 关单扫描间隔秒数(动态窗口下建议 30s 起) | +| `DEVICE_LOAD_CAP` | 否 | `0` | 单设备 WAIT_PAY 订单承载上限(0=按公式 `(MAX-60s)/poll成本` 自动算≈135);超限设备被均衡分配跳过 | +| `DEVICE_OVERLOAD_RATIO` | 否 | `0.8` | 设备降权阈值(占 CAP 比例):负载进入缓冲带后分配降权,避免临界抖动 | +| `DEVICE_POLL_COST_MS` | 否 | `4000` | 单次 poll 成本估算(ms),动态窗口与设备能力计算用 | +| `EPAY_PUBLIC_BASE_URL` | 否 | 自动 | 网关对外地址(用于通知注册);同机 `http://127.0.0.1:8080`,公网填隧道 / 域名 | +| `PERSONAL_BASE_URL` | 否 | =EPAY | 个人免签监听端可达地址 | +| `ALIPAY_NOTIFY_URL` / `WECHAT_NOTIFY_URL` | 否 | `http://localhost:8080/...` | 通道回调地址,一般无需手填(隧道会覆盖) | +| `PERSONAL_MAX_AMOUNT` | 否 | `50000` | 个人免签单笔限额(分;50000 = 500 元;0 = 不限) | +| `PERSONAL_BIG_AMOUNT` | 否 | `20000` | 大额告警阈值(分;超此触发 Bark / Webhook) | +| `PERSONAL_MAX_PER_MIN` | 否 | `30` | 单设备每分钟收款推送上限(防监听端异常刷消息) | +| `ALERT_BARK_URL` | 否 | 空 | Bark 推送 URL(`https://api.day.app/`),空 = 禁用 | +| `ALERT_WEBHOOK_URL` | 否 | 空 | 通用 Webhook URL(POST JSON:掉线 / 账本异常 / 风控文案检测) | +| `PUBLIC_RATE_MAX` | 否 | `60` | 公网接口滑动窗口内单 IP 最大请求数(仅覆盖 `/submit.php` `/api.php` `/qr/refresh` `/mq/:token`),0 = 禁用 | +| `PUBLIC_RATE_WINDOW_SEC` | 否 | `60` | 上一项的滑动窗口秒数,0 = 禁用 | + +> ⚠️ **密钥三件套(`ACCOUNT_MASTER_KEY` / `LEDGER_PRIVATE_KEY` / `QR_TOKEN_SECRET`)更换/丢失的影响各不同,详见 `.env.example` 头部说明**。账户 / 上游密钥一律走管理后台,不在 `.env` 里。 + +### 3. 启动 + +**推荐:systemd 守护** + +```bash +sudo cp deploy/paygateway.service /etc/systemd/system/ +sudo systemctl daemon-reload +sudo systemctl enable --now paygateway +journalctl -u paygateway -f # 实时日志 +sudo systemctl {status,restart,stop} paygateway +``` + +**裸跑:项目脚本(pid 文件 + 防双启)** + +```bash +./start.sh # 后台启动,写 ./.gateway.pid,重复运行会拒绝 +./status.sh # 查看运行状态 +./stop.sh # 优雅停止(SIGTERM → 10s 后 SIGKILL) +./run.sh # 前台调试运行(go run,不读 .gateway.pid) +tail -f logs/paygateway.log +``` + +> 启动后看 `logs/paygateway.log` 的 `=== 启动配置自检 ===` 段:`0 错误` 才算就绪。 + +### 4. 配置账户(首次必做) + +打开 `http://127.0.0.1:8081` 登录: + +1. **上游对接** → 新增上游(pid + 密钥),否则下单会被拒绝 +2. **账户池** → 按需新增: + - 商家通道:填 AppID + 私钥(`alipay` / `wechat`) + - 设备事件适配器:仅在获得授权的测试环境中配置 + +--- + +## 📡 下单 API(epay 协议兼容) + +```bash +# 参数(MD5 签名,排除 sign/sign_type/pay_type) +curl "http://127.0.0.1:8080/submit.php?pid=1001&type=alipay&out_trade_no=TEST001&name=商品&money=10.00¬ify_url=https://your.com/notify&return_url=https://your.com/return&sign=MD5签名&sign_type=MD5" +``` + +- `type`:`alipay`(个人优先→商家回退)、`wxpay`、`personal_alipay`(强制个人) +- 返回:支付页(个人免签=套码二维码+备注码提示;商家=官方二维码/跳转) +- 异步通知:`trade_status=TRADE_SUCCESS` + MD5 签名 +- 查单:`/api.php?pid=..&out_trade_no=..&sign=..` + +--- + +## 🔒 安全设计 + +- 回调验签(商家 RSA2 / 个人 HMAC-SHA256)+ 时间戳防重放(±5min) +- 金额校验(回调金额 ≠ 订单金额 → 拒绝入账 + 异常池) +- 状态机条件更新(SQL `WHERE status=?` 互斥,防并发双写) +- 账本哈希链 + ECDSA 签名,篡改可检测 +- 敏感密钥 AES-256-GCM 加密入库,仅内存明文 +- JWT 管理后台鉴权(随机密钥,禁可预测) +- **后台任务 panic 自恢复**:全部后台 goroutine(关单扫描/收款码维护/账本日任务/隧道/告警等)带 `defer recover`,单点异常不拖垮进程,panic + 栈记录日志便于定位 + +--- + +## 🧪 测试 + +```bash +go vet ./... +go test ./... +go test -race ./internal/... # 全包并发安全验证 + +# 授权设备端到端测试(需要自行准备测试设备) +bash scripts/e2e_usb_realdevice.sh +``` + +覆盖:设备池隔离、动态调度、离线安全降级、通道窗口、均衡分配、设备健康埋点、事件匹配和账本哈希链。 + +--- + +## 📁 目录结构 + +``` +cmd/gateway/ 主程序(路由/启动/配置/自检/动态关单与均衡分配装配) +internal/ + account/ 账户池(调度/加密/风控/加权分配) + adbmgr/ adb 设备管理 / 配置下发 + admin/ 管理后台 API + embed 前端 + alert/ 告警客户端(Bark/Webhook) + channel/ 支付通道抽象 + 实现(alipay/wechat/personal) + epay/ epay 协议层(签名/参数) + ledger/ 审计账本(哈希链/快照) + ledgermon/ ADB 监听编排(设备健康埋点/公平调度/账本识别) + model/ 领域模型 + notify/ 通知发送 worker + notifywatcher/ 通知栏事件驱动监听(比账本快 ~15s) + order/ 订单编排(状态机/幂等/结算/安全关单) + personal/ 个人免签(备注码/签名/风控) + pool/ WAIT_PAY 设备维度池(动态关单窗口/负载快照/CloseAt) + ratelimit/ 公网接口 IP 限流 + reconciler/ 确认调度器/对账/异常池 + scrcpy/ 真机投屏(adb forward) + store/ SQLite 存储层 + tunnel/ Cloudflare 隧道 + upstream/ 上游池(多商户) + web/ 支付页面生成 +deploy/ systemd unit + 每日 SQLite 备份脚本 +scripts/ 运维 / E2E 脚本 +web/admin/ 管理后台前端源码(Vue3) +docs/ 设计文档 +.env.example 配置示例(cp 为 .env 修改) +start.sh 裸跑启动(带 pid 文件 / 防双启) +stop.sh / status.sh 裸跑对应停止 / 状态查询 +``` + +--- + +## 📚 相关文档 + +- [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) — 系统架构(协议/编排/通道三层) +- [`docs/PLAN-DYNAMIC-CLOSE-ALLOC.md`](docs/PLAN-DYNAMIC-CLOSE-ALLOC.md) — 动态关单窗口 + 设备/账户均衡分配设计与实现基线 +- [`docs/MIANQIAN.md`](docs/MIANQIAN.md) — 设备自动化与个人通道的学习概览 +- [`docs/RESILIENCE.md`](docs/RESILIENCE.md) — 弹性与对账(崩溃恢复/异常池/每日快照) +- [`docs/OPERATIONS.md`](docs/OPERATIONS.md) — 运维手册(升级/回滚/备份恢复/密钥轮换/常见故障/健康检查) +- [`docs/DESIGN-DEVICE-ACTOR.md`](docs/DESIGN-DEVICE-ACTOR.md) — ADB 设备操作串行化(DeviceActor 模型) +- [`docs/DESIGN-FAIR-SCHEDULER.md`](docs/DESIGN-FAIR-SCHEDULER.md) — 单设备多账户公平时间占比调度 + +## 📌 注意 + +- 本项目仅用于学习、授权测试和合法的商户支付集成。部署者必须自行确认支付合规、平台条款、隐私保护和数据安全要求。 +- 设备自动化适配器不是第三方平台授权的替代品,不得用于未经授权访问账户、绕过安全控制或其他违法活动。 +- 账本提供防篡改检测和可追溯性,不等同于法律、税务或司法意义上的审计结论。 diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..06331f4 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,12 @@ +# Security Policy + +Please do not disclose credentials, private keys, payment data, personal data, +or production endpoints in a public issue. + +For a suspected security issue, contact the maintainers privately with the +affected version or commit, a minimal reproduction, the impact, and logs with +secrets and personal data removed. + +This project is a learning and research reference. Deployers are responsible +for authorization, platform terms, payment compliance, data protection, and +operational security in their own environment. diff --git a/assets/logo/README.md b/assets/logo/README.md new file mode 100644 index 0000000..13c60b8 --- /dev/null +++ b/assets/logo/README.md @@ -0,0 +1,18 @@ +# PayGateway Brand Assets + +The mark represents three ideas in one restrained route glyph: + +- converging rails for a plugin-based gateway; +- a shared spine for order and channel orchestration; +- the green seam for a verifiable ledger trail. + +The assets use the admin design tokens `#7170FF`, `#22A069`, `#0D0D0F`, and +`#FAFAFA`. Keep the mark clear of QR codes, payment-card illustrations, and +third-party platform logos when creating derivatives. + +Files: + +- `paygateway-mark.svg`: dark-background mark; +- `paygateway-mark-light.svg`: light-background mark; +- `paygateway-wordmark.svg`: README and documentation lockup; +- `favicon.svg`: compact browser/app icon. diff --git a/assets/logo/favicon.svg b/assets/logo/favicon.svg new file mode 100644 index 0000000..41d8b51 --- /dev/null +++ b/assets/logo/favicon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/logo/paygateway-mark-light.svg b/assets/logo/paygateway-mark-light.svg new file mode 100644 index 0000000..86ca7ec --- /dev/null +++ b/assets/logo/paygateway-mark-light.svg @@ -0,0 +1,11 @@ + + PayGateway + A routed payment gateway with a verifiable ledger spine. + + + + + + + + diff --git a/assets/logo/paygateway-mark.svg b/assets/logo/paygateway-mark.svg new file mode 100644 index 0000000..e973ddf --- /dev/null +++ b/assets/logo/paygateway-mark.svg @@ -0,0 +1,12 @@ + + PayGateway + A routed payment gateway with a verifiable ledger spine. + + + + + + + + + diff --git a/assets/logo/paygateway-wordmark.svg b/assets/logo/paygateway-wordmark.svg new file mode 100644 index 0000000..73633de --- /dev/null +++ b/assets/logo/paygateway-wordmark.svg @@ -0,0 +1,8 @@ + + PayGateway + PayGateway, a verifiable and extensible payment gateway. + + + + PayGateway + diff --git a/cmd/e2e_ledger/main.go b/cmd/e2e_ledger/main.go new file mode 100644 index 0000000..0dccffd --- /dev/null +++ b/cmd/e2e_ledger/main.go @@ -0,0 +1,67 @@ +// 临时 E2E 工具:解密上游密钥 + 构造签名下单请求(验证后删除) +package main + +import ( + "context" + "fmt" + "net/http" + "net/url" + "os" + "strings" + "time" + + "paygateway/internal/account" + "paygateway/internal/epay" + "paygateway/internal/store" +) + +func main() { + keyHex := os.Getenv("ACCOUNT_MASTER_KEY") + if err := account.InitCrypto(keyHex); err != nil { + panic("InitCrypto: " + err.Error()) + } + db, err := store.NewUpstreamDB("data/upstreams.db") + if err != nil { + panic("NewUpstreamDB: " + err.Error()) + } + defer db.Close() + up, err := db.GetUpstreamByPid(context.Background(), "2001") + if err != nil || up == nil { + panic(fmt.Sprintf("GetByPid: %v up=%v", err, up)) + } + key := up.KeyEnc + dec, err := account.Decrypt(key) + if err != nil { + panic("Decrypt: " + err.Error()) + } + fmt.Println("UPSTREAM_KEY_DECRYPTED len:", len(dec)) + + // 构造 epay 下单参数(type=alipay 会优先路由 personal.alipay) + outNo := fmt.Sprintf("E2E%d", time.Now().Unix()) + params := map[string]string{ + "pid": "2001", + "type": "alipay", + "out_trade_no": outNo, + "name": "e2e测试", + "money": "1.00", + "notify_url": "http://127.0.0.1:8080/notify", + "return_url": "http://127.0.0.1:8080/return", + "pay_type": "alipay", + "device": "pc", + } + signed := epay.BuildSignedParams(params, string(dec)) + form := url.Values{} + for k, v := range signed { + form.Set(k, v) + } + resp, err := http.Post("http://127.0.0.1:8080/submit.php", + "application/x-www-form-urlencoded", strings.NewReader(form.Encode())) + if err != nil { + panic("POST: " + err.Error()) + } + defer resp.Body.Close() + buf := make([]byte, 4096) + n, _ := resp.Body.Read(buf) + fmt.Println("RESP:", resp.StatusCode) + fmt.Println(string(buf[:n])) +} diff --git a/cmd/gateway/alloc.go b/cmd/gateway/alloc.go new file mode 100644 index 0000000..a4b9ddd --- /dev/null +++ b/cmd/gateway/alloc.go @@ -0,0 +1,263 @@ +// paygateway 支付网关 alloc(从 main.go 拆分,均衡分配算法) +package main + +import ( + "context" + "fmt" + "paygateway/internal/account" + "paygateway/internal/ledgermon" + "paygateway/internal/logger" + "paygateway/internal/personal" + "paygateway/internal/pool" + "paygateway/internal/store" + "time" +) + +func decryptDeviceSecret(encHex string) (string, error) { + if encHex == "" { + return "", fmt.Errorf("设备密钥为空") + } + data, err := account.Decrypt(encHex) + if err != nil { + return "", err + } + return string(data), nil +} + +// autoRegisterDevice 首连自动注册设备(极端情况:仅有固定域名 + 可访问公网即可接入)。 +// 监听端首次启动未配置时自动生成 device_id + secret,心跳携带 register_secret 完成注册。 +// 返回注册后的设备(含解密密钥)。 +func (a *App) resolveLedgerDevices(ctx context.Context) ([]ledgermon.MonitoredDevice, error) { + devs, err := a.personalDB.ListDevices(ctx) + if err != nil { + return nil, err + } + adb := adbMgr() + var out []ledgermon.MonitoredDevice + for _, d := range devs { + serial := deviceADBSerial(d) + if serial == "" { + continue + } + // 实时 adb 可达判定(含自动 adb connect 重连;~1s,reconcile 30s 周期可接受) + if !adb.Online(ctx, serial) { + continue + } + for _, id := range store.DeviceAccountIDs(ctx, a.personalDB, d.ID, d.AccountID) { + acct := a.accountPool.GetByID(id) + if acct == nil { + continue + } + provider, ok := ledgerProviderOf(acct.Channel) + if !ok { + continue // 非个人免签通道(沙箱/企业)无账本页可监听 + } + out = append(out, ledgermon.MonitoredDevice{ + DeviceID: d.DeviceID, + Serial: serial, + Provider: provider, + AccountID: id, + UserID: ledgerUserSpaceOf(ctx, a.personalDB, d.DeviceID, provider, acct.QRContent), + }) + } + } + return out, nil +} + +// deviceADBSerial 解析设备 adb serial(USB host[:port] 或 WiFi host:port),不可达返回空串。 +func deviceADBSerial(d *personal.DeviceEncrypted) string { + if d.AdbHost != "" { + if d.AdbPort > 0 { + return fmt.Sprintf("%s:%d", d.AdbHost, d.AdbPort) + } + return d.AdbHost + } + if d.AdbWifiHost != "" && d.AdbWifiPort > 0 { + return fmt.Sprintf("%s:%d", d.AdbWifiHost, d.AdbWifiPort) + } + return "" +} + +// accountDeviceOf 账户 → 设备 ID 映射(WAIT_PAY 池设备维度子池用)。 +// 遍历全部设备找账户归属(主账户或附加账户);一对多时取首个(业务上账户唯一绑定一台收款设备)。 +// 返回 (deviceID, found)。低频(下单时调用一次),全量遍历可接受。 +func accountDeviceOf(ctx context.Context, pdb store.PersonalStore, accountID int64) (string, bool) { + devs, err := pdb.ListDevices(ctx) + if err != nil { + logger.Warn("main", "设备列表查询失败(订单归入兜底桶,Reconcile 兜底)", "err", err) + return "", false + } + for _, d := range devs { + for _, id := range store.DeviceAccountIDs(ctx, pdb, d.ID, d.AccountID) { + if id == accountID { + return d.DeviceID, true + } + } + } + return "", false +} + +// devCandidate 均衡分配候选设备(含负载与健康度,供两层评分) +type devCandidate struct { + deviceID string + serial string + load int // 设备 WAIT_PAY 待确认总数(设备负载) + health float64 // 设备 poll 成功率(0~1) + acctIDs []int64 // 设备绑定账户(主+附加) + loadOverRatio bool // 负载进入缓冲带(ratio×cap ≤ load < cap):评分降权 +} + +// deviceLoadCapOf 计算设备订单承载上限:配置优先;0=按公式自动算。 +// 公式:(MAX窗口 - 支付操作预留) / (单次poll × 每账户最低配额1) × 排队修正。 +// 上限即"在用户可接受确认延迟内设备能扛的订单数"(PLAN-DYNAMIC-CLOSE-ALLOC §4.5)。 +func deviceLoadCapOf(cfg *Config, dynMax time.Duration) int { + if cfg.DeviceLoadCap > 0 { + return cfg.DeviceLoadCap + } + // 与 main 的退化逻辑保持一致:动态窗口配置非法(MIN≤0/MAX≤0/MIN≥MAX)→ 静态窗口语义,不启用超限过滤 + if dynMax <= 0 || cfg.OrderCloseMin <= 0 || cfg.OrderCloseMax <= 0 || cfg.OrderCloseMin >= cfg.OrderCloseMax { + return 0 + } + payReserve := 60 * time.Second + pollCost := time.Duration(cfg.DevicePollCostMS) * time.Millisecond + if pollCost <= 0 { + pollCost = 4 * time.Second + } + n := int((dynMax - payReserve) / (pollCost * 1)) + if n < 1 { + n = 1 + } + return n +} + +// balanceSelectAccount 均衡分配:先选设备(最闲最健康),再选设备内账户(最闲最健康)。 +// 设备评分 = health × 1/(load+1);账户评分 = 健康 × 1/(pool.Count+1)。 +// 设备超限(load ≥ cap)→ 排除;全部超限 → 返回 nil(下单层报错 + 告警)。 +// channelName 形如 "personal.alipay"/"personal.wechat"(账户池通道过滤)。 +func balanceSelectAccount(ctx context.Context, pdb store.PersonalStore, + pool *account.AccountPool, wp *pool.WaitPayPool, lm *ledgermon.Manager, + cfg *Config, channelName string) *account.Account { + if wp == nil || pool == nil { + return nil + } + devs, err := pdb.ListDevices(ctx) + if err != nil { + logger.Error("main", "均衡分配:设备列表查询失败", "err", err) + return nil + } + + // 健康度快照(ledgermon 未启动 → 全部 0.9 中性) + health := map[string]float64{} + if lm != nil { + health = lm.DevicePollHealth() + } + + // 收集在线设备候选(adb 可达) + var cands []devCandidate + for _, d := range devs { + serial := deviceADBSerial(d) + if serial == "" || !adbMgr().Online(ctx, serial) { + continue + } + ids := store.DeviceAccountIDs(ctx, pdb, d.ID, d.AccountID) + if len(ids) == 0 { + continue + } + st := wp.DeviceSnapshot(d.DeviceID) + load := 0 + if st != nil { + load = st.Total + } + h := health[d.DeviceID] + if h <= 0 { + h = 0.9 + } + cands = append(cands, devCandidate{deviceID: d.DeviceID, serial: serial, load: load, health: h, acctIDs: ids}) + } + if len(cands) == 0 { + return nil + } + + // 设备层:缓冲带降权(load ≥ ratio×cap → 降权)→ 超限排除(load ≥ cap)→ 评分排序 + dynMax := time.Duration(cfg.OrderCloseMax) * time.Minute + capN := deviceLoadCapOf(cfg, dynMax) + softCap := 0 + if capN > 0 && cfg.DeviceOverloadRatio > 0 && cfg.DeviceOverloadRatio < 1 { + softCap = int(float64(capN) * cfg.DeviceOverloadRatio) + } + usable := cands[:0] + for _, c := range cands { + if capN > 0 && c.load >= capN { + logger.Warn("main", "均衡分配:设备负载超限,跳过", + "device_id", c.deviceID, "load", c.load, "cap", capN) + continue + } + if softCap > 0 && c.load >= softCap { + c.loadOverRatio = true // 缓冲带:评分降权(最闲设备优先时让位) + } + usable = append(usable, c) + } + if len(usable) == 0 { + logger.Error("main", "均衡分配:全部设备负载超限,建议增加收款设备", + "loads", fmt.Sprintf("%+v", cands)) + return nil + } + // 设备评分 argmax(最闲最健康;缓冲带内降权;评分相等 → load 小优先,再按 deviceID 字典序确定性) + best := usable[0] + bestScore := deviceScore(best) + for _, c := range usable[1:] { + s := deviceScore(c) + if s > bestScore || (s == bestScore && (c.load < best.load || (c.load == best.load && c.deviceID < best.deviceID))) { + best, bestScore = c, s + } + } + + // 账户层:设备内选最闲最健康账户 + allowed := make(map[int64]bool, len(best.acctIDs)) + for _, id := range best.acctIDs { + allowed[id] = true + } + return pool.SelectFromWeighted(channelName, "default", allowed, + func(acct *account.Account) float64 { + return accountScore(acct, wp) + }) +} + +// deviceScore 设备评分:health × 1/(load+1)。 +// 缓冲带内(loadOverRatio)额外 ×0.5 降权:让最闲设备优先,避免临界抖动。 +func deviceScore(c devCandidate) float64 { + s := c.health / float64(c.load+1) + if c.loadOverRatio { + s *= 0.5 + } + return s +} + +// accountScore 账户评分:健康(状态/日限额/失败)× 1/(账户负载+1) +// 只罚短板,不叠加长处(PLAN-DYNAMIC-CLOSE-ALLOC §5.3) +func accountScore(acct *account.Account, wp *pool.WaitPayPool) float64 { + if acct == nil { + return 0 + } + health := 1.0 + if acct.DailyLimit > 0 { + health *= 1 - float64(acct.DailyUsed)/float64(acct.DailyLimit) // 日限额余量 + if health < 0 { + health = 0 + } + } + switch { + case acct.FailCount >= 3: + health *= 0.2 + case acct.FailCount >= 1: + health *= 0.5 + } + load := 1 + if wp != nil { + load = wp.Count(acct.ID) + 1 + } + return health / float64(load) +} + +// deviceSerialFromDevice 解析设备 adb serial(内存态 Device 版本,镜像 deviceADBSerial 逻辑)。 +// 探测链路用(dispatchLedgerRows 拿到的是 personalDeviceCtx 的 *personal.Device)。 diff --git a/cmd/gateway/channels.go b/cmd/gateway/channels.go new file mode 100644 index 0000000..022abce --- /dev/null +++ b/cmd/gateway/channels.go @@ -0,0 +1,169 @@ +// paygateway 支付网关 channels(从 main.go 拆分,通道注册/设备管理/中间件) +package main + +import ( + "fmt" + "paygateway/internal/account" + "paygateway/internal/adbmgr" + "paygateway/internal/admin" + "paygateway/internal/channel" + alipaych "paygateway/internal/channel/alipay" + personalch "paygateway/internal/channel/personal" + wechatch "paygateway/internal/channel/wechat" + "paygateway/internal/logger" + "paygateway/internal/store" + "strings" + "time" + + "github.com/gin-gonic/gin" +) + +func adbMgr() *adbmgr.Manager { + return adbmgr.New("") +} + +// regChannels 注册支付通道(每加一种支付方式 = 这里加一行) +// 返回个人免签通道实例(main 用于动态更新公网地址 + 推送处理) +func regChannels(cfg *Config, accountPool *account.AccountPool, st store.Store, personalDB store.PersonalStore) (*personalch.Channel, *personalch.Channel) { + // 通道始终注册(可插拔),支付账户一律从账户池读取(管理后台「账户池」配置,加密入库) + // 空 Config 仅保留回调地址:账户的 AppID/私钥/公钥/环境由各自账户决定 + alipayCh, err := alipaych.New(alipaych.Config{ + NotifyURL: cfg.AlipayNotifyURL, + Debug: cfg.ChannelDebug, + }, accountPool) + if err != nil { + logger.Error("main", "支付宝通道初始化失败", "err", err) + } else { + channel.Register(alipayCh) + logger.Info("main", "支付宝通道注册成功 ✅", "accounts", accountPool.CountByChannel("alipay")) + } + + wxCh, err := wechatch.New(wechatch.Config{ + NotifyURL: cfg.WechatNotifyURL, + Debug: cfg.ChannelDebug, + }, accountPool) + if err != nil { + logger.Error("main", "微信通道初始化失败", "err", err) + } else { + channel.Register(wxCh) + logger.Info("main", "微信通道注册成功 ✅", "accounts", accountPool.CountByChannel("wechat")) + } + + // 个人免签通道(阶段 2):personal.alipay / personal.wechat + // 账户池 channel = "personal.alipay" / "personal.wechat",每个账户 = 一个收款码(qr_content) + baseURL := cfg.PersonalBaseURL + if baseURL == "" { + port := strings.TrimPrefix(cfg.ListenAddr, ":") + baseURL = "http://127.0.0.1:" + port + } + var personalAlipay, personalWechat *personalch.Channel + personalAlipay = personalch.New(personalch.Config{BaseURL: baseURL, MaxAmount: cfg.PersonalMaxAmount}, channel.ProviderAlipay, accountPool, st, personalDB) + channel.Register(personalAlipay) + logger.Info("main", "个人免签支付宝通道注册成功 ✅", "accounts", accountPool.CountByChannel("personal.alipay")) + + personalWechat = personalch.New(personalch.Config{BaseURL: baseURL, MaxAmount: cfg.PersonalMaxAmount}, channel.ProviderWechat, accountPool, st, personalDB) + channel.Register(personalWechat) + logger.Info("main", "个人免签微信通道注册成功 ✅", "accounts", accountPool.CountByChannel("personal.wechat")) + + return personalAlipay, personalWechat +} + +// buildEpayInfo 易支付服务地址信息(admin 展示用) +// 多商户模式下不再有全局 pid/key:每个上游的 pid/密钥/对接指引在「上游对接」页独立配置 +// 展示地址优先公网(固定隧道域名):给上游看的对接信息写 127.0.0.1 会让对方配成内网兜底失效 +func buildEpayInfo(cfg *Config) *admin.EpayInfo { + base := cfg.EpayPublicBase + if base == "" { + base = cfg.PersonalBaseURL + } + if base == "" { + port := strings.TrimPrefix(cfg.ListenAddr, ":") + base = "http://127.0.0.1:" + port + } + base = strings.TrimSuffix(base, "/") + return &admin.EpayInfo{ + BaseURL: base, + SubmitURL: base + "/submit.php", + QueryURL: base + "/api.php", + Pid: "", + Key: "", + SignType: "MD5", + } +} + +// buildChannelInfos 组装通道信息给管理后台(admin 不依赖具体通道包) +func buildChannelInfos(pool *account.AccountPool) []admin.ChannelInfo { + var out []admin.ChannelInfo + for _, name := range channel.AllNames() { + ch, ok := channel.Lookup(name) + if !ok { + continue + } + info := admin.ChannelInfo{ + Name: name, + Enabled: channel.IsEnabled(name), + Kind: "callback", + } + // 按通道实际类型修正 kind(callback / listener) + if ch.Kind() == channel.KindListener { + info.Kind = "listener" + } + // 账户配置状态(引导用) + if pool != nil { + info.AccountCount = pool.CountByChannel(name) + info.Configured = info.AccountCount > 0 + } + for _, m := range ch.Modes() { + info.Modes = append(info.Modes, string(m)) + } + // 通道特有信息 + // 支付模式启停状态(有 EnabledModes 方法的通道) + type modeProvider interface { + EnabledModes() map[string]bool + } + if mp, ok := ch.(modeProvider); ok { + info.ModesEnabled = mp.EnabledModes() + } + + // 支付宝特有信息(AppID/环境从账户池取第一个账户展示) + if ac, ok := ch.(*alipaych.Channel); ok { + info.NotifyURL = ac.NotifyURL() + if accts := pool.ListByChannel("alipay"); len(accts) > 0 { + info.AppID = accts[0].AppID + info.IsProd = accts[0].IsProd + } + } + // 微信通道特有信息 + if wc, ok := ch.(*wechatch.Channel); ok { + info.NotifyURL = wc.NotifyURL() + if accts := pool.ListByChannel("wechat"); len(accts) > 0 { + info.AppID = accts[0].AppID + info.MchID = accts[0].MchID + info.IsProd = accts[0].IsProd + } + } + out = append(out, info) + } + return out +} + +// traceMiddleware 为每个请求生成 trace_id + +// traceMiddleware 为每个请求生成 trace_id +func traceMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + traceID := c.GetHeader("X-Trace-ID") + if traceID == "" { + traceID = fmt.Sprintf("tr-%d", time.Now().UnixNano()) + } + c.Set("trace_id", traceID) + c.Header("X-Trace-ID", traceID) + c.Next() + } +} + +// ========== 易支付下单接口 ========== + +// epayKeyFor 按 pid 返回验签密钥(多商户,严格模式) +// 返回 (key, true):pid 已登记且启用 +// 返回 ("", false):pid 未登记或已禁用 → 拒绝(不再有任何 .env / 默认密钥回退) diff --git a/cmd/gateway/check.go b/cmd/gateway/check.go new file mode 100755 index 0000000..65d10fe --- /dev/null +++ b/cmd/gateway/check.go @@ -0,0 +1,164 @@ +// Package main 启动配置自检(RESILIENCE.md P1-2) +// +// 目标:启动时一次性暴露配置错误,避免生产事故: +// - 沙箱/正式错配(is_prod) +// - 密钥缺失(主密钥 / 账本密钥 / 套码 token) +// - 回调地址未配置(隧道未就绪) +// - 个人免签通道缺账户 / 缺监听设备 +// - 告警未配置(可观测性缺失) +// +// 检查结果分级:✅ 正常 / ⚠️ 警告(可运行但建议修复)/ ❌ 错误(功能不可用) +// 通过结构化日志输出,管理后台「系统日志」可查。 +package main + +import ( + "context" + "fmt" + "strings" + "time" + + "paygateway/internal/account" + "paygateway/internal/channel" + "paygateway/internal/logger" + "paygateway/internal/personal" + "paygateway/internal/store" +) + +// checkItem 单条检查结果 +type checkItem struct { + level string // ok / warn / err + msg string +} + +// runStartupChecks 启动配置自检(在账户池加载、通道注册、隧道启动前调用) +func runStartupChecks(cfg *Config, accountPool *account.AccountPool, st store.Store, personalDB store.PersonalStore) { + var items []checkItem + now := time.Now() + + // 1. 密钥检查 + // 账户主密钥在 main.go 已 fail-fast:到这里一定非空,但保留检查分支以应对未来可能的"可选"开关。 + if cfg.AccountMasterKey == "" { + items = append(items, checkItem{"err", "账户主密钥未配置(应在 main.go fail-fast 退出,不会走到此处)"}) + } else { + items = append(items, checkItem{"ok", "账户主密钥:已配置"}) + } + if cfg.LedgerPrivKey == "" { + items = append(items, checkItem{"ok", "审计账本密钥:自动生成(settings 持久化)"}) + } else { + items = append(items, checkItem{"ok", "审计账本密钥:已配置"}) + } + if personal.QrTokenSecretSet() { + items = append(items, checkItem{"ok", "套码 token 密钥:已初始化"}) + } else { + items = append(items, checkItem{"err", "套码 token 密钥未初始化(个人免签二维码将无法生成)"}) + } + + // 2. 账户池检查 + total := accountPool.Count() + if total == 0 { + items = append(items, checkItem{"warn", "账户池为空:无任何支付账户,所有通道无法接单(请到管理后台「账户池」添加)"}) + } else { + items = append(items, checkItem{"ok", fmt.Sprintf("账户池:%d 个账户", total)}) + } + + // 3. 个人免签通道检查 + personalNames := []string{} + for _, name := range channel.AllNames() { + if strings.HasPrefix(name, "personal.") { + personalNames = append(personalNames, name) + } + } + if len(personalNames) == 0 { + items = append(items, checkItem{"err", "个人免签通道未注册(personal.alipay / personal.wechat)"}) + } else { + items = append(items, checkItem{"ok", "个人免签通道:已注册 " + strings.Join(personalNames, ", ")}) + // 各个人通道账户数 + for _, name := range personalNames { + n := accountPool.CountByChannel(name) + if n == 0 { + items = append(items, checkItem{"warn", fmt.Sprintf("通道 %s 无收款账户:该通道无法接单(需配置收款码 qr_content)", name)}) + } else { + items = append(items, checkItem{"ok", fmt.Sprintf("通道 %s:%d 个收款账户", name, n)}) + } + } + } + + // 4. 监听设备检查(在线 = adb 可达,2026-08-29 重构:不再依赖监听 App 心跳) + if personalDB != nil { + devs, err := personalDB.ListDevices(context.Background()) + if err != nil { + items = append(items, checkItem{"err", "监听设备列表查询失败: " + err.Error()}) + } else { + online := 0 + for _, d := range devs { + serial := deviceADBSerial(d) + if serial != "" && adbMgr().Online(context.Background(), serial) { + online++ + } + } + if len(devs) == 0 { + items = append(items, checkItem{"warn", "无监听端设备:个人免签无法确认收款(请绑定收款账户创建设备)"}) + } else { + items = append(items, checkItem{"ok", fmt.Sprintf("监听设备:%d 台(adb 在线 %d)", len(devs), online)}) + } + } + } + + // 5. 告警配置 + if cfg.AlertBarkURL == "" && cfg.AlertWebhookURL == "" { + items = append(items, checkItem{"warn", "告警未配置(ALERT_BARK_URL / ALERT_WEBHOOK_URL 为空):异常进池/账本失败/dead 通知将无法推送"}) + } else { + items = append(items, checkItem{"ok", "告警通道:已配置"}) + } + + // 6. 单笔限额风控 + if cfg.PersonalMaxAmount > 0 { + items = append(items, checkItem{"ok", fmt.Sprintf("个人通道单笔限额:¥%d.%02d", cfg.PersonalMaxAmount/100, cfg.PersonalMaxAmount%100)}) + } else { + items = append(items, checkItem{"warn", "个人通道单笔限额未设置(PERSONAL_MAX_AMOUNT=0):大额风险无防护"}) + } + + // 7. 生产/沙箱错配检查(仅在有账户时) + prodAccts, sandboxAccts := 0, 0 + for _, a := range accountPool.ListAll() { + if a.IsProd { + prodAccts++ + } else { + sandboxAccts++ + } + } + if sandboxAccts > 0 && prodAccts > 0 { + items = append(items, checkItem{"warn", fmt.Sprintf("生产/沙箱账户混用:%d 生产 + %d 沙箱(正式环境应全部 is_prod=true)", prodAccts, sandboxAccts)}) + } else if sandboxAccts == 0 && prodAccts > 0 { + items = append(items, checkItem{"ok", fmt.Sprintf("账户环境一致:全部为生产(%d 个)", prodAccts)}) + } + + // 8. 数据文件(settings 表)可读 + if _, err := st.GetAllSettings(context.Background()); err != nil { + items = append(items, checkItem{"err", "settings 表不可读: " + err.Error()}) + } else { + items = append(items, checkItem{"ok", "业务数据库:可读写"}) + } + + // 输出报告 + okCount, warnCount, errCount := 0, 0, 0 + logger.Info("main", "=== 启动配置自检 ===") + for _, it := range items { + switch it.level { + case "ok": + okCount++ + logger.Info("main", "自检 ✅ "+it.msg) + case "warn": + warnCount++ + logger.Warn("main", "自检 ⚠️ "+it.msg) + case "err": + errCount++ + logger.Error("main", "自检 ❌ "+it.msg) + } + } + logger.Info("main", fmt.Sprintf("启动自检完成:%d 通过 / %d 警告 / %d 错误(用时 %s)", + okCount, warnCount, errCount, time.Since(now).Round(time.Millisecond))) + if errCount > 0 { + logger.Warn("main", "存在错误级配置问题,请修复后重启(服务将继续启动,但相关功能不可用)") + } +} diff --git a/cmd/gateway/close_time_test.go b/cmd/gateway/close_time_test.go new file mode 100644 index 0000000..aee49e5 --- /dev/null +++ b/cmd/gateway/close_time_test.go @@ -0,0 +1,59 @@ +package main + +import ( + "testing" + "time" + + "paygateway/internal/model" + "paygateway/internal/pool" +) + +// TestPersonalOrderCloseAt_StaticFallback 静态关单模式(无动态窗口):使用 ORDER_CLOSE_MINUTES。 +func TestPersonalOrderCloseAt_StaticFallback(t *testing.T) { + app := &App{cfg: &Config{OrderCloseMinutes: 30}} + now := time.Now() + o := &model.Order{OutTradeNo: "X", CreatedAt: now, Channel: "personal.alipay"} + got := app.personalOrderCloseAt(o) + want := now.Add(30 * time.Minute) + if !got.Equal(want) { + t.Fatalf("静态窗口下应 = CreatedAt+30min,实际 %v want %v", got, want) + } +} + +// TestPersonalOrderCloseAt_DynamicNotOverrideStatic 动态窗口已启用:个人订单回退用动态下限 ORDER_CLOSE_MIN, +// 绝不落静态 ORDER_CLOSE_MINUTES(防止环境配置拉长个人订单关单——幽灵订单根因之一)。 +func TestPersonalOrderCloseAt_DynamicNotOverrideStatic(t *testing.T) { + app := &App{cfg: &Config{OrderCloseMinutes: 30, OrderCloseMin: 5, OrderCloseMax: 10}} + now := time.Now() + o := &model.Order{OutTradeNo: "X", CreatedAt: now, Channel: "personal.wechat"} + got := app.personalOrderCloseAt(o) + want := now.Add(5 * time.Minute) + if !got.Equal(want) { + t.Fatalf("动态窗口下个人订单应 = CreatedAt+ORDER_CLOSE_MIN(5min),实际 %v want %v", got, want) + } +} + +// TestPersonalOrderCloseAt_PoolCloseAt 订单在池中:用池内设备负载驱动的 CloseAt(最精确,与关单扫描器一致)。 +func TestPersonalOrderCloseAt_PoolCloseAt(t *testing.T) { + p := pool.NewWaitPayPool(30) + now := time.Now() + o := &model.Order{OutTradeNo: "POOL1", AccountID: 7, CreatedAt: now, Channel: "personal.alipay"} + p.Add(o) + app := &App{cfg: &Config{OrderCloseMinutes: 30, OrderCloseMin: 5, OrderCloseMax: 10}, waitPayPool: p} + got := app.personalOrderCloseAt(o) + if got.IsZero() { + t.Fatalf("池内订单应返回 CloseAt,实际零值") + } + if !got.After(now) { + t.Fatalf("池内 CloseAt 应在未来,实际 %v (now %v)", got, now) + } +} + +// TestPersonalOrderCloseAt_Disabled 自动关单未开启(所有窗口为 0):返回零值(页面不显示倒计时)。 +func TestPersonalOrderCloseAt_Disabled(t *testing.T) { + app := &App{cfg: &Config{OrderCloseMinutes: 0, OrderCloseMin: 0, OrderCloseMax: 0}} + o := &model.Order{OutTradeNo: "X", CreatedAt: time.Now(), Channel: "personal.alipay"} + if got := app.personalOrderCloseAt(o); !got.IsZero() { + t.Fatalf("自动关单未开启应返回零值,实际 %v", got) + } +} diff --git a/cmd/gateway/config.go b/cmd/gateway/config.go new file mode 100644 index 0000000..d411561 --- /dev/null +++ b/cmd/gateway/config.go @@ -0,0 +1,297 @@ +// paygateway 支付网关 config(从 main.go 拆分,配置结构/加载/env 解析/密钥) +package main + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "encoding/pem" + "fmt" + "os" + "paygateway/internal/channel" + "paygateway/internal/logger" + "paygateway/internal/store" + "strconv" + "strings" + "time" +) + +type Config struct { + ListenAddr string + AdminAddr string + DBFile string + LogFile string + LogLevel string + + JWTSecret string // JWT 签名密钥(留空自动生成) + + OrderCloseMinutes int // 未支付订单自动关闭时间(分钟),0=关闭功能 + OrderCloseInterval int // 扫描间隔(秒) + // 动态关单窗口上下限(分钟,PLAN-DYNAMIC-CLOSE-ALLOC §4.1): + // 窗口 = clamp(设备一轮耗时×1.5+支付操作预留, MIN, MAX)。0 或 MIN>MAX → 用静态 OrderCloseMinutes。 + OrderCloseMin int + OrderCloseMax int + // 设备处理能力:单设备可承载的 WAIT_PAY 订单上限(0=按公式自动算)。 + // 超限 → 分配器跳过该设备;全部超限 → 拒绝下单 + 告警建议加设备。 + DeviceLoadCap int + // 设备降权阈值(占 DeviceLoadCap 比例,默认 0.8):超过后该设备在分配器中降权。 + DeviceOverloadRatio float64 + // 单次 poll 成本估算(ms,动态窗口/设备能力计算;0=默认 4000,埋点校准后设置) + DevicePollCostMS int + + // EpayPublicBase 对外易支付服务基础地址(其他项目对接用) + // 同机项目填 http://127.0.0.1:8080;局域网填 http://:8080;公网填隧道/域名 + // 留空则自动推断为 http://127.0.0.1:<端口> + EpayPublicBase string + + // 通道回调地址(启动时由隧道动态更新;账户密钥一律在管理后台「账户池」配置,不走环境变量) + AlipayNotifyURL string + WechatNotifyURL string + + // ChannelDebug 支付通道 SDK debug 日志(每笔请求/响应/签名全量 dump) + // 默认关闭:真机事故——沙箱查单失败风暴时 gopay 每次失败刷 4 条 DEBUG,两天刷出 12w 行 + ChannelDebug bool + + // NotifyAllowPrivateIPs 允许商户 notify_url 指向私网/环回地址(SSRF 防护开关)。 + // 同机部署 New API 需要置 true(默认 true 保持兼容);网关公网暴露且商户不可信时必须置 false。 + NotifyAllowPrivateIPs bool + + LedgerPrivKey string // 审计密钥(空则自动生成并持久化到 settings,重启复用) + AccountMasterKey string // 账户加密主密钥(空则自动生成) + QrTokenSecret string // 套码 token 签名密钥(空则自动生成并持久化) + PersonalBaseURL string // 个人免签公网基础地址(优先隧道域名,空则用 EpayPublicBase) + + // 个人免签风控(docs/MIANQIAN.md §10):金额单位均为分 + PersonalMaxAmount int64 // 单笔限额(默认 50000 = 500 元,0=不限) + PersonalBigAmount int64 // 大额告警阈值(默认 20000 = 200 元) + MaxOrdersPerIPPerMin int // 每 IP 每分钟下单上限(默认 10,0=不限;防脚本刷单) + + // 告警(RESILIENCE P1-3) + AlertBarkURL string // Bark 推送 URL(https://api.day.app/),空=禁用 + AlertWebhookURL string // 通用 Webhook URL(POST JSON),空=禁用 + + // 公网接口 IP 限流(防刷/防 DoS,仅作用于公网支付路由,不影响 /alipay/notify /wechat/notify 等签名回调) + PublicRateMax int + PublicRateWindowSec int + + // 账本页服务端监听(docs/PLAN-ADB-LEDGER-IMPL.md §6.3) + LedgerPollInterval time.Duration // 无活跃订单轮询间隔(默认 5s) + LedgerActiveInterval time.Duration // 有活跃订单轮询间隔(默认 1s) + + // 通知栏快速通道(NotifyWatcher,2026-08-30 新增) + NotifyPollInterval time.Duration // 有 WAIT_PAY 订单轮询间隔(默认 500ms) + NotifyIdleInterval time.Duration // 池空低频探测间隔(默认 5s) + + // TunnelHostname 固定隧道域名(如 paygateway.de5.net)。空 = 快速隧道随机域名。 + // 命名隧道需要 ~/.cloudflared/paygateway.yml + paygateway.json 凭据。 + TunnelHostname string +} + +// loadEnv 加载 .env 文件到环境变量 +func loadEnv(path string) { + data, err := os.ReadFile(path) + if err != nil { + return // 文件不存在就跳过 + } + for _, line := range strings.Split(string(data), "\n") { + line = strings.TrimSpace(line) + if line == "" || strings.HasPrefix(line, "#") { + continue + } + parts := strings.SplitN(line, "=", 2) + if len(parts) != 2 { + continue + } + key := strings.TrimSpace(parts[0]) + val := strings.TrimSpace(parts[1]) + if key != "" && val != "" { + os.Setenv(key, val) + } + } +} + +func loadConfig() *Config { + return &Config{ + ListenAddr: envOr("GATEWAY_ADDR", ":8080"), + AdminAddr: envOr("ADMIN_ADDR", ":8081"), + DBFile: envOr("ORDER_DB", "data/paygateway.db"), + LogFile: envOr("LOG_FILE", "logs/paygateway-{date}.log"), + LogLevel: envOr("LOG_LEVEL", "info"), + JWTSecret: envOr("JWT_SECRET", ""), + + OrderCloseMinutes: envInt("ORDER_CLOSE_MINUTES", 30), + OrderCloseInterval: envInt("ORDER_CLOSE_INTERVAL", 30), + OrderCloseMin: envInt("ORDER_CLOSE_MIN", 5), + OrderCloseMax: envInt("ORDER_CLOSE_MAX", 10), + DeviceLoadCap: envInt("DEVICE_LOAD_CAP", 0), + DeviceOverloadRatio: envFloat("DEVICE_OVERLOAD_RATIO", 0.8), + DevicePollCostMS: envInt("DEVICE_POLL_COST_MS", 4000), + EpayPublicBase: envOr("EPAY_PUBLIC_BASE_URL", ""), + AlipayNotifyURL: envOr("ALIPAY_NOTIFY_URL", "http://localhost:8080/alipay/notify"), + WechatNotifyURL: envOr("WECHAT_NOTIFY_URL", "http://localhost:8080/wechat/notify"), + ChannelDebug: envOr("CHANNEL_DEBUG", "0") == "1", + + NotifyAllowPrivateIPs: envBool("NOTIFY_ALLOW_PRIVATE_IPS", true), + LedgerPrivKey: envOr("LEDGER_PRIVATE_KEY", ""), + AccountMasterKey: envOr("ACCOUNT_MASTER_KEY", ""), + QrTokenSecret: envOr("QR_TOKEN_SECRET", ""), + PersonalBaseURL: envOr("PERSONAL_BASE_URL", ""), + PersonalMaxAmount: envInt64("PERSONAL_MAX_AMOUNT", 50000), + PersonalBigAmount: envInt64("PERSONAL_BIG_AMOUNT", 20000), + MaxOrdersPerIPPerMin: envInt("MAX_ORDERS_PER_IP_PER_MIN", 10), + AlertBarkURL: envOr("ALERT_BARK_URL", ""), + AlertWebhookURL: envOr("ALERT_WEBHOOK_URL", ""), + PublicRateMax: envInt("PUBLIC_RATE_MAX", 60), + PublicRateWindowSec: envInt("PUBLIC_RATE_WINDOW_SEC", 60), + LedgerPollInterval: envDur("LEDGER_POLL_INTERVAL", 5*time.Second), + LedgerActiveInterval: envDur("LEDGER_ACTIVE_INTERVAL", 1*time.Second), + NotifyPollInterval: envDur("NOTIFY_POLL_INTERVAL", 500*time.Millisecond), + NotifyIdleInterval: envDur("NOTIFY_IDLE_INTERVAL", 5*time.Second), + TunnelHostname: envOr("CF_TUNNEL_HOSTNAME", ""), + } +} + +func envInt64(key string, def int64) int64 { + if v := os.Getenv(key); v != "" { + if n, err := strconv.ParseInt(v, 10, 64); err == nil { + return n + } + } + return def +} + +func envFloat(key string, def float64) float64 { + if v := os.Getenv(key); v != "" { + if f, err := strconv.ParseFloat(v, 64); err == nil { + return f + } + } + return def +} + +func envOr(key, def string) string { + if v := os.Getenv(key); v != "" { + return v + } + return def +} + +func envBool(key string, def bool) bool { + if v := os.Getenv(key); v != "" { + b, err := strconv.ParseBool(v) + if err == nil { + return b + } + } + return def +} + +// applySettings 从 DB 加载运行时设置,覆盖 .env 配置 +func applySettings(st store.Store, cfg *Config) { + ctx := context.Background() + settings, err := st.GetAllSettings(ctx) + if err != nil || len(settings) == 0 { + return + } + if v, ok := settings["epay_public_base_url"]; ok && v != "" { + cfg.EpayPublicBase = v + } + if v, ok := settings["order_close_minutes"]; ok && v != "" { + if n, err := strconv.Atoi(v); err == nil { + cfg.OrderCloseMinutes = n + } + } + logger.Info("main", "运行时设置已加载", "count", len(settings)) +} + +// resolveLedgerKey 解析审计账本私钥:.env > settings 表 > 首次生成并持久化到 settings +// 保证重启后密钥不变,旧账本条目验签可继续通过 +func resolveLedgerKey(st store.Store, cfg *Config) (string, error) { + ctx := context.Background() + // 1. .env 优先 + if cfg.LedgerPrivKey != "" { + return cfg.LedgerPrivKey, nil + } + // 2. settings 表 + settings, err := st.GetAllSettings(ctx) + if err == nil { + if k, ok := settings["ledger_private_key"]; ok && k != "" { + return k, nil + } + } + // 3. 生成并持久化 + key, err := generateLedgerKeyPEM() + if err != nil { + return "", err + } + if err := st.SetSetting(ctx, "ledger_private_key", key); err != nil { + return "", fmt.Errorf("保存审计密钥到 settings 失败: %w", err) + } + logger.Info("main", "已生成新的审计账本密钥并持久化到 settings(重启复用)") + return key, nil +} + +// generateLedgerKeyPEM 生成 ECDSA P-256 私钥 PEM +func generateLedgerKeyPEM() (string, error) { + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return "", fmt.Errorf("生成审计密钥失败: %w", err) + } + der, err := x509.MarshalECPrivateKey(key) + if err != nil { + return "", fmt.Errorf("序列化审计密钥失败: %w", err) + } + return string(pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: der})), nil +} + +// applyChannelRuntimeSettings 从 DB 加载通道运行时设置(需在通道注册后调用) +// 包括:通道启停状态 + 支付模式启停 +func applyChannelRuntimeSettings(st store.Store) { + ctx := context.Background() + settings, err := st.GetAllSettings(ctx) + if err != nil || len(settings) == 0 { + return + } + // 通道启用状态 + for _, name := range channel.AllNames() { + if v, ok := settings["channel_enabled_"+name]; ok { + channel.SetEnabled(name, v == "true") + } + } + // 支付宝支付模式启停 + if ch, ok := channel.Lookup("merchant.alipay"); ok { + type modeSetter interface { + SetModeEnabled(mode string, enabled bool) + } + if ms, ok := ch.(modeSetter); ok { + for _, mode := range []string{"page", "wap", "qrcode"} { + if v, ok := settings["alipay_mode_"+mode]; ok { + ms.SetModeEnabled(mode, v == "true") + } + } + } + } + logger.Info("main", "通道运行时设置已加载", "count", len(settings)) +} + +func envInt(key string, def int) int { + if v := os.Getenv(key); v != "" { + if n, err := strconv.Atoi(v); err == nil { + return n + } + } + return def +} + +func envDur(key string, def time.Duration) time.Duration { + if v := os.Getenv(key); v != "" { + if d, err := time.ParseDuration(v); err == nil { + return d + } + } + return def +} + +// ========== 依赖容器 ========== diff --git a/cmd/gateway/devices.go b/cmd/gateway/devices.go new file mode 100644 index 0000000..a007666 --- /dev/null +++ b/cmd/gateway/devices.go @@ -0,0 +1,38 @@ +// paygateway 支付网关 devices(从 main.go 拆分,设备/渠道映射) +package main + +import ( + "fmt" + "paygateway/internal/personal" +) + +func deviceSerialFromDevice(d *personal.Device) string { + if d == nil { + return "" + } + if d.AdbHost != "" { + if d.AdbPort > 0 { + return fmt.Sprintf("%s:%d", d.AdbHost, d.AdbPort) + } + return d.AdbHost + } + if d.AdbWifiHost != "" && d.AdbWifiPort > 0 { + return fmt.Sprintf("%s:%d", d.AdbWifiHost, d.AdbWifiPort) + } + return "" +} + +// ledgerProviderOf 账户通道 → 账本 provider(仅个人免签通道有账本页可监听) +func ledgerProviderOf(channel string) (string, bool) { + switch channel { + case "personal.alipay": + return "alipay", true + case "personal.wechat": + return "wechat", true + } + return "", false +} + +// ledgerUserSpaceOf 账户所属用户空间:收款码内容匹配该设备 (平台, 用户空间) 最新码。 +// 主空间(user 0)与双开(user 999)的码内容不同,用 QRContent 精确匹配归属; +// 匹配不到(无码/未上报)默认主空间 0。 diff --git a/cmd/gateway/e2e_lifecycle_test.go b/cmd/gateway/e2e_lifecycle_test.go new file mode 100644 index 0000000..f38ecb7 --- /dev/null +++ b/cmd/gateway/e2e_lifecycle_test.go @@ -0,0 +1,263 @@ +package main + +// 端到端集成测试:完整资金闭环 —— +// 下单(CreateOrder) → 账本监听确认(dispatchLedgerRows) → 入账+账本写入 → 回调 outbox(EnqueueNotify)。 +// 验证个人免签通道"从下单到通知上游"全链路在真实装配下自洽(不依赖真实 adb/支付平台)。 + +import ( + "context" + "net/http" + "path/filepath" + "testing" + "time" + + "paygateway/internal/account" + "paygateway/internal/admin/capture" + "paygateway/internal/channel" + "paygateway/internal/ledger" + "paygateway/internal/model" + "paygateway/internal/order" + "paygateway/internal/pool" + "paygateway/internal/reconciler" + "paygateway/internal/store" + "paygateway/internal/upstream" +) + +// e2eStubPersonalChannel 端到端测试用个人通道桩:CreatePayment 返回固定备注码, +// 让 CreateOrder 完整走"选账户→分配备注码→生成凭据"路径。 +type e2eStubPersonalChannel struct{} + +func (e2eStubPersonalChannel) Name() string { return "personal.alipay" } +func (e2eStubPersonalChannel) Type() channel.ChannelType { return channel.TypePersonal } +func (e2eStubPersonalChannel) Kind() channel.ChannelKind { return channel.KindListener } +func (e2eStubPersonalChannel) Modes() []channel.PayMode { return []channel.PayMode{channel.ModeQRCode} } +func (e2eStubPersonalChannel) ConfirmAbility() channel.ConfirmAbility { return channel.ConfirmByManual } +func (e2eStubPersonalChannel) Provider() channel.Provider { return channel.ProviderAlipay } +func (e2eStubPersonalChannel) CreatePayment(_ context.Context, req *channel.PayRequest) (*channel.PayCredential, error) { + return &channel.PayCredential{ + QRCode: "https://qr.alipay.com/e2e", + AccountID: 1, // 测试账户池中的账户(真实通道由 selectServableAccount 决定) + Mode: channel.ModeQRCode, + NoteCode: "8833", // 固定备注码,账本行携带后精确匹配 + QrToken: "e2etoken", + QRContent: "https://qr.alipay.com/e2e", + }, nil +} +func (e2eStubPersonalChannel) VerifyNotify(context.Context, *http.Request) (*channel.NotifyResult, error) { + return nil, nil +} +func (e2eStubPersonalChannel) QueryOrder(context.Context, string) (*channel.OrderStatus, error) { + return nil, order.ErrNotQueryable +} +func (e2eStubPersonalChannel) Listener() channel.EventListener { return nil } + +// newE2EEnv 构建端到端测试环境:业务库 + 账本 + 账户池 + 订单服务 + 池 + 通道注册 +func newE2EEnv(t *testing.T) (*App, *store.PersonalDB) { + t.Helper() + dir := t.TempDir() + + st, err := store.NewSQLite(filepath.Join(dir, "test.db")) + if err != nil { + t.Fatalf("创建业务库失败: %v", err) + } + t.Cleanup(func() { st.Close() }) + + ldb, err := store.NewLedgerDB(filepath.Join(dir, "ledger.db")) + if err != nil { + t.Fatalf("创建账本库失败: %v", err) + } + t.Cleanup(func() { ldb.Close() }) + + pdb, err := store.NewPersonalDB(filepath.Join(dir, "personal.db")) + if err != nil { + t.Fatalf("创建个人库失败: %v", err) + } + t.Cleanup(func() { pdb.Close() }) + + if err := account.InitCrypto("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"); err != nil { + t.Fatalf("初始化加密失败: %v", err) + } + + // 注册 e2e 个人通道(幂等) + if _, ok := channel.Lookup("personal.alipay"); !ok { + channel.Register(e2eStubPersonalChannel{}) + } + + accountPool := account.NewPool() + lg, err := ledger.New(ldb, "") + if err != nil { + t.Fatalf("创建账本失败: %v", err) + } + svc := order.New(st, lg, accountPool, upstream.NewPool()) + waitPayPool := pool.NewWaitPayPool(30) + svc.SetWaitPayHooks(&order.WaitPayHooks{ + Add: waitPayPool.Add, + Upsert: waitPayPool.Upsert, + Remove: waitPayPool.Remove, + }) + // 设备维度 + 动态窗口(与生产装配一致) + waitPayPool.SetDeviceOf(func(accountID int64) (string, bool) { + return "dev-e2e", true + }) + waitPayPool.SetWindowCalc(func(active, total int) time.Duration { return 5 * time.Minute }) + + app := &App{ + store: st, + personalDB: pdb, + accountPool: accountPool, + orders: svc, + confirmSched: reconciler.NewScheduler(st, svc, nil, nil), + waitPayPool: waitPayPool, + } + return app, pdb +} + +// TestE2E_PersonalOrderFullLifecycle 个人免签完整资金闭环: +// 下单 → 备注码匹配 → 入账(账本写入) → 回调 outbox 生成 → 订单终态 SUCCESS。 +func TestE2E_PersonalOrderFullLifecycle(t *testing.T) { + ctx := context.Background() + app, pdb := newE2EEnv(t) + + // 账户池:一个可用个人账户(CreateOrder 选账户用) + app.accountPool.Add(&account.Account{ + ID: 1, Channel: "personal.alipay", Name: "e2e账户", Status: account.StatusNormal, + QRContent: "https://qr.alipay.com/e2e", + }) + // 设备 + 绑定账户(监听确认需要设备上下文) + dev := newTestDevice(t, pdb, app.accountPool, 1, "dev-e2e") + + // ① 下单(走 CreateOrder 完整路径,stub 通道返回备注码 8833) + res, err := app.orders.CreateOrder(ctx, &order.CreateRequest{ + OutTradeNo: "E2E-001", + Type: "personal.alipay", + Name: "测试商品", + Money: "10.00", + NotifyURL: "http://example.com/notify", + BuyerKey: "e2e-buyer-1", + }) + if err != nil { + t.Fatalf("下单失败: %v", err) + } + if res.Order.Status != model.StatusWaitPay { + t.Fatalf("下单后应 WAIT_PAY,实际 %s", res.Order.Status) + } + if res.Order.NoteCode != "8833" { + t.Fatalf("订单应携带备注码 8833,实际 %q", res.Order.NoteCode) + } + + // ② 账本监听:用户已转账 10.00,账本页出现收款行(携带备注码 8833) + rows := []capture.AlipayLedgerRow{{ + Payer: "*测", Date: "今日", Time: time.Now().Format("15:04:05"), + Amount: "+10.00", AmountMinor: 1000, + Note: "8833", + }} + if err := app.dispatchLedgerRows(ctx, dev.DeviceID, "alipay", 0, rows); err != nil { + t.Fatalf("dispatchLedgerRows 失败: %v", err) + } + + // ③ 订单入账 + 账本写入 + 回调 outbox + o, err := app.store.GetOrder(ctx, "E2E-001") + if err != nil || o == nil { + t.Fatalf("查询订单失败: %v", err) + } + if o.Status != model.StatusSuccess { + t.Fatalf("订单应 TRADE_SUCCESS,实际 %s", o.Status) + } + if o.PaidAt.IsZero() { + t.Fatalf("订单应记录 PaidAt") + } + if o.DeviceID != dev.DeviceID { + t.Fatalf("监听确认后订单应回填设备 dev-e2e,实际 %q", o.DeviceID) + } + + // 回调 outbox 应有通知任务(status=pending) + pending, err := app.store.CountNotifyTasks(ctx, "pending") + if err != nil { + t.Fatalf("CountNotifyTasks: %v", err) + } + if pending < 1 { + t.Fatalf("入账后应生成回调任务, pending=%d", pending) + } + + // ④ 池内订单应已移除(终态清理) + if e := app.waitPayPool.MatchByNote(1, "8833", time.Now().Add(-time.Hour)); e != nil { + t.Fatalf("终态订单应从池中移除, 仍命中: %+v", e) + } +} + +// TestE2E_DuplicateLedgerNoDoubleSettle 重复推送同一收款行 → 幂等不重复入账 +func TestE2E_DuplicateLedgerNoDoubleSettle(t *testing.T) { + ctx := context.Background() + app, pdb := newE2EEnv(t) + + app.accountPool.Add(&account.Account{ + ID: 1, Channel: "personal.alipay", Name: "e2e账户", Status: account.StatusNormal, + QRContent: "https://qr.alipay.com/e2e", + }) + dev := newTestDevice(t, pdb, app.accountPool, 1, "dev-e2e-dup") + + if _, err := app.orders.CreateOrder(ctx, &order.CreateRequest{ + OutTradeNo: "E2E-DUP", Type: "personal.alipay", Name: "测试", Money: "5.00", + NotifyURL: "http://example.com/notify", BuyerKey: "e2e-buyer-dup", + }); err != nil { + t.Fatalf("下单失败: %v", err) + } + + row := capture.AlipayLedgerRow{ + Payer: "*重", Date: "今日", Time: time.Now().Format("15:04:05"), + Amount: "+5.00", AmountMinor: 500, Note: "8833", + } + // 第一次推送 → 入账 + if err := app.dispatchLedgerRows(ctx, dev.DeviceID, "alipay", 0, []capture.AlipayLedgerRow{row}); err != nil { + t.Fatalf("首次 dispatch 失败: %v", err) + } + // 第二次推送同一行(模拟轮询重复读取)→ 应去重,不重复入账 + if err := app.dispatchLedgerRows(ctx, dev.DeviceID, "alipay", 0, []capture.AlipayLedgerRow{row}); err != nil { + t.Fatalf("二次 dispatch 失败: %v", err) + } + + o, _ := app.store.GetOrder(ctx, "E2E-DUP") + if o.Status != model.StatusSuccess { + t.Fatalf("订单应 SUCCESS: %s", o.Status) + } + // 重复推送不应重复入账:回调 outbox 对同一订单只有一条待发任务(订单 ID 唯一) + tasks, err := app.store.GetPendingNotifyTasks(ctx, 10) + if err != nil { + t.Fatalf("GetPendingNotifyTasks: %v", err) + } + dupCount := 0 + for _, task := range tasks { + if task.OrderID == o.ID { + dupCount++ + } + } + if dupCount > 1 { + t.Fatalf("重复推送不应产生重复回调任务, OrderID=%d 任务数=%d", o.ID, dupCount) + } +} + +// TestE2E_NoOrderNoSettle 收款行无匹配订单 → 不产生任何入账/回调 +func TestE2E_NoOrderNoSettle(t *testing.T) { + ctx := context.Background() + app, pdb := newE2EEnv(t) + + app.accountPool.Add(&account.Account{ + ID: 1, Channel: "personal.alipay", Name: "e2e账户", Status: account.StatusNormal, + QRContent: "https://qr.alipay.com/e2e", + }) + dev := newTestDevice(t, pdb, app.accountPool, 1, "dev-e2e-none") + + // 无订单的收款行 + row := capture.AlipayLedgerRow{ + Payer: "*无", Date: "今日", Time: time.Now().Format("15:04:05"), + Amount: "+9.99", AmountMinor: 999, Note: "0000", + } + if err := app.dispatchLedgerRows(ctx, dev.DeviceID, "alipay", 0, []capture.AlipayLedgerRow{row}); err != nil { + t.Fatalf("dispatch 失败(应静默不匹配): %v", err) + } + // 无订单不应产生回调任务 + pending, _ := app.store.CountNotifyTasks(ctx, "pending") + if pending != 0 { + t.Fatalf("无订单不应产生回调任务, 实际 %d", pending) + } +} diff --git a/cmd/gateway/handlers_epay.go b/cmd/gateway/handlers_epay.go new file mode 100644 index 0000000..e9d4489 --- /dev/null +++ b/cmd/gateway/handlers_epay.go @@ -0,0 +1,363 @@ +// paygateway 支付网关 handlers_epay(从 main.go 拆分,易支付下单/回调/查单) +package main + +import ( + "net/http" + "paygateway/internal/account" + "paygateway/internal/channel" + wechatch "paygateway/internal/channel/wechat" + "paygateway/internal/epay" + "paygateway/internal/logger" + "paygateway/internal/model" + "paygateway/internal/notify" + "paygateway/internal/order" + "paygateway/internal/upstream" + "paygateway/internal/web" + "strings" + "time" + + "github.com/gin-gonic/gin" +) + +func (a *App) epayKeyFor(pid string) (string, bool) { + if a.upstreamPool == nil || pid == "" { + return "", false + } + u := a.upstreamPool.ByPid(pid) + if u == nil || u.Status != upstream.StatusEnabled || u.Key == "" { + return "", false + } + return u.Key, true +} + +// isExplicitPersonalType 是否显式指定个人免签通道(personal_alipay / personal_wxpay / personal_qq) +// 显式指定时不回退商家(用户明确选择个人通道,回退可能造成歧义) +func isExplicitPersonalType(epayType string) bool { + switch epayType { + case "personal_alipay", "personal_wxpay", "personal_qq": + return true + } + return false +} + +// personalChannelReady 检查个人通道是否有可用收款账户 +// (normal 状态 + 已配收款码 + 未冷却 + 未达日限额,任一满足即可接单) +func (a *App) personalChannelReady(name string) bool { + for _, acct := range a.accountPool.ListByChannel(name) { + if acct.Status != account.StatusNormal { + continue + } + if acct.QRContent == "" { + continue + } + if acct.FrozenUntil != nil && acct.FrozenUntil.After(time.Now()) { + continue + } + if acct.DailyLimit > 0 && acct.DailyUsed >= acct.DailyLimit { + continue + } + return true + } + return false +} + +func (a *App) handleEpaySubmit(c *gin.Context) { + params := collectParams(c) + + // 验签(严格多商户:pid 必须已登记且启用,密钥按 pid 路由) + key, ok := a.epayKeyFor(params["pid"]) + if !ok { + logger.Error("epay", "商户未登记或已禁用 ❌", "pid", params["pid"]) + c.String(http.StatusOK, "商户ID错误或已禁用") + return + } + if !epay.VerifySign(params, key) { + logger.Error("epay", "下单签名验证失败 ❌", "pid", params["pid"]) + c.String(http.StatusOK, "签名验证失败") + return + } + logger.Info("epay", "下单签名验证通过 ✅", "pid", params["pid"], "out_trade_no", params["out_trade_no"]) + + // 设备智能识别:优先 URL 参数 device,其次 User-Agent 自动判断 + // 电脑浏览器 → pc(收银台/扫码),手机浏览器 → mobile(WAP) + device := c.Query("device") + if device == "" { + ua := c.GetHeader("User-Agent") + if web.IsMobileUA(ua) { + device = "mobile" + } else { + device = "pc" + } + logger.Info("epay", "设备智能识别", "device", device, "ua", ua) + } + + buyerKey, fpKey := buyerKeyFor(c) + req := &order.CreateRequest{ + OutTradeNo: params["out_trade_no"], + Type: params["type"], + Name: params["name"], + Money: params["money"], + NotifyURL: params["notify_url"], + ReturnURL: params["return_url"], + PayMode: params["pay_type"], + Device: device, + BuyerKey: buyerKey, + FPKey: fpKey, + RawParams: params, + } + if req.OutTradeNo == "" || req.Money == "" || req.Type == "" { + c.String(http.StatusOK, "参数不完整") + return + } + // return_url 兜底重写:上游传内网/localhost → Referer/上游公网地址逐层替换 + // (return_url 是付款人浏览器的回跳,与 notify_url 的 SSRF 校验语义不同,允许改写) + req.ReturnURL = a.sanitizeReturnURL(c, req.OutTradeNo, params["pid"], req.ReturnURL) + params["return_url"] = req.ReturnURL + + // SSRF 防护:下单时即拒绝非法回调地址(scheme 白名单 + 可选私网封锁), + // 不让恶意 URL 进入 outbox 等待发送时才暴露 + if err := notify.ValidateNotifyURL(req.NotifyURL, a.cfg.NotifyAllowPrivateIPs); err != nil { + logger.Warn("epay", "notify_url 校验失败", "err", err, "out_trade_no", req.OutTradeNo) + c.String(http.StatusOK, "notify_url 非法: "+err.Error()) + return + } + + // 校验支付类型(epay 协议 → 通道注册名映射) + // 个人免签优先:personal.alipay/personal.wechat 已注册且启用 → 优先路由(0 费率) + chName := channel.ResolveEpayType(req.Type) + // 增强:个人通道无可用收款账户(未配置/冻结/限额/冷却)→ 自动回退商家通道 + // 仅对隐式类型(alipay/wxpay/qqpay)回退;显式 personal_alipay/personal_wxpay 不回退(报错提示更明确) + if strings.HasPrefix(chName, "personal.") && !isExplicitPersonalType(req.Type) { + if !a.personalChannelReady(chName) { + if mName, ok := channel.EpayTypeToChannel[req.Type]; ok { + logger.Info("epay", "个人通道无可用账户,回退商家通道", + "type", req.Type, "from", chName, "to", mName) + chName = mName + } + } + } + if chName == "" { + logger.Warn("epay", "不支持的支付类型", "type", req.Type) + c.String(http.StatusOK, "不支持的支付类型: "+req.Type) + return + } + // 同 IP 下单限流(防脚本批量刷单;仅个人免签通道启用,商家通道不限) + if a.cfg.MaxOrdersPerIPPerMin > 0 && strings.HasPrefix(chName, "personal.") { + ip := c.ClientIP() + if !a.ipLimiter.Allow(ip) { + logger.Warn("epay", "下单限流:IP 超限", "ip", ip, "limit", a.cfg.MaxOrdersPerIPPerMin, "out_trade_no", params["out_trade_no"]) + c.String(http.StatusOK, "下单过于频繁,请稍后重试") + return + } + } + if _, ok := channel.Get(chName); !ok { + logger.Warn("epay", "支付通道未注册", "type", req.Type, "channel", chName) + c.String(http.StatusOK, "不支持的支付类型: "+req.Type) + return + } + req.Type = chName // 转为通道注册名(如 "merchant.alipay" / "personal.alipay") + + res, err := a.orders.CreateOrder(c.Request.Context(), req) + if err != nil { + if res != nil && res.Reused { + // 复用已有订单:按原凭据重新渲染 + a.renderPayment(c, res.Existing) + return + } + logger.Error("epay", "下单失败", "err", err, "out_trade_no", req.OutTradeNo) + c.String(http.StatusOK, "下单失败: "+err.Error()) + return + } + // 进入确认调度器:超时未回调 → 主动查单兜底 + a.confirmSched.OnOrderCreated(res.Order) + // 账本页 adb 监听(docs/PLAN-ADB-LEDGER-IMPL.md §2.2):下单路由账户后激活该账户 + // 监听循环 → 高频轮询 + 高优先级窗口申请(下单后用户很可能马上付款,1s 级确认) + if a.ledgermon != nil && strings.HasPrefix(res.Order.Channel, "personal.") { + a.ledgermon.ActivateAccount(res.Order.AccountID) + } + a.renderPayment(c, res.Order) +} + +// renderPayment 根据订单凭据渲染支付页面 +func (a *App) renderPayment(c *gin.Context, o *model.Order) { + c.Header("Content-Type", "text/html; charset=utf-8") + // 已支付订单:直接渲染成功页(付款人刷新/回退支付 URL 时不该再看到收款码) + if o.Status == model.StatusSuccess { + a.setPaymentPageHeaders(c) + c.String(http.StatusOK, web.BuildSuccessPage(o, o.Channel)) + return + } + chName := o.Channel + if chName == "" { + chName = "alipay" + } + // 个人免签通道:直发码支付页(真实收款码 + 关单倒计时) + if strings.HasPrefix(chName, "personal.") && o.QrCode != "" { + var imgSrc string + if strings.HasPrefix(o.QrCode, "/qr/real") { + imgSrc = o.QrCode + } else { + // 历史数据兜底:旧套码 token 二维码内嵌渲染 + imgSrc = web.QRPngDataURI(o.QrCode) + } + // 关单时间(个人免签订单):动态窗口取池内 CloseAt(设备负载驱动); + // 否则 = 创建时间 + 动态下限 ORDER_CLOSE_MIN(动态窗口已启用时绝不回退静态 + // ORDER_CLOSE_MINUTES,否则环境配置会拉长个人订单关单+轮询时长)。0=未开启自动关单。 + expireAt := a.personalOrderCloseAt(o) + a.setPaymentPageHeaders(c) + // 支付方式切换无需页面按钮:上游换支付模式入口(同号不同 type)下单时自动迁移订单 + c.String(http.StatusOK, web.BuildPersonalQRPage(o, imgSrc, chName, expireAt)) + return + } + switch { + case o.QrCode != "": + // 有二维码优先展示二维码页面(扫码/H5 备用 URL 不覆盖) + a.setPaymentPageHeaders(c) + c.String(http.StatusOK, web.BuildQRPage(o, o.QrCode, chName)) + case strings.Contains(o.PayURL, "alipay.trade.page.pay"): + c.String(http.StatusOK, web.BuildRedirectPage(o, o.PayURL, "正在跳转到支付宝收银台...")) + case strings.Contains(o.PayURL, "alipay.trade.wap.pay"): + c.String(http.StatusOK, web.BuildRedirectPage(o, o.PayURL, "正在跳转到支付宝...")) + case strings.Contains(o.PayURL, "wx.tenpay.com") || strings.Contains(o.PayURL, "weixin://"): + c.String(http.StatusOK, web.BuildRedirectPage(o, o.PayURL, "正在跳转到微信支付...")) + default: + c.String(http.StatusOK, "订单创建成功,无支付凭据") + } +} + +// personalOrderCloseAt 计算个人免签订单的有效关单时间点;零值=未开启自动关单。 +// 语义(与关单扫描器 CloseExpiredOrders 对齐,避免页面倒计时与后台关单漂移): +// 1. 动态窗口模式:取 WAIT_PAY 池内 CloseAt(设备负载驱动,最精确) +// 2. 池内无该订单:动态窗口已启用则用动态下限 ORDER_CLOSE_MIN +// 3. 动态窗口未启用:用静态 ORDER_CLOSE_MINUTES +func (a *App) personalOrderCloseAt(o *model.Order) time.Time { + if o == nil { + return time.Time{} + } + if a.waitPayPool != nil { + if ct, ok := a.waitPayPool.CloseAtOf(o.OutTradeNo); ok && !ct.IsZero() { + return ct + } + } + var win time.Duration + if a.cfg.OrderCloseMin > 0 && a.cfg.OrderCloseMax > a.cfg.OrderCloseMin { + win = time.Duration(a.cfg.OrderCloseMin) * time.Minute + } else if a.cfg.OrderCloseMinutes > 0 { + win = time.Duration(a.cfg.OrderCloseMinutes) * time.Minute + } + if win <= 0 || (o.CreatedAt.IsZero()) { + return time.Time{} + } + closeAt := o.CreatedAt.Add(win) + if !o.SessionExpiresAt.IsZero() && o.SessionExpiresAt.Before(closeAt) { + return o.SessionExpiresAt + } + return closeAt +} + +// setPaymentPageHeaders 支付页安全响应头:防二维码替换/爬取/点击劫持 +// script-src 'unsafe-inline':支付页内联 JS 用于浏览器指纹采集(防换 IP 刷单) +// connect-src 'self':轮询 /qr/status 的 XHR 与指纹 sendBeacon 需要;缺省回落 default-src 'none' +// 会静默拦截所有 XHR(真机事故:倒计时能跳 /qr/expired 但支付成功永远探测不到,根因即此)。 +// Content-Type 必须显式 text/html:c.String 默认 text/plain 会把 HTML 当源码显示 +func (a *App) setPaymentPageHeaders(c *gin.Context) { + c.Header("Content-Type", "text/html; charset=utf-8") + c.Header("Content-Security-Policy", "default-src 'none'; img-src 'self' data:; style-src 'unsafe-inline'; script-src 'unsafe-inline'; connect-src 'self'; frame-ancestors 'self'; base-uri 'none'; form-action 'self'") + c.Header("X-Content-Type-Options", "nosniff") + c.Header("Referrer-Policy", "no-referrer") +} + +// ========== 支付宝异步回调 ========== +func (a *App) handleAlipayNotify(c *gin.Context) { + ch, ok := channel.Get("merchant.alipay") + if !ok { + c.String(http.StatusOK, "fail") + return + } + nr, err := ch.VerifyNotify(c.Request.Context(), c.Request) + if err != nil { + logger.Error("epay", "支付宝回调验签失败", "err", err) + c.String(http.StatusOK, "fail") + return + } + logger.Info("epay", "支付宝验签通过 ✅", "out_trade_no", nr.OutTradeNo, "status", nr.Status) + + if nr.Status == model.StatusSuccess { + if ok, err := a.orders.SettlePayment(c.Request.Context(), nr); err != nil || !ok { + logger.Error("epay", "支付结算失败", "err", err, "ok", ok) + c.String(http.StatusOK, "fail") + return + } + a.confirmSched.OnSettled(nr.OutTradeNo) + } else if nr.Status == model.StatusClosed { + logger.Info("epay", "订单关闭通知", "out_trade_no", nr.OutTradeNo) + a.confirmSched.OnClosed(nr.OutTradeNo) + } + + // 支付宝要求返回 success + c.String(http.StatusOK, "success") +} + +// ========== 微信异步回调 ========== +func (a *App) handleWechatNotify(c *gin.Context) { + ch, ok := channel.Get("merchant.wechat") + if !ok { + c.String(http.StatusOK, wechatch.NotifyFailResponse()) + return + } + nr, err := ch.VerifyNotify(c.Request.Context(), c.Request) + if err != nil { + logger.Error("epay", "微信回调验签失败", "err", err) + c.String(http.StatusOK, wechatch.NotifyFailResponse()) + return + } + logger.Info("epay", "微信验签通过 ✅", "out_trade_no", nr.OutTradeNo, "status", nr.Status) + + if nr.Status == model.StatusSuccess { + if ok, err := a.orders.SettlePayment(c.Request.Context(), nr); err != nil || !ok { + logger.Error("epay", "支付结算失败", "err", err, "ok", ok) + c.String(http.StatusOK, wechatch.NotifyFailResponse()) + return + } + a.confirmSched.OnSettled(nr.OutTradeNo) + } else if nr.Status == model.StatusClosed { + logger.Info("epay", "订单关闭通知", "out_trade_no", nr.OutTradeNo) + a.confirmSched.OnClosed(nr.OutTradeNo) + } + + // 微信要求返回 XML SUCCESS + c.String(http.StatusOK, wechatch.NotifySuccessResponse()) +} + +func (a *App) handleEpayQuery(c *gin.Context) { + params := make(map[string]string) + for k, v := range c.Request.URL.Query() { + if len(v) > 0 { + params[k] = v[0] + } + } + key, ok := a.epayKeyFor(params["pid"]) + if !ok || !epay.VerifySign(params, key) { + c.JSON(http.StatusOK, gin.H{"code": -1, "msg": "签名验证失败"}) + return + } + o, err := a.orders.QueryOrder(c.Request.Context(), params["out_trade_no"]) + if err != nil || o == nil { + c.JSON(http.StatusOK, gin.H{"code": -1, "msg": "订单不存在"}) + return + } + c.JSON(http.StatusOK, gin.H{ + "code": 1, + "msg": "success", + "out_trade_no": o.OutTradeNo, + "trade_no": o.GatewayNo, + "money": o.MoneyYuan(), + "trade_status": o.Status, + }) +} + +// handleQrRefresh 刷新二维码:为原订单重新生成二维码/备注码(保持原单号不变) +// 易支付协议 out_trade_no 是上游业务主键,改单号会导致回调上游找不到订单 → 入账失败 +// handleBindFingerprint 绑定浏览器指纹到订单 buyer_key(防换 IP 刷单) +// GET /personal/bind-fp?o={out_trade_no}&fp={base64(JSON指纹)} +// 指纹并入 buyer_key:原 buyer_key = H(ip|ua|device|buyer_id),追加指纹后抗换 IP 攻击。 diff --git a/cmd/gateway/handlers_qr.go b/cmd/gateway/handlers_qr.go new file mode 100644 index 0000000..ce0bd64 --- /dev/null +++ b/cmd/gateway/handlers_qr.go @@ -0,0 +1,250 @@ +// paygateway 支付网关 handlers_qr(从 main.go 拆分,QR 系接口/指纹/参数收集) +package main + +import ( + "crypto/sha256" + "encoding/base64" + "fmt" + "net/http" + "paygateway/internal/logger" + "paygateway/internal/model" + "paygateway/internal/web" + "strings" + "time" + + "github.com/gin-gonic/gin" +) + +func (a *App) handleBindFingerprint(c *gin.Context) { + orderNo := c.Query("o") + fpB64 := c.Query("fp") + if orderNo == "" || fpB64 == "" { + c.Status(http.StatusOK) + return + } + // base64 解码指纹 JSON(失败静默,指纹是增强非必须) + fpJSON, err := base64.StdEncoding.DecodeString(fpB64) + if err != nil || len(fpJSON) == 0 || len(fpJSON) > 1024 { + c.Status(http.StatusOK) + return + } + // 指纹单独存 fp_key 列(不改写 buyer_key): + // buyer_key 保持 = H(ip|ua|device|buyer_id) 不变,串行检查 buyer_key 或 fp_key 均命中; + // 换 IP 场景:buyer_key 变但 fp_key 同 → 仍被串行拦截 + fpHash := sha256.Sum256(fpJSON) + fpKey := fmt.Sprintf("%x", fpHash[:16]) + ctx := c.Request.Context() + o, err := a.store.GetOrder(ctx, orderNo) + if err != nil || o == nil || o.Status != model.StatusWaitPay { + c.Status(http.StatusOK) + return + } + if _, err := a.store.UpdateOrderStatus(ctx, orderNo, model.StatusWaitPay, model.StatusWaitPay, func(upd *model.Order) { + upd.FPKey = fpKey + }); err != nil { + logger.Warn("personal", "指纹绑定失败", "err", err, "order", orderNo) + } + // Set-Cookie 写入指纹 hash:后续下单携带该 cookie,服务端按 fp_key 匹配(换 IP 串行兜底) + c.SetCookie("pg_fp", fpKey, 86400, "/", "", false, true) + logger.Info("personal", "浏览器指纹已绑定", "order", orderNo, "fp_key", fpKey[:8], "fp_len", len(fpJSON)) + c.Status(http.StatusOK) +} + +func (a *App) handleQrRefresh(c *gin.Context) { + oldNo := c.Query("out_trade_no") + if oldNo == "" { + c.String(http.StatusOK, "参数缺失: out_trade_no") + return + } + ctx := c.Request.Context() + + // 查原订单,校验状态 + o, err := a.store.GetOrder(ctx, oldNo) + if err != nil || o == nil { + c.String(http.StatusOK, "订单不存在") + return + } + if o.Status != model.StatusWaitPay { + c.String(http.StatusOK, "订单当前状态("+o.Status+")不可刷新") + return + } + + logger.Info("qr", "刷新二维码", "out_trade_no", oldNo) + + // 保持原单号刷新支付凭据(重新分配备注码 + 套码 token) + refresh, err := a.orders.RefreshPayment(ctx, o) + if err != nil { + logger.Error("qr", "刷新二维码失败", "err", err, "old", oldNo) + c.String(http.StatusOK, "刷新失败: "+err.Error()) + return + } + // 通知栏监听驱动:账本导航兜底已废弃(用户决策),刷新后由监听端通知推送匹配入账 + a.renderPayment(c, refresh) +} + +func (a *App) handleQrExpired(c *gin.Context) { + outNo := c.Query("out_trade_no") + provider := "" + if outNo != "" { + if o, err := a.store.GetOrder(c.Request.Context(), outNo); err == nil && o != nil { + if strings.HasSuffix(o.Channel, "wechat") { + provider = "微信" + } else if strings.HasSuffix(o.Channel, "alipay") { + provider = "支付宝" + } + } + } + a.setPaymentPageHeaders(c) + // 过期订单是终态,不提供复活入口(防僵尸订单刷单);引导返回上游重新下单 + c.String(http.StatusOK, web.BuildExpiredPage(provider, outNo, c.Query("return_url"))) +} + +// handleQrStatus 订单状态探测:过期页 JS 周期性轮询。 +// 上游同号重提复活订单后返回 alive=true,过期页自动跳回支付页继续支付。 +// 仅返回轻量 JSON,不携带敏感信息。 +func (a *App) handleQrStatus(c *gin.Context) { + outNo := c.Query("out_trade_no") + logger.Info("qr", "status 探测", "out_trade_no", outNo, "ua", c.Request.UserAgent()) + if outNo == "" { + c.JSON(http.StatusOK, gin.H{"alive": false}) + return + } + o, err := a.store.GetOrder(c.Request.Context(), outNo) + if err != nil || o == nil { + c.JSON(http.StatusOK, gin.H{"alive": false}) + return + } + if o.Status == model.StatusWaitPay && (o.PayURL != "" || o.QrCode != "") { + // 个人免签订单:关单时间已到(页面倒计时已归零 / 后台扫描器即将关单)→ 视为过期, + // 返回 alive=false,让过期页停在该分支,避免「过期页→/qr/refresh→支付页」死循环幽灵订单。 + if strings.HasPrefix(o.Channel, "personal.") { + if ct := a.personalOrderCloseAt(o); !ct.IsZero() && time.Now().After(ct) { + c.JSON(http.StatusOK, gin.H{"alive": false, "expired": true, "status": model.StatusClosed}) + return + } + } + c.JSON(http.StatusOK, gin.H{"alive": true, "channel": o.Channel}) + return + } + c.JSON(http.StatusOK, gin.H{"alive": false, "status": o.Status}) +} + +// handleQrSuccess 支付成功落地页:仅当订单状态为 TRADE_SUCCESS 时渲染成功页, +// 否则(未支付/已关闭/不存在)回退到支付页或过期页,防伪造"支付成功"页面。 +// GET /qr/success?out_trade_no={no} +func (a *App) handleQrSuccess(c *gin.Context) { + outNo := c.Query("out_trade_no") + if outNo == "" { + c.String(http.StatusOK, "参数缺失: out_trade_no") + return + } + o, err := a.store.GetOrder(c.Request.Context(), outNo) + if err != nil || o == nil { + c.String(http.StatusOK, "订单不存在") + return + } + if o.Status != model.StatusSuccess { + // 未支付/确认中 → 回到支付页继续支付;已关闭/失败 → 过期引导页 + if o.Status == model.StatusWaitPay || o.Status == model.StatusConfirming { + a.renderPayment(c, o) + return + } + a.setPaymentPageHeaders(c) + c.String(http.StatusOK, web.BuildExpiredPage("", outNo, o.ReturnURL)) + return + } + a.setPaymentPageHeaders(c) + c.String(http.StatusOK, web.BuildSuccessPage(o, o.Channel)) +} + +func (a *App) handleQrSwitch(c *gin.Context) { + outNo := c.Query("out_trade_no") + method := c.Query("method") // alipay / wechat + if outNo == "" || method == "" { + c.String(http.StatusOK, "参数缺失: out_trade_no / method") + return + } + ctx := c.Request.Context() + o, err := a.store.GetOrder(ctx, outNo) + if err != nil || o == nil { + c.String(http.StatusOK, "订单不存在") + return + } + if !strings.HasPrefix(o.Channel, "personal.") { + c.String(http.StatusOK, "该订单不支持切换支付方式") + return + } + // method → 个人免签通道名 + newChannel := "" + switch method { + case "alipay": + newChannel = "personal.alipay" + case "wechat": + newChannel = "personal.wechat" + } + if newChannel == "" || newChannel == o.Channel { + c.String(http.StatusOK, "无效的切换目标") + return + } + // 目标通道必须有可用收款账户(无账户切过去会白屏报错) + if !a.personalChannelReady(newChannel) { + c.String(http.StatusOK, "该支付方式暂不可用(无可用收款账户)") + return + } + logger.Info("qr", "切换支付方式", "out_trade_no", outNo, "from", o.Channel, "to", newChannel) + switched, err := a.orders.SwitchChannel(ctx, o, newChannel) + if err != nil { + logger.Error("qr", "切换支付方式失败", "err", err, "out_trade_no", outNo) + c.String(http.StatusOK, "切换支付方式失败: "+err.Error()) + return + } + a.renderPayment(c, switched) +} + +// collectParams 收集 GET query + POST form 参数 +// buyerKeyFor 计算买家环境唯一标识(买家级串行/防刷单)。 +// 环境 = 客户端 IP + User-Agent + device 参数 + 可选 buyer_id(下游可透传真实用户标识), +// SHA-256 哈希后作为 buyer_key;同环境同时只允许一笔待支付订单。 +// 浏览器指纹(cookie pg_fp)单独作为 fp_key 返回,用于换 IP 场景串行兜底。 +func buyerKeyFor(c *gin.Context) (buyerKey, fpKey string) { + ip := c.ClientIP() + ua := c.GetHeader("User-Agent") + device := c.Query("device") + bid := c.Query("buyer_id") + if ip == "" && bid == "" { + return "", "" + } + h := sha256.Sum256([]byte(ip + "|" + ua + "|" + device + "|" + bid)) + fp := c.GetHeader("Cookie") + if fp != "" { + for _, part := range strings.Split(fp, ";") { + part = strings.TrimSpace(part) + if strings.HasPrefix(part, "pg_fp=") { + fpKey = strings.TrimPrefix(part, "pg_fp=") + break + } + } + } + return fmt.Sprintf("%x", h[:16]), fpKey +} + +func collectParams(c *gin.Context) map[string]string { + params := make(map[string]string) + for k, v := range c.Request.URL.Query() { + if len(v) > 0 { + params[k] = v[0] + } + } + if c.Request.Method == http.MethodPost { + c.Request.ParseForm() + for k, v := range c.Request.PostForm { + params[k] = v[0] + } + } + return params +} + +// ========== 个人免签通道:监听端通信 ========== + +// decryptDeviceSecret 解密设备通信密钥(复用账户 AES 主密钥) +// 设备密钥以密文存 personal.db,这里用账户包的全局解密函数 diff --git a/cmd/gateway/ledger.go b/cmd/gateway/ledger.go new file mode 100644 index 0000000..9c8cb8c --- /dev/null +++ b/cmd/gateway/ledger.go @@ -0,0 +1,448 @@ +// paygateway 支付网关 ledger(从 main.go 拆分,账本监听/匹配引擎) +package main + +import ( + "context" + "fmt" + "paygateway/internal/account" + "paygateway/internal/admin/capture" + "paygateway/internal/channel" + "paygateway/internal/ledgermon" + "paygateway/internal/logger" + "paygateway/internal/model" + "paygateway/internal/personal" + "paygateway/internal/pool" + "paygateway/internal/store" + "strings" + "time" +) + +func (a *App) newLedgerReader(d ledgermon.MonitoredDevice) ledgermon.LedgerReader { + switch d.Provider { + case "alipay": + return ledgermon.NewAlipayReader(adbMgr(), d.Serial, d.UserID) + case "wechat": + return ledgermon.NewWechatReader(adbMgr(), d.Serial, d.UserID) + } + return nil +} + +// dispatchLedgerRows 账本新收款行推送(ledgermon.DispatchFunc 实现): +// 复用个人通道匹配入账链路(/personal/push 同款),source=ledger,窗口 30min(账本轮询兜底)。 +// 匹配顺序(池消费模型,每笔金额独立匹配池中订单): +// 1. 备注码精确匹配(微信账本行自带"付款方备注";支付宝账本页无备注需探测) +// 2. 金额+时间窗口匹配:唯一候选 → 直接命中(免探测);零候选 → no_order 转人工 +// 3. 多候选歧义:支付宝可点金额进详情页探测备注码消歧(探测失败/无备注码 → 保持 ambiguous 转人工, +// 禁止自动挑选——资金安全不变量) +// +// 注:探测在 DeviceActor 串行窗口内执行(PollCmd.Execute → Dispatch),无 adb 并发冲突。 +func (a *App) dispatchLedgerRows(ctx context.Context, deviceID string, provider string, space int, rows []capture.AlipayLedgerRow) error { + // 1. 设备加载(验签不需要;匹配链路需要 dev.ID/AccountID 解析账户集合) + dev, err := a.personalDeviceCtx(ctx, deviceID) + if err != nil { + logger.Warn("ledgermon", "账本推送:设备加载失败", "err", err, "device_id", deviceID) + return err + } + if dev.Status == personal.DeviceFrozen { + logger.Warn("ledgermon", "账本推送:设备已冻结,跳过", "device_id", deviceID) + return nil + } + // 2. 逐行匹配入账(窗口 30min:账本轮询兜底语义,覆盖读取间隔+推送延迟) + window := 30 * time.Minute + pushed := false + for _, r := range rows { + // 幂等去重:同一设备同原文哈希只处理一次(fingerprint=时间|金额,同指纹跨轮去重) + raw := fmt.Sprintf("ledger:%s|%s|%s", r.Payer, r.Time, r.Amount) + rawHash := fmt.Sprintf("%x", sha256Hash([]byte(raw))) + dup, err := a.personalDB.PushExists(ctx, deviceID, rawHash) + if err != nil { + logger.Error("ledgermon", "账本推送去重查询失败", "err", err, "device_id", deviceID) + continue + } + if dup { + continue + } + // 大额告警(对齐原 /personal/push 链路):账本行金额超阈值 → 告警留痕。 + // listener 下线后服务端是唯一告警点,走 alertC(原实现仅 logger,此处升级)。 + if a.cfg != nil && a.cfg.PersonalBigAmount > 0 && r.AmountMinor >= a.cfg.PersonalBigAmount { + logger.Warn("ledgermon", "大额收款告警", + "device_id", deviceID, "provider", provider, + "amount", r.AmountMinor, "row", raw, "threshold", a.cfg.PersonalBigAmount) + if a.alertC != nil { + a.alertC.Send("warn", "大额收款", + fmt.Sprintf("账本监听:设备 %s 收到 %d 分,需留意", deviceID, r.AmountMinor)) + } + } + orderNo, matched, orderAccountID, effNote, err := a.matchLedgerRow(ctx, dev, provider, space, r, window, a.ledgerProber()) + if err != nil { + logger.Error("ledgermon", "账本订单匹配失败", "err", err, "device_id", deviceID, "amount", r.AmountMinor) + continue + } + r.Note = effNote // 审计:流水记录实际用于消歧的备注码(探测命中时非空) + // 记录推送流水(审计,先记后入账) + push := &personal.Push{ + DeviceID: deviceID, Provider: provider, AmountMinor: r.AmountMinor, + Note: r.Note, RawHash: rawHash, Raw: raw, + MatchedOrder: orderNo, MatchResult: matched, Space: space, + } + if err := a.personalDB.InsertPush(ctx, push); err != nil { + logger.Error("ledgermon", "账本推送流水写入失败", "err", err, "device_id", deviceID) + } + pushed = true + switch matched { + case "matched": + // 账户冻结守卫(对齐原 /personal/push 链路):冻结账户不自动入账,转异常池人工核实 + //(钱已在账户里,不会丢;只是不自动确认)。原实现仅 continue 导致行只进审计流水 + // 不进异常池、且指纹已提交永不再现——2026-08-30 迁移补 EnqueueException 保全核查路径。 + if acct := a.accountPool.GetByID(orderAccountID); acct != nil && acct.Status == account.StatusFrozen { + logger.Warn("ledgermon", "账户已冻结,账本收款转人工", "order", orderNo, "account_id", orderAccountID) + if cur, cerr := a.store.GetOrder(ctx, orderNo); cerr == nil && cur != nil { + a.orders.EnqueueException(ctx, cur, model.ExReasonListenOffline, "PAID_WHILE_FROZEN", + "", raw, "账本监听:收款到达时账户处于冻结态,需人工核实际收款后入账") + } + continue + } + nr := &channel.NotifyResult{ + Valid: true, + OutTradeNo: orderNo, + ChannelTxID: fmt.Sprintf("LEDGER-%s-%d", deviceID, time.Now().UnixNano()), + Status: model.StatusSuccess, + AmountMinor: r.AmountMinor, + Payer: "ledger:" + deviceID, + DeviceID: deviceID, + AccountID: orderAccountID, + RawPayload: raw, + } + if ok, serr := a.orders.SettlePayment(ctx, nr); serr != nil { + logger.Error("ledgermon", "账本入账失败", "err", serr, "order", orderNo) + continue + } else if !ok { + // ok=false, serr=nil:并发已支付/已关单(幂等),非异常不重复入异常池 + if cur, cerr := a.store.GetOrder(ctx, orderNo); cerr == nil && cur != nil && cur.Status == model.StatusClosed { + a.orders.EnqueueException(ctx, cur, model.ExReasonStateConflict, "TRADE_SUCCESS_AFTER_CLOSED", + nr.ChannelTxID, raw, "账本监听:关单后收到收款记录,需人工核对实际收款") + } + continue + } + // 回写订单 device_id(审计追溯) + if _, uerr := a.store.UpdateOrderStatus(ctx, orderNo, model.StatusSuccess, model.StatusSuccess, func(upd *model.Order) { + upd.DeviceID = deviceID + }); uerr != nil { + logger.Warn("ledgermon", "回写订单 device_id 失败", "err", uerr, "order", orderNo) + } + a.confirmSched.OnSettled(orderNo) + logger.Info("ledgermon", "账本监听收款入账成功 ✅", + "order", orderNo, "amount", r.AmountMinor, "device", deviceID, "row", raw) + case "settled_dup": + // 同一笔收款已被通知栏/账本另一链路入账(真机实测 5s 内双链同捕) + logger.Info("ledgermon", "账本重复捕获,另一链路已入账(幂等跳过)", + "order", orderNo, "amount", r.AmountMinor, "device", deviceID) + case "no_order": + a.saveUnmatched(ctx, provider, r.AmountMinor, r.Note, raw, r.Payer, deviceID) + logger.Warn("ledgermon", "账本无订单收款(需人工处理)", "device", deviceID, "amount", r.AmountMinor) + default: // ambiguous + a.saveUnmatched(ctx, provider, r.AmountMinor, r.Note, raw, r.Payer, deviceID) + logger.Warn("ledgermon", "账本多订单歧义(需人工确认)", "device", deviceID, "amount", r.AmountMinor) + } + } + // 有推送流水则刷新 last_push(管理后台展示真实推送时间;listener 下线后由账本链路维护) + if pushed { + if err := a.personalDB.SetDevicePushTime(ctx, deviceID); err != nil { + logger.Warn("ledgermon", "更新设备推送时间失败", "err", err, "device_id", deviceID) + } + } + return nil +} + +// alipayNoteProber 支付宝备注码探测函数(多候选歧义消解用;注入便于测试) +// 生产 = probeAlipayLedgerNote(adb 详情页探测);测试 = 桩返回固定备注码。 +type alipayNoteProber func(ctx context.Context, dev *personal.Device, bounds string) (string, error) + +// matchLedgerRow 单账本行池消费匹配: +// 1. matchPersonalOrder:备注码精确(微信行自带)> 金额+时间窗口(唯一候选直接命中,免探测) +// 2. 多候选歧义(支付宝且行有金额坐标)→ prober 探测详情页备注码 → 重新匹配消歧 +// 3. 探测失败/无备注码/重新匹配仍歧义 → 保持 ambiguous(禁止自动挑选,资金安全) +// +// 返回 (订单号, 匹配结果, 订单账户ID, 实际备注码, error)。实际备注码供审计流水记录 +// (探测命中时非空,未命中沿用行内 Note)。 +func (a *App) matchLedgerRow(ctx context.Context, dev *personal.Device, provider string, space int, r capture.AlipayLedgerRow, window time.Duration, prober alipayNoteProber) (string, string, int64, string, error) { + orderNo, matched, orderAccountID, err := a.matchPersonalOrder(ctx, dev, provider, r.Note, r.AmountMinor, time.Now(), window, space) + if err != nil { + return "", "", 0, r.Note, err + } + effNote := r.Note + // 多候选歧义消解:支付宝账本行无备注 → 探测详情页备注码后重新匹配(池消费模型第 3 步)。 + // 探测耗时 ≈3.5s/行(点击+dump+返回),仅歧义行触发(金额唯一已短路免探测)。 + if matched == "ambiguous" && provider == "alipay" && r.AmountBounds != "" && prober != nil && a.waitPayPool != nil { + probed, perr := prober(ctx, dev, r.AmountBounds) + if perr != nil { + logger.Warn("ledgermon", "支付宝备注码探测失败,保持多候选转人工", + "err", perr, "device_id", dev.DeviceID, "amount", r.AmountMinor) + } else if probed != "" { + orderNo2, matched2, acct2, err2 := a.matchPersonalOrder(ctx, dev, provider, probed, r.AmountMinor, time.Now(), window, space) + if err2 != nil { + logger.Error("ledgermon", "探测备注码重新匹配失败", "err", err2, "device_id", dev.DeviceID, "amount", r.AmountMinor) + } else if matched2 == "matched" { + effNote = probed + logger.Info("ledgermon", "支付宝备注码探测消歧成功 ✅", + "order", orderNo2, "amount", r.AmountMinor, "note", probed, "device", dev.DeviceID) + return orderNo2, matched2, acct2, effNote, nil + } + } + } + return orderNo, matched, orderAccountID, effNote, nil +} + +// ledgerProber 返回备注码探测函数:测试注入桩优先(alipayProber 字段),nil 回退生产 adb 探针。 +func (a *App) ledgerProber() alipayNoteProber { + if a.alipayProber != nil { + return a.alipayProber + } + return a.probeAlipayLedgerNote +} + +// probeAlipayLedgerNote 生产备注码探针:adb 详情页探测(DeviceActor 串行窗口内执行,无并发冲突)。 +// 无可用 serial 时返回错误(调用方保持歧义转人工,不阻塞入账)。 +func (a *App) probeAlipayLedgerNote(ctx context.Context, dev *personal.Device, bounds string) (string, error) { + serial := deviceSerialFromDevice(dev) + if serial == "" { + return "", fmt.Errorf("设备无可用 adb serial") + } + return capture.ProbeAlipayNote(ctx, adbMgr(), serial, bounds) +} +func (a *App) personalDeviceCtx(ctx context.Context, deviceID string) (*personal.Device, error) { + enc, err := a.personalDB.GetDevice(ctx, deviceID) + if err != nil { + return nil, err + } + if enc == nil { + return nil, fmt.Errorf("设备未登记: %s", deviceID) + } + secret, err := decryptDeviceSecret(enc.SecretEnc) + if err != nil { + return nil, fmt.Errorf("解密设备密钥失败: %w", err) + } + return &personal.Device{ + ID: enc.ID, DeviceID: enc.DeviceID, Name: enc.Name, + AccountID: enc.AccountID, Provider: enc.Provider, Secret: secret, + Status: personal.DeviceStatus(enc.Status), + AdbHost: enc.AdbHost, + AdbPort: enc.AdbPort, + AdbWifiHost: enc.AdbWifiHost, + AdbWifiPort: enc.AdbWifiPort, + SetupProgress: enc.SetupProgress, + TsIP: enc.TsIP, + AdbListen: enc.AdbListen != 0, + ControlState: enc.ControlState, + }, nil +} + +// matchPersonalOrder 匹配订单(通知栏监听方案:备注码可选加速 + 金额时间窗口) +// 1. 备注码精确匹配:note 非空且命中唯一 WAIT_PAY 订单 → 直接命中(通知栏方案下通常为空,自禁用) +// 2. 金额+时间窗口:候选唯一 → matched;多个 → ambiguous(转人工,禁止自动挑选);零个 → no_order +// +// 时间窗口语义(2026-08-30 通知栏方案修正): +// - anchor 为收款发生时刻(账本链路传 time.Now();通知栏链路传通知 when 毫秒时间戳); +// - 窗口 = [anchor-30min, anchor]:订单必须创建于收款之前(收款时订单必已存在), +// 且不早于窗口下界(防止远古订单被新收款误匹配)。 +// 不能用纯下界(通知不清理会一直在,旧通知会错误匹配之后创建的新订单)。 +// +// 返回 (订单号, 匹配结果, 订单账户ID, error);匹配结果: matched / no_order / ambiguous +// 订单账户ID 用于入账账本记录(区分主账户/附加账户收款),0 表示未匹配成功。 +func (a *App) matchPersonalOrder(ctx context.Context, dev *personal.Device, provider, note string, amountMinor int64, anchor time.Time, window time.Duration, space int) (string, string, int64, error) { + since := anchor.Add(-window) + // 设备可服务的账户集合(主账户 + 附加账户,应用双开多账户): + // 匹配时订单账户必须属于该集合(防串单),通道校验也按该集合的通道判断。 + acctIDs := store.DeviceAccountIDs(ctx, a.personalDB, dev.ID, dev.AccountID) + inAcctIDs := func(id int64) bool { + for _, v := range acctIDs { + if v == id { + return true + } + } + return false + } + // 空间账户定位:按通知来源空间(0主/999双开) + 设备 + 平台 定位该空间专属账户。 + // 通过 device_qrcodes 拿该空间的收款码 → 匹配账户池中同收款码的账户, + // 匹配时优先只在该账户的订单中找(区分双开账户收款,杜绝跨空间错配)。 + spaceAcctID := a.accountIDForSpace(ctx, dev, provider, space) + if spaceAcctID > 0 && !inAcctIDs(spaceAcctID) { + spaceAcctID = 0 // 空间账户不在设备绑定集合 → 不启用空间过滤(数据不一致兜底) + } + // 设备可服务通道集合(绑定账户的通道,如 {personal.alipay, personal.wechat}) + servesChannel := func(ch string) bool { + if !strings.HasPrefix(ch, "personal.") { + return false + } + for _, id := range acctIDs { + if acct := a.accountPool.GetByID(id); acct != nil && acct.Channel == ch { + return true + } + } + return false + } + + // 1. 备注码精确匹配(首选,资金不错配核心): + // 内存池索引优先(空间账户定位 + noteCode 精确命中,零 DB); + // 池未命中/池不可用 → 回退 DB GetOrderByNoteCode(DB 是真相,两路径结果等价)。 + // 命中后统一 DB 二次校验(状态仍 WAIT_PAY 才采信)+ 通道/账户校验。 + if note != "" { + // 优先池匹配(生产路径;测试环境无池自动回退 DB) + var noteTarget string + if e := a.noteMatchFromPool(dev, provider, space, note, since, spaceAcctID); e != nil { + noteTarget = e.OutTradeNo + } else if o, err := a.store.GetOrderByNoteCode(ctx, note, since); err == nil && o != nil { + noteTarget = o.OutTradeNo + } + if noteTarget != "" { + // DB 二次校验:订单仍待支付才采信(池可能落后于 DB 状态变更) + o, err := a.store.GetOrder(ctx, noteTarget) + if err != nil { + return "", "", 0, err + } + if o != nil && o.Status == model.StatusWaitPay { + // 校验通道 + 账户双重匹配:订单账户必须 ∈ 设备绑定账户集合(防串单) + if servesChannel(o.Channel) && inAcctIDs(o.AccountID) { + return o.OutTradeNo, "matched", o.AccountID, nil + } + logger.Warn("personal", "备注码命中的订单账户设备不可服务", "note", note, + "order_channel", o.Channel, "order_account", o.AccountID, "device_id", dev.DeviceID) + } + } + } + + // 2. 金额 + 时间窗口匹配(通知栏监听方案主路径:仅 包名+金额+时间 信号) + // 候选收集后按数量分派:0→no_order;1→matched;≥2→ambiguous 转人工。 + // 空间账户已定位(spaceAcctID>0)时不回退设备集合:钱落在该空间的收款码上, + // 其他账户的订单不可能是归属方,回退 = 跨账户错配。 + orders, err := a.store.GetOrdersByAmount(ctx, amountMinor, since) + if err != nil { + return "", "", 0, err + } + var candidates []*model.Order + for _, o := range orders { + if !servesChannel(o.Channel) { + continue + } + if o.AccountID > 0 && !inAcctIDs(o.AccountID) { + continue + } + if spaceAcctID > 0 && o.AccountID != spaceAcctID { + continue + } + // 上界过滤:订单必须创建于收款时刻之前(收款时订单必已存在)。 + // 纯下界会允许"通知之后创建的新订单"被旧通知误匹配(通知不清理会一直在)。 + if o.CreatedAt.After(anchor) { + continue + } + candidates = append(candidates, o) + } + switch len(candidates) { + case 0: + // 零 WAIT_PAY 候选 ≠ 无订单:通知栏/账本双链 5s 内同时捕获同一笔收款时, + // 胜者已把订单结算(TRADE_SUCCESS),败者这时匹配查不到 WAIT_PAY。 + // 回查本设备可服务账户的同金额最近已支付订单(按账户过滤,防同金额异地/他户撞车): + // - 过滤后又按空间账户精确限缩:1 笔 → settled_dup(另一链路已入账,幂等跳过) + // - 0 笔 → 真无订单(no_order) ≥2 笔 → 同账户金额歧义,保持 no_order(资金安全,不猜,转人工) + settled, serr := a.store.GetRecentSettledByAmount(ctx, amountMinor, since, acctIDs) + if serr == nil { + var owned []*model.Order + for _, o := range settled { + if servesChannel(o.Channel) && (o.AccountID == 0 || inAcctIDs(o.AccountID)) { + if spaceAcctID > 0 && o.AccountID != spaceAcctID { + continue + } + owned = append(owned, o) + } + } + if len(owned) == 1 { + logger.Info("personal", "重复捕获判定:同账户订单已由另一链路入账,幂等跳过", + "order", owned[0].OutTradeNo, "amount", amountMinor, "device", dev.DeviceID) + return owned[0].OutTradeNo, "settled_dup", owned[0].AccountID, nil + } + if len(owned) > 1 { + logger.Warn("personal", "同账户同金额多笔已支付,不可归属,保持 no_order 转人工", + "amount", amountMinor, "device", dev.DeviceID, "settled", len(owned)) + return "", "no_order", 0, nil + } + } + return "", "no_order", 0, nil + case 1: + logger.Info("personal", "金额+时间窗口匹配成功", "order", candidates[0].OutTradeNo, + "amount", amountMinor, "device", dev.DeviceID, "account_id", candidates[0].AccountID) + return candidates[0].OutTradeNo, "matched", candidates[0].AccountID, nil + default: + ids := make([]string, 0, len(candidates)) + for _, o := range candidates { + ids = append(ids, fmt.Sprintf("%s(acct=%d)", o.OutTradeNo, o.AccountID)) + } + logger.Warn("personal", "同金额多候选,拒绝自动入账转人工", + "amount", amountMinor, "device", dev.DeviceID, "candidates", strings.Join(ids, ", ")) + return "", "ambiguous", 0, nil + } +} + +// noteMatchFromPool 备注码池匹配:优先空间账户(spaceAcctID>0 时只在该账户桶内找, +// 杜绝跨空间错配);未定位空间账户时遍历设备绑定账户桶。返回池条目或 nil。 +// 注意:仅池内索引匹配,调用方必须 DB 二次校验。 +func (a *App) noteMatchFromPool(dev *personal.Device, provider string, space int, note string, since time.Time, spaceAcctID int64) *pool.Entry { + if a.waitPayPool == nil { + return nil + } + if spaceAcctID > 0 { + if e := a.waitPayPool.MatchByNote(spaceAcctID, note, since); e != nil { + return e + } + return nil // 空间已定位:其他账户桶的备注码不可能是本空间收款(防跨空间错配) + } + for _, id := range store.DeviceAccountIDs(context.Background(), a.personalDB, dev.ID, dev.AccountID) { + if e := a.waitPayPool.MatchByNote(id, note, since); e != nil { + return e + } + } + return nil +} + +// accountIDForSpace 按通知来源空间定位设备专属账户: +// 查 device_qrcodes(设备+平台+用户空间)的最新收款码 → 与账户池中账户的 QRContent 匹配。 +// space=0(主空间)与 space=999(双开)都解析:主空间收款只匹配主账户订单、双开只匹配双开账户订单, +// 彻底消除同设备同金额下主/双开账户错配。查不到(无码/未绑定/码不匹配)返回 0, +// 此时匹配范围放宽为设备绑定账户集合(歧义由多候选拒绝机制兜底,不再自动挑选)。 +func (a *App) accountIDForSpace(ctx context.Context, dev *personal.Device, provider string, space int) int64 { + if dev == nil { + return 0 + } + qr, err := a.personalDB.GetLatestQrcodeByPlatformUser(ctx, dev.DeviceID, provider, space) + if err != nil || qr == nil || qr.QRContent == "" { + return 0 + } + for _, id := range store.DeviceAccountIDs(ctx, a.personalDB, dev.ID, dev.AccountID) { + acct := a.accountPool.GetByID(id) + if acct != nil && acct.QRContent == qr.QRContent { + return acct.ID + } + } + return 0 +} + +// resolvePersonalQr 校验套码 token 并解析 (订单, 个人通道, 账户);/mq 与 /qr/real 共用 +// 校验链:token 签名/过期 → 订单存在 → WAIT_PAY → token 与订单匹配 → 账户收款码存在 + +// ledgerUserSpaceOf 账户所属用户空间:收款码内容匹配该设备 (平台, 用户空间) 最新码。 +// 主空间(user 0)与双开(user 999)的码内容不同,用 QRContent 精确匹配归属; +// 匹配不到(无码/未上报)默认主空间 0。 +func ledgerUserSpaceOf(ctx context.Context, pdb store.PersonalStore, deviceID, provider, qrContent string) int { + if qrContent == "" { + return 0 + } + for _, space := range []int{999, 0} { + qr, err := pdb.GetLatestQrcodeByPlatformUser(ctx, deviceID, provider, space) + if err == nil && qr != nil && qr.QRContent == qrContent { + return space + } + } + return 0 +} + +// newLedgerReader 按设备构造账本读取器(ledgermon.ReaderFactory 实现)。 +// Phase 1 仅支付宝 dump 文本通道;微信 OCR 通道 Phase 2 接入。 diff --git a/cmd/gateway/ledger_match_test.go b/cmd/gateway/ledger_match_test.go new file mode 100644 index 0000000..2963622 --- /dev/null +++ b/cmd/gateway/ledger_match_test.go @@ -0,0 +1,804 @@ +package main + +// 资金核心链路测试:账本新收款行 → 订单匹配 → 入账(dispatchLedgerRows / matchPersonalOrder)。 +// 覆盖资金安全关键分支:唯一匹配入账、多候选拒绝(ambiguous)、无订单(no_order)、 +// 冻结账户守卫(不自动入账)、重复去重(幂等)、通道/账户归属校验。 + +import ( + "context" + "fmt" + "path/filepath" + "testing" + "time" + + "paygateway/internal/account" + "paygateway/internal/admin/capture" + "paygateway/internal/channel" + "paygateway/internal/ledger" + "paygateway/internal/model" + "paygateway/internal/order" + "paygateway/internal/personal" + "paygateway/internal/pool" + "paygateway/internal/reconciler" + "paygateway/internal/store" + "paygateway/internal/upstream" +) + +// newMatchTestEnv 构建匹配链路测试环境:业务库 + personal.db + 账户池 + 订单服务 + App +func newMatchTestEnv(t *testing.T) (*App, *store.PersonalDB) { + t.Helper() + dir := t.TempDir() + + st, err := store.NewSQLite(filepath.Join(dir, "test.db")) + if err != nil { + t.Fatalf("创建业务库失败: %v", err) + } + t.Cleanup(func() { st.Close() }) + + ldb, err := store.NewLedgerDB(filepath.Join(dir, "ledger.db")) + if err != nil { + t.Fatalf("创建账本库失败: %v", err) + } + t.Cleanup(func() { ldb.Close() }) + + pdb, err := store.NewPersonalDB(filepath.Join(dir, "personal.db")) + if err != nil { + t.Fatalf("创建个人库失败: %v", err) + } + t.Cleanup(func() { pdb.Close() }) + + if err := account.InitCrypto("0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"); err != nil { + t.Fatalf("初始化加密失败: %v", err) + } + + accountPool := account.NewPool() + lg, err := ledger.New(ldb, "") + if err != nil { + t.Fatalf("创建账本失败: %v", err) + } + svc := order.New(st, lg, accountPool, upstream.NewPool()) + waitPayPool := pool.NewWaitPayPool(30) + svc.SetWaitPayHooks(&order.WaitPayHooks{ + Add: waitPayPool.Add, + Upsert: waitPayPool.Upsert, + Remove: waitPayPool.Remove, + }) + + app := &App{ + store: st, + personalDB: pdb, + accountPool: accountPool, + orders: svc, + confirmSched: reconciler.NewScheduler(st, svc, nil, nil), + waitPayPool: waitPayPool, + } + return app, pdb +} + +// newTestDevice 创建设备(含加密密钥)+ 绑定账户到池 +func newTestDevice(t *testing.T, pdb *store.PersonalDB, pool *account.AccountPool, accountID int64, deviceID string) *personal.Device { + t.Helper() + secretEnc, err := account.Encrypt([]byte("test-secret-1234567890")) + if err != nil { + t.Fatalf("加密设备密钥失败: %v", err) + } + rowID, err := pdb.CreateDevice(context.Background(), &personal.DeviceEncrypted{ + DeviceID: deviceID, Name: "测试机", AccountID: accountID, + Provider: "alipay", SecretEnc: secretEnc, Status: string(personal.DeviceOnline), + }) + if err != nil { + t.Fatalf("创建设备失败: %v", err) + } + return &personal.Device{ID: rowID, DeviceID: deviceID, Name: "测试机", AccountID: accountID, Provider: "alipay", Secret: "test-secret-1234567890", Status: personal.DeviceOnline} +} + +// newTestOrder 创建一笔 WAIT_PAY 订单 +func newTestOrder(t *testing.T, st store.Store, outNo string, amountMinor int64, accountID int64) { + t.Helper() + newTestOrderNote(t, st, outNo, amountMinor, accountID, "") +} + +// newTestOrderNote 创建一笔 WAIT_PAY 订单(可带备注码) +func newTestOrderNote(t *testing.T, st store.Store, outNo string, amountMinor int64, accountID int64, noteCode string) { + t.Helper() + o := &model.Order{ + OutTradeNo: outNo, GatewayNo: "G-" + outNo, Channel: "personal.alipay", + AccountID: accountID, AmountMinor: amountMinor, Status: model.StatusWaitPay, + NoteCode: noteCode, CreatedAt: time.Now(), + } + if err := st.CreateOrder(context.Background(), o); err != nil { + t.Fatalf("创建订单失败: %v", err) + } +} + +func TestDispatchLedgerRows_MatchAndSettle(t *testing.T) { + ctx := context.Background() + app, pdb := newMatchTestEnv(t) + + // 账户池:两个支付宝账户(主 + 附加,双开场景) + app.accountPool.Add(&account.Account{ + ID: 1, Channel: "personal.alipay", Name: "主账户", Status: account.StatusNormal, + QRContent: "https://qr.alipay.com/a1", + }) + app.accountPool.Add(&account.Account{ + ID: 2, Channel: "personal.alipay", Name: "附加账户", Status: account.StatusNormal, + QRContent: "https://qr.alipay.com/a2", + }) + dev := newTestDevice(t, pdb, app.accountPool, 1, "dev-match-1") + if err := pdb.AddDeviceAccount(ctx, dev.ID, 2); err != nil { + t.Fatalf("绑定附加账户失败: %v", err) + } + + // 一笔 WAIT_PAY 订单(金额 100 分,账户 1) + newTestOrder(t, app.store, "ORD-MATCH-1", 100, 1) + + // 账本新收款行:付款方 *怡 / 10:00:00 / +1.00(100分),30 分钟窗口内 + rows := []capture.AlipayLedgerRow{{ + Payer: "*怡", Date: "今日", Time: time.Now().Format("15:04:05"), + Amount: "+1.00", AmountMinor: 100, + }} + if err := app.dispatchLedgerRows(ctx, dev.DeviceID, "alipay", 0, rows); err != nil { + t.Fatalf("dispatchLedgerRows 失败: %v", err) + } + + // 订单应入账成功 + o, err := app.store.GetOrder(ctx, "ORD-MATCH-1") + if err != nil || o == nil { + t.Fatalf("查询订单失败: %v", err) + } + if o.Status != model.StatusSuccess { + t.Fatalf("订单应 TRADE_SUCCESS,实际 %s", o.Status) + } + if o.DeviceID != dev.DeviceID { + t.Errorf("订单 device_id 应回写,实际 %q", o.DeviceID) + } + + // 推送流水应记录 matched(rawHash = sha256("ledger:*怡|HH:MM:SS|+1.00")) + raw := "ledger:*怡|" + rows[0].Time + "|+1.00" + rawHash := fmt.Sprintf("%x", sha256Hash([]byte(raw))) + dup, err := pdb.PushExists(ctx, dev.DeviceID, rawHash) + if err != nil || !dup { + t.Fatalf("推送流水应存在(matched),err=%v dup=%v", err, dup) + } +} + +func TestDispatchLedgerRows_FrozenAccountGuard(t *testing.T) { + ctx := context.Background() + app, pdb := newMatchTestEnv(t) + + app.accountPool.Add(&account.Account{ + ID: 1, Channel: "personal.alipay", Name: "冻结账户", Status: account.StatusFrozen, + QRContent: "https://qr.alipay.com/f1", + }) + dev := newTestDevice(t, pdb, app.accountPool, 1, "dev-frozen-1") + newTestOrder(t, app.store, "ORD-FROZEN-1", 100, 1) + + rows := []capture.AlipayLedgerRow{{ + Payer: "*测", Date: "今日", Time: time.Now().Format("15:04:05"), + Amount: "+1.00", AmountMinor: 100, + }} + if err := app.dispatchLedgerRows(ctx, dev.DeviceID, "alipay", 0, rows); err != nil { + t.Fatalf("dispatchLedgerRows 失败: %v", err) + } + + // 冻结账户守卫:不自动入账,订单保持 WAIT_PAY + o, err := app.store.GetOrder(ctx, "ORD-FROZEN-1") + if err != nil || o == nil { + t.Fatalf("查询订单失败: %v", err) + } + if o.Status != model.StatusWaitPay { + t.Fatalf("冻结账户订单不应自动入账,实际 %s", o.Status) + } +} + +func TestMatchPersonalOrder_NoOrder(t *testing.T) { + ctx := context.Background() + app, pdb := newMatchTestEnv(t) + app.accountPool.Add(&account.Account{ + ID: 1, Channel: "personal.alipay", Name: "主账户", Status: account.StatusNormal, + }) + dev := newTestDevice(t, pdb, app.accountPool, 1, "dev-noorder-1") + + // 无匹配订单 → no_order + orderNo, matched, acctID, err := app.matchPersonalOrder(ctx, dev, "alipay", "", 999, time.Now(), 30*time.Minute, 0) + if err != nil { + t.Fatalf("matchPersonalOrder 失败: %v", err) + } + if matched != "no_order" || orderNo != "" || acctID != 0 { + t.Fatalf("应 no_order,实际 matched=%q order=%q acct=%d", matched, orderNo, acctID) + } +} + +func TestMatchPersonalOrder_AmbiguousRefused(t *testing.T) { + ctx := context.Background() + app, pdb := newMatchTestEnv(t) + app.accountPool.Add(&account.Account{ + ID: 1, Channel: "personal.alipay", Name: "主账户", Status: account.StatusNormal, + }) + dev := newTestDevice(t, pdb, app.accountPool, 1, "dev-ambig-1") + + // 同金额两笔 WAIT_PAY 订单 → ambiguous(拒绝自动挑选) + newTestOrder(t, app.store, "ORD-A1", 100, 1) + newTestOrder(t, app.store, "ORD-A2", 100, 1) + + orderNo, matched, acctID, err := app.matchPersonalOrder(ctx, dev, "alipay", "", 100, time.Now(), 30*time.Minute, 0) + if err != nil { + t.Fatalf("matchPersonalOrder 失败: %v", err) + } + if matched != "ambiguous" || orderNo != "" || acctID != 0 { + t.Fatalf("同金额多订单应 ambiguous(资金安全),实际 matched=%q order=%q acct=%d", matched, orderNo, acctID) + } +} + +func TestDispatchLedgerRows_Dedup(t *testing.T) { + ctx := context.Background() + app, pdb := newMatchTestEnv(t) + app.accountPool.Add(&account.Account{ + ID: 1, Channel: "personal.alipay", Name: "主账户", Status: account.StatusNormal, + }) + dev := newTestDevice(t, pdb, app.accountPool, 1, "dev-dedup-1") + newTestOrder(t, app.store, "ORD-DEDUP-1", 100, 1) + + // 同一行推两次:第二次应被幂等去重(不重复入账/不重复匹配) + row := capture.AlipayLedgerRow{ + Payer: "*重", Date: "今日", Time: time.Now().Format("15:04:05"), + Amount: "+1.00", AmountMinor: 100, + } + rows := []capture.AlipayLedgerRow{row} + if err := app.dispatchLedgerRows(ctx, dev.DeviceID, "alipay", 0, rows); err != nil { + t.Fatalf("首次推送失败: %v", err) + } + if err := app.dispatchLedgerRows(ctx, dev.DeviceID, "alipay", 0, rows); err != nil { + t.Fatalf("重复推送失败: %v", err) + } + + // 订单已成功(首次入账),重复推送幂等 + o, err := app.store.GetOrder(ctx, "ORD-DEDUP-1") + if err != nil || o == nil { + t.Fatalf("查询订单失败: %v", err) + } + if o.Status != model.StatusSuccess { + t.Fatalf("订单应 TRADE_SUCCESS,实际 %s", o.Status) + } +} + +// TestMatchPersonalOrder_NoteCode 备注码精确匹配(命中唯一订单直接入账,不做金额候选扩散) +func TestMatchPersonalOrder_NoteCode(t *testing.T) { + ctx := context.Background() + app, pdb := newMatchTestEnv(t) + app.accountPool.Add(&account.Account{ + ID: 1, Channel: "personal.alipay", Name: "主账户", Status: account.StatusNormal, + }) + dev := newTestDevice(t, pdb, app.accountPool, 1, "dev-note-1") + + // 备注码唯一订单(即使同金额有另一笔不同备注码订单,备注码应精确命中) + newTestOrderNote(t, app.store, "ORD-N1", 100, 1, "abcd") + newTestOrderNote(t, app.store, "ORD-N2", 100, 1, "efgh") + // 池同步(等价于下单钩子):两单都进池 + app.waitPayPool.Rebuild(ctx, mustOrders(t, app.store, "ORD-N1", "ORD-N2")) + + orderNo, matched, acctID, err := app.matchPersonalOrder(ctx, dev, "alipay", "abcd", 0, time.Now(), 30*time.Minute, 0) + if err != nil { + t.Fatalf("matchPersonalOrder 失败: %v", err) + } + if matched != "matched" || orderNo != "ORD-N1" || acctID != 1 { + t.Fatalf("备注码应精确命中 ORD-N1,实际 matched=%q order=%q acct=%d", matched, orderNo, acctID) + } +} + +// TestMatchPersonalOrder_NoteCodePoolPath 池路径:备注码经内存池命中(零 DB 索引查询), +// DB 二次校验仍生效(池中订单已入账 → 池条目仍命中但 DB 状态非 WAIT_PAY → 不采信,走金额兜底)。 +func TestMatchPersonalOrder_NoteCodePoolPath(t *testing.T) { + ctx := context.Background() + app, pdb := newMatchTestEnv(t) + app.accountPool.Add(&account.Account{ + ID: 1, Channel: "personal.alipay", Name: "主账户", Status: account.StatusNormal, + }) + dev := newTestDevice(t, pdb, app.accountPool, 1, "dev-note-pool-1") + + newTestOrderNote(t, app.store, "ORD-POOL-1", 100, 1, "wxyz") + app.waitPayPool.Rebuild(ctx, mustOrders(t, app.store, "ORD-POOL-1")) + + // 池路径命中 + orderNo, matched, acctID, err := app.matchPersonalOrder(ctx, dev, "alipay", "wxyz", 0, time.Now(), 30*time.Minute, 0) + if err != nil || matched != "matched" || orderNo != "ORD-POOL-1" || acctID != 1 { + t.Fatalf("池路径备注码应命中 ORD-POOL-1,实际 matched=%q order=%q acct=%d err=%v", matched, orderNo, acctID, err) + } + // 订单入账(SUCCESS)→ 池未移除时(模拟漂移),DB 二次校验应拒绝备注码采信 + if ok, serr := app.orders.SettlePayment(ctx, &channel.NotifyResult{ + OutTradeNo: "ORD-POOL-1", AmountMinor: 100, ChannelTxID: "TX-POOL-1", + }); serr != nil || !ok { + t.Fatalf("入账失败: ok=%v err=%v", ok, serr) + } + _, matched2, _, err2 := app.matchPersonalOrder(ctx, dev, "alipay", "wxyz", 0, time.Now(), 30*time.Minute, 0) + if err2 != nil { + t.Fatalf("匹配失败: %v", err2) + } + if matched2 == "matched" { + t.Fatal("已入账订单备注码不应再采信(DB 二次校验拦截),实际仍 matched") + } +} + +// TestWaitPayPool_SyncWithOrderLifecycle 池与订单生命周期同步:下单进池 → 入账出池 +func TestWaitPayPool_SyncWithOrderLifecycle(t *testing.T) { + ctx := context.Background() + app, _ := newMatchTestEnv(t) + app.accountPool.Add(&account.Account{ + ID: 1, Channel: "personal.alipay", Name: "主账户", Status: account.StatusNormal, + }) + // 模拟下单(hooks 已注入):DB 写订单 + 池 Add(与生产下单链路一致) + o := &model.Order{ + OutTradeNo: "ORD-LIFE-1", GatewayNo: "G-ORD-LIFE-1", Channel: "personal.alipay", AccountID: 1, + AmountMinor: 100, Status: model.StatusWaitPay, CreatedAt: time.Now(), + } + if err := app.store.CreateOrder(ctx, o); err != nil { + t.Fatalf("创建订单失败: %v", err) + } + app.waitPayPool.Add(o) + if got := app.waitPayPool.Count(1); got != 1 { + t.Fatalf("下单后池应有 1 笔,实际 %d", got) + } + // 入账 → hooks Remove → 池出 + if ok, serr := app.orders.SettlePayment(ctx, &channel.NotifyResult{ + OutTradeNo: "ORD-LIFE-1", AmountMinor: 100, ChannelTxID: "TX-LIFE-1", + }); serr != nil || !ok { + t.Fatalf("入账失败: ok=%v err=%v", ok, serr) + } + if got := app.waitPayPool.Count(1); got != 0 { + t.Fatalf("入账后池应清空该账户,实际 %d", got) + } +} + +// TestMatchPersonalOrder_ChannelMismatchRefused 通道归属校验:订单通道/账户不属于设备绑定集合 → 拒绝(防串单) +func TestMatchPersonalOrder_ChannelMismatchRefused(t *testing.T) { + ctx := context.Background() + app, pdb := newMatchTestEnv(t) + // 设备只绑定账户 1(支付宝);账户 2 是微信(不在设备集合) + app.accountPool.Add(&account.Account{ + ID: 1, Channel: "personal.alipay", Name: "主账户", Status: account.StatusNormal, + }) + app.accountPool.Add(&account.Account{ + ID: 2, Channel: "personal.wechat", Name: "微信账户", Status: account.StatusNormal, + }) + dev := newTestDevice(t, pdb, app.accountPool, 1, "dev-chmismatch-1") + + // 同金额订单:一笔是微信通道(应被排除),一笔支付宝通道(应被选中) + newTestOrderChannel(t, app.store, "ORD-WX", 100, 2, "personal.wechat") + newTestOrderChannel(t, app.store, "ORD-ALI", 100, 1, "personal.alipay") + + orderNo, matched, acctID, err := app.matchPersonalOrder(ctx, dev, "alipay", "", 100, time.Now(), 30*time.Minute, 0) + if err != nil { + t.Fatalf("matchPersonalOrder 失败: %v", err) + } + if matched != "matched" || orderNo != "ORD-ALI" || acctID != 1 { + t.Fatalf("应命中支付宝订单 ORD-ALI(微信订单被通道校验排除),实际 matched=%q order=%q acct=%d", matched, orderNo, acctID) + } +} + +// newTestOrderChannel 创建指定通道的 WAIT_PAY 订单(防串单测试用) +func newTestOrderChannel(t *testing.T, st store.Store, outNo string, amountMinor int64, accountID int64, channel string) { + t.Helper() + o := &model.Order{ + OutTradeNo: outNo, GatewayNo: "G-" + outNo, Channel: channel, + AccountID: accountID, AmountMinor: amountMinor, Status: model.StatusWaitPay, + CreatedAt: time.Now(), + } + if err := st.CreateOrder(context.Background(), o); err != nil { + t.Fatalf("创建订单失败: %v", err) + } +} + +// mustOrders 按 outTradeNo 列表从 store 取订单(池重建测试用;取不到直接失败) +func mustOrders(t *testing.T, st store.Store, outNos ...string) []*model.Order { + t.Helper() + out := make([]*model.Order, 0, len(outNos)) + for _, no := range outNos { + o, err := st.GetOrder(context.Background(), no) + if err != nil || o == nil { + t.Fatalf("查询订单 %s 失败: %v", no, err) + } + out = append(out, o) + } + return out +} + +// stubNoteProber 构造备注码探测桩:固定返回 note(探测成功)/ 固定错误(探测失败) +func stubNoteProber(note string, err error) alipayNoteProber { + return func(ctx context.Context, dev *personal.Device, bounds string) (string, error) { + return note, err + } +} + +// TestMatchLedgerRow_ProbeDisambiguates 多候选歧义 → 探测详情页备注码消歧 → 精确命中唯一订单。 +// 池消费模型核心场景:同金额两笔订单,备注码探测区分归属,不再转人工。 +func TestMatchLedgerRow_ProbeDisambiguates(t *testing.T) { + ctx := context.Background() + app, pdb := newMatchTestEnv(t) + app.accountPool.Add(&account.Account{ + ID: 1, Channel: "personal.alipay", Name: "主账户", Status: account.StatusNormal, + QRContent: "https://qr.alipay.com/a1", + }) + dev := newTestDevice(t, pdb, app.accountPool, 1, "dev-probe-1") + + // 同金额两笔 WAIT_PAY 订单,备注码不同(jzt4 / 7x57) + newTestOrderNote(t, app.store, "ORD-PROBE-A", 100, 1, "jzt4") + newTestOrderNote(t, app.store, "ORD-PROBE-B", 100, 1, "7x57") + app.waitPayPool.Rebuild(ctx, mustOrders(t, app.store, "ORD-PROBE-A", "ORD-PROBE-B")) + + row := capture.AlipayLedgerRow{ + Payer: "*怡", Date: "今日", Time: time.Now().Format("15:04:05"), + Amount: "+1.00", AmountMinor: 100, AmountBounds: "[451,374][1013,470]", + } + orderNo, matched, acctID, effNote, err := app.matchLedgerRow(ctx, dev, "alipay", 0, row, 30*time.Minute, stubNoteProber("jzt4", nil)) + if err != nil { + t.Fatalf("matchLedgerRow 失败: %v", err) + } + if matched != "matched" || orderNo != "ORD-PROBE-A" || acctID != 1 { + t.Fatalf("探测应消歧命中 ORD-PROBE-A,实际 matched=%q order=%q acct=%d", matched, orderNo, acctID) + } + if effNote != "jzt4" { + t.Errorf("审计备注码应为探测到的 jzt4,实际 %q", effNote) + } +} + +// TestMatchLedgerRow_ProbeFailsStaysAmbiguous 探测失败 → 保持 ambiguous 转人工(资金安全:禁止自动挑选)。 +func TestMatchLedgerRow_ProbeFailsStaysAmbiguous(t *testing.T) { + ctx := context.Background() + app, pdb := newMatchTestEnv(t) + app.accountPool.Add(&account.Account{ + ID: 1, Channel: "personal.alipay", Name: "主账户", Status: account.StatusNormal, + }) + dev := newTestDevice(t, pdb, app.accountPool, 1, "dev-probe-fail-1") + + newTestOrder(t, app.store, "ORD-PF-A", 100, 1) + newTestOrder(t, app.store, "ORD-PF-B", 100, 1) + app.waitPayPool.Rebuild(ctx, mustOrders(t, app.store, "ORD-PF-A", "ORD-PF-B")) + + row := capture.AlipayLedgerRow{ + Payer: "*怡", Date: "今日", Time: time.Now().Format("15:04:05"), + Amount: "+1.00", AmountMinor: 100, AmountBounds: "[451,374][1013,470]", + } + orderNo, matched, _, _, err := app.matchLedgerRow(ctx, dev, "alipay", 0, row, 30*time.Minute, stubNoteProber("", fmt.Errorf("adb 不可达"))) + if err != nil { + t.Fatalf("matchLedgerRow 不应因探测失败报错(降级歧义转人工): %v", err) + } + if matched != "ambiguous" || orderNo != "" { + t.Fatalf("探测失败应保持 ambiguous,实际 matched=%q order=%q", matched, orderNo) + } +} + +// TestMatchLedgerRow_ProbeEmptyNoteStaysAmbiguous 探测成功但无备注码(详情页无付款留言)→ 保持 ambiguous。 +func TestMatchLedgerRow_ProbeEmptyNoteStaysAmbiguous(t *testing.T) { + ctx := context.Background() + app, pdb := newMatchTestEnv(t) + app.accountPool.Add(&account.Account{ + ID: 1, Channel: "personal.alipay", Name: "主账户", Status: account.StatusNormal, + }) + dev := newTestDevice(t, pdb, app.accountPool, 1, "dev-probe-empty-1") + + newTestOrder(t, app.store, "ORD-PE-A", 100, 1) + newTestOrder(t, app.store, "ORD-PE-B", 100, 1) + app.waitPayPool.Rebuild(ctx, mustOrders(t, app.store, "ORD-PE-A", "ORD-PE-B")) + + row := capture.AlipayLedgerRow{ + Payer: "*怡", Date: "今日", Time: time.Now().Format("15:04:05"), + Amount: "+1.00", AmountMinor: 100, AmountBounds: "[451,374][1013,470]", + } + orderNo, matched, _, _, err := app.matchLedgerRow(ctx, dev, "alipay", 0, row, 30*time.Minute, stubNoteProber("", nil)) + if err != nil { + t.Fatalf("matchLedgerRow 失败: %v", err) + } + if matched != "ambiguous" || orderNo != "" { + t.Fatalf("空备注码应保持 ambiguous,实际 matched=%q order=%q", matched, orderNo) + } +} + +// TestMatchLedgerRow_UniqueAmountSkipsProbe 金额唯一 → 直接命中,探测桩不应被调用(短路免探测优化)。 +func TestMatchLedgerRow_UniqueAmountSkipsProbe(t *testing.T) { + ctx := context.Background() + app, pdb := newMatchTestEnv(t) + app.accountPool.Add(&account.Account{ + ID: 1, Channel: "personal.alipay", Name: "主账户", Status: account.StatusNormal, + }) + dev := newTestDevice(t, pdb, app.accountPool, 1, "dev-unique-1") + + newTestOrder(t, app.store, "ORD-UNIQ-1", 100, 1) + app.waitPayPool.Rebuild(ctx, mustOrders(t, app.store, "ORD-UNIQ-1")) + + probeCalled := false + prober := func(ctx context.Context, dev *personal.Device, bounds string) (string, error) { + probeCalled = true + return "", fmt.Errorf("不应被调用") + } + row := capture.AlipayLedgerRow{ + Payer: "*怡", Date: "今日", Time: time.Now().Format("15:04:05"), + Amount: "+1.00", AmountMinor: 100, AmountBounds: "[451,374][1013,470]", + } + orderNo, matched, _, _, err := app.matchLedgerRow(ctx, dev, "alipay", 0, row, 30*time.Minute, prober) + if err != nil { + t.Fatalf("matchLedgerRow 失败: %v", err) + } + if matched != "matched" || orderNo != "ORD-UNIQ-1" { + t.Fatalf("金额唯一应直接命中,实际 matched=%q order=%q", matched, orderNo) + } + if probeCalled { + t.Error("金额唯一时不应触发备注码探测(短路免探测)") + } +} + +// TestMatchLedgerRow_WechatRowNoteDirect 微信账本行自带"付款方备注" → 备注码精确命中,无需探测。 +func TestMatchLedgerRow_WechatRowNoteDirect(t *testing.T) { + ctx := context.Background() + app, pdb := newMatchTestEnv(t) + app.accountPool.Add(&account.Account{ + ID: 2, Channel: "personal.wechat", Name: "微信账户", Status: account.StatusNormal, + }) + dev := newTestDevice(t, pdb, app.accountPool, 2, "dev-wx-note-1") + + // 微信订单备注码直接入库(与支付宝一致的 note_code 机制) + if err := app.store.CreateOrder(ctx, &model.Order{ + OutTradeNo: "ORD-WXNOTE-1", GatewayNo: "G-ORD-WXNOTE-1", Channel: "personal.wechat", + AccountID: 2, AmountMinor: 100, Status: model.StatusWaitPay, + NoteCode: "wxcode", CreatedAt: time.Now(), + }); err != nil { + t.Fatalf("创建微信订单失败: %v", err) + } + app.waitPayPool.Rebuild(ctx, mustOrders(t, app.store, "ORD-WXNOTE-1")) + + row := capture.AlipayLedgerRow{ + Payer: "*微", Date: "今日", Time: time.Now().Format("15:04:05"), + Amount: "+1.00", AmountMinor: 100, Note: "wxcode", // 微信账本行自带备注 + } + orderNo, matched, _, effNote, err := app.matchLedgerRow(ctx, dev, "wechat", 0, row, 30*time.Minute, nil) + if err != nil { + t.Fatalf("matchLedgerRow 失败: %v", err) + } + if matched != "matched" || orderNo != "ORD-WXNOTE-1" { + t.Fatalf("微信行备注应精确命中,实际 matched=%q order=%q", matched, orderNo) + } + if effNote != "wxcode" { + t.Errorf("审计备注码应为 wxcode,实际 %q", effNote) + } +} + +// TestDispatchLedgerRows_ProbeThroughDispatch 全链路:dispatchLedgerRows 多候选歧义 → 探测消歧 → 入账。 +// 通过临时替换 app 的探测函数注入桩(生产路径 = adb 探测,测试 = 桩)。 +func TestDispatchLedgerRows_ProbeThroughDispatch(t *testing.T) { + ctx := context.Background() + app, pdb := newMatchTestEnv(t) + app.accountPool.Add(&account.Account{ + ID: 1, Channel: "personal.alipay", Name: "主账户", Status: account.StatusNormal, + QRContent: "https://qr.alipay.com/a1", + }) + dev := newTestDevice(t, pdb, app.accountPool, 1, "dev-dispatch-probe-1") + + newTestOrderNote(t, app.store, "ORD-DPROBE-A", 100, 1, "jzt4") + newTestOrderNote(t, app.store, "ORD-DPROBE-B", 100, 1, "7x57") + app.waitPayPool.Rebuild(ctx, mustOrders(t, app.store, "ORD-DPROBE-A", "ORD-DPROBE-B")) + + // 注入探测桩 + app.alipayProber = stubNoteProber("jzt4", nil) + t.Cleanup(func() { app.alipayProber = nil }) + + rows := []capture.AlipayLedgerRow{{ + Payer: "*怡", Date: "今日", Time: time.Now().Format("15:04:05"), + Amount: "+1.00", AmountMinor: 100, AmountBounds: "[451,374][1013,470]", + }} + if err := app.dispatchLedgerRows(ctx, dev.DeviceID, "alipay", 0, rows); err != nil { + t.Fatalf("dispatchLedgerRows 失败: %v", err) + } + + o, err := app.store.GetOrder(ctx, "ORD-DPROBE-A") + if err != nil || o == nil { + t.Fatalf("查询订单失败: %v", err) + } + if o.Status != model.StatusSuccess { + t.Fatalf("探测消歧订单应入账成功,实际 %s", o.Status) + } + // 另一笔保持 WAIT_PAY(未被误配) + oB, err := app.store.GetOrder(ctx, "ORD-DPROBE-B") + if err != nil || oB == nil { + t.Fatalf("查询订单失败: %v", err) + } + if oB.Status != model.StatusWaitPay { + t.Fatalf("未命中订单应保持 WAIT_PAY,实际 %s", oB.Status) + } +} + +// TestDispatchLedgerRows_MultiRowsNoMiss 多行一次推送全部处理(不漏行):每笔金额独立匹配池中订单。 +func TestDispatchLedgerRows_MultiRowsNoMiss(t *testing.T) { + ctx := context.Background() + app, pdb := newMatchTestEnv(t) + app.accountPool.Add(&account.Account{ + ID: 1, Channel: "personal.alipay", Name: "主账户", Status: account.StatusNormal, + }) + dev := newTestDevice(t, pdb, app.accountPool, 1, "dev-multi-1") + + newTestOrder(t, app.store, "ORD-M1", 100, 1) + newTestOrder(t, app.store, "ORD-M2", 200, 1) + newTestOrder(t, app.store, "ORD-M3", 300, 1) + app.waitPayPool.Rebuild(ctx, mustOrders(t, app.store, "ORD-M1", "ORD-M2", "ORD-M3")) + + now := time.Now().Format("15:04:05") + rows := []capture.AlipayLedgerRow{ + {Payer: "*甲", Date: "今日", Time: now, Amount: "+1.00", AmountMinor: 100}, + {Payer: "*乙", Date: "今日", Time: now, Amount: "+2.00", AmountMinor: 200}, + {Payer: "*丙", Date: "今日", Time: now, Amount: "+3.00", AmountMinor: 300}, + } + if err := app.dispatchLedgerRows(ctx, dev.DeviceID, "alipay", 0, rows); err != nil { + t.Fatalf("dispatchLedgerRows 失败: %v", err) + } + + for _, no := range []string{"ORD-M1", "ORD-M2", "ORD-M3"} { + o, err := app.store.GetOrder(ctx, no) + if err != nil || o == nil { + t.Fatalf("查询订单 %s 失败: %v", no, err) + } + if o.Status != model.StatusSuccess { + t.Errorf("多行推送不应漏单:%s 应入账成功,实际 %s", no, o.Status) + } + } +} + +// TestDispatchLedgerRows_ZeroCandidateNoMiss 零候选行不阻断后续行(不漏行:歧义/无单行独立转人工)。 +func TestDispatchLedgerRows_ZeroCandidateNoMiss(t *testing.T) { + ctx := context.Background() + app, pdb := newMatchTestEnv(t) + app.accountPool.Add(&account.Account{ + ID: 1, Channel: "personal.alipay", Name: "主账户", Status: account.StatusNormal, + }) + dev := newTestDevice(t, pdb, app.accountPool, 1, "dev-zero-1") + + // 只有 100 分订单;推 100(命中)+ 999(零候选)+ 100 重复(幂等去重) + newTestOrder(t, app.store, "ORD-Z1", 100, 1) + app.waitPayPool.Rebuild(ctx, mustOrders(t, app.store, "ORD-Z1")) + + now := time.Now().Format("15:04:05") + rows := []capture.AlipayLedgerRow{ + {Payer: "*甲", Date: "今日", Time: now, Amount: "+1.00", AmountMinor: 100}, + {Payer: "*零", Date: "今日", Time: now, Amount: "+9.99", AmountMinor: 999}, + } + if err := app.dispatchLedgerRows(ctx, dev.DeviceID, "alipay", 0, rows); err != nil { + t.Fatalf("dispatchLedgerRows 失败: %v", err) + } + + // 命中行入账 + o, err := app.store.GetOrder(ctx, "ORD-Z1") + if err != nil || o == nil { + t.Fatalf("查询订单失败: %v", err) + } + if o.Status != model.StatusSuccess { + t.Fatalf("零候选行不应影响命中行入账,实际 %s", o.Status) + } + // 零候选行应转人工(unmatched 记录) + unmatched, err := pdb.ListUnmatched(ctx, "pending", 10) + if err != nil { + t.Fatalf("查询未匹配记录失败: %v", err) + } + found := false + for _, u := range unmatched { + if u.AmountMinor == 999 { + found = true + break + } + } + if !found { + t.Error("零候选行应记录到 unmatched_payments(转人工),未找到 999 分记录") + } +} + +// TestMatchPersonalOrder_NotifyAnchor 通知栏窗口锚点语义(2026-08-30 用户需求确认): +// 通知不清理会一直在通知栏,匹配窗口必须以通知 when 为锚,不能用 time.Now()。 +// 场景:新订单刚创建,但旧通知(when 在 2 小时前)不应匹配到它。 +func TestMatchPersonalOrder_NotifyAnchor(t *testing.T) { + ctx := context.Background() + app, pdb := newMatchTestEnv(t) + app.accountPool.Add(&account.Account{ + ID: 1, Channel: "personal.alipay", Name: "主账户", Status: account.StatusNormal, + QRContent: "https://qr.alipay.com/new-notify-anchor", + }) + dev := newTestDevice(t, pdb, app.accountPool, 1, "dev-notify-anchor") + // 设备绑定空间0的收款码(让 space 账户定位生效,走同一链路) + _, _ = pdb.UpsertQrcode(ctx, &personal.DeviceQrcode{ + DeviceID: dev.DeviceID, Platform: "alipay", UserID: 0, + QRContent: "https://qr.alipay.com/new-notify-anchor", Status: "active", + }) + // 创建一笔 WAIT_PAY 订单(刚创建) + newTestOrder(t, app.store, "ORD-NOTIFY-NEW", 500, 1) + + // 旧通知:when 在 2 小时前(通知栏一直没清) + oldNotify := time.Now().Add(-2 * time.Hour) + orderNo, matched, _, err := app.matchPersonalOrder(ctx, dev, "alipay", "", 500, oldNotify, 30*time.Minute, 0) + if err != nil { + t.Fatalf("matchPersonalOrder 失败: %v", err) + } + if matched != "no_order" || orderNo != "" { + t.Fatalf("旧通知不应匹配新订单:matched=%q order=%q(期望 no_order)", matched, orderNo) + } + + // 对照:通知 when = 刚刚(新通知)→ 锚点窗口覆盖订单 → 应命中 + newNotify := time.Now() + orderNo2, matched2, _, err := app.matchPersonalOrder(ctx, dev, "alipay", "", 500, newNotify, 30*time.Minute, 0) + if err != nil { + t.Fatalf("matchPersonalOrder 失败: %v", err) + } + if matched2 != "matched" || orderNo2 != "ORD-NOTIFY-NEW" { + t.Fatalf("新通知应匹配新订单:matched=%q order=%q(期望 matched ORD-NOTIFY-NEW)", matched2, orderNo2) + } +} + +// 双链同捕竞态:通知栏/账本 5s 内先后捕获同一笔收款,胜者已把订单结算, +// 败者匹配时零 WAIT_PAY 候选 → 应判定 settled_dup(重复捕获),不得误报 no_order 入人工池。 +func TestMatchPersonalOrder_SettledDup(t *testing.T) { + ctx := context.Background() + app, pdb := newMatchTestEnv(t) + app.accountPool.Add(&account.Account{ + ID: 1, Channel: "personal.alipay", Name: "主账户", Status: account.StatusNormal, + }) + dev := newTestDevice(t, pdb, app.accountPool, 1, "dev-dup-1") + + newTestOrder(t, app.store, "ORD-DUP-1", 100, 1) + if _, err := app.store.UpdateOrderStatus(ctx, "ORD-DUP-1", model.StatusWaitPay, model.StatusSuccess, + func(upd *model.Order) { upd.ChannelTxID = "LEDGER-x" }); err != nil { + t.Fatalf("模拟另一链路入账失败: %v", err) + } + + orderNo, matched, acctID, err := app.matchPersonalOrder(ctx, dev, "alipay", "", 100, time.Now(), 30*time.Minute, 0) + if err != nil { + t.Fatalf("matchPersonalOrder 失败: %v", err) + } + if matched != "settled_dup" || orderNo != "ORD-DUP-1" || acctID != 1 { + t.Fatalf("应 settled_dup,实际 matched=%q order=%q acct=%d", matched, orderNo, acctID) + } +} + +// 窗口内同金额已结算订单多于 1 笔 → 无法归属,保持 no_order(资金安全,不猜)。 +func TestMatchPersonalOrder_SettledDupAmbiguous(t *testing.T) { + ctx := context.Background() + app, pdb := newMatchTestEnv(t) + app.accountPool.Add(&account.Account{ + ID: 1, Channel: "personal.alipay", Name: "主账户", Status: account.StatusNormal, + }) + dev := newTestDevice(t, pdb, app.accountPool, 1, "dev-dup-2") + + newTestOrder(t, app.store, "ORD-DUP-A", 100, 1) + newTestOrder(t, app.store, "ORD-DUP-B", 100, 1) + for _, no := range []struct{ id, tx string }{{"ORD-DUP-A", "LEDGER-a"}, {"ORD-DUP-B", "LEDGER-b"}} { + if _, err := app.store.UpdateOrderStatus(ctx, no.id, model.StatusWaitPay, model.StatusSuccess, + func(upd *model.Order) { upd.ChannelTxID = no.tx }); err != nil { + t.Fatalf("模拟入账失败: %v", err) + } + } + + orderNo, matched, _, err := app.matchPersonalOrder(ctx, dev, "alipay", "", 100, time.Now(), 30*time.Minute, 0) + if err != nil { + t.Fatalf("matchPersonalOrder 失败: %v", err) + } + if matched != "no_order" || orderNo != "" { + t.Fatalf("多笔已结算不可归属,应 no_order,实际 matched=%q order=%q", matched, orderNo) + } +} + +// 通道校验同样约束 settled_dup:已结算订单的通道不在设备绑定集合 → no_order。 +func TestMatchPersonalOrder_SettledDupWrongChannel(t *testing.T) { + ctx := context.Background() + app, pdb := newMatchTestEnv(t) + app.accountPool.Add(&account.Account{ + ID: 1, Channel: "personal.wechat", Name: "微信账户", Status: account.StatusNormal, + }) + dev := newTestDevice(t, pdb, app.accountPool, 1, "dev-dup-3") + + newTestOrder(t, app.store, "ORD-DUP-W", 100, 1) + if _, err := app.store.UpdateOrderStatus(ctx, "ORD-DUP-W", model.StatusWaitPay, model.StatusSuccess, + func(upd *model.Order) { upd.ChannelTxID = "LEDGER-x" }); err != nil { + t.Fatalf("模拟入账失败: %v", err) + } + + orderNo, matched, _, err := app.matchPersonalOrder(ctx, dev, "alipay", "", 100, time.Now(), 30*time.Minute, 0) + if err != nil { + t.Fatalf("matchPersonalOrder 失败: %v", err) + } + if matched != "no_order" || orderNo != "" { + t.Fatalf("通道不符不应归属,实际 matched=%q order=%q", matched, orderNo) + } +} diff --git a/cmd/gateway/main.go b/cmd/gateway/main.go new file mode 100755 index 0000000..d59033e --- /dev/null +++ b/cmd/gateway/main.go @@ -0,0 +1,918 @@ +// paygateway 支付网关入口:main() 装配 + App 容器。 +// 配置/通道/路由/分配/账本匹配已拆分到同包文件: +// +// config.go 配置加载与环境变量 +// channels.go 通道注册/设备管理/中间件 +// handlers_epay.go 易支付下单/回调/查单 +// handlers_qr.go QR 系接口/指纹 +// alloc.go 均衡分配算法 +// devices.go 设备/渠道映射 +// ledger.go 账本监听/匹配引擎 +// notify.go QR real/通知处理 +package main + +import ( + "context" + "fmt" + "net/http" + "os" + "runtime/debug" + "strings" + "sync" + "time" + + "paygateway/internal/account" + "paygateway/internal/admin" + "paygateway/internal/alert" + "paygateway/internal/channel" + alipaych "paygateway/internal/channel/alipay" + personalch "paygateway/internal/channel/personal" + wechatch "paygateway/internal/channel/wechat" + "paygateway/internal/ledger" + "paygateway/internal/ledgermon" + "paygateway/internal/logger" + "paygateway/internal/model" + "paygateway/internal/notify" + "paygateway/internal/notifywatcher" + "paygateway/internal/order" + "paygateway/internal/personal" + "paygateway/internal/pool" + "paygateway/internal/ratelimit" + "paygateway/internal/reconciler" + "paygateway/internal/scrcpy" + "paygateway/internal/store" + "paygateway/internal/tunnel" + "paygateway/internal/upstream" + + "github.com/gin-gonic/gin" +) + +// ========== 配置 ========== + +type App struct { + cfg *Config + store store.Store + ledger *ledger.Ledger + orders *order.Service + notifyW *notify.Worker + upstreamPool *upstream.UpstreamPool // 上游池(多商户验签/签名路由) + confirmSched *reconciler.Scheduler // 订单确认调度器(主动查单兜底) + + // 个人免签通道 + personalDB store.PersonalStore + personalAlipay *personalch.Channel + personalWechat *personalch.Channel + accountPool *account.AccountPool // 账户池(离线联动冻结/解冻) + accountDB *store.AccountDB // 账户存储(收款码上报时自动创建个人账户) + ipLimiter *personal.RateLimiter // 下单限流:每 IP 每分钟下单上限(防脚本刷单) + alertC *alert.Client // 告警系统(nil=未配置) + getTun func() *tunnel.Tunnel // 当前隧道(管理后台展示用;nil=无隧道) + ledgermon *ledgermon.Manager // 账本页服务端 adb 监听编排(docs/PLAN-ADB-LEDGER-IMPL.md) + waitPayPool *pool.WaitPayPool // WAIT_PAY 内存订单池(公平调度 + 备注码/金额匹配数据源) + alipayProber alipayNoteProber // 支付宝备注码探测(nil=默认 adb 探测;测试注入桩用) +} + +// bgRecover 后台 goroutine 的统一 panic 防护:记录 panic + 栈,避免整个进程崩溃。 +// 用法:每个 go func() 第一行 defer bgRecover(component)。 +func bgRecover(component string) { + if r := recover(); r != nil { + logger.Error("main", "后台任务 panic(已恢复,进程继续运行)", + "component", component, "panic", r, "stack", string(debug.Stack())) + } +} + +func main() { + // 加载 .env 文件 + loadEnv(".env") + + cfg := loadConfig() + + // 1. 日志 + logger.Init(logger.Options{ + Level: cfg.LogLevel, + LogFile: cfg.LogFile, + MaxSizeMB: 50, MaxBackups: 10, MaxAgeDays: 30, + }) + defer logger.Close() + + logger.Info("main", "=== 支付网关启动 ===") + logger.Info("main", "监听地址", "addr", cfg.ListenAddr) + logger.Info("main", "数据库", "file", cfg.DBFile) + + // 2. 存储(SQLite) + st, err := store.NewSQLite(cfg.DBFile) + if err != nil { + logger.Error("main", "数据库初始化失败", "err", err) + os.Exit(1) + } + defer st.Close() + + // 2.5 从 DB 加载运行时设置,覆盖 .env 配置 + applySettings(st, cfg) + + // 2.6 独立账本数据库(ledger.db,0600 权限,只增不改) + ledgerDB, err := store.NewLedgerDB("data/ledger.db") + if err != nil { + logger.Error("main", "账本数据库初始化失败", "err", err) + os.Exit(1) + } + defer ledgerDB.Close() + + // 3. 审计账本(日志落库交给 store 实现 logger.DBWriter) + // 私钥持久化:.env > settings 表 > 首次生成并保存到 settings(重启复用,不再每次换新导致验签失败) + ledgerKey, err := resolveLedgerKey(st, cfg) + if err != nil { + logger.Error("main", "审计密钥初始化失败", "err", err) + os.Exit(1) + } + cfg.LedgerPrivKey = ledgerKey + lg, err := ledger.New(ledgerDB, ledgerKey) + if err != nil { + logger.Error("main", "账本初始化失败", "err", err) + os.Exit(1) + } + // 启用日志写库(管理界面查询):日志写入独立日志库 logs.db,与订单主库分离,支持长期保存 + logDB, err := store.NewLogDB("data/logs.db") + if err != nil { + logger.Error("main", "日志数据库初始化失败", "err", err) + os.Exit(1) + } + defer logDB.Close() + logger.SetDBWriter(logDB) + + // 3.5 账户池(独立 accounts.db + 内存缓存) + accountDB, err := store.NewAccountDB("data/accounts.db") + if err != nil { + logger.Error("main", "账户数据库初始化失败", "err", err) + os.Exit(1) + } + defer accountDB.Close() + + // 账户加密主密钥:必须显式通过 .env 提供。 + // 不自动生成并落盘到 settings 表——主密钥与密文同库会让"拿到 accounts.db 就能解出全部账户密钥", + // 与审计密钥/套码 token 密钥等签名用途密钥的安全等级不同,不应等同处理。 + // 缺失直接 fail-fast,避免 accoutns.db / upstreams.db 已有密文时被新随机密钥静默解密失败、服务起不来反而更难排查。 + accountMasterKey := cfg.AccountMasterKey + if accountMasterKey == "" { + logger.Error("main", "ACCOUNT_MASTER_KEY 未配置:拒绝启动。请在 .env 中设置 ACCOUNT_MASTER_KEY(首次部署可用 `openssl rand -hex 32` 生成),更换会导致已有账户/上游密文无法解密") + os.Exit(1) + } + + // 创建账户池 + 处理器(唯一访问 accounts.db 的组件) + accountPool := account.NewPool() + accountHandler, err := admin.NewAccountHandler(accountDB, accountPool, accountMasterKey) + if err != nil { + logger.Error("main", "账户处理器初始化失败", "err", err) + os.Exit(1) + } + + // 从 accounts.db 加载已有账户,解密后初始化内存池 + if err := accountHandler.InitFromDB(context.Background()); err != nil { + logger.Error("main", "加载账户池失败", "err", err) + os.Exit(1) + } + + // 3.6 上游池(独立 upstreams.db + 内存缓存,多商户) + upstreamDB, err := store.NewUpstreamDB("data/upstreams.db") + if err != nil { + logger.Error("main", "上游数据库初始化失败", "err", err) + os.Exit(1) + } + defer upstreamDB.Close() + + upstreamPool := upstream.NewPool() + upstreamHandler, err := admin.NewUpstreamHandler(upstreamDB, upstreamPool, accountMasterKey) + if err != nil { + logger.Error("main", "上游处理器初始化失败", "err", err) + os.Exit(1) + } + // 从 upstreams.db 加载已有上游,解密后初始化内存池 + // 严格模式:上游只能通过管理后台配置,不从 .env / settings 加载 + if err := upstreamHandler.InitFromDB(context.Background()); err != nil { + logger.Error("main", "加载上游池失败", "err", err) + os.Exit(1) + } + if upstreamPool.Count() == 0 { + logger.Warn("main", "上游池为空:请到管理后台「上游对接」页配置至少一个上游(pid + 密钥),否则所有下单将被拒绝") + } + + // 3.7 个人免签通道存储(独立 personal.db:设备/推送流水/无订单收款) + personalDB, err := store.NewPersonalDB("data/personal.db") + if err != nil { + logger.Error("main", "个人通道数据库初始化失败", "err", err) + os.Exit(1) + } + defer personalDB.Close() + // 自动迁移收款码表(加 user_id 列支持双开空间) + if err := personalDB.EnsureQrcodeSchema(context.Background()); err != nil { + logger.Error("main", "收款码表迁移失败", "err", err) + os.Exit(1) + } + + // 套码 token 签名密钥:.env > settings 表 > 首次生成并持久化(重启复用) + qrTokenSecret := cfg.QrTokenSecret + if qrTokenSecret == "" { + if v, err := st.GetSetting(context.Background(), "qr_token_secret"); err == nil && v != "" { + qrTokenSecret = v + } + } + if qrTokenSecret == "" { + key, err := account.GenerateMasterKey() // 32 字节 hex,复用生成器 + if err != nil { + logger.Error("main", "生成套码 token 密钥失败", "err", err) + os.Exit(1) + } + qrTokenSecret = key + if err := st.SetSetting(context.Background(), "qr_token_secret", qrTokenSecret); err != nil { + logger.Warn("main", "保存套码 token 密钥失败(重启将重新生成,旧二维码失效)", "err", err) + } + logger.Info("main", "已生成新的套码 token 密钥并持久化到 settings") + } + if err := personal.InitQrToken(qrTokenSecret); err != nil { + logger.Error("main", "套码 token 密钥初始化失败", "err", err) + os.Exit(1) + } + + // 4. 注册通道(始终注册,账户数据从池中取;管理后台「账户池」配置密钥) + personalAlipay, personalWechat := regChannels(cfg, accountPool, st, personalDB) + applyChannelRuntimeSettings(st) // 通道注册后加载启停 + 支付模式设置 + + // 4.1 启动配置自检(RESILIENCE P1-2) + runStartupChecks(cfg, accountPool, st, personalDB) + + // 5. 订单服务 + 回调 worker + orderSvc := order.New(st, lg, accountPool, upstreamPool) + orderSvc.SetCloseWindow(cfg.OrderCloseMinutes) + // 备注码释放回调(docs/MIANQIAN.md §6.4):订单终态 SUCCESS/CLOSED 时释放备注码, + // 避免仅靠 5 分钟 TTL 惰性回收;通道名 → 对应 personal 通道实例 + orderSvc.SetNoteCodeReleaser(func(channelName, noteCode string) { + var ch *personalch.Channel + switch channelName { + case "personal.alipay": + ch = personalAlipay + case "personal.wechat": + ch = personalWechat + } + if ch == nil { + return + } + ch.ReleaseNoteCode(noteCode) + }) + // 5.0.1 WAIT_PAY 内存订单池(公平调度数据源,docs/DESIGN-FAIR-SCHEDULER.md): + // 下单/入账/关单/刷新/换通道/复活 全入口同步;DB 是真相,池是索引。 + // 设备维度子池 + 动态关单窗口(docs/PLAN-DYNAMIC-CLOSE-ALLOC.md): + // - deviceOf:账户 → 设备映射(personal.db 设备绑定),多设备隔离、互不干扰 + // - winCalc:动态关单窗口 = clamp(设备一轮耗时×1.5+60s, MIN, MAX),负载驱动 + waitPayPool := pool.NewWaitPayPool(cfg.OrderCloseMinutes) + // 动态窗口配置校验:MIN/MAX 非法(0 或 MIN>MAX)→ 退化为静态窗口(OrderCloseMinutes) + dynMin := time.Duration(cfg.OrderCloseMin) * time.Minute + dynMax := time.Duration(cfg.OrderCloseMax) * time.Minute + if cfg.OrderCloseMin <= 0 || cfg.OrderCloseMax <= 0 || dynMin >= dynMax { + dynMin, dynMax = 0, 0 + logger.Warn("main", "动态关单窗口配置非法,退化为静态窗口", + "min_minutes", cfg.OrderCloseMin, "max_minutes", cfg.OrderCloseMax) + } + if personalDB != nil { + waitPayPool.SetDeviceOf(func(accountID int64) (string, bool) { + return accountDeviceOf(context.Background(), personalDB, accountID) + }) + } + if dynMin > 0 && dynMax > 0 { + // 动态关单窗口计算器:设备负载驱动,物理锚点见设计文档 §4.1。 + // 设备一轮耗时 = 活跃账户数 × 配额(3) × 单次 poll 成本(默认 4s,可配置)。 + pollCostMS := cfg.DevicePollCostMS + if pollCostMS <= 0 { + pollCostMS = 4000 + } + const ( + baseQuota = 3 // 调度器 BaseQuota(与 ledgermon.DefaultSchedulerConfig 一致) + queueFactor = 1.5 // 最坏排队位置系数 + payReserve = 60 * time.Second + activeFactor = float64(baseQuota) / 1000 + ) + waitPayPool.SetWindowCalc(func(activeAccounts, total int) time.Duration { + _ = total // 负载维度(活跃账户数已隐含;保留签名供未来按订单数加权) + est := time.Duration(float64(activeAccounts) * activeFactor * float64(pollCostMS) * queueFactor) + w := est + payReserve + if w < dynMin { + w = dynMin + } + if w > dynMax { + w = dynMax + } + return w + }) + } + // 通知栏快速通道监听器(延迟到 6 节构造;此处先声明变量,供 hooks 闭包唤醒)。 + var nw *notifywatcher.Manager + orderSvc.SetWaitPayHooks(&order.WaitPayHooks{ + Add: func(o *model.Order) { + waitPayPool.Add(o) + // 事件驱动:新订单进池 → 唤醒通知栏监听(池空休眠态立即恢复轮询) + if nw != nil { + nw.Wake() + } + }, + Upsert: waitPayPool.Upsert, + Remove: waitPayPool.Remove, + // 动态关单窗口:扫描器用池中记录的 CloseAt 判断是否真正到期(未到则跳过) + CloseAtOf: waitPayPool.CloseAtOf, + }) + notifyW := notify.NewWorker(st, cfg.NotifyAllowPrivateIPs) + logger.Info("main", "回调 worker SSRF 防护状态", "allow_private_notify", cfg.NotifyAllowPrivateIPs) + + // 5.0 告警系统(RESILIENCE P1-3):进池 / 账本失败 / dead 通知 + alertC := alert.New(alert.Options{BarkURL: cfg.AlertBarkURL, WebhookURL: cfg.AlertWebhookURL}) + if alertC != nil { + orderSvc.SetAlert(alertC) + notifyW.SetAlert(alertC) + logger.Info("main", "告警系统已启用", + "bark", cfg.AlertBarkURL != "", "webhook", cfg.AlertWebhookURL != "") + } else { + logger.Warn("main", "告警系统未配置(ALERT_BARK_URL / ALERT_WEBHOOK_URL 为空)") + } + + // 5.1 确认调度器(主动查单兜底):注入通道查单函数(遍历通道注册表) + queryFunc := func(ctx context.Context, outTradeNo string) (*channel.OrderStatus, error) { + var lastErr error + for _, name := range channel.Names() { + ch, ok := channel.Get(name) + if !ok { + continue + } + st, err := ch.QueryOrder(ctx, outTradeNo) + if err != nil { + lastErr = err + continue + } + return st, nil + } + if lastErr == nil { + lastErr = fmt.Errorf("无可查询通道") + } + return nil, lastErr + } + confirmSched := reconciler.NewScheduler(st, orderSvc, queryFunc, nil) + + // 个人通道监听端检查(ConfirmByManual 通道关单前调用): + // 设备 adb 不可达 → 订单进异常池人工核实(用户可能已付款但未监听到),绝不直接关单。 + // 在线判定 = 实时 adb 可达性(2026-08-29 重构:不再依赖监听 App 心跳/DB status)。 + // 该函数同时注入 confirmSched(事件驱动主路径)与 orderSvc(DB 扫描兜底路径), + // 保证两条关单路径的安全检查完全一致(PLAN-DYNAMIC-CLOSE-ALLOC §4.3)。 + var personalCheckFn func(ctx context.Context, o *model.Order) (bool, string) + if personalDB != nil { + personalCheckFn = func(ctx context.Context, o *model.Order) (bool, string) { + // 订单已绑定设备:adb 实时可达判定 + if o.DeviceID != "" { + enc, err := personalDB.GetDevice(ctx, o.DeviceID) + if err != nil { + return true, "设备查询失败: " + err.Error() + } + if enc == nil { + return true, "设备不存在: " + o.DeviceID + } + serial := deviceADBSerial(enc) + if serial == "" { + return true, "设备未配置 adb 通道" + } + if !adbMgr().Online(ctx, serial) { + return true, "设备 adb 不可达" + } + return false, "" + } + // 订单未绑定设备:按 account_id 反查承载该账户的设备(一台设备承载 + // 多账户多通道 device_accounts 多对多,不能按 personal_devices.provider + // 单值过滤——跨通道订单会误判设备离线致关单永久卡异常池,2026-09-13 实测) + if o.AccountID > 0 { + devIDs, err := personalDB.DeviceIDsOfAccount(ctx, o.AccountID) + if err != nil { + return true, "账户设备反查失败: " + err.Error() + } + for _, devID := range devIDs { + enc, err := personalDB.GetDeviceByID(ctx, devID) + if err != nil || enc == nil { + continue + } + serial := deviceADBSerial(enc) + if serial != "" && adbMgr().Online(ctx, serial) { + return false, "" + } + } + } + // 账户未反查到在线设备 → 按 provider 匹配任意在线设备兜底 + devs, err := personalDB.ListDevices(ctx) + if err != nil { + return true, "设备列表查询失败" + } + provider := channel.ProviderFromName(o.Channel) + for _, d := range devs { + if d.Provider != provider { + continue + } + serial := deviceADBSerial(d) + if serial != "" && adbMgr().Online(ctx, serial) { + return false, "" + } + } + return true, "无 adb 可达设备(provider=" + provider + ")" + } + confirmSched.SetPersonalCheck(personalCheckFn) + // 漏单修复:CloseExpiredOrders 对个人通道也走同一安全检查(此前直接关单绕过离线检查) + orderSvc.SetPersonalCheck(personalCheckFn) + } + + app := &App{cfg: cfg, store: st, ledger: lg, orders: orderSvc, notifyW: notifyW, upstreamPool: upstreamPool, confirmSched: confirmSched, + personalDB: personalDB, personalAlipay: personalAlipay, personalWechat: personalWechat, accountPool: accountPool, accountDB: accountDB, + ipLimiter: personal.NewRateLimiter(cfg.MaxOrdersPerIPPerMin, time.Minute, 100000), + waitPayPool: waitPayPool} + + // tun 全局隧道状态(同时用于 admin / watcher 重启逻辑取隧道 URL)。 + // 在 watcher 启动前声明,确保 watcher 的 recoverer 闭包能引用到。 + var tun *tunnel.Tunnel + var tunMu sync.RWMutex + setTun := func(t *tunnel.Tunnel) { tunMu.Lock(); tun = t; tunMu.Unlock() } + getTun := func() *tunnel.Tunnel { tunMu.RLock(); defer tunMu.RUnlock(); return tun } + app.getTun = getTun + + // 6. 启动回调 worker + 确认调度器 + 设备离线检测(后台) + ctx, cancel := context.WithCancel(context.Background()) + defer cancel() + go notifyW.Start(ctx) + go confirmSched.Start(ctx) + // 账本页服务端 adb 监听(docs/PLAN-ADB-LEDGER-IMPL.md Phase 1 支付宝闭环): + // 服务端直连手机读收款记录页 → 增量检测 → 复用 /personal/push 匹配入账链路。 + // 依赖 personal.db(设备+账户绑定)+ adb 管理器(WiFi adb 5555 直连/USB)。 + if personalDB != nil && adbMgr() != nil { + // 订单驱动:账户有待支付订单才轮询(导航+下拉刷新+读取),订单终结自动暂停。 + // 避免持续占用手机前台与频繁刷新支付宝(2026-08-27 真机反馈修正)。 + // 资金安全:DB 查询错误视为"仍有待支付订单"(失败开放),保持轮询不误停。 + hasPending := func(ctx context.Context, accountID int64) bool { + if accountID <= 0 { + return false + } + orders, err := st.ListWaitPayByAccount(ctx, accountID) + if err != nil { + logger.Warn("main", "待支付订单查询失败(按仍有订单处理,保持轮询)", + "err", err, "account_id", accountID) + return true + } + return len(orders) > 0 + } + app.ledgermon = ledgermon.NewManager( + adbMgr(), + app.resolveLedgerDevices, + app.newLedgerReader, + app.dispatchLedgerRows, + hasPending, + cfg.LedgerActiveInterval, + personalDB, + waitPayPool, // 公平调度模式:DeviceScheduler 按内存池快照统一决策轮询 + ) + app.ledgermon.Start(ctx) + logger.Info("main", "账本页 adb 监听已启动(DeviceActor 模型,公平时间占比调度)", + "poll", cfg.LedgerActiveInterval) + // WAIT_PAY 池启动重建(DB 全量待支付订单)+ 周期对账(30s 防漂移) + if orders, err := st.ListWaitPayAll(ctx); err != nil { + logger.Error("main", "WAIT_PAY 池重建失败(调度将按空池运行,Reconcile 兜底)", "err", err) + } else { + waitPayPool.Rebuild(ctx, orders) + logger.Info("main", "WAIT_PAY 池重建完成", "orders", len(orders)) + } + go waitPayPool.ReconcileLoop(ctx, 30*time.Second, func() ([]*model.Order, error) { + return st.ListWaitPayAll(context.Background()) + }) + // 通知栏快速通道(NotifyWatcher):独立 goroutine + 独立 adbMgr 实例(Manager.mu 独立, + // 与 ledgermon DeviceActor 串行队列天然并行,实测 dumpsys notification ∥ dump 无冲突)。 + // 事件驱动:池有 WAIT_PAY 订单 → 高频轮询;池空 → 休眠(新订单进池经 hooks Wake 唤醒)。 + // 窗口锚点用通知 when(见 handleNotify),快速通道比账本页快 ~15s。 + nw = notifywatcher.New( + adbMgr(), // 独立实例:与 ledgermon 持有的实例 mutex 隔离 + func(ctx context.Context) ([]notifywatcher.Device, error) { + devs, err := personalDB.ListDevices(ctx) + if err != nil { + return nil, err + } + var out []notifywatcher.Device + for _, d := range devs { + serial := deviceADBSerial(d) + if serial == "" { + continue + } + if !adbMgr().Online(ctx, serial) { + continue + } + out = append(out, notifywatcher.Device{DeviceID: d.DeviceID, Serial: serial}) + } + return out, nil + }, + func(ctx context.Context) bool { + return waitPayPool.Snapshot().Total > 0 + }, + app.handleNotify, + personalDB, + notifywatcher.Config{ + PollEvery: cfg.NotifyPollInterval, + IdleEvery: cfg.NotifyIdleInterval, + ShellTimeout: 3 * time.Second, + }, + ) + go nw.Start(ctx) + logger.Info("main", "通知栏快速通道监听已启动", + "poll", cfg.NotifyPollInterval, "idle", cfg.NotifyIdleInterval) + } + // 监听端离线检测:60s 无心跳 → 设备 OFFLINE → 冻结账户 → WAIT_PAY 订单进异常池 + // 注入 recoverer:handleOffline 起后台 goroutine 调 AttemptRestartByID + // 自动尝试 docker restart + am start,限频 5min/次(personalHandler 内部管理)。 + // 控制面状态机:注入 adb prober;nil 时状态机静默关闭。 + // 2026-08-29 重构:监听 App 已弃用,adb 反向隧道(relay)全部移除。 + var pushHandler *admin.PersonalHandler + if personalDB != nil { + // 设备在线判定注入(2026-08-29 重构):个人通道选号/可服务账户判定以 + // adb 可达性为准(实时探测 + 自动 connect)。 + adbOnline := func(ctx context.Context, serial string) bool { + return adbMgr().Online(ctx, serial) + } + personalAlipay.SetAdbOnline(adbOnline) + personalWechat.SetAdbOnline(adbOnline) + // 均衡分配器注入(docs/PLAN-DYNAMIC-CLOSE-ALLOC.md §5): + // 两层分配——先选设备(最闲最健康,超限排除),再选设备内账户(最闲最健康)。 + // 负载数据源:waitPayPool(设备/账户负载)+ app.ledgermon(设备 poll 健康度)。 + // provider 显式传入:支付宝/微信各自注册自己的分配器(SelectFromWeighted 按通道过滤)。 + balancerFor := func(provider channel.Provider) func(ctx context.Context) *account.Account { + return func(ctx context.Context) *account.Account { + return balanceSelectAccount(ctx, personalDB, accountPool, waitPayPool, app.ledgermon, cfg, "personal."+string(provider)) + } + } + personalAlipay.SetBalancedSelect(balancerFor(channel.ProviderAlipay)) + personalWechat.SetBalancedSelect(balancerFor(channel.ProviderWechat)) + + var watcherRecoverer reconciler.Recoverer + adbProber := reconciler.AsAdbProber(adbMgr()) + if adbMgr() != nil { + // 提前构造一份 personalHandler 用作 watcher 的 recoverer; + // adminSrv 后面会用同一参数构造自己的实例,二者独立的 restartLimiter 可接受。 + rh := admin.NewPersonalHandler(personalDB, accountPool, accountDB, adbMgr()) + watcherRecoverer = rh + pushHandler = rh + } + go reconciler.NewDeviceWatcher(personalDB, accountPool, st, orderSvc, nil, watcherRecoverer, adbProber).Start(ctx) + } + + // 6.5 自动关闭超时未支付订单(定时扫描) + // 候选集 = max(商家固定窗口 ORDER_CLOSE_MINUTES, 个人动态窗口上限 MAX) + 裕量。 + // 具体订单是否到期由 CloseExpiredOrders 二次判断: + // - 个人免签:池内动态 CloseAt(设备负载驱动,未到跳过) + // - 商家通道:ORDER_CLOSE_MINUTES 固定窗口(isExpiredWindow,不受动态窗口影响) + if cfg.OrderCloseMinutes > 0 { + go func() { + defer bgRecover("close_scanner") + interval := time.Duration(cfg.OrderCloseInterval) * time.Second + if interval <= 0 { + interval = 60 * time.Second + } + // 候选集 = min(商家固定窗口, 个人动态窗口下限)。 + // 个人订单在 age≥dynMin 时进入候选集,由池内 CloseAtOf 按各自动态窗口精确过滤; + // 商家订单在 age≥dynMin 时进入候选集,由 isExpiredWindow(ORDER_CLOSE_MINUTES) 过滤。 + // 若用 max() 则个人订单要等到 30min 才进候选集,动态窗口(5~10min)完全失效。 + timeout := time.Duration(cfg.OrderCloseMinutes) * time.Minute + if dynMin > 0 && dynMin < timeout { + timeout = dynMin + } + logger.Info("main", "超时订单自动关闭已启动", + "candidate_window", timeout.String(), "interval_sec", int(interval/time.Second), + "merchant_close_minutes", cfg.OrderCloseMinutes, + "dynamic", dynMin > 0, "personal_min", cfg.OrderCloseMin, "personal_max", cfg.OrderCloseMax) + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + before := time.Now().Add(-timeout) + if _, err := orderSvc.CloseExpiredOrders(ctx, before); err != nil { + logger.Error("order", "自动关闭扫描失败", "err", err) + } + } + } + }() + } + + // 6.5b 收款码定期清理(DB 过期码 + 设备端相册旧码,防存储打满) + if pushHandler != nil { + go func() { + defer bgRecover("qrcode_cleanup") + interval := 60 * time.Minute + logger.Info("main", "收款码定期清理已启动", "interval_minutes", int(interval/time.Minute)) + // 启动时先清一次 + pushHandler.CleanupQrcodes(ctx) + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + pushHandler.CleanupQrcodes(ctx) + } + } + }() + } + + // 6.5c 收款码自动续期(每设备每平台一条 active 码,缺失/过期才补捕;静态码长期有效几乎不触发) + if pushHandler != nil { + go func() { + defer bgRecover("qrcode_renew") + interval := 60 * time.Minute + logger.Info("main", "收款码自动续期已启动", "interval_minutes", int(interval/time.Minute)) + // 启动时先补一轮(确保已有有效码) + pushHandler.RenewQrcodes(ctx) + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + pushHandler.RenewQrcodes(ctx) + } + } + }() + } + + // 6.5d 服务端账本读取轮询已废弃(用户决策:账本导航兜底延迟高/自动化困难, + // 实时到账完全依赖监听端通知推送;如需查账可用账本校验 API 手动触发)。 + + // 6.7 账本校验函数(调度器 + API 共用) + doVerify := func() *admin.VerifyLedgerResult { + res, err := lg.Verify(context.Background()) + r := &admin.VerifyLedgerResult{CheckedAt: time.Now().Format(time.RFC3339)} + if err != nil { + r.Errors = []string{err.Error()} + logger.Error("ledger", "账本校验失败", "err", err) + } else { + r.Valid = res.Valid + r.TotalEntries = res.TotalEntries + r.Errors = res.Errors + r.SnapshotOK = res.SnapshotOK + r.Duration = time.Since(res.StartTime).Round(time.Millisecond).String() + if res.Valid { + logger.Info("ledger", "账本完整 ✅", "count", res.TotalEntries, "snapshot_ok", res.SnapshotOK, "duration", r.Duration) + } else { + logger.Error("ledger", "账本校验失败 ❌", "errors", res.Errors) + } + } + return r + } + + // 6.8 账本定时任务:每日 00:00 快照 + 校验 + go func() { + defer bgRecover("ledger_daily") + // 先创建今日快照(刷新锚点到当前链尾),再校验 —— + // 避免"先校验"读到昨日快照锚点与今日新增条目不匹配而误报 ERROR + today := time.Now().Format("2006-01-02") + if err := lg.CreateSnapshot(ctx, today); err != nil { + logger.Warn("ledger", "创建快照失败(可能已存在)", "err", err) + } else { + logger.Info("ledger", "今日快照已创建", "date", today) + } + // 启动时执行一次校验 + doVerify() + + // 每 24 小时检查一次(每分钟检查是否到 00:00) + ticker := time.NewTicker(1 * time.Minute) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + now := time.Now() + // 每天 00:01 执行(给 1 分钟缓冲,避免刚好 00:00:00 没执行到) + if now.Hour() == 0 && now.Minute() == 1 { + logger.Info("ledger", "每日账本任务开始") + // 每日重置账户日已用额度(DB + 内存池同步清零) + if err := accountDB.ResetDailyUsed(ctx); err != nil { + logger.Error("main", "重置账户日额度(DB)失败", "err", err) + } else { + accountPool.ResetDailyUsed() + logger.Info("main", "账户日额度已重置") + } + // 创建前一日快照 + yesterday := now.AddDate(0, 0, -1).Format("2006-01-02") + if err := lg.CreateSnapshot(ctx, yesterday); err != nil { + logger.Warn("ledger", "创建快照失败", "date", yesterday, "err", err) + } else { + logger.Info("ledger", "快照已创建", "date", yesterday) + } + // 全链校验 + doVerify() + // 日终对账报告(RESILIENCE P2-1):昨日全量核对 + if rep, err := reconciler.BuildDailyReport(ctx, st, yesterday); err != nil { + logger.Error("reconciler", "日终对账报告生成失败", "err", err) + } else { + rep.Log() + } + // 睡 59 秒,避免一天内重复触发 + time.Sleep(59 * time.Second) + } + } + } + }() + + // 7. 启动 Cloudflare Tunnel(后台,获取公网回调地址) + // 隧道域名每次启动随机分配,通过回调变量动态更新,不硬编码 + // 隧道重启换域名时,OnURLChange 自动更新支付宝通道的回调地址 + + go func() { + defer bgRecover("cloudflare_tunnel") + logger.Info("main", "正在启动 Cloudflare Tunnel...") + + // 从 .env(CF_TUNNEL_CREDENTIALS + CF_TUNNEL_HOSTNAME)生成 ~/.cloudflared/ 配置。 + // 换环境只需复制 .env 中对应行,无需手动创建凭据文件。 + if err := tunnel.EnsureCredentials(); err != nil { + logger.Warn("main", "自动生成隧道配置失败(不影响已有文件)", "err", err) + } + + // updateAlipayNotifyURL 更新支付宝通道的回调地址 + updateAlipayNotifyURL := func(tunURL string) { + if tunURL == "" { + return + } + notifyURL := tunURL + "/alipay/notify" + cfg.AlipayNotifyURL = notifyURL + // 通过类型断言更新支付宝通道的回调地址 + if ch, ok := channel.Get("merchant.alipay"); ok { + if alipayCh, ok := ch.(*alipaych.Channel); ok { + alipayCh.SetNotifyURL(notifyURL) + } + } + // 更新微信通道回调地址 + wxNotifyURL := tunURL + "/wechat/notify" + cfg.WechatNotifyURL = wxNotifyURL + if ch, ok := channel.Get("merchant.wechat"); ok { + if wc, ok := ch.(*wechatch.Channel); ok { + wc.SetNotifyURL(wxNotifyURL) + } + } + // 更新个人免签通道公网地址(套码二维码用) + if personalAlipay != nil { + personalAlipay.SetBaseURL(tunURL) + } + if personalWechat != nil { + personalWechat.SetBaseURL(tunURL) + } + cfg.PersonalBaseURL = tunURL + // 对接信息同源更新:env 里只写了 localhost/内网(历史默认)时由固定隧道域名接管, + // 显式配置了公网域名的场景不覆盖(尊重运维意图) + if cfg.EpayPublicBase == "" || notify.IsPrivateURL(cfg.EpayPublicBase) { + cfg.EpayPublicBase = tunURL + } + // 保存到文件,方便查看当前回调地址 + _ = os.WriteFile("current_callback.txt", []byte("alipay: "+notifyURL+"\nwechat: "+wxNotifyURL+"\ntunnel: "+tunURL+"\n"), 0644) + logger.Info("main", "回调地址已更新", "alipay", notifyURL, "wechat", wxNotifyURL, "tunnel", tunURL) + } + + // 启动隧道(阻塞直到就绪或超时) + // OnURLChange 回调:隧道就绪 + 重启换域名时自动触发 + tun, err := tunnel.New(ctx, &tunnel.Config{ + LocalPort: 8080, + CallbackPath: "/alipay/notify", + StartTimeout: 30 * time.Second, + Connections: 4, + OnURLChange: updateAlipayNotifyURL, + Hostname: cfg.TunnelHostname, + }) + if err != nil { + logger.Warn("main", "隧道启动失败(不影响本地服务,但支付宝回调不可用)", "err", err) + return + } + setTun(tun) + + logger.Info("main", "隧道就绪 ✅", "url", tun.URL(), "callback", cfg.AlipayNotifyURL) + + // 启动健康检查 + 自动重启(隧道挂了会自动换域名重连) + tun.HealthLoop(ctx, 30*time.Second) + }() + + // 8. HTTP 服务 + r := gin.Default() + r.Use(traceMiddleware()) + + // 公网支付路由组:按 IP 限流(防刷 / 防 DoS),不影响签名回调路由 + publicLimiter := ratelimit.IPRateLimit(cfg.PublicRateMax, time.Duration(cfg.PublicRateWindowSec)*time.Second) + public := r.Group("", publicLimiter) + + // 易支付协议接口(New API 调用,保持兼容) + public.GET("/submit.php", app.handleEpaySubmit) + public.POST("/submit.php", app.handleEpaySubmit) + public.GET("/api.php", app.handleEpayQuery) + + // 二维码刷新(生成新订单 + 新二维码) + public.GET("/qr/refresh", app.handleQrRefresh) + + // 订单过期页(支付页倒计时归零后跳转;return_url 指向商家页用于重新下单) + public.GET("/qr/expired", app.handleQrExpired) + + // 订单状态探测(过期页 JS 轮询:上游同号重提复活后自动跳回支付页,不死在过期页) + public.GET("/qr/status", app.handleQrStatus) + + // 支付成功落地页(付款人支付后展示成功状态;仅 TRADE_SUCCESS 渲染) + public.GET("/qr/success", app.handleQrSuccess) + + // 切换支付方式(订单智能迁移到目标通道可用账户,支持过期订单复活) + public.GET("/qr/switch", app.handleQrSwitch) + + // 浏览器指纹绑定(支付页 JS 采集 canvas/时区/屏幕后上报,增强 buyer_key 抗换 IP 刷单) + public.GET("/personal/bind-fp", app.handleBindFingerprint) + + // 支付宝异步回调 + r.POST("/alipay/notify", app.handleAlipayNotify) + + // 微信异步回调 + r.POST("/wechat/notify", app.handleWechatNotify) + + // 个人免签通道:直发码图片(真实收款码二维码;token 校验,图片不进支付页 HTML) + public.GET("/qr/real", app.handleQrReal) + + // 健康检查 + r.GET("/health", func(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"status": "ok", "time": time.Now().Format(time.RFC3339)}) + }) + + // 管理后台(独立端口 8081,Vue3 前端 + JSON API) + if cfg.AdminAddr != "" { + adminSrv := admin.New(st, ledgerDB, "/alipay/notify", + func() string { + if t := getTun(); t != nil { + return t.URL() + } + return "" + }, + func() bool { + if t := getTun(); t != nil { + return t.Healthy() + } + return false + }, + func() []admin.ChannelInfo { + return buildChannelInfos(accountPool) + }, + func() *admin.EpayInfo { return buildEpayInfo(cfg) }, + func(key, value string) { + // 设置变更回调:更新通道运行时配置 + // 支付模式切换 + if strings.HasPrefix(key, "alipay_mode_") { + mode := strings.TrimPrefix(key, "alipay_mode_") + if ch, ok := channel.Lookup("merchant.alipay"); ok { + type modeSetter interface { + SetModeEnabled(m string, enabled bool) + } + if ms, ok := ch.(modeSetter); ok { + ms.SetModeEnabled(mode, value == "true") + } + } + } + }, + doVerify, + func() error { + ctx := context.Background() + today := time.Now().Format("2006-01-02") + return lg.CreateSnapshot(ctx, today) + }, + func() string { return lg.PublicKeyPEM() }, + accountHandler, + upstreamHandler, + admin.NewReconcilerHandler(st, orderSvc, confirmSched), + admin.NewPersonalHandler(personalDB, accountPool, accountDB, adbMgr()), + cfg.JWTSecret, + ) + // 实时设备流服务(云手机式操作):注入 StreamServer,管理后台提供 WebSocket 入口 + streamSrv := scrcpy.NewStreamServer() + adminSrv.SetStreamServer(streamSrv) + adminSrv.SetLogStore(logDB) + // 账本页监听状态(只读,docs/PLAN-ADB-LEDGER-IMPL.md §7) + adminSrv.SetLedgerStatus(func() any { + if app.ledgermon == nil { + return []any{} + } + return app.ledgermon.Status() + }) + // 设备手动预热(DeviceActor 模型:手动预热是队列中的一个命令,天然互斥) + adminSrv.SetLedgerWarmup(func(ctx context.Context, deviceID string, providers map[string]bool) error { + if app.ledgermon == nil { + return fmt.Errorf("账本监听未启用") + } + return app.ledgermon.WarmupDevice(ctx, deviceID, providers) + }) + go adminSrv.Run(cfg.AdminAddr) + } + + logger.Info("main", "服务启动完成 ✅") + if err := r.Run(cfg.ListenAddr); err != nil { + logger.Error("main", "服务退出", "err", err) + } +} + +// adbMgr 创建设备操作管理器(web 可视化操作 USB 真机 / adb over WiFi 设备) +// adb 二进制从系统 PATH 获取(宿主机需安装 adb);构建产物与运行环境需一致。 diff --git a/cmd/gateway/notify.go b/cmd/gateway/notify.go new file mode 100644 index 0000000..1096d0f --- /dev/null +++ b/cmd/gateway/notify.go @@ -0,0 +1,192 @@ +// paygateway 支付网关 notify(从 main.go 拆分,QR real/通知处理) +package main + +import ( + "context" + "crypto/sha256" + "fmt" + "net/http" + "paygateway/internal/account" + "paygateway/internal/channel" + personalch "paygateway/internal/channel/personal" + "paygateway/internal/logger" + "paygateway/internal/model" + "paygateway/internal/notifywatcher" + "paygateway/internal/personal" + "paygateway/internal/web" + "time" + + "github.com/gin-gonic/gin" +) + +func (a *App) resolvePersonalQr(c *gin.Context, token string) (*model.Order, *personalch.Channel, *account.Account, error) { + key, _, err := personal.ParseQrToken(token) + if err != nil { + return nil, nil, nil, fmt.Errorf("二维码无效或已过期: %w", err) + } + o, err := a.store.GetOrder(c.Request.Context(), key) + if err != nil || o == nil { + return nil, nil, nil, fmt.Errorf("订单不存在") + } + if o.Status != model.StatusWaitPay { + return nil, nil, nil, fmt.Errorf("订单已处理(状态: %s)", o.Status) + } + if o.QrToken != "" && o.QrToken != token { + return nil, nil, nil, fmt.Errorf("二维码与订单不匹配") + } + ch := a.personalChannelFor(o.Channel) + if ch == nil { + return nil, nil, nil, fmt.Errorf("通道配置错误") + } + acct := ch.GetAccountByID(o.AccountID) + if acct == nil || acct.QRContent == "" { + return nil, nil, nil, fmt.Errorf("收款码配置错误") + } + return o, ch, acct, nil +} + +// handleQrReal 直发码:校验 token → 生成真实收款码 PNG → 返回图片(不进支付页 HTML) +// GET /qr/real?token={token} +func (a *App) handleQrReal(c *gin.Context) { + token := c.Query("token") + if token == "" { + c.String(http.StatusNotFound, "无效二维码") + return + } + o, _, acct, err := a.resolvePersonalQr(c, token) + if err != nil { + logger.Warn("personal", "直发码 token 校验失败", "err", err) + c.String(http.StatusNotFound, "二维码无效或已过期") + return + } + png := web.QRPng(acct.QRContent) + if len(png) == 0 { + c.String(http.StatusInternalServerError, "收款码生成失败") + return + } + c.Header("Content-Type", "image/png") + c.Header("Cache-Control", "no-store") + c.Header("X-Content-Type-Options", "nosniff") + c.Header("Content-Security-Policy", "default-src 'none'") + logger.Info("personal", "直发码图片下发", "out_trade_no", o.OutTradeNo, "amount", o.AmountMinor) + c.Data(http.StatusOK, "image/png", png) +} + +// personalChannelFor 按订单通道名返回个人通道实例 +func (a *App) personalChannelFor(chName string) *personalch.Channel { + switch chName { + case "personal.alipay": + return a.personalAlipay + case "personal.wechat": + return a.personalWechat + } + return nil +} + +// sha256Hash 计算 SHA-256 哈希(hex) +func sha256Hash(data []byte) []byte { + h := sha256.Sum256(data) + return h[:] +} + +// handleNotify 通知栏收款事件回调(notifywatcher.Handler 实现)。 +// +// 快速通道语义(2026-08-30 用户需求确认): +// - 通知栏比账本页快 ~15s,收到收款通知即尝试快速匹配入账; +// - 匹配窗口以通知 when(收款发生时刻)为锚:since = when - 30min, +// 不能用 time.Now()(通知不清理会一直在,旧通知会错误匹配新订单); +// - 复用 matchPersonalOrder(空间账户过滤 + 金额时间窗 + 备注码), +// 与账本链路共享同一套资金安全逻辑; +// - 金额唯一 → 直接入账(冻结守卫 + SettlePayment 幂等); +// 多候选(ambiguous) / 无订单(no_order) → 不动作,留给账本复查兜底。 +func (a *App) handleNotify(ctx context.Context, ev notifywatcher.Event) error { + dev, err := a.personalDeviceCtx(ctx, ev.DeviceID) + if err != nil { + return fmt.Errorf("设备加载失败: %w", err) + } + if dev.Status == personal.DeviceFrozen { + logger.Warn("notifywatcher", "通知回调:设备已冻结,跳过", "device_id", ev.DeviceID) + return nil + } + // 窗口锚点 = 通知 when(毫秒时间戳);与账本 30min 窗口语义一致 + notifyTime := time.UnixMilli(ev.When) + window := 30 * time.Minute + + orderNo, matched, acctID, err := a.matchPersonalOrder(ctx, dev, ev.Provider, "", ev.AmountMinor, notifyTime, window, ev.Space) + if err != nil { + return fmt.Errorf("通知订单匹配失败: %w", err) + } + + // 指纹落库(幂等去重的写入侧):notify 事件此前从不回写 raw_hash,PushExists + // 每轮查空 → 同一通知每轮重复捕获+重复匹配(真机风暴实测 30s 内 21 次)。 + // 账本链路(ledger.go)本就在写本表,此处对齐(空间隔离:指纹含 space+when)。 + rawHash := ev.Fingerprint() + if rawHash != "" { + if dup, derr := a.personalDB.PushExists(ctx, ev.DeviceID, rawHash); derr == nil && !dup { + _ = a.personalDB.InsertPush(ctx, &personal.Push{ + DeviceID: ev.DeviceID, Provider: ev.Provider, AmountMinor: ev.AmountMinor, + RawHash: rawHash, MatchedOrder: orderNo, MatchResult: matched, + Space: ev.Space, Raw: ev.Raw, + }) + } + } + + raw := ev.Raw + switch matched { + case "matched": + // 冻结账户守卫(对齐账本链路):冻结账户不自动入账,转异常池人工核实 + if acct := a.accountPool.GetByID(acctID); acct != nil && acct.Status == account.StatusFrozen { + logger.Warn("notifywatcher", "账户已冻结,通知收款转人工", "order", orderNo, "account_id", acctID) + if cur, cerr := a.store.GetOrder(ctx, orderNo); cerr == nil && cur != nil { + a.orders.EnqueueException(ctx, cur, model.ExReasonListenOffline, "PAID_WHILE_FROZEN", + "", raw, "通知栏监听:收款到达时账户处于冻结态,需人工核实际收款后入账") + } + return nil + } + nr := &channel.NotifyResult{ + Valid: true, + OutTradeNo: orderNo, + ChannelTxID: fmt.Sprintf("NOTIFY-%s-%d", ev.DeviceID, ev.When), + Status: model.StatusSuccess, + AmountMinor: ev.AmountMinor, + Payer: "notify:" + ev.DeviceID, + DeviceID: ev.DeviceID, + AccountID: acctID, + RawPayload: raw, + } + if ok, serr := a.orders.SettlePayment(ctx, nr); serr != nil { + logger.Error("notifywatcher", "通知快速入账失败", "err", serr, "order", orderNo) + return serr + } else if !ok { + // ok=false, serr=nil:并发已支付/已关单(幂等),账本复查也会幂等跳过 + logger.Info("notifywatcher", "通知入账幂等跳过(已支付/已关单)", "order", orderNo) + return nil + } + if _, uerr := a.store.UpdateOrderStatus(ctx, orderNo, model.StatusSuccess, model.StatusSuccess, func(upd *model.Order) { + upd.DeviceID = ev.DeviceID + }); uerr != nil { + logger.Warn("notifywatcher", "回写订单 device_id 失败", "err", uerr, "order", orderNo) + } + a.confirmSched.OnSettled(orderNo) + logger.Info("notifywatcher", "通知栏快速入账成功 ✅(比账本快 ~15s)", + "order", orderNo, "amount", ev.AmountMinor, "device", ev.DeviceID, + "provider", ev.Provider, "space", ev.Space) + case "settled_dup": + // 同一笔收款已被账本/通知另一链路入账(双链同捕竞态,真机实测 5s 内), + // 幂等跳过:不动作(防重复/防入账),仅留审计 + logger.Info("notifywatcher", "通知重复捕获,另一链路已入账(幂等跳过)", "order", orderNo) + case "no_order": + // 通知栏可能是手动收款等非订单场景;账本是 no_order 唯一判定者,避免双写。 + // 但通知栏先到达时该笔收款尚未落 unmatched——账本 5s 后复查(含 settled_dup), + // 若账本侧也无主则重复有一次记录留痕(幂等由 CreateUnmatched 无唯一约束,可接受), + // 待账本落库后此处不再单独入 unmatched(避免双链重复记录)。 + logger.Debug("notifywatcher", "通知无匹配订单(留给账本复查)", + "device", ev.DeviceID, "amount", ev.AmountMinor, "provider", ev.Provider) + default: // ambiguous + logger.Warn("notifywatcher", "通知多订单歧义(账本复查确认)", + "device", ev.DeviceID, "amount", ev.AmountMinor, "provider", ev.Provider) + } + return nil +} + +// 防止 gopay 未使用(保持依赖) diff --git a/cmd/gateway/return_url.go b/cmd/gateway/return_url.go new file mode 100644 index 0000000..68a173d --- /dev/null +++ b/cmd/gateway/return_url.go @@ -0,0 +1,72 @@ +// paygateway return_url 兜底重写:上游商户传来的回跳地址指向内网时,付款人支付成功后 +// 跳回浏览器打不开的 localhost/内网页面。网关侧无法修改上游配置,但手里有可靠来源 +// (Referer = 真实发起支付的前端地址、上游表的公网服务地址),按可信度逐层兜底。 +package main + +import ( + "net/url" + "paygateway/internal/logger" + "paygateway/internal/notify" + "strings" + + "github.com/gin-gonic/gin" +) + +// sanitizeReturnURL 返回修正后的 return_url: +// 1. 上游传的是公网地址(或空) → 原样返回 +// 2. 内网/localhost → 用 Referer origin 重写(保留 path/query) +// 3. Referer 不可用 → 用上游后台配置的公网服务地址(BaseURL)重写 +// 4. 无法判定 → 原样返回(附警示日志) +func (a *App) sanitizeReturnURL(c *gin.Context, orderNo, pid, returnURL string) string { + if returnURL == "" || !notify.IsPrivateURL(returnURL) { + return returnURL + } + fixed, source := a.publicOriginCandidate(c, orderNo, pid) + if fixed == "" { + return returnURL + } + out := rehost(returnURL, fixed) + if out == "" { + return returnURL + } + logger.Info("epay", "return_url 指向内网,已重写为付款人可达地址", + "out_trade_no", orderNo, "source", source, "origin", fixed, "to", out) + return out +} + +// publicOriginCandidate 返回可用的公网 origin + 来源标注("" 表示无可用候选) +func (a *App) publicOriginCandidate(c *gin.Context, orderNo, pid string) (string, string) { + if ref := c.GetHeader("Referer"); ref != "" { + if u, err := url.Parse(ref); err == nil && + (u.Scheme == "https" || u.Scheme == "http") && u.Host != "" && + !notify.IsPrivateURL(u.Scheme+"://"+u.Host) { + return u.Scheme + "://" + u.Host, "referer" + } + } + if a.upstreamPool != nil { + // pid 用 handleSubmit 已解析的参数(POST 表单场景 c.Query 取不到) + if up := a.upstreamPool.ByPid(pid); up != nil && up.BaseURL != "" && + !notify.IsPrivateURL(up.BaseURL) { + return strings.TrimRight(up.BaseURL, "/"), "upstream_base_url" + } + } + logger.Warn("epay", "return_url 指向内网且无公网候选,将原样跳回(付款人可能打不开)", + "out_trade_no", orderNo) + return "", "" +} + +// rehost 以 publicOrigin 的 host/origin 重写原始 URL,path/query/fragment 保留 +func rehost(raw, origin string) string { + src, err := url.Parse(raw) + if err != nil { + return "" + } + dst, err := url.Parse(origin) + if err != nil || dst.Host == "" { + return "" + } + out := *src + out.Scheme = dst.Scheme + out.Host = dst.Host + return out.String() +} diff --git a/cmd/gateway/return_url_test.go b/cmd/gateway/return_url_test.go new file mode 100644 index 0000000..0406cb1 --- /dev/null +++ b/cmd/gateway/return_url_test.go @@ -0,0 +1,59 @@ +// return_url 兜底重写测试:上游传内网/localhost 时的三层替换与保留语义。 +// +// Given 公网 return_url(或空):原样保留,不触发重写 +// Given 内网 return_url + Referer:重写为 Referer origin,路径与查询参数无损 +// Given 内网 return_url(无有效候选):返回原值 +package main + +import ( + "net/http/httptest" + "testing" + + "github.com/gin-gonic/gin" +) + +func ctxWithReferer(t *testing.T, referer string) *gin.Context { + t.Helper() + req := httptest.NewRequest("GET", "/submit.php", nil) + if referer != "" { + req.Header.Set("Referer", referer) + } + w := httptest.NewRecorder() + ctx, _ := gin.CreateTestContext(w) + ctx.Request = req + return ctx +} + +func TestRehost(t *testing.T) { + got := rehost("http://localhost:3000/usage-logs/common?q=2", "https://api.example.com") + want := "https://api.example.com/usage-logs/common?q=2" + if got != want { + t.Fatalf("rehost = %q want %q", got, want) + } + if rehost("http://localhost:3000/x", "") != "" { + t.Fatal("空 origin 应返回空") + } +} + +func TestSanitizeReturnURL_PublicKept(t *testing.T) { + app := &App{} + c := ctxWithReferer(t, "https://other.tld/checkout") + got := app.sanitizeReturnURL(c, "O1", "2001", "https://shop.example.com/ok") + if got != "https://shop.example.com/ok" { + t.Fatalf("公网 return_url 不应被改写,实际 %q", got) + } + if app.sanitizeReturnURL(c, "O1", "2001", "") != "" { + t.Fatal("空 return_url 应原样保留") + } +} + +// Referer 兜底:localhost return_url 重写为 Referer origin(真实发起支付的前端)。 +func TestSanitizeReturnURL_RefererWins(t *testing.T) { + app := &App{} + c := ctxWithReferer(t, "https://xapi-public.example.com/pay/checkout") + got := app.sanitizeReturnURL(c, "O2", "2001", "http://localhost:3000/usage-logs/common") + want := "https://xapi-public.example.com/usage-logs/common" + if got != want { + t.Fatalf("应重写为 Referer origin,实际 %q want %q", got, want) + } +} diff --git a/cmd/gateway/risk.go b/cmd/gateway/risk.go new file mode 100644 index 0000000..1a1ab8f --- /dev/null +++ b/cmd/gateway/risk.go @@ -0,0 +1,104 @@ +// paygateway 无订单收款行为标记(2026-09-14 需求确认): +// 直发码无法阻止他人转账,熔断无意义且会误伤正常收款—— +// 方向改为【准确记录 + 行为标记 + 告警提示立即核实】,保留司法可核对记录。 +// 打分只做信息标注(risk_level/risk_tags),绝不影响入账与订单链路。 +package main + +import ( + "context" + "fmt" + "paygateway/internal/logger" + "paygateway/internal/personal" + "strings" + "time" +) + +// 风险等级 +const ( + riskNone = 0 + riskSuspect = 1 // 建议立即核实 + riskHighWarning = 2 // 多重特征叠加,高警戒 +) + +const riskWindow = 30 * time.Minute + +// assessUnmatched 行为模式标记:对一笔无订单收款计算风险标签。 +// 标签(保守,宁可少标不误标): +// +// big_amount 单笔 ≥ PersonalBigAmount(默认 200 元,admin 可调) +// burst 同设备窗口内无订单收款 ≥5 笔(批量二维码套现特征) +// repeat_payer 同付款人窗口内重复出现 ≥2 笔(同源资金试探特征) +func (a *App) assessUnmatched(ctx context.Context, provider string, amountMinor int64, payer, deviceID string) (tags []string, level int) { + if cfg := a.cfg; cfg != nil && cfg.PersonalBigAmount > 0 && amountMinor >= cfg.PersonalBigAmount { + tags = append(tags, "big_amount") + } + recent, err := a.personalDB.ListUnmatched(ctx, "", 200) + if err == nil { + tags = append(tags, scoreFromRows(amountMinor, payer, deviceID, recent, time.Now(), riskWindow)...) + } + level = riskNone + switch { + case len(tags) >= 2: + level = riskHighWarning + case len(tags) == 1 && tags[0] == "big_amount": + level = riskHighWarning + case len(tags) == 1: + level = riskSuspect + } + return tags, level +} + +// scoreFromRows 纯计数逻辑(便于单测):按窗口过滤历史行并产出 burst/repeat_payer 标签 +func scoreFromRows(amountMinor int64, payer, deviceID string, rows []*personal.UnmatchedPayment, now time.Time, window time.Duration) (tags []string) { + burstMax, repeatMax := 0, 0 + for _, u := range rows { + if u.CreatedAt.IsZero() || now.Sub(u.CreatedAt) > window { + continue + } + if u.DeviceID != "" && u.DeviceID == deviceID { + burstMax++ + } + if payer != "" && u.Payer != "" && payer == u.Payer { + repeatMax++ + } + } + if burstMax+1 >= 5 { + tags = append(tags, "burst") + } + if repeatMax+1 >= 2 { + tags = append(tags, "repeat_payer") + } + return tags +} + +// saveUnmatched 记录无订单收款(富化 + 行为标记 + 告警提示)。 +func (a *App) saveUnmatched(ctx context.Context, provider string, amountMinor int64, note, raw, payer, deviceID string) { + tags, level := a.assessUnmatched(ctx, provider, amountMinor, payer, deviceID) + if _, err := a.personalDB.CreateUnmatched(ctx, &personal.UnmatchedPayment{ + Provider: provider, AmountMinor: amountMinor, Note: note, Raw: raw, + Status: "pending", Payer: payer, DeviceID: deviceID, + RiskTags: strings.Join(tags, ","), RiskLevel: level, + }); err != nil { + logger.Error("risk", "无订单收款落库失败", "err", err, "amount", amountMinor) + return + } + if level <= riskNone { + logger.Info("risk", "无订单收款已记录(无风险特征)", "provider", provider, "amount", amountMinor, "device", deviceID) + return + } + logger.Warn("risk", "无订单收款行为标记,建议立即核实", + "risk_tags", strings.Join(tags, ","), "risk_level", level, + "provider", provider, "amount", amountMinor, "payer", payer, "device", deviceID) + if a.alertC != nil { + a.alertC.Send("error", "疑似无订单收款(黑钱风险提示)", + fmt.Sprintf("来源: %s\n金额: ¥%.2f\n付款人: %s\n收款设备: %s\n特征: %s\n\n疑似黑钱/赃款风险,建议立即核实该笔收款;确认异常的请保全记录并联系司法部门处理。", + provider, float64(amountMinor)/100, payerOrDash(payer), deviceID, strings.Join(tags, ","))) + } +} + +func payerOrDash(p string) string { + if p == "" { + return "(通知栏无付款人信息)" + } + return p +} diff --git a/cmd/gateway/risk_test.go b/cmd/gateway/risk_test.go new file mode 100644 index 0000000..d3a69d6 --- /dev/null +++ b/cmd/gateway/risk_test.go @@ -0,0 +1,68 @@ +// 无订单收款行为标记测试:只做信息标注(tags/level),不影响入账链路。 +package main + +import ( + "context" + "testing" + "time" + + "paygateway/internal/personal" +) + +func TestAssessUnmatched_BigAmount(t *testing.T) { + app, _ := newMatchTestEnv(t) + app.cfg = &Config{PersonalBigAmount: 20000} // 200 元 + tags, level := app.assessUnmatched(context.Background(), "alipay", 20000, "", "dev-1") + if len(tags) == 0 || tags[0] != "big_amount" || level != riskHighWarning { + t.Fatalf("大额应标记 big_amount 高风险,实际 tags=%v level=%d", tags, level) + } +} + +func TestAssessUnmatched_BurstWindow(t *testing.T) { + ctx := context.Background() + app, pdb := newMatchTestEnv(t) + for i := 0; i < 5; i++ { + _, _ = pdb.CreateUnmatched(ctx, &personal.UnmatchedPayment{ + Provider: "alipay", AmountMinor: 100, DeviceID: "dev-burst", + CreatedAt: time.Now(), + }) + } + tags, level := app.assessUnmatched(ctx, "alipay", 100, "", "dev-burst") + has := false + for _, tag := range tags { + if tag == "burst" { + has = true + } + } + if !has || level != riskSuspect { + t.Fatalf("5 笔/30min 应标记 burst 疑似核实,实际 tags=%v level=%d", tags, level) + } +} + +// 窗口外的历史记录不构成 burst(防陈旧计数)。 +func TestAssessUnmatched_StaleNotCounted(t *testing.T) { + users := make([]*personal.UnmatchedPayment, 0, 6) + for i := 0; i < 6; i++ { + users = append(users, &personal.UnmatchedPayment{ + Provider: "alipay", AmountMinor: 100, DeviceID: "dev-old", + CreatedAt: time.Now().Add(-2 * time.Hour), + }) + } + tags := scoreFromRows(100, "", "dev-old", users, time.Now(), riskWindow) + for _, tag := range tags { + if tag == "burst" { + t.Fatalf("窗口外记录不应构成 burst: %v", tags) + } + } +} + +// 同付款人窗口内重复 ≥2 笔 → repeat_payer。 +func TestAssessUnmatched_RepeatPayer(t *testing.T) { + rows := []*personal.UnmatchedPayment{ + {Provider: "alipay", AmountMinor: 200, Payer: "*杰", CreatedAt: time.Now()}, + } + tags := scoreFromRows(200, "*杰", "dev-x", rows, time.Now(), riskWindow) + if len(tags) != 1 || tags[0] != "repeat_payer" { + t.Fatalf("同付款人应标记 repeat_payer,实际 %v", tags) + } +} diff --git a/cmd/tunnel/main.go b/cmd/tunnel/main.go new file mode 100755 index 0000000..fa24953 --- /dev/null +++ b/cmd/tunnel/main.go @@ -0,0 +1,86 @@ +// Package main Cloudflare Tunnel 管理工具 +// +// 启动 trycloudflare 隧道到本地端口,输出回调地址。 +// 用法: +// +// go run ./cmd/tunnel/ # 启动隧道到 8080 +// go run ./cmd/tunnel/ --port 8080 # 指定端口 +// go run ./cmd/tunnel/ --daemon # 后台运行,域名写入文件 +package main + +import ( + "context" + "flag" + "fmt" + "os" + "os/signal" + "syscall" + "time" + + "paygateway/internal/tunnel" +) + +func main() { + port := flag.Int("port", 8080, "本地服务端口") + daemon := flag.Bool("daemon", false, "后台运行,域名写入 tunnel_domain.txt") + flag.Parse() + + fmt.Println("╔" + repeat("═", 58) + "╗") + fmt.Println("║ Cloudflare Tunnel 隧道工具 ║") + fmt.Println("╚" + repeat("═", 58) + "╝") + fmt.Printf("\n[启动] 隧道 -> localhost:%d\n", *port) + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + tun, err := tunnel.New(ctx, &tunnel.Config{ + LocalPort: *port, + StartTimeout: 30 * time.Second, + }) + if err != nil { + fmt.Printf("\n[!] 隧道启动失败: %v\n", err) + fmt.Println("\n请确保已安装 cloudflared:") + fmt.Println(" curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared && chmod +x /usr/local/bin/cloudflared") + os.Exit(1) + } + + fmt.Printf("\n 🎉 隧道就绪!\n") + fmt.Printf(" 公网地址: %s\n", tun.URL()) + fmt.Printf(" 回调地址: %s/alipay/notify\n", tun.URL()) + fmt.Printf(" 本地端口: localhost:%d\n", *port) + + if *daemon { + domain := tun.URL() + if domain != "" { + _ = os.WriteFile("tunnel_domain.txt", []byte(domain+"\n"), 0644) + _ = os.WriteFile("tunnel_callback.txt", []byte(domain+"/alipay/notify\n"), 0644) + fmt.Printf("\n[✓] 域名已保存到 tunnel_domain.txt\n") + fmt.Printf("[✓] 回调地址已保存到 tunnel_callback.txt\n") + } + fmt.Println("\n[完成] 隧道已在后台运行") + return + } + + // 前台运行:启动健康检查 + go tun.HealthLoop(context.Background(), 30*time.Second) + + fmt.Println("\n[提示] 隧道会一直运行直到 Ctrl+C") + fmt.Println("按 Ctrl+C 停止隧道") + + // 等待信号 + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM) + <-sigCh + + fmt.Println("\n\n[停止] 正在关闭隧道...") + tun.Stop() + fmt.Println("[完成]") +} + +func repeat(s string, n int) string { + r := make([]byte, n) + for i := range r { + r[i] = s[0] + } + return string(r) +} diff --git a/current_callback.example.txt b/current_callback.example.txt new file mode 100644 index 0000000..f1112ff --- /dev/null +++ b/current_callback.example.txt @@ -0,0 +1,5 @@ +# Copy this file to current_callback.txt for local development only. +# Do not commit production callback URLs. +alipay: https://example.com/alipay/notify +wechat: https://example.com/wechat/notify +tunnel: https://example.com diff --git a/deploy/Dockerfile b/deploy/Dockerfile new file mode 100644 index 0000000..68d5bb0 --- /dev/null +++ b/deploy/Dockerfile @@ -0,0 +1,38 @@ +# 多阶段构建:golang 编译 → alpine 运行时(含 adb 客户端) +# +# 网络桥接方案:容器内 adb 通过 ADB_SERVER_SOCKET 指向宿主机 adb server(USB 设备), +# 无显式依赖(每次操作 adbmgr 自动重连),容器重启不丢设备状态。 +FROM golang:1.26-alpine AS build +WORKDIR /src +# 国内网络:模块代理镜像(proxy.golang.org 直连超时) +ENV GOPROXY=https://goproxy.cn,direct +# curl+bash: go generate 下载 cloudflared 二进制(//go:embed 需要) +# APK 国内镜像:官方 dl-cdn.alpinelinux.org 国内拉包极慢(实测 70 包卡 1 分钟+) +RUN sed -i 's#https://dl-cdn.alpinelinux.org#https://mirrors.tuna.tsinghua.edu.cn#g' /etc/apk/repositories && apk add --no-cache curl bash +COPY go.mod go.sum ./ +RUN go mod download +COPY cmd cmd +COPY internal internal +COPY web web +COPY scripts scripts +# 下载 cloudflared 并嵌入二进制(用于 CF Tunnel 回调地址) +RUN bash scripts/download-cloudflared.sh +RUN CGO_ENABLED=0 go build -ldflags "-s -w" -o /out/gateway ./cmd/gateway + +FROM alpine:3.20 +# adb 客户端 + CA 证书(云通道/回调 HTTPS) +RUN sed -i 's#https://dl-cdn.alpinelinux.org#https://mirrors.tuna.tsinghua.edu.cn#g' /etc/apk/repositories && \ + apk add --no-cache android-tools ca-certificates tzdata && \ + addgroup -S pgw && adduser -S pgw -G pgw +COPY --from=build /out/gateway /app/gateway +WORKDIR /app +# 数据/日志/配置挂载点:sqlite(data/3 个库)、lumberjack 日志、.env 敏感配置 +VOLUME ["/app/data", "/app/logs"] +ENV GATEWAY_ADDR=":8080" \ + ADMIN_ADDR=":8081" \ + ORDER_DB="data/paygateway.db" \ + LOG_FILE="logs/paygateway-{date}.log" \ + TZ="Asia/Shanghai" +USER pgw +EXPOSE 8080 8081 +ENTRYPOINT ["/app/gateway"] diff --git a/deploy/backup.sh b/deploy/backup.sh new file mode 100755 index 0000000..51419fa --- /dev/null +++ b/deploy/backup.sh @@ -0,0 +1,54 @@ +#!/bin/bash +# PayGateway 每日数据库备份(RESILIENCE.md P0-3) +# +# 功能: +# - 备份全部 SQLite 库(paygateway / ledger / accounts / personal / upstreams) +# - 用 sqlite3 .backup 命令在线备份(WAL 模式安全,无需停服) +# - 保留最近 N 份(默认 14 天),旧备份自动清理 +# +# 使用: +# 手动: bash deploy/backup.sh +# crontab: 0 2 * * * /opt/paygateway/deploy/backup.sh >> /var/log/paygateway-backup.log 2>&1 +# systemd timer(推荐):见 deploy/paygateway.service 同目录的 deploy/README 或 docs/OPERATIONS.md "备份" +# +# 注意:请先确认系统装有 sqlite3 命令行工具(apt install sqlite3) + +set -euo pipefail + +PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)" +DATA_DIR="$PROJECT_DIR/data" +BACKUP_DIR="${PAYGWAY_BACKUP_DIR:-$DATA_DIR/backups}" +KEEP_DAYS="${PAYGWAY_BACKUP_KEEP_DAYS:-14}" + +# 需要备份的数据库文件(相对 data/) +DBS=(paygateway.db ledger.db accounts.db personal.db upstreams.db) + +mkdir -p "$BACKUP_DIR" +STAMP="$(date +%Y%m%d-%H%M%S)" +BACKUP_DIR_TS="$BACKUP_DIR/$STAMP" +mkdir -p "$BACKUP_DIR_TS" + +FAILED=0 +for db in "${DBS[@]}"; do + SRC="$DATA_DIR/$db" + if [ ! -f "$SRC" ]; then + echo "[skip] $db 不存在" + continue + fi + DST="$BACKUP_DIR_TS/$db" + # 在线备份:SQLite 官方推荐方式,WAL 模式下安全(非简单 cp) + if sqlite3 "$SRC" ".backup '$DST'" 2>/dev/null; then + # 设置权限与源一致(含敏感账户库) + chmod 600 "$DST" + echo "[ok] $db ($(du -h "$DST" | cut -f1))" + else + echo "[FAIL] $db 备份失败(sqlite3 是否安装?)" + FAILED=1 + fi +done + +# 清理过期备份(按日期目录保留 KEEP_DAYS 天) +find "$BACKUP_DIR" -maxdepth 1 -type d -name "20*" -mtime +"$KEEP_DAYS" -exec rm -rf {} \; 2>/dev/null || true + +echo "=== 备份完成: $BACKUP_DIR_TS(保留 ${KEEP_DAYS} 天) ===" +exit $FAILED diff --git a/deploy/docker-compose.yml b/deploy/docker-compose.yml new file mode 100644 index 0000000..2ae98b9 --- /dev/null +++ b/deploy/docker-compose.yml @@ -0,0 +1,30 @@ +# PayGateway 容器化部署(WiFi adb 网络桥接版) +# +# 使用前准备: +# 2. .env 含敏感配置(ACCOUNT_MASTER_KEY / JWT_SECRET / ORDER_DB 等),只读挂载。 +# 3. 首次启动:docker compose -f deploy/docker-compose.yml up -d --build +# +# USB 直连手机走另一条路:宿主机跑 adb server,取消 network_mode 注释此行按需切换, +# environment: +# - ADB_SERVER_SOCKET=tcp:172.17.0.1:5037 # docker0 宿主地址 +services: + gateway: + build: + context: .. + dockerfile: deploy/Dockerfile + container_name: paygateway + network_mode: host + user: "1000:1000" + env_file: ../.env + environment: + - HOME=/app/data # pgw 用户的 HOME 默认 / 不可写,cloudflared 缓存目录需可写路径 + volumes: + - ../data:/app/data + - ../logs:/app/logs + restart: unless-stopped + healthcheck: + test: ["CMD", "wget", "-q", "-O", "/dev/null", "http://127.0.0.1:8080/health"] + interval: 30s + timeout: 5s + retries: 3 + start_period: 10s diff --git a/deploy/paygateway.service b/deploy/paygateway.service new file mode 100644 index 0000000..1bf7a7f --- /dev/null +++ b/deploy/paygateway.service @@ -0,0 +1,44 @@ +# PayGateway systemd 守护单元(RESILIENCE.md P0-3) +# +# 安装步骤: +# sudo cp deploy/paygateway.service /etc/systemd/system/ +# sudo systemctl daemon-reload +# sudo systemctl enable --now paygateway +# +# 前提: +# 1. 已编译二进制:go build -o gateway ./cmd/gateway/ +# 2. .env 位于 WorkingDirectory 下(服务自动加载) +# 3. 建议先手动跑一遍 go run ./cmd/gateway 确认启动正常 +# 4. 若部署路径与下方 WorkingDirectory 不同,请同步替换 4 处路径 +# (WorkingDirectory / ExecStart / ReadWritePaths 中各 1 处) +# +# 常用命令: +# systemctl status paygateway # 查看状态 +# journalctl -u paygateway -f # 实时日志 +# systemctl restart paygateway # 重启(CF 隧道域名每次重启会变,详见 README 升级章节) +[Unit] +Description=PayGateway 支付网关(易支付协议 + 个人免签) +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +# 工作目录 = 项目根目录(.env / data/ 都在这里) +WorkingDirectory=/opt/paygateway +# 编译产物路径(请先执行 go build -o gateway ./cmd/gateway/) +ExecStart=/opt/paygateway/gateway +Restart=always +RestartSec=3 +# 优雅停止:给 10s 让回调 worker 收尾 +TimeoutStopSec=10 +KillSignal=SIGTERM + +# 安全加固 +NoNewPrivileges=true +PrivateTmp=true +ProtectSystem=full +ReadWritePaths=/opt/paygateway/data +ProtectHome=read-only + +[Install] +WantedBy=multi-user.target diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md new file mode 100755 index 0000000..3131c18 --- /dev/null +++ b/docs/ARCHITECTURE.md @@ -0,0 +1,560 @@ +# PayGateway 架构设计(最终版) + +> 目标:高性能、高稳定、资金零差错、**支付方式可插拔**的支付网关 +> 通道底座:**gopay v1.5.119**(9 种通道现成:支付宝/微信/抖音/QQ/通联/拉卡拉/PayPal/Apple/扫呗) +> 未来扩展:链上支付(crypto)→ **预留扩展位**,不现在实现 + +--- + +## 1. 核心思想:三层各司其职,不重复造轮子 + +``` +┌────────────────────────────────────────────────────────────┐ +│ ① 协议层(对外接口,永不改变) │ +│ epay: /submit.php /api.php ← New API 等易支付系 │ +│ REST: /v1/orders ← 未来其他项目(预留) │ +│ type=alipay|wxpay|paypal|usdt → 路由到对应通道 │ +├────────────────────────────────────────────────────────────┤ +│ ② 编排层(资金安全核心,与具体通道无关) │ +│ 订单状态机 / 金额校验 / 幂等 / 可靠回调(outbox) / 对账 │ +│ → 只认统一接口 PaymentChannel,不认任何 SDK 细节 │ +├────────────────────────────────────────────────────────────┤ +│ ③ 通道层(可插拔,每个通道一个包) │ +│ ┌─────────────── gopay 库(现成 9 通道)──────────────┐ │ +│ │ alipay / wechat / paypal / douyin / qq / allinpay │ │ +│ │ lakala / saobei / apple │ │ +│ └────────────────────────────────────────────────────┘ │ +│ crypto/(★ 预留目录:未来链上支付,接口已留好) │ +└────────────────────────────────────────────────────────────┘ +``` + +**职责划分:** +- **gopay** = 通道适配器工厂(把 9 种支付 SDK 的差异封装掉) +- **paygateway** = 协议翻译 + 资金安全编排(不重复实现支付 SDK,只做编排) +- **crypto** = 未来加一个目录,接口现成 + +--- + +## 2. 通道抽象(接口 + 注册表) + +### 2.1 统一接口 + +```go +package channel + +// PaymentChannel 所有支付通道必须实现的统一接口 +type PaymentChannel interface { + // Name 通道标识:alipay / wechat / paypal / usdt ... + Name() string + + // Kind 通道类型:回调驱动 or 监听驱动(链上) + Kind() ChannelKind // CallbackKind / ListenerKind + + // Modes 支持的支付模式 + Modes() []PayMode // Page / Wap / QRCode / Address + + // CreatePayment 下单:返回支付凭据(跳转链接/二维码/链上地址) + CreatePayment(ctx context.Context, req *PayRequest) (*PayCredential, error) + + // VerifyNotify 验签回调(CallbackKind 用) + VerifyNotify(ctx context.Context, raw []byte) (*NotifyResult, error) + + // QueryOrder 主动查单(对账 + 补偿兜底) + QueryOrder(ctx context.Context, outTradeNo string) (*OrderStatus, error) + + // Listener 事件监听(ListenerKind 必须实现;CallbackKind 返回 nil) + Listener() EventListener +} + +type PayRequest struct { + OutTradeNo string // 网关订单号 + Subject string // 商品名 + AmountMinor int64 // 金额(最小单位,法币=分,链上=wei/satoshi) + Currency string // CNY / USD / USDT + NotifyURL string + ReturnURL string + PayMode PayMode // 用户选择的支付模式 +} + +type PayCredential struct { + PayURL string // 跳转链接 + QRCode string // 二维码内容 + Address string // 链上收款地址(未来) + ExpireAt time.Time +} + +type NotifyResult struct { + Valid bool // 验签是否通过 + ChannelTxID string // 通道交易号(去重依据) + Status string // SUCCESS / CLOSED / FAILED + AmountMinor int64 // 实付金额(必须与订单比对!) +} +``` + +### 2.2 注册表(配置驱动,零代码路由) + +```go +// 注册:每个通道 init() 或显式调用 +func Register(c PaymentChannel) { registry[c.Name()] = c } + +// 路由:协议层只写一行 +ch, ok := channel.Get(c.Query("type")) // "alipay" → 支付宝通道 +if !ok { c.String(400, "不支持的支付方式") } +``` + +**新增一种支付方式 = 3 步:** +1. `internal/channel/wechat/wechat.go` — 实现 4 个方法(内部调 gopay 的微信 SDK) +2. `Register(...)` 一行注册 +3. 配置 `.env` 加微信的密钥 + +→ 协议层、订单核心、存储、页面 **零改动**。 + +### 2.3 crypto 预留位(未来链上) + +```go +// internal/channel/crypto/ 目前只放: +// README.md — 设计说明(HD钱包/确认数/重组处理) +// stub.go — 返回 "未启用" 的占位实现(注册表可查但拒绝下单) + +// 未来实现时: +// Kind() = ListenerKind +// Listener() = 区块监听器(WebSocket + 轮询兜底) +// CreatePayment() = HD 钱包派生一次性地址 +// QueryOrder() = 查链上确认数 +``` + +注册表已经支持 `ListenerKind` 通道,**未来 crypto 落地 = 填实现,不动架构**。 + +--- + +## 3. 资金安全编排层(最不能错的部分) + +``` +下单: 协议层 → order.Create() → ① 事务写订单(CREATED) → ② 调通道 + → ③ 事务更新支付凭据 → ④ 返回页面 + +支付: 通道事件(回调/监听) + → ① 验签 → ② 金额比对(实付==订单,不等→拒绝+告警) + → ③ 幂等检查(channel_tx_id 唯一,重复→直接应答成功) + → ④ 条件更新状态(WHERE status='WAIT_PAY',失败→拒绝) + → ⑤ 写 payment_events 流水 → ⑥ 写 outbox 回调任务 + +结算: outbox worker → 回调接入方(失败重试 1m/5m/15m/1h → dead 告警) + 定时补偿:PAID 且未通知 > 30s → 补发 + 定时对账:通道实付 vs 订单金额 +``` + +**5 条铁律**(写入代码评审清单): +1. 金额只用最小单位 int64,永不用 float +2. 状态变更必须 `WHERE status=当前态`(防回跳/防覆盖) +3. 实付金额 ≠ 订单金额 → 拒绝 + 告警 +4. 同一 channel_tx_id 重复事件 → 幂等返回成功 +5. 每笔资金事件落 payment_events,可追溯 + +--- + +## 4. 数据库设计(资金安全地基,最关键的组件) + +### 4.1 选型决策 + +| 阶段 | 数据库 | 理由 | +|------|--------|------| +| **P1 开发/单机生产** | **SQLite**(`modernc.org/sqlite` 纯 Go,无 CGO) | 本机已有;单文件零运维;事务/行锁/唯一约束完整;交易量几百 TPS 足够 | +| **正式生产/多机** | PostgreSQL | 用户已声明基础设施有;行级锁 + 强一致 + 可扩展 | + +> **关键**:`Store` 接口抽象,SQLite 与 PG 实现可无缝切换。**P1 直接上 SQLite,不做 JSON 过渡** —— 资金数据从一开始就落在真数据库里。 + +### 4.2 数据模型(SQLite 版,PG 同构) + +```sql +-- 订单表:唯一约束防重复下单,条件更新防覆盖 +CREATE TABLE orders ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + out_trade_no TEXT NOT NULL UNIQUE, -- 接入方订单号(幂等依据) + gateway_no TEXT NOT NULL UNIQUE, -- 网关订单号 + channel TEXT NOT NULL, -- alipay/wechat/paypal/usdt + channel_tx_id TEXT, -- 通道交易号(回调去重) + amount_minor INTEGER NOT NULL, -- 金额(最小单位:分) + currency TEXT NOT NULL DEFAULT 'CNY', + status TEXT NOT NULL, -- CREATED/WAIT_PAY/PAID/... + payer TEXT, -- 付款方标识 + pay_url TEXT, -- 支付链接/二维码/链上地址 + return_url TEXT, + notify_url TEXT, + created_at TEXT NOT NULL, + paid_at TEXT, + notified_at TEXT, + UNIQUE (channel, channel_tx_id) -- 防回调重放 +); +CREATE INDEX idx_orders_status ON orders(status); +CREATE INDEX idx_orders_created ON orders(created_at); +CREATE INDEX idx_orders_channel ON orders(channel); + +-- 交易流水:资金审计,只增不改 +CREATE TABLE payment_events ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + order_id INTEGER NOT NULL, + event_type TEXT NOT NULL, -- created/paid/closed/failed/refunded + channel_tx_id TEXT, + amount_minor INTEGER NOT NULL, + raw_payload TEXT, -- 原始回调/区块数据 JSON + created_at TEXT NOT NULL +); +CREATE INDEX idx_events_order ON payment_events(order_id); + +-- 可靠回调投递(Outbox) +CREATE TABLE notify_tasks ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + order_id INTEGER NOT NULL, + payload TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'pending', -- pending/success/dead + retry_count INTEGER NOT NULL DEFAULT 0, + next_retry_at TEXT NOT NULL, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL +); +CREATE INDEX idx_notify_status ON notify_tasks(status, next_retry_at); + +-- 回调投递日志 +CREATE TABLE callback_logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + task_id INTEGER NOT NULL, + http_status INTEGER, + response TEXT, + latency_ms INTEGER, + created_at TEXT NOT NULL +); + +-- 应用日志(管理界面查询用,与文件日志双写) +CREATE TABLE app_logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + level TEXT NOT NULL, -- debug/info/warn/error + scope TEXT, -- 模块:order/channel/notify/scheduler + message TEXT NOT NULL, + trace_id TEXT, -- 请求追踪 ID + fields TEXT, -- 结构化字段 JSON + created_at TEXT NOT NULL +); +CREATE INDEX idx_logs_level_time ON app_logs(level, created_at); +CREATE INDEX idx_logs_scope_time ON app_logs(scope, created_at); +CREATE INDEX idx_logs_trace ON app_logs(trace_id); +``` + +### 4.3 资金安全约束(写库必须遵守) + +```go +// 状态变更必须条件更新(防回跳/防覆盖) +UPDATE orders SET status='PAID', paid_at=?, channel_tx_id=? +WHERE out_trade_no=? AND status='WAIT_PAY' +// 影响行数 = 0 → 状态非法,拒绝(幂等/防覆盖双保险) + +// 金额校验 +实付金额(amount_minor) == 订单金额 → 才允许 PAID +不等 → 记录 payment_events(event_type='amount_mismatch') + 告警 + 拒绝 + +// 回调去重 +INSERT INTO orders(channel, channel_tx_id) ... ON CONFLICT DO NOTHING +``` + +--- + +## 5. 交易不可篡改设计(资金审计账本) + +> 目标:**每一笔交易都不可篡改** —— 任何人(包括管理员/DBA)改任何一条记录都能被检测。 +> 方案:**哈希链 + 数字签名 + 只增不改 + 每日快照锚定**(企业级审计账本标准做法)。 + +### 5.1 哈希链(账本防篡改核心) + +``` +genesis(固定) → entry1 → entry2 → entry3 → ... → entryN + ↑ ↑ ↑ + prev_hash prev_hash prev_hash ← 每条都链接前一条 + +entry_hash(i) = SHA256( prev_hash(i-1) + payload_hash(i) ) +payload_hash(i) = SHA256( 事件全部内容: 订单号+金额+状态+通道交易号+时间+原始数据 ) +``` + +**篡改任意一条 → 它后面的所有 entry_hash 全部失配 → 校验必失败。** +**数据库整体被替换 → 与每日快照锚点比对 → 发现。** + +### 5.2 数字签名(防伪造 + 来源验证) + +- 独立**审计密钥对**(与支付签名密钥物理分离),私钥存 env/密钥文件 +- 每条 entry 的 `entry_hash` 用私钥 **ECDSA 签名** +- 任何人想改数据必须重新签名 → 没有私钥就无法伪造合法记录 +- 验签失败 = 数据被改过或来源可疑 → 立即告警 + +### 5.3 数据库层硬约束(SQLite/PG 双实现) + +```sql +-- 账本表:只追加,禁止修改删除(SQLite 触发器强制) +CREATE TABLE audit_ledger ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + prev_hash TEXT NOT NULL, -- 前一哈希(链) + payload_hash TEXT NOT NULL, -- 本条事件内容哈希 + entry_hash TEXT NOT NULL, -- SHA256(prev+payload) + signature TEXT NOT NULL, -- ECDSA(entry_hash) + event_id INTEGER, -- 关联 payment_events.id + created_at TEXT NOT NULL +); + +-- 禁止 UPDATE / DELETE(数据库层防篡改) +CREATE TRIGGER trg_ledger_no_update BEFORE UPDATE ON audit_ledger +BEGIN SELECT RAISE(ABORT, 'audit_ledger is append-only'); END; +CREATE TRIGGER trg_ledger_no_delete BEFORE DELETE ON audit_ledger +BEGIN SELECT RAISE(ABORT, 'audit_ledger is append-only'); END; +``` + +> PG 版:DB 账号只授予 INSERT/SELECT 权限(无 UPDATE/DELETE),双保险。 + +### 5.4 每日快照(外部锚定点) + +```sql +CREATE TABLE daily_snapshots ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + snapshot_date TEXT NOT NULL UNIQUE, + last_hash TEXT NOT NULL, -- 当日最后 entry_hash(锚点) + signature TEXT NOT NULL, -- 对锚点签名 + created_at TEXT NOT NULL +); +``` + +- 每日 00:00 生成快照:当前链尾哈希 + 签名 +- 可选增强:把快照哈希发布到外部(邮件/对象存储/链上)→ 防数据库整体替换 +- **改任何历史数据 → 与快照锚点比对即发现** + +### 5.5 完整性校验任务(scheduler 每日 + 手动) + +```go +VerifyLedger() → 从 genesis(或上一快照)重算整条哈希链 + → 校验每条 ECDSA 签名 + → 比对 daily_snapshots 锚点 + → 全过 → INFO 记录"账本完整" + → 任何失配 → ERROR + 管理后台红色告警(重大安全事件) +``` + +### 5.6 资金事件写入流程(唯一入口,防绕过) + +``` +通道回调/监听 → order服务校验通过 + → ① payment_events INSERT(业务库,只增) + → ② audit_ledger.Append(事件内容) ← 哈希链+签名(独立账本库 ledger.db) + → ③ 账本写入失败 → 回滚订单状态 + 告警(资金事件必须有账本记录) +``` + +**所有资金变更只能走这一个入口**,不允许其他代码直接写事件表。 +> 注:账本在独立数据库中,无法与业务库同事务;因此采用"业务库先记事件 → 账本库追加 → 失败则回滚订单状态并告警"的顺序,确保资金事件必有账本记录。 + +### 5.7 管理后台联动 + +| 功能 | 说明 | +|------|------| +| 账本状态 | 显示最后校验时间/结果/链长 | +| 手动校验 | 按钮触发全链校验 | +| 篡改告警 | 校验失败 → 顶部红色横幅 + 告警日志 | +| 事件详情 | 每条事件可看 payload_hash / signature / 原始数据 | + +### 5.8 独立账本数据库(ledger.db,安全隔离) + +> **架构升级**:账本从业务库中独立出来,单独一个 SQLite 文件,与订单/日志/回调完全隔离。 + +``` +paygateway.db(业务库) ledger.db(账本库 🛡️) +├── orders ├── audit_ledger +├── payment_events └── daily_snapshots +├── notify_tasks +├── callback_logs +├── app_logs +└── settings +``` + +**隔离带来的安全提升:** + +| 防护层 | 机制 | 效果 | +|--------|------|------| +| 文件隔离 | 账本独立 `ledger.db` | 删业务库不影响账本,审计线索永存 | +| 文件权限 | `chmod 0600` | 仅文件所有者可读写,其他用户/进程无法访问 | +| 触发器 | 两个表均有 UPDATE/DELETE 触发器 | 通过 SQL 无法修改/删除任何账本数据 | +| WAL 模式 | `PRAGMA journal_mode=WAL` | 崩溃安全,写入不丢数据 | +| FULL 同步 | `PRAGMA synchronous=FULL` | 每次写入等待落盘,防断电丢数据 | +| 连接隔离 | `SetMaxOpenConns(1)` | 单连接串行写,避免并发写入冲突 | + +**实现方式:** + +```go +// store.LedgerDB 独立账本存储(internal/store/ledgerdb.go) +ledgerDB, err := store.NewLedgerDB("ledger.db") // 0600 + WAL + 触发器 +lg, err := ledger.New(ledgerDB, cfg.LedgerPrivKey) // 账本只依赖独立库 +// admin 同时注入业务库 + 账本库 +adminSrv := admin.New(st, ledgerDB, ...) +``` + +### 5.9 管理后台 API 与前端 + +**账本相关 API:** + +| 端点 | 说明 | +|------|------| +| `GET /api/ledger` | 账本条目列表(哈希链可视化) | +| `POST /api/ledger/verify` | 手动触发全链校验(哈希链 + 签名 + 快照锚点) | +| `GET /api/ledger/snapshots` | 每日快照列表 | +| `POST /api/ledger/snapshot` | 手动创建当日快照 | +| `GET /api/ledger/public-key` | 返回 ECDSA P256 公钥(供第三方验证) | +| `GET /api/ledger/export` | 导出账本 + 快照为 CSV(审计报告) | + +**前端功能(Vue3 Ledger 页面):** + +- 📋 **账本条目**:哈希链可视化(每条含 prev_hash / entry_hash / 签名) +- 🔍 **校验账本**:一键全链校验,弹窗展示结果(通过/失败 + 错误明细 + 快照锚点状态) +- 📸 **快照管理**:卡片列表 + 详情弹窗 + **锚点核对**(对比快照链尾与当前链尾) +- 🔑 **账本公钥**:展示 PEM 公钥,可复制供外部审计验证 +- ⬇️ **导出 CSV**:一键下载完整审计报告(含所有条目 + 快照) +- 🔗 **事件跳转**:点击事件 ID 跳转订单页搜索 + +**测试覆盖(internal/ledger/ledger_test.go):** + +| 测试 | 验证点 | +|------|--------| +| TestAppendAndVerify | 追加 + 全链校验通过 | +| TestHashChainLinked | 哈希链正确连接 | +| TestTamperDetected | 篡改条目 → 校验失败 | +| TestTamperSignatureDetected | 篡改内容 → 签名校验失败 | +| TestSnapshotCreateAndVerify | 快照创建 + 锚点匹配 | +| TestSnapshotAnchorMismatch | 数据库替换 → 锚点不匹配 | +| TestSignatureUnique | 防重放(同内容不同事件不同签名) | + +--- + +## 6. 日志系统设计(健壮 + 可查询) + +### 5.1 双写架构:文件(排障)+ 数据库(查询) + +``` +应用 → slog(结构化) → ① 文件轮转日志(/var/log/paygateway/*.log) + → ② 异步批量写 app_logs 表(管理界面查询) +``` + +### 5.2 技术选型 + +| 项 | 方案 | +|----|------| +| 日志库 | Go 标准库 **slog**(结构化,JSON 输出) | +| 轮转 | `gopkg.in/natefinch/lumberjack.v2`(按大小/天数切割,防磁盘爆) | +| 写库 | 异步批量(攒 50 条或 1s flush),避免日志拖慢交易主链路 | +| 级别 | debug / info / warn / error(env 可配) | +| 追踪 | 每个 HTTP 请求生成 `trace_id`(中间件),贯穿 下单→回调→通知 全链路 | + +### 5.3 关键审计日志(钱相关的必须全记) + +```go +// 资金事件:writeEvent(orderID, "paid", amountMinor, rawPayload) +// - created / paid / closed / failed / amount_mismatch / refunded +// 回调事件:writeEvent(orderID, "notify_attempt", ..., httpStatus) +// 安全事件:writeEvent(orderID, "verify_failed", ..., 原因) ← 验签失败必须告警 +``` + +--- + +## 7. Web 管理后台设计 + +### 6.1 形态:同进程嵌入式(单二进制部署) + +- 同一 Go 进程,路由组 `/admin/*`(端口可独立配置 `ADMIN_ADDR`) +- 内置 HTML/JS/CSS(`embed` 打包),无前端构建链 +- **登录鉴权**:管理 token(`ADMIN_TOKEN` env)+ 可选 Basic Auth;生产必须启用 +- **接口全部走 `/admin/api/*`**,返回 JSON + +### 6.2 功能清单 + +| 页面 | 功能 | +|------|------| +| **仪表盘** | 今日订单量/交易额/成功率/待通知数/告警数(卡片 + 简易图表) | +| **订单管理** | 列表(状态/通道/金额/时间筛选,分页)→ 详情(状态机时间线 + 支付凭据 + 原始回调) | +| **交易流水** | payment_events 查询(按订单/事件类型/时间) | +| **日志查看** | app_logs 查询(级别/模块/trace_id 过滤,实时 tail) | +| **回调管理** | notify_tasks 列表;**手动补发**按钮(dead 任务重投);重试状态 | +| **通道管理** | 已注册通道列表 + 启用/停用开关(配置驱动) | +| **对账报告** | 定时对账结果(订单 vs 通道实付) | +| **账本校验** | 哈希链完整性校验 + 篡改告警(红色横幅) | +| **系统状态** | 运行时长/goroutine/内存/最近错误 | + +### 6.3 页面路由规划 + +``` +GET /admin/login # 登录页 +GET /admin/ # 仪表盘(SPA,JS 拉 /admin/api/*) +GET /admin/orders # 订单页 +GET /admin/logs # 日志页 +GET /admin/notify # 回调页 +GET /admin/api/dashboard # JSON API +GET /admin/api/orders?status=&channel=&page= +GET /admin/api/orders/:no # 订单详情(含事件流) +GET /admin/api/logs?level=&scope=&trace=&page= +GET /admin/api/notify/tasks?status= +POST /admin/api/notify/:id/retry # 手动补发 +GET /admin/api/channels # 通道状态 +GET /admin/api/health # 系统状态 +``` + +--- + +## 8. 目录结构(最终目标态) + +``` +paygateway/ +├── cmd/gateway/main.go # 入口:组装 config/store/registry/router +├── internal/ +│ ├── config/ # .env 加载 + 通道密钥管理 +│ ├── model/ # Order / 状态常量 / 金额类型 +│ ├── logger/ # slog 封装:文件轮转 + 异步写库 + trace_id +│ ├── epay/ # 易支付协议(submit/api/notify 签名) +│ ├── rest/ # REST API(预留) +│ ├── order/ # 编排层:状态机/幂等/金额校验/对账 +│ ├── channel/ +│ │ ├── registry.go # 注册表 + Get() +│ │ ├── interface.go # PaymentChannel / PayRequest / ... +│ │ ├── alipay/ # 支付宝(gopay)——现有代码迁入 +│ │ ├── wechat/ # 微信(gopay)——未来 +│ │ ├── paypal/ # PayPal(gopay)——未来 +│ │ └── crypto/ # ★ 预留:stub + README(未来链上) +│ ├── store/ # Store 接口 + sqlite(默认)/pg 实现 +│ │ ├── store.go # Store 接口 + 数据模型 +│ │ ├── sqlite.go # 业务库 SQLite 实现(订单/事件/日志) +│ │ ├── sqlite_rest.go # 业务库查询实现 +│ │ └── ledgerdb.go # ★ 独立账本库(ledger.db,0600 权限 + 触发器) +│ ├── notify/ # outbox worker + 重试 +│ ├── ledger/ # ★ 资金审计账本(哈希链+签名+校验) +│ ├── scheduler/ # 关单/补偿/对账/账本校验定时任务 +│ ├── admin/ # ★ 管理后台:路由 + 页面(embed) + API +│ ├── web/ # 二维码页/中转页渲染 +│ └── metrics/ # Prometheus(预留) +├── migrations/ # SQL 迁移(sqlite/pg) +├── docs/ARCHITECTURE.md # 本文档 +└── go.mod +``` + +--- + +## 9. 实施计划(保持现有功能不回归) + +| 阶段 | 内容 | 验收 | +|------|------|------| +| **P1 基础重建** | 目录分层 + Store 接口 + **SQLite 落地** + 支付宝迁入 `channel/alipay` + 日志系统(文件+DB) + **audit_ledger 哈希链** | 现有沙箱链路回归 ✅;订单/日志/账本入库 ✅ | +| **P2 管理后台** | `/admin` 页面(仪表盘/订单/日志/回调)+ API + token 鉴权 | 浏览器可查订单、日志、交易流水 | +| **P3 资金安全** | 状态机条件更新 + 金额校验 + 幂等 + outbox 回调重试 + **账本完整性校验任务** + **独立账本库 ledger.db** + **账本管理前端** | 模拟重复/篡改回调不产生错误;篡改一条记录 → 校验失败 ✅;账本独立库 + 0600 权限 + 触发器 ✅;前端校验/快照/公钥/导出 ✅ | +| **P4 通道扩展** | 用 gopay 接微信(验证"3 步加通道"流程) | 微信沙箱链路跑通 | +| **P5 crypto 预留** | crypto stub + README + 注册表支持 ListenerKind | `type=usdt` 返回"未启用" | +| **P6 生产化** | 按你们基础设施切 PG + 监控 + 对账 + 部署脚本 | 生产验收 | + +--- + +## 10. 为什么这是最佳实践 + +1. **不重复造轮子**:9 种支付 SDK 由 gopay 维护,paygateway 只做编排 → 通道越多,收益越大 +2. **加支付方式像插 U 盘**:新通道 = 新包 + 注册 + 配置,核心零改动 +3. **协议层稳定**:易支付/REST 接口永不变 → 现有上游(New API)不受影响 +4. **资金安全集中**:所有通道都过同一套状态机/金额校验/幂等 → 只维护一套安全逻辑 +5. **链上预留**:接口已含 ListenerKind + Address 凭据 → 未来 crypto 落地不动架构 +6. **存储可换**:SQLite(单机起步) → PG(生产),`Store` 接口平滑切换 +7. **可观测性内置**:日志双写(文件+DB)+ 管理后台 → 出问题能查、能补、能对账 +8. **交易不可篡改**:哈希链 + 签名 + 只增不改 + 每日快照 → 任何篡改必被发现 diff --git a/docs/DESIGN-DEVICE-ACTOR.md b/docs/DESIGN-DEVICE-ACTOR.md new file mode 100644 index 0000000..9812c68 --- /dev/null +++ b/docs/DESIGN-DEVICE-ACTOR.md @@ -0,0 +1,25 @@ +# DeviceActor:设备操作串行化 + +## 背景 + +多个后台任务可能同时需要访问同一台授权设备。直接并发执行会导致前台状态、读取结果和恢复动作互相干扰,因此设备访问需要统一的串行化入口。 + +## 模型 + +```text +caller -> queue -> DeviceActor -> device adapter + | + +-> lease / timeout / health event +``` + +- 一个设备同一时刻只有一个 active actor; +- 请求通过队列进入,调用方等待结果或超时返回; +- lease 到期后自动释放,避免异常任务永久占用设备; +- 所有失败转化为结构化健康事件并可触发告警; +- 手动动作和自动动作不能绕过 actor 直接操作设备。 + +## 适用边界 + +该模型适用于授权的移动设备测试、UI 自动化和设备实验室。它不提供未经授权访问第三方账户或绕过平台安全控制的方法。 + +实现位于 `internal/ledgermon/` 和 `internal/adbmgr/`,测试位于对应的 `*_test.go` 文件。 diff --git a/docs/DESIGN-FAIR-SCHEDULER.md b/docs/DESIGN-FAIR-SCHEDULER.md new file mode 100644 index 0000000..f06cd9f --- /dev/null +++ b/docs/DESIGN-FAIR-SCHEDULER.md @@ -0,0 +1,20 @@ +# 公平设备调度:设计概览 + +本文档描述多任务设备池的通用调度原则,不包含特定平台的账号、页面或操作细节。 + +## 目标 + +- 在设备能力上限内分配任务; +- 避免单个账户或任务长期占用设备; +- 设备异常时快速降权或排除; +- 让调度结果可解释、可观测、可测试。 + +## 评分因素 + +调度器可以综合考虑当前负载、历史任务耗时、设备健康状态、最近失败次数、账户冷却时间、任务优先级、设备能力上限和队列长度。 + +调度器只负责选择可用执行资源,不负责确认业务事实。业务确认仍由订单状态机、匹配器和审计账本完成。 + +## 安全边界 + +设备操作必须经过 actor 串行化和授权检查。设备离线、任务超时或结果不明确时,调度器应停止继续分配并生成健康事件,而不是猜测任务已成功。 diff --git a/docs/DESIGN-NOTIFY-RETRY-LATEST-URL.md b/docs/DESIGN-NOTIFY-RETRY-LATEST-URL.md new file mode 100644 index 0000000..cd40aa1 --- /dev/null +++ b/docs/DESIGN-NOTIFY-RETRY-LATEST-URL.md @@ -0,0 +1,224 @@ +# dead 通知手动重试(用最新回调地址) + 删除 实施设计(DESIGN-NOTIFY-RETRY-LATEST-URL) + +> 状态:**已实现**(`order.Service.BuildNotifyPayload` 单一来源;`handleRetryDead` 重发前按订单最新 +> NotifyURL 重建 payload 并重新签名;新增 `handleDeleteDead` + `DeleteNotifyTask`;store 新增 +> `GetNotifyTask`/`UpdateNotifyTaskPayload`;Reconciler.vue 死信页增删除按钮,NotifyTasks.vue 手动重试接入真实 API) +> 关联:docs/RESILIENCE.md(对账兜底)、docs/ARCHITECTURE.md(outbox 回调队列) +> 影响模块:`internal/order`、`internal/store`、`internal/admin/reconciler`、`web/admin` +> 目标:**死信/失败回调任务手动重试时,用订单当前最新的 notify_url 重建 payload 并重新签名投递, +> 解决「配置时 notify_url 错误 → 重发仍打到旧地址 → 一直回调不了 → 死信」;并提供彻底删除死信任务的能力。** + +--- + +## 1. 背景与问题(现状代码事实) + +### 1.1 Outbox 回调队列现状 + +- 生产者 `order.Service.EnqueueNotify`(internal/order/service.go:1126):支付成功/人工入账后调用, + 构造易支付协议参数(含 `notify_url` = `o.NotifyURL`),`epay.BuildSignedParams(params, signKey(pid))` + 签名,JSON 序列化为 `payload`,`CreateNotifyTask` 落库。 +- 消费者 `notify.Worker`(internal/notify/worker.go):`process` 解析 payload, + `notifyURL := params["notify_url"]`(worker.go:88)作为发送地址,body = 全部 params(含签名)。 + 超过 `MaxRetries=6` 标记 `dead`(worker.go:142-144)。 +- 状态:`pending` / `success` / `dead`(store.go:121)。 + +### 1.2 关键缺陷:payload 固化 notify_url,重放不刷新 + +- `EnqueueNotify` 在**任务创建时**把 `o.NotifyURL` 写进 payload。 +- worker 发送从 **payload** 读 `notify_url`,不是订单当前值。 +- 现有死信重发 `admin.Handler.handleRetryDead`(internal/admin/reconciler.go:287)只调 + `UpdateNotifyTask(ctx, id, "pending", 0, now)`——仅更新 `status/retry_count/next_retry_at/updated_at`, + **完全不碰 payload**。因此死信重发仍打到**旧地址**。 +- 后果:商户配置时把 notify_url 配错,或之后改了回调地址,死信重发永远打到旧地址 → 一直回调不了。 + +### 1.3 现状已有 vs 缺失 + +| 能力 | 现状 | +|---|---| +| 死信列表 | 已有 `GET /api/reconciler/notify-dead`(handleListDead) | +| 死信重发 | 已有 `POST /api/reconciler/notify-dead/:id/retry`,但**不刷新地址** | +| NotifyTasks.vue 手动重试 | **占位**(web/admin/src/views/NotifyTasks.vue:123-126,只弹提示) | +| 彻底删除死信 | **完全没有**(无 DeleteNotifyTask / DELETE 路由) | + +--- + +## 2. 设计决策 + +### 2.1 重试用订单最新 notify_url(核心) + +重发前:通过 `task.OrderID` → `GetOrderByID` 取订单,**用订单当前 `NotifyURL` 重建 payload 并重新签名**, +再重置为 `pending`。理由: +- worker 发送地址 = payload 里的 `notify_url`,所以必须重建 payload 才能换地址。 +- 商户端按"收到的全部参数重算签名"验签(worker.go:94-96 注释),改了 notify_url 必须**重新签名**, + 否则验签失败。因此必须复用 `EnqueueNotify` 的完整构造+签名逻辑,不能只改 notify_url 字符串。 + +**单一来源重构**:把 `EnqueueNotify` 的 payload 构造逻辑提取为 `Service` 的导出方法 +`BuildNotifyPayload(o *model.Order) string`,`EnqueueNotify` 与重试 handler 都调用它, +保证字段与签名完全一致、单一来源。 + +### 2.2 彻底删除死信任务 + +提供 `DELETE` 语义:新增 `handleDeleteDead`(`POST /reconciler/notify-dead/:id/delete`)。 +**风险提示**:若订单已支付但通知未送达,删除后无法再自动补发(人工可重新入队兜底,见下)。属"确认放弃该笔通知"操作。 + +### 2.3 前端 + +- `Reconciler.vue` 死信页:现有"重发"按钮后端已会刷新地址(无需前端改逻辑);新增"删除"按钮(二次确认)。 +- `NotifyTasks.vue`:修正占位手动重试——复用 `retryDeadNotify`/`delete`,接入真实后端;或移除占位避免误导。 + (最小改动:让占位按钮调用真实 retry API。) + +--- + +## 3. 实现细节 + +### 3.1 `internal/order/service.go` — payload 构造单一来源 + +新增导出方法(含签名),`EnqueueNotify` 内部复用它: + +```go +// BuildNotifyPayload 构造易支付协议回调参数并签名,返回 JSON payload。 +// 供 EnqueueNotify(下单入队)与死信手动重发(用订单最新 notify_url)复用,保证字段/签名单一来源。 +func (s *Service) BuildNotifyPayload(o *model.Order) string { + params := map[string]string{ + "trade_no": o.GatewayNo, + "out_trade_no": o.OutTradeNo, + "type": o.Channel, + "name": o.Subject, + "money": o.MoneyYuan(), + "trade_status": "TRADE_SUCCESS", + "notify_url": o.NotifyURL, + } + if o.Attach != "" { + params["attach"] = o.Attach + } + if o.ChannelTxID != "" { + params["trade_no"] = o.ChannelTxID + } + epay.BuildSignedParams(params, s.signKey(o.Pid)) + payload, _ := json.Marshal(params) + return string(payload) +} + +func (s *Service) EnqueueNotify(ctx context.Context, o *model.Order) { + payload := s.BuildNotifyPayload(o) + taskID, err := s.store.CreateNotifyTask(ctx, o.ID, payload) + // ...原有逻辑不变 +} +``` + +### 3.2 `internal/store/store.go` — 接口新增 + +```go +// UpdateNotifyTaskPayload 更新回调任务 payload(死信重发时按订单最新 notify_url 重建) +UpdateNotifyTaskPayload(ctx context.Context, id int64, payload string) error +// DeleteNotifyTask 删除回调任务(彻底移除死信,确认放弃该笔通知) +DeleteNotifyTask(ctx context.Context, id int64) error +``` + +### 3.3 `internal/store/sqlite_rest.go` — 实现 + +```go +func (s *SQLiteStore) UpdateNotifyTaskPayload(ctx context.Context, id int64, payload string) error { + _, err := s.db.ExecContext(ctx, + `UPDATE notify_tasks SET payload=?, updated_at=? WHERE id=?`, payload, nowStr(), id) + return err +} + +func (s *SQLiteStore) DeleteNotifyTask(ctx context.Context, id int64) error { + _, err := s.db.ExecContext(ctx, `DELETE FROM notify_tasks WHERE id=?`, id) + return err +} +``` + +### 3.4 `internal/admin/reconciler.go` — 重发刷新地址 + 删除 + +```go +func (h *ReconcilerHandler) handleRetryDead(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + operator := c.GetString("username") + ctx := c.Request.Context() + + // 取任务 + tasks, err := h.store.ListNotifyTasks(ctx, "dead", 100) // 或新增 GetNotifyTask(id) + // (List 按 id DESC,需从列表定位 task;更干净:新增 GetNotifyTask(id)) + var task *store.NotifyTask + for _, t := range tasks { if t.ID == id { task = t; break } } + if task == nil { c.JSON(404, ...); return } + + // 取订单,用最新 NotifyURL 重建 payload + 重签 + o, err := h.store.GetOrderByID(ctx, task.OrderID) + var payload string + if err != nil || o == nil { + // 订单丢失:保留原 payload(无法重建),仅重置 pending + payload = task.Payload + } else { + payload = h.svc.BuildNotifyPayload(o) + } + + if err := h.store.UpdateNotifyTaskPayload(ctx, id, payload); err != nil { ... } + h.store.UpdateNotifyTask(ctx, id, "pending", 0, time.Now()) + logger.Warn("admin", "dead 通知重发(用最新 notify_url)", "task_id", id, "operator", operator) + c.JSON(http.StatusOK, gin.H{"data": "已按最新回调地址重新入队"}) +} + +func (h *ReconcilerHandler) handleDeleteDead(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + operator := c.GetString("username") + if err := h.store.DeleteNotifyTask(c.Request.Context(), id); err != nil { ... } + logger.Warn("admin", "dead 通知已删除", "task_id", id, "operator", operator) + c.JSON(http.StatusOK, gin.H{"data": "已删除"}) +} +``` + +> 更干净:store 新增 `GetNotifyTask(ctx, id)` 避免在列表中扫描定位。**设计决策:新增 `GetNotifyTask`**。 + +### 3.5 路由 + +```go +rg.POST("/reconciler/notify-dead/:id/retry", h.handleRetryDead) +rg.POST("/reconciler/notify-dead/:id/delete", h.handleDeleteDead) // 新增 +``` + +### 3.6 前端 `api.js` + `Reconciler.vue` + +```js +// api.js +deleteDeadNotify: (id) => request(`/reconciler/notify-dead/${id}/delete`, { method: 'POST' }), +``` + +`Reconciler.vue` 死信行操作区加"删除"按钮(ElMessageBox.confirm 二次确认), +重发按钮保持(后端已刷新地址)。 + +### 3.7 `NotifyTasks.vue` 占位手动重试 + +将 `manualRetry` 占位改为调用真实 retry API(当前选中任务 `current.id`): + +```js +async function manualRetry() { + try { await api.retryDeadNotify(current.value.id); ElMessage.success('已按最新回调地址重新入队'); } + catch (e) { ElMessage.error('重试失败: ' + e.message) } + finally { payloadDialog.value = false; load() } +} +``` + +> 依赖 api.js 暴露 `retryDeadNotify`(已有,api.js:148)。 + +--- + +## 4. 边界与风险 + +| 场景 | 处理 | 风险 | +|---|---|---| +| 订单已删除/丢失 | 无法重建 payload → 保留原 payload 仅重置 pending | 仍打旧地址;提示人工 | +| 订单 NotifyURL 为空 | 重建后 notify_url 空 → worker 标记 success 跳过 | 极端;需人工 | +| 删除已支付但未通知的死信 | 不再自动补发,人工可从异常池/重新入队 | 资金通知可能丢失——删除前二次确认 | +| 并发(worker 同时取到同任务重试) | 重置 pending 后 next_retry_at=now,worker 下一轮即发 | 重复投递一次,幂等可接受 | +| NotifyTasks 全量页与 Reconciler 死信页 | 两处都接入真实 API | 保持一致 | + +--- + +## 5. 回归测试 + +- `internal/store`:新增 `UpdateNotifyTaskPayload` / `DeleteNotifyTask` 的单测(落库、读回、删除)。 +- `internal/admin`:`handleRetryDead` 重建 payload 用最新 notify_url(伪造 task+order,断言 payload 内 notify_url 为最新值且签名有效)。 +- `internal/order`:`BuildNotifyPayload` 与 `EnqueueNotify` 产物一致(单一来源)。 diff --git a/docs/DESIGN-REUSE-UPDATE-AMOUNT.md b/docs/DESIGN-REUSE-UPDATE-AMOUNT.md new file mode 100644 index 0000000..af288e2 --- /dev/null +++ b/docs/DESIGN-REUSE-UPDATE-AMOUNT.md @@ -0,0 +1,86 @@ +# 订单金额不可变与买家订单替换设计 + +> 状态:**已修正**(原“复用订单覆盖金额”方案已废弃) +> 关联:docs/MIANQIAN.md、docs/RESILIENCE.md +> 修正原因:生产事故中,上游先创建 10 元订单,买家随后改为 1 元;网关把新请求错误地复用到旧订单号并覆盖金额,导致 1 元支付回调到了上游 10 元订单,新的 1 元上游订单则保持 pending。 + +## 1. 事故根因 + +易支付上游的 `out_trade_no` 与金额是不可变业务主键。旧实现的买家级串行保护在发现同一环境存在 WAIT_PAY 订单时,无论本次请求的订单号是否不同,都调用旧订单的 `reuseOrCreateError`: + +```text +上游 OLD=10 元 -> 网关创建 OLD WAIT_PAY +上游 NEW=1 元 -> buyer_key 命中 OLD,网关没有创建 NEW +网关把 OLD 改成 1 元并刷新 OLD 凭据 +用户支付 1 元 -> 网关回调 OLD+1 元 +上游按 OLD 的原始 10 元订单归属回调,NEW 订单无回调保持 pending +``` + +生产日志中可见 `pending=OLD,new=NEW`,随后凭据仍以 OLD 的订单号创建。 + +## 2. 不可变规则 + +### 2.1 同一订单号 + +同一 `out_trade_no` 一旦存在,金额不能变化: + +| 请求 | 处理 | +|---|---| +| 同号、同金额 | 幂等复用原订单 | +| 同号、不同金额 | 直接拒绝,提示使用新订单号 | +| 同号、已成功 | 返回已处理 | +| 同号、已关闭、同金额 | 按原有同号复活语义处理 | +| 同号、已关闭、不同金额 | 拒绝,不复活改价 | + +网关不再调用 `UpdateOrderAmount` 修改已存在订单。该方法保留在 store 接口中仅为兼容现有实现,当前订单流程不应调用它。 + +### 2.2 不同订单号 + +个人免签通道中,同一 `buyer_key/fp_key` 只能有一个有效 WAIT_PAY,但金额变化必须通过新订单号实现: + +1. 对旧 WAIT_PAY 执行条件更新 `WAIT_BUYER_PAY -> TRADE_CLOSED`。 +2. 仅首次 CAS 成功时写一条 `replaced` 事件,释放旧备注码并移出 WAIT_PAY 池。 +3. 继续正常创建请求中的新订单号、金额、凭据和 `CreatedAt`。 +4. 新订单进入 WAIT_PAY 池,动态 `CloseAt` 取设备窗口和 `SessionExpiresAt` 的较早值;前端倒计时与后端一致,但不得超过原支付会话硬截止时间。 + +旧订单号绝不能被新订单请求复用。商家通道不启用买家级个人免签锁,保持按订单号自身幂等。 + +### 2.3 支付会话生命周期 + +订单替换不能通过新的 `CreatedAt` 无限续命。个人订单首次创建时持久化 +`SessionExpiresAt`(默认使用当前订单关闭窗口),替换订单继承该值;WAIT_PAY 池和支付页倒计时 +都使用 `min(设备动态窗口截止时间, SessionExpiresAt)`。 + +当 `SessionExpiresAt` 已到期时,新的金额/订单号替换请求直接拒绝,要求重新发起充值。这样可以同时满足: + +- 支持买家在会话内改变充值金额; +- 同一时间同一环境只有一个 WAIT_PAY; +- 连续改变金额不能永久占用账户、监听和备注码资源。 + +## 3. 并发与资源 + +替换使用现有 `UpdateOrderStatus` 的条件更新作为 CAS: + +- CAS 成功:旧订单关闭,记录 `replaced`,释放备注码,移除 WAIT_PAY 池,然后创建新单。 +- CAS 失败且旧单已成功:结算胜出,不创建新单,避免重复收款。 +- CAS 失败且旧单已关闭:允许当前新订单继续正常创建,但不重复写替换事件或释放资源。 + +替换不是普通超时关单,不调用通用 `CloseOrder`,避免产生重复 generic `closed` 事件、日志和异常池记录。 + +旧二维码对应旧订单,订单关闭后按现有状态/过期逻辑失效;旧订单迟到支付不会转移到新订单,继续走关闭订单的人工核查保护。 + +## 4. 个人免签匹配安全 + +个人免签金额匹配使用 DB 中 `status=WAIT_BUYER_PAY`、精确 `amount_minor` 和收款时间窗口;`SettlePayment` 再次严格校验实付金额与订单金额。因此本次修复不改变匹配算法,修复点是下单时不能吞掉新订单号。 + +## 5. 回归测试 + +`internal/order/service_test.go` 覆盖: + +1. 旧 10 元 WAIT_PAY + 新订单号 1 元:旧单关闭,新订单号创建,金额/回调身份使用新单。 +2. 同订单号不同金额:拒绝,DB 金额、状态、凭据和事件不变。 +3. 同订单号同金额:幂等复用。 +4. 替换只产生一条 `replaced` 事件,备注码和 WAIT_PAY 池只释放一次。 +5. 并发替换/结算:CAS 保证只能由一方获胜,不重复结算、不重复创建新单。 + +旧版“同号改金额并刷新凭据”的测试已改为金额不可变测试,避免把事故逻辑重新锁定为正确行为。 diff --git a/docs/MIANQIAN.md b/docs/MIANQIAN.md new file mode 100644 index 0000000..d3e83e5 --- /dev/null +++ b/docs/MIANQIAN.md @@ -0,0 +1,39 @@ +# 设备自动化与个人通道:学习概览 + +> 本文档只保留架构和工程实践概览,不提供特定平台的收款操作、设备坐标、账号配置、连接参数或规避平台限制的方法。相关实现代码保留在仓库中,供获得授权的学习和测试使用。 + +## 目标 + +个人通道是一个可选的实验性适配器,用于研究如何把外部设备事件接入订单状态机。它复用统一的订单、幂等、异常池、账本和告警基础设施,不改变商家通道的核心流程。 + +## 工程边界 + +- 设备访问通过受控的设备管理层封装,业务代码不直接散落调用设备命令。 +- 设备事件必须经过来源认证、时间窗口、金额校验和幂等去重。 +- 设备离线、解析失败、状态冲突和无法确认的事件进入异常池,不自动入账。 +- 设备调度使用串行化访问、租约、健康状态和负载感知,避免多个任务同时操作同一设备。 +- 任何个人账户、支付平台或设备的接入,都必须获得账户所有者授权并遵守适用的平台规则。 + +## 代码阅读入口 + +| 主题 | 目录 | +| --- | --- | +| 设备连接与健康检查 | `internal/adbmgr/` | +| 设备操作编排 | `internal/ledgermon/` | +| 事件读取与去重 | `internal/notifywatcher/`、`internal/admin/capture/` | +| 个人通道抽象 | `internal/channel/personal/`、`internal/personal/` | +| 订单确认与异常处理 | `internal/order/`、`internal/reconciler/` | +| 审计账本 | `internal/ledger/`、`internal/store/` | + +## 保留的通用实践 + +- DeviceActor/租约式设备访问串行化; +- 设备离线与任务超时的安全降级; +- 事件指纹、窗口匹配和重复回调幂等; +- 设备负载驱动的任务调度; +- 账本哈希链、签名和异常事件留痕; +- 运行状态、健康指标和告警。 + +## 免责声明 + +本项目仅用于软件工程学习、授权测试和合法的商户支付集成研究。个人收款、设备自动化和第三方平台接入可能受到平台条款、隐私保护和支付监管要求限制。部署者必须自行确认授权和合规性,不得用于绕过安全控制、未经授权访问账户、欺诈、洗钱或其他违法活动。 diff --git a/docs/OPERATIONS.md b/docs/OPERATIONS.md new file mode 100644 index 0000000..1487aa5 --- /dev/null +++ b/docs/OPERATIONS.md @@ -0,0 +1,53 @@ +# Operations + +本文档覆盖通用部署、备份、健康检查和账务异常处理。设备自动化只适用于授权测试环境,具体平台操作不在公开运维手册中展开。 + +## 部署原则 + +- 使用独立 Linux 用户和 `/opt/paygateway` 等专用目录; +- `.env`、数据库、日志、备份和密钥只放在部署主机,不提交到仓库; +- 使用 systemd 或受控容器运行,限制文件系统和网络权限; +- 启动后先检查配置自检和 `/api/health`; +- 生产升级前备份数据库并保留可回滚版本。 + +## 备份与恢复 + +使用 `deploy/backup.sh` 通过 SQLite 在线备份方式复制业务库。备份目录必须设置访问权限,备份文件不能公开上传。恢复前停止服务、校验备份文件、保留当前数据快照,并在恢复后运行账本校验和健康检查。 + +## 健康检查 + +关注: + +- HTTP 健康状态; +- 数据库可读写状态; +- 回调失败率; +- 通知队列和异常池积压; +- 账本追加与校验结果; +- 后台任务崩溃恢复日志; +- 授权设备的连接状态和读取器错误。 + +设备离线或读取结果不明确时,不应猜测业务已成功。相关订单应保持可追溯状态并进入人工核查流程。 + +## 订单与账务异常 + +异常处理遵循以下顺序: + +1. 先保留原始订单和事件记录; +2. 检查签名、金额、时间窗口和状态转换; +3. 重复事件只执行一次; +4. 无法确认的情况进入异常池; +5. 人工处理必须留下操作者、原因和结果; +6. 对账完成后运行账本链校验。 + +不要直接修改数据库中的订单状态或账本记录来“修复”问题。应使用应用层提供的补单、关闭或异常解决流程。 + +## 日志与隐私 + +- 日志不应包含密钥、完整支付凭证、个人信息、API Key 或原始设备标识; +- 调试日志仅在授权环境短时启用; +- 对外分享事故记录时只使用脱敏和聚合数据; +- 日志和数据库备份按最小权限保存并设置生命周期。 + +## 合规边界 + +部署者必须自行确认商户资质、支付平台条款、隐私保护、数据留存和适用监管要求。设备自动化不得用于未经授权访问账户、绕过安全控制或其他违法活动。 diff --git a/docs/PLAN-DYNAMIC-CLOSE-ALLOC.md b/docs/PLAN-DYNAMIC-CLOSE-ALLOC.md new file mode 100644 index 0000000..8b2982b --- /dev/null +++ b/docs/PLAN-DYNAMIC-CLOSE-ALLOC.md @@ -0,0 +1,388 @@ +# 动态关单与均衡分配:设计与实现基线 + +> 本文档记录动态关单和资源均衡的设计基线,代码以实际实现为准。 +> 关联:docs/DESIGN-FAIR-SCHEDULER.md(公平调度)、docs/MIANQIAN.md(设备自动化概览)、docs/RESILIENCE.md(对账兜底) +> 目标:**不漏单(不可商量底线)+ 高/低负载自适应 + 多设备负载均衡 + 性能/稳定性兼顾** +> +> 本文档覆盖两个模块的开发设计: +> - 模块一:WAIT_PAY 订单动态关单时间智能调度(含漏单路径修复) +> - 模块二:免签账户订单自动均衡分配(设备 × 账户两层) +> - 前置改造:WAIT_PAY 池从「全局单池」升级为「设备维度子池」 + +--- + +## 1. 现状代码事实(设计依据) + +### 1.1 关单链路(两条路径,安全检查不一致 ⚠️) + +**路径 A:`order.Service.CloseExpiredOrders`**(internal/order/service.go:821,main.go:738 每 60s 触发) + +```go +for _, o := range expired { + if ch, ok := channel.Lookup(o.Channel); ok && ch.ConfirmAbility() != channel.ConfirmByQuery { + // 个人/监听通道:直接超时关单 —— 无离线检查、无最后复查 ⚠️ + if s.closeExpiredOrder(ctx, o, nil, "") { closed++ } + continue + } + // 商家通道:关单前必查单 ... +} +``` + +**路径 B:`reconciler.Scheduler.confirmPersonal`**(internal/reconciler/scheduler.go:303) + +```go +if s.personalCheck != nil { + offline, reason := s.personalCheck(ctx, o) // 有离线检查 ✅ + if offline { + s.svc.EnqueueException(...) // 进异常池,绝不直接关 + return + } +} +// 监听端在线 + 无推送 + 未超复查阈值 → 才关单 +``` + +**结论**:路径 A 对个人通道**直接关单**,绕过路径 B 的离线检查——**设备离线时用户已付款但未监听到,订单被硬关 = 钱收了单关了 = 漏单**。这是本设计首先要修的。 + +### 1.2 WAIT_PAY 池(internal/pool/waitpay_pool.go) + +- 结构:`buckets[accountID][outTradeNo] *Entry` + `byNote` + `byAmount`,单 RWMutex,`atomic.Pointer[Snapshot]` 零锁快照 +- `Entry.CloseAt = CreatedAt + closeWindow`(**固定 30min**,`NewWaitPayPool(closeWindowMinutes)` 传入) +- `Snapshot.Accounts[i] = {AccountID, Count, OldestAge, MinTTL}`(按账户聚合) +- `DeviceScheduler`(ledgermon/scheduler.go:170)每设备一个 goroutine,读**全局快照**再过滤本设备账户(`onDevice` 映射) +- 订单 → 账户 → 设备映射:订单只有 `AccountID`;设备绑定在 personal.db(`Device.AccountID` 主账户 + `DeviceAccountIDs` 附加账户) + +### 1.3 选号链路(internal/channel/personal/personal.go:244) + +```go +func (c *Channel) selectServableAccount(ctx) *account.Account { + servable := c.servableAccountIDs(ctx) // adb 在线设备的账户集(主+附加) + return c.pool.SelectFrom(c.channelName(), "default", servable) // 加权随机,无负载感知 +} +``` + +- `AccountPool.SelectFrom`(account/pool.go:268):`filterAvailable`(状态/日限额/冷却)→ priority 分组 → `weightedRandom`(Weight 加权) +- **没有任何负载维度**:不感知设备上有多少待确认订单,不感知账户池内积压 + +### 1.4 可用信号清单(无需新增即可用) + +| 信号 | 来源 | 用途 | +|---|---|---| +| 设备 adb 在线 | `adbmgr.Online` 实时探测 | 关单前检查、分配硬过滤 | +| 每账户待确认数 | `WaitPayPool.Count(accountID)` | 账户负载 | +| 每账户池快照 | `Snapshot.Accounts[i]` | 负载/最久等待/MinTTL | +| 活跃账户数 | 池快照中 Count>0 的账户数 | 设备一轮耗时估算 | +| 账户状态/失败数/日限额 | `AccountPool`(normal/cooling/frozen/disabled) | 账户健康 | +| 单次 poll 成本 | 实测:支付宝≈4.3s、微信≈2.9s(dump 2.5s/OCR 0.7s) | 设备能力估算 | +| 调度器参数 | `BaseQuota=3, CapPct=0.6, DwellMax=3, UrgentTTL=8s` | 一轮耗时估算 | + +--- + +## 2. 设计原则(不可商量) + +1. **不漏单**:设备离线/未监听时绝不关单;关单前必须「最后复查」;宁可不关转人工,绝不误关。 +2. **动态窗口有物理锚点**:下限 = 用户支付操作时间 + 一轮完整确认(5min);上限 = 用户体验红线(10min),超过转人工升级处理,不是无限挂起。 +3. **按设备负载伸缩,非全局统一**:窗口由订单所在设备的实时负载决定。 +4. **设备是物理瓶颈**:分配先看设备(最闲最健康),再看设备内账户(最闲最健康);只惩罚短板,不叠加长处。 +5. **超限显式处理**:设备超限 → 换设备;全部超限 → 拒绝下单 + 日志告警建议加设备。 +6. **已付款用户秒级到账**:窗口只影响「未支付订单」何时关,不影响确认链路(notify/ledger 照常秒级入账)。 + +--- + +## 3. 前置改造:WAIT_PAY 池 → 设备维度子池 + +### 3.1 数据结构 + +```go +// internal/pool/waitpay_pool.go —— 总池改造成设备维度管理 + +type WaitPayPool struct { + mu sync.RWMutex + devices map[string]*devicePool // deviceID → 设备子池(新增) + // 账户→设备 解析(main.go 注入;解析不到 → 归入 "" 兜底桶,Reconcile 修复) + deviceOf func(accountID int64) (deviceID string, ok bool) + snap atomic.Pointer[Snapshot] // 全局快照(notifywatcher 的 Total>0 判断用,保持) + closeWindow time.Duration // 仅作兜底(设备信息缺失时) + winCalc func(deviceLoad int) time.Duration // 动态窗口计算器(模块二注入;nil=固定窗口) +} + +type devicePool struct { + buckets map[int64]map[string]*Entry + byNote map[int64]map[string]string + byAmount map[int64]map[int64]map[string]struct{} + // 设备级聚合(每子池独立快照,调度器/分配器零锁读) + stat atomic.Pointer[DeviceStat] +} + +type DeviceStat struct { + DeviceID string + Total int // 该设备 WAIT_PAY 总数(设备负载) + Accounts []AccountStat // 该设备内按账户聚合(与现 AccountStat 同构) + ActiveAccts int // 活跃账户数 + EstRound time.Duration // 估算一轮耗时 = ActiveAccts × 配额 × pollCost + CloseWindow time.Duration // 当前动态窗口(模块二算好放这) + UpdatedAt time.Time +} +``` + +### 3.2 操作接口(保持对外语义不变,内部按设备分池) + +| 现有方法 | 改造后行为 | +|---|---| +| `Add(o)` / `Upsert(o)` | 解析 `deviceID = deviceOf(o.AccountID)` → 写入对应子池;CloseAt 用**该设备当前动态窗口**计算 | +| `Remove(outTradeNo)` | 遍历子池删除(防御性全表,语义同现在) | +| `Snapshot()` | 全局聚合(Total = Σ 设备 Total),**保持**(notifywatcher / 调度器兼容) | +| `Count(accountID)` | 不变(按账户计数) | +| `MatchByNote / MatchByAmount` | 不变(账户维度匹配,跨设备天然隔离) | +| `Rebuild / Reconcile / ReconcileLoop` | 遍历时按 deviceOf 分桶;Reconcile 兜底:解析不到设备的订单归入 `""` 桶并告警 | +| **新增** `DeviceSnapshot(deviceID) *DeviceStat` | 设备级快照(零锁),供 DeviceScheduler 与分配器使用 | + +### 3.3 关键改造点 + +1. **`orderToEntry`**:`CloseAt` 从固定 `closeWindow` 改为 `winCalc(deviceLoad)`——设备负载在 Add 时点计算。 +2. **`publishSnapshotLocked`**:先按设备分桶聚合,再汇总全局;同时发布每子池 `DeviceStat`。 +3. **`DeviceScheduler`**:`plan()` 改为读 `pool.DeviceSnapshot(s.deviceID)`(不再全局快照过滤),活跃账户判断与配额计算**只在本设备子池内进行** → 多设备天然隔离、互不干扰。 +4. **notifywatcher**:`Snapshot().Total > 0` 判断不变(全局快照保持兼容)。 +5. **注入 `deviceOf`**:main.go 构造(从 personal.db 设备 + `DeviceAccountIDs` 建 accountID→deviceID 映射;映射在设备增删时刷新)。 + +--- + +## 4. 模块一:动态关单时间算法 + +### 4.1 窗口公式(物理锚点) + +``` +设备负载 L = 该设备子池 WAIT_PAY 订单总数 +活跃账户数 A = 该设备子池内 Count>0 的账户数 +单次 poll 成本 P = 通道加权实测值(支付宝 4.3s / 微信 2.9s,默认取 4s 保守) +一轮耗时 T = A × BaseQuota(3) × P ≈ 12s × A +排队系数 = 1.5(最坏队尾位置) +支付操作预留 = 60s(用户扫码→支付操作时间下限) + +动态窗口 W = clamp( T × 1.5 + 60s, MIN=5min, MAX=10min ) +CloseAt = CreatedAt + W +``` + +**单调性(防误关)**:`CloseAt` 在订单生命周期内**只增不减**。负载升高 → 允许推后;负载降低 → 不提前(保守,避免已排队确认的订单被提前关掉)。实现:Add 时记录 `CloseAt`,`RefreshCloseAt`(Upsert 触发)仅在 `newCloseAt > oldCloseAt` 时更新。 + +### 4.2 窗口取值表 + +| 设备状态 | L / A | T | W | +|---|---|---|---| +| 空闲(1 账户 1 单) | A=1 | 12s | **5min(下限)** | +| 正常(2 账户各有单) | A=2 | 24s | **5min** | +| 高峰(4 账户多单) | A=4 | 48s | **5~6min** | +| 高负载(6 账户积压) | A=6 | 72s | **~7min** | +| 接近超限 | A≥8 | ≥96s | **8~10min** | +| 超限 | L ≥ C | — | **10min(上限)→ 换设备** | + +### 4.3 漏单路径修复(最高优先级,先行落地) + +**改造 `CloseExpiredOrders`(order/service.go:821)**: + +```go +// 个人/监听通道:不再直接关单,改为「复查后关」——与 confirmPersonal 同语义 +if ch.ConfirmAbility() != channel.ConfirmByQuery { + // ① 设备离线检查(注入 personalCheck,与 reconciler 共用) + if s.personalCheck != nil { + offline, reason := s.personalCheck(ctx, o) + if offline { + s.EnqueueException(ctx, o, ExReasonListenOffline, "LISTEN_OFFLINE", "", ..., + "监听端掉线,无法确认是否已付款,需人工核实: " + reason) + continue // 不关单 + } + } + // ② 关单前最后复查(新增):检查池子是否还有该订单 + 复查次数上限 + if !s.poolStillHolds(ctx, o.OutTradeNo) { continue } // 已入账/已移除,跳过 + // ③ 复查次数超限 → 进异常池(复用 confirmPersonal 的 QueryFailAlert 语义) + // ④ 确认未支付 → 关单(走 closeExpiredOrder,含池同步) +} +``` + +> 语义统一:个人通道关单**只有一条安全路径**(设备在线 + 无新记录 + 未超复查上限)。`CloseExpiredOrders` 是 DB 扫描兜底(防 confirmSched 堆丢失),`confirmPersonal` 是事件驱动主路径,两者最终都收敛到同一安全检查。 + +**新增 `personalCheck` 注入**:main.go 组装(`reconciler.AsPersonalCheck(personalDB, adbMgr())`),order.Service 与 reconciler.Scheduler 共用同一检查函数,避免两处逻辑分叉。 + +### 4.4 关单扫描改造 + +- 扫描器(main.go:738):`before` 从 `now - 固定30min` 改为 `now - MAX_WINDOW(10min) - 裕量(1min)`,候选集放宽到上限内所有 WAIT_PAY。 +- 命中候选后:在池子中查该订单的 `CloseAt`(动态),`now < CloseAt` → 跳过;`now ≥ CloseAt` → 走 §4.3 复查流程。 +- `ORDER_CLOSE_INTERVAL` 从 60s 收紧到 **30s**(窗口变小,扫描粒度需跟上)。 + +### 4.5 设备超限处理(配合模块三) + +``` +设备能力 C = (MAX_WINDOW - 支付操作预留) / (单次poll × 每账户最低配额1) × 排队修正 + = (600s - 60s) / (4s × 1) ≈ 135 单(理论值,可按实测 poll 成本校准) + +L ≥ C×80% → 分配器降权该设备(模块三) +L ≥ C → 分配器跳过该设备(换设备处理) +全部设备超限 → 下单返回「设备负载超限,暂无可服务设备」+ 日志 ERROR「设备负载超限,建议增加收款设备」 +``` + +--- + +## 5. 模块二:免签账户订单自动均衡分配 + +### 5.1 两层分配模型 + +``` +下单(个人通道 CreatePayment) + → 第一层:选设备(最闲 + 最健康) + → 第二层:该设备内选账户(最闲 + 最健康) + → 现有 filterAvailable 安全过滤始终保留(状态/日限额/冷却) +``` + +### 5.2 第一层:设备评分(物理瓶颈优先) + +```go +// internal/channel/personal/ 新增 deviceSelector(或 internal/pool 提供负载查询后在此实现) +type DeviceCandidate struct { + DeviceID string + Serial string + Load int // 设备子池 WAIT_PAY 总数(DeviceSnapshot().Total) + Health float64 // 最近 N 次 poll 成功率(埋点,见 §6) +} + +score_device = Health × 1 / (Load + 1) + +// 规则: +// - 设备 adb 离线 → 排除(现有 servableAccountIDs 已过滤) +// - Load ≥ C(超限)→ 排除(换设备) +// - 其余按 score_device 降序选最优;得分并列 → 按 Load 升序;再并列 → 确定性(DeviceID 字典序) +``` + +**设备健康度埋点**(新增,§6.2):`ledgermon.DeviceActor` 每次 PollCmd 执行记录成败,滑动窗口最近 20 次,`Health = 成功数/20`。设备无历史(<5 次)→ `Health = 0.9` 中性(不因无数据饿死新设备)。 + +### 5.3 第二层:账户评分(设备内) + +```go +score_account = Health × 1 / (Count + 1) + +// Health(账户健康,只罚短板): +// - 状态 normal=1(cooling/frozen/disabled 已由 filterAvailable 排除) +// - 日限额余量:1 - DailyUsed/DailyLimit(DailyLimit=0 视为 1) +// - 失败折扣:FailCount>=3 → ×0.2;FailCount>=1 → ×0.5(复用现 ReportResult 计数) +// Count = WaitPayPool.Count(accountID)(账户负载) +``` + +### 5.4 与现有代码的落点 + +改造 `selectServableAccount`(personal.go:244): + +```go +func (c *Channel) selectServableAccount(ctx) *account.Account { + // ① 现有:adb 在线设备 + 账户集(硬过滤,保留) + devices, acctIDs := c.servableDevicesAndAccounts(ctx) // 从 servableAccountIDs 拆出设备维度 + // ② 设备超限过滤(Load ≥ C → 剔除设备及其账户) + devices = filterOverloaded(devices) + if len(devices) == 0 { + logger.Error("personal", "设备负载超限,建议增加收款设备", ...) + return nil + } + // ③ 第一层:选设备(score_device 最高) + dev := pickDevice(devices) + // ④ 第二层:该设备内选账户(score_account 最高,替代 weightedRandom) + return c.pool.SelectFromWeighted(c.channelName(), "default", acctIDsOf(dev), + func(id int64) float64 { return accountScore(c.pool, id, c.loadOf, c.healthOf) }) +} +``` + +> `SelectFrom` 保留(管理后台/其他调用不受影响),新增 `SelectFromWeighted`(带评分函数参数,替换 weightedRandom 为 argmax 评分)。备注码分配器**不动**(全局唯一 + TTL 5min,均衡分配不改变码的唯一性;byNote 索引按 accountID 隔离,同码跨账户天然不冲突)。 + +### 5.5 振荡防护 + +- 评分分母 `Load+1` 天然带惯性:分配后目标负载上升 → 分数下降 → 下次转移,无需额外冷却。 +- 设备健康度滑动窗口(20 次)平滑抖动,不因单次 poll 失败跳变。 +- 多设备间负载在 `C×80%` 阈值内自然均衡,不会在两台设备间反复横跳(每单只分配一次,分配后负载立即反映)。 + +--- + +## 6. 新增埋点(轻量,均为内存滑动窗口,不落库) + +| 埋点 | 位置 | 实现 | +|---|---|---| +| 设备 poll 成功率 | `ledgermon.DeviceActor`(PollCmd 执行处) | 环形缓冲 20 次,`RecordPoll(ok bool)`;`PollHealth() float64` | +| 设备一轮实际耗时 | `DeviceScheduler` 窗口结束 | `RecordRound(dur)`;滑动平均(用于校准 pollCost 而非写死 4s) | +| 通知栏快路径命中 | `notifywatcher.Handle` 成功匹配时 | 每设备计数器(10min 窗口),供设备健康加成(可选,v0.1 不接) | +| 设备超限事件 | 分配器拒绝时 | 日志 ERROR + 可选 Bark/Webhook(复用 alert.Client) | + +--- + +## 7. 实施步骤(顺序即依赖) + +| 步骤 | 内容 | 涉及文件 | 验收 | +|---|---|---|---| +| **S1 漏单路径修复** | 个人通道关单统一安全检查(personalCheck 注入 + 关单前复查) | order/service.go、reconciler/、main.go | 设备离线时订单绝不关单(单测 + 模拟) | +| **S2 池子设备维度改造** | deviceOf 注入、devicePool 分池、DeviceSnapshot、动态窗口计算器挂载 | pool/waitpay_pool.go、ledgermon/scheduler.go、main.go | 多设备各自独立快照;调度器只读本设备池 | +| **S3 动态关单** | winCalc 实现(公式 §4.1)、CloseAt 单调更新、扫描器改造(30s/10min 窗口) | pool/、order/service.go、main.go | 窗口随负载在 5~10min 内变化;回归测试 | +| **S4 均衡分配** | 设备评分 + 账户评分 + SelectFromWeighted、设备超限过滤 | personal.go、account/pool.go、新增 deviceSelector | 多设备订单分布均衡;超限设备不再接单 | +| **S5 埋点** | 设备 poll 成功率 / 一轮耗时滑动统计 | ledgermon/ | 健康度随真实 poll 结果变化 | +| **S6 全量验证** | 编译 + vet + 单测 + 资金链路测试 + 真机验证 | — | 见 §8 | + +--- + +## 8. 验收标准 + +### 模块一(动态关单) +1. [ ] 设备离线时,个人通道 WAIT_PAY 订单**不关闭**(转异常池人工核实),无漏单 +2. [ ] 高峰(4+ 活跃账户)订单窗口自动放宽至 ≥6min;空闲(1 账户)收敛至 5min 下限 +3. [ ] 窗口恒定在 [5min, 10min] 内,不无限低/无限高 +4. [ ] 设备负载 ≥ C 时新订单不再路由该设备;全部超限时下单拒绝 + 日志告警 +5. [ ] 已付款订单在任何负载下都秒级入账(窗口不影响确认链路) +6. [ ] 回归:`go build ./... && go vet ./... && go test ./... -count=1` 全绿 + +### 模块二(均衡分配) +1. [ ] 多设备下订单按设备负载均衡分布,不出现单设备积压 +2. [ ] 某设备超限/离线 → 流量自动转移至健康设备 +3. [ ] 设备内多账户按账户负载 + 日限额余量分配,限额快满账户自动降权 +4. [ ] 备注码全局唯一性在均衡分配下不退化(byNote 账户隔离验证) +5. [ ] 分配失败(无可用设备/账户)返回明确错误,不留死单 + +--- + +## 9. 风险与兜底 + +| 风险 | 等级 | 兜底 | +|---|---|---| +| 动态窗口计算器 bug 导致窗口过短 | 高 | 下限 clamp 5min 硬保护 + 单调只增 + 关单前复查三重防线 | +| 设备→账户映射漂移(订单在池中但设备已删) | 中 | `""` 兜底桶 + Reconcile 修复 + 告警 | +| poll 成本估算偏差(真机实测 vs 4s 默认) | 中 | §6 一轮实际耗时埋点校准,env 可配 `DEVICE_POLL_COST_MS` | +| 均衡分配振荡 | 低 | Load+1 分母天然惯性 + 健康度滑动窗口 | +| 全设备超限误拒单 | 低 | 超限阈值 C×80% 降权先于 C 硬拒绝,有缓冲带 | +| 关单扫描与 confirmSched 双路径竞态 | 中 | 两者收敛同一安全检查 + UpdateOrderStatus 条件更新(WHERE status=WAIT_PAY)幂等 | + +--- + +## 10. 配置项(.env 新增) + +| 变量 | 默认 | 说明 | +|---|---|---| +| `ORDER_CLOSE_MIN` | `5` | 动态关单窗口下限(分钟) | +| `ORDER_CLOSE_MAX` | `10` | 动态关单窗口上限(分钟) | +| `ORDER_CLOSE_INTERVAL` | `30` | 关单扫描间隔(秒,从 60 收紧) | +| `DEVICE_POLL_COST_MS` | `4000` | 单次 poll 成本估算(ms,埋点校准后可不配) | +| `DEVICE_LOAD_CAP` | `0` | 设备订单承载上限(0=按公式自动算) | +| `DEVICE_OVERLOAD_RATIO` | `0.8` | 设备降权阈值(占 C 的比例) | +| `DEVICE_HEALTH_WINDOW` | `20` | 设备健康度滑动窗口(次) | + +--- + +## 11. 生产上线前代码审查修复记录(2026-08-31) + +Oracle 只读审查发现的阻断级与中等问题,均已修复并验证: + +| # | 级别 | 问题 | 修复 | +|---|------|------|------| +| P0 | 🔴 崩溃 | `waitpay_pool.go` Add 在锁外调 `orderToEntry`→`windowFor` 遍历 `dp.buckets`,与并发写操作形成 map 竞态(可致 fatal crash);`-race` 未捕获因并发测试未设 winCalc | `Add` 内 `orderToEntry` 移入写锁;并发测试注入 winCalc+deviceOf 以覆盖该路径 | +| P1 | 🔴 功能 | 扫描器候选集 `max(OrderCloseMinutes, dynMax+1min)` 使动态窗口完全失效:默认 30min>11min 时个人订单 5-10min 到期不进候选集 | 候选集改 `min(OrderCloseMinutes, dynMin)`,个人订单 age≥dynMin 起进入,由池内 `CloseAtOf` 精确过滤 | +| P2-1 | 🟡 资金 | `CloseExpiredOrders` 个人分支在线直接关单,绕过 `confirmPersonal` 的 LISTEN_TIMEOUT 复查保护(监听端僵死时误关已付款订单) | 在线且已有 LISTEN_TIMEOUT 异常记录 → 进异常池人工核实,不直接关单 | +| P2-2 | 🟡 资金 | `Add` 覆盖未删旧设备桶:设备映射变更后同订单残留双设备子池,负载虚高 | `unindexLocked(old)` 后显式 delete 旧桶 + 清理空桶/`acctDev` | +| P2-3 | 🟡 一致 | `balanceSelectAccount` 直读 `cfg.OrderCloseMax` 绕过配置退化逻辑(静态窗口下超限过滤仍生效) | `deviceLoadCapOf` 内重复校验 MIN/MAX 合法性,非法 → 返回 0(不启用过滤) | +| P2-4 | 🟡 性能 | 扫描器对每个到期订单逐次 `adb Online()` 探测,无设备级缓存 | `CloseExpiredOrders` 内扫描级设备在线缓存(仅缓存已绑定设备,单次扫描内复用) | + +**验证**:`go build`/`go vet`/14 包测试全绿;`go test -race ./internal/pool/ ./internal/order/ ./internal/ledgermon/` 全绿;新增/强化单测:`TestWaitPayPool_ConcurrentStorm`(winCalc 路径)、`TestCloseExpiredOrders_PersonalOnline_StalledListener`、`TestCloseExpiredOrders_Merchant_RespectsFixedWindow`/`OverFixedWindow`。 + +**记录为已知限制(非阻断,后续加固)**: +- ~~全代码库 0 处 `recover()`~~ **[已修复 2026-08-31]**:8 个后台 goroutine 已加 `defer recover`(关单扫描器/收款码清理+续期/账本日任务/Cloudflare Tunnel/设备加锁/视频转发/告警发送),main.go 统一 `bgRecover` helper 记录 panic+栈;同时修复 notifywatcher 测试闭包变量的 DATA RACE(`orderDriven` 改 atomic.Bool) +- `SetWindowCalc` 原地修改已发布 Entry 的 CloseAt:仅启动时调用一次,无并发读者,实际无害 +- 动态窗口公式 `clamp(active×12s×1.5+60s, 5min, 10min)` 依赖 `DEVICE_POLL_COST_MS=4000` 估算,真机实测可校准 diff --git a/docs/RESILIENCE.md b/docs/RESILIENCE.md new file mode 100755 index 0000000..d4c845a --- /dev/null +++ b/docs/RESILIENCE.md @@ -0,0 +1,173 @@ +# 支付网关容灾与对账体系设计 v3(主动查账 · 事件驱动 · 三级兜底 · 人工异常池) + +> 版本:v3.0(2026-08-09) +> 设计约束: +> - 临时隧道是防攻击特性,不引入固定域名;回调丢失是常态,主动查账是核心保障。 +> - 兜底分**三级**:① 自动查单补账(ConfirmScheduler)→ ② 退避重试(查单/通知)→ ③ **人工异常处理池**(自动化兜不住时的最终兜底)。 +> - 所有资金写入(含人工处理)只走幂等入口(SettlePayment / CloseOrder),不绕过状态机、不手改账本。 +> - 高性能:事件驱动 + timer heap,不轮询;不出错:幂等 + 关单前查单 + 冲突进差错。 + +--- + +## 一、设计哲学 + +1. **回调是优化,查单是保障** —— 快路径(回调入账)与慢路径(查单补账)殊途同归,都进幂等 `SettlePayment`。 +2. **数据经过网关,就有信息返回** —— 每个环节可自我验证,异常即触发兜底。 +3. **三级兜底**:自动补偿 → 退避重试 → **人工异常池**。自动化兜不住才进人工,人工操作也走同一幂等入口。 + +--- + +## 二、每环节"正常路径 vs 兜底" + +| 环节 | 正常 | 异常→兜底触发 | +|------|------|----------------| +| ① 上游→网关(下单) | 验签→建单→返回凭据 | 验签/参数失败→拒绝并记录(无资金影响) | +| ② 用户→支付宝(支付) | 回调→入账(快路径) | **回调丢失→ConfirmScheduler 到期→查单补账(慢路径)** | +| ③ 网关→上游(通知) | 通知 200→标记 | 失败→outbox 重试 31h→dead→人工重发 | +| ④ 日终对账 | 一致 | 不一致→差错单→人工 | +| ⑤ **任何环节异常不明** | — | **进人工异常池**(兜底链终点) | + +--- + +## 三、核心模块:ConfirmScheduler(订单确认调度器) + +内存 min-heap(按 nextCheckAt 排序),事件驱动,**非轮询**。 + +``` +OnOrderCreated → push(now + T1 60s) +OnCallbackReceived → remove + SettlePayment(幂等) +到期 Confirm(tradeNo): + ├─ 已 SUCCESS → remove + ├─ 仍 WAIT 未到关单时间 → QueryOrder 查单 + │ ├─ TRADE_SUCCESS → SettlePayment 补账 → 通知 → remove + │ ├─ 未支付 → push(+30s~5m 自适应) + │ └─ 查单失败 → push(+退避),连续失败 N 次 → 进异常池(query_failed) + └─ 到关单时间 → 最后查一次:已支付补账;未支付 CloseOrder;查单失败 → 推迟关单 + 进异常池 +``` + +冲突(查单金额≠订单金额、状态矛盾)→ **不自动入账** → 进异常池(amount_mismatch / state_conflict)。 + +--- + +## 四、人工异常处理池(Manual Exception Pool)★新增核心 + +### 4.1 定位 +**自动化兜底失败后的最终兜底**:任何"不按正常流程"的订单(回调全丢且查单不明、金额矛盾、关单存疑、通知 dead 无法重发)自动进入本池。人工核对证据 → 按流程填写必要信息 → 提交后经幂等入口恢复为正常订单,**订单永久标记"人工处理"**,全程审计。 + +### 4.2 数据模型(新表 + orders 标记) + +```sql +-- 异常订单池(只增不改,resolved 后可 reopen,不可删除) +CREATE TABLE exception_orders ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + out_trade_no TEXT NOT NULL UNIQUE, -- 关联订单 + pid TEXT, upstream_name TEXT, channel TEXT, + amount_minor INTEGER, -- 网关记录金额(分) + order_status TEXT, -- 当前订单状态 + channel_status TEXT, -- 最近查单结果(TRADE_SUCCESS/CLOSED/QUERY_FAILED/UNKNOWN) + channel_tx_id TEXT, -- 查单拿到的通道交易号 + paid_amount INTEGER, -- 人工确认的实际支付金额(默认=amount_minor) + payer TEXT, -- 人工确认的付款方 + reason TEXT, -- query_failed / amount_mismatch / state_conflict / close_uncertain / notify_dead / manual + phase TEXT DEFAULT 'pending', -- pending / processing / resolved + resolved_action TEXT, -- settle / close / noop / refund + evidence TEXT, -- 证据时间线 JSON(自动收集,不可手改) + suggestion TEXT, -- 系统建议动作(依据最近查单结果) + handler TEXT, handler_note TEXT, + resolved_at TEXT, updated_at TEXT, + created_at TEXT +); + +-- orders 表新增人工处理标记(列表/详情可见,审计可追溯) +ALTER TABLE orders ADD COLUMN exception_id INTEGER DEFAULT 0; -- 关联异常池 +ALTER TABLE orders ADD COLUMN handled_by TEXT DEFAULT ''; -- 处理人 +ALTER TABLE orders ADD COLUMN handled_action TEXT DEFAULT ''; -- manual_settle / manual_close / noop +ALTER TABLE orders ADD COLUMN handled_at TEXT DEFAULT ''; +ALTER TABLE orders ADD COLUMN handled_note TEXT DEFAULT ''; + +-- 操作审计(谁在何时对哪个异常单做了什么) +CREATE TABLE exception_logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + exception_id INTEGER, action TEXT, operator TEXT, detail TEXT, created_at TEXT +); +``` + +### 4.3 自动进池触发点(ConfirmScheduler / notify 集成) + +| 触发 | reason | 进入时自动收集的证据 | +|------|--------|----------------------| +| 查单连续失败 ≥ `query_fail_alert`(5 次) | query_failed | 下单/回调/通知记录 + 每次查单结果时间线 | +| 查单金额 ≠ 订单金额 | amount_mismatch | 查单快照(金额、交易号、时间) | +| 回调与查单状态矛盾 | state_conflict | 两方原始数据 | +| 关单时查单失败(存疑) | close_uncertain | 查单错误信息 | +| 通知 dead 且重发仍失败 | notify_dead | 通知任务重试历史 | +| 管理后台手动加入 | manual | 人工填写的说明 | + +进池时同时**暂停 ConfirmScheduler 对该订单的自动动作**(避免人工处理与自动查单打架),订单转入"待人工"。 + +### 4.4 人工处理流程(管理后台「异常订单」页) + +``` +① 列表(筛选 phase/reason/上游/日期)→ 打开详情 +② 详情:基本信息 + 证据时间线(自动收集,只读)+ 系统建议动作 + (可点「再次查单」刷新证据:调用通道查单,把最新结果并入时间线) +③ 填写处理表单: + 动作:settle(确认入账)/ close(确认未支付,关单)/ noop(无需处理) + 实际支付金额(默认=订单金额,可改) + 通道交易号(可从支付宝账单人工核对后填入) + 付款方(可选) + 备注 +④ 提交 → 后端校验(金额>0、订单未处理过、动作合法、表单与证据不冲突) + → 执行: + settle → Service.SettlePayment(幂等,参数含人工金额/交易号/付款方/来源=manual) + → 成功自动创建通知任务 → 通知上游到账 + close → Service.CloseOrder(幂等,单笔) + noop → 仅记录 + → 成功:orders 写 handled_* 标记 + exception_orders 置 resolved + exception_logs 记审计 +⑤ 该订单在列表/详情永久显示「⚠️ 人工处理」徽标(含处理人/时间/备注/动作) +``` + +### 4.5 防呆与安全 + +| 项 | 设计 | +|----|------| +| 并发防重 | 处理时 `phase=pending→processing` 行锁,同一订单不可并发处理;resolved 后不可再处理(可管理员 reopen) | +| 幂等入口 | settle/close 一律走 Service 幂等方法,**绝不直接 UPDATE 订单状态/金额** | +| 金额校验 | 人工金额必须 >0 且为整数分;与查单金额不一致时强制二次确认(输入框要求重复输入) | +| 证据只读 | evidence 自动收集,人工不可编辑(防掩盖);人工信息只写在 paid_amount/payer/handler_note | +| 权限 | 异常池操作走管理后台认证;敏感动作(settle)可加二次确认口令(可配置) | +| 审计 | exception_logs 全程留痕:谁、何时、看了什么、填了什么、做了什么 | +| 账本 | settle 走 SettlePayment 自动过账本 hash 链;人工无法绕账本 | + +### 4.6 与上游通知不到的联动 +人工 settle 成功后自动创建通知任务 → 若上游仍不可达 → outbox 重试 31h → dead → 此时订单已 SUCCESS,**dead 通知单独成清单**("上游未确认"),可在异常池关联查看/重发;上游也可主动调 `/api.php` 查单(网关数据正确,拉取是上游的事)。 + +--- + +## 五、分阶段落地 + +| 阶段 | 内容 | 说明 | +|------|------|------| +| **P0-1** | 通知重试增强(31h 窗口)+ dead 管理 API | 修复"付钱未到账" | +| **P0-2** | ConfirmScheduler 最小版(timer heap + 查单补账 + 关单前查单) | 回调丢失自动兜底 | +| **P0-3** | systemd 守护(gateway+new-api)+ 每日 sqlite 备份 | 进程/数据安全 | +| **P1-1** | **人工异常处理池**(表 + 进池触发 + 处理流程 + 管理后台页) | 最终兜底 + 人工台 | +| **P1-2** | 启动配置自检(沙箱/正式、密钥、回调地址) | is_prod 错配早暴露 | +| **P1-3** | 告警(Bark/Webhook:查单连续失败、验签失败率、dead、账本失败、**进池事件**) | 可观测 | +| **P2-1** | 日终对账报告(网关 vs 通道 vs 上游) | 资金最终核对 | + +> **存量订单 116**:P0-2 落地后由 ConfirmScheduler 自然发现;若查单亦失败(极端),P1-1 人工池接管——人工从支付宝账单核对后 settle 恢复,标记人工处理。 + +--- + +## 六、模块与现有代码对应 + +| 新组件 | 复用 | 新增 | +|--------|------|------| +| `reconciler.ConfirmScheduler` | `Channel.QueryOrder`、`Service.SettlePayment`、`CloseExpiredOrders` | timer heap、事件接口 | +| `reconciler.ExceptionPool` | admin 框架、审计日志模式、SettlePayment/CloseOrder | exception 表、进池触发、处理流程 API | +| notify 增强 | `notify.Worker`、`notify_tasks` | 重试参数、dead 管理 API | +| admin 页面 | admin 框架 | 异常订单池 Vue 页(证据时间线 + 处理表单) | +| systemd + 备份 | gateway_autostart.sh | service 单元、备份脚本 | + +**核心承诺**:所有资金写入(自动或人工)只走 `SettlePayment` / `CloseOrder` 等幂等方法;人工处理同样过账本 hash 链、同样触发通知、同样留审计;订单永久可追溯"是否人工处理过、谁处理的、依据什么"。 diff --git a/go.mod b/go.mod new file mode 100755 index 0000000..a30d89e --- /dev/null +++ b/go.mod @@ -0,0 +1,55 @@ +module paygateway + +go 1.25.0 + +require ( + github.com/gin-gonic/gin v1.10.0 + github.com/go-pay/gopay v1.5.119 + github.com/golang-jwt/jwt/v5 v5.3.1 + github.com/gorilla/websocket v1.5.3 + github.com/makiuchi-d/gozxing v0.1.1 + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e + golang.org/x/crypto v0.48.0 + gopkg.in/natefinch/lumberjack.v2 v2.2.1 + modernc.org/sqlite v1.56.0 +) + +require ( + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-pay/crypto v0.0.1 // indirect + github.com/go-pay/util v0.0.4 // indirect + github.com/go-pay/xlog v0.0.3 // indirect + github.com/go-pay/xtime v0.0.2 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.20.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.7 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.24 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/ncruces/go-strftime v1.0.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/sys v0.47.0 // indirect + golang.org/x/text v0.34.0 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + google.golang.org/protobuf v1.34.1 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + modernc.org/libc v1.74.4 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100755 index 0000000..038fd10 --- /dev/null +++ b/go.sum @@ -0,0 +1,154 @@ +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-pay/crypto v0.0.1 h1:B6InT8CLfSLc6nGRVx9VMJRBBazFMjr293+jl0lLXUY= +github.com/go-pay/crypto v0.0.1/go.mod h1:41oEIvHMKbNcYlWUlRWtsnC6+ASgh7u29z0gJXe5bes= +github.com/go-pay/gopay v1.5.119 h1:E+Uc3HW/A9guCmbGW8CkGUwhjfEw/xKn+azW67gPH6A= +github.com/go-pay/gopay v1.5.119/go.mod h1:2R6Ggc9FqYnbC37yoOJZH4eZMFpykPDVOobnZNP+R2A= +github.com/go-pay/util v0.0.4 h1:TuwSU9o3Qd7m9v1PbzFuIA/8uO9FJnA6P7neG/NwPyk= +github.com/go-pay/util v0.0.4/go.mod h1:Tsdhs8Ib9J9b4+NKNO1PHh5hWHhlg98PthsX0ckq6PM= +github.com/go-pay/xlog v0.0.3 h1:avyMhCL/JgBHreoGx/am/kHxfs1udDOAeVqbmzP/Yes= +github.com/go-pay/xlog v0.0.3/go.mod h1:mH47xbobrdsSHWsmFtSF5agWbMHFP+tK0ZbVCk5OAEw= +github.com/go-pay/xtime v0.0.2 h1:7YR4/iuELsEHpJ6LUO0SVK80hQxDO9MLCfuVYIiTCRM= +github.com/go-pay/xtime v0.0.2/go.mod h1:W1yRbJaSt4CSBcdAtLBQ8xajiN/Pl5hquGczUcUE9xE= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBExVwjEviJTixqxL8= +github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3 h1:LMLX+LgTNWpfvCBdFebv6EsYotImrt/Ppc5cXIriCSo= +github.com/google/pprof v0.0.0-20260802141513-ef3492d7dac3/go.mod h1:jl5iWTm0/hd5PjEYEOuwAJ57L/CibdZfrqZ5XA5GrCk= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.7 h1:ZWSB3igEs+d0qvnxR/ZBzXVmxkgt8DdzP6m9pfuVLDM= +github.com/klauspost/cpuid/v2 v2.2.7/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/makiuchi-d/gozxing v0.1.1 h1:xxqijhoedi+/lZlhINteGbywIrewVdVv2wl9r5O9S1I= +github.com/makiuchi-d/gozxing v0.1.1/go.mod h1:eRIHbOjX7QWxLIDJoQuMLhuXg9LAuw6znsUtRkNw9DU= +github.com/mattn/go-isatty v0.0.24 h1:tGZZoVgT/KiqK1c8ocVLeDS8BSWMRd47J3Lbz7vsReI= +github.com/mattn/go-isatty v0.0.24/go.mod h1:nMCL3Zebbrt45jsMDgnfIwz6ydEQApk5oEI3HqDio6A= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w= +github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= +golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +modernc.org/cc/v4 v4.29.1 h1:MKgdCV3WykTSPqpVrnxdEDS0HEd2FHpKZDzxzU5LyeI= +modernc.org/cc/v4 v4.29.1/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI= +modernc.org/ccgo/v4 v4.34.6 h1:sBgfIwyN0TQ9C5hwIeuqyeAKyMWnbvj2fvpF4L11uzU= +modernc.org/ccgo/v4 v4.34.6/go.mod h1:SZ8YcN9NG7XVsQYdm6jYBvi8PQP1qi+kqB6OhjqI3Fk= +modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM= +modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/gc/v3 v3.1.4 h1:2g65LGVSmFQrXeITAw97x7hCRvZFcyE1uDP+7Vng7JI= +modernc.org/gc/v3 v3.1.4/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY= +modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks= +modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= +modernc.org/libc v1.74.4 h1:fX1Omw4o2/1C2iRkkIsrQTasJQldLhRmuPreXLoWs9k= +modernc.org/libc v1.74.4/go.mod h1:eeQAS9W3sZeKYMFubydxJpII9ybHWshk+7or7bLG9co= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg= +modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.56.0 h1:/D8e2RfFqoy/Zc6PuC76U28zFwmI/sYx1Kjm4yEn9e0= +modernc.org/sqlite v1.56.0/go.mod h1:yCJ2cmAaIkHQ25oXWrF8H4O1lIfPYPR26yCEDj2P3pQ= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/internal/account/crypto.go b/internal/account/crypto.go new file mode 100755 index 0000000..10e134f --- /dev/null +++ b/internal/account/crypto.go @@ -0,0 +1,112 @@ +// Package account 账户池:多支付账户管理 + 加密存储 + 调度器 +package account + +import ( + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/hex" + "encoding/json" + "errors" + "fmt" + "io" +) + +// MasterKeySize AES-256 密钥长度(32 字节) +const MasterKeySize = 32 + +// GenerateMasterKey 生成新的主密钥(hex 编码,64 字符) +func GenerateMasterKey() (string, error) { + key := make([]byte, MasterKeySize) + if _, err := rand.Read(key); err != nil { + return "", fmt.Errorf("生成主密钥失败: %w", err) + } + return hex.EncodeToString(key), nil +} + +// masterKey 运行时主密钥(字节形式) +var masterKey []byte + +// InitCrypto 初始化加密模块(传入 hex 编码的主密钥) +func InitCrypto(hexKey string) error { + key, err := hex.DecodeString(hexKey) + if err != nil { + return fmt.Errorf("主密钥格式错误: %w", err) + } + if len(key) != MasterKeySize { + return fmt.Errorf("主密钥长度错误: 需要 %d 字节,实际 %d", MasterKeySize, len(key)) + } + masterKey = key + return nil +} + +// Encrypt AES-256-GCM 加密,返回 hex 编码密文 +func Encrypt(plaintext []byte) (string, error) { + if masterKey == nil { + return "", errors.New("加密模块未初始化") + } + block, err := aes.NewCipher(masterKey) + if err != nil { + return "", fmt.Errorf("创建加密器失败: %w", err) + } + aesGCM, err := cipher.NewGCM(block) + if err != nil { + return "", fmt.Errorf("创建 GCM 失败: %w", err) + } + nonce := make([]byte, aesGCM.NonceSize()) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + return "", fmt.Errorf("生成 nonce 失败: %w", err) + } + ciphertext := aesGCM.Seal(nonce, nonce, plaintext, nil) + return hex.EncodeToString(ciphertext), nil +} + +// Decrypt 解密 hex 编码的 AES-256-GCM 密文 +func Decrypt(encryptedHex string) ([]byte, error) { + if masterKey == nil { + return nil, errors.New("加密模块未初始化") + } + ciphertext, err := hex.DecodeString(encryptedHex) + if err != nil { + return nil, fmt.Errorf("密文格式错误: %w", err) + } + block, err := aes.NewCipher(masterKey) + if err != nil { + return nil, fmt.Errorf("创建解密器失败: %w", err) + } + aesGCM, err := cipher.NewGCM(block) + if err != nil { + return nil, fmt.Errorf("创建 GCM 失败: %w", err) + } + nonceSize := aesGCM.NonceSize() + if len(ciphertext) < nonceSize { + return nil, errors.New("密文太短") + } + nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:] + plaintext, err := aesGCM.Open(nil, nonce, ciphertext, nil) + if err != nil { + return nil, fmt.Errorf("解密失败(密钥错误或数据损坏): %w", err) + } + return plaintext, nil +} + +// EncryptJSON 将任意结构体加密为 hex 密文(JSON 序列化后加密) +func EncryptJSON(v any) (string, error) { + data, err := json.Marshal(v) + if err != nil { + return "", fmt.Errorf("JSON 序列化失败: %w", err) + } + return Encrypt(data) +} + +// DecryptJSON 解密 hex 密文并反序列化到目标结构体 +func DecryptJSON(encryptedHex string, v any) error { + data, err := Decrypt(encryptedHex) + if err != nil { + return err + } + if err := json.Unmarshal(data, v); err != nil { + return fmt.Errorf("JSON 反序列化失败: %w", err) + } + return nil +} diff --git a/internal/account/model.go b/internal/account/model.go new file mode 100755 index 0000000..6ba8b44 --- /dev/null +++ b/internal/account/model.go @@ -0,0 +1,100 @@ +// Package account 账户池:多支付账户管理 + 加密存储 + 调度器 +// +// 安全设计: +// - accounts.db 仅管理后台 API 可读写,其他模块不得直接访问 +// - 敏感字段(private_key / public_key / api_key)AES-256-GCM 加密存储 +// - AccountPool 是内存中的唯一数据源,其他模块通过它获取解密后的账户 +package account + +import "time" + +// AccountStatus 账户状态 +type AccountStatus string + +const ( + StatusNormal AccountStatus = "normal" // 正常 + StatusCooling AccountStatus = "cooling" // 风控冷却中(自动恢复) + StatusFrozen AccountStatus = "frozen" // 冻结(按 FrozenReason 区分来源) + StatusDisabled AccountStatus = "disabled" // 永久禁用 +) + +// 账户冻结原因(StatusFrozen 不区分来源,用 FrozenReason 精确控制自动解冻边界)。 +// 只有 FrozenReasonOffline 允许设备恢复时自动解冻;其余一律仅人工解冻。 +const ( + FrozenReasonOffline = "offline" // 设备离线联动:设备 adb 恢复可达时自动解冻 + FrozenReasonRisk = "risk" // 风控上报冻结:仅人工解冻 + FrozenReasonManual = "manual" // 管理员手动冻结:仅人工解冻 + FrozenReasonRetire = "retire" // 删除设备级联停用:仅人工解冻 +) + +// Account 支付账户(内存中的完整表示,含解密后的密钥) +// accounts.db 中存储的是加密版本,Admin API 解密后推送到 AccountPool +type Account struct { + ID int64 `json:"id"` + Channel string `json:"channel"` // alipay / wechat / qq / paypal / personal.alipay / personal.wechat + Name string `json:"name"` // "支付宝-主账户" + AppID string `json:"app_id"` // 明文,用于回调反查 + MchID string `json:"mch_id,omitempty"` // 微信商户号 + + // 以下字段在 accounts.db 中加密存储,AccountPool 中为明文 + PrivateKey string `json:"-"` // 应用私钥(仅内存) + PublicKey string `json:"-"` // 支付宝公钥/微信API证书(仅内存) + ApiKey string `json:"-"` // API 密钥(仅内存) + QRContent string `json:"-"` // 收款码内容(个人免签通道用,仅内存) + + // 调度配置 + Weight int `json:"weight"` // 轮询权重(越大越容易被选中) + Priority int `json:"priority"` // 优先级(越大越优先) + Status AccountStatus `json:"status"` // normal / cooling / frozen / disabled + FrozenReason string `json:"frozen_reason,omitempty"` // 冻结原因(offline / risk / manual / retire;仅 frozen 时有效) + AutoBan bool `json:"auto_ban"` // 失败自动禁用 + DailyLimit int64 `json:"daily_limit"` // 日限额(分),0=不限 + DailyUsed int64 `json:"daily_used"` // 今日已用(分) + FailCount int `json:"fail_count"` // 连续失败次数 + FrozenUntil *time.Time `json:"frozen_until,omitempty"` // 冷却到期时间 + Group string `json:"group"` // 业务分组 + IsProd bool `json:"is_prod"` // 生产环境 + Remark string `json:"remark,omitempty"` + + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// AccountEncrypted accounts.db 中存储的加密版本 +// 敏感字段以 hex 密文存储,非敏感字段明文 +type AccountEncrypted struct { + ID int64 `json:"id"` + Channel string `json:"channel"` + Name string `json:"name"` + AppID string `json:"app_id"` + MchID string `json:"mch_id,omitempty"` + + // AES-256-GCM 加密后的 hex 密文 + PrivateKeyEnc string `json:"private_key_enc"` + PublicKeyEnc string `json:"public_key_enc,omitempty"` + ApiKeyEnc string `json:"api_key_enc,omitempty"` + QRContentEnc string `json:"qr_content_enc,omitempty"` // 收款码内容(个人免签通道) + + Weight int `json:"weight"` + Priority int `json:"priority"` + Status string `json:"status"` + FrozenReason string `json:"frozen_reason,omitempty"` // 冻结原因(offline / risk / manual / retire) + AutoBan bool `json:"auto_ban"` + DailyLimit int64 `json:"daily_limit"` + DailyUsed int64 `json:"daily_used"` // 今日已用(分),持久化(每日 00:00 重置) + Group string `json:"group"` + IsProd bool `json:"is_prod"` + Remark string `json:"remark,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// AccountLog 账户操作审计日志 +type AccountLog struct { + ID int64 `json:"id"` + AccountID int64 `json:"account_id"` + Action string `json:"action"` // create / update / delete / freeze / unfreeze / enable / disable + Detail string `json:"detail"` // 操作详情 + Operator string `json:"operator"` // 操作人 + CreatedAt time.Time `json:"created_at"` +} diff --git a/internal/account/pool.go b/internal/account/pool.go new file mode 100755 index 0000000..4ef1c27 --- /dev/null +++ b/internal/account/pool.go @@ -0,0 +1,492 @@ +// Package account 账户池:内存缓存 + 调度器 +// +// AccountPool 是各模块获取账户数据的唯一入口: +// - alipay.Channel 持有 pool,下单选账户 +// - order.Service 持有 pool,回调反查 app_id +// - Admin API 持有 pool,增删改查后同步到 pool +// +// 设计约束: +// - 不直接访问 accounts.db(仅 Admin API 有权限) +// - 所有账户数据在内存中解密,不落盘 +package account + +import ( + "math/rand" + "sync" + "time" +) + +// AccountPool 内存账户池(线程安全) +// 负责:账户选择、日限额检查、风控冷却 +type AccountPool struct { + mu sync.RWMutex + accounts map[int64]*Account // id → Account + byAppID map[string]*Account // app_id → Account(支付宝回调反查) + byMchID map[string]*Account // mch_id → Account(微信回调反查) + // offlineFrozen 记录因「设备离线联动」冻结的账户(仅内存,重启后由 Add 依据 FrozenReason 重建)。 + // 自动解冻只能针对此集合——风控冻结/手动冻结的账户绝不允许被设备恢复自动解冻。 + offlineFrozen map[int64]bool +} + +// NewPool 创建空账户池 +func NewPool() *AccountPool { + return &AccountPool{ + accounts: make(map[int64]*Account), + byAppID: make(map[string]*Account), + byMchID: make(map[string]*Account), + offlineFrozen: make(map[int64]bool), + } +} + +// FreezeByOffline 离线联动冻结账户(设备 adb 不可达时调用)。 +// 记录 FrozenReason=offline + offlineFrozen 标记,设备恢复时允许自动解冻。 +func (p *AccountPool) FreezeByOffline(id int64) { + p.freezeWithReason(id, FrozenReasonOffline) +} + +// FreezeByRisk 风控上报冻结账户(仅人工解冻,绝不允许自动解冻) +func (p *AccountPool) FreezeByRisk(id int64) { + p.freezeWithReason(id, FrozenReasonRisk) +} + +// FreezeManual 手动冻结账户(管理员操作,仅人工解冻) +func (p *AccountPool) FreezeManual(id int64) { + p.freezeWithReason(id, FrozenReasonManual) +} + +// FreezeByRetire 删除设备级联停用(僵尸自动账户,仅人工恢复) +func (p *AccountPool) FreezeByRetire(id int64) { + p.freezeWithReason(id, FrozenReasonRetire) +} + +// freezeWithReason 统一冻结入口:记录来源 + 维护 offlineFrozen 标记 +func (p *AccountPool) freezeWithReason(id int64, reason string) { + p.mu.Lock() + defer p.mu.Unlock() + acct, ok := p.accounts[id] + if !ok { + return + } + acct.Status = StatusFrozen + acct.FrozenReason = reason + if reason == FrozenReasonOffline { + p.offlineFrozen[id] = true + } else { + delete(p.offlineFrozen, id) + } +} + +// UnfreezeByOffline 仅解冻「离线联动冻结」的账户(设备 adb 恢复时调用)。 +// 手动冻结/风控冻结的账户(未在 offlineFrozen 中)不受影响,保持 frozen。 +func (p *AccountPool) UnfreezeByOffline(id int64) { + p.mu.Lock() + defer p.mu.Unlock() + if !p.offlineFrozen[id] { + return + } + delete(p.offlineFrozen, id) + if acct, ok := p.accounts[id]; ok { + acct.Status = StatusNormal + acct.FrozenReason = "" + } +} + +// IsOfflineFrozen 返回账户是否因「设备离线联动」冻结(仅内部测试/诊断用) +func (p *AccountPool) IsOfflineFrozen(id int64) bool { + p.mu.RLock() + defer p.mu.RUnlock() + return p.offlineFrozen[id] +} + +// ---------- 管理接口(仅 Admin API 调用)---------- + +// Add 添加账户到池中(Admin API 解密后调用) +func (p *AccountPool) Add(acct *Account) { + p.mu.Lock() + defer p.mu.Unlock() + p.accounts[acct.ID] = acct + if acct.AppID != "" { + p.byAppID[acct.AppID] = acct + } + if acct.MchID != "" { + p.byMchID[acct.MchID] = acct + } + // 重启后重建离线联动冻结标记(FrozenReason 持久化,服务重启自动解冻能力不丢) + if acct.Status == StatusFrozen && acct.FrozenReason == FrozenReasonOffline { + p.offlineFrozen[acct.ID] = true + } +} + +// Update 更新池中账户 +func (p *AccountPool) Update(acct *Account) { + p.mu.Lock() + defer p.mu.Unlock() + // 移除旧索引 + if old, ok := p.accounts[acct.ID]; ok { + if old.AppID != "" { + delete(p.byAppID, old.AppID) + } + if old.MchID != "" { + delete(p.byMchID, old.MchID) + } + } + p.accounts[acct.ID] = acct + if acct.AppID != "" { + p.byAppID[acct.AppID] = acct + } + if acct.MchID != "" { + p.byMchID[acct.MchID] = acct + } +} + +// Remove 从池中移除账户 +func (p *AccountPool) Remove(id int64) { + p.mu.Lock() + defer p.mu.Unlock() + if old, ok := p.accounts[id]; ok { + if old.AppID != "" { + delete(p.byAppID, old.AppID) + } + if old.MchID != "" { + delete(p.byMchID, old.MchID) + } + delete(p.accounts, id) + } +} + +// SetStatus 设置账户状态 +func (p *AccountPool) SetStatus(id int64, status AccountStatus) { + p.mu.Lock() + defer p.mu.Unlock() + if acct, ok := p.accounts[id]; ok { + acct.Status = status + } +} + +// ListAll 返回所有账户的快照(管理后台用) +func (p *AccountPool) ListAll() []*Account { + p.mu.RLock() + defer p.mu.RUnlock() + out := make([]*Account, 0, len(p.accounts)) + for _, acct := range p.accounts { + // 复制一份,避免外部修改 + c := *acct + out = append(out, &c) + } + return out +} + +// ListByChannel 返回某通道所有账户(查单/对账用) +func (p *AccountPool) ListByChannel(channel string) []*Account { + p.mu.RLock() + defer p.mu.RUnlock() + var out []*Account + for _, acct := range p.accounts { + if acct.Channel == channel { + c := *acct + out = append(out, &c) + } + } + return out +} + +// CountByChannel 返回某通道的账户数量 +func (p *AccountPool) CountByChannel(channel string) int { + return len(p.ListByChannel(channel)) +} + +// GetByID 按 ID 获取账户 +func (p *AccountPool) GetByID(id int64) *Account { + p.mu.RLock() + defer p.mu.RUnlock() + if acct, ok := p.accounts[id]; ok { + c := *acct + return &c + } + return nil +} + +// ---------- 查询接口(各模块调用)---------- + +// LookupByAppID 按 app_id 反查账户(支付宝回调用) +func (p *AccountPool) LookupByAppID(appID string) *Account { + p.mu.RLock() + defer p.mu.RUnlock() + if acct, ok := p.byAppID[appID]; ok { + c := *acct + return &c + } + return nil +} + +// LookupByMchID 按 mch_id 反查账户(微信回调用) +func (p *AccountPool) LookupByMchID(mchID string) *Account { + p.mu.RLock() + defer p.mu.RUnlock() + if acct, ok := p.byMchID[mchID]; ok { + c := *acct + return &c + } + return nil +} + +// ---------- 选择调度接口 ---------- + +// Select 选一个可用账户(加权随机 + 日限额 + 冷却检查) +// channel: 通道类型(alipay / wechat) +// group: 业务分组("default") +// 返回 nil 表示无可用账户 +func (p *AccountPool) Select(channel, group string) *Account { + // 1. 过滤可用账户 + candidates := p.filterAvailable(channel, group) + if len(candidates) == 0 { + return nil + } + + // 2. 按 priority 分组(高优先级优先) + groups := groupByPriority(candidates) + + // 3. 从最高优先级开始选 + priorities := sortedDesc(groups) + for _, pri := range priorities { + accts := groups[pri] + if len(accts) == 0 { + continue + } + // 4. 同优先级内加权随机 + selected := weightedRandom(accts) + if selected != nil { + return selected + } + } + + return nil +} + +// SelectFrom 在可用账户 ∩ allowedIDs 中按优先级/权重选取(设备感知路由用) +// allowedIDs 为空 = 等价于 Select(不过滤);返回 nil 表示无可用账户 +func (p *AccountPool) SelectFrom(channel, group string, allowedIDs map[int64]bool) *Account { + candidates := p.filterAvailable(channel, group) + if len(allowedIDs) > 0 && len(candidates) > 0 { + filtered := make([]*Account, 0, len(candidates)) + for _, acct := range candidates { + if allowedIDs[acct.ID] { + filtered = append(filtered, acct) + } + } + candidates = filtered + } + if len(candidates) == 0 { + return nil + } + groups := groupByPriority(candidates) + priorities := sortedDesc(groups) + for _, pri := range priorities { + if selected := weightedRandom(groups[pri]); selected != nil { + return selected + } + } + return nil +} + +// SelectFromWeighted 在可用账户 ∩ allowedIDs 中按评分函数选最优(均衡分配用)。 +// score 返回账户评分(越高越优先);评分只用于同优先级内排序,优先级分组仍生效。 +// allowedIDs 为空 = 不过滤;score 为 nil = 退化为 weightedRandom(等价 SelectFrom)。 +// 返回 nil 表示无可用账户。 +func (p *AccountPool) SelectFromWeighted(channel, group string, allowedIDs map[int64]bool, score func(acct *Account) float64) *Account { + candidates := p.filterAvailable(channel, group) + if len(allowedIDs) > 0 && len(candidates) > 0 { + filtered := make([]*Account, 0, len(candidates)) + for _, acct := range candidates { + if allowedIDs[acct.ID] { + filtered = append(filtered, acct) + } + } + candidates = filtered + } + if len(candidates) == 0 { + return nil + } + if score == nil { + return p.SelectFrom(channel, group, allowedIDs) + } + // 按 priority 分组,组内按评分 argmax(评分相等 → 按 ID 升序,确定性) + groups := groupByPriority(candidates) + priorities := sortedDesc(groups) + for _, pri := range priorities { + accts := groups[pri] + best := accts[0] + bestScore := score(best) + for _, acct := range accts[1:] { + s := score(acct) + if s > bestScore || (s == bestScore && acct.ID < best.ID) { + best, bestScore = acct, s + } + } + if best != nil { + c := *best + return &c + } + } + return nil +} + +// ReportResult 上报下单结果(成功/失败),用于熔断统计 +// 由 channel 在下单后调用 +func (p *AccountPool) ReportResult(accountID int64, success bool) { + p.mu.Lock() + defer p.mu.Unlock() + + acct, ok := p.accounts[accountID] + if !ok { + return + } + + if success { + // 成功:重置失败计数 + acct.FailCount = 0 + } else { + // 失败:递增失败计数 + acct.FailCount++ + if acct.AutoBan && acct.FailCount >= 3 { + // 连续失败 3 次 → 冷却 15 分钟 + acct.Status = StatusCooling + now := time.Now() + until := now.Add(15 * time.Minute) + acct.FrozenUntil = &until + } + } +} + +// AddDailyUsed 增加日已用额度(下单成功后调用) +func (p *AccountPool) AddDailyUsed(accountID int64, amount int64) { + p.mu.Lock() + defer p.mu.Unlock() + if acct, ok := p.accounts[accountID]; ok { + acct.DailyUsed += amount + } +} + +// ResetDailyUsed 重置所有账户的日已用额度(每日 00:00 调用,与 DB 重置同步) +func (p *AccountPool) ResetDailyUsed() { + p.mu.Lock() + defer p.mu.Unlock() + for _, acct := range p.accounts { + acct.DailyUsed = 0 + } +} + +// Count 返回池中账户总数 +func (p *AccountPool) Count() int { + p.mu.RLock() + defer p.mu.RUnlock() + return len(p.accounts) +} + +// ---------- 内部辅助 ---------- + +// filterAvailable 过滤可用账户 +// 注意:本函数会"恢复过期冷却"(写操作),因此必须持有写锁(由 Select 调用时保证) +func (p *AccountPool) filterAvailable(channel, group string) []*Account { + p.mu.Lock() + defer p.mu.Unlock() + + now := time.Now() + var out []*Account + for _, acct := range p.accounts { + // 通道匹配 + if acct.Channel != channel { + continue + } + // 分组匹配 + if acct.Group != group { + continue + } + // 冷却到期的自动恢复:必须先于状态过滤。 + // 只恢复 cooling(自动熔断到期);frozen(离线联动/风控/手动)不在此解冻, + // 由设备心跳恢复联动或管理员操作解除。 + if acct.Status == StatusCooling && acct.FrozenUntil != nil && !acct.FrozenUntil.After(now) { + acct.Status = StatusNormal + acct.FrozenUntil = nil + acct.FailCount = 0 + } + // 状态检查:仅 normal 可用 + if acct.Status != StatusNormal { + continue + } + // 日限额检查 + if acct.DailyLimit > 0 && acct.DailyUsed >= acct.DailyLimit { + continue + } + // 冷却中 + if acct.FrozenUntil != nil && acct.FrozenUntil.After(now) { + continue + } + out = append(out, acct) + } + return out +} + +// groupByPriority 按 priority 分组(高→低) +func groupByPriority(accts []*Account) map[int][]*Account { + groups := make(map[int][]*Account) + for _, acct := range accts { + groups[acct.Priority] = append(groups[acct.Priority], acct) + } + return groups +} + +// sortedDesc 返回排序后的 key 列表(降序) +func sortedDesc(m map[int][]*Account) []int { + keys := make([]int, 0, len(m)) + for k := range m { + keys = append(keys, k) + } + // 简单排序(降序) + for i := 0; i < len(keys); i++ { + for j := i + 1; j < len(keys); j++ { + if keys[j] > keys[i] { + keys[i], keys[j] = keys[j], keys[i] + } + } + } + return keys +} + +// weightedRandom 加权随机选择 +func weightedRandom(accts []*Account) *Account { + if len(accts) == 0 { + return nil + } + if len(accts) == 1 { + c := *accts[0] + return &c + } + + totalWeight := 0 + for _, acct := range accts { + weight := acct.Weight + if weight <= 0 { + weight = 1 + } + totalWeight += weight + } + + r := rand.Intn(totalWeight) + cumulative := 0 + for _, acct := range accts { + weight := acct.Weight + if weight <= 0 { + weight = 1 + } + cumulative += weight + if r < cumulative { + c := *acct + return &c + } + } + + // fallback + c := *accts[len(accts)-1] + return &c +} diff --git a/internal/account/qrcode.go b/internal/account/qrcode.go new file mode 100644 index 0000000..c7fdb5b --- /dev/null +++ b/internal/account/qrcode.go @@ -0,0 +1,34 @@ +package account + +import "strings" + +// InvalidQRContent 校验账户收款码内容格式。返回非空字符串 = 非法原因("" = 合法)。 +// 目的(资金安全防线):QRContent 直接决定扫码后用户的支付去向,脏数据(如把报错 +// 文案/任意文本误存为码)会生成一个无法支付的二维码,直接毁掉支付体验。 +// 合法格式(对齐两平台实际码结构): +// - 支付宝(personal.alipay):https:// 开头的收款码 URL +// - 微信(personal.wechat):wxp:// 或 weixin:// 开头的私有收款协议 +// +// 两平台都要求内容非空且不含空白/控制字符(QR 内容带换行/空格会编码异常)。 +// channel 形如 "personal.alipay" / "personal.wechat";空/未知通道按 https URL 要求。 +func InvalidQRContent(channel, content string) string { + if content == "" { + return "内容为空" + } + // 内容中任何位置都不允许空白/控制字符(QR 编码空格/换行会变异,扫码后内容不符) + if strings.IndexFunc(content, func(r rune) bool { + return r == ' ' || r == '\t' || r == '\r' || r == '\n' + }) >= 0 { + return "内容含空白或控制字符" + } + if strings.HasPrefix(channel, "personal.wechat") { + if strings.HasPrefix(content, "wxp://") || strings.HasPrefix(content, "weixin://") { + return "" + } + return "微信收款码必须以 wxp:// 或 weixin:// 开头" + } + if !strings.HasPrefix(content, "https://") { + return "收款码必须为 https:// URL" + } + return "" +} diff --git a/internal/account/qrcode_test.go b/internal/account/qrcode_test.go new file mode 100644 index 0000000..22acf5c --- /dev/null +++ b/internal/account/qrcode_test.go @@ -0,0 +1,143 @@ +package account + +import "testing" + +// TestInvalidQRContent_Wechat 微信收款码格式校验:wxp:// 与 weixin:// 合法,其它非法。 +func TestInvalidQRContent_Wechat(t *testing.T) { + cases := []struct { + name string + content string + wantErr bool + }{ + {"wxp 合法", "wxp://f2f01PGBgX9q6lITQMxL7rZ14-C8jDWNkaOPZ7mw2cYhTYW8NiseyukoiyOI8nWRURWQ", false}, + {"weixin 合法", "weixin://wxpay/bizpayurl/up?pr=NwY5Mz9", false}, + {"空字符串非法", "", true}, + {"TESTMAIN 报错文案非法", "TESTMAIN-AGAIN", true}, + {"普通文本非法", "hello world", true}, + {"带换行非法", "wxp://abc\n", true}, + {"带空格非法", "wxp://ab c", true}, + {"带 tab 非法", "wxp://abc\t", true}, + {"https 微信码非法", "https://qr.wechat.com/xxx", true}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + reason := InvalidQRContent("personal.wechat", tc.content) + if tc.wantErr && reason == "" { + t.Fatalf("期望非法(内容 %q),实际通过校验", tc.content) + } + if !tc.wantErr && reason != "" { + t.Fatalf("期望合法(内容 %q),实际拒绝: %s", tc.content, reason) + } + }) + } +} + +// TestInvalidQRContent_Alipay 支付宝收款码格式校验:https URL 合法,其它非法。 +func TestInvalidQRContent_Alipay(t *testing.T) { + cases := []struct { + name string + content string + wantErr bool + }{ + {"qr.alipay 合法", "https://qr.alipay.com/fkx11063uortchy1ft90ibb", false}, + {"通用 https 合法", "https://example.com/pay", false}, + {"空字符串非法", "", true}, + {"报错文案非法", "SYSTEM_ERROR", true}, + {"http 非法", "http://qr.alipay.com/xxx", true}, + {"wxp 支付宝码非法", "wxp://f2f0xxx", true}, + {"带换行非法", "https://qr.alipay.com/xx\n", true}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + reason := InvalidQRContent("personal.alipay", tc.content) + if tc.wantErr && reason == "" { + t.Fatalf("期望非法(内容 %q),实际通过校验", tc.content) + } + if !tc.wantErr && reason != "" { + t.Fatalf("期望合法(内容 %q),实际拒绝: %s", tc.content, reason) + } + }) + } +} + +// TestInvalidQRContent_UnknownChannel 未知通道按 https URL 要求校验(保守默认)。 +func TestInvalidQRContent_UnknownChannel(t *testing.T) { + if reason := InvalidQRContent("merchant.wechat", "wxp://abc"); reason == "" { + t.Fatal("非 personal 通道不应接受 wxp 码") + } + if reason := InvalidQRContent("", "https://x.com/pay"); reason != "" { + t.Fatalf("空通道 https URL 应合法,实际拒绝: %s", reason) + } +} + +// TestSelectFromWeighted_PicksHighestScore 均衡分配:SelectFromWeighted 选评分最高账户 +func TestSelectFromWeighted_PicksHighestScore(t *testing.T) { + p := NewPool() + p.Add(&Account{ID: 1, Channel: "personal.alipay", Group: "default", Name: "A", Priority: 1, + DailyUsed: 900, DailyLimit: 1000, Status: StatusNormal}) + p.Add(&Account{ID: 2, Channel: "personal.alipay", Group: "default", Name: "B", Priority: 1, + DailyUsed: 100, DailyLimit: 1000, Status: StatusNormal}) + p.Add(&Account{ID: 3, Channel: "personal.alipay", Group: "default", Name: "C", Priority: 1, + DailyUsed: 0, DailyLimit: 1000, Status: StatusNormal}) + + // 评分 = 日限额余量(1 - used/limit):C(1.0) > B(0.9) > A(0.1) + allowed := map[int64]bool{1: true, 2: true, 3: true} + got := p.SelectFromWeighted("personal.alipay", "default", allowed, + func(acct *Account) float64 { + return 1 - float64(acct.DailyUsed)/float64(acct.DailyLimit) + }) + if got == nil || got.ID != 3 { + t.Fatalf("应选 C(3),got %+v", got) + } +} + +// TestSelectFromWeighted_ExcludesDisallowed 均衡分配:allowedIDs 过滤生效 +func TestSelectFromWeighted_ExcludesDisallowed(t *testing.T) { + p := NewPool() + p.Add(&Account{ID: 1, Channel: "personal.alipay", Group: "default", Name: "A", Priority: 1, + DailyUsed: 900, DailyLimit: 1000, Status: StatusNormal}) + p.Add(&Account{ID: 2, Channel: "personal.alipay", Group: "default", Name: "B", Priority: 1, + DailyUsed: 100, DailyLimit: 1000, Status: StatusNormal}) + + // 只允许 1(评分低的 A)→ 必须选 A + allowed := map[int64]bool{1: true} + got := p.SelectFromWeighted("personal.alipay", "default", allowed, + func(acct *Account) float64 { + return 1 - float64(acct.DailyUsed)/float64(acct.DailyLimit) + }) + if got == nil || got.ID != 1 { + t.Fatalf("应选 A(1)(allowed 过滤),got %+v", got) + } + + // 不允许任何 → nil + got2 := p.SelectFromWeighted("personal.alipay", "default", map[int64]bool{999: true}, func(acct *Account) float64 { return 1 }) + if got2 != nil { + t.Fatalf("无 allowed 账户应 nil,got %+v", got2) + } +} + +// TestSelectFromWeighted_ScoreTieBreakByID 评分相等 → ID 升序确定性 +func TestSelectFromWeighted_ScoreTieBreakByID(t *testing.T) { + p := NewPool() + p.Add(&Account{ID: 5, Channel: "personal.alipay", Group: "default", Name: "E", Priority: 1, + DailyUsed: 500, DailyLimit: 1000, Status: StatusNormal}) + p.Add(&Account{ID: 2, Channel: "personal.alipay", Group: "default", Name: "B", Priority: 1, + DailyUsed: 500, DailyLimit: 1000, Status: StatusNormal}) + // 评分相同(0.5)→ 应选 ID 更小的 B(2) + got := p.SelectFromWeighted("personal.alipay", "default", nil, + func(acct *Account) float64 { return 0.5 }) + if got == nil || got.ID != 2 { + t.Fatalf("评分相等应选 ID 升序的 B(2),got %+v", got) + } +} + +// TestSelectFromWeighted_NilScoreFallsBack 评分函数为 nil → 退化为 SelectFrom(不 panic) +func TestSelectFromWeighted_NilScoreFallsBack(t *testing.T) { + p := NewPool() + p.Add(&Account{ID: 1, Channel: "personal.alipay", Group: "default", Name: "A", Priority: 1, + DailyUsed: 500, DailyLimit: 1000, Status: StatusNormal}) + got := p.SelectFromWeighted("personal.alipay", "default", nil, nil) + if got == nil || got.ID != 1 { + t.Fatalf("nil score 应退化选取,got %+v", got) + } +} diff --git a/internal/adbmgr/adbmgr.go b/internal/adbmgr/adbmgr.go new file mode 100644 index 0000000..cad6af6 --- /dev/null +++ b/internal/adbmgr/adbmgr.go @@ -0,0 +1,809 @@ +// Package adbmgr 管理 adb 触达的安卓设备(USB 直连真机 / adb over WiFi) +// +// 定位:web 前端可视化操作 adb 触达的安卓设备(docs/MIANQIAN.md §7.0 增强)。 +// 不做完整云手机(无实时屏幕流/远程点击控制),只提供"按需渲染 app 界面"所需的操作: +// - Screenshot: 截图当前屏幕(按需渲染) +// - Tap/Swipe/InputText: 轻量操作(完成登录等) +// - StartApp/ForegroundApp: 启动/查看前台 app +// - Online: 设备在线检测 +// +// 实现:直接调用系统 adb 二进制(-s 定位设备)。 +// USB 真机:serial 为纯设备号(如 "4f2bdc30"),无需 adb connect,直接 get-state 验证。 +// adb over WiFi:serial 为 host:port,需 adb connect 后再操作。 +package adbmgr + +import ( + "bytes" + "context" + "fmt" + "os" + "os/exec" + "regexp" + "strconv" + "strings" + "sync" + "time" +) + +// Device 一台 adb 触达的安卓设备(USB 真机 / adb over WiFi) +type Device struct { + Serial string // adb serial:USB 直连设备号 "4f2bdc30" 或 TCP "host:port"(adb over WiFi) + Port int // adb 端口(USB 直连为 0;adb over WiFi 使用) + Name string // 备注名(来自 personal_devices.name) +} + +// Manager adb 操作管理器(线程安全,可并发操作多设备) +type Manager struct { + adbBin string // adb 可执行路径(默认 "adb") + mu sync.Mutex + + // screenCache 设备实际生效分辨率进程内缓存(serial → 分辨率)。 + // 物理分辨率运行时不变 → 缓存无失效时间;查询/解析失败不写入(下次调用重试)。 + // 独立锁 screenMu 保护:不复用 mu(mu 是 adb 执行串行锁,ScreenSize 需在 + // 不持锁时调 Shell→adb() 再次锁 mu,共用一个非重入锁会死锁)。 + screenMu sync.Mutex + screenCache map[string]screenSize +} + +// screenSize 设备实际生效分辨率(宽/高,像素) +type screenSize struct { + w, h int +} + +// 参考分辨率:capture/ledgermon 全部硬编码坐标均按 1080x2400 真机实测。 +// TapRef/SwipeRef 调用前先按实际生效分辨率等比缩放(见各方法注释)。 +const ( + refW = 1080 + refH = 2400 +) + +// New 创建 adb 管理器;adbBin 为空时使用系统 PATH 中的 adb +func New(adbBin string) *Manager { + if adbBin == "" { + adbBin = "adb" + } + return &Manager{adbBin: adbBin, screenCache: make(map[string]screenSize)} +} + +// adb 执行单条 adb 命令(带超时),返回 stdout。 +// 全局串行化:WiFi adb 并发执行多条命令时 connect/命令互相竞争会超时 +// (2026-08-28 实测:预热导航与监听轮询并发打 adb,connect context deadline exceeded)。 +func (m *Manager) adb(ctx context.Context, timeout time.Duration, serial string, args ...string) (string, error) { + m.mu.Lock() + defer m.mu.Unlock() + + cctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + full := append([]string{"-s", serial}, args...) + cmd := exec.CommandContext(cctx, m.adbBin, full...) + var out, errb bytes.Buffer + cmd.Stdout = &out + cmd.Stderr = &errb + if err := cmd.Run(); err != nil { + msg := strings.TrimSpace(errb.String()) + if msg == "" { + msg = err.Error() + } + return "", fmt.Errorf("adb %s: %w (%s)", strings.Join(full, " "), err, msg) + } + return out.String(), nil +} + +// Connect 连接设备(幂等)。失败返回错误(设备离线/容器未起)。 +// serial 含 ":" 视为 TCP 地址(host:port,如 127.0.0.1:5555),执行 adb connect; +// 纯 serial(如 USB 直连设备 "4f2bdc30")跳过 connect,直接 get-state 验证。 +func (m *Manager) Connect(ctx context.Context, serial string) error { + if !strings.Contains(serial, ":") { + // USB 直连设备:无需 adb connect,验证状态即可 + out, err := m.adb(ctx, 5*time.Second, serial, "get-state") + if err != nil { + return err + } + if !strings.Contains(out, "device") { + return fmt.Errorf("adb %s: 设备状态异常 (%s)", serial, strings.TrimSpace(out)) + } + return nil + } + cctx, cancel := context.WithTimeout(ctx, 8*time.Second) + defer cancel() + // 与 adb() 共享全局锁:adb connect 也串行执行,避免并发竞争超时 + m.mu.Lock() + defer m.mu.Unlock() + cmd := exec.CommandContext(cctx, m.adbBin, "connect", serial) + var out, errb bytes.Buffer + cmd.Stdout = &out + cmd.Stderr = &errb + if err := cmd.Run(); err != nil { + return fmt.Errorf("adb connect %s: %w", serial, err) + } + res := out.String() + errb.String() + if strings.Contains(res, "cannot connect") || strings.Contains(res, "failed") || strings.Contains(res, "unable") { + return fmt.Errorf("adb connect %s: %s", serial, strings.TrimSpace(res)) + } + return nil +} + +// Online 设备是否在线(可连且已认证)。每次调用实时检测。 +func (m *Manager) Online(ctx context.Context, serial string) bool { + if err := m.Connect(ctx, serial); err != nil { + return false + } + out, err := m.adb(ctx, 5*time.Second, serial, "get-state") + if err != nil { + return false + } + return strings.Contains(out, "device") +} + +// 设备状态枚举(来自 `adb devices -l`) +const ( + StateDevice = "device" // 已授权可用 + StateUnauthorized = "unauthorized" // 待手机端授权 USB 调试 + StateOffline = "offline" // adb 链接异常 +) + +// USBDevice 一台已连 adb 的真机/虚拟设备的瞬时状态(引导向导用) +type USBDevice struct { + Serial string // adb serial:USB 设备号(如 "4f2bdc30")或 WiFi host:port(如 "192.168.1.119:5555") + State string // device / unauthorized / offline + Model string // 设备型号(如 "MI_8"),可能为空 + Kind string // "usb"(USB 直连)或 "wifi"(adb over WiFi,host:port) +} + +// ListDevices 列出当前已连到本机的真机(adb devices -l 输出,过滤模拟器/容器)。 +// 同时返回 USB 直连设备(serial 不含 ":")与 adb over WiFi 设备(serial 含 ":",已 connect 过的)。 +func (m *Manager) ListDevices(ctx context.Context) ([]USBDevice, error) { + cctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + cmd := exec.CommandContext(cctx, m.adbBin, "devices", "-l") + var out, errb bytes.Buffer + cmd.Stdout = &out + cmd.Stderr = &errb + if err := cmd.Run(); err != nil { + return nil, fmt.Errorf("adb devices: %w (%s)", err, strings.TrimSpace(errb.String())) + } + var devs []USBDevice + for _, line := range strings.Split(out.String(), "\n") { + line = strings.TrimSpace(line) + if line == "" || strings.HasPrefix(line, "List of devices") { + continue + } + fields := strings.Fields(line) + if len(fields) < 2 { + continue + } + serial := fields[0] + kind := "usb" + if strings.Contains(serial, ":") { + kind = "wifi" + } + state := fields[1] + model := "" + for _, f := range fields[2:] { + if strings.HasPrefix(f, "model:") { + model = strings.TrimPrefix(f, "model:") + break + } + } + devs = append(devs, USBDevice{Serial: serial, State: state, Model: model, Kind: kind}) + } + return devs, nil +} + +// Screenshot 截图当前屏幕,返回 PNG 字节 +func (m *Manager) Screenshot(ctx context.Context, serial string) ([]byte, error) { + if err := m.Connect(ctx, serial); err != nil { + return nil, err + } + cctx, cancel := context.WithTimeout(ctx, 15*time.Second) + defer cancel() + cmd := exec.CommandContext(cctx, m.adbBin, "-s", serial, "exec-out", "screencap", "-p") + var out bytes.Buffer + var errb bytes.Buffer + cmd.Stdout = &out + cmd.Stderr = &errb + if err := cmd.Run(); err != nil { + return nil, fmt.Errorf("screencap %s: %w (%s)", serial, err, strings.TrimSpace(errb.String())) + } + if out.Len() == 0 { + return nil, fmt.Errorf("screencap %s: 空输出(设备可能刚启动或锁屏)", serial) + } + return out.Bytes(), nil +} + +// ListFiles 列出设备端目录(shell ls -lt,按修改时间倒序),返回文件名列表 +func (m *Manager) ListFiles(ctx context.Context, serial, dir string) ([]string, error) { + if err := m.Connect(ctx, serial); err != nil { + return nil, err + } + out, err := m.adb(ctx, 10*time.Second, serial, "shell", "ls", "-t", dir) + if err != nil { + return nil, err + } + var names []string + for _, line := range strings.Split(out, "\n") { + line = strings.TrimSpace(line) + if line != "" { + names = append(names, line) + } + } + return names, nil +} + +// PullFile 从设备拉取文件到本地临时目录(adb pull),返回本地路径 +func (m *Manager) PullFile(ctx context.Context, serial, devicePath string) (string, error) { + if err := m.Connect(ctx, serial); err != nil { + return "", err + } + cctx, cancel := context.WithTimeout(ctx, 20*time.Second) + defer cancel() + tmp, err := os.CreateTemp("", "adbpull_*.png") + if err != nil { + return "", err + } + local := tmp.Name() + tmp.Close() + cmd := exec.CommandContext(cctx, m.adbBin, "-s", serial, "pull", devicePath, local) + var errb bytes.Buffer + cmd.Stderr = &errb + if err := cmd.Run(); err != nil { + os.Remove(local) + return "", fmt.Errorf("adb pull %s: %w (%s)", devicePath, err, strings.TrimSpace(errb.String())) + } + return local, nil +} + +// DumpUI 导出设备当前屏幕的 UI 层级树(uiautomator dump),返回 XML 文本。 +// 用途:服务端直接解析账本页/收款记录页等界面(不受 FLAG_SECURE 限制,adb 是系统级通道)。 +// 性能说明(2026-08-30 红米k60/MIUI14 实测): +// - 单次 dump 固定约 2.3s 为 uiautomator 进程启动 + MIUI 主题初始化开销(无法规避,无 root); +// - 复杂页面(支付宝账本 95-104KB)在动画中会 idle 等待至 10s+(实测偶发); +// - --compressed 实测仅省 ~8% 体积、无耗时收益,故不用(保持与解析器兼容); +// - 优化方向是减少 dump 调用次数(页面无变化时用哈希跳过,见 ledgermon reader), +// 而非压缩单次 dump。 +// +// 注意:MIUI 每次 dump 会向 stderr 打印 theme_compatibility.xml 缺失堆栈(约 2KB), +// 不影响结果,属固定开销。 +func (m *Manager) DumpUI(ctx context.Context, serial string) (string, error) { + if err := m.Connect(ctx, serial); err != nil { + return "", err + } + if _, err := m.adb(ctx, 8*time.Second, serial, "shell", "uiautomator", "dump", "/sdcard/pgw_ui.xml"); err != nil { + return "", err + } + // dump 落盘后 cat 读取(uiautomator dump 输出在 stdout 提示,XML 写入文件) + out, err := m.adb(ctx, 8*time.Second, serial, "shell", "cat", "/sdcard/pgw_ui.xml") + if err != nil { + return "", err + } + return out, nil +} + +// Shell 执行任意设备端 shell 命令(超时受控),返回 stdout +func (m *Manager) Shell(ctx context.Context, timeout time.Duration, serial, command string) (string, error) { + if err := m.Connect(ctx, serial); err != nil { + return "", err + } + return m.adb(ctx, timeout, serial, "shell", command) +} + +// ScreenSize 查询设备实际生效分辨率(adb shell wm size),返回 (宽, 高) 像素。 +// 结果按 serial 进程内缓存:同一设备多次查询不重复走 adb(缓存无失效时间, +// 分辨率运行时不变)。失败(adb 不可达/输出不可解析/数值非法)返回错误—— +// 调用方据此跳过本轮操作,不误操作设备。 +func (m *Manager) ScreenSize(ctx context.Context, serial string) (w, h int, err error) { + m.screenMu.Lock() + if sz, ok := m.screenCache[serial]; ok { + m.screenMu.Unlock() + return sz.w, sz.h, nil + } + m.screenMu.Unlock() + + out, err := m.Shell(ctx, 5*time.Second, serial, "wm size") + if err != nil { + return 0, 0, err + } + w, h, err = parseWmSize(out) + if err != nil { + return 0, 0, err + } + m.screenMu.Lock() + m.screenCache[serial] = screenSize{w: w, h: h} + m.screenMu.Unlock() + return w, h, nil +} + +// wmSizeRe 匹配 "WxH" 分辨率标记(如 1080x2400)。包级编译一次复用。 +var wmSizeRe = regexp.MustCompile(`(\d+)x(\d+)`) + +// parseWmSize 解析 `adb shell wm size` 输出 → 实际生效分辨率 (w, h)。 +// 关键:uiautomator dump / input tap 的坐标基于**当前生效分辨率**——设备设置了 +// override(开发者选项「最小宽度」/wm size 覆盖)时生效的是 Override 值而非 Physical +// (实测红米 K60:Physical 1440x3200 + Override 1080x2400,UI 坐标按 1080x2400)。 +// 解析顺序:优先 "Override size: WxH" 行;缺省回退 "Physical size: WxH"; +// 再兜底任意首个含 "WxH" 的行(裸格式)。均无合法分辨率返回错误。 +func parseWmSize(out string) (w, h int, err error) { + lines := strings.Split(out, "\n") + for _, ln := range lines { + if strings.HasPrefix(strings.TrimSpace(ln), "Override size:") { + if w, h, ok := parseSizeToken(ln); ok { + return w, h, nil + } + } + } + for _, ln := range lines { + if strings.HasPrefix(strings.TrimSpace(ln), "Physical size:") { + if w, h, ok := parseSizeToken(ln); ok { + return w, h, nil + } + } + } + for _, ln := range lines { + if w, h, ok := parseSizeToken(ln); ok { + return w, h, nil + } + } + return 0, 0, fmt.Errorf("wm size 输出无法解析出分辨率 (输出: %q)", strings.TrimSpace(out)) +} + +// parseSizeToken 提取行内首个合法 "WxH" 分辨率标记;无/数值非法返回 ok=false +func parseSizeToken(ln string) (w, h int, ok bool) { + m := wmSizeRe.FindStringSubmatch(ln) + if m == nil { + return 0, 0, false + } + w, errW := strconv.Atoi(m[1]) + h, errH := strconv.Atoi(m[2]) + if errW != nil || errH != nil || w <= 0 || h <= 0 { + return 0, 0, false + } + return w, h, true +} + +// scaleRef 把参考坐标系(refW x refH)坐标缩放到实际生效分辨率(四舍五入)。 +// ScreenSize 查询失败返回错误:宁可跳过本轮点击,也不在未知分辨率下盲点。 +func (m *Manager) scaleRef(ctx context.Context, serial string, x, y int) (int, int, error) { + w, h, err := m.ScreenSize(ctx, serial) + if err != nil { + return 0, 0, err + } + return (x*w + refW/2) / refW, (y*h + refH/2) / refH, nil +} + +// TapRef 按 1080x2400 参考坐标系自动缩放到实际分辨率后点击。 +// 用于真机实测于 1080x2400 的硬编码坐标;dump bounds 派生坐标用 Tap 不经缩放。 +// ScreenSize 缓存命中零开销;查询失败时返回错误(不盲点)。 +func (m *Manager) TapRef(ctx context.Context, serial string, x, y int) error { + sx, sy, err := m.scaleRef(ctx, serial, x, y) + if err != nil { + return fmt.Errorf("参考坐标缩放 (%d,%d) 失败: %w", x, y, err) + } + return m.Tap(ctx, serial, sx, sy) +} + +// SwipeRef 按 1080x2400 参考坐标系自动缩放到实际分辨率后滑动。 +func (m *Manager) SwipeRef(ctx context.Context, serial string, x1, y1, x2, y2, durationMs int) error { + sx1, sy1, err := m.scaleRef(ctx, serial, x1, y1) + if err != nil { + return fmt.Errorf("参考坐标缩放 (%d,%d) 失败: %w", x1, y1, err) + } + sx2, sy2, err := m.scaleRef(ctx, serial, x2, y2) + if err != nil { + return fmt.Errorf("参考坐标缩放 (%d,%d) 失败: %w", x2, y2, err) + } + return m.Swipe(ctx, serial, sx1, sy1, sx2, sy2, durationMs) +} + +// StartAppUser 以指定系统用户(双开/分身空间)启动应用;userId<=0 等价 StartApp +func (m *Manager) StartAppUser(ctx context.Context, serial, packageName, activity string, userId int) error { + if err := m.Connect(ctx, serial); err != nil { + return err + } + userArg := "" + if userId > 0 { + userArg = fmt.Sprintf(" --user %d", userId) + } + if activity != "" { + _, err := m.adb(ctx, 10*time.Second, serial, "shell", "am", "start", userArg, "-n", packageName+"/"+activity) + return err + } + _, err := m.adb(ctx, 10*time.Second, serial, "shell", "am", "start", userArg, + "-a", "android.intent.action.MAIN", + "-c", "android.intent.category.LAUNCHER", + "-p", packageName) + if err != nil { + _, err = m.adb(ctx, 10*time.Second, serial, "shell", "monkey", "-p", packageName, "-c", "android.intent.category.LAUNCHER", "1") + } + return err +} + +// ProcessAlive 目标包名进程是否存活(任意用户空间)。 +// 安卓后台 LMK 会杀 App 进程:任务栈随之丢失,热启动 am start 无法恢复到账本页, +// 必须先检测进程再决定走热/冷路径。双开(user 999)进程同样能被 pidof 查到。 +func (m *Manager) ProcessAlive(ctx context.Context, serial, packageName string) (bool, error) { + if err := m.Connect(ctx, serial); err != nil { + return false, err + } + out, err := m.adb(ctx, 8*time.Second, serial, "shell", "pidof", packageName) + if err != nil { + // pidof 对不存在的进程返回非零退出码 + 空输出 → 视为进程不存在(非 adb 故障) + return false, nil + } + return strings.TrimSpace(out) != "", nil +} + +// RestartAppUser 强制停止并重新启动指定用户空间的应用(确保从干净首页开始, +// 避免微信等 app 恢复到旧页面导致坐标导航落空)。只影响指定 user,不影响其它空间。 +// 启动用显式 activity(MAIN/LAUNCHER 类别对双开 user 不可靠,实测启动失败)。 +// 启动后轮询 ForegroundApp 直到目标包名出现在前台(最长 waitTimeout),避免固定 sleep 的时序抖动。 +func (m *Manager) RestartAppUser(ctx context.Context, serial, packageName, activity string, userId int) error { + if err := m.Connect(ctx, serial); err != nil { + return err + } + userArg := "" + if userId > 0 { + userArg = fmt.Sprintf(" --user %d", userId) + } + if _, err := m.adb(ctx, 10*time.Second, serial, "shell", "am", "force-stop", userArg, packageName); err != nil { + return err + } + time.Sleep(1500 * time.Millisecond) + if err := m.StartAppUser(ctx, serial, packageName, activity, userId); err != nil { + return err + } + // 轮询前台确认(最长 10s);确认不了不视为失败(部分 app 前台判定口径差异), + // 由调用方后续 activity 校验兜底。 + deadline := time.Now().Add(10 * time.Second) + for time.Now().Before(deadline) { + fg, err := m.ForegroundApp(ctx, serial) + if err == nil && fg == packageName { + return nil + } + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(800 * time.Millisecond): + } + } + return nil +} + +// Rm 删除设备端文件/目录(用于相册旧码清理) +func (m *Manager) Rm(ctx context.Context, serial, path string) error { + if err := m.Connect(ctx, serial); err != nil { + return err + } + _, err := m.adb(ctx, 10*time.Second, serial, "shell", "rm", "-f", path) + return err +} + +// Tap 模拟点击屏幕坐标(像素,基于截图分辨率) +func (m *Manager) Tap(ctx context.Context, serial string, x, y int) error { + if err := m.Connect(ctx, serial); err != nil { + return err + } + _, err := m.adb(ctx, 8*time.Second, serial, "shell", "input", "tap", fmt.Sprintf("%d", x), fmt.Sprintf("%d", y)) + return err +} + +// Swipe 模拟滑动 +func (m *Manager) Swipe(ctx context.Context, serial string, x1, y1, x2, y2, durationMs int) error { + if err := m.Connect(ctx, serial); err != nil { + return err + } + _, err := m.adb(ctx, 8*time.Second, serial, "shell", "input", "swipe", + fmt.Sprintf("%d", x1), fmt.Sprintf("%d", y1), fmt.Sprintf("%d", x2), fmt.Sprintf("%d", y2), + fmt.Sprintf("%d", durationMs)) + return err +} + +// InputText 输入文本(模拟键盘;特殊字符按 adb input 规则转义) +func (m *Manager) InputText(ctx context.Context, serial, text string) error { + if err := m.Connect(ctx, serial); err != nil { + return err + } + // adb shell input text 对空格/特殊字符敏感:用 %s 转义 + escaped := strings.ReplaceAll(text, " ", "%s") + escaped = strings.ReplaceAll(escaped, "&", "\\&") + _, err := m.adb(ctx, 8*time.Second, serial, "shell", "input", "text", escaped) + return err +} + +// KeyEvent 发送按键(如 KEYCODE_BACK=4, KEYCODE_HOME=3, KEYCODE_ENTER=66, KEYCODE_CLEAR=28) +func (m *Manager) KeyEvent(ctx context.Context, serial string, keycode int) error { + if err := m.Connect(ctx, serial); err != nil { + return err + } + _, err := m.adb(ctx, 8*time.Second, serial, "shell", "input", "keyevent", fmt.Sprintf("%d", keycode)) + return err +} + +// StartApp 启动应用(按包名 + 可选 activity) +// 未指定 activity 时通过 LAUNCHER 类别启动默认入口(避免依赖 monkey,精简镜像无 monkey) +func (m *Manager) StartApp(ctx context.Context, serial, packageName, activity string) error { + if err := m.Connect(ctx, serial); err != nil { + return err + } + if activity != "" { + _, err := m.adb(ctx, 10*time.Second, serial, "shell", "am", "start", "-n", packageName+"/"+activity) + return err + } + // 优先:am start 带 MAIN/LAUNCHER 类别(等效 monkey 但更可靠) + _, err := m.adb(ctx, 10*time.Second, serial, "shell", "am", "start", + "-a", "android.intent.action.MAIN", + "-c", "android.intent.category.LAUNCHER", + "-p", packageName) + if err != nil { + // 兜底:monkey(部分镜像支持) + _, err = m.adb(ctx, 10*time.Second, serial, "shell", "monkey", "-p", packageName, "-c", "android.intent.category.LAUNCHER", "1") + } + return err +} + +// ForegroundApp 当前前台 app 描述串(形如 "u0 com.eg.android.AlipayGphone/.PayeeQRActivity t30545")。 +// 保留原语义:capture 包用 strings.Contains 匹配 Activity 名(如 "PayeeQRActivity"), +// 只返回包名会破坏这些检查。需要结构化三元组(pkg/activity/userID)请用 ForegroundAppDetail。 +func (m *Manager) ForegroundApp(ctx context.Context, serial string) (string, error) { + if err := m.Connect(ctx, serial); err != nil { + return "", err + } + out, err := m.adb(ctx, 8*time.Second, serial, "shell", "dumpsys", "activity", "activities") + if err != nil { + return "", err + } + for _, line := range strings.Split(out, "\n") { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "mResumedActivity") || strings.HasPrefix(line, "topResumedActivity") || strings.HasPrefix(line, "mFocusedApp") { + if idx := strings.Index(line, "{"); idx >= 0 { + rest := line[idx:] + if p := strings.Index(rest, " "); p >= 0 { + return strings.TrimPrefix(rest[p+1:], "u0/"), nil + } + } + } + } + return "", fmt.Errorf("未找到前台 activity") +} + +// ForegroundAppDetail 当前前台 app 三要素:包名 + Activity + 用户空间(0=主空间,999=双开分身)。 +// +// dumpsys activity activities 中 mResumedActivity 行实际格式: +// +// mResumedActivity: ActivityRecord{974988a u0 com.eg.android.AlipayGphone/.XRiverTransActivity t30545} +// mResumedActivity: ActivityRecord{974988a u999 com.tencent.mm/.ui.LauncherUI t30545} +// +// 双开(user 999)与主空间(user 0)的同一 Activity 名前缀不同(u0 / u999), +// 账本页监听必须按 userID 区分(否则 user0 的监听循环会把 user999 的账本页误判为已就绪 → 读错数据)。 +// 解析失败(如前台是桌面/无 resumed activity)返回空三元组 + 错误。 +func (m *Manager) ForegroundAppDetail(ctx context.Context, serial string) (pkg, activity string, userID int, err error) { + if err = m.Connect(ctx, serial); err != nil { + return "", "", 0, err + } + out, err := m.adb(ctx, 8*time.Second, serial, "shell", "dumpsys", "activity", "activities") + if err != nil { + return "", "", 0, err + } + for _, line := range strings.Split(out, "\n") { + line = strings.TrimSpace(line) + if !strings.HasPrefix(line, "mResumedActivity") && !strings.HasPrefix(line, "topResumedActivity") && !strings.HasPrefix(line, "mFocusedApp") { + continue + } + if idx := strings.Index(line, "{"); idx >= 0 { + rest := line[idx:] + if p := strings.Index(rest, " "); p >= 0 { + fields := strings.Fields(rest[p+1:]) + if len(fields) == 0 { + continue + } + first := fields[0] + // 用户空间前缀:u0 / u999(Android 多用户);无前缀视为主空间 + if strings.HasPrefix(first, "u") && len(first) > 1 && first[1] >= '0' && first[1] <= '9' { + if n, nerr := strconv.Atoi(first[1:]); nerr == nil { + userID = n + if len(fields) >= 2 { + first = fields[1] + } else { + continue + } + } + } + pkg, activity = splitPackageActivity(first) + if pkg == "" { + continue + } + return pkg, activity, userID, nil + } + } + } + return "", "", 0, fmt.Errorf("未找到前台 activity") +} + +// splitPackageActivity 拆分 "com.pkg/.Activity" → (包名, Activity 名) +// Activity 可能为 ".Xxx"(相对)或 "com.pkg.Xxx"(全名);无 Activity 时 activity 为空。 +func splitPackageActivity(full string) (pkg, activity string) { + if i := strings.Index(full, "/"); i >= 0 { + pkg = full[:i] + activity = full[i+1:] + if strings.HasPrefix(activity, ".") { + activity = pkg + activity + } + return pkg, activity + } + return full, "" +} + +// WakeUp 唤醒屏幕并解锁(模拟电源键 + 滑动解锁) +func (m *Manager) WakeUp(ctx context.Context, serial string) error { + if err := m.Connect(ctx, serial); err != nil { + return err + } + m.adb(ctx, 5*time.Second, serial, "shell", "input", "keyevent", "224") // KEYCODE_WAKEUP + return nil +} + +// GoHome 派发 HOME 键回桌面,清掉前一个账户驻留的前台。 +// 冷启动导航前调用:MIUI 双用户隔离下,跨 user(如 u999 微信占前台时)冷启动另一 +// 用户(u0 支付宝)会被驻留前台挡住,am start 无法抢占(实测 30s 前台纹丝不动)。 +// 先清 PINNED(若残留 task lock 会拦截 HOME),再发 HOME、等桌面稳定。 +func (m *Manager) GoHome(ctx context.Context, serial string) error { + if err := m.Connect(ctx, serial); err != nil { + return err + } + _, _ = m.adb(ctx, 5*time.Second, serial, "shell", "am", "task", "lock", "stop") // 清 PINNED(幂等) + time.Sleep(300 * time.Millisecond) + _, err := m.adb(ctx, 5*time.Second, serial, "shell", "input", "keyevent", "3") // KEYCODE_HOME + return err +} + +// PinCurrentTask 屏幕固定当前前台任务(task lock,2026-08-28 真机验证): +// 固定期间其他 app 无法抢占前台(am start 被拦截,error 101 START_CANCELED), +// 用于冷启动导航等长操作期间防止监听 app/系统弹窗打断。 +// 返回当前前台 taskId(固定成功后该 task 即被锁定)。 +func (m *Manager) PinCurrentTask(ctx context.Context, serial string) (string, error) { + if err := m.Connect(ctx, serial); err != nil { + return "", err + } + out, err := m.adb(ctx, 8*time.Second, serial, "shell", "dumpsys", "activity", "activities") + if err != nil { + return "", err + } + taskID := "" + for _, line := range strings.Split(out, "\n") { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "topResumedActivity") || strings.HasPrefix(line, "mResumedActivity") { + if i := strings.LastIndex(line, "t"); i >= 0 { + cand := strings.TrimSuffix(line[i+1:], "}") + if cand != "" { + taskID = cand + } + } + break + } + } + if taskID == "" { + return "", fmt.Errorf("未找到前台任务 taskId") + } + if _, err := m.adb(ctx, 8*time.Second, serial, "shell", "am", "task", "lock", taskID); err != nil { + return "", fmt.Errorf("固定前台任务失败: %w", err) + } + return taskID, nil +} + +// UnpinTask 解除屏幕固定(am task lock stop)。解锁后其他 app 可正常抢占前台。 +// 幂等:非固定状态调用无副作用(返回 nil)。 +func (m *Manager) UnpinTask(ctx context.Context, serial string) error { + if err := m.Connect(ctx, serial); err != nil { + return err + } + _, err := m.adb(ctx, 8*time.Second, serial, "shell", "am", "task", "lock", "stop") + return err +} + +// FindLedgerTaskID 扫描 dumpsys 查询指定 user 的账本页任务栈 ID(如 lite01 亲和栈)。 +// 在 dumpsys activity activities 输出中找包含 actSubstr 且匹配 userID 的 ActivityRecord, +// 提取其 taskId(如 "22233")。账本页不存在/进程被清时返回空 taskID+错误。 +func (m *Manager) FindLedgerTaskID(ctx context.Context, serial, pkg, actSubstr string, userID int) (string, error) { + if err := m.Connect(ctx, serial); err != nil { + return "", err + } + out, err := m.adb(ctx, 8*time.Second, serial, "shell", "dumpsys", "activity", "activities") + if err != nil { + return "", fmt.Errorf("dumpsys 失败: %w", err) + } + userPrefix := fmt.Sprintf("u%d", userID) + for _, line := range strings.Split(out, "\n") { + line = strings.TrimSpace(line) + // 匹配 ActivityRecord{... uN pkg/...actSubstr...} tNNNN} + // 示例:ActivityRecord{fe2acf2 u999 com.eg.android.AlipayGphone/...XRiverActivity$App01} t22233} + if !strings.Contains(line, userPrefix) || !strings.Contains(line, actSubstr) { + continue + } + // 从行尾提取 tNNNN(如 "t22233}" → "22233") + if i := strings.LastIndex(line, " t"); i >= 0 { + cand := strings.TrimSuffix(line[i+2:], "}") + // 校验纯数字,避免匹配其他含 t 的 token + if cand != "" && cand[0] >= '0' && cand[0] <= '9' { + return cand, nil + } + } + } + return "", fmt.Errorf("未找到匹配的账本页任务栈(pkg=%s, act=%s, user=%d)", pkg, actSubstr, userID) +} + +// TaskLock 用 am task lock 锁定并唤醒指定任务栈到前台。相当于微信热启动方案 +// taskLock(serial, taskID),~160ms。 +// 注意:锁定后设备进入 PINNED 模式,会拦截 input tap/swipe,调用方应在确认 +// 前台后调用 UnpinTask 释放。 +func (m *Manager) TaskLock(ctx context.Context, serial, taskID string) error { + if err := m.Connect(ctx, serial); err != nil { + return err + } + _, err := m.adb(ctx, 8*time.Second, serial, "shell", "am", "task", "lock", taskID) + return err +} + +// TCPIP 开启设备的无线 adb(`adb tcpip `)。 +// USB 直连时调用:设备进入 TCP/IP 监听模式,之后同一局域网可通过 : 连接。 +// 幂等:已开启会输出 "restarting in TCP mode port: N",重复调用无副作用。 +func (m *Manager) TCPIP(ctx context.Context, serial string, port int) error { + if err := m.Connect(ctx, serial); err != nil { + return err + } + out, err := m.adb(ctx, 10*time.Second, serial, "tcpip", fmt.Sprintf("%d", port)) + if err != nil { + return fmt.Errorf("adb tcpip 失败: %w", err) + } + if !strings.Contains(out, "restarting in TCP mode port") { + return fmt.Errorf("adb tcpip 未确认成功: %s", strings.TrimSpace(out)) + } + return nil +} + +// WifiIP 获取设备 WiFi 局域网 IP(引导向导自动配置 WiFi 备用通道用)。 +// 依次尝试多种方式:wlan0 网卡 → eth0 → getprop(部分 ROM 无 ip 命令时的兜底)。 +// 返回 "" 表示无法确定(设备未连 WiFi 或命令不可用)。 +func (m *Manager) WifiIP(ctx context.Context, serial string) string { + // 1. `ip addr show wlan0`(标准 Android) + if out, err := m.adb(ctx, 6*time.Second, serial, "shell", "ip", "addr", "show", "wlan0"); err == nil { + if ip := parseIPv4(out); ip != "" { + return ip + } + } + // 2. eth0(部分设备 wlan0 名为 eth0) + if out, err := m.adb(ctx, 6*time.Second, serial, "shell", "ip", "addr", "show", "eth0"); err == nil { + if ip := parseIPv4(out); ip != "" { + return ip + } + } + // 3. dhcp getprop(无 ip 命令的精简 ROM) + if out, err := m.adb(ctx, 6*time.Second, serial, "shell", "getprop", "dhcp.wlan0.ipaddress"); err == nil { + if ip := strings.TrimSpace(out); ip != "" { + return ip + } + } + return "" +} + +// parseIPv4 从 `ip addr` 输出提取首个内网 IPv4(跳过 127.0.0.1) +func parseIPv4(out string) string { + for _, line := range strings.Split(out, "\n") { + line = strings.TrimSpace(line) + if !strings.HasPrefix(line, "inet ") { + continue + } + fields := strings.Fields(line) + if len(fields) < 2 { + continue + } + addr := fields[1] + if idx := strings.Index(addr, "/"); idx >= 0 { + addr = addr[:idx] + } + if addr == "" || strings.HasPrefix(addr, "127.") { + continue + } + return addr + } + return "" +} diff --git a/internal/adbmgr/adbmgr_screen_test.go b/internal/adbmgr/adbmgr_screen_test.go new file mode 100644 index 0000000..23e8440 --- /dev/null +++ b/internal/adbmgr/adbmgr_screen_test.go @@ -0,0 +1,50 @@ +package adbmgr + +import ( + "testing" +) + +// TestParseWmSize 验证 `adb shell wm size` 输出解析(ScreenSize 内部逻辑,与 adb 解耦)。 +// 覆盖:Override/Physical/裸格式三种来源、优先级(Override 优先——input/dump 坐标基准)、 +// 空白/换行容错、非法与空输出。 +func TestParseWmSize(t *testing.T) { + cases := []struct { + name string + out string + wantW int + wantH int + wantErr bool + }{ + {"Physical 标准格式", "Physical size: 1080x2400\n", 1080, 2400, false}, + {"Override 标准格式", "Override size: 720x1600\n", 720, 1600, false}, + {"裸格式", "1440x3200", 1440, 3200, false}, + {"Override 优先于 Physical", "Physical size: 1080x2400\nOverride size: 720x1600\n", 720, 1600, false}, + {"红米K60 双尺寸实测", "Physical size: 1440x3200\nOverride size: 1080x2400\n", 1080, 2400, false}, + {"Physical 在第二行", "Override size: 720x1600\nPhysical size: 1080x2400\n", 720, 1600, false}, + {"前后多余空白", " \nOverride size: 720x1600 \n \n", 720, 1600, false}, + {"CRLF 行尾", "Physical size: 1080x2400\r\nOverride size: 720x1600\r\n", 720, 1600, false}, + {"Override 后跟无关行", "Override size: 720x1600\nsome garbage here\n", 720, 1600, false}, + {"垃圾输出", "no size available\n", 0, 0, true}, + {"空输出", "", 0, 0, true}, + {"Physical 行数值非法但有 Override", "Physical size: abcxdef\nOverride size: 720x1600\n", 720, 1600, false}, + {"半截分辨率", "Physical size: 1080x\n", 0, 0, true}, + {"零分辨率非法", "Physical size: 0x0\n", 0, 0, true}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + w, h, err := parseWmSize(tc.out) + if tc.wantErr { + if err == nil { + t.Fatalf("parseWmSize(%q) = (%d, %d, nil), 期望错误", tc.out, w, h) + } + return + } + if err != nil { + t.Fatalf("parseWmSize(%q) 意外错误: %v", tc.out, err) + } + if w != tc.wantW || h != tc.wantH { + t.Fatalf("parseWmSize(%q) = (%d, %d), want (%d, %d)", tc.out, w, h, tc.wantW, tc.wantH) + } + }) + } +} diff --git a/internal/adbmgr/keepalive.go b/internal/adbmgr/keepalive.go new file mode 100644 index 0000000..8a645c9 --- /dev/null +++ b/internal/adbmgr/keepalive.go @@ -0,0 +1,113 @@ +package adbmgr + +import ( + "context" + "fmt" + "strings" + "time" + + "paygateway/internal/logger" +) + +// keepalive 保活命令集:为支付通道 app(微信/支付宝)配置 Android/MIUI 后台存活保障。 +// +// 背景(2026-08-30 红米k60/MIUI14 实测): +// - 网关通过 adb 控制真机上的微信/支付宝读取账本/出收款码,账本页必须常驻后台; +// - MIUI 是"杀后台大户"(省电策略/墓碑冻结),但标准 adb 命令可覆盖其中一部分; +// - 本文件只封装"实测有效"的命令,避免给运维虚假安全感。 +// +// 实测有效的命令(全部无 root): +// - deviceidle whitelist + 加入电池优化白名单(dumpsys deviceidle 验证通过) +// - appops RUN_IN_BACKGROUND allow 允许后台运行 +// - appops RUN_ANY_IN_BACKGROUND allow 允许任意后台运行(实测微信/支付宝均已生效) +// +// 实测无效 / 被 MIUI 接管(不封装,避免误导): +// - am set-standby-bucket active 返回 bucket=5(restricted),MIUI 覆盖标准机制 +// - appops MIUIOP:1000x set Unknown operation string(MIUI 私有 op 不暴露 shell) +// - BOOT_COMPLETED / FOREGROUND_SERVICE appops key(Android 13 无此 op 名) +// - MIUI whetstone 冻结豁免(miui.whetstone.power 服务无 shell 入口,需 UI 设"无限制"省电) +// +// 补充:锁任务(LockPaymentTasks,见 alipay_lock.go)用 uiautomator 长按任务卡片实现, +// 是一键清理后仍保留的关键手段,与本命令集互补。 +// 局限:锁任务/白名单都防不住 MIUI 墓碑冻结(进程冻结后热唤醒回主进程)——这是 UI 层 +// "省电策略→无限制"才能解决的(无 root 无 adb 入口),cached_apps_freezer 只关掉原生层。 + +// 支付通道应用包名(保活目标;与 ledgermon reader 各自声明的常量一致) +const ( + WechatPkg = "com.tencent.mm" + AlipayPkg = "com.eg.android.AlipayGphone" +) + +// KeepAliveApply 对单个包应用全部实测有效的保活命令。 +// 返回实际应用了哪些命令(供日志/展示)。幂等:重复调用无害。 +func (m *Manager) KeepAliveApply(ctx context.Context, serial, pkg string) ([]string, error) { + if err := m.Connect(ctx, serial); err != nil { + return nil, err + } + applied := make([]string, 0, 3) + + // 1. 电池优化白名单(deviceidle) + if _, err := m.adb(ctx, 10*time.Second, serial, "shell", "dumpsys", "deviceidle", "whitelist", "+"+pkg); err != nil { + return applied, fmt.Errorf("deviceidle 白名单 %s: %w", pkg, err) + } + applied = append(applied, "deviceidle-whitelist") + + // 2. 允许后台运行 + for _, op := range []string{"RUN_IN_BACKGROUND", "RUN_ANY_IN_BACKGROUND"} { + if _, err := m.adb(ctx, 10*time.Second, serial, "shell", "cmd", "appops", "set", pkg, op, "allow"); err != nil { + return applied, fmt.Errorf("appops %s %s: %w", pkg, op, err) + } + applied = append(applied, "appops-"+strings.ToLower(op)) + } + + logger.Info("adbmgr", "已应用保活命令", "serial", serial, "pkg", pkg, "applied", strings.Join(applied, ",")) + return applied, nil +} + +// KeepAliveDeviceFreezer 禁用设备级 Cached Apps Freezer(AOSP 缓存应用冻结器)。 +// Android 12+ 系统会在内存压力下冻结后台缓存进程(isFrozen=true),冻结进程热唤醒 +// 可能丢任务栈状态。实测设置后 use_freezer=false(生效)。设备级设置,幂等。 +func (m *Manager) KeepAliveDeviceFreezer(ctx context.Context, serial string) error { + if err := m.Connect(ctx, serial); err != nil { + return err + } + if _, err := m.adb(ctx, 10*time.Second, serial, "shell", "settings", "put", "global", "cached_apps_freezer", "disabled"); err != nil { + return fmt.Errorf("禁用 cached_apps_freezer: %w", err) + } + logger.Info("adbmgr", "已禁用设备缓存应用冻结器", "serial", serial) + return nil +} + +// KeepAliveStatus 查询某包当前的保活状态(供运维/前端展示,只读)。 +// 返回 map:deviceidle 白名单 / RUN_IN_BACKGROUND / RUN_ANY_IN_BACKGROUND 的 allow 状态。 +func (m *Manager) KeepAliveStatus(ctx context.Context, serial, pkg string) (map[string]bool, error) { + if err := m.Connect(ctx, serial); err != nil { + return nil, err + } + st := map[string]bool{} + + // deviceidle 白名单:dumpsys deviceidle whitelist 含 "user,," 行即已加入 + out, err := m.adb(ctx, 10*time.Second, serial, "shell", "dumpsys", "deviceidle", "whitelist") + if err == nil { + st["deviceidle"] = strings.Contains(out, "user,"+pkg+",") + } + + // appops 状态:RUN_IN_BACKGROUND / RUN_ANY_IN_BACKGROUND 行含 ": allow" + out, err = m.adb(ctx, 10*time.Second, serial, "shell", "cmd", "appops", "get", pkg) + if err == nil { + for _, op := range []string{"RUN_IN_BACKGROUND", "RUN_ANY_IN_BACKGROUND"} { + st[strings.ToLower(op)] = opLineAllow(out, op) + } + } + return st, nil +} + +// opLineAllow 在 appops get 输出中查找 ": allow" 行。 +func opLineAllow(out, op string) bool { + for _, line := range strings.Split(out, "\n") { + if strings.HasPrefix(line, op+":") { + return strings.Contains(line, "allow") + } + } + return false +} diff --git a/internal/adbmgr/keepalive_test.go b/internal/adbmgr/keepalive_test.go new file mode 100644 index 0000000..8d6c6bb --- /dev/null +++ b/internal/adbmgr/keepalive_test.go @@ -0,0 +1,75 @@ +package adbmgr + +import ( + "strings" + "testing" +) + +// TestOpLineAllow 验证 appops get 输出解析(KeepAliveStatus 内部逻辑)。 +func TestOpLineAllow(t *testing.T) { + cases := []struct { + name string + out string + op string + want bool + }{ + {"allow 命中", "RUN_IN_BACKGROUND: allow\nFOO: deny", "RUN_IN_BACKGROUND", true}, + {"deny 不命中", "RUN_IN_BACKGROUND: deny\n", "RUN_IN_BACKGROUND", false}, + {"op 不存在", "SOME_OTHER: allow\n", "RUN_IN_BACKGROUND", false}, + {"allow 带时间戳", "RUN_ANY_IN_BACKGROUND: allow; time=+2d22h47m34s ago", "RUN_ANY_IN_BACKGROUND", true}, + {"前缀不误匹配", "RUN_ANY_IN_BACKGROUND_EXTRA: allow\n", "RUN_ANY_IN_BACKGROUND", false}, + {"空输出", "", "RUN_IN_BACKGROUND", false}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + if got := opLineAllow(tc.out, tc.op); got != tc.want { + t.Fatalf("opLineAllow(%q, %q) = %v, want %v", tc.out, tc.op, got, tc.want) + } + }) + } +} + +// TestKeepAliveStatus 用假设备输出验证 status 组装(不依赖真实 adb)。 +// 通过覆盖 adb 命令返回模拟输出——这里直接测解析层逻辑(与 adb 解耦)。 +func TestKeepAliveStatusParsing(t *testing.T) { + // 模拟 appops get 输出 + appopsOut := "RUN_IN_BACKGROUND: allow\nRUN_ANY_IN_BACKGROUND: allow; time=+2d ago\n" + devidleOut := "user,com.tencent.mm,10277\nuser,com.eg.android.AlipayGphone,10250\n" + + if !opLineAllow(appopsOut, "RUN_IN_BACKGROUND") { + t.Fatal("RUN_IN_BACKGROUND 应解析为 allow") + } + if !opLineAllow(appopsOut, "RUN_ANY_IN_BACKGROUND") { + t.Fatal("RUN_ANY_IN_BACKGROUND 应解析为 allow") + } + // deviceidle 输出含 "user,," 即已加入白名单 + if !containsPkg(devidleOut, "com.tencent.mm") { + t.Fatal("deviceidle 输出应含 com.tencent.mm") + } +} + +func containsPkg(out, pkg string) bool { + for _, line := range splitLines(out) { + if strings.HasPrefix(line, "user,"+pkg+",") { + return true + } + } + return false +} + +func splitLines(s string) []string { + var out []string + cur := "" + for _, r := range s { + if r == '\n' { + out = append(out, cur) + cur = "" + continue + } + cur += string(r) + } + if cur != "" { + out = append(out, cur) + } + return out +} diff --git a/internal/admin/accounts.go b/internal/admin/accounts.go new file mode 100755 index 0000000..a48e4c1 --- /dev/null +++ b/internal/admin/accounts.go @@ -0,0 +1,861 @@ +// Package admin 管理后台:账户池管理 API +// +// 安全设计: +// - 本文件是唯一访问 accounts.db 的组件 +// - 敏感字段(private_key / public_key / api_key)AES-256-GCM 加密后落库 +// - 解密后的账户数据推送到 AccountPool(内存),供其他模块使用 +// - API 响应不返回明文密钥(仅 "****" + 后4位) +// - 唯一例外:GET /accounts/:id/secret(需登录,供管理后台「查看/复制完整密钥」),返回解密后的明文 +package admin + +import ( + "context" + "fmt" + "net/http" + "strconv" + "strings" + "time" + + "github.com/gin-gonic/gin" + + "paygateway/internal/account" + "paygateway/internal/channel" + "paygateway/internal/logger" + "paygateway/internal/store" +) + +// AccountHandler 账户管理处理器 +type AccountHandler struct { + accountDB *store.AccountDB // 唯一访问 accounts.db 的组件 + pool *account.AccountPool + crypto *accountCrypto // 加密/解密工具 +} + +// accountCrypto 封装 AES 加解密(避免直接暴露 account 包的全局函数) +type accountCrypto struct { + init bool + encFn func([]byte) (string, error) + decFn func(string) ([]byte, error) + encJFn func(any) (string, error) + decJFn func(string, any) error +} + +func newAccountCrypto(masterKey string) (*accountCrypto, error) { + if err := account.InitCrypto(masterKey); err != nil { + return nil, fmt.Errorf("账户加密初始化失败: %w", err) + } + return &accountCrypto{ + init: true, + encFn: account.Encrypt, + decFn: account.Decrypt, + encJFn: account.EncryptJSON, + decJFn: account.DecryptJSON, + }, nil +} + +func (c *accountCrypto) encrypt(plaintext []byte) (string, error) { return c.encFn(plaintext) } +func (c *accountCrypto) decrypt(hex string) ([]byte, error) { return c.decFn(hex) } + +// NewAccountHandler 创建账户管理处理器 +func NewAccountHandler(db *store.AccountDB, pool *account.AccountPool, masterKey string) (*AccountHandler, error) { + crypto, err := newAccountCrypto(masterKey) + if err != nil { + return nil, err + } + return &AccountHandler{ + accountDB: db, + pool: pool, + crypto: crypto, + }, nil +} + +// InitFromDB 启动时从 accounts.db 加载所有账户,解密后初始化 AccountPool +func (h *AccountHandler) InitFromDB(ctx context.Context) error { + encrypted, err := h.accountDB.ListAccounts(ctx, "") + if err != nil { + return fmt.Errorf("加载账户列表失败: %w", err) + } + var decryptErrs []string + for _, e := range encrypted { + acct, err := h.decryptAccount(e) + if err != nil { + logger.Error("account", "解密账户失败(主密钥不一致或数据损坏)", + "id", e.ID, "name", e.Name, "err", err) + decryptErrs = append(decryptErrs, fmt.Sprintf("id=%d name=%s: %v", e.ID, e.Name, err)) + continue + } + h.pool.Add(acct) + } + // P1 守卫:DB 非空但所有/部分记录解密失败 = 主密钥变更或数据损坏,禁止静默启动(否则网关收款链路全断) + if len(decryptErrs) > 0 { + return fmt.Errorf("accounts.db 解密失败 %d/%d 条(疑似 ACCOUNT_MASTER_KEY 变更),已拒绝启动;请恢复 .env 中的 ACCOUNT_MASTER_KEY 或清空 accounts.db 后重新建号。失败明细:\n%s", + len(decryptErrs), len(encrypted), strings.Join(decryptErrs, "\n")) + } + logger.Info("account", "账户池初始化完成", "count", h.pool.Count()) + return nil +} + +// RegisterRoutes 注册账户管理路由(需在 authMiddleware 组内) +func (h *AccountHandler) RegisterRoutes(rg *gin.RouterGroup) { + rg.GET("/accounts", h.handleListAccounts) + rg.GET("/accounts/:id", h.handleGetAccount) + rg.POST("/accounts", h.handleCreateAccount) + rg.PUT("/accounts/:id", h.handleUpdateAccount) + rg.DELETE("/accounts/:id", h.handleDeleteAccount) + rg.POST("/accounts/:id/freeze", h.handleFreezeAccount) + rg.POST("/accounts/:id/unfreeze", h.handleUnfreezeAccount) + rg.POST("/accounts/:id/enable", h.handleEnableAccount) + rg.POST("/accounts/:id/disable", h.handleDisableAccount) + rg.GET("/accounts/:id/logs", h.handleAccountLogs) + rg.GET("/accounts/:id/secret", h.handleAccountSecret) + // 收款码图片解析(docs/MIANQIAN.md §7.6 阶段一:上传收款码 → 服务端解析 → 绑定账户) + rg.POST("/accounts/decode-qrcode", h.handleDecodeQRCode) +} + +// ========== API Handlers ========== + +// AccountResponse 前端展示的账户信息(密钥脱敏) +type AccountResponse struct { + ID int64 `json:"id"` + Channel string `json:"channel"` + Name string `json:"name"` + AppID string `json:"app_id"` + MchID string `json:"mch_id,omitempty"` + PrivateKey string `json:"private_key"` // 脱敏显示 + PublicKey string `json:"public_key,omitempty"` // 脱敏显示 + ApiKey string `json:"api_key,omitempty"` // 脱敏显示 + QRContent string `json:"qr_content,omitempty"` // 收款码内容(脱敏,个人通道) + Weight int `json:"weight"` + Priority int `json:"priority"` + Status string `json:"status"` + FrozenReason string `json:"frozen_reason,omitempty"` // 冻结原因(offline/risk/manual/retire;前端展示用) + AutoBan bool `json:"auto_ban"` + DailyLimit int64 `json:"daily_limit"` + DailyUsed int64 `json:"daily_used"` // 今日已用(0 必须返回,omitempty 会导致前端 NaN) + FailCount int `json:"fail_count"` // 连续失败次数(同上,0 必须返回) + Group string `json:"group"` + IsProd bool `json:"is_prod"` + Remark string `json:"remark,omitempty"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +// maskKey 脱敏:只显示后 4 位;空值返回空串(前端据此显示「未配置」) +func maskKey(key string) string { + if key == "" { + return "" + } + if len(key) <= 4 { + return "****" + } + return "****" + key[len(key)-4:] +} + +// maskQR 收款码内容脱敏:只显示后 8 位(含 https:// 前缀时截断显示);空值返回空串 +func maskQR(qr string) string { + if qr == "" { + return "" + } + if len(qr) <= 8 { + return "****" + } + return "****" + qr[len(qr)-8:] +} + +// acctToResponse 将 Account 转为前端响应(脱敏密钥) +func acctToResponse(acct *account.Account) *AccountResponse { + return &AccountResponse{ + ID: acct.ID, + Channel: acct.Channel, + Name: acct.Name, + AppID: acct.AppID, + MchID: acct.MchID, + PrivateKey: maskKey(acct.PrivateKey), + PublicKey: maskKey(acct.PublicKey), + ApiKey: maskKey(acct.ApiKey), + QRContent: maskQR(acct.QRContent), + Weight: acct.Weight, + Priority: acct.Priority, + Status: string(acct.Status), + FrozenReason: acct.FrozenReason, + AutoBan: acct.AutoBan, + DailyLimit: acct.DailyLimit, + DailyUsed: acct.DailyUsed, + FailCount: acct.FailCount, + Group: acct.Group, + IsProd: acct.IsProd, + Remark: acct.Remark, + CreatedAt: acct.CreatedAt.Format(time.RFC3339), + UpdatedAt: acct.UpdatedAt.Format(time.RFC3339), + } +} + +// encryptAccount 加密账户的敏感字段,返回 AccountEncrypted(用于落库) +func (h *AccountHandler) EncryptAccount(acct *account.Account) (*account.AccountEncrypted, error) { + e := &account.AccountEncrypted{ + Channel: acct.Channel, + Name: acct.Name, + AppID: acct.AppID, + MchID: acct.MchID, + Weight: acct.Weight, + Priority: acct.Priority, + Status: string(acct.Status), + FrozenReason: acct.FrozenReason, + AutoBan: acct.AutoBan, + DailyLimit: acct.DailyLimit, + DailyUsed: acct.DailyUsed, + Group: acct.Group, + IsProd: acct.IsProd, + Remark: acct.Remark, + CreatedAt: acct.CreatedAt, + UpdatedAt: acct.UpdatedAt, + } + var err error + if acct.PrivateKey != "" { + e.PrivateKeyEnc, err = h.crypto.encrypt([]byte(acct.PrivateKey)) + if err != nil { + return nil, fmt.Errorf("加密应用私钥失败: %w", err) + } + } + if acct.PublicKey != "" { + e.PublicKeyEnc, err = h.crypto.encrypt([]byte(acct.PublicKey)) + if err != nil { + return nil, fmt.Errorf("加密公钥失败: %w", err) + } + } + if acct.ApiKey != "" { + e.ApiKeyEnc, err = h.crypto.encrypt([]byte(acct.ApiKey)) + if err != nil { + return nil, fmt.Errorf("加密 API 密钥失败: %w", err) + } + } + if acct.QRContent != "" { + e.QRContentEnc, err = h.crypto.encrypt([]byte(acct.QRContent)) + if err != nil { + return nil, fmt.Errorf("加密收款码内容失败: %w", err) + } + } + return e, nil +} + +// decryptAccount 解密账户的敏感字段,返回 Account(用于内存) +func (h *AccountHandler) decryptAccount(e *account.AccountEncrypted) (*account.Account, error) { + acct := &account.Account{ + ID: e.ID, + Channel: e.Channel, + Name: e.Name, + AppID: e.AppID, + MchID: e.MchID, + Weight: e.Weight, + Priority: e.Priority, + Status: account.AccountStatus(e.Status), + FrozenReason: e.FrozenReason, + AutoBan: e.AutoBan, + DailyLimit: e.DailyLimit, + DailyUsed: e.DailyUsed, + Group: e.Group, + IsProd: e.IsProd, + Remark: e.Remark, + CreatedAt: e.CreatedAt, + UpdatedAt: e.UpdatedAt, + } + if e.PrivateKeyEnc != "" { + data, err := h.crypto.decrypt(e.PrivateKeyEnc) + if err != nil { + return nil, fmt.Errorf("解密应用私钥失败: %w", err) + } + acct.PrivateKey = string(data) + } + if e.PublicKeyEnc != "" { + data, err := h.crypto.decrypt(e.PublicKeyEnc) + if err != nil { + return nil, fmt.Errorf("解密公钥失败: %w", err) + } + acct.PublicKey = string(data) + } + if e.ApiKeyEnc != "" { + data, err := h.crypto.decrypt(e.ApiKeyEnc) + if err != nil { + return nil, fmt.Errorf("解密 API 密钥失败: %w", err) + } + acct.ApiKey = string(data) + } + if e.QRContentEnc != "" { + data, err := h.crypto.decrypt(e.QRContentEnc) + if err != nil { + return nil, fmt.Errorf("解密收款码内容失败: %w", err) + } + acct.QRContent = string(data) + } + return acct, nil +} + +// ========== List ========== + +func (h *AccountHandler) handleListAccounts(c *gin.Context) { + channel := c.Query("channel") + + // 从内存池取(已包含运行状态:daily_used, fail_count, frozen_until) + poolAccts := h.pool.ListAll() + if channel != "" { + poolAccts = h.pool.ListByChannel(channel) + } + + resp := make([]*AccountResponse, 0, len(poolAccts)) + for _, acct := range poolAccts { + resp = append(resp, acctToResponse(acct)) + } + + c.JSON(http.StatusOK, gin.H{"data": resp}) +} + +// ========== Get ========== + +func (h *AccountHandler) handleGetAccount(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + if id <= 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "无效账户ID"}) + return + } + + // 先从内存池取 + acct := h.pool.GetByID(id) + if acct == nil { + // 再查数据库(可能被删了但池没更新) + enc, err := h.accountDB.GetAccount(c.Request.Context(), id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + if enc == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "账户不存在"}) + return + } + acct, err = h.decryptAccount(enc) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "解密账户失败"}) + return + } + } + + c.JSON(http.StatusOK, gin.H{"data": acctToResponse(acct)}) +} + +// handleAccountSecret 返回账户完整敏感字段(明文,需登录)。 +// 用途:管理后台「查看/复制完整密钥」,用于确认对接配置是否准确、复制真实可用的密钥给外部系统。 +// 安全提示:仅登录用户可调用;前端展示时应二次确认并提示勿泄露。 +func (h *AccountHandler) handleAccountSecret(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + if id <= 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "无效账户ID"}) + return + } + + // 先从内存池取(明文),未命中再回退 DB 解密 + acct := h.pool.GetByID(id) + if acct == nil { + enc, err := h.accountDB.GetAccount(c.Request.Context(), id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + if enc == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "账户不存在"}) + return + } + acct, err = h.decryptAccount(enc) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "解密账户失败"}) + return + } + } + + logger.Info("account", "账户密钥被查看(明文)", "id", id, "name", acct.Name, "channel", acct.Channel, "operator", c.GetString("username")) + c.JSON(http.StatusOK, gin.H{"data": gin.H{ + "id": acct.ID, + "name": acct.Name, + "channel": acct.Channel, + "app_id": acct.AppID, + "mch_id": acct.MchID, + "private_key": acct.PrivateKey, + "public_key": acct.PublicKey, + "api_key": acct.ApiKey, + "qr_content": acct.QRContent, + }}) +} + +// ========== Create ========== + +// 账户通道白名单(前后端差异化:商家=密钥,个人=收款码) +var validAccountChannels = map[string]bool{ + "alipay": true, // 商家·支付宝(gopay 官方 API) + "wechat": true, // 商家·微信(gopay 官方 API) + "qq": true, // 商家·QQ(预留) + "paypal": true, // 商家·PayPal(预留) + "personal.alipay": true, // 个人·支付宝(收款码监听) + "personal.wechat": true, // 个人·微信(收款码监听) + "personal.qq": true, // 个人·QQ(预留) +} + +// isPersonalChannel 是否个人免签通道(收款码监听类) +func isPersonalChannel(ch string) bool { + return strings.HasPrefix(ch, "personal.") +} + +// validateAccountReq 按通道类型校验账户字段(docs/MIANQIAN.md 通道大类差异化) +// - 个人通道(personal.*):必须配置收款码内容 qr_content(无官方 API 密钥) +// - 商家通道(merchant.*):必须配置 app_id(官方 API 必需),密钥二选一 +func validateAccountReq(req createAccountReq) error { + if !validAccountChannels[req.Channel] { + return fmt.Errorf("不支持的通道类型: %s(可选:alipay/wechat/qq/paypal/personal.alipay/personal.wechat/personal.qq)", req.Channel) + } + if isPersonalChannel(req.Channel) { + // 个人免签:收款码是核心凭据,必须配置 + if strings.TrimSpace(req.QRContent) == "" { + return fmt.Errorf("个人免签通道必须配置收款码内容(qr_content),可从收款码图片上传自动解析") + } + // 个人通道不应携带官方 API 密钥(避免混淆) + if req.PrivateKey != "" || req.ApiKey != "" || req.AppID != "" { + return fmt.Errorf("个人免签通道无需配置官方密钥/AppID,请仅填写收款码内容") + } + } else { + // 商家通道:官方 API 必须配置 AppID + if strings.TrimSpace(req.AppID) == "" { + return fmt.Errorf("商家通道必须配置 AppID(官方 API 凭据)") + } + if strings.TrimSpace(req.PrivateKey) == "" && strings.TrimSpace(req.ApiKey) == "" { + return fmt.Errorf("商家通道必须配置应用私钥或 API 密钥(至少一项)") + } + } + return nil +} + +// validateAccountReqUpdate 账户编辑校验:与 validateAccountReq 一致,但个人通道允许 +// qr_content 为空——空表示「沿用已有收款码」(编辑场景,资金安全由调用方 handleUpdateAccount +// 的复用逻辑保证)。若编辑时填写了新的收款码,则必须通过 InvalidQRContent 校验(脏数据防线)。 +func validateAccountReqUpdate(req createAccountReq) error { + if !validAccountChannels[req.Channel] { + return fmt.Errorf("不支持的通道类型: %s(可选:alipay/wechat/qq/paypal/personal.alipay/personal.wechat/personal.qq)", req.Channel) + } + if isPersonalChannel(req.Channel) { + // 编辑场景:收款码可为空(沿用已有)。 + if strings.TrimSpace(req.QRContent) != "" { + if reason := account.InvalidQRContent(req.Channel, strings.TrimSpace(req.QRContent)); reason != "" { + return fmt.Errorf("收款码内容非法(%s),请重新上传解析", reason) + } + } + // 个人通道不应携带官方 API 密钥(避免混淆;更新时也保持该约束) + if req.PrivateKey != "" || req.ApiKey != "" || req.AppID != "" { + return fmt.Errorf("个人免签通道无需配置官方密钥/AppID,请仅填写收款码内容") + } + } else { + // 商家通道:官方 API 必须配置 AppID + if strings.TrimSpace(req.AppID) == "" { + return fmt.Errorf("商家通道必须配置 AppID(官方 API 凭据)") + } + if strings.TrimSpace(req.PrivateKey) == "" && strings.TrimSpace(req.ApiKey) == "" { + return fmt.Errorf("商家通道必须配置应用私钥或 API 密钥(至少一项)") + } + } + return nil +} + +type createAccountReq struct { + Channel string `json:"channel" binding:"required"` + Name string `json:"name" binding:"required"` + AppID string `json:"app_id"` + MchID string `json:"mch_id"` + PrivateKey string `json:"private_key"` + PublicKey string `json:"public_key"` + ApiKey string `json:"api_key"` + QRContent string `json:"qr_content"` + Weight int `json:"weight"` + Priority int `json:"priority"` + AutoBan bool `json:"auto_ban"` + DailyLimit int64 `json:"daily_limit"` + Group string `json:"group"` + IsProd bool `json:"is_prod"` + Remark string `json:"remark"` +} + +func (h *AccountHandler) handleCreateAccount(c *gin.Context) { + var req createAccountReq + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误: " + err.Error()}) + return + } + // 按通道类型校验(商家=官方密钥 / 个人=收款码) + if err := validateAccountReq(req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + if req.Weight <= 0 { + req.Weight = 50 + } + if req.Group == "" { + req.Group = "default" + } + + now := time.Now() + acct := &account.Account{ + Channel: req.Channel, + Name: req.Name, + AppID: req.AppID, + MchID: req.MchID, + PrivateKey: req.PrivateKey, + PublicKey: req.PublicKey, + ApiKey: req.ApiKey, + QRContent: req.QRContent, + Weight: req.Weight, + Priority: req.Priority, + Status: account.StatusNormal, + AutoBan: req.AutoBan, + DailyLimit: req.DailyLimit, + Group: req.Group, + IsProd: req.IsProd, + Remark: req.Remark, + CreatedAt: now, + UpdatedAt: now, + } + + // 加密敏感字段 + enc, err := h.EncryptAccount(acct) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + // 写入 accounts.db(仅 Admin API 可访问) + id, err := h.accountDB.CreateAccount(c.Request.Context(), enc) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "创建账户失败: " + err.Error()}) + return + } + acct.ID = id + + // 推送到内存池(其他模块立即可用) + h.pool.Add(acct) + + // 审计日志 + _ = h.accountDB.InsertAccountLog(c.Request.Context(), &account.AccountLog{ + AccountID: id, + Action: "create", + Detail: fmt.Sprintf("创建%s账户: %s", req.Channel, req.Name), + Operator: c.GetString("username"), + }) + + logger.Info("account", "账户创建成功", "id", id, "name", req.Name, "channel", req.Channel) + c.JSON(http.StatusOK, gin.H{"data": acctToResponse(acct)}) +} + +// ========== Update ========== + +type updateAccountReq struct { + Channel string `json:"channel" binding:"required"` + Name string `json:"name" binding:"required"` + AppID string `json:"app_id"` + MchID string `json:"mch_id"` + PrivateKey string `json:"private_key"` + PublicKey string `json:"public_key"` + ApiKey string `json:"api_key"` + QRContent string `json:"qr_content"` + Weight int `json:"weight"` + Priority int `json:"priority"` + AutoBan bool `json:"auto_ban"` + DailyLimit int64 `json:"daily_limit"` + Group string `json:"group"` + IsProd bool `json:"is_prod"` + Remark string `json:"remark"` +} + +func (h *AccountHandler) handleUpdateAccount(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + if id <= 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "无效账户ID"}) + return + } + + var req updateAccountReq + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误: " + err.Error()}) + return + } + // 按通道类型校验(编辑场景:个人通道允许 qr_content 为空 → 沿用已有;新填须合法) + if err := validateAccountReqUpdate(createAccountReq(req)); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + if req.Group == "" { + req.Group = "default" + } + + now := time.Now() + acct := &account.Account{ + ID: id, + Channel: req.Channel, + Name: req.Name, + AppID: req.AppID, + MchID: req.MchID, + PrivateKey: req.PrivateKey, + PublicKey: req.PublicKey, + ApiKey: req.ApiKey, + QRContent: req.QRContent, + Weight: req.Weight, + Priority: req.Priority, + AutoBan: req.AutoBan, + DailyLimit: req.DailyLimit, + Group: req.Group, + IsProd: req.IsProd, + Remark: req.Remark, + UpdatedAt: now, + } + + // 保留原有状态(先从内存池读,池未命中再回退 DB;二者均无则账户不存在) + existing := h.pool.GetByID(id) + if existing == nil { + // 池未命中:从 DB 拉取并解密,避免静默用零值覆盖 CreatedAt/状态/密钥 + enc, err := h.accountDB.GetAccount(c.Request.Context(), id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "查询账户失败: " + err.Error()}) + return + } + if enc == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "账户不存在"}) + return + } + existing, err = h.decryptAccount(enc) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "解密账户失败"}) + return + } + } + acct.Status = existing.Status + acct.DailyUsed = existing.DailyUsed + acct.FailCount = existing.FailCount + acct.FrozenUntil = existing.FrozenUntil + acct.CreatedAt = existing.CreatedAt + // 密钥字段前端不传(空)时保留原值;qr_content 额外跳过脱敏占位("****" 前缀 = 回显值,非真实内容) + if req.PrivateKey == "" { + acct.PrivateKey = existing.PrivateKey + } + if req.PublicKey == "" { + acct.PublicKey = existing.PublicKey + } + if req.ApiKey == "" { + acct.ApiKey = existing.ApiKey + } + if req.QRContent == "" || strings.HasPrefix(req.QRContent, "****") { + acct.QRContent = existing.QRContent + } + + // 加密敏感字段 + enc, err := h.EncryptAccount(acct) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + enc.ID = id + + // 更新 accounts.db + if err := h.accountDB.UpdateAccount(c.Request.Context(), enc); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "更新账户失败: " + err.Error()}) + return + } + + // 更新内存池 + h.pool.Update(acct) + + // 清除对应商家通道的 gopay client 缓存(app_id/密钥变更后旧缓存会用旧参数下单) + invalidateChannelClient(acct.Channel, id) + + // 审计日志 + _ = h.accountDB.InsertAccountLog(c.Request.Context(), &account.AccountLog{ + AccountID: id, + Action: "update", + Detail: fmt.Sprintf("更新%s账户: %s", req.Channel, req.Name), + Operator: c.GetString("username"), + }) + + logger.Info("account", "账户更新成功", "id", id) + c.JSON(http.StatusOK, gin.H{"data": acctToResponse(acct)}) +} + +// invalidateChannelClient 清除账户对应商家通道的 gopay client 缓存。 +// 账户 app_id / 密钥变更后,缓存的旧 client 仍用旧参数下单(旧 app_id 会导致支付宝 +// 返回 invalid-app-id),必须强制重建。个人免签通道无 gopay client,无需处理。 +func invalidateChannelClient(accountChannel string, accountID int64) { + name := "merchant." + strings.TrimPrefix(accountChannel, "merchant.") + if strings.HasPrefix(accountChannel, "personal.") { + return + } + ch, ok := channel.Lookup(name) + if !ok { + return + } + if inv, ok := ch.(channel.ClientCacheInvalidator); ok { + inv.RemoveClient(accountID) + } +} + +// ========== Delete ========== + +func (h *AccountHandler) handleDeleteAccount(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + if id <= 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "无效账户ID"}) + return + } + + ctx := c.Request.Context() + + // 删除前先取账户(用于删除后清除通道缓存) + var delChannel string + if enc, err := h.accountDB.GetAccount(ctx, id); err == nil && enc != nil { + delChannel = enc.Channel + } + + // 删除数据库 + if err := h.accountDB.DeleteAccount(ctx, id); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "删除账户失败: " + err.Error()}) + return + } + + // 移除内存池 + h.pool.Remove(id) + + // 清除商家通道的 client 缓存,避免残留 + if delChannel != "" { + invalidateChannelClient(delChannel, id) + } + + // 审计日志 + _ = h.accountDB.InsertAccountLog(ctx, &account.AccountLog{ + AccountID: id, + Action: "delete", + Detail: fmt.Sprintf("删除账户 ID: %d", id), + Operator: c.GetString("username"), + }) + + logger.Info("account", "账户已删除", "id", id) + c.JSON(http.StatusOK, gin.H{"data": "ok"}) +} + +// ========== Freeze / Unfreeze / Enable / Disable ========== + +func (h *AccountHandler) handleFreezeAccount(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + if id <= 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "无效账户ID"}) + return + } + ctx := c.Request.Context() + + if err := h.accountDB.SetAccountFrozen(ctx, id, account.FrozenReasonManual); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + h.pool.FreezeManual(id) + + _ = h.accountDB.InsertAccountLog(ctx, &account.AccountLog{ + AccountID: id, Action: "freeze", Detail: "手动冻结", Operator: c.GetString("username"), + }) + logger.Info("account", "账户已冻结", "id", id) + c.JSON(http.StatusOK, gin.H{"data": "ok"}) +} + +func (h *AccountHandler) handleUnfreezeAccount(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + if id <= 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "无效账户ID"}) + return + } + ctx := c.Request.Context() + + // 人工解冻:任意冻结原因都可解冻(SetAccountStatus 同时清空 frozen_reason) + if err := h.accountDB.SetAccountStatus(ctx, id, string(account.StatusNormal)); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + h.pool.SetStatus(id, account.StatusNormal) + + _ = h.accountDB.InsertAccountLog(ctx, &account.AccountLog{ + AccountID: id, Action: "unfreeze", Detail: "手动解冻", Operator: c.GetString("username"), + }) + logger.Info("account", "账户已解冻", "id", id) + c.JSON(http.StatusOK, gin.H{"data": "ok"}) +} + +func (h *AccountHandler) handleEnableAccount(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + if id <= 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "无效账户ID"}) + return + } + ctx := c.Request.Context() + + if err := h.accountDB.SetAccountStatus(ctx, id, string(account.StatusNormal)); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + h.pool.SetStatus(id, account.StatusNormal) + + _ = h.accountDB.InsertAccountLog(ctx, &account.AccountLog{ + AccountID: id, Action: "enable", Detail: "手动启用", Operator: c.GetString("username"), + }) + logger.Info("account", "账户已启用", "id", id) + c.JSON(http.StatusOK, gin.H{"data": "ok"}) +} + +func (h *AccountHandler) handleDisableAccount(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + if id <= 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "无效账户ID"}) + return + } + ctx := c.Request.Context() + + if err := h.accountDB.SetAccountStatus(ctx, id, string(account.StatusDisabled)); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + h.pool.SetStatus(id, account.StatusDisabled) + + _ = h.accountDB.InsertAccountLog(ctx, &account.AccountLog{ + AccountID: id, Action: "disable", Detail: "手动禁用", Operator: c.GetString("username"), + }) + logger.Info("account", "账户已禁用", "id", id) + c.JSON(http.StatusOK, gin.H{"data": "ok"}) +} + +// ========== Account Logs ========== + +// AccountName 返回账户池中指定账户的名称(订单列表展示用;查不到返回空) +func (h *AccountHandler) AccountName(id int64) string { + if h == nil || id <= 0 { + return "" + } + acct := h.pool.GetByID(id) + if acct == nil { + return "" + } + return acct.Name +} + +func (h *AccountHandler) handleAccountLogs(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + if id <= 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "无效账户ID"}) + return + } + limit, _ := strconv.Atoi(c.DefaultQuery("limit", "30")) + if limit <= 0 || limit > 100 { + limit = 30 + } + + logs, err := h.accountDB.ListAccountLogs(c.Request.Context(), id, limit) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"data": logs}) +} diff --git a/internal/admin/admin.go b/internal/admin/admin.go new file mode 100755 index 0000000..ca5d9dc --- /dev/null +++ b/internal/admin/admin.go @@ -0,0 +1,1213 @@ +// Package admin 管理后台:静态页面 + JSON API +// +// 前端为 Vue3 + Element Plus 构建产物,embed 进二进制(internal/admin/static)。 +// 部署时只需一个 gateway 二进制,无需单独部署前端。 +package admin + +import ( + "context" + "crypto/rand" + "embed" + "encoding/hex" + "fmt" + "io/fs" + "mime" + "net/http" + "os" + "path/filepath" + "strconv" + "strings" + "sync" + "time" + + "github.com/gin-gonic/gin" + "github.com/golang-jwt/jwt/v5" + "golang.org/x/crypto/bcrypt" + + "paygateway/internal/channel" + "paygateway/internal/logger" + "paygateway/internal/model" + "paygateway/internal/scrcpy" + "paygateway/internal/store" +) + +// mimeTypeByPath 根据文件扩展名返回 Content-Type +func mimeTypeByPath(p string) string { + switch ext := filepath.Ext(p); ext { + case ".js": + return "text/javascript; charset=utf-8" + case ".css": + return "text/css; charset=utf-8" + case ".html": + return "text/html; charset=utf-8" + case ".json": + return "application/json; charset=utf-8" + case ".svg": + return "image/svg+xml" + case ".png": + return "image/png" + case ".jpg", ".jpeg": + return "image/jpeg" + case ".gif": + return "image/gif" + case ".webp": + return "image/webp" + case ".woff", ".woff2": + return "font/woff2" + case ".ttf": + return "font/ttf" + case ".ico": + return "image/x-icon" + default: + if mt := mime.TypeByExtension(ext); mt != "" { + return mt + } + return "application/octet-stream" + } +} + +//go:embed all:static +var staticFS embed.FS + +// TunnelStatus 隧道状态提供者(由 main 注入,避免循环依赖) +type TunnelStatus struct { + URL string `json:"url"` // 隧道地址(空 = 未启动) + Callback string `json:"callback"` // 完整回调地址 + Healthy bool `json:"healthy"` // 健康状态 + Running bool `json:"running"` // 是否已启动 + Error string `json:"error,omitempty"` + UpdatedAt string `json:"updated_at"` +} + +// EpayInfo 易支付对接信息(其他项目配置 paygateway 时使用) +type EpayInfo struct { + BaseURL string `json:"base_url"` // 服务基础地址 + SubmitURL string `json:"submit_url"` // 下单接口(New API 的"易支付网关地址"填这个) + QueryURL string `json:"query_url"` // 订单查询接口 + Pid string `json:"pid"` // 商户ID + Key string `json:"key"` // 商户密钥 + SignType string `json:"sign_type"` // 签名方式 +} + +// ChannelInfo 支付通道信息(由 main 组装,admin 不依赖具体通道包) +type ChannelInfo struct { + Name string `json:"name"` + Enabled bool `json:"enabled"` + Kind string `json:"kind"` // callback / listener + Modes []string `json:"modes"` // 支持模式 + ModesEnabled map[string]bool `json:"modes_enabled,omitempty"` // 各模式启停状态 + AppID string `json:"app_id,omitempty"` + MchID string `json:"mch_id,omitempty"` + IsProd bool `json:"is_prod"` + NotifyURL string `json:"notify_url,omitempty"` + // 账户配置状态(引导用):是否已配置账户 + 账户数量 + Configured bool `json:"configured"` + AccountCount int `json:"account_count"` + // 统计(由 admin 填充) + TodayOrders int64 `json:"today_orders"` + TodayAmount int64 `json:"today_amount"` + TotalOrders int64 `json:"total_orders"` + TotalAmount int64 `json:"total_amount"` +} + +// LogStore 独立日志库接口(查询管理界面日志用,与订单主库分离) +type LogStore interface { + QueryLogs(ctx context.Context, level, scope string, limit, offset int) ([]*store.LogRecord, error) + CountLogs(ctx context.Context, level, scope string) (int64, error) +} + +// Server 管理后台服务 +type Server struct { + st store.Store + logStore LogStore // 独立日志数据库(长期保存,查询日志用) + ledgerStore store.LedgerStore // 独立账本数据库 + callbackPath string // /alipay/notify + tunnelURL func() string // 获取隧道 URL + tunnelHealth func() bool // 隧道健康 + tunnelErr func() string // 隧道错误信息(可选) + channels func() []ChannelInfo // 通道信息提供者 + epayInfo func() *EpayInfo // 易支付对接信息提供者 + onSettingsChange func(key, value string) // 设置变更回调(通知 main 更新通道等) + onLedgerVerify func() *VerifyLedgerResult // 账本校验回调 + onLedgerSnapshot func() error // 创建快照回调 + onLedgerPublicKey func() string // 账本公钥回调 + + // 账户池管理(唯一访问 accounts.db 的组件) + accountHandler *AccountHandler + // 上游管理(唯一访问 upstreams.db 的组件) + upstreamHandler *UpstreamHandler + reconcilerHandler *ReconcilerHandler + // 个人免签通道管理(设备/推送流水/无订单收款) + personalHandler *PersonalHandler + // 实时设备流服务(云手机式操作;nil = 未启用) + streamSrv *scrcpy.StreamServer + // 账本页服务端监听状态提供者(main.go 注入;nil = 未启用)。返回 JSON 序列化数据。 + ledgerStatus func() any + // ledgerWarmup 设备手动预热函数(main.go 注入;nil = 未启用)。DeviceActor 模型中 + // 手动预热是队列中的一个命令,与自动预热/轮询天然互斥,无需窗口独占。 + ledgerWarmup func(ctx context.Context, deviceID string, providers map[string]bool) error + + // 鉴权:凭据来源 DB(settings);无凭据时 adminSetupNeeded=true 引导注册 + adminUser string + adminPass []byte // bcrypt 哈希,禁止明文比对 + adminSetupNeeded bool // 首次启动,尚无管理员,需引导注册 + setupMu sync.Mutex // 保护注册的并发竞态(防多个请求同时通过检查) + jwtSecret []byte + + startTime time.Time + + loginLimiter *loginLimiter +} + +// VerifyLedgerResult 账本校验结果(admin 展示用) +type VerifyLedgerResult struct { + Valid bool `json:"valid"` + TotalEntries int64 `json:"total_entries"` + Errors []string `json:"errors,omitempty"` + SnapshotOK bool `json:"snapshot_ok"` + Duration string `json:"duration"` + CheckedAt string `json:"checked_at"` +} + +// tokenExpiry token 过期时间 +const tokenExpiry = 24 * time.Hour + +// New 创建管理后台服务。 +// 管理员凭据完全由数据库驱动(settings 表 admin_username / admin_password_hash,bcrypt)。 +// 无凭据时 adminSetupNeeded=true,前端引导首次注册;.env 不再承载管理员凭据。 +func New(st store.Store, ledgerStore store.LedgerStore, callbackPath string, tunURL func() string, tunHealth func() bool, channels func() []ChannelInfo, epayInfo func() *EpayInfo, onSettingsChange func(key, value string), onLedgerVerify func() *VerifyLedgerResult, onLedgerSnapshot func() error, onLedgerPublicKey func() string, accountHandler *AccountHandler, upstreamHandler *UpstreamHandler, reconcilerHandler *ReconcilerHandler, personalHandler *PersonalHandler, jwtSecret string) *Server { + secret := jwtSecret + if secret == "" { + // 安全:未配置 JWT_SECRET 时生成随机密钥(禁止可预测的日期密钥,防止伪造 token) + // 注意:随机密钥仅内存持有,服务重启后已发 token 失效(重新登录即可) + buf := make([]byte, 32) + if _, err := rand.Read(buf); err != nil { + // crypto/rand 失败几乎不可能;兜底用时间戳+pid 混合,仍不可预测 + buf = []byte(fmt.Sprintf("pgw-%d-%d", time.Now().UnixNano(), os.Getpid())) + } + secret = hex.EncodeToString(buf) + logger.Warn("admin", "JWT_SECRET 未配置,已生成随机密钥(重启后需重新登录)") + } + s := &Server{ + st: st, + ledgerStore: ledgerStore, + callbackPath: callbackPath, + tunnelURL: tunURL, + tunnelHealth: tunHealth, + channels: channels, + epayInfo: epayInfo, + onSettingsChange: onSettingsChange, + onLedgerVerify: onLedgerVerify, + onLedgerSnapshot: onLedgerSnapshot, + onLedgerPublicKey: onLedgerPublicKey, + accountHandler: accountHandler, + upstreamHandler: upstreamHandler, + reconcilerHandler: reconcilerHandler, + personalHandler: personalHandler, + jwtSecret: []byte(secret), + startTime: time.Now(), + loginLimiter: newLoginLimiter(), + } + s.loadAdminFromDB() + return s +} + +// SetLogStore 绑定独立日志数据库(New 之后调用;日志查询/统计均走该库) +func (s *Server) SetLogStore(ls LogStore) { s.logStore = ls } + +// loadAdminFromDB 从 settings 表加载管理员凭据(admin_username / admin_password_hash,bcrypt) +func (s *Server) loadAdminFromDB() { + ctx := context.Background() + user, errU := s.st.GetSetting(ctx, "admin_username") + pass, errP := s.st.GetSetting(ctx, "admin_password_hash") + if errU == nil && errP == nil && user != "" && strings.HasPrefix(pass, "$2") { + s.adminUser = user + s.adminPass = []byte(pass) + logger.Info("admin", "管理员凭据已从数据库加载", "username", user) + return + } + // 无管理员凭据 → 首次启动,引导注册 + s.adminSetupNeeded = true + logger.Info("admin", "首次启动:无管理员凭据,等待注册(POST /api/auth/register)") +} + +// SetStreamServer 注入实时设备流服务(web 控制台云手机式操作) +// 不通过 New 注入以保持签名稳定;未设置时设备控制台回退到截图模式 +func (s *Server) SetStreamServer(ss *scrcpy.StreamServer) { + s.streamSrv = ss +} + +// SetLedgerStatus 注入账本页监听状态提供者(docs/PLAN-ADB-LEDGER-IMPL.md §7 只读接口) +func (s *Server) SetLedgerStatus(fn func() any) { s.ledgerStatus = fn } + +// SetLedgerWarmup 注入设备手动预热函数(DeviceActor 模型:手动预热是队列中的一个命令,天然互斥)。 +// fn 接收 (ctx, deviceID, providers),provider 指定通道({"alipay":true,"wechat":true},空=全部)。 +func (s *Server) SetLedgerWarmup(fn func(ctx context.Context, deviceID string, providers map[string]bool) error) { + s.ledgerWarmup = fn +} + +// Run 启动管理后台(阻塞) +func (s *Server) Run(addr string) { + gin.SetMode(gin.ReleaseMode) + r := gin.New() + r.Use(gin.Recovery()) + + // ---- 登录(无需鉴权)---- + r.POST("/api/auth/login", s.handleLogin) + // ---- 首次启动注册(无需鉴权;已配置管理员后自动拒绝)---- + r.POST("/api/auth/register", s.handleRegister) + r.GET("/api/auth/setup-status", s.handleSetupStatus) + + // ---- 健康检查(无需鉴权,供 LB/监控/heartbeat 用)---- + // 返回 200 = 服务进程活着 + 业务 DB 可读 + 账本表可读;隧道状态作为子项返回不影响 200。 + // 失败的子项以 503 + JSON 暴露给运维 / 监控告警。 + r.GET("/api/health", s.handleHealth) + + // ---- JSON API(需鉴权)---- + api := r.Group("/api", s.authMiddleware()) + { + api.GET("/dashboard", s.handleDashboard) + api.GET("/epay-info", s.handleEpayInfo) + api.GET("/channels", s.handleChannels) + api.GET("/orders", s.handleListOrders) + api.GET("/orders/:id/events", s.handleOrderEvents) + api.GET("/notify-tasks", s.handleNotifyTasks) + api.GET("/notify-stats", s.handleNotifyStats) + api.GET("/logs", s.handleLogs) + api.GET("/ledger", s.handleLedger) + api.POST("/ledger/verify", s.handleLedgerVerify) + api.GET("/ledger/snapshots", s.handleLedgerSnapshots) + api.POST("/ledger/snapshot", s.handleCreateSnapshot) + api.GET("/ledger/public-key", s.handleLedgerPublicKey) + api.GET("/ledger/export", s.handleLedgerExport) + api.GET("/tunnel", s.handleTunnel) + api.GET("/settings", s.handleSettings) + api.POST("/settings", s.handleUpdateSettings) + api.POST("/auth/reset", s.handleResetAdmin) + // 安全密钥查看(确认后返回完整值,含账本私钥/QR token 等) + api.GET("/settings/keys", s.handleSettingsKeys) + api.POST("/settings/channel/:name/toggle", s.handleToggleChannel) + api.POST("/settings/channel/:name/mode/:mode/toggle", s.handleToggleChannelMode) + + // 账户池管理路由(仅 Admin API 可访问 accounts.db) + if s.accountHandler != nil { + s.accountHandler.RegisterRoutes(api) + } + // 上游管理路由(仅 Admin API 可访问 upstreams.db) + if s.upstreamHandler != nil { + s.upstreamHandler.RegisterRoutes(api) + } + if s.reconcilerHandler != nil { + s.reconcilerHandler.RegisterRoutes(api) + } + // 个人免签通道管理路由(设备/推送流水/无订单收款) + if s.personalHandler != nil { + if s.ledgerWarmup != nil { + s.personalHandler.SetLedgerWarmup(s.ledgerWarmup) + } + s.personalHandler.RegisterRoutes(api) + } + // 账本页服务端监听状态(只读,docs/PLAN-ADB-LEDGER-IMPL.md §7) + if s.ledgerStatus != nil { + api.GET("/ledger-monitors", s.handleLedgerMonitors) + } + } + // 实时设备流(云手机式操作)—— WebSocket 升级无法携带 Authorization header, + // 必须注册在根路由(无 authMiddleware),token 走 query 参数在 handler 内校验 + if s.streamSrv != nil { + r.GET("/api/personal/devices/stream", s.handleDeviceStream) + } + + // ---- 静态页面(SPA,NoRoute 统一处理,避免与 /api 路由冲突)---- + sub, err := fs.Sub(staticFS, "static") + if err != nil { + logger.Error("admin", "静态资源加载失败", "err", err) + sub = nil + } + r.NoRoute(func(c *gin.Context) { + if sub != nil && c.Request.Method == http.MethodGet { + // 1) 先尝试从静态目录读真实文件(/assets/*.js 等) + p := strings.TrimPrefix(c.Request.URL.Path, "/") + if p == "" { + p = "index.html" + } + if data, err := fs.ReadFile(sub, p); err == nil { + // index.html 不缓存(保证 SPA 入口始终引用最新 bundle;JS/CSS 带 hash 名天然不冲突) + if p == "index.html" { + c.Header("Cache-Control", "no-cache, no-store, must-revalidate") + } + c.Data(http.StatusOK, mimeTypeByPath(p), data) + return + } + // 2) 读不到 → SPA 回退到 index.html(前端路由) + if data, err := fs.ReadFile(sub, "index.html"); err == nil { + c.Header("Cache-Control", "no-cache, no-store, must-revalidate") + c.Data(http.StatusOK, "text/html; charset=utf-8", data) + return + } + } + c.JSON(http.StatusNotFound, gin.H{"error": "not found"}) + }) + + logger.Info("admin", "管理后台启动", "addr", addr) + if err := r.Run(addr); err != nil { + logger.Error("admin", "管理后台退出", "err", err) + } +} + +// ========== Dashboard ========== + +func (s *Server) handleDashboard(c *gin.Context) { + ctx := c.Request.Context() + d := gin.H{} + var err error + + if d["total_orders"], err = s.st.CountOrders(ctx, ""); err != nil { + s.err(c, err) + return + } + if d["success_orders"], err = s.st.CountOrders(ctx, model.StatusSuccess); err != nil { + s.err(c, err) + return + } + if d["wait_orders"], err = s.st.CountOrders(ctx, model.StatusWaitPay); err != nil { + s.err(c, err) + return + } + if d["closed_orders"], err = s.st.CountOrders(ctx, model.StatusClosed); err != nil { + s.err(c, err) + return + } + if d["total_amount_minor"], err = s.st.SumAmount(ctx, model.StatusSuccess); err != nil { + s.err(c, err) + return + } + + // 通知任务统计 + if d["notify_pending"], err = s.st.CountNotifyTasks(ctx, "pending"); err != nil { + s.err(c, err) + return + } + if d["notify_success"], err = s.st.CountNotifyTasks(ctx, "success"); err != nil { + s.err(c, err) + return + } + if d["notify_dead"], err = s.st.CountNotifyTasks(ctx, "dead"); err != nil { + s.err(c, err) + return + } + + // 日志统计(独立日志库;未配置时跳过) + if s.logStore != nil { + if d["log_errors"], err = s.logStore.CountLogs(ctx, "ERROR", ""); err != nil { + s.err(c, err) + return + } + if d["log_warns"], err = s.logStore.CountLogs(ctx, "WARN", ""); err != nil { + s.err(c, err) + return + } + } else { + d["log_errors"] = int64(0) + d["log_warns"] = int64(0) + } + + // 账本(独立数据库) + if d["ledger_count"], err = s.ledgerStore.CountLedger(ctx); err != nil { + s.err(c, err) + return + } + last, err := s.ledgerStore.GetLastLedgerEntry(ctx) + if err != nil { + s.err(c, err) + return + } + if last != nil { + d["ledger_last_hash"] = last.EntryHash + d["ledger_last_time"] = last.CreatedAt.Format(time.RFC3339) + } + + // 最近订单 + recent, err := s.st.ListOrders(ctx, "", "", 8, 0) + if err != nil { + s.err(c, err) + return + } + d["recent_orders"] = recent + + // 订单/金额趋势:支持 start/end(日历范围);无则用旧的 days 快捷参数 + start := c.Query("start") + end := c.Query("end") + if start == "" || end == "" { + days, _ := strconv.Atoi(c.DefaultQuery("days", "14")) + if days <= 0 { + days = 14 + } + end = time.Now().Format("2006-01-02") + start = time.Now().AddDate(0, 0, -(days - 1)).Format("2006-01-02") + } + trend, err := s.st.OrdersTrend(ctx, start, end) + if err != nil { + s.err(c, err) + return + } + d["trend"] = trend + + // 隧道状态 + d["tunnel"] = s.tunnelStatus() + + c.JSON(http.StatusOK, gin.H{"data": d}) +} + +// ========== Epay Info ========== + +func (s *Server) handleEpayInfo(c *gin.Context) { + if s.epayInfo == nil { + c.JSON(http.StatusOK, gin.H{"data": nil}) + return + } + c.JSON(http.StatusOK, gin.H{"data": s.epayInfo()}) +} + +// ========== Channels ========== + +func (s *Server) handleChannels(c *gin.Context) { + ctx := c.Request.Context() + var out []ChannelInfo + + if s.channels != nil { + out = s.channels() + } else { + out = []ChannelInfo{} + } + + // 填充统计:每个通道的今日/累计订单与金额 + today := time.Now().Format("2006-01-02") + for i := range out { + // 累计 + if n, err := s.st.CountOrdersByChannel(ctx, "", out[i].Name); err == nil { + out[i].TotalOrders = n + } + if a, err := s.st.SumAmountByChannel(ctx, model.StatusSuccess, out[i].Name); err == nil { + out[i].TotalAmount = a + } + // 今日(通过 trend 或直接查:简单方式查今日创建的全部订单) + if n, err := s.st.CountOrdersByChannelToday(ctx, out[i].Name, today); err == nil { + out[i].TodayOrders = n + } + if a, err := s.st.SumAmountByChannelToday(ctx, out[i].Name, today); err == nil { + out[i].TodayAmount = a + } + } + c.JSON(http.StatusOK, gin.H{"data": out}) +} + +// ========== Notify Stats ========== + +func (s *Server) handleNotifyStats(c *gin.Context) { + ctx := c.Request.Context() + d := gin.H{} + var err error + if d["pending"], err = s.st.CountNotifyTasks(ctx, "pending"); err != nil { + s.err(c, err) + return + } + if d["success"], err = s.st.CountNotifyTasks(ctx, "success"); err != nil { + s.err(c, err) + return + } + if d["dead"], err = s.st.CountNotifyTasks(ctx, "dead"); err != nil { + s.err(c, err) + return + } + // 待重试(即将到期的 pending) + if d["total"], err = s.st.CountNotifyTasks(ctx, ""); err != nil { + s.err(c, err) + return + } + c.JSON(http.StatusOK, gin.H{"data": d}) +} + +// ========== Orders ========== + +func (s *Server) handleListOrders(c *gin.Context) { + ctx := c.Request.Context() + status := c.Query("status") + channel := c.Query("channel") + keyword := c.Query("keyword") + from := c.Query("from") + to := c.Query("to") + limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20")) + offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0")) + if limit <= 0 || limit > 200 { + limit = 20 + } + + total, err := s.st.CountOrdersFiltered(ctx, status, channel, keyword, from, to) + if err != nil { + s.err(c, err) + return + } + list, err := s.st.ListOrdersFiltered(ctx, status, channel, keyword, from, to, limit, offset) + if err != nil { + s.err(c, err) + return + } + // 附加账户名称(从账户池内存查询) + if s.accountHandler != nil { + for _, o := range list { + if o.AccountID > 0 { + o.AccountName = s.accountHandler.AccountName(o.AccountID) + } + } + } + c.JSON(http.StatusOK, gin.H{"data": gin.H{"total": total, "list": list}}) +} + +func (s *Server) handleOrderEvents(c *gin.Context) { + ctx := c.Request.Context() + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + if id <= 0 { + s.err(c, fmt.Errorf("无效订单ID")) + return + } + limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20")) + if limit <= 0 || limit > 100 { + limit = 20 + } + events, err := s.st.ListEvents(ctx, id, limit) + if err != nil { + s.err(c, err) + return + } + c.JSON(http.StatusOK, gin.H{"data": events}) +} + +// ========== Notify Tasks ========== + +func (s *Server) handleNotifyTasks(c *gin.Context) { + ctx := c.Request.Context() + status := c.Query("status") + limit, _ := strconv.Atoi(c.DefaultQuery("limit", "50")) + if limit <= 0 || limit > 200 { + limit = 50 + } + list, err := s.st.ListNotifyTasks(ctx, status, limit) + if err != nil { + s.err(c, err) + return + } + // 关联订单号 + type taskWithOrder struct { + *store.NotifyTask + OutTradeNo string `json:"out_trade_no"` + Subject string `json:"subject"` + AmountMinor int64 `json:"amount_minor"` + } + var out []taskWithOrder + for _, t := range list { + item := taskWithOrder{NotifyTask: t} + if o, err := s.st.GetOrderByID(ctx, t.OrderID); err == nil && o != nil { + item.OutTradeNo = o.OutTradeNo + item.Subject = o.Subject + item.AmountMinor = o.AmountMinor + } + out = append(out, item) + } + c.JSON(http.StatusOK, gin.H{"data": out}) +} + +// ========== Logs ========== + +func (s *Server) handleLogs(c *gin.Context) { + ctx := c.Request.Context() + if s.logStore == nil { + c.JSON(http.StatusOK, gin.H{"data": gin.H{"total": 0, "list": []*store.LogRecord{}}}) + return + } + level := c.Query("level") + scope := c.Query("scope") + limit, _ := strconv.Atoi(c.DefaultQuery("limit", "50")) + offset, _ := strconv.Atoi(c.DefaultQuery("offset", "0")) + if limit <= 0 || limit > 200 { + limit = 50 + } + + total, err := s.logStore.CountLogs(ctx, level, scope) + if err != nil { + s.err(c, err) + return + } + list, err := s.logStore.QueryLogs(ctx, level, scope, limit, offset) + if err != nil { + s.err(c, err) + return + } + c.JSON(http.StatusOK, gin.H{"data": gin.H{"total": total, "list": list}}) +} + +// ========== Ledger ========== + +func (s *Server) handleLedger(c *gin.Context) { + ctx := c.Request.Context() + limit, _ := strconv.Atoi(c.DefaultQuery("limit", "30")) + if limit <= 0 || limit > 100 { + limit = 30 + } + list, err := s.ledgerStore.ListLedger(ctx, limit) + if err != nil { + s.err(c, err) + return + } + count, err := s.ledgerStore.CountLedger(ctx) + if err != nil { + s.err(c, err) + return + } + c.JSON(http.StatusOK, gin.H{"data": gin.H{"total": count, "list": list}}) +} + +// handleLedgerVerify 手动触发账本校验 +func (s *Server) handleLedgerVerify(c *gin.Context) { + if s.onLedgerVerify == nil { + c.JSON(http.StatusOK, gin.H{"data": gin.H{"valid": false, "error": "未配置账本校验回调"}}) + return + } + logger.Info("admin", "手动触发账本校验") + res := s.onLedgerVerify() + c.JSON(http.StatusOK, gin.H{"data": res}) +} + +// handleLedgerSnapshots 快照列表 +func (s *Server) handleLedgerSnapshots(c *gin.Context) { + ctx := c.Request.Context() + limit, _ := strconv.Atoi(c.DefaultQuery("limit", "30")) + if limit <= 0 || limit > 100 { + limit = 30 + } + list, err := s.ledgerStore.ListSnapshots(ctx, limit) + if err != nil { + s.err(c, err) + return + } + c.JSON(http.StatusOK, gin.H{"data": list}) +} + +// handleCreateSnapshot 手动创建快照 +func (s *Server) handleCreateSnapshot(c *gin.Context) { + if s.onLedgerSnapshot == nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "未配置账本"}) + return + } + if err := s.onLedgerSnapshot(); err != nil { + logger.Error("admin", "创建快照失败", "err", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + logger.Info("admin", "手动创建快照成功") + c.JSON(http.StatusOK, gin.H{"data": gin.H{"message": "快照创建成功"}}) +} + +// handleLedgerPublicKey 返回账本公钥(供外部验证签名) +func (s *Server) handleLedgerPublicKey(c *gin.Context) { + if s.onLedgerPublicKey == nil { + c.JSON(http.StatusOK, gin.H{"data": gin.H{"public_key": "未配置账本"}}) + return + } + c.JSON(http.StatusOK, gin.H{"data": gin.H{"public_key": s.onLedgerPublicKey()}}) +} + +// handleLedgerExport 导出账本为 CSV(审计报告) +func (s *Server) handleLedgerExport(c *gin.Context) { + ctx := c.Request.Context() + + // 账本条目 CSV + entries, err := s.ledgerStore.ListLedger(ctx, 100000) + if err != nil { + s.err(c, err) + return + } + + // 快照 CSV + snapshots, err := s.ledgerStore.ListSnapshots(ctx, 1000) + if err != nil { + s.err(c, err) + return + } + + var b strings.Builder + // 写入 UTF-8 BOM(Excel 中文兼容) + b.WriteString("\xEF\xBB\xBF") + b.WriteString("# PayGateway 审计账本导出\n") + b.WriteString("# 生成时间: " + time.Now().Format("2006-01-02 15:04:05") + "\n") + b.WriteString("# 账本条目数: " + strconv.Itoa(len(entries)) + ", 快照数: " + strconv.Itoa(len(snapshots)) + "\n\n") + + b.WriteString("== 账本条目 ==\n") + b.WriteString("id,event_id,prev_hash,payload_hash,entry_hash,signature,created_at\n") + for _, e := range entries { + b.WriteString(fmt.Sprintf("%d,%d,%s,%s,%s,%s,%s\n", + e.ID, e.EventID, e.PrevHash, e.PayloadHash, e.EntryHash, e.Signature, + e.CreatedAt.Format(time.RFC3339))) + } + + b.WriteString("\n== 每日快照 ==\n") + b.WriteString("id,snapshot_date,last_hash,signature,created_at\n") + for _, sn := range snapshots { + b.WriteString(fmt.Sprintf("%d,%s,%s,%s,%s\n", + sn.ID, sn.SnapshotDate, sn.LastHash, sn.Signature, + sn.CreatedAt.Format(time.RFC3339))) + } + + // 设置下载响应头 + filename := fmt.Sprintf("ledger_audit_%s.csv", time.Now().Format("20060102")) + c.Header("Content-Disposition", "attachment; filename="+filename) + c.Header("Content-Type", "text/csv; charset=utf-8") + c.Data(http.StatusOK, "text/csv; charset=utf-8", []byte(b.String())) +} + +// ========== Tunnel ========== + +func (s *Server) handleTunnel(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"data": s.tunnelStatus()}) +} + +// ========== 账本页服务端监听状态(只读) ========== + +func (s *Server) handleLedgerMonitors(c *gin.Context) { + if s.ledgerStatus == nil { + c.JSON(http.StatusOK, gin.H{"data": []any{}, "enabled": false}) + return + } + c.JSON(http.StatusOK, gin.H{"data": s.ledgerStatus(), "enabled": true}) +} + +func (s *Server) tunnelStatus() TunnelStatus { + ts := TunnelStatus{UpdatedAt: time.Now().Format(time.RFC3339)} + if s.tunnelURL != nil { + ts.URL = s.tunnelURL() + } + if s.tunnelHealth != nil { + ts.Healthy = s.tunnelHealth() + } + if s.tunnelErr != nil { + ts.Error = s.tunnelErr() + } + if ts.URL != "" { + ts.Running = true + ts.Callback = ts.URL + s.callbackPath + } + return ts +} + +// handleHealth 深度健康检查(无鉴权,挂载在 r.GET("/api/health"),供 LB / 监控 / 告警调用)。 +// +// HTTP 状态码契约(运维 / 监控据此配 alert): +// - 200 OK : 服务进程活着 + 业务 DB 可读 + 账本表可读。 +// - 503 + JSON : 任一关键子项(db / ledger)失败;tunnel 失败不影响整体 200(隧道仅是支付回调通道,本地支付能力不受影响)。 +func (s *Server) handleHealth(c *gin.Context) { + ctx := c.Request.Context() + + resp := gin.H{"status": "ok", "checked_at": time.Now().Format(time.RFC3339)} + if !s.startTime.IsZero() { + resp["uptime_sec"] = int64(time.Since(s.startTime).Seconds()) + } + + criticalFail := false + + disk := gin.H{} + if n, err := s.st.CountOrders(ctx, ""); err != nil { + disk["ok"] = false + disk["error"] = err.Error() + criticalFail = true + } else { + disk["ok"] = true + disk["orders"] = n + } + resp["db"] = disk + + led := gin.H{} + if s.ledgerStore != nil { + if n, err := s.ledgerStore.CountLedger(ctx); err != nil { + led["ok"] = false + led["error"] = err.Error() + criticalFail = true + } else { + led["ok"] = true + led["count"] = n + } + } else { + led["ok"] = false + led["error"] = "ledgerStore 未注入" + criticalFail = true + } + resp["ledger"] = led + + tun := gin.H{} + if s.tunnelHealth != nil { + tun["healthy"] = s.tunnelHealth() + } + if s.tunnelURL != nil { + tun["url"] = s.tunnelURL() + } + resp["tunnel"] = tun + + if criticalFail { + resp["status"] = "error" + c.JSON(http.StatusServiceUnavailable, resp) + return + } + c.JSON(http.StatusOK, resp) +} + +// ========== 鉴权 ========== + +type loginReq struct { + Username string `json:"username"` + Password string `json:"password"` +} + +// authMiddleware 鉴权中间件,验证 JWT token +func (s *Server) authMiddleware() gin.HandlerFunc { + return func(c *gin.Context) { + token := c.GetHeader("Authorization") + if token == "" { + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "未登录"}) + return + } + // 去掉 "Bearer " 前缀 + if len(token) > 7 && token[:7] == "Bearer " { + token = token[7:] + } + claims := &jwtClaims{} + parsed, err := jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (interface{}, error) { + return s.jwtSecret, nil + }) + if err != nil || !parsed.Valid { + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "token 无效或已过期"}) + return + } + c.Set("username", claims.Username) + c.Next() + } +} + +type jwtClaims struct { + Username string `json:"username"` + jwt.RegisteredClaims +} + +func (s *Server) handleLogin(c *gin.Context) { + var req loginReq + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"}) + return + } + + // 失败退避限速:IP + username 双维度校验,已锁定直接 429 + if s.enforceLoginLimit(c, req.Username) { + return + } + + if s.adminSetupNeeded { + // 首次启动尚未注册管理员,引导注册(与"登录未启用"区分,避免管理员困惑) + c.JSON(http.StatusConflict, gin.H{"error": "管理员未注册,请先完成初始化"}) + return + } + if s.adminUser == "" || len(s.adminPass) == 0 || !strings.HasPrefix(string(s.adminPass), "$2") { + // 后台登录被禁用(配置不全或非 bcrypt 哈希),统一返回 403 避免泄露配置状态 + c.JSON(http.StatusForbidden, gin.H{"error": "管理后台登录未启用"}) + return + } + if req.Username != s.adminUser { + // 用户名错误也走 bcrypt 路径以保持固定时间,避免用户名枚举 + _ = bcrypt.CompareHashAndPassword(s.adminPass, []byte("00"+req.Password)) + s.recordLoginOutcome(c, req.Username, true) + c.JSON(http.StatusUnauthorized, gin.H{"error": "用户名或密码错误"}) + return + } + if err := bcrypt.CompareHashAndPassword(s.adminPass, []byte(req.Password)); err != nil { + s.recordLoginOutcome(c, req.Username, true) + c.JSON(http.StatusUnauthorized, gin.H{"error": "用户名或密码错误"}) + return + } + + s.recordLoginOutcome(c, req.Username, false) + + now := time.Now() + claims := &jwtClaims{ + Username: req.Username, + RegisteredClaims: jwt.RegisteredClaims{ + ExpiresAt: jwt.NewNumericDate(now.Add(tokenExpiry)), + IssuedAt: jwt.NewNumericDate(now), + Issuer: "paygateway", + }, + } + token, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(s.jwtSecret) + if err != nil { + logger.Error("admin", "token 生成失败", "err", err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "生成 token 失败"}) + return + } + logger.Info("admin", "登录成功", "username", req.Username) + c.JSON(http.StatusOK, gin.H{"data": gin.H{ + "token": token, + "expires": now.Add(tokenExpiry).Unix(), + }}) +} + +// ========== 首次启动注册 ========== + +type registerReq struct { + Username string `json:"username" binding:"required,min=3,max=32"` + Password string `json:"password" binding:"required,min=8,max=72"` +} + +// handleSetupStatus 查询是否需要首次注册(前端据此决定展示注册页 or 登录页) +func (s *Server) handleSetupStatus(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"data": gin.H{"setup_needed": s.adminSetupNeeded}}) +} + +// handleRegister 首次启动注册管理员(bcrypt 哈希存入 settings 表) +// 安全:仅在尚无管理员凭据时可调用;注册成功后立即禁用 +func (s *Server) handleRegister(c *gin.Context) { + s.setupMu.Lock() + defer s.setupMu.Unlock() + // 双重校验:内存标志 + DB 实况(防内存竞态绕过导致重复注册覆盖凭据) + if !s.adminSetupNeeded { + c.JSON(http.StatusForbidden, gin.H{"error": "管理员已配置,注册已关闭"}) + return + } + ctx := c.Request.Context() + if user, err := s.st.GetSetting(ctx, "admin_username"); err == nil && user != "" { + s.adminSetupNeeded = false + c.JSON(http.StatusForbidden, gin.H{"error": "管理员已配置,注册已关闭"}) + return + } + var req registerReq + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误:用户名 3-32 位,密码至少 8 位"}) + return + } + hash, err := bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "密码加密失败"}) + return + } + // 先写哈希再写用户名:任一失败都不留半配置(用户名无值 = 注册未完成) + if err := s.st.SetSetting(ctx, "admin_password_hash", string(hash)); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "保存管理员密码失败: " + err.Error()}) + return + } + if err := s.st.SetSetting(ctx, "admin_username", req.Username); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "保存管理员失败: " + err.Error()}) + return + } + // 立即生效:内存凭据切换为 DB 值,关闭注册入口 + s.adminUser = req.Username + s.adminPass = hash + s.adminSetupNeeded = false + logger.Info("admin", "管理员注册成功", "username", req.Username) + c.JSON(http.StatusOK, gin.H{"data": gin.H{"ok": true}}) +} + +// ========== 重置管理员(设置页入口) ========== + +type resetAdminReq struct { + Password string `json:"password" binding:"required"` +} + +// handleResetAdmin 重置管理员凭据:验证当前密码后清空 DB 与内存凭据,系统回到首次注册态。 +// 安全:需登录 + 提交当前密码(防误操作/CSRF);重置后所有已发 token 仍有效但登录校验立即切换到注册态, +// 前端收到 ok 后清除本地 token 并跳转注册页。重置动作记审计日志。 +func (s *Server) handleResetAdmin(c *gin.Context) { + if s.adminSetupNeeded { + c.JSON(http.StatusBadRequest, gin.H{"error": "当前已是未注册状态,无需重置"}) + return + } + var req resetAdminReq + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"}) + return + } + // 验证当前管理员密码(防未授权操作) + if err := bcrypt.CompareHashAndPassword(s.adminPass, []byte(req.Password)); err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "当前密码错误"}) + return + } + s.setupMu.Lock() + defer s.setupMu.Unlock() + ctx := c.Request.Context() + // 删除 DB 凭据 + _ = s.st.DeleteSetting(ctx, "admin_username") + _ = s.st.DeleteSetting(ctx, "admin_password_hash") + // 清空内存凭据,回到注册态 + s.adminUser = "" + s.adminPass = nil + s.adminSetupNeeded = true + logger.Warn("admin", "管理员凭据已重置,系统回到首次注册态", "operator", c.GetString("username")) + c.JSON(http.StatusOK, gin.H{"data": gin.H{"ok": true}}) +} + +// ========== Settings ========== + +func (s *Server) handleSettings(c *gin.Context) { + ctx := c.Request.Context() + dbSettings, err := s.st.GetAllSettings(ctx) + if err != nil { + s.err(c, err) + return + } + // 多商户模式:易支付商户凭据已迁移到「上游对接」页(upstreams.db), + // 系统设置不再返回/管理 epay_pid/epay_key/epay_public_base_url + delete(dbSettings, "epay_pid") + delete(dbSettings, "epay_key") + delete(dbSettings, "epay_public_base_url") + // 管理员凭据哈希不得暴露给前端(仅 /api/auth/* 使用) + delete(dbSettings, "admin_username") + delete(dbSettings, "admin_password_hash") + // 账本私钥 / QR token 等完整密钥不随常规设置下发,仅由 /api/settings/keys 确认后返回 + delete(dbSettings, "ledger_private_key") + delete(dbSettings, "qr_token_secret") + // 合并通道启用状态 + channels := channel.AllNames() + channelEnabled := make(map[string]bool) + for _, name := range channels { + channelEnabled[name] = channel.IsEnabled(name) + } + c.JSON(http.StatusOK, gin.H{"data": gin.H{ + "settings": dbSettings, + "channel_enabled": channelEnabled, + "channels": channels, + }}) +} + +// handleSettingsKeys 返回安全密钥状态与完整值(需登录鉴权)。 +// 仅返回有实际意义的密钥:账本私钥 / QR token 密钥;调用即记审计日志。 +func (s *Server) handleSettingsKeys(c *gin.Context) { + ctx := c.Request.Context() + dbSettings, err := s.st.GetAllSettings(ctx) + if err != nil { + s.err(c, err) + return + } + keys := gin.H{} + if v, ok := dbSettings["ledger_private_key"]; ok && v != "" { + keys["ledger_private_key"] = v + } + if v, ok := dbSettings["qr_token_secret"]; ok && v != "" { + keys["qr_token_secret"] = v + } + logger.Info("admin", "安全密钥被查看(明文)", "operator", c.GetString("username")) + c.JSON(http.StatusOK, gin.H{"data": keys}) +} + +type settingsUpdate struct { + Key string `json:"key"` + Value string `json:"value"` +} + +func (s *Server) handleUpdateSettings(c *gin.Context) { + var req struct { + Settings []settingsUpdate `json:"settings"` + } + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误: " + err.Error()}) + return + } + ctx := c.Request.Context() + for _, item := range req.Settings { + // 多商户模式:易支付商户凭据一律禁止通过系统设置保存(只认「上游对接」页) + switch item.Key { + case "epay_pid", "epay_key", "epay_public_base_url": + logger.Warn("admin", "拒绝保存已废弃的易支付系统设置", "key", item.Key) + continue + } + if err := s.st.SetSetting(ctx, item.Key, item.Value); err != nil { + s.err(c, err) + return + } + // 通知主进程更新运行时配置(如通道环境切换) + if s.onSettingsChange != nil { + s.onSettingsChange(item.Key, item.Value) + } + } + logger.Info("admin", "设置已更新", "count", len(req.Settings)) + c.JSON(http.StatusOK, gin.H{"data": "ok"}) +} + +func (s *Server) handleToggleChannel(c *gin.Context) { + name := c.Param("name") + on := !channel.IsEnabled(name) + channel.SetEnabled(name, on) + // 持久化到 settings 表 + ctx := c.Request.Context() + val := "false" + if on { + val = "true" + } + _ = s.st.SetSetting(ctx, "channel_enabled_"+name, val) + logger.Info("admin", "通道启停", "channel", name, "enabled", on) + c.JSON(http.StatusOK, gin.H{"data": gin.H{"name": name, "enabled": on}}) +} + +// handleToggleChannelMode 开启/关闭通道的某个支付模式 +func (s *Server) handleToggleChannelMode(c *gin.Context) { + name := c.Param("name") + mode := c.Param("mode") + ch, ok := channel.Lookup(name) + if !ok { + c.JSON(http.StatusBadRequest, gin.H{"error": "通道不存在"}) + return + } + // 通过类型断言判断是否支持模式切换 + type modeToggler interface { + IsModeEnabled(mode string) bool + SetModeEnabled(mode string, enabled bool) + EnabledModes() map[string]bool + } + toggler, ok := ch.(modeToggler) + if !ok { + c.JSON(http.StatusBadRequest, gin.H{"error": "该通道不支持模式切换"}) + return + } + on := !toggler.IsModeEnabled(mode) + toggler.SetModeEnabled(mode, on) + // 持久化到 settings 表 + ctx := c.Request.Context() + val := "false" + if on { + val = "true" + } + _ = s.st.SetSetting(ctx, "alipay_mode_"+mode, val) + logger.Info("admin", "通道支付模式启停", "channel", name, "mode", mode, "enabled", on) + c.JSON(http.StatusOK, gin.H{"data": gin.H{"channel": name, "mode": mode, "enabled": on}}) +} + +// handleDeviceStream 实时设备流 WebSocket(云手机式操作) +// GET /api/personal/devices/stream?serial=127.0.0.1:5556&token= +// token 走 query(浏览器 WebSocket 无法自定义 header) +func (s *Server) handleDeviceStream(c *gin.Context) { + token := c.Query("token") + if token == "" { + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "未登录"}) + return + } + claims := &jwtClaims{} + parsed, err := jwt.ParseWithClaims(token, claims, func(t *jwt.Token) (interface{}, error) { + return s.jwtSecret, nil + }) + if err != nil || !parsed.Valid { + c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "token 无效或已过期"}) + return + } + if s.streamSrv == nil { + c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{"error": "实时流未启用"}) + return + } + s.streamSrv.HandleStream(c.Writer, c.Request) +} + +func (s *Server) err(c *gin.Context, err error) { + logger.Error("admin", "API错误", "err", err, "path", c.Request.URL.Path) + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) +} diff --git a/internal/admin/capture/alipay.go b/internal/admin/capture/alipay.go new file mode 100644 index 0000000..b707bfb --- /dev/null +++ b/internal/admin/capture/alipay.go @@ -0,0 +1,356 @@ +// 支付宝收款码服务端 adb 捕获。 +// +// 链路(微信式「保存到相册」方案,实测更稳定):启动支付宝 → 点「收付款」→ 点「收钱」进入 +// 个人收款码页(PayeeQRActivity) → 点「保存图片」→ 图片落系统相册 DCIM/Alipay(不受 +// FLAG_SECURE 限制)→ 拉取最新 → 解码。不再依赖截图链路(截图方案易卡在收付款总页/付款码页)。 +package capture + +import ( + "context" + "fmt" + "os" + "strings" + "time" + + "paygateway/internal/adbmgr" + "paygateway/internal/logger" +) + +// alipayPackage 支付宝主包名(主空间 user 0 与分身 user 999 同包名; +// 本设备主空间支付宝被禁用 enabled=0,实际使用 user 999 分身) +const alipayPackage = "com.eg.android.AlipayGphone" + +// alipayLaunchActivity 支付宝启动 activity(resolve-activity 实测) +const alipayLaunchActivity = ".AlipayLogin" + +// alipayHomePayEntry 支付宝首页「收付款」入口(手机 OCR (405,382)) +var alipayHomePayEntry = wxNavPt{405, 382} + +// alipayCollectMoneyBtn 「收付款」页(向商家付款)内的「收钱」按钮(手机 (225,1688), +// 2026-09-12 uiautomator dump 实测标定)。 +// 必须点它才能进入个人收款码页(PayeeQRActivity);漏掉会停留在付款码页。 +var alipayCollectMoneyBtn = wxNavPt{225, 1688} + +// alipaySaveImgBtn 个人收款码页(PayeeQRActivity)「保存图片」按钮(手机 (540,1202), +// 2026-09-12 uiautomator dump 实测标定)。 +// 点击后支付宝把收款码写入系统相册 DCIM/Alipay/,不受 FLAG_SECURE 限制。 +var alipaySaveImgBtn = wxNavPt{540, 1202} + +// alipayPersonalTab 「个人收钱」tab(手机 (252,290))。 +// 收款码页有「个人收钱」/「经营收钱」双态:经营态需开通且无个人收款码, +// 点此 tab 确保切回个人收钱态(与微信个人/经营收款码同机制)。 +var alipayPersonalTab = wxNavPt{252, 290} + +// AlipayQrDir 支付宝收款码图片落盘目录(user 0 主空间;双开 user 由调用方拼接)。 +const AlipayQrDir = "/storage/emulated/0/DCIM/Alipay" + +// CaptureAlipayQrcode 核心:启动支付宝 → 点「收付款」→ 点「收钱」进收款码页 → 点「保存图片」 +// → 从相册拉取最新图片 → 解码。 +// 返回解码内容。userId>0 时以该 user 启动(双开/分身空间),相册路径同步切换。 +func CaptureAlipayQrcode(ctx context.Context, adb *adbmgr.Manager, serial string, userId int) (content string, err error) { + // 1. 唤醒屏幕 + 强制重启支付宝到干净首页(指定双开 user) + if err = wakeHome(ctx, adb, serial); err != nil { + return "", fmt.Errorf("唤醒屏幕失败: %w", err) + } + if err = adb.RestartAppUser(ctx, serial, alipayPackage, alipayLaunchActivity, userId); err != nil { + return "", fmt.Errorf("启动支付宝失败: %w", err) + } + time.Sleep(5 * time.Second) // 支付宝冷启动 + 首页加载(低端机/网络慢时需更久) + + // 前台守卫:支付宝未到前台绝不开始盲点坐标(重启失败/弹窗拦截时坐标会落进错误 app) + if err = awaitForeground(ctx, adb, serial, alipayPackage, foregroundWait); err != nil { + return "", fmt.Errorf("启动支付宝失败: %w", err) + } + + // 2. 点首页「收付款」进入收付款总页(向商家付款页) + if err = tapNav(ctx, adb, serial, alipayHomePayEntry); err != nil { + return "", fmt.Errorf("点击收付款入口失败: %w", err) + } + time.Sleep(2500 * time.Millisecond) + + // 3. 点「收钱」进入个人收款码页(漏此步停留在付款码页,无收款码) + if err = tapNav(ctx, adb, serial, alipayCollectMoneyBtn); err != nil { + return "", fmt.Errorf("点击收钱按钮失败: %w", err) + } + // 必须确认已进入收款码页(PayeeQRActivity);否则坐标已偏移,后续点击全落空 + if err = requireAlipayPayeePage(ctx, adb, serial); err != nil { + return "", err + } + + // 4. 直接点「保存图片」→ 检查相册是否新增(经营态该位置无按钮不会误点,个人态点击即保存)。 + // 相册未新增 → 说明在经营态 → 点「个人收钱」tab 切回个人态 → 再点保存。 + dir := AlipayQrDir + if userId > 0 { + dir = fmt.Sprintf("/storage/emulated/%d/DCIM/Alipay", userId) + } + // 相册基线:点击前已有的收款码图(防止把旧图当成新保存的) + base := listAlipayImages(ctx, adb, serial, dir) + + // trySave 点「保存图片」并轮询等待相册新增(落盘延迟在慢网络/低端机上可达 10s+,固定 sleep 不可靠) + trySave := func() bool { + if terr := tapNav(ctx, adb, serial, alipaySaveImgBtn); terr != nil { + return false + } + return pollForNewFile(ctx, base, 20*time.Second, func() []string { + return listAlipayImages(ctx, adb, serial, dir) + }) + } + // 第一次保存失败 → 说明不在个人态(如经营引导态无保存按钮)。 + // 循环「点个人收钱 tab → 再点保存」最多 3 轮:切态点击可能被吞/未生效, + // 单次补救不稳定(实测需多轮才能落到个人态并保存成功)。 + if !trySave() { + for round := 1; round <= 3; round++ { + if err = tapNav(ctx, adb, serial, alipayPersonalTab); err != nil { + return "", fmt.Errorf("点击个人收钱 tab 失败: %w", err) + } + time.Sleep(2500 * time.Millisecond) + if trySave() { + break + } + if round == 3 { + return "", fmt.Errorf("多轮切回个人态后保存图片仍失败(可能不在收款码页或按钮位置偏移)") + } + } + } + + // 5. 拉取相册最新支付宝收款码图片(按文件名倒序取第一张) + files, lerr := adb.ListFiles(ctx, serial, dir) + if lerr != nil { + return "", fmt.Errorf("读取相册失败: %w", lerr) + } + if len(files) == 0 { + return "", fmt.Errorf("支付宝相册为空(保存图片未落盘)") + } + latest := files[0] // ListFiles 按 -t 倒序,第一个最新 + local, perr := adb.PullFile(ctx, serial, dir+"/"+latest) + if perr != nil { + return "", fmt.Errorf("拉取收款码图片失败: %w", perr) + } + imgData, rerr := os.ReadFile(local) + if rerr != nil { + os.Remove(local) + return "", fmt.Errorf("读取图片失败: %w", rerr) + } + content, derr := DecodeQRContent(imgData) + if derr != nil { + os.Remove(local) + return "", fmt.Errorf("收款码图片未识别到二维码: %w", derr) + } + return content, nil +} + +// requireAlipayPayeePage 确认支付宝前台已到达收款码页(PayeeQRActivity)。 +// 带重试(最多 6 次×1.5s)。若点「收钱」后仍停在收付款页,重试点击一次再校验。 +func requireAlipayPayeePage(ctx context.Context, adb *adbmgr.Manager, serial string) error { + for i := 0; i < 6; i++ { + fg, err := adb.ForegroundApp(ctx, serial) + // 双开 user 前台字符串形如 "u999 com.eg.android.AlipayGphone/...PayeeQRActivity",用 Contains 匹配 + if err == nil && strings.Contains(fg, "PayeeQRActivity") { + return nil + } + if i > 0 && i%3 == 0 { + // 页面加载慢导致第一次点击被吞:重试点「收钱」 + if terr := tapNav(ctx, adb, serial, alipayCollectMoneyBtn); terr != nil { + return fmt.Errorf("重试点击收钱失败: %w", terr) + } + } + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(1500 * time.Millisecond): + } + } + fg, _ := adb.ForegroundApp(ctx, serial) + return fmt.Errorf("未进入支付宝收款码页(当前前台 %q)", fg) +} + +// alipayRecordSummary 收款码页(PayeeQRActivity)下方「收款记录」入口 +// (2026-09-12 uiautomator dump 实测:收款码页「收款记录」中心 (251,1401))。 +// 注意:点击后进入的是【脆弱界面】(XRiverTransActivity),仅 24h 内记录且不稳定—— +// 需按 CaptureAlipayLedger 全链路(脆弱→灰区→总览→稳定)继续导航。 +var alipayRecordSummary = wxNavPt{251, 1401} + +// 脆弱界面→稳定界面导航段(2026-09-12 uiautomator dump 实测标定): +// - alipayLedgerFragileGray 脆弱界面(XRiverTransActivity)「收款记录」标题上方约 1 公分处 +// (灰色区域),点击后收起脆弱界面回到收款总览界面(XRiverActivity)。实测脆弱界面 +// 「收款记录」标题中心 (540,426),上方约 1 公分 ≈ (540,262)。 +// - alipayLedgerOverviewEntry 收款总览界面(XRiverActivity)内的「收款记录」按钮 +// (uiautomator dump 实测中心 (203,583)),点击后进入稳定的完整收款记录页 +// (XRiverActivity$App01,历史全部记录,支持下拉刷新)。 +var alipayLedgerFragileGray = wxNavPt{540, 262} +var alipayLedgerOverviewEntry = wxNavPt{203, 583} + +// CaptureAlipayLedger 导航到支付宝稳定的完整收款记录页(监听界面,PLAN-EVENT-MONITOR §2.3)。 +// +// 链路(2026-08-27 真机反馈修正):启动支付宝 → 点「收付款」→ 点「收钱」进收款码页(PayeeQRActivity) +// → 点「收款记录」→ 进入【脆弱界面】(XRiverTransActivity,仅 24h 内记录,不稳定,可能显示成别的) +// → 点脆弱界面上方灰色区域 → 回到收款总览界面(XRiverActivity) → 收款总览界面点「收款记录」 +// → 进入【稳定界面】(XRiverActivity$App01,历史全部记录,支持下拉刷新)。 +// +// 关键:不能把 XRiverTransActivity 当作"已到达"判据——那是脆弱界面,误判成功会导致 +// 读到不完整的 24h 内记录。必须走完「脆弱→灰区→总览→稳定」全链路。userId>0 时以该 user 启动。 +func CaptureAlipayLedger(ctx context.Context, adb *adbmgr.Manager, serial string, userId int) error { + if err := wakeHome(ctx, adb, serial); err != nil { + return fmt.Errorf("唤醒屏幕失败: %w", err) + } + if err := adb.RestartAppUser(ctx, serial, alipayPackage, alipayLaunchActivity, userId); err != nil { + return fmt.Errorf("启动支付宝失败: %w", err) + } + time.Sleep(5 * time.Second) // 冷启动 + 首页加载 + + // 前台守卫(同 CaptureAlipayQrcode):支付宝未到前台绝不开始盲点坐标 + if err := awaitForeground(ctx, adb, serial, alipayPackage, foregroundWait); err != nil { + return fmt.Errorf("启动支付宝失败: %w", err) + } + + // 屏幕固定保险:固定支付宝前台任务,导航期间其他 app(监听 app/系统弹窗) + // 无法抢占前台(实测 am start 被拦截 error 101),保证坐标导航不被打断。 + // 导航结束(含失败路径)defer 解锁,恢复自由切换。 + if taskID, perr := adb.PinCurrentTask(ctx, serial); perr == nil { + defer func() { _ = adb.UnpinTask(ctx, serial) }() + _ = taskID + } else { + logger.Warn("capture", "屏幕固定失败(导航将无保护执行,可能被其他 app 打断)", + "err", perr, "serial", serial) + } + + // 广告弹窗防御:首页布局就绪判定 = dump 出现「收付款」。广告弹窗遮挡时 + // 入口不可见 → 点一次收付款位置关闭弹窗回首页,再给足时间等首页出现。 + // 简单高效:只做一轮判断,不循环轮询(导航可靠性靠固定等待,不靠状态判断)。 + xml, _ := adb.DumpUI(ctx, serial) + if !strings.Contains(xml, "收付款") { + if terr := tapPinned(ctx, adb, serial, alipayHomePayEntry.X, alipayHomePayEntry.Y); terr != nil { + return fmt.Errorf("点击收付款入口(广告弹窗防御)失败: %w", terr) + } + time.Sleep(3 * time.Second) + } + + if err := tapPinned(ctx, adb, serial, alipayHomePayEntry.X, alipayHomePayEntry.Y); err != nil { + return fmt.Errorf("点击收付款入口失败: %w", err) + } + time.Sleep(3 * time.Second) // 收付款页加载(低端机/网络慢需更久) + + if err := tapPinned(ctx, adb, serial, alipayCollectMoneyBtn.X, alipayCollectMoneyBtn.Y); err != nil { + return fmt.Errorf("点击收钱按钮失败: %w", err) + } + if err := requireAlipayPayeePage(ctx, adb, serial); err != nil { + return err + } + // 经营态处理:收款码页默认可能是「经营收钱」态(页面布局不同,「收款记录」入口位置 + // 与个人态不一致)。进账本前先点「个人收钱」tab 切回个人态,保证后续账本入口坐标有效。 + // 与 CaptureAlipayQrcode 的经营态补救同机制,但这里主动先切(幂等:个人态点它无副作用)。 + if err := tapPinned(ctx, adb, serial, alipayPersonalTab.X, alipayPersonalTab.Y); err != nil { + return fmt.Errorf("点击个人收钱 tab 失败: %w", err) + } + time.Sleep(1500 * time.Millisecond) // 切态动画 + + // ① 收款码页点「收款记录」→ 进入脆弱界面(一定脆弱,无需 if 判断) + if err := tapPinned(ctx, adb, serial, alipayRecordSummary.X, alipayRecordSummary.Y); err != nil { + return fmt.Errorf("点击收款记录入口失败: %w", err) + } + time.Sleep(2500 * time.Millisecond) // 脆弱界面加载(WebView 渲染较慢) + // ② 脆弱界面点上方灰色区域 → 回到收款总览界面 + if err := tapPinned(ctx, adb, serial, alipayLedgerFragileGray.X, alipayLedgerFragileGray.Y); err != nil { + return fmt.Errorf("点击脆弱界面灰色区域失败: %w", err) + } + // ③ 从总览界面点「收款记录」→ 进入稳定界面 + return NavigateOverviewToLedger(ctx, adb, serial) +} + +// NavigateOverviewToLedger 从收款总览界面点「收款记录」进入稳定账本页。 +// 供冷启动全链路(CaptureAlipayLedger)与热启动补点(EnsureForeground 恢复时 +// 前台在总览界面)共用。 +func NavigateOverviewToLedger(ctx context.Context, adb *adbmgr.Manager, serial string) error { + // 屏幕固定(PINNED)模式下 input tap/swipe 注入事件会被拦截(2026-08-28 实测: + // 固定态点击无效,解锁后同坐标立即生效)——必须先释放固定,否则「收款记录」 + // 点击不生效卡在总览界面。解锁后 defer 仍会幂等解锁。 + if err := adb.UnpinTask(ctx, serial); err != nil { + logger.Warn("capture", "释放屏幕固定失败(继续尝试点击)", + "err", err, "serial", serial) + } + // 总览界面含横幅加载动画,固定等待 2s 让布局稳定(2026-08-28 实测)。 + // 不做 dump 文本检测(「今日收款」)作为点击前置——dump 时序不可靠会误判 + // 未就绪导致断链(2026-08-28 实测:预热走到此步因检测失败而中断,界面实际已就绪)。 + time.Sleep(2 * time.Second) + // 点击「收款记录」进入稳定账本页;以活动名确认到达,失败重试(最多 3 次)。 + var lastErr error + for i := 0; i < 3; i++ { + if err := tapNav(ctx, adb, serial, alipayLedgerOverviewEntry); err != nil { + return fmt.Errorf("点击总览界面收款记录失败: %w", err) + } + if err := requireAlipayLedgerPage(ctx, adb, serial); err == nil { + return nil + } else { + lastErr = err + } + time.Sleep(1500 * time.Millisecond) + } + return fmt.Errorf("点击收款记录后未到达稳定账本页: %w", lastErr) +} + +// waitOverviewReady 轮询等待收款总览界面加载完成(dump 出现「今日收款」文本)。 +// 总览界面有顶部横幅加载动画,固定 sleep 可能在布局未稳定时进入下一步。 +// 用 dump 文本判断是用户认可的"快速首页判定"同款机制,非 Activity 状态判断。 +func waitOverviewReady(ctx context.Context, adb *adbmgr.Manager, serial string, timeout time.Duration) bool { + deadline := time.Now().Add(timeout) + for time.Now().Before(deadline) { + xml, err := adb.DumpUI(ctx, serial) + if err == nil && strings.Contains(xml, "今日收款") { + return true + } + select { + case <-ctx.Done(): + return false + case <-time.After(800 * time.Millisecond): + } + } + return false +} + +// requireAlipayLedgerPage 确认支付宝前台已到达稳定的完整收款记录页。 +// 判据 = XRiverActivity$App01(稳定界面)。注意不能匹配 XRiverTransActivity—— +// 那是脆弱界面(24h 内记录,不稳定),误判会导致读到不完整数据。 +func requireAlipayLedgerPage(ctx context.Context, adb *adbmgr.Manager, serial string) error { + for i := 0; i < 6; i++ { + fg, err := adb.ForegroundApp(ctx, serial) + if err == nil && strings.Contains(fg, "XRiverActivity$App") { + return nil + } + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(1500 * time.Millisecond): + } + } + fg, _ := adb.ForegroundApp(ctx, serial) + return fmt.Errorf("未进入支付宝稳定收款记录页(当前前台 %q)", fg) +} + +// CleanupAlipayGallery 清理指定 user 空间支付宝相册里的旧收款码图(保留最新 QrcodeKeepOnDevice 张)。 +// 与微信相册清理同策略:防止频繁捕获打满存储。 +func CleanupAlipayGallery(ctx context.Context, adb *adbmgr.Manager, serial string, userId int) (deleted int) { + dir := AlipayQrDir + if userId > 0 { + dir = fmt.Sprintf("/storage/emulated/%d/DCIM/Alipay", userId) + } + files, err := adb.ListFiles(ctx, serial, dir) + if err != nil || len(files) <= QrcodeKeepOnDevice { + return 0 // 目录不存在/不可读 或 未超保留数 → 无码可清 + } + for _, f := range files[QrcodeKeepOnDevice:] { // ListFiles 按 -t 倒序,越靠后越旧 + if err := adb.Rm(ctx, serial, dir+"/"+f); err != nil { + continue + } + deleted++ + } + return deleted +} + +// listAlipayImages 列出支付宝相册图片文件名(按 -t 倒序)。 +func listAlipayImages(ctx context.Context, adb *adbmgr.Manager, serial, dir string) []string { + files, err := adb.ListFiles(ctx, serial, dir) + if err != nil { + return nil + } + return files +} diff --git a/internal/admin/capture/alipay_ledger.go b/internal/admin/capture/alipay_ledger.go new file mode 100644 index 0000000..13ad2fe --- /dev/null +++ b/internal/admin/capture/alipay_ledger.go @@ -0,0 +1,258 @@ +// 支付宝账本页(收款记录 XRiverTransActivity)服务端读取与解析。 +// 背景:账本页是小程序容器(FLAG_SECURE),监听端无障碍 rootInActiveWindow 读不到节点树; +// 但 adb shell 的 uiautomator dump 是系统级通道,不受 SECURE 限制(实测可读出完整账本行)。 +// 链路:adb uiautomator dump → XML → 解析账本行(付款方 | HH:MM:SS | +金额)。 +// 账本行无备注(支付宝账本当前无备注行,Note 恒为空),去重指纹 = 时间|金额(秒级时间戳 + 金额)。 +package capture + +import ( + "encoding/xml" + "fmt" + "regexp" + "strings" +) + +// AlipayLedgerRow 账本页单行收款记录(去重指纹 = 时间|金额) +type AlipayLedgerRow struct { + Payer string // 付款方(脱敏,如 **怡 / 朱*6) + Date string // 所属日期分组("今日/M月d日"原始文案,如 "昨日 8月24日" / "7月1日") + Time string // HH:MM:SS + Amount string // 金额字符串(支付宝 +1.00 / 微信 ¥39.00) + AmountMinor int64 // 金额(分) + Note string // 付款方备注文本(微信账本"付款方备注:xxx"行;支付宝账本页无备注→空,需详情页探测) + AmountBounds string // 金额节点 bounds "[l,t][r,b]"(探测详情页备注码的点击坐标;微信账本无需探测→空) + onScreen bool // 是否屏幕可见行(离屏虚拟化行 bounds [0,0][0,0] 为 false;解析器内部用) +} + +// Fingerprint 行去重指纹:时间|金额(秒级时间到秒,同金额多笔靠时间区分) +func (r AlipayLedgerRow) Fingerprint() string { + return r.Time + "|" + r.Amount +} + +// ReadAlipayLedger 通过 adb uiautomator dump 读取当前前台账本页,解析可见收款行。 +// 前置条件:设备前台已在账本页(XRiverTransActivity);不在则该次调用返回 0 行(调用方自行判断)。 +// 离屏虚拟化行(bounds [0,0][0,0])会被过滤,只返回当前屏幕可见行。 +func ReadAlipayLedger(xmlText string) ([]AlipayLedgerRow, error) { + root, err := parseUINodeTree(xmlText) + if err != nil { + return nil, fmt.Errorf("解析账本页 XML 失败: %w", err) + } + var rows []AlipayLedgerRow + collectLedgerRows(root, &rows) + return rows, nil +} + +// uiNode uiautomator dump XML 的最小节点结构(只保留解析所需字段) +type uiNode struct { + Text string + Desc string + Bounds string + Kids []*uiNode +} + +// parseUINodeTree 用 encoding/xml 解码 uiautomator 层级树 +func parseUINodeTree(xmlText string) (*uiNode, error) { + dec := xml.NewDecoder(strings.NewReader(xmlText)) + var stack []*uiNode + var root *uiNode + for { + tok, err := dec.Token() + if err != nil { + if err.Error() == "EOF" { + break + } + return nil, err + } + switch t := tok.(type) { + case xml.StartElement: + n := &uiNode{} + for _, a := range t.Attr { + switch a.Name.Local { + case "text": + n.Text = a.Value + case "content-desc": + n.Desc = a.Value + case "bounds": + n.Bounds = a.Value + } + } + if len(stack) > 0 { + parent := stack[len(stack)-1] + parent.Kids = append(parent.Kids, n) + } else { + root = n + } + stack = append(stack, n) + case xml.EndElement: + stack = stack[:len(stack)-1] + } + } + if root == nil { + return nil, fmt.Errorf("空 XML 层级树") + } + return root, nil +} + +var ( + ledgerTimeRe = regexp.MustCompile(`^\d{2}:\d{2}:\d{2}$`) // HH:MM:SS + ledgerAmountRe = regexp.MustCompile(`^\+\d+(\.\d{2})?$`) // +1.00 / +10 + zeroBoundsRe = regexp.MustCompile(`\[0,0\]\[0,0\]`) +) + +// collectLedgerRows 递归收集账本行:行 = 子树内恰好含 1 个时间 + 1 个金额文本的最小容器。 +// 过滤:离屏虚拟化行(bounds [0,0][0,0],Android 懒加载未渲染的真实节点)。 +func collectLedgerRows(n *uiNode, rows *[]AlipayLedgerRow) { + collectLedgerRowsGroup(n, "", rows) +} + +// collectLedgerRowsGroup 带日期分组下钻:dateGroup 为当前所属日期分组文案(如 "今日 8月25日")。 +// 日期分组头("今日/昨日/M月d日")在账本中是独立文本节点,作为行收集时下钻到其子树继承。 +func collectLedgerRowsGroup(n *uiNode, dateGroup string, rows *[]AlipayLedgerRow) { + if n == nil { + return + } + if dg, ok := ledgerDateGroup(n.Text); ok { + dateGroup = dg + } + if timeText, amountText, isRow := ledgerRowTexts(n); isRow { + if !zeroBoundsRe.MatchString(n.Bounds) { + *rows = append(*rows, AlipayLedgerRow{ + Payer: ledgerRowPayer(n, timeText, amountText), + Date: dateGroup, + Time: timeText, + Amount: amountText, + AmountMinor: ledgerAmountMinor(amountText), + AmountBounds: ledgerAmountBounds(n), + }) + } + return // 行容器内不再深入(子节点无独立行) + } + for _, k := range n.Kids { + collectLedgerRowsGroup(k, dateGroup, rows) + } +} + +// ledgerDateGroup 识别日期分组头文本("今日 8月25日" / "昨日 8月24日" / "8月19日")。 +func ledgerDateGroup(text string) (string, bool) { + if text == "" || len(text) > 16 { + return "", false + } + if strings.HasPrefix(text, "今日 ") || strings.HasPrefix(text, "昨日 ") { + return text, true + } + // 纯 "M月d日"(如 "8月19日") + if strings.Contains(text, "月") && strings.HasSuffix(text, "日") && !strings.ContainsAny(text, " 笔收额") { + return text, true + } + return "", false +} + +// ledgerRowTexts 判断节点是否为"账本行最小容器":直系子树内含恰好 1 个时间 + 1 个金额文本。 +// 返回 (时间文本, 金额文本, 是否行容器)。摘要区(收款笔数/总额 ¥2.00)无 HH:MM:SS 时间 → 不是行。 +func ledgerRowTexts(n *uiNode) (string, string, bool) { + var timeText, amountText string + for _, k := range n.Kids { + tt, at, ok := ledgerNodeTexts(k) + if !ok { + continue + } + if tt != "" { + if timeText != "" { + return "", "", false // 多时间 → 是分组容器,非单行 + } + timeText = tt + } + if at != "" { + if amountText != "" { + return "", "", false + } + amountText = at + } + } + if timeText != "" && amountText != "" { + return timeText, amountText, true + } + return "", "", false +} + +// ledgerNodeTexts 递归收集子树内的(首个)时间文本与金额文本。 +func ledgerNodeTexts(n *uiNode) (timeText, amountText string, ok bool) { + if n == nil { + return "", "", false + } + if n.Text != "" { + if ledgerTimeRe.MatchString(n.Text) { + return n.Text, "", true + } + if ledgerAmountRe.MatchString(n.Text) { + return "", n.Text, true + } + } + for _, k := range n.Kids { + tt, at, o := ledgerNodeTexts(k) + if o { + return tt, at, true + } + } + return "", "", false +} + +// ledgerAmountBounds 提取行容器内金额节点的 bounds("[l,t][r,b]")。 +// 用途:支付宝账本页金额可点击进入收款详情页读备注码——探测点击坐标 = 金额 bounds 中心。 +// 找不到返回 ""(调用方跳过探测)。 +func ledgerAmountBounds(n *uiNode) string { + if n == nil { + return "" + } + if n.Text != "" && ledgerAmountRe.MatchString(n.Text) { + return n.Bounds + } + for _, k := range n.Kids { + if b := ledgerAmountBounds(k); b != "" { + return b + } + } + return "" +} + +// ledgerRowPayer 提取行容器内付款方文本:非时间、非金额、非空白的文本(如 **怡)。 +func ledgerRowPayer(n *uiNode, timeText, amountText string) string { + var payer string + var findPayer func(*uiNode) + findPayer = func(m *uiNode) { + if payer != "" { + return + } + if m.Text != "" && m.Text != timeText && m.Text != amountText && !ledgerTimeRe.MatchString(m.Text) && !ledgerAmountRe.MatchString(m.Text) { + payer = m.Text + return + } + for _, k := range m.Kids { + findPayer(k) + } + } + findPayer(n) + return payer +} + +// ledgerAmountMinor 金额文本转分("+1.00" → 100) +func ledgerAmountMinor(amount string) int64 { + s := strings.TrimPrefix(amount, "+") + var major, minor int64 + fmt.Sscanf(s, "%d.%d", &major, &minor) + // "1.5" 补零成 "1.50";"1" 无小数视为整元 + if i := strings.IndexByte(s, '.'); i >= 0 { + frac := s[i+1:] + switch len(frac) { + case 1: + minor *= 10 + case 2: + // 保持原样 + default: + minor = 0 + } + } else { + minor = 0 + } + return major*100 + minor +} diff --git a/internal/admin/capture/alipay_ledger_test.go b/internal/admin/capture/alipay_ledger_test.go new file mode 100644 index 0000000..8765fda --- /dev/null +++ b/internal/admin/capture/alipay_ledger_test.go @@ -0,0 +1,58 @@ +package capture + +import ( + "os" + "testing" +) + +func TestReadAlipayLedger(t *testing.T) { + data, err := os.ReadFile("testdata/alipay_ledger_ref.xml") + if err != nil { + t.Fatalf("读取测试数据失败: %v", err) + } + rows, err := ReadAlipayLedger(string(data)) + if err != nil { + t.Fatalf("解析失败: %v", err) + } + // 参考 dump 可见行:昨日(8月24日) 2 笔(18:18:38 +1.00、17:16:02 +1.00); + // 8月19日 离屏行 bounds=[0,0][0,0] 应被过滤。 + if len(rows) != 2 { + t.Fatalf("期望 2 行可见账本行(离屏行被过滤),实际 %d 行: %+v", len(rows), rows) + } + got := map[string]AlipayLedgerRow{} + for _, r := range rows { + got[r.Time] = r + } + if r, ok := got["18:18:38"]; !ok { + t.Errorf("缺少 18:18:38 行: %+v", rows) + } else if r.Amount != "+1.00" || r.AmountMinor != 100 { + t.Errorf("18:18:38 行金额错误: %+v", r) + } else if r.Payer != "**怡" { + t.Errorf("18:18:38 行付款方错误: %+v", r) + } + if r, ok := got["17:16:02"]; !ok { + t.Errorf("缺少 17:16:02 行: %+v", rows) + } else if r.Amount != "+1.00" || r.AmountMinor != 100 { + t.Errorf("17:16:02 行金额错误: %+v", r) + } +} + +func TestLedgerAmountMinor(t *testing.T) { + cases := map[string]int64{ + "+1.00": 100, + "+1.50": 150, + "+10": 1000, + "+0.01": 1, + } + for in, want := range cases { + if got := ledgerAmountMinor(in); got != want { + t.Errorf("ledgerAmountMinor(%q) = %d, 期望 %d", in, got, want) + } + } +} + +func TestReadAlipayLedgerInvalidXML(t *testing.T) { + if _, err := ReadAlipayLedger("not xml"); err == nil { + t.Error("无效 XML 应返回错误") + } +} diff --git a/internal/admin/capture/alipay_lock.go b/internal/admin/capture/alipay_lock.go new file mode 100644 index 0000000..b0f0db2 --- /dev/null +++ b/internal/admin/capture/alipay_lock.go @@ -0,0 +1,183 @@ +// 支付宝最近任务锁定(MIUI/HyperOS 防一键清理)。 +// +// MIUI 的"锁定最近任务"是纯客户端 UI 特性:状态存于 Recents 进程内存, +// 无广播/binder/shell 命令(am task lock 是 kiosk 模式,非此机制)。 +// 唯一稳定可自动化路径 = uiautomator 定位任务卡片 → 长按 → 点「锁定任务」。 +// 2026-08-28 真机实测完整闭环:加锁/解锁可逆,一键清理后锁定任务保留。 +package capture + +import ( + "context" + "fmt" + "strings" + "time" + + "paygateway/internal/adbmgr" + "paygateway/internal/logger" +) + +// recentsActivity MIUI/HyperOS 最近任务界面组件(am start 直达,比 KEYCODE_APP_SWITCH +// 稳定——后者在部分机型会打开桌面多屏视图而非最近任务)。 +const recentsActivity = "com.miui.home/.recents.RecentsActivity" + +// lockTaskBtnText 长按任务卡片弹出菜单中的按钮文本(国内版 MIUI 用 TextView,无 resource id)。 +// 已锁定任务的菜单显示「解除锁定」,未锁定显示「锁定任务」,二者可区分。 +const lockTaskBtnText = "锁定任务" + +// LockPaymentTasks 锁定最近任务中指定支付应用的主任务卡片(每应用最多锁一张), +// 防止一键清理移除任务栈。一个应用锁定它的主进程任务栈即可——锁定多个同应用进程 +// 会触发 MIUI 保护性杀进程(系统判定异常)。 +// 流程:直达 RecentsActivity → dump 定位未加锁的匹配应用卡片 → 逐张长按→点锁定。 +// 锁定状态存于内存(设备重启失效),调用方应在每次预热/恢复账本页后调用。 +func LockPaymentTasks(ctx context.Context, adb *adbmgr.Manager, serial string, appNames ...string) (int, error) { + locked := 0 + // 最多 3 轮:每轮锁一张未加锁卡片,锁定后列表刷新,重进最近任务避免坐标漂移 + for attempt := 0; attempt < 3; attempt++ { + unlocked, err := findUnlockedCards(ctx, adb, serial, appNames) + if err != nil { + return locked, err + } + if len(unlocked) == 0 { + return locked, nil + } + // 只锁第一张未加锁的卡片(每应用只锁主进程任务栈,足够防清理) + if err := lockOneCard(ctx, adb, serial, unlocked[0]); err != nil { + logger.Warn("capture", "锁定支付应用任务卡片失败", + "err", err, "serial", serial) + continue + } + locked++ + // 重新进最近任务列表以避免坐标漂移 + _, _ = adb.Shell(ctx, 3*time.Second, serial, "input keyevent KEYCODE_BACK") + time.Sleep(500 * time.Millisecond) + } + return locked, nil +} + +// findCards 进入最近任务并返回所有匹配应用名卡片的 bounds(无论锁定状态)。 +func findCards(ctx context.Context, adb *adbmgr.Manager, serial string, appNames []string) ([]string, error) { + if _, err := adb.Shell(ctx, 8*time.Second, serial, "am start -n "+recentsActivity); err != nil { + return nil, fmt.Errorf("打开最近任务失败: %w", err) + } + time.Sleep(2500 * time.Millisecond) + xmlText, err := adb.DumpUI(ctx, serial) + if err != nil { + return nil, fmt.Errorf("dump 最近任务失败: %w", err) + } + root, err := parseUINodeTree(xmlText) + if err != nil { + return nil, fmt.Errorf("解析最近任务 UI 失败: %w", err) + } + var out []string + walkUINodes(root, func(n *uiNode) { + if matchAnyName(n.Desc, appNames) && n.Bounds != "" { + out = append(out, n.Bounds) + } + }) + return out, nil +} + +// matchAnyName 判断 content-desc 是否包含任一应用名(如"支付宝,未加锁"匹配"支付宝")。 +func matchAnyName(desc string, appNames []string) bool { + for _, name := range appNames { + if strings.Contains(desc, name) { + return true + } + } + return false +} + +// findUnlockedCards 进入最近任务并返回所有未加锁的匹配应用名卡片的 bounds。 +func findUnlockedCards(ctx context.Context, adb *adbmgr.Manager, serial string, appNames []string) ([]string, error) { + if _, err := adb.Shell(ctx, 8*time.Second, serial, "am start -n "+recentsActivity); err != nil { + return nil, fmt.Errorf("打开最近任务失败: %w", err) + } + time.Sleep(2500 * time.Millisecond) + xmlText, err := adb.DumpUI(ctx, serial) + if err != nil { + return nil, fmt.Errorf("dump 最近任务失败: %w", err) + } + root, err := parseUINodeTree(xmlText) + if err != nil { + return nil, fmt.Errorf("解析最近任务 UI 失败: %w", err) + } + var out []string + walkUINodes(root, func(n *uiNode) { + // 任务卡片 content-desc 形如 "支付宝,未加锁"("打开支付宝应用信息。"等 + // 信息按钮也含应用名但无锁定状态后缀,必须排除,否则长按会点到按钮) + if matchAnyName(n.Desc, appNames) && strings.Contains(n.Desc, "未加锁") && n.Bounds != "" { + out = append(out, n.Bounds) + } + }) + return out, nil +} + +// walkUINodes 深度优先遍历 UI 节点树,对每个节点调用 fn。 +func walkUINodes(n *uiNode, fn func(*uiNode)) { + if n == nil { + return + } + fn(n) + for _, k := range n.Kids { + walkUINodes(k, fn) + } +} + +// lockOneCard 长按任务卡片 → 点「锁定任务」。bounds 形如 "[l,t][r,b]"。 +func lockOneCard(ctx context.Context, adb *adbmgr.Manager, serial, bounds string) error { + cx, cy, err := parseBoundsCenter(bounds) + if err != nil { + return err + } + // 长按 = swipe 同点 + 长 duration(input tap 无法表达长按,实测 swipe 900ms 有效) + if err := adb.Swipe(ctx, serial, cx, cy, cx, cy, 900); err != nil { + return fmt.Errorf("长按任务卡片失败: %w", err) + } + time.Sleep(1500 * time.Millisecond) + xmlText, err := adb.DumpUI(ctx, serial) + if err != nil { + return fmt.Errorf("dump 锁定菜单失败: %w", err) + } + btn, err := findButtonBounds(xmlText, lockTaskBtnText) + if err != nil { + return err + } + if btn == "" { + return fmt.Errorf("未找到锁定按钮(长按菜单未弹出,HyperOS 已知卡死问题)") + } + bx, by, err := parseBoundsCenter(btn) + if err != nil { + return err + } + if err := adb.Tap(ctx, serial, bx, by); err != nil { + return fmt.Errorf("点击锁定任务失败: %w", err) + } + time.Sleep(1500 * time.Millisecond) + return nil +} + +// findButtonBounds 在 dump XML 中找匹配按钮的 bounds。 +// 菜单按钮可能是 TextView(text 属性)或 ImageView(content-desc 属性, +// 如 "锁定任务" 是 com.miui.home:id/menu_item_lock ImageView),需两者都匹配。 +func findButtonBounds(xmlText, want string) (string, error) { + root, err := parseUINodeTree(xmlText) + if err != nil { + return "", err + } + var found string + walkUINodes(root, func(n *uiNode) { + if found == "" && n.Bounds != "" && (strings.Contains(n.Text, want) || strings.Contains(n.Desc, want)) { + found = n.Bounds + } + }) + return found, nil +} + +// parseBoundsCenter 解析 "[l,t][r,b]" 为中心坐标。 +func parseBoundsCenter(bounds string) (x, y int, err error) { + var l, t, r, b int + if _, err := fmt.Sscanf(bounds, "[%d,%d][%d,%d]", &l, &t, &r, &b); err != nil { + return 0, 0, fmt.Errorf("解析 bounds %q 失败: %w", bounds, err) + } + return (l + r) / 2, (t + b) / 2, nil +} diff --git a/internal/admin/capture/alipay_note.go b/internal/admin/capture/alipay_note.go new file mode 100644 index 0000000..ded714d --- /dev/null +++ b/internal/admin/capture/alipay_note.go @@ -0,0 +1,85 @@ +// 支付宝收款详情页备注码探测(2026-08-29 真机实测)。 +// +// 背景:支付宝账本页(XRiverActivity$App01)只显示 付款方|时间|金额,不显示备注码; +// 备注码(4 位字母数字)只存在于单笔收款详情页,需点击该行金额进入详情页读取。 +// +// 实测链路(红米K60 / 主空间支付宝,2026-08-29): +// +// 账本页金额节点 bounds 中心点击 → 详情页(收款详情)→ dump → "付款留言" 标签后 +// 相邻节点文本即备注码(如 jzt4 / 7x57 / 7u6g)→ KEYCODE_BACK 返回账本页。 +// +// 耗时实测:点击 0.17s + 详情加载 1.0s + dump 2.36s + 返回 0.25s ≈ 单笔 3.5s。 +// 优化:金额唯一时跳过探测(调用方短路);详情加载等待取 0.8s(布局静态,实测 0.5s 已渲染)。 +package capture + +import ( + "context" + "fmt" + "strings" + "time" + + "paygateway/internal/adbmgr" +) + +// alipayDetailNoteLabel 详情页备注码标签文案(实测:收款详情页"付款留言"行) +const alipayDetailNoteLabel = "付款留言" + +// alipayDetailLoadWait 点击金额后详情页渲染等待(实测 0.5s 已就绪,留 0.8s 余量) +const alipayDetailLoadWait = 800 * time.Millisecond + +// ProbeAlipayNote 从支付宝账本行探测备注码:点金额 → 详情页 dump → 提取备注 → 返回账本页。 +// bounds 为账本行金额节点 bounds("[l,t][r,b]"),点击其中心进入收款详情页。 +// 返回备注码(可能为空:详情页无"付款留言"标签);失败返回错误(调用方跳过该行探测, +// 金额兜底匹配仍可用——探测只是加速精确定位,不是资金安全前置)。 +func ProbeAlipayNote(ctx context.Context, adb *adbmgr.Manager, serial, bounds string) (string, error) { + if bounds == "" { + return "", fmt.Errorf("金额 bounds 为空,无法探测备注码") + } + cx, cy, err := parseBoundsCenter(bounds) + if err != nil { + return "", fmt.Errorf("解析金额 bounds 失败: %w", err) + } + // 1. 点击金额进入收款详情页 + if err := adb.Tap(ctx, serial, cx, cy); err != nil { + return "", fmt.Errorf("点击金额进入详情失败: %w", err) + } + select { + case <-ctx.Done(): + return "", ctx.Err() + case <-time.After(alipayDetailLoadWait): + } + // 2. dump 详情页并解析备注码 + xml, err := adb.DumpUI(ctx, serial) + if err != nil { + return "", fmt.Errorf("详情页 dump 失败: %w", err) + } + note := parseAlipayDetailNote(xml) + // 3. 返回账本页(不等动画,下轮 EnsureForeground 恢复) + _ = adb.KeyEvent(ctx, serial, 4) // KEYCODE_BACK + return note, nil +} + +// parseAlipayDetailNote 从收款详情页 dump XML 提取备注码。 +// 规则(实测结构):找 "付款留言" 标签节点,其后紧跟的兄弟节点 text 即备注码。 +// 返回 "" 表示详情页无付款留言(未命中备注码——调用方降级金额兜底)。 +func parseAlipayDetailNote(xmlText string) string { + root, err := parseUINodeTree(xmlText) + if err != nil { + return "" + } + // 扁平收集带文本节点(保序),找标签后下一个非空文本 + var nodes []textNode + collectFlatTexts(root, &nodes) + for i, n := range nodes { + if strings.TrimSpace(n.text) == alipayDetailNoteLabel { + // 备注码 = 标签后首个非空文本节点(实测 index 6→7 相邻) + for j := i + 1; j < len(nodes); j++ { + v := strings.TrimSpace(nodes[j].text) + if v != "" { + return v + } + } + } + } + return "" +} diff --git a/internal/admin/capture/alipay_note_test.go b/internal/admin/capture/alipay_note_test.go new file mode 100644 index 0000000..9304ca1 --- /dev/null +++ b/internal/admin/capture/alipay_note_test.go @@ -0,0 +1,44 @@ +package capture + +import ( + "os" + "testing" +) + +// TestParseAlipayDetailNote 详情页备注码提取(实测 fixture:付款留言标签后相邻节点文本即备注码) +func TestParseAlipayDetailNote(t *testing.T) { + data, err := os.ReadFile("testdata/alipay_detail_note.xml") + if err != nil { + t.Fatalf("读取测试数据失败: %v", err) + } + if got := parseAlipayDetailNote(string(data)); got != "jzt4" { + t.Fatalf("备注码应为 jzt4,实际 %q", got) + } +} + +// TestParseAlipayDetailNote_NoLabel 无付款留言标签(非详情页/无备注)→ 返回空 +func TestParseAlipayDetailNote_NoLabel(t *testing.T) { + if got := parseAlipayDetailNote(``); got != "" { + t.Fatalf("无标签应返回空,实际 %q", got) + } +} + +// TestLedgerAmountBounds 账本行金额节点 bounds 提取(探测点击坐标) +func TestLedgerAmountBounds(t *testing.T) { + data, err := os.ReadFile("testdata/alipay_ledger_ref.xml") + if err != nil { + t.Fatalf("读取测试数据失败: %v", err) + } + rows, err := ReadAlipayLedger(string(data)) + if err != nil { + t.Fatalf("解析失败: %v", err) + } + if len(rows) == 0 { + t.Fatal("应至少 1 行") + } + for _, r := range rows { + if r.AmountBounds == "" { + t.Errorf("行 %s 缺少 AmountBounds(金额节点 bounds 应被填充): %+v", r.Time, r) + } + } +} diff --git a/internal/admin/capture/common.go b/internal/admin/capture/common.go new file mode 100644 index 0000000..042c450 --- /dev/null +++ b/internal/admin/capture/common.go @@ -0,0 +1,138 @@ +// Package capture 设备端收款码自动捕获(服务端 adb 驱动,纯自动化逻辑)。 +// +// 与 admin 包解耦:本包只依赖 adbmgr(adb 操作),不依赖 store / 账户池 / HTTP, +// 便于独立测试与复用。HTTP 层(admin 包)负责设备查找、去重、入库、账户名解析。 +// +// 背景:微信收付款页带 FLAG_SECURE(对无障碍隐藏节点树 + 屏蔽系统手势注入), +// 监听端 APP 内无法自动化微信(支付宝无此限制)。但服务器 adb shell 的 +// input tap / screencap 不受 SECURE 限制(adb 是系统级通道)→ 微信捕获改由服务器驱动。 +package capture + +import ( + "bytes" + "context" + "fmt" + "image" + _ "image/gif" + _ "image/jpeg" + _ "image/png" + "regexp" + "strings" + "time" + + "github.com/makiuchi-d/gozxing" + "github.com/makiuchi-d/gozxing/qrcode" + + "paygateway/internal/adbmgr" +) + +// MaxQRUploadSize 二维码图片大小上限(2MB) +const MaxQRUploadSize = 2 << 20 + +// QrcodeKeepOnDevice 每设备相册保留的微信收款码图片张数(每账户只留最新 1 张,防存储打满) +const QrcodeKeepOnDevice = 1 + +// foregroundWait 坐标导航启动 app 后等待其到前台的上限。 +// 30s 给慢速跨 user 冷启动留足余量(守卫仍会快速失败,只是不误杀偏慢的冷启)。 +const foregroundWait = 30 * time.Second + +// wakeHome 唤醒屏幕 + 回桌面,清掉前一个账户驻留的前台。 +// 供各 Capture*Ledger/Qrcode 冷启动前调用:避免跨 user app 冷启被驻留前台挡住。 +func wakeHome(ctx context.Context, adb *adbmgr.Manager, serial string) error { + if err := adb.WakeUp(ctx, serial); err != nil { + return err + } + return adb.GoHome(ctx, serial) +} + +// DegenerateDump 判断 dump XML 是否为退化空树(无障碍未填充/页面未渲染)。 +// 判据:整棵 hierarchy 无任何携带非空 text/content-desc 的节点。正常渲染的账本页 +// (哪怕 0 交易行)总有标题/tab/页脚等静态文本节点;退化空树只有根 FrameLayout。 +// 用于账本读取:退化空 dump 是'读取失败'而非'账户真实为空',此时不得建立空基线, +// 否则后续真实历史行会被误判为 fresh 入账(2026-09-13 资金 P0 根因)。 +var ( + degenerateTextRe = regexp.MustCompile(`text="([^"]*)"`) + degenerateDescRe = regexp.MustCompile(`content-desc="([^"]*)"`) +) + +func DegenerateDump(xmlText string) bool { + hasContent := false + for _, m := range degenerateTextRe.FindAllStringSubmatch(xmlText, -1) { + if m[1] != "" { + hasContent = true + break + } + } + if !hasContent { + for _, m := range degenerateDescRe.FindAllStringSubmatch(xmlText, -1) { + if m[1] != "" { + hasContent = true + break + } + } + } + return !hasContent +} + +// DecodeQRContent 解析二维码图片内容(纯 Go,支持 PNG/JPEG/GIF) +// 返回解码后的字符串(如 https://qr.alipay.com/xxx 或 weixin://wxpay/bizpayurl?xxx) +func DecodeQRContent(data []byte) (string, error) { + if len(data) == 0 { + return "", fmt.Errorf("图片为空") + } + if len(data) > MaxQRUploadSize { + return "", fmt.Errorf("图片超过 2MB 限制") + } + img, _, err := image.Decode(bytes.NewReader(data)) + if err != nil { + return "", fmt.Errorf("图片解码失败(请上传清晰的收款码截图): %w", err) + } + bmp, err := gozxing.NewBinaryBitmapFromImage(img) + if err != nil { + return "", fmt.Errorf("二维码图像预处理失败: %w", err) + } + qrReader := qrcode.NewQRCodeReader() + result, err := qrReader.Decode(bmp, nil) + if err != nil { + return "", fmt.Errorf("未识别到二维码(请确认图片包含清晰的收款码): %w", err) + } + content := result.String() + if content == "" { + return "", fmt.Errorf("二维码内容为空") + } + return content, nil +} + +// TruncateStr 截断字符串(日志安全:不打印完整码内容) +func TruncateStr(s string, n int) string { + if len(s) <= n { + return s + } + return s[:n] + "..." +} + +// awaitForeground 带重试等待指定包名到前台。 +// 坐标导航的前置守卫:目标 app 未到前台时绝不能开始盲点导航坐标—— +// 重启失败/弹窗拦截/切前台慢时坐标会落进wrong app(实测点到微信/桌面/其他界面), +// 这正是捕获导航"乱来"的根源。双开空间前台串形如 "u999 com.xxx/...",Contains 匹配。 +func awaitForeground(ctx context.Context, adb *adbmgr.Manager, serial, pkg string, timeout time.Duration) error { + deadline := time.Now().Add(timeout) + var last string + for { + fg, err := adb.ForegroundApp(ctx, serial) + if err == nil { + last = fg + if strings.Contains(fg, pkg) { + return nil + } + } + if time.Now().After(deadline) { + return fmt.Errorf("等待 %s 前台超时 %v(当前前台 %q)", pkg, timeout, last) + } + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(time.Second): + } + } +} diff --git a/internal/admin/capture/testdata/alipay_detail_note.xml b/internal/admin/capture/testdata/alipay_detail_note.xml new file mode 100644 index 0000000..e8f5302 --- /dev/null +++ b/internal/admin/capture/testdata/alipay_detail_note.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/internal/admin/capture/testdata/alipay_ledger_ref.xml b/internal/admin/capture/testdata/alipay_ledger_ref.xml new file mode 100644 index 0000000..b2b9435 --- /dev/null +++ b/internal/admin/capture/testdata/alipay_ledger_ref.xml @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/internal/admin/capture/wait.go b/internal/admin/capture/wait.go new file mode 100644 index 0000000..c139f1d --- /dev/null +++ b/internal/admin/capture/wait.go @@ -0,0 +1,30 @@ +// 收款码捕获公共等待辅助:轮询相册直到出现新文件或超时。 +// +// 背景:adb 走 WiFi/反代通道有网络延迟,手机冷启动/低端机有性能延迟, +// 点「保存图片」后文件落盘耗时波动大(实测 2s~15s 不等),固定 sleep 不可靠—— +// 过早检查误判"未保存",过久等待拖慢整个捕获流程。轮询在"尽快返回"和"不误判"间取得平衡。 +package capture + +import ( + "context" + "time" +) + +// pollForNewFile 轮询目录列表(listFn 过滤),直到数量超过 base 数量或超时。 +// 每 2.5s 轮询一次,总超时 timeout;ctx 取消立即返回 false。 +func pollForNewFile(ctx context.Context, base []string, timeout time.Duration, listFn func() []string) bool { + deadline := time.Now().Add(timeout) + for { + if len(listFn()) > len(base) { + return true + } + if time.Now().After(deadline) { + return false + } + select { + case <-ctx.Done(): + return false + case <-time.After(2500 * time.Millisecond): + } + } +} diff --git a/internal/admin/capture/wechat.go b/internal/admin/capture/wechat.go new file mode 100644 index 0000000..9225b54 --- /dev/null +++ b/internal/admin/capture/wechat.go @@ -0,0 +1,387 @@ +// 微信收款码自动捕获(服务器 adb 驱动)。 +// +// 链路(与支付宝不同):微信收付款页 FLAG_SECURE 下 adb 截图返回空 → 无法直接解码; +// 改为 导航到「个人收款码」页 → 点「保存收款码」→ 图片落相册(不受 SECURE 限制)→ +// 拉取最新图片 → 解码 → 入库。 +package capture + +import ( + "context" + "fmt" + "os" + "strings" + "time" + + "paygateway/internal/adbmgr" + "paygateway/internal/logger" +) + +// wxNavPt 导航坐标点(1080x2400 手机参考系,TapRef 自动缩放实际分辨率)。 +// 仅保留手机坐标系——平板坐标系及其形态判定逻辑已整体移除(引入不可控问题)。 +type wxNavPt struct { + X, Y int +} + +// wechatQrTapSeq 微信收款码导航序列(前三步跨首页→服务页,1080x2400 真机标定): +// +// 微信首页 → 「我」tab → 「服务」 → 「收付款」 +var wechatQrTapSeq = []wxNavPt{ + {945, 2300}, // 「我」tab + {540, 700}, // 「服务」 + {300, 490}, // 「收付款」绿横幅 +} + +// wechatQrTapService 「服务」步——该步偶发 PINNED 固定态下 tap 失效,需解锁点击再固定 +var wechatQrTapService = wechatQrTapSeq[1] + +// tapNav 点击导航点:手机参考系坐标 → TapRef 自动缩放到实际生效分辨率。 +func tapNav(ctx context.Context, adb *adbmgr.Manager, serial string, p wxNavPt) error { + return adb.TapRef(ctx, serial, p.X, p.Y) +} + +// wechatQrTapDelay 每步点击后的等待。微信页面切换动画 + adb 通道网络延迟 + 设备 +// 性能波动非线性(2026-08-28 实测),必须给足余量;3s 与支付宝关键步骤一致。 +const wechatQrTapDelay = 3000 * time.Millisecond + +// wechatQrEntry 收付款总页内「二维码收款」入口(手机 632 下半屏,实测 (540,1830), +// 该格下方为「面对面红包」,上方为转账等格) +var wechatQrEntry = wxNavPt{540, 1830} + +// wechatQrPersonalTab 收款码页内「个人收款码」tab(手机参考 (270,335)) +var wechatQrPersonalTab = wxNavPt{270, 335} + +// wechatQrSaveBtn 「个人收款码」页「保存收款码」按钮(手机 OCR 中心 (483,1315), +// 点击后已实测落图 mm_facetoface_collect_qrcode_.png) +var wechatQrSaveBtn = wxNavPt{483, 1315} + +// WechatQrDir 微信收款码图片落盘目录(主空间 user 0;双开空间路径由调用方拼接 user id) +const WechatQrDir = "/storage/emulated/0/Pictures/WeiXin" + +// WechatQrPrefix 微信收款码图片文件名前缀(保存收款码写入) +const WechatQrPrefix = "mm_facetoface_collect_qrcode_" + +// CaptureWechatQrcode 核心:导航到微信个人收款码页 → 点「保存收款码」→ 从相册拉取最新图片 → 解码。 +// 返回 图片本地路径(调用方负责 os.Remove)+ 文件名 + 解码内容。 +// userId>0 时以该系统用户启动微信(双开/分身空间),相册路径同步切换。 +func CaptureWechatQrcode(ctx context.Context, adb *adbmgr.Manager, serial string, userId int) (localPath, fileName, content string, err error) { + // 1. 唤醒屏幕 + 强制重启微信到干净首页(指定双开 user;force-stop 避免旧页面残留) + if err = wakeHome(ctx, adb, serial); err != nil { + return "", "", "", fmt.Errorf("唤醒屏幕失败: %w", err) + } + // 显式 LauncherUI activity:MAIN/LAUNCHER 类别对双开 user(999) 实测启动不可靠 + if err = adb.RestartAppUser(ctx, serial, "com.tencent.mm", ".ui.LauncherUI", userId); err != nil { + return "", "", "", fmt.Errorf("启动微信失败: %w", err) + } + time.Sleep(5 * time.Second) // 微信冷启动 + 首页加载(低端机/网络慢时需更久) + + // 前台守卫:微信未到前台绝不开始盲点坐标(重启失败时坐标会落进桌面/其他 app) + if err = awaitForeground(ctx, adb, serial, "com.tencent.mm", foregroundWait); err != nil { + return "", "", "", fmt.Errorf("启动微信失败: %w", err) + } + + // 2. 依次点击导航到收付款总页 + for i, step := range wechatQrTapSeq { + if err = tapNav(ctx, adb, serial, step); err != nil { + return "", "", "", fmt.Errorf("点击第 %d 步失败: %w", i+1, err) + } + time.Sleep(wechatQrTapDelay) + } + time.Sleep(500 * time.Millisecond) + + // 3. 收付款总页 → 点「二维码收款」进入收款码页(漏此步停留总页) + if err = tapNav(ctx, adb, serial, wechatQrEntry); err != nil { + return "", "", "", fmt.Errorf("点击二维码收款入口失败: %w", err) + } + time.Sleep(wechatQrTapDelay) + // 必须确认已进入收款码页(WalletCollectQrCodeUI);否则坐标导航已偏移,后续点击全落空 + if err = requireWechatCollectPage(ctx, adb, serial); err != nil { + return "", "", "", err + } + + // 4. 直接点「保存收款码」→ 检查相册是否新增(商家/经营态该位置无按钮不会误点,个人态点击即保存)。 + // 相册未新增 → 说明在经营态 → 点「个人收款码」tab 切回个人态 → 再点保存。 + dir := WechatQrDir + if userId > 0 { + dir = fmt.Sprintf("/storage/emulated/%d/Pictures/WeiXin", userId) + } + // 相册基线:点击前已有的收款码图(防止把旧图当成新保存的) + base := listWechatQrFiles(ctx, adb, serial, dir) + + // trySave 点「保存收款码」并轮询等待相册新增(落盘延迟在慢网络/低端机上可达 10s+,固定 sleep 不可靠) + trySave := func() bool { + if terr := tapNav(ctx, adb, serial, wechatQrSaveBtn); terr != nil { + return false + } + return pollForNewFile(ctx, base, 20*time.Second, func() []string { + return listWechatQrFiles(ctx, adb, serial, dir) + }) + } + // 第一次保存失败 → 说明不在个人态(如经营收款码态无保存按钮)。 + // 循环「点个人收款码 tab → 再点保存」最多 3 轮:切态点击可能被吞/未生效,单次补救不稳定。 + if !trySave() { + for round := 1; round <= 3; round++ { + if err = tapNav(ctx, adb, serial, wechatQrPersonalTab); err != nil { + return "", "", "", fmt.Errorf("点击个人收款码 tab 失败: %w", err) + } + time.Sleep(wechatQrTapDelay) + if trySave() { + break + } + if round == 3 { + return "", "", "", fmt.Errorf("多轮切回个人态后保存收款码仍失败(可能不在收款码页或按钮位置偏移)") + } + } + } + + // 5. 拉取相册最新微信收款码图片(按文件名倒序取第一张) + files, lerr := adb.ListFiles(ctx, serial, dir) + if lerr != nil { + return "", "", "", fmt.Errorf("读取相册失败: %w", lerr) + } + latest := "" + for _, f := range files { + if strings.HasPrefix(f, WechatQrPrefix) && strings.HasSuffix(f, ".png") { + latest = f + break + } + } + if latest == "" { + return "", "", "", fmt.Errorf("相册中未找到微信收款码图片(保存收款码未落盘)") + } + local, perr := adb.PullFile(ctx, serial, dir+"/"+latest) + if perr != nil { + return "", "", "", fmt.Errorf("拉取收款码图片失败: %w", perr) + } + imgData, rerr := os.ReadFile(local) + if rerr != nil { + os.Remove(local) + return "", "", "", fmt.Errorf("读取图片失败: %w", rerr) + } + content, derr := DecodeQRContent(imgData) + if derr != nil { + os.Remove(local) + return "", "", "", fmt.Errorf("收款码图片未识别到二维码: %w", derr) + } + return local, latest, content, nil +} + +// RequireWechatCollectPage 确认微信前台已到达收款码页(WalletCollectQrCodeUI)。 +// 带重试(页面切换动画 + 加载),最多尝试 5 次×1.5s。用于导航步骤间的页面就绪校验, +// 防止坐标偏移后盲目继续点击。 +func requireWechatCollectPage(ctx context.Context, adb *adbmgr.Manager, serial string) error { + for i := 0; i < 5; i++ { + fg, err := adb.ForegroundApp(ctx, serial) + // 双开 user 前台字符串形如 "u999 com.tencent.mm/...activity",用 Contains 匹配 + if err == nil && strings.Contains(fg, "com.tencent.mm/") { + if strings.Contains(fg, "WalletCollectQrCodeUI") { + return nil + } + // 前台仍是微信但不是收款页:再等一次(可能动画中) + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(1500 * time.Millisecond): + } + continue + } + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(1500 * time.Millisecond): + } + } + return fmt.Errorf("未进入微信收款码页(当前前台 %q),坐标导航可能已偏移", func() string { + fg, _ := adb.ForegroundApp(ctx, serial) + return fg + }()) +} + +// CleanupWechatGallery 清理指定 user 空间微信相册里的旧收款码图(保留最新 QrcodeKeepOnDevice 张)。 +func CleanupWechatGallery(ctx context.Context, adb *adbmgr.Manager, serial string, userId int) (deleted int) { + dir := WechatQrDir + if userId > 0 { + dir = fmt.Sprintf("/storage/emulated/%d/Pictures/WeiXin", userId) + } + files, err := adb.ListFiles(ctx, serial, dir) + if err != nil { + return 0 // 目录不存在/不可读 → 无码可清 + } + var qrFiles []string + for _, f := range files { + if strings.HasPrefix(f, WechatQrPrefix) && strings.HasSuffix(f, ".png") { + qrFiles = append(qrFiles, f) + } + } + if len(qrFiles) <= QrcodeKeepOnDevice { + return 0 + } + for _, f := range qrFiles[QrcodeKeepOnDevice:] { // ListFiles 按 -t 倒序,越靠后越旧 + if err := adb.Rm(ctx, serial, dir+"/"+f); err != nil { + continue + } + deleted++ + } + return deleted +} + +// listWechatQrFiles 列出微信收款码图片文件名(mm_facetoface_collect_qrcode_*.png,按 -t 倒序)。 +func listWechatQrFiles(ctx context.Context, adb *adbmgr.Manager, serial, dir string) []string { + files, err := adb.ListFiles(ctx, serial, dir) + if err != nil { + return nil + } + var out []string + for _, f := range files { + if strings.HasPrefix(f, WechatQrPrefix) && strings.HasSuffix(f, ".png") { + out = append(out, f) + } + } + return out +} + +// 微信账本页冷启动导航坐标(PLAN-ADB-LEDGER.md §2.2 实测): +// - wechatLedgerEntry 收款码页(WalletCollectQrCodeUI)内「收款小账本」整行按钮 +// (手机 x 居中 (540,1745),整行可点区域以 x=540 居中点击最可靠,与「服务」步同理)。 +// 点击后进入收款小账本 +// - wechatLedgerRecords 小账本内「收款记录」按钮(统计卡片内蓝色小按钮,手机 y≈748), +// 点击后进入收款记录页(AppBrandUI00 小程序容器) +var wechatLedgerEntry = wxNavPt{540, 1745} +var wechatLedgerRecords = wxNavPt{540, 748} + +// CaptureWechatLedger 冷启动导航到微信稳定的收款记录页(AppBrandUI00 小程序容器)。 +// 链路:复用 CaptureWechatQrcode 前半段(首页→我→服务→收付款→二维码收款页) +// → 收款码页点「收款小账本」→ 小账本点「收款记录」→ 进入收款记录页。 +// 供预热阶段调用;下单实时回调只走 task lock 热启动(EnsureForeground)。 +// userId>0 时以该 user 启动(双开/分身空间)。 +func CaptureWechatLedger(ctx context.Context, adb *adbmgr.Manager, serial string, userId int) error { + if err := wakeHome(ctx, adb, serial); err != nil { + return fmt.Errorf("唤醒屏幕失败: %w", err) + } + if err := adb.RestartAppUser(ctx, serial, "com.tencent.mm", ".ui.LauncherUI", userId); err != nil { + return fmt.Errorf("启动微信失败: %w", err) + } + time.Sleep(5 * time.Second) // 冷启动 + 首页加载 + + // 前台守卫(同 CaptureWechatQrcode):微信未到前台绝不开始盲点坐标 + if err := awaitForeground(ctx, adb, serial, "com.tencent.mm", foregroundWait); err != nil { + return fmt.Errorf("启动微信失败: %w", err) + } + + // 屏幕固定保险:导航期间其他 app 无法抢占前台(同 CaptureAlipayLedger) + if taskID, perr := adb.PinCurrentTask(ctx, serial); perr == nil { + defer func() { _ = adb.UnpinTask(ctx, serial) }() + _ = taskID + } else { + logger.Warn("capture", "屏幕固定失败(导航将无保护执行,可能被其他 app 打断)", + "err", perr, "serial", serial) + } + + // 微信首页 → 我 → 服务 → 收付款(复用收款码导航坐标序列) + for _, p := range wechatQrTapSeq { + var err error + if p == wechatQrTapService { + // 「服务」步偶发 PINNED 固定态下 tap 失效(2026-08-28 实测): + // 先释放固定 → 点击 → 重新固定(与支付宝总览补点同 bug 处理) + err = tapPinned(ctx, adb, serial, p.X, p.Y) + } else { + err = adb.TapRef(ctx, serial, p.X, p.Y) + } + if err != nil { + return fmt.Errorf("微信导航点击失败: %w", err) + } + time.Sleep(wechatQrTapDelay) + } + // 收付款 → 二维码收款(进入收款码页,PINNED 固定态下 tap 偶发失效——先解锁再点)。 + if err := tapPinned(ctx, adb, serial, wechatQrEntry.X, wechatQrEntry.Y); err != nil { + return fmt.Errorf("点击二维码收款失败: %w", err) + } + if err := requireWechatCollectPage(ctx, adb, serial); err != nil { + return err + } + time.Sleep(1500 * time.Millisecond) + + // 收款码页(WalletCollectQrCodeUI)→ 收款小账本 → 收款记录 → 收款记录页(AppBrandUI00)。 + // 关键(2026-08-29 真机验证):PINNED 固定态会**拦截小程序新 task 启动**——「收款小账本」 + // 是独立小程序容器(新 task t22284),固定时点击无响应/被拦。必须在此**解除固定**, + // 后续点击全部在解锁态执行(不再重新固定)。 + if err := adb.UnpinTask(ctx, serial); err != nil { + logger.Warn("capture", "微信导航:进入收款码页后释放固定失败(继续尝试点击)", + "err", err, "serial", serial) + } + time.Sleep(500 * time.Millisecond) + + // 收款码页默认可能是「经营收款码」态(约 50%,页面布局不同「收款小账本」入口点击无效) + // 或「个人收款码」态(入口可点)。不可依赖单次点击——每次点「收款小账本」后用前台 + // 是否进入小账本容器(AppBrandUI00)校验:进入=个人态成功;未进=经营态点不动, + // 点「个人收款码」tab 切回个人态再续试。每轮失败都重新点 tab 兜底(对齐 + // CaptureWechatQrcode 的多轮切态机制),最多 3 轮内必达个人态(与实测一致)。 + const ledgerRounds = 3 + var lastErr error + for round := 0; round < ledgerRounds; round++ { + if round > 0 { + // 非首次:上一轮未进入账本容器 → 疑经营态,点「个人收款码」tab 切回个人态 + if err := tapNav(ctx, adb, serial, wechatQrPersonalTab); err != nil { + return fmt.Errorf("点击个人收款码 tab 失败: %w", err) + } + time.Sleep(wechatQrTapDelay) // 切态动画 + 页面重载 + } + // 点「收款小账本」→ 校验是否进入容器(个人态成功标准) + if err := tapNav(ctx, adb, serial, wechatLedgerEntry); err != nil { + return fmt.Errorf("点击收款小账本失败: %w", err) + } + time.Sleep(3000 * time.Millisecond) // 小账本加载(小程序冷启动较慢) + if err := requireWechatLedgerPage(ctx, adb, serial); err != nil { + lastErr = err + continue // 未进入容器 → 下轮切个人态再试 + } + // 已进入小账本容器 → 点「收款记录」进账本页 + if err := tapNav(ctx, adb, serial, wechatLedgerRecords); err != nil { + return fmt.Errorf("点击收款记录失败: %w", err) + } + time.Sleep(3000 * time.Millisecond) + if err := requireWechatLedgerPage(ctx, adb, serial); err != nil { + lastErr = err + continue + } + return nil + } + return fmt.Errorf("进入微信收款记录页失败(含切个人收款码态重试): %w", lastErr) +} + +// tapPinned PINNED 固定态下的安全点击:先解锁(UnpinTask)→ 点击 → 重新固定。 +// PINNED 固定态下 input tap 偶发失效(2026-08-28 实测微信「服务」步/支付宝总览界面), +// 解锁后同坐标点击立即生效。重新固定失败不致命。微信/支付宝导航通用。 +func tapPinned(ctx context.Context, adb *adbmgr.Manager, serial string, x, y int) error { + if err := adb.UnpinTask(ctx, serial); err != nil { + logger.Warn("capture", "释放屏幕固定失败(继续尝试点击)", + "err", err, "serial", serial) + } + time.Sleep(300 * time.Millisecond) + if err := adb.TapRef(ctx, serial, x, y); err != nil { + return err + } + time.Sleep(300 * time.Millisecond) + if _, err := adb.PinCurrentTask(ctx, serial); err != nil { + logger.Warn("capture", "重新固定屏幕失败(导航将无保护执行)", + "err", err, "serial", serial) + } + return nil +} + +// requireWechatLedgerPage 确认微信前台已到达收款记录页(AppBrandUI00 小程序容器)。 +// 带重试(小程序页面加载),最多尝试 6 次×1.5s。 +func requireWechatLedgerPage(ctx context.Context, adb *adbmgr.Manager, serial string) error { + for i := 0; i < 6; i++ { + fg, err := adb.ForegroundApp(ctx, serial) + if err == nil && strings.Contains(fg, "AppBrandUI00") { + return nil + } + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(1500 * time.Millisecond): + } + } + fg, _ := adb.ForegroundApp(ctx, serial) + return fmt.Errorf("未进入微信收款记录页(当前前台 %q)", fg) +} diff --git a/internal/admin/capture/wechat_ledger.go b/internal/admin/capture/wechat_ledger.go new file mode 100644 index 0000000..5c300d8 --- /dev/null +++ b/internal/admin/capture/wechat_ledger.go @@ -0,0 +1,164 @@ +// 微信收款记录页(AppBrandUI00 小程序容器)服务端读取与解析。 +// 背景:微信对无障碍严格屏蔽,uiautomator dump 默认黑盒(仅 1 个空 FrameLayout 节点)。 +// 解锁:系统无障碍开启(settings put secure accessibility_enabled 1)后 XWeb 小程序 +// 才构建/暴露 accessibility 节点树(docs/DESIGN-DEVICE-ACTOR.md §9.1 实测确认)。 +// 链路:adb uiautomator dump → XML → 解析账本行(付款方 | ¥金额 | HH:MM:SS)。 +// 与支付宝差异:微信行是扁平文本节点(无嵌套容器),金额带 ¥ 前缀,日期无"今日/昨日"。 +package capture + +import ( + "fmt" + "regexp" + "strconv" + "strings" +) + +// 微信账本行正则(实测 2026-08-29 真机): +// - 金额:¥39.00 / ¥100(带 ¥ 前缀) +// - 时间:17:24:47(HH:MM:SS,与支付宝同格式) +// - 日期分组:7月1日 / 9月14日(M月d日,无"今日/昨日"前缀) +var ( + wechatAmountRe = regexp.MustCompile(`^¥\d+(\.\d{2})?$`) // ¥39.00 / ¥100 + wechatDateRe = regexp.MustCompile(`^\d{1,2}月\d{1,2}日$`) + // 付款方备注行(携带"备注码",是入账对账的反错配主信号) + wechatNoteRe = regexp.MustCompile(`^付款方备注:(.+)$`) + // 非付款方的干扰文本(页脚/摘要/入口等,付款方是短脱敏名不应含这些词) + wechatNonPayerRe = regexp.MustCompile(`点单收款|自定义查询|共收款|累计|仅展示|选择时间|查看更多`) +) + +// ReadWechatLedger 通过 adb uiautomator dump 读取当前前台微信收款记录页,解析可见收款行。 +// 前置条件:系统无障碍已开启(accessibility_enabled=1)+ 前台在账本页(AppBrandUI00)。 +// 行结构(扁平节点,同 y 带三要素): +// +// [210,y] 付款方(脱敏,如 朱*6 / *学) +// [~900,y] 金额(¥39.00) +// [210,y+81] 时间(17:24:47) +// +// 日期分组独立成行(M月d日),作为后续行的 Date 继承; +// 备注行(付款方备注:xxx)捕获到当前/下一行的 Note 字段,是入账对账的反错配主信号。 +func ReadWechatLedger(xmlText string) ([]AlipayLedgerRow, error) { + root, err := parseUINodeTree(xmlText) + if err != nil { + return nil, fmt.Errorf("解析微信账本页 XML 失败: %w", err) + } + // 扁平收集所有带文本的节点(微信账本行是扁平结构,无嵌套容器) + var nodes []textNode + collectFlatTexts(root, &nodes) + + var rows []AlipayLedgerRow + dateGroup := "" + // dump 输出顺序即屏幕从上到下(xml 深度优先),顺序扫描: + // 行 = 金额节点(触发新行)→ 补时间 → 补付款方;离屏行(bounds [0,0][0,0])过滤。 + // 付款方在金额之前出现([210,y] vs [900,y]),但此时 pending 未创建, + // 所以用"最近未消费的候选付款方"机制:非金额/时间/日期/干扰词的短文本先暂存, + // 遇到金额时若暂存候选存在且 y 相近则归属该行。 + var payerCandidate string // 金额节点前最近的付款方候选 + var pending *AlipayLedgerRow + var noteCandidate string // 备注文本候选(出现在金额之前则暂存,归属下一个 pending 行) + for _, n := range nodes { + switch { + case wechatDateRe.MatchString(n.text): + // 日期分组行:更新当前日期上下文,结束未完成行,清候选 + flushWechatRow(&rows, &pending, payerCandidate) + dateGroup = n.text + payerCandidate = "" + case wechatAmountRe.MatchString(n.text): + // 金额行:新行起点,归属最近的付款方候选(若有) + flushWechatRow(&rows, &pending, payerCandidate) + pending = &AlipayLedgerRow{ + Date: dateGroup, Payer: payerCandidate, Amount: n.text, + AmountMinor: wechatAmountMinor(n.text), onScreen: !zeroBoundsRe.MatchString(n.bounds), + } + if noteCandidate != "" { + pending.Note = noteCandidate + noteCandidate = "" + } + payerCandidate = "" + case ledgerTimeRe.MatchString(n.text) && pending != nil && pending.Time == "": + // 时间行:归属当前待完成行 + pending.Time = n.text + case wechatNoteRe.MatchString(n.text): + // 备注行:归属当前 pending(若有),否则作为候选归属下一个 pending 行 + m := wechatNoteRe.FindStringSubmatch(n.text) + note := m[1] + if pending != nil { + pending.Note = note + noteCandidate = "" + } else { + noteCandidate = note + } + case wechatPayerText(n.text): + // 付款方候选(非金额/时间/日期/干扰词的短文本) + payerCandidate = n.text + } + } + flushWechatRow(&rows, &pending, payerCandidate) + return rows, nil +} + +// wechatPayerText 判断文本是否为付款方候选(短脱敏名,非金额/时间/日期/干扰词) +func wechatPayerText(text string) bool { + if text == "" || len([]rune(text)) > 8 { + return false + } + if wechatDateRe.MatchString(text) || ledgerTimeRe.MatchString(text) || wechatAmountRe.MatchString(text) { + return false + } + if wechatNonPayerRe.MatchString(text) { + return false + } + return true +} + +// textNode 带 bounds 的扁平文本节点 +type textNode struct { + text string + bounds string +} + +// collectFlatTexts 扁平收集所有带文本的节点(按 dump 顺序即屏幕从上到下)。 +func collectFlatTexts(n *uiNode, out *[]textNode) { + if n == nil { + return + } + if n.Text != "" { + *out = append(*out, textNode{n.Text, n.Bounds}) + } + for _, k := range n.Kids { + collectFlatTexts(k, out) + } +} + +// flushWechatRow 结束当前待完成行并加入结果(缺时间/金额/离屏的行丢弃) +func flushWechatRow(rows *[]AlipayLedgerRow, pending **AlipayLedgerRow, payerCandidate string) { + p := *pending + if p == nil { + return + } + if p.Payer == "" { + p.Payer = payerCandidate + } + if p.Time != "" && p.Amount != "" && p.onScreen { + *rows = append(*rows, *p) + } + *pending = nil +} + +// wechatAmountMinor 微信金额文本转分("¥39.00" → 3900;"¥100" → 10000) +func wechatAmountMinor(amount string) int64 { + s := strings.TrimPrefix(amount, "¥") + if i := strings.IndexByte(s, '.'); i >= 0 { + major, _ := strconv.ParseInt(s[:i], 10, 64) + frac := s[i+1:] + minor := int64(0) + switch len(frac) { + case 1: + minor, _ = strconv.ParseInt(frac+"0", 10, 64) + case 2: + minor, _ = strconv.ParseInt(frac, 10, 64) + } + return major*100 + minor + } + major, _ := strconv.ParseInt(s, 10, 64) + return major * 100 +} diff --git a/internal/admin/capture/wechat_ledger_test.go b/internal/admin/capture/wechat_ledger_test.go new file mode 100644 index 0000000..fa954ce --- /dev/null +++ b/internal/admin/capture/wechat_ledger_test.go @@ -0,0 +1,240 @@ +package capture + +import ( + "strings" + "testing" +) + +// 微信账本页真实 dump 片段(2026-08-29 真机红米K60,无障碍开启后 XWeb 暴露节点树)。 +// 覆盖:多日期分组、金额 ¥ 前缀、付款方脱敏、备注行、离屏虚拟化行。 +const wechatLedgerSampleXML = ` + + + + + + + + + + + + + + + + + + + + + + + +` + +// 含离屏虚拟化行(bounds [0,0][0,0])的片段:应被过滤 +const wechatLedgerVirtualXML = ` + + + + + + + +` + +func TestReadWechatLedger(t *testing.T) { + rows, err := ReadWechatLedger(wechatLedgerSampleXML) + if err != nil { + t.Fatalf("解析失败: %v", err) + } + if len(rows) != 3 { + t.Fatalf("应解析出 3 行,实际 %d 行: %+v", len(rows), rows) + } + + // 第 1 行:7月1日 / 朱*6 / ¥39.00 / 17:24:47 + r0 := rows[0] + if r0.Date != "7月1日" || r0.Payer != "朱*6" || r0.Amount != "¥39.00" || r0.Time != "17:24:47" { + t.Errorf("第1行解析错误: %+v", r0) + } + if r0.AmountMinor != 3900 { + t.Errorf("第1行金额应为 3900 分,实际 %d", r0.AmountMinor) + } + + // 第 2 行:6月3日 / *学 / ¥100.00 / 15:36:32(金额无小数补零) + r1 := rows[1] + if r1.Date != "6月3日" || r1.Payer != "*学" || r1.Amount != "¥100.00" || r1.Time != "15:36:32" { + t.Errorf("第2行解析错误: %+v", r1) + } + if r1.AmountMinor != 10000 { + t.Errorf("第2行金额应为 10000 分,实际 %d", r1.AmountMinor) + } + + // 第 3 行:9月14日 / *游 / ¥200.00 / 16:13:03 + r2 := rows[2] + if r2.Date != "9月14日" || r2.Payer != "*游" || r2.Amount != "¥200.00" || r2.Time != "16:13:03" { + t.Errorf("第3行解析错误: %+v", r2) + } + if r2.AmountMinor != 20000 { + t.Errorf("第3行金额应为 20000 分,实际 %d", r2.AmountMinor) + } + + // 备注行"付款方备注:电信这回"应捕获到 r2.Note,且不污染 Payer + if r2.Note != "电信这回" { + t.Errorf("备注应进入 r2.Note,实际 %q", r2.Note) + } + if strings.Contains(r2.Payer, "备注") { + t.Errorf("备注文本不应进入付款方: %q", r2.Payer) + } + + // 指纹(时间|金额)格式与支付宝一致 + if fp := r0.Fingerprint(); fp != "17:24:47|¥39.00" { + t.Errorf("指纹格式错误: %q", fp) + } +} + +func TestReadWechatLedgerFiltersVirtualRows(t *testing.T) { + rows, err := ReadWechatLedger(wechatLedgerVirtualXML) + if err != nil { + t.Fatalf("解析失败: %v", err) + } + if len(rows) != 0 { + t.Fatalf("离屏虚拟化行应被过滤(金额/时间 bounds [0,0][0,0]),实际 %d 行: %+v", len(rows), rows) + } +} + +func TestReadWechatLedgerEmpty(t *testing.T) { + // 无障碍未开启 → 微信 dump 黑盒(仅空 FrameLayout)→ 0 行 + rows, err := ReadWechatLedger(``) + if err != nil { + t.Fatalf("解析失败: %v", err) + } + if len(rows) != 0 { + t.Fatalf("空树应返回 0 行,实际 %d", len(rows)) + } +} + +// TestReadWechatLedgerNoteBeforeAmount 备注行出现在金额之前:应作为 noteCandidate 暂存, +// 在下一个 pending 行(金额触发)创建时挂到该行的 Note 上。 +func TestReadWechatLedgerNoteBeforeAmount(t *testing.T) { + const xml = ` + + + + + + + + +` + rows, err := ReadWechatLedger(xml) + if err != nil { + t.Fatalf("解析失败: %v", err) + } + if len(rows) != 1 { + t.Fatalf("期望 1 行,实际 %d 行: %+v", len(rows), rows) + } + r := rows[0] + if r.Payer != "*游" || r.Amount != "¥200.00" || r.Time != "16:13:03" { + t.Errorf("行基础字段错误: %+v", r) + } + if r.Note != "先备后付" { + t.Errorf("备注应挂到下一行 Note,实际 %q", r.Note) + } +} + +// TestReadWechatLedgerTwoRemarksConsecutive 两笔相邻收款各自带备注,备注必须各自归位, +// 不能跨行漂移、不能合并、不能成为伪行。 +func TestReadWechatLedgerTwoRemarksConsecutive(t *testing.T) { + const xml = ` + + + + + + + + + + + + +` + rows, err := ReadWechatLedger(xml) + if err != nil { + t.Fatalf("解析失败: %v", err) + } + if len(rows) != 2 { + t.Fatalf("期望 2 行(备注不形成伪行),实际 %d 行: %+v", len(rows), rows) + } + if rows[0].Payer != "甲" || rows[0].Amount != "¥10.00" || rows[0].Time != "10:00:00" { + t.Errorf("第 1 行基础字段错误: %+v", rows[0]) + } + if rows[0].Note != "甲备注码" { + t.Errorf("第 1 行 Note 应为 %q,实际 %q", "甲备注码", rows[0].Note) + } + if rows[1].Payer != "乙" || rows[1].Amount != "¥20.00" || rows[1].Time != "10:05:00" { + t.Errorf("第 2 行基础字段错误: %+v", rows[1]) + } + if rows[1].Note != "乙备注码" { + t.Errorf("第 2 行 Note 应为 %q,实际 %q", "乙备注码", rows[1].Note) + } +} + +// TestReadWechatLedgerNoteDoesNotChangeRowCount 备注行不构成独立行:含 2 笔收款 + 3 条备注 +// 的 dump 必须仍解析为 2 行。 +func TestReadWechatLedgerNoteDoesNotChangeRowCount(t *testing.T) { + const xml = ` + + + + + + + + + + + + + +` + rows, err := ReadWechatLedger(xml) + if err != nil { + t.Fatalf("解析失败: %v", err) + } + if len(rows) != 2 { + t.Fatalf("期望 2 行(3 条备注均不构成伪行),实际 %d 行: %+v", len(rows), rows) + } + // 第 1 行:3 条备注全部挂到 pending 行,最后一条胜出 + if rows[0].Note != "备注C" { + t.Errorf("第 1 行 Note 应为最后一条 %q,实际 %q", "备注C", rows[0].Note) + } + if rows[1].Note != "" { + t.Errorf("第 2 行 Note 应为空(无备注行),实际 %q", rows[1].Note) + } +} + +// TestDegenerateDump 退化空 dump(无障碍未填充→只有根 FrameLayout)vs 真实渲染空页: +// 前者是'读取失败'不得建基线,后者(有标题/tab静态文本)可正常建空基线。 +func TestDegenerateDump(t *testing.T) { + degen := `` + if !DegenerateDump(degen) { + t.Errorf("退化空树应被判定为退化,实际返回 false") + } + // 真实渲染空页:有标题/页脚静态文本(即使 0 交易行)→ 非退化 + rendered := ` + + + + +` + if DegenerateDump(rendered) { + t.Errorf("真实渲染空页(有标题/页脚文本)不应被判为退化,实际返回 true") + } + // 正常有交易行的 dump 显然非退化 + if DegenerateDump(wechatLedgerSampleXML) { + t.Errorf("正常含交易行的 dump 不应被判为退化") + } +} diff --git a/internal/admin/deviceconsole.go b/internal/admin/deviceconsole.go new file mode 100644 index 0000000..68a6b87 --- /dev/null +++ b/internal/admin/deviceconsole.go @@ -0,0 +1,415 @@ +package admin + +import ( + "context" + "fmt" + "net/http" + "strconv" + "strings" + "sync" + "time" + + "github.com/gin-gonic/gin" + + "paygateway/internal/logger" + "paygateway/internal/personal" + "paygateway/internal/store" +) + +// 设备控制台:web 前端可视化操作 adb 触达的安卓设备(USB 真机 / adb over WiFi; +// 微信 / 支付宝) +// +// 能力(按需渲染,非完整云手机): +// - 截图:查看设备当前屏幕(渲染微信 / 支付宝界面) +// - 操作:点击 / 滑动 / 输入文本 / 按键(完成 app 登录等) +// - 启动 app:拉起微信 / 支付宝 +// - 手动预热:导航到账本页并锁定任务(DeviceActor 命令,账本监听专用) +// +// 前置条件:设备已配置 adb_host / adb_wifi_host(USB 真机留空 adb_port 即视作 USB 直连)。 +// 本控制台以 adb 为协议基线,USB 直连(纯 serial)与 adb over WiFi(TCP adb)均适用。 + +// deviceSerial 从设备记录构造 adb serial(USB 优先,WiFi 备用) +// - adb_host 无端口 → USB 直连 serial(如 "4f2bdc30",真机直连) +// - adb_host 有端口 → TCP 地址(如 "192.168.1.119:5555",adb over WiFi) +// - adb_wifi_host 配置且 USB 通道不可用 → WiFi 备用 +func (h *PersonalHandler) deviceSerial(d *personal.DeviceEncrypted) (string, error) { + if d.AdbHost != "" { + if d.AdbPort > 0 { + return fmt.Sprintf("%s:%d", d.AdbHost, d.AdbPort), nil + } + return d.AdbHost, nil + } + if d.AdbWifiHost != "" && d.AdbWifiPort > 0 { + return fmt.Sprintf("%s:%d", d.AdbWifiHost, d.AdbWifiPort), nil + } + return "", fmt.Errorf("设备未配置 adb 连接(adb_host / adb_wifi_host 为空)") +} + +// getDeviceWithAdb 按 id 取设备并校验 adb 可用(USB 优先,失败自动切 WiFi 备用) +func (h *PersonalHandler) getDeviceWithAdb(c *gin.Context) (*personal.DeviceEncrypted, string, bool) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + if id <= 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"}) + return nil, "", false + } + if h.adbMgr == nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "设备控制台未启用(adb 管理器未配置)"}) + return nil, "", false + } + dev, err := h.pdb.GetDeviceByID(c.Request.Context(), id) + if err != nil || dev == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "设备不存在"}) + return nil, "", false + } + serial, all, ok := h.pickOnlineSerial(c.Request.Context(), dev) + if !ok { + c.JSON(http.StatusBadGateway, gin.H{"error": "设备离线:所有 adb 通道不可用(" + strings.Join(all, " / ") + ")"}) + return nil, "", false + } + return dev, serial, true +} + +// pickOnlineSerial 取设备所有候选 serial(USB 主通道优先、WiFi 备用次之)并返回首个 online 的。 +func (h *PersonalHandler) pickOnlineSerial(ctx context.Context, dev *personal.DeviceEncrypted) (serial string, allSerials []string, ok bool) { + all := make([]string, 0, 2) + if dev.AdbHost != "" { + if dev.AdbPort > 0 { + all = append(all, fmt.Sprintf("%s:%d", dev.AdbHost, dev.AdbPort)) + } else { + all = append(all, dev.AdbHost) + } + } + if dev.AdbWifiHost != "" && dev.AdbWifiPort > 0 { + all = append(all, fmt.Sprintf("%s:%d", dev.AdbWifiHost, dev.AdbWifiPort)) + } + if len(all) == 0 { + return "", all, false + } + for _, s := range all { + if h.adbMgr.Online(ctx, s) { + return s, all, true + } + } + return "", all, false +} + +// handleDeviceScreenshot 设备截图(按需渲染:前端点开设备才拉取) +// GET /api/admin/personal/devices/:id/screenshot → PNG +func (h *PersonalHandler) handleDeviceScreenshot(c *gin.Context) { + dev, serial, ok := h.getDeviceWithAdb(c) + if !ok { + return + } + ctx, cancel := context.WithTimeout(c.Request.Context(), 20*time.Second) + defer cancel() + + img, err := h.adbMgr.Screenshot(ctx, serial) + if err != nil { + logger.Warn("admin", "设备截图失败", "device", dev.DeviceID, "serial", serial, "err", err) + c.JSON(http.StatusBadGateway, gin.H{"error": "设备截图失败: " + err.Error()}) + return + } + c.Header("Content-Type", "image/png") + c.Header("Cache-Control", "no-store") + c.Data(http.StatusOK, "image/png", img) +} + +// handleDeviceAction 设备操作(点击/滑动/输入/按键/启动 app/刷新前台) +// POST /api/admin/personal/devices/:id/action {"action":"tap|swipe|input|key|start|front", ...} +func (h *PersonalHandler) handleDeviceAction(c *gin.Context) { + dev, serial, ok := h.getDeviceWithAdb(c) + if !ok { + return + } + var req struct { + Action string `json:"action"` + X int `json:"x"` + Y int `json:"y"` + X2 int `json:"x2"` + Y2 int `json:"y2"` + Duration int `json:"duration"` // swipe 毫秒 + Text string `json:"text"` // input 文本 + Keycode int `json:"keycode"` // key 按键 + Package string `json:"package"` // start 包名 + Activity string `json:"activity"` // start activity(可选) + } + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误: " + err.Error()}) + return + } + ctx, cancel := context.WithTimeout(c.Request.Context(), 15*time.Second) + defer cancel() + + var err error + switch req.Action { + case "tap": + err = h.adbMgr.Tap(ctx, serial, req.X, req.Y) + case "swipe": + err = h.adbMgr.Swipe(ctx, serial, req.X, req.Y, req.X2, req.Y2, req.Duration) + case "input": + if req.Text == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "input 需要 text"}) + return + } + err = h.adbMgr.InputText(ctx, serial, req.Text) + case "key": + if req.Keycode == 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "key 需要 keycode(4=返回 3=主页 66=回车 28=删除)"}) + return + } + err = h.adbMgr.KeyEvent(ctx, serial, req.Keycode) + case "start": + if req.Package == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "start 需要 package"}) + return + } + err = h.adbMgr.StartApp(ctx, serial, req.Package, req.Activity) + case "front": + var pkg string + pkg, err = h.adbMgr.ForegroundApp(ctx, serial) + if err == nil { + c.JSON(http.StatusOK, gin.H{"data": gin.H{"ok": true, "foreground": pkg}}) + return + } + case "warmup": + // 手动预热:向设备 DeviceActor 投递 ManualWarmupCmd(冷启动导航到账本页 + 锁定任务)。 + // provider 参数指定通道:alipay(默认,设备全部支付宝账户主空间+双开) + // 或 wechat(设备全部微信账户);provider 为空 = 全部通道全部账户。 + // DeviceActor 模型:手动预热是队列中的一个命令,与自动预热/轮询天然互斥。 + // 冷启动导航等待波动(网络/UI 渲染非线性),微信小账本为小程序加载更慢—— + // 每步 3s + 重试,多账户累计可能超 120s,给足 240s 余量。 + ctx, cancel2 := context.WithTimeout(c.Request.Context(), 240*time.Second) + defer cancel2() + providers := map[string]bool{} + switch req.Package { + case "wechat": + providers["wechat"] = true + case "alipay": + providers["alipay"] = true + default: + providers["alipay"] = true + providers["wechat"] = true + } + if h.ledgerWarmup == nil { + c.JSON(http.StatusBadGateway, gin.H{"error": "账本监听未启用(服务端未配置 adb)"}) + return + } + if werr := h.ledgerWarmup(ctx, dev.DeviceID, providers); werr != nil { + logger.Warn("admin", "手动预热失败", "device", dev.DeviceID, "err", werr) + c.JSON(http.StatusBadGateway, gin.H{"error": "手动预热失败: " + werr.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"data": gin.H{ + "ok": true, "note": "账本页已就绪,任务卡片已锁定", + }}) + return + default: + c.JSON(http.StatusBadRequest, gin.H{"error": "未知 action: " + req.Action}) + return + } + if err != nil { + logger.Warn("admin", "设备操作失败", "device", dev.DeviceID, "action", req.Action, "err", err) + c.JSON(http.StatusBadGateway, gin.H{"error": "设备操作失败: " + err.Error()}) + return + } + logger.Info("admin", "设备操作成功", "device", dev.DeviceID, "serial", serial, "action", req.Action) + c.JSON(http.StatusOK, gin.H{"data": gin.H{"ok": true}}) +} + +// deviceAlipayUserIDs 解析设备全部支付宝账户的用户空间(0=主空间,999=双开): +// 遍历设备绑定账户中的 alipay 账户,取其收款码与该账户 QRContent 比对(与 +// cmd/gateway resolveLedgerDevices 的 ledgerUserSpaceOf 同逻辑),去重返回 userID 列表。 +func (h *PersonalHandler) deviceProviderUserIDs(c *gin.Context, dev *personal.DeviceEncrypted, channel string) ([]int, error) { + ctx := c.Request.Context() + ids := store.DeviceAccountIDs(ctx, h.pdb, dev.ID, dev.AccountID) + platform := "alipay" + if channel == "personal.wechat" { + platform = "wechat" + } + var out []int + seen := map[int]bool{} + for _, aid := range ids { + acct := h.pool.GetByID(aid) + if acct == nil || acct.Channel != channel { + continue + } + if acct.QRContent == "" { + return nil, fmt.Errorf("账户 %d 未配置收款码(无法判定用户空间)", aid) + } + for _, space := range []int{999, 0} { + qr, err := h.pdb.GetLatestQrcodeByPlatformUser(ctx, dev.DeviceID, platform, space) + if err == nil && qr != nil && qr.QRContent == acct.QRContent && !seen[space] { + seen[space] = true + out = append(out, space) + } + } + } + if len(out) == 0 { + return nil, fmt.Errorf("设备未绑定 %s 账户", platform) + } + return out, nil +} + +// handleRestartDevice 重启设备(设备离线自愈 / 用户手动触发) +// +// POST /api/admin/personal/devices/:id/restart +// +// 2026-08-30 重构:监听端 APP 弃用,恢复链路收敛为纯 adb 可达性自愈—— +// 轮询 adb.Online 直到任一 serial 可用(每 5s×9 = 45s),45s 内恢复即成功; +// 始终不可达返回 ok=false + warning。不会直接修改设备 status, +// 设备恢复后由 DeviceWatcher 按 adb 可达性自动判定上线。 +func (h *PersonalHandler) handleRestartDevice(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + if id <= 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"}) + return + } + if h.adbMgr == nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "adb 管理器未配置,重启功能不可用"}) + return + } + dev, err := h.pdb.GetDeviceByID(c.Request.Context(), id) + if err != nil || dev == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "设备不存在"}) + return + } + + result, rerr := h.Restart(c.Request.Context(), dev) + if rerr != nil && result == nil { + c.JSON(http.StatusBadRequest, gin.H{"error": rerr.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"data": result}) +} + +// AttemptRestart 按 id 重启设备(实现 reconciler.Recoverer 接口)。 +// 用于 DeviceWatcher 自动自愈;限频在内部已处理。 +func (h *PersonalHandler) AttemptRestart(ctx context.Context, id int64) error { + if h.adbMgr == nil { + return fmt.Errorf("adb 管理器未配置") + } + dev, err := h.pdb.GetDeviceByID(ctx, id) + if err != nil { + return fmt.Errorf("查询设备失败: %w", err) + } + if dev == nil { + return fmt.Errorf("设备 %d 不存在", id) + } + if _, rerr := h.Restart(ctx, dev); rerr != nil { + return rerr + } + return nil +} + +// Restart 公开重启入口,供 admin HTTP handler 和 DeviceWatcher 自动自愈复用。 +// 限频由本方法负责(in-memory map + TTL),调用方不必关心防抖。 +// 返回的 map 结构稳定可序列化为 JSON 给 HTTP 响应;err 仅在「无法开始重启流程」时返回, +// 例:adb 管理器未配置 / serial 候选空(HTTP 侧据此返回 400)。 +// +// 2026-08-30 重构:监听端 APP 弃用,重启链路收敛为纯 adb 可达性自愈—— +// 轮询 adb.Online 直到任一 serial 可用(每 5s×9 = 45s);45s 内恢复即成功, +// 始终不可达则返回 ok=false + warning(由调用方/DeviceWatcher 按告警策略处理)。 +func (h *PersonalHandler) Restart(ctx context.Context, dev *personal.DeviceEncrypted) (map[string]any, error) { + if h.adbMgr == nil { + return nil, fmt.Errorf("adb 管理器未配置") + } + serials := h.deviceSerials(dev) + if len(serials) == 0 { + return nil, fmt.Errorf("设备未配置 adb 连接(adb_host / adb_wifi_host 为空)") + } + + // 限频:5 分钟内同设备只允许一次重启尝试;recoverer 可由 watcher 频繁触发。 + if !h.restartLimiter.allow(dev.ID, 5*time.Minute) { + return map[string]any{ + "ok": true, + "serials": serials, + "throttled": true, + "hint": "距上次重启尝试不足 5 分钟,已忽略本次请求", + }, nil + } + + cctx, cancel := context.WithTimeout(ctx, 90*time.Second) + defer cancel() + + resp := map[string]any{ + "ok": true, + "serials": serials, + "stages": []map[string]any{}, + "warnings": []string{}, + } + stages := []map[string]any{} + warnings := []string{} + + // 阶段 1:轮询 adb.Online(每 5s/次,共 9 次) + serial := "" + var online bool + var polls int + for polls = 0; polls < 9; polls++ { + for _, s := range serials { + if h.adbMgr.Online(cctx, s) { + serial = s + online = true + break + } + } + if online { + break + } + time.Sleep(5 * time.Second) + } + if online { + resp["online_recovered"] = true + resp["waited_seconds"] = (polls + 1) * 5 + stages = append(stages, map[string]any{"stage": "adb探测", "online": true, "serial": serial}) + } else { + resp["ok"] = false + resp["waited_seconds"] = 45 + warnings = append(warnings, "45s 内 adb 未恢复在线(USB/WiFi 通道均不可达)") + } + + resp["stages"] = stages + if len(warnings) > 0 { + resp["warnings"] = warnings + } + logger.Info("admin", "设备重启指令执行完成", "device", dev.DeviceID, "ok", resp["ok"], + "online_recovered", resp["online_recovered"], "stages", len(stages)) + return resp, nil +} + +// restartLimiter 限频器:每设备 5min/次的重启尝试上限;进程重启即清空,可接受。 +type restartLimiter struct { + mu sync.Mutex + last map[int64]time.Time +} + +func newRestartLimiter() *restartLimiter { + return &restartLimiter{last: make(map[int64]time.Time)} +} + +// allow 返回 true 表示放行,false 表示被限频。 +func (r *restartLimiter) allow(id int64, window time.Duration) bool { + r.mu.Lock() + defer r.mu.Unlock() + if t, ok := r.last[id]; ok && time.Since(t) < window { + return false + } + r.last[id] = time.Now() + return true +} + +// deviceSerials 返回该设备的所有 adb serial 候选(USB 主通道 + WiFi 备用通道)。 +// 用于本机指令在通道间依次尝试;与 getDeviceWithAdb 一致。 +func (h *PersonalHandler) deviceSerials(d *personal.DeviceEncrypted) []string { + out := make([]string, 0, 2) + if d.AdbHost != "" { + if d.AdbPort > 0 { + out = append(out, fmt.Sprintf("%s:%d", d.AdbHost, d.AdbPort)) + } else { + out = append(out, d.AdbHost) + } + } + if d.AdbWifiHost != "" && d.AdbWifiPort > 0 { + out = append(out, fmt.Sprintf("%s:%d", d.AdbWifiHost, d.AdbWifiPort)) + } + return out +} diff --git a/internal/admin/loginlimiter.go b/internal/admin/loginlimiter.go new file mode 100644 index 0000000..6ac379f --- /dev/null +++ b/internal/admin/loginlimiter.go @@ -0,0 +1,185 @@ +package admin + +import ( + "net/http" + "sync" + "time" + + "github.com/gin-gonic/gin" + + "paygateway/internal/logger" +) + +// 登录失败退避限速参数。 +const ( + loginMaxFailures = 5 // 窗口内失败次数上限,达到即锁定 + loginWindow = 15 * time.Minute // 滑动窗口长度,同时作为锁定时长 +) + +// loginAttempt 单条失败记录的时间戳。 +type loginAttempt struct { + ts time.Time +} + +// loginBucket 单个键(IP 或 username)的失败记录桶。 +type loginBucket struct { + mu sync.Mutex + failures []time.Time // 滑动窗口内的失败时间戳 + lockedUntil time.Time // 锁定截止时间,零值表示未锁定 +} + +// loginLimiter 全局登录失败退避限速器。 +// 采用 (IP, username) 双维度独立计数:单 IP 撞多个 username 会被锁,单 username 被多 IP 撞也会被锁。 +// 设计目的:阻止暴力破解与用户名枚举。多实例部署需在外层(如 nginx)共享状态,本实现只覆盖单进程场景。 +type loginLimiter struct { + mu sync.Mutex + buckets map[string]*loginBucket +} + +func newLoginLimiter() *loginLimiter { + return &loginLimiter{buckets: make(map[string]*loginBucket)} +} + +// bucket 取或建桶。调用方负责加锁外层。 +func (l *loginLimiter) bucket(key string) *loginBucket { + b, ok := l.buckets[key] + if !ok { + b = &loginBucket{} + l.buckets[key] = b + } + return b +} + +// prune 删除超过 loginWindow 未活动的桶,避免长期泄漏内存。 +func (l *loginLimiter) prune(now time.Time) { + cutoff := now.Add(-loginWindow) + for key, b := range l.buckets { + b.mu.Lock() + stale := len(b.failures) == 0 && (b.lockedUntil.IsZero() || b.lockedUntil.Before(cutoff)) + b.mu.Unlock() + if stale { + delete(l.buckets, key) + } + } +} + +// checkLocked 返回 (是否锁定, 锁定剩余秒)。 +func (l *loginLimiter) checkLocked(key string, now time.Time) (bool, int) { + l.mu.Lock() + b := l.bucket(key) + l.mu.Unlock() + + b.mu.Lock() + defer b.mu.Unlock() + if b.lockedUntil.IsZero() || b.lockedUntil.Before(now) { + return false, 0 + } + return true, int(b.lockedUntil.Sub(now).Seconds()) +} + +// recordFailure 记录一次失败,超过阈值则置锁定。 +func (l *loginLimiter) recordFailure(key string, now time.Time) { + l.mu.Lock() + if now.IsZero() || len(l.buckets)%50 == 0 { + l.prune(now) + } + b := l.bucket(key) + l.mu.Unlock() + + b.mu.Lock() + defer b.mu.Unlock() + cutoff := now.Add(-loginWindow) + keep := b.failures[:0] + for _, t := range b.failures { + if t.After(cutoff) { + keep = append(keep, t) + } + } + b.failures = append(keep, now) + if len(b.failures) >= loginMaxFailures { + b.lockedUntil = now.Add(loginWindow) + logger.Warn("admin", "登录失败次数达上限,已锁定", "key", key, "failures", len(b.failures), "lock_sec", int(loginWindow.Seconds())) + } +} + +// recordSuccess 清零该键的失败记录与锁定状态。 +func (l *loginLimiter) recordSuccess(key string) { + l.mu.Lock() + b, ok := l.buckets[key] + l.mu.Unlock() + if !ok { + return + } + b.mu.Lock() + b.failures = nil + b.lockedUntil = time.Time{} + b.mu.Unlock() +} + +// enforceLoginLimit 在 handleLogin 入口调用。 +// 返回 true 表示已被限速、调用方应直接 return(已写 429 响应)。 +// ipKey / userKey 各自独立触发,取较严格的判定。 +func (s *Server) enforceLoginLimit(c *gin.Context, username string) bool { + now := time.Now() + ip := c.ClientIP() + + ipKey := "ip:" + ip + if locked, remain := s.loginLimiter.checkLocked(ipKey, now); locked { + logger.Warn("admin", "登录被限速(IP 锁定中)", "ip", ip, "remain_sec", remain) + c.Header("Retry-After", itoa(remain)) + c.JSON(http.StatusTooManyRequests, gin.H{"error": "登录尝试过于频繁,请稍后再试", "retry_after_sec": remain}) + return true + } + userKey := "user:" + username + if username != "" { + if locked, remain := s.loginLimiter.checkLocked(userKey, now); locked { + logger.Warn("admin", "登录被限速(用户名锁定中)", "username", username, "remain_sec", remain) + c.Header("Retry-After", itoa(remain)) + c.JSON(http.StatusTooManyRequests, gin.H{"error": "该账号尝试过于频繁,请稍后再试", "retry_after_sec": remain}) + return true + } + } + return false +} + +// recordLoginOutcome 在 handleLogin 内判定登录结果后调用, +// failure=true 记录失败,failure=false 清零计数。 +// ip 与 username 双维度同步,避免单维度绕过。 +func (s *Server) recordLoginOutcome(c *gin.Context, username string, failure bool) { + now := time.Now() + ip := c.ClientIP() + if failure { + s.loginLimiter.recordFailure("ip:"+ip, now) + if username != "" { + s.loginLimiter.recordFailure("user:"+username, now) + } + return + } + s.loginLimiter.recordSuccess("ip:" + ip) + if username != "" { + s.loginLimiter.recordSuccess("user:" + username) + } +} + +// itoa 简单十进制转字符串,避免引入 strconv 只为一处。 +func itoa(n int) string { + if n == 0 { + return "0" + } + neg := n < 0 + if neg { + n = -n + } + var buf [12]byte + i := len(buf) + for n > 0 { + i-- + buf[i] = byte('0' + n%10) + n /= 10 + } + if neg { + i-- + buf[i] = '-' + } + return string(buf[i:]) +} diff --git a/internal/admin/personal.go b/internal/admin/personal.go new file mode 100755 index 0000000..b3fe9f5 --- /dev/null +++ b/internal/admin/personal.go @@ -0,0 +1,721 @@ +package admin + +import ( + "context" + "fmt" + "net/http" + "strconv" + "strings" + "time" + + "github.com/gin-gonic/gin" + "paygateway/internal/account" + "paygateway/internal/adbmgr" + "paygateway/internal/admin/capture" + "paygateway/internal/logger" + "paygateway/internal/personal" + "paygateway/internal/store" +) + +// PersonalHandler 个人免签通道管理:监听设备 + 推送流水 + 无订单收款 +type PersonalHandler struct { + pdb store.PersonalStore + pool *account.AccountPool + // accountDB 账户存储(用于自动创建个人账户) + accountDB *store.AccountDB + // adb 设备操作管理器(web 可视化操作 USB 真机 / adb over WiFi 设备;nil = 未启用) + adbMgr *adbmgr.Manager + // restartLimiter 设备重启 5min/次的限频器(同时被 HTTP handler 和 watcher 自动自愈复用) + restartLimiter *restartLimiter + // renewLimiter 收款码自动补捕限频器(每设备每平台最小间隔,防重捕风暴) + renewLimiter *qrcodeLastRenew + // ledgerWarmup 设备手动预热(main.go 注入;nil = 未启用)。DeviceActor 模型中 + // 手动预热是队列中的一个命令,与自动预热/轮询天然互斥,无需窗口独占。 + ledgerWarmup func(ctx context.Context, deviceID string, providers map[string]bool) error +} + +// SetLedgerWarmup 注入设备手动预热函数(main 设置,warmup 走 DeviceActor 命令队列) +func (h *PersonalHandler) SetLedgerWarmup(fn func(ctx context.Context, deviceID string, providers map[string]bool) error) { + h.ledgerWarmup = fn +} + +// NewPersonalHandler 创建个人通道管理处理器 +// adbMgr: adb 操作管理器(可选,nil = 设备控制台不可用) +func NewPersonalHandler(pdb store.PersonalStore, pool *account.AccountPool, accountDB *store.AccountDB, adbMgr *adbmgr.Manager) *PersonalHandler { + return &PersonalHandler{pdb: pdb, pool: pool, accountDB: accountDB, adbMgr: adbMgr, restartLimiter: newRestartLimiter(), renewLimiter: &qrcodeLastRenew{lasts: make(map[string]time.Time)}} +} + +// RegisterRoutes 注册路由(需在 authMiddleware 组内) +func (h *PersonalHandler) RegisterRoutes(rg *gin.RouterGroup) { + // 监听设备(新设备统一走 USB 引导向导的 wizard-start,手填表单已移除) + rg.GET("/personal/devices", h.handleListDevices) + rg.DELETE("/personal/devices/:id", h.handleDeleteDevice) + rg.POST("/personal/devices/:id/freeze", h.handleFreezeDevice) + rg.POST("/personal/devices/:id/unfreeze", h.handleUnfreezeDevice) + rg.POST("/personal/devices/:id/restart", h.handleRestartDevice) + // 设备控制台(web 可视化操作 USB 真机 / adb over WiFi 设备) + rg.GET("/personal/devices/:id/screenshot", h.handleDeviceScreenshot) + rg.POST("/personal/devices/:id/action", h.handleDeviceAction) + rg.POST("/personal/devices/:id/adb", h.handleSetDeviceAdb) + // 设备 ↔ 账户 多对多绑定(应用双开多账户) + rg.GET("/personal/devices/:id/accounts", h.handleListDeviceAccounts) + rg.POST("/personal/devices/:id/accounts", h.handleAddDeviceAccount) + rg.DELETE("/personal/devices/:id/accounts/:accountId", h.handleRemoveDeviceAccount) + rg.POST("/personal/devices/batch/restart", h.handleBatchRestartDevices) + rg.POST("/personal/devices/batch/status", h.handleBatchSetDeviceStatus) + // 引导向导(USB 真机集成自动化) + rg.GET("/personal/usb-detect", h.handleUSBCheck) + rg.POST("/personal/devices/wizard-start", h.handleWizardStart) + rg.POST("/personal/devices/:id/wizard-wifi", h.handleWizardWifiBackup) + rg.POST("/personal/devices/:id/wizard-verify", h.handleWizardVerify) + rg.POST("/personal/devices/:id/wifi-reconnect", h.handleWifiReconnect) + // 推送流水(审计) + rg.GET("/personal/pushes", h.handleListPushes) + // 无订单收款(人工处理) + rg.GET("/personal/unmatched", h.handleListUnmatched) + rg.POST("/personal/unmatched/:id/resolve", h.handleResolveUnmatched) + // 动态收款码管理 + rg.GET("/personal/qrcodes", h.handleListQrcodes) + rg.DELETE("/personal/qrcodes/:deviceId/:platform/:userId", h.handleDeleteQrcode) + rg.POST("/personal/qrcodes/:deviceId/wechat/capture", h.handleCaptureWechatQrcode) + rg.POST("/personal/qrcodes/:deviceId/alipay/capture", h.handleCaptureAlipayQrcode) + // 导航到收款记录页(监听界面,PLAN-EVENT-MONITOR §2.3) + rg.POST("/personal/devices/:id/navigate-ledger", h.handleNavigateAlipayLedger) +} + +// ========== 监听设备 ========== + +// handleListDevices 设备列表(解密密钥不返回,仅展示状态) +func (h *PersonalHandler) handleListDevices(c *gin.Context) { + devs, err := h.pdb.ListDevices(c.Request.Context()) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + out := make([]gin.H, 0, len(devs)) + for _, d := range devs { + out = append(out, gin.H{ + "id": d.ID, "device_id": d.DeviceID, "name": d.Name, + "account_id": d.AccountID, "provider": d.Provider, + "status": d.Status, + "last_push": d.LastPush, "ip": d.IP, "created_at": d.CreatedAt, + "adb_host": d.AdbHost, "adb_port": d.AdbPort, + "adb_wifi_host": d.AdbWifiHost, "adb_wifi_port": d.AdbWifiPort, + "setup_progress": personal.ParseSetupProgress(d.SetupProgress), + // P2P 控制面字段(docs/P2P-ADB-RECOVERY.md Phase 1) + "ts_ip": d.TsIP, + "adb_listen": d.AdbListen, + "control_state": d.ControlState, + }) + } + c.JSON(http.StatusOK, gin.H{"data": gin.H{"devices": out}}) +} + +// accountHasConnectedDevice 判断指定账户是否已有 **adb 可达**的设备。 +// 用于完成度门槛守恒:新建/删除设备时若账户没有 adb 在线设备则冻结账户,避免账户裸奔接单。 +// 在线判定 = 实时 adb 连通性(2026-08-29 重构:不再依赖监听 App 心跳/setup 进度)。 +func (h *PersonalHandler) accountHasConnectedDevice(ctx context.Context, accountID int64) bool { + devs, err := h.pdb.ListDevices(ctx) + if err != nil { + return false + } + for _, d := range devs { + if d.AccountID != accountID { + continue + } + serial := adbSerialOf(d) + if serial != "" && h.adbMgr != nil && h.adbMgr.Online(ctx, serial) { + return true + } + } + return false +} + +// adbSerialOf 解析设备 adb serial(TCP host:port 或 USB 纯 serial) +func adbSerialOf(d *personal.DeviceEncrypted) string { + if d.AdbHost != "" { + if d.AdbPort > 0 { + return fmt.Sprintf("%s:%d", d.AdbHost, d.AdbPort) + } + return d.AdbHost + } + if d.AdbWifiHost != "" && d.AdbWifiPort > 0 { + return fmt.Sprintf("%s:%d", d.AdbWifiHost, d.AdbWifiPort) + } + return "" +} + +// handleSetDeviceAdb 设置设备 adb 连接信息(USB serial/host:port + WiFi 备用通道) +// POST /api/admin/personal/devices/:id/adb {adb_host, adb_port, adb_wifi_host?, adb_wifi_port?} +// adb_host 可为 USB 直连 serial(无端口,此时 adb_port=0)或 host:port +func (h *PersonalHandler) handleSetDeviceAdb(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + if id <= 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"}) + return + } + var req struct { + AdbHost string `json:"adb_host"` + AdbPort int `json:"adb_port"` + AdbWifiHost string `json:"adb_wifi_host"` + AdbWifiPort int `json:"adb_wifi_port"` + } + if err := c.ShouldBindJSON(&req); err != nil || req.AdbHost == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误:需要 adb_host(USB serial 或 host)"}) + return + } + if req.AdbPort > 65535 || req.AdbWifiPort > 65535 { + c.JSON(http.StatusBadRequest, gin.H{"error": "端口超出范围"}) + return + } + if err := h.pdb.SetDeviceAdb(c.Request.Context(), id, req.AdbHost, req.AdbPort, req.AdbWifiHost, req.AdbWifiPort); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + logger.Info("admin", "设备 adb 信息已更新", "device_id", id, "adb", req.AdbHost, + "wifi", fmt.Sprintf("%s:%d", req.AdbWifiHost, req.AdbWifiPort)) + c.JSON(http.StatusOK, gin.H{"data": gin.H{"ok": true}}) +} + +// handleListDeviceAccounts 列出设备全部绑定账户(主账户 + 附加账户)。 +// GET /api/admin/personal/devices/:id/accounts +// 响应:{"data":{"primary_account_id":3,"accounts":[{"account_id":3,...},{"account_id":4,...}]}} +func (h *PersonalHandler) handleListDeviceAccounts(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + if id <= 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"}) + return + } + dev, err := h.pdb.GetDeviceByID(c.Request.Context(), id) + if err != nil || dev == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "设备不存在"}) + return + } + // 主账户 + 附加账户合并去重,附带账户信息(名称/通道/状态) + ids := store.DeviceAccountIDs(c.Request.Context(), h.pdb, id, dev.AccountID) + out := make([]gin.H, 0, len(ids)) + for _, aid := range ids { + acct := h.pool.GetByID(aid) + if acct == nil { + continue + } + out = append(out, gin.H{ + "account_id": acct.ID, "name": acct.Name, + "channel": acct.Channel, "status": string(acct.Status), + "is_primary": aid == dev.AccountID, + }) + } + c.JSON(http.StatusOK, gin.H{"data": gin.H{"primary_account_id": dev.AccountID, "accounts": out}}) +} + +// handleAddDeviceAccount 绑定附加账户到设备(应用双开多账户)。 +// POST /api/admin/personal/devices/:id/accounts {account_id} +// 校验:账户存在 + 属于个人通道 + 与设备主账户同通道(双开只区分空间,不混通道)。 +func (h *PersonalHandler) handleAddDeviceAccount(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + if id <= 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"}) + return + } + var req struct { + AccountID int64 `json:"account_id"` + } + if err := c.ShouldBindJSON(&req); err != nil || req.AccountID <= 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误:需要 account_id"}) + return + } + dev, err := h.pdb.GetDeviceByID(c.Request.Context(), id) + if err != nil || dev == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "设备不存在"}) + return + } + acct := h.pool.GetByID(req.AccountID) + if acct == nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "账户不存在"}) + return + } + if !isPersonalChannelName(acct.Channel) { + c.JSON(http.StatusBadRequest, gin.H{"error": "账户不是个人通道账户(channel=" + acct.Channel + ")"}) + return + } + if acct.QRContent == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "账户未配置收款码(qr_content),请先上传收款码"}) + return + } + // 允许跨通道绑定:一台设备(双开微信 + 支付宝)可服务多通道账户, + // 监听端按包名识别通道(com.tencent.mm / com.eg.android.AlipayGphone),单实例即可接收全部通知。 + if err := h.pdb.AddDeviceAccount(c.Request.Context(), id, req.AccountID); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "绑定账户失败: " + err.Error()}) + return + } + logger.Info("admin", "设备绑定附加账户", "device_id", id, "account_id", req.AccountID, "name", acct.Name) + c.JSON(http.StatusOK, gin.H{"data": gin.H{"ok": true, "account_id": req.AccountID}}) +} + +// handleRemoveDeviceAccount 解绑设备附加账户(主账户不可解绑,需删除设备或改用主账户字段)。 +// DELETE /api/admin/personal/devices/:id/accounts/:accountId +func (h *PersonalHandler) handleRemoveDeviceAccount(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + accountID, _ := strconv.ParseInt(c.Param("accountId"), 10, 64) + if id <= 0 || accountID <= 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"}) + return + } + dev, err := h.pdb.GetDeviceByID(c.Request.Context(), id) + if err != nil || dev == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "设备不存在"}) + return + } + if accountID == dev.AccountID { + c.JSON(http.StatusBadRequest, gin.H{"error": "主账户不可解绑(请先删除设备或更换主账户)"}) + return + } + if err := h.pdb.RemoveDeviceAccount(c.Request.Context(), id, accountID); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "解绑账户失败: " + err.Error()}) + return + } + logger.Info("admin", "设备解绑附加账户", "device_id", id, "account_id", accountID) + c.JSON(http.StatusOK, gin.H{"data": gin.H{"ok": true}}) +} + +func (h *PersonalHandler) handleDeleteDevice(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + if id <= 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"}) + return + } + // 删前查 account_id 用于守恒门槛:删完后若账户再无在线完成 setup 的设备则冻结账户 + dev, _ := h.pdb.GetDeviceByID(c.Request.Context(), id) + if err := h.pdb.DeleteDevice(c.Request.Context(), id); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + // 级联清理:收款码记录、账户绑定;仅被该设备绑定的自动账户一并停用, + // 否则幽灵设备的码卡与僵尸账户会永远留在池子里 + if dev != nil { + if err := h.pdb.DeleteQrcodeByDevice(c.Request.Context(), dev.DeviceID); err != nil { + logger.Warn("admin", "清理设备收款码失败", "device_id", dev.DeviceID, "err", err) + } + h.unbindAndRetireAccounts(c.Request.Context(), id, dev) + } + if dev != nil && dev.AccountID > 0 && !h.accountHasConnectedDevice(c.Request.Context(), dev.AccountID) { + if a := h.pool.GetByID(dev.AccountID); a != nil && a.Status == account.StatusNormal { + // 无在线设备 → 按离线联动语义冻结:新设备 wizard 接入 adb 可达时自动解冻 + h.pool.FreezeByOffline(dev.AccountID) + logger.Info("admin", "账户已冻结(删除最后一台在线设备)", "account_id", dev.AccountID, "name", a.Name) + } + } + c.JSON(http.StatusOK, gin.H{"data": "ok"}) +} + +// handleFreezeDevice 冻结设备(不可接新单,心跳返回 disable 指令) +func (h *PersonalHandler) handleFreezeDevice(c *gin.Context) { + h.setDeviceStatus(c, string(personal.DeviceFrozen)) +} + +// unbindAndRetireAccounts 设备删除后的账户收尾:解绑全部绑定账户; +// 其中"自动创建"且不再属于任何设备的个人账户 → 停用(保留记录可查,管理员可在账户池彻底删除) +func (h *PersonalHandler) unbindAndRetireAccounts(ctx context.Context, devRowID int64, dev *personal.DeviceEncrypted) { + for _, acctID := range store.DeviceAccountIDs(ctx, h.pdb, devRowID, 0) { + if err := h.pdb.RemoveDeviceAccount(ctx, devRowID, acctID); err != nil { + logger.Warn("admin", "解除设备账户绑定失败", "device_row_id", devRowID, "account_id", acctID, "err", err) + continue + } + acct := h.pool.GetByID(acctID) + if acct == nil || !strings.HasSuffix(acct.Name, "自动") || !strings.HasPrefix(acct.Channel, "personal.") { + continue + } + if bound, err := h.pdb.AccountBound(ctx, acctID); err == nil && bound { + continue // 仍被其他设备绑定 + } + h.pool.FreezeByRetire(acctID) + if err := h.accountDB.SetAccountFrozen(ctx, acctID, account.FrozenReasonRetire); err != nil { + logger.Warn("admin", "停用僵尸自动账户失败", "account_id", acctID, "err", err) + continue + } + logger.Info("admin", "已停用无主自动账户(设备删除级联)", "account_id", acctID, "name", acct.Name) + } +} + +// handleUnfreezeDevice 解冻设备 +func (h *PersonalHandler) handleUnfreezeDevice(c *gin.Context) { + h.setDeviceStatus(c, string(personal.DeviceOffline)) +} + +func (h *PersonalHandler) setDeviceStatus(c *gin.Context, status string) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + if id <= 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"}) + return + } + if err := h.pdb.SetDeviceStatus(c.Request.Context(), id, status); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"data": "ok"}) +} + +// ========== 推送流水 ========== + +// handleListPushes 收款推送流水(审计) +func (h *PersonalHandler) handleListPushes(c *gin.Context) { + limit, _ := strconv.Atoi(c.DefaultQuery("limit", "50")) + pushes, err := h.pdb.ListPushes(c.Request.Context(), limit) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"data": gin.H{"pushes": pushes}}) +} + +// ========== 无订单收款 ========== + +// handleListUnmatched 无订单收款列表(pending / resolved / refunded) +func (h *PersonalHandler) handleListUnmatched(c *gin.Context) { + status := c.Query("status") + limit, _ := strconv.Atoi(c.DefaultQuery("limit", "50")) + list, err := h.pdb.ListUnmatched(c.Request.Context(), status, limit) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"data": gin.H{"unmatched": list}}) +} + +// handleResolveUnmatched 处理无订单收款 +// POST /api/admin/personal/unmatched/:id/resolve {status: resolved|refunded, note} +func (h *PersonalHandler) handleResolveUnmatched(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + var req struct { + Status string `json:"status"` + Note string `json:"note"` + } + if err := c.ShouldBindJSON(&req); err != nil || id <= 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"}) + return + } + if req.Status != "resolved" && req.Status != "refunded" { + c.JSON(http.StatusBadRequest, gin.H{"error": "status 必须为 resolved 或 refunded"}) + return + } + if err := h.pdb.ResolveUnmatched(c.Request.Context(), id, req.Status, req.Note); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"data": "ok"}) +} + +// ---------- 辅助 ---------- + +// isPersonalChannelName 判断账户 channel 是否个人通道(personal.alipay / personal.wechat ...) +func isPersonalChannelName(ch string) bool { + return len(ch) > 9 && ch[:9] == "personal." +} + +// providerFromChannelName 从账户 channel 提取 provider(personal.alipay → alipay) +func providerFromChannelName(ch string) string { + for i := len(ch) - 1; i >= 0; i-- { + if ch[i] == '.' { + return ch[i+1:] + } + } + return ch +} + +// ---------- 批量运维 ---------- + +// batchDeviceReq 批量操作的通用请求体。ids 为空时若 all=true 则作用于全部设备;ids 非空时以 ids 为准。 +type batchDeviceReq struct { + IDs []int64 `json:"ids"` + All bool `json:"all"` +} + +func (h *PersonalHandler) resolveBatchTargets(c *gin.Context) ([]*personal.DeviceEncrypted, []int64, bool) { + var req batchDeviceReq + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"}) + return nil, nil, false + } + ctx := c.Request.Context() + if len(req.IDs) == 0 { + if !req.All { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误:需提供 ids 或 all=true"}) + return nil, nil, false + } + devs, err := h.pdb.ListDevices(ctx) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return nil, nil, false + } + enc := make([]*personal.DeviceEncrypted, len(devs)) + for i, d := range devs { + enc[i] = d + } + return enc, nil, true + } + seen := make(map[int64]bool) + missing := make([]int64, 0) + out := make([]*personal.DeviceEncrypted, 0, len(req.IDs)) + for _, id := range req.IDs { + if seen[id] { + continue + } + seen[id] = true + dev, err := h.pdb.GetDeviceByID(ctx, id) + if err != nil || dev == nil { + missing = append(missing, id) + continue + } + out = append(out, dev) + } + return out, missing, true +} + +func (h *PersonalHandler) batchDeviceError(c *gin.Context, device *personal.DeviceEncrypted, err error) { + logger.Warn("admin", "批量设备操作失败", "device_id", device.ID, "device", device.DeviceID, "err", err) +} + +// POST /api/admin/personal/devices/batch/restart {ids:[...]|all:true} +// 逐个调 h.Restart(内部自带 5min 限频 + 90s 超时),不因单个失败中断其余设备。 +func (h *PersonalHandler) handleBatchRestartDevices(c *gin.Context) { + devs, missing, ok := h.resolveBatchTargets(c) + if !ok { + return + } + if h.adbMgr == nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "adb 管理器未配置,批量重启不可用"}) + return + } + results := make([]map[string]any, 0, len(devs)) + hasFail := false + for _, dev := range devs { + res, rerr := h.Restart(c.Request.Context(), dev) + if rerr != nil { + h.batchDeviceError(c, dev, rerr) + results = append(results, map[string]any{"device_id": dev.DeviceID, "id": dev.ID, "ok": false, "error": rerr.Error()}) + hasFail = true + continue + } + res["device_id"] = dev.DeviceID + res["id"] = dev.ID + results = append(results, res) + } + code := http.StatusOK + if hasFail { + code = http.StatusMultiStatus + } + c.JSON(code, gin.H{ + "data": gin.H{"results": results, "missing_ids": missing, "total": len(devs)}, + }) +} + +// POST /api/admin/personal/devices/batch/status {ids:[...]|all:true, status:"frozen"|"offline"} +func (h *PersonalHandler) handleBatchSetDeviceStatus(c *gin.Context) { + var req struct { + batchDeviceReq + Status string `json:"status"` + } + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"}) + return + } + switch req.Status { + case string(personal.DeviceFrozen), string(personal.DeviceOffline): + default: + c.JSON(http.StatusBadRequest, gin.H{"error": "status 必须为 frozen 或 offline"}) + return + } + devs, missing, ok := h.resolveBatchTargets(c) + if !ok { + return + } + results := make([]map[string]any, 0, len(devs)) + hasFail := false + for _, dev := range devs { + entry := map[string]any{"device_id": dev.DeviceID, "id": dev.ID} + if err := h.pdb.SetDeviceStatus(c.Request.Context(), dev.ID, req.Status); err != nil { + h.batchDeviceError(c, dev, err) + entry["ok"] = false + entry["error"] = err.Error() + hasFail = true + } else { + entry["ok"] = true + } + results = append(results, entry) + } + code := http.StatusOK + if hasFail { + code = http.StatusMultiStatus + } + c.JSON(code, gin.H{"data": gin.H{"results": results, "missing_ids": missing, "total": len(devs)}}) +} + +// ========== 动态收款码管理 ========== + +// handleListQrcodes 获取所有设备最新收款码 +func (h *PersonalHandler) handleListQrcodes(c *gin.Context) { + qs, err := h.pdb.ListQrcodes(c.Request.Context(), 100) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"data": qs}) +} + +// handleDeleteQrcode 移除单条收款码记录(设备退役/换绑后手动清理用) +func (h *PersonalHandler) handleDeleteQrcode(c *gin.Context) { + deviceID := c.Param("deviceId") + platform := c.Param("platform") + userID, _ := strconv.Atoi(c.Param("userId")) + if deviceID == "" || (platform != "alipay" && platform != "wechat") { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"}) + return + } + if err := h.pdb.DeleteQrcode(c.Request.Context(), deviceID, platform, userID); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + logger.Info("admin", "已移除收款码记录", "device_id", deviceID, "platform", platform, "user_id", userID) + c.JSON(http.StatusOK, gin.H{"data": gin.H{"ok": true}}) +} + +// ensureAccountForQR 确保捕获到的收款码有对应的 pay_accounts 记录。 +// 如果账户池中无匹配该码内容的个人账户,自动创建并绑定到设备,使支付通道可用。 +// space: 0=主空间, 999=双开(XSpace)。返回 account_id、是否新建、error。 +func (h *PersonalHandler) ensureAccountForQR(ctx context.Context, dev *personal.DeviceEncrypted, platform, qrContent string, space int) (int64, bool, error) { + if h.accountDB == nil || h.pool == nil || qrContent == "" { + return 0, false, nil + } + return EnsureAccountForQR(ctx, h.pdb, h.pool, h.accountDB, dev, platform, qrContent, space) +} + +// EnsureAccountForQR 全局可复用版(admin handler 与 main.go 监听端上报共用)。 +// 依据:收款码内容匹配账户池 → 无则自动创建 personal.* 账户并绑定设备。 +// space: 0=主空间, 999=双开(XSpace)。同一设备+平台+空间 只保留一个账户: +// 复用已绑定账户(主空间/双开按账户名后缀区分),动态码每次上报不新建重复账户。 +func EnsureAccountForQR(ctx context.Context, pdb store.PersonalStore, pool *account.AccountPool, accountDB *store.AccountDB, dev *personal.DeviceEncrypted, platform, qrContent string, space int) (int64, bool, error) { + if accountDB == nil || pool == nil || qrContent == "" || dev == nil { + return 0, false, nil + } + prefix := "personal.alipay" + if platform == "wechat" { + prefix = "personal.wechat" + } + // 已有匹配账户 → 复用(同一设备+平台+空间,不管码内容是否变化) + // 匹配顺序:① 收款码内容完全一致(最可靠)→ ② 空间标记(账户名含"双开")。 + // 创建时必须写入空间标记,否则下次上报匹配不到 → 重复建户(历史 bug 的根源)。 + var reuse *account.Account + for _, id := range store.DeviceAccountIDs(ctx, pdb, dev.ID, dev.AccountID) { + acct := pool.GetByID(id) + if acct == nil || !strings.HasPrefix(acct.Channel, prefix) { + continue + } + if acct.QRContent == qrContent { + reuse = acct + break + } + } + if reuse == nil { + for _, id := range store.DeviceAccountIDs(ctx, pdb, dev.ID, dev.AccountID) { + acct := pool.GetByID(id) + if acct == nil || !strings.HasPrefix(acct.Channel, prefix) { + continue + } + if space == 0 && !strings.Contains(acct.Name, "双开") { + reuse = acct + break + } + if space == 999 && strings.Contains(acct.Name, "双开") { + reuse = acct + break + } + } + } + if reuse != nil { + // 收款码内容变化 → 更新到账户(保持单一账户跟随最新码) + if reuse.QRContent != qrContent { + // 资金安全防线:新码必须合法(与下单路由同规则),非法内容绝不覆盖账户 + // (脏数据一旦写入,后续下单全部路由到坏码 → 支付全挂,历史教训 2026-08-29) + if reason := account.InvalidQRContent(reuse.Channel, qrContent); reason != "" { + logger.Error("admin", "收款码内容非法,拒绝更新账户(脏数据防线)", + "device", dev.DeviceID, "platform", platform, "account_id", reuse.ID, + "reason", reason, "content", capture.TruncateStr(qrContent, 30)) + return reuse.ID, false, nil + } + qrEnc, err := account.Encrypt([]byte(qrContent)) + if err != nil { + return 0, false, fmt.Errorf("加密收款码内容失败: %w", err) + } + if err := accountDB.UpdateAccount(ctx, &account.AccountEncrypted{ID: reuse.ID, Channel: reuse.Channel, Name: reuse.Name, + AppID: reuse.AppID, MchID: reuse.MchID, QRContentEnc: qrEnc, Status: string(reuse.Status), + Weight: reuse.Weight, Priority: reuse.Priority, AutoBan: reuse.AutoBan, DailyLimit: reuse.DailyLimit, + DailyUsed: reuse.DailyUsed, Group: reuse.Group, IsProd: reuse.IsProd, Remark: reuse.Remark}); err != nil { + return 0, false, fmt.Errorf("更新账户收款码失败: %w", err) + } + reuse.QRContent = qrContent + // GetByID 返回副本,必须写回池;否则池内旧码一直残留(重启前匹配全用旧码) + pool.Update(reuse) + logger.Info("admin", "账户收款码已更新", "device", dev.DeviceID, "platform", platform, "account_id", reuse.ID) + } + return reuse.ID, false, nil + } + // 加密收款码内容 + qrContentEnc, err := account.Encrypt([]byte(qrContent)) + if err != nil { + return 0, false, fmt.Errorf("加密收款码内容失败: %w", err) + } + // 生成账户名:设备名 + 平台 + 空间标记(主空间/双开都标注,管理后台一眼可区分) + devName := dev.Name + if devName == "" { + // 设备未命名时用设备 ID 前 8 位兜底,避免多设备混淆 + devName = dev.DeviceID + if len(devName) > 8 { + devName = devName[:8] + } + } + acctName := devName + if platform == "wechat" { + acctName += " 微信自动" + } else { + acctName += " 支付宝自动" + } + if space == 999 { + acctName += "(双开)" + } else { + acctName += "(主空间)" + } + // 资金安全防线:新建账户的码也必须合法(与更新路径同规则),脏数据不建账户 + if reason := account.InvalidQRContent(prefix, qrContent); reason != "" { + logger.Error("admin", "收款码内容非法,拒绝创建账户(脏数据防线)", + "device", dev.DeviceID, "platform", platform, "user", space, "reason", reason, + "content", capture.TruncateStr(qrContent, 30)) + return 0, false, nil + } + // 创建账户记录 + newID, err := accountDB.CreateAccount(ctx, &account.AccountEncrypted{ + Channel: prefix, + Name: acctName, + QRContentEnc: qrContentEnc, + Status: "normal", + IsProd: true, + }) + if err != nil { + return 0, false, fmt.Errorf("创建个人账户失败: %w", err) + } + // 添加账户到内存池(使支付通道立即可见) + pool.Add(&account.Account{ + ID: newID, + Channel: prefix, + Name: acctName, + QRContent: qrContent, + Status: account.StatusNormal, + IsProd: true, + }) + // 绑定到设备 + if err := pdb.AddDeviceAccount(ctx, dev.ID, newID); err != nil { + return newID, true, fmt.Errorf("绑定账户到设备失败: %w", err) + } + logger.Info("admin", "已自动创建个人账户", "device", dev.DeviceID, "platform", platform, + "account_id", newID, "name", acctName) + return newID, true, nil +} diff --git a/internal/admin/qrcode.go b/internal/admin/qrcode.go new file mode 100755 index 0000000..1dc2051 --- /dev/null +++ b/internal/admin/qrcode.go @@ -0,0 +1,44 @@ +// 收款码图片解析(docs/MIANQIAN.md §7.6 阶段一)。 +// +// 决策 #8/#9:收款码获取 = 双保险(手动上传 + 监听端自动采集) +// 阶段一(最快跑通):管理后台上传收款码图片 → 服务端解析二维码内容 → 绑定账户 +// 用户从支付宝/微信 App「保存收款码」→ 上传 → 自动解析出 qr.alipay.com/xxx 或 weixin://wxpay/... 内容 +package admin + +import ( + "io" + "net/http" + + "github.com/gin-gonic/gin" + + "paygateway/internal/admin/capture" +) + +// maxQRUploadSize 收款码图片上传大小上限(2MB,对齐 capture.MaxQRUploadSize) +const maxQRUploadSize = capture.MaxQRUploadSize + +// handleDecodeQRCode 上传收款码图片 → 解析二维码内容 +// POST /api/admin/accounts/decode-qrcode (multipart/form-data: file) +func (h *AccountHandler) handleDecodeQRCode(c *gin.Context) { + // 限制上传大小(Go 1.19+ 自动处理超限) + c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, maxQRUploadSize+1<<10) + + file, _, err := c.Request.FormFile("file") + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "缺少收款码图片(字段名 file)"}) + return + } + defer file.Close() + + data, err := io.ReadAll(io.LimitReader(file, maxQRUploadSize)) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "读取图片失败: " + err.Error()}) + return + } + content, err := capture.DecodeQRContent(data) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"data": gin.H{"content": content}}) +} diff --git a/internal/admin/qrcode_alipay.go b/internal/admin/qrcode_alipay.go new file mode 100644 index 0000000..bfd36ad --- /dev/null +++ b/internal/admin/qrcode_alipay.go @@ -0,0 +1,145 @@ +// 支付宝收款码捕获 HTTP 层:设备查找 → 调用 capture 包自动化 → 去重 → 入库。 +package admin + +import ( + "context" + "net/http" + "strconv" + "strings" + "time" + + "github.com/gin-gonic/gin" + + "paygateway/internal/admin/capture" + "paygateway/internal/logger" + "paygateway/internal/personal" + "paygateway/internal/store" +) + +// handleCaptureAlipayQrcode 通过服务器 adb 捕获支付宝收款码 +// POST /api/personal/qrcodes/:deviceId/alipay/capture?user=0|999 +func (h *PersonalHandler) handleCaptureAlipayQrcode(c *gin.Context) { + deviceID := c.Param("deviceId") + if deviceID == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"}) + return + } + if h.adbMgr == nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "设备控制台未启用(adb 管理器未配置)"}) + return + } + dev, err := h.pdb.GetDevice(c.Request.Context(), deviceID) + if err != nil || dev == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "设备不存在"}) + return + } + userId, _ := strconv.Atoi(c.DefaultQuery("user", "0")) + serial, _, ok := h.pickOnlineSerial(c.Request.Context(), dev) + if !ok { + c.JSON(http.StatusBadGateway, gin.H{"error": "设备离线:所有 adb 通道不可用"}) + return + } + ctx, cancel := context.WithTimeout(c.Request.Context(), 90*time.Second) + defer cancel() + + content, cerr := capture.CaptureAlipayQrcode(ctx, h.adbMgr, serial, userId) + if cerr != nil { + c.JSON(http.StatusBadGateway, gin.H{"error": cerr.Error()}) + return + } + // 去重:与库中最新码相同则跳过 + if latest, lerr := h.pdb.GetLatestQrcode(ctx, deviceID); lerr == nil && latest != nil && + latest.Platform == "alipay" && latest.QRContent == content && latest.Status == "active" { + logger.Info("admin", "支付宝收款码未变化,跳过入库", "device_id", deviceID) + // 码未变不代表账户同步过:仍需确保账户池 QRContent 与码一致(历史不同步的补救路径) + if _, created, aerr := h.ensureAccountForQR(c.Request.Context(), dev, "alipay", content, userId); aerr != nil { + logger.Error("admin", "同步账户池失败", "device_id", deviceID, "user", userId, "err", aerr) + } else if created { + logger.Info("admin", "已自动创建账户", "device_id", deviceID, "platform", "alipay") + } + c.JSON(http.StatusOK, gin.H{"data": gin.H{"ok": true, "unchanged": true, "content": content}}) + return + } + accountName := h.resolveQrcodeAccount(ctx, dev, "alipay", userId, content) + q := &personal.DeviceQrcode{ + DeviceID: dev.DeviceID, + Platform: "alipay", + UserID: userId, + QRContent: content, + Account: accountName, + ExpireIn: qrcodeExpireIn, + ExpireAt: store.ExpireAtStr(qrcodeExpireIn), + Status: "active", + CreatedAt: time.Now().Format(time.RFC3339), + } + if _, err := h.pdb.UpsertQrcode(c.Request.Context(), q); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "收款码入库失败: " + err.Error()}) + return + } + logger.Info("admin", "支付宝收款码已捕获", "device_id", dev.DeviceID, "user", userId, + "name", accountName, "content", capture.TruncateStr(content, 40)) + // 同步账户池:码内容变了 → 更新绑定账户的 QRContent;无主码 → 自动建户(与监听端上报路径同逻辑) + if _, created, aerr := h.ensureAccountForQR(c.Request.Context(), dev, "alipay", content, userId); aerr != nil { + logger.Error("admin", "同步账户池失败(码已入库但账户未更新)", "device_id", dev.DeviceID, "user", userId, "err", aerr) + } else if created { + logger.Info("admin", "监听端上报已自动创建账户", "device_id", dev.DeviceID, "platform", "alipay") + } + // 捕获后顺带清理该空间支付宝相册旧码(保留最新 1 张),防频繁捕获打满存储 + if n := capture.CleanupAlipayGallery(ctx, h.adbMgr, serial, userId); n > 0 { + logger.Info("admin", "已清理设备旧支付宝收款码图", "device", serial, "user", userId, "deleted", n) + } + c.JSON(http.StatusOK, gin.H{"data": gin.H{"ok": true, "user": userId, "account": accountName, "content": content}}) +} + +// handleNavigateAlipayLedger 通过服务器 adb 导航到支付宝完整收款记录页(监听界面)。 +// 供事件驱动监听(PLAN-EVENT-MONITOR §2.3)手动触发/测试;监听端收到窗口事件后建立基线进入监听态。 +// POST /api/personal/devices/:id/navigate-ledger?provider=alipay&user=0|999 +func (h *PersonalHandler) handleNavigateAlipayLedger(c *gin.Context) { + deviceID := c.Param("id") + if deviceID == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"}) + return + } + if h.adbMgr == nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "设备控制台未启用(adb 管理器未配置)"}) + return + } + dev, err := h.pdb.GetDevice(c.Request.Context(), deviceID) + if err != nil || dev == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "设备不存在"}) + return + } + userId, _ := strconv.Atoi(c.DefaultQuery("user", "0")) + serial, _, ok := h.pickOnlineSerial(c.Request.Context(), dev) + if !ok { + c.JSON(http.StatusBadGateway, gin.H{"error": "设备离线:所有 adb 通道不可用"}) + return + } + ctx, cancel := context.WithTimeout(c.Request.Context(), 90*time.Second) + defer cancel() + + if err := capture.CaptureAlipayLedger(ctx, h.adbMgr, serial, userId); err != nil { + c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()}) + return + } + logger.Info("admin", "已导航到支付宝收款记录页", "device_id", dev.DeviceID, "user", userId) + c.JSON(http.StatusOK, gin.H{"data": gin.H{"ok": true, "user": userId}}) +} + +// alipayAccountName 从设备绑定账户解析支付宝账户名(device_accounts 附加账户优先,主账户兜底)。 +func (h *PersonalHandler) alipayAccountName(ctx context.Context, dev *personal.DeviceEncrypted) string { + if dev == nil || h.pool == nil { + return "" + } + if ids, err := h.pdb.ListDeviceAccounts(ctx, dev.ID); err == nil { + for _, id := range ids { + if acct := h.pool.GetByID(id); acct != nil && strings.HasPrefix(acct.Channel, "personal.alipay") { + return acct.Name + } + } + } + if acct := h.pool.GetByID(dev.AccountID); acct != nil && strings.HasPrefix(acct.Channel, "personal.alipay") { + return acct.Name + } + return dev.Name +} diff --git a/internal/admin/qrcode_renew.go b/internal/admin/qrcode_renew.go new file mode 100644 index 0000000..6665c26 --- /dev/null +++ b/internal/admin/qrcode_renew.go @@ -0,0 +1,247 @@ +// 收款码自动续期 + 定期清理调度。 +// 续期:扫描设备各平台收款码,缺失/非 active/过期 → 自动 adb 捕获补新码(静态码长期有效,正常不触发)。 +// 清理:DB 删过期记录(每设备每平台保留最新);设备端微信相册保留最近 N 张。 +package admin + +import ( + "context" + "fmt" + "strings" + "sync" + "time" + + "paygateway/internal/admin/capture" + "paygateway/internal/logger" + "paygateway/internal/personal" + "paygateway/internal/store" +) + +// qrcodeExpireIn 保存的收款码有效期(秒)。微信/支付宝「保存收款码」是静态码长期有效, +// 设 7 天作安全缓冲(防极端情况下码失效),到期由续期调度补捕。 +const qrcodeExpireIn = 7 * 24 * 3600 + +// qrcodeRenewPlatforms 续期检查的平台(服务端 adb 主动捕获) +var qrcodeRenewPlatforms = []string{"wechat", "alipay"} + +// qrcodeMinRenewInterval 每平台两次自动捕获的最小间隔(防调度异常时无限重捕风暴) +const qrcodeMinRenewInterval = 10 * time.Minute + +// qrcodeUserSpaces 每设备自动捕获的 Android 用户空间。 +// 0 = 主空间(机主),999 = 双开(小米 XSpace 分身)。 +// 覆盖微信/支付宝在双开空间也有安装的场景。 +var qrcodeUserSpaces = []int{0, 999} + +// RenewQrcodes 自动续期调度:扫描所有设备各平台各用户空间的收款码,发现缺失/非 active/已过期 +// → 自动触发服务端 adb 捕获补新码。静态码长期有效,正常情况几乎不触发; +// 仅在码丢失或明确过期时补捕,且每平台每用户空间受最小间隔限制。 +// +// 由 main 以固定间隔(默认 1 小时)调用。 +func (h *PersonalHandler) RenewQrcodes(ctx context.Context) { + if h.adbMgr == nil || h.pdb == nil { + return + } + devs, err := h.pdb.ListDevices(ctx) + if err != nil { + logger.Error("admin", "续期扫描: 获取设备列表失败", "err", err) + return + } + for _, dev := range devs { + serial, _, ok := h.pickOnlineSerial(ctx, dev) + if !ok { + continue // 设备离线,无法捕获 + } + for _, userID := range qrcodeUserSpaces { + for _, platform := range qrcodeRenewPlatforms { + if !h.qrcodeNeedsRenew(ctx, dev.DeviceID, platform, userID) { + continue + } + // 限频:距上次自动捕获不足最小间隔则跳过(防重捕风暴) + if h.renewLimiter != nil && !h.renewLimiter.allow(dev.DeviceID, platform, userID) { + logger.Debug("admin", "收款码补捕被限频跳过", "device", dev.DeviceID, "platform", platform, "user", userID) + continue + } + h.renewOneQrcode(ctx, dev, serial, platform, userID) + } + } + } +} + +// qrcodeNeedsRenew 判断设备某平台某用户空间是否需要补捕:无码 / 非 active / 已过期。 +// 静态码未过期不触发(不做提前续期,避免无意义捕获)。 +func (h *PersonalHandler) qrcodeNeedsRenew(ctx context.Context, deviceID, platform string, userID int) bool { + latest, err := h.pdb.GetLatestQrcodeByPlatformUser(ctx, deviceID, platform, userID) + if err != nil || latest == nil { + return true // 无码 → 需要 + } + if latest.Status != "active" { + return true + } + expireAt, err := time.Parse(time.RFC3339, latest.ExpireAt) + if err != nil { + return true // 过期时间解析失败 → 重捕 + } + return time.Now().After(expireAt) +} + +// qrcodeLastRenew 记录每设备每平台每用户空间最近一次自动捕获时间(限频) +type qrcodeLastRenew struct { + mu sync.Mutex + lasts map[string]time.Time // key: deviceID|platform|userID +} + +// allow 检查并记录该设备该平台该用户空间是否允许自动捕获(距上次至少 qrcodeMinRenewInterval) +func (r *qrcodeLastRenew) allow(deviceID, platform string, userID int) bool { + key := fmt.Sprintf("%s|%s|%d", deviceID, platform, userID) + r.mu.Lock() + defer r.mu.Unlock() + if t, ok := r.lasts[key]; ok && time.Since(t) < qrcodeMinRenewInterval { + return false + } + r.lasts[key] = time.Now() + return true +} + +// renewOneQrcode 对单个设备单平台单用户空间执行一次续期捕获并入库(失败记日志,下轮再试)。 +func (h *PersonalHandler) renewOneQrcode(ctx context.Context, dev *personal.DeviceEncrypted, serial, platform string, userID int) { + ctx2, cancel := context.WithTimeout(ctx, 90*time.Second) + defer cancel() + + var content string + switch platform { + case "wechat": + _, _, c, err := capture.CaptureWechatQrcode(ctx2, h.adbMgr, serial, userID) + if err != nil { + logger.Warn("admin", "微信收款码续期失败", "device", dev.DeviceID, "user", userID, "err", err) + return + } + content = c + case "alipay": + c, err := capture.CaptureAlipayQrcode(ctx2, h.adbMgr, serial, userID) + if err != nil { + logger.Warn("admin", "支付宝收款码续期失败", "device", dev.DeviceID, "user", userID, "err", err) + return + } + content = c + default: + return + } + // 确保有对应的支付账户(自动创建 + 绑定),使支付闭环可用 + if _, created, aerr := h.ensureAccountForQR(ctx, dev, platform, content, userID); aerr != nil { + logger.Error("admin", "自动创建账户失败", "device", dev.DeviceID, "platform", platform, "err", aerr) + } else if created { + logger.Info("admin", "收款码自动续期已同步创建账户", "device", dev.DeviceID, "platform", platform) + } + q := &personal.DeviceQrcode{ + DeviceID: dev.DeviceID, + Platform: platform, + UserID: userID, + QRContent: content, + Account: h.resolveQrcodeAccount(ctx, dev, platform, userID, content), + ExpireIn: qrcodeExpireIn, + ExpireAt: store.ExpireAtStr(qrcodeExpireIn), + Status: "active", + CreatedAt: time.Now().Format(time.RFC3339), + } + if _, err := h.pdb.UpsertQrcode(ctx, q); err != nil { + logger.Error("admin", "续期收款码入库失败", "device", dev.DeviceID, "platform", platform, "user", userID, "err", err) + return + } + logger.Info("admin", "收款码自动续期完成", "device", dev.DeviceID, "platform", platform, "user", userID, + "account", q.Account, "content", capture.TruncateStr(content, 40)) +} + +// platformAccountName 按平台解析设备绑定账户名(微信/支付宝),并加上用户空间后缀(主空间/双开)。 +// 使管理后台能区分同一平台不同空间的收款码归属于哪个账户。 +func (h *PersonalHandler) platformAccountName(ctx context.Context, dev *personal.DeviceEncrypted, platform string, userID int) string { + var base string + switch platform { + case "wechat": + base = h.wechatAccountName(ctx, dev) + case "alipay": + base = h.alipayAccountName(ctx, dev) + default: + base = dev.Name + } + return spaceAccountName(base, userID) +} + +// spaceAccountName 账户名追加用户空间后缀(主空间/双开),区分同一平台不同空间的收款码归属。 +func spaceAccountName(base string, userID int) string { + spaceLabel := "主空间" + if userID == 999 { + spaceLabel = "双开" + } + return base + " (" + spaceLabel + ")" +} + +// resolveQrcodeAccount 自动识别捕获到的收款码归属账户。 +// 原理:每个 personal 账户在账户池中配置了收款码内容(QRContent), +// 用本次捕获到的二维码内容与设备绑定账户逐一比对,命中即确认账户身份, +// 从而「自动知道那个账户是那个空间的什么账户」。 +// 匹配失败时回退 platformAccountName(绑定顺序取第一个同平台账户)。 +func (h *PersonalHandler) resolveQrcodeAccount(ctx context.Context, dev *personal.DeviceEncrypted, platform string, userID int, qrContent string) string { + if dev == nil || qrContent == "" { + return "" + } + if h.pool != nil { + prefix := "personal.alipay" + if platform == "wechat" { + prefix = "personal.wechat" + } + // 附加账户优先,主账户兜底:同一平台可能绑定多个账户(主空间+双开各一个) + ids := store.DeviceAccountIDs(ctx, h.pdb, dev.ID, dev.AccountID) + for _, id := range ids { + acct := h.pool.GetByID(id) + if acct == nil || !strings.HasPrefix(acct.Channel, prefix) { + continue + } + if acct.QRContent == qrContent { + logger.Info("admin", "收款码账户自动识别", "device", dev.DeviceID, "platform", platform, + "user", userID, "account", acct.Name) + return spaceAccountName(acct.Name, userID) + } + } + logger.Warn("admin", "收款码账户未匹配,回退绑定账户", "device", dev.DeviceID, + "platform", platform, "user", userID, "content", capture.TruncateStr(qrContent, 30)) + } + return h.platformAccountName(ctx, dev, platform, userID) +} + +// CleanupQrcodes 定期清理: +// 1. DB 层删除过期收款码记录(每设备保留最新一条,防清空后无码可用) +// 2. 设备端相册清理:对每台在线设备保留最近 N 张微信收款码图片,删更旧的 +// +// 由 main 以固定间隔(默认 1 小时)调用。 +func (h *PersonalHandler) CleanupQrcodes(ctx context.Context) { + // 1. DB 清理 + if h.pdb != nil { + if n, err := h.pdb.DeleteExpiredQrcodes(ctx); err != nil { + logger.Error("admin", "收款码 DB 清理失败", "err", err) + } else if n > 0 { + logger.Info("admin", "已清理过期收款码记录", "count", n) + } + } + // 2. 设备端相册清理 + if h.adbMgr == nil { + return + } + devs, err := h.pdb.ListDevices(ctx) + if err != nil { + logger.Error("admin", "收款码相册清理: 获取设备列表失败", "err", err) + return + } + for _, dev := range devs { + serial, _, ok := h.pickOnlineSerial(ctx, dev) + if !ok { + continue // 设备离线,跳过(离线时相册不增长) + } + for _, userId := range []int{0, 999} { + if n := capture.CleanupWechatGallery(ctx, h.adbMgr, serial, userId); n > 0 { + logger.Info("admin", "已清理设备旧收款码图", "device", serial, "user", userId, "deleted", n) + } + if n := capture.CleanupAlipayGallery(ctx, h.adbMgr, serial, userId); n > 0 { + logger.Info("admin", "已清理设备旧支付宝收款码图", "device", serial, "user", userId, "deleted", n) + } + } + } +} diff --git a/internal/admin/qrcode_test.go b/internal/admin/qrcode_test.go new file mode 100755 index 0000000..5702c5b --- /dev/null +++ b/internal/admin/qrcode_test.go @@ -0,0 +1,72 @@ +package admin + +import ( + "strings" + "testing" + + qrcode "github.com/skip2/go-qrcode" + + "paygateway/internal/admin/capture" +) + +// TestDecodeQRContent 生成二维码 → 解析回原文(端到端验证解码链路) +func TestDecodeQRContent(t *testing.T) { + contents := []string{ + "https://qr.alipay.com/fkx12345abc", + "weixin://wxpay/bizpayurl?pr=AbCdEf123", + } + for _, want := range contents { + png, err := qrcode.Encode(want, qrcode.Medium, 256) + if err != nil { + t.Fatalf("生成二维码失败: %v", err) + } + got, err := capture.DecodeQRContent(png) + if err != nil { + t.Fatalf("解析二维码失败: %v", err) + } + if got != want { + t.Fatalf("解析内容不一致: want=%q got=%q", want, got) + } + } +} + +// TestDecodeQRContentInvalid 非二维码图片 → 报错 +func TestDecodeQRContentInvalid(t *testing.T) { + // 纯色 PNG(无二维码) + if _, err := capture.DecodeQRContent([]byte("not an image")); err == nil { + t.Fatal("非法输入应报错") + } + // 有效图片但无二维码 + png, _ := qrcode.Encode("x", qrcode.Medium, 64) + if _, err := capture.DecodeQRContent(png[:32]); err == nil { + t.Fatal("截断图片应报错") + } +} + +// TestDecodeQRContentEmpty 空输入 → 报错 +func TestDecodeQRContentEmpty(t *testing.T) { + if _, err := capture.DecodeQRContent(nil); err == nil { + t.Fatal("空输入应报错") + } + if _, err := capture.DecodeQRContent([]byte{}); err == nil { + t.Fatal("空输入应报错") + } +} + +// TestDecodeQRContentTooLarge 超 2MB → 报错 +func TestDecodeQRContentTooLarge(t *testing.T) { + big := make([]byte, capture.MaxQRUploadSize+1) + if _, err := capture.DecodeQRContent(big); err == nil { + t.Fatal("超大图片应报错") + } + if !strings.Contains(decodeErr(capture.DecodeQRContent(big)), "2MB") { + t.Fatal("错误信息应包含大小限制说明") + } +} + +func decodeErr(_ string, err error) string { + if err == nil { + return "" + } + return err.Error() +} diff --git a/internal/admin/qrcode_wechat.go b/internal/admin/qrcode_wechat.go new file mode 100644 index 0000000..38fc4a4 --- /dev/null +++ b/internal/admin/qrcode_wechat.go @@ -0,0 +1,128 @@ +// 微信收款码捕获 HTTP 层:设备查找 → 调用 capture 包自动化 → 去重 → 入库。 +package admin + +import ( + "context" + "net/http" + "os" + "strconv" + "strings" + "time" + + "github.com/gin-gonic/gin" + + "paygateway/internal/admin/capture" + "paygateway/internal/logger" + "paygateway/internal/personal" + "paygateway/internal/store" +) + +// handleCaptureWechatQrcode 通过服务器 adb 全自动捕获微信个人收款码 +// POST /api/personal/qrcodes/:deviceId/wechat/capture?user=0|999 +// 流程:唤醒 → 启动微信(可指定双开 user) → 导航到收付款页 → 切「个人收款码」→ 点「保存收款码」 +// +// → 相册拉最新 → 解码 → 与库中最新码去重 → 入库 +func (h *PersonalHandler) handleCaptureWechatQrcode(c *gin.Context) { + deviceID := c.Param("deviceId") + if deviceID == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"}) + return + } + if h.adbMgr == nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "设备控制台未启用(adb 管理器未配置)"}) + return + } + dev, err := h.pdb.GetDevice(c.Request.Context(), deviceID) + if err != nil || dev == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "设备不存在"}) + return + } + // 双开空间参数(默认 0 = 主空间;>0 如 999 = 小米 XSpace 分身) + userId, _ := strconv.Atoi(c.DefaultQuery("user", "0")) + // 找可用的 adb serial(USB 优先 → WiFi 备用) + serial, _, ok := h.pickOnlineSerial(c.Request.Context(), dev) + if !ok { + c.JSON(http.StatusBadGateway, gin.H{"error": "设备离线:所有 adb 通道不可用"}) + return + } + ctx, cancel := context.WithTimeout(c.Request.Context(), 90*time.Second) + defer cancel() + + // 核心捕获(自动化逻辑在 capture 包) + local, fileName, content, cerr := capture.CaptureWechatQrcode(ctx, h.adbMgr, serial, userId) + if cerr != nil { + c.JSON(http.StatusBadGateway, gin.H{"error": cerr.Error()}) + return + } + defer os.Remove(local) + + // 去重:与库中该设备最新码比对,相同则不再入库 + if latest, lerr := h.pdb.GetLatestQrcode(ctx, deviceID); lerr == nil && latest != nil && + latest.Platform == "wechat" && latest.QRContent == content && latest.Status == "active" { + logger.Info("admin", "微信收款码未变化,跳过入库", "device_id", deviceID, "file", fileName) + // 码未变不代表账户同步过:仍需确保账户池 QRContent 与码一致(历史不同步的补救路径) + if _, created, aerr := h.ensureAccountForQR(ctx, dev, "wechat", content, userId); aerr != nil { + logger.Error("admin", "同步账户池失败", "device_id", deviceID, "user", userId, "err", aerr) + } else if created { + logger.Info("admin", "已自动创建账户", "device_id", deviceID, "platform", "wechat") + } + if n := capture.CleanupWechatGallery(ctx, h.adbMgr, serial, userId); n > 0 { + logger.Info("admin", "已清理设备旧收款码图", "device", serial, "user", userId, "deleted", n) + } + c.JSON(http.StatusOK, gin.H{"data": gin.H{"ok": true, "unchanged": true, "content": content}}) + return + } + + // 入库(保存的静态码长期有效,7 天安全缓冲;手动 adb 捕获可随时刷新) + accountName := h.resolveQrcodeAccount(ctx, dev, "wechat", userId, content) + q := &personal.DeviceQrcode{ + DeviceID: dev.DeviceID, + Platform: "wechat", + UserID: userId, + QRContent: content, + Account: accountName, + ExpireIn: qrcodeExpireIn, + ExpireAt: store.ExpireAtStr(qrcodeExpireIn), + Status: "active", + CreatedAt: time.Now().Format(time.RFC3339), + } + if _, err := h.pdb.UpsertQrcode(c.Request.Context(), q); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "收款码入库失败: " + err.Error()}) + return + } + logger.Info("admin", "微信收款码已捕获", "device_id", dev.DeviceID, "user", userId, + "name", accountName, "file", fileName, "content", capture.TruncateStr(content, 40)) + // 同步账户池:码内容变了 → 更新绑定账户的 QRContent;无主码 → 自动建户(与监听端上报路径同逻辑) + if _, created, aerr := h.ensureAccountForQR(c.Request.Context(), dev, "wechat", content, userId); aerr != nil { + logger.Error("admin", "同步账户池失败(码已入库但账户未更新)", "device_id", dev.DeviceID, "user", userId, "err", aerr) + } else if created { + logger.Info("admin", "监听端上报已自动创建账户", "device_id", dev.DeviceID, "platform", "wechat") + } + // 每次捕获后顺带清理该空间相册旧码(保留最近 5 张),防频繁捕获打满存储 + if n := capture.CleanupWechatGallery(ctx, h.adbMgr, serial, userId); n > 0 { + logger.Info("admin", "已清理设备旧收款码图", "device", serial, "user", userId, "deleted", n) + } + c.JSON(http.StatusOK, gin.H{"data": gin.H{"ok": true, "user": userId, "file": fileName, "account": accountName, "content": content}}) +} + +// wechatAccountName 从设备绑定账户解析微信账户名(device_accounts 附加账户优先,主账户兜底)。 +// 识别依据是账户 channel=personal.wechat,不依赖相册路径。 +func (h *PersonalHandler) wechatAccountName(ctx context.Context, dev *personal.DeviceEncrypted) string { + if dev == nil { + return "" + } + if h.pool == nil { + return "" + } + if ids, err := h.pdb.ListDeviceAccounts(ctx, dev.ID); err == nil { + for _, id := range ids { + if acct := h.pool.GetByID(id); acct != nil && strings.HasPrefix(acct.Channel, "personal.wechat") { + return acct.Name + } + } + } + if acct := h.pool.GetByID(dev.AccountID); acct != nil && strings.HasPrefix(acct.Channel, "personal.wechat") { + return acct.Name + } + return dev.Name +} diff --git a/internal/admin/reconciler.go b/internal/admin/reconciler.go new file mode 100755 index 0000000..be22812 --- /dev/null +++ b/internal/admin/reconciler.go @@ -0,0 +1,347 @@ +package admin + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "strconv" + "time" + + "github.com/gin-gonic/gin" + "paygateway/internal/logger" + "paygateway/internal/model" + "paygateway/internal/order" + "paygateway/internal/reconciler" + "paygateway/internal/store" +) + +// ReconcilerHandler 容灾对账中心:人工异常池 + dead 通知管理 +type ReconcilerHandler struct { + store store.Store + svc *order.Service + sched *reconciler.Scheduler +} + +// NewReconcilerHandler 创建 +func NewReconcilerHandler(st store.Store, svc *order.Service, sched *reconciler.Scheduler) *ReconcilerHandler { + return &ReconcilerHandler{store: st, svc: svc, sched: sched} +} + +// RegisterRoutes 注册路由(需在 authMiddleware 组内) +func (h *ReconcilerHandler) RegisterRoutes(rg *gin.RouterGroup) { + rg.GET("/reconciler/exceptions", h.handleListExceptions) + rg.GET("/reconciler/exceptions/:id", h.handleGetException) + rg.POST("/reconciler/exceptions/:id/requery", h.handleRequery) + rg.POST("/reconciler/exceptions/:id/resolve", h.handleResolve) + rg.POST("/reconciler/exceptions/:id/reopen", h.handleReopen) + rg.POST("/reconciler/exceptions", h.handleManualAdd) // 手动加入异常池 + rg.GET("/reconciler/notify-dead", h.handleListDead) + rg.POST("/reconciler/notify-dead/:id/retry", h.handleRetryDead) + rg.POST("/reconciler/notify-dead/:id/delete", h.handleDeleteDead) + // 日终对账报告(RESILIENCE P2-1):GET /reconciler/daily-report?date=YYYY-MM-DD + rg.GET("/reconciler/daily-report", h.handleDailyReport) +} + +// ========== 异常池 ========== + +func (h *ReconcilerHandler) handleListExceptions(c *gin.Context) { + phase := c.Query("phase") + reason := c.Query("reason") + page, _ := strconv.Atoi(c.DefaultQuery("page", "1")) + pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) + if page < 1 { + page = 1 + } + if pageSize < 1 || pageSize > 100 { + pageSize = 20 + } + limit, offset := pageSize, (page-1)*pageSize + + list, err := h.store.ListExceptions(c.Request.Context(), phase, reason, limit, offset) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + total, err := h.store.CountExceptions(c.Request.Context(), phase) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"data": gin.H{"list": list, "total": total, "page": page}}) +} + +func (h *ReconcilerHandler) handleGetException(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + e, err := h.store.GetException(c.Request.Context(), id) + if err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "异常单不存在"}) + return + } + logs, err := h.store.ListExceptionLogs(c.Request.Context(), id, 100) + if err != nil { + logs = nil + } + c.JSON(http.StatusOK, gin.H{"data": gin.H{"exception": e, "logs": logs}}) +} + +// handleRequery 再次查单,刷新证据(证据自动收集,人工不可编辑) +func (h *ReconcilerHandler) handleRequery(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + e, err := h.store.GetException(c.Request.Context(), id) + if err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "异常单不存在"}) + return + } + st, qerr := h.sched.Query(c.Request.Context(), e.OutTradeNo) + now := time.Now().Format(time.RFC3339) + evidence := map[string]any{"action": "manual_requery", "time": now} + if qerr != nil { + evidence["error"] = qerr.Error() + evidence["channel_status"] = "QUERY_FAILED" + } else { + evidence["channel_status"] = st.Status + evidence["channel_tx_id"] = st.ChannelTxID + evidence["amount_minor"] = st.AmountMinor + } + evJSON, _ := json.Marshal(evidence) + + // 追加证据(存到 evidence,简单拼接时间线) + refreshedStatus := "" + if qerr != nil { + refreshedStatus = "QUERY_FAILED" + } else { + refreshedStatus = st.Status + } + _ = h.store.UpdateException(c.Request.Context(), id, func(up *model.ExceptionOrder) { + up.ChannelStatus = refreshedStatus + if qerr == nil && st.ChannelTxID != "" { + up.ChannelTxID = st.ChannelTxID + } + if up.Evidence != "" { + up.Evidence = up.Evidence + "\n" + string(evJSON) + } else { + up.Evidence = string(evJSON) + } + }) + operator := c.GetString("username") + _ = h.store.InsertExceptionLog(c.Request.Context(), id, "requery", operator, string(evJSON)) + c.JSON(http.StatusOK, gin.H{"data": gin.H{"channel_status": refreshedStatus, "evidence": evJSON}}) +} + +// handleResolve 人工处理:settle / close / noop +func (h *ReconcilerHandler) handleResolve(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + var req struct { + Action string `json:"action"` + PaidAmount int64 `json:"paid_amount"` + ChannelTxID string `json:"channel_tx_id"` + Payer string `json:"payer"` + Note string `json:"note"` + } + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误: " + err.Error()}) + return + } + if req.Action != model.ExActionSettle && req.Action != model.ExActionClose && req.Action != model.ExActionNoop { + c.JSON(http.StatusBadRequest, gin.H{"error": "动作必须是 settle/close/noop"}) + return + } + operator := c.GetString("username") + ctx := c.Request.Context() + + e, err := h.store.GetException(ctx, id) + if err != nil { + c.JSON(http.StatusNotFound, gin.H{"error": "异常单不存在"}) + return + } + if e.Phase == model.ExPhaseResolved { + c.JSON(http.StatusBadRequest, gin.H{"error": "该异常单已处理"}) + return + } + + // 并发防重:pending -> processing 条件更新(抢占锁) + locked, err := h.store.UpdateExceptionPhase(ctx, id, model.ExPhasePending, model.ExPhaseProcessing) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "加锁失败: " + err.Error()}) + return + } + if !locked { + // 锁未抢到:说明存在并发处理(另一个请求已把 pending → processing) + c.JSON(http.StatusConflict, gin.H{"error": "该异常单正在被处理或已被抢占,请刷新后重试"}) + return + } + if req.Action == model.ExActionSettle && req.PaidAmount < 0 { + _ = h.store.UpdateException(ctx, id, func(up *model.ExceptionOrder) { up.Phase = model.ExPhasePending }) + c.JSON(http.StatusBadRequest, gin.H{"error": "金额不能为负"}) + return + } + + var result string + switch req.Action { + case model.ExActionSettle: + ok, serr := h.svc.SettleManual(ctx, e.OutTradeNo, req.ChannelTxID, req.PaidAmount, req.Payer, operator, req.Note) + if serr != nil { + _ = h.store.UpdateException(ctx, id, func(up *model.ExceptionOrder) { up.Phase = model.ExPhasePending }) + c.JSON(http.StatusInternalServerError, gin.H{"error": "入账失败: " + serr.Error()}) + return + } + if !ok { + _ = h.store.UpdateException(ctx, id, func(up *model.ExceptionOrder) { up.Phase = model.ExPhasePending }) + c.JSON(http.StatusConflict, gin.H{"error": "订单状态不允许入账(可能已被处理)"}) + return + } + // 订单人工处理标记 + _ = h.store.UpdateOrderHandled(ctx, e.OutTradeNo, id, "manual_settle", operator, req.Note) + result = "已确认入账,并通知上游" + case model.ExActionClose: + ok, cerr := h.svc.CloseOrder(ctx, e.OutTradeNo, "manual_confirm_not_paid") + if cerr != nil { + _ = h.store.UpdateException(ctx, id, func(up *model.ExceptionOrder) { up.Phase = model.ExPhasePending }) + c.JSON(http.StatusInternalServerError, gin.H{"error": "关单失败: " + cerr.Error()}) + return + } + if !ok { + _ = h.store.UpdateException(ctx, id, func(up *model.ExceptionOrder) { up.Phase = model.ExPhasePending }) + c.JSON(http.StatusConflict, gin.H{"error": "订单状态不允许关闭(可能已支付)"}) + return + } + _ = h.store.UpdateOrderHandled(ctx, e.OutTradeNo, id, "manual_close", operator, req.Note) + result = "已确认未支付并关闭" + case model.ExActionNoop: + _ = h.store.UpdateOrderHandled(ctx, e.OutTradeNo, id, "noop", operator, req.Note) + result = "无需处理,已记录" + } + + _ = h.store.UpdateException(ctx, id, func(up *model.ExceptionOrder) { + up.Phase = model.ExPhaseResolved + up.ResolvedAction = req.Action + up.Handler = operator + up.HandlerNote = req.Note + if req.PaidAmount > 0 { + up.PaidAmount = req.PaidAmount + } + if req.ChannelTxID != "" { + up.ChannelTxID = req.ChannelTxID + } + up.ResolvedAt = time.Now().Format(time.RFC3339) + }) + detail := fmt.Sprintf("action=%s note=%q", req.Action, req.Note) + _ = h.store.InsertExceptionLog(ctx, id, "resolve", operator, detail) + logger.Warn("admin", "异常单人工处理完成", "exception_id", id, "out_trade_no", e.OutTradeNo, "action", req.Action, "operator", operator) + c.JSON(http.StatusOK, gin.H{"data": gin.H{"result": result}}) +} + +// handleReopen 重新打开异常单(处理后发现问题) +func (h *ReconcilerHandler) handleReopen(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + operator := c.GetString("username") + err := h.store.UpdateException(c.Request.Context(), id, func(up *model.ExceptionOrder) { + up.Phase = model.ExPhasePending + up.ResolvedAction = "" + up.ResolvedAt = "" + }) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + _ = h.store.InsertExceptionLog(c.Request.Context(), id, "reopen", operator, "") + c.JSON(http.StatusOK, gin.H{"data": "已重新打开"}) +} + +// handleManualAdd 手动加入异常池 +func (h *ReconcilerHandler) handleManualAdd(c *gin.Context) { + var req struct { + OutTradeNo string `json:"out_trade_no"` + Note string `json:"note"` + } + if err := c.ShouldBindJSON(&req); err != nil || req.OutTradeNo == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误: 需要 out_trade_no"}) + return + } + o, err := h.store.GetOrder(c.Request.Context(), req.OutTradeNo) + if err != nil || o == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "订单不存在"}) + return + } + h.svc.EnqueueException(c.Request.Context(), o, model.ExReasonManual, o.Status, "", "{}", req.Note) + operator := c.GetString("username") + // 记录 operator + if e, err := h.store.GetExceptionByOutTradeNo(c.Request.Context(), req.OutTradeNo); err == nil && e != nil { + _ = h.store.InsertExceptionLog(c.Request.Context(), e.ID, "manual_add", operator, req.Note) + } + c.JSON(http.StatusOK, gin.H{"data": "已加入异常池"}) +} + +// ========== dead 通知管理 ========== + +func (h *ReconcilerHandler) handleListDead(c *gin.Context) { + tasks, err := h.store.ListNotifyTasks(c.Request.Context(), "dead", 100) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"data": gin.H{"list": tasks, "total": len(tasks)}}) +} + +// handleRetryDead 重发 dead 通知(重置为 pending,重试计数清零)。 +// 重发前按订单当前 NotifyURL 重建 payload 并重新签名,解决「配置时回调地址错误 → 重发仍打旧地址 → 一直回调不了」。 +func (h *ReconcilerHandler) handleRetryDead(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + operator := c.GetString("username") + ctx := c.Request.Context() + + task, err := h.store.GetNotifyTask(ctx, id) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + if task == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "回调任务不存在"}) + return + } + // 用订单最新 NotifyURL 重建 payload;订单丢失/svc 缺失则保留原 payload(避免丢通知) + payload := task.Payload + if h.svc != nil { + if o, oerr := h.store.GetOrderByID(ctx, task.OrderID); oerr == nil && o != nil { + payload = h.svc.BuildNotifyPayload(o) + } + } + if err := h.store.UpdateNotifyTaskPayload(ctx, id, payload); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + if err := h.store.UpdateNotifyTask(ctx, id, "pending", 0, time.Now()); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + logger.Warn("admin", "dead 通知重发(已刷新为最新回调地址)", "task_id", id, "operator", operator) + c.JSON(http.StatusOK, gin.H{"data": "已按最新回调地址重新入队"}) +} + +// handleDeleteDead 彻底删除 dead 通知任务(确认放弃该笔通知;若订单已支付但未通知,删除后无法自动补发) +func (h *ReconcilerHandler) handleDeleteDead(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + operator := c.GetString("username") + if err := h.store.DeleteNotifyTask(c.Request.Context(), id); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + logger.Warn("admin", "dead 通知已删除", "task_id", id, "operator", operator) + c.JSON(http.StatusOK, gin.H{"data": "已删除"}) +} + +// ========== 日终对账报告(RESILIENCE P2-1) ========== + +// handleDailyReport 实时生成日终对账报告 +// GET /api/admin/reconciler/daily-report?date=YYYY-MM-DD(空=今天) +func (h *ReconcilerHandler) handleDailyReport(c *gin.Context) { + report, err := reconciler.BuildDailyReport(c.Request.Context(), h.store, c.Query("date")) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"data": report}) +} + +var _ = context.Background // keep import if unused diff --git a/internal/admin/static/assets/index-BT-lxyOz.css b/internal/admin/static/assets/index-BT-lxyOz.css new file mode 100644 index 0000000..45416bc --- /dev/null +++ b/internal/admin/static/assets/index-BT-lxyOz.css @@ -0,0 +1 @@ +@import"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;410;500;510;590;700&display=swap>";@import"https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&display=swap>";:root{--el-color-white:#fff;--el-color-black:#000;--el-color-primary-rgb:64, 158, 255;--el-color-success-rgb:103, 194, 58;--el-color-warning-rgb:230, 162, 60;--el-color-danger-rgb:245, 108, 108;--el-color-error-rgb:245, 108, 108;--el-color-info-rgb:144, 147, 153;--el-font-size-extra-large:20px;--el-font-size-large:18px;--el-font-size-medium:16px;--el-font-size-base:14px;--el-font-size-small:13px;--el-font-size-extra-small:12px;--el-font-family:"Helvetica Neue", Helvetica, "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", "微软雅黑", Arial, sans-serif;--el-font-weight-primary:500;--el-font-line-height-primary:24px;--el-index-normal:1;--el-index-top:1000;--el-index-popper:2000;--el-border-radius-base:4px;--el-border-radius-small:2px;--el-border-radius-round:20px;--el-border-radius-circle:100%;--el-transition-duration:.3s;--el-transition-duration-fast:.2s;--el-transition-function-ease-in-out-bezier:cubic-bezier(.645, .045, .355, 1);--el-transition-function-fast-bezier:cubic-bezier(.23, 1, .32, 1);--el-transition-all:all var(--el-transition-duration) var(--el-transition-function-ease-in-out-bezier);--el-transition-fade:opacity var(--el-transition-duration) var(--el-transition-function-fast-bezier);--el-transition-md-fade:transform var(--el-transition-duration) var(--el-transition-function-fast-bezier), opacity var(--el-transition-duration) var(--el-transition-function-fast-bezier);--el-transition-fade-linear:opacity var(--el-transition-duration-fast) linear;--el-transition-border:border-color var(--el-transition-duration-fast) var(--el-transition-function-ease-in-out-bezier);--el-transition-box-shadow:box-shadow var(--el-transition-duration-fast) var(--el-transition-function-ease-in-out-bezier);--el-transition-color:color var(--el-transition-duration-fast) var(--el-transition-function-ease-in-out-bezier);--el-component-size-large:40px;--el-component-size:32px;--el-component-size-small:24px;--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;--el-color-primary:#409eff;--el-color-primary-light-3:#79bbff;--el-color-primary-light-5:#a0cfff;--el-color-primary-light-7:#c6e2ff;--el-color-primary-light-8:#d9ecff;--el-color-primary-light-9:#ecf5ff;--el-color-primary-dark-2:#337ecc;--el-color-success:#67c23a;--el-color-success-light-3:#95d475;--el-color-success-light-5:#b3e19d;--el-color-success-light-7:#d1edc4;--el-color-success-light-8:#e1f3d8;--el-color-success-light-9:#f0f9eb;--el-color-success-dark-2:#529b2e;--el-color-warning:#e6a23c;--el-color-warning-light-3:#eebe77;--el-color-warning-light-5:#f3d19e;--el-color-warning-light-7:#f8e3c5;--el-color-warning-light-8:#faecd8;--el-color-warning-light-9:#fdf6ec;--el-color-warning-dark-2:#b88230;--el-color-danger:#f56c6c;--el-color-danger-light-3:#f89898;--el-color-danger-light-5:#fab6b6;--el-color-danger-light-7:#fcd3d3;--el-color-danger-light-8:#fde2e2;--el-color-danger-light-9:#fef0f0;--el-color-danger-dark-2:#c45656;--el-color-error:#f56c6c;--el-color-error-light-3:#f89898;--el-color-error-light-5:#fab6b6;--el-color-error-light-7:#fcd3d3;--el-color-error-light-8:#fde2e2;--el-color-error-light-9:#fef0f0;--el-color-error-dark-2:#c45656;--el-color-info:#909399;--el-color-info-light-3:#b1b3b8;--el-color-info-light-5:#c8c9cc;--el-color-info-light-7:#dedfe0;--el-color-info-light-8:#e9e9eb;--el-color-info-light-9:#f4f4f5;--el-color-info-dark-2:#73767a;--el-bg-color:#fff;--el-bg-color-page:#f2f3f5;--el-bg-color-overlay:#fff;--el-text-color-primary:#303133;--el-text-color-regular:#606266;--el-text-color-secondary:#909399;--el-text-color-placeholder:#a8abb2;--el-text-color-disabled:#c0c4cc;--el-border-color:#dcdfe6;--el-border-color-light:#e4e7ed;--el-border-color-lighter:#ebeef5;--el-border-color-extra-light:#f2f6fc;--el-border-color-dark:#d4d7de;--el-border-color-darker:#cdd0d6;--el-fill-color:#f0f2f5;--el-fill-color-light:#f5f7fa;--el-fill-color-lighter:#fafafa;--el-fill-color-extra-light:#fafcff;--el-fill-color-dark:#ebedf0;--el-fill-color-darker:#e6e8eb;--el-fill-color-blank:#fff;--el-box-shadow:0px 12px 32px 4px #0000000a, 0px 8px 20px #00000014;--el-box-shadow-light:0px 0px 12px #0000001f;--el-box-shadow-lighter:0px 0px 6px #0000001f;--el-box-shadow-dark:0px 16px 48px 16px #00000014, 0px 12px 32px #0000001f, 0px 8px 16px -8px #00000029;--el-disabled-bg-color:var(--el-fill-color-light);--el-disabled-text-color:var(--el-text-color-placeholder);--el-disabled-border-color:var(--el-border-color-light);--el-overlay-color:#000c;--el-overlay-color-light:#000000b3;--el-overlay-color-lighter:#00000080;--el-mask-color:#ffffffe6;--el-mask-color-extra-light:#ffffff4d;--el-border-width:1px;--el-border-style:solid;--el-border-color-hover:var(--el-text-color-disabled);--el-border:var(--el-border-width) var(--el-border-style) var(--el-border-color);--el-svg-monochrome-grey:var(--el-border-color)}.fade-in-linear-enter-active,.fade-in-linear-leave-active{transition:var(--el-transition-fade-linear)}.fade-in-linear-enter-from,.fade-in-linear-leave-to{opacity:0}.el-fade-in-linear-enter-active,.el-fade-in-linear-leave-active{transition:var(--el-transition-fade-linear)}.el-fade-in-linear-enter-from,.el-fade-in-linear-leave-to{opacity:0}.el-fade-in-enter-active,.el-fade-in-leave-active{transition:all var(--el-transition-duration) cubic-bezier(.55,0,.1,1)}.el-fade-in-enter-from,.el-fade-in-leave-active{opacity:0}.el-zoom-in-center-enter-active,.el-zoom-in-center-leave-active{transition:all var(--el-transition-duration) cubic-bezier(.55,0,.1,1)}.el-zoom-in-center-enter-from,.el-zoom-in-center-leave-active{opacity:0;transform:scaleX(0)}.el-zoom-in-top-enter-active,.el-zoom-in-top-leave-active{opacity:1;transition:var(--el-transition-md-fade);transform-origin:top;transform:scaleY(1)}.el-zoom-in-top-enter-active[data-popper-placement^=top],.el-zoom-in-top-leave-active[data-popper-placement^=top]{transform-origin:bottom}.el-zoom-in-top-enter-from,.el-zoom-in-top-leave-active{opacity:0;transform:scaleY(0)}.el-zoom-in-bottom-enter-active,.el-zoom-in-bottom-leave-active{opacity:1;transition:var(--el-transition-md-fade);transform-origin:bottom;transform:scaleY(1)}.el-zoom-in-bottom-enter-from,.el-zoom-in-bottom-leave-active{opacity:0;transform:scaleY(0)}.el-zoom-in-left-enter-active,.el-zoom-in-left-leave-active{opacity:1;transition:var(--el-transition-md-fade);transform-origin:0 0;transform:scale(1)}.el-zoom-in-left-enter-from,.el-zoom-in-left-leave-active{opacity:0;transform:scale(.45)}.collapse-transition{transition:var(--el-transition-duration) height ease-in-out,var(--el-transition-duration) padding-top ease-in-out,var(--el-transition-duration) padding-bottom ease-in-out}.el-collapse-transition-leave-active,.el-collapse-transition-enter-active{transition:var(--el-transition-duration) max-height ease-in-out,var(--el-transition-duration) padding-top ease-in-out,var(--el-transition-duration) padding-bottom ease-in-out}.horizontal-collapse-transition{transition:var(--el-transition-duration) width ease-in-out,var(--el-transition-duration) padding-left ease-in-out,var(--el-transition-duration) padding-right ease-in-out}.el-list-enter-active,.el-list-leave-active{transition:all 1s}.el-list-enter-from,.el-list-leave-to{opacity:0;transform:translateY(-30px)}.el-list-leave-active{position:absolute!important}.el-opacity-transition{transition:opacity var(--el-transition-duration) cubic-bezier(.55,0,.1,1)}.el-icon--right{margin-left:5px}.el-icon--left{margin-right:5px}@keyframes rotating{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.el-icon{--color:inherit;fill:currentColor;width:1em;height:1em;color:var(--color);line-height:1em;font-size:inherit;justify-content:center;align-items:center;display:inline-flex;position:relative}.el-icon.is-loading{animation:2s linear infinite rotating}.el-icon svg{width:1em;height:1em}.el-affix--fixed{position:fixed}.el-alert{--el-alert-padding:8px 16px;--el-alert-border-radius-base:var(--el-border-radius-base);--el-alert-title-font-size:14px;--el-alert-title-with-description-font-size:16px;--el-alert-description-font-size:14px;--el-alert-close-font-size:16px;--el-alert-close-customed-font-size:14px;--el-alert-icon-size:16px;--el-alert-icon-large-size:28px;width:100%;padding:var(--el-alert-padding);box-sizing:border-box;border-radius:var(--el-alert-border-radius-base);background-color:var(--el-color-white);opacity:1;transition:opacity var(--el-transition-duration-fast);align-items:center;margin:0;display:flex;position:relative;overflow:hidden}.el-alert.is-light .el-alert__close-btn{color:var(--el-text-color-placeholder)}.el-alert.is-dark .el-alert__close-btn,.el-alert.is-dark .el-alert__description{color:var(--el-color-white)}.el-alert.is-center{justify-content:center}.el-alert--primary{--el-alert-bg-color:var(--el-color-primary-light-9)}.el-alert--primary.is-light{background-color:var(--el-alert-bg-color);color:var(--el-color-primary)}.el-alert--primary.is-light .el-alert__description{color:var(--el-color-primary)}.el-alert--primary.is-dark{background-color:var(--el-color-primary);color:var(--el-color-white)}.el-alert--success{--el-alert-bg-color:var(--el-color-success-light-9)}.el-alert--success.is-light{background-color:var(--el-alert-bg-color);color:var(--el-color-success)}.el-alert--success.is-light .el-alert__description{color:var(--el-color-success)}.el-alert--success.is-dark{background-color:var(--el-color-success);color:var(--el-color-white)}.el-alert--info{--el-alert-bg-color:var(--el-color-info-light-9)}.el-alert--info.is-light{background-color:var(--el-alert-bg-color);color:var(--el-color-info)}.el-alert--info.is-light .el-alert__description{color:var(--el-color-info)}.el-alert--info.is-dark{background-color:var(--el-color-info);color:var(--el-color-white)}.el-alert--warning{--el-alert-bg-color:var(--el-color-warning-light-9)}.el-alert--warning.is-light{background-color:var(--el-alert-bg-color);color:var(--el-color-warning)}.el-alert--warning.is-light .el-alert__description{color:var(--el-color-warning)}.el-alert--warning.is-dark{background-color:var(--el-color-warning);color:var(--el-color-white)}.el-alert--error{--el-alert-bg-color:var(--el-color-error-light-9)}.el-alert--error.is-light{background-color:var(--el-alert-bg-color);color:var(--el-color-error)}.el-alert--error.is-light .el-alert__description{color:var(--el-color-error)}.el-alert--error.is-dark{background-color:var(--el-color-error);color:var(--el-color-white)}.el-alert__content{flex-direction:column;gap:4px;display:flex}.el-alert .el-alert__icon{font-size:var(--el-alert-icon-size);width:var(--el-alert-icon-size);margin-right:8px}.el-alert .el-alert__icon.is-big{font-size:var(--el-alert-icon-large-size);width:var(--el-alert-icon-large-size);margin-right:12px}.el-alert__title{font-size:var(--el-alert-title-font-size);line-height:24px}.el-alert__title.with-description{font-size:var(--el-alert-title-with-description-font-size)}.el-alert .el-alert__description{font-size:var(--el-alert-description-font-size);margin:0}.el-alert .el-alert__close-btn{font-size:var(--el-alert-close-font-size);opacity:1;cursor:pointer;position:absolute;top:12px;right:16px}.el-alert .el-alert__close-btn.is-customed{font-style:normal;font-size:var(--el-alert-close-customed-font-size);line-height:24px;top:8px}.el-alert-fade-enter-from,.el-alert-fade-leave-active{opacity:0}.el-aside{box-sizing:border-box;width:var(--el-aside-width,300px);flex-shrink:0;overflow:auto}.el-autocomplete{--el-input-text-color:var(--el-text-color-regular);--el-input-border:var(--el-border);--el-input-hover-border:var(--el-border-color-hover);--el-input-focus-border:var(--el-color-primary);--el-input-transparent-border:0 0 0 1px transparent inset;--el-input-border-color:var(--el-border-color);--el-input-border-radius:var(--el-border-radius-base);--el-input-bg-color:var(--el-fill-color-blank);--el-input-icon-color:var(--el-text-color-placeholder);--el-input-placeholder-color:var(--el-text-color-placeholder);--el-input-hover-border-color:var(--el-border-color-hover);--el-input-clear-hover-color:var(--el-text-color-secondary);--el-input-focus-border-color:var(--el-color-primary);--el-input-width:100%;width:var(--el-input-width);display:inline-block;position:relative}.el-autocomplete__popper.el-popper{background:var(--el-bg-color-overlay);border:1px solid var(--el-border-color-light);box-shadow:var(--el-box-shadow-light)}.el-autocomplete__popper.el-popper .el-popper__arrow:before{border:1px solid var(--el-border-color-light)}.el-autocomplete__popper.el-popper[data-popper-placement^=top] .el-popper__arrow:before{border-top-color:#0000;border-left-color:#0000}.el-autocomplete__popper.el-popper[data-popper-placement^=bottom] .el-popper__arrow:before{border-bottom-color:#0000;border-right-color:#0000}.el-autocomplete__popper.el-popper[data-popper-placement^=left] .el-popper__arrow:before{border-bottom-color:#0000;border-left-color:#0000}.el-autocomplete__popper.el-popper[data-popper-placement^=right] .el-popper__arrow:before{border-top-color:#0000;border-right-color:#0000}.el-autocomplete-suggestion{border-radius:var(--el-border-radius-base);box-sizing:border-box}.el-autocomplete-suggestion__header{border-bottom:1px solid var(--el-border-color-lighter);padding:10px}.el-autocomplete-suggestion__footer{border-top:1px solid var(--el-border-color-lighter);padding:10px}.el-autocomplete-suggestion__wrap{box-sizing:border-box;max-height:280px;padding:10px 0}.el-autocomplete-suggestion__list{margin:0;padding:0}.el-autocomplete-suggestion li{cursor:pointer;color:var(--el-text-color-regular);line-height:34px;font-size:var(--el-font-size-base);text-align:left;text-overflow:ellipsis;white-space:nowrap;margin:0;padding:0 20px;list-style:none;overflow:hidden}.el-autocomplete-suggestion li:hover,.el-autocomplete-suggestion li.highlighted{background-color:var(--el-fill-color-light)}.el-autocomplete-suggestion li.divider{border-top:1px solid var(--el-color-black);margin-top:6px}.el-autocomplete-suggestion li.divider:last-child{margin-bottom:-6px}.el-autocomplete-suggestion.is-loading li{cursor:default;height:100px;color:var(--el-text-color-secondary);justify-content:center;align-items:center;font-size:20px;display:flex}.el-autocomplete-suggestion.is-loading li:hover{background-color:var(--el-bg-color-overlay)}.el-avatar{--el-avatar-text-color:var(--el-color-white);--el-avatar-bg-color:var(--el-text-color-disabled);--el-avatar-text-size:14px;--el-avatar-icon-size:18px;--el-avatar-border-radius:var(--el-border-radius-base);--el-avatar-size-large:56px;--el-avatar-size:40px;--el-avatar-size-small:24px;box-sizing:border-box;text-align:center;color:var(--el-avatar-text-color);background:var(--el-avatar-bg-color);width:var(--el-avatar-size);height:var(--el-avatar-size);font-size:var(--el-avatar-text-size);outline:none;justify-content:center;align-items:center;display:inline-flex;overflow:hidden}.el-avatar>img{width:100%;height:100%;display:block}.el-avatar--circle{border-radius:50%}.el-avatar--square{border-radius:var(--el-avatar-border-radius)}.el-avatar--icon{font-size:var(--el-avatar-icon-size)}.el-avatar--small{--el-avatar-size:24px}.el-avatar--large{--el-avatar-size:56px}.el-avatar-group{--el-avatar-group-item-gap:-8px;--el-avatar-group-collapse-item-gap:4px;display:inline-flex}.el-avatar-group .el-avatar{border:1px solid var(--el-border-color-extra-light)}.el-avatar-group .el-avatar:not(:first-child){margin-left:var(--el-avatar-group-item-gap)}.el-avatar-group__collapse-avatars{--el-avatar-group-item-gap:-8px;--el-avatar-group-collapse-item-gap:4px}.el-avatar-group__collapse-avatars .el-avatar:not(:first-child){margin-left:var(--el-avatar-group-collapse-item-gap)}.el-backtop{--el-backtop-bg-color:var(--el-bg-color-overlay);--el-backtop-text-color:var(--el-color-primary);--el-backtop-hover-bg-color:var(--el-border-color-extra-light);background-color:var(--el-backtop-bg-color);width:40px;height:40px;color:var(--el-backtop-text-color);box-shadow:var(--el-box-shadow-lighter);cursor:pointer;z-index:5;border-radius:50%;justify-content:center;align-items:center;font-size:20px;display:flex;position:fixed}.el-backtop:hover{background-color:var(--el-backtop-hover-bg-color)}.el-backtop__icon{font-size:20px}.el-badge{--el-badge-bg-color:var(--el-color-danger);--el-badge-radius:10px;--el-badge-font-size:12px;--el-badge-padding:6px;--el-badge-size:18px;vertical-align:middle;width:-moz-fit-content;width:fit-content;display:inline-block;position:relative}.el-badge__content{background-color:var(--el-badge-bg-color);border-radius:var(--el-badge-radius);color:var(--el-color-white);font-size:var(--el-badge-font-size);height:var(--el-badge-size);padding:0 var(--el-badge-padding);white-space:nowrap;border:1px solid var(--el-bg-color);justify-content:center;align-items:center;display:inline-flex}.el-badge__content.is-fixed{top:0;right:calc(1px + var(--el-badge-size) / 2);z-index:var(--el-index-normal);position:absolute;transform:translateY(-50%)translate(100%)}.el-badge__content.is-fixed.is-dot{right:5px}.el-badge__content.is-dot{border-radius:50%;width:8px;height:8px;padding:0;right:0}.el-badge__content.is-hide-zero{display:none}.el-badge__content--primary{background-color:var(--el-color-primary)}.el-badge__content--success{background-color:var(--el-color-success)}.el-badge__content--warning{background-color:var(--el-color-warning)}.el-badge__content--info{background-color:var(--el-color-info)}.el-badge__content--danger{background-color:var(--el-color-danger)}.el-breadcrumb__separator{color:var(--el-text-color-placeholder);margin:0 9px;font-weight:700}.el-breadcrumb__separator.el-icon{margin:0 6px;font-weight:400}.el-breadcrumb__separator.el-icon svg{vertical-align:middle}.el-breadcrumb__item{float:left;align-items:center;display:inline-flex}.el-breadcrumb__inner{color:var(--el-text-color-regular)}.el-breadcrumb__inner.is-link,.el-breadcrumb__inner a{transition:var(--el-transition-color);color:var(--el-text-color-primary);font-weight:700;text-decoration:none}.el-breadcrumb__inner.is-link:hover,.el-breadcrumb__inner a:hover{color:var(--el-color-primary);cursor:pointer}.el-breadcrumb__item:last-child .el-breadcrumb__inner,.el-breadcrumb__item:last-child .el-breadcrumb__inner:hover,.el-breadcrumb__item:last-child .el-breadcrumb__inner a,.el-breadcrumb__item:last-child .el-breadcrumb__inner a:hover{color:var(--el-text-color-regular);cursor:text;font-weight:400}.el-breadcrumb__item:last-child .el-breadcrumb__separator{display:none}.el-breadcrumb{font-size:14px;line-height:1}.el-breadcrumb:before,.el-breadcrumb:after{content:"";display:table}.el-breadcrumb:after{clear:both}.el-button-group>.el-button+.el-button{margin-left:0}.el-button-group>.el-button:first-child:last-child{border-top-right-radius:var(--el-border-radius-base);border-bottom-right-radius:var(--el-border-radius-base);border-top-left-radius:var(--el-border-radius-base);border-bottom-left-radius:var(--el-border-radius-base)}.el-button-group>.el-button:first-child:last-child.is-round{border-radius:var(--el-border-radius-round)}.el-button-group>.el-button:first-child:last-child.is-circle{border-radius:50%}.el-button-group>.el-button:not(:first-child):not(:last-child){border-radius:0}.el-button-group>.el-button:hover,.el-button-group>.el-button:focus,.el-button-group>.el-button:active,.el-button-group>.el-button.is-active{z-index:1}.el-button-group--horizontal{vertical-align:middle;display:inline-block}.el-button-group--horizontal:before,.el-button-group--horizontal:after{content:"";display:table}.el-button-group--horizontal:after{clear:both}.el-button-group--horizontal>.el-button{float:left;position:relative}.el-button-group--horizontal>.el-button:first-child{border-top-right-radius:0;border-bottom-right-radius:0}.el-button-group--horizontal>.el-button:last-child{border-top-left-radius:0;border-bottom-left-radius:0}.el-button-group--horizontal>.el-button:not(:last-child){margin-right:-1px}.el-button-group--horizontal .el-button--primary:first-child{border-right-color:var(--el-button-divide-border-color)}.el-button-group--horizontal .el-button--primary:last-child{border-left-color:var(--el-button-divide-border-color)}.el-button-group--horizontal .el-button--primary:not(:first-child):not(:last-child){border-left-color:var(--el-button-divide-border-color);border-right-color:var(--el-button-divide-border-color)}.el-button-group--horizontal .el-button--success:first-child{border-right-color:var(--el-button-divide-border-color)}.el-button-group--horizontal .el-button--success:last-child{border-left-color:var(--el-button-divide-border-color)}.el-button-group--horizontal .el-button--success:not(:first-child):not(:last-child){border-left-color:var(--el-button-divide-border-color);border-right-color:var(--el-button-divide-border-color)}.el-button-group--horizontal .el-button--warning:first-child{border-right-color:var(--el-button-divide-border-color)}.el-button-group--horizontal .el-button--warning:last-child{border-left-color:var(--el-button-divide-border-color)}.el-button-group--horizontal .el-button--warning:not(:first-child):not(:last-child){border-left-color:var(--el-button-divide-border-color);border-right-color:var(--el-button-divide-border-color)}.el-button-group--horizontal .el-button--danger:first-child{border-right-color:var(--el-button-divide-border-color)}.el-button-group--horizontal .el-button--danger:last-child{border-left-color:var(--el-button-divide-border-color)}.el-button-group--horizontal .el-button--danger:not(:first-child):not(:last-child){border-left-color:var(--el-button-divide-border-color);border-right-color:var(--el-button-divide-border-color)}.el-button-group--horizontal .el-button--info:first-child{border-right-color:var(--el-button-divide-border-color)}.el-button-group--horizontal .el-button--info:last-child{border-left-color:var(--el-button-divide-border-color)}.el-button-group--horizontal .el-button--info:not(:first-child):not(:last-child){border-left-color:var(--el-button-divide-border-color);border-right-color:var(--el-button-divide-border-color)}.el-button-group--horizontal>.el-dropdown>.el-button{border-left-color:var(--el-button-divide-border-color);border-top-left-radius:0;border-bottom-left-radius:0}.el-button-group--vertical{flex-direction:column;align-items:stretch;display:inline-flex}.el-button-group--vertical>.el-button{margin-top:-1px}.el-button-group--vertical>.el-button:first-child{border-bottom-right-radius:0;border-bottom-left-radius:0}.el-button-group--vertical>.el-button:last-child{border-top-left-radius:0;border-top-right-radius:0}.el-button-group--vertical>.el-dropdown{margin-top:-1px}.el-button-group--vertical>.el-dropdown>.el-button{border-left-color:var(--el-button-divide-border-color);border-top-left-radius:0;border-top-right-radius:0}.el-button-group--vertical .el-button--primary:first-child{border-bottom-color:var(--el-button-divide-border-color)}.el-button-group--vertical .el-button--primary:last-child{border-top-color:var(--el-button-divide-border-color)}.el-button-group--vertical .el-button--primary:not(:first-child):not(:last-child){border-top-color:var(--el-button-divide-border-color);border-bottom-color:var(--el-button-divide-border-color)}.el-button-group--vertical .el-button--success:first-child{border-bottom-color:var(--el-button-divide-border-color)}.el-button-group--vertical .el-button--success:last-child{border-top-color:var(--el-button-divide-border-color)}.el-button-group--vertical .el-button--success:not(:first-child):not(:last-child){border-top-color:var(--el-button-divide-border-color);border-bottom-color:var(--el-button-divide-border-color)}.el-button-group--vertical .el-button--warning:first-child{border-bottom-color:var(--el-button-divide-border-color)}.el-button-group--vertical .el-button--warning:last-child{border-top-color:var(--el-button-divide-border-color)}.el-button-group--vertical .el-button--warning:not(:first-child):not(:last-child){border-top-color:var(--el-button-divide-border-color);border-bottom-color:var(--el-button-divide-border-color)}.el-button-group--vertical .el-button--danger:first-child{border-bottom-color:var(--el-button-divide-border-color)}.el-button-group--vertical .el-button--danger:last-child{border-top-color:var(--el-button-divide-border-color)}.el-button-group--vertical .el-button--danger:not(:first-child):not(:last-child){border-top-color:var(--el-button-divide-border-color);border-bottom-color:var(--el-button-divide-border-color)}.el-button-group--vertical .el-button--info:first-child{border-bottom-color:var(--el-button-divide-border-color)}.el-button-group--vertical .el-button--info:last-child{border-top-color:var(--el-button-divide-border-color)}.el-button-group--vertical .el-button--info:not(:first-child):not(:last-child){border-top-color:var(--el-button-divide-border-color);border-bottom-color:var(--el-button-divide-border-color)}.el-button{--el-button-font-weight:var(--el-font-weight-primary);--el-button-border-color:var(--el-border-color);--el-button-bg-color:var(--el-fill-color-blank);--el-button-text-color:var(--el-text-color-regular);--el-button-disabled-text-color:var(--el-disabled-text-color);--el-button-disabled-bg-color:var(--el-fill-color-blank);--el-button-disabled-border-color:var(--el-border-color-light);--el-button-divide-border-color:#ffffff80;--el-button-hover-text-color:var(--el-color-primary);--el-button-hover-bg-color:var(--el-color-primary-light-9);--el-button-hover-border-color:var(--el-color-primary-light-7);--el-button-active-text-color:var(--el-button-hover-text-color);--el-button-active-border-color:var(--el-color-primary);--el-button-active-bg-color:var(--el-button-hover-bg-color);--el-button-outline-color:var(--el-color-primary-light-5);--el-button-hover-link-text-color:var(--el-text-color-secondary);--el-button-active-color:var(--el-text-color-primary);white-space:nowrap;cursor:pointer;height:32px;color:var(--el-button-text-color);text-align:center;box-sizing:border-box;line-height:1;font-weight:var(--el-button-font-weight);-webkit-user-select:none;user-select:none;vertical-align:middle;-webkit-appearance:none;background-color:var(--el-button-bg-color);border:var(--el-border);border-color:var(--el-button-border-color);outline:none;justify-content:center;align-items:center;transition:all .1s;display:inline-flex}.el-button:hover{color:var(--el-button-hover-text-color);border-color:var(--el-button-hover-border-color);background-color:var(--el-button-hover-bg-color);outline:none}.el-button:active{color:var(--el-button-active-text-color);border-color:var(--el-button-active-border-color);background-color:var(--el-button-active-bg-color);outline:none}.el-button:focus-visible{outline:2px solid var(--el-button-outline-color);outline-offset:1px;transition:outline-offset,outline}.el-button>span{align-items:center;display:inline-flex}.el-button+.el-button{margin-left:12px}.el-button{font-size:var(--el-font-size-base);border-radius:var(--el-border-radius-base);padding:8px 15px}.el-button.is-round{padding:8px 15px}.el-button::-moz-focus-inner{border:0}.el-button [class*=el-icon]+span{margin-left:6px}.el-button [class*=el-icon] svg{vertical-align:bottom}.el-button.is-plain{--el-button-hover-text-color:var(--el-color-primary);--el-button-hover-bg-color:var(--el-fill-color-blank);--el-button-hover-border-color:var(--el-color-primary)}.el-button.is-active{color:var(--el-button-active-text-color);border-color:var(--el-button-active-border-color);background-color:var(--el-button-active-bg-color);outline:none}.el-button.is-disabled,.el-button.is-disabled:hover{color:var(--el-button-disabled-text-color);cursor:not-allowed;background-image:none;background-color:var(--el-button-disabled-bg-color);border-color:var(--el-button-disabled-border-color)}.el-button.is-loading{pointer-events:none;position:relative}.el-button.is-loading:before{z-index:1;pointer-events:none;content:"";border-radius:inherit;background-color:var(--el-mask-color-extra-light);position:absolute;top:-1px;bottom:-1px;left:-1px;right:-1px}.el-button.is-round{border-radius:var(--el-border-radius-round)}.el-button.is-dashed{--el-button-hover-text-color:var(--el-color-primary);--el-button-hover-bg-color:var(--el-fill-color-blank);--el-button-hover-border-color:var(--el-color-primary);border-style:dashed}.el-button.is-circle{border-radius:50%;width:32px;padding:8px}.el-button.is-text{color:var(--el-button-text-color);background-color:#0000;border:0 solid #0000}.el-button.is-text.is-disabled{color:var(--el-button-disabled-text-color);background-color:#0000!important}.el-button.is-text:not(.is-disabled):hover{background-color:var(--el-fill-color-light)}.el-button.is-text:not(.is-disabled):focus-visible{outline:2px solid var(--el-button-outline-color);outline-offset:1px;transition:outline-offset,outline}.el-button.is-text:not(.is-disabled):active{background-color:var(--el-fill-color)}.el-button.is-text:not(.is-disabled).is-has-bg{background-color:var(--el-fill-color-light)}.el-button.is-text:not(.is-disabled).is-has-bg:hover{background-color:var(--el-fill-color)}.el-button.is-text:not(.is-disabled).is-has-bg:active{background-color:var(--el-fill-color-dark)}.el-button__text--expand{letter-spacing:.3em;margin-right:-.3em}.el-button.is-link{color:var(--el-button-text-color);background:0 0;border-color:#0000;height:auto;padding:2px}.el-button.is-link:hover{color:var(--el-button-hover-link-text-color)}.el-button.is-link.is-disabled{color:var(--el-button-disabled-text-color);background-color:#0000!important;border-color:#0000!important}.el-button.is-link:not(.is-disabled):hover{background-color:#0000;border-color:#0000}.el-button.is-link:not(.is-disabled):active{color:var(--el-button-active-color);background-color:#0000;border-color:#0000}.el-button--text{color:var(--el-color-primary);background:0 0;border-color:#0000;padding-left:0;padding-right:0}.el-button--text.is-disabled{color:var(--el-button-disabled-text-color);background-color:#0000!important;border-color:#0000!important}.el-button--text:not(.is-disabled):hover{color:var(--el-color-primary-light-3);background-color:#0000;border-color:#0000}.el-button--text:not(.is-disabled):active{color:var(--el-color-primary-dark-2);background-color:#0000;border-color:#0000}.el-button__link--expand{letter-spacing:.3em;margin-right:-.3em}.el-button--primary{--el-button-text-color:var(--el-color-white);--el-button-bg-color:var(--el-color-primary);--el-button-border-color:var(--el-color-primary);--el-button-outline-color:var(--el-color-primary-light-5);--el-button-active-color:var(--el-color-primary-dark-2);--el-button-hover-text-color:var(--el-color-white);--el-button-hover-link-text-color:var(--el-color-primary-light-5);--el-button-hover-bg-color:var(--el-color-primary-light-3);--el-button-hover-border-color:var(--el-color-primary-light-3);--el-button-active-bg-color:var(--el-color-primary-dark-2);--el-button-active-border-color:var(--el-color-primary-dark-2);--el-button-disabled-text-color:var(--el-color-white);--el-button-disabled-bg-color:var(--el-color-primary-light-5);--el-button-disabled-border-color:var(--el-color-primary-light-5)}.el-button--primary.is-plain,.el-button--primary.is-text,.el-button--primary.is-link{--el-button-text-color:var(--el-color-primary);--el-button-bg-color:var(--el-color-primary-light-9);--el-button-border-color:var(--el-color-primary-light-5);--el-button-hover-text-color:var(--el-color-white);--el-button-hover-bg-color:var(--el-color-primary);--el-button-hover-border-color:var(--el-color-primary);--el-button-active-text-color:var(--el-color-white)}.el-button--primary.is-plain.is-disabled,.el-button--primary.is-plain.is-disabled:hover,.el-button--primary.is-plain.is-disabled:focus,.el-button--primary.is-plain.is-disabled:active,.el-button--primary.is-text.is-disabled,.el-button--primary.is-text.is-disabled:hover,.el-button--primary.is-text.is-disabled:focus,.el-button--primary.is-text.is-disabled:active,.el-button--primary.is-link.is-disabled,.el-button--primary.is-link.is-disabled:hover,.el-button--primary.is-link.is-disabled:focus,.el-button--primary.is-link.is-disabled:active{color:var(--el-color-primary-light-5);background-color:var(--el-color-primary-light-9);border-color:var(--el-color-primary-light-8)}.el-button--primary.is-dashed{--el-button-text-color:var(--el-color-primary);--el-button-bg-color:var(--el-color-primary-light-9);--el-button-border-color:var(--el-color-primary-light-5);--el-button-hover-text-color:var(--el-color-primary);--el-button-hover-bg-color:var(--el-color-primary-light-9);--el-button-hover-border-color:var(--el-color-primary-light-3);--el-button-active-text-color:var(--el-color-primary-dark-2);--el-button-active-bg-color:var(--el-color-primary-light-9);--el-button-active-border-color:var(--el-color-primary-dark-2)}.el-button--primary.is-dashed.is-disabled,.el-button--primary.is-dashed.is-disabled:hover,.el-button--primary.is-dashed.is-disabled:focus,.el-button--primary.is-dashed.is-disabled:active{color:var(--el-color-primary-light-5);background-color:var(--el-color-primary-light-9);border-color:var(--el-color-primary-light-8)}.el-button--success{--el-button-text-color:var(--el-color-white);--el-button-bg-color:var(--el-color-success);--el-button-border-color:var(--el-color-success);--el-button-outline-color:var(--el-color-success-light-5);--el-button-active-color:var(--el-color-success-dark-2);--el-button-hover-text-color:var(--el-color-white);--el-button-hover-link-text-color:var(--el-color-success-light-5);--el-button-hover-bg-color:var(--el-color-success-light-3);--el-button-hover-border-color:var(--el-color-success-light-3);--el-button-active-bg-color:var(--el-color-success-dark-2);--el-button-active-border-color:var(--el-color-success-dark-2);--el-button-disabled-text-color:var(--el-color-white);--el-button-disabled-bg-color:var(--el-color-success-light-5);--el-button-disabled-border-color:var(--el-color-success-light-5)}.el-button--success.is-plain,.el-button--success.is-text,.el-button--success.is-link{--el-button-text-color:var(--el-color-success);--el-button-bg-color:var(--el-color-success-light-9);--el-button-border-color:var(--el-color-success-light-5);--el-button-hover-text-color:var(--el-color-white);--el-button-hover-bg-color:var(--el-color-success);--el-button-hover-border-color:var(--el-color-success);--el-button-active-text-color:var(--el-color-white)}.el-button--success.is-plain.is-disabled,.el-button--success.is-plain.is-disabled:hover,.el-button--success.is-plain.is-disabled:focus,.el-button--success.is-plain.is-disabled:active,.el-button--success.is-text.is-disabled,.el-button--success.is-text.is-disabled:hover,.el-button--success.is-text.is-disabled:focus,.el-button--success.is-text.is-disabled:active,.el-button--success.is-link.is-disabled,.el-button--success.is-link.is-disabled:hover,.el-button--success.is-link.is-disabled:focus,.el-button--success.is-link.is-disabled:active{color:var(--el-color-success-light-5);background-color:var(--el-color-success-light-9);border-color:var(--el-color-success-light-8)}.el-button--success.is-dashed{--el-button-text-color:var(--el-color-success);--el-button-bg-color:var(--el-color-success-light-9);--el-button-border-color:var(--el-color-success-light-5);--el-button-hover-text-color:var(--el-color-success);--el-button-hover-bg-color:var(--el-color-success-light-9);--el-button-hover-border-color:var(--el-color-success-light-3);--el-button-active-text-color:var(--el-color-success-dark-2);--el-button-active-bg-color:var(--el-color-success-light-9);--el-button-active-border-color:var(--el-color-success-dark-2)}.el-button--success.is-dashed.is-disabled,.el-button--success.is-dashed.is-disabled:hover,.el-button--success.is-dashed.is-disabled:focus,.el-button--success.is-dashed.is-disabled:active{color:var(--el-color-success-light-5);background-color:var(--el-color-success-light-9);border-color:var(--el-color-success-light-8)}.el-button--warning{--el-button-text-color:var(--el-color-white);--el-button-bg-color:var(--el-color-warning);--el-button-border-color:var(--el-color-warning);--el-button-outline-color:var(--el-color-warning-light-5);--el-button-active-color:var(--el-color-warning-dark-2);--el-button-hover-text-color:var(--el-color-white);--el-button-hover-link-text-color:var(--el-color-warning-light-5);--el-button-hover-bg-color:var(--el-color-warning-light-3);--el-button-hover-border-color:var(--el-color-warning-light-3);--el-button-active-bg-color:var(--el-color-warning-dark-2);--el-button-active-border-color:var(--el-color-warning-dark-2);--el-button-disabled-text-color:var(--el-color-white);--el-button-disabled-bg-color:var(--el-color-warning-light-5);--el-button-disabled-border-color:var(--el-color-warning-light-5)}.el-button--warning.is-plain,.el-button--warning.is-text,.el-button--warning.is-link{--el-button-text-color:var(--el-color-warning);--el-button-bg-color:var(--el-color-warning-light-9);--el-button-border-color:var(--el-color-warning-light-5);--el-button-hover-text-color:var(--el-color-white);--el-button-hover-bg-color:var(--el-color-warning);--el-button-hover-border-color:var(--el-color-warning);--el-button-active-text-color:var(--el-color-white)}.el-button--warning.is-plain.is-disabled,.el-button--warning.is-plain.is-disabled:hover,.el-button--warning.is-plain.is-disabled:focus,.el-button--warning.is-plain.is-disabled:active,.el-button--warning.is-text.is-disabled,.el-button--warning.is-text.is-disabled:hover,.el-button--warning.is-text.is-disabled:focus,.el-button--warning.is-text.is-disabled:active,.el-button--warning.is-link.is-disabled,.el-button--warning.is-link.is-disabled:hover,.el-button--warning.is-link.is-disabled:focus,.el-button--warning.is-link.is-disabled:active{color:var(--el-color-warning-light-5);background-color:var(--el-color-warning-light-9);border-color:var(--el-color-warning-light-8)}.el-button--warning.is-dashed{--el-button-text-color:var(--el-color-warning);--el-button-bg-color:var(--el-color-warning-light-9);--el-button-border-color:var(--el-color-warning-light-5);--el-button-hover-text-color:var(--el-color-warning);--el-button-hover-bg-color:var(--el-color-warning-light-9);--el-button-hover-border-color:var(--el-color-warning-light-3);--el-button-active-text-color:var(--el-color-warning-dark-2);--el-button-active-bg-color:var(--el-color-warning-light-9);--el-button-active-border-color:var(--el-color-warning-dark-2)}.el-button--warning.is-dashed.is-disabled,.el-button--warning.is-dashed.is-disabled:hover,.el-button--warning.is-dashed.is-disabled:focus,.el-button--warning.is-dashed.is-disabled:active{color:var(--el-color-warning-light-5);background-color:var(--el-color-warning-light-9);border-color:var(--el-color-warning-light-8)}.el-button--danger{--el-button-text-color:var(--el-color-white);--el-button-bg-color:var(--el-color-danger);--el-button-border-color:var(--el-color-danger);--el-button-outline-color:var(--el-color-danger-light-5);--el-button-active-color:var(--el-color-danger-dark-2);--el-button-hover-text-color:var(--el-color-white);--el-button-hover-link-text-color:var(--el-color-danger-light-5);--el-button-hover-bg-color:var(--el-color-danger-light-3);--el-button-hover-border-color:var(--el-color-danger-light-3);--el-button-active-bg-color:var(--el-color-danger-dark-2);--el-button-active-border-color:var(--el-color-danger-dark-2);--el-button-disabled-text-color:var(--el-color-white);--el-button-disabled-bg-color:var(--el-color-danger-light-5);--el-button-disabled-border-color:var(--el-color-danger-light-5)}.el-button--danger.is-plain,.el-button--danger.is-text,.el-button--danger.is-link{--el-button-text-color:var(--el-color-danger);--el-button-bg-color:var(--el-color-danger-light-9);--el-button-border-color:var(--el-color-danger-light-5);--el-button-hover-text-color:var(--el-color-white);--el-button-hover-bg-color:var(--el-color-danger);--el-button-hover-border-color:var(--el-color-danger);--el-button-active-text-color:var(--el-color-white)}.el-button--danger.is-plain.is-disabled,.el-button--danger.is-plain.is-disabled:hover,.el-button--danger.is-plain.is-disabled:focus,.el-button--danger.is-plain.is-disabled:active,.el-button--danger.is-text.is-disabled,.el-button--danger.is-text.is-disabled:hover,.el-button--danger.is-text.is-disabled:focus,.el-button--danger.is-text.is-disabled:active,.el-button--danger.is-link.is-disabled,.el-button--danger.is-link.is-disabled:hover,.el-button--danger.is-link.is-disabled:focus,.el-button--danger.is-link.is-disabled:active{color:var(--el-color-danger-light-5);background-color:var(--el-color-danger-light-9);border-color:var(--el-color-danger-light-8)}.el-button--danger.is-dashed{--el-button-text-color:var(--el-color-danger);--el-button-bg-color:var(--el-color-danger-light-9);--el-button-border-color:var(--el-color-danger-light-5);--el-button-hover-text-color:var(--el-color-danger);--el-button-hover-bg-color:var(--el-color-danger-light-9);--el-button-hover-border-color:var(--el-color-danger-light-3);--el-button-active-text-color:var(--el-color-danger-dark-2);--el-button-active-bg-color:var(--el-color-danger-light-9);--el-button-active-border-color:var(--el-color-danger-dark-2)}.el-button--danger.is-dashed.is-disabled,.el-button--danger.is-dashed.is-disabled:hover,.el-button--danger.is-dashed.is-disabled:focus,.el-button--danger.is-dashed.is-disabled:active{color:var(--el-color-danger-light-5);background-color:var(--el-color-danger-light-9);border-color:var(--el-color-danger-light-8)}.el-button--info{--el-button-text-color:var(--el-color-white);--el-button-bg-color:var(--el-color-info);--el-button-border-color:var(--el-color-info);--el-button-outline-color:var(--el-color-info-light-5);--el-button-active-color:var(--el-color-info-dark-2);--el-button-hover-text-color:var(--el-color-white);--el-button-hover-link-text-color:var(--el-color-info-light-5);--el-button-hover-bg-color:var(--el-color-info-light-3);--el-button-hover-border-color:var(--el-color-info-light-3);--el-button-active-bg-color:var(--el-color-info-dark-2);--el-button-active-border-color:var(--el-color-info-dark-2);--el-button-disabled-text-color:var(--el-color-white);--el-button-disabled-bg-color:var(--el-color-info-light-5);--el-button-disabled-border-color:var(--el-color-info-light-5)}.el-button--info.is-plain,.el-button--info.is-text,.el-button--info.is-link{--el-button-text-color:var(--el-color-info);--el-button-bg-color:var(--el-color-info-light-9);--el-button-border-color:var(--el-color-info-light-5);--el-button-hover-text-color:var(--el-color-white);--el-button-hover-bg-color:var(--el-color-info);--el-button-hover-border-color:var(--el-color-info);--el-button-active-text-color:var(--el-color-white)}.el-button--info.is-plain.is-disabled,.el-button--info.is-plain.is-disabled:hover,.el-button--info.is-plain.is-disabled:focus,.el-button--info.is-plain.is-disabled:active,.el-button--info.is-text.is-disabled,.el-button--info.is-text.is-disabled:hover,.el-button--info.is-text.is-disabled:focus,.el-button--info.is-text.is-disabled:active,.el-button--info.is-link.is-disabled,.el-button--info.is-link.is-disabled:hover,.el-button--info.is-link.is-disabled:focus,.el-button--info.is-link.is-disabled:active{color:var(--el-color-info-light-5);background-color:var(--el-color-info-light-9);border-color:var(--el-color-info-light-8)}.el-button--info.is-dashed{--el-button-text-color:var(--el-color-info);--el-button-bg-color:var(--el-color-info-light-9);--el-button-border-color:var(--el-color-info-light-5);--el-button-hover-text-color:var(--el-color-info);--el-button-hover-bg-color:var(--el-color-info-light-9);--el-button-hover-border-color:var(--el-color-info-light-3);--el-button-active-text-color:var(--el-color-info-dark-2);--el-button-active-bg-color:var(--el-color-info-light-9);--el-button-active-border-color:var(--el-color-info-dark-2)}.el-button--info.is-dashed.is-disabled,.el-button--info.is-dashed.is-disabled:hover,.el-button--info.is-dashed.is-disabled:focus,.el-button--info.is-dashed.is-disabled:active{color:var(--el-color-info-light-5);background-color:var(--el-color-info-light-9);border-color:var(--el-color-info-light-8)}.el-button--large{--el-button-size:40px;height:var(--el-button-size)}.el-button--large [class*=el-icon]+span{margin-left:8px}.el-button--large{font-size:var(--el-font-size-base);border-radius:var(--el-border-radius-base);padding:12px 19px}.el-button--large.is-round{padding:12px 19px}.el-button--large.is-circle{width:var(--el-button-size);padding:12px}.el-button--small{--el-button-size:24px;height:var(--el-button-size)}.el-button--small [class*=el-icon]+span{margin-left:4px}.el-button--small{border-radius:calc(var(--el-border-radius-base) - 1px);padding:5px 11px;font-size:12px}.el-button--small.is-round{padding:5px 11px}.el-button--small.is-circle{width:var(--el-button-size);padding:5px}.el-calendar{--el-calendar-border:var(--el-table-border,1px solid var(--el-border-color-lighter));--el-calendar-header-border-bottom:var(--el-calendar-border);--el-calendar-selected-bg-color:var(--el-color-primary-light-9);--el-calendar-cell-width:85px;background-color:var(--el-fill-color-blank)}.el-calendar__header{border-bottom:var(--el-calendar-header-border-bottom);justify-content:space-between;padding:12px 20px;display:flex}.el-calendar__title{color:var(--el-text-color);align-self:center}.el-calendar__body{padding:12px 20px 35px}.el-calendar__select-controller .el-select{margin-right:8px}.el-calendar__select-controller .el-calendar-select__year{width:120px}.el-calendar__select-controller .el-calendar-select__month{width:60px}.el-calendar-table{table-layout:fixed;width:100%}.el-calendar-table thead th{color:var(--el-text-color-regular);padding:12px 0;font-weight:400}.el-calendar-table:not(.is-range) td.prev,.el-calendar-table:not(.is-range) td.next{color:var(--el-text-color-placeholder)}.el-calendar-table td{border-bottom:var(--el-calendar-border);border-right:var(--el-calendar-border);vertical-align:top;transition:background-color var(--el-transition-duration-fast) ease}.el-calendar-table td.is-selected{background-color:var(--el-calendar-selected-bg-color)}.el-calendar-table td.is-today{color:var(--el-color-primary)}.el-calendar-table tr:first-child td{border-top:var(--el-calendar-border)}.el-calendar-table tr td:first-child{border-left:var(--el-calendar-border)}.el-calendar-table tr.el-calendar-table__row--hide-border td{border-top:none}.el-calendar-table .el-calendar-day{box-sizing:border-box;height:var(--el-calendar-cell-width);padding:8px}.el-calendar-table .el-calendar-day:hover{cursor:pointer;background-color:var(--el-calendar-selected-bg-color)}.el-card{--el-card-border-color:var(--el-border-color-light);--el-card-border-radius:4px;--el-card-padding:20px;--el-card-bg-color:var(--el-fill-color-blank);border-radius:var(--el-card-border-radius);border:1px solid var(--el-card-border-color);background-color:var(--el-card-bg-color);color:var(--el-text-color-primary);transition:var(--el-transition-duration);flex-direction:column;display:flex;overflow:hidden}.el-card.is-always-shadow,.el-card.is-hover-shadow:hover,.el-card.is-hover-shadow:focus{box-shadow:var(--el-box-shadow-light)}.el-card__header{padding:calc(var(--el-card-padding) - 2px) var(--el-card-padding);border-bottom:1px solid var(--el-card-border-color);box-sizing:border-box}.el-card__body{padding:var(--el-card-padding);flex-grow:1;overflow:auto}.el-card__footer{padding:calc(var(--el-card-padding) - 2px) var(--el-card-padding);border-top:1px solid var(--el-card-border-color);box-sizing:border-box}.el-carousel__item{width:100%;height:100%;z-index:calc(var(--el-index-normal) - 1);display:inline-block;position:absolute;top:0;left:0;overflow:hidden}.el-carousel__item.is-active{z-index:calc(var(--el-index-normal) - 1)}.el-carousel__item.is-animating{transition:transform .4s ease-in-out}.el-carousel__item--card{width:50%;transition:transform .4s ease-in-out}.el-carousel__item--card.is-in-stage{cursor:pointer;z-index:var(--el-index-normal)}.el-carousel__item--card.is-in-stage:hover .el-carousel__mask,.el-carousel__item--card.is-in-stage.is-hover .el-carousel__mask{opacity:.12}.el-carousel__item--card.is-active{z-index:calc(var(--el-index-normal) + 1)}.el-carousel__item--card-vertical{width:100%;height:50%}.el-carousel__mask{background-color:var(--el-color-white);opacity:.24;width:100%;height:100%;transition:var(--el-transition-duration-fast);position:absolute;top:0;left:0}.el-carousel{--el-carousel-arrow-font-size:12px;--el-carousel-arrow-size:36px;--el-carousel-arrow-background:#1f2d3d1c;--el-carousel-arrow-hover-background:#1f2d3d3b;--el-carousel-indicator-width:30px;--el-carousel-indicator-height:2px;--el-carousel-indicator-padding-horizontal:4px;--el-carousel-indicator-padding-vertical:12px;--el-carousel-indicator-out-color:var(--el-border-color-hover);position:relative}.el-carousel--horizontal,.el-carousel--vertical{overflow:hidden}.el-carousel.is-vertical-outside{flex-direction:row;align-items:center;display:flex}.el-carousel.is-vertical-outside .el-carousel__container{flex:1}.el-carousel__container{height:300px;position:relative}.el-carousel__arrow{height:var(--el-carousel-arrow-size);width:var(--el-carousel-arrow-size);cursor:pointer;transition:var(--el-transition-duration);background-color:var(--el-carousel-arrow-background);color:#fff;z-index:10;text-align:center;font-size:var(--el-carousel-arrow-font-size);border:none;border-radius:50%;outline:none;justify-content:center;align-items:center;margin:0;padding:0;display:inline-flex;position:absolute;top:50%;transform:translateY(-50%)}.el-carousel__arrow--left{left:16px}.el-carousel__arrow--right{right:16px}.el-carousel__arrow:hover{background-color:var(--el-carousel-arrow-hover-background)}.el-carousel__arrow i{cursor:pointer}.el-carousel__indicators{z-index:calc(var(--el-index-normal) + 1);margin:0;padding:0;list-style:none;position:absolute}.el-carousel__indicators--horizontal{bottom:0;left:50%;transform:translate(-50%)}.el-carousel__indicators--vertical{top:50%;right:0;transform:translateY(-50%)}.el-carousel__indicators--outside{text-align:center;position:static;transform:none}.el-carousel__indicators--outside .el-carousel__indicator:hover button{opacity:.64}.el-carousel__indicators--outside button{background-color:var(--el-carousel-indicator-out-color);opacity:.24}.el-carousel__indicators--right{right:0}.el-carousel__indicators--labels .el-carousel__button{color:#000;padding:2px 18px;font-size:12px}.el-carousel__indicators--labels .el-carousel__indicator{padding:6px 4px}.el-carousel__indicator{cursor:pointer;background-color:#0000}.el-carousel__indicator:hover button{opacity:.72}.el-carousel__indicator--horizontal{padding:var(--el-carousel-indicator-padding-vertical) var(--el-carousel-indicator-padding-horizontal);display:inline-block}.el-carousel__indicator--vertical{padding:var(--el-carousel-indicator-padding-horizontal) var(--el-carousel-indicator-padding-vertical)}.el-carousel__indicator--vertical .el-carousel__button{width:var(--el-carousel-indicator-height);height:calc(var(--el-carousel-indicator-width) / 2)}.el-carousel__indicator.is-active button{opacity:1}.el-carousel__button{opacity:.48;width:var(--el-carousel-indicator-width);height:var(--el-carousel-indicator-height);cursor:pointer;transition:var(--el-transition-duration);background-color:#fff;border:none;outline:none;margin:0;padding:0;display:block}.el-carousel__indicators--labels .el-carousel__button{width:auto;height:auto}.carousel-arrow-left-enter-from,.carousel-arrow-left-leave-active{opacity:0;transform:translateY(-50%)translate(-10px)}.carousel-arrow-right-enter-from,.carousel-arrow-right-leave-active{opacity:0;transform:translateY(-50%)translate(10px)}.el-transitioning{filter:url(#elCarouselHorizontal)}.el-transitioning-vertical{filter:url(#elCarouselVertical)}.el-cascader-panel{--el-cascader-menu-text-color:var(--el-text-color-regular);--el-cascader-menu-selected-text-color:var(--el-color-primary);--el-cascader-menu-fill:var(--el-bg-color-overlay);--el-cascader-menu-font-size:var(--el-font-size-base);--el-cascader-menu-radius:var(--el-border-radius-base);--el-cascader-menu-border:solid 1px var(--el-border-color-light);--el-cascader-menu-shadow:var(--el-box-shadow-light);--el-cascader-node-background-hover:var(--el-fill-color-light);--el-cascader-node-color-disabled:var(--el-text-color-placeholder);--el-cascader-color-empty:var(--el-text-color-placeholder);--el-cascader-tag-background:var(--el-fill-color);border-radius:var(--el-cascader-menu-radius);width:-moz-fit-content;width:fit-content;font-size:var(--el-cascader-menu-font-size);display:flex}.el-cascader-panel.is-bordered{border:var(--el-cascader-menu-border);border-radius:var(--el-cascader-menu-radius)}.el-cascader-menu{box-sizing:border-box;min-width:180px;color:var(--el-cascader-menu-text-color);border-right:var(--el-cascader-menu-border)}.el-cascader-menu:last-child{border-right:none}.el-cascader-menu:last-child .el-cascader-node{padding-right:20px}.el-cascader-menu__wrap.el-scrollbar__wrap{height:204px}.el-cascader-menu__list{box-sizing:border-box;min-height:100%;margin:0;padding:6px 0;list-style:none;position:relative}.el-cascader-menu__list.el-vl__window{margin:6px 0;padding:0}.el-cascader-menu__list.el-vl__window ul{margin:0;padding:0}.el-cascader-menu__hover-zone{pointer-events:none;width:100%;height:100%;position:absolute;top:0;left:0}.el-cascader-menu__empty-text{color:var(--el-cascader-color-empty);align-items:center;display:flex;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.el-cascader-menu__empty-text .is-loading{margin-right:2px}.el-cascader-node{box-sizing:border-box;outline:none;align-items:center;height:34px;padding:0 30px 0 20px;line-height:34px;display:flex;position:relative}.el-cascader-node.is-selectable.in-active-path{color:var(--el-cascader-menu-text-color)}.el-cascader-node.in-active-path,.el-cascader-node.is-selectable.in-checked-path,.el-cascader-node.is-active{color:var(--el-cascader-menu-selected-text-color);font-weight:700}.el-cascader-node:not(.is-disabled){cursor:pointer}.el-cascader-node:not(.is-disabled):hover,.el-cascader-node:not(.is-disabled):focus{background:var(--el-cascader-node-background-hover)}.el-cascader-node.is-disabled{color:var(--el-cascader-node-color-disabled);cursor:not-allowed}.el-cascader-node__prefix{position:absolute;left:10px}.el-cascader-node__postfix{position:absolute;right:10px}.el-cascader-node__label{text-align:left;white-space:nowrap;text-overflow:ellipsis;flex:1;padding:0 8px;overflow:hidden}.el-cascader-node>.el-checkbox,.el-cascader-node>.el-radio{margin-right:0}.el-cascader-node>.el-radio .el-radio__label{padding-left:0}.el-cascader{--el-cascader-menu-text-color:var(--el-text-color-regular);--el-cascader-menu-selected-text-color:var(--el-color-primary);--el-cascader-menu-fill:var(--el-bg-color-overlay);--el-cascader-menu-font-size:var(--el-font-size-base);--el-cascader-menu-radius:var(--el-border-radius-base);--el-cascader-menu-border:solid 1px var(--el-border-color-light);--el-cascader-menu-shadow:var(--el-box-shadow-light);--el-cascader-node-background-hover:var(--el-fill-color-light);--el-cascader-node-color-disabled:var(--el-text-color-placeholder);--el-cascader-color-empty:var(--el-text-color-placeholder);--el-cascader-tag-background:var(--el-fill-color);vertical-align:middle;font-size:var(--el-font-size-base);outline:none;line-height:32px;display:inline-block;position:relative}.el-cascader:not(.is-disabled):hover .el-input__wrapper{cursor:pointer;box-shadow:0 0 0 1px var(--el-input-hover-border-color) inset}.el-cascader .el-input{cursor:pointer;display:flex}.el-cascader .el-input .el-input__inner{text-overflow:ellipsis}.el-cascader .el-input .el-input__inner:read-only{cursor:pointer}.el-cascader .el-input .el-input__inner:disabled{cursor:not-allowed}.el-cascader .el-input .el-input__suffix-inner .el-icon svg{vertical-align:middle}.el-cascader .el-input .icon-arrow-down{transition:transform var(--el-transition-duration);font-size:14px}.el-cascader .el-input .icon-arrow-down.is-reverse{transform:rotate(180deg)}.el-cascader .el-input .icon-circle-close:hover{color:var(--el-input-clear-hover-color,var(--el-text-color-secondary))}.el-cascader .el-input.is-focus .el-input__wrapper{box-shadow:0 0 0 1px var(--el-input-focus-border-color,var(--el-color-primary)) inset}.el-cascader--large{font-size:14px;line-height:40px}.el-cascader--large .el-cascader__tags{gap:6px;padding:8px}.el-cascader--large .el-cascader__search-input{height:24px;margin-left:7px}.el-cascader--small{font-size:12px;line-height:24px}.el-cascader--small .el-cascader__tags{gap:4px;padding:2px}.el-cascader--small .el-cascader__search-input{height:20px;margin-left:5px}.el-cascader.is-disabled .el-cascader__label{z-index:calc(var(--el-index-normal) + 1);color:var(--el-disabled-text-color)}.el-cascader__dropdown{--el-cascader-menu-text-color:var(--el-text-color-regular);--el-cascader-menu-selected-text-color:var(--el-color-primary);--el-cascader-menu-fill:var(--el-bg-color-overlay);--el-cascader-menu-font-size:var(--el-font-size-base);--el-cascader-menu-radius:var(--el-border-radius-base);--el-cascader-menu-border:solid 1px var(--el-border-color-light);--el-cascader-menu-shadow:var(--el-box-shadow-light);--el-cascader-node-background-hover:var(--el-fill-color-light);--el-cascader-node-color-disabled:var(--el-text-color-placeholder);--el-cascader-color-empty:var(--el-text-color-placeholder);--el-cascader-tag-background:var(--el-fill-color);font-size:var(--el-cascader-menu-font-size);border-radius:var(--el-cascader-menu-radius)}.el-cascader__dropdown.el-popper{background:var(--el-cascader-menu-fill);border:var(--el-cascader-menu-border);box-shadow:var(--el-cascader-menu-shadow)}.el-cascader__dropdown.el-popper .el-popper__arrow:before{border:var(--el-cascader-menu-border)}.el-cascader__dropdown.el-popper[data-popper-placement^=top] .el-popper__arrow:before{border-top-color:#0000;border-left-color:#0000}.el-cascader__dropdown.el-popper[data-popper-placement^=bottom] .el-popper__arrow:before{border-bottom-color:#0000;border-right-color:#0000}.el-cascader__dropdown.el-popper[data-popper-placement^=left] .el-popper__arrow:before{border-bottom-color:#0000;border-left-color:#0000}.el-cascader__dropdown.el-popper[data-popper-placement^=right] .el-popper__arrow:before{border-top-color:#0000;border-right-color:#0000}.el-cascader__dropdown.el-popper{box-shadow:var(--el-cascader-menu-shadow)}.el-cascader__header{border-bottom:1px solid var(--el-border-color-light);padding:10px}.el-cascader__footer{border-top:1px solid var(--el-border-color-light);padding:10px}.el-cascader__tags{text-align:left;box-sizing:border-box;flex-wrap:wrap;gap:6px;padding:4px;line-height:normal;display:flex;position:absolute;top:50%;left:0;right:30px;transform:translateY(-50%)}.el-cascader__tags .el-tag{text-overflow:ellipsis;background:var(--el-cascader-tag-background);align-items:center;max-width:100%;display:inline-flex}.el-cascader__tags .el-tag.el-tag--dark,.el-cascader__tags .el-tag.el-tag--plain{background-color:var(--el-tag-bg-color)}.el-cascader__tags .el-tag:not(.is-hit){border-color:#0000}.el-cascader__tags .el-tag:not(.is-hit).el-tag--dark,.el-cascader__tags .el-tag:not(.is-hit).el-tag--plain{border-color:var(--el-tag-border-color)}.el-cascader__tags .el-tag>span{text-overflow:ellipsis;flex:1;line-height:normal;overflow:hidden}.el-cascader__tags .el-tag .el-icon-close{background-color:var(--el-text-color-placeholder);color:var(--el-color-white);flex:none}.el-cascader__tags .el-tag .el-icon-close:hover{background-color:var(--el-text-color-secondary)}.el-cascader__tags .el-tag+input{margin-left:0}.el-cascader__tags.is-validate{right:55px}.el-cascader__collapse-tags{white-space:normal;z-index:var(--el-index-normal)}.el-cascader__collapse-tags .el-tag{text-overflow:ellipsis;background:var(--el-fill-color);align-items:center;max-width:100%;display:inline-flex}.el-cascader__collapse-tags .el-tag.el-tag--dark,.el-cascader__collapse-tags .el-tag.el-tag--plain{background-color:var(--el-tag-bg-color)}.el-cascader__collapse-tags .el-tag:not(.is-hit){border-color:#0000}.el-cascader__collapse-tags .el-tag:not(.is-hit).el-tag--dark,.el-cascader__collapse-tags .el-tag:not(.is-hit).el-tag--plain{border-color:var(--el-tag-border-color)}.el-cascader__collapse-tags .el-tag>span{text-overflow:ellipsis;flex:1;line-height:normal;overflow:hidden}.el-cascader__collapse-tags .el-tag .el-icon-close{background-color:var(--el-text-color-placeholder);color:var(--el-color-white);flex:none}.el-cascader__collapse-tags .el-tag .el-icon-close:hover{background-color:var(--el-text-color-secondary)}.el-cascader__collapse-tags .el-tag+input{margin-left:0}.el-cascader__collapse-tags .el-tag{margin:2px 0}.el-cascader__suggestion-panel{border-radius:var(--el-cascader-menu-radius)}.el-cascader__suggestion-wrap{max-height:204px}.el-cascader__suggestion-list{font-size:var(--el-font-size-base);color:var(--el-cascader-menu-text-color);text-align:center;margin:0;padding:6px 0;list-style:none}.el-cascader__suggestion-list.el-vl__window{margin:6px 0;padding:0}.el-cascader__suggestion-list.el-vl__window ul{margin:0;padding:0}.el-cascader__suggestion-item{text-align:left;cursor:pointer;box-sizing:border-box;outline:none;justify-content:space-between;align-items:center;height:34px;padding:0 15px;display:flex}.el-cascader__suggestion-item:hover,.el-cascader__suggestion-item:focus{background:var(--el-cascader-node-background-hover)}.el-cascader__suggestion-item.is-checked{color:var(--el-cascader-menu-selected-text-color);font-weight:700}.el-cascader__suggestion-item>:not(.el-icon){white-space:nowrap;text-overflow:ellipsis;flex:1;min-width:0;overflow:hidden}.el-cascader__suggestion-item>span{margin-right:10px}.el-cascader__suggestion-item .el-icon{flex-shrink:0}.el-cascader__empty-text{color:var(--el-cascader-color-empty);margin:10px 0}.el-cascader__search-input{min-width:60px;height:24px;color:var(--el-cascader-menu-text-color);box-sizing:border-box;background:0 0;border:none;outline:none;flex:1;margin-left:7px;padding:0}.el-cascader__search-input::placeholder{color:#0000}.el-check-tag{background-color:var(--el-color-info-light-9);border-radius:var(--el-border-radius-base);color:var(--el-color-info);cursor:pointer;font-size:var(--el-font-size-base);line-height:var(--el-font-size-base);transition:var(--el-transition-all);padding:7px 15px;font-weight:700;display:inline-block}.el-check-tag:hover{background-color:var(--el-color-info-light-7)}.el-check-tag.el-check-tag--primary.is-checked{background-color:var(--el-color-primary-light-8);color:var(--el-color-primary)}.el-check-tag.el-check-tag--primary.is-checked:hover{background-color:var(--el-color-primary-light-7)}.el-check-tag.el-check-tag--primary.is-checked.is-disabled{background-color:var(--el-color-primary-light-8);color:var(--el-disabled-text-color);cursor:not-allowed}.el-check-tag.el-check-tag--primary.is-checked.is-disabled:hover{background-color:var(--el-color-primary-light-8)}.el-check-tag.el-check-tag--primary.is-disabled{background-color:var(--el-color-info-light-9);color:var(--el-disabled-text-color);cursor:not-allowed}.el-check-tag.el-check-tag--primary.is-disabled:hover{background-color:var(--el-color-info-light-9)}.el-check-tag.el-check-tag--success.is-checked{background-color:var(--el-color-success-light-8);color:var(--el-color-success)}.el-check-tag.el-check-tag--success.is-checked:hover{background-color:var(--el-color-success-light-7)}.el-check-tag.el-check-tag--success.is-checked.is-disabled{background-color:var(--el-color-success-light-8);color:var(--el-disabled-text-color);cursor:not-allowed}.el-check-tag.el-check-tag--success.is-checked.is-disabled:hover{background-color:var(--el-color-success-light-8)}.el-check-tag.el-check-tag--success.is-disabled{background-color:var(--el-color-success-light-9);color:var(--el-disabled-text-color);cursor:not-allowed}.el-check-tag.el-check-tag--success.is-disabled:hover{background-color:var(--el-color-success-light-9)}.el-check-tag.el-check-tag--warning.is-checked{background-color:var(--el-color-warning-light-8);color:var(--el-color-warning)}.el-check-tag.el-check-tag--warning.is-checked:hover{background-color:var(--el-color-warning-light-7)}.el-check-tag.el-check-tag--warning.is-checked.is-disabled{background-color:var(--el-color-warning-light-8);color:var(--el-disabled-text-color);cursor:not-allowed}.el-check-tag.el-check-tag--warning.is-checked.is-disabled:hover{background-color:var(--el-color-warning-light-8)}.el-check-tag.el-check-tag--warning.is-disabled{background-color:var(--el-color-warning-light-9);color:var(--el-disabled-text-color);cursor:not-allowed}.el-check-tag.el-check-tag--warning.is-disabled:hover{background-color:var(--el-color-warning-light-9)}.el-check-tag.el-check-tag--danger.is-checked{background-color:var(--el-color-danger-light-8);color:var(--el-color-danger)}.el-check-tag.el-check-tag--danger.is-checked:hover{background-color:var(--el-color-danger-light-7)}.el-check-tag.el-check-tag--danger.is-checked.is-disabled{background-color:var(--el-color-danger-light-8);color:var(--el-disabled-text-color);cursor:not-allowed}.el-check-tag.el-check-tag--danger.is-checked.is-disabled:hover{background-color:var(--el-color-danger-light-8)}.el-check-tag.el-check-tag--danger.is-disabled{background-color:var(--el-color-danger-light-9);color:var(--el-disabled-text-color);cursor:not-allowed}.el-check-tag.el-check-tag--danger.is-disabled:hover{background-color:var(--el-color-danger-light-9)}.el-check-tag.el-check-tag--error.is-checked{background-color:var(--el-color-error-light-8);color:var(--el-color-error)}.el-check-tag.el-check-tag--error.is-checked:hover{background-color:var(--el-color-error-light-7)}.el-check-tag.el-check-tag--error.is-checked.is-disabled{background-color:var(--el-color-error-light-8);color:var(--el-disabled-text-color);cursor:not-allowed}.el-check-tag.el-check-tag--error.is-checked.is-disabled:hover{background-color:var(--el-color-error-light-8)}.el-check-tag.el-check-tag--error.is-disabled{background-color:var(--el-color-error-light-9);color:var(--el-disabled-text-color);cursor:not-allowed}.el-check-tag.el-check-tag--error.is-disabled:hover{background-color:var(--el-color-error-light-9)}.el-check-tag.el-check-tag--info.is-checked{background-color:var(--el-color-info-light-8);color:var(--el-color-info)}.el-check-tag.el-check-tag--info.is-checked:hover{background-color:var(--el-color-info-light-7)}.el-check-tag.el-check-tag--info.is-checked.is-disabled{background-color:var(--el-color-info-light-8);color:var(--el-disabled-text-color);cursor:not-allowed}.el-check-tag.el-check-tag--info.is-checked.is-disabled:hover{background-color:var(--el-color-info-light-8)}.el-check-tag.el-check-tag--info.is-disabled{background-color:var(--el-color-info-light-9);color:var(--el-disabled-text-color);cursor:not-allowed}.el-check-tag.el-check-tag--info.is-disabled:hover{background-color:var(--el-color-info-light-9)}.el-checkbox-button{--el-checkbox-button-checked-bg-color:var(--el-color-primary);--el-checkbox-button-checked-text-color:var(--el-color-white);--el-checkbox-button-checked-border-color:var(--el-color-primary);--el-checkbox-button-disabled-checked-fill:var(--el-border-color-extra-light);display:inline-block;position:relative}.el-checkbox-button__inner{line-height:1;font-weight:var(--el-checkbox-font-weight);white-space:nowrap;vertical-align:middle;cursor:pointer;background:var(--el-button-bg-color,var(--el-fill-color-blank));outline:var(--el-border);color:var(--el-button-text-color,var(--el-text-color-regular));-webkit-appearance:none;text-align:center;box-sizing:border-box;transition:var(--el-transition-all);-webkit-user-select:none;user-select:none;font-size:var(--el-font-size-base);border-radius:0;margin:0;padding:8px 15px;display:inline-block;position:relative}.el-checkbox-button__inner.is-round{padding:8px 15px}.el-checkbox-button__inner:hover{color:var(--el-color-primary)}.el-checkbox-button__inner [class*=el-icon-]{line-height:.9}.el-checkbox-button__inner [class*=el-icon-]+span{margin-left:5px}.el-checkbox-button__original{opacity:0;z-index:-1;outline:none;margin:0;position:absolute}.el-checkbox-button.is-checked .el-checkbox-button__inner{color:var(--el-checkbox-button-checked-text-color);background-color:var(--el-checkbox-button-checked-bg-color);border-color:var(--el-checkbox-button-checked-border-color);box-shadow:-1px 0 0 0 var(--el-color-primary-light-7)}.el-checkbox-button.is-checked:first-child .el-checkbox-button__inner{border-left-color:var(--el-checkbox-button-checked-border-color)}.el-checkbox-button.is-disabled .el-checkbox-button__inner{color:var(--el-disabled-text-color);cursor:not-allowed;background-image:none;background-color:var(--el-button-disabled-bg-color,var(--el-fill-color-blank));border-color:var(--el-button-disabled-border-color,var(--el-border-color-light));box-shadow:none}.el-checkbox-button.is-disabled:first-child .el-checkbox-button__inner{border-left-color:var(--el-button-disabled-border-color,var(--el-border-color-light))}.el-checkbox-button.is-disabled.is-checked .el-checkbox-button__inner{background-color:var(--el-checkbox-button-disabled-checked-fill)}.el-checkbox-button:first-child .el-checkbox-button__inner{border-top-left-radius:var(--el-border-radius-base);border-bottom-left-radius:var(--el-border-radius-base);box-shadow:none!important}.el-checkbox-button.is-focus .el-checkbox-button__inner{border-color:var(--el-checkbox-button-checked-border-color)}.el-checkbox-button:last-child .el-checkbox-button__inner{border-top-right-radius:var(--el-border-radius-base);border-bottom-right-radius:var(--el-border-radius-base)}.el-checkbox-button--large .el-checkbox-button__inner{font-size:var(--el-font-size-base);border-radius:0;padding:12px 19px}.el-checkbox-button--large .el-checkbox-button__inner.is-round{padding:12px 19px}.el-checkbox-button--small .el-checkbox-button__inner{border-radius:0;padding:5px 11px;font-size:12px}.el-checkbox-button--small .el-checkbox-button__inner.is-round{padding:5px 11px}.el-checkbox-group{font-size:0;line-height:0}.el-checkbox{--el-checkbox-font-size:14px;--el-checkbox-font-weight:var(--el-font-weight-primary);--el-checkbox-text-color:var(--el-text-color-regular);--el-checkbox-input-height:14px;--el-checkbox-input-width:14px;--el-checkbox-border-radius:var(--el-border-radius-small);--el-checkbox-bg-color:var(--el-fill-color-blank);--el-checkbox-input-border:var(--el-border);--el-checkbox-disabled-border-color:var(--el-border-color);--el-checkbox-disabled-input-fill:var(--el-fill-color-light);--el-checkbox-disabled-icon-color:var(--el-text-color-placeholder);--el-checkbox-disabled-checked-input-fill:var(--el-border-color-extra-light);--el-checkbox-disabled-checked-input-border-color:var(--el-border-color);--el-checkbox-disabled-checked-icon-color:var(--el-text-color-placeholder);--el-checkbox-checked-text-color:var(--el-color-primary);--el-checkbox-checked-input-border-color:var(--el-color-primary);--el-checkbox-checked-bg-color:var(--el-color-primary);--el-checkbox-checked-icon-color:var(--el-color-white);--el-checkbox-input-border-color-hover:var(--el-color-primary);color:var(--el-checkbox-text-color);font-weight:var(--el-checkbox-font-weight);font-size:var(--el-font-size-base);cursor:pointer;white-space:nowrap;-webkit-user-select:none;user-select:none;height:var(--el-checkbox-height,32px);align-items:center;margin-right:30px;display:inline-flex;position:relative}.el-checkbox.is-disabled{cursor:not-allowed}.el-checkbox.is-bordered{border-radius:var(--el-border-radius-base);border:var(--el-border);box-sizing:border-box;padding:0 15px 0 9px}.el-checkbox.is-bordered.is-checked{border-color:var(--el-color-primary)}.el-checkbox.is-bordered.is-disabled{border-color:var(--el-border-color-lighter)}.el-checkbox.is-bordered.el-checkbox--large{border-radius:var(--el-border-radius-base);padding:0 19px 0 11px}.el-checkbox.is-bordered.el-checkbox--large .el-checkbox__label{font-size:var(--el-font-size-base)}.el-checkbox.is-bordered.el-checkbox--large .el-checkbox__inner{width:14px;height:14px}.el-checkbox.is-bordered.el-checkbox--small{border-radius:calc(var(--el-border-radius-base) - 1px);padding:0 11px 0 7px}.el-checkbox.is-bordered.el-checkbox--small .el-checkbox__label{font-size:12px}.el-checkbox.is-bordered.el-checkbox--small .el-checkbox__inner{width:12px;height:12px}.el-checkbox.is-bordered.el-checkbox--small .el-checkbox__inner:after{width:2px;height:6px}.el-checkbox input:focus-visible+.el-checkbox__inner{outline:2px solid var(--el-checkbox-input-border-color-hover);outline-offset:1px;border-radius:var(--el-checkbox-border-radius)}.el-checkbox__input{white-space:nowrap;cursor:pointer;outline:none;display:inline-flex;position:relative}.el-checkbox__input.is-disabled .el-checkbox__inner{background-color:var(--el-checkbox-disabled-input-fill);border-color:var(--el-checkbox-disabled-border-color);cursor:not-allowed}.el-checkbox__input.is-disabled .el-checkbox__inner:after{cursor:not-allowed;border-color:var(--el-checkbox-disabled-icon-color);will-change:transform}.el-checkbox__input.is-disabled.is-checked .el-checkbox__inner{background-color:var(--el-checkbox-disabled-checked-input-fill);border-color:var(--el-checkbox-disabled-checked-input-border-color)}.el-checkbox__input.is-disabled.is-checked .el-checkbox__inner:after{border-color:var(--el-checkbox-disabled-checked-icon-color)}.el-checkbox__input.is-disabled.is-indeterminate .el-checkbox__inner{background-color:var(--el-checkbox-disabled-checked-input-fill);border-color:var(--el-checkbox-disabled-checked-input-border-color)}.el-checkbox__input.is-disabled.is-indeterminate .el-checkbox__inner:before{background-color:var(--el-checkbox-disabled-checked-icon-color);border-color:var(--el-checkbox-disabled-checked-icon-color)}.el-checkbox__input.is-disabled+span.el-checkbox__label{color:var(--el-disabled-text-color);cursor:not-allowed}.el-checkbox__input.is-checked .el-checkbox__inner{background-color:var(--el-checkbox-checked-bg-color);border-color:var(--el-checkbox-checked-input-border-color)}.el-checkbox__input.is-checked .el-checkbox__inner:after{border-color:var(--el-checkbox-checked-icon-color);transform:translate(-45%,-60%)rotate(45deg)scaleY(1)}.el-checkbox__input.is-checked+.el-checkbox__label{color:var(--el-checkbox-checked-text-color)}.el-checkbox__input.is-focus:not(.is-checked) .el-checkbox__original:not(:focus-visible){border-color:var(--el-checkbox-input-border-color-hover)}.el-checkbox__input.is-indeterminate .el-checkbox__inner{background-color:var(--el-checkbox-checked-bg-color);border-color:var(--el-checkbox-checked-input-border-color)}.el-checkbox__input.is-indeterminate .el-checkbox__inner:before{content:"";background-color:var(--el-checkbox-checked-icon-color);height:2px;display:block;position:absolute;top:5px;left:0;right:0;transform:scale(.5)}.el-checkbox__input.is-indeterminate .el-checkbox__inner:after{display:none}.el-checkbox__inner{border:var(--el-checkbox-input-border);border-radius:var(--el-checkbox-border-radius);box-sizing:border-box;width:var(--el-checkbox-input-width);height:var(--el-checkbox-input-height);background-color:var(--el-checkbox-bg-color);z-index:var(--el-index-normal);transition:border-color .25s cubic-bezier(.71,-.46,.29,1.46),background-color .25s cubic-bezier(.71,-.46,.29,1.46),outline .25s cubic-bezier(.71,-.46,.29,1.46);display:inline-block;position:relative}.el-checkbox__inner:hover{border-color:var(--el-checkbox-input-border-color-hover)}.el-checkbox__inner:after{box-sizing:content-box;content:"";transform-origin:50%;border:1px solid #0000;border-top:0;border-left:0;width:3px;height:7px;transition:transform .15s ease-in 50ms;position:absolute;top:50%;left:50%;transform:translate(-45%,-60%)rotate(45deg)scaleY(0)}.el-checkbox__original{opacity:0;z-index:-1;outline:none;width:0;height:0;margin:0;position:absolute}.el-checkbox__label{line-height:1;font-size:var(--el-checkbox-font-size);padding-left:8px;display:inline-block}.el-checkbox.el-checkbox--large{height:40px}.el-checkbox.el-checkbox--large .el-checkbox__label{font-size:14px}.el-checkbox.el-checkbox--large .el-checkbox__inner{width:14px;height:14px}.el-checkbox.el-checkbox--small{height:24px}.el-checkbox.el-checkbox--small .el-checkbox__label{font-size:12px}.el-checkbox.el-checkbox--small .el-checkbox__inner{width:12px;height:12px}.el-checkbox.el-checkbox--small .el-checkbox__input.is-indeterminate .el-checkbox__inner:before{top:4px}.el-checkbox.el-checkbox--small .el-checkbox__inner:after{width:2px;height:6px}.el-checkbox:last-of-type{margin-right:0}[class*=el-col-]{box-sizing:border-box}[class*=el-col-].is-guttered{min-height:1px;display:block}.el-col-0{flex:0 0;max-width:0%;display:none}.el-col-0.is-guttered{display:none}.el-col-offset-0{margin-left:0%}.el-col-pull-0{position:relative;right:0%}.el-col-push-0{position:relative;left:0%}.el-col-1{flex:0 0 4.16667%;max-width:4.16667%;display:block}.el-col-1.is-guttered{display:block}.el-col-offset-1{margin-left:4.16667%}.el-col-pull-1{position:relative;right:4.16667%}.el-col-push-1{position:relative;left:4.16667%}.el-col-2{flex:0 0 8.33333%;max-width:8.33333%;display:block}.el-col-2.is-guttered{display:block}.el-col-offset-2{margin-left:8.33333%}.el-col-pull-2{position:relative;right:8.33333%}.el-col-push-2{position:relative;left:8.33333%}.el-col-3{flex:0 0 12.5%;max-width:12.5%;display:block}.el-col-3.is-guttered{display:block}.el-col-offset-3{margin-left:12.5%}.el-col-pull-3{position:relative;right:12.5%}.el-col-push-3{position:relative;left:12.5%}.el-col-4{flex:0 0 16.6667%;max-width:16.6667%;display:block}.el-col-4.is-guttered{display:block}.el-col-offset-4{margin-left:16.6667%}.el-col-pull-4{position:relative;right:16.6667%}.el-col-push-4{position:relative;left:16.6667%}.el-col-5{flex:0 0 20.8333%;max-width:20.8333%;display:block}.el-col-5.is-guttered{display:block}.el-col-offset-5{margin-left:20.8333%}.el-col-pull-5{position:relative;right:20.8333%}.el-col-push-5{position:relative;left:20.8333%}.el-col-6{flex:0 0 25%;max-width:25%;display:block}.el-col-6.is-guttered{display:block}.el-col-offset-6{margin-left:25%}.el-col-pull-6{position:relative;right:25%}.el-col-push-6{position:relative;left:25%}.el-col-7{flex:0 0 29.1667%;max-width:29.1667%;display:block}.el-col-7.is-guttered{display:block}.el-col-offset-7{margin-left:29.1667%}.el-col-pull-7{position:relative;right:29.1667%}.el-col-push-7{position:relative;left:29.1667%}.el-col-8{flex:0 0 33.3333%;max-width:33.3333%;display:block}.el-col-8.is-guttered{display:block}.el-col-offset-8{margin-left:33.3333%}.el-col-pull-8{position:relative;right:33.3333%}.el-col-push-8{position:relative;left:33.3333%}.el-col-9{flex:0 0 37.5%;max-width:37.5%;display:block}.el-col-9.is-guttered{display:block}.el-col-offset-9{margin-left:37.5%}.el-col-pull-9{position:relative;right:37.5%}.el-col-push-9{position:relative;left:37.5%}.el-col-10{flex:0 0 41.6667%;max-width:41.6667%;display:block}.el-col-10.is-guttered{display:block}.el-col-offset-10{margin-left:41.6667%}.el-col-pull-10{position:relative;right:41.6667%}.el-col-push-10{position:relative;left:41.6667%}.el-col-11{flex:0 0 45.8333%;max-width:45.8333%;display:block}.el-col-11.is-guttered{display:block}.el-col-offset-11{margin-left:45.8333%}.el-col-pull-11{position:relative;right:45.8333%}.el-col-push-11{position:relative;left:45.8333%}.el-col-12{flex:0 0 50%;max-width:50%;display:block}.el-col-12.is-guttered{display:block}.el-col-offset-12{margin-left:50%}.el-col-pull-12{position:relative;right:50%}.el-col-push-12{position:relative;left:50%}.el-col-13{flex:0 0 54.1667%;max-width:54.1667%;display:block}.el-col-13.is-guttered{display:block}.el-col-offset-13{margin-left:54.1667%}.el-col-pull-13{position:relative;right:54.1667%}.el-col-push-13{position:relative;left:54.1667%}.el-col-14{flex:0 0 58.3333%;max-width:58.3333%;display:block}.el-col-14.is-guttered{display:block}.el-col-offset-14{margin-left:58.3333%}.el-col-pull-14{position:relative;right:58.3333%}.el-col-push-14{position:relative;left:58.3333%}.el-col-15{flex:0 0 62.5%;max-width:62.5%;display:block}.el-col-15.is-guttered{display:block}.el-col-offset-15{margin-left:62.5%}.el-col-pull-15{position:relative;right:62.5%}.el-col-push-15{position:relative;left:62.5%}.el-col-16{flex:0 0 66.6667%;max-width:66.6667%;display:block}.el-col-16.is-guttered{display:block}.el-col-offset-16{margin-left:66.6667%}.el-col-pull-16{position:relative;right:66.6667%}.el-col-push-16{position:relative;left:66.6667%}.el-col-17{flex:0 0 70.8333%;max-width:70.8333%;display:block}.el-col-17.is-guttered{display:block}.el-col-offset-17{margin-left:70.8333%}.el-col-pull-17{position:relative;right:70.8333%}.el-col-push-17{position:relative;left:70.8333%}.el-col-18{flex:0 0 75%;max-width:75%;display:block}.el-col-18.is-guttered{display:block}.el-col-offset-18{margin-left:75%}.el-col-pull-18{position:relative;right:75%}.el-col-push-18{position:relative;left:75%}.el-col-19{flex:0 0 79.1667%;max-width:79.1667%;display:block}.el-col-19.is-guttered{display:block}.el-col-offset-19{margin-left:79.1667%}.el-col-pull-19{position:relative;right:79.1667%}.el-col-push-19{position:relative;left:79.1667%}.el-col-20{flex:0 0 83.3333%;max-width:83.3333%;display:block}.el-col-20.is-guttered{display:block}.el-col-offset-20{margin-left:83.3333%}.el-col-pull-20{position:relative;right:83.3333%}.el-col-push-20{position:relative;left:83.3333%}.el-col-21{flex:0 0 87.5%;max-width:87.5%;display:block}.el-col-21.is-guttered{display:block}.el-col-offset-21{margin-left:87.5%}.el-col-pull-21{position:relative;right:87.5%}.el-col-push-21{position:relative;left:87.5%}.el-col-22{flex:0 0 91.6667%;max-width:91.6667%;display:block}.el-col-22.is-guttered{display:block}.el-col-offset-22{margin-left:91.6667%}.el-col-pull-22{position:relative;right:91.6667%}.el-col-push-22{position:relative;left:91.6667%}.el-col-23{flex:0 0 95.8333%;max-width:95.8333%;display:block}.el-col-23.is-guttered{display:block}.el-col-offset-23{margin-left:95.8333%}.el-col-pull-23{position:relative;right:95.8333%}.el-col-push-23{position:relative;left:95.8333%}.el-col-24{flex:0 0 100%;max-width:100%;display:block}.el-col-24.is-guttered{display:block}.el-col-offset-24{margin-left:100%}.el-col-pull-24{position:relative;right:100%}.el-col-push-24{position:relative;left:100%}@media only screen and (max-width:767px){.el-col-xs-0{flex:0 0;max-width:0%;display:none}.el-col-xs-0.is-guttered{display:none}.el-col-xs-offset-0{margin-left:0%}.el-col-xs-pull-0{position:relative;right:0%}.el-col-xs-push-0{position:relative;left:0%}.el-col-xs-1{flex:0 0 4.16667%;max-width:4.16667%;display:block}.el-col-xs-1.is-guttered{display:block}.el-col-xs-offset-1{margin-left:4.16667%}.el-col-xs-pull-1{position:relative;right:4.16667%}.el-col-xs-push-1{position:relative;left:4.16667%}.el-col-xs-2{flex:0 0 8.33333%;max-width:8.33333%;display:block}.el-col-xs-2.is-guttered{display:block}.el-col-xs-offset-2{margin-left:8.33333%}.el-col-xs-pull-2{position:relative;right:8.33333%}.el-col-xs-push-2{position:relative;left:8.33333%}.el-col-xs-3{flex:0 0 12.5%;max-width:12.5%;display:block}.el-col-xs-3.is-guttered{display:block}.el-col-xs-offset-3{margin-left:12.5%}.el-col-xs-pull-3{position:relative;right:12.5%}.el-col-xs-push-3{position:relative;left:12.5%}.el-col-xs-4{flex:0 0 16.6667%;max-width:16.6667%;display:block}.el-col-xs-4.is-guttered{display:block}.el-col-xs-offset-4{margin-left:16.6667%}.el-col-xs-pull-4{position:relative;right:16.6667%}.el-col-xs-push-4{position:relative;left:16.6667%}.el-col-xs-5{flex:0 0 20.8333%;max-width:20.8333%;display:block}.el-col-xs-5.is-guttered{display:block}.el-col-xs-offset-5{margin-left:20.8333%}.el-col-xs-pull-5{position:relative;right:20.8333%}.el-col-xs-push-5{position:relative;left:20.8333%}.el-col-xs-6{flex:0 0 25%;max-width:25%;display:block}.el-col-xs-6.is-guttered{display:block}.el-col-xs-offset-6{margin-left:25%}.el-col-xs-pull-6{position:relative;right:25%}.el-col-xs-push-6{position:relative;left:25%}.el-col-xs-7{flex:0 0 29.1667%;max-width:29.1667%;display:block}.el-col-xs-7.is-guttered{display:block}.el-col-xs-offset-7{margin-left:29.1667%}.el-col-xs-pull-7{position:relative;right:29.1667%}.el-col-xs-push-7{position:relative;left:29.1667%}.el-col-xs-8{flex:0 0 33.3333%;max-width:33.3333%;display:block}.el-col-xs-8.is-guttered{display:block}.el-col-xs-offset-8{margin-left:33.3333%}.el-col-xs-pull-8{position:relative;right:33.3333%}.el-col-xs-push-8{position:relative;left:33.3333%}.el-col-xs-9{flex:0 0 37.5%;max-width:37.5%;display:block}.el-col-xs-9.is-guttered{display:block}.el-col-xs-offset-9{margin-left:37.5%}.el-col-xs-pull-9{position:relative;right:37.5%}.el-col-xs-push-9{position:relative;left:37.5%}.el-col-xs-10{flex:0 0 41.6667%;max-width:41.6667%;display:block}.el-col-xs-10.is-guttered{display:block}.el-col-xs-offset-10{margin-left:41.6667%}.el-col-xs-pull-10{position:relative;right:41.6667%}.el-col-xs-push-10{position:relative;left:41.6667%}.el-col-xs-11{flex:0 0 45.8333%;max-width:45.8333%;display:block}.el-col-xs-11.is-guttered{display:block}.el-col-xs-offset-11{margin-left:45.8333%}.el-col-xs-pull-11{position:relative;right:45.8333%}.el-col-xs-push-11{position:relative;left:45.8333%}.el-col-xs-12{flex:0 0 50%;max-width:50%;display:block}.el-col-xs-12.is-guttered{display:block}.el-col-xs-offset-12{margin-left:50%}.el-col-xs-pull-12{position:relative;right:50%}.el-col-xs-push-12{position:relative;left:50%}.el-col-xs-13{flex:0 0 54.1667%;max-width:54.1667%;display:block}.el-col-xs-13.is-guttered{display:block}.el-col-xs-offset-13{margin-left:54.1667%}.el-col-xs-pull-13{position:relative;right:54.1667%}.el-col-xs-push-13{position:relative;left:54.1667%}.el-col-xs-14{flex:0 0 58.3333%;max-width:58.3333%;display:block}.el-col-xs-14.is-guttered{display:block}.el-col-xs-offset-14{margin-left:58.3333%}.el-col-xs-pull-14{position:relative;right:58.3333%}.el-col-xs-push-14{position:relative;left:58.3333%}.el-col-xs-15{flex:0 0 62.5%;max-width:62.5%;display:block}.el-col-xs-15.is-guttered{display:block}.el-col-xs-offset-15{margin-left:62.5%}.el-col-xs-pull-15{position:relative;right:62.5%}.el-col-xs-push-15{position:relative;left:62.5%}.el-col-xs-16{flex:0 0 66.6667%;max-width:66.6667%;display:block}.el-col-xs-16.is-guttered{display:block}.el-col-xs-offset-16{margin-left:66.6667%}.el-col-xs-pull-16{position:relative;right:66.6667%}.el-col-xs-push-16{position:relative;left:66.6667%}.el-col-xs-17{flex:0 0 70.8333%;max-width:70.8333%;display:block}.el-col-xs-17.is-guttered{display:block}.el-col-xs-offset-17{margin-left:70.8333%}.el-col-xs-pull-17{position:relative;right:70.8333%}.el-col-xs-push-17{position:relative;left:70.8333%}.el-col-xs-18{flex:0 0 75%;max-width:75%;display:block}.el-col-xs-18.is-guttered{display:block}.el-col-xs-offset-18{margin-left:75%}.el-col-xs-pull-18{position:relative;right:75%}.el-col-xs-push-18{position:relative;left:75%}.el-col-xs-19{flex:0 0 79.1667%;max-width:79.1667%;display:block}.el-col-xs-19.is-guttered{display:block}.el-col-xs-offset-19{margin-left:79.1667%}.el-col-xs-pull-19{position:relative;right:79.1667%}.el-col-xs-push-19{position:relative;left:79.1667%}.el-col-xs-20{flex:0 0 83.3333%;max-width:83.3333%;display:block}.el-col-xs-20.is-guttered{display:block}.el-col-xs-offset-20{margin-left:83.3333%}.el-col-xs-pull-20{position:relative;right:83.3333%}.el-col-xs-push-20{position:relative;left:83.3333%}.el-col-xs-21{flex:0 0 87.5%;max-width:87.5%;display:block}.el-col-xs-21.is-guttered{display:block}.el-col-xs-offset-21{margin-left:87.5%}.el-col-xs-pull-21{position:relative;right:87.5%}.el-col-xs-push-21{position:relative;left:87.5%}.el-col-xs-22{flex:0 0 91.6667%;max-width:91.6667%;display:block}.el-col-xs-22.is-guttered{display:block}.el-col-xs-offset-22{margin-left:91.6667%}.el-col-xs-pull-22{position:relative;right:91.6667%}.el-col-xs-push-22{position:relative;left:91.6667%}.el-col-xs-23{flex:0 0 95.8333%;max-width:95.8333%;display:block}.el-col-xs-23.is-guttered{display:block}.el-col-xs-offset-23{margin-left:95.8333%}.el-col-xs-pull-23{position:relative;right:95.8333%}.el-col-xs-push-23{position:relative;left:95.8333%}.el-col-xs-24{flex:0 0 100%;max-width:100%;display:block}.el-col-xs-24.is-guttered{display:block}.el-col-xs-offset-24{margin-left:100%}.el-col-xs-pull-24{position:relative;right:100%}.el-col-xs-push-24{position:relative;left:100%}}@media only screen and (min-width:768px){.el-col-sm-0{flex:0 0;max-width:0%;display:none}.el-col-sm-0.is-guttered{display:none}.el-col-sm-offset-0{margin-left:0%}.el-col-sm-pull-0{position:relative;right:0%}.el-col-sm-push-0{position:relative;left:0%}.el-col-sm-1{flex:0 0 4.16667%;max-width:4.16667%;display:block}.el-col-sm-1.is-guttered{display:block}.el-col-sm-offset-1{margin-left:4.16667%}.el-col-sm-pull-1{position:relative;right:4.16667%}.el-col-sm-push-1{position:relative;left:4.16667%}.el-col-sm-2{flex:0 0 8.33333%;max-width:8.33333%;display:block}.el-col-sm-2.is-guttered{display:block}.el-col-sm-offset-2{margin-left:8.33333%}.el-col-sm-pull-2{position:relative;right:8.33333%}.el-col-sm-push-2{position:relative;left:8.33333%}.el-col-sm-3{flex:0 0 12.5%;max-width:12.5%;display:block}.el-col-sm-3.is-guttered{display:block}.el-col-sm-offset-3{margin-left:12.5%}.el-col-sm-pull-3{position:relative;right:12.5%}.el-col-sm-push-3{position:relative;left:12.5%}.el-col-sm-4{flex:0 0 16.6667%;max-width:16.6667%;display:block}.el-col-sm-4.is-guttered{display:block}.el-col-sm-offset-4{margin-left:16.6667%}.el-col-sm-pull-4{position:relative;right:16.6667%}.el-col-sm-push-4{position:relative;left:16.6667%}.el-col-sm-5{flex:0 0 20.8333%;max-width:20.8333%;display:block}.el-col-sm-5.is-guttered{display:block}.el-col-sm-offset-5{margin-left:20.8333%}.el-col-sm-pull-5{position:relative;right:20.8333%}.el-col-sm-push-5{position:relative;left:20.8333%}.el-col-sm-6{flex:0 0 25%;max-width:25%;display:block}.el-col-sm-6.is-guttered{display:block}.el-col-sm-offset-6{margin-left:25%}.el-col-sm-pull-6{position:relative;right:25%}.el-col-sm-push-6{position:relative;left:25%}.el-col-sm-7{flex:0 0 29.1667%;max-width:29.1667%;display:block}.el-col-sm-7.is-guttered{display:block}.el-col-sm-offset-7{margin-left:29.1667%}.el-col-sm-pull-7{position:relative;right:29.1667%}.el-col-sm-push-7{position:relative;left:29.1667%}.el-col-sm-8{flex:0 0 33.3333%;max-width:33.3333%;display:block}.el-col-sm-8.is-guttered{display:block}.el-col-sm-offset-8{margin-left:33.3333%}.el-col-sm-pull-8{position:relative;right:33.3333%}.el-col-sm-push-8{position:relative;left:33.3333%}.el-col-sm-9{flex:0 0 37.5%;max-width:37.5%;display:block}.el-col-sm-9.is-guttered{display:block}.el-col-sm-offset-9{margin-left:37.5%}.el-col-sm-pull-9{position:relative;right:37.5%}.el-col-sm-push-9{position:relative;left:37.5%}.el-col-sm-10{flex:0 0 41.6667%;max-width:41.6667%;display:block}.el-col-sm-10.is-guttered{display:block}.el-col-sm-offset-10{margin-left:41.6667%}.el-col-sm-pull-10{position:relative;right:41.6667%}.el-col-sm-push-10{position:relative;left:41.6667%}.el-col-sm-11{flex:0 0 45.8333%;max-width:45.8333%;display:block}.el-col-sm-11.is-guttered{display:block}.el-col-sm-offset-11{margin-left:45.8333%}.el-col-sm-pull-11{position:relative;right:45.8333%}.el-col-sm-push-11{position:relative;left:45.8333%}.el-col-sm-12{flex:0 0 50%;max-width:50%;display:block}.el-col-sm-12.is-guttered{display:block}.el-col-sm-offset-12{margin-left:50%}.el-col-sm-pull-12{position:relative;right:50%}.el-col-sm-push-12{position:relative;left:50%}.el-col-sm-13{flex:0 0 54.1667%;max-width:54.1667%;display:block}.el-col-sm-13.is-guttered{display:block}.el-col-sm-offset-13{margin-left:54.1667%}.el-col-sm-pull-13{position:relative;right:54.1667%}.el-col-sm-push-13{position:relative;left:54.1667%}.el-col-sm-14{flex:0 0 58.3333%;max-width:58.3333%;display:block}.el-col-sm-14.is-guttered{display:block}.el-col-sm-offset-14{margin-left:58.3333%}.el-col-sm-pull-14{position:relative;right:58.3333%}.el-col-sm-push-14{position:relative;left:58.3333%}.el-col-sm-15{flex:0 0 62.5%;max-width:62.5%;display:block}.el-col-sm-15.is-guttered{display:block}.el-col-sm-offset-15{margin-left:62.5%}.el-col-sm-pull-15{position:relative;right:62.5%}.el-col-sm-push-15{position:relative;left:62.5%}.el-col-sm-16{flex:0 0 66.6667%;max-width:66.6667%;display:block}.el-col-sm-16.is-guttered{display:block}.el-col-sm-offset-16{margin-left:66.6667%}.el-col-sm-pull-16{position:relative;right:66.6667%}.el-col-sm-push-16{position:relative;left:66.6667%}.el-col-sm-17{flex:0 0 70.8333%;max-width:70.8333%;display:block}.el-col-sm-17.is-guttered{display:block}.el-col-sm-offset-17{margin-left:70.8333%}.el-col-sm-pull-17{position:relative;right:70.8333%}.el-col-sm-push-17{position:relative;left:70.8333%}.el-col-sm-18{flex:0 0 75%;max-width:75%;display:block}.el-col-sm-18.is-guttered{display:block}.el-col-sm-offset-18{margin-left:75%}.el-col-sm-pull-18{position:relative;right:75%}.el-col-sm-push-18{position:relative;left:75%}.el-col-sm-19{flex:0 0 79.1667%;max-width:79.1667%;display:block}.el-col-sm-19.is-guttered{display:block}.el-col-sm-offset-19{margin-left:79.1667%}.el-col-sm-pull-19{position:relative;right:79.1667%}.el-col-sm-push-19{position:relative;left:79.1667%}.el-col-sm-20{flex:0 0 83.3333%;max-width:83.3333%;display:block}.el-col-sm-20.is-guttered{display:block}.el-col-sm-offset-20{margin-left:83.3333%}.el-col-sm-pull-20{position:relative;right:83.3333%}.el-col-sm-push-20{position:relative;left:83.3333%}.el-col-sm-21{flex:0 0 87.5%;max-width:87.5%;display:block}.el-col-sm-21.is-guttered{display:block}.el-col-sm-offset-21{margin-left:87.5%}.el-col-sm-pull-21{position:relative;right:87.5%}.el-col-sm-push-21{position:relative;left:87.5%}.el-col-sm-22{flex:0 0 91.6667%;max-width:91.6667%;display:block}.el-col-sm-22.is-guttered{display:block}.el-col-sm-offset-22{margin-left:91.6667%}.el-col-sm-pull-22{position:relative;right:91.6667%}.el-col-sm-push-22{position:relative;left:91.6667%}.el-col-sm-23{flex:0 0 95.8333%;max-width:95.8333%;display:block}.el-col-sm-23.is-guttered{display:block}.el-col-sm-offset-23{margin-left:95.8333%}.el-col-sm-pull-23{position:relative;right:95.8333%}.el-col-sm-push-23{position:relative;left:95.8333%}.el-col-sm-24{flex:0 0 100%;max-width:100%;display:block}.el-col-sm-24.is-guttered{display:block}.el-col-sm-offset-24{margin-left:100%}.el-col-sm-pull-24{position:relative;right:100%}.el-col-sm-push-24{position:relative;left:100%}}@media only screen and (min-width:992px){.el-col-md-0{flex:0 0;max-width:0%;display:none}.el-col-md-0.is-guttered{display:none}.el-col-md-offset-0{margin-left:0%}.el-col-md-pull-0{position:relative;right:0%}.el-col-md-push-0{position:relative;left:0%}.el-col-md-1{flex:0 0 4.16667%;max-width:4.16667%;display:block}.el-col-md-1.is-guttered{display:block}.el-col-md-offset-1{margin-left:4.16667%}.el-col-md-pull-1{position:relative;right:4.16667%}.el-col-md-push-1{position:relative;left:4.16667%}.el-col-md-2{flex:0 0 8.33333%;max-width:8.33333%;display:block}.el-col-md-2.is-guttered{display:block}.el-col-md-offset-2{margin-left:8.33333%}.el-col-md-pull-2{position:relative;right:8.33333%}.el-col-md-push-2{position:relative;left:8.33333%}.el-col-md-3{flex:0 0 12.5%;max-width:12.5%;display:block}.el-col-md-3.is-guttered{display:block}.el-col-md-offset-3{margin-left:12.5%}.el-col-md-pull-3{position:relative;right:12.5%}.el-col-md-push-3{position:relative;left:12.5%}.el-col-md-4{flex:0 0 16.6667%;max-width:16.6667%;display:block}.el-col-md-4.is-guttered{display:block}.el-col-md-offset-4{margin-left:16.6667%}.el-col-md-pull-4{position:relative;right:16.6667%}.el-col-md-push-4{position:relative;left:16.6667%}.el-col-md-5{flex:0 0 20.8333%;max-width:20.8333%;display:block}.el-col-md-5.is-guttered{display:block}.el-col-md-offset-5{margin-left:20.8333%}.el-col-md-pull-5{position:relative;right:20.8333%}.el-col-md-push-5{position:relative;left:20.8333%}.el-col-md-6{flex:0 0 25%;max-width:25%;display:block}.el-col-md-6.is-guttered{display:block}.el-col-md-offset-6{margin-left:25%}.el-col-md-pull-6{position:relative;right:25%}.el-col-md-push-6{position:relative;left:25%}.el-col-md-7{flex:0 0 29.1667%;max-width:29.1667%;display:block}.el-col-md-7.is-guttered{display:block}.el-col-md-offset-7{margin-left:29.1667%}.el-col-md-pull-7{position:relative;right:29.1667%}.el-col-md-push-7{position:relative;left:29.1667%}.el-col-md-8{flex:0 0 33.3333%;max-width:33.3333%;display:block}.el-col-md-8.is-guttered{display:block}.el-col-md-offset-8{margin-left:33.3333%}.el-col-md-pull-8{position:relative;right:33.3333%}.el-col-md-push-8{position:relative;left:33.3333%}.el-col-md-9{flex:0 0 37.5%;max-width:37.5%;display:block}.el-col-md-9.is-guttered{display:block}.el-col-md-offset-9{margin-left:37.5%}.el-col-md-pull-9{position:relative;right:37.5%}.el-col-md-push-9{position:relative;left:37.5%}.el-col-md-10{flex:0 0 41.6667%;max-width:41.6667%;display:block}.el-col-md-10.is-guttered{display:block}.el-col-md-offset-10{margin-left:41.6667%}.el-col-md-pull-10{position:relative;right:41.6667%}.el-col-md-push-10{position:relative;left:41.6667%}.el-col-md-11{flex:0 0 45.8333%;max-width:45.8333%;display:block}.el-col-md-11.is-guttered{display:block}.el-col-md-offset-11{margin-left:45.8333%}.el-col-md-pull-11{position:relative;right:45.8333%}.el-col-md-push-11{position:relative;left:45.8333%}.el-col-md-12{flex:0 0 50%;max-width:50%;display:block}.el-col-md-12.is-guttered{display:block}.el-col-md-offset-12{margin-left:50%}.el-col-md-pull-12{position:relative;right:50%}.el-col-md-push-12{position:relative;left:50%}.el-col-md-13{flex:0 0 54.1667%;max-width:54.1667%;display:block}.el-col-md-13.is-guttered{display:block}.el-col-md-offset-13{margin-left:54.1667%}.el-col-md-pull-13{position:relative;right:54.1667%}.el-col-md-push-13{position:relative;left:54.1667%}.el-col-md-14{flex:0 0 58.3333%;max-width:58.3333%;display:block}.el-col-md-14.is-guttered{display:block}.el-col-md-offset-14{margin-left:58.3333%}.el-col-md-pull-14{position:relative;right:58.3333%}.el-col-md-push-14{position:relative;left:58.3333%}.el-col-md-15{flex:0 0 62.5%;max-width:62.5%;display:block}.el-col-md-15.is-guttered{display:block}.el-col-md-offset-15{margin-left:62.5%}.el-col-md-pull-15{position:relative;right:62.5%}.el-col-md-push-15{position:relative;left:62.5%}.el-col-md-16{flex:0 0 66.6667%;max-width:66.6667%;display:block}.el-col-md-16.is-guttered{display:block}.el-col-md-offset-16{margin-left:66.6667%}.el-col-md-pull-16{position:relative;right:66.6667%}.el-col-md-push-16{position:relative;left:66.6667%}.el-col-md-17{flex:0 0 70.8333%;max-width:70.8333%;display:block}.el-col-md-17.is-guttered{display:block}.el-col-md-offset-17{margin-left:70.8333%}.el-col-md-pull-17{position:relative;right:70.8333%}.el-col-md-push-17{position:relative;left:70.8333%}.el-col-md-18{flex:0 0 75%;max-width:75%;display:block}.el-col-md-18.is-guttered{display:block}.el-col-md-offset-18{margin-left:75%}.el-col-md-pull-18{position:relative;right:75%}.el-col-md-push-18{position:relative;left:75%}.el-col-md-19{flex:0 0 79.1667%;max-width:79.1667%;display:block}.el-col-md-19.is-guttered{display:block}.el-col-md-offset-19{margin-left:79.1667%}.el-col-md-pull-19{position:relative;right:79.1667%}.el-col-md-push-19{position:relative;left:79.1667%}.el-col-md-20{flex:0 0 83.3333%;max-width:83.3333%;display:block}.el-col-md-20.is-guttered{display:block}.el-col-md-offset-20{margin-left:83.3333%}.el-col-md-pull-20{position:relative;right:83.3333%}.el-col-md-push-20{position:relative;left:83.3333%}.el-col-md-21{flex:0 0 87.5%;max-width:87.5%;display:block}.el-col-md-21.is-guttered{display:block}.el-col-md-offset-21{margin-left:87.5%}.el-col-md-pull-21{position:relative;right:87.5%}.el-col-md-push-21{position:relative;left:87.5%}.el-col-md-22{flex:0 0 91.6667%;max-width:91.6667%;display:block}.el-col-md-22.is-guttered{display:block}.el-col-md-offset-22{margin-left:91.6667%}.el-col-md-pull-22{position:relative;right:91.6667%}.el-col-md-push-22{position:relative;left:91.6667%}.el-col-md-23{flex:0 0 95.8333%;max-width:95.8333%;display:block}.el-col-md-23.is-guttered{display:block}.el-col-md-offset-23{margin-left:95.8333%}.el-col-md-pull-23{position:relative;right:95.8333%}.el-col-md-push-23{position:relative;left:95.8333%}.el-col-md-24{flex:0 0 100%;max-width:100%;display:block}.el-col-md-24.is-guttered{display:block}.el-col-md-offset-24{margin-left:100%}.el-col-md-pull-24{position:relative;right:100%}.el-col-md-push-24{position:relative;left:100%}}@media only screen and (min-width:1200px){.el-col-lg-0{flex:0 0;max-width:0%;display:none}.el-col-lg-0.is-guttered{display:none}.el-col-lg-offset-0{margin-left:0%}.el-col-lg-pull-0{position:relative;right:0%}.el-col-lg-push-0{position:relative;left:0%}.el-col-lg-1{flex:0 0 4.16667%;max-width:4.16667%;display:block}.el-col-lg-1.is-guttered{display:block}.el-col-lg-offset-1{margin-left:4.16667%}.el-col-lg-pull-1{position:relative;right:4.16667%}.el-col-lg-push-1{position:relative;left:4.16667%}.el-col-lg-2{flex:0 0 8.33333%;max-width:8.33333%;display:block}.el-col-lg-2.is-guttered{display:block}.el-col-lg-offset-2{margin-left:8.33333%}.el-col-lg-pull-2{position:relative;right:8.33333%}.el-col-lg-push-2{position:relative;left:8.33333%}.el-col-lg-3{flex:0 0 12.5%;max-width:12.5%;display:block}.el-col-lg-3.is-guttered{display:block}.el-col-lg-offset-3{margin-left:12.5%}.el-col-lg-pull-3{position:relative;right:12.5%}.el-col-lg-push-3{position:relative;left:12.5%}.el-col-lg-4{flex:0 0 16.6667%;max-width:16.6667%;display:block}.el-col-lg-4.is-guttered{display:block}.el-col-lg-offset-4{margin-left:16.6667%}.el-col-lg-pull-4{position:relative;right:16.6667%}.el-col-lg-push-4{position:relative;left:16.6667%}.el-col-lg-5{flex:0 0 20.8333%;max-width:20.8333%;display:block}.el-col-lg-5.is-guttered{display:block}.el-col-lg-offset-5{margin-left:20.8333%}.el-col-lg-pull-5{position:relative;right:20.8333%}.el-col-lg-push-5{position:relative;left:20.8333%}.el-col-lg-6{flex:0 0 25%;max-width:25%;display:block}.el-col-lg-6.is-guttered{display:block}.el-col-lg-offset-6{margin-left:25%}.el-col-lg-pull-6{position:relative;right:25%}.el-col-lg-push-6{position:relative;left:25%}.el-col-lg-7{flex:0 0 29.1667%;max-width:29.1667%;display:block}.el-col-lg-7.is-guttered{display:block}.el-col-lg-offset-7{margin-left:29.1667%}.el-col-lg-pull-7{position:relative;right:29.1667%}.el-col-lg-push-7{position:relative;left:29.1667%}.el-col-lg-8{flex:0 0 33.3333%;max-width:33.3333%;display:block}.el-col-lg-8.is-guttered{display:block}.el-col-lg-offset-8{margin-left:33.3333%}.el-col-lg-pull-8{position:relative;right:33.3333%}.el-col-lg-push-8{position:relative;left:33.3333%}.el-col-lg-9{flex:0 0 37.5%;max-width:37.5%;display:block}.el-col-lg-9.is-guttered{display:block}.el-col-lg-offset-9{margin-left:37.5%}.el-col-lg-pull-9{position:relative;right:37.5%}.el-col-lg-push-9{position:relative;left:37.5%}.el-col-lg-10{flex:0 0 41.6667%;max-width:41.6667%;display:block}.el-col-lg-10.is-guttered{display:block}.el-col-lg-offset-10{margin-left:41.6667%}.el-col-lg-pull-10{position:relative;right:41.6667%}.el-col-lg-push-10{position:relative;left:41.6667%}.el-col-lg-11{flex:0 0 45.8333%;max-width:45.8333%;display:block}.el-col-lg-11.is-guttered{display:block}.el-col-lg-offset-11{margin-left:45.8333%}.el-col-lg-pull-11{position:relative;right:45.8333%}.el-col-lg-push-11{position:relative;left:45.8333%}.el-col-lg-12{flex:0 0 50%;max-width:50%;display:block}.el-col-lg-12.is-guttered{display:block}.el-col-lg-offset-12{margin-left:50%}.el-col-lg-pull-12{position:relative;right:50%}.el-col-lg-push-12{position:relative;left:50%}.el-col-lg-13{flex:0 0 54.1667%;max-width:54.1667%;display:block}.el-col-lg-13.is-guttered{display:block}.el-col-lg-offset-13{margin-left:54.1667%}.el-col-lg-pull-13{position:relative;right:54.1667%}.el-col-lg-push-13{position:relative;left:54.1667%}.el-col-lg-14{flex:0 0 58.3333%;max-width:58.3333%;display:block}.el-col-lg-14.is-guttered{display:block}.el-col-lg-offset-14{margin-left:58.3333%}.el-col-lg-pull-14{position:relative;right:58.3333%}.el-col-lg-push-14{position:relative;left:58.3333%}.el-col-lg-15{flex:0 0 62.5%;max-width:62.5%;display:block}.el-col-lg-15.is-guttered{display:block}.el-col-lg-offset-15{margin-left:62.5%}.el-col-lg-pull-15{position:relative;right:62.5%}.el-col-lg-push-15{position:relative;left:62.5%}.el-col-lg-16{flex:0 0 66.6667%;max-width:66.6667%;display:block}.el-col-lg-16.is-guttered{display:block}.el-col-lg-offset-16{margin-left:66.6667%}.el-col-lg-pull-16{position:relative;right:66.6667%}.el-col-lg-push-16{position:relative;left:66.6667%}.el-col-lg-17{flex:0 0 70.8333%;max-width:70.8333%;display:block}.el-col-lg-17.is-guttered{display:block}.el-col-lg-offset-17{margin-left:70.8333%}.el-col-lg-pull-17{position:relative;right:70.8333%}.el-col-lg-push-17{position:relative;left:70.8333%}.el-col-lg-18{flex:0 0 75%;max-width:75%;display:block}.el-col-lg-18.is-guttered{display:block}.el-col-lg-offset-18{margin-left:75%}.el-col-lg-pull-18{position:relative;right:75%}.el-col-lg-push-18{position:relative;left:75%}.el-col-lg-19{flex:0 0 79.1667%;max-width:79.1667%;display:block}.el-col-lg-19.is-guttered{display:block}.el-col-lg-offset-19{margin-left:79.1667%}.el-col-lg-pull-19{position:relative;right:79.1667%}.el-col-lg-push-19{position:relative;left:79.1667%}.el-col-lg-20{flex:0 0 83.3333%;max-width:83.3333%;display:block}.el-col-lg-20.is-guttered{display:block}.el-col-lg-offset-20{margin-left:83.3333%}.el-col-lg-pull-20{position:relative;right:83.3333%}.el-col-lg-push-20{position:relative;left:83.3333%}.el-col-lg-21{flex:0 0 87.5%;max-width:87.5%;display:block}.el-col-lg-21.is-guttered{display:block}.el-col-lg-offset-21{margin-left:87.5%}.el-col-lg-pull-21{position:relative;right:87.5%}.el-col-lg-push-21{position:relative;left:87.5%}.el-col-lg-22{flex:0 0 91.6667%;max-width:91.6667%;display:block}.el-col-lg-22.is-guttered{display:block}.el-col-lg-offset-22{margin-left:91.6667%}.el-col-lg-pull-22{position:relative;right:91.6667%}.el-col-lg-push-22{position:relative;left:91.6667%}.el-col-lg-23{flex:0 0 95.8333%;max-width:95.8333%;display:block}.el-col-lg-23.is-guttered{display:block}.el-col-lg-offset-23{margin-left:95.8333%}.el-col-lg-pull-23{position:relative;right:95.8333%}.el-col-lg-push-23{position:relative;left:95.8333%}.el-col-lg-24{flex:0 0 100%;max-width:100%;display:block}.el-col-lg-24.is-guttered{display:block}.el-col-lg-offset-24{margin-left:100%}.el-col-lg-pull-24{position:relative;right:100%}.el-col-lg-push-24{position:relative;left:100%}}@media only screen and (min-width:1920px){.el-col-xl-0{flex:0 0;max-width:0%;display:none}.el-col-xl-0.is-guttered{display:none}.el-col-xl-offset-0{margin-left:0%}.el-col-xl-pull-0{position:relative;right:0%}.el-col-xl-push-0{position:relative;left:0%}.el-col-xl-1{flex:0 0 4.16667%;max-width:4.16667%;display:block}.el-col-xl-1.is-guttered{display:block}.el-col-xl-offset-1{margin-left:4.16667%}.el-col-xl-pull-1{position:relative;right:4.16667%}.el-col-xl-push-1{position:relative;left:4.16667%}.el-col-xl-2{flex:0 0 8.33333%;max-width:8.33333%;display:block}.el-col-xl-2.is-guttered{display:block}.el-col-xl-offset-2{margin-left:8.33333%}.el-col-xl-pull-2{position:relative;right:8.33333%}.el-col-xl-push-2{position:relative;left:8.33333%}.el-col-xl-3{flex:0 0 12.5%;max-width:12.5%;display:block}.el-col-xl-3.is-guttered{display:block}.el-col-xl-offset-3{margin-left:12.5%}.el-col-xl-pull-3{position:relative;right:12.5%}.el-col-xl-push-3{position:relative;left:12.5%}.el-col-xl-4{flex:0 0 16.6667%;max-width:16.6667%;display:block}.el-col-xl-4.is-guttered{display:block}.el-col-xl-offset-4{margin-left:16.6667%}.el-col-xl-pull-4{position:relative;right:16.6667%}.el-col-xl-push-4{position:relative;left:16.6667%}.el-col-xl-5{flex:0 0 20.8333%;max-width:20.8333%;display:block}.el-col-xl-5.is-guttered{display:block}.el-col-xl-offset-5{margin-left:20.8333%}.el-col-xl-pull-5{position:relative;right:20.8333%}.el-col-xl-push-5{position:relative;left:20.8333%}.el-col-xl-6{flex:0 0 25%;max-width:25%;display:block}.el-col-xl-6.is-guttered{display:block}.el-col-xl-offset-6{margin-left:25%}.el-col-xl-pull-6{position:relative;right:25%}.el-col-xl-push-6{position:relative;left:25%}.el-col-xl-7{flex:0 0 29.1667%;max-width:29.1667%;display:block}.el-col-xl-7.is-guttered{display:block}.el-col-xl-offset-7{margin-left:29.1667%}.el-col-xl-pull-7{position:relative;right:29.1667%}.el-col-xl-push-7{position:relative;left:29.1667%}.el-col-xl-8{flex:0 0 33.3333%;max-width:33.3333%;display:block}.el-col-xl-8.is-guttered{display:block}.el-col-xl-offset-8{margin-left:33.3333%}.el-col-xl-pull-8{position:relative;right:33.3333%}.el-col-xl-push-8{position:relative;left:33.3333%}.el-col-xl-9{flex:0 0 37.5%;max-width:37.5%;display:block}.el-col-xl-9.is-guttered{display:block}.el-col-xl-offset-9{margin-left:37.5%}.el-col-xl-pull-9{position:relative;right:37.5%}.el-col-xl-push-9{position:relative;left:37.5%}.el-col-xl-10{flex:0 0 41.6667%;max-width:41.6667%;display:block}.el-col-xl-10.is-guttered{display:block}.el-col-xl-offset-10{margin-left:41.6667%}.el-col-xl-pull-10{position:relative;right:41.6667%}.el-col-xl-push-10{position:relative;left:41.6667%}.el-col-xl-11{flex:0 0 45.8333%;max-width:45.8333%;display:block}.el-col-xl-11.is-guttered{display:block}.el-col-xl-offset-11{margin-left:45.8333%}.el-col-xl-pull-11{position:relative;right:45.8333%}.el-col-xl-push-11{position:relative;left:45.8333%}.el-col-xl-12{flex:0 0 50%;max-width:50%;display:block}.el-col-xl-12.is-guttered{display:block}.el-col-xl-offset-12{margin-left:50%}.el-col-xl-pull-12{position:relative;right:50%}.el-col-xl-push-12{position:relative;left:50%}.el-col-xl-13{flex:0 0 54.1667%;max-width:54.1667%;display:block}.el-col-xl-13.is-guttered{display:block}.el-col-xl-offset-13{margin-left:54.1667%}.el-col-xl-pull-13{position:relative;right:54.1667%}.el-col-xl-push-13{position:relative;left:54.1667%}.el-col-xl-14{flex:0 0 58.3333%;max-width:58.3333%;display:block}.el-col-xl-14.is-guttered{display:block}.el-col-xl-offset-14{margin-left:58.3333%}.el-col-xl-pull-14{position:relative;right:58.3333%}.el-col-xl-push-14{position:relative;left:58.3333%}.el-col-xl-15{flex:0 0 62.5%;max-width:62.5%;display:block}.el-col-xl-15.is-guttered{display:block}.el-col-xl-offset-15{margin-left:62.5%}.el-col-xl-pull-15{position:relative;right:62.5%}.el-col-xl-push-15{position:relative;left:62.5%}.el-col-xl-16{flex:0 0 66.6667%;max-width:66.6667%;display:block}.el-col-xl-16.is-guttered{display:block}.el-col-xl-offset-16{margin-left:66.6667%}.el-col-xl-pull-16{position:relative;right:66.6667%}.el-col-xl-push-16{position:relative;left:66.6667%}.el-col-xl-17{flex:0 0 70.8333%;max-width:70.8333%;display:block}.el-col-xl-17.is-guttered{display:block}.el-col-xl-offset-17{margin-left:70.8333%}.el-col-xl-pull-17{position:relative;right:70.8333%}.el-col-xl-push-17{position:relative;left:70.8333%}.el-col-xl-18{flex:0 0 75%;max-width:75%;display:block}.el-col-xl-18.is-guttered{display:block}.el-col-xl-offset-18{margin-left:75%}.el-col-xl-pull-18{position:relative;right:75%}.el-col-xl-push-18{position:relative;left:75%}.el-col-xl-19{flex:0 0 79.1667%;max-width:79.1667%;display:block}.el-col-xl-19.is-guttered{display:block}.el-col-xl-offset-19{margin-left:79.1667%}.el-col-xl-pull-19{position:relative;right:79.1667%}.el-col-xl-push-19{position:relative;left:79.1667%}.el-col-xl-20{flex:0 0 83.3333%;max-width:83.3333%;display:block}.el-col-xl-20.is-guttered{display:block}.el-col-xl-offset-20{margin-left:83.3333%}.el-col-xl-pull-20{position:relative;right:83.3333%}.el-col-xl-push-20{position:relative;left:83.3333%}.el-col-xl-21{flex:0 0 87.5%;max-width:87.5%;display:block}.el-col-xl-21.is-guttered{display:block}.el-col-xl-offset-21{margin-left:87.5%}.el-col-xl-pull-21{position:relative;right:87.5%}.el-col-xl-push-21{position:relative;left:87.5%}.el-col-xl-22{flex:0 0 91.6667%;max-width:91.6667%;display:block}.el-col-xl-22.is-guttered{display:block}.el-col-xl-offset-22{margin-left:91.6667%}.el-col-xl-pull-22{position:relative;right:91.6667%}.el-col-xl-push-22{position:relative;left:91.6667%}.el-col-xl-23{flex:0 0 95.8333%;max-width:95.8333%;display:block}.el-col-xl-23.is-guttered{display:block}.el-col-xl-offset-23{margin-left:95.8333%}.el-col-xl-pull-23{position:relative;right:95.8333%}.el-col-xl-push-23{position:relative;left:95.8333%}.el-col-xl-24{flex:0 0 100%;max-width:100%;display:block}.el-col-xl-24.is-guttered{display:block}.el-col-xl-offset-24{margin-left:100%}.el-col-xl-pull-24{position:relative;right:100%}.el-col-xl-push-24{position:relative;left:100%}}.el-collapse-item.is-disabled .el-collapse-item__header{color:var(--el-text-color-disabled);cursor:not-allowed}.el-collapse-item__header{width:100%;min-height:var(--el-collapse-header-height);line-height:var(--el-collapse-header-height);background-color:var(--el-collapse-header-bg-color);color:var(--el-collapse-header-text-color);cursor:pointer;border:none;border-bottom:1px solid var(--el-collapse-border-color);font-size:var(--el-collapse-header-font-size);transition:border-bottom-color var(--el-transition-duration);box-sizing:border-box;outline:none;align-items:center;padding:0;font-weight:500;display:flex}.el-collapse-item__arrow{transition:transform var(--el-transition-duration);font-weight:300}.el-collapse-item__arrow.is-active{transform:rotate(90deg)}.el-collapse-item__title{text-align:left;flex:auto}.el-collapse-item__header.focusing:focus:not(:hover){color:var(--el-color-primary)}.el-collapse-item__header.is-active{border-bottom-color:#0000}.el-collapse-item__wrap{will-change:height;background-color:var(--el-collapse-content-bg-color);box-sizing:border-box;border-bottom:1px solid var(--el-collapse-border-color);overflow:hidden}.el-collapse-item__content{font-size:var(--el-collapse-content-font-size);color:var(--el-collapse-content-text-color);padding-bottom:25px;line-height:1.76923}.el-collapse-item:last-child{margin-bottom:-1px}.el-collapse{--el-collapse-border-color:var(--el-border-color-lighter);--el-collapse-header-height:48px;--el-collapse-header-bg-color:var(--el-fill-color-blank);--el-collapse-header-text-color:var(--el-text-color-primary);--el-collapse-header-font-size:13px;--el-collapse-content-bg-color:var(--el-fill-color-blank);--el-collapse-content-font-size:13px;--el-collapse-content-text-color:var(--el-text-color-primary);border-top:1px solid var(--el-collapse-border-color);border-bottom:1px solid var(--el-collapse-border-color)}.el-collapse-icon-position-left .el-collapse-item__header{gap:8px}.el-collapse-icon-position-left .el-collapse-item__title{order:1}.el-collapse-icon-position-right .el-collapse-item__header{padding-right:8px}.el-color-picker-panel{--el-colorpicker-bg-color:var(--el-bg-color-overlay);--el-fill-color-blank:var(--el-colorpicker-bg-color);box-sizing:content-box;background:var(--el-colorpicker-bg-color);width:300px;padding:12px}.el-color-picker-panel.is-border{border:solid 1px var(--el-border-color-lighter);border-radius:4px}.el-color-picker-panel__wrapper{margin-bottom:6px}.el-color-picker-panel__footer{text-align:right;justify-content:space-between;margin-top:12px;display:flex}.el-color-picker-panel__footer .el-input{color:#000;width:160px;font-size:12px;line-height:26px}.el-color-picker-panel.is-disabled .el-color-svpanel,.el-color-picker-panel.is-disabled .el-color-hue-slider{cursor:not-allowed;opacity:.3}.el-color-picker-panel.is-disabled .el-color-hue-slider__thumb{cursor:not-allowed}.el-color-picker-panel.is-disabled .el-color-alpha-slider,.el-color-picker-panel.is-disabled .el-color-predefine .el-color-predefine__color-selector{cursor:not-allowed;opacity:.3}.el-color-predefine{width:280px;margin-top:8px;font-size:12px;display:flex}.el-color-predefine__colors{flex-wrap:wrap;flex:1;gap:8px;display:flex}.el-color-predefine__color-selector{border-radius:var(--el-border-radius-base);cursor:pointer;border:none;outline:none;width:20px;height:20px;padding:0;overflow:hidden}.el-color-predefine__color-selector.selected{box-shadow:0 0 3px 2px var(--el-color-primary)}.el-color-predefine__color-selector:focus-visible{outline:2px solid var(--el-color-primary);outline-offset:2px}.el-color-predefine__color-selector>div{height:100%;display:flex}.el-color-predefine__color-selector.is-alpha{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAIAAADZF8uwAAAAGUlEQVQYV2M4gwH+YwCGIasIUwhT25BVBADtzYNYrHvv4gAAAABJRU5ErkJggg==)}.el-color-hue-slider{box-sizing:border-box;float:right;background-color:red;width:280px;height:12px;padding:0 2px;position:relative}.el-color-hue-slider__bar{background:linear-gradient(90deg,red,#ff0 17%,#0f0 33%,#0ff,#00f 67%,#f0f 83%,red);height:100%;position:relative}.el-color-hue-slider__thumb{cursor:pointer;box-sizing:border-box;border:1px solid var(--el-border-color-lighter);z-index:1;background:#fff;border-radius:1px;width:4px;height:100%;position:absolute;top:0;left:0;box-shadow:0 0 2px #0009}.el-color-hue-slider__thumb:focus-visible{outline:2px solid var(--el-color-primary);outline-offset:1px}.el-color-hue-slider.is-vertical{width:12px;height:180px;padding:2px 0}.el-color-hue-slider.is-vertical .el-color-hue-slider__bar{background:linear-gradient(red,#ff0 17%,#0f0 33%,#0ff,#00f 67%,#f0f 83%,red)}.el-color-hue-slider.is-vertical .el-color-hue-slider__thumb{width:100%;height:4px;top:0;left:0}.el-color-svpanel{background-image:linear-gradient(#0000,#000),linear-gradient(90deg,#fff,#fff0);width:280px;height:180px;position:relative}.el-color-svpanel__cursor{cursor:pointer;border-radius:50%;width:4px;height:4px;position:absolute;transform:translate(-2px,-2px);box-shadow:0 0 0 1.5px #fff,inset 0 0 1px 1px #0000004d,0 0 1px 2px #0006}.el-color-svpanel__cursor:focus-visible{outline:2px solid var(--el-color-primary);outline-offset:2px}.el-color-alpha-slider{box-sizing:border-box;background-image:linear-gradient(45deg,var(--el-color-picker-alpha-bg-a) 25%,var(--el-color-picker-alpha-bg-b) 25%),linear-gradient(135deg,var(--el-color-picker-alpha-bg-a) 25%,var(--el-color-picker-alpha-bg-b) 25%),linear-gradient(45deg,var(--el-color-picker-alpha-bg-b) 75%,var(--el-color-picker-alpha-bg-a) 75%),linear-gradient(135deg,var(--el-color-picker-alpha-bg-b) 75%,var(--el-color-picker-alpha-bg-a) 75%);background-position:0 0,6px 0,6px -6px,0 6px;background-size:12px 12px;width:280px;height:12px;position:relative}.el-color-alpha-slider.is-disabled .el-color-alpha-slider__thumb{cursor:not-allowed}.el-color-alpha-slider__bar{background:linear-gradient(to right,#fff0 0%,var(--el-bg-color) 100%);height:100%;position:relative}.el-color-alpha-slider__thumb{cursor:pointer;box-sizing:border-box;border:1px solid var(--el-border-color-lighter);z-index:1;background:#fff;border-radius:1px;width:4px;height:100%;position:absolute;top:0;left:0;box-shadow:0 0 2px #0009}.el-color-alpha-slider__thumb:focus-visible{outline:2px solid var(--el-color-primary);outline-offset:1px}.el-color-alpha-slider.is-vertical{width:20px;height:180px}.el-color-alpha-slider.is-vertical .el-color-alpha-slider__bar{background:linear-gradient(#fff0,#fff)}.el-color-alpha-slider.is-vertical .el-color-alpha-slider__thumb{width:100%;height:4px;top:0;left:0}.el-color-picker-panel{--el-color-picker-alpha-bg-a:#ccc;--el-color-picker-alpha-bg-b:transparent}.dark .el-color-picker-panel{--el-color-picker-alpha-bg-a:#333}.el-color-picker{outline:none;width:32px;height:32px;line-height:normal;display:inline-block;position:relative}.el-color-picker:hover:not(:-webkit-any(.is-disabled,.is-focused)) .el-color-picker__trigger{border-color:var(--el-border-color-hover)}.el-color-picker:hover:not(:is(.is-disabled,.is-focused)) .el-color-picker__trigger{border-color:var(--el-border-color-hover)}.el-color-picker:focus-visible:not(.is-disabled) .el-color-picker__trigger{outline:2px solid var(--el-color-primary);outline-offset:1px}.el-color-picker.is-focused .el-color-picker__trigger{border-color:var(--el-color-primary)}.el-color-picker.is-disabled .el-color-picker__trigger{cursor:not-allowed;background-color:var(--el-fill-color-light)}.el-color-picker.is-disabled .el-color-picker__color{opacity:.3}.el-color-picker--large{width:40px;height:40px}.el-color-picker--small{width:24px;height:24px}.el-color-picker--small .el-color-picker__icon,.el-color-picker--small .el-color-picker__empty{transform:scale(.8)}.el-color-picker__trigger{box-sizing:border-box;border:1px solid var(--el-border-color);cursor:pointer;border-radius:4px;justify-content:center;align-items:center;width:100%;height:100%;padding:4px;font-size:0;display:inline-flex;position:relative}.el-color-picker__color{box-sizing:border-box;border:1px solid var(--el-text-color-secondary);border-radius:var(--el-border-radius-small);text-align:center;width:100%;height:100%;display:block;position:relative}.el-color-picker__color.is-alpha{background-image:linear-gradient(45deg,var(--el-color-picker-alpha-bg-a) 25%,var(--el-color-picker-alpha-bg-b) 25%),linear-gradient(135deg,var(--el-color-picker-alpha-bg-a) 25%,var(--el-color-picker-alpha-bg-b) 25%),linear-gradient(45deg,var(--el-color-picker-alpha-bg-b) 75%,var(--el-color-picker-alpha-bg-a) 75%),linear-gradient(135deg,var(--el-color-picker-alpha-bg-b) 75%,var(--el-color-picker-alpha-bg-a) 75%);background-position:0 0,6px 0,6px -6px,0 6px;background-size:12px 12px}.el-color-picker__color-inner{justify-content:center;align-items:center;width:100%;height:100%;display:inline-flex}.el-color-picker .el-color-picker__empty{color:var(--el-text-color-secondary);font-size:12px}.el-color-picker .el-color-picker__icon{color:#fff;justify-content:center;align-items:center;font-size:12px;display:inline-flex}.el-color-picker__panel{border-radius:var(--el-border-radius-base);box-shadow:var(--el-box-shadow-light);background-color:#fff}.el-color-picker__panel.el-popper{border:1px solid var(--el-border-color-lighter)}.el-color-picker,.el-color-picker__panel{--el-color-picker-alpha-bg-a:#ccc;--el-color-picker-alpha-bg-b:transparent}.dark .el-color-picker,.dark .el-color-picker__panel{--el-color-picker-alpha-bg-a:#333}.el-container{box-sizing:border-box;flex-direction:row;flex:auto;min-width:0;display:flex}.el-container.is-vertical{flex-direction:column}.el-date-table{-webkit-user-select:none;user-select:none;font-size:12px}.el-date-table.is-week-mode .el-date-table__row:hover .el-date-table-cell{background-color:var(--el-datepicker-inrange-bg-color)}.el-date-table.is-week-mode .el-date-table__row:hover td.available:hover{color:var(--el-datepicker-text-color)}.el-date-table.is-week-mode .el-date-table__row:hover td:first-child .el-date-table-cell{border-top-left-radius:15px;border-bottom-left-radius:15px;margin-left:5px}.el-date-table.is-week-mode .el-date-table__row:hover td:last-child .el-date-table-cell{border-top-right-radius:15px;border-bottom-right-radius:15px;margin-right:5px}.el-date-table.is-week-mode .el-date-table__row.current .el-date-table-cell{background-color:var(--el-datepicker-inrange-bg-color)}.el-date-table td{box-sizing:border-box;text-align:center;cursor:pointer;width:32px;height:30px;padding:4px 0;position:relative}.el-date-table td .el-date-table-cell{box-sizing:border-box;height:30px;padding:3px 0}.el-date-table td .el-date-table-cell .el-date-table-cell__text{border-radius:50%;width:24px;height:24px;margin:0 auto;line-height:24px;display:block;position:absolute;left:50%;transform:translate(-50%)}.el-date-table td.next-month,.el-date-table td.prev-month{color:var(--el-datepicker-off-text-color)}.el-date-table td.today{position:relative}.el-date-table td.today .el-date-table-cell__text{color:var(--el-color-primary);font-weight:700}.el-date-table td.today.start-date .el-date-table-cell__text,.el-date-table td.today.end-date .el-date-table-cell__text{color:#fff}.el-date-table td.available:hover{color:var(--el-datepicker-hover-text-color)}.el-date-table td.in-range .el-date-table-cell{background-color:var(--el-datepicker-inrange-bg-color)}.el-date-table td.in-range .el-date-table-cell:hover{background-color:var(--el-datepicker-inrange-hover-bg-color)}.el-date-table td.current:not(.disabled) .el-date-table-cell__text{color:#fff;background-color:var(--el-datepicker-active-color)}.el-date-table td.current:not(.disabled):focus-visible .el-date-table-cell__text{outline:2px solid var(--el-datepicker-active-color);outline-offset:1px}.el-date-table td.start-date .el-date-table-cell,.el-date-table td.end-date .el-date-table-cell{color:#fff}.el-date-table td.start-date .el-date-table-cell__text,.el-date-table td.end-date .el-date-table-cell__text{background-color:var(--el-datepicker-active-color)}.el-date-table td.start-date .el-date-table-cell{border-top-left-radius:15px;border-bottom-left-radius:15px;margin-left:5px}.el-date-table td.end-date .el-date-table-cell{border-top-right-radius:15px;border-bottom-right-radius:15px;margin-right:5px}.el-date-table td.disabled .el-date-table-cell{background-color:var(--el-fill-color-light);opacity:1;cursor:not-allowed;color:var(--el-text-color-placeholder)}.el-date-table td.selected .el-date-table-cell{border-radius:15px;margin-left:5px;margin-right:5px}.el-date-table td.selected .el-date-table-cell__text{background-color:var(--el-datepicker-active-color);color:#fff;border-radius:15px}.el-date-table td.week{color:var(--el-datepicker-off-text-color);cursor:default;font-size:80%}.el-date-table td:focus{outline:none}.el-date-table th{color:var(--el-datepicker-header-text-color);border-bottom:solid 1px var(--el-border-color-lighter);padding:5px;font-weight:400}.el-date-table th.el-date-table__week-header{width:24px;padding:0}.el-month-table{border-collapse:collapse;margin:-1px;font-size:12px}.el-month-table td{text-align:center;cursor:pointer;width:68px;padding:8px 0;position:relative}.el-month-table td .el-date-table-cell{box-sizing:border-box;height:48px;padding:6px 0}.el-month-table td.today .el-date-table-cell__text{color:var(--el-color-primary);font-weight:700}.el-month-table td.today.start-date .el-date-table-cell__text,.el-month-table td.today.end-date .el-date-table-cell__text{color:#fff}.el-month-table td.disabled .el-date-table-cell__text{background-color:var(--el-fill-color-light);cursor:not-allowed;color:var(--el-text-color-placeholder)}.el-month-table td.disabled .el-date-table-cell__text:hover{color:var(--el-text-color-placeholder)}.el-month-table td .el-date-table-cell__text{width:54px;height:36px;color:var(--el-datepicker-text-color);border-radius:18px;margin:0 auto;line-height:36px;display:block;position:absolute;left:50%;transform:translate(-50%)}.el-month-table td .el-date-table-cell__text:hover{color:var(--el-datepicker-hover-text-color)}.el-month-table td.in-range .el-date-table-cell{background-color:var(--el-datepicker-inrange-bg-color)}.el-month-table td.in-range .el-date-table-cell:hover{background-color:var(--el-datepicker-inrange-hover-bg-color)}.el-month-table td.start-date .el-date-table-cell,.el-month-table td.end-date .el-date-table-cell{color:#fff}.el-month-table td.start-date .el-date-table-cell__text,.el-month-table td.end-date .el-date-table-cell__text{color:#fff;background-color:var(--el-datepicker-active-color)}.el-month-table td.start-date .el-date-table-cell{border-top-left-radius:24px;border-bottom-left-radius:24px;margin-left:3px}.el-month-table td.end-date .el-date-table-cell{border-top-right-radius:24px;border-bottom-right-radius:24px;margin-right:3px}.el-month-table td.current:not(.disabled) .el-date-table-cell{border-radius:24px;margin-left:3px;margin-right:3px}.el-month-table td.current:not(.disabled) .el-date-table-cell__text{color:#fff;background-color:var(--el-datepicker-active-color)}.el-month-table td:focus-visible{outline:none}.el-month-table td:focus-visible .el-date-table-cell__text{outline:2px solid var(--el-datepicker-active-color);outline-offset:1px}.el-year-table{border-collapse:collapse;margin:-1px;font-size:12px}.el-year-table .el-icon{color:var(--el-datepicker-icon-color)}.el-year-table td{text-align:center;cursor:pointer;width:68px;padding:8px 0;position:relative}.el-year-table td .el-date-table-cell{box-sizing:border-box;height:48px;padding:6px 0}.el-year-table td.today .el-date-table-cell__text{color:var(--el-color-primary);font-weight:700}.el-year-table td.today.start-date .el-date-table-cell__text,.el-year-table td.today.end-date .el-date-table-cell__text{color:#fff}.el-year-table td.disabled .el-date-table-cell__text{background-color:var(--el-fill-color-light);cursor:not-allowed;color:var(--el-text-color-placeholder)}.el-year-table td.disabled .el-date-table-cell__text:hover{color:var(--el-text-color-placeholder)}.el-year-table td .el-date-table-cell__text{width:60px;height:36px;color:var(--el-datepicker-text-color);border-radius:18px;margin:0 auto;line-height:36px;display:block;position:absolute;left:50%;transform:translate(-50%)}.el-year-table td .el-date-table-cell__text:hover{color:var(--el-datepicker-hover-text-color)}.el-year-table td.in-range .el-date-table-cell{background-color:var(--el-datepicker-inrange-bg-color)}.el-year-table td.in-range .el-date-table-cell:hover{background-color:var(--el-datepicker-inrange-hover-bg-color)}.el-year-table td.start-date .el-date-table-cell,.el-year-table td.end-date .el-date-table-cell{color:#fff}.el-year-table td.start-date .el-date-table-cell__text,.el-year-table td.end-date .el-date-table-cell__text{color:#fff;background-color:var(--el-datepicker-active-color)}.el-year-table td.start-date .el-date-table-cell{border-top-left-radius:24px;border-bottom-left-radius:24px}.el-year-table td.end-date .el-date-table-cell{border-top-right-radius:24px;border-bottom-right-radius:24px}.el-year-table td.current:not(.disabled) .el-date-table-cell__text{color:#fff;background-color:var(--el-datepicker-active-color)}.el-year-table td:focus-visible{outline:none}.el-year-table td:focus-visible .el-date-table-cell__text{outline:2px solid var(--el-datepicker-active-color);outline-offset:1px}.el-time-spinner.has-seconds .el-time-spinner__wrapper{width:33.3%}.el-time-spinner__wrapper{vertical-align:top;width:50%;max-height:192px;display:inline-block;position:relative;overflow:auto}.el-time-spinner__wrapper.el-scrollbar__wrap:not(.el-scrollbar__wrap--hidden-default){padding-bottom:15px}.el-time-spinner__wrapper.is-arrow{box-sizing:border-box;text-align:center;overflow:hidden}.el-time-spinner__wrapper.is-arrow .el-time-spinner__list{transform:translateY(-32px)}.el-time-spinner__wrapper.is-arrow .el-time-spinner__item:hover:not(.is-disabled):not(.is-active){background:var(--el-fill-color-light);cursor:default}.el-time-spinner__arrow{color:var(--el-text-color-secondary);width:100%;z-index:var(--el-index-normal);text-align:center;cursor:pointer;height:30px;font-size:12px;line-height:30px;position:absolute;left:0}.el-time-spinner__arrow:hover{color:var(--el-color-primary)}.el-time-spinner__arrow.arrow-up{top:10px}.el-time-spinner__arrow.arrow-down{bottom:10px}.el-time-spinner__input.el-input{width:70%}.el-time-spinner__input.el-input .el-input__inner{text-align:center;padding:0}.el-time-spinner__list{text-align:center;margin:0;padding:0;list-style:none}.el-time-spinner__list:after,.el-time-spinner__list:before{content:"";width:100%;height:80px;display:block}.el-time-spinner__item{height:32px;color:var(--el-text-color-regular);font-size:12px;line-height:32px}.el-time-spinner__item:hover:not(.is-disabled):not(.is-active){background:var(--el-fill-color-light);cursor:pointer}.el-time-spinner__item.is-active:not(.is-disabled){color:var(--el-text-color-primary);font-weight:700}.el-time-spinner__item.is-disabled{color:var(--el-text-color-placeholder);cursor:not-allowed}.el-picker__popper{--el-datepicker-border-color:var(--el-disabled-border-color)}.el-picker__popper.el-popper{background:var(--el-bg-color-overlay);border:1px solid var(--el-datepicker-border-color);box-shadow:var(--el-box-shadow-light)}.el-picker__popper.el-popper .el-popper__arrow:before{border:1px solid var(--el-datepicker-border-color)}.el-picker__popper.el-popper[data-popper-placement^=top] .el-popper__arrow:before{border-top-color:#0000;border-left-color:#0000}.el-picker__popper.el-popper[data-popper-placement^=bottom] .el-popper__arrow:before{border-bottom-color:#0000;border-right-color:#0000}.el-picker__popper.el-popper[data-popper-placement^=left] .el-popper__arrow:before{border-bottom-color:#0000;border-left-color:#0000}.el-picker__popper.el-popper[data-popper-placement^=right] .el-popper__arrow:before{border-top-color:#0000;border-right-color:#0000}.el-date-editor{--el-date-editor-width:220px;--el-date-editor-monthrange-width:300px;--el-date-editor-daterange-width:350px;--el-date-editor-datetimerange-width:400px;--el-input-text-color:var(--el-text-color-regular);--el-input-border:var(--el-border);--el-input-hover-border:var(--el-border-color-hover);--el-input-focus-border:var(--el-color-primary);--el-input-transparent-border:0 0 0 1px transparent inset;--el-input-border-color:var(--el-border-color);--el-input-border-radius:var(--el-border-radius-base);--el-input-bg-color:var(--el-fill-color-blank);--el-input-icon-color:var(--el-text-color-placeholder);--el-input-placeholder-color:var(--el-text-color-placeholder);--el-input-hover-border-color:var(--el-border-color-hover);--el-input-clear-hover-color:var(--el-text-color-secondary);--el-input-focus-border-color:var(--el-color-primary);--el-input-width:100%;text-align:left;vertical-align:middle;position:relative}.el-date-editor.el-input__wrapper{box-shadow:0 0 0 1px var(--el-input-border-color,var(--el-border-color)) inset}.el-date-editor.el-input__wrapper:hover{box-shadow:0 0 0 1px var(--el-input-hover-border-color) inset}.el-date-editor.is-focus .el-input__wrapper{box-shadow:0 0 0 1px var(--el-input-focus-border-color) inset}.el-date-editor.el-input,.el-date-editor.el-input__wrapper{width:var(--el-date-editor-width);height:var(--el-input-height,var(--el-component-size))}.el-date-editor--monthrange{--el-date-editor-width:var(--el-date-editor-monthrange-width)}.el-date-editor--daterange,.el-date-editor--timerange{--el-date-editor-width:var(--el-date-editor-daterange-width)}.el-date-editor--datetimerange{--el-date-editor-width:var(--el-date-editor-datetimerange-width)}.el-date-editor--dates .el-input__wrapper{text-overflow:ellipsis;white-space:nowrap}.el-date-editor .close-icon,.el-date-editor .clear-icon{cursor:pointer}.el-date-editor .clear-icon:hover{color:var(--el-input-clear-hover-color)}.el-date-editor .el-range__icon{height:inherit;color:var(--el-text-color-placeholder);float:left;font-size:14px}.el-date-editor .el-range__icon svg{vertical-align:middle}.el-date-editor .el-range-input{-webkit-appearance:none;-moz-appearance:none;appearance:none;text-align:center;width:39%;height:30px;line-height:30px;font-size:var(--el-font-size-base);color:var(--el-text-color-regular);background-color:#0000;border:none;outline:none;margin:0;padding:0;display:inline-block}.el-date-editor .el-range-input::placeholder{color:var(--el-text-color-placeholder)}.el-date-editor .el-range-separator{overflow-wrap:break-word;height:100%;color:var(--el-text-color-primary);flex:1;justify-content:center;align-items:center;margin:0;padding:0 5px;font-size:14px;display:inline-flex}.el-date-editor .el-range__close-icon{color:var(--el-text-color-placeholder);height:inherit;width:unset;cursor:pointer;font-size:14px}.el-date-editor .el-range__close-icon:hover{color:var(--el-input-clear-hover-color)}.el-date-editor .el-range__close-icon svg{vertical-align:middle}.el-date-editor .el-range__close-icon--hidden{opacity:0;visibility:hidden}.el-range-editor.el-input__wrapper{vertical-align:middle;align-items:center;padding:0 10px;display:inline-flex}.el-range-editor.is-active,.el-range-editor.is-active:hover{box-shadow:0 0 0 1px var(--el-input-focus-border-color) inset}.el-range-editor--large{line-height:var(--el-component-size-large)}.el-range-editor--large.el-input__wrapper{height:var(--el-component-size-large)}.el-range-editor--large .el-range-separator{font-size:14px;line-height:40px}.el-range-editor--large .el-range-input{height:38px;font-size:14px;line-height:38px}.el-range-editor--small{line-height:var(--el-component-size-small)}.el-range-editor--small.el-input__wrapper{height:var(--el-component-size-small)}.el-range-editor--small .el-range-separator{font-size:12px;line-height:24px}.el-range-editor--small .el-range-input{height:22px;font-size:12px;line-height:22px}.el-range-editor.is-disabled{background-color:var(--el-disabled-bg-color);border-color:var(--el-disabled-border-color);color:var(--el-disabled-text-color);cursor:not-allowed}.el-range-editor.is-disabled:hover,.el-range-editor.is-disabled:focus{border-color:var(--el-disabled-border-color)}.el-range-editor.is-disabled input{background-color:var(--el-disabled-bg-color);color:var(--el-disabled-text-color);cursor:not-allowed}.el-range-editor.is-disabled input::placeholder{color:var(--el-text-color-placeholder)}.el-range-editor.is-disabled .el-range-separator{color:var(--el-disabled-text-color)}.el-picker-panel{color:var(--el-text-color-regular);background:var(--el-datepicker-bg-color);border-radius:var(--el-popper-border-radius,var(--el-border-radius-base));line-height:30px}.el-picker-panel .el-time-panel{border:solid 1px var(--el-datepicker-border-color);background-color:var(--el-datepicker-bg-color);box-shadow:var(--el-box-shadow-light);margin:5px 0}.el-picker-panel__body:after,.el-picker-panel__body-wrapper:after{content:"";clear:both;display:table}.el-picker-panel__content{margin:15px;position:relative}.el-picker-panel__footer{border-top:1px solid var(--el-datepicker-inner-border-color);text-align:right;background-color:var(--el-datepicker-bg-color);padding:4px 12px;font-size:0;position:relative}.el-picker-panel__shortcut{width:100%;color:var(--el-datepicker-text-color);text-align:left;cursor:pointer;background-color:#0000;border:0;outline:none;padding-left:12px;font-size:14px;line-height:28px;display:block}.el-picker-panel__shortcut:hover{color:var(--el-datepicker-hover-text-color)}.el-picker-panel__shortcut.active{color:var(--el-datepicker-active-color);background-color:#e6f1fe}.el-picker-panel__btn{border:1px solid var(--el-fill-color-darker);color:var(--el-text-color-primary);cursor:pointer;background-color:#0000;border-radius:2px;outline:none;padding:0 20px;font-size:12px;line-height:24px}.el-picker-panel__btn[disabled]{color:var(--el-text-color-disabled);cursor:not-allowed}.el-picker-panel__icon-btn{color:var(--el-datepicker-icon-color);cursor:pointer;background:0 0;border:0;outline:none;margin-top:8px;padding:1px 6px;font-size:12px;line-height:1}.el-picker-panel__icon-btn:hover{color:var(--el-datepicker-hover-text-color)}.el-picker-panel__icon-btn:focus-visible{color:var(--el-datepicker-hover-text-color)}.el-picker-panel__icon-btn.is-disabled{color:var(--el-text-color-disabled)}.el-picker-panel__icon-btn.is-disabled:hover{cursor:not-allowed}.el-picker-panel__icon-btn.is-disabled .el-icon{cursor:inherit}.el-picker-panel__icon-btn .el-icon{cursor:pointer;font-size:inherit}.el-picker-panel__link-btn{vertical-align:middle}.el-picker-panel.is-disabled .el-picker-panel__prev-btn{color:var(--el-text-color-disabled)}.el-picker-panel.is-disabled .el-picker-panel__prev-btn:hover{cursor:not-allowed}.el-picker-panel.is-disabled .el-picker-panel__prev-btn .el-icon{cursor:inherit}.el-picker-panel.is-disabled .el-picker-panel__next-btn{color:var(--el-text-color-disabled)}.el-picker-panel.is-disabled .el-picker-panel__next-btn:hover{cursor:not-allowed}.el-picker-panel.is-disabled .el-picker-panel__next-btn .el-icon{cursor:inherit}.el-picker-panel.is-disabled .el-picker-panel__icon-btn{color:var(--el-text-color-disabled)}.el-picker-panel.is-disabled .el-picker-panel__icon-btn:hover{cursor:not-allowed}.el-picker-panel.is-disabled .el-picker-panel__icon-btn .el-icon{cursor:inherit}.el-picker-panel.is-disabled .el-picker-panel__shortcut{color:var(--el-text-color-disabled)}.el-picker-panel.is-disabled .el-picker-panel__shortcut:hover{cursor:not-allowed}.el-picker-panel.is-disabled .el-picker-panel__shortcut .el-icon{cursor:inherit}.el-picker-panel [slot=sidebar],.el-picker-panel__sidebar{border-right:1px solid var(--el-datepicker-inner-border-color);box-sizing:border-box;width:110px;padding-top:6px;position:absolute;top:0;bottom:0;overflow:auto}.el-picker-panel [slot=sidebar]+.el-picker-panel__body,.el-picker-panel__sidebar+.el-picker-panel__body{margin-left:110px}.el-date-picker{--el-datepicker-text-color:var(--el-text-color-regular);--el-datepicker-off-text-color:var(--el-text-color-placeholder);--el-datepicker-header-text-color:var(--el-text-color-regular);--el-datepicker-icon-color:var(--el-text-color-primary);--el-datepicker-border-color:var(--el-disabled-border-color);--el-datepicker-inner-border-color:var(--el-border-color-light);--el-datepicker-inrange-bg-color:var(--el-border-color-extra-light);--el-datepicker-inrange-hover-bg-color:var(--el-border-color-extra-light);--el-datepicker-active-color:var(--el-color-primary);--el-datepicker-hover-text-color:var(--el-color-primary);--el-datepicker-bg-color:var(--el-bg-color-overlay);--el-fill-color-blank:var(--el-datepicker-bg-color);width:322px}.el-date-picker.has-sidebar.has-time{width:434px}.el-date-picker.has-sidebar{width:438px}.el-date-picker.has-time .el-picker-panel__body-wrapper{position:relative}.el-date-picker .el-picker-panel__content{width:292px}.el-date-picker table{table-layout:fixed;width:100%}.el-date-picker__editor-wrap{padding:0 5px;display:table-cell;position:relative}.el-date-picker__time-header{border-bottom:1px solid var(--el-datepicker-inner-border-color);box-sizing:border-box;width:100%;padding:8px 5px 5px;font-size:12px;display:table;position:relative}.el-date-picker__header{text-align:center;padding:12px 12px 0}.el-date-picker__header--bordered{border-bottom:solid 1px var(--el-border-color-lighter);margin-bottom:0;padding-bottom:12px}.el-date-picker__header--bordered+.el-picker-panel__content{margin-top:0}.el-date-picker__header-label{text-align:center;cursor:pointer;color:var(--el-text-color-regular);padding:0 5px;font-size:16px;font-weight:500;line-height:22px}.el-date-picker__header-label:hover{color:var(--el-datepicker-hover-text-color)}.el-date-picker__header-label:focus-visible{color:var(--el-datepicker-hover-text-color);outline:none}.el-date-picker__header-label.active{color:var(--el-datepicker-active-color)}.el-date-picker__prev-btn{float:left}.el-date-picker__next-btn{float:right}.el-date-picker__time-wrap{text-align:center;padding:10px}.el-date-picker__time-label{float:left;cursor:pointer;margin-left:10px;line-height:30px}.el-date-picker .el-time-panel{position:absolute}.el-date-picker.is-disabled .el-date-picker__header-label{color:var(--el-text-color-disabled)}.el-date-picker.is-disabled .el-date-picker__header-label:hover{cursor:not-allowed}.el-date-picker.is-disabled .el-date-picker__header-label .el-icon{cursor:inherit}.el-date-range-picker{--el-datepicker-text-color:var(--el-text-color-regular);--el-datepicker-off-text-color:var(--el-text-color-placeholder);--el-datepicker-header-text-color:var(--el-text-color-regular);--el-datepicker-icon-color:var(--el-text-color-primary);--el-datepicker-border-color:var(--el-disabled-border-color);--el-datepicker-inner-border-color:var(--el-border-color-light);--el-datepicker-inrange-bg-color:var(--el-border-color-extra-light);--el-datepicker-inrange-hover-bg-color:var(--el-border-color-extra-light);--el-datepicker-active-color:var(--el-color-primary);--el-datepicker-hover-text-color:var(--el-color-primary);--el-datepicker-bg-color:var(--el-bg-color-overlay);width:646px}.el-date-range-picker.has-sidebar{width:756px}.el-date-range-picker.has-time .el-picker-panel__body-wrapper{position:relative}.el-date-range-picker table{table-layout:fixed;width:100%}.el-date-range-picker .el-picker-panel__body{min-width:513px}.el-date-range-picker .el-picker-panel__content{margin:0}.el-date-range-picker.single-panel{width:322px}.el-date-range-picker.single-panel .el-picker-panel__body{min-width:322px}.el-date-range-picker.single-panel.has-sidebar.has-time{width:434px}.el-date-range-picker.single-panel.has-sidebar{width:438px}.el-date-range-picker.single-panel .el-picker-panel__content{width:292px;margin:15px;padding:0;display:block;position:relative}.el-date-range-picker__header{text-align:center;height:28px;position:relative}.el-date-range-picker__header [class*=arrow-left]{float:left}.el-date-range-picker__header [class*=arrow-right]{float:right}.el-date-range-picker__header div{margin-right:50px;font-size:16px;font-weight:500}.el-date-range-picker__header-label{text-align:center;cursor:pointer;color:var(--el-text-color-regular);padding:0 5px;font-size:16px;font-weight:500;line-height:22px}.el-date-range-picker__header-label:hover{color:var(--el-datepicker-hover-text-color)}.el-date-range-picker__header-label:focus-visible{color:var(--el-datepicker-hover-text-color);outline:none}.el-date-range-picker__header-label.active{color:var(--el-datepicker-active-color)}.el-date-range-picker__content{box-sizing:border-box;width:50%;margin:0;padding:16px;display:table-cell}.el-date-range-picker__content.is-left{border-right:1px solid var(--el-datepicker-inner-border-color)}.el-date-range-picker__content .el-date-range-picker__header div{margin-left:50px;margin-right:50px}.el-date-range-picker__editors-wrap{box-sizing:border-box;display:table-cell}.el-date-range-picker__editors-wrap.is-right{text-align:right}.el-date-range-picker__time-header{border-bottom:1px solid var(--el-datepicker-inner-border-color);box-sizing:border-box;width:100%;padding:8px 5px 5px;font-size:12px;display:table;position:relative}.el-date-range-picker__time-header>.el-icon-arrow-right{vertical-align:middle;color:var(--el-datepicker-icon-color);font-size:20px;display:table-cell}.el-date-range-picker__time-picker-wrap{padding:0 5px;display:table-cell;position:relative}.el-date-range-picker__time-picker-wrap .el-picker-panel{z-index:1;background:#fff;position:absolute;top:13px;right:0}.el-date-range-picker__time-picker-wrap .el-time-panel{position:absolute}.el-date-range-picker.is-disabled .el-date-range-picker__header-label{color:var(--el-text-color-disabled)}.el-date-range-picker.is-disabled .el-date-range-picker__header-label:hover{cursor:not-allowed}.el-date-range-picker.is-disabled .el-date-range-picker__header-label .el-icon{cursor:inherit}.el-time-range-picker{width:354px;overflow:visible}.el-time-range-picker__content{text-align:center;z-index:1;padding:10px;position:relative}.el-time-range-picker__cell{box-sizing:border-box;width:50%;margin:0;padding:4px 7px 7px;display:inline-block}.el-time-range-picker__header{text-align:center;margin-bottom:5px;font-size:14px}.el-time-range-picker__body{border:1px solid var(--el-datepicker-border-color);border-radius:2px}.el-time-panel{width:180px;z-index:var(--el-index-top);-webkit-user-select:none;user-select:none;box-sizing:content-box;border-radius:2px;position:relative;left:0}.el-time-panel__content{font-size:0;position:relative;overflow:hidden}.el-time-panel__content:after,.el-time-panel__content:before{content:"";z-index:-1;box-sizing:border-box;text-align:left;height:32px;margin-top:-16px;padding-top:6px;position:absolute;top:50%;left:0;right:0}.el-time-panel__content:after{margin-left:12%;margin-right:12%;left:50%}.el-time-panel__content:before{border-top:1px solid var(--el-border-color-light);border-bottom:1px solid var(--el-border-color-light);margin-left:12%;margin-right:12%;padding-left:50%}.el-time-panel__content.has-seconds:after{left:66.6667%}.el-time-panel__content.has-seconds:before{padding-left:33.3333%}.el-time-panel__footer{border-top:1px solid var(--el-timepicker-inner-border-color,var(--el-border-color-light));text-align:right;box-sizing:border-box;height:36px;padding:4px;line-height:25px}.el-time-panel__btn{cursor:pointer;color:var(--el-text-color-primary);background-color:#0000;border:none;outline:none;margin:0 5px;padding:0 5px;font-size:12px;line-height:28px}.el-time-panel__btn.confirm{color:var(--el-timepicker-active-color,var(--el-color-primary));font-weight:800}.el-picker-panel.is-border{border:solid 1px var(--el-border-color-lighter)}.el-picker-panel.is-border .el-picker-panel__body-wrapper{position:relative}.el-picker-panel.is-border.el-picker-panel [slot=sidebar],.el-picker-panel.is-border.el-picker-panel__sidebar{border-right:1px solid var(--el-datepicker-inner-border-color);box-sizing:border-box;width:110px;height:100%;padding-top:6px;position:absolute;top:0;overflow:auto}.el-descriptions{--el-descriptions-table-border:1px solid var(--el-border-color-lighter);--el-descriptions-item-bordered-label-background:var(--el-fill-color-light);box-sizing:border-box;font-size:var(--el-font-size-base);color:var(--el-text-color-primary)}.el-descriptions__header{justify-content:space-between;align-items:center;margin-bottom:16px;display:flex}.el-descriptions__title{color:var(--el-text-color-primary);font-size:16px;font-weight:700}.el-descriptions__body{background-color:var(--el-fill-color-blank)}.el-descriptions__body .el-descriptions__table{border-collapse:collapse;width:100%}.el-descriptions__body .el-descriptions__table .el-descriptions__cell{box-sizing:border-box;text-align:left;font-size:14px;line-height:23px}.el-descriptions__body .el-descriptions__table .el-descriptions__cell.is-left{text-align:left}.el-descriptions__body .el-descriptions__table .el-descriptions__cell.is-center{text-align:center}.el-descriptions__body .el-descriptions__table .el-descriptions__cell.is-right{text-align:right}.el-descriptions__body .el-descriptions__table.is-bordered .el-descriptions__cell{border:var(--el-descriptions-table-border);padding:8px 11px}.el-descriptions__body .el-descriptions__table:not(.is-bordered) .el-descriptions__cell{padding-bottom:12px}.el-descriptions--large{font-size:14px}.el-descriptions--large .el-descriptions__header{margin-bottom:20px}.el-descriptions--large .el-descriptions__header .el-descriptions__title{font-size:16px}.el-descriptions--large .el-descriptions__body .el-descriptions__table .el-descriptions__cell{font-size:14px}.el-descriptions--large .el-descriptions__body .el-descriptions__table.is-bordered .el-descriptions__cell{padding:12px 15px}.el-descriptions--large .el-descriptions__body .el-descriptions__table:not(.is-bordered) .el-descriptions__cell{padding-bottom:16px}.el-descriptions--small{font-size:12px}.el-descriptions--small .el-descriptions__header{margin-bottom:12px}.el-descriptions--small .el-descriptions__header .el-descriptions__title{font-size:14px}.el-descriptions--small .el-descriptions__body .el-descriptions__table .el-descriptions__cell{font-size:12px}.el-descriptions--small .el-descriptions__body .el-descriptions__table.is-bordered .el-descriptions__cell{padding:4px 7px}.el-descriptions--small .el-descriptions__body .el-descriptions__table:not(.is-bordered) .el-descriptions__cell{padding-bottom:8px}.el-descriptions__label.el-descriptions__cell.is-bordered-label{color:var(--el-text-color-regular);background:var(--el-descriptions-item-bordered-label-background);font-weight:700}.el-descriptions__label:not(.is-bordered-label){color:var(--el-text-color-primary);margin-right:16px}.el-descriptions__label.el-descriptions__cell:not(.is-bordered-label).is-vertical-label{padding-bottom:6px}.el-descriptions__content.el-descriptions__cell.is-bordered-content{color:var(--el-text-color-primary)}.el-descriptions__content:not(.is-bordered-label){color:var(--el-text-color-regular)}.el-descriptions--large .el-descriptions__label:not(.is-bordered-label){margin-right:16px}.el-descriptions--large .el-descriptions__label.el-descriptions__cell:not(.is-bordered-label).is-vertical-label{padding-bottom:8px}.el-descriptions--small .el-descriptions__label:not(.is-bordered-label){margin-right:12px}.el-descriptions--small .el-descriptions__label.el-descriptions__cell:not(.is-bordered-label).is-vertical-label{padding-bottom:4px}:root{--el-popup-modal-bg-color:var(--el-color-black);--el-popup-modal-opacity:.5}.v-modal-enter{animation:v-modal-in var(--el-transition-duration-fast) ease}.v-modal-leave{animation:v-modal-out var(--el-transition-duration-fast) ease forwards}@keyframes v-modal-in{0%{opacity:0}}@keyframes v-modal-out{to{opacity:0}}.v-modal{width:100%;height:100%;opacity:var(--el-popup-modal-opacity);background:var(--el-popup-modal-bg-color);position:fixed;top:0;left:0}.el-popup-parent--hidden{overflow:hidden}.el-dialog{--el-dialog-width:50%;--el-dialog-margin-top:15vh;--el-dialog-bg-color:var(--el-bg-color);--el-dialog-box-shadow:var(--el-box-shadow);--el-dialog-title-font-size:var(--el-font-size-large);--el-dialog-content-font-size:14px;--el-dialog-font-line-height:var(--el-font-line-height-primary);--el-dialog-padding-primary:16px;--el-dialog-border-radius:var(--el-border-radius-base);margin:var(--el-dialog-margin-top,15vh) auto 50px;background:var(--el-dialog-bg-color);border-radius:var(--el-dialog-border-radius);box-shadow:var(--el-dialog-box-shadow);box-sizing:border-box;padding:var(--el-dialog-padding-primary);width:var(--el-dialog-width,50%);overflow-wrap:break-word;position:relative}.el-dialog:focus{outline:none!important}.el-dialog.is-align-center{margin:auto}.el-dialog.is-fullscreen{--el-dialog-width:100%;--el-dialog-margin-top:0;border-radius:0;height:100%;margin-bottom:0;overflow:auto}.el-dialog__wrapper{margin:0;position:fixed;top:0;bottom:0;left:0;right:0;overflow:auto}.el-dialog.is-draggable .el-dialog__header{cursor:move;-webkit-user-select:none;user-select:none}.el-dialog__header{padding-bottom:var(--el-dialog-padding-primary)}.el-dialog__header.show-close{padding-right:calc(var(--el-dialog-padding-primary) + var(--el-message-close-size,16px))}.el-dialog__headerbtn{cursor:pointer;width:48px;height:48px;font-size:var(--el-message-close-size,16px);background:0 0;border:none;outline:none;padding:0;position:absolute;top:0;right:0}.el-dialog__headerbtn .el-dialog__close{color:var(--el-color-info);font-size:inherit}.el-dialog__headerbtn:focus .el-dialog__close,.el-dialog__headerbtn:hover .el-dialog__close{color:var(--el-color-primary)}.el-dialog__title{line-height:var(--el-dialog-font-line-height);font-size:var(--el-dialog-title-font-size);color:var(--el-text-color-primary)}.el-dialog__body{color:var(--el-text-color-regular);font-size:var(--el-dialog-content-font-size)}.el-dialog__footer{padding-top:var(--el-dialog-padding-primary);text-align:right;box-sizing:border-box}.el-dialog--center{text-align:center}.el-dialog--center .el-dialog__body{text-align:initial}.el-dialog--center .el-dialog__footer{text-align:inherit}.el-modal-dialog.is-penetrable{pointer-events:none}.el-modal-dialog.is-penetrable .el-dialog{pointer-events:auto}.el-overlay-dialog{position:fixed;top:0;bottom:0;left:0;right:0;overflow:auto}.el-overlay-dialog.is-closing .el-dialog{pointer-events:none}.dialog-fade-enter-active{animation:modal-fade-in var(--el-transition-duration)}.dialog-fade-enter-active .el-overlay-dialog{animation:dialog-fade-in var(--el-transition-duration)}.dialog-fade-leave-active{animation:modal-fade-out var(--el-transition-duration)}.dialog-fade-leave-active .el-overlay-dialog{animation:dialog-fade-out var(--el-transition-duration)}@keyframes dialog-fade-in{0%{opacity:0;transform:translateY(-20px)}to{opacity:1;transform:translate(0)}}@keyframes dialog-fade-out{0%{opacity:1;transform:translate(0)}to{opacity:0;transform:translateY(-20px)}}@keyframes modal-fade-in{0%{opacity:0}to{opacity:1}}@keyframes modal-fade-out{0%{opacity:1}to{opacity:0}}.el-divider{position:relative}.el-divider--horizontal{border-top:1px var(--el-border-color) var(--el-border-style);width:100%;height:1px;margin:24px 0;display:block}.el-divider--vertical{vertical-align:middle;border-left:1px var(--el-border-color) var(--el-border-style);width:1px;height:1em;margin:0 8px;display:inline-block;position:relative}.el-divider__text{background-color:var(--el-bg-color);color:var(--el-text-color-primary);padding:0 20px;font-size:14px;font-weight:500;position:absolute}.el-divider__text.is-left{left:20px;transform:translateY(-50%)}.el-divider__text.is-center{left:50%;transform:translate(-50%)translateY(-50%)}.el-divider__text.is-right{right:20px;transform:translateY(-50%)}.el-overlay.is-drawer{overflow:hidden}.el-drawer{--el-drawer-bg-color:var(--el-dialog-bg-color,var(--el-bg-color));--el-drawer-padding-primary:var(--el-dialog-padding-primary,20px);--el-drawer-dragger-size:8px;box-sizing:border-box;background-color:var(--el-drawer-bg-color);box-shadow:var(--el-box-shadow-dark);transition:all var(--el-transition-duration);flex-direction:column;display:flex;position:absolute}.el-drawer .rtl,.el-drawer .ltr,.el-drawer .ttb,.el-drawer .btt{transform:translate(0)}.el-drawer__sr-focus:focus{outline:none!important}.el-drawer__header{color:var(--el-text-color-primary);padding:var(--el-drawer-padding-primary);align-items:center;margin-bottom:32px;padding-bottom:0;display:flex;overflow:hidden}.el-drawer__header>:first-child{flex:1}.el-drawer__title{line-height:inherit;flex:1;margin:0;font-size:16px}.el-drawer__footer{padding:var(--el-drawer-padding-primary);text-align:right;padding-top:10px;overflow:hidden}.el-drawer__close-btn{cursor:pointer;font-size:var(--el-font-size-extra-large);color:inherit;background-color:#0000;border:none;outline:none;display:inline-flex}.el-drawer__close-btn:focus i,.el-drawer__close-btn:hover i{color:var(--el-color-primary)}.el-drawer__body{padding:var(--el-drawer-padding-primary);flex:1;overflow:auto}.el-drawer__body>*{box-sizing:border-box}.el-drawer.is-dragging{transition:none}.el-drawer__dragger{-webkit-user-select:none;user-select:none;background-color:#0000;transition:all .2s;position:absolute}.el-drawer__dragger:before{content:"";background-color:#0000;transition:all .2s;position:absolute}.el-drawer__dragger:hover:before{background-color:var(--el-color-primary)}.el-drawer.ltr,.el-drawer.rtl{height:100%;top:0;bottom:0}.el-drawer.ltr>.el-drawer__dragger,.el-drawer.rtl>.el-drawer__dragger{height:100%;width:var(--el-drawer-dragger-size);cursor:ew-resize;top:0;bottom:0}.el-drawer.ltr>.el-drawer__dragger:before,.el-drawer.rtl>.el-drawer__dragger:before{width:3px;top:0;bottom:0}.el-drawer.ttb,.el-drawer.btt{width:100%;left:0;right:0}.el-drawer.ttb>.el-drawer__dragger,.el-drawer.btt>.el-drawer__dragger{width:100%;height:var(--el-drawer-dragger-size);cursor:ns-resize;left:0;right:0}.el-drawer.ttb>.el-drawer__dragger:before,.el-drawer.btt>.el-drawer__dragger:before{height:3px;left:0;right:0}.el-drawer.ltr{left:0}.el-drawer.ltr>.el-drawer__dragger{right:0}.el-drawer.ltr>.el-drawer__dragger:before{right:-2px}.el-drawer.rtl{right:0}.el-drawer.rtl>.el-drawer__dragger{left:0}.el-drawer.rtl>.el-drawer__dragger:before{left:-2px}.el-drawer.ttb{top:0}.el-drawer.ttb>.el-drawer__dragger{bottom:0}.el-drawer.ttb>.el-drawer__dragger:before{bottom:-2px}.el-drawer.btt{bottom:0}.el-drawer.btt>.el-drawer__dragger{top:0}.el-drawer.btt>.el-drawer__dragger:before{top:-2px}.el-modal-drawer.is-penetrable{pointer-events:none}.el-modal-drawer.is-penetrable .el-drawer{pointer-events:auto}.el-drawer-fade-enter-active,.el-drawer-fade-leave-active{transition:all var(--el-transition-duration)}.el-drawer-fade-enter-from,.el-drawer-fade-enter-active,.el-drawer-fade-enter-to,.el-drawer-fade-leave-from,.el-drawer-fade-leave-active,.el-drawer-fade-leave-to{overflow:hidden!important}.el-drawer-fade-enter-from,.el-drawer-fade-leave-to{background-color:#0000!important}.el-drawer-fade-enter-from .rtl,.el-drawer-fade-leave-to .rtl{transform:translate(100%)}.el-drawer-fade-enter-from .ltr,.el-drawer-fade-leave-to .ltr{transform:translate(-100%)}.el-drawer-fade-enter-from .ttb,.el-drawer-fade-leave-to .ttb{transform:translateY(-100%)}.el-drawer-fade-enter-from .btt,.el-drawer-fade-leave-to .btt{transform:translateY(100%)}.el-dropdown{--el-dropdown-menu-box-shadow:var(--el-box-shadow-light);--el-dropdown-menuItem-hover-fill:var(--el-color-primary-light-9);--el-dropdown-menuItem-hover-color:var(--el-color-primary);--el-dropdown-menu-index:10;color:var(--el-text-color-regular);font-size:var(--el-font-size-base);vertical-align:top;line-height:1;display:inline-flex;position:relative}.el-dropdown.is-disabled{color:var(--el-text-color-placeholder);cursor:not-allowed}.el-dropdown__popper{--el-dropdown-menu-box-shadow:var(--el-box-shadow-light);--el-dropdown-menuItem-hover-fill:var(--el-color-primary-light-9);--el-dropdown-menuItem-hover-color:var(--el-color-primary);--el-dropdown-menu-index:10}.el-dropdown__popper.el-popper{background:var(--el-bg-color-overlay);border:1px solid var(--el-border-color-light);box-shadow:var(--el-dropdown-menu-box-shadow)}.el-dropdown__popper.el-popper .el-popper__arrow:before{border:1px solid var(--el-border-color-light)}.el-dropdown__popper.el-popper[data-popper-placement^=top] .el-popper__arrow:before{border-top-color:#0000;border-left-color:#0000}.el-dropdown__popper.el-popper[data-popper-placement^=bottom] .el-popper__arrow:before{border-bottom-color:#0000;border-right-color:#0000}.el-dropdown__popper.el-popper[data-popper-placement^=left] .el-popper__arrow:before{border-bottom-color:#0000;border-left-color:#0000}.el-dropdown__popper.el-popper[data-popper-placement^=right] .el-popper__arrow:before{border-top-color:#0000;border-right-color:#0000}.el-dropdown__popper .el-dropdown-menu{border:none}.el-dropdown__popper .el-dropdown__popper-selfdefine{outline:none}.el-dropdown__popper .el-scrollbar__bar{z-index:calc(var(--el-dropdown-menu-index) + 1)}.el-dropdown__popper .el-dropdown__list{box-sizing:border-box;margin:0;padding:0;list-style:none}.el-dropdown .el-dropdown__caret-button{border-left:none;justify-content:center;align-items:center;width:32px;padding-left:0;padding-right:0;display:inline-flex}.el-dropdown .el-dropdown__caret-button>span{display:inline-flex}.el-dropdown .el-dropdown__caret-button:before{content:"";background:var(--el-overlay-color-lighter);width:1px;display:block;position:absolute;top:-1px;bottom:-1px;left:0}.el-dropdown .el-dropdown__caret-button.el-button:before{background:var(--el-border-color);opacity:.5}.el-dropdown .el-dropdown__caret-button .el-dropdown__icon{font-size:inherit;padding-left:0}.el-dropdown .el-dropdown-selfdefine{outline:none}.el-dropdown--large .el-dropdown__caret-button{width:40px}.el-dropdown--small .el-dropdown__caret-button{width:24px}.el-dropdown-menu{z-index:var(--el-dropdown-menu-index);background-color:var(--el-bg-color-overlay);border-radius:var(--el-border-radius-base);box-shadow:none;border:none;margin:0;padding:5px 0;list-style:none;position:relative;top:0;left:0}.el-dropdown-menu__item{white-space:nowrap;line-height:22px;font-size:var(--el-font-size-base);color:var(--el-text-color-regular);cursor:pointer;outline:none;align-items:center;margin:0;padding:5px 16px;list-style:none;display:flex}.el-dropdown-menu__item:not(.is-disabled):hover,.el-dropdown-menu__item:not(.is-disabled):focus{background-color:var(--el-dropdown-menuItem-hover-fill);color:var(--el-dropdown-menuItem-hover-color)}.el-dropdown-menu__item i{margin-right:5px}.el-dropdown-menu__item--divided{border-top:1px solid var(--el-border-color-lighter);margin:6px 0}.el-dropdown-menu__item.is-disabled{cursor:not-allowed;color:var(--el-text-color-disabled)}.el-dropdown-menu--large{padding:7px 0}.el-dropdown-menu--large .el-dropdown-menu__item{padding:7px 20px;font-size:14px;line-height:22px}.el-dropdown-menu--large .el-dropdown-menu__item--divided{margin:8px 0}.el-dropdown-menu--small{padding:3px 0}.el-dropdown-menu--small .el-dropdown-menu__item{padding:2px 12px;font-size:12px;line-height:20px}.el-dropdown-menu--small .el-dropdown-menu__item--divided{margin:4px 0}.el-empty{--el-empty-padding:40px 0;--el-empty-image-width:160px;--el-empty-description-margin-top:20px;--el-empty-bottom-margin-top:20px;--el-empty-fill-color-0:var(--el-color-white);--el-empty-fill-color-1:#fcfcfd;--el-empty-fill-color-2:#f8f9fb;--el-empty-fill-color-3:#f7f8fc;--el-empty-fill-color-4:#eeeff3;--el-empty-fill-color-5:#edeef2;--el-empty-fill-color-6:#e9ebef;--el-empty-fill-color-7:#e5e7e9;--el-empty-fill-color-8:#e0e3e9;--el-empty-fill-color-9:#d5d7de;text-align:center;box-sizing:border-box;padding:var(--el-empty-padding);flex-direction:column;justify-content:center;align-items:center;display:flex}.el-empty__image{width:var(--el-empty-image-width)}.el-empty__image img{-webkit-user-select:none;user-select:none;vertical-align:top;object-fit:contain;width:100%;height:100%}.el-empty__image svg{color:var(--el-svg-monochrome-grey);fill:currentColor;vertical-align:top;width:100%;height:100%}.el-empty__description{margin-top:var(--el-empty-description-margin-top)}.el-empty__description p{font-size:var(--el-font-size-base);color:var(--el-text-color-secondary);margin:0}.el-empty__bottom{margin-top:var(--el-empty-bottom-margin-top)}.el-footer{--el-footer-padding:0 20px;--el-footer-height:60px;padding:var(--el-footer-padding);box-sizing:border-box;height:var(--el-footer-height);flex-shrink:0}.el-form-item{--font-size:14px;margin-bottom:18px;display:flex}.el-form-item .el-form-item{margin-bottom:0}.el-form-item .el-input__validateIcon{display:none}.el-form-item--large{--font-size:14px;--el-form-label-font-size:var(--font-size);margin-bottom:22px}.el-form-item--large .el-form-item__label{height:40px;line-height:40px}.el-form-item--large .el-form-item__content{line-height:40px}.el-form-item--large .el-form-item__error{padding-top:4px}.el-form-item--default{--font-size:14px;--el-form-label-font-size:var(--font-size);margin-bottom:18px}.el-form-item--default .el-form-item__label{height:32px;line-height:32px}.el-form-item--default .el-form-item__content{line-height:32px}.el-form-item--default .el-form-item__error{padding-top:2px}.el-form-item--small{--font-size:12px;--el-form-label-font-size:var(--font-size);margin-bottom:18px}.el-form-item--small .el-form-item__label{height:24px;line-height:24px}.el-form-item--small .el-form-item__content{line-height:24px}.el-form-item--small .el-form-item__error{padding-top:2px}.el-form-item--label-left .el-form-item__label{text-align:left;justify-content:flex-start}.el-form-item--label-right .el-form-item__label{text-align:right;justify-content:flex-end}.el-form-item--label-top{display:block}.el-form-item--label-top .el-form-item__label{text-align:left;width:-moz-fit-content;width:fit-content;height:auto;margin-bottom:8px;padding-right:0;line-height:22px;display:block}.el-form-item__label-wrap{display:flex}.el-form-item__label{font-size:var(--el-form-label-font-size);color:var(--el-text-color-regular);box-sizing:border-box;flex:none;align-items:flex-start;height:32px;padding:0 12px 0 0;line-height:32px;display:inline-flex}.el-form-item__content{line-height:32px;font-size:var(--font-size);flex-wrap:wrap;flex:1;align-items:center;min-width:0;display:flex;position:relative}.el-form-item__content .el-input-group{vertical-align:top}.el-form-item__error{color:var(--el-color-danger);padding-top:2px;font-size:12px;line-height:1;position:absolute;top:100%;left:0}.el-form-item__error--inline{margin-left:10px;display:inline-block;position:relative;top:auto;left:auto}.el-form-item.is-required:not(.is-no-asterisk).asterisk-left>.el-form-item__label:before,.el-form-item.is-required:not(.is-no-asterisk).asterisk-left>.el-form-item__label-wrap>.el-form-item__label:before{content:"*";color:var(--el-color-danger);margin-right:4px}.el-form-item.is-required:not(.is-no-asterisk).asterisk-right>.el-form-item__label:after,.el-form-item.is-required:not(.is-no-asterisk).asterisk-right>.el-form-item__label-wrap>.el-form-item__label:after{content:"*";color:var(--el-color-danger);margin-left:4px}.el-form-item.is-error .el-form-item__content .el-input__wrapper,.el-form-item.is-error .el-form-item__content .el-input__wrapper:hover,.el-form-item.is-error .el-form-item__content .el-input__wrapper:focus,.el-form-item.is-error .el-form-item__content .el-input__wrapper.is-focus,.el-form-item.is-error .el-form-item__content .el-textarea__inner,.el-form-item.is-error .el-form-item__content .el-textarea__inner:hover,.el-form-item.is-error .el-form-item__content .el-textarea__inner:focus,.el-form-item.is-error .el-form-item__content .el-textarea__inner.is-focus,.el-form-item.is-error .el-form-item__content .el-select__wrapper,.el-form-item.is-error .el-form-item__content .el-select__wrapper:hover,.el-form-item.is-error .el-form-item__content .el-select__wrapper:focus,.el-form-item.is-error .el-form-item__content .el-select__wrapper.is-focus,.el-form-item.is-error .el-form-item__content .el-input-tag__wrapper,.el-form-item.is-error .el-form-item__content .el-input-tag__wrapper:hover,.el-form-item.is-error .el-form-item__content .el-input-tag__wrapper:focus,.el-form-item.is-error .el-form-item__content .el-input-tag__wrapper.is-focus,.el-form-item.is-error .el-form-item__content :not(.el-input-otp--underlined) .el-input-otp__input-field,.el-form-item.is-error .el-form-item__content :not(.el-input-otp--underlined) .el-input-otp__input-field:hover,.el-form-item.is-error .el-form-item__content :not(.el-input-otp--underlined) .el-input-otp__input-field:focus,.el-form-item.is-error .el-form-item__content :not(.el-input-otp--underlined) .el-input-otp__input-field.is-focus,.el-form-item.is-error .el-form-item__content .el-input-otp--underlined .el-input-otp__input-field:after,.el-form-item.is-error .el-form-item__content .el-input-otp--underlined .el-input-otp__input-field:hover:after,.el-form-item.is-error .el-form-item__content .el-input-otp--underlined .el-input-otp__input-field:focus:after,.el-form-item.is-error .el-form-item__content .el-input-otp--underlined .el-input-otp__input-field.is-focus:after{box-shadow:0 0 0 1px var(--el-color-danger) inset}.el-form-item.is-error .el-form-item__content .el-input-group__append .el-input__wrapper,.el-form-item.is-error .el-form-item__content .el-input-group__prepend .el-input__wrapper{box-shadow:inset 0 0 0 1px #0000}.el-form-item.is-error .el-form-item__content .el-input-group__append .el-input__validateIcon,.el-form-item.is-error .el-form-item__content .el-input-group__prepend .el-input__validateIcon{display:none}.el-form-item.is-error .el-form-item__content .el-input__validateIcon{color:var(--el-color-danger)}.el-form-item--feedback .el-input__validateIcon{display:inline-flex}.el-form{--el-form-label-font-size:var(--el-font-size-base);--el-form-inline-content-width:220px}.el-form--inline .el-form-item{vertical-align:middle;margin-right:32px;display:inline-flex}.el-form--inline .el-form-item:last-child{margin-right:0}.el-form--inline.el-form--label-top{flex-wrap:wrap;display:flex}.el-form--inline.el-form--label-top .el-form-item{display:block}.el-header{--el-header-padding:0 20px;--el-header-height:60px;padding:var(--el-header-padding);box-sizing:border-box;height:var(--el-header-height);flex-shrink:0}.el-image-viewer__wrapper{position:fixed;top:0;bottom:0;left:0;right:0}.el-image-viewer__wrapper:focus{outline:none!important}.el-image-viewer__btn{z-index:1;opacity:.8;cursor:pointer;box-sizing:border-box;-webkit-user-select:none;user-select:none;border-radius:50%;justify-content:center;align-items:center;display:flex;position:absolute}.el-image-viewer__btn .el-icon{cursor:pointer}.el-image-viewer__close{width:40px;height:40px;font-size:40px;top:40px;right:40px}.el-image-viewer__canvas{-webkit-user-select:none;user-select:none;justify-content:center;align-items:center;width:100%;height:100%;display:flex;position:static}.el-image-viewer__actions{background-color:var(--el-text-color-regular);border-color:#fff;border-radius:22px;height:44px;padding:0 23px;bottom:30px;left:50%;transform:translate(-50%)}.el-image-viewer__actions__inner{cursor:default;color:#fff;justify-content:space-around;align-items:center;gap:22px;width:100%;height:100%;padding:0 6px;font-size:23px;display:flex}.el-image-viewer__actions__divider{margin:0 -6px}.el-image-viewer__progress{cursor:default;color:#fff;bottom:90px;left:50%;transform:translate(-50%)}.el-image-viewer__prev{color:#fff;background-color:var(--el-text-color-regular);border-color:#fff;width:44px;height:44px;font-size:24px;top:50%;left:40px;transform:translateY(-50%)}.el-image-viewer__next{text-indent:2px;color:#fff;background-color:var(--el-text-color-regular);border-color:#fff;width:44px;height:44px;font-size:24px;top:50%;right:40px;transform:translateY(-50%)}.el-image-viewer__close{color:#fff;background-color:var(--el-text-color-regular);border-color:#fff;width:44px;height:44px;font-size:24px}.el-image-viewer__mask{opacity:.5;background:#000;width:100%;height:100%;position:absolute;top:0;left:0}.el-image-viewer-parent--hidden{overflow:hidden}.viewer-fade-enter-active{animation:viewer-fade-in var(--el-transition-duration)}.viewer-fade-leave-active{animation:viewer-fade-out var(--el-transition-duration)}@keyframes viewer-fade-in{0%{opacity:0;transform:translateY(-20px)}to{opacity:1;transform:translate(0)}}@keyframes viewer-fade-out{0%{opacity:1;transform:translate(0)}to{opacity:0;transform:translateY(-20px)}}.el-image__error,.el-image__placeholder,.el-image__wrapper,.el-image__inner{width:100%;height:100%}.el-image{display:inline-block;position:relative;overflow:hidden}.el-image__inner{vertical-align:top;opacity:1}.el-image__inner.is-loading{opacity:0}.el-image__wrapper{position:absolute;top:0;left:0}.el-image__placeholder{background:var(--el-fill-color-light)}.el-image__error{background:var(--el-fill-color-light);color:var(--el-text-color-placeholder);vertical-align:middle;justify-content:center;align-items:center;font-size:14px;display:flex}.el-image__preview{cursor:pointer}.el-textarea{--el-input-text-color:var(--el-text-color-regular);--el-input-border:var(--el-border);--el-input-hover-border:var(--el-border-color-hover);--el-input-focus-border:var(--el-color-primary);--el-input-transparent-border:0 0 0 1px transparent inset;--el-input-border-color:var(--el-border-color);--el-input-border-radius:var(--el-border-radius-base);--el-input-bg-color:var(--el-fill-color-blank);--el-input-icon-color:var(--el-text-color-placeholder);--el-input-placeholder-color:var(--el-text-color-placeholder);--el-input-hover-border-color:var(--el-border-color-hover);--el-input-clear-hover-color:var(--el-text-color-secondary);--el-input-focus-border-color:var(--el-color-primary);--el-input-width:100%;vertical-align:bottom;width:100%;font-size:var(--el-font-size-base);display:inline-block;position:relative}.el-textarea__inner{cursor:auto;resize:vertical;box-sizing:border-box;width:100%;line-height:1.5;font-size:inherit;color:var(--el-input-text-color,var(--el-text-color-regular));background-color:var(--el-input-bg-color,var(--el-fill-color-blank));-webkit-appearance:none;box-shadow:0 0 0 1px var(--el-input-border-color,var(--el-border-color)) inset;border-radius:var(--el-input-border-radius,var(--el-border-radius-base));transition:var(--el-transition-box-shadow);background-image:none;border:none;padding:5px 11px;font-family:inherit;display:block;position:relative}.el-textarea__inner.is-clearable{padding:5px 26px 5px 11px}.el-textarea__inner::placeholder{color:var(--el-input-placeholder-color,var(--el-text-color-placeholder))}.el-textarea__inner:hover{box-shadow:0 0 0 1px var(--el-input-hover-border-color) inset}.el-textarea__inner:focus{box-shadow:0 0 0 1px var(--el-input-focus-border-color) inset;outline:none}.el-textarea__clear{color:var(--el-input-icon-color);cursor:pointer;font-size:14px;position:absolute;top:15px;right:11px;transform:translateY(-50%)}.el-textarea__clear:hover{color:var(--el-input-clear-hover-color)}.el-textarea .el-input__count{color:var(--el-color-info);background:var(--el-fill-color-blank);font-size:12px;line-height:14px;position:absolute;bottom:5px;right:10px}.el-textarea .el-input__count.is-outside{top:100%;right:0;bottom:unset;background:0 0;padding-top:2px;line-height:1;position:absolute}.el-textarea.is-disabled .el-textarea__inner{box-shadow:0 0 0 1px var(--el-disabled-border-color) inset;background-color:var(--el-disabled-bg-color);color:var(--el-disabled-text-color);cursor:not-allowed}.el-textarea.is-disabled .el-textarea__inner::placeholder{color:var(--el-text-color-placeholder)}.el-textarea.is-exceed .el-textarea__inner{box-shadow:0 0 0 1px var(--el-color-danger) inset}.el-textarea.is-exceed .el-input__count{color:var(--el-color-danger)}.el-input{--el-input-text-color:var(--el-text-color-regular);--el-input-border:var(--el-border);--el-input-hover-border:var(--el-border-color-hover);--el-input-focus-border:var(--el-color-primary);--el-input-transparent-border:0 0 0 1px transparent inset;--el-input-border-color:var(--el-border-color);--el-input-border-radius:var(--el-border-radius-base);--el-input-bg-color:var(--el-fill-color-blank);--el-input-icon-color:var(--el-text-color-placeholder);--el-input-placeholder-color:var(--el-text-color-placeholder);--el-input-hover-border-color:var(--el-border-color-hover);--el-input-clear-hover-color:var(--el-text-color-secondary);--el-input-focus-border-color:var(--el-color-primary);--el-input-width:100%;--el-input-height:var(--el-component-size);font-size:var(--el-font-size-base);width:var(--el-input-width);line-height:var(--el-input-height);box-sizing:border-box;vertical-align:middle;display:inline-flex;position:relative}.el-input::-webkit-scrollbar{z-index:11;width:6px}.el-input::-webkit-scrollbar:horizontal{height:6px}.el-input::-webkit-scrollbar-thumb{background:var(--el-text-color-disabled);border-radius:5px;width:6px}.el-input::-webkit-scrollbar-corner{background:var(--el-fill-color-blank)}.el-input::-webkit-scrollbar-track{background:var(--el-fill-color-blank)}.el-input::-webkit-scrollbar-track-piece{background:var(--el-fill-color-blank);width:6px}.el-input .el-input__clear,.el-input .el-input__password{color:var(--el-input-icon-color);cursor:pointer;font-size:14px}.el-input .el-input__clear:hover,.el-input .el-input__password:hover{color:var(--el-input-clear-hover-color)}.el-input .el-input__count{height:100%;color:var(--el-color-info);align-items:center;font-size:12px;display:inline-flex}.el-input .el-input__count .el-input__count-inner{background:var(--el-fill-color-blank);line-height:initial;padding-left:8px;display:inline-block}.el-input .el-input__count.is-outside{height:unset;padding-top:2px;position:absolute;top:100%;right:0}.el-input .el-input__count.is-outside .el-input__count-inner{background:0 0;padding-left:0;line-height:1}.el-input__wrapper{background-color:var(--el-input-bg-color,var(--el-fill-color-blank));border-radius:var(--el-input-border-radius,var(--el-border-radius-base));cursor:text;transition:var(--el-transition-box-shadow);box-shadow:0 0 0 1px var(--el-input-border-color,var(--el-border-color)) inset;background-image:none;flex-grow:1;justify-content:center;align-items:center;padding:1px 11px;display:inline-flex;transform:translateZ(0)}.el-input__wrapper:hover{box-shadow:0 0 0 1px var(--el-input-hover-border-color) inset}.el-input__wrapper.is-focus{box-shadow:0 0 0 1px var(--el-input-focus-border-color) inset}.el-input{--el-input-inner-height:calc(var(--el-input-height,32px) - 2px)}.el-input__inner{-webkit-appearance:none;width:100%;color:var(--el-input-text-color,var(--el-text-color-regular));font-size:inherit;height:var(--el-input-inner-height);line-height:var(--el-input-inner-height);box-sizing:border-box;background:0 0;border:none;outline:none;flex-grow:1;padding:0}.el-input__inner:focus{outline:none}.el-input__inner::placeholder{color:var(--el-input-placeholder-color,var(--el-text-color-placeholder))}.el-input__inner[type=password]::-ms-reveal{display:none}.el-input__inner[type=number]{line-height:1}.el-input__prefix{white-space:nowrap;height:100%;line-height:var(--el-input-inner-height);text-align:center;color:var(--el-input-icon-color,var(--el-text-color-placeholder));transition:all var(--el-transition-duration);pointer-events:none;flex-wrap:nowrap;flex-shrink:0;display:inline-flex}.el-input__prefix-inner{pointer-events:all;justify-content:center;align-items:center;display:inline-flex}.el-input__prefix-inner>:last-child{margin-right:8px}.el-input__prefix-inner>:first-child,.el-input__prefix-inner>:first-child.el-input__icon{margin-left:0}.el-input__suffix{white-space:nowrap;height:100%;line-height:var(--el-input-inner-height);text-align:center;color:var(--el-input-icon-color,var(--el-text-color-placeholder));transition:all var(--el-transition-duration);pointer-events:none;flex-wrap:nowrap;flex-shrink:0;display:inline-flex}.el-input__suffix-inner{pointer-events:all;justify-content:center;align-items:center;display:inline-flex}.el-input__suffix-inner>:first-child{margin-left:8px}.el-input .el-input__icon{height:inherit;line-height:inherit;transition:all var(--el-transition-duration);justify-content:center;align-items:center;margin-left:8px;display:flex}.el-input .el-input__clear{transition:color var(--el-transition-duration)}.el-input__validateIcon{pointer-events:none}.el-input.is-active .el-input__wrapper{box-shadow:0 0 0 1px var(--el-input-focus-color, ) inset}.el-input.is-disabled{cursor:not-allowed}.el-input.is-disabled .el-input__wrapper{background-color:var(--el-disabled-bg-color);cursor:not-allowed;box-shadow:0 0 0 1px var(--el-disabled-border-color) inset}.el-input.is-disabled .el-input__inner{color:var(--el-disabled-text-color);-webkit-text-fill-color:var(--el-disabled-text-color);cursor:not-allowed}.el-input.is-disabled .el-input__inner::placeholder{color:var(--el-text-color-placeholder)}.el-input.is-disabled .el-input__icon{cursor:not-allowed}.el-input.is-disabled .el-input__prefix-inner,.el-input.is-disabled .el-input__suffix-inner{pointer-events:none}.el-input.is-exceed .el-input__wrapper{box-shadow:0 0 0 1px var(--el-color-danger) inset}.el-input.is-exceed .el-input__suffix .el-input__count{color:var(--el-color-danger)}.el-input--large{--el-input-height:var(--el-component-size-large);font-size:14px}.el-input--large .el-input__wrapper{padding:1px 15px}.el-input--large{--el-input-inner-height:calc(var(--el-input-height,40px) - 2px)}.el-input--small{--el-input-height:var(--el-component-size-small);font-size:12px}.el-input--small .el-input__wrapper{padding:1px 7px}.el-input--small{--el-input-inner-height:calc(var(--el-input-height,24px) - 2px)}.el-input-group{align-items:stretch;width:100%;display:inline-flex}.el-input-group__append,.el-input-group__prepend{background-color:var(--el-fill-color-light);color:var(--el-color-info);border-radius:var(--el-input-border-radius);white-space:nowrap;justify-content:center;align-items:center;min-height:100%;padding:0 20px;display:inline-flex;position:relative}.el-input-group__append:focus,.el-input-group__prepend:focus{outline:none}.el-input-group__append .el-select,.el-input-group__append .el-button,.el-input-group__prepend .el-select,.el-input-group__prepend .el-button{flex:1;margin:0 -20px;display:inline-block}.el-input-group__append button.el-button,.el-input-group__append button.el-button:hover,.el-input-group__append div.el-select .el-select__wrapper,.el-input-group__append div.el-select:hover .el-select__wrapper,.el-input-group__prepend button.el-button,.el-input-group__prepend button.el-button:hover,.el-input-group__prepend div.el-select .el-select__wrapper,.el-input-group__prepend div.el-select:hover .el-select__wrapper{color:inherit;background-color:#0000;border-color:#0000}.el-input-group__append .el-button,.el-input-group__append .el-input,.el-input-group__prepend .el-button,.el-input-group__prepend .el-input{font-size:inherit}.el-input-group__prepend{box-shadow:1px 0 0 0 var(--el-input-border-color) inset,0 1px 0 0 var(--el-input-border-color) inset,0 -1px 0 0 var(--el-input-border-color) inset;border-right:0;border-top-right-radius:0;border-bottom-right-radius:0}.el-input-group__append{box-shadow:0 1px 0 0 var(--el-input-border-color) inset,0 -1px 0 0 var(--el-input-border-color) inset,-1px 0 0 0 var(--el-input-border-color) inset;border-left:0;border-top-left-radius:0;border-bottom-left-radius:0}.el-input-group--prepend>.el-input__wrapper{border-top-left-radius:0;border-bottom-left-radius:0}.el-input-group--prepend .el-input-group__prepend .el-select .el-select__wrapper{box-shadow:1px 0 0 0 var(--el-input-border-color) inset,0 1px 0 0 var(--el-input-border-color) inset,0 -1px 0 0 var(--el-input-border-color) inset;border-top-right-radius:0;border-bottom-right-radius:0}.el-input-group--append>.el-input__wrapper{border-top-right-radius:0;border-bottom-right-radius:0}.el-input-group--append .el-input-group__append .el-select .el-select__wrapper{box-shadow:0 1px 0 0 var(--el-input-border-color) inset,0 -1px 0 0 var(--el-input-border-color) inset,-1px 0 0 0 var(--el-input-border-color) inset;border-top-left-radius:0;border-bottom-left-radius:0}.el-input-hidden{display:none!important}.el-input-number{vertical-align:middle;width:150px;line-height:30px;display:inline-flex;position:relative}.el-input-number .el-input__wrapper{padding-left:42px;padding-right:42px}.el-input-number .el-input__inner{-webkit-appearance:none;-moz-appearance:textfield;text-align:center;line-height:1}.el-input-number .el-input__inner::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}.el-input-number .el-input__inner::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.el-input-number.is-left .el-input__inner{text-align:left}.el-input-number.is-right .el-input__inner{text-align:right}.el-input-number.is-center .el-input__inner{text-align:center}.el-input-number__increase,.el-input-number__decrease{z-index:1;background:var(--el-fill-color-light);width:32px;height:auto;color:var(--el-text-color-regular);cursor:pointer;-webkit-user-select:none;user-select:none;justify-content:center;align-items:center;font-size:13px;display:flex;position:absolute;top:1px;bottom:1px}.el-input-number__increase:hover,.el-input-number__decrease:hover{color:var(--el-color-primary)}.el-input-number__increase:hover~.el-input:not(.is-disabled) .el-input__wrapper,.el-input-number__decrease:hover~.el-input:not(.is-disabled) .el-input__wrapper{box-shadow:0 0 0 1px var(--el-input-focus-border-color,var(--el-color-primary)) inset}.el-input-number__increase.is-disabled,.el-input-number__decrease.is-disabled{color:var(--el-disabled-text-color);cursor:not-allowed}.el-input-number__increase{border-radius:0 var(--el-border-radius-base) var(--el-border-radius-base) 0;border-left:var(--el-border);right:1px}.el-input-number__decrease{border-radius:var(--el-border-radius-base) 0 0 var(--el-border-radius-base);border-right:var(--el-border);left:1px}.el-input-number.is-disabled .el-input-number__increase,.el-input-number.is-disabled .el-input-number__decrease{border-color:var(--el-disabled-border-color);color:var(--el-disabled-border-color)}.el-input-number.is-disabled .el-input-number__increase:hover,.el-input-number.is-disabled .el-input-number__decrease:hover{color:var(--el-disabled-border-color);cursor:not-allowed}.el-input-number--large{width:180px;line-height:38px}.el-input-number--large .el-input-number__increase,.el-input-number--large .el-input-number__decrease{width:40px;font-size:14px}.el-input-number--large.is-controls-right .el-input--large .el-input__wrapper{padding-right:47px}.el-input-number--large .el-input--large .el-input__wrapper{padding-left:47px;padding-right:47px}.el-input-number--small{width:120px;line-height:22px}.el-input-number--small .el-input-number__increase,.el-input-number--small .el-input-number__decrease{width:24px;font-size:12px}.el-input-number--small.is-controls-right .el-input--small .el-input__wrapper{padding-right:31px}.el-input-number--small .el-input--small .el-input__wrapper{padding-left:31px;padding-right:31px}.el-input-number--small .el-input-number__increase [class*=el-icon],.el-input-number--small .el-input-number__decrease [class*=el-icon]{transform:scale(.9)}.el-input-number.is-without-controls .el-input__wrapper{padding-left:15px;padding-right:15px}.el-input-number.is-controls-right .el-input__wrapper{padding-left:15px;padding-right:42px}.el-input-number.is-controls-right .el-input-number__increase,.el-input-number.is-controls-right .el-input-number__decrease{--el-input-number-controls-height:15px;height:var(--el-input-number-controls-height);line-height:var(--el-input-number-controls-height)}.el-input-number.is-controls-right .el-input-number__increase [class*=el-icon],.el-input-number.is-controls-right .el-input-number__decrease [class*=el-icon]{transform:scale(.8)}.el-input-number.is-controls-right .el-input-number__increase{border-radius:0 var(--el-border-radius-base) 0 0;border-bottom:var(--el-border);bottom:auto;left:auto}.el-input-number.is-controls-right .el-input-number__decrease{border-right:none;border-left:var(--el-border);border-radius:0 0 var(--el-border-radius-base) 0;top:auto;left:auto;right:1px}.el-input-number.is-controls-right[class*=large] [class*=increase],.el-input-number.is-controls-right[class*=large] [class*=decrease]{--el-input-number-controls-height:19px}.el-input-number.is-controls-right[class*=small] [class*=increase],.el-input-number.is-controls-right[class*=small] [class*=decrease]{--el-input-number-controls-height:11px}.el-input-tag{--el-input-tag-border-color-hover:var(--el-border-color-hover);--el-input-tag-placeholder-color:var(--el-text-color-placeholder);--el-input-tag-disabled-color:var(--el-disabled-text-color);--el-input-tag-disabled-border:var(--el-disabled-border-color);--el-input-tag-font-size:var(--el-font-size-base);--el-input-tag-close-hover-color:var(--el-text-color-secondary);--el-input-tag-text-color:var(--el-text-color-regular);--el-input-tag-input-focus-border-color:var(--el-color-primary);--el-input-tag-width:100%;--el-input-tag-mini-height:var(--el-component-size);--el-input-tag-gap:6px;--el-input-tag-padding:4px;--el-input-tag-inner-padding:8px;--el-input-tag-line-height:24px;box-sizing:border-box;cursor:pointer;font-size:var(--el-input-tag-font-size);padding:var(--el-input-tag-padding);width:var(--el-input-tag-width);min-height:var(--el-input-tag-mini-height);line-height:var(--el-input-tag-line-height);border-radius:var(--el-border-radius-base);background-color:var(--el-fill-color-blank);transition:var(--el-transition-duration);box-shadow:0 0 0 1px var(--el-border-color) inset;align-items:center;display:flex;transform:translate(0)}.el-input-tag.is-focused{box-shadow:0 0 0 1px var(--el-color-primary) inset}.el-input-tag.is-hovering:not(.is-focused){box-shadow:0 0 0 1px var(--el-border-color-hover) inset}.el-input-tag.is-disabled{cursor:not-allowed;background-color:var(--el-fill-color-light);box-shadow:0 0 0 1px var(--el-input-tag-disabled-border) inset}.el-input-tag.is-disabled:hover{box-shadow:0 0 0 1px var(--el-input-tag-disabled-border) inset}.el-input-tag.is-disabled.is-focus{box-shadow:0 0 0 1px var(--el-input-focus-border-color) inset}.el-input-tag.is-disabled .el-input-tag__inner .el-input-tag__input,.el-input-tag.is-disabled .el-input-tag__inner .el-tag{cursor:not-allowed}.el-input-tag__prefix{padding:0 var(--el-input-tag-inner-padding);color:var(--el-input-icon-color,var(--el-text-color-placeholder));flex-shrink:0;align-items:center;display:flex}.el-input-tag__suffix{padding:0 var(--el-input-tag-inner-padding);color:var(--el-input-icon-color,var(--el-text-color-placeholder));flex-shrink:0;align-items:center;gap:8px;display:flex}.el-input-tag__collapse-tag{line-height:1}.el-input-tag__input-tag-list{flex-wrap:wrap;flex:1;align-items:center;gap:6px;min-width:0;display:flex;position:relative}.el-input-tag__input-tag-list.is-near{margin-left:-8px}.el-input-tag__input-tag-list .el-tag{cursor:pointer;border-color:#0000}.el-input-tag__input-tag-list .el-tag.el-tag--plain{border-color:var(--el-tag-border-color)}.el-input-tag__input-tag-list .el-tag .el-tag__content{min-width:0}.el-input-tag__inner{align-items:center;gap:var(--el-input-tag-gap);flex-wrap:wrap;flex:1;min-width:0;max-width:100%;display:flex;position:relative}.el-input-tag__inner.is-left-space{margin-left:var(--el-input-tag-inner-padding)}.el-input-tag__inner.is-right-space{margin-right:var(--el-input-tag-inner-padding)}.el-input-tag__inner.is-draggable .el-tag{cursor:move;-webkit-user-select:none;user-select:none}.el-input-tag__drop-indicator{width:1px;height:var(--el-input-tag-line-height);background-color:var(--el-color-primary);position:absolute;top:0}.el-input-tag__inner .el-tag{cursor:pointer;border-color:#0000;max-width:100%}.el-input-tag__inner .el-tag.el-tag--plain{border-color:var(--el-tag-border-color)}.el-input-tag__inner .el-tag .el-tag__content{text-overflow:ellipsis;white-space:nowrap;min-width:0;line-height:normal;overflow:hidden}.el-input-tag__input-wrapper{flex:1}.el-input-tag__input{color:var(--el-input-tag-text-color);font-size:inherit;font-family:inherit;line-height:inherit;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#0000;border:none;outline:none;width:100%;padding:0}.el-input-tag__input::placeholder{color:var(--el-input-tag-placeholder-color)}.el-input-tag__input-calculator{visibility:hidden;white-space:pre;max-width:100%;position:absolute;top:0;left:0;overflow:hidden}.el-input-tag--large{--el-input-tag-gap:6px;--el-input-tag-padding:8px;--el-input-tag-padding-left:8px;--el-input-tag-font-size:14px}.el-input-tag--small{--el-input-tag-gap:4px;--el-input-tag-padding:2px;--el-input-tag-padding-left:6px;--el-input-tag-font-size:12px;--el-input-tag-line-height:20px;--el-input-tag-mini-height:var(--el-component-size-small)}.el-input-otp{--el-input-otp-text-color:var(--el-text-color-regular);--el-input-otp-field-width:calc(var(--el-input-otp-size) - 4px);--el-input-otp-gap:8px;--el-input-otp-font-size:14px;--el-input-otp-size:32px}.el-input-otp--large{--el-input-otp-gap:12px;--el-input-otp-font-size:14px;--el-input-otp-size:40px}.el-input-otp--small{--el-input-otp-gap:6px;--el-input-otp-font-size:12px;--el-input-otp-size:24px}.el-input-otp{align-items:center;column-gap:var(--el-input-otp-gap);font-size:var(--el-input-otp-font-size);height:var(--el-input-otp-size);flex-wrap:nowrap;display:inline-flex}.el-input-otp.is-disabled,.el-input-otp.is-disabled .el-input-otp__input{cursor:not-allowed}.el-input-otp__input-field{width:var(--el-input-otp-field-width,calc(var(--el-input-otp-size) - 4px));border-radius:var(--el-border-radius-base);height:100%;line-height:1;position:relative}.el-input-otp__input{text-align:center;width:100%;height:100%;line-height:1;font-size:inherit;color:var(--el-input-otp-text-color,var(--el-text-color-regular));background-color:#0000;border:none;outline:none;padding:0}.el-input-otp.is-disabled .el-input-otp__input{color:var(--el-text-color-disabled)}.el-input-otp--outlined .el-input-otp__input-field{box-shadow:0 0 0 1px var(--el-border-color) inset}@media (hover:hover){.el-input-otp--outlined .el-input-otp__input-field:hover{box-shadow:0 0 0 1px var(--el-color-primary-light-5) inset}}.el-input-otp--outlined .el-input-otp__input-field:focus-within{box-shadow:0 0 0 1px var(--el-color-primary) inset}.el-input-otp--outlined.is-disabled .el-input-otp__input-field{background-color:var(--el-fill-color-light)}@media (hover:hover){.el-input-otp--outlined.is-disabled .el-input-otp__input-field:hover{box-shadow:0 0 0 1px var(--el-border-color) inset}}.el-input-otp--filled .el-input-otp__input-field{background-color:var(--el-fill-color)}@media (hover:hover){.el-input-otp--filled .el-input-otp__input-field:hover{background-color:var(--el-fill-color-dark)}}.el-input-otp--filled .el-input-otp__input-field:focus-within{background-color:var(--el-fill-color-blank);box-shadow:0 0 0 1px var(--el-color-primary) inset}.el-input-otp--filled.is-disabled .el-input-otp__input-field{background-color:var(--el-fill-color-light)}@media (hover:hover){.el-input-otp--filled.is-disabled .el-input-otp__input-field:hover{background-color:var(--el-fill-color-light)}}.el-input-otp--underlined .el-input-otp__input-field:after{content:"";background-color:var(--el-border-color);width:100%;height:1px;display:block;position:absolute;bottom:0;left:0}@media (hover:hover){.el-input-otp--underlined .el-input-otp__input-field:hover:after{background-color:var(--el-color-primary-light-5)}}.el-input-otp--underlined .el-input-otp__input-field:focus-within:after{background-color:var(--el-color-primary)}@media (hover:hover){.el-input-otp--underlined.is-disabled .el-input-otp__input-field:hover:after{background-color:var(--el-border-color)}}.el-link{--el-link-font-size:var(--el-font-size-base);--el-link-font-weight:var(--el-font-weight-primary);--el-link-text-color:var(--el-text-color-regular);--el-link-hover-text-color:var(--el-color-primary);--el-link-disabled-text-color:var(--el-text-color-placeholder);vertical-align:middle;cursor:pointer;font-size:var(--el-link-font-size);font-weight:var(--el-link-font-weight);color:var(--el-link-text-color);outline:none;flex-direction:row;justify-content:center;align-items:center;padding:0;text-decoration:none;display:inline-flex;position:relative}.el-link.is-hover-underline:hover:after{content:"";border-bottom:1px solid var(--el-link-hover-text-color);height:0;position:absolute;bottom:0;left:0;right:0}.el-link.is-underline:after{content:"";border-bottom:1px solid var(--el-link-text-color);height:0;position:absolute;bottom:0;left:0;right:0}.el-link:hover{color:var(--el-link-hover-text-color)}.el-link:hover:after{border-color:var(--el-link-hover-text-color)}.el-link [class*=el-icon-]+span{margin-left:5px}.el-link__inner{justify-content:center;align-items:center;display:inline-flex}.el-link.el-link--primary{--el-link-text-color:var(--el-color-primary);--el-link-hover-text-color:var(--el-color-primary-light-3);--el-link-disabled-text-color:var(--el-color-primary-light-5)}.el-link.el-link--success{--el-link-text-color:var(--el-color-success);--el-link-hover-text-color:var(--el-color-success-light-3);--el-link-disabled-text-color:var(--el-color-success-light-5)}.el-link.el-link--warning{--el-link-text-color:var(--el-color-warning);--el-link-hover-text-color:var(--el-color-warning-light-3);--el-link-disabled-text-color:var(--el-color-warning-light-5)}.el-link.el-link--danger{--el-link-text-color:var(--el-color-danger);--el-link-hover-text-color:var(--el-color-danger-light-3);--el-link-disabled-text-color:var(--el-color-danger-light-5)}.el-link.el-link--error{--el-link-text-color:var(--el-color-error);--el-link-hover-text-color:var(--el-color-error-light-3);--el-link-disabled-text-color:var(--el-color-error-light-5)}.el-link.el-link--info{--el-link-text-color:var(--el-color-info);--el-link-hover-text-color:var(--el-color-info-light-3);--el-link-disabled-text-color:var(--el-color-info-light-5)}.el-link.is-disabled{color:var(--el-link-disabled-text-color);cursor:not-allowed}.el-link.is-disabled:after{border-color:var(--el-link-disabled-text-color)}:root{--el-loading-spinner-size:42px;--el-loading-fullscreen-spinner-size:50px}.el-loading-parent--relative{position:relative!important}.el-loading-parent--hidden{overflow:hidden!important}.el-loading-mask{z-index:2000;background-color:var(--el-mask-color);transition:opacity var(--el-transition-duration);margin:0;position:absolute;top:0;bottom:0;left:0;right:0}.el-loading-mask.is-fullscreen{position:fixed}.el-loading-mask.is-fullscreen .el-loading-spinner{margin-top:calc((0px - var(--el-loading-fullscreen-spinner-size)) / 2)}.el-loading-mask.is-fullscreen .el-loading-spinner .circular{height:var(--el-loading-fullscreen-spinner-size);width:var(--el-loading-fullscreen-spinner-size)}.el-loading-spinner{margin-top:calc((0px - var(--el-loading-spinner-size)) / 2);text-align:center;width:100%;position:absolute;top:50%}.el-loading-spinner .el-loading-text{color:var(--el-color-primary);margin:3px 0;font-size:14px}.el-loading-spinner .circular{height:var(--el-loading-spinner-size);width:var(--el-loading-spinner-size);animation:2s linear infinite loading-rotate;display:inline}.el-loading-spinner .path{stroke-dasharray:90 150;stroke-dashoffset:0;stroke-width:2px;stroke:var(--el-color-primary);stroke-linecap:round;animation:1.5s ease-in-out infinite loading-dash}.el-loading-spinner i{color:var(--el-color-primary)}.el-loading-fade-enter-from,.el-loading-fade-leave-to{opacity:0}@keyframes loading-rotate{to{transform:rotate(360deg)}}@keyframes loading-dash{0%{stroke-dasharray:1 200;stroke-dashoffset:0}50%{stroke-dasharray:90 150;stroke-dashoffset:-40px}to{stroke-dasharray:90 150;stroke-dashoffset:-120px}}.el-main{--el-main-padding:20px;box-sizing:border-box;padding:var(--el-main-padding);flex:auto;display:block;overflow:auto}:root{--el-menu-active-color:var(--el-color-primary);--el-menu-text-color:var(--el-text-color-primary);--el-menu-hover-text-color:var(--el-color-primary);--el-menu-bg-color:var(--el-fill-color-blank);--el-menu-hover-bg-color:var(--el-color-primary-light-9);--el-menu-item-height:56px;--el-menu-sub-item-height:calc(var(--el-menu-item-height) - 6px);--el-menu-horizontal-height:60px;--el-menu-horizontal-sub-item-height:36px;--el-menu-item-font-size:var(--el-font-size-base);--el-menu-item-hover-fill:var(--el-color-primary-light-9);--el-menu-border-color:var(--el-border-color);--el-menu-base-level-padding:20px;--el-menu-level-padding:20px;--el-menu-icon-width:24px}.el-menu{border-right:solid 1px var(--el-menu-border-color);background-color:var(--el-menu-bg-color);box-sizing:border-box;margin:0;padding-left:0;list-style:none;position:relative}.el-menu--vertical:not(.el-menu--collapse):not(.el-menu--popup-container) .el-menu-item,.el-menu--vertical:not(.el-menu--collapse):not(.el-menu--popup-container) .el-sub-menu__title,.el-menu--vertical:not(.el-menu--collapse):not(.el-menu--popup-container) .el-menu-item-group__title{white-space:nowrap;padding-left:calc(var(--el-menu-base-level-padding) + var(--el-menu-level) * var(--el-menu-level-padding))}.el-menu:not(.el-menu--collapse) .el-sub-menu__title{padding-right:calc(var(--el-menu-base-level-padding) + var(--el-menu-icon-width))}.el-menu--horizontal{height:var(--el-menu-horizontal-height);border-right:none;flex-wrap:nowrap;display:flex}.el-menu--horizontal.el-menu--popup-container{height:unset}.el-menu--horizontal.el-menu{border-bottom:solid 1px var(--el-menu-border-color)}.el-menu--horizontal>.el-menu-item{height:100%;color:var(--el-menu-text-color);border-bottom:2px solid #0000;justify-content:center;align-items:center;margin:0;display:inline-flex}.el-menu--horizontal>.el-menu-item a,.el-menu--horizontal>.el-menu-item a:hover{color:inherit}.el-menu--horizontal>.el-sub-menu:focus,.el-menu--horizontal>.el-sub-menu:hover{outline:none}.el-menu--horizontal>.el-sub-menu:hover .el-sub-menu__title{color:var(--el-menu-hover-text-color)}.el-menu--horizontal>.el-sub-menu.is-active .el-sub-menu__title{border-bottom:2px solid var(--el-menu-active-color);color:var(--el-menu-active-color)}.el-menu--horizontal>.el-sub-menu .el-sub-menu__title{height:100%;color:var(--el-menu-text-color);border-bottom:2px solid #0000}.el-menu--horizontal>.el-sub-menu .el-sub-menu__title:hover{background-color:var(--el-menu-bg-color)}.el-menu--horizontal .el-menu .el-menu-item,.el-menu--horizontal .el-menu .el-sub-menu__title{background-color:var(--el-menu-bg-color);height:var(--el-menu-horizontal-sub-item-height);line-height:var(--el-menu-horizontal-sub-item-height);color:var(--el-menu-text-color);align-items:center;padding:0 10px;display:flex}.el-menu--horizontal .el-menu .el-sub-menu__title{padding-right:40px}.el-menu--horizontal .el-menu .el-menu-item.is-active,.el-menu--horizontal .el-menu .el-menu-item.is-active:hover,.el-menu--horizontal .el-menu .el-sub-menu.is-active>.el-sub-menu__title,.el-menu--horizontal .el-menu .el-sub-menu.is-active>.el-sub-menu__title:hover{color:var(--el-menu-active-color)}.el-menu--horizontal .el-menu-item:not(.is-disabled):hover,.el-menu--horizontal .el-menu-item:not(.is-disabled):focus{color:var(--el-menu-active-color,var(--el-menu-hover-text-color));background-color:var(--el-menu-hover-bg-color);outline:none}.el-menu--horizontal>.el-menu-item.is-active{border-bottom:2px solid var(--el-menu-active-color);color:var(--el-menu-active-color)!important}.el-menu--collapse{width:calc(var(--el-menu-icon-width) + var(--el-menu-base-level-padding) * 2)}.el-menu--collapse>.el-menu-item [class^=el-icon],.el-menu--collapse>.el-sub-menu>.el-sub-menu__title [class^=el-icon],.el-menu--collapse>.el-menu-item-group>ul>.el-sub-menu>.el-sub-menu__title [class^=el-icon]{vertical-align:middle;width:var(--el-menu-icon-width);text-align:center;margin:0}.el-menu--collapse>.el-menu-item .el-sub-menu__icon-arrow,.el-menu--collapse>.el-sub-menu>.el-sub-menu__title .el-sub-menu__icon-arrow,.el-menu--collapse>.el-menu-item-group>ul>.el-sub-menu>.el-sub-menu__title .el-sub-menu__icon-arrow{display:none}.el-menu--collapse>.el-menu-item>span,.el-menu--collapse>.el-sub-menu>.el-sub-menu__title>span,.el-menu--collapse>.el-menu-item-group>ul>.el-sub-menu>.el-sub-menu__title>span{visibility:hidden;width:0;height:0;display:inline-block;overflow:hidden}.el-menu--collapse>.el-menu-item.is-active i{color:inherit}.el-menu--collapse .el-menu .el-sub-menu{min-width:200px}.el-menu--collapse .el-sub-menu.is-active .el-sub-menu__title{color:var(--el-menu-active-color)}.el-menu--popup{z-index:100;border-radius:var(--el-border-radius-small);min-width:200px;box-shadow:var(--el-box-shadow-light);border:none;padding:5px 0}.el-menu .el-icon{flex-shrink:0}.el-menu-item{height:var(--el-menu-item-height);line-height:var(--el-menu-item-height);font-size:var(--el-menu-item-font-size);color:var(--el-menu-text-color);padding:0 var(--el-menu-base-level-padding);cursor:pointer;transition:border-color var(--el-transition-duration),background-color var(--el-transition-duration),color var(--el-transition-duration);box-sizing:border-box;white-space:nowrap;align-items:center;list-style:none;display:flex;position:relative}.el-menu-item *{vertical-align:bottom}.el-menu-item i{color:inherit}.el-menu-item:hover,.el-menu-item:focus{outline:none}.el-menu-item:hover{background-color:var(--el-menu-hover-bg-color)}.el-menu-item.is-disabled{opacity:.25;cursor:not-allowed;background:0 0!important}.el-menu-item [class^=el-icon]{width:var(--el-menu-icon-width);text-align:center;vertical-align:middle;margin-right:5px;font-size:18px}.el-menu-item.is-active{color:var(--el-menu-active-color)}.el-menu-item.is-active i{color:inherit}.el-menu-item .el-menu-tooltip__trigger{box-sizing:border-box;width:100%;height:100%;padding:0 var(--el-menu-base-level-padding);align-items:center;display:inline-flex;position:absolute;top:0;left:0}.el-sub-menu{margin:0;padding-left:0;list-style:none}.el-sub-menu__title{height:var(--el-menu-item-height);line-height:var(--el-menu-item-height);font-size:var(--el-menu-item-font-size);color:var(--el-menu-text-color);padding:0 var(--el-menu-base-level-padding);cursor:pointer;transition:border-color var(--el-transition-duration),background-color var(--el-transition-duration),color var(--el-transition-duration);box-sizing:border-box;white-space:nowrap;align-items:center;list-style:none;display:flex;position:relative}.el-sub-menu__title *{vertical-align:bottom}.el-sub-menu__title i{color:inherit}.el-sub-menu__title:hover,.el-sub-menu__title:focus{outline:none}.el-sub-menu__title.is-disabled{opacity:.25;cursor:not-allowed;background:0 0!important}.el-sub-menu__title:hover{background-color:var(--el-menu-hover-bg-color)}.el-sub-menu .el-menu{border:none}.el-sub-menu .el-menu-item{height:var(--el-menu-sub-item-height);line-height:var(--el-menu-sub-item-height)}.el-sub-menu.el-sub-menu__hide-arrow .el-sub-menu__title{padding-right:var(--el-menu-base-level-padding)}.el-sub-menu__hide-arrow .el-sub-menu__icon-arrow{display:none!important}.el-sub-menu.is-active .el-sub-menu__title{border-bottom-color:var(--el-menu-active-color)}.el-sub-menu.is-disabled .el-sub-menu__title,.el-sub-menu.is-disabled .el-menu-item{opacity:.25;cursor:not-allowed;background:0 0!important}.el-sub-menu .el-icon{vertical-align:middle;width:var(--el-menu-icon-width);text-align:center;margin-right:5px;font-size:18px}.el-sub-menu .el-icon.el-sub-menu__icon-more{margin-right:0!important}.el-sub-menu .el-sub-menu__icon-arrow{top:50%;right:var(--el-menu-base-level-padding);transition:transform var(--el-transition-duration);width:inherit;margin-top:-6px;margin-right:0;font-size:12px;position:absolute}.el-menu-item-group>ul{padding:0}.el-menu-item-group__title{padding:7px 0 7px var(--el-menu-base-level-padding);color:var(--el-text-color-secondary);font-size:12px;line-height:normal}.horizontal-collapse-transition .el-sub-menu__title .el-sub-menu__icon-arrow{transition:var(--el-transition-duration-fast);opacity:0}.el-popper,.el-menu--popup-container,.el-menu{outline:none}.el-message-box{--el-messagebox-title-color:var(--el-text-color-primary);--el-messagebox-width:420px;--el-messagebox-border-radius:4px;--el-messagebox-box-shadow:var(--el-box-shadow);--el-messagebox-font-size:var(--el-font-size-large);--el-messagebox-content-font-size:var(--el-font-size-base);--el-messagebox-content-color:var(--el-text-color-regular);--el-messagebox-error-font-size:12px;--el-messagebox-padding-primary:12px;--el-messagebox-font-line-height:var(--el-font-line-height-primary);max-width:var(--el-messagebox-width);width:100%;padding:var(--el-messagebox-padding-primary);vertical-align:middle;background-color:var(--el-bg-color);border-radius:var(--el-messagebox-border-radius);font-size:var(--el-messagebox-font-size);box-shadow:var(--el-messagebox-box-shadow);text-align:left;-webkit-backface-visibility:hidden;backface-visibility:hidden;box-sizing:border-box;overflow-wrap:break-word;display:inline-block;position:relative;overflow:hidden}.el-message-box:focus{outline:none!important}.is-message-box .el-overlay-message-box{text-align:center;padding:16px;position:fixed;top:0;bottom:0;left:0;right:0;overflow:auto}.is-message-box .el-overlay-message-box:after{content:"";vertical-align:middle;width:0;height:100%;display:inline-block}.el-message-box.is-draggable .el-message-box__header{cursor:move;-webkit-user-select:none;user-select:none}.el-message-box__header{padding-bottom:var(--el-messagebox-padding-primary)}.el-message-box__header.show-close{padding-right:calc(var(--el-messagebox-padding-primary) + var(--el-message-close-size,16px))}.el-message-box__title{font-size:var(--el-messagebox-font-size);line-height:var(--el-messagebox-font-line-height);color:var(--el-messagebox-title-color)}.el-message-box__headerbtn{width:40px;height:40px;font-size:var(--el-message-close-size,16px);cursor:pointer;background:0 0;border:none;outline:none;padding:0;position:absolute;top:0;right:0}.el-message-box__headerbtn .el-message-box__close{color:var(--el-color-info);font-size:inherit}.el-message-box__headerbtn:focus .el-message-box__close,.el-message-box__headerbtn:hover .el-message-box__close{color:var(--el-color-primary)}.el-message-box__content{color:var(--el-messagebox-content-color);font-size:var(--el-messagebox-content-font-size)}.el-message-box__container{align-items:center;gap:12px;display:flex}.el-message-box__input{padding-top:12px}.el-message-box__input div.invalid>input,.el-message-box__input div.invalid>input:focus{border-color:var(--el-color-error)}.el-message-box__status{font-size:24px}.el-message-box__status.el-message-box-icon--primary{--el-messagebox-color:var(--el-color-primary);color:var(--el-messagebox-color)}.el-message-box__status.el-message-box-icon--success{--el-messagebox-color:var(--el-color-success);color:var(--el-messagebox-color)}.el-message-box__status.el-message-box-icon--info{--el-messagebox-color:var(--el-color-info);color:var(--el-messagebox-color)}.el-message-box__status.el-message-box-icon--warning{--el-messagebox-color:var(--el-color-warning);color:var(--el-messagebox-color)}.el-message-box__status.el-message-box-icon--error{--el-messagebox-color:var(--el-color-error);color:var(--el-messagebox-color)}.el-message-box__message{min-width:0;margin:0}.el-message-box__message p{line-height:var(--el-messagebox-font-line-height);margin:0}.el-message-box__errormsg{color:var(--el-color-error);font-size:var(--el-messagebox-error-font-size);line-height:var(--el-messagebox-font-line-height)}.el-message-box__btns{padding-top:var(--el-messagebox-padding-primary);flex-wrap:wrap;justify-content:flex-end;align-items:center;display:flex}.el-message-box--center .el-message-box__title{justify-content:center;align-items:center;gap:6px;display:flex}.el-message-box--center .el-message-box__status{font-size:inherit}.el-message-box--center .el-message-box__btns,.el-message-box--center .el-message-box__container{justify-content:center}.el-message-box-parent--hidden{overflow:hidden}.fade-in-linear-enter-active .el-overlay-message-box{animation:msgbox-fade-in var(--el-transition-duration)}.fade-in-linear-leave-active .el-overlay-message-box{animation:msgbox-fade-in var(--el-transition-duration) reverse}@keyframes msgbox-fade-in{0%{opacity:0;transform:translateY(-20px)}to{opacity:1;transform:translate(0)}}.el-message{--el-message-bg-color:var(--el-color-info-light-9);--el-message-border-color:var(--el-border-color-lighter);--el-message-padding:11px 15px;--el-message-close-size:16px;--el-message-close-icon-color:var(--el-text-color-placeholder);--el-message-close-hover-color:var(--el-text-color-secondary);box-sizing:border-box;border-radius:var(--el-border-radius-base);border-width:var(--el-border-width);border-style:var(--el-border-style);border-color:var(--el-message-border-color);background-color:var(--el-message-bg-color);width:max-content;max-width:calc(100% - 32px);transition:opacity var(--el-transition-duration),transform .4s,top .4s,bottom .4s;padding:var(--el-message-padding);align-items:center;gap:8px;display:flex;position:fixed}.el-message.is-left{left:16px}.el-message.is-right{right:16px}.el-message.is-center{left:50%;transform:translate(-50%)}.el-message.is-plain{background-color:var(--el-bg-color-overlay);border-color:var(--el-bg-color-overlay);box-shadow:var(--el-box-shadow-light)}.el-message p{margin:0}.el-message--primary{--el-message-bg-color:var(--el-color-primary-light-9);--el-message-border-color:var(--el-color-primary-light-8);--el-message-text-color:var(--el-color-primary)}.el-message--primary .el-message__content{color:var(--el-message-text-color);overflow-wrap:break-word}.el-message .el-message-icon--primary{color:var(--el-message-text-color)}.el-message--success{--el-message-bg-color:var(--el-color-success-light-9);--el-message-border-color:var(--el-color-success-light-8);--el-message-text-color:var(--el-color-success)}.el-message--success .el-message__content{color:var(--el-message-text-color);overflow-wrap:break-word}.el-message .el-message-icon--success{color:var(--el-message-text-color)}.el-message--info{--el-message-bg-color:var(--el-color-info-light-9);--el-message-border-color:var(--el-color-info-light-8);--el-message-text-color:var(--el-color-info)}.el-message--info .el-message__content{color:var(--el-message-text-color);overflow-wrap:break-word}.el-message .el-message-icon--info{color:var(--el-message-text-color)}.el-message--warning{--el-message-bg-color:var(--el-color-warning-light-9);--el-message-border-color:var(--el-color-warning-light-8);--el-message-text-color:var(--el-color-warning)}.el-message--warning .el-message__content{color:var(--el-message-text-color);overflow-wrap:break-word}.el-message .el-message-icon--warning{color:var(--el-message-text-color)}.el-message--error{--el-message-bg-color:var(--el-color-error-light-9);--el-message-border-color:var(--el-color-error-light-8);--el-message-text-color:var(--el-color-error)}.el-message--error .el-message__content{color:var(--el-message-text-color);overflow-wrap:break-word}.el-message .el-message-icon--error{color:var(--el-message-text-color)}.el-message .el-message__badge{position:absolute;top:-8px;right:-8px}.el-message__content{padding:0;font-size:14px;line-height:1}.el-message__content:focus{outline-width:0}.el-message .el-message__closeBtn{cursor:pointer;color:var(--el-message-close-icon-color);font-size:var(--el-message-close-size)}.el-message .el-message__closeBtn:focus{outline-width:0}.el-message .el-message__closeBtn:hover{color:var(--el-message-close-hover-color)}.el-message-fade-enter-from,.el-message-fade-leave-to{opacity:0}.el-message-fade-enter-from.is-left,.el-message-fade-enter-from.is-right,.el-message-fade-leave-to.is-left,.el-message-fade-leave-to.is-right{transform:translateY(-100%)}.el-message-fade-enter-from.is-left.is-bottom,.el-message-fade-enter-from.is-right.is-bottom,.el-message-fade-leave-to.is-left.is-bottom,.el-message-fade-leave-to.is-right.is-bottom{transform:translateY(100%)}.el-message-fade-enter-from.is-center,.el-message-fade-leave-to.is-center{transform:translate(-50%,-100%)}.el-message-fade-enter-from.is-center.is-bottom,.el-message-fade-leave-to.is-center.is-bottom{transform:translate(-50%,100%)}.el-notification{--el-notification-width:330px;--el-notification-padding:14px 26px 14px 13px;--el-notification-radius:8px;--el-notification-shadow:var(--el-box-shadow-light);--el-notification-border-color:var(--el-border-color-lighter);--el-notification-icon-size:24px;--el-notification-close-font-size:var(--el-message-close-size,16px);--el-notification-group-margin-left:13px;--el-notification-group-margin-right:8px;--el-notification-content-font-size:var(--el-font-size-base);--el-notification-content-color:var(--el-text-color-regular);--el-notification-title-font-size:16px;--el-notification-title-color:var(--el-text-color-primary);--el-notification-close-color:var(--el-text-color-secondary);--el-notification-close-hover-color:var(--el-text-color-regular);width:var(--el-notification-width);padding:var(--el-notification-padding);border-radius:var(--el-notification-radius);box-sizing:border-box;border:1px solid var(--el-notification-border-color);background-color:var(--el-bg-color-overlay);box-shadow:var(--el-notification-shadow);transition:opacity var(--el-transition-duration),transform var(--el-transition-duration),left var(--el-transition-duration),right var(--el-transition-duration),top .4s,bottom var(--el-transition-duration);overflow-wrap:break-word;z-index:9999;display:flex;position:fixed;overflow:hidden}.el-notification.right{right:16px}.el-notification.left{left:16px}.el-notification__group{min-width:0;margin-left:var(--el-notification-group-margin-left);margin-right:var(--el-notification-group-margin-right);flex:1}.el-notification__title{font-weight:700;font-size:var(--el-notification-title-font-size);line-height:var(--el-notification-icon-size);color:var(--el-notification-title-color);margin:0}.el-notification__content{font-size:var(--el-notification-content-font-size);color:var(--el-notification-content-color);margin:6px 0 0;line-height:24px}.el-notification__content p{margin:0}.el-notification .el-notification__icon{height:var(--el-notification-icon-size);width:var(--el-notification-icon-size);font-size:var(--el-notification-icon-size);flex-shrink:0}.el-notification .el-notification__closeBtn{cursor:pointer;color:var(--el-notification-close-color);font-size:var(--el-notification-close-font-size);position:absolute;top:18px;right:15px}.el-notification .el-notification__closeBtn:hover{color:var(--el-notification-close-hover-color)}.el-notification .el-notification--primary{--el-notification-icon-color:var(--el-color-primary);color:var(--el-notification-icon-color)}.el-notification .el-notification--success{--el-notification-icon-color:var(--el-color-success);color:var(--el-notification-icon-color)}.el-notification .el-notification--info{--el-notification-icon-color:var(--el-color-info);color:var(--el-notification-icon-color)}.el-notification .el-notification--warning{--el-notification-icon-color:var(--el-color-warning);color:var(--el-notification-icon-color)}.el-notification .el-notification--error{--el-notification-icon-color:var(--el-color-error);color:var(--el-notification-icon-color)}.el-notification__progress{position:absolute;bottom:0;left:0;right:0}.el-notification__progress .el-progress-bar__inner{transition:width .1s linear}.el-notification-fade-enter-from.right{right:0;transform:translate(100%)}.el-notification-fade-enter-from.left{left:0;transform:translate(-100%)}.el-notification-fade-leave-to{opacity:0}.el-overlay{z-index:2000;background-color:var(--el-overlay-color-lighter);height:100%;position:fixed;top:0;bottom:0;left:0;right:0;overflow:auto}.el-overlay .el-overlay-root{height:0}.el-page-header.is-contentful .el-page-header__main{border-top:1px solid var(--el-border-color-light);margin-top:16px}.el-page-header__header{justify-content:space-between;align-items:center;line-height:24px;display:flex}.el-page-header__left{align-items:center;margin-right:40px;display:flex;position:relative}.el-page-header__back{cursor:pointer;align-items:center;display:flex}.el-page-header__left .el-divider--vertical{margin:0 16px}.el-page-header__icon{align-items:center;margin-right:10px;font-size:16px;display:flex}.el-page-header__icon .el-icon{font-size:inherit}.el-page-header__title{font-size:14px;font-weight:500}.el-page-header__content{color:var(--el-text-color-primary);font-size:18px}.el-page-header__breadcrumb{margin-bottom:16px}.el-pagination{--el-pagination-font-size:14px;--el-pagination-bg-color:var(--el-fill-color-blank);--el-pagination-text-color:var(--el-text-color-primary);--el-pagination-border-radius:2px;--el-pagination-button-color:var(--el-text-color-primary);--el-pagination-button-width:32px;--el-pagination-button-height:32px;--el-pagination-button-disabled-color:var(--el-text-color-placeholder);--el-pagination-button-disabled-bg-color:var(--el-fill-color-blank);--el-pagination-button-bg-color:var(--el-fill-color);--el-pagination-hover-color:var(--el-color-primary);--el-pagination-font-size-small:12px;--el-pagination-button-width-small:24px;--el-pagination-button-height-small:24px;--el-pagination-button-width-large:40px;--el-pagination-button-height-large:40px;--el-pagination-item-gap:16px;white-space:nowrap;color:var(--el-pagination-text-color);font-size:var(--el-pagination-font-size);align-items:center;font-weight:400;display:flex}.el-pagination .el-input__inner{text-align:center;-moz-appearance:textfield}.el-pagination .el-select{width:128px}.el-pagination .btn-prev,.el-pagination .btn-next{font-size:var(--el-pagination-font-size);min-width:var(--el-pagination-button-width);height:var(--el-pagination-button-height);line-height:var(--el-pagination-button-height);color:var(--el-pagination-button-color);background:var(--el-pagination-bg-color);border-radius:var(--el-pagination-border-radius);cursor:pointer;text-align:center;box-sizing:border-box;border:none;justify-content:center;align-items:center;padding:0 4px;display:flex}.el-pagination .btn-prev *,.el-pagination .btn-next *{pointer-events:none}.el-pagination .btn-prev:focus,.el-pagination .btn-next:focus{outline:none}.el-pagination .btn-prev:hover,.el-pagination .btn-next:hover{color:var(--el-pagination-hover-color)}.el-pagination .btn-prev.is-active,.el-pagination .btn-next.is-active{color:var(--el-pagination-hover-color);cursor:default;font-weight:700}.el-pagination .btn-prev.is-active.is-disabled,.el-pagination .btn-next.is-active.is-disabled{color:var(--el-text-color-secondary);font-weight:700}.el-pagination .btn-prev:disabled,.el-pagination .btn-prev.is-disabled,.el-pagination .btn-next:disabled,.el-pagination .btn-next.is-disabled{color:var(--el-pagination-button-disabled-color);background-color:var(--el-pagination-button-disabled-bg-color);cursor:not-allowed}.el-pagination .btn-prev:focus-visible{outline:1px solid var(--el-pagination-hover-color);outline-offset:-1px}.el-pagination .btn-next:focus-visible{outline:1px solid var(--el-pagination-hover-color);outline-offset:-1px}.el-pagination .btn-prev .el-icon,.el-pagination .btn-next .el-icon{width:inherit;font-size:12px;font-weight:700;display:block}.el-pagination>.is-first{margin-left:0!important}.el-pagination>.is-last{margin-right:0!important}.el-pagination .btn-prev{margin-left:var(--el-pagination-item-gap)}.el-pagination__sizes,.el-pagination__total{margin-left:var(--el-pagination-item-gap);color:var(--el-text-color-regular);font-weight:400}.el-pagination__total[disabled=true]{color:var(--el-text-color-placeholder)}.el-pagination__jump{margin-left:var(--el-pagination-item-gap);color:var(--el-text-color-regular);align-items:center;font-weight:400;display:flex}.el-pagination__jump[disabled=true]{color:var(--el-text-color-placeholder)}.el-pagination__goto{margin-right:8px}.el-pagination__editor{text-align:center;box-sizing:border-box}.el-pagination__editor.el-input{width:56px}.el-pagination__editor .el-input__inner::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}.el-pagination__editor .el-input__inner::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.el-pagination__classifier{margin-left:8px}.el-pagination__rightwrapper{flex:1;justify-content:flex-end;align-items:center;display:flex}.el-pagination.is-background .btn-prev,.el-pagination.is-background .btn-next,.el-pagination.is-background .el-pager li{background-color:var(--el-pagination-button-bg-color);margin:0 4px}.el-pagination.is-background .btn-prev.is-active,.el-pagination.is-background .btn-next.is-active,.el-pagination.is-background .el-pager li.is-active{background-color:var(--el-color-primary);color:var(--el-color-white)}.el-pagination.is-background .btn-prev:disabled,.el-pagination.is-background .btn-prev.is-disabled,.el-pagination.is-background .btn-next:disabled,.el-pagination.is-background .btn-next.is-disabled,.el-pagination.is-background .el-pager li:disabled,.el-pagination.is-background .el-pager li.is-disabled{color:var(--el-text-color-placeholder);background-color:var(--el-disabled-bg-color)}.el-pagination.is-background .btn-prev:disabled.is-active,.el-pagination.is-background .btn-prev.is-disabled.is-active,.el-pagination.is-background .btn-next:disabled.is-active,.el-pagination.is-background .btn-next.is-disabled.is-active,.el-pagination.is-background .el-pager li:disabled.is-active,.el-pagination.is-background .el-pager li.is-disabled.is-active{color:var(--el-text-color-secondary);background-color:var(--el-fill-color-dark)}.el-pagination.is-background .btn-prev{margin-left:var(--el-pagination-item-gap)}.el-pagination--small .btn-prev,.el-pagination--small .btn-next,.el-pagination--small .el-pager li{height:var(--el-pagination-button-height-small);line-height:var(--el-pagination-button-height-small);font-size:var(--el-pagination-font-size-small);min-width:var(--el-pagination-button-width-small)}.el-pagination--small span:not([class*=suffix]),.el-pagination--small button{font-size:var(--el-pagination-font-size-small)}.el-pagination--small .el-select{width:100px}.el-pagination--large .btn-prev,.el-pagination--large .btn-next,.el-pagination--large .el-pager li{height:var(--el-pagination-button-height-large);line-height:var(--el-pagination-button-height-large);min-width:var(--el-pagination-button-width-large)}.el-pagination--large .el-select .el-input{width:160px}.el-pager{-webkit-user-select:none;user-select:none;align-items:center;margin:0;padding:0;font-size:0;list-style:none;display:flex}.el-pager li{font-size:var(--el-pagination-font-size);min-width:var(--el-pagination-button-width);height:var(--el-pagination-button-height);line-height:var(--el-pagination-button-height);color:var(--el-pagination-button-color);background:var(--el-pagination-bg-color);border-radius:var(--el-pagination-border-radius);cursor:pointer;text-align:center;box-sizing:border-box;border:none;justify-content:center;align-items:center;padding:0 4px;display:flex}.el-pager li *{pointer-events:none}.el-pager li:focus{outline:none}.el-pager li:hover{color:var(--el-pagination-hover-color)}.el-pager li.is-active{color:var(--el-pagination-hover-color);cursor:default;font-weight:700}.el-pager li.is-active.is-disabled{color:var(--el-text-color-secondary);font-weight:700}.el-pager li:disabled,.el-pager li.is-disabled{color:var(--el-pagination-button-disabled-color);background-color:var(--el-pagination-button-disabled-bg-color);cursor:not-allowed}.el-pager li:focus-visible{outline:1px solid var(--el-pagination-hover-color);outline-offset:-1px}.el-popconfirm{outline:none}.el-popconfirm__main{align-items:center;display:flex}.el-popconfirm__icon{margin-right:5px}.el-popconfirm__action{text-align:right;margin-top:8px}.el-popover{--el-popover-bg-color:var(--el-bg-color-overlay);--el-popover-font-size:var(--el-font-size-base);--el-popover-border-color:var(--el-border-color-lighter);--el-popover-padding:12px;--el-popover-padding-large:18px 20px;--el-popover-title-font-size:16px;--el-popover-title-text-color:var(--el-text-color-primary);--el-popover-border-radius:4px}.el-popover.el-popper{background:var(--el-popover-bg-color);border-radius:var(--el-popover-border-radius);border:1px solid var(--el-popover-border-color);min-width:150px;padding:var(--el-popover-padding);z-index:var(--el-index-popper);color:var(--el-text-color-regular);line-height:1.4;font-size:var(--el-popover-font-size);box-shadow:var(--el-box-shadow-light);overflow-wrap:break-word;box-sizing:border-box}.el-popover.el-popper--plain{padding:var(--el-popover-padding-large)}.el-popover__title{color:var(--el-popover-title-text-color);font-size:var(--el-popover-title-font-size);margin-bottom:12px;line-height:1}.el-popover__reference:focus:not(.focusing),.el-popover__reference:focus:hover{outline-width:0}.el-popover.el-popper.is-dark{--el-popover-bg-color:var(--el-text-color-primary);--el-popover-border-color:var(--el-text-color-primary);--el-popover-title-text-color:var(--el-bg-color);color:var(--el-bg-color)}.el-popover.el-popper:focus:active,.el-popover.el-popper:focus{outline-width:0}.el-progress{align-items:center;line-height:1;display:flex;position:relative}.el-progress__text{color:var(--el-text-color-regular);min-width:50px;margin-left:5px;font-size:14px;line-height:1}.el-progress__text i{vertical-align:middle;display:block}.el-progress--circle,.el-progress--dashboard{display:inline-block}.el-progress--circle .el-progress__text,.el-progress--dashboard .el-progress__text{text-align:center;width:100%;margin:0;position:absolute;top:50%;left:0;transform:translateY(-50%)}.el-progress--circle .el-progress__text i,.el-progress--dashboard .el-progress__text i{vertical-align:middle;display:inline-block}.el-progress--without-text .el-progress__text{display:none}.el-progress--without-text .el-progress-bar{margin-right:0;padding-right:0;display:block}.el-progress--text-inside .el-progress-bar{margin-right:0;padding-right:0}.el-progress.is-success .el-progress-bar__inner{background-color:var(--el-color-success)}.el-progress.is-success .el-progress__text{color:var(--el-color-success)}.el-progress.is-warning .el-progress-bar__inner{background-color:var(--el-color-warning)}.el-progress.is-warning .el-progress__text{color:var(--el-color-warning)}.el-progress.is-exception .el-progress-bar__inner{background-color:var(--el-color-danger)}.el-progress.is-exception .el-progress__text{color:var(--el-color-danger)}.el-progress-bar{box-sizing:border-box;flex-grow:1}.el-progress-bar__outer{background-color:var(--el-border-color-lighter);vertical-align:middle;border-radius:100px;height:6px;position:relative;overflow:hidden}.el-progress-bar__inner{background-color:var(--el-color-primary);text-align:right;white-space:nowrap;border-radius:100px;height:100%;line-height:1;transition:width .6s;position:absolute;top:0;left:0}.el-progress-bar__inner:after{content:"";vertical-align:middle;height:100%;display:inline-block}.el-progress-bar__inner--indeterminate{animation:3s infinite indeterminate;transform:translateZ(0)}.el-progress-bar__inner--striped{background-image:linear-gradient(45deg,#0000001a 25%,#0000 25%,#0000 50%,#0000001a 50%,#0000001a 75%,#0000 75%,#0000);background-size:1.25em 1.25em}.el-progress-bar__inner--striped.el-progress-bar__inner--striped-flow{animation:3s linear infinite striped-flow}.el-progress-bar__innerText{vertical-align:middle;color:#fff;margin:0 5px;font-size:12px;display:inline-block}@keyframes progress{0%{background-position:0 0}to{background-position:32px 0}}@keyframes indeterminate{0%{left:-100%}to{left:100%}}@keyframes striped-flow{0%{background-position:-100%}to{background-position:100%}}.el-radio-button{--el-radio-button-checked-bg-color:var(--el-color-primary);--el-radio-button-checked-text-color:var(--el-color-white);--el-radio-button-checked-border-color:var(--el-color-primary);--el-radio-button-disabled-checked-fill:var(--el-border-color-extra-light);outline:none;display:inline-block;position:relative}.el-radio-button__inner{white-space:nowrap;vertical-align:middle;background:var(--el-button-bg-color,var(--el-fill-color-blank));outline:var(--el-border);line-height:1;font-weight:var(--el-button-font-weight,var(--el-font-weight-primary));color:var(--el-button-text-color,var(--el-text-color-regular));-webkit-appearance:none;text-align:center;box-sizing:border-box;cursor:pointer;transition:var(--el-transition-all);-webkit-user-select:none;user-select:none;font-size:var(--el-font-size-base);border-radius:0;margin:0;padding:8px 15px;display:inline-block;position:relative}.el-radio-button__inner.is-round{padding:8px 15px}.el-radio-button__inner:hover{color:var(--el-color-primary)}.el-radio-button__inner [class*=el-icon-]{line-height:.9}.el-radio-button__inner [class*=el-icon-]+span{margin-left:5px}.el-radio-button:first-child .el-radio-button__inner{border-radius:var(--el-border-radius-base) 0 0 var(--el-border-radius-base);box-shadow:none!important}.el-radio-button.is-active .el-radio-button__original-radio:not(:disabled)+.el-radio-button__inner{color:var(--el-radio-button-checked-text-color,var(--el-color-white));background-color:var(--el-radio-button-checked-bg-color,var(--el-color-primary));border-color:var(--el-radio-button-checked-border-color,var(--el-color-primary));box-shadow:-1px 0 0 0 var(--el-radio-button-checked-border-color,var(--el-color-primary))}.el-radio-button__original-radio{opacity:0;z-index:-1;outline:none;position:absolute}.el-radio-button__original-radio:focus-visible+.el-radio-button__inner{border-left:var(--el-border);border-left-color:var(--el-radio-button-checked-border-color,var(--el-color-primary));outline:2px solid var(--el-radio-button-checked-border-color);outline-offset:1px;z-index:2;border-radius:var(--el-border-radius-base);box-shadow:none}.el-radio-button__original-radio:disabled+.el-radio-button__inner{color:var(--el-disabled-text-color);cursor:not-allowed;background-image:none;background-color:var(--el-button-disabled-bg-color,var(--el-fill-color-blank));border-color:var(--el-button-disabled-border-color,var(--el-border-color-light));box-shadow:none}.el-radio-button__original-radio:disabled:checked+.el-radio-button__inner{background-color:var(--el-radio-button-disabled-checked-fill)}.el-radio-button:last-child .el-radio-button__inner{border-radius:0 var(--el-border-radius-base) var(--el-border-radius-base) 0}.el-radio-button:first-child:last-child .el-radio-button__inner{border-radius:var(--el-border-radius-base)}.el-radio-button--large .el-radio-button__inner{font-size:var(--el-font-size-base);border-radius:0;padding:12px 19px}.el-radio-button--large .el-radio-button__inner.is-round{padding:12px 19px}.el-radio-button--small .el-radio-button__inner{border-radius:0;padding:5px 11px;font-size:12px}.el-radio-button--small .el-radio-button__inner.is-round{padding:5px 11px}.el-radio-group{flex-wrap:wrap;align-items:center;font-size:0;display:inline-flex}.el-radio{--el-radio-font-size:var(--el-font-size-base);--el-radio-text-color:var(--el-text-color-regular);--el-radio-font-weight:var(--el-font-weight-primary);--el-radio-input-height:14px;--el-radio-input-width:14px;--el-radio-input-border-radius:var(--el-border-radius-circle);--el-radio-input-bg-color:var(--el-fill-color-blank);--el-radio-input-border:var(--el-border);--el-radio-input-border-color:var(--el-border-color);--el-radio-input-border-color-hover:var(--el-color-primary);color:var(--el-radio-text-color);font-weight:var(--el-radio-font-weight);cursor:pointer;white-space:nowrap;font-size:var(--el-font-size-base);-webkit-user-select:none;user-select:none;outline:none;align-items:center;height:32px;margin-right:30px;display:inline-flex;position:relative}.el-radio.el-radio--large{height:40px}.el-radio.el-radio--small{height:24px}.el-radio.is-bordered{border-radius:var(--el-border-radius-base);border:var(--el-border);box-sizing:border-box;padding:0 15px 0 9px}.el-radio.is-bordered.is-checked{border-color:var(--el-color-primary)}.el-radio.is-bordered.is-disabled{cursor:not-allowed;border-color:var(--el-border-color-lighter)}.el-radio.is-bordered.el-radio--large{border-radius:var(--el-border-radius-base);padding:0 19px 0 11px}.el-radio.is-bordered.el-radio--large .el-radio__label{font-size:var(--el-font-size-base)}.el-radio.is-bordered.el-radio--large .el-radio__inner{width:14px;height:14px}.el-radio.is-bordered.el-radio--small{border-radius:var(--el-border-radius-base);padding:0 11px 0 7px}.el-radio.is-bordered.el-radio--small .el-radio__label{font-size:12px}.el-radio.is-bordered.el-radio--small .el-radio__inner{width:12px;height:12px}.el-radio:last-child{margin-right:0}.el-radio__input{white-space:nowrap;cursor:pointer;vertical-align:middle;outline:none;display:inline-flex;position:relative}.el-radio__input.is-disabled .el-radio__inner{background-color:var(--el-disabled-bg-color);border-color:var(--el-disabled-border-color);cursor:not-allowed}.el-radio__input.is-disabled .el-radio__inner:after{cursor:not-allowed;background-color:var(--el-disabled-bg-color)}.el-radio__input.is-disabled .el-radio__inner+.el-radio__label{cursor:not-allowed}.el-radio__input.is-disabled.is-checked .el-radio__inner{background-color:var(--el-disabled-bg-color);border-color:var(--el-disabled-border-color)}.el-radio__input.is-disabled.is-checked .el-radio__inner:after{background-color:var(--el-text-color-placeholder)}.el-radio__input.is-disabled+span.el-radio__label{color:var(--el-text-color-placeholder);cursor:not-allowed}.el-radio__input.is-checked .el-radio__inner{border-color:var(--el-color-primary);background:var(--el-color-primary)}.el-radio__input.is-checked .el-radio__inner:after{background-color:var(--el-color-white);transform:translate(-50%,-50%)scale(1)}.el-radio__input.is-checked+.el-radio__label{color:var(--el-color-primary)}.el-radio__input.is-focus .el-radio__inner{border-color:var(--el-radio-input-border-color-hover)}.el-radio__inner{border:var(--el-radio-input-border);border-radius:var(--el-radio-input-border-radius);width:var(--el-radio-input-width);height:var(--el-radio-input-height);background-color:var(--el-radio-input-bg-color);cursor:pointer;box-sizing:border-box;transition:all .3s;display:inline-block;position:relative}.el-radio__inner:hover{border-color:var(--el-radio-input-border-color-hover)}.el-radio__inner:after{border-radius:var(--el-radio-input-border-radius);content:"";width:4px;height:4px;transition:transform .15s ease-in;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)scale(0)}.el-radio__original{opacity:0;z-index:-1;outline:none;margin:0;position:absolute;top:0;bottom:0;left:0;right:0}.el-radio__original:focus-visible+.el-radio__inner{outline:2px solid var(--el-radio-input-border-color-hover);outline-offset:1px;border-radius:var(--el-radio-input-border-radius)}.el-radio:focus:not(:focus-visible):not(.is-focus):not(:active):not(.is-disabled) .el-radio__inner{box-shadow:0 0 2px 2px var(--el-radio-input-border-color-hover)}.el-radio__label{font-size:var(--el-radio-font-size);padding-left:8px}.el-radio.el-radio--large .el-radio__label{font-size:14px}.el-radio.el-radio--large .el-radio__inner{width:14px;height:14px}.el-radio.el-radio--small .el-radio__label{font-size:12px}.el-radio.el-radio--small .el-radio__inner{width:12px;height:12px}.el-rate{--el-rate-height:20px;--el-rate-font-size:var(--el-font-size-base);--el-rate-icon-size:18px;--el-rate-icon-margin:6px;--el-rate-void-color:var(--el-border-color-darker);--el-rate-fill-color:#f7ba2a;--el-rate-disabled-void-color:var(--el-fill-color);--el-rate-text-color:var(--el-text-color-primary);--el-rate-outline-color:var(--el-color-primary-light-5);align-items:center;height:32px;display:inline-flex}.el-rate:focus,.el-rate:active{outline:none}.el-rate:focus-visible .el-rate__item .el-rate__icon.is-focus-visible{outline:2px solid var(--el-rate-outline-color);transition:outline-offset,outline}.el-rate__item{cursor:pointer;vertical-align:middle;color:var(--el-rate-void-color);font-size:0;line-height:normal;display:inline-block;position:relative}.el-rate .el-rate__icon{font-size:var(--el-rate-icon-size);margin-right:var(--el-rate-icon-margin);transition:var(--el-transition-duration);display:inline-block;position:relative}.el-rate .el-rate__icon.hover{transform:scale(1.15)}.el-rate .el-rate__icon .path2{position:absolute;top:0;left:0}.el-rate .el-rate__icon.is-active{color:var(--el-rate-fill-color)}.el-rate__decimal{color:var(--el-rate-fill-color);display:inline-block;position:absolute;top:0;left:0;overflow:hidden}.el-rate__decimal--box{position:absolute;top:0;left:0}.el-rate__text{font-size:var(--el-rate-font-size);vertical-align:middle;color:var(--el-rate-text-color)}.el-rate--large{height:40px}.el-rate--small{height:24px}.el-rate--small .el-rate__icon{font-size:14px}.el-rate.is-disabled .el-rate__item{cursor:not-allowed;color:var(--el-rate-disabled-void-color)}.el-result{--el-result-padding:40px 30px;--el-result-icon-font-size:64px;--el-result-title-font-size:20px;--el-result-title-margin-top:20px;--el-result-subtitle-margin-top:10px;--el-result-extra-margin-top:30px;text-align:center;box-sizing:border-box;padding:var(--el-result-padding);flex-direction:column;justify-content:center;align-items:center;display:flex}.el-result__icon svg{width:var(--el-result-icon-font-size);height:var(--el-result-icon-font-size)}.el-result__title{margin-top:var(--el-result-title-margin-top)}.el-result__title p{font-size:var(--el-result-title-font-size);color:var(--el-text-color-primary);margin:0;line-height:1.3}.el-result__subtitle{margin-top:var(--el-result-subtitle-margin-top)}.el-result__subtitle p{font-size:var(--el-font-size-base);color:var(--el-text-color-regular);margin:0;line-height:1.3}.el-result__extra{margin-top:var(--el-result-extra-margin-top)}.el-result .icon-primary{--el-result-color:var(--el-color-primary);color:var(--el-result-color)}.el-result .icon-success{--el-result-color:var(--el-color-success);color:var(--el-result-color)}.el-result .icon-warning{--el-result-color:var(--el-color-warning);color:var(--el-result-color)}.el-result .icon-danger{--el-result-color:var(--el-color-danger);color:var(--el-result-color)}.el-result .icon-error{--el-result-color:var(--el-color-error);color:var(--el-result-color)}.el-result .icon-info{--el-result-color:var(--el-color-info);color:var(--el-result-color)}.el-row{box-sizing:border-box;flex-wrap:wrap;display:flex;position:relative}.el-row.is-justify-center{justify-content:center}.el-row.is-justify-end{justify-content:flex-end}.el-row.is-justify-space-between{justify-content:space-between}.el-row.is-justify-space-around{justify-content:space-around}.el-row.is-justify-space-evenly{justify-content:space-evenly}.el-row.is-align-top{align-items:flex-start}.el-row.is-align-middle{align-items:center}.el-row.is-align-bottom{align-items:flex-end}.el-scrollbar{--el-scrollbar-opacity:.3;--el-scrollbar-bg-color:var(--el-text-color-secondary);--el-scrollbar-hover-opacity:.5;--el-scrollbar-hover-bg-color:var(--el-text-color-secondary);height:100%;position:relative;overflow:hidden}.el-scrollbar__wrap{height:100%;overflow:auto}.el-scrollbar__wrap--hidden-default{scrollbar-width:none}.el-scrollbar__wrap--hidden-default::-webkit-scrollbar{display:none}.el-scrollbar__thumb{cursor:pointer;border-radius:inherit;background-color:var(--el-scrollbar-bg-color,var(--el-text-color-secondary));width:0;height:0;transition:var(--el-transition-duration) background-color;opacity:var(--el-scrollbar-opacity,.3);display:block;position:relative}.el-scrollbar__thumb:hover{background-color:var(--el-scrollbar-hover-bg-color,var(--el-text-color-secondary));opacity:var(--el-scrollbar-hover-opacity,.5)}.el-scrollbar__bar{z-index:1;border-radius:4px;position:absolute;bottom:2px;right:2px}.el-scrollbar__bar.is-vertical{width:6px;top:2px}.el-scrollbar__bar.is-vertical>div{width:100%}.el-scrollbar__bar.is-horizontal{height:6px;left:2px}.el-scrollbar__bar.is-horizontal>div{height:100%}.el-scrollbar-fade-enter-active{transition:opacity .34s ease-out}.el-scrollbar-fade-leave-active{transition:opacity .12s ease-out}.el-scrollbar-fade-enter-from,.el-scrollbar-fade-leave-active{opacity:0}.el-select-dropdown{z-index:calc(var(--el-index-top) + 1);border-radius:var(--el-border-radius-base);box-sizing:border-box}.el-select-dropdown .el-scrollbar.is-empty .el-select-dropdown__list{padding:0}.el-select-dropdown__loading,.el-select-dropdown__empty{text-align:center;color:var(--el-text-color-secondary);font-size:var(--el-select-font-size);margin:0;padding:10px 0}.el-select-dropdown__wrap{max-height:274px}.el-select-dropdown__list{box-sizing:border-box;margin:0;padding:6px 0;list-style:none}.el-select-dropdown__list.el-vl__window{margin:6px 0;padding:0}.el-select-dropdown__header{border-bottom:1px solid var(--el-border-color-light);padding:10px}.el-select-dropdown__footer{border-top:1px solid var(--el-border-color-light);padding:10px}.el-select-dropdown__item{font-size:var(--el-font-size-base);white-space:nowrap;text-overflow:ellipsis;color:var(--el-text-color-regular);box-sizing:border-box;cursor:pointer;height:34px;padding:0 32px 0 20px;line-height:34px;position:relative;overflow:hidden}.el-select-dropdown__item.is-hovering{background-color:var(--el-fill-color-light)}.el-select-dropdown__item.is-selected{color:var(--el-color-primary);font-weight:700}.el-select-dropdown__item.is-disabled{color:var(--el-text-color-placeholder);cursor:not-allowed;background-color:unset}.el-select-dropdown.is-multiple .el-select-dropdown__item.is-selected:after{content:"";background-position:50%;background-repeat:no-repeat;background-color:var(--el-color-primary);-webkit-mask:url("data:image/svg+xml;utf8,%3Csvg class='icon' width='200' height='200' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='currentColor' d='M406.656 706.944L195.84 496.256a32 32 0 10-45.248 45.248l256 256 512-512a32 32 0 00-45.248-45.248L406.592 706.944z'%3E%3C/path%3E%3C/svg%3E") 0 0/100% 100% no-repeat;mask:url("data:image/svg+xml;utf8,%3Csvg class='icon' width='200' height='200' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='currentColor' d='M406.656 706.944L195.84 496.256a32 32 0 10-45.248 45.248l256 256 512-512a32 32 0 00-45.248-45.248L406.592 706.944z'%3E%3C/path%3E%3C/svg%3E") 0 0/100% 100% no-repeat;border-top:none;border-right:none;width:12px;height:12px;position:absolute;top:50%;right:20px;transform:translateY(-50%);-webkit-mask:url("data:image/svg+xml;utf8,%3Csvg class='icon' width='200' height='200' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='currentColor' d='M406.656 706.944L195.84 496.256a32 32 0 10-45.248 45.248l256 256 512-512a32 32 0 00-45.248-45.248L406.592 706.944z'%3E%3C/path%3E%3C/svg%3E") 0 0/100% 100% no-repeat}.el-select-dropdown.is-multiple .el-select-dropdown__item.is-disabled:after{background-color:var(--el-text-color-placeholder)}.el-select-group{margin:0;padding:0}.el-select-group__wrap{margin:0;padding:0;list-style:none;position:relative}.el-select-group__title{box-sizing:border-box;color:var(--el-color-info);text-overflow:ellipsis;white-space:nowrap;padding:0 20px;font-size:12px;line-height:34px;overflow:hidden}.el-select-group .el-select-dropdown__item{padding-left:20px}.el-select{--el-select-border-color-hover:var(--el-border-color-hover);--el-select-disabled-color:var(--el-disabled-text-color);--el-select-disabled-border:var(--el-disabled-border-color);--el-select-font-size:var(--el-font-size-base);--el-select-close-hover-color:var(--el-text-color-secondary);--el-select-input-color:var(--el-text-color-placeholder);--el-select-multiple-input-color:var(--el-text-color-regular);--el-select-input-focus-border-color:var(--el-color-primary);--el-select-input-font-size:14px;--el-select-width:100%;vertical-align:middle;width:var(--el-select-width);display:inline-block;position:relative}.el-select__wrapper{box-sizing:border-box;cursor:pointer;text-align:left;border-radius:var(--el-border-radius-base);background-color:var(--el-fill-color-blank);min-height:32px;transition:var(--el-transition-duration);box-shadow:0 0 0 1px var(--el-border-color) inset;align-items:center;gap:6px;padding:4px 12px;font-size:14px;line-height:24px;display:flex;position:relative;transform:translateZ(0)}.el-select__wrapper.is-filterable{cursor:text}.el-select__wrapper.is-focused{box-shadow:0 0 0 1px var(--el-color-primary) inset}.el-select__wrapper.is-hovering:not(.is-focused){box-shadow:0 0 0 1px var(--el-border-color-hover) inset}.el-select__wrapper.is-disabled{cursor:not-allowed;background-color:var(--el-fill-color-light);color:var(--el-text-color-placeholder);box-shadow:0 0 0 1px var(--el-select-disabled-border) inset}.el-select__wrapper.is-disabled:hover{box-shadow:0 0 0 1px var(--el-select-disabled-border) inset}.el-select__wrapper.is-disabled.is-focus{box-shadow:0 0 0 1px var(--el-input-focus-border-color) inset}.el-select__wrapper.is-disabled .el-select__selected-item{color:var(--el-select-disabled-color)}.el-select__wrapper.is-disabled .el-select__caret,.el-select__wrapper.is-disabled .el-tag,.el-select__wrapper.is-disabled input{cursor:not-allowed}.el-select__wrapper.is-disabled .el-select__prefix,.el-select__wrapper.is-disabled .el-select__suffix{pointer-events:none}.el-select__prefix,.el-select__suffix{color:var(--el-input-icon-color,var(--el-text-color-placeholder));flex-shrink:0;align-items:center;gap:6px;display:flex}.el-select__caret{color:var(--el-select-input-color);font-size:var(--el-select-input-font-size);transition:var(--el-transition-duration);cursor:pointer;transform:rotate(0)}.el-select__caret.is-reverse{transform:rotate(180deg)}.el-select__clear{cursor:pointer}.el-select__clear:hover{color:var(--el-select-close-hover-color)}.el-select__selection{flex-wrap:wrap;flex:1;align-items:center;gap:6px;min-width:0;display:flex;position:relative}.el-select__selection.is-near{margin-left:-8px}.el-select__selection .el-tag{cursor:pointer;border-color:#0000}.el-select__selection .el-tag.el-tag--plain{border-color:var(--el-tag-border-color)}.el-select__selection .el-tag .el-tag__content{min-width:0}.el-select__selected-item{-webkit-user-select:none;user-select:none;flex-wrap:wrap;display:flex}.el-select__tags-text{text-overflow:ellipsis;white-space:nowrap;line-height:normal;display:block;overflow:hidden}.el-select__placeholder{z-index:-1;text-overflow:ellipsis;white-space:nowrap;width:100%;color:var(--el-input-text-color,var(--el-text-color-regular));display:block;position:absolute;top:50%;overflow:hidden;transform:translateY(-50%)}.el-select__placeholder.is-transparent{-webkit-user-select:none;user-select:none;color:var(--el-text-color-placeholder)}.el-select__popper.el-popper{background:var(--el-bg-color-overlay);border:1px solid var(--el-border-color-light);box-shadow:var(--el-box-shadow-light)}.el-select__popper.el-popper .el-popper__arrow:before{border:1px solid var(--el-border-color-light)}.el-select__popper.el-popper[data-popper-placement^=top] .el-popper__arrow:before{border-top-color:#0000;border-left-color:#0000}.el-select__popper.el-popper[data-popper-placement^=bottom] .el-popper__arrow:before{border-bottom-color:#0000;border-right-color:#0000}.el-select__popper.el-popper[data-popper-placement^=left] .el-popper__arrow:before{border-bottom-color:#0000;border-left-color:#0000}.el-select__popper.el-popper[data-popper-placement^=right] .el-popper__arrow:before{border-top-color:#0000;border-right-color:#0000}.el-select__input-wrapper{flex:1}.el-select__input-wrapper.is-hidden{opacity:0;z-index:-1;position:absolute}.el-select__input{color:var(--el-select-multiple-input-color);font-size:inherit;-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#0000;border:none;outline:none;width:100%;height:24px;padding:0;font-family:inherit}.el-select__input-calculator{visibility:hidden;white-space:pre;max-width:100%;position:absolute;top:0;left:0;overflow:hidden}.el-select--large .el-select__wrapper{gap:6px;min-height:40px;padding:8px 16px;font-size:14px;line-height:24px}.el-select--large .el-select__selection{gap:6px}.el-select--large .el-select__selection.is-near{margin-left:-8px}.el-select--large .el-select__prefix,.el-select--large .el-select__suffix{gap:6px}.el-select--large .el-select__input{height:24px}.el-select--small .el-select__wrapper{gap:4px;min-height:24px;padding:2px 8px;font-size:12px;line-height:20px}.el-select--small .el-select__selection{gap:4px}.el-select--small .el-select__selection.is-near{margin-left:-6px}.el-select--small .el-select__prefix,.el-select--small .el-select__suffix{gap:4px}.el-select--small .el-select__input{height:20px}.el-skeleton{--el-skeleton-circle-size:var(--el-avatar-size)}.el-skeleton__item{background:var(--el-skeleton-color);border-radius:var(--el-border-radius-base);width:100%;height:16px;display:inline-block}.el-skeleton__circle{width:var(--el-skeleton-circle-size);height:var(--el-skeleton-circle-size);line-height:var(--el-skeleton-circle-size);border-radius:50%}.el-skeleton__button{border-radius:4px;width:64px;height:40px}.el-skeleton__p{width:100%}.el-skeleton__p.is-last{width:61%}.el-skeleton__p.is-first{width:33%}.el-skeleton__text{width:100%;height:var(--el-font-size-small)}.el-skeleton__caption{height:var(--el-font-size-extra-small)}.el-skeleton__h1{height:var(--el-font-size-extra-large)}.el-skeleton__h3{height:var(--el-font-size-large)}.el-skeleton__h5{height:var(--el-font-size-medium)}.el-skeleton__image{width:unset;border-radius:0;justify-content:center;align-items:center;display:flex}.el-skeleton__image svg{color:var(--el-svg-monochrome-grey);fill:currentColor;width:22%;height:22%}.el-skeleton{--el-skeleton-color:var(--el-fill-color);--el-skeleton-to-color:var(--el-fill-color-darker)}@keyframes el-skeleton-loading{0%{background-position:100%}to{background-position:0}}.el-skeleton{width:100%}.el-skeleton__first-line,.el-skeleton__paragraph{background:var(--el-skeleton-color);height:16px;margin-top:16px}.el-skeleton.is-animated .el-skeleton__item{background:linear-gradient(90deg,var(--el-skeleton-color) 25%,var(--el-skeleton-to-color) 37%,var(--el-skeleton-color) 63%);background-size:400% 100%;animation:1.4s infinite el-skeleton-loading}.el-slider{--el-slider-main-bg-color:var(--el-color-primary);--el-slider-runway-bg-color:var(--el-border-color-light);--el-slider-stop-bg-color:var(--el-color-white);--el-slider-disabled-color:var(--el-text-color-placeholder);--el-slider-border-radius:3px;--el-slider-height:6px;--el-slider-button-size:20px;--el-slider-button-wrapper-size:36px;--el-slider-button-wrapper-offset:-15px;align-items:center;width:100%;height:32px;display:flex}.el-slider__runway{height:var(--el-slider-height);background-color:var(--el-slider-runway-bg-color);border-radius:var(--el-slider-border-radius);cursor:pointer;flex:1;position:relative}.el-slider__runway.show-input{width:auto;margin-right:30px}.el-slider__runway.is-disabled{cursor:default}.el-slider__runway.is-disabled .el-slider__bar{background-color:var(--el-slider-disabled-color)}.el-slider__runway.is-disabled .el-slider__button{border-color:var(--el-slider-disabled-color)}.el-slider__runway.is-disabled .el-slider__button-wrapper:hover,.el-slider__runway.is-disabled .el-slider__button-wrapper.hover,.el-slider__runway.is-disabled .el-slider__button-wrapper.dragging{cursor:not-allowed}.el-slider__runway.is-disabled .el-slider__button:hover,.el-slider__runway.is-disabled .el-slider__button.hover,.el-slider__runway.is-disabled .el-slider__button.dragging{cursor:not-allowed;transform:scale(1)}.el-slider__input{flex-shrink:0;width:130px}.el-slider__bar{height:var(--el-slider-height);background-color:var(--el-slider-main-bg-color);border-top-left-radius:var(--el-slider-border-radius);border-bottom-left-radius:var(--el-slider-border-radius);position:absolute}.el-slider__button-wrapper{height:var(--el-slider-button-wrapper-size);width:var(--el-slider-button-wrapper-size);z-index:1;top:var(--el-slider-button-wrapper-offset);text-align:center;-webkit-user-select:none;user-select:none;background-color:#0000;outline:none;line-height:normal;position:absolute;transform:translate(-50%)}.el-slider__button-wrapper:after{content:"";vertical-align:middle;height:100%;display:inline-block}.el-slider__button-wrapper:hover,.el-slider__button-wrapper.hover{cursor:grab}.el-slider__button-wrapper.dragging{cursor:grabbing}.el-slider__button{width:var(--el-slider-button-size);height:var(--el-slider-button-size);vertical-align:middle;border:solid 2px var(--el-slider-main-bg-color);background-color:var(--el-color-white);box-sizing:border-box;transition:var(--el-transition-duration-fast);-webkit-user-select:none;user-select:none;border-radius:50%;display:inline-block}.el-slider__button:hover,.el-slider__button.hover,.el-slider__button.dragging{transform:scale(1.2)}.el-slider__button:hover,.el-slider__button.hover{cursor:grab}.el-slider__button.dragging{cursor:grabbing}.el-slider__stop{height:var(--el-slider-height);width:var(--el-slider-height);border-radius:var(--el-border-radius-circle);background-color:var(--el-slider-stop-bg-color);position:absolute;transform:translate(-50%)}.el-slider__marks{width:18px;height:100%;top:0;left:12px}.el-slider__marks-text{color:var(--el-color-info);white-space:pre;margin-top:15px;font-size:14px;position:absolute;transform:translate(-50%)}.el-slider.is-vertical{flex:0;width:auto;height:100%;display:inline-flex;position:relative}.el-slider.is-vertical .el-slider__runway{width:var(--el-slider-height);height:100%;margin:0 16px}.el-slider.is-vertical .el-slider__bar{width:var(--el-slider-height);border-radius:0 0 3px 3px;height:auto}.el-slider.is-vertical .el-slider__button-wrapper{top:auto;left:var(--el-slider-button-wrapper-offset);transform:translateY(50%)}.el-slider.is-vertical .el-slider__stop{transform:translateY(50%)}.el-slider.is-vertical .el-slider__marks-text{margin-top:0;left:15px;transform:translateY(50%)}.el-slider--large{height:40px}.el-slider--small{height:24px}.el-space{vertical-align:top;display:inline-flex}.el-space__item{flex-wrap:wrap;display:flex}.el-space__item>*{flex:1}.el-space--vertical{flex-direction:column}.el-time-spinner{white-space:nowrap;width:100%}.el-spinner{vertical-align:middle;display:inline-block}.el-spinner-inner{width:50px;height:50px;animation:2s linear infinite rotate}.el-spinner-inner .path{stroke:var(--el-border-color-lighter);stroke-linecap:round;animation:1.5s ease-in-out infinite dash}@keyframes rotate{to{transform:rotate(360deg)}}@keyframes dash{0%{stroke-dasharray:1 150;stroke-dashoffset:0}50%{stroke-dasharray:90 150;stroke-dashoffset:-35px}to{stroke-dasharray:90 150;stroke-dashoffset:-124px}}.el-step{flex-shrink:1;position:relative}.el-step:last-of-type .el-step__line{display:none}.el-step:last-of-type.is-flex{flex-grow:0;flex-shrink:0;flex-basis:auto!important}.el-step:last-of-type .el-step__main,.el-step:last-of-type .el-step__description{padding-right:0}.el-step__head{width:100%;position:relative}.el-step__head.is-process{color:var(--el-text-color-primary);border-color:var(--el-text-color-primary)}.el-step__head.is-wait{color:var(--el-text-color-placeholder);border-color:var(--el-text-color-placeholder)}.el-step__head.is-success{color:var(--el-color-success);border-color:var(--el-color-success)}.el-step__head.is-error{color:var(--el-color-danger);border-color:var(--el-color-danger)}.el-step__head.is-finish{color:var(--el-color-primary);border-color:var(--el-color-primary)}.el-step__icon{z-index:1;box-sizing:border-box;background:var(--el-bg-color);justify-content:center;align-items:center;width:24px;height:24px;font-size:14px;transition:all .15s ease-out;display:inline-flex;position:relative}.el-step__icon.is-text{border:2px solid;border-radius:50%}.el-step__icon.is-icon{width:40px}.el-step__icon-inner{-webkit-user-select:none;user-select:none;text-align:center;color:inherit;font-weight:700;line-height:1;display:inline-block}.el-step__icon-inner[class*=el-icon]:not(.is-status){font-size:25px;font-weight:400}.el-step__icon-inner.is-status{transform:translateY(1px)}.el-step__line{background-color:var(--el-text-color-placeholder);border-color:currentColor;position:absolute}.el-step__line-inner{box-sizing:border-box;border:1px solid;width:0;height:0;transition:all .15s ease-out;display:block}.el-step__main{white-space:normal;text-align:left}.el-step__title{font-size:16px;line-height:38px}.el-step__title.is-process{color:var(--el-text-color-primary);font-weight:700}.el-step__title.is-wait{color:var(--el-text-color-placeholder)}.el-step__title.is-success{color:var(--el-color-success)}.el-step__title.is-error{color:var(--el-color-danger)}.el-step__title.is-finish{color:var(--el-color-primary)}.el-step__description{margin-top:-5px;padding-right:10%;font-size:12px;font-weight:400;line-height:20px}.el-step__description.is-process{color:var(--el-text-color-primary)}.el-step__description.is-wait{color:var(--el-text-color-placeholder)}.el-step__description.is-success{color:var(--el-color-success)}.el-step__description.is-error{color:var(--el-color-danger)}.el-step__description.is-finish{color:var(--el-color-primary)}.el-step.is-horizontal{display:inline-block}.el-step.is-horizontal .el-step__line{height:2px;top:11px;left:0;right:0}.el-step.is-vertical{display:flex}.el-step.is-vertical .el-step__head{flex-grow:0;width:24px}.el-step.is-vertical .el-step__main{flex-grow:1;padding-left:10px}.el-step.is-vertical .el-step__title{padding-bottom:8px;line-height:24px}.el-step.is-vertical .el-step__line{width:2px;top:0;bottom:0;left:11px}.el-step.is-vertical .el-step__icon.is-icon{width:24px}.el-step.is-vertical .el-step__description{padding-right:0}.el-step.is-center .el-step__head,.el-step.is-center .el-step__main{text-align:center}.el-step.is-center .el-step__description{padding-left:20%;padding-right:20%}.el-step.is-center .el-step__line{left:50%;right:-50%}.el-step.is-simple{align-items:center;display:flex}.el-step.is-simple .el-step__head{width:auto;padding-right:10px;font-size:0}.el-step.is-simple .el-step__icon{background:0 0;width:16px;height:16px;font-size:12px}.el-step.is-simple .el-step__icon-inner[class*=el-icon]:not(.is-status){font-size:18px}.el-step.is-simple .el-step__icon-inner.is-status{transform:scale(.8)translateY(1px)}.el-step.is-simple .el-step__main{flex-grow:1;align-items:stretch;display:flex;position:relative}.el-step.is-simple .el-step__title{font-size:16px;line-height:20px}.el-step.is-simple:not(:last-of-type) .el-step__title{overflow-wrap:break-word;max-width:50%}.el-step.is-simple .el-step__arrow{flex-grow:1;justify-content:center;align-items:center;display:flex}.el-step.is-simple .el-step__arrow:before,.el-step.is-simple .el-step__arrow:after{content:"";background:var(--el-text-color-placeholder);width:1px;height:15px;display:inline-block;position:absolute}.el-step.is-simple .el-step__arrow:before{transform-origin:0 0;transform:rotate(-45deg)translateY(-4px)}.el-step.is-simple .el-step__arrow:after{transform-origin:100% 100%;transform:rotate(45deg)translateY(4px)}.el-step.is-simple:last-of-type .el-step__arrow{display:none}.el-steps{line-height:normal;display:flex}.el-steps--simple{background:var(--el-fill-color-light);border-radius:4px;padding:13px 8%}.el-steps--horizontal{white-space:nowrap}.el-steps--vertical{flex-flow:column;height:100%}.el-switch{--el-switch-on-color:var(--el-color-primary);--el-switch-off-color:var(--el-border-color);vertical-align:middle;align-items:center;height:32px;font-size:14px;line-height:20px;display:inline-flex;position:relative}.el-switch.is-disabled .el-switch__core,.el-switch.is-disabled .el-switch__label{cursor:not-allowed}.el-switch__label{transition:var(--el-transition-duration-fast);cursor:pointer;vertical-align:middle;height:20px;color:var(--el-text-color-primary);font-size:14px;font-weight:500;display:inline-block}.el-switch__label.is-active{color:var(--el-color-primary)}.el-switch__label--left{margin-right:10px}.el-switch__label--right{margin-left:10px}.el-switch__label *{font-size:14px;line-height:1;display:inline-block}.el-switch__label .el-icon{height:inherit}.el-switch__label .el-icon svg{vertical-align:middle}.el-switch__input{opacity:0;width:0;height:0;margin:0;position:absolute}.el-switch__input:focus-visible~.el-switch__core{outline:2px solid var(--el-switch-on-color);outline-offset:1px}.el-switch__core{border:1px solid var(--el-switch-border-color,var(--el-switch-off-color));box-sizing:border-box;background:var(--el-switch-off-color);cursor:pointer;min-width:40px;height:20px;transition:border-color var(--el-transition-duration),background-color var(--el-transition-duration);border-radius:10px;outline:none;align-items:center;display:inline-flex;position:relative}.el-switch__core .el-switch__inner{width:100%;transition:all var(--el-transition-duration);justify-content:center;align-items:center;height:16px;padding:0 4px 0 18px;display:flex;overflow:hidden}.el-switch__core .el-switch__inner-wrapper{color:var(--el-color-white);-webkit-user-select:none;user-select:none;text-overflow:ellipsis;white-space:nowrap;align-items:center;font-size:12px;display:flex;overflow:hidden}.el-switch__core .el-switch__action{border-radius:var(--el-border-radius-circle);transition:all var(--el-transition-duration);background-color:var(--el-color-white);width:16px;height:16px;color:var(--el-switch-off-color);justify-content:center;align-items:center;display:flex;position:absolute;left:1px}.el-switch.is-checked .el-switch__core{border-color:var(--el-switch-border-color,var(--el-switch-on-color));background-color:var(--el-switch-on-color)}.el-switch.is-checked .el-switch__core .el-switch__action{color:var(--el-switch-on-color);left:calc(100% - 17px)}.el-switch.is-checked .el-switch__core .el-switch__inner{padding:0 18px 0 4px}.el-switch.is-disabled{opacity:.6}.el-switch--wide .el-switch__label.el-switch__label--left span{left:10px}.el-switch--wide .el-switch__label.el-switch__label--right span{right:10px}.el-switch .label-fade-enter-from,.el-switch .label-fade-leave-active{opacity:0}.el-switch--large{height:40px;font-size:14px;line-height:24px}.el-switch--large .el-switch__label{height:24px;font-size:14px}.el-switch--large .el-switch__label *{font-size:14px}.el-switch--large .el-switch__core{border-radius:12px;min-width:50px;height:24px}.el-switch--large .el-switch__core .el-switch__inner{height:20px;padding:0 6px 0 22px}.el-switch--large .el-switch__core .el-switch__action{width:20px;height:20px}.el-switch--large.is-checked .el-switch__core .el-switch__action{left:calc(100% - 21px)}.el-switch--large.is-checked .el-switch__core .el-switch__inner{padding:0 22px 0 6px}.el-switch--small{height:24px;font-size:12px;line-height:16px}.el-switch--small .el-switch__label{height:16px;font-size:12px}.el-switch--small .el-switch__label *{font-size:12px}.el-switch--small .el-switch__core{border-radius:8px;min-width:30px;height:16px}.el-switch--small .el-switch__core .el-switch__inner{height:12px;padding:0 2px 0 14px}.el-switch--small .el-switch__core .el-switch__action{width:12px;height:12px}.el-switch--small.is-checked .el-switch__core .el-switch__action{left:calc(100% - 13px)}.el-switch--small.is-checked .el-switch__core .el-switch__inner{padding:0 14px 0 2px}.el-table-column--selection .cell{padding-left:14px;padding-right:14px}.el-table-filter{border:solid 1px var(--el-border-color-lighter);box-shadow:var(--el-box-shadow-light);box-sizing:border-box;background-color:#fff;border-radius:2px}.el-table-filter__list{outline:none;min-width:100px;margin:0;padding:5px 0;list-style:none}.el-table-filter__list-item{cursor:pointer;line-height:36px;font-size:var(--el-font-size-base);outline:none;padding:0 10px}.el-table-filter__list-item:hover,.el-table-filter__list-item:focus{background-color:var(--el-color-primary-light-9);color:var(--el-color-primary)}.el-table-filter__list-item.is-active{background-color:var(--el-color-primary);color:#fff}.el-table-filter__multiple{outline:none}.el-table-filter__content{min-width:100px}.el-table-filter__bottom{border-top:1px solid var(--el-border-color-lighter);padding:8px}.el-table-filter__bottom button{cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:var(--el-border-radius-base);transition:transform var(--el-transition-duration-fast) ease-in-out;background-color:#0000;border:none;outline:none;margin:0;padding:0}.el-table-filter__bottom button:focus-visible{outline:2px solid var(--el-color-primary);outline-offset:2px}.el-table-filter__bottom button{color:var(--el-text-color-regular);font-size:var(--el-font-size-small);padding:0 3px}.el-table-filter__bottom button:hover{color:var(--el-color-primary)}.el-table-filter__bottom button.is-disabled{color:var(--el-disabled-text-color);cursor:not-allowed}.el-table-filter__wrap{max-height:280px}.el-table-filter__checkbox-group{padding:10px}.el-table-filter__checkbox-group label.el-checkbox{height:unset;align-items:center;margin-bottom:12px;margin-left:5px;margin-right:5px;display:flex}.el-table-filter__checkbox-group .el-checkbox:last-child{margin-bottom:0}.el-table{--el-table-border-color:var(--el-border-color-lighter);--el-table-border:1px solid var(--el-table-border-color);--el-table-text-color:var(--el-text-color-regular);--el-table-header-text-color:var(--el-text-color-secondary);--el-table-row-hover-bg-color:var(--el-fill-color-light);--el-table-current-row-bg-color:var(--el-color-primary-light-9);--el-table-header-bg-color:var(--el-fill-color-blank);--el-table-fixed-box-shadow:var(--el-box-shadow-light);--el-table-bg-color:var(--el-fill-color-blank);--el-table-tr-bg-color:var(--el-fill-color-blank);--el-table-expanded-cell-bg-color:var(--el-fill-color-blank);--el-table-fixed-left-column:inset 10px 0 10px -10px #00000026;--el-table-fixed-right-column:inset -10px 0 10px -10px #00000026;--el-table-index:var(--el-index-normal);box-sizing:border-box;background-color:var(--el-table-bg-color);width:100%;max-width:100%;height:-moz-fit-content;height:fit-content;font-size:var(--el-font-size-base);color:var(--el-table-text-color);position:relative;overflow:hidden}.el-table__inner-wrapper{flex-direction:column;height:100%;display:flex;position:relative}.el-table__inner-wrapper:before{height:1px;bottom:0;left:0}.el-table tbody:focus-visible{outline:none}.el-table.has-footer.el-table--scrollable-y tr:last-child td.el-table__cell,.el-table.has-footer.el-table--fluid-height tr:last-child td.el-table__cell{border-bottom-color:#0000}.el-table__empty-block{text-align:center;justify-content:center;align-items:center;width:100%;min-height:60px;display:flex;position:sticky;left:0}.el-table__empty-text{width:50%;color:var(--el-text-color-secondary);line-height:60px}.el-table__expand-column .cell{text-align:center;-webkit-user-select:none;user-select:none;padding:0}.el-table__expand-icon{cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:var(--el-border-radius-base);transition:transform var(--el-transition-duration-fast) ease-in-out;background-color:#0000;border:none;outline:none;margin:0;padding:0}.el-table__expand-icon:focus-visible{outline:2px solid var(--el-color-primary);outline-offset:-2px}.el-table__expand-icon{color:var(--el-text-color-regular);width:min(23px,100%);height:23px;font-size:12px;line-height:12px}.el-table__expand-icon.is-disabled{color:var(--el-disabled-text-color);cursor:not-allowed}.el-table__expand-icon--expanded{transform:rotate(90deg)}.el-table__expand-icon>.el-icon{font-size:12px}.el-table__expanded-cell{background-color:var(--el-table-expanded-cell-bg-color)}.el-table__expanded-cell[class*=cell]{padding:20px 50px}.el-table__expanded-cell:hover{background-color:#0000!important}.el-table__placeholder{width:20px;display:inline-block}.el-table__append-wrapper{overflow:hidden}.el-table--fit{border-bottom:0;border-right:0}.el-table--fit .el-table__cell.gutter{border-right-width:1px}.el-table--fit .el-table__inner-wrapper:before{width:100%}.el-table thead{color:var(--el-table-header-text-color)}.el-table thead th{font-weight:600}.el-table thead.is-group th.el-table__cell{background:var(--el-fill-color-light)}.el-table .el-table__cell{box-sizing:border-box;text-overflow:ellipsis;vertical-align:middle;text-align:left;min-width:0;z-index:var(--el-table-index);padding:8px 0;position:relative}.el-table .el-table__cell.is-center{text-align:center}.el-table .el-table__cell.is-right{text-align:right}.el-table .el-table__cell.gutter{border-bottom-width:0;border-right-width:0;width:15px;padding:0}.el-table .el-table__cell.is-hidden>*{visibility:hidden}.el-table .cell{box-sizing:border-box;text-overflow:ellipsis;white-space:normal;overflow-wrap:break-word;padding:0 12px;line-height:23px;overflow:hidden}.el-table .cell.el-tooltip{white-space:nowrap;min-width:50px}.el-table--large{font-size:var(--el-font-size-base)}.el-table--large .el-table__cell{padding:12px 0}.el-table--large .cell{padding:0 16px}.el-table--default{font-size:var(--el-font-size-base)}.el-table--default .el-table__cell{padding:8px 0}.el-table--default .cell{padding:0 12px}.el-table--small{font-size:var(--el-font-size-extra-small)}.el-table--small .el-table__cell{padding:4px 0}.el-table--small .cell{padding:0 8px}.el-table tr{background-color:var(--el-table-tr-bg-color)}.el-table tr input[type=checkbox]{margin:0}.el-table th.el-table__cell.is-leaf,.el-table td.el-table__cell{border-bottom:var(--el-table-border)}.el-table th.el-table__cell.is-sortable{cursor:pointer}.el-table th.el-table__cell{background-color:var(--el-table-header-bg-color)}.el-table th.el-table__cell>.cell.highlight{color:var(--el-color-primary)}.el-table th.el-table__cell.required>div:before{content:"";vertical-align:middle;background:#ff4d51;border-radius:50%;width:8px;height:8px;margin-right:5px;display:inline-block}.el-table td.el-table__cell div{box-sizing:border-box}.el-table td.el-table__cell.gutter{width:0}.el-table--border:after,.el-table--border:before,.el-table--border .el-table__inner-wrapper:after,.el-table__inner-wrapper:before{content:"";background-color:var(--el-table-border-color);z-index:calc(var(--el-table-index) + 2);position:absolute}.el-table--border .el-table__inner-wrapper:after{width:100%;height:1px;z-index:calc(var(--el-table-index) + 2);top:0;left:0}.el-table--border:before{width:1px;height:100%;top:-1px;left:0}.el-table--border:after{width:1px;height:100%;top:-1px;right:0}.el-table--border .el-table__inner-wrapper{border-bottom:none;border-right:none}.el-table--border .el-table__footer-wrapper{flex-shrink:0;position:relative}.el-table--border .el-table__cell{border-right:var(--el-table-border)}.el-table--border th.el-table__cell.gutter:last-of-type{border-bottom:var(--el-table-border);border-bottom-width:1px}.el-table--border th.el-table__cell{border-bottom:var(--el-table-border)}.el-table--hidden{visibility:hidden}.el-table__header-wrapper,.el-table__body-wrapper,.el-table__footer-wrapper{width:100%}.el-table__header-wrapper tr td.el-table-fixed-column--left,.el-table__header-wrapper tr td.el-table-fixed-column--right,.el-table__header-wrapper tr th.el-table-fixed-column--left,.el-table__header-wrapper tr th.el-table-fixed-column--right,.el-table__body-wrapper tr td.el-table-fixed-column--left,.el-table__body-wrapper tr td.el-table-fixed-column--right,.el-table__body-wrapper tr th.el-table-fixed-column--left,.el-table__body-wrapper tr th.el-table-fixed-column--right,.el-table__footer-wrapper tr td.el-table-fixed-column--left,.el-table__footer-wrapper tr td.el-table-fixed-column--right,.el-table__footer-wrapper tr th.el-table-fixed-column--left,.el-table__footer-wrapper tr th.el-table-fixed-column--right{background:inherit;z-index:calc(var(--el-table-index) + 1);position:sticky!important}.el-table__header-wrapper tr td.el-table-fixed-column--left.is-last-column:before,.el-table__header-wrapper tr td.el-table-fixed-column--left.is-first-column:before,.el-table__header-wrapper tr td.el-table-fixed-column--right.is-last-column:before,.el-table__header-wrapper tr td.el-table-fixed-column--right.is-first-column:before,.el-table__header-wrapper tr th.el-table-fixed-column--left.is-last-column:before,.el-table__header-wrapper tr th.el-table-fixed-column--left.is-first-column:before,.el-table__header-wrapper tr th.el-table-fixed-column--right.is-last-column:before,.el-table__header-wrapper tr th.el-table-fixed-column--right.is-first-column:before,.el-table__body-wrapper tr td.el-table-fixed-column--left.is-last-column:before,.el-table__body-wrapper tr td.el-table-fixed-column--left.is-first-column:before,.el-table__body-wrapper tr td.el-table-fixed-column--right.is-last-column:before,.el-table__body-wrapper tr td.el-table-fixed-column--right.is-first-column:before,.el-table__body-wrapper tr th.el-table-fixed-column--left.is-last-column:before,.el-table__body-wrapper tr th.el-table-fixed-column--left.is-first-column:before,.el-table__body-wrapper tr th.el-table-fixed-column--right.is-last-column:before,.el-table__body-wrapper tr th.el-table-fixed-column--right.is-first-column:before,.el-table__footer-wrapper tr td.el-table-fixed-column--left.is-last-column:before,.el-table__footer-wrapper tr td.el-table-fixed-column--left.is-first-column:before,.el-table__footer-wrapper tr td.el-table-fixed-column--right.is-last-column:before,.el-table__footer-wrapper tr td.el-table-fixed-column--right.is-first-column:before,.el-table__footer-wrapper tr th.el-table-fixed-column--left.is-last-column:before,.el-table__footer-wrapper tr th.el-table-fixed-column--left.is-first-column:before,.el-table__footer-wrapper tr th.el-table-fixed-column--right.is-last-column:before,.el-table__footer-wrapper tr th.el-table-fixed-column--right.is-first-column:before{content:"";width:10px;box-shadow:none;touch-action:none;pointer-events:none;position:absolute;top:0;bottom:0;overflow:hidden}.el-table__header-wrapper tr td.el-table-fixed-column--left.is-first-column:before,.el-table__header-wrapper tr td.el-table-fixed-column--right.is-first-column:before,.el-table__header-wrapper tr th.el-table-fixed-column--left.is-first-column:before,.el-table__header-wrapper tr th.el-table-fixed-column--right.is-first-column:before,.el-table__body-wrapper tr td.el-table-fixed-column--left.is-first-column:before,.el-table__body-wrapper tr td.el-table-fixed-column--right.is-first-column:before,.el-table__body-wrapper tr th.el-table-fixed-column--left.is-first-column:before,.el-table__body-wrapper tr th.el-table-fixed-column--right.is-first-column:before,.el-table__footer-wrapper tr td.el-table-fixed-column--left.is-first-column:before,.el-table__footer-wrapper tr td.el-table-fixed-column--right.is-first-column:before,.el-table__footer-wrapper tr th.el-table-fixed-column--left.is-first-column:before,.el-table__footer-wrapper tr th.el-table-fixed-column--right.is-first-column:before{left:-10px}.el-table__header-wrapper tr td.el-table-fixed-column--left.is-last-column:before,.el-table__header-wrapper tr td.el-table-fixed-column--right.is-last-column:before,.el-table__header-wrapper tr th.el-table-fixed-column--left.is-last-column:before,.el-table__header-wrapper tr th.el-table-fixed-column--right.is-last-column:before,.el-table__body-wrapper tr td.el-table-fixed-column--left.is-last-column:before,.el-table__body-wrapper tr td.el-table-fixed-column--right.is-last-column:before,.el-table__body-wrapper tr th.el-table-fixed-column--left.is-last-column:before,.el-table__body-wrapper tr th.el-table-fixed-column--right.is-last-column:before,.el-table__footer-wrapper tr td.el-table-fixed-column--left.is-last-column:before,.el-table__footer-wrapper tr td.el-table-fixed-column--right.is-last-column:before,.el-table__footer-wrapper tr th.el-table-fixed-column--left.is-last-column:before,.el-table__footer-wrapper tr th.el-table-fixed-column--right.is-last-column:before{right:-10px}.el-table__header-wrapper tr td.el-table__fixed-right-patch,.el-table__header-wrapper tr th.el-table__fixed-right-patch,.el-table__body-wrapper tr td.el-table__fixed-right-patch,.el-table__body-wrapper tr th.el-table__fixed-right-patch,.el-table__footer-wrapper tr td.el-table__fixed-right-patch,.el-table__footer-wrapper tr th.el-table__fixed-right-patch{z-index:calc(var(--el-table-index) + 1);background:#fff;right:0;position:sticky!important}.el-table__header-wrapper{flex-shrink:0}.el-table__header-wrapper tr th.el-table-fixed-column--left,.el-table__header-wrapper tr th.el-table-fixed-column--right{background-color:var(--el-table-header-bg-color)}.el-table__header,.el-table__body,.el-table__footer{table-layout:fixed;border-collapse:separate}.el-table__header-wrapper{overflow:hidden}.el-table__header-wrapper tbody td.el-table__cell{background-color:var(--el-table-row-hover-bg-color);color:var(--el-table-text-color)}.el-table__footer-wrapper{flex-shrink:0;overflow:hidden}.el-table__footer-wrapper tfoot td.el-table__cell{background-color:var(--el-table-row-hover-bg-color);color:var(--el-table-text-color)}.el-table__header-wrapper .el-table-column--selection>.cell,.el-table__body-wrapper .el-table-column--selection>.cell{align-items:center;height:23px;display:inline-flex}.el-table__header-wrapper .el-table-column--selection .el-checkbox,.el-table__body-wrapper .el-table-column--selection .el-checkbox{height:unset}.el-table.is-scrolling-left .el-table-fixed-column--right.is-first-column:before{box-shadow:var(--el-table-fixed-right-column)}.el-table.is-scrolling-left.el-table--border .el-table-fixed-column--left.is-last-column.el-table__cell{border-right:var(--el-table-border)}.el-table.is-scrolling-left th.el-table-fixed-column--left{background-color:var(--el-table-header-bg-color)}.el-table.is-scrolling-right .el-table-fixed-column--left.is-last-column:before{box-shadow:var(--el-table-fixed-left-column)}.el-table.is-scrolling-right .el-table-fixed-column--left.is-last-column.el-table__cell{border-right:none}.el-table.is-scrolling-right th.el-table-fixed-column--right{background-color:var(--el-table-header-bg-color)}.el-table.is-scrolling-middle .el-table-fixed-column--left.is-last-column.el-table__cell{border-right:none}.el-table.is-scrolling-middle .el-table-fixed-column--right.is-first-column:before{box-shadow:var(--el-table-fixed-right-column)}.el-table.is-scrolling-middle .el-table-fixed-column--left.is-last-column:before{box-shadow:var(--el-table-fixed-left-column)}.el-table.is-scrolling-none .el-table-fixed-column--left.is-first-column:before,.el-table.is-scrolling-none .el-table-fixed-column--left.is-last-column:before,.el-table.is-scrolling-none .el-table-fixed-column--right.is-first-column:before,.el-table.is-scrolling-none .el-table-fixed-column--right.is-last-column:before{box-shadow:none}.el-table.is-scrolling-none th.el-table-fixed-column--left,.el-table.is-scrolling-none th.el-table-fixed-column--right{background-color:var(--el-table-header-bg-color)}.el-table__body-wrapper{flex:1;position:relative;overflow:hidden}.el-table__body-wrapper .el-scrollbar__bar{z-index:calc(var(--el-table-index) + 2)}.el-table .caret-wrapper{cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:var(--el-border-radius-base);transition:transform var(--el-transition-duration-fast) ease-in-out;background-color:#0000;border:none;outline:none;margin:0;padding:0}.el-table .caret-wrapper:focus-visible{outline:2px solid var(--el-color-primary);outline-offset:2px}.el-table .caret-wrapper{vertical-align:middle;width:24px;height:14px;overflow:initial;flex-direction:column;align-items:center;display:inline-flex;position:relative}.el-table .sort-caret{border:5px solid #0000;width:0;height:0;position:absolute;left:7px}.el-table .sort-caret.ascending{border-bottom-color:var(--el-text-color-placeholder);top:-5px}.el-table .sort-caret.descending{border-top-color:var(--el-text-color-placeholder);bottom:-3px}.el-table .ascending .sort-caret.ascending{border-bottom-color:var(--el-color-primary)}.el-table .descending .sort-caret.descending{border-top-color:var(--el-color-primary)}.el-table .hidden-columns{visibility:hidden;z-index:-1;position:absolute}.el-table--striped .el-table__body tr.el-table__row--striped td.el-table__cell{background:var(--el-fill-color-lighter)}.el-table--striped .el-table__body tr.el-table__row--striped.current-row td.el-table__cell{background-color:var(--el-table-current-row-bg-color)}.el-table__body tr.hover-row>td.el-table__cell,.el-table__body tr.hover-row.current-row>td.el-table__cell,.el-table__body tr.hover-row.el-table__row--striped>td.el-table__cell,.el-table__body tr.hover-row.el-table__row--striped.current-row>td.el-table__cell,.el-table__body tr>td.hover-cell{background-color:var(--el-table-row-hover-bg-color)}.el-table__body tr.current-row>td.el-table__cell{background-color:var(--el-table-current-row-bg-color)}.el-table.el-table--scrollable-y .el-table__body-header{z-index:calc(var(--el-table-index) + 2);position:sticky;top:0}.el-table.el-table--scrollable-y .el-table__body-footer{z-index:calc(var(--el-table-index) + 2);position:sticky;bottom:0}.el-table__column-resize-proxy{border-left:var(--el-table-border);width:0;z-index:calc(var(--el-table-index) + 9);position:absolute;top:0;bottom:0;left:200px}.el-table__column-filter-trigger{cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:var(--el-border-radius-base);transition:transform var(--el-transition-duration-fast) ease-in-out;background-color:#0000;border:none;outline:none;margin:0;padding:0}.el-table__column-filter-trigger:focus-visible{outline:2px solid var(--el-color-primary);outline-offset:2px}.el-table__column-filter-trigger{display:inline-block}.el-table__column-filter-trigger i{color:var(--el-color-info);vertical-align:middle;font-size:14px}.el-table__border-left-patch{width:1px;height:100%;z-index:calc(var(--el-table-index) + 2);background-color:var(--el-table-border-color);position:absolute;top:0;left:0}.el-table__border-bottom-patch{height:1px;z-index:calc(var(--el-table-index) + 2);background-color:var(--el-table-border-color);position:absolute;left:0}.el-table__border-right-patch{width:1px;height:100%;z-index:calc(var(--el-table-index) + 2);background-color:var(--el-table-border-color);position:absolute;top:0}.el-table--enable-row-transition .el-table__body td.el-table__cell{transition:background-color .25s 1ms}.el-table--enable-row-hover .el-table__body tr:hover>td.el-table__cell{background-color:var(--el-table-row-hover-bg-color)}.el-table [class*=el-table__row--level] .el-table__expand-icon{text-align:center;width:20px;display:inline-block}.el-table .el-table.el-table--border .el-table__cell{border-right:var(--el-table-border)}.el-table:not(.el-table--border) .el-table__cell{border-right:none}.el-table:not(.el-table--border)>.el-table__inner-wrapper:after{content:none}.el-table-v2{--el-table-border-color:var(--el-border-color-lighter);--el-table-border:1px solid var(--el-table-border-color);--el-table-text-color:var(--el-text-color-regular);--el-table-header-text-color:var(--el-text-color-secondary);--el-table-row-hover-bg-color:var(--el-fill-color-light);--el-table-current-row-bg-color:var(--el-color-primary-light-9);--el-table-header-bg-color:var(--el-fill-color-blank);--el-table-fixed-box-shadow:var(--el-box-shadow-light);--el-table-bg-color:var(--el-fill-color-blank);--el-table-tr-bg-color:var(--el-fill-color-blank);--el-table-expanded-cell-bg-color:var(--el-fill-color-blank);--el-table-fixed-left-column:inset 10px 0 10px -10px #00000026;--el-table-fixed-right-column:inset -10px 0 10px -10px #00000026;--el-table-index:var(--el-index-normal);font-size:var(--el-font-size-base)}.el-table-v2 *{box-sizing:border-box}.el-table-v2__root{position:relative}.el-table-v2__root:hover .el-table-v2__main .el-virtual-scrollbar{opacity:1}.el-table-v2__main{background-color:var(--el-bg-color);flex-direction:column-reverse;display:flex;position:absolute;top:0;left:0;overflow:hidden}.el-table-v2__main .el-vl__horizontal,.el-table-v2__main .el-vl__vertical{z-index:2}.el-table-v2__left{background-color:var(--el-bg-color);flex-direction:column-reverse;display:flex;position:absolute;top:0;left:0;overflow:hidden;box-shadow:2px 0 4px #0000000f}.el-table-v2__left .el-virtual-scrollbar{opacity:0}.el-table-v2__left .el-vl__vertical,.el-table-v2__left .el-vl__horizontal{z-index:-1}.el-table-v2__right{background-color:var(--el-bg-color);flex-direction:column-reverse;display:flex;position:absolute;top:0;right:0;overflow:hidden;box-shadow:-2px 0 4px #0000000f}.el-table-v2__right .el-virtual-scrollbar{opacity:0}.el-table-v2__right .el-vl__vertical,.el-table-v2__right .el-vl__horizontal{z-index:-1}.el-table-v2__header-row,.el-table-v2__row{padding-inline-end:var(--el-table-scrollbar-size)}.el-table-v2__header-wrapper{overflow:hidden}.el-table-v2__header{position:relative;overflow:hidden}.el-table-v2__header .el-checkbox{z-index:0}.el-table-v2__footer{position:absolute;bottom:0;left:0;right:0;overflow:hidden}.el-table-v2__empty{position:absolute;left:0}.el-table-v2__overlay{z-index:9999;position:absolute;top:0;bottom:0;left:0;right:0}.el-table-v2__header-row{border-bottom:var(--el-table-border);display:flex}.el-table-v2__header-cell{-webkit-user-select:none;user-select:none;background-color:var(--el-table-header-bg-color);height:100%;color:var(--el-table-header-text-color);align-items:center;padding:0 8px;font-weight:700;display:flex;overflow:hidden}.el-table-v2__header-cell.is-align-center{text-align:center;justify-content:center}.el-table-v2__header-cell.is-align-right{text-align:right;justify-content:flex-end}.el-table-v2__header-cell.is-sortable{cursor:pointer}.el-table-v2__header-cell:hover .el-icon{display:block}.el-table-v2__sort-icon{cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:var(--el-border-radius-base);transition:transform var(--el-transition-duration-fast) ease-in-out;background-color:#0000;border:none;outline:none;margin:0;padding:0}.el-table-v2__sort-icon:focus-visible{outline:2px solid var(--el-color-primary);outline-offset:2px}.el-table-v2__sort-icon{transition:opacity,display var(--el-transition-duration);opacity:.6;display:none}.el-table-v2__sort-icon.is-sorting{opacity:1;display:flex}.el-table-v2__row{border-bottom:var(--el-table-border);transition:background-color var(--el-transition-duration);align-items:center;display:flex}.el-table-v2__row.is-hovered,.el-table-v2__row:hover{background-color:var(--el-table-row-hover-bg-color)}.el-table-v2__row-cell{align-items:center;height:100%;padding:0 8px;display:flex;overflow:hidden}.el-table-v2__row-cell.is-align-center{text-align:center;justify-content:center}.el-table-v2__row-cell.is-align-right{text-align:right;justify-content:flex-end}.el-table-v2__expand-icon{cursor:pointer;-webkit-appearance:none;-moz-appearance:none;appearance:none;border-radius:var(--el-border-radius-base);transition:transform var(--el-transition-duration-fast) ease-in-out;background-color:#0000;border:none;outline:none;margin:0;padding:0}.el-table-v2__expand-icon:focus-visible{outline:2px solid var(--el-color-primary);outline-offset:2px}.el-table-v2__expand-icon{-webkit-user-select:none;user-select:none;margin:0 4px}.el-table-v2__expand-icon svg{transition:transform var(--el-transition-duration)}.el-table-v2__expand-icon.is-expanded svg{transform:rotate(90deg)}.el-table-v2:not(.is-dynamic) .el-table-v2__cell-text{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.el-table-v2.is-dynamic .el-table-v2__row{align-items:stretch;overflow:hidden}.el-table-v2.is-dynamic .el-table-v2__row .el-table-v2__row-cell{overflow-wrap:break-word}.el-tabs{--el-tabs-header-height:40px;display:flex}.el-tabs__header{justify-content:space-between;align-items:center;margin:0 0 15px;padding:0;display:flex;position:relative}.el-tabs__header-vertical{flex-direction:column}.el-tabs__active-bar{background-color:var(--el-color-primary);z-index:1;height:2px;transition:width var(--el-transition-duration) var(--el-transition-function-ease-in-out-bezier),transform var(--el-transition-duration) var(--el-transition-function-ease-in-out-bezier);list-style:none;position:absolute;bottom:0;left:0}.el-tabs__active-bar.is-bottom{bottom:auto}.el-tabs__new-tab{border:1px solid var(--el-border-color);text-align:center;width:20px;height:20px;color:var(--el-text-color-primary);cursor:pointer;border-radius:3px;flex-shrink:0;justify-content:center;align-items:center;margin:10px 0 10px 10px;font-size:12px;line-height:20px;transition:all .15s;display:flex}.el-tabs__new-tab .is-icon-plus{height:inherit;width:inherit;transform:scale(.8)}.el-tabs__new-tab .is-icon-plus svg{vertical-align:middle}.el-tabs__new-tab:hover{color:var(--el-color-primary)}.el-tabs__new-tab-vertical{margin-left:0}.el-tabs__nav-wrap{flex:auto;margin-bottom:-1px;position:relative;overflow:hidden}.el-tabs__nav-wrap:after{content:"";background-color:var(--el-border-color-light);width:100%;height:2px;z-index:var(--el-index-normal);position:absolute;bottom:0;left:0}.el-tabs__nav-wrap.is-bottom:after{top:0;bottom:auto}.el-tabs__nav-wrap.is-scrollable{box-sizing:border-box;padding:0 20px}.el-tabs__nav-scroll{overflow:hidden}.el-tabs__nav-next,.el-tabs__nav-prev{cursor:pointer;color:var(--el-text-color-secondary);text-align:center;width:20px;font-size:12px;line-height:44px;position:absolute}.el-tabs__nav-next.is-disabled,.el-tabs__nav-prev.is-disabled{color:var(--el-text-color-disabled);cursor:not-allowed}.el-tabs__nav-next{right:0}.el-tabs__nav-prev{left:0}.el-tabs__nav{white-space:nowrap;transition:transform var(--el-transition-duration);float:left;z-index:calc(var(--el-index-normal) + 1);display:flex;position:relative}.el-tabs__nav.is-stretch{min-width:100%;display:flex}.el-tabs__nav.is-stretch>*{text-align:center;flex:1}.el-tabs__item{height:var(--el-tabs-header-height);box-sizing:border-box;font-size:var(--el-font-size-base);color:var(--el-text-color-primary);justify-content:center;align-items:center;padding:0 20px;font-weight:500;list-style:none;display:flex;position:relative}.el-tabs__item:focus,.el-tabs__item:focus:active{outline:none}.el-tabs__item:focus-visible{box-shadow:0 0 2px 2px var(--el-color-primary) inset;border-radius:3px}.el-tabs__item .is-icon-close{text-align:center;transition:all var(--el-transition-duration) var(--el-transition-function-ease-in-out-bezier);border-radius:50%;margin-left:5px}.el-tabs__item .is-icon-close:before{display:inline-block;transform:scale(.9)}.el-tabs__item .is-icon-close:hover{background-color:var(--el-text-color-placeholder);color:#fff}.el-tabs__item.is-active{color:var(--el-color-primary)}.el-tabs__item:hover{color:var(--el-color-primary);cursor:pointer}.el-tabs__item.is-disabled{color:var(--el-disabled-text-color);cursor:not-allowed}.el-tabs__content{flex-grow:1;position:relative;overflow:hidden}.el-tabs--top>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--bottom>.el-tabs__header .el-tabs__item:nth-child(2){padding-left:0}.el-tabs--top>.el-tabs__header .el-tabs__item:last-child,.el-tabs--bottom>.el-tabs__header .el-tabs__item:last-child{padding-right:0}.el-tabs--top.el-tabs--border-card>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--top.el-tabs--card>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--bottom.el-tabs--border-card>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--bottom.el-tabs--card>.el-tabs__header .el-tabs__item:nth-child(2){padding-left:20px}.el-tabs--top.el-tabs--border-card>.el-tabs__header .el-tabs__item:last-child,.el-tabs--top.el-tabs--card>.el-tabs__header .el-tabs__item:last-child,.el-tabs--bottom.el-tabs--border-card>.el-tabs__header .el-tabs__item:last-child,.el-tabs--bottom.el-tabs--card>.el-tabs__header .el-tabs__item:last-child{padding-right:20px}.el-tabs--card>.el-tabs__header{border-bottom:1px solid var(--el-border-color-light);height:var(--el-tabs-header-height);box-sizing:border-box}.el-tabs--card>.el-tabs__header .el-tabs__nav-wrap:after{content:none}.el-tabs--card>.el-tabs__header .el-tabs__nav{border:1px solid var(--el-border-color-light);box-sizing:border-box;border-bottom:none;border-radius:4px 4px 0 0}.el-tabs--card>.el-tabs__header .el-tabs__active-bar{display:none}.el-tabs--card>.el-tabs__header .el-tabs__item .is-icon-close{transform-origin:100%;width:0;height:14px;font-size:12px;position:relative;right:-2px;overflow:hidden}.el-tabs--card>.el-tabs__header .el-tabs__item{border-bottom:1px solid #0000;border-left:1px solid var(--el-border-color-light);transition:color var(--el-transition-duration) var(--el-transition-function-ease-in-out-bezier),padding var(--el-transition-duration) var(--el-transition-function-ease-in-out-bezier);margin-top:-1px}.el-tabs--card>.el-tabs__header .el-tabs__item:first-child{border-left:none}.el-tabs--card>.el-tabs__header .el-tabs__item.is-closable:hover{padding-left:13px;padding-right:13px}.el-tabs--card>.el-tabs__header .el-tabs__item.is-closable:hover .is-icon-close{width:14px}.el-tabs--card>.el-tabs__header .el-tabs__item.is-active{border-bottom-color:var(--el-bg-color)}.el-tabs--card>.el-tabs__header .el-tabs__item.is-active.is-closable{padding-left:20px;padding-right:20px}.el-tabs--card>.el-tabs__header .el-tabs__item.is-active.is-closable .is-icon-close{width:14px}.el-tabs--border-card{background:var(--el-bg-color-overlay);border:1px solid var(--el-border-color)}.el-tabs--border-card>.el-tabs__content{padding:15px}.el-tabs--border-card>.el-tabs__header{background-color:var(--el-fill-color-light);border-bottom:1px solid var(--el-border-color-light);margin:0}.el-tabs--border-card>.el-tabs__header .el-tabs__nav-wrap:after{content:none}.el-tabs--border-card>.el-tabs__header .el-tabs__item{transition:all var(--el-transition-duration) var(--el-transition-function-ease-in-out-bezier);color:var(--el-text-color-secondary);border:1px solid #0000;margin-top:-1px}.el-tabs--border-card>.el-tabs__header .el-tabs__item:first-child,.el-tabs--border-card>.el-tabs__header .el-tabs__item+.el-tabs__item{margin-left:-1px}.el-tabs--border-card>.el-tabs__header .el-tabs__item.is-active{color:var(--el-color-primary);background-color:var(--el-bg-color-overlay);border-right-color:var(--el-border-color);border-left-color:var(--el-border-color)}.el-tabs--border-card>.el-tabs__header .el-tabs__item:not(.is-disabled):hover{color:var(--el-color-primary)}.el-tabs--border-card>.el-tabs__header .el-tabs__item.is-disabled{color:var(--el-disabled-text-color)}.el-tabs--border-card>.el-tabs__header .is-scrollable .el-tabs__item:first-child{margin-left:0}.el-tabs--bottom{flex-direction:column}.el-tabs--bottom .el-tabs__header.is-bottom{margin-top:10px;margin-bottom:0}.el-tabs--bottom.el-tabs--border-card .el-tabs__header.is-bottom{border-bottom:0;border-top:1px solid var(--el-border-color)}.el-tabs--bottom.el-tabs--border-card .el-tabs__nav-wrap.is-bottom{margin-top:-1px;margin-bottom:0}.el-tabs--bottom.el-tabs--border-card .el-tabs__item.is-bottom:not(.is-active){border:1px solid #0000}.el-tabs--bottom.el-tabs--border-card .el-tabs__item.is-bottom{margin:0 -1px -1px}.el-tabs--card>.el-tabs__header.is-left,.el-tabs--card>.el-tabs__header.is-right,.el-tabs--border-card>.el-tabs__header.is-left,.el-tabs--border-card>.el-tabs__header.is-right{border-bottom:none}.el-tabs--left,.el-tabs--right{overflow:hidden}.el-tabs--left .el-tabs__header.is-left,.el-tabs--left .el-tabs__header.is-right,.el-tabs--left .el-tabs__nav-wrap.is-left,.el-tabs--left .el-tabs__nav-wrap.is-right,.el-tabs--left .el-tabs__nav-scroll,.el-tabs--right .el-tabs__header.is-left,.el-tabs--right .el-tabs__header.is-right,.el-tabs--right .el-tabs__nav-wrap.is-left,.el-tabs--right .el-tabs__nav-wrap.is-right,.el-tabs--right .el-tabs__nav-scroll{height:100%}.el-tabs--left .el-tabs__active-bar.is-left,.el-tabs--left .el-tabs__active-bar.is-right,.el-tabs--right .el-tabs__active-bar.is-left,.el-tabs--right .el-tabs__active-bar.is-right{width:2px;height:auto;top:0;bottom:auto}.el-tabs--left .el-tabs__nav-wrap.is-left,.el-tabs--left .el-tabs__nav-wrap.is-right,.el-tabs--right .el-tabs__nav-wrap.is-left,.el-tabs--right .el-tabs__nav-wrap.is-right{margin-bottom:0}.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev,.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-next,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-next,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-next,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-next{text-align:center;cursor:pointer;width:100%;height:30px;line-height:30px}.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev i,.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-next i,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev i,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-next i,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev i,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-next i,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev i,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-next i{transform:rotate(90deg)}.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev.is-disabled,.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-next.is-disabled,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev.is-disabled,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-next.is-disabled,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev.is-disabled,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-next.is-disabled,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev.is-disabled,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-next.is-disabled{cursor:not-allowed}.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev{top:0;left:auto}.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-next,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-next,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-next,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-next{bottom:0;right:auto}.el-tabs--left .el-tabs__nav-wrap.is-left.is-scrollable,.el-tabs--left .el-tabs__nav-wrap.is-right.is-scrollable,.el-tabs--right .el-tabs__nav-wrap.is-left.is-scrollable,.el-tabs--right .el-tabs__nav-wrap.is-right.is-scrollable{padding:30px 0}.el-tabs--left .el-tabs__nav-wrap.is-left:after,.el-tabs--left .el-tabs__nav-wrap.is-right:after,.el-tabs--right .el-tabs__nav-wrap.is-left:after,.el-tabs--right .el-tabs__nav-wrap.is-right:after{width:2px;height:100%;top:0;bottom:auto}.el-tabs--left .el-tabs__nav.is-left,.el-tabs--left .el-tabs__nav.is-right,.el-tabs--right .el-tabs__nav.is-left,.el-tabs--right .el-tabs__nav.is-right{flex-direction:column}.el-tabs--left .el-tabs__item.is-left,.el-tabs--right .el-tabs__item.is-left{justify-content:flex-end}.el-tabs--left .el-tabs__item.is-right,.el-tabs--right .el-tabs__item.is-right{justify-content:flex-start}.el-tabs--left{flex-direction:row}.el-tabs--left .el-tabs__header.is-left{margin-bottom:0;margin-right:10px}.el-tabs--left .el-tabs__nav-wrap.is-left{margin-right:-1px}.el-tabs--left .el-tabs__nav-wrap.is-left:after,.el-tabs--left .el-tabs__active-bar.is-left{left:auto;right:0}.el-tabs--left .el-tabs__item.is-left{text-align:right}.el-tabs--left.el-tabs--card .el-tabs__active-bar.is-left{display:none}.el-tabs--left.el-tabs--card .el-tabs__item.is-left{border-left:none;border-right:1px solid var(--el-border-color-light);border-bottom:none;border-top:1px solid var(--el-border-color-light);text-align:left}.el-tabs--left.el-tabs--card .el-tabs__item.is-left:first-child{border-right:1px solid var(--el-border-color-light);border-top:none}.el-tabs--left.el-tabs--card .el-tabs__item.is-left.is-active{border:1px solid var(--el-border-color-light);border-bottom:none;border-left:none;border-right-color:#fff}.el-tabs--left.el-tabs--card .el-tabs__item.is-left.is-active:first-child{border-top:none}.el-tabs--left.el-tabs--card .el-tabs__item.is-left.is-active:last-child{border-bottom:none}.el-tabs--left.el-tabs--card .el-tabs__nav{border-bottom:1px solid var(--el-border-color-light);border-right:none;border-radius:4px 0 0 4px}.el-tabs--left.el-tabs--card .el-tabs__new-tab{float:none}.el-tabs--left.el-tabs--border-card .el-tabs__header.is-left{border-right:1px solid var(--el-border-color)}.el-tabs--left.el-tabs--border-card .el-tabs__item.is-left{border:1px solid #0000;margin:-1px 0 -1px -1px}.el-tabs--left.el-tabs--border-card .el-tabs__item.is-left.is-active{border-color:#d1dbe5 #0000}.el-tabs--left>.el-tabs__content+.el-tabs__header{order:-1}.el-tabs--right .el-tabs__header.is-right{margin-bottom:0;margin-left:10px}.el-tabs--right .el-tabs__nav-wrap.is-right{margin-left:-1px}.el-tabs--right .el-tabs__nav-wrap.is-right:after{left:0;right:auto}.el-tabs--right .el-tabs__active-bar.is-right{left:0}.el-tabs--right.el-tabs--card .el-tabs__active-bar.is-right{display:none}.el-tabs--right.el-tabs--card .el-tabs__item.is-right{border-bottom:none;border-top:1px solid var(--el-border-color-light)}.el-tabs--right.el-tabs--card .el-tabs__item.is-right:first-child{border-left:1px solid var(--el-border-color-light);border-top:none}.el-tabs--right.el-tabs--card .el-tabs__item.is-right.is-active{border:1px solid var(--el-border-color-light);border-bottom:none;border-left-color:#fff;border-right:none}.el-tabs--right.el-tabs--card .el-tabs__item.is-right.is-active:first-child{border-top:none}.el-tabs--right.el-tabs--card .el-tabs__item.is-right.is-active:last-child{border-bottom:none}.el-tabs--right.el-tabs--card .el-tabs__nav{border-bottom:1px solid var(--el-border-color-light);border-left:none;border-radius:0 4px 4px 0}.el-tabs--right.el-tabs--border-card .el-tabs__header.is-right{border-left:1px solid var(--el-border-color)}.el-tabs--right.el-tabs--border-card .el-tabs__item.is-right{border:1px solid #0000;margin:-1px -1px -1px 0}.el-tabs--right.el-tabs--border-card .el-tabs__item.is-right.is-active{border-color:#d1dbe5 #0000}.el-tabs--top{flex-direction:column}.el-tabs--top>.el-tabs__content+.el-tabs__header{order:-1}.slideInRight-transition,.slideInLeft-transition{display:inline-block}.slideInRight-enter{animation:slideInRight-enter var(--el-transition-duration)}.slideInRight-leave{animation:slideInRight-leave var(--el-transition-duration);position:absolute;left:0;right:0}.slideInLeft-enter{animation:slideInLeft-enter var(--el-transition-duration)}.slideInLeft-leave{animation:slideInLeft-leave var(--el-transition-duration);position:absolute;left:0;right:0}@keyframes slideInRight-enter{0%{opacity:0;transform-origin:0 0;transform:translate(100%)}to{opacity:1;transform-origin:0 0;transform:translate(0)}}@keyframes slideInRight-leave{0%{transform-origin:0 0;opacity:1;transform:translate(0)}to{transform-origin:0 0;opacity:0;transform:translate(100%)}}@keyframes slideInLeft-enter{0%{opacity:0;transform-origin:0 0;transform:translate(-100%)}to{opacity:1;transform-origin:0 0;transform:translate(0)}}@keyframes slideInLeft-leave{0%{transform-origin:0 0;opacity:1;transform:translate(0)}to{transform-origin:0 0;opacity:0;transform:translate(-100%)}}.el-tag{--el-tag-font-size:12px;--el-tag-border-radius:4px;--el-tag-border-radius-rounded:9999px;background-color:var(--el-tag-bg-color);border-color:var(--el-tag-border-color);color:var(--el-tag-text-color);vertical-align:middle;height:24px;font-size:var(--el-tag-font-size);border-radius:var(--el-tag-border-radius);box-sizing:border-box;white-space:nowrap;--el-icon-size:14px;--el-tag-bg-color:var(--el-color-primary-light-9);--el-tag-border-color:var(--el-color-primary-light-8);--el-tag-hover-color:var(--el-color-primary);border-style:solid;border-width:1px;justify-content:center;align-items:center;padding:0 9px;line-height:1;display:inline-flex}.el-tag.el-tag--primary{--el-tag-bg-color:var(--el-color-primary-light-9);--el-tag-border-color:var(--el-color-primary-light-8);--el-tag-hover-color:var(--el-color-primary)}.el-tag.el-tag--success{--el-tag-bg-color:var(--el-color-success-light-9);--el-tag-border-color:var(--el-color-success-light-8);--el-tag-hover-color:var(--el-color-success)}.el-tag.el-tag--warning{--el-tag-bg-color:var(--el-color-warning-light-9);--el-tag-border-color:var(--el-color-warning-light-8);--el-tag-hover-color:var(--el-color-warning)}.el-tag.el-tag--danger{--el-tag-bg-color:var(--el-color-danger-light-9);--el-tag-border-color:var(--el-color-danger-light-8);--el-tag-hover-color:var(--el-color-danger)}.el-tag.el-tag--error{--el-tag-bg-color:var(--el-color-error-light-9);--el-tag-border-color:var(--el-color-error-light-8);--el-tag-hover-color:var(--el-color-error)}.el-tag.el-tag--info{--el-tag-bg-color:var(--el-color-info-light-9);--el-tag-border-color:var(--el-color-info-light-8);--el-tag-hover-color:var(--el-color-info)}.el-tag.is-hit{border-color:var(--el-color-primary)}.el-tag.is-round{border-radius:var(--el-tag-border-radius-rounded)}.el-tag .el-tag__close{color:var(--el-tag-text-color);flex-shrink:0}.el-tag .el-tag__close:hover{color:var(--el-color-white);background-color:var(--el-tag-hover-color)}.el-tag.el-tag--primary{--el-tag-text-color:var(--el-color-primary)}.el-tag.el-tag--success{--el-tag-text-color:var(--el-color-success)}.el-tag.el-tag--warning{--el-tag-text-color:var(--el-color-warning)}.el-tag.el-tag--danger{--el-tag-text-color:var(--el-color-danger)}.el-tag.el-tag--error{--el-tag-text-color:var(--el-color-error)}.el-tag.el-tag--info{--el-tag-text-color:var(--el-color-info)}.el-tag .el-icon{cursor:pointer;font-size:calc(var(--el-icon-size) - 2px);height:var(--el-icon-size);width:var(--el-icon-size);border-radius:50%}.el-tag .el-tag__close{background-color:#0000;border:none;border-radius:50%;outline:none;margin-left:6px;padding:0;overflow:hidden}.el-tag .el-tag__close:focus-visible{outline:2px solid var(--el-color-primary);outline-offset:2px}.el-tag .el-tag__close .el-icon{display:flex}.el-tag--dark{--el-tag-text-color:var(--el-color-white);--el-tag-bg-color:var(--el-color-primary);--el-tag-border-color:var(--el-color-primary);--el-tag-hover-color:var(--el-color-primary-light-3)}.el-tag--dark.el-tag--primary{--el-tag-bg-color:var(--el-color-primary);--el-tag-border-color:var(--el-color-primary);--el-tag-hover-color:var(--el-color-primary-light-3)}.el-tag--dark.el-tag--success{--el-tag-bg-color:var(--el-color-success);--el-tag-border-color:var(--el-color-success);--el-tag-hover-color:var(--el-color-success-light-3)}.el-tag--dark.el-tag--warning{--el-tag-bg-color:var(--el-color-warning);--el-tag-border-color:var(--el-color-warning);--el-tag-hover-color:var(--el-color-warning-light-3)}.el-tag--dark.el-tag--danger{--el-tag-bg-color:var(--el-color-danger);--el-tag-border-color:var(--el-color-danger);--el-tag-hover-color:var(--el-color-danger-light-3)}.el-tag--dark.el-tag--error{--el-tag-bg-color:var(--el-color-error);--el-tag-border-color:var(--el-color-error);--el-tag-hover-color:var(--el-color-error-light-3)}.el-tag--dark.el-tag--info{--el-tag-bg-color:var(--el-color-info);--el-tag-border-color:var(--el-color-info);--el-tag-hover-color:var(--el-color-info-light-3)}.el-tag--dark.el-tag--primary,.el-tag--dark.el-tag--success,.el-tag--dark.el-tag--warning,.el-tag--dark.el-tag--danger,.el-tag--dark.el-tag--error,.el-tag--dark.el-tag--info{--el-tag-text-color:var(--el-color-white)}.el-tag--plain,.el-tag--plain.el-tag--primary{--el-tag-bg-color:var(--el-fill-color-blank);--el-tag-border-color:var(--el-color-primary-light-5);--el-tag-hover-color:var(--el-color-primary)}.el-tag--plain.el-tag--success{--el-tag-bg-color:var(--el-fill-color-blank);--el-tag-border-color:var(--el-color-success-light-5);--el-tag-hover-color:var(--el-color-success)}.el-tag--plain.el-tag--warning{--el-tag-bg-color:var(--el-fill-color-blank);--el-tag-border-color:var(--el-color-warning-light-5);--el-tag-hover-color:var(--el-color-warning)}.el-tag--plain.el-tag--danger{--el-tag-bg-color:var(--el-fill-color-blank);--el-tag-border-color:var(--el-color-danger-light-5);--el-tag-hover-color:var(--el-color-danger)}.el-tag--plain.el-tag--error{--el-tag-bg-color:var(--el-fill-color-blank);--el-tag-border-color:var(--el-color-error-light-5);--el-tag-hover-color:var(--el-color-error)}.el-tag--plain.el-tag--info{--el-tag-bg-color:var(--el-fill-color-blank);--el-tag-border-color:var(--el-color-info-light-5);--el-tag-hover-color:var(--el-color-info)}.el-tag.is-closable{padding-right:5px}.el-tag--large{--el-icon-size:16px;height:32px;padding:0 11px}.el-tag--large .el-tag__close{margin-left:8px}.el-tag--large.is-closable{padding-right:7px}.el-tag--small{--el-icon-size:12px;height:20px;padding:0 7px}.el-tag--small .el-tag__close{margin-left:4px}.el-tag--small.is-closable{padding-right:3px}.el-tag--small .el-icon-close{transform:scale(.8)}.el-tag.el-tag--primary.is-hit{border-color:var(--el-color-primary)}.el-tag.el-tag--success.is-hit{border-color:var(--el-color-success)}.el-tag.el-tag--warning.is-hit{border-color:var(--el-color-warning)}.el-tag.el-tag--danger.is-hit{border-color:var(--el-color-danger)}.el-tag.el-tag--error.is-hit{border-color:var(--el-color-error)}.el-tag.el-tag--info.is-hit{border-color:var(--el-color-info)}.el-text{--el-text-font-size:var(--el-font-size-base);--el-text-color:var(--el-text-color-regular);font-size:var(--el-text-font-size);color:var(--el-text-color);overflow-wrap:break-word;align-self:center;margin:0;padding:0}.el-text.is-truncated{text-overflow:ellipsis;white-space:nowrap;max-width:100%;display:inline-block;overflow:hidden}.el-text.is-line-clamp{-webkit-box-orient:vertical;display:-webkit-inline-box;overflow:hidden}.el-text--large{--el-text-font-size:var(--el-font-size-medium)}.el-text--default{--el-text-font-size:var(--el-font-size-base)}.el-text--small{--el-text-font-size:var(--el-font-size-extra-small)}.el-text.el-text--primary{--el-text-color:var(--el-color-primary)}.el-text.el-text--success{--el-text-color:var(--el-color-success)}.el-text.el-text--warning{--el-text-color:var(--el-color-warning)}.el-text.el-text--danger{--el-text-color:var(--el-color-danger)}.el-text.el-text--error{--el-text-color:var(--el-color-error)}.el-text.el-text--info{--el-text-color:var(--el-color-info)}.el-text>.el-icon{vertical-align:-2px}.time-select{min-width:0;margin:5px 0}.time-select .el-picker-panel__content{max-height:200px;margin:0}.time-select-item{padding:8px 10px;font-size:14px;line-height:20px}.time-select-item.disabled{color:var(--el-datepicker-border-color);cursor:not-allowed}.time-select-item:hover{background-color:var(--el-fill-color-light);cursor:pointer;font-weight:700}.time-select .time-select-item.selected:not(.disabled){color:var(--el-color-primary);font-weight:700}.el-timeline-item{padding-bottom:20px;position:relative}.el-timeline-item__wrapper{box-sizing:content-box;position:relative;top:-3px}.el-timeline-item__tail{border-left:2px solid var(--el-timeline-node-color);height:100%;position:absolute}.el-timeline-item .el-timeline-item__icon{color:var(--el-color-white);font-size:var(--el-font-size-small)}.el-timeline-item__node{background-color:var(--el-timeline-node-color);border-color:var(--el-timeline-node-color);box-sizing:border-box;border-radius:50%;justify-content:center;align-items:center;display:flex;position:absolute}.el-timeline-item__node--normal{width:var(--el-timeline-node-size-normal);height:var(--el-timeline-node-size-normal)}.el-timeline-item__node--large{width:var(--el-timeline-node-size-large);height:var(--el-timeline-node-size-large)}.el-timeline-item__node.is-hollow{background:var(--el-color-white);border-style:solid;border-width:2px}.el-timeline-item__node--primary{background-color:var(--el-color-primary);border-color:var(--el-color-primary)}.el-timeline-item__node--success{background-color:var(--el-color-success);border-color:var(--el-color-success)}.el-timeline-item__node--warning{background-color:var(--el-color-warning);border-color:var(--el-color-warning)}.el-timeline-item__node--danger{background-color:var(--el-color-danger);border-color:var(--el-color-danger)}.el-timeline-item__node--info{background-color:var(--el-color-info);border-color:var(--el-color-info)}.el-timeline-item__dot{justify-content:center;align-items:center;display:flex;position:absolute}.el-timeline-item__content{color:var(--el-text-color-primary)}.el-timeline-item__timestamp{color:var(--el-text-color-secondary);line-height:1;font-size:var(--el-font-size-small)}.el-timeline-item__timestamp.is-top{margin-bottom:8px;padding-top:4px}.el-timeline-item__timestamp.is-bottom{margin-top:8px}.el-timeline-item.is-start .el-timeline-item__wrapper{padding-left:28px}.el-timeline-item.is-start .el-timeline-item__tail{left:4px}.el-timeline-item.is-start .el-timeline-item__node--normal{left:-1px}.el-timeline-item.is-start .el-timeline-item__node--large{left:-2px}.el-timeline-item.is-end .el-timeline-item__wrapper{text-align:right;padding-right:28px}.el-timeline-item.is-end .el-timeline-item__tail{right:4px}.el-timeline-item.is-end .el-timeline-item__node--normal{right:-1px}.el-timeline-item.is-end .el-timeline-item__node--large{right:-2px}.el-timeline-item.is-alternate .el-timeline-item__tail,.el-timeline-item.is-alternate .el-timeline-item__node,.el-timeline-item.is-alternate-reverse .el-timeline-item__tail,.el-timeline-item.is-alternate-reverse .el-timeline-item__node{left:50%;transform:translate(-50%)}.el-timeline{--el-timeline-node-size-normal:12px;--el-timeline-node-size-large:14px;--el-timeline-node-color:var(--el-border-color-light);font-size:var(--el-font-size-base);margin:0;list-style:none}.el-timeline .el-timeline-item:last-child .el-timeline-item__tail{display:none}.el-timeline .el-timeline-item__center{align-items:center;display:flex}.el-timeline .el-timeline-item__center .el-timeline-item__wrapper{width:100%}.el-timeline .el-timeline-item__center .el-timeline-item__tail{top:0}.el-timeline .el-timeline-item__center:first-child .el-timeline-item__tail{height:calc(50% + 10px);top:calc(50% - 10px)}.el-timeline .el-timeline-item__center:last-child .el-timeline-item__tail{height:calc(50% - 10px);display:block}.el-timeline.is-start{padding-left:40px;padding-right:0}.el-timeline.is-end{padding-left:0;padding-right:40px}.el-timeline.is-alternate{padding-left:20px;padding-right:20px}.el-timeline.is-alternate .el-timeline-item:nth-child(odd) .el-timeline-item__wrapper{width:calc(50% - 28px);left:calc(50% - var(--el-timeline-node-size-large) / 2);padding-left:28px}.el-timeline.is-alternate .el-timeline-item:nth-child(2n) .el-timeline-item__wrapper{width:calc(50% - 28px + var(--el-timeline-node-size-large) / 2);text-align:right;padding-right:28px}.el-timeline.is-alternate-reverse{padding-left:20px;padding-right:20px}.el-timeline.is-alternate-reverse .el-timeline-item:nth-child(odd) .el-timeline-item__wrapper{width:calc(50% - 28px + var(--el-timeline-node-size-large) / 2);text-align:right;padding-right:28px}.el-timeline.is-alternate-reverse .el-timeline-item:nth-child(2n) .el-timeline-item__wrapper{width:calc(50% - 28px);left:calc(50% - var(--el-timeline-node-size-large) / 2);padding-left:28px}.el-transfer{--el-transfer-border-color:var(--el-border-color-lighter);--el-transfer-border-radius:var(--el-border-radius-base);--el-transfer-panel-width:200px;--el-transfer-panel-header-height:40px;--el-transfer-panel-header-bg-color:var(--el-fill-color-light);--el-transfer-panel-footer-height:40px;--el-transfer-panel-body-height:278px;--el-transfer-item-height:30px;--el-transfer-filter-height:32px;font-size:var(--el-font-size-base)}.el-transfer__buttons{vertical-align:middle;padding:0 30px;display:inline-block}.el-transfer__button{vertical-align:top}.el-transfer__button:nth-child(2){margin:0 0 0 10px}.el-transfer__button i,.el-transfer__button span{font-size:14px}.el-transfer__button .el-icon+span{margin-left:0}.el-transfer-panel{background:var(--el-bg-color-overlay);text-align:left;vertical-align:middle;width:var(--el-transfer-panel-width);box-sizing:border-box;max-height:100%;display:inline-block;position:relative;overflow:hidden}.el-transfer-panel__body{height:var(--el-transfer-panel-body-height);border-left:1px solid var(--el-transfer-border-color);border-right:1px solid var(--el-transfer-border-color);border-bottom:1px solid var(--el-transfer-border-color);border-bottom-left-radius:var(--el-transfer-border-radius);border-bottom-right-radius:var(--el-transfer-border-radius);overflow:hidden}.el-transfer-panel__body.is-with-footer{border-bottom:none;border-bottom-right-radius:0;border-bottom-left-radius:0}.el-transfer-panel__list{height:var(--el-transfer-panel-body-height);box-sizing:border-box;margin:0;padding:6px 0;list-style:none;overflow:auto}.el-transfer-panel__list.is-filterable{height:calc(100% - var(--el-transfer-filter-height) - 30px);padding-top:0}.el-transfer-panel__item{height:var(--el-transfer-item-height);line-height:var(--el-transfer-item-height);padding-left:15px;display:block!important}.el-transfer-panel__item+.el-transfer-panel__item{margin-left:0}.el-transfer-panel__item.el-checkbox{color:var(--el-text-color-regular);margin-right:30px}.el-transfer-panel__item:hover{color:var(--el-color-primary)}.el-transfer-panel__item.el-checkbox .el-checkbox__label{text-overflow:ellipsis;white-space:nowrap;box-sizing:border-box;width:100%;line-height:var(--el-transfer-item-height);padding-left:22px;display:block;overflow:hidden}.el-transfer-panel__item .el-checkbox__input{position:absolute;top:8px}.el-transfer-panel__filter{text-align:center;box-sizing:border-box;padding:15px}.el-transfer-panel__filter .el-input__inner{height:var(--el-transfer-filter-height);box-sizing:border-box;width:100%;font-size:12px;display:inline-block}.el-transfer-panel__filter .el-icon-circle-close{cursor:pointer}.el-transfer-panel .el-transfer-panel__header{height:var(--el-transfer-panel-header-height);background:var(--el-transfer-panel-header-bg-color);border:1px solid var(--el-transfer-border-color);border-top-left-radius:var(--el-transfer-border-radius);border-top-right-radius:var(--el-transfer-border-radius);box-sizing:border-box;color:var(--el-color-black);align-items:center;margin:0;padding-left:15px;display:flex}.el-transfer-panel .el-transfer-panel__header .el-checkbox{align-items:center;width:100%;display:flex;position:relative}.el-transfer-panel .el-transfer-panel__header .el-checkbox .el-checkbox__label{min-width:0;color:var(--el-text-color-primary);flex:1;align-items:center;font-size:16px;font-weight:400;display:flex}.el-transfer-panel .el-transfer-panel__header-title{text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0;overflow:hidden}.el-transfer-panel .el-transfer-panel__header-count{color:var(--el-text-color-secondary);flex-shrink:0;margin-left:8px;margin-right:15px;font-size:12px}.el-transfer-panel .el-transfer-panel__footer{height:var(--el-transfer-panel-footer-height);background:var(--el-bg-color-overlay);border:1px solid var(--el-transfer-border-color);border-bottom-left-radius:var(--el-transfer-border-radius);border-bottom-right-radius:var(--el-transfer-border-radius);margin:0;padding:0}.el-transfer-panel .el-transfer-panel__footer:after{content:"";vertical-align:middle;height:100%;display:inline-block}.el-transfer-panel .el-transfer-panel__footer .el-checkbox{color:var(--el-text-color-regular);padding-left:20px}.el-transfer-panel .el-transfer-panel__empty{height:var(--el-transfer-item-height);line-height:var(--el-transfer-item-height);color:var(--el-text-color-secondary);text-align:center;margin:0;padding:6px 15px 0}.el-transfer-panel .el-checkbox__label{padding-left:8px}.el-tree{--el-tree-node-content-height:26px;--el-tree-node-hover-bg-color:var(--el-fill-color-light);--el-tree-text-color:var(--el-text-color-regular);--el-tree-expand-icon-color:var(--el-text-color-placeholder);cursor:default;background:var(--el-fill-color-blank);color:var(--el-tree-text-color);font-size:var(--el-font-size-base);position:relative}.el-tree__empty-block{text-align:center;width:100%;height:100%;min-height:60px;position:relative}.el-tree__empty-text{color:var(--el-text-color-secondary);font-size:var(--el-font-size-base);position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.el-tree__drop-indicator{background-color:var(--el-color-primary);height:1px;position:absolute;left:0;right:0}.el-tree-node{white-space:nowrap;outline:none}.el-tree-node:focus>.el-tree-node__content{background-color:var(--el-tree-node-hover-bg-color)}.el-tree-node.is-drop-inner>.el-tree-node__content .el-tree-node__label{background-color:var(--el-color-primary);color:#fff}.el-tree-node__content{--el-checkbox-height:var(--el-tree-node-content-height);height:var(--el-tree-node-content-height);cursor:pointer;align-items:center;display:flex}.el-tree-node__content>.el-tree-node__expand-icon{box-sizing:content-box;padding:6px}.el-tree-node__content>label.el-checkbox{margin-right:8px}.el-tree-node__content:hover{background-color:var(--el-tree-node-hover-bg-color)}.el-tree.is-dragging .el-tree-node__content{cursor:move}.el-tree.is-dragging .el-tree-node__content *{pointer-events:none}.el-tree.is-dragging.is-drop-not-allow .el-tree-node__content{cursor:not-allowed}.el-tree-node__expand-icon{cursor:pointer;color:var(--el-tree-expand-icon-color);transition:transform var(--el-transition-duration) ease-in-out;font-size:12px;transform:rotate(0)}.el-tree-node__expand-icon.expanded{transform:rotate(90deg)}.el-tree-node__expand-icon.is-leaf{color:#0000;cursor:default;visibility:hidden}.el-tree-node__expand-icon.is-hidden{visibility:hidden}.el-tree-node__loading-icon{font-size:var(--el-font-size-base);color:var(--el-tree-expand-icon-color);margin-right:8px}.el-tree-node>.el-tree-node__children{background-color:#0000;overflow:hidden}.el-tree-node.is-expanded>.el-tree-node__children{display:block}.el-tree--highlight-current .el-tree-node.is-current>.el-tree-node__content{background-color:var(--el-color-primary-light-9)}.el-tree-select{--el-tree-node-content-height:26px;--el-tree-node-hover-bg-color:var(--el-fill-color-light);--el-tree-text-color:var(--el-text-color-regular);--el-tree-expand-icon-color:var(--el-text-color-placeholder)}.el-tree-select__popper .el-tree-node__expand-icon{margin-left:8px}.el-tree-select__popper .el-tree-node.is-checked>.el-tree-node__content .el-select-dropdown__item.selected:after{content:none}.el-tree-select__popper .el-select-dropdown__list>.el-select-dropdown__item{padding-left:32px}.el-tree-select__popper .el-select-dropdown__item{flex:1;height:20px;padding-left:0;line-height:20px;background:0 0!important}.el-upload{--el-upload-dragger-padding-horizontal:10px;--el-upload-dragger-padding-vertical:40px;--el-upload-list-picture-card-size:var(--el-upload-picture-card-size);--el-upload-picture-card-size:148px;cursor:pointer;outline:none;justify-content:center;align-items:center;display:inline-flex}.el-upload.is-disabled{cursor:not-allowed}.el-upload.is-disabled:focus{border-color:var(--el-border-color-darker);color:inherit}.el-upload.is-disabled:focus .el-upload-dragger{border-color:var(--el-border-color-darker)}.el-upload.is-disabled .el-upload-dragger{cursor:not-allowed;background-color:var(--el-disabled-bg-color)}.el-upload.is-disabled .el-upload-dragger .el-upload__text{color:var(--el-text-color-placeholder)}.el-upload.is-disabled .el-upload-dragger .el-upload__text em{color:var(--el-disabled-text-color)}.el-upload.is-disabled .el-upload-dragger:hover{border-color:var(--el-border-color-darker)}.el-upload__input{display:none}.el-upload__tip{color:var(--el-text-color-regular);margin-top:7px;font-size:12px}.el-upload iframe{z-index:-1;opacity:0;filter:alpha(opacity=0);position:absolute;top:0;left:0}.el-upload--picture-card{background-color:var(--el-fill-color-lighter);border:1px dashed var(--el-border-color-darker);box-sizing:border-box;width:var(--el-upload-picture-card-size);height:var(--el-upload-picture-card-size);cursor:pointer;vertical-align:top;border-radius:6px;justify-content:center;align-items:center;display:inline-flex}.el-upload--picture-card>i{color:var(--el-text-color-secondary);font-size:28px}.el-upload--picture-card:hover{border-color:var(--el-color-primary);color:var(--el-color-primary)}.el-upload.is-drag{display:block}.el-upload:focus{border-color:var(--el-color-primary);color:var(--el-color-primary)}.el-upload:focus .el-upload-dragger{border-color:var(--el-color-primary)}.el-upload-dragger{padding:var(--el-upload-dragger-padding-vertical) var(--el-upload-dragger-padding-horizontal);background-color:var(--el-fill-color-blank);border:1px dashed var(--el-border-color);box-sizing:border-box;text-align:center;cursor:pointer;border-radius:6px;position:relative;overflow:hidden}.el-upload-dragger .el-icon--upload{color:var(--el-text-color-placeholder);margin-bottom:16px;font-size:67px;line-height:50px}.el-upload-dragger+.el-upload__tip{text-align:center}.el-upload-dragger~.el-upload__files{border-top:var(--el-border);margin-top:7px;padding-top:5px}.el-upload-dragger .el-upload__text{color:var(--el-text-color-regular);text-align:center;font-size:14px}.el-upload-dragger .el-upload__text em{color:var(--el-color-primary);font-style:normal}.el-upload-dragger:hover{border-color:var(--el-color-primary)}.el-upload-dragger.is-dragover{padding:calc(var(--el-upload-dragger-padding-vertical) - 1px) calc(var(--el-upload-dragger-padding-horizontal) - 1px);background-color:var(--el-color-primary-light-9);border:2px dashed var(--el-color-primary)}.el-upload-list{--el-upload-dragger-padding-horizontal:10px;--el-upload-dragger-padding-vertical:40px;--el-upload-list-picture-card-size:var(--el-upload-picture-card-size);--el-upload-picture-card-size:148px;margin:10px 0 0;padding:0;list-style:none;position:relative}.el-upload-list__item{color:var(--el-text-color-regular);box-sizing:border-box;border-radius:4px;width:100%;margin-bottom:5px;font-size:14px;transition:all .5s cubic-bezier(.55,0,.1,1);position:relative}.el-upload-list__item .el-progress{width:100%;position:absolute;top:20px}.el-upload-list__item .el-progress__text{position:absolute;top:-13px;right:0}.el-upload-list__item .el-progress-bar{margin-right:0;padding-right:0}.el-upload-list__item .el-icon--upload-success{color:var(--el-color-success)}.el-upload-list__item .el-icon--close{cursor:pointer;opacity:.75;color:var(--el-text-color-regular);transition:opacity var(--el-transition-duration);display:none;position:absolute;top:50%;right:5px;transform:translateY(-50%)}.el-upload-list__item .el-icon--close:hover{opacity:1;color:var(--el-color-primary)}.el-upload-list__item .el-icon--close-tip{cursor:pointer;opacity:1;color:var(--el-color-primary);font-size:12px;font-style:normal;display:none;position:absolute;top:1px;right:5px}.el-upload-list__item:hover,.el-upload-list__item:focus-within{background-color:var(--el-fill-color-light)}.el-upload-list__item:hover .el-icon--close,.el-upload-list__item:focus-within .el-icon--close{display:inline-flex}.el-upload-list__item:hover .el-icon--close-tip,.el-upload-list__item:focus-within .el-icon--close-tip{right:24px}.el-upload-list__item:hover .el-progress__text,.el-upload-list__item:focus-within .el-progress__text{display:none}.el-upload-list__item .el-upload-list__item-info{flex-direction:column;justify-content:center;width:calc(100% - 30px);margin-left:4px;display:inline-flex}.el-upload-list__item.is-success .el-upload-list__item-status-label{display:inline-flex}.el-upload-list__item.is-success .el-upload-list__item-name:hover,.el-upload-list__item.is-success .el-upload-list__item-name:focus{color:var(--el-color-primary);cursor:pointer}.el-upload-list__item.is-success:focus:not(:hover) .el-icon--close-tip{display:inline-block}.el-upload-list__item.is-success:not(.focusing):focus,.el-upload-list__item.is-success:active{outline-width:0}.el-upload-list__item.is-success:not(.focusing):focus .el-icon--close-tip,.el-upload-list__item.is-success:active .el-icon--close-tip{display:none}.el-upload-list__item.is-success:hover .el-upload-list__item-status-label,.el-upload-list__item.is-success:focus .el-upload-list__item-status-label,.el-upload-list__item.is-success:focus-within .el-upload-list__item-status-label{opacity:0;display:none}.el-upload-list__item-name{color:var(--el-text-color-regular);text-align:center;transition:color var(--el-transition-duration);font-size:var(--el-font-size-base);align-items:center;padding:0 4px;display:inline-flex}.el-upload-list__item-name .el-icon{color:var(--el-text-color-secondary);margin-right:6px}.el-upload-list__item-file-name{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.el-upload-list__item-status-label{line-height:inherit;height:100%;transition:opacity var(--el-transition-duration);justify-content:center;align-items:center;display:none;position:absolute;top:0;right:5px}.el-upload-list__item-delete{color:var(--el-text-color-regular);font-size:12px;display:none;position:absolute;top:0;right:10px}.el-upload-list__item-delete:hover{color:var(--el-color-primary)}.el-upload-list--picture-card{flex-wrap:wrap;margin:0;display:inline-flex}.el-upload-list--picture-card .el-upload-list__item{background-color:var(--el-fill-color-blank);border:1px solid var(--el-border-color);box-sizing:border-box;width:var(--el-upload-list-picture-card-size);height:var(--el-upload-list-picture-card-size);border-radius:6px;margin:0 8px 8px 0;padding:0;display:inline-flex;overflow:hidden}.el-upload-list--picture-card .el-upload-list__item .el-icon--check,.el-upload-list--picture-card .el-upload-list__item .el-icon--circle-check{color:#fff}.el-upload-list--picture-card .el-upload-list__item .el-icon--close{display:none}.el-upload-list--picture-card .el-upload-list__item:hover .el-upload-list__item-status-label{opacity:0;display:block}.el-upload-list--picture-card .el-upload-list__item:hover .el-progress__text{display:block}.el-upload-list--picture-card .el-upload-list__item .el-upload-list__item-name{display:none}.el-upload-list--picture-card .el-upload-list__item-thumbnail{object-fit:contain;width:100%;height:100%}.el-upload-list--picture-card .el-upload-list__item-status-label{background:var(--el-color-success);text-align:center;width:40px;height:24px;top:-6px;right:-15px;transform:rotate(45deg)}.el-upload-list--picture-card .el-upload-list__item-status-label i{margin-top:11px;font-size:12px;transform:rotate(-45deg)}.el-upload-list--picture-card .el-upload-list__item-actions{cursor:default;color:#fff;opacity:0;background-color:var(--el-overlay-color-lighter);width:100%;height:100%;transition:opacity var(--el-transition-duration);justify-content:center;align-items:center;font-size:20px;display:inline-flex;position:absolute;top:0;left:0}.el-upload-list--picture-card .el-upload-list__item-actions span{cursor:pointer;display:none}.el-upload-list--picture-card .el-upload-list__item-actions span+span{margin-left:16px}.el-upload-list--picture-card .el-upload-list__item-actions .el-upload-list__item-delete{font-size:inherit;color:inherit;position:static}.el-upload-list--picture-card .el-upload-list__item-actions:hover{opacity:1}.el-upload-list--picture-card .el-upload-list__item-actions:hover span{display:inline-flex}.el-upload-list--picture-card .el-progress{width:126px;top:50%;bottom:auto;left:50%;transform:translate(-50%,-50%)}.el-upload-list--picture-card .el-progress .el-progress__text{top:50%}.el-upload-list--picture .el-upload-list__item{z-index:0;background-color:var(--el-fill-color-blank);border:1px solid var(--el-border-color);box-sizing:border-box;border-radius:6px;align-items:center;margin-top:10px;padding:10px;display:flex;overflow:hidden}.el-upload-list--picture .el-upload-list__item .el-icon--check,.el-upload-list--picture .el-upload-list__item .el-icon--circle-check{color:#fff}.el-upload-list--picture .el-upload-list__item:hover .el-upload-list__item-status-label{opacity:0;display:inline-flex}.el-upload-list--picture .el-upload-list__item:hover .el-progress__text{display:block}.el-upload-list--picture .el-upload-list__item.is-success .el-upload-list__item-name i{display:none}.el-upload-list--picture .el-upload-list__item .el-icon--close{top:5px;transform:translateY(0)}.el-upload-list--picture .el-upload-list__item-thumbnail{object-fit:contain;z-index:1;background-color:var(--el-color-white);justify-content:center;align-items:center;width:70px;height:70px;display:inline-flex;position:relative}.el-upload-list--picture .el-upload-list__item-status-label{background:var(--el-color-success);text-align:center;width:46px;height:26px;position:absolute;top:-7px;right:-17px;transform:rotate(45deg)}.el-upload-list--picture .el-upload-list__item-status-label i{margin-top:12px;font-size:12px;transform:rotate(-45deg)}.el-upload-list--picture .el-progress{position:relative;top:-7px}.el-upload-cover{z-index:10;cursor:default;width:100%;height:100%;position:absolute;top:0;left:0;overflow:hidden}.el-upload-cover:after{content:"";vertical-align:middle;height:100%;display:inline-block}.el-upload-cover img{width:100%;height:100%;display:block}.el-upload-cover__label{background:var(--el-color-success);text-align:center;width:40px;height:24px;top:-6px;right:-15px;transform:rotate(45deg)}.el-upload-cover__label i{color:#fff;margin-top:11px;font-size:12px;transform:rotate(-45deg)}.el-upload-cover__progress{vertical-align:middle;width:243px;display:inline-block;position:static}.el-upload-cover__progress+.el-upload__inner{opacity:0}.el-upload-cover__content{width:100%;height:100%;position:absolute;top:0;left:0}.el-upload-cover__interact{background-color:var(--el-overlay-color-light);text-align:center;width:100%;height:100%;position:absolute;bottom:0;left:0}.el-upload-cover__interact .btn{color:#fff;cursor:pointer;vertical-align:middle;transition:var(--el-transition-md-fade);margin-top:60px;font-size:14px;display:inline-block}.el-upload-cover__interact .btn i{margin-top:0}.el-upload-cover__interact .btn span{opacity:0;transition:opacity .15s linear}.el-upload-cover__interact .btn:not(:first-child){margin-left:35px}.el-upload-cover__interact .btn:hover{transform:translateY(-13px)}.el-upload-cover__interact .btn:hover span{opacity:1}.el-upload-cover__interact .btn i{color:#fff;font-size:24px;line-height:inherit;margin:0 auto 5px;display:block}.el-upload-cover__title{text-overflow:ellipsis;white-space:nowrap;text-align:left;width:100%;height:36px;color:var(--el-text-color-primary);background-color:#fff;margin:0;padding:0 10px;font-size:14px;font-weight:400;line-height:36px;position:absolute;bottom:0;left:0;overflow:hidden}.el-upload-cover+.el-upload__inner{opacity:0;z-index:1;position:relative}.el-vl__wrapper{position:relative}.el-vl__wrapper:hover .el-virtual-scrollbar,.el-vl__wrapper.always-on .el-virtual-scrollbar{opacity:1}.el-vl__window{scrollbar-width:none}.el-vl__window::-webkit-scrollbar{display:none}.el-virtual-scrollbar{opacity:0;transition:opacity .34s ease-out}.el-virtual-scrollbar.always-on{opacity:1}.el-vg__wrapper{position:relative}.el-popper{--el-popper-border-radius:var(--el-popover-border-radius,4px);--el-popper-bg-color-light:var(--el-bg-color-overlay);--el-popper-bg-color-dark:var(--el-text-color-primary);border-radius:var(--el-popper-border-radius);z-index:2000;overflow-wrap:break-word;word-break:normal;visibility:visible;min-width:10px;padding:5px 11px;font-size:12px;line-height:20px;position:absolute}.el-popper.is-dark{--el-fill-color-blank:var(--el-popper-bg-color-dark);color:var(--el-bg-color);background:var(--el-popper-bg-color-dark);border:1px solid var(--el-text-color-primary)}.el-popper.is-dark>.el-popper__arrow:before{border:1px solid var(--el-text-color-primary);background:var(--el-popper-bg-color-dark);right:0}.el-popper.is-light{--el-fill-color-blank:var(--el-popper-bg-color-light);background:var(--el-popper-bg-color-light);border:1px solid var(--el-border-color-light)}.el-popper.is-light>.el-popper__arrow:before{border:1px solid var(--el-border-color-light);background:var(--el-popper-bg-color-light);right:0}.el-popper.is-pure{padding:0}.el-popper__arrow{z-index:-1;width:10px;height:10px;position:absolute}.el-popper__arrow:before{z-index:-1;content:" ";background:var(--el-text-color-primary);box-sizing:border-box;width:10px;height:10px;position:absolute;transform:rotate(45deg)}.el-popper[data-popper-placement^=top]>.el-popper__arrow{bottom:-5px}.el-popper[data-popper-placement^=top]>.el-popper__arrow:before{border-bottom-right-radius:2px}.el-popper[data-popper-placement^=bottom]>.el-popper__arrow{top:-5px}.el-popper[data-popper-placement^=bottom]>.el-popper__arrow:before{border-top-left-radius:2px}.el-popper[data-popper-placement^=left]>.el-popper__arrow{right:-5px}.el-popper[data-popper-placement^=left]>.el-popper__arrow:before{border-top-right-radius:2px}.el-popper[data-popper-placement^=right]>.el-popper__arrow{left:-5px}.el-popper[data-popper-placement^=right]>.el-popper__arrow:before{border-bottom-left-radius:2px}.el-popper[data-popper-placement^=top]>.el-popper__arrow:before{border-top-color:#0000!important;border-left-color:#0000!important}.el-popper[data-popper-placement^=bottom]>.el-popper__arrow:before{border-bottom-color:#0000!important;border-right-color:#0000!important}.el-popper[data-popper-placement^=left]>.el-popper__arrow:before{border-bottom-color:#0000!important;border-left-color:#0000!important}.el-popper[data-popper-placement^=right]>.el-popper__arrow:before{border-top-color:#0000!important;border-right-color:#0000!important}.el-statistic{--el-statistic-title-font-weight:400;--el-statistic-title-font-size:var(--el-font-size-extra-small);--el-statistic-title-color:var(--el-text-color-regular);--el-statistic-content-font-weight:400;--el-statistic-content-font-size:var(--el-font-size-extra-large);--el-statistic-content-color:var(--el-text-color-primary)}.el-statistic__head{font-weight:var(--el-statistic-title-font-weight);font-size:var(--el-statistic-title-font-size);color:var(--el-statistic-title-color);margin-bottom:4px;line-height:20px}.el-statistic__content{font-weight:var(--el-statistic-content-font-weight);font-size:var(--el-statistic-content-font-size);color:var(--el-statistic-content-color)}.el-statistic__value{display:inline-block}.el-statistic__prefix{margin-right:4px;display:inline-block}.el-statistic__suffix{margin-left:4px;display:inline-block}.el-tour{--el-tour-width:520px;--el-tour-padding-primary:12px;--el-tour-font-line-height:var(--el-font-line-height-primary);--el-tour-title-font-size:16px;--el-tour-title-text-color:var(--el-text-color-primary);--el-tour-title-font-weight:400;--el-tour-close-color:var(--el-color-info);--el-tour-font-size:14px;--el-tour-color:var(--el-text-color-primary);--el-tour-bg-color:var(--el-bg-color);--el-tour-border-radius:4px}.el-tour__hollow{transition:all var(--el-transition-duration) ease}.el-tour__content{border-radius:var(--el-tour-border-radius);width:var(--el-tour-width);padding:var(--el-tour-padding-primary);background:var(--el-tour-bg-color);box-shadow:var(--el-box-shadow-light);box-sizing:border-box;overflow-wrap:break-word;outline:none}.el-tour__arrow{background:var(--el-tour-bg-color);pointer-events:none;box-sizing:border-box;width:10px;height:10px;position:absolute;transform:rotate(45deg)}.el-tour__content[data-side^=top] .el-tour__arrow{border-top-color:#0000;border-left-color:#0000}.el-tour__content[data-side^=bottom] .el-tour__arrow{border-bottom-color:#0000;border-right-color:#0000}.el-tour__content[data-side^=left] .el-tour__arrow{border-bottom-color:#0000;border-left-color:#0000}.el-tour__content[data-side^=right] .el-tour__arrow{border-top-color:#0000;border-right-color:#0000}.el-tour__content[data-side^=top] .el-tour__arrow{bottom:-5px}.el-tour__content[data-side^=bottom] .el-tour__arrow{top:-5px}.el-tour__content[data-side^=left] .el-tour__arrow{right:-5px}.el-tour__content[data-side^=right] .el-tour__arrow{left:-5px}.el-tour__closebtn{cursor:pointer;width:40px;height:40px;font-size:var(--el-message-close-size,16px);background:0 0;border:none;outline:none;padding:0;position:absolute;top:0;right:0}.el-tour__closebtn .el-tour__close{color:var(--el-tour-close-color);font-size:inherit}.el-tour__closebtn:focus .el-tour__close,.el-tour__closebtn:hover .el-tour__close{color:var(--el-color-primary)}.el-tour__header{padding-bottom:var(--el-tour-padding-primary)}.el-tour__header.show-close{padding-right:calc(var(--el-tour-padding-primary) + var(--el-message-close-size,16px))}.el-tour__title{line-height:var(--el-tour-font-line-height);font-size:var(--el-tour-title-font-size);color:var(--el-tour-title-text-color);font-weight:var(--el-tour-title-font-weight)}.el-tour__body{color:var(--el-tour-text-color);font-size:var(--el-tour-font-size)}.el-tour__body img,.el-tour__body video{max-width:100%}.el-tour__footer{padding-top:var(--el-tour-padding-primary);box-sizing:border-box;justify-content:space-between;display:flex}.el-tour__content .el-tour-indicators{flex:1;display:inline-block}.el-tour__content .el-tour-indicator{background:var(--el-color-info-light-9);border-radius:50%;width:6px;height:6px;margin-right:6px;display:inline-block}.el-tour__content .el-tour-indicator.is-active{background:var(--el-color-primary)}.el-tour.el-tour--primary{--el-tour-title-text-color:#fff;--el-tour-text-color:#fff;--el-tour-bg-color:var(--el-color-primary);--el-tour-close-color:#fff}.el-tour.el-tour--primary .el-tour__closebtn:focus .el-tour__close,.el-tour.el-tour--primary .el-tour__closebtn:hover .el-tour__close{color:var(--el-tour-title-text-color)}.el-tour.el-tour--primary .el-button--default{color:var(--el-color-primary);border-color:var(--el-color-primary);background:#fff}.el-tour.el-tour--primary .el-button--primary{border-color:#fff}.el-tour.el-tour--primary .el-tour-indicator{background:#ffffff26}.el-tour.el-tour--primary .el-tour-indicator.is-active{background:#fff}.el-tour-parent--hidden{overflow:hidden}.el-anchor{--el-anchor-bg-color:var(--el-bg-color);--el-anchor-padding-indent:14px;--el-anchor-line-height:22px;--el-anchor-font-size:12px;--el-anchor-color:var(--el-text-color-secondary);--el-anchor-active-color:var(--el-color-primary);--el-anchor-hover-color:var(--el-text-color-regular);--el-anchor-marker-bg-color:var(--el-color-primary);background-color:var(--el-anchor-bg-color);position:relative}.el-anchor__marker{background-color:var(--el-anchor-marker-bg-color);opacity:0;z-index:0;border-radius:4px;position:absolute}.el-anchor.el-anchor--vertical .el-anchor__marker{width:4px;height:14px;transition:top .25s ease-in-out,opacity .25s;top:8px;left:0}.el-anchor.el-anchor--vertical .el-anchor__list{padding-left:var(--el-anchor-padding-indent)}.el-anchor.el-anchor--vertical.el-anchor--underline:before{content:"";background-color:#0505050f;width:2px;height:100%;position:absolute;left:0}.el-anchor.el-anchor--vertical.el-anchor--underline .el-anchor__marker{border-radius:unset;width:2px}.el-anchor.el-anchor--horizontal .el-anchor__marker{width:20px;height:2px;transition:left .25s ease-in-out,opacity .25s,width .25s;bottom:0}.el-anchor.el-anchor--horizontal .el-anchor__list{padding-bottom:4px;display:flex}.el-anchor.el-anchor--horizontal .el-anchor__list .el-anchor__item{padding-left:16px}.el-anchor.el-anchor--horizontal .el-anchor__list .el-anchor__item:first-child{padding-left:0}.el-anchor.el-anchor--horizontal.el-anchor--underline:before{content:"";background-color:#0505050f;width:100%;height:2px;position:absolute;bottom:0}.el-anchor.el-anchor--horizontal.el-anchor--underline .el-anchor__marker{border-radius:unset;height:2px}.el-anchor__item{flex-direction:column;display:flex}.el-anchor__link{font-size:var(--el-anchor-font-size);line-height:var(--el-anchor-line-height);color:var(--el-anchor-color);transition:color var(--el-transition-duration);white-space:nowrap;text-overflow:ellipsis;cursor:pointer;outline:none;max-width:100%;padding:4px 0;text-decoration:none;overflow:hidden}.el-anchor__link:hover,.el-anchor__link:focus{color:var(--el-hover-color)}.el-anchor__link:focus-visible{border-radius:var(--el-border-radius-base);outline:2px solid var(--el-color-primary)}.el-anchor__link.is-active{color:var(--el-anchor-active-color)}.el-anchor .el-anchor__list .el-anchor__item a{display:inline-block}.el-segmented--vertical{flex-direction:column}.el-segmented--vertical .el-segmented__item{padding:11px}.el-segmented{--el-segmented-color:var(--el-text-color-regular);--el-segmented-bg-color:var(--el-fill-color-light);--el-segmented-padding:2px;--el-segmented-item-selected-color:var(--el-color-white);--el-segmented-item-selected-bg-color:var(--el-color-primary);--el-segmented-item-selected-disabled-bg-color:var(--el-color-primary-light-5);--el-segmented-item-hover-color:var(--el-text-color-primary);--el-segmented-item-hover-bg-color:var(--el-fill-color-dark);--el-segmented-item-active-bg-color:var(--el-fill-color-darker);--el-segmented-item-disabled-color:var(--el-text-color-placeholder);background:var(--el-segmented-bg-color);min-height:32px;padding:var(--el-segmented-padding);border-radius:var(--el-border-radius-base);color:var(--el-segmented-color);box-sizing:border-box;align-items:stretch;font-size:14px;display:inline-flex}.el-segmented__group{align-items:stretch;width:100%;display:flex;position:relative}.el-segmented__item-selected{background:var(--el-segmented-item-selected-bg-color);border-radius:calc(var(--el-border-radius-base) - 2px);pointer-events:none;width:10px;height:100%;transition:all .3s;position:absolute;top:0;left:0}.el-segmented__item-selected.is-disabled{background:var(--el-segmented-item-selected-disabled-bg-color)}.el-segmented__item-selected.is-focus-visible:before{content:"";border-radius:inherit;outline:2px solid var(--el-segmented-item-selected-bg-color);outline-offset:1px;position:absolute;top:0;bottom:0;left:0;right:0}.el-segmented__item{cursor:pointer;border-radius:calc(var(--el-border-radius-base) - 2px);flex:1;align-items:center;padding:0 11px;display:flex}.el-segmented__item:not(.is-disabled):not(.is-selected):hover{color:var(--el-segmented-item-hover-color);background:var(--el-segmented-item-hover-bg-color)}.el-segmented__item:not(.is-disabled):not(.is-selected):active{background:var(--el-segmented-item-active-bg-color)}.el-segmented__item.is-selected,.el-segmented__item.is-selected.is-disabled{color:var(--el-segmented-item-selected-color)}.el-segmented__item.is-disabled{cursor:not-allowed;color:var(--el-segmented-item-disabled-color)}.el-segmented__item-input{opacity:0;pointer-events:none;width:0;height:0;margin:0;position:absolute}.el-segmented__item-label{text-align:center;text-overflow:ellipsis;white-space:nowrap;z-index:1;flex:1;line-height:normal;transition:color .3s;overflow:hidden}.el-segmented.is-block{display:flex}.el-segmented.is-block .el-segmented__item{min-width:0}.el-segmented--large{border-radius:var(--el-border-radius-base);min-height:40px;font-size:16px}.el-segmented--large .el-segmented__item-selected{border-radius:calc(var(--el-border-radius-base) - 2px)}.el-segmented--large .el-segmented--vertical .el-segmented__item{padding:11px}.el-segmented--large .el-segmented__item{border-radius:calc(var(--el-border-radius-base) - 2px);padding:0 11px}.el-segmented--small{border-radius:calc(var(--el-border-radius-base) - 1px);min-height:24px;font-size:14px}.el-segmented--small .el-segmented__item-selected{border-radius:calc(calc(var(--el-border-radius-base) - 1px) - 2px)}.el-segmented--small .el-segmented--vertical .el-segmented__item{padding:7px}.el-segmented--small .el-segmented__item{border-radius:calc(calc(var(--el-border-radius-base) - 1px) - 2px);padding:0 7px}.el-mention{width:100%;position:relative}.el-mention__popper.el-popper{background:var(--el-bg-color-overlay);border:1px solid var(--el-border-color-light);box-shadow:var(--el-box-shadow-light)}.el-mention__popper.el-popper .el-popper__arrow:before{border:1px solid var(--el-border-color-light)}.el-mention__popper.el-popper[data-popper-placement^=top] .el-popper__arrow:before{border-top-color:#0000;border-left-color:#0000}.el-mention__popper.el-popper[data-popper-placement^=bottom] .el-popper__arrow:before{border-bottom-color:#0000;border-right-color:#0000}.el-mention__popper.el-popper[data-popper-placement^=left] .el-popper__arrow:before{border-bottom-color:#0000;border-left-color:#0000}.el-mention__popper.el-popper[data-popper-placement^=right] .el-popper__arrow:before{border-top-color:#0000;border-right-color:#0000}.el-mention-dropdown{--el-mention-font-size:var(--el-font-size-base);--el-mention-bg-color:var(--el-bg-color-overlay);--el-mention-shadow:var(--el-box-shadow-light);--el-mention-border:1px solid var(--el-border-color-light);--el-mention-option-color:var(--el-text-color-regular);--el-mention-option-height:34px;--el-mention-option-min-width:100px;--el-mention-option-hover-background:var(--el-fill-color-light);--el-mention-option-selected-color:var(--el-color-primary);--el-mention-option-disabled-color:var(--el-text-color-placeholder);--el-mention-option-loading-color:var(--el-text-color-secondary);--el-mention-option-loading-padding:10px 0;--el-mention-max-height:174px;--el-mention-padding:6px 0;--el-mention-header-padding:10px;--el-mention-footer-padding:10px}.el-mention-dropdown__item{font-size:var(--el-mention-font-size);white-space:nowrap;text-overflow:ellipsis;color:var(--el-mention-option-color);height:var(--el-mention-option-height);line-height:var(--el-mention-option-height);box-sizing:border-box;min-width:var(--el-mention-option-min-width);cursor:pointer;padding:0 20px;position:relative;overflow:hidden}.el-mention-dropdown__item.is-hovering{background-color:var(--el-mention-option-hover-background)}.el-mention-dropdown__item.is-selected{color:var(--el-mention-option-selected-color);font-weight:700}.el-mention-dropdown__item.is-disabled{color:var(--el-mention-option-disabled-color);cursor:not-allowed;background-color:unset}.el-mention-dropdown{z-index:calc(var(--el-index-top) + 1);border-radius:var(--el-border-radius-base);box-sizing:border-box}.el-mention-dropdown__loading{text-align:center;color:var(--el-mention-option-loading-color);min-width:var(--el-mention-option-min-width);margin:0;padding:10px 0;font-size:12px}.el-mention-dropdown__wrap{max-height:var(--el-mention-max-height)}.el-mention-dropdown__list{padding:var(--el-mention-padding);box-sizing:border-box;margin:0;list-style:none}.el-mention-dropdown__header{padding:var(--el-mention-header-padding);border-bottom:var(--el-mention-border)}.el-mention-dropdown__footer{padding:var(--el-mention-footer-padding);border-top:var(--el-mention-border)}.el-splitter{width:100%;height:100%;margin:0;padding:0;display:flex;position:relative}.el-splitter__mask{z-index:999;position:absolute;top:0;bottom:0;left:0;right:0}.el-splitter__mask-horizontal{cursor:ew-resize}.el-splitter__mask-vertical{cursor:ns-resize}.el-splitter__horizontal{flex-direction:row}.el-splitter__vertical{flex-direction:column}.el-splitter-bar{-webkit-user-select:none;user-select:none;flex:none;position:relative}.el-splitter-bar__dragger{z-index:1;background:0 0;position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.el-splitter-bar__dragger:before,.el-splitter-bar__dragger:after{content:"";background-color:var(--el-border-color-light);position:absolute;top:50%;left:50%;transform:translate(-50%,-50%)}.el-splitter-bar__dragger:not(.is-lazy):after{display:none}.el-splitter-bar__dragger:after{opacity:.4}.el-splitter-bar__dragger:hover:not(.is-disabled):before{background-color:var(--el-color-primary-light-5)}.el-splitter-bar__dragger-horizontal:before,.el-splitter-bar__dragger-horizontal:after{width:2px;height:100%}.el-splitter-bar__dragger-vertical:before,.el-splitter-bar__dragger-vertical:after{width:100%;height:2px}.el-splitter-bar__dragger-active:before,.el-splitter-bar__dragger-active:after{background-color:var(--el-color-primary-light-3)}.el-splitter-bar__dragger-active.el-splitter-bar__dragger-horizontal:after{transform:translate(calc(-50% + var(--el-splitter-bar-offset)),-50%)}.el-splitter-bar__dragger-active.el-splitter-bar__dragger-vertical:after{transform:translate(-50%,calc(-50% + var(--el-splitter-bar-offset)))}.el-splitter-bar:hover .el-splitter-bar__collapse-icon{opacity:1}.el-splitter-bar__collapse-icon{background:var(--el-border-color-light);cursor:pointer;opacity:0;z-index:9;border-radius:2px;justify-content:center;align-items:center;display:flex;position:absolute}.el-splitter-bar__collapse-icon:hover{opacity:1;background-color:var(--el-color-primary-light-5)}.el-splitter-bar__horizontal-collapse-icon-start{width:16px;height:24px;top:50%;left:-12px;transform:translate(-50%,-50%)}.el-splitter-bar__horizontal-collapse-icon-end{width:16px;height:24px;top:50%;left:12px;transform:translate(-50%,-50%)}.el-splitter-bar__vertical-collapse-icon-start{width:24px;height:16px;top:-12px;right:50%;transform:translate(50%,-50%)}.el-splitter-bar__vertical-collapse-icon-end{width:24px;height:16px;top:12px;right:50%;transform:translate(50%,-50%)}.el-splitter-panel{scrollbar-width:thin;box-sizing:border-box;flex-grow:0;overflow:auto}html.dark{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;--el-color-primary:#409eff;--el-color-primary-light-3:#3375b9;--el-color-primary-light-5:#2a598a;--el-color-primary-light-7:#213d5b;--el-color-primary-light-8:#1d3043;--el-color-primary-light-9:#18222b;--el-color-primary-dark-2:#66b1ff;--el-color-success:#67c23a;--el-color-success-light-3:#4e8e2f;--el-color-success-light-5:#3e6b27;--el-color-success-light-7:#2d481f;--el-color-success-light-8:#25371c;--el-color-success-light-9:#1c2518;--el-color-success-dark-2:#85ce61;--el-color-warning:#e6a23c;--el-color-warning-light-3:#a77730;--el-color-warning-light-5:#7d5b28;--el-color-warning-light-7:#533f20;--el-color-warning-light-8:#3e301c;--el-color-warning-light-9:#292218;--el-color-warning-dark-2:#ebb563;--el-color-danger:#f56c6c;--el-color-danger-light-3:#b25252;--el-color-danger-light-5:#854040;--el-color-danger-light-7:#582e2e;--el-color-danger-light-8:#412626;--el-color-danger-light-9:#2a1d1d;--el-color-danger-dark-2:#f78989;--el-color-error:#f56c6c;--el-color-error-light-3:#b25252;--el-color-error-light-5:#854040;--el-color-error-light-7:#582e2e;--el-color-error-light-8:#412626;--el-color-error-light-9:#2a1d1d;--el-color-error-dark-2:#f78989;--el-color-info:#909399;--el-color-info-light-3:#6b6d71;--el-color-info-light-5:#525457;--el-color-info-light-7:#393a3c;--el-color-info-light-8:#2d2d2f;--el-color-info-light-9:#202121;--el-color-info-dark-2:#a6a9ad;--el-box-shadow:0px 12px 32px 4px #0000005c, 0px 8px 20px #000000b8;--el-box-shadow-light:0px 0px 12px #000000b8;--el-box-shadow-lighter:0px 0px 6px #000000b8;--el-box-shadow-dark:0px 16px 48px 16px #000000b8, 0px 12px 32px #000, 0px 8px 16px -8px #000;--el-bg-color-page:#0a0a0a;--el-bg-color:#141414;--el-bg-color-overlay:#1d1e1f;--el-text-color-primary:#e5eaf3;--el-text-color-regular:#cfd3dc;--el-text-color-secondary:#a3a6ad;--el-text-color-placeholder:#8d9095;--el-text-color-disabled:#6c6e72;--el-border-color-darker:#636466;--el-border-color-dark:#58585b;--el-border-color:#4c4d4f;--el-border-color-light:#414243;--el-border-color-lighter:#363637;--el-border-color-extra-light:#2b2b2c;--el-fill-color-darker:#424243;--el-fill-color-dark:#39393a;--el-fill-color:#303030;--el-fill-color-light:#262727;--el-fill-color-lighter:#1d1d1d;--el-fill-color-extra-light:#191919;--el-fill-color-blank:#141414;--el-mask-color:#000c;--el-mask-color-extra-light:#0000004d}html.dark .el-button{--el-button-disabled-text-color:#ffffff80}html.dark .el-card{--el-card-bg-color:var(--el-bg-color-overlay);--el-fill-color-blank:var(--el-card-bg-color)}html.dark .el-empty{--el-empty-fill-color-0:var(--el-color-black);--el-empty-fill-color-1:#4b4b52;--el-empty-fill-color-2:#36383d;--el-empty-fill-color-3:#1e1e20;--el-empty-fill-color-4:#262629;--el-empty-fill-color-5:#202124;--el-empty-fill-color-6:#212224;--el-empty-fill-color-7:#1b1c1f;--el-empty-fill-color-8:#1c1d1f;--el-empty-fill-color-9:#18181a}.login-page[data-v-40e3134b]{min-height:100vh;display:flex;align-items:center;justify-content:center;background:var(--bg-base);transition:background .18s ease}.login-card[data-v-40e3134b]{background:var(--bg-surface);border:1px solid var(--border);border-radius:10px;padding:36px 32px 28px;width:360px;box-shadow:0 2px 12px #0000000f;transition:background .18s ease,border-color .18s ease,box-shadow .18s ease}.login-header[data-v-40e3134b]{text-align:center;margin-bottom:28px}.login-logo[data-v-40e3134b]{width:44px;height:44px;border-radius:10px;background:var(--accent);color:#fff;display:flex;align-items:center;justify-content:center;margin:0 auto 12px}.login-title[data-v-40e3134b]{margin:0 0 5px;font-size:18px;font-weight:590;color:var(--text-primary);letter-spacing:-.02em}.login-sub[data-v-40e3134b]{margin:0;font-size:12px;color:var(--text-muted);font-weight:410}.mb12[data-v-40e3134b]{margin-bottom:12px}[data-v-40e3134b] .login-input .el-input__wrapper{background:var(--bg-subtle)!important;border:1px solid var(--border)!important;box-shadow:none!important;border-radius:6px!important;padding:2px 12px!important;transition:all .12s ease!important}[data-v-40e3134b] .login-input .el-input__wrapper:hover{border-color:var(--border-strong)!important}[data-v-40e3134b] .login-input .el-input__wrapper.is-focus{border-color:var(--accent)!important;box-shadow:0 0 0 2px var(--accent-light)!important;background:var(--bg-surface)!important}[data-v-40e3134b] .login-input .el-input__inner{color:var(--text-primary)!important;font-size:13px!important}[data-v-40e3134b] .login-input .el-input__inner::placeholder{color:var(--text-placeholder)!important}[data-v-40e3134b] .login-input .el-input__prefix .el-icon{color:var(--text-muted)!important;font-size:14px!important}.login-btn[data-v-40e3134b]{height:40px!important;font-size:13px!important;font-weight:590!important;border-radius:6px!important;border:none!important;background:var(--accent)!important;color:#fff!important;letter-spacing:.02em!important;transition:background .12s ease!important}.login-btn[data-v-40e3134b]:hover{background:var(--accent-hover)!important}.login-btn[data-v-40e3134b]:active{background:var(--accent)!important;transform:scale(.99)}.login-error[data-v-40e3134b]{display:flex;align-items:center;justify-content:center;gap:6px;color:var(--danger);font-size:12px;margin-top:10px;padding:8px 12px;background:var(--danger-bg);border:1px solid var(--danger-border);border-radius:6px}.dash[data-v-a6ea8b68]{max-width:1400px;margin:0 auto;display:flex;flex-direction:column;gap:20px}.kpi-grid[data-v-a6ea8b68]{display:grid;grid-template-columns:repeat(5,1fr);gap:14px}.kpi-card[data-v-a6ea8b68]{display:flex;flex-direction:column;padding:18px 20px;cursor:default}.kpi-label[data-v-a6ea8b68]{font-size:11px;font-weight:510;color:var(--text-muted);text-transform:uppercase;letter-spacing:.06em;line-height:1.2}.kpi-value[data-v-a6ea8b68]{font-size:24px;font-weight:590;color:var(--text-primary);letter-spacing:-.02em;line-height:1.2;margin-top:6px;font-feature-settings:"tnum"}.kpi-delta[data-v-a6ea8b68]{font-size:11px;color:var(--text-muted);margin-top:4px;font-weight:400}.chart-row[data-v-a6ea8b68]{display:grid;grid-template-columns:2fr 1fr;gap:14px}.chart-card[data-v-a6ea8b68]{overflow:hidden}.chart-title[data-v-a6ea8b68]{font-weight:590;font-size:13px;color:var(--text-primary)}.trend-controls[data-v-a6ea8b68]{display:flex;align-items:center;gap:10px}.chart-box[data-v-a6ea8b68]{height:220px;padding:8px 8px 10px}.status-row[data-v-a6ea8b68]{display:grid;grid-template-columns:1fr 1fr;gap:14px}.status-card[data-v-a6ea8b68]{overflow:hidden}.tunnel-badge[data-v-a6ea8b68]{display:flex;align-items:center;gap:6px;font-size:11px;color:var(--danger);font-weight:510}.tunnel-badge.ok[data-v-a6ea8b68]{color:var(--success)}.tunnel-badge .dot[data-v-a6ea8b68]{width:6px;height:6px;border-radius:50%;background:currentColor}.tunnel-info[data-v-a6ea8b68]{display:flex;align-items:center;gap:12px}.tunnel-icon[data-v-a6ea8b68]{color:var(--text-muted);flex-shrink:0}.tunnel-url[data-v-a6ea8b68]{font-size:13px;color:var(--text-body);word-break:break-all;font-feature-settings:"tnum"}.tunnel-cb[data-v-a6ea8b68]{font-size:11px;color:var(--text-muted);margin-top:2px;word-break:break-all}.queue-grid[data-v-a6ea8b68]{display:grid;grid-template-columns:repeat(4,1fr);gap:8px}.queue-item[data-v-a6ea8b68]{text-align:center;padding:12px 6px;background:var(--bg-subtle);border-radius:6px}.queue-num[data-v-a6ea8b68]{font-size:18px;font-weight:590;color:var(--text-primary);font-feature-settings:"tnum"}.queue-label[data-v-a6ea8b68]{font-size:10px;color:var(--text-muted);margin-top:4px;font-weight:510;text-transform:uppercase;letter-spacing:.05em}.rate-label[data-v-a6ea8b68]{font-size:11px;margin-top:10px;text-align:center}.ledger-grid[data-v-a6ea8b68]{display:grid;grid-template-columns:repeat(4,1fr);gap:8px}.ledger-item[data-v-a6ea8b68]{text-align:center;padding:14px 6px;background:var(--bg-subtle);border-radius:6px}.ledger-num[data-v-a6ea8b68]{font-size:17px;font-weight:590;color:var(--text-primary)}.ledger-label[data-v-a6ea8b68]{font-size:10px;color:var(--text-muted);margin-top:4px;font-weight:510;text-transform:uppercase;letter-spacing:.05em}.link-btn[data-v-a6ea8b68]{display:flex;align-items:center;gap:4px;border:none;background:transparent;color:var(--accent);font-size:12px;font-weight:510;cursor:pointer;padding:4px 8px;border-radius:4px;transition:background .12s ease}.link-btn[data-v-a6ea8b68]:hover{background:var(--accent-light)}.text-mono[data-v-a6ea8b68]{font-family:JetBrains Mono,SF Mono,Consolas,monospace;font-feature-settings:"tnum"}.text-muted[data-v-a6ea8b68]{color:var(--text-muted)}.text-success[data-v-a6ea8b68]{color:var(--success)}.text-warning[data-v-a6ea8b68]{color:var(--warning)}.text-danger[data-v-a6ea8b68]{color:var(--danger)}.font-mono[data-v-a6ea8b68]{font-family:JetBrains Mono,SF Mono,Consolas,monospace;font-feature-settings:"tnum"}.mt14[data-v-a6ea8b68]{margin-top:14px}@media (max-width: 1200px){.kpi-grid[data-v-a6ea8b68]{grid-template-columns:repeat(3,1fr)}.chart-row[data-v-a6ea8b68],.status-row[data-v-a6ea8b68]{grid-template-columns:1fr}}.channels-page[data-v-77316d40]{max-width:1400px;margin:0 auto;display:flex;flex-direction:column;gap:16px}.section-label[data-v-77316d40]{font-weight:590;font-size:13px;color:var(--text-primary)}.channel-grid[data-v-77316d40]{display:grid;grid-template-columns:repeat(auto-fill,minmax(340px,1fr));gap:12px}.channel-card[data-v-77316d40]{padding:16px;cursor:default}.channel-card[data-v-77316d40]:hover{border-color:var(--border-strong)}.channel-head[data-v-77316d40]{display:flex;align-items:center;gap:10px}.channel-logo[data-v-77316d40]{width:38px;height:38px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:16px;font-weight:600;color:#fff;background:var(--accent);flex-shrink:0}.channel-logo.wechat[data-v-77316d40]{background:#22a069}.channel-name-wrap[data-v-77316d40]{flex:1;min-width:0}.channel-name[data-v-77316d40]{font-size:13px;font-weight:590;color:var(--text-primary)}.channel-id[data-v-77316d40]{font-size:11px;font-feature-settings:"tnum"}.channel-stats[data-v-77316d40]{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;margin-top:14px}.stat[data-v-77316d40]{text-align:center;padding:10px 4px;background:var(--bg-subtle);border-radius:6px}.stat-num[data-v-77316d40]{font-size:14px;font-weight:590;color:var(--text-primary);font-feature-settings:"tnum"}.stat-label[data-v-77316d40]{font-size:10px;color:var(--text-muted);margin-top:2px;font-weight:510;text-transform:uppercase;letter-spacing:.04em}.channel-foot[data-v-77316d40]{display:flex;justify-content:space-between;align-items:center;margin-top:14px;padding-top:12px;border-top:1px solid var(--border-subtle)}.step[data-v-77316d40]{padding:16px;border-radius:8px;background:var(--bg-subtle)}.step-num[data-v-77316d40]{width:22px;height:22px;border-radius:50%;background:var(--accent);color:#fff;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:590;margin-bottom:10px}.step-title[data-v-77316d40]{font-size:13px;font-weight:590;margin-bottom:4px;color:var(--text-primary)}.step-desc[data-v-77316d40]{font-size:11px;color:var(--text-muted);line-height:1.5}.mode-switches[data-v-77316d40]{display:flex;flex-wrap:wrap;gap:8px}.mode-switch-item[data-v-77316d40]{display:flex;align-items:center;gap:4px}.mt10[data-v-77316d40]{margin-top:10px}.mt12[data-v-77316d40]{margin-top:12px}.mb16[data-v-77316d40]{margin-bottom:16px}.epay-info-card[data-v-77316d40]{border:1px solid var(--success-border)}.cfg-item[data-v-77316d40]{background:var(--bg-subtle);border-radius:6px;padding:12px 14px;margin-bottom:8px}.cfg-label[data-v-77316d40]{font-size:11px;color:var(--text-muted);margin-bottom:6px;font-weight:510;text-transform:uppercase;letter-spacing:.04em}.cfg-value[data-v-77316d40]{font-size:13px;word-break:break-all;display:flex;align-items:center;gap:8px}.cfg-value code[data-v-77316d40]{background:var(--bg-surface);padding:2px 8px;border-radius:4px;font-size:12px;border:1px solid var(--border);font-family:JetBrains Mono,monospace}.font-mono[data-v-77316d40]{font-family:JetBrains Mono,SF Mono,Consolas,monospace;font-feature-settings:"tnum"}.text-muted[data-v-77316d40]{color:var(--text-muted)}.orders-page[data-v-490d8a35]{max-width:1400px;margin:0 auto;display:flex;flex-direction:column;gap:16px}.section-label[data-v-490d8a35]{font-weight:590;font-size:13px;color:var(--text-primary)}.filter-bar[data-v-490d8a35]{display:flex;gap:8px;flex-wrap:wrap;padding:14px 16px 0}.pager[data-v-490d8a35]{display:flex;justify-content:flex-end;padding:0 20px 16px}.mt10[data-v-490d8a35]{margin-top:10px}.mt16[data-v-490d8a35]{margin-top:16px}.detail-head[data-v-490d8a35]{display:flex;align-items:center;gap:12px;margin-bottom:20px}.detail-amount[data-v-490d8a35]{font-size:26px;font-weight:700;color:var(--text-primary)}.section-title[data-v-490d8a35]{margin:20px 0 12px;font-size:14px;font-weight:600;color:var(--text-primary)}.empty-tip[data-v-490d8a35]{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:32px 0;gap:6px}.empty-text[data-v-490d8a35]{font-size:13px;color:var(--text-muted)}.empty-sub[data-v-490d8a35]{font-size:12px;color:var(--text-muted)}.font-mono[data-v-490d8a35]{font-family:JetBrains Mono,Fira Code,Consolas,monospace}.text-muted[data-v-490d8a35]{color:var(--text-muted)}.notify-page[data-v-52866146]{max-width:1400px;margin:0 auto;display:flex;flex-direction:column;gap:12px}.section-label[data-v-52866146]{font-weight:590;font-size:13px;color:var(--text-primary)}.stat-grid[data-v-52866146]{display:grid;grid-template-columns:repeat(4,1fr);gap:12px}.stat-card[data-v-52866146]{display:flex;align-items:center;gap:14px;padding:16px 18px}.stat-icon[data-v-52866146]{width:36px;height:36px;border-radius:8px;display:flex;align-items:center;justify-content:center;flex-shrink:0}.stat-icon.success[data-v-52866146]{background:var(--success-bg);color:var(--success)}.stat-icon.warn[data-v-52866146]{background:var(--warning-bg);color:var(--warning)}.stat-icon.danger[data-v-52866146]{background:var(--danger-bg);color:var(--danger)}.stat-icon.info[data-v-52866146]{background:var(--info-bg);color:var(--info)}.stat-num[data-v-52866146]{font-size:20px;font-weight:590;color:var(--text-primary);font-family:JetBrains Mono,monospace;font-feature-settings:"tnum"}.stat-label[data-v-52866146]{font-size:12px;color:var(--text-muted);margin-top:2px;font-weight:500}.mt12[data-v-52866146]{margin-top:12px}.payload-head[data-v-52866146]{display:flex;gap:10px;align-items:center;margin-bottom:12px}.payload[data-v-52866146]{background:var(--bg-subtle);padding:14px;border-radius:8px;font-family:JetBrains Mono,Consolas,monospace;font-size:12px;max-height:400px;overflow:auto;white-space:pre-wrap;word-break:break-all;margin:0;color:var(--text-body);border:1px solid var(--border)}.font-mono[data-v-52866146]{font-family:JetBrains Mono,Fira Code,Consolas,monospace}.text-muted[data-v-52866146]{color:var(--text-muted)}.logs-page[data-v-d4a5eb1d]{max-width:1400px;margin:0 auto;display:flex;flex-direction:column;gap:12px}.section-label[data-v-d4a5eb1d]{font-weight:590;font-size:13px;color:var(--text-primary)}.stat-grid[data-v-d4a5eb1d]{display:grid;grid-template-columns:repeat(4,1fr);gap:12px}.stat-card[data-v-d4a5eb1d]{display:flex;align-items:center;gap:12px;padding:16px 18px}.stat-icon[data-v-d4a5eb1d]{width:34px;height:34px;border-radius:8px;display:flex;align-items:center;justify-content:center;flex-shrink:0}.stat-icon.danger[data-v-d4a5eb1d]{background:var(--danger-bg);color:var(--danger)}.stat-icon.warn[data-v-d4a5eb1d]{background:var(--warning-bg);color:var(--warning)}.stat-icon.info[data-v-d4a5eb1d]{background:var(--info-bg);color:var(--info)}.stat-icon.primary[data-v-d4a5eb1d]{background:var(--accent-light);color:var(--accent)}.stat-num[data-v-d4a5eb1d]{font-size:20px;font-weight:590;color:var(--text-primary);font-family:JetBrains Mono,monospace;font-feature-settings:"tnum"}.stat-label[data-v-d4a5eb1d]{font-size:12px;color:var(--text-muted);margin-top:2px;font-weight:500}.log-stream[data-v-d4a5eb1d]{padding:6px 12px;max-height:520px;overflow-y:auto;font-family:JetBrains Mono,Fira Code,Consolas,monospace;font-size:12px}.log-line[data-v-d4a5eb1d]{display:flex;align-items:center;gap:10px;padding:5px 8px;border-radius:5px;transition:background .1s}.log-line[data-v-d4a5eb1d]:hover{background:var(--bg-hover)}.log-time[data-v-d4a5eb1d]{color:var(--text-muted);flex-shrink:0;font-size:11px}.log-level[data-v-d4a5eb1d]{flex-shrink:0;width:46px;text-align:center;padding:1px 5px;border-radius:4px;font-size:10px;font-weight:700}.log-level.lv-error[data-v-d4a5eb1d],.log-line.lv-error .log-time[data-v-d4a5eb1d]{color:var(--danger)}.log-level.lv-error[data-v-d4a5eb1d]{background:var(--danger-bg)}.log-level.lv-warn[data-v-d4a5eb1d],.log-line.lv-warn .log-time[data-v-d4a5eb1d]{color:var(--warning)}.log-level.lv-warn[data-v-d4a5eb1d]{background:var(--warning-bg)}.log-level.lv-info[data-v-d4a5eb1d],.log-line.lv-info .log-time[data-v-d4a5eb1d]{color:var(--info)}.log-level.lv-info[data-v-d4a5eb1d]{background:var(--info-bg)}.log-level.lv-debug[data-v-d4a5eb1d],.log-line.lv-debug .log-time[data-v-d4a5eb1d]{color:var(--text-muted)}.log-level.lv-debug[data-v-d4a5eb1d]{background:var(--bg-subtle)}.log-scope[data-v-d4a5eb1d]{color:var(--text-muted);flex-shrink:0;width:52px;font-size:11px}.log-msg[data-v-d4a5eb1d]{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.pager[data-v-d4a5eb1d]{display:flex;justify-content:flex-end;padding:0 16px 16px}.mt16[data-v-d4a5eb1d]{margin-top:16px}.empty-tip[data-v-d4a5eb1d]{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:48px 0;gap:8px}.empty-text[data-v-d4a5eb1d]{font-size:13px;color:var(--text-muted)}.empty-sub[data-v-d4a5eb1d]{font-size:12px;color:var(--text-muted)}.fields[data-v-d4a5eb1d]{background:var(--bg-subtle);padding:10px;border-radius:6px;font-family:JetBrains Mono,Consolas,monospace;font-size:12px;margin:0;white-space:pre-wrap;word-break:break-all;border:1px solid var(--border)}.font-mono[data-v-d4a5eb1d]{font-family:JetBrains Mono,Fira Code,Consolas,monospace}.ledger-page[data-v-05660c6d]{max-width:1000px;margin:0 auto}.mb16[data-v-05660c6d]{margin-bottom:16px}.mt12[data-v-05660c6d]{margin-top:12px}.mt16[data-v-05660c6d]{margin-top:16px}.section-label[data-v-05660c6d]{font-weight:600;font-size:14px;color:var(--text-primary)}.action-bar[data-v-05660c6d]{display:flex;align-items:center;gap:10px;padding:14px 18px;flex-wrap:wrap}.verify-tag[data-v-05660c6d]{margin-left:auto}.verify-tag .tag-muted[data-v-05660c6d]{opacity:.6;margin-left:4px;font-size:11px}.chain[data-v-05660c6d]{display:flex;flex-direction:column;align-items:center;padding:8px 0}.chain-node[data-v-05660c6d]{width:100%;display:flex;align-items:center;gap:14px;padding:12px 16px;background:var(--bg-subtle);border-radius:8px;border:1px solid var(--border)}.chain-node.head[data-v-05660c6d]{border-color:var(--success);box-shadow:0 0 0 1px var(--success),0 4px 12px -4px #00875a33}.chain-badge[data-v-05660c6d]{width:36px;height:36px;border-radius:50%;background:var(--accent-gradient);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:12px;flex-shrink:0}.chain-node.head .chain-badge[data-v-05660c6d]{background:var(--success)}.chain-body[data-v-05660c6d]{flex:1;min-width:0}.chain-row[data-v-05660c6d]{display:flex;gap:10px;font-size:12px;padding:2px 0;align-items:center}.chain-label[data-v-05660c6d]{color:var(--text-muted);width:66px;flex-shrink:0;font-weight:500}.chain-link[data-v-05660c6d]{color:var(--success);padding:3px 0;display:flex;justify-content:center}.snapshot-grid[data-v-05660c6d]{display:grid;gap:10px}.snapshot-card[data-v-05660c6d]{display:flex;align-items:flex-start;gap:14px;padding:14px;background:var(--bg-subtle);border-radius:8px;border:1px solid var(--border);position:relative}.snapshot-card[data-v-05660c6d]:hover{border-color:var(--accent)}.snapshot-date[data-v-05660c6d]{width:72px;height:56px;border-radius:8px;background:var(--accent-gradient);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:13px;flex-shrink:0;text-align:center;line-height:1.3}.snapshot-body[data-v-05660c6d]{flex:1;min-width:0}.sn-row[data-v-05660c6d]{display:flex;gap:10px;font-size:12px;padding:2px 0;align-items:center}.sn-label[data-v-05660c6d]{color:var(--text-muted);width:66px;flex-shrink:0;font-weight:500}.detail-icon[data-v-05660c6d]{position:absolute;right:14px;top:50%;transform:translateY(-50%);opacity:.5}.verify-result[data-v-05660c6d]{text-align:center}.verify-status[data-v-05660c6d]{display:flex;flex-direction:column;align-items:center;gap:8px;padding:20px;border-radius:10px}.verify-status.pass[data-v-05660c6d]{background:var(--success-bg);color:var(--success)}.verify-status.fail[data-v-05660c6d]{background:var(--danger-bg);color:var(--danger)}.verify-status-text[data-v-05660c6d]{font-size:18px;font-weight:590}.verify-errors[data-v-05660c6d]{text-align:left;background:var(--danger-bg);border:1px solid var(--danger-border);border-radius:8px;padding:14px}.verify-errors-title[data-v-05660c6d]{font-weight:590;font-size:13px;margin-bottom:8px;color:var(--danger)}.verify-error-item[data-v-05660c6d]{display:flex;align-items:center;gap:8px;font-size:12px;padding:4px 0;color:var(--danger)}.verify-loading[data-v-05660c6d]{min-height:80px;display:flex;align-items:center;justify-content:center}.pubkey-container[data-v-05660c6d]{padding:8px 0}.pubkey-block[data-v-05660c6d]{background:#1a1e2e;color:#c9d1d9;padding:14px;border-radius:8px;font-family:JetBrains Mono,Consolas,monospace;font-size:12px;line-height:1.6;white-space:pre-wrap;word-break:break-all;max-height:280px;overflow-y:auto}.event-link[data-v-05660c6d]{color:var(--accent);cursor:pointer;text-decoration:underline;font-weight:600}.event-link[data-v-05660c6d]:hover{color:var(--accent-hover)}.snap-detail[data-v-05660c6d]{padding:8px 0}.anchor-match[data-v-05660c6d]{display:flex;align-items:center;gap:8px;padding:12px 14px;background:var(--success-bg);border-radius:8px;color:var(--success);font-weight:600;font-size:13px}.anchor-mismatch[data-v-05660c6d]{display:flex;align-items:center;gap:8px;padding:12px 14px;background:var(--danger-bg);border-radius:8px;color:var(--danger);font-weight:600;font-size:13px}.font-mono[data-v-05660c6d]{font-family:JetBrains Mono,Fira Code,Consolas,monospace}.hash[data-v-05660c6d]{font-size:11px;word-break:break-all}.muted[data-v-05660c6d]{color:var(--text-muted)}.font-muted[data-v-05660c6d]{color:var(--text-muted);font-size:12px}.accounts-page[data-v-e8a4a312]{max-width:1400px;margin:0 auto;display:flex;flex-direction:column;gap:16px}.section-label[data-v-e8a4a312]{font-weight:590;font-size:13px;color:var(--text-primary)}.account-grid[data-v-e8a4a312]{display:grid;grid-template-columns:repeat(auto-fill,minmax(380px,1fr));gap:12px}.account-card[data-v-e8a4a312]{padding:18px;cursor:default}.account-card[data-v-e8a4a312]:hover{border-color:var(--border-strong)}.account-head[data-v-e8a4a312]{display:flex;align-items:center;gap:10px}.account-logo[data-v-e8a4a312]{width:40px;height:40px;border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:18px;font-weight:700;color:#fff;background:linear-gradient(135deg,#5c6ac4,#7c7fe4);flex-shrink:0}.account-logo.wechat[data-v-e8a4a312]{background:linear-gradient(135deg,#07c160,#3ddc84)}.account-info[data-v-e8a4a312]{flex:1;min-width:0}.account-name[data-v-e8a4a312]{font-size:14px;font-weight:600;color:var(--text-primary)}.account-channel[data-v-e8a4a312]{font-size:11px}.account-actions[data-v-e8a4a312]{display:flex;gap:2px;flex-wrap:wrap;margin-top:14px;padding-top:12px;border-top:1px solid var(--border)}.filter-bar[data-v-e8a4a312]{display:flex;gap:10px;align-items:center}.header-actions[data-v-e8a4a312]{display:flex;align-items:center;gap:10px}.mt10[data-v-e8a4a312]{margin-top:10px}.mt12[data-v-e8a4a312]{margin-top:12px}.ml8[data-v-e8a4a312]{margin-left:8px}.mb16[data-v-e8a4a312]{margin-bottom:16px}.log-item[data-v-e8a4a312]{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.log-detail[data-v-e8a4a312]{font-size:13px}.log-operator[data-v-e8a4a312]{font-size:11px}.text-muted[data-v-e8a4a312]{color:var(--text-muted)}.text-danger[data-v-e8a4a312]{color:var(--danger)}.font-mono[data-v-e8a4a312]{font-family:JetBrains Mono,Fira Code,Consolas,monospace}.qr-upload-row[data-v-e8a4a312]{display:flex;align-items:center;gap:10px;margin-top:8px;flex-wrap:wrap}.secret-box[data-v-e8a4a312]{display:flex;flex-direction:column;gap:10px}.secret-item[data-v-e8a4a312]{background:var(--bg-subtle);border-radius:6px;padding:10px 12px}.secret-label[data-v-e8a4a312]{font-size:11px;color:var(--text-muted);margin-bottom:6px;font-weight:510;text-transform:uppercase;letter-spacing:.04em}.secret-value[data-v-e8a4a312]{font-size:13px;display:flex;align-items:center;gap:10px}.secret-value .font-mono[data-v-e8a4a312]{flex:1;min-width:0;font-size:12px}.upstreams-page[data-v-cefda71d]{max-width:1400px;margin:0 auto;display:flex;flex-direction:column;gap:16px}.section-label[data-v-cefda71d]{font-weight:590;font-size:13px;color:var(--text-primary)}.mb16[data-v-cefda71d]{margin-bottom:16px}.mt10[data-v-cefda71d]{margin-top:10px}.upstream-grid[data-v-cefda71d]{display:grid;grid-template-columns:repeat(auto-fill,minmax(320px,1fr));gap:12px}.upstream-card[data-v-cefda71d]{padding:16px;cursor:default}.upstream-card[data-v-cefda71d]:hover{border-color:var(--border-strong)}.upstream-head[data-v-cefda71d]{display:flex;align-items:center;gap:10px;margin-bottom:12px}.upstream-logo[data-v-cefda71d]{width:36px;height:36px;border-radius:9px;background:var(--accent-gradient);color:#fff;font-weight:700;font-size:16px;display:flex;align-items:center;justify-content:center;flex-shrink:0}.upstream-info[data-v-cefda71d]{flex:1;min-width:0}.upstream-name[data-v-cefda71d]{font-weight:600;font-size:14px;color:var(--text-primary)}.upstream-pid[data-v-cefda71d]{font-size:11px;margin-top:2px}.upstream-actions[data-v-cefda71d]{display:flex;gap:2px;margin-top:12px;flex-wrap:wrap;border-top:1px solid var(--border);padding-top:10px}.log-item[data-v-cefda71d]{display:flex;align-items:center;gap:8px;flex-wrap:wrap}.log-detail[data-v-cefda71d]{font-size:13px}.log-operator[data-v-cefda71d]{font-size:11px}.text-muted[data-v-cefda71d]{color:var(--text-muted)}.font-mono[data-v-cefda71d]{font-family:JetBrains Mono,Fira Code,Consolas,monospace}.header-actions[data-v-cefda71d]{display:flex;align-items:center;gap:10px}.setup-steps[data-v-cefda71d]{margin-top:16px}.guide-steps[data-v-cefda71d]{display:flex;flex-direction:column;gap:14px}.guide-step[data-v-cefda71d]{display:flex;gap:12px}.guide-step-num[data-v-cefda71d]{width:24px;height:24px;border-radius:50%;background:var(--accent);color:#fff;display:flex;align-items:center;justify-content:center;font-size:12px;font-weight:590;flex-shrink:0;margin-top:1px}.guide-step-body[data-v-cefda71d]{flex:1;min-width:0}.guide-step-title[data-v-cefda71d]{font-weight:590;font-size:13px;color:var(--text-primary);margin-bottom:4px}.guide-step-desc[data-v-cefda71d]{font-size:12px;color:var(--text-body);line-height:1.7}.guide-step-desc code[data-v-cefda71d]{background:var(--bg-subtle);padding:1px 6px;border-radius:4px;font-family:JetBrains Mono,Consolas,monospace;font-size:11px}.guide-callback[data-v-cefda71d]{margin-top:6px;padding:8px 10px;background:var(--bg-subtle);border:1px solid var(--border);border-radius:6px;font-size:12px;color:var(--text-primary);word-break:break-all}.guide-note[data-v-cefda71d]{margin-top:8px;padding:8px 10px;background:var(--warning-bg, #fff7e6);border:1px solid var(--warning, #e6a23c);border-radius:6px;font-size:12px;color:#b88230;line-height:1.7}.guide-note code[data-v-cefda71d]{background:#0000000f;padding:1px 5px;border-radius:4px}.help-text[data-v-cefda71d]{font-size:12px;color:var(--text-muted);line-height:1.7}.help-text code[data-v-cefda71d]{background:var(--bg-subtle);padding:1px 5px;border-radius:4px}.reconciler-page[data-v-e25116cd]{max-width:1400px;margin:0 auto}.filter-bar[data-v-e25116cd]{display:flex;gap:8px;flex-wrap:wrap;padding:14px 16px 0}.pager[data-v-e25116cd]{display:flex;justify-content:flex-end;padding:0 16px 16px}.mt10[data-v-e25116cd]{margin-top:10px}.mt16[data-v-e25116cd]{margin-top:16px}.section-title[data-v-e25116cd]{margin:18px 0 10px;font-size:14px;font-weight:590;color:var(--text-primary)}.evidence-box[data-v-e25116cd]{background:var(--bg-subtle);padding:12px;border-radius:6px;font-size:12px;white-space:pre-wrap;max-height:200px;overflow-y:auto;border:1px solid var(--border);margin:0}.report-head[data-v-e25116cd]{display:flex;align-items:center;gap:8px;margin-bottom:12px}.report-stats[data-v-e25116cd]{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:10px}.stat-box[data-v-e25116cd]{padding:16px;text-align:center}.stat-val[data-v-e25116cd]{font-size:20px;font-weight:590;color:var(--text-primary);font-family:JetBrains Mono,monospace;font-feature-settings:"tnum"}.stat-label[data-v-e25116cd]{font-size:11px;color:var(--text-muted);margin-top:4px;font-weight:500}.empty-text[data-v-e25116cd]{text-align:center;padding:40px 0;color:var(--text-muted);font-size:13px}.font-mono[data-v-e25116cd]{font-family:JetBrains Mono,Fira Code,Consolas,monospace}.text-muted[data-v-e25116cd]{color:var(--text-muted)}.text-success[data-v-e25116cd]{color:var(--success)}.text-warning[data-v-e25116cd]{color:var(--warning)}.text-danger[data-v-e25116cd]{color:var(--danger)}.device-stream[data-v-c99e0b77]{display:flex;flex-direction:column;gap:10px;height:100%}.stream-wrap[data-v-c99e0b77]{flex:1;display:flex;align-items:center;justify-content:center;min-height:0;overflow:hidden}.stream-stage[data-v-c99e0b77]{position:relative;background:#000;border-radius:10px;overflow:hidden;height:100%;aspect-ratio:9 / 16;max-width:100%}.stream-canvas[data-v-c99e0b77]{width:100%;height:100%;display:block;cursor:crosshair;touch-action:none}.stream-overlay[data-v-c99e0b77]{position:absolute;top:0;right:0;bottom:0;left:0;display:flex;flex-direction:column;gap:12px;align-items:center;justify-content:center;color:#999;background:#0000004d}.stream-msg[data-v-c99e0b77]{font-size:13px;color:#bbb}.personal-devices-page[data-v-3b83dd8d]{max-width:1400px;margin:0 auto;display:flex;flex-direction:column;gap:16px}.section-label[data-v-3b83dd8d]{font-weight:600;font-size:14px;color:var(--text-primary)}.mb16[data-v-3b83dd8d]{margin-bottom:16px}.header-actions[data-v-3b83dd8d]{display:flex;align-items:center;gap:10px}.font-mono[data-v-3b83dd8d]{font-family:JetBrains Mono,Fira Code,Consolas,monospace}.text-muted[data-v-3b83dd8d]{color:var(--text-muted)}.device-grid[data-v-3b83dd8d]{display:grid;grid-template-columns:repeat(auto-fill,minmax(220px,1fr));gap:20px}.phone-card[data-v-3b83dd8d]{background:var(--bg-surface);border-radius:14px;border:1px solid var(--border);padding:16px 16px 12px;cursor:pointer;transition:box-shadow .2s,transform .2s;text-align:center;position:relative}.phone-card[data-v-3b83dd8d]:hover{box-shadow:0 4px 16px #0000001a;transform:translateY(-2px)}.phone-frame[data-v-3b83dd8d]{width:100%;max-width:180px;margin:0 auto;background:#1a1a1a;border-radius:20px;padding:6px;position:relative;box-shadow:0 2px 8px #00000040}.phone-notch[data-v-3b83dd8d]{width:80px;height:16px;background:#1a1a1a;border-radius:0 0 10px 10px;margin:-6px auto 0}.phone-screen[data-v-3b83dd8d]{aspect-ratio:9 / 16;background:#111;border-radius:12px;overflow:hidden;display:flex;align-items:center;justify-content:center;margin:0;position:relative}.phone-screenshot[data-v-3b83dd8d]{width:100%;height:100%;object-fit:cover;display:block}.phone-placeholder[data-v-3b83dd8d]{text-align:center;padding:24px 8px;color:#555}.phone-placeholder-icon[data-v-3b83dd8d]{font-size:32px;margin-bottom:4px}.phone-placeholder-name[data-v-3b83dd8d]{font-size:11px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.phone-home[data-v-3b83dd8d]{width:30px;height:30px;border:1.5px solid #333;border-radius:50%;margin:4px auto 0;cursor:pointer}.phone-info[data-v-3b83dd8d]{margin-top:10px}.phone-name[data-v-3b83dd8d]{font-weight:600;font-size:13px;color:var(--text-primary);margin-bottom:4px}.phone-meta[data-v-3b83dd8d]{display:flex;align-items:center;justify-content:center;gap:4px;flex-wrap:wrap}.snapshot-tag[data-v-3b83dd8d]{font-size:10px;color:var(--text-muted)}.phone-actions[data-v-3b83dd8d]{display:flex;justify-content:center;gap:2px;margin-top:6px;flex-wrap:wrap}.empty-state[data-v-3b83dd8d]{grid-column:1 / -1;text-align:center;padding:60px 0}.console-wrap[data-v-3b83dd8d]{display:flex;flex-direction:column;gap:12px;height:640px}.console-header[data-v-3b83dd8d]{display:flex;align-items:flex-start;justify-content:space-between;gap:12px}.console-device-info[data-v-3b83dd8d]{font-size:13px}.console-stream[data-v-3b83dd8d]{flex:1;min-height:0}.integration-guide[data-v-3b83dd8d]{margin-top:4px;border-top:1px dashed var(--border);padding-top:8px}.integration-guide[data-v-3b83dd8d] .el-collapse-item__header{font-weight:500}.integration-guide[data-v-3b83dd8d] .el-collapse-item__wrap{padding-bottom:20px;border-bottom:none}.integration-guide[data-v-3b83dd8d] .el-collapse-item__content{padding-bottom:0}.guide-title[data-v-3b83dd8d]{margin-right:8px;color:var(--text-primary)}.guide-tag[data-v-3b83dd8d]{margin-left:auto}.guide-content[data-v-3b83dd8d]{padding:12px 0 4px 8px}.step-list[data-v-3b83dd8d]{margin:4px 0 0;padding-left:20px;line-height:1.8;color:var(--text-primary)}.step-list li[data-v-3b83dd8d]{margin-bottom:4px}.step-list code[data-v-3b83dd8d]{background:var(--bg-surface);padding:1px 6px;border-radius:4px;font-family:JetBrains Mono,Consolas,monospace;font-size:12px}.mt8[data-v-3b83dd8d]{margin-top:8px;margin-bottom:16px}.phone-checkbox[data-v-3b83dd8d]{position:absolute;top:8px;left:8px;z-index:10}.phone-card-selected[data-v-3b83dd8d]{border-color:var(--el-color-warning);box-shadow:0 0 0 2px var(--el-color-warning-light-7)}.batch-actions-bar[data-v-3b83dd8d]{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;margin-top:16px;background:var(--bg-surface);border-radius:8px;border:1px solid var(--border)}.batch-actions-left[data-v-3b83dd8d]{display:flex;align-items:center;gap:12px}.batch-actions-right[data-v-3b83dd8d]{display:flex;align-items:center;gap:8px}.wizard-body[data-v-3b83dd8d]{min-height:200px}.usb-device-list[data-v-3b83dd8d]{display:flex;flex-direction:column;gap:8px}.usb-device-item[data-v-3b83dd8d]{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;border:1px solid var(--border);border-radius:8px;cursor:pointer;transition:border-color .2s,background .2s}.usb-device-item[data-v-3b83dd8d]:hover{border-color:var(--el-color-primary);background:var(--el-fill-color-light)}.usb-device-item-selected[data-v-3b83dd8d]{border-color:var(--el-color-success);background:var(--el-color-success-light-9)}.usb-device-info[data-v-3b83dd8d]{display:flex;flex-direction:column;gap:2px}.usb-device-serial[data-v-3b83dd8d]{font-size:13px;color:var(--text-primary)}.usb-device-right[data-v-3b83dd8d]{display:flex;align-items:center}.unmatched-page[data-v-1df8350a]{max-width:1400px;margin:0 auto;display:flex;flex-direction:column;gap:16px}.section-label[data-v-1df8350a]{font-weight:600;font-size:14px;color:var(--text-primary)}.mb16[data-v-1df8350a]{margin-bottom:16px}.header-actions[data-v-1df8350a]{display:flex;align-items:center;gap:10px}.raw-cell[data-v-1df8350a]{max-width:300px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-size:12px;color:var(--text-muted)}.font-mono[data-v-1df8350a]{font-family:JetBrains Mono,Fira Code,Consolas,monospace}.text-muted[data-v-1df8350a]{color:var(--text-muted)}.text-danger[data-v-1df8350a]{color:var(--danger)}.settings-page[data-v-49bef0e4]{max-width:800px;margin:0 auto}.section-label[data-v-49bef0e4]{font-weight:590;font-size:13px;color:var(--text-primary)}.mb16[data-v-49bef0e4]{margin-bottom:16px}.mt16[data-v-49bef0e4]{margin-top:16px}.sec-title[data-v-49bef0e4]{font-size:14px;margin:0 0 12px;color:var(--text-primary);font-weight:600}.form-tip[data-v-49bef0e4]{font-size:11px;color:var(--text-muted);margin-top:4px}.saved-hint[data-v-49bef0e4]{display:inline-flex;align-items:center;gap:4px;margin-left:12px;color:var(--success);font-size:13px;font-weight:500}.channel-grid[data-v-49bef0e4]{display:flex;flex-wrap:wrap;gap:10px;margin-bottom:16px}.channel-switch[data-v-49bef0e4]{display:flex;align-items:center;gap:14px;padding:12px 16px;background:var(--bg-subtle);border-radius:8px;border:1px solid var(--border);min-width:150px}.channel-switch.disabled[data-v-49bef0e4]{opacity:.5}.ch-name[data-v-49bef0e4]{font-weight:600;font-size:13px;color:var(--text-primary)}.ch-status[data-v-49bef0e4]{font-size:11px;color:var(--text-muted);margin-top:2px}.mb12[data-v-49bef0e4]{margin-bottom:12px}.mt12[data-v-49bef0e4]{margin-top:12px}.key-grid[data-v-49bef0e4]{display:flex;flex-direction:column;gap:10px}.key-item[data-v-49bef0e4]{background:var(--bg-subtle);border:1px solid var(--border);border-radius:8px;padding:12px 14px;display:flex;align-items:center;gap:14px;flex-wrap:wrap}.key-label[data-v-49bef0e4]{font-size:12px;color:var(--text-muted);font-weight:510;width:180px;flex-shrink:0}.key-value[data-v-49bef0e4]{flex:1;min-width:160px;font-size:12px;color:var(--text-primary);word-break:break-all;font-family:JetBrains Mono,Fira Code,Consolas,monospace}.key-actions[data-v-49bef0e4]{display:flex;gap:2px;flex-shrink:0}.admin-item[data-v-49bef0e4]{display:flex;align-items:center;justify-content:space-between;gap:14px;background:var(--bg-subtle);border:1px solid var(--border);border-radius:8px;padding:12px 14px}.admin-label[data-v-49bef0e4]{font-size:11px;color:var(--text-muted);margin-bottom:4px;font-weight:510}.admin-value[data-v-49bef0e4]{font-size:13px;color:var(--text-primary)}.qrcode-grid[data-v-9290643a]{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:16px;padding:16px 0}.qrcode-card[data-v-9290643a]{background:#fff;border-radius:8px;border:1px solid var(--el-border-color-lighter);padding:16px;transition:box-shadow .2s}.qrcode-card[data-v-9290643a]:hover{box-shadow:0 2px 12px #00000014}:root.dark .qrcode-card[data-v-9290643a]{background:var(--el-bg-color-overlay)}.card-header[data-v-9290643a]{display:flex;gap:8px;align-items:center;margin-bottom:12px}.device-name[data-v-9290643a]{font-weight:600;font-size:14px;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.card-account[data-v-9290643a]{font-size:13px;color:#666;margin-bottom:10px;padding-left:2px}.card-account .account-name[data-v-9290643a]{color:var(--el-color-primary);font-weight:500}.card-account .account-unknown[data-v-9290643a]{color:#bbb}.card-body[data-v-9290643a]{display:flex;justify-content:center;margin-bottom:12px}.qr-area[data-v-9290643a]{text-align:center}.qr-img[data-v-9290643a]{width:180px;height:180px;display:block;margin:0 auto;border:1px solid #eee;border-radius:4px}.qr-deadline[data-v-9290643a]{margin-top:8px;font-size:13px;color:#999}.qr-deadline .urgent[data-v-9290643a]{color:#f56c6c;font-weight:600}.no-qr[data-v-9290643a]{text-align:center;color:#ccc;font-size:14px;padding:40px 0}.card-footer[data-v-9290643a]{display:flex;justify-content:center}.empty[data-v-9290643a]{margin-top:60px}.mb-3[data-v-9290643a]{margin-bottom:12px}.app-shell[data-v-b117130e]{display:flex;height:100vh;overflow:hidden}.sidebar[data-v-b117130e]{width:var(--sidebar-width);flex-shrink:0;display:flex;flex-direction:column;background:var(--bg-sidebar);border-right:1px solid var(--border);transition:background .18s ease,border-color .18s ease}.brand[data-v-b117130e]{display:flex;align-items:center;gap:10px;padding:14px 14px 12px;border-bottom:1px solid var(--border)}.brand-logo[data-v-b117130e]{width:30px;height:30px;border-radius:7px;background:var(--accent);color:#fff;display:flex;align-items:center;justify-content:center;flex-shrink:0}.brand-name[data-v-b117130e]{font-size:13px;font-weight:590;color:var(--text-primary);letter-spacing:-.01em;line-height:1.2}.brand-sub[data-v-b117130e]{font-size:11px;color:var(--text-muted);line-height:1.2;margin-top:1px}.nav[data-v-b117130e]{flex:1;padding:8px;display:flex;flex-direction:column;gap:1px;overflow-y:auto}.nav-item[data-v-b117130e]{display:flex;align-items:center;gap:8px;padding:0 10px;height:32px;border:none;background:transparent;border-radius:6px;color:var(--text-muted);font-size:12px;font-weight:410;cursor:pointer;transition:background .12s ease,color .12s ease;text-align:left;width:100%;position:relative;letter-spacing:.01em}.nav-item[data-v-b117130e]:hover{background:var(--bg-hover);color:var(--text-body)}.nav-item.active[data-v-b117130e]{background:var(--accent-light);color:var(--accent);font-weight:510}.nav-item.active[data-v-b117130e]:before{content:"";position:absolute;left:0;top:6px;bottom:6px;width:2px;border-radius:0 2px 2px 0;background:var(--accent)}.nav-badge[data-v-b117130e]{margin-left:auto;font-size:10px;font-weight:590;padding:1px 5px;border-radius:4px;background:var(--danger-bg);color:var(--danger);border:1px solid var(--danger-border);letter-spacing:.02em}.nav-badge.warn[data-v-b117130e]{background:var(--warning-bg);color:var(--warning);border-color:var(--warning-border)}.sidebar-foot[data-v-b117130e]{padding:10px 14px;border-top:1px solid var(--border);margin-top:auto}.tunnel-mini[data-v-b117130e]{display:flex;align-items:center;gap:7px;font-size:11px;color:var(--text-muted);font-weight:410}.tunnel-mini .dot[data-v-b117130e]{width:6px;height:6px;border-radius:50%;background:var(--danger);flex-shrink:0;transition:background .2s}.tunnel-mini.ok .dot[data-v-b117130e]{background:var(--success)}.tunnel-mini.ok .tunnel-text[data-v-b117130e]{color:var(--success)}.main-area[data-v-b117130e]{flex:1;display:flex;flex-direction:column;min-width:0;background:var(--bg-base)}.topbar[data-v-b117130e]{height:var(--topbar-height);flex-shrink:0;display:flex;align-items:center;justify-content:space-between;padding:0 20px;background:var(--bg-topbar);border-bottom:1px solid var(--border);transition:background .18s ease,border-color .18s ease}.page-title[data-v-b117130e]{font-size:13px;font-weight:590;color:var(--text-primary);letter-spacing:-.01em}.topbar-right[data-v-b117130e]{display:flex;align-items:center;gap:4px}.icon-btn[data-v-b117130e]{width:30px;height:30px;border:1px solid transparent;background:transparent;border-radius:6px;color:var(--text-muted);cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .12s ease}.icon-btn[data-v-b117130e]:hover{color:var(--accent);border-color:var(--border);background:var(--bg-hover)}.avatar[data-v-b117130e]{width:28px;height:28px;border-radius:50%;background:var(--accent);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:590;font-size:12px;cursor:pointer;margin-left:4px;letter-spacing:.02em}.content[data-v-b117130e]{flex:1;overflow-y:auto;padding:20px 24px}.page-enter-active[data-v-b117130e]{transition:opacity .15s ease,transform .15s cubic-bezier(.16,1,.3,1)}.page-leave-active[data-v-b117130e]{transition:opacity .1s ease}.page-enter-from[data-v-b117130e]{opacity:0;transform:translateY(4px)}.page-leave-to[data-v-b117130e]{opacity:0}:root{--bg-base: #FAFAFA;--bg-surface: #FFFFFF;--bg-subtle: #F4F4F5;--bg-hover: rgba(0, 0, 0, .04);--bg-active: rgba(0, 0, 0, .06);--bg-sidebar: #FFFFFF;--bg-topbar: #FFFFFF;--border: #E4E4E7;--border-subtle: rgba(0, 0, 0, .06);--border-strong: #D1D1D6;--text-primary: #09090B;--text-body: #3F3F46;--text-muted: #71717A;--text-placeholder: #A1A1AA;--text-inverse: #FFFFFF;--accent: #7170FF;--accent-hover: #8284FF;--accent-light: rgba(113, 112, 255, .08);--accent-muted: rgba(113, 112, 255, .14);--success: #16A34A;--success-bg: rgba(22, 163, 74, .08);--success-border: rgba(22, 163, 74, .18);--warning: #B45309;--warning-bg: rgba(180, 83, 9, .08);--warning-border: rgba(180, 83, 9, .18);--danger: #DC2626;--danger-bg: rgba(220, 38, 38, .08);--danger-border: rgba(220, 38, 38, .18);--info: #2563EB;--info-bg: rgba(37, 99, 235, .08);--info-border: rgba(37, 99, 235, .18);--shadow-xs: 0 1px 2px rgba(0, 0, 0, .04);--shadow-sm: 0 1px 4px rgba(0, 0, 0, .06), 0 0 0 1px rgba(0, 0, 0, .04);--shadow-md: 0 2px 8px rgba(0, 0, 0, .08), 0 0 0 1px rgba(0, 0, 0, .04);--sidebar-width: 200px;--topbar-height: 48px;--card-radius: 8px;--input-radius: 6px;--btn-radius: 6px;--el-color-primary: #7170FF;--el-color-primary-light-3: #9899FF;--el-color-primary-light-5: #B5B5FF;--el-color-primary-light-7: #D5D5FF;--el-color-primary-light-8: #E8E8FF;--el-color-primary-light-9: #F4F4FF;--el-color-primary-dark-2: #5B5BD6;--el-color-success: #22A069;--el-color-warning: #C27803;--el-color-danger: #D92D20;--el-color-info: #0A6FD4;--el-border-radius-base: 6px;--el-border-radius-small: 4px;--el-border-radius-round: 9999px;--el-font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;--el-font-size-base: 13px;--el-text-color-primary: var(--text-primary);--el-text-color-regular: var(--text-body);--el-text-color-secondary: var(--text-muted);--el-text-color-placeholder: var(--text-placeholder);--el-border-color: var(--border);--el-border-color-light: var(--border);--el-border-color-lighter: var(--border);--el-fill-color-blank: var(--bg-surface);--el-fill-color-light: var(--bg-subtle);--el-fill-color-lighter: var(--bg-base);--el-mask-color: rgba(0, 0, 0, .15);--el-overlay-color: rgba(0, 0, 0, .15)}html.dark{--bg-base: #0D0D0F;--bg-surface: #18181B;--bg-subtle: #222226;--bg-hover: rgba(255, 255, 255, .05);--bg-active: rgba(255, 255, 255, .08);--bg-sidebar: #111114;--bg-topbar: #18181B;--border: rgba(255, 255, 255, .08);--border-subtle: rgba(255, 255, 255, .05);--border-strong: rgba(255, 255, 255, .14);--text-primary: #FAFAFA;--text-body: #C4C4C8;--text-muted: #71717A;--text-placeholder: #52525B;--text-inverse: #09090B;--accent: #7170FF;--accent-hover: #8284FF;--accent-light: rgba(113, 112, 255, .12);--accent-muted: rgba(113, 112, 255, .2);--success: #22C55E;--success-bg: rgba(34, 197, 94, .1);--success-border: rgba(34, 197, 94, .22);--warning: #D97706;--warning-bg: rgba(217, 119, 6, .1);--warning-border: rgba(217, 119, 6, .22);--danger: #EF4444;--danger-bg: rgba(239, 68, 68, .1);--danger-border: rgba(239, 68, 68, .22);--info: #3B82F6;--info-bg: rgba(59, 130, 246, .1);--info-border: rgba(59, 130, 246, .22);--shadow-sm: 0 1px 4px rgba(0, 0, 0, .3), 0 0 0 1px rgba(255, 255, 255, .04);--shadow-md: 0 2px 8px rgba(0, 0, 0, .4), 0 0 0 1px rgba(255, 255, 255, .04);--el-color-primary: #7170FF;--el-color-primary-light-3: #9899FF;--el-color-primary-light-5: #B5B5FF;--el-color-primary-light-7: #D5D5FF;--el-color-primary-light-8: #5B5BD6;--el-color-primary-light-9: #18181B;--el-color-primary-dark-2: #5B5BD6;--el-bg-color: #18181B;--el-bg-color-overlay: #222226;--el-fill-color-blank: #18181B;--el-text-color-primary: var(--text-primary);--el-text-color-regular: var(--text-body);--el-text-color-secondary: var(--text-muted);--el-text-color-placeholder: var(--text-placeholder);--el-border-color: var(--border);--el-border-color-light: var(--border);--el-border-color-lighter: var(--border);--el-fill-color-light: #222226;--el-fill-color-lighter: #18181B;--el-mask-color: rgba(0, 0, 0, .65);--el-overlay-color: rgba(0, 0, 0, .55)}*,*:before,*:after{box-sizing:border-box}html{font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,sans-serif;font-size:14px;line-height:1.5;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-rendering:optimizeLegibility;font-feature-settings:"cv01","ss03"}html,body{height:100%;margin:0;padding:0}body{background:var(--bg-base);color:var(--text-body);transition:background .18s ease,color .18s ease}::-webkit-scrollbar{width:5px;height:5px}::-webkit-scrollbar-thumb{background:var(--border-strong);border-radius:3px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb:hover{background:var(--text-muted)}::selection{background:var(--accent-light);color:var(--accent)}html.dark ::selection{background:#7170ff40;color:var(--text-primary)}.font-mono{font-family:JetBrains Mono,SF Mono,Consolas,monospace;font-feature-settings:normal;font-variant-numeric:tabular-nums}.font-medium{font-weight:510}.font-semibold{font-weight:590}.font-bold{font-weight:700}.text-primary{color:var(--text-primary)}.text-body{color:var(--text-body)}.text-muted{color:var(--text-muted)}.text-accent{color:var(--accent)}.text-success{color:var(--success)}.text-warning{color:var(--warning)}.text-danger{color:var(--danger)}.text-info{color:var(--info)}.pg-card{background:var(--bg-surface);border:1px solid var(--border);border-radius:var(--card-radius);transition:background .15s ease,border-color .15s ease}.pg-card:hover{border-color:var(--border-strong)}html.dark .pg-card:hover{border-color:#ffffff1f}.pg-card-header{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 16px;border-bottom:1px solid var(--border-subtle);font-size:13px;font-weight:590;color:var(--text-primary)}.pg-card-body{padding:16px}.el-table{--el-table-bg-color: transparent;--el-table-tr-bg-color: transparent;--el-table-header-bg-color: var(--bg-subtle);--el-table-row-hover-bg-color: var(--bg-hover);--el-table-border-color: var(--border-subtle);--el-table-text-color: var(--text-body);--el-table-header-text-color: var(--text-muted);border:none!important;border-radius:0}.el-table__inner-wrapper:before{display:none}.el-table th.el-table__cell{font-size:11px;font-weight:590;letter-spacing:.05em;text-transform:uppercase;color:var(--text-muted)!important;background:var(--bg-subtle)!important;border-bottom:1px solid var(--border-subtle)!important;padding:8px 12px!important}.el-table td.el-table__cell{font-size:13px;color:var(--text-body);padding:10px 12px!important;border-bottom:1px solid var(--border-subtle)!important}.el-table__body tr:hover>td.el-table__cell{background:var(--bg-hover)!important}.el-table__body tr:last-child>td.el-table__cell{border-bottom:none!important}.el-table__empty-text{color:var(--text-muted);font-size:13px}.el-tag{border-radius:4px!important;font-size:11px!important;font-weight:590!important;letter-spacing:.02em;padding:0 6px!important;height:20px!important;line-height:18px!important;border:1px solid!important}.el-tag--success{background:var(--success-bg)!important;border-color:var(--success-border)!important;color:var(--success)!important}.el-tag--warning{background:var(--warning-bg)!important;border-color:var(--warning-border)!important;color:var(--warning)!important}.el-tag--danger{background:var(--danger-bg)!important;border-color:var(--danger-border)!important;color:var(--danger)!important}.el-tag--info{background:var(--info-bg)!important;border-color:var(--info-border)!important;color:var(--info)!important}html.dark .el-tag--success{background:var(--success-bg)!important;color:#4ade80!important}html.dark .el-tag--danger{background:var(--danger-bg)!important;color:#f87171!important}.el-button{border-radius:var(--btn-radius)!important;font-weight:510!important;font-size:13px!important;height:32px!important;padding:0 14px!important;transition:all .12s cubic-bezier(.16,1,.3,1)!important;border:1px solid var(--border)!important;background:var(--bg-surface)!important;color:var(--text-body)!important}.el-button:hover{border-color:var(--border-strong)!important;background:var(--bg-hover)!important;color:var(--text-primary)!important}.el-button:active{transform:scale(.98)}.el-button--primary{background:var(--accent)!important;border-color:var(--accent)!important;color:var(--text-inverse)!important}.el-button--primary:hover{background:var(--accent-hover)!important;border-color:var(--accent-hover)!important;color:var(--text-inverse)!important}.el-button--primary:active{background:var(--accent)!important;transform:scale(.98)}.el-button--danger{background:var(--danger)!important;border-color:var(--danger)!important;color:var(--text-inverse)!important}.el-button--small{height:28px!important;padding:0 10px!important;font-size:12px!important}.el-button--small.is-icon--inline{width:28px!important;padding:0!important}.el-button--text{border:none!important;background:transparent!important;color:var(--accent)!important;padding:0 6px!important}.el-button--text:hover{background:var(--accent-light)!important;color:var(--accent-hover)!important}.el-input__wrapper{border-radius:var(--input-radius)!important;box-shadow:0 0 0 1px var(--border) inset!important;background:var(--bg-subtle)!important;transition:box-shadow .12s ease!important;padding:0 12px!important}.el-input__wrapper:hover{box-shadow:0 0 0 1px var(--border-strong) inset!important}.el-input__wrapper.is-focus{box-shadow:0 0 0 2px var(--accent-light),0 0 0 1px var(--accent) inset!important;background:var(--bg-surface)!important}.el-input__inner{font-size:13px!important;color:var(--text-primary)!important}.el-input__prefix .el-icon{color:var(--text-muted)!important}.el-select .el-input__wrapper{border-radius:var(--input-radius)!important}.el-dialog{border-radius:10px!important;border:1px solid var(--border)!important;box-shadow:var(--shadow-md)!important;overflow:hidden}.el-dialog__header{padding:16px 20px 12px!important;border-bottom:1px solid var(--border-subtle)!important;margin-right:0!important}.el-dialog__title{font-weight:590;font-size:14px;color:var(--text-primary)}.el-dialog__body{padding:20px!important}.el-dialog__footer{padding:12px 20px 16px!important;border-top:1px solid var(--border-subtle)!important}.el-drawer__header{padding:16px 20px 12px!important;border-bottom:1px solid var(--border-subtle)!important;margin-bottom:0!important;font-weight:590;font-size:14px;color:var(--text-primary)}.el-drawer__body{padding:16px 20px!important}.el-pagination{--el-pagination-hover-color: var(--accent);font-weight:510;font-size:13px}.el-pagination.is-background .el-pager li:not(.is-disabled).is-active{background:var(--accent)!important;color:var(--text-inverse)!important}.el-pager li{font-weight:510;border-radius:4px}.el-tabs__nav-wrap:after{display:none}.el-tabs__item{font-weight:510;font-size:13px;color:var(--text-muted);transition:color .12s ease}.el-tabs__item:hover{color:var(--text-primary)}.el-tabs__item.is-active{color:var(--accent);font-weight:590}.el-tabs__active-bar{background:var(--accent);transition:all .18s cubic-bezier(.16,1,.3,1)}.el-alert{border-radius:6px!important;border:1px solid!important;padding:10px 14px!important}.el-alert--success{background:var(--success-bg)!important;border-color:var(--success-border)!important;color:var(--success)!important}.el-alert--warning{background:var(--warning-bg)!important;border-color:var(--warning-border)!important;color:var(--warning)!important}.el-alert--error{background:var(--danger-bg)!important;border-color:var(--danger-border)!important;color:var(--danger)!important}.el-alert--info{background:var(--info-bg)!important;border-color:var(--info-border)!important;color:var(--info)!important}.el-alert__title{font-size:12px!important;font-weight:500!important}html.dark .el-alert--success{color:#4ade80!important}html.dark .el-alert--error{color:#f87171!important}.el-form-item__label{font-weight:510;font-size:13px;color:var(--text-body)}.el-descriptions__cell{padding:8px 12px!important}.el-descriptions__label{color:var(--text-muted)!important;font-weight:510;font-size:12px}.el-descriptions__content{color:var(--text-body);font-size:13px}.el-progress__text{font-weight:590;font-size:11px!important}.el-progress-bar__outer{background:var(--bg-subtle)!important}.el-step__icon{border:none!important}.el-timeline-item__node{background:var(--accent)!important;border-color:var(--accent)!important}.el-empty__description{color:var(--text-muted);font-size:13px}.el-popper.is-light{border:1px solid var(--border)!important;border-radius:6px!important;background:var(--bg-surface)!important;color:var(--text-body)!important;font-size:12px!important;box-shadow:var(--shadow-sm)!important}.el-popper.is-light .el-popper__arrow:before{background:var(--bg-surface)!important;border-color:var(--border)!important}.el-message{border-radius:8px!important;border:1px solid var(--border)!important;background:var(--bg-surface)!important;box-shadow:var(--shadow-md)!important}.el-notification{border-radius:8px!important;border:1px solid var(--border)!important;background:var(--bg-surface)!important}.el-radio-button__inner{border-radius:4px!important;font-weight:510!important;font-size:12px!important}.el-radio-button__original-radio:checked+.el-radio-button__inner{background-color:var(--accent)!important;border-color:var(--accent)!important;color:var(--text-inverse)!important;box-shadow:none!important}.el-switch.is-checked .el-switch__core{background-color:var(--accent)!important;border-color:var(--accent)!important}.el-dropdown-menu{border:1px solid var(--border)!important;border-radius:8px!important;background:var(--bg-surface)!important;box-shadow:var(--shadow-md)!important;padding:4px!important}.el-dropdown-menu__item{font-size:13px!important;font-weight:510!important;color:var(--text-body)!important;border-radius:4px!important;padding:6px 10px!important}.el-dropdown-menu__item:hover{background:var(--bg-hover)!important;color:var(--text-primary)!important}.el-date-editor{border-radius:var(--input-radius)!important}.el-picker-panel{border:1px solid var(--border)!important;border-radius:8px!important;background:var(--bg-surface)!important}.mt8{margin-top:8px}.mt10{margin-top:10px}.mt12{margin-top:12px}.mt16{margin-top:16px}.mb8{margin-bottom:8px}.mb12{margin-bottom:12px}.mb16{margin-bottom:16px}.gap8{gap:8px}.gap12{gap:12px}.gap16{gap:16px} diff --git a/internal/admin/static/assets/index-Bdo7HwZH.js b/internal/admin/static/assets/index-Bdo7HwZH.js new file mode 100644 index 0000000..2b9396e --- /dev/null +++ b/internal/admin/static/assets/index-Bdo7HwZH.js @@ -0,0 +1,127 @@ +var uV=(e,t)=>()=>(t||e((t={exports:{}}).exports,t),t.exports);var lze=uV((no,ao)=>{(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const r of document.querySelectorAll('link[rel="modulepreload"]'))a(r);new MutationObserver(r=>{for(const o of r)if(o.type==="childList")for(const i of o.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&a(i)}).observe(document,{childList:!0,subtree:!0});function n(r){const o={};return r.integrity&&(o.integrity=r.integrity),r.referrerPolicy&&(o.referrerPolicy=r.referrerPolicy),r.crossOrigin==="use-credentials"?o.credentials="include":r.crossOrigin==="anonymous"?o.credentials="omit":o.credentials="same-origin",o}function a(r){if(r.ep)return;r.ep=!0;const o=n(r);fetch(r.href,o)}})();/** +* @vue/shared v3.5.41 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/function $3(e){const t=Object.create(null);for(const n of e.split(","))t[n]=1;return n=>n in t}const jn={},hd=[],kt=()=>{},cE=()=>!1,a1=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),r1=e=>e.startsWith("onUpdate:"),ka=Object.assign,R3=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},cV=Object.prototype.hasOwnProperty,en=(e,t)=>cV.call(e,t),Ae=Array.isArray,pd=e=>zh(e)==="[object Map]",o1=e=>zh(e)==="[object Set]",Is=e=>zh(e)==="[object Date]",Qe=e=>typeof e=="function",Ye=e=>typeof e=="string",io=e=>typeof e=="symbol",_t=e=>e!==null&&typeof e=="object",Ls=e=>(_t(e)||Qe(e))&&Qe(e.then)&&Qe(e.catch),dE=Object.prototype.toString,zh=e=>dE.call(e),dV=e=>zh(e).slice(8,-1),qv=e=>zh(e)==="[object Object]",i1=e=>Ye(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,Sv=$3(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),l1=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},fV=/-\w/g,Va=l1(e=>e.replace(fV,t=>t.slice(1).toUpperCase())),vV=/\B([A-Z])/g,$l=l1(e=>e.replace(vV,"-$1").toLowerCase()),Fh=l1(e=>e.charAt(0).toUpperCase()+e.slice(1)),Wu=l1(e=>e?`on${Fh(e)}`:""),Mi=(e,t)=>!Object.is(e,t),G0=(e,...t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:a,value:n})},N3=e=>{const t=parseFloat(e);return isNaN(t)?e:t},hV=e=>{const t=Ye(e)?Number(e):NaN;return isNaN(t)?e:t};let TS;const s1=()=>TS||(TS=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function et(e){if(Ae(e)){const t={};for(let n=0;n{if(n){const a=n.split(mV);a.length>1&&(t[a[0].trim()]=a[1].trim())}}),t}function U(e){let t="";if(Ye(e))t=e;else if(Ae(e))for(let n=0;nrc(n,t))}const pE=e=>!!(e&&e.__v_isRef===!0),Q=e=>Ye(e)?e:e==null?"":Ae(e)||_t(e)&&(e.toString===dE||!Qe(e.toString))?pE(e)?Q(e.value):JSON.stringify(e,mE,2):String(e),mE=(e,t)=>pE(t)?mE(e,t.value):pd(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((n,[a,r],o)=>(n[k2(a,o)+" =>"]=r,n),{})}:o1(t)?{[`Set(${t.size})`]:[...t.values()].map(n=>k2(n))}:io(t)?k2(t):_t(t)&&!Ae(t)&&!qv(t)?String(t):t,k2=(e,t="")=>{var n;return io(e)?`Symbol(${(n=e.description)!=null?n:t})`:e};/** +* @vue/reactivity v3.5.41 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/let La;class gE{constructor(t=!1){this.detached=t,this._active=!0,this._on=0,this.effects=[],this.cleanups=[],this._isPaused=!1,this._warnOnRun=!0,this.__v_skip=!0,!t&&La&&(La.active?(this.parent=La,this.index=(La.scopes||(La.scopes=[])).push(this)-1):(this._active=!1,this._warnOnRun=!1))}get active(){return this._active}pause(){if(this._active){this._isPaused=!0;let t,n;if(this.scopes){const a=this.scopes.slice();for(t=0,n=a.length;t0&&--this._on===0){if(La===this)La=this.prevScope;else{let t=La;for(;t;){if(t.prevScope===this){t.prevScope=this.prevScope;break}t=t.prevScope}}this.prevScope=void 0}}stop(t){if(this._active){this._active=!1;let n,a;for(n=0,a=this.effects.length;n0)return;if(xv){let t=xv;for(xv=void 0;t;){const n=t.next;t.next=void 0,t.flags&=-9,t=n}}let e;for(;Cv;){let t=Cv;for(Cv=void 0;t;){const n=t.next;if(t.next=void 0,t.flags&=-9,t.flags&1)try{t.trigger()}catch(a){e||(e=a)}t=n}}if(e)throw e}function SE(e){for(let t=e.deps;t;t=t.nextDep)t.version=-1,t.prevActiveLink=t.dep.activeLink,t.dep.activeLink=t}function CE(e){let t,n=e.depsTail,a=n;for(;a;){const r=a.prevDep;a.version===-1?(a===n&&(n=r),F3(a),CV(a)):t=a,a.dep.activeLink=a.prevActiveLink,a.prevActiveLink=void 0,a=r}e.deps=t,e.depsTail=n}function Gb(e){for(let t=e.deps;t;t=t.nextDep)if(t.dep.version!==t.version||t.dep.computed&&(xE(t.dep.computed)||t.dep.version!==t.version))return!0;return!!e._dirty}function xE(e){if(e.flags&4&&!(e.flags&16)||(e.flags&=-17,e.globalVersion===Xv)||(e.globalVersion=Xv,!e.isSSR&&e.flags&128&&(!e.deps&&!e._dirty||!Gb(e))))return;e.flags|=2;const t=e.dep,n=ta,a=qo;ta=e,qo=!0;try{SE(e);const r=e.fn(e._value);(t.version===0||Mi(r,e._value))&&(e.flags|=128,e._value=r,t.version++)}catch(r){throw t.version++,r}finally{ta=n,qo=a,CE(e),e.flags&=-3}}function F3(e,t=!1){const{dep:n,prevSub:a,nextSub:r}=e;if(a&&(a.nextSub=r,e.prevSub=void 0),r&&(r.prevSub=a,e.nextSub=void 0),n.subs===e&&(n.subs=a,!a&&n.computed)){n.computed.flags&=-5;for(let o=n.computed.deps;o;o=o.nextDep)F3(o,!0)}!t&&!--n.sc&&n.map&&n.map.delete(n.key)}function CV(e){const{prevDep:t,nextDep:n}=e;t&&(t.nextDep=n,e.prevDep=void 0),n&&(n.prevDep=t,e.nextDep=void 0)}let qo=!0;const kE=[];function kl(){kE.push(qo),qo=!1}function Tl(){const e=kE.pop();qo=e===void 0?!0:e}function MS(e){const{cleanup:t}=e;if(e.cleanup=void 0,t){const n=ta;ta=void 0;try{t()}finally{ta=n}}}let Xv=0;class xV{constructor(t,n){this.sub=t,this.dep=n,this.version=n.version,this.nextDep=this.prevDep=this.nextSub=this.prevSub=this.prevActiveLink=void 0}}class H3{constructor(t){this.computed=t,this.version=0,this.activeLink=void 0,this.subs=void 0,this.map=void 0,this.key=void 0,this.sc=0,this.__v_skip=!0}track(t){if(!ta||!qo||ta===this.computed)return;let n=this.activeLink;if(n===void 0||n.sub!==ta)n=this.activeLink=new xV(ta,this),ta.deps?(n.prevDep=ta.depsTail,ta.depsTail.nextDep=n,ta.depsTail=n):ta.deps=ta.depsTail=n,TE(n);else if(n.version===-1&&(n.version=this.version,n.nextDep)){const a=n.nextDep;a.prevDep=n.prevDep,n.prevDep&&(n.prevDep.nextDep=a),n.prevDep=ta.depsTail,n.nextDep=void 0,ta.depsTail.nextDep=n,ta.depsTail=n,ta.deps===n&&(ta.deps=a)}return n}trigger(t){this.version++,Xv++,this.notify(t)}notify(t){V3();try{for(let n=this.subs;n;n=n.prevSub)n.sub.notify()&&n.sub.dep.notify()}finally{z3()}}}function TE(e){if(e.dep.sc++,e.sub.flags&4){const t=e.dep.computed;if(t&&!e.dep.subs){t.flags|=20;for(let a=t.deps;a;a=a.nextDep)TE(a)}const n=e.dep.subs;n!==e&&(e.prevSub=n,n&&(n.nextSub=e)),e.dep.subs=e}}const Im=new WeakMap,Ku=Symbol(""),Yb=Symbol(""),Zv=Symbol("");function nr(e,t,n){if(qo&&ta){let a=Im.get(e);a||Im.set(e,a=new Map);let r=a.get(n);r||(a.set(n,r=new H3),r.map=a,r.key=n),r.track()}}function hl(e,t,n,a,r,o){const i=Im.get(e);if(!i){Xv++;return}const l=s=>{s&&s.trigger()};if(V3(),t==="clear")i.forEach(l);else{const s=Ae(e),u=s&&i1(n);if(s&&n==="length"){const c=Number(a);i.forEach((d,v)=>{(v==="length"||v===Zv||!io(v)&&v>=c)&&l(d)})}else switch((n!==void 0||i.has(void 0))&&l(i.get(n)),u&&l(i.get(Zv)),t){case"add":s?u&&l(i.get("length")):(l(i.get(Ku)),pd(e)&&l(i.get(Yb)));break;case"delete":s||(l(i.get(Ku)),pd(e)&&l(i.get(Yb)));break;case"set":pd(e)&&l(i.get(Ku));break}}z3()}function kV(e,t){const n=Im.get(e);return n&&n.get(t)}function Pc(e){const t=wn(e);return t===e?t:(nr(t,"iterate",Zv),ro(e)?t:t.map(Qo))}function u1(e){return nr(e=wn(e),"iterate",Zv),e}function ki(e,t){return Ml(e)?Pd(Uu(e)?Qo(t):t):Qo(t)}const TV={__proto__:null,[Symbol.iterator](){return M2(this,Symbol.iterator,e=>ki(this,e))},concat(...e){return Pc(this).concat(...e.map(t=>Ae(t)?Pc(t):t))},entries(){return M2(this,"entries",e=>(e[1]=ki(this,e[1]),e))},every(e,t){return ol(this,"every",e,t,void 0,arguments)},filter(e,t){return ol(this,"filter",e,t,n=>n.map(a=>ki(this,a)),arguments)},find(e,t){return ol(this,"find",e,t,n=>ki(this,n),arguments)},findIndex(e,t){return ol(this,"findIndex",e,t,void 0,arguments)},findLast(e,t){return ol(this,"findLast",e,t,n=>ki(this,n),arguments)},findLastIndex(e,t){return ol(this,"findLastIndex",e,t,void 0,arguments)},forEach(e,t){return ol(this,"forEach",e,t,void 0,arguments)},includes(...e){return E2(this,"includes",e)},indexOf(...e){return E2(this,"indexOf",e)},join(e){return Pc(this).join(e)},lastIndexOf(...e){return E2(this,"lastIndexOf",e)},map(e,t){return ol(this,"map",e,t,void 0,arguments)},pop(){return Tf(this,"pop")},push(...e){return Tf(this,"push",e)},reduce(e,...t){return ES(this,"reduce",e,t)},reduceRight(e,...t){return ES(this,"reduceRight",e,t)},shift(){return Tf(this,"shift")},some(e,t){return ol(this,"some",e,t,void 0,arguments)},splice(...e){return Tf(this,"splice",e)},toReversed(){return Pc(this).toReversed()},toSorted(e){return Pc(this).toSorted(e)},toSpliced(...e){return Pc(this).toSpliced(...e)},unshift(...e){return Tf(this,"unshift",e)},values(){return M2(this,"values",e=>ki(this,e))}};function M2(e,t,n){const a=u1(e),r=a[t]();return a!==e&&!ro(e)&&(r._next=r.next,r.next=()=>{const o=r._next();return o.done||(o.value=n(o.value)),o}),r}const MV=Array.prototype;function ol(e,t,n,a,r,o){const i=u1(e),l=i!==e&&!ro(e),s=i[t];if(s!==MV[t]){const d=s.apply(e,o);return l?Qo(d):d}let u=n;i!==e&&(l?u=function(d,v){return n.call(this,ki(e,d),v,e)}:n.length>2&&(u=function(d,v){return n.call(this,d,v,e)}));const c=s.call(i,u,a);return l&&r?r(c):c}function ES(e,t,n,a){const r=u1(e),o=r!==e&&!ro(e);let i=n,l=!1;r!==e&&(o?(l=a.length===0,i=function(u,c,d){return l&&(l=!1,u=ki(e,u)),n.call(this,u,ki(e,c),d,e)}):n.length>3&&(i=function(u,c,d){return n.call(this,u,c,d,e)}));const s=r[t](i,...a);return l?ki(e,s):s}function E2(e,t,n){const a=wn(e);nr(a,"iterate",Zv);const r=a[t](...n);return(r===-1||r===!1)&&f1(n[0])?(n[0]=wn(n[0]),a[t](...n)):r}function Tf(e,t,n=[]){kl(),V3();const a=wn(e)[t].apply(e,n);return z3(),Tl(),a}const EV=$3("__proto__,__v_isRef,__isVue"),ME=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!=="arguments"&&e!=="caller").map(e=>Symbol[e]).filter(io));function AV(e){io(e)||(e=String(e));const t=wn(this);return nr(t,"has",e),t.hasOwnProperty(e)}class EE{constructor(t=!1,n=!1){this._isReadonly=t,this._isShallow=n}get(t,n,a){if(n==="__v_skip")return t.__v_skip;const r=this._isReadonly,o=this._isShallow;if(n==="__v_isReactive")return!r;if(n==="__v_isReadonly")return r;if(n==="__v_isShallow")return o;if(n==="__v_raw")return a===(r?o?OE:DE:o?PE:LE).get(t)||Object.getPrototypeOf(t)===Object.getPrototypeOf(a)?t:void 0;const i=Ae(t);if(!r){let s;if(i&&(s=TV[n]))return s;if(n==="hasOwnProperty")return AV}const l=Reflect.get(t,n,xn(t)?t:a);if((io(n)?ME.has(n):EV(n))||(r||nr(t,"get",n),o))return l;if(xn(l)){const s=i&&i1(n)?l:l.value;return r&&_t(s)?Ld(s):s}return _t(l)?r?Ld(l):nn(l):l}}class AE extends EE{constructor(t=!1){super(!1,t)}set(t,n,a,r){let o=t[n];const i=Ae(t)&&i1(n);if(!this._isShallow){const u=Ml(o);if(!ro(a)&&!Ml(a)&&(o=wn(o),a=wn(a)),!i&&xn(o)&&!xn(a))return u||(o.value=a),!0}const l=i?Number(n)e,Mp=e=>Reflect.getPrototypeOf(e);function OV(e,t,n){return function(...a){const r=this.__v_raw,o=wn(r),i=pd(o),l=e==="entries"||e===Symbol.iterator&&i,s=e==="keys"&&i,u=r[e](...a),c=n?jb:t?Pd:Qo;return!t&&nr(o,"iterate",s?Yb:Ku),ka(Object.create(u),{next(){const{value:d,done:v}=u.next();return v?{value:d,done:v}:{value:l?[c(d[0]),c(d[1])]:c(d),done:v}}})}}function Ep(e){return function(...t){return e==="delete"?!1:e==="clear"?void 0:this}}function $V(e,t){const n={get(r){const o=this.__v_raw,i=wn(o),l=wn(r);e||(Mi(r,l)&&nr(i,"get",r),nr(i,"get",l));const{has:s}=Mp(i),u=t?jb:e?Pd:Qo;if(s.call(i,r))return u(o.get(r));if(s.call(i,l))return u(o.get(l));o!==i&&o.get(r)},get size(){const r=this.__v_raw;return!e&&nr(wn(r),"iterate",Ku),r.size},has(r){const o=this.__v_raw,i=wn(o),l=wn(r);return e||(Mi(r,l)&&nr(i,"has",r),nr(i,"has",l)),r===l?o.has(r):o.has(r)||o.has(l)},forEach(r,o){const i=this,l=i.__v_raw,s=wn(l),u=t?jb:e?Pd:Qo;return!e&&nr(s,"iterate",Ku),l.forEach((c,d)=>r.call(o,u(c),u(d),i))}};return ka(n,e?{add:Ep("add"),set:Ep("set"),delete:Ep("delete"),clear:Ep("clear")}:{add(r){const o=wn(this),i=Mp(o),l=wn(r),s=!t&&!ro(r)&&!Ml(r)?l:r;return i.has.call(o,s)||Mi(r,s)&&i.has.call(o,r)||Mi(l,s)&&i.has.call(o,l)||(o.add(s),hl(o,"add",s,s)),this},set(r,o){!t&&!ro(o)&&!Ml(o)&&(o=wn(o));const i=wn(this),{has:l,get:s}=Mp(i);let u=l.call(i,r);u||(r=wn(r),u=l.call(i,r));const c=s.call(i,r);return i.set(r,o),u?Mi(o,c)&&hl(i,"set",r,o):hl(i,"add",r,o),this},delete(r){const o=wn(this),{has:i,get:l}=Mp(o);let s=i.call(o,r);s||(r=wn(r),s=i.call(o,r)),l&&l.call(o,r);const u=o.delete(r);return s&&hl(o,"delete",r,void 0),u},clear(){const r=wn(this),o=r.size!==0,i=r.clear();return o&&hl(r,"clear",void 0,void 0),i}}),["keys","values","entries",Symbol.iterator].forEach(r=>{n[r]=OV(r,e,t)}),n}function c1(e,t){const n=$V(e,t);return(a,r,o)=>r==="__v_isReactive"?!e:r==="__v_isReadonly"?e:r==="__v_raw"?a:Reflect.get(en(n,r)&&r in a?n:a,r,o)}const RV={get:c1(!1,!1)},NV={get:c1(!1,!0)},BV={get:c1(!0,!1)},VV={get:c1(!0,!0)},LE=new WeakMap,PE=new WeakMap,DE=new WeakMap,OE=new WeakMap;function zV(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function nn(e){return Ml(e)?e:d1(e,!1,IV,RV,LE)}function W3(e){return d1(e,!1,PV,NV,PE)}function Ld(e){return d1(e,!0,LV,BV,DE)}function K3(e){return d1(e,!0,DV,VV,OE)}function d1(e,t,n,a,r){if(!_t(e)||e.__v_raw&&!(t&&e.__v_isReactive)||e.__v_skip||!Object.isExtensible(e))return e;const o=r.get(e);if(o)return o;const i=zV(dV(e));if(i===0)return e;const l=new Proxy(e,i===2?a:n);return r.set(e,l),l}function Uu(e){return Ml(e)?Uu(e.__v_raw):!!(e&&e.__v_isReactive)}function Ml(e){return!!(e&&e.__v_isReadonly)}function ro(e){return!!(e&&e.__v_isShallow)}function f1(e){return e?!!e.__v_raw:!1}function wn(e){const t=e&&e.__v_raw;return t?wn(t):e}function $i(e){return!en(e,"__v_skip")&&Object.isExtensible(e)&&fE(e,"__v_skip",!0),e}const Qo=e=>_t(e)?nn(e):e,Pd=e=>_t(e)?Ld(e):e;function xn(e){return e?e.__v_isRef===!0:!1}function F(e){return $E(e,!1)}function Ht(e){return $E(e,!0)}function $E(e,t){return xn(e)?e:new FV(e,t)}class FV{constructor(t,n){this.dep=new H3,this.__v_isRef=!0,this.__v_isShallow=!1,this._rawValue=n?t:wn(t),this._value=n?t:Qo(t),this.__v_isShallow=n}get value(){return this.dep.track(),this._value}set value(t){const n=this._rawValue,a=this.__v_isShallow||ro(t)||Ml(t);t=a?t:wn(t),Mi(t,n)&&(this._rawValue=t,this._value=a?t:Qo(t),this.dep.trigger())}}function Y0(e){e.dep&&e.dep.trigger()}function p(e){return xn(e)?e.value:e}function ia(e){return Qe(e)?e():p(e)}const HV={get:(e,t,n)=>t==="__v_raw"?e:p(Reflect.get(e,t,n)),set:(e,t,n,a)=>{const r=e[t];return xn(r)&&!xn(n)?(r.value=n,!0):Reflect.set(e,t,n,a)}};function RE(e){return Uu(e)?e:new Proxy(e,HV)}function Ta(e){const t=Ae(e)?new Array(e.length):{};for(const n in e)t[n]=NE(e,n);return t}class WV{constructor(t,n,a){this._object=t,this._defaultValue=a,this.__v_isRef=!0,this._value=void 0,this._key=io(n)?n:String(n),this._raw=wn(t);let r=!0,o=t;if(!Ae(t)||io(this._key)||!i1(this._key))do r=!f1(o)||ro(o);while(r&&(o=o.__v_raw));this._shallow=r}get value(){let t=this._object[this._key];return this._shallow&&(t=p(t)),this._value=t===void 0?this._defaultValue:t}set value(t){if(this._shallow&&xn(this._raw[this._key])){const n=this._object[this._key];if(xn(n)){n.value=t;return}}this._object[this._key]=t}get dep(){return kV(this._raw,this._key)}}class KV{constructor(t){this._getter=t,this.__v_isRef=!0,this.__v_isReadonly=!0,this._value=void 0}get value(){return this._value=this._getter()}}function cn(e,t,n){return xn(e)?e:Qe(e)?new KV(e):_t(e)&&arguments.length>1?NE(e,t,n):F(e)}function NE(e,t,n){return new WV(e,t,n)}class UV{constructor(t,n,a){this.fn=t,this.setter=n,this._value=void 0,this.dep=new H3(this),this.__v_isRef=!0,this.deps=void 0,this.depsTail=void 0,this.flags=16,this.globalVersion=Xv-1,this.next=void 0,this.effect=this,this.__v_isReadonly=!n,this.isSSR=a}notify(){if(this.flags|=16,!(this.flags&8)&&ta!==this)return wE(this,!0),!0}get value(){const t=this.dep.track();return xE(this),t&&(t.version=this.dep.version),this._value}set value(t){this.setter&&this.setter(t)}}function GV(e,t,n=!1){let a,r;return Qe(e)?a=e:(a=e.get,r=e.set),new UV(a,r,n)}const Ap={},Lm=new WeakMap;let Mu;function YV(e,t=!1,n=Mu){if(n){let a=Lm.get(n);a||Lm.set(n,a=[]),a.push(e)}}function jV(e,t,n=jn){const{immediate:a,deep:r,once:o,scheduler:i,augmentJob:l,call:s}=n,u=w=>r?w:ro(w)||r===!1||r===0?pl(w,1):pl(w);let c,d,v,f,h=!1,m=!1;if(xn(e)?(d=()=>e.value,h=ro(e)):Uu(e)?(d=()=>u(e),h=!0):Ae(e)?(m=!0,h=e.some(w=>Uu(w)||ro(w)),d=()=>e.map(w=>{if(xn(w))return w.value;if(Uu(w))return u(w);if(Qe(w))return s?s(w,2):w()})):Qe(e)?t?d=s?()=>s(e,2):e:d=()=>{if(v){kl();try{v()}finally{Tl()}}const w=Mu;Mu=c;try{return s?s(e,3,[f]):e(f)}finally{Mu=w}}:d=kt,t&&r){const w=d,S=r===!0?1/0:r;d=()=>pl(w(),S)}const g=B3(),y=()=>{c.stop(),g&&g.active&&R3(g.effects,c)};if(o&&t){const w=t;t=(...S)=>{const k=w(...S);return y(),k}}let b=m?new Array(e.length).fill(Ap):Ap;const _=w=>{if(!(!(c.flags&1)||!c.dirty&&!w))if(t){const S=c.run();if(w||r||h||(m?S.some((k,T)=>Mi(k,b[T])):Mi(S,b))){v&&v();const k=Mu;Mu=c;try{const T=[S,b===Ap?void 0:m&&b[0]===Ap?[]:b,f];b=S,s?s(t,3,T):t(...T)}finally{Mu=k}}}else c.run()};return l&&l(_),c=new bE(d),c.scheduler=i?()=>i(_,!1):_,f=w=>YV(w,!1,c),v=c.onStop=()=>{const w=Lm.get(c);if(w){if(s)s(w,4);else for(const S of w)S();Lm.delete(c)}},t?a?_(!0):b=c.run():i?i(_.bind(null,!0),!0):c.run(),y.pause=c.pause.bind(c),y.resume=c.resume.bind(c),y.stop=y,y}function pl(e,t=1/0,n){if(t<=0||!_t(e)||e.__v_skip||(n=n||new Map,(n.get(e)||0)>=t))return e;if(n.set(e,t),t--,xn(e))pl(e.value,t,n);else if(Ae(e))for(let a=0;a{pl(a,t,n)});else if(qv(e)){for(const a in e)pl(e[a],t,n);for(const a of Object.getOwnPropertySymbols(e))Object.prototype.propertyIsEnumerable.call(e,a)&&pl(e[a],t,n)}return e}/** +* @vue/runtime-core v3.5.41 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/function Hh(e,t,n,a){try{return a?e(...a):e()}catch(r){v1(r,t,n)}}function Ro(e,t,n,a){if(Qe(e)){const r=Hh(e,t,n,a);return r&&Ls(r)&&r.catch(o=>{v1(o,t,n)}),r}if(Ae(e)){const r=[];for(let o=0;o>>1,r=br[a],o=Jv(r);o=Jv(n)?br.push(e):br.splice(XV(t),0,e),e.flags|=1,VE()}}function VE(){Pm||(Pm=BE.then(FE))}function ZV(e){if(!Ae(e))ds&&e.id===-1?ds.splice(td+1,0,e):e.flags&1||(md.push(e),e.flags|=1);else for(let t=0;tJv(n)-Jv(a));if(md.length=0,ds){for(let n=0;ne.id==null?e.flags&2?-1:1/0:e.id;function FE(e){try{for(xi=0;xi{a._d&&Nm(-1);const o=Dm(t),i=_l.length;let l;try{l=e(...r)}finally{for(let s=_l.length;s>i;s--)t6();Dm(o),a._d&&Nm(1)}return l};return a._n=!0,a._c=!0,a._d=!0,a}function st(e,t){if(Ga===null)return e;const n=b1(Ga),a=e.dirs||(e.dirs=[]);for(let r=0;r1)return n&&Qe(t)?t.call(a&&a.proxy):t}}function WE(){return!!(Et()||Gu)}const JV=Symbol.for("v-scx"),QV=()=>Ze(JV);function ur(e,t){return G3(e,null,t)}function ye(e,t,n){return G3(e,t,n)}function G3(e,t,n=jn){const{immediate:a,deep:r,flush:o,once:i}=n,l=ka({},n),s=t&&a||!t&&o!=="post";let u;if(nh){if(o==="sync"){const f=QV();u=f.__watcherHandles||(f.__watcherHandles=[])}else if(!s){const f=()=>{};return f.stop=kt,f.resume=kt,f.pause=kt,f}}const c=rr;l.call=(f,h,m)=>Ro(f,c,h,m);let d=!1;o==="post"?l.scheduler=f=>{mr(f,c&&c.suspense)}:o!=="sync"&&(d=!0,l.scheduler=(f,h)=>{h?f():U3(f)}),l.augmentJob=f=>{t&&(f.flags|=4),d&&(f.flags|=2,c&&(f.id=c.uid,f.i=c))};const v=jV(e,t,l);return nh&&(u?u.push(v):s&&v()),v}function ez(e,t,n){const a=this.proxy,r=Ye(e)?e.includes(".")?KE(a,e):()=>a[e]:e.bind(a,a);let o;Qe(t)?o=t:(o=t.handler,n=t);const i=Kh(this),l=G3(r,o.bind(a),n);return i(),l}function KE(e,t){const n=t.split(".");return()=>{let a=e;for(let r=0;re.__isTeleport,Au=e=>e&&(e.disabled||e.disabled===""),tz=e=>e&&(e.defer||e.defer===""),IS=e=>typeof SVGElement<"u"&&e instanceof SVGElement,LS=e=>typeof MathMLElement=="function"&&e instanceof MathMLElement,qb=(e,t)=>{const n=e&&e.to;return Ye(n)?t?t(n):null:n},nz={name:"Teleport",__isTeleport:!0,process(e,t,n,a,r,o,i,l,s,u){const{mc:c,pc:d,pbc:v,o:{insert:f,querySelector:h,createText:m,createComment:g,parentNode:y}}=u,b=Au(t.props);let{dynamicChildren:_}=t;const w=(T,M,E)=>{T.shapeFlag&16&&c(T.children,M,E,r,o,i,l,s)},S=(T=t)=>{const M=Au(T.props),E=T.target=qb(T.props,h),A=Xb(E,T,m,f);E&&(i!=="svg"&&IS(E)?i="svg":i!=="mathml"&&LS(E)&&(i="mathml"),r&&r.isCE&&(r.ce._teleportTargets||(r.ce._teleportTargets=new Set)).add(E),M||(w(T,E,A),tv(T,!1)))},k=T=>{const M=()=>{if(as.get(T)===M){if(as.delete(T),Au(T.props)){const E=y(T.el)||n;w(T,E,T.anchor),tv(T,!0)}S(T)}};as.set(T,M),mr(M,o)};if(e==null){const T=t.el=m(""),M=t.anchor=m("");if(f(T,n,a),f(M,n,a),tz(t.props)||o&&o.pendingBranch){k(t);return}b&&(w(t,n,M),tv(t,!0)),S()}else{t.el=e.el;const T=t.anchor=e.anchor,M=as.get(e);if(M){M.flags|=8,as.delete(e),k(t);return}t.targetStart=e.targetStart;const E=t.target=e.target,A=t.targetAnchor=e.targetAnchor,I=Au(e.props),P=I?n:E,R=I?T:A;if(i==="svg"||IS(E)?i="svg":(i==="mathml"||LS(E))&&(i="mathml"),_?(v(e.dynamicChildren,_,P,r,o,i,l),e6(e,t,!0)):s||d(e,t,P,R,r,o,i,l,!1),b)I?t.props&&e.props&&t.props.to!==e.props.to&&(t.props.to=e.props.to):Ip(t,n,T,u,1);else if((t.props&&t.props.to)!==(e.props&&e.props.to)){const G=qb(t.props,h);G&&(t.target=G,Ip(t,G,null,u,0))}else I&&Ip(t,E,A,u,1);tv(t,b)}},remove(e,t,n,{um:a,o:{remove:r}},o){const{shapeFlag:i,children:l,anchor:s,targetStart:u,targetAnchor:c,target:d,props:v}=e,f=Au(v),h=o||!f,m=as.get(e);if(m&&(m.flags|=8,as.delete(e)),d&&(r(u),r(c)),o&&r(s),!m&&(f||d)&&i&16)for(let g=0;g{e.isMounted=!0}),rn(()=>{e.isUnmounting=!0}),e}const fo=[Function,Array],YE={mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:fo,onEnter:fo,onAfterEnter:fo,onEnterCancelled:fo,onBeforeLeave:fo,onLeave:fo,onAfterLeave:fo,onLeaveCancelled:fo,onBeforeAppear:fo,onAppear:fo,onAfterAppear:fo,onAppearCancelled:fo},jE=e=>{const t=e.subTree;return t.component?jE(t.component):t},rz={name:"BaseTransition",props:YE,setup(e,{slots:t}){const n=Et(),a=GE();return()=>{const r=t.default&&Y3(t.default(),!0),o=r&&r.length?qE(r):n.subTree?te():void 0;if(!o)return;const i=wn(e),{mode:l}=i;if(a.isLeaving)return A2(o);const s=Om(o);if(!s)return A2(o);let u=Qv(s,i,a,n,d=>u=d);s.type!==oa&&oc(s,u);let c=n.subTree&&Om(n.subTree);if(c&&c.type!==oa&&!Iu(c,s)&&jE(n).type!==oa){let d=Qv(c,i,a,n);if(oc(c,d),l==="out-in"&&s.type!==oa)return a.isLeaving=!0,d.afterLeave=()=>{a.isLeaving=!1,n.job.flags&8||n.update(),delete d.afterLeave,c=void 0},A2(o);l==="in-out"&&s.type!==oa?d.delayLeave=(v,f,h)=>{const m=XE(a,c);m[String(c.key)]=c,v[_o]=()=>{f(),v[_o]=void 0,delete u.delayedLeave,c=void 0},u.delayedLeave=()=>{h(),delete u.delayedLeave,c=void 0}}:c=void 0}else c&&(c=void 0);return o}}};function qE(e){let t=e[0];if(e.length>1){for(const n of e)if(n.type!==oa){t=n;break}}return t}const oz=rz;function XE(e,t){const{leavingVNodes:n}=e;let a=n.get(t.type);return a||(a=Object.create(null),n.set(t.type,a)),a}function Qv(e,t,n,a,r){const{appear:o,mode:i,persisted:l=!1,onBeforeEnter:s,onEnter:u,onAfterEnter:c,onEnterCancelled:d,onBeforeLeave:v,onLeave:f,onAfterLeave:h,onLeaveCancelled:m,onBeforeAppear:g,onAppear:y,onAfterAppear:b,onAppearCancelled:_}=t,w=String(e.key),S=XE(n,e),k=(E,A)=>{E&&Ro(E,a,9,A)},T=(E,A)=>{const I=A[1];k(E,A),Ae(E)?E.every(P=>P.length<=1)&&I():E.length<=1&&I()},M={mode:i,persisted:l,beforeEnter(E){let A=s;if(!n.isMounted)if(o)A=g||s;else return;E[_o]&&E[_o](!0);const I=S[w];I&&Iu(e,I)&&I.el[_o]&&I.el[_o](),k(A,[E])},enter(E){if(S[w]===e)return;let A=u,I=c,P=d;if(!n.isMounted)if(o)A=y||u,I=b||c,P=_||d;else return;let R=!1;E[Mf]=K=>{R||(R=!0,K?k(P,[E]):k(I,[E]),M.delayedLeave&&M.delayedLeave(),E[Mf]=void 0)};const G=E[Mf].bind(null,!1);A?T(A,[E,G]):G()},leave(E,A){const I=String(e.key);if(E[Mf]&&E[Mf](!0),n.isUnmounting)return A();k(v,[E]);let P=!1;E[_o]=G=>{P||(P=!0,A(),G?k(m,[E]):k(h,[E]),E[_o]=void 0,S[I]===e&&delete S[I])};const R=E[_o].bind(null,!1);S[I]=e,f?T(f,[E,R]):R()},clone(E){const A=Qv(E,t,n,a,r);return r&&r(A),A}};return M}function A2(e){if(p1(e))return e=ei(e),e.children=null,e}function Om(e){if(!p1(e))return h1(e.type)&&e.children?qE(e.children):e;if(e.component)return e.component.subTree;const{shapeFlag:t,children:n}=e;if(n){if(t&16)return n[0];if(t&32&&Qe(n.default))return n.default()}}function oc(e,t){if(e.shapeFlag&6&&e.component){e.transition=t;const n=e.component.subTree;oc(h1(n.type)&&Om(n)||n,t)}else e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function Y3(e,t=!1,n){let a=[],r=0;for(let o=0;o1)for(let o=0;okv(m,t&&(Ae(t)?t[g]:t),n,a,r));return}if(gd(a)&&!r){a.shapeFlag&512&&a.type.__asyncResolved&&a.component.subTree.component&&kv(e,t,n,a.component.subTree);return}const o=a.shapeFlag&4?b1(a.component):a.el,i=r?null:o,{i:l,r:s}=e,u=t&&t.r,c=l.refs===jn?l.refs={}:l.refs,d=l.setupState,v=wn(d),f=d===jn?cE:m=>PS(c,m)?!1:en(v,m),h=(m,g)=>!(g&&PS(c,g));if(u!=null&&u!==s){if(DS(t),Ye(u))c[u]=null,f(u)&&(d[u]=null);else if(xn(u)){const m=t;h(u,m.k)&&(u.value=null),m.k&&(c[m.k]=null)}}if(Qe(s))Hh(s,l,12,[i,c]);else{const m=Ye(s),g=xn(s);if(m||g){const y=()=>{if(e.f){const b=m?f(s)?d[s]:c[s]:h()||!e.k?s.value:c[e.k];if(r)Ae(b)&&R3(b,o);else if(Ae(b))b.includes(o)||b.push(o);else if(m)c[s]=[o],f(s)&&(d[s]=c[s]);else{const _=[o];h(s,e.k)&&(s.value=_),e.k&&(c[e.k]=_)}}else m?(c[s]=i,f(s)&&(d[s]=i)):g&&(h(s,e.k)&&(s.value=i),e.k&&(c[e.k]=i))};if(i){const b=()=>{y(),$m.delete(e)};b.id=-1,$m.set(e,b),mr(b,n)}else DS(e),y()}}}function DS(e){const t=$m.get(e);t&&(t.flags|=8,$m.delete(e))}s1().requestIdleCallback;s1().cancelIdleCallback;const gd=e=>!!e.type.__asyncLoader,p1=e=>e.type.__isKeepAlive;function Wh(e,t){JE(e,"a",t)}function j3(e,t){JE(e,"da",t)}function JE(e,t,n=rr){const a=e.__wdc||(e.__wdc=()=>{let r=n;for(;r;){if(r.isDeactivated)return;r=r.parent}return e()});if(m1(t,a,n),n){let r=n.parent;for(;r&&r.parent;)p1(r.parent.vnode)&&iz(a,t,n,r),r=r.parent}}function iz(e,t,n,a){const r=m1(t,e,a,!0);Us(()=>{R3(a[t],r)},n)}function m1(e,t,n=rr,a=!1){if(n){const r=n[e]||(n[e]=[]),o=t.__weh||(t.__weh=(...i)=>{kl();const l=Kh(n),s=Ro(t,n,e,i);return l(),Tl(),s});return a?r.unshift(o):r.push(o),o}}const Rl=e=>(t,n=rr)=>{(!nh||e==="sp")&&m1(e,(...a)=>t(...a),n)},g1=Rl("bm"),mt=Rl("m"),q3=Rl("bu"),oi=Rl("u"),rn=Rl("bum"),Us=Rl("um"),lz=Rl("sp"),sz=Rl("rtg"),uz=Rl("rtc");function cz(e,t=rr){m1("ec",e,t)}const X3="components",dz="directives";function be(e,t){return Z3(X3,e,!0,t)||e}const QE=Symbol.for("v-ndc");function St(e){return Ye(e)?Z3(X3,e,!1)||e:e||QE}function Gr(e){return Z3(dz,e)}function Z3(e,t,n=!0,a=!1){const r=Ga||rr;if(r){const o=r.type;if(e===X3){const l=Gz(o,!1);if(l&&(l===t||l===Va(t)||l===Fh(Va(t))))return o}const i=OS(r[e]||o[e],t)||OS(r.appContext[e],t);return!i&&a?o:i}}function OS(e,t){return e&&(e[t]||e[Va(t)]||e[Fh(Va(t))])}function dt(e,t,n,a){let r;const o=n,i=Ae(e);if(i||Ye(e)){const l=i&&Uu(e);let s=!1,u=!1;l&&(s=!ro(e),u=Ml(e),e=u1(e)),r=new Array(e.length);for(let c=0,d=e.length;ct(l,s,void 0,o));else{const l=Object.keys(e);r=new Array(l.length);for(let s=0,u=l.length;s{const o=a.fn(...r);return o&&(o.key=a.key),o}:a.fn)}return e}function ce(e,t,n,a,r,o){if(n==null&&(n={}),Ga.ce||Ga.parent&&gd(Ga.parent)&&Ga.parent.ce){const u=n,c=Object.keys(u).length>0;return t!=="default"&&(u.name=t),x(),ne(Ve,null,[L("slot",u,a&&a())],c?-2:64)}let i=e[t];i&&i._c&&(i._d=!1);const l=_l.length;x();let s;try{const u=i&&e7(i(n)),c=n.key||o||u&&u.key;s=ne(Ve,{key:(c&&!io(c)?c:`_${t}`)+(!u&&a?"_fb":"")},u||(a?a():[]),u&&e._===1?64:-2)}catch(u){for(let c=_l.length;c>l;c--)t6();throw u}finally{i&&i._c&&(i._d=!0)}return s.scopeId&&(s.slotScopeIds=[s.scopeId+"-s"]),s}function e7(e){return e.some(t=>bn(t)?!(t.type===oa||t.type===Ve&&!e7(t.children)):!0)?e:null}function fz(e,t){const n={};for(const a in e)n[Wu(a)]=e[a];return n}const Zb=e=>e?w7(e)?b1(e):Zb(e.parent):null,Tv=ka(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>Zb(e.parent),$root:e=>Zb(e.root),$host:e=>e.ce,$emit:e=>e.emit,$options:e=>a7(e),$forceUpdate:e=>e.f||(e.f=()=>{U3(e.update)}),$nextTick:e=>e.n||(e.n=He.bind(e.proxy)),$watch:e=>ez.bind(e)}),I2=(e,t)=>e!==jn&&!e.__isScriptSetup&&en(e,t),vz={get({_:e},t){if(t==="__v_skip")return!0;const{ctx:n,setupState:a,data:r,props:o,accessCache:i,type:l,appContext:s}=e;if(t[0]!=="$"){const v=i[t];if(v!==void 0)switch(v){case 1:return a[t];case 2:return r[t];case 4:return n[t];case 3:return o[t]}else{if(I2(a,t))return i[t]=1,a[t];if(r!==jn&&en(r,t))return i[t]=2,r[t];if(en(o,t))return i[t]=3,o[t];if(n!==jn&&en(n,t))return i[t]=4,n[t];Jb&&(i[t]=0)}}const u=Tv[t];let c,d;if(u)return t==="$attrs"&&nr(e.attrs,"get",""),u(e);if((c=l.__cssModules)&&(c=c[t]))return c;if(n!==jn&&en(n,t))return i[t]=4,n[t];if(d=s.config.globalProperties,en(d,t))return d[t]},set({_:e},t,n){const{data:a,setupState:r,ctx:o}=e;return I2(r,t)?(r[t]=n,!0):a!==jn&&en(a,t)?(a[t]=n,!0):en(e.props,t)||t[0]==="$"&&t.slice(1)in e?!1:(o[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:a,appContext:r,props:o,type:i}},l){let s;return!!(n[l]||e!==jn&&l[0]!=="$"&&en(e,l)||I2(t,l)||en(o,l)||en(a,l)||en(Tv,l)||en(r.config.globalProperties,l)||(s=i.__cssModules)&&s[l])},defineProperty(e,t,n){return n.get!=null?e._.accessCache[t]=0:en(n,"value")&&this.set(e,t,n.value,null),Reflect.defineProperty(e,t,n)}};function Xn(){return t7().slots}function Nl(){return t7().attrs}function t7(e){const t=Et();return t.setupContext||(t.setupContext=C7(t))}function $S(e){return Ae(e)?e.reduce((t,n)=>(t[n]=null,t),{}):e}let Jb=!0;function hz(e){const t=a7(e),n=e.proxy,a=e.ctx;Jb=!1,t.beforeCreate&&RS(t.beforeCreate,e,"bc");const{data:r,computed:o,methods:i,watch:l,provide:s,inject:u,created:c,beforeMount:d,mounted:v,beforeUpdate:f,updated:h,activated:m,deactivated:g,beforeDestroy:y,beforeUnmount:b,destroyed:_,unmounted:w,render:S,renderTracked:k,renderTriggered:T,errorCaptured:M,serverPrefetch:E,expose:A,inheritAttrs:I,components:P,directives:R,filters:G}=t;if(u&&pz(u,a,null),i)for(const z in i){const B=i[z];Qe(B)&&(a[z]=B.bind(n))}if(r){const z=r.call(n,n);_t(z)&&(e.data=nn(z))}if(Jb=!0,o)for(const z in o){const B=o[z],W=Qe(B)?B.bind(n,n):Qe(B.get)?B.get.bind(n,n):kt,N=!Qe(B)&&Qe(B.set)?B.set.bind(n):kt,V=D({get:W,set:N});Object.defineProperty(a,z,{enumerable:!0,configurable:!0,get:()=>V.value,set:Y=>V.value=Y})}if(l)for(const z in l)n7(l[z],a,n,z);if(s){const z=Qe(s)?s.call(n):s;Reflect.ownKeys(z).forEach(B=>{Ft(B,z[B])})}c&&RS(c,e,"c");function H(z,B){Ae(B)?B.forEach(W=>z(W.bind(n))):B&&z(B.bind(n))}if(H(g1,d),H(mt,v),H(q3,f),H(oi,h),H(Wh,m),H(j3,g),H(cz,M),H(uz,k),H(sz,T),H(rn,b),H(Us,w),H(lz,E),Ae(A))if(A.length){const z=e.exposed||(e.exposed={});A.forEach(B=>{Object.defineProperty(z,B,{get:()=>n[B],set:W=>n[B]=W,enumerable:!0})})}else e.exposed||(e.exposed={});S&&e.render===kt&&(e.render=S),I!=null&&(e.inheritAttrs=I),P&&(e.components=P),R&&(e.directives=R),E&&ZE(e)}function pz(e,t,n=kt){Ae(e)&&(e=Qb(e));for(const a in e){const r=e[a];let o;_t(r)?"default"in r?o=Ze(r.from||a,r.default,!0):o=Ze(r.from||a):o=Ze(r),xn(o)?Object.defineProperty(t,a,{enumerable:!0,configurable:!0,get:()=>o.value,set:i=>o.value=i}):t[a]=o}}function RS(e,t,n){Ro(Ae(e)?e.map(a=>a.bind(t.proxy)):e.bind(t.proxy),t,n)}function n7(e,t,n,a){let r=a.includes(".")?KE(n,a):()=>n[a];if(Ye(e)){const o=t[e];Qe(o)&&ye(r,o)}else if(Qe(e))ye(r,e.bind(n));else if(_t(e))if(Ae(e))e.forEach(o=>n7(o,t,n,a));else{const o=Qe(e.handler)?e.handler.bind(n):t[e.handler];Qe(o)&&ye(r,o,e)}}function a7(e){const t=e.type,{mixins:n,extends:a}=t,{mixins:r,optionsCache:o,config:{optionMergeStrategies:i}}=e.appContext,l=o.get(t);let s;return l?s=l:!r.length&&!n&&!a?s=t:(s={},r.length&&r.forEach(u=>Rm(s,u,i,!0)),Rm(s,t,i)),_t(t)&&o.set(t,s),s}function Rm(e,t,n,a=!1){const{mixins:r,extends:o}=t;o&&Rm(e,o,n,!0),r&&r.forEach(i=>Rm(e,i,n,!0));for(const i in t)if(!(a&&i==="expose")){const l=mz[i]||n&&n[i];e[i]=l?l(e[i],t[i]):t[i]}return e}const mz={data:NS,props:BS,emits:BS,methods:nv,computed:nv,beforeCreate:pr,created:pr,beforeMount:pr,mounted:pr,beforeUpdate:pr,updated:pr,beforeDestroy:pr,beforeUnmount:pr,destroyed:pr,unmounted:pr,activated:pr,deactivated:pr,errorCaptured:pr,serverPrefetch:pr,components:nv,directives:nv,watch:yz,provide:NS,inject:gz};function NS(e,t){return t?e?function(){return ka(Qe(e)?e.call(this,this):e,Qe(t)?t.call(this,this):t)}:t:e}function gz(e,t){return nv(Qb(e),Qb(t))}function Qb(e){if(Ae(e)){const t={};for(let n=0;nt==="modelValue"||t==="model-value"?e.modelModifiers:e[`${t}Modifiers`]||e[`${Va(t)}Modifiers`]||e[`${$l(t)}Modifiers`];function Sz(e,t,...n){if(e.isUnmounted)return;const a=e.vnode.props||jn;let r=n;const o=t.startsWith("update:"),i=o&&wz(a,t.slice(7));i&&(i.trim&&(r=n.map(c=>Ye(c)?c.trim():c)),i.number&&(r=n.map(N3)));let l,s=a[l=Wu(t)]||a[l=Wu(Va(t))];!s&&o&&(s=a[l=Wu($l(t))]),s&&Ro(s,e,6,r);const u=a[l+"Once"];if(u){if(!e.emitted)e.emitted={};else if(e.emitted[l])return;e.emitted[l]=!0,Ro(u,e,6,r)}}const Cz=new WeakMap;function o7(e,t,n=!1){const a=n?Cz:t.emitsCache,r=a.get(e);if(r!==void 0)return r;const o=e.emits;let i={},l=!1;if(!Qe(e)){const s=u=>{const c=o7(u,t,!0);c&&(l=!0,ka(i,c))};!n&&t.mixins.length&&t.mixins.forEach(s),e.extends&&s(e.extends),e.mixins&&e.mixins.forEach(s)}return!o&&!l?(_t(e)&&a.set(e,null),null):(Ae(o)?o.forEach(s=>i[s]=null):ka(i,o),_t(e)&&a.set(e,i),i)}function y1(e,t){return!e||!a1(t)?!1:(t=t.slice(2),t=t==="Once"?t:t.replace(/Once$/,""),en(e,t[0].toLowerCase()+t.slice(1))||en(e,$l(t))||en(e,t))}function VS(e){const{type:t,vnode:n,proxy:a,withProxy:r,propsOptions:[o],slots:i,attrs:l,emit:s,render:u,renderCache:c,props:d,data:v,setupState:f,ctx:h,inheritAttrs:m}=e,g=Dm(e);let y,b;try{if(n.shapeFlag&4){const w=r||a,S=w;y=Ti(u.call(S,w,c,d,f,v,h)),b=l}else{const w=t;y=Ti(w.length>1?w(d,{attrs:l,slots:i,emit:s}):w(d,null)),b=t.props?l:xz(l)}}catch(w){_l.length=0,v1(w,e,1),y=L(oa)}let _=y;if(b&&m!==!1){const w=Object.keys(b),{shapeFlag:S}=_;w.length&&S&7&&(o&&w.some(r1)&&(b=kz(b,o)),_=ei(_,b,!1,!0))}if(n.dirs&&(_=ei(_,null,!1,!0),_.dirs=_.dirs?_.dirs.concat(n.dirs):n.dirs),n.transition){const w=h1(_.type)&&Om(_)||_;oc(w,n.transition)}return y=_,Dm(g),y}const xz=e=>{let t;for(const n in e)(n==="class"||n==="style"||a1(n))&&((t||(t={}))[n]=e[n]);return t},kz=(e,t)=>{const n={};for(const a in e)(!r1(a)||!(a.slice(9)in t))&&(n[a]=e[a]);return n};function Tz(e,t,n){const{props:a,children:r,component:o}=e,{props:i,children:l,patchFlag:s}=t,u=o.emitsOptions;if(t.dirs||t.transition)return!0;if(n&&s>=0){if(s&1024)return!0;if(s&16)return a?zS(a,i,u):!!i;if(s&8){const c=t.dynamicProps;for(let d=0;dObject.create(l7),u7=e=>Object.getPrototypeOf(e)===l7;function Ez(e,t,n,a=!1){const r={},o=s7();e.propsDefaults=Object.create(null),c7(e,t,r,o);for(const i in e.propsOptions[0])i in r||(r[i]=void 0);n?e.props=a?r:W3(r):e.type.props?e.props=r:e.props=o,e.attrs=o}function Az(e,t,n,a){const{props:r,attrs:o,vnode:{patchFlag:i}}=e,l=wn(r),[s]=e.propsOptions;let u=!1;if((a||i>0)&&!(i&16)){if(i&8){const c=e.vnode.dynamicProps;for(let d=0;d{s=!0;const[v,f]=d7(d,t,!0);ka(i,v),f&&l.push(...f)};!n&&t.mixins.length&&t.mixins.forEach(c),e.extends&&c(e.extends),e.mixins&&e.mixins.forEach(c)}if(!o&&!s)return _t(e)&&a.set(e,hd),hd;if(Ae(o))for(let c=0;ce==="_"||e==="_ctx"||e==="$stable",Q3=e=>Ae(e)?e.map(Ti):[Ti(e)],Lz=(e,t,n)=>{if(t._n)return t;const a=$((...r)=>Q3(t(...r)),n);return a._c=!1,a},f7=(e,t,n)=>{const a=e._ctx;for(const r in e){if(J3(r))continue;const o=e[r];if(Qe(o))t[r]=Lz(r,o,a);else if(o!=null){const i=Q3(o);t[r]=()=>i}}},v7=(e,t)=>{const n=Q3(t);e.slots.default=()=>n},h7=(e,t,n)=>{for(const a in t)(n||!J3(a))&&(e[a]=t[a])},Pz=(e,t,n)=>{const a=e.slots=s7();if(e.vnode.shapeFlag&32){const r=t._;r?(h7(a,t,n),n&&fE(a,"_",r,!0)):f7(t,a)}else t&&v7(e,t)},Dz=(e,t,n)=>{const{vnode:a,slots:r}=e;let o=!0,i=jn;if(a.shapeFlag&32){const l=t._;l?n&&l===1?o=!1:h7(r,t,n):(o=!t.$stable,f7(t,r)),i=t}else t&&(v7(e,t),i={default:1});if(o)for(const l in r)!J3(l)&&i[l]==null&&delete r[l]},mr=Bz;function Oz(e){return $z(e)}function $z(e,t){const n=s1();n.__VUE__=!0;const{insert:a,remove:r,patchProp:o,createElement:i,createText:l,createComment:s,setText:u,setElementText:c,parentNode:d,nextSibling:v,setScopeId:f=kt,insertStaticContent:h}=e,m=(j,oe,se,fe=null,ge=null,Se=null,De=void 0,Te=null,Re=!!oe.dynamicChildren)=>{if(j===oe)return;j&&!Iu(j,oe)&&(fe=le(j),Y(j,ge,Se,!0),j=null),oe.patchFlag===-2&&(Re=!1,oe.dynamicChildren=null);const{type:Ce,ref:Le,shapeFlag:me}=oe;switch(Ce){case uf:g(j,oe,se,fe);break;case oa:y(j,oe,se,fe);break;case j0:j==null&&b(oe,se,fe,De);break;case Ve:P(j,oe,se,fe,ge,Se,De,Te,Re);break;default:me&1?S(j,oe,se,fe,ge,Se,De,Te,Re):me&6?R(j,oe,se,fe,ge,Se,De,Te,Re):(me&64||me&128)&&Ce.process(j,oe,se,fe,ge,Se,De,Te,Re,J)}Le!=null&&ge?kv(Le,j&&j.ref,Se,oe||j,!oe):Le==null&&j&&j.ref!=null&&kv(j.ref,null,Se,j,!0)},g=(j,oe,se,fe)=>{if(j==null)a(oe.el=l(oe.children),se,fe);else{const ge=oe.el=j.el;oe.children!==j.children&&u(ge,oe.children)}},y=(j,oe,se,fe)=>{j==null?a(oe.el=s(oe.children||""),se,fe):oe.el=j.el},b=(j,oe,se,fe)=>{[j.el,j.anchor]=h(j.children,oe,se,fe,j.el,j.anchor)},_=({el:j,anchor:oe},se,fe)=>{let ge;for(;j&&j!==oe;)ge=v(j),a(j,se,fe),j=ge;a(oe,se,fe)},w=({el:j,anchor:oe})=>{let se;for(;j&&j!==oe;)se=v(j),r(j),j=se;r(oe)},S=(j,oe,se,fe,ge,Se,De,Te,Re)=>{if(oe.type==="svg"?De="svg":oe.type==="math"&&(De="mathml"),j==null)k(oe,se,fe,ge,Se,De,Te,Re);else{const Ce=j.el&&j.el._isVueCE?j.el:null;try{Ce&&Ce._beginPatch(),E(j,oe,ge,Se,De,Te,Re)}finally{Ce&&Ce._endPatch()}}},k=(j,oe,se,fe,ge,Se,De,Te)=>{let Re,Ce;const{props:Le,shapeFlag:me,transition:Be,dirs:ze}=j;if(Re=j.el=i(j.type,Se,Le&&Le.is,Le),me&8?c(Re,j.children):me&16&&M(j.children,Re,null,fe,ge,L2(j,Se),De,Te),ze&&Zs(j,null,fe,"created"),T(Re,j,j.scopeId,De,fe),Le){for(const rt in Le)rt!=="value"&&!Sv(rt)&&o(Re,rt,null,Le[rt],Se,fe);"value"in Le&&o(Re,"value",null,Le.value,Se),(Ce=Le.onVnodeBeforeMount)&&mi(Ce,fe,j)}ze&&Zs(j,null,fe,"beforeMount");const xe=Rz(ge,Be);xe&&Be.beforeEnter(Re),a(Re,oe,se),((Ce=Le&&Le.onVnodeMounted)||xe||ze)&&mr(()=>{try{Ce&&mi(Ce,fe,j),xe&&Be.enter(Re),ze&&Zs(j,null,fe,"mounted")}finally{}},ge)},T=(j,oe,se,fe,ge)=>{if(se&&f(j,se),fe)for(let Se=0;Se{for(let Ce=Re;Ce{const Te=oe.el=j.el;let{patchFlag:Re,dynamicChildren:Ce,dirs:Le}=oe;Re|=j.patchFlag&16;const me=j.props||jn,Be=oe.props||jn;let ze;if(se&&Js(se,!1),(ze=Be.onVnodeBeforeUpdate)&&mi(ze,se,oe,j),Le&&Zs(oe,j,se,"beforeUpdate"),se&&Js(se,!0),Ce&&(!j.dynamicChildren||j.dynamicChildren.length!==Ce.length)&&(Re=0,De=!1,Ce=null),(me.innerHTML&&Be.innerHTML==null||me.textContent&&Be.textContent==null)&&c(Te,""),Ce?A(j.dynamicChildren,Ce,Te,se,fe,L2(oe,ge),Se):De||B(j,oe,Te,null,se,fe,L2(oe,ge),Se,!1),Re>0){if(Re&16)I(Te,me,Be,se,ge);else if(Re&2&&me.class!==Be.class&&o(Te,"class",null,Be.class,ge),Re&4&&o(Te,"style",me.style,Be.style,ge),Re&8){const xe=oe.dynamicProps;for(let rt=0;rt{ze&&mi(ze,se,oe,j),Le&&Zs(oe,j,se,"updated")},fe)},A=(j,oe,se,fe,ge,Se,De)=>{for(let Te=0;Te{if(oe!==se){if(oe!==jn)for(const Se in oe)!Sv(Se)&&!(Se in se)&&o(j,Se,oe[Se],null,ge,fe);for(const Se in se){if(Sv(Se))continue;const De=se[Se],Te=oe[Se];De!==Te&&Se!=="value"&&o(j,Se,Te,De,ge,fe)}"value"in se&&o(j,"value",oe.value,se.value,ge)}},P=(j,oe,se,fe,ge,Se,De,Te,Re)=>{const Ce=oe.el=j?j.el:l(""),Le=oe.anchor=j?j.anchor:l("");let{patchFlag:me,dynamicChildren:Be,slotScopeIds:ze}=oe;ze&&(Te=Te?Te.concat(ze):ze),j==null?(a(Ce,se,fe),a(Le,se,fe),M(oe.children||[],se,Le,ge,Se,De,Te,Re)):me>0&&me&64&&Be&&j.dynamicChildren&&j.dynamicChildren.length===Be.length?(A(j.dynamicChildren,Be,se,ge,Se,De,Te),(oe.key!=null||ge&&oe===ge.subTree)&&e6(j,oe,!0)):B(j,oe,se,Le,ge,Se,De,Te,Re)},R=(j,oe,se,fe,ge,Se,De,Te,Re)=>{oe.slotScopeIds=Te,j==null?oe.shapeFlag&512?ge.ctx.activate(oe,se,fe,De,Re):G(oe,se,fe,ge,Se,De,Re):K(j,oe,Re)},G=(j,oe,se,fe,ge,Se,De)=>{const Te=j.component=Hz(j,fe,ge);if(p1(j)&&(Te.ctx.renderer=J),Wz(Te,!1,De),Te.asyncDep){if(ge&&ge.registerDep(Te,H,De),!j.el){const Re=Te.subTree=L(oa);y(null,Re,oe,se),j.placeholder=Re.el}}else H(Te,j,oe,se,ge,Se,De)},K=(j,oe,se)=>{const fe=oe.component=j.component;if(Tz(j,oe,se))if(fe.asyncDep&&!fe.asyncResolved){z(fe,oe,se);return}else fe.next=oe,fe.update();else oe.el=j.el,fe.vnode=oe},H=(j,oe,se,fe,ge,Se,De)=>{const Te=()=>{if(j.isMounted){let{next:me,bu:Be,u:ze,parent:xe,vnode:rt}=j;{const Rt=p7(j);if(Rt){me&&(me.el=rt.el,z(j,me,De)),Rt.asyncDep.then(()=>{mr(()=>{j.isUnmounted||Ce()},ge)});return}}let nt=me,Pt;Js(j,!1),me?(me.el=rt.el,z(j,me,De)):me=rt,Be&&G0(Be),(Pt=me.props&&me.props.onVnodeBeforeUpdate)&&mi(Pt,xe,me,rt),Js(j,!0);const Wt=VS(j),Jt=j.subTree;j.subTree=Wt,m(Jt,Wt,d(Jt.el),le(Jt),j,ge,Se),me.el=Wt.el,nt===null&&Mz(j,Wt.el),ze&&mr(ze,ge),(Pt=me.props&&me.props.onVnodeUpdated)&&mr(()=>mi(Pt,xe,me,rt),ge)}else{let me;const{el:Be,props:ze}=oe,{bm:xe,m:rt,parent:nt,root:Pt,type:Wt}=j,Jt=gd(oe);Js(j,!1),xe&&G0(xe),!Jt&&(me=ze&&ze.onVnodeBeforeMount)&&mi(me,nt,oe),Js(j,!0);{Pt.ce&&Pt.ce._hasShadowRoot()&&Pt.ce._injectChildStyle(Wt,j.parent?j.parent.type:void 0);const Rt=j.subTree=VS(j);m(null,Rt,se,fe,j,ge,Se),oe.el=Rt.el}if(rt&&mr(rt,ge),!Jt&&(me=ze&&ze.onVnodeMounted)){const Rt=oe;mr(()=>mi(me,nt,Rt),ge)}(oe.shapeFlag&256||nt&&gd(nt.vnode)&&nt.vnode.shapeFlag&256)&&j.a&&mr(j.a,ge),j.isMounted=!0,oe=se=fe=null}};j.scope.on();const Re=j.effect=new bE(Te);j.scope.off();const Ce=j.update=Re.run.bind(Re),Le=j.job=Re.runIfDirty.bind(Re);Le.i=j,Le.id=j.uid,Re.scheduler=()=>U3(Le),Js(j,!0),Ce()},z=(j,oe,se)=>{oe.component=j;const fe=j.vnode.props;j.vnode=oe,j.next=null,Az(j,oe.props,fe,se),Dz(j,oe.children,se),kl(),AS(j),Tl()},B=(j,oe,se,fe,ge,Se,De,Te,Re=!1)=>{const Ce=j&&j.children,Le=j?j.shapeFlag:0,me=oe.children,{patchFlag:Be,shapeFlag:ze}=oe;if(Be>0){if(Be&128){N(Ce,me,se,fe,ge,Se,De,Te,Re);return}else if(Be&256){W(Ce,me,se,fe,ge,Se,De,Te,Re);return}}ze&8?(Le&16&&ie(Ce,ge,Se),me!==Ce&&c(se,me)):Le&16?ze&16?N(Ce,me,se,fe,ge,Se,De,Te,Re):ie(Ce,ge,Se,!0):(Le&8&&c(se,""),ze&16&&M(me,se,fe,ge,Se,De,Te,Re))},W=(j,oe,se,fe,ge,Se,De,Te,Re)=>{j=j||hd,oe=oe||hd;const Ce=j.length,Le=oe.length,me=Math.min(Ce,Le);let Be;for(Be=0;BeLe?ie(j,ge,Se,!0,!1,me):M(oe,se,fe,ge,Se,De,Te,Re,me)},N=(j,oe,se,fe,ge,Se,De,Te,Re)=>{let Ce=0;const Le=oe.length;let me=j.length-1,Be=Le-1;for(;Ce<=me&&Ce<=Be;){const ze=j[Ce],xe=oe[Ce]=Re?fl(oe[Ce]):Ti(oe[Ce]);if(Iu(ze,xe))m(ze,xe,se,null,ge,Se,De,Te,Re);else break;Ce++}for(;Ce<=me&&Ce<=Be;){const ze=j[me],xe=oe[Be]=Re?fl(oe[Be]):Ti(oe[Be]);if(Iu(ze,xe))m(ze,xe,se,null,ge,Se,De,Te,Re);else break;me--,Be--}if(Ce>me){if(Ce<=Be){const ze=Be+1,xe=zeBe)for(;Ce<=me;)Y(j[Ce],ge,Se,!0),Ce++;else{const ze=Ce,xe=Ce,rt=new Map;for(Ce=xe;Ce<=Be;Ce++){const Ne=oe[Ce]=Re?fl(oe[Ce]):Ti(oe[Ce]);Ne.key!=null&&rt.set(Ne.key,Ce)}let nt,Pt=0;const Wt=Be-xe+1;let Jt=!1,Rt=0;const Qt=new Array(Wt);for(Ce=0;Ce=Wt){Y(Ne,ge,Se,!0);continue}let tt;if(Ne.key!=null)tt=rt.get(Ne.key);else for(nt=xe;nt<=Be;nt++)if(Qt[nt-xe]===0&&Iu(Ne,oe[nt])){tt=nt;break}tt===void 0?Y(Ne,ge,Se,!0):(Qt[tt-xe]=Ce+1,tt>=Rt?Rt=tt:Jt=!0,m(Ne,oe[tt],se,null,ge,Se,De,Te,Re),Pt++)}const _e=Jt?Nz(Qt):hd;for(nt=_e.length-1,Ce=Wt-1;Ce>=0;Ce--){const Ne=xe+Ce,tt=oe[Ne],Me=oe[Ne+1],Ue=Ne+1{const{el:Se,type:De,transition:Te,children:Re,shapeFlag:Ce}=j;if(Ce&6){V(j.component.subTree,oe,se,fe);return}if(Ce&128){j.suspense.move(oe,se,fe);return}if(Ce&64){De.move(j,oe,se,J);return}if(De===Ve){a(Se,oe,se);for(let me=0;meTe.enter(Se),ge));else{const{leave:me,delayLeave:Be,afterLeave:ze}=Te,xe=()=>{j.ctx.isUnmounted?r(Se):a(Se,oe,se)},rt=()=>{const nt=Se._isLeaving||!!Se[_o];Se._isLeaving&&Se[_o](!0),Te.persisted&&!nt?xe():me(Se,()=>{xe(),ze&&ze()})};Be?Be(Se,xe,rt):rt()}else a(Se,oe,se)},Y=(j,oe,se,fe=!1,ge=!1)=>{const{type:Se,props:De,ref:Te,children:Re,dynamicChildren:Ce,shapeFlag:Le,patchFlag:me,dirs:Be,cacheIndex:ze,memo:xe}=j;if(me===-2&&(ge=!1),Te!=null&&(kl(),kv(Te,null,se,j,!0),Tl()),ze!=null&&(oe.renderCache[ze]=void 0),Le&256){oe.ctx.deactivate(j);return}const rt=Le&1&&Be,nt=!gd(j);let Pt;if(nt&&(Pt=De&&De.onVnodeBeforeUnmount)&&mi(Pt,oe,j),Le&6)re(j.component,se,fe);else{if(Le&128){j.suspense.unmount(se,fe);return}rt&&Zs(j,null,oe,"beforeUnmount"),Le&64?j.type.remove(j,oe,se,J,fe):Ce&&!Ce.hasOnce&&(Se!==Ve||me>0&&me&64)?ie(Ce,oe,se,!1,!0):(Se===Ve&&me&384||!ge&&Le&16)&&ie(Re,oe,se),fe&&X(j)}const Wt=xe!=null&&ze==null;(nt&&(Pt=De&&De.onVnodeUnmounted)||rt||Wt)&&mr(()=>{Pt&&mi(Pt,oe,j),rt&&Zs(j,null,oe,"unmounted"),Wt&&(j.el=null)},se)},X=j=>{const{type:oe,el:se,anchor:fe,transition:ge}=j;if(oe===Ve){ee(se,fe);return}if(oe===j0){w(j);return}const Se=()=>{r(se),ge&&!ge.persisted&&ge.afterLeave&&ge.afterLeave()};if(j.shapeFlag&1&&ge&&!ge.persisted){const{leave:De,delayLeave:Te}=ge,Re=()=>De(se,Se);Te?Te(j.el,Se,Re):Re()}else Se()},ee=(j,oe)=>{let se;for(;j!==oe;)se=v(j),r(j),j=se;r(oe)},re=(j,oe,se)=>{const{bum:fe,scope:ge,job:Se,subTree:De,um:Te,m:Re,a:Ce}=j;HS(Re),HS(Ce),fe&&G0(fe),ge.stop(),Se&&(Se.flags|=8,Y(De,j,oe,se)),Te&&mr(Te,oe),mr(()=>{j.isUnmounted=!0},oe)},ie=(j,oe,se,fe=!1,ge=!1,Se=0)=>{for(let De=Se;De{if(j.shapeFlag&6)return le(j.component.subTree);if(j.shapeFlag&128)return j.suspense.next();const oe=v(j.anchor||j.el),se=oe&&oe[UE];return se?v(se):oe};let ve=!1;const he=(j,oe,se)=>{let fe;j==null?oe._vnode&&(Y(oe._vnode,null,null,!0),fe=oe._vnode.component):m(oe._vnode||null,j,oe,null,null,null,se),oe._vnode=j,ve||(ve=!0,AS(fe),zE(),ve=!1)},J={p:m,um:Y,m:V,r:X,mt:G,mc:M,pc:B,pbc:A,n:le,o:e};return{render:he,hydrate:void 0,createApp:_z(he)}}function L2({type:e,props:t},n){return n==="svg"&&e==="foreignObject"||n==="mathml"&&e==="annotation-xml"&&t&&t.encoding&&t.encoding.includes("html")?void 0:n}function Js({effect:e,job:t},n){n?(e.flags|=32,t.flags|=4):(e.flags&=-33,t.flags&=-5)}function Rz(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}function e6(e,t,n=!1){const a=e.children,r=t.children;if(Ae(a)&&Ae(r))for(let o=0;o>1,e[n[l]]0&&(t[a]=n[o-1]),n[o]=a)}}for(o=n.length,i=n[o-1];o-- >0;)n[o]=i,i=t[i];return n}function p7(e){const t=e.subTree.component;if(t)return t.asyncDep&&!t.asyncResolved?t:p7(t)}function HS(e){if(e)for(let t=0;te.__isSuspense;function Bz(e,t){t&&t.pendingBranch?Ae(e)?t.effects.push(...e):t.effects.push(e):ZV(e)}const Ve=Symbol.for("v-fgt"),uf=Symbol.for("v-txt"),oa=Symbol.for("v-cmt"),j0=Symbol.for("v-stc"),_l=[];let Jr=null;function x(e=!1){_l.push(Jr=e?null:[])}function t6(){_l.pop(),Jr=_l[_l.length-1]||null}let eh=1;function Nm(e,t=!1){eh+=e,e<0&&Jr&&t&&(Jr.hasOnce=!0)}function y7(e){return e.dynamicChildren=eh>0?Jr||hd:null,t6(),eh>0&&Jr&&Jr.push(e),e}function O(e,t,n,a,r,o){return y7(C(e,t,n,a,r,o,!0))}function ne(e,t,n,a,r){return y7(L(e,t,n,a,r,!0))}function bn(e){return e?e.__v_isVNode===!0:!1}function Iu(e,t){return e.type===t.type&&e.key===t.key}const b7=({key:e})=>e??null,q0=({ref:e,ref_key:t,ref_for:n})=>(typeof e=="number"&&(e=""+e),e!=null?Ye(e)||xn(e)||Qe(e)?{i:Ga,r:e,k:t,f:!!n}:e:null);function C(e,t=null,n=null,a=0,r=null,o=e===Ve?0:1,i=!1,l=!1){const s={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&b7(t),ref:t&&q0(t),scopeId:HE,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetStart:null,targetAnchor:null,staticCount:0,shapeFlag:o,patchFlag:a,dynamicProps:r,dynamicChildren:null,appContext:null,ctx:Ga};return l?(Bm(s,n),o&128&&e.normalize(s)):n&&(s.shapeFlag|=Ye(n)?8:16),eh>0&&!i&&Jr&&(s.patchFlag>0||o&6)&&s.patchFlag!==32&&Jr.push(s),s}const L=Vz;function Vz(e,t=null,n=null,a=0,r=null,o=!1){if((!e||e===QE)&&(e=oa),bn(e)){const l=ei(e,t,!0);return n&&Bm(l,n),eh>0&&!o&&Jr&&(l.shapeFlag&6?Jr[Jr.indexOf(e)]=l:Jr.push(l)),l.patchFlag=-2,l}if(Yz(e)&&(e=e.__vccOpts),t){t=wl(t);let{class:l,style:s}=t;l&&!Ye(l)&&(t.class=U(l)),_t(s)&&(f1(s)&&!Ae(s)&&(s=ka({},s)),t.style=et(s))}const i=Ye(e)?1:g7(e)?128:h1(e)?64:_t(e)?4:Qe(e)?2:0;return C(e,t,n,a,r,i,o,!0)}function wl(e){return e?f1(e)||u7(e)?ka({},e):e:null}function ei(e,t,n=!1,a=!1){const{props:r,ref:o,patchFlag:i,children:l,transition:s}=e,u=t?Tt(r||{},t):r,c={__v_isVNode:!0,__v_skip:!0,type:e.type,props:u,key:u&&b7(u),ref:t&&t.ref?n&&o?Ae(o)?o.concat(q0(t)):[o,q0(t)]:q0(t):o,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:l,target:e.target,targetStart:e.targetStart,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==Ve?i===-1?16:i|16:i,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:s,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&ei(e.ssContent),ssFallback:e.ssFallback&&ei(e.ssFallback),placeholder:e.placeholder,el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce};return s&&a&&oc(c,s.clone(c)),c}function ue(e=" ",t=0){return L(uf,null,e,t)}function _7(e,t){const n=L(j0,null,e);return n.staticCount=t,n}function te(e="",t=!1){return t?(x(),ne(oa,null,e)):L(oa,null,e)}function Ti(e){return e==null||typeof e=="boolean"?L(oa):Ae(e)?L(Ve,null,e.slice()):bn(e)?fl(e):L(uf,null,String(e))}function fl(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:ei(e)}function Bm(e,t){let n=0;const{shapeFlag:a}=e;if(t==null)t=null;else if(Ae(t))n=16;else if(typeof t=="object")if(a&65){const r=t.default;r&&(r._c&&(r._d=!1),Bm(e,r()),r._c&&(r._d=!0));return}else{n=32;const r=t._;!r&&!u7(t)?t._ctx=Ga:r===3&&Ga&&(Ga.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else if(Qe(t)){if(a&65){Bm(e,{default:t});return}t={default:t,_ctx:Ga},n=32}else t=String(t),a&64?(n=16,t=[ue(t)]):n=8;e.children=t,e.shapeFlag|=n}function Tt(...e){const t={};for(let n=0;nrr||Ga;let Vm,th;{const e=s1(),t=(n,a)=>{let r;return(r=e[n])||(r=e[n]=[]),r.push(a),o=>{r.length>1?r.forEach(i=>i(o)):r[0](o)}};Vm=t("__VUE_INSTANCE_SETTERS__",n=>rr=n),th=t("__VUE_SSR_SETTERS__",n=>nh=n)}const Kh=e=>{const t=rr;return Vm(e),e.scope.on(),()=>{e.scope.off(),Vm(t)}},WS=()=>{rr&&rr.scope.off(),Vm(null)};function w7(e){return e.vnode.shapeFlag&4}let nh=!1;function Wz(e,t=!1,n=!1){t&&th(t);const{props:a,children:r}=e.vnode,o=w7(e);Ez(e,a,o,t),Pz(e,r,n||t);const i=o?Kz(e,t):void 0;return t&&th(!1),i}function Kz(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,vz);const{setup:a}=n;if(a){kl();const r=e.setupContext=a.length>1?C7(e):null,o=Kh(e),i=Hh(a,e,0,[e.props,r]),l=Ls(i);if(Tl(),o(),(l||e.sp)&&!gd(e)&&ZE(e),l){if(i.then(WS,WS),t)return i.then(s=>{th(!0);try{KS(e,s,t)}finally{th(!1)}}).catch(s=>{v1(s,e,0)});e.asyncDep=i}else KS(e,i)}else S7(e)}function KS(e,t,n){Qe(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:_t(t)&&(e.setupState=RE(t)),S7(e)}function S7(e,t,n){const a=e.type;e.render||(e.render=a.render||kt);{const r=Kh(e);kl();try{hz(e)}finally{Tl(),r()}}}const Uz={get(e,t){return nr(e,"get",""),e[t]}};function C7(e){const t=n=>{e.exposed=n||{}};return{attrs:new Proxy(e.attrs,Uz),slots:e.slots,emit:e.emit,expose:t}}function b1(e){return e.exposed?e.exposeProxy||(e.exposeProxy=new Proxy(RE($i(e.exposed)),{get(t,n){if(n in t)return t[n];if(n in Tv)return Tv[n](e)},has(t,n){return n in t||n in Tv}})):e.proxy}function Gz(e,t=!0){return Qe(e)?e.displayName||e.name:e.name||t&&e.__name}function Yz(e){return Qe(e)&&"__vccOpts"in e}const D=(e,t)=>GV(e,t,nh);function ot(e,t,n){try{Nm(-1);const a=arguments.length;return a===2?_t(t)&&!Ae(t)?bn(t)?L(e,null,[t]):L(e,t):L(e,null,t):(a>3?n=Array.prototype.slice.call(arguments,2):a===3&&bn(n)&&(n=[n]),L(e,t,n))}finally{Nm(1)}}const jz="3.5.41",qz=kt;/** +* @vue/runtime-dom v3.5.41 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/let t4;const US=typeof window<"u"&&window.trustedTypes;if(US)try{t4=US.createPolicy("vue",{createHTML:e=>e})}catch{}const x7=t4?e=>t4.createHTML(e):e=>e,Xz="http://www.w3.org/2000/svg",Zz="http://www.w3.org/1998/Math/MathML",cl=typeof document<"u"?document:null,GS=cl&&cl.createElement("template"),Jz={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,a)=>{const r=t==="svg"?cl.createElementNS(Xz,e):t==="mathml"?cl.createElementNS(Zz,e):n?cl.createElement(e,{is:n}):cl.createElement(e);return e==="select"&&a&&a.multiple!=null&&r.setAttribute("multiple",a.multiple),r},createText:e=>cl.createTextNode(e),createComment:e=>cl.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>cl.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,a,r,o){const i=n?n.previousSibling:t.lastChild;if(r&&(r===o||r.nextSibling))for(;t.insertBefore(r.cloneNode(!0),n),!(r===o||!(r=r.nextSibling)););else{GS.innerHTML=x7(a==="svg"?`${e}`:a==="mathml"?`${e}`:e);const l=GS.content;if(a==="svg"||a==="mathml"){const s=l.firstChild;for(;s.firstChild;)l.appendChild(s.firstChild);l.removeChild(s)}t.insertBefore(l,n)}return[i?i.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}},jl="transition",Ef="animation",Dd=Symbol("_vtc"),k7={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String},T7=ka({},YE,k7),Qz=e=>(e.displayName="Transition",e.props=T7,e),Pa=Qz((e,{slots:t})=>ot(oz,M7(e),t)),Qs=(e,t=[])=>{Ae(e)?e.forEach(n=>n(...t)):e&&e(...t)},YS=e=>e?Ae(e)?e.some(t=>t.length>1):e.length>1:!1;function M7(e){const t={};for(const P in e)P in k7||(t[P]=e[P]);if(e.css===!1)return t;const{name:n="v",type:a,duration:r,enterFromClass:o=`${n}-enter-from`,enterActiveClass:i=`${n}-enter-active`,enterToClass:l=`${n}-enter-to`,appearFromClass:s=o,appearActiveClass:u=i,appearToClass:c=l,leaveFromClass:d=`${n}-leave-from`,leaveActiveClass:v=`${n}-leave-active`,leaveToClass:f=`${n}-leave-to`}=e,h=eF(r),m=h&&h[0],g=h&&h[1],{onBeforeEnter:y,onEnter:b,onEnterCancelled:_,onLeave:w,onLeaveCancelled:S,onBeforeAppear:k=y,onAppear:T=b,onAppearCancelled:M=_}=t,E=(P,R,G,K)=>{P._enterCancelled=K,rs(P,R?c:l),rs(P,R?u:i),G&&G()},A=(P,R)=>{P._isLeaving=!1,rs(P,d),rs(P,f),rs(P,v),R&&R()},I=P=>(R,G)=>{const K=P?T:b,H=()=>E(R,P,G);Qs(K,[R,H]),jS(()=>{rs(R,P?s:o),Ci(R,P?c:l),YS(K)||qS(R,a,m,H)})};return ka(t,{onBeforeEnter(P){Qs(y,[P]),Ci(P,o),Ci(P,i)},onBeforeAppear(P){Qs(k,[P]),Ci(P,s),Ci(P,u)},onEnter:I(!1),onAppear:I(!0),onLeave(P,R){P._isLeaving=!0;const G=()=>A(P,R);Ci(P,d),P._enterCancelled?(Ci(P,v),n4(P)):(n4(P),Ci(P,v)),jS(()=>{P._isLeaving&&(rs(P,d),Ci(P,f),YS(w)||qS(P,a,g,G))}),Qs(w,[P,G])},onEnterCancelled(P){E(P,!1,void 0,!0),Qs(_,[P])},onAppearCancelled(P){E(P,!0,void 0,!0),Qs(M,[P])},onLeaveCancelled(P){A(P),Qs(S,[P])}})}function eF(e){if(e==null)return null;if(_t(e))return[P2(e.enter),P2(e.leave)];{const t=P2(e);return[t,t]}}function P2(e){return hV(e)}function Ci(e,t){t.split(/\s+/).forEach(n=>n&&e.classList.add(n)),(e[Dd]||(e[Dd]=new Set)).add(t)}function rs(e,t){t.split(/\s+/).forEach(a=>a&&e.classList.remove(a));const n=e[Dd];n&&(n.delete(t),n.size||(e[Dd]=void 0))}function jS(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}let tF=0;function qS(e,t,n,a){const r=e._endId=++tF,o=()=>{r===e._endId&&a()};if(n!=null)return setTimeout(o,n);const{type:i,timeout:l,propCount:s}=E7(e,t);if(!i)return a();const u=i+"end";let c=0;const d=()=>{e.removeEventListener(u,v),o()},v=f=>{f.target===e&&++c>=s&&d()};setTimeout(()=>{c(n[h]||"").split(", "),r=a(`${jl}Delay`),o=a(`${jl}Duration`),i=XS(r,o),l=a(`${Ef}Delay`),s=a(`${Ef}Duration`),u=XS(l,s);let c=null,d=0,v=0;t===jl?i>0&&(c=jl,d=i,v=o.length):t===Ef?u>0&&(c=Ef,d=u,v=s.length):(d=Math.max(i,u),c=d>0?i>u?jl:Ef:null,v=c?c===jl?o.length:s.length:0);const f=c===jl&&/\b(?:transform|all)(?:,|$)/.test(a(`${jl}Property`).toString());return{type:c,timeout:d,propCount:v,hasTransform:f}}function XS(e,t){for(;e.lengthZS(n)+ZS(e[a])))}function ZS(e){return e==="auto"?0:Number(e.slice(0,-1).replace(",","."))*1e3}function n4(e){return(e?e.ownerDocument:document).body.offsetHeight}function nF(e,t,n){const a=e[Dd];a&&(t=(t?[t,...a]:[...a]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}const zm=Symbol("_vod"),n6=Symbol("_vsh"),Zt={name:"show",beforeMount(e,{value:t},{transition:n}){e[zm]=e.style.display==="none"?"":e.style.display,n&&t?n.beforeEnter(e):Af(e,t)},mounted(e,{value:t},{transition:n}){n&&t&&n.enter(e)},updated(e,{value:t,oldValue:n},{transition:a}){!t!=!n&&(a?t?(a.beforeEnter(e),Af(e,!0),a.enter(e)):a.leave(e,()=>{Af(e,!1)}):Af(e,t))},beforeUnmount(e,{value:t}){Af(e,t)}};function Af(e,t){e.style.display=t?e[zm]:"none",e[n6]=!t}const aF=Symbol(""),rF=/(?:^|;)\s*display\s*:/;function oF(e,t,n){const a=e.style,r=Ye(n);let o=!1;if(n&&!r){if(t)if(Ye(t))for(const i of t.split(";")){const l=i.slice(0,i.indexOf(":")).trim();n[l]==null&&av(a,l,"")}else for(const i in t)n[i]==null&&av(a,i,"");for(const i in n){i==="display"&&(o=!0);const l=n[i];l!=null?lF(e,i,!Ye(t)&&t?t[i]:void 0,l)||av(a,i,l):av(a,i,"")}}else if(r){if(t!==n){const i=a[aF];i&&(n+=";"+i),a.cssText=n,o=rF.test(n)}}else t&&e.removeAttribute("style");zm in e&&(e[zm]=o?a.display:"",e[n6]&&(a.display="none"))}const JS=/\s*!important$/;function av(e,t,n){if(Ae(n))n.forEach(a=>av(e,t,a));else if(n==null&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{const a=iF(e,t);JS.test(n)?e.setProperty($l(a),n.replace(JS,""),"important"):e[a]=n}}const QS=["Webkit","Moz","ms"],D2={};function iF(e,t){const n=D2[t];if(n)return n;let a=Va(t);if(a!=="filter"&&a in e)return D2[t]=a;a=Fh(a);for(let r=0;rO2||(vF.then(()=>O2=0),O2=Date.now());function pF(e,t){const n=a=>{if(!a._vts)a._vts=Date.now();else if(a._vts<=n.attached)return;const r=n.value;if(Ae(r)){const o=a.stopImmediatePropagation;a.stopImmediatePropagation=()=>{o.call(a),a._stopped=!0};const i=r.slice(),l=[a];for(let s=0;se.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,mF=(e,t,n,a,r,o)=>{const i=r==="svg";t==="class"?nF(e,a,i):t==="style"?oF(e,n,a):a1(t)?r1(t)||uF(e,t,n,a,o):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):gF(e,t,a,i))?(nC(e,t,a),!e.tagName.includes("-")&&(t==="value"||t==="checked"||t==="selected")&&tC(e,t,a,i,o,t!=="value")):e._isVueCE&&(yF(e,t)||e._def.__asyncLoader&&(/[A-Z]/.test(t)||!Ye(a)))?nC(e,Va(t),a,o,t):(t==="true-value"?e._trueValue=a:t==="false-value"&&(e._falseValue=a),tC(e,t,a,i))};function gF(e,t,n,a){if(a)return!!(t==="innerHTML"||t==="textContent"||t in e&&rC(t)&&Qe(n));if(t==="spellcheck"||t==="draggable"||t==="translate"||t==="autocorrect"||t==="sandbox"&&e.tagName==="IFRAME"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA")return!1;if(t==="width"||t==="height"){const r=e.tagName;if(r==="IMG"||r==="VIDEO"||r==="CANVAS"||r==="SOURCE")return!1}return rC(t)&&Ye(n)?!1:t in e}function yF(e,t){const n=e._def.props;if(!n)return!1;const a=Va(t);return Array.isArray(n)?n.some(r=>Va(r)===a):Object.keys(n).some(r=>Va(r)===a)}const A7=new WeakMap,I7=new WeakMap,Fm=Symbol("_moveCb"),oC=Symbol("_enterCb"),bF=e=>(delete e.props.mode,e),_F=bF({name:"TransitionGroup",props:ka({},T7,{tag:String,moveClass:String}),setup(e,{slots:t}){const n=Et(),a=GE();let r,o;return oi(()=>{if(!r.length)return;const i=e.moveClass||`${e.name||"v"}-move`;if(!xF(r[0].el,n.vnode.el,i)){r=[];return}r.forEach(wF),r.forEach(SF);const l=r.filter(CF);n4(n.vnode.el),l.forEach(s=>{const u=s.el,c=u.style;Ci(u,i),c.transform=c.webkitTransform=c.transitionDuration="";const d=u[Fm]=v=>{v&&v.target!==u||(!v||v.propertyName.endsWith("transform"))&&(u.removeEventListener("transitionend",d),u[Fm]=null,rs(u,i))};u.addEventListener("transitionend",d)}),r=[]}),()=>{const i=wn(e),l=M7(i);let s=i.tag||Ve;if(r=[],o)for(let u=0;u{l.split(/\s+/).forEach(s=>s&&a.classList.remove(s))}),n.split(/\s+/).forEach(l=>l&&a.classList.add(l)),a.style.display="none";const o=t.nodeType===1?t:t.parentNode;o.appendChild(a);const{hasTransform:i}=E7(a);return o.removeChild(a),i}const Od=e=>{const t=e.props["onUpdate:modelValue"]||!1;return Ae(t)?n=>G0(t,n):t};function kF(e){e.target.composing=!0}function iC(e){const t=e.target;t.composing&&(t.composing=!1,t.dispatchEvent(new Event("input")))}const Ii=Symbol("_assign"),Lp=Symbol("_initialValue");function $2(e,t,n){return t&&(e=e.trim()),n&&(e=N3(e)),e}const D7={created(e,{modifiers:{lazy:t,trim:n,number:a}},r){e.parentNode&&(e.type==="text"?e[Lp]=e.defaultValue.replace(/[\r\n]/g,""):e.type==="textarea"&&(e[Lp]=e.defaultValue.replace(/\r\n?/g,` +`))),e[Ii]=Od(r);const o=a||r.props&&r.props.type==="number";fs(e,t?"change":"input",i=>{i.target.composing||e[Ii]($2(e.value,n,o))}),(n||o)&&fs(e,"change",()=>{e.value=$2(e.value,n,o)}),t||(fs(e,"compositionstart",kF),fs(e,"compositionend",iC),fs(e,"change",iC))},mounted(e,{value:t,modifiers:{trim:n,number:a}}){const r=t??"",o=e[Lp];delete e[Lp],o!==void 0&&(e.type==="text"||e.type==="textarea")&&e.value!==o?e[Ii]($2(e.value,n,a)):e.value=r},beforeUpdate(e,{value:t,oldValue:n,modifiers:{lazy:a,trim:r,number:o}},i){if(e[Ii]=Od(i),e.composing)return;const l=(o||e.type==="number")&&!/^0\d/.test(e.value)?N3(e.value):e.value,s=t??"";if(l===s)return;const u=e.getRootNode();(u instanceof Document||u instanceof ShadowRoot)&&u.activeElement===e&&e.type!=="range"&&(a&&t===n||r&&e.value.trim()===s)||(e.value=s)}},O7={deep:!0,created(e,t,n){e[Ii]=Od(n),fs(e,"change",()=>{const a=e._modelValue,r=R7(e),o=e.checked,i=e[Ii];if(Ae(a)){const l=hE(a,r),s=l!==-1;if(o&&!s)i(a.concat(r));else if(!o&&s){const u=[...a];u.splice(l,1),i(u)}}else if(o1(a)){const l=new Set(a);o?l.add(r):l.delete(r),i(l)}else i(N7(e,o))})},mounted:lC,beforeUpdate(e,t,n){e[Ii]=Od(n),lC(e,t,n)}};function lC(e,{value:t,oldValue:n},a){e._modelValue=t;let r;if(Ae(t))r=hE(t,a.props.value)>-1;else if(o1(t))r=t.has(a.props.value);else{if(t===n)return;r=rc(t,N7(e,!0))}e.checked!==r&&(e.checked=r)}const $7={created(e,{value:t},n){e.checked=rc(t,n.props.value),e[Ii]=Od(n),fs(e,"change",()=>{e[Ii](R7(e))})},beforeUpdate(e,{value:t,oldValue:n},a){e[Ii]=Od(a),t!==n&&(e.checked=rc(t,a.props.value))}};function R7(e){return"_value"in e?e._value:e.value}function N7(e,t){const n=t?"_trueValue":"_falseValue";return n in e?e[n]:t}const TF=["ctrl","shift","alt","meta"],MF={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&e.button!==0,middle:e=>"button"in e&&e.button!==1,right:e=>"button"in e&&e.button!==2,exact:(e,t)=>TF.some(n=>e[`${n}Key`]&&!t.includes(n))},at=(e,t)=>{if(!e)return e;const n=e._withMods||(e._withMods={}),a=t.join(".");return n[a]||(n[a]=(r,...o)=>{for(let i=0;i{const n=e._withKeys||(e._withKeys={}),a=t.join(".");return n[a]||(n[a]=r=>{if(!("key"in r))return;const o=$l(r.key);if(t.some(i=>i===o||EF[i]===o))return e(r)})},AF=ka({patchProp:mF},Jz);let sC;function B7(){return sC||(sC=Oz(AF))}const Ps=(...e)=>{B7().render(...e)},V7=(...e)=>{const t=B7().createApp(...e),{mount:n}=t;return t.mount=a=>{const r=LF(a);if(!r)return;const o=t._component;!Qe(o)&&!o.render&&!o.template&&(o.template=r.innerHTML),r.nodeType===1&&(r.textContent="");const i=n(r,!1,IF(r));return r instanceof Element&&(r.removeAttribute("v-cloak"),r.setAttribute("data-v-app","")),i},t};function IF(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function LF(e){return Ye(e)?document.querySelector(e):e}const Pe={tab:"Tab",enter:"Enter",space:"Space",left:"ArrowLeft",up:"ArrowUp",right:"ArrowRight",down:"ArrowDown",esc:"Escape",delete:"Delete",backspace:"Backspace",numpadEnter:"NumpadEnter",pageUp:"PageUp",pageDown:"PageDown",home:"Home",end:"End"},PF=["year","years","month","months","date","dates","week","datetime","datetimerange","daterange","monthrange","yearrange"],R2=["sun","mon","tue","wed","thu","fri","sat"],bt="update:modelValue",Ot="change",ua="input",uC=Symbol("INSTALLED_KEY"),ii=["","default","small","large"],cC=["left","center","right"];var z7=typeof global=="object"&&global&&global.Object===Object&&global,DF=typeof self=="object"&&self&&self.Object===Object&&self,li=z7||DF||Function("return this")(),No=li.Symbol,F7=Object.prototype,OF=F7.hasOwnProperty,$F=F7.toString,If=No?No.toStringTag:void 0;function RF(e){var t=OF.call(e,If),n=e[If];try{e[If]=void 0;var a=!0}catch{}var r=$F.call(e);return a&&(t?e[If]=n:delete e[If]),r}var NF=Object.prototype,BF=NF.toString;function VF(e){return BF.call(e)}var zF="[object Null]",FF="[object Undefined]",dC=No?No.toStringTag:void 0;function Cc(e){return e==null?e===void 0?FF:zF:dC&&dC in Object(e)?RF(e):VF(e)}function Gi(e){return e!=null&&typeof e=="object"}var HF="[object Symbol]";function _1(e){return typeof e=="symbol"||Gi(e)&&Cc(e)==HF}function a6(e,t){for(var n=-1,a=e==null?0:e.length,r=Array(a);++n0){if(++t>=mH)return arguments[0]}else t=0;return e.apply(void 0,arguments)}}function _H(e){return function(){return e}}var Hm=function(){try{var e=kc(Object,"defineProperty");return e({},"",{}),e}catch{}}(),wH=Hm?function(e,t){return Hm(e,"toString",{configurable:!0,enumerable:!1,value:_H(t),writable:!0})}:r6,K7=bH(wH);function SH(e,t){for(var n=-1,a=e==null?0:e.length;++n-1}var MH=9007199254740991,EH=/^(?:0|[1-9]\d*)$/;function w1(e,t){var n=typeof e;return t=t??MH,!!t&&(n=="number"||n!="symbol"&&EH.test(e))&&e>-1&&e%1==0&&e-1&&e%1==0&&e<=LH}function df(e){return e!=null&&l6(e.length)&&!o6(e)}function PH(e,t,n){if(!Tr(n))return!1;var a=typeof t;return(a=="number"?df(n)&&w1(t,n.length):a=="string"&&t in n)?Uh(n[t],e):!1}function DH(e){return Y7(function(t,n){var a=-1,r=n.length,o=r>1?n[r-1]:void 0,i=r>2?n[2]:void 0;for(o=e.length>3&&typeof o=="function"?(r--,o):void 0,i&&PH(n[0],n[1],i)&&(o=r<3?void 0:o,r=1),t=Object(t);++a-1}function WW(e,t){var n=this.__data__,a=C1(n,e);return a<0?(++this.size,n.push([e,t])):n[a][1]=t,this}function Bl(e){var t=-1,n=e==null?0:e.length;for(this.clear();++t0&&n(l)?t>1?jh(l,t-1,n,a,r):f6(r,l):a||(r[r.length]=l)}return r}function Wm(e){var t=e==null?0:e.length;return t?jh(e,1):[]}function Q7(e){return K7(G7(e,void 0,Wm),e+"")}var v6=J7(Object.getPrototypeOf,Object),rK="[object Object]",oK=Function.prototype,iK=Object.prototype,eA=oK.toString,lK=iK.hasOwnProperty,sK=eA.call(Object);function h6(e){if(!Gi(e)||Cc(e)!=rK)return!1;var t=v6(e);if(t===null)return!0;var n=lK.call(t,"constructor")&&t.constructor;return typeof n=="function"&&n instanceof n&&eA.call(n)==sK}function uK(e,t,n){var a=-1,r=e.length;t<0&&(t=-t>r?0:r+t),n=n>r?r:n,n<0&&(n+=r),r=t>n?0:n-t>>>0,t>>>=0;for(var o=Array(r);++a=t?e:t)),e}function Br(e,t,n){return n===void 0&&(n=t,t=void 0),n!==void 0&&(n=Mv(n),n=n===n?n:0),t!==void 0&&(t=Mv(t),t=t===t?t:0),cK(Mv(e),t,n)}function dK(){this.__data__=new Bl,this.size=0}function fK(e){var t=this.__data__,n=t.delete(e);return this.size=t.size,n}function vK(e){return this.__data__.get(e)}function hK(e){return this.__data__.has(e)}var pK=200;function mK(e,t){var n=this.__data__;if(n instanceof Bl){var a=n.__data__;if(!ih||a.lengthl))return!1;var u=o.get(e),c=o.get(t);if(u&&c)return u==t&&c==e;var d=-1,v=!0,f=n&KU?new lh:void 0;for(o.set(e,t),o.set(t,e);++d=t||T<0||d&&M>=o}function y(){var k=z2();if(g(k))return b(k);l=setTimeout(y,m(k))}function b(k){return l=void 0,v&&a?f(k):(a=r=void 0,i)}function _(){l!==void 0&&clearTimeout(l),u=0,a=s=r=l=void 0}function w(){return l===void 0?i:b(z2())}function S(){var k=z2(),T=g(k);if(a=arguments,r=this,s=k,T){if(l===void 0)return h(s);if(d)return clearTimeout(l),l=setTimeout(y,t),f(s)}return l===void 0&&(l=setTimeout(y,t)),i}return S.cancel=_,S.flush=w,S}function l4(e,t,n){(n!==void 0&&!Uh(e[t],n)||n===void 0&&!(t in e))&&S1(e,t,n)}function bA(e){return Gi(e)&&df(e)}function s4(e,t){if(!(t==="constructor"&&typeof e[t]=="function")&&t!="__proto__")return e[t]}function DG(e){return cf(e,Yh(e))}function OG(e,t,n,a,r,o,i){var l=s4(e,n),s=s4(t,n),u=i.get(s);if(u){l4(e,n,u);return}var c=o?o(l,s,n+"",e,t,i):void 0,d=c===void 0;if(d){var v=kr(s),f=!v&&rh(s),h=!v&&!f&&c6(s);c=s,v||f||h?kr(l)?c=l:bA(l)?c=W7(l):f?(d=!1,c=nA(s,!0)):h?(d=!1,c=lA(s,!0)):c=[]:h6(s)||ah(s)?(c=l,ah(l)?c=DG(l):(!Tr(l)||o6(l))&&(c=sA(s))):d=!1}d&&(i.set(s,c),r(c,s,a,o,i),i.delete(s)),l4(e,n,c)}function _A(e,t,n,a,r){e!==t&&yA(t,function(o,i){if(r||(r=new Xo),Tr(o))OG(e,t,i,n,_A,a,r);else{var l=a?a(s4(e,i),o,i+"",e,t,r):void 0;l===void 0&&(l=o),l4(e,i,l)}},Yh)}function $G(e){var t=e==null?0:e.length;return t?e[t-1]:void 0}function wA(e,t,n){var a=e==null?0:e.length;if(!a)return-1;var r=a-1;return U7(e,gA(t),r,!0)}function RG(e,t){var n=-1,a=df(e)?Array(e.length):[];return AG(e,function(r,o,i){a[++n]=t(r,o,i)}),a}function NG(e,t){var n=kr(e)?a6:RG;return n(e,gA(t))}function SA(e,t){return jh(NG(e,t),1)}var BG=1/0;function VG(e){var t=e==null?0:e.length;return t?jh(e,BG):[]}function Rd(e){for(var t=-1,n=e==null?0:e.length,a={};++t1),o}),cf(e,iA(e),n),a&&(n=Av(n,UG|GG|YG,KG));for(var r=t.length;r--;)WG(n,t[r]);return n});function kA(e,t,n,a){if(!Tr(e))return e;t=ff(t,e);for(var r=-1,o=t.length,i=o-1,l=e;l!=null&&++r=eY){var u=QG(e);if(u)return g6(u);i=!1,r=fA,s=new lh}else s=l;e:for(;++ae===void 0,vn=e=>typeof e=="boolean",Je=e=>typeof e=="number",_r=e=>!e&&e!==0||Ae(e)&&e.length===0||_t(e)&&!Object.keys(e).length,Vr=e=>typeof Element>"u"?!1:e instanceof Element,Qr=e=>Un(e),nY=e=>Ye(e)?!Number.isNaN(Number(e)):!1,qh=e=>e===window,sh=e=>Object.keys(e),TA=e=>Object.entries(e),Cs=(e,t,n)=>({get value(){return na(e,t,n)},set value(a){XG(e,t,a)}}),aY="__epPropKey",ae=e=>e,rY=e=>_t(e)&&!!e.__epPropKey,si=(e,t)=>{if(!_t(e)||rY(e))return e;const{values:n,required:a,default:r,type:o,validator:i}=e,l={type:o,required:!!a,validator:n||i?s=>{let u=!1,c=[];if(n&&(c=Array.from(n),en(e,"default")&&c.push(r),u||(u=c.includes(s))),i&&(u||(u=i(s))),!u&&c.length>0){const d=[...new Set(c)].map(v=>JSON.stringify(v)).join(", ");qz(`Invalid prop: validation failed${t?` for prop "${t}"`:""}. Expected one of [${d}], got value ${JSON.stringify(s)}.`)}return u}:void 0,[aY]:!0};return en(e,"default")&&(l.default=r),l},$e=e=>Rd(Object.entries(e).map(([t,n])=>[t,si(n,t)])),oY=$e({zIndex:{type:ae([Number,String]),default:100},target:{type:String,default:""},offset:{type:Number,default:0},position:{type:String,values:["top","bottom"],default:"top"},teleported:Boolean,appendTo:{type:ae([String,Object]),default:"body"}}),iY={scroll:({scrollTop:e,fixed:t})=>Je(e)&&vn(t),[Ot]:e=>vn(e)};var MA=class extends Error{constructor(e){super(e),this.name="ElementPlusError"}};function Dn(e,t){throw new MA(`[${e}] ${t}`)}function un(e,t){{const n=Ye(e)?new MA(`[${e}] ${t}`):e;console.warn(n)}}const lY=["class","style"],sY=/^on[A-Z]/,A1=(e={})=>{const{excludeListeners:t=!1,excludeKeys:n}=e,a=D(()=>((n==null?void 0:n.value)||[]).concat(lY)),r=Et();return r?D(()=>{var o;return Rd(Object.entries((o=r.proxy)==null?void 0:o.$attrs).filter(([i])=>!a.value.includes(i)&&!(t&&sY.test(i))))}):(un("use-attrs","getCurrentInstance() returned null. useAttrs() must be called at the top of a setup function"),D(()=>({})))};function EA(e,t){var n;const a=Ht();return ur(()=>{a.value=e()},{...t,flush:(n=t==null?void 0:t.flush)!==null&&n!==void 0?n:"sync"}),Ld(a)}function Ds(e,t){return B3()?(yE(e,t),!0):!1}const H2=new WeakMap,uY=(...e)=>{var t;const n=e[0],a=(t=Et())===null||t===void 0?void 0:t.proxy,r=a??B3();if(r==null&&!WE())throw new Error("injectLocal must be called in setup");return r&&H2.has(r)&&n in H2.get(r)?H2.get(r)[n]:Ze(...e)},qt=typeof window<"u"&&typeof document<"u";typeof WorkerGlobalScope<"u"&&globalThis instanceof WorkerGlobalScope;const cY=e=>typeof e<"u",AA=e=>e!=null,dY=Object.prototype.toString,fY=e=>dY.call(e)==="[object Object]",I1=(e,t,n)=>Math.min(n,Math.max(t,e)),Dr=()=>{},Nd=vY();function vY(){var e,t,n;return qt&&!!(!((e=window)===null||e===void 0||(e=e.navigator)===null||e===void 0)&&e.userAgent)&&(/iP(?:ad|hone|od)/.test(window.navigator.userAgent)||((t=window)===null||t===void 0||(t=t.navigator)===null||t===void 0?void 0:t.maxTouchPoints)>2&&/iPad|Macintosh/.test((n=window)===null||n===void 0?void 0:n.navigator.userAgent))}function IA(e,t){function n(...a){return new Promise((r,o)=>{Promise.resolve(e(()=>t.apply(this,a),{fn:t,thisArg:this,args:a})).then(r).catch(o)})}return n}function hY(e,t={}){let n,a,r=Dr;const o=s=>{clearTimeout(s),r(),r=Dr};let i;return s=>{const u=ia(e),c=ia(t.maxWait);return n&&o(n),u<=0||c!==void 0&&c<=0?(a&&(o(a),a=void 0),Promise.resolve(s())):new Promise((d,v)=>{r=t.rejectOnCancel?v:d,i=s,c&&!a&&(a=setTimeout(()=>{n&&o(n),a=void 0,d(i())},c)),n=setTimeout(()=>{a&&o(a),a=void 0,d(s())},u)})}}function pY(...e){let t=0,n,a=!0,r=Dr,o,i,l,s,u;!xn(e[0])&&typeof e[0]=="object"?{delay:i,trailing:l=!0,leading:s=!0,rejectOnCancel:u=!1}=e[0]:[i,l=!0,s=!0,u=!1]=e;const c=()=>{n&&(clearTimeout(n),n=void 0,r(),r=Dr)};return v=>{const f=ia(i),h=Date.now()-t,m=()=>o=v();return c(),f<=0?(t=Date.now(),m()):(h>f?(t=Date.now(),(s||!a)&&m()):l&&(o=new Promise((g,y)=>{r=u?y:g,n=setTimeout(()=>{t=Date.now(),a=!0,g(m()),c()},Math.max(0,f-h))})),!s&&!n&&(n=setTimeout(()=>a=!0,f)),a=!1,o)}}function zC(e){return e.endsWith("rem")?Number.parseFloat(e)*16:Number.parseFloat(e)}function bd(e){return Array.isArray(e)?e:[e]}function mY(e){return Et()}function gY(e){return nn(xn(e)?new Proxy({},{get(t,n,a){return p(Reflect.get(e.value,n,a))},set(t,n,a){return xn(e.value[n])&&!xn(a)?e.value[n].value=a:e.value[n]=a,!0},deleteProperty(t,n){return Reflect.deleteProperty(e.value,n)},has(t,n){return Reflect.has(e.value,n)},ownKeys(){return Object.keys(e.value)},getOwnPropertyDescriptor(){return{enumerable:!0,configurable:!0}}}):e)}function yY(e){return gY(D(e))}function Xh(e,t=200,n={}){return IA(hY(t,n),e)}function bY(e,t=200,n={}){const a=F(ia(e)),r=Xh(()=>{a.value=e.value},t,n);return ye(e,()=>r()),K3(a)}function LA(e,t=200,n=!1,a=!0,r=!1){return IA(pY(t,n,a,r),e)}function y6(e,t=!0,n){mY()?mt(e,n):t?e():He(e)}function _Y(e,t=1e3,n={}){const{immediate:a=!0,immediateCallback:r=!1}=n;let o=null;const i=Ht(!1);function l(){o&&(clearInterval(o),o=null)}function s(){i.value=!1,l()}function u(){const c=ia(t);c<=0||(i.value=!0,r&&e(),l(),i.value&&(o=setInterval(e,c)))}return a&&qt&&u(),(xn(t)||typeof t=="function")&&Ds(ye(t,()=>{i.value&&qt&&u()})),Ds(s),{isActive:K3(i),pause:s,resume:u}}function Bd(e,t,n={}){const{immediate:a=!0,immediateCallback:r=!1}=n,o=Ht(!1);let i;function l(){i&&(clearTimeout(i),i=void 0)}function s(){o.value=!1,l()}function u(...c){r&&e(),l(),o.value=!0,i=setTimeout(()=>{o.value=!1,i=void 0,e(...c)},ia(t))}return a&&(o.value=!0,qt&&u()),Ds(s),{isPending:K3(o),start:u,stop:s}}function wY(e,t,n){return ye(e,t,{...n,immediate:!0})}const Ho=qt?window:void 0,SY=qt?window.document:void 0;function da(e){var t;const n=ia(e);return(t=n==null?void 0:n.$el)!==null&&t!==void 0?t:n}function on(...e){const t=(a,r,o,i)=>(a.addEventListener(r,o,i),()=>a.removeEventListener(r,o,i)),n=D(()=>{const a=bd(ia(e[0])).filter(r=>r!=null);return a.every(r=>typeof r!="string")?a:void 0});return wY(()=>{var a,r;return[(a=(r=n.value)===null||r===void 0?void 0:r.map(o=>da(o)))!==null&&a!==void 0?a:[Ho].filter(o=>o!=null),bd(ia(n.value?e[1]:e[0])),bd(p(n.value?e[2]:e[1])),ia(n.value?e[3]:e[2])]},([a,r,o,i],l,s)=>{if(!(a!=null&&a.length)||!(r!=null&&r.length)||!(o!=null&&o.length))return;const u=fY(i)?{...i}:i,c=a.flatMap(d=>r.flatMap(v=>o.map(f=>t(d,v,f,u))));s(()=>{c.forEach(d=>d())})},{flush:"post"})}let FC=!1;function b6(e,t,n={}){const{window:a=Ho,ignore:r=[],capture:o=!0,detectIframe:i=!1,controls:l=!1}=n;if(!a)return l?{stop:Dr,cancel:Dr,trigger:Dr}:Dr;if(Nd&&!FC){FC=!0;const g={passive:!0};Array.from(a.document.body.children).forEach(y=>y.addEventListener("click",Dr,g)),a.document.documentElement.addEventListener("click",Dr,g)}let s=!0;const u=g=>ia(r).some(y=>{if(typeof y=="string")return Array.from(a.document.querySelectorAll(y)).some(b=>b===g.target||g.composedPath().includes(b));{const b=da(y);return b&&(g.target===b||g.composedPath().includes(b))}});function c(g){const y=ia(g);return y&&y.$.subTree.shapeFlag===16}function d(g,y){const b=ia(g),_=b.$.subTree&&b.$.subTree.children;return _==null||!Array.isArray(_)?!1:_.some(w=>w.el===y.target||y.composedPath().includes(w.el))}const v=g=>{const y=da(e);if(g.target!=null&&!(!(y instanceof Element)&&c(e)&&d(e,g))&&!(!y||y===g.target||g.composedPath().includes(y))){if("detail"in g&&g.detail===0&&(s=!u(g)),!s){s=!0;return}t(g)}};let f=!1;const h=[on(a,"click",g=>{f||(f=!0,setTimeout(()=>{f=!1},0),v(g))},{passive:!0,capture:o}),on(a,"pointerdown",g=>{const y=da(e);s=!u(g)&&!!(y&&!g.composedPath().includes(y))},{passive:!0}),i&&on(a,"blur",g=>{setTimeout(()=>{const y=da(e);let b=a.document.activeElement;for(;b!=null&&b.shadowRoot;)b=b.shadowRoot.activeElement;(b==null?void 0:b.tagName)==="IFRAME"&&!(y!=null&&y.contains(a.document.activeElement))&&t(g)},0)},{passive:!0})].filter(Boolean),m=()=>h.forEach(g=>g());return l?{stop:m,cancel:()=>{s=!1},trigger:g=>{s=!0,v(g),s=!1}}:m}function CY(){const e=Ht(!1),t=Et();return t&&mt(()=>{e.value=!0},t),e}function L1(e){const t=CY();return D(()=>(t.value,!!e()))}function Zh(e,t,n={}){const{window:a=Ho,...r}=n;let o;const i=L1(()=>a&&"MutationObserver"in a),l=()=>{o&&(o.disconnect(),o=void 0)},s=ye(D(()=>{const d=bd(ia(e)).map(da).filter(AA);return new Set(d)}),d=>{l(),i.value&&d.size&&(o=new MutationObserver(t),d.forEach(v=>o.observe(v,r)))},{immediate:!0,flush:"post"}),u=()=>o==null?void 0:o.takeRecords(),c=()=>{s(),l()};return Ds(c),{isSupported:i,stop:c,takeRecords:u}}function xY(e,t,n={}){const{window:a=Ho,document:r=a==null?void 0:a.document,flush:o="sync"}=n;if(!a||!r)return Dr;let i;const l=c=>{i==null||i(),i=c},s=ur(()=>{const c=da(e);if(c){const{stop:d}=Zh(r,v=>{v.map(f=>[...f.removedNodes]).flat().some(f=>f===c||f.contains(c))&&t(v)},{window:a,childList:!0,subtree:!0});l(d)}},{flush:o}),u=()=>{s(),l()};return Ds(u),u}function kY(e={}){var t;const{window:n=Ho,deep:a=!0,triggerOnRemoval:r=!1}=e,o=(t=e.document)!==null&&t!==void 0?t:n==null?void 0:n.document,i=()=>{let u=o==null?void 0:o.activeElement;if(a)for(var c;u!=null&&u.shadowRoot;)u=u==null||(c=u.shadowRoot)===null||c===void 0?void 0:c.activeElement;return u},l=Ht(),s=()=>{l.value=i()};if(n){const u={capture:!0,passive:!0};on(n,"blur",c=>{c.relatedTarget===null&&s()},u),on(n,"focus",s,u)}return r&&xY(l,s,{document:o}),s(),l}const TY=Symbol("vueuse-ssr-width");function MY(){const e=WE()?uY(TY,null):null;return typeof e=="number"?e:void 0}function EY(e,t={}){const{window:n=Ho,ssrWidth:a=MY()}=t,r=L1(()=>n&&"matchMedia"in n&&typeof n.matchMedia=="function"),o=Ht(typeof a=="number"),i=Ht(),l=Ht(!1),s=u=>{l.value=u.matches};return ur(()=>{if(o.value){o.value=!r.value,l.value=ia(e).split(",").some(u=>{const c=u.includes("not all"),d=u.match(/\(\s*min-width:\s*(-?\d+(?:\.\d*)?[a-z]+\s*)\)/),v=u.match(/\(\s*max-width:\s*(-?\d+(?:\.\d*)?[a-z]+\s*)\)/);let f=!!(d||v);return d&&f&&(f=a>=zC(d[1])),v&&f&&(f=a<=zC(v[1])),c?!f:f});return}r.value&&(i.value=n.matchMedia(ia(e)),l.value=i.value.matches)}),on(i,"change",s,{passive:!0}),D(()=>l.value)}function AY(e){return JSON.parse(JSON.stringify(e))}function IY(e,t,n={}){const{window:a=Ho,initialValue:r,observe:o=!1}=n,i=Ht(r),l=D(()=>{var u;return da(t)||(a==null||(u=a.document)===null||u===void 0?void 0:u.documentElement)});function s(){const u=ia(e),c=ia(l);if(c&&a&&u){var d;i.value=((d=a.getComputedStyle(c).getPropertyValue(u))===null||d===void 0?void 0:d.trim())||i.value||r}}return o&&Zh(l,s,{attributeFilter:["style","class"],window:a}),ye([l,()=>ia(e)],(u,c)=>{c[0]&&c[1]&&c[0].style.removeProperty(c[1]),s()},{immediate:!0}),ye([i,l],([u,c])=>{const d=ia(e);c!=null&&c.style&&d&&(u==null?c.style.removeProperty(d):c.style.setProperty(d,u))},{immediate:!0}),i}function LY(e={}){const{document:t=SY}=e;if(!t)return Ht("visible");const n=Ht(t.visibilityState);return on(t,"visibilitychange",()=>{n.value=t.visibilityState},{passive:!0}),n}function Mn(e,t,n={}){const{window:a=Ho,...r}=n;let o;const i=L1(()=>a&&"ResizeObserver"in a),l=()=>{o&&(o.disconnect(),o=void 0)},s=ye(D(()=>{const c=ia(e);return Array.isArray(c)?c.map(d=>da(d)):[da(c)]}),c=>{if(l(),i.value&&a){o=new ResizeObserver(t);for(const d of c)d&&o.observe(d,r)}},{immediate:!0,flush:"post"}),u=()=>{l(),s()};return Ds(u),{isSupported:i,stop:u}}function HC(e,t={}){const{reset:n=!0,windowResize:a=!0,windowScroll:r=!0,immediate:o=!0,updateTiming:i="sync"}=t,l=Ht(0),s=Ht(0),u=Ht(0),c=Ht(0),d=Ht(0),v=Ht(0),f=Ht(0),h=Ht(0);function m(){const y=da(e);if(!y){n&&(l.value=0,s.value=0,u.value=0,c.value=0,d.value=0,v.value=0,f.value=0,h.value=0);return}const b=y.getBoundingClientRect();l.value=b.height,s.value=b.bottom,u.value=b.left,c.value=b.right,d.value=b.top,v.value=b.width,f.value=b.x,h.value=b.y}function g(){i==="sync"?m():i==="next-frame"&&requestAnimationFrame(()=>m())}return Mn(e,g),ye(()=>da(e),y=>!y&&g()),Zh(e,g,{attributeFilter:["style","class"]}),r&&on("scroll",g,{capture:!0,passive:!0}),a&&on("resize",g,{passive:!0}),y6(()=>{o&&g()}),{height:l,bottom:s,left:u,right:c,top:d,width:v,x:f,y:h,update:g}}function Um(e,t={width:0,height:0},n={}){const{window:a=Ho,box:r="content-box"}=n,o=D(()=>{var d;return(d=da(e))===null||d===void 0||(d=d.namespaceURI)===null||d===void 0?void 0:d.includes("svg")}),i=Ht(t.width),l=Ht(t.height),{stop:s}=Mn(e,([d])=>{const v=r==="border-box"?d.borderBoxSize:r==="content-box"?d.contentBoxSize:d.devicePixelContentBoxSize;if(a&&o.value){const f=da(e);if(f){const h=f.getBoundingClientRect();i.value=h.width,l.value=h.height}}else if(v){const f=bd(v);i.value=f.reduce((h,{inlineSize:m})=>h+m,0),l.value=f.reduce((h,{blockSize:m})=>h+m,0)}else i.value=d.contentRect.width,l.value=d.contentRect.height},n);y6(()=>{const d=da(e);d&&(i.value="offsetWidth"in d?d.offsetWidth:t.width,l.value="offsetHeight"in d?d.offsetHeight:t.height)});const u=ye(()=>da(e),d=>{i.value=d?t.width:0,l.value=d?t.height:0});function c(){s(),u()}return{width:i,height:l,stop:c}}function PY(e,t,n={}){const{root:a,rootMargin:r,threshold:o=0,window:i=Ho,immediate:l=!0}=n,s=L1(()=>i&&"IntersectionObserver"in i),u=D(()=>bd(ia(e)).map(da).filter(AA));let c=Dr;const d=Ht(l),v=s.value?ye(()=>[u.value,da(a),ia(r),d.value],([h,m,g])=>{if(c(),!d.value||!h.length)return;const y=new IntersectionObserver(t,{root:da(m),rootMargin:g,threshold:o});h.forEach(b=>b&&y.observe(b)),c=()=>{y.disconnect(),c=Dr}},{immediate:l,flush:"post"}):Dr,f=()=>{c(),v(),d.value=!1};return Ds(f),{isSupported:s,isActive:d,pause(){c(),d.value=!1},resume(){d.value=!0},stop:f}}function DY(e,t,n,a={}){var r,o;const{clone:i=!1,passive:l=!1,eventName:s,deep:u=!1,defaultValue:c,shouldEmit:d}=a,v=Et(),f=(v==null?void 0:v.emit)||(v==null||(r=v.$emit)===null||r===void 0?void 0:r.bind(v))||(v==null||(o=v.proxy)===null||o===void 0||(o=o.$emit)===null||o===void 0?void 0:o.bind(v==null?void 0:v.proxy));let h=s;h=h||`update:${t.toString()}`;const m=b=>i?typeof i=="function"?i(b):AY(b):b,g=()=>cY(e[t])?m(e[t]):c,y=b=>{d?d(b)&&f(h,b):f(h,b)};if(l){const b=F(g());let _=!1;return ye(()=>e[t],w=>{_||(_=!0,b.value=m(w),He(()=>_=!1))}),ye(b,w=>{!_&&(w!==e[t]||u)&&y(w)},{deep:u}),b}else return D({get(){return g()},set(b){y(b)}})}function OY(e={}){const{window:t=Ho}=e;if(!t)return Ht(!1);const n=Ht(t.document.hasFocus()),a={passive:!0};return on(t,"blur",()=>{n.value=!1},a),on(t,"focus",()=>{n.value=!0},a),n}function _6(e={}){const{window:t=Ho,initialWidth:n=Number.POSITIVE_INFINITY,initialHeight:a=Number.POSITIVE_INFINITY,listenOrientation:r=!0,includeScrollbar:o=!0,type:i="inner"}=e,l=Ht(n),s=Ht(a),u=()=>{if(t)if(i==="outer")l.value=t.outerWidth,s.value=t.outerHeight;else if(i==="visual"&&t.visualViewport){const{width:d,height:v,scale:f}=t.visualViewport;l.value=Math.round(d*f),s.value=Math.round(v*f)}else o?(l.value=t.innerWidth,s.value=t.innerHeight):(l.value=t.document.documentElement.clientWidth,s.value=t.document.documentElement.clientHeight)};u(),y6(u);const c={passive:!0};return on("resize",u,c),t&&i==="visual"&&t.visualViewport&&on(t.visualViewport,"resize",u,c),r&&ye(EY("(orientation: portrait)"),()=>u()),{width:l,height:s}}function w6(){const e=Ht(),t=F(0),n=D(()=>({minWidth:`${Math.max(t.value,11)}px`}));return Mn(e,()=>{var r;t.value=((r=e.value)==null?void 0:r.getBoundingClientRect().width)??0}),{calculatorRef:e,calculatorWidth:t,inputStyle:n}}const Ri=({from:e,replacement:t,scope:n,version:a,ref:r,type:o="API"},i)=>{ye(()=>p(i),l=>{l&&un(n,`[${o}] ${e} is about to be deprecated in version ${a}, please use ${t} instead. +For more detail, please visit: ${r} +`)},{immediate:!0})},$Y='a[href],button:not([disabled]),button:not([hidden]),:not([tabindex="-1"]),input:not([disabled]),input:not([type="hidden"]),select:not([disabled]),textarea:not([disabled])',PA=e=>typeof ShadowRoot>"u"?!1:e instanceof ShadowRoot,WC=e=>typeof Element>"u"?!1:e instanceof Element,RY=e=>getComputedStyle(e).position==="fixed"?!1:e.offsetParent!==null,KC=e=>Array.from(e.querySelectorAll($Y)).filter(t=>sc(t)&&RY(t)),sc=e=>{if(e.tabIndex>0||e.tabIndex===0&&e.getAttribute("tabIndex")!==null)return!0;if(e.tabIndex<0||e.hasAttribute("disabled")||e.getAttribute("aria-disabled")==="true")return!1;switch(e.nodeName){case"A":return!!e.href&&e.rel!=="ignore";case"INPUT":return!(e.type==="hidden"||e.type==="file");case"BUTTON":case"SELECT":case"TEXTAREA":return!0;default:return!1}},X0=function(e,t,...n){let a;t.includes("mouse")||t.includes("click")?a="MouseEvents":t.includes("key")?a="KeyboardEvent":a="HTMLEvents";const r=document.createEvent(a);return r.initEvent(t,...n),e.dispatchEvent(r),e},DA=e=>!e.getAttribute("aria-owns"),OA=(e,t,n)=>{const{parentNode:a}=e;if(!a)return null;const r=a.querySelectorAll(n);return r[Array.prototype.indexOf.call(r,e)+t]||null},Jh=(e,t)=>{if(!e||!e.focus)return;let n=!1;WC(e)&&!sc(e)&&!e.getAttribute("tabindex")&&(e.setAttribute("tabindex","-1"),n=!0),e.focus(t),WC(e)&&n&&e.removeAttribute("tabindex")},_d=e=>{e&&(Jh(e),!DA(e)&&e.click())},P1=()=>qt&&/firefox/i.test(window.navigator.userAgent),$A=()=>qt&&/android/i.test(window.navigator.userAgent),S6=(e="")=>e.replace(/[|\\{}()[\]^$+*?.]/g,"\\$&").replace(/-/g,"\\x2d"),W2=e=>Fh(e),NY="utils/dom/style",RA=(e="")=>e.split(" ").filter(t=>!!t.trim()),Ni=(e,t)=>{if(!e||!t)return!1;if(t.includes(" "))throw new Error("className should not contain space.");return e.classList.contains(t)},Eo=(e,t)=>{!e||!t.trim()||e.classList.add(...RA(t))},ir=(e,t)=>{!e||!t.trim()||e.classList.remove(...RA(t))},ml=(e,t)=>{var a;if(!qt||!e||!t||PA(e))return"";let n=Va(t);n==="float"&&(n="cssFloat");try{const r=e.style[n];if(r)return r;const o=(a=document.defaultView)==null?void 0:a.getComputedStyle(e,"");return o?o[n]:""}catch{return e.style[n]}},NA=(e,t,n)=>{if(!(!e||!t))if(_t(t))TA(t).forEach(([a,r])=>NA(e,a,r));else{const a=Va(t);e.style[a]=n}};function Vn(e,t="px"){if(!e&&e!==0)return"";if(Je(e)||nY(e))return`${e}${t}`;if(Ye(e))return e;un(NY,"binding value must be a string or number")}const BA=(e,t,n,a)=>{const r={offsetX:0,offsetY:0},o=F(!1),i=(v,f)=>{if(e.value){const{offsetX:h,offsetY:m}=r,g=e.value.getBoundingClientRect(),y=Math.max(g.left,0),b=Math.max(g.top,0),_=g.width,w=g.height,S=document.documentElement.clientWidth,k=document.documentElement.clientHeight,T=-y+h,M=-b+m,E=S-y-_+h,A=k-b-(w{const f=v.clientX,h=v.clientY,{offsetX:m,offsetY:g}=r,y=_=>{o.value||(o.value=!0),i(m+_.clientX-f,g+_.clientY-h)},b=()=>{o.value=!1,document.removeEventListener("mousemove",y),document.removeEventListener("mouseup",b)};document.addEventListener("mousemove",y),document.addEventListener("mouseup",b)},s=()=>{t.value&&e.value&&(t.value.addEventListener("mousedown",l),window.addEventListener("resize",d))},u=()=>{t.value&&e.value&&(t.value.removeEventListener("mousedown",l),window.removeEventListener("resize",d))},c=()=>{r.offsetX=0,r.offsetY=0,e.value&&(e.value.style.transform="")},d=()=>{const{offsetX:v,offsetY:f}=r;i(v,f)};return mt(()=>{ur(()=>{n.value?s():u()})}),rn(()=>{u()}),{isDragging:o,resetPosition:c,updatePosition:d}};var BY={name:"en",el:{breadcrumb:{label:"Breadcrumb"},colorpicker:{confirm:"OK",clear:"Clear",defaultLabel:"color picker",description:"current color is {color}. press enter to select a new color.",alphaLabel:"pick alpha value",alphaDescription:"alpha {alpha}, current color is {color}",hueLabel:"pick hue value",hueDescription:"hue {hue}, current color is {color}",svLabel:"pick saturation and brightness value",svDescription:"saturation {saturation}, brightness {brightness}, current color is {color}",predefineDescription:"select {value} as the color"},datepicker:{now:"Now",today:"Today",cancel:"Cancel",clear:"Clear",confirm:"OK",dateTablePrompt:"Use the arrow keys and enter to select the day of the month",monthTablePrompt:"Use the arrow keys and enter to select the month",yearTablePrompt:"Use the arrow keys and enter to select the year",selectedDate:"Selected date",selectDate:"Select date",selectTime:"Select time",startDate:"Start Date",startTime:"Start Time",endDate:"End Date",endTime:"End Time",prevYear:"Previous Year",nextYear:"Next Year",prevMonth:"Previous Month",nextMonth:"Next Month",year:"",month1:"January",month2:"February",month3:"March",month4:"April",month5:"May",month6:"June",month7:"July",month8:"August",month9:"September",month10:"October",month11:"November",month12:"December",weeks:{sun:"Sun",mon:"Mon",tue:"Tue",wed:"Wed",thu:"Thu",fri:"Fri",sat:"Sat"},weeksFull:{sun:"Sunday",mon:"Monday",tue:"Tuesday",wed:"Wednesday",thu:"Thursday",fri:"Friday",sat:"Saturday"},months:{jan:"Jan",feb:"Feb",mar:"Mar",apr:"Apr",may:"May",jun:"Jun",jul:"Jul",aug:"Aug",sep:"Sep",oct:"Oct",nov:"Nov",dec:"Dec"}},inputNumber:{decrease:"decrease number",increase:"increase number"},select:{loading:"Loading",noMatch:"No matching data",noData:"No data",placeholder:"Select"},mention:{loading:"Loading"},dropdown:{toggleDropdown:"Toggle Dropdown"},cascader:{noMatch:"No matching data",loading:"Loading",placeholder:"Select",noData:"No data"},pagination:{goto:"Go to",pagesize:"/page",total:"Total {total}",pageClassifier:"",page:"Page",prev:"Go to previous page",next:"Go to next page",currentPage:"page {pager}",prevPages:"Previous {pager} pages",nextPages:"Next {pager} pages",deprecationWarning:"Deprecated usages detected, please refer to the el-pagination documentation for more details"},dialog:{close:"Close this dialog"},drawer:{close:"Close this dialog"},messagebox:{title:"Message",confirm:"OK",cancel:"Cancel",error:"Illegal input",close:"Close this dialog"},upload:{deleteTip:"press delete to remove",delete:"Delete",preview:"Preview",continue:"Continue"},slider:{defaultLabel:"slider between {min} and {max}",defaultRangeStartLabel:"pick start value",defaultRangeEndLabel:"pick end value"},table:{emptyText:"No Data",confirmFilter:"Confirm",resetFilter:"Reset",clearFilter:"All",sumText:"Sum",selectAllLabel:"Select all rows",selectRowLabel:"Select this row",expandRowLabel:"Expand this row",collapseRowLabel:"Collapse this row",sortLabel:"Sort by {column}",filterLabel:"Filter by {column}"},tag:{close:"Close this tag"},tour:{next:"Next",previous:"Previous",finish:"Finish",close:"Close this dialog"},tree:{emptyText:"No Data"},transfer:{noMatch:"No matching data",noData:"No data",titles:["List 1","List 2"],filterPlaceholder:"Enter keyword",noCheckedFormat:"{total} items",hasCheckedFormat:"{checked}/{total} checked"},image:{error:"FAILED"},pageHeader:{title:"Back"},popconfirm:{confirmButtonText:"Yes",cancelButtonText:"No"},carousel:{leftArrow:"Carousel arrow left",rightArrow:"Carousel arrow right",indicator:"Carousel switch to index {index}"},inputOTP:{groupLabel:"OTP Input",defaultLabel:"Please enter OTP character {index}"}}};const VY=e=>(t,n)=>zY(t,n,p(e)),zY=(e,t,n)=>na(n,e,e).replace(/\{(\w+)\}/g,(a,r)=>`${(t==null?void 0:t[r])??`{${r}}`}`),FY=e=>({lang:D(()=>p(e).name),locale:xn(e)?e:F(e),t:VY(e)}),VA=Symbol("localeContextKey"),Vt=e=>{const t=e||Ze(VA,F());return FY(D(()=>t.value||BY))},HY="is-",eu=(e,t,n,a,r)=>{let o=`${e}-${t}`;return n&&(o+=`-${n}`),a&&(o+=`__${a}`),r&&(o+=`--${r}`),o},zA=Symbol("namespaceContextKey"),C6=e=>{const t=e||(Et()?Ze(zA,F("el")):F("el"));return D(()=>p(t)||"el")},ke=(e,t)=>{const n=C6(t);return{namespace:n,b:(m="")=>eu(n.value,e,m,"",""),e:m=>m?eu(n.value,e,"",m,""):"",m:m=>m?eu(n.value,e,"","",m):"",be:(m,g)=>m&&g?eu(n.value,e,m,g,""):"",em:(m,g)=>m&&g?eu(n.value,e,"",m,g):"",bm:(m,g)=>m&&g?eu(n.value,e,m,"",g):"",bem:(m,g,y)=>m&&g&&y?eu(n.value,e,m,g,y):"",is:(m,...g)=>{const y=g.length>=1?g[0]:!0;return m&&y?`${HY}${m}`:""},cssVar:m=>{const g={};for(const y in m)m[y]&&(g[`--${n.value}-${y}`]=m[y]);return g},cssVarName:m=>`--${n.value}-${m}`,cssVarBlock:m=>{const g={};for(const y in m)m[y]&&(g[`--${n.value}-${e}-${y}`]=m[y]);return g},cssVarBlockName:m=>`--${n.value}-${e}-${m}`}};function WY(e,t,n,a){const r=n-t;return e/=a/2,e<1?r/2*e*e*e+t:r/2*((e-=2)*e*e+2)+t}const Wr=e=>qt?window.requestAnimationFrame(e):setTimeout(e,16),ti=e=>qt?window.cancelAnimationFrame(e):clearTimeout(e),KY=(e,t)=>{if(!qt)return!1;const n={undefined:"overflow",true:"overflow-y",false:"overflow-x"}[String(t)],a=ml(e,n);return["scroll","auto","overlay"].some(r=>a.includes(r))},x6=(e,t)=>{if(!qt)return;let n=e;for(;n;){if([window,document,document.documentElement].includes(n))return window;if(KY(n,t))return n;PA(n)?n=n.host:n=n.parentNode}return n};let Dp;const FA=e=>{var o;if(!qt)return 0;if(Dp!==void 0)return Dp;const t=document.createElement("div");t.className=`${e}-scrollbar__wrap`,t.style.visibility="hidden",t.style.width="100px",t.style.position="absolute",t.style.top="-9999px",document.body.appendChild(t);const n=t.offsetWidth;t.style.overflow="scroll";const a=document.createElement("div");a.style.width="100%",t.appendChild(a);const r=a.offsetWidth;return(o=t.parentNode)==null||o.removeChild(t),Dp=n-r,Dp};function k6(e,t){if(!qt)return;if(!t){e.scrollTop=0;return}const n=[];let a=t.offsetParent;for(;a!==null&&e!==a&&e.contains(a);)n.push(a),a=a.offsetParent;const r=t.offsetTop+n.reduce((s,u)=>s+u.offsetTop,0),o=r+t.offsetHeight,i=e.scrollTop,l=i+e.clientHeight;rl&&(e.scrollTop=o-e.clientHeight)}function UY(e,t,n,a,r){const o=Date.now();let i;const l=()=>{const s=Date.now()-o,u=WY(s>a?a:s,t,n,a);qh(e)?e.scrollTo(window.pageXOffset,u):e.scrollTop=u,s{i&&ti(i)}}const UC=(e,t)=>qh(t)?e.ownerDocument.documentElement:t,GC=e=>qh(e)?window.scrollY:e.scrollTop,D1=(e,t={})=>{xn(e)||Dn("[useLockscreen]","You need to pass a ref param to this function");const n=t.ns||ke("popup"),a=D(()=>n.bm("parent","hidden"));let r=0,o=!1,i="0",l=!1;const s=()=>{l||(l=!0,setTimeout(()=>{typeof document>"u"||o&&document&&(document.body.style.width=i,ir(document.body,a.value))},200))};ye(e,u=>{if(!u){s();return}l=!1,o=!Ni(document.body,a.value),o&&(i=document.body.style.width,Eo(document.body,a.value)),r=FA(n.namespace.value);const c=document.documentElement.clientHeight0&&(c||d==="scroll")&&o&&(document.body.style.width=`calc(100% - ${r}px)`)}),yE(()=>s())},_a=(e,t,{checkForDefaultPrevented:n=!0}={})=>r=>{const o=e==null?void 0:e(r);if(n===!1||!o)return t==null?void 0:t(r)},YC=e=>t=>t.pointerType==="mouse"?e(t):void 0,mn=e=>{if(e.code&&e.code!=="Unidentified")return e.code;const t=HA(e);if(t){if(Object.values(Pe).includes(t))return t;switch(t){case" ":return Pe.space;default:return""}}return""},HA=e=>{let t=e.key&&e.key!=="Unidentified"?e.key:"";if(!t&&e.type==="keyup"&&$A()){const n=e.target;t=n.value.charAt(n.selectionStart-1)}return t},GY=si({type:ae(Boolean),default:null}),YY=si({type:ae(Function)}),jY=e=>{const t=`update:${e}`,n=`onUpdate:${e}`,a=[t],r={[e]:GY,[n]:YY};return{useModelToggle:({indicator:i,toggleReason:l,shouldHideWhenRouteChanges:s,shouldProceed:u,onShow:c,onHide:d})=>{const v=Et(),{emit:f}=v,h=v.props,m=D(()=>Qe(h[n])),g=D(()=>h[e]===null),y=T=>{i.value!==!0&&(i.value=!0,l&&(l.value=T),Qe(c)&&c(T))},b=T=>{i.value!==!1&&(i.value=!1,l&&(l.value=T),Qe(d)&&d(T))},_=T=>{if(h.disabled===!0||Qe(u)&&!u())return;const M=m.value&&qt;M&&f(t,!0),(g.value||!M)&&y(T)},w=T=>{if(h.disabled===!0||!qt)return;const M=m.value&&qt;M&&f(t,!1),(g.value||!M)&&b(T)},S=T=>{vn(T)&&(h.disabled&&T?m.value&&f(t,!1):i.value!==T&&(T?y():b()))},k=()=>{i.value?w():_()};return ye(()=>h[e],S),s&&v.appContext.config.globalProperties.$route!==void 0&&ye(()=>({...v.proxy.$route}),()=>{s.value&&i.value&&w()}),mt(()=>{S(h[e])}),{hide:w,show:_,toggle:k,hasUpdateHandler:m}},useModelToggleProps:r,useModelToggleEmits:a}},WA=e=>{const t=Et();return D(()=>{var n,a;return(a=(n=t==null?void 0:t.proxy)==null?void 0:n.$props)==null?void 0:a[e]})};var zr="top",Bo="bottom",Vo="right",Fr="left",T6="auto",Qh=[zr,Bo,Vo,Fr],Vd="start",uh="end",qY="clippingParents",KA="viewport",Lf="popper",XY="reference",jC=Qh.reduce(function(e,t){return e.concat([t+"-"+Vd,t+"-"+uh])},[]),tl=[].concat(Qh,[T6]).reduce(function(e,t){return e.concat([t,t+"-"+Vd,t+"-"+uh])},[]),ZY="beforeRead",JY="read",QY="afterRead",ej="beforeMain",tj="main",nj="afterMain",aj="beforeWrite",rj="write",oj="afterWrite",ij=[ZY,JY,QY,ej,tj,nj,aj,rj,oj];function qi(e){return e?(e.nodeName||"").toLowerCase():null}function lo(e){if(e==null)return window;if(e.toString()!=="[object Window]"){var t=e.ownerDocument;return t&&t.defaultView||window}return e}function uc(e){var t=lo(e).Element;return e instanceof t||e instanceof Element}function Lo(e){var t=lo(e).HTMLElement;return e instanceof t||e instanceof HTMLElement}function M6(e){if(typeof ShadowRoot>"u")return!1;var t=lo(e).ShadowRoot;return e instanceof t||e instanceof ShadowRoot}function lj(e){var t=e.state;Object.keys(t.elements).forEach(function(n){var a=t.styles[n]||{},r=t.attributes[n]||{},o=t.elements[n];!Lo(o)||!qi(o)||(Object.assign(o.style,a),Object.keys(r).forEach(function(i){var l=r[i];l===!1?o.removeAttribute(i):o.setAttribute(i,l===!0?"":l)}))})}function sj(e){var t=e.state,n={popper:{position:t.options.strategy,left:"0",top:"0",margin:"0"},arrow:{position:"absolute"},reference:{}};return Object.assign(t.elements.popper.style,n.popper),t.styles=n,t.elements.arrow&&Object.assign(t.elements.arrow.style,n.arrow),function(){Object.keys(t.elements).forEach(function(a){var r=t.elements[a],o=t.attributes[a]||{},i=Object.keys(t.styles.hasOwnProperty(a)?t.styles[a]:n[a]),l=i.reduce(function(s,u){return s[u]="",s},{});!Lo(r)||!qi(r)||(Object.assign(r.style,l),Object.keys(o).forEach(function(s){r.removeAttribute(s)}))})}}var UA={name:"applyStyles",enabled:!0,phase:"write",fn:lj,effect:sj,requires:["computeStyles"]};function Bi(e){return e.split("-")[0]}var Yu=Math.max,Gm=Math.min,zd=Math.round;function u4(){var e=navigator.userAgentData;return e!=null&&e.brands&&Array.isArray(e.brands)?e.brands.map(function(t){return t.brand+"/"+t.version}).join(" "):navigator.userAgent}function GA(){return!/^((?!chrome|android).)*safari/i.test(u4())}function Fd(e,t,n){t===void 0&&(t=!1),n===void 0&&(n=!1);var a=e.getBoundingClientRect(),r=1,o=1;t&&Lo(e)&&(r=e.offsetWidth>0&&zd(a.width)/e.offsetWidth||1,o=e.offsetHeight>0&&zd(a.height)/e.offsetHeight||1);var i=uc(e)?lo(e):window,l=i.visualViewport,s=!GA()&&n,u=(a.left+(s&&l?l.offsetLeft:0))/r,c=(a.top+(s&&l?l.offsetTop:0))/o,d=a.width/r,v=a.height/o;return{width:d,height:v,top:c,right:u+d,bottom:c+v,left:u,x:u,y:c}}function E6(e){var t=Fd(e),n=e.offsetWidth,a=e.offsetHeight;return Math.abs(t.width-n)<=1&&(n=t.width),Math.abs(t.height-a)<=1&&(a=t.height),{x:e.offsetLeft,y:e.offsetTop,width:n,height:a}}function YA(e,t){var n=t.getRootNode&&t.getRootNode();if(e.contains(t))return!0;if(n&&M6(n)){var a=t;do{if(a&&e.isSameNode(a))return!0;a=a.parentNode||a.host}while(a)}return!1}function El(e){return lo(e).getComputedStyle(e)}function uj(e){return["table","td","th"].indexOf(qi(e))>=0}function Gs(e){return((uc(e)?e.ownerDocument:e.document)||window.document).documentElement}function O1(e){return qi(e)==="html"?e:e.assignedSlot||e.parentNode||(M6(e)?e.host:null)||Gs(e)}function qC(e){return!Lo(e)||El(e).position==="fixed"?null:e.offsetParent}function cj(e){var t=/firefox/i.test(u4()),n=/Trident/i.test(u4());if(n&&Lo(e)){var a=El(e);if(a.position==="fixed")return null}var r=O1(e);for(M6(r)&&(r=r.host);Lo(r)&&["html","body"].indexOf(qi(r))<0;){var o=El(r);if(o.transform!=="none"||o.perspective!=="none"||o.contain==="paint"||["transform","perspective"].indexOf(o.willChange)!==-1||t&&o.willChange==="filter"||t&&o.filter&&o.filter!=="none")return r;r=r.parentNode}return null}function ep(e){for(var t=lo(e),n=qC(e);n&&uj(n)&&El(n).position==="static";)n=qC(n);return n&&(qi(n)==="html"||qi(n)==="body"&&El(n).position==="static")?t:n||cj(e)||t}function A6(e){return["top","bottom"].indexOf(e)>=0?"x":"y"}function Iv(e,t,n){return Yu(e,Gm(t,n))}function dj(e,t,n){var a=Iv(e,t,n);return a>n?n:a}function jA(){return{top:0,right:0,bottom:0,left:0}}function qA(e){return Object.assign({},jA(),e)}function XA(e,t){return t.reduce(function(n,a){return n[a]=e,n},{})}var fj=function(e,t){return e=typeof e=="function"?e(Object.assign({},t.rects,{placement:t.placement})):e,qA(typeof e!="number"?e:XA(e,Qh))};function vj(e){var t,n=e.state,a=e.name,r=e.options,o=n.elements.arrow,i=n.modifiersData.popperOffsets,l=Bi(n.placement),s=A6(l),u=[Fr,Vo].indexOf(l)>=0,c=u?"height":"width";if(!(!o||!i)){var d=fj(r.padding,n),v=E6(o),f=s==="y"?zr:Fr,h=s==="y"?Bo:Vo,m=n.rects.reference[c]+n.rects.reference[s]-i[s]-n.rects.popper[c],g=i[s]-n.rects.reference[s],y=ep(o),b=y?s==="y"?y.clientHeight||0:y.clientWidth||0:0,_=m/2-g/2,w=d[f],S=b-v[c]-d[h],k=b/2-v[c]/2+_,T=Iv(w,k,S),M=s;n.modifiersData[a]=(t={},t[M]=T,t.centerOffset=T-k,t)}}function hj(e){var t=e.state,n=e.options,a=n.element,r=a===void 0?"[data-popper-arrow]":a;r!=null&&(typeof r=="string"&&(r=t.elements.popper.querySelector(r),!r)||YA(t.elements.popper,r)&&(t.elements.arrow=r))}var pj={name:"arrow",enabled:!0,phase:"main",fn:vj,effect:hj,requires:["popperOffsets"],requiresIfExists:["preventOverflow"]};function Hd(e){return e.split("-")[1]}var mj={top:"auto",right:"auto",bottom:"auto",left:"auto"};function gj(e,t){var n=e.x,a=e.y,r=t.devicePixelRatio||1;return{x:zd(n*r)/r||0,y:zd(a*r)/r||0}}function XC(e){var t,n=e.popper,a=e.popperRect,r=e.placement,o=e.variation,i=e.offsets,l=e.position,s=e.gpuAcceleration,u=e.adaptive,c=e.roundOffsets,d=e.isFixed,v=i.x,f=v===void 0?0:v,h=i.y,m=h===void 0?0:h,g=typeof c=="function"?c({x:f,y:m}):{x:f,y:m};f=g.x,m=g.y;var y=i.hasOwnProperty("x"),b=i.hasOwnProperty("y"),_=Fr,w=zr,S=window;if(u){var k=ep(n),T="clientHeight",M="clientWidth";if(k===lo(n)&&(k=Gs(n),El(k).position!=="static"&&l==="absolute"&&(T="scrollHeight",M="scrollWidth")),k=k,r===zr||(r===Fr||r===Vo)&&o===uh){w=Bo;var E=d&&k===S&&S.visualViewport?S.visualViewport.height:k[T];m-=E-a.height,m*=s?1:-1}if(r===Fr||(r===zr||r===Bo)&&o===uh){_=Vo;var A=d&&k===S&&S.visualViewport?S.visualViewport.width:k[M];f-=A-a.width,f*=s?1:-1}}var I=Object.assign({position:l},u&&mj),P=c===!0?gj({x:f,y:m},lo(n)):{x:f,y:m};if(f=P.x,m=P.y,s){var R;return Object.assign({},I,(R={},R[w]=b?"0":"",R[_]=y?"0":"",R.transform=(S.devicePixelRatio||1)<=1?"translate("+f+"px, "+m+"px)":"translate3d("+f+"px, "+m+"px, 0)",R))}return Object.assign({},I,(t={},t[w]=b?m+"px":"",t[_]=y?f+"px":"",t.transform="",t))}function yj(e){var t=e.state,n=e.options,a=n.gpuAcceleration,r=a===void 0?!0:a,o=n.adaptive,i=o===void 0?!0:o,l=n.roundOffsets,s=l===void 0?!0:l,u={placement:Bi(t.placement),variation:Hd(t.placement),popper:t.elements.popper,popperRect:t.rects.popper,gpuAcceleration:r,isFixed:t.options.strategy==="fixed"};t.modifiersData.popperOffsets!=null&&(t.styles.popper=Object.assign({},t.styles.popper,XC(Object.assign({},u,{offsets:t.modifiersData.popperOffsets,position:t.options.strategy,adaptive:i,roundOffsets:s})))),t.modifiersData.arrow!=null&&(t.styles.arrow=Object.assign({},t.styles.arrow,XC(Object.assign({},u,{offsets:t.modifiersData.arrow,position:"absolute",adaptive:!1,roundOffsets:s})))),t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-placement":t.placement})}var ZA={name:"computeStyles",enabled:!0,phase:"beforeWrite",fn:yj,data:{}},Op={passive:!0};function bj(e){var t=e.state,n=e.instance,a=e.options,r=a.scroll,o=r===void 0?!0:r,i=a.resize,l=i===void 0?!0:i,s=lo(t.elements.popper),u=[].concat(t.scrollParents.reference,t.scrollParents.popper);return o&&u.forEach(function(c){c.addEventListener("scroll",n.update,Op)}),l&&s.addEventListener("resize",n.update,Op),function(){o&&u.forEach(function(c){c.removeEventListener("scroll",n.update,Op)}),l&&s.removeEventListener("resize",n.update,Op)}}var JA={name:"eventListeners",enabled:!0,phase:"write",fn:function(){},effect:bj,data:{}},_j={left:"right",right:"left",bottom:"top",top:"bottom"};function Z0(e){return e.replace(/left|right|bottom|top/g,function(t){return _j[t]})}var wj={start:"end",end:"start"};function ZC(e){return e.replace(/start|end/g,function(t){return wj[t]})}function I6(e){var t=lo(e),n=t.pageXOffset,a=t.pageYOffset;return{scrollLeft:n,scrollTop:a}}function L6(e){return Fd(Gs(e)).left+I6(e).scrollLeft}function Sj(e,t){var n=lo(e),a=Gs(e),r=n.visualViewport,o=a.clientWidth,i=a.clientHeight,l=0,s=0;if(r){o=r.width,i=r.height;var u=GA();(u||!u&&t==="fixed")&&(l=r.offsetLeft,s=r.offsetTop)}return{width:o,height:i,x:l+L6(e),y:s}}function Cj(e){var t,n=Gs(e),a=I6(e),r=(t=e.ownerDocument)==null?void 0:t.body,o=Yu(n.scrollWidth,n.clientWidth,r?r.scrollWidth:0,r?r.clientWidth:0),i=Yu(n.scrollHeight,n.clientHeight,r?r.scrollHeight:0,r?r.clientHeight:0),l=-a.scrollLeft+L6(e),s=-a.scrollTop;return El(r||n).direction==="rtl"&&(l+=Yu(n.clientWidth,r?r.clientWidth:0)-o),{width:o,height:i,x:l,y:s}}function P6(e){var t=El(e),n=t.overflow,a=t.overflowX,r=t.overflowY;return/auto|scroll|overlay|hidden/.test(n+r+a)}function QA(e){return["html","body","#document"].indexOf(qi(e))>=0?e.ownerDocument.body:Lo(e)&&P6(e)?e:QA(O1(e))}function Lv(e,t){var n;t===void 0&&(t=[]);var a=QA(e),r=a===((n=e.ownerDocument)==null?void 0:n.body),o=lo(a),i=r?[o].concat(o.visualViewport||[],P6(a)?a:[]):a,l=t.concat(i);return r?l:l.concat(Lv(O1(i)))}function c4(e){return Object.assign({},e,{left:e.x,top:e.y,right:e.x+e.width,bottom:e.y+e.height})}function xj(e,t){var n=Fd(e,!1,t==="fixed");return n.top=n.top+e.clientTop,n.left=n.left+e.clientLeft,n.bottom=n.top+e.clientHeight,n.right=n.left+e.clientWidth,n.width=e.clientWidth,n.height=e.clientHeight,n.x=n.left,n.y=n.top,n}function JC(e,t,n){return t===KA?c4(Sj(e,n)):uc(t)?xj(t,n):c4(Cj(Gs(e)))}function kj(e){var t=Lv(O1(e)),n=["absolute","fixed"].indexOf(El(e).position)>=0,a=n&&Lo(e)?ep(e):e;return uc(a)?t.filter(function(r){return uc(r)&&YA(r,a)&&qi(r)!=="body"}):[]}function Tj(e,t,n,a){var r=t==="clippingParents"?kj(e):[].concat(t),o=[].concat(r,[n]),i=o[0],l=o.reduce(function(s,u){var c=JC(e,u,a);return s.top=Yu(c.top,s.top),s.right=Gm(c.right,s.right),s.bottom=Gm(c.bottom,s.bottom),s.left=Yu(c.left,s.left),s},JC(e,i,a));return l.width=l.right-l.left,l.height=l.bottom-l.top,l.x=l.left,l.y=l.top,l}function eI(e){var t=e.reference,n=e.element,a=e.placement,r=a?Bi(a):null,o=a?Hd(a):null,i=t.x+t.width/2-n.width/2,l=t.y+t.height/2-n.height/2,s;switch(r){case zr:s={x:i,y:t.y-n.height};break;case Bo:s={x:i,y:t.y+t.height};break;case Vo:s={x:t.x+t.width,y:l};break;case Fr:s={x:t.x-n.width,y:l};break;default:s={x:t.x,y:t.y}}var u=r?A6(r):null;if(u!=null){var c=u==="y"?"height":"width";switch(o){case Vd:s[u]=s[u]-(t[c]/2-n[c]/2);break;case uh:s[u]=s[u]+(t[c]/2-n[c]/2);break}}return s}function ch(e,t){t===void 0&&(t={});var n=t,a=n.placement,r=a===void 0?e.placement:a,o=n.strategy,i=o===void 0?e.strategy:o,l=n.boundary,s=l===void 0?qY:l,u=n.rootBoundary,c=u===void 0?KA:u,d=n.elementContext,v=d===void 0?Lf:d,f=n.altBoundary,h=f===void 0?!1:f,m=n.padding,g=m===void 0?0:m,y=qA(typeof g!="number"?g:XA(g,Qh)),b=v===Lf?XY:Lf,_=e.rects.popper,w=e.elements[h?b:v],S=Tj(uc(w)?w:w.contextElement||Gs(e.elements.popper),s,c,i),k=Fd(e.elements.reference),T=eI({reference:k,element:_,placement:r}),M=c4(Object.assign({},_,T)),E=v===Lf?M:k,A={top:S.top-E.top+y.top,bottom:E.bottom-S.bottom+y.bottom,left:S.left-E.left+y.left,right:E.right-S.right+y.right},I=e.modifiersData.offset;if(v===Lf&&I){var P=I[r];Object.keys(A).forEach(function(R){var G=[Vo,Bo].indexOf(R)>=0?1:-1,K=[zr,Bo].indexOf(R)>=0?"y":"x";A[R]+=P[K]*G})}return A}function Mj(e,t){t===void 0&&(t={});var n=t,a=n.placement,r=n.boundary,o=n.rootBoundary,i=n.padding,l=n.flipVariations,s=n.allowedAutoPlacements,u=s===void 0?tl:s,c=Hd(a),d=c?l?jC:jC.filter(function(h){return Hd(h)===c}):Qh,v=d.filter(function(h){return u.indexOf(h)>=0});v.length===0&&(v=d);var f=v.reduce(function(h,m){return h[m]=ch(e,{placement:m,boundary:r,rootBoundary:o,padding:i})[Bi(m)],h},{});return Object.keys(f).sort(function(h,m){return f[h]-f[m]})}function Ej(e){if(Bi(e)===T6)return[];var t=Z0(e);return[ZC(e),t,ZC(t)]}function Aj(e){var t=e.state,n=e.options,a=e.name;if(!t.modifiersData[a]._skip){for(var r=n.mainAxis,o=r===void 0?!0:r,i=n.altAxis,l=i===void 0?!0:i,s=n.fallbackPlacements,u=n.padding,c=n.boundary,d=n.rootBoundary,v=n.altBoundary,f=n.flipVariations,h=f===void 0?!0:f,m=n.allowedAutoPlacements,g=t.options.placement,y=Bi(g),b=y===g,_=s||(b||!h?[Z0(g)]:Ej(g)),w=[g].concat(_).reduce(function(ee,re){return ee.concat(Bi(re)===T6?Mj(t,{placement:re,boundary:c,rootBoundary:d,padding:u,flipVariations:h,allowedAutoPlacements:m}):re)},[]),S=t.rects.reference,k=t.rects.popper,T=new Map,M=!0,E=w[0],A=0;A=0,K=G?"width":"height",H=ch(t,{placement:I,boundary:c,rootBoundary:d,altBoundary:v,padding:u}),z=G?R?Vo:Fr:R?Bo:zr;S[K]>k[K]&&(z=Z0(z));var B=Z0(z),W=[];if(o&&W.push(H[P]<=0),l&&W.push(H[z]<=0,H[B]<=0),W.every(function(ee){return ee})){E=I,M=!1;break}T.set(I,W)}if(M)for(var N=h?3:1,V=function(ee){var re=w.find(function(ie){var le=T.get(ie);if(le)return le.slice(0,ee).every(function(ve){return ve})});if(re)return E=re,"break"},Y=N;Y>0;Y--){var X=V(Y);if(X==="break")break}t.placement!==E&&(t.modifiersData[a]._skip=!0,t.placement=E,t.reset=!0)}}var Ij={name:"flip",enabled:!0,phase:"main",fn:Aj,requiresIfExists:["offset"],data:{_skip:!1}};function QC(e,t,n){return n===void 0&&(n={x:0,y:0}),{top:e.top-t.height-n.y,right:e.right-t.width+n.x,bottom:e.bottom-t.height+n.y,left:e.left-t.width-n.x}}function e5(e){return[zr,Vo,Bo,Fr].some(function(t){return e[t]>=0})}function Lj(e){var t=e.state,n=e.name,a=t.rects.reference,r=t.rects.popper,o=t.modifiersData.preventOverflow,i=ch(t,{elementContext:"reference"}),l=ch(t,{altBoundary:!0}),s=QC(i,a),u=QC(l,r,o),c=e5(s),d=e5(u);t.modifiersData[n]={referenceClippingOffsets:s,popperEscapeOffsets:u,isReferenceHidden:c,hasPopperEscaped:d},t.attributes.popper=Object.assign({},t.attributes.popper,{"data-popper-reference-hidden":c,"data-popper-escaped":d})}var Pj={name:"hide",enabled:!0,phase:"main",requiresIfExists:["preventOverflow"],fn:Lj};function Dj(e,t,n){var a=Bi(e),r=[Fr,zr].indexOf(a)>=0?-1:1,o=typeof n=="function"?n(Object.assign({},t,{placement:e})):n,i=o[0],l=o[1];return i=i||0,l=(l||0)*r,[Fr,Vo].indexOf(a)>=0?{x:l,y:i}:{x:i,y:l}}function Oj(e){var t=e.state,n=e.options,a=e.name,r=n.offset,o=r===void 0?[0,0]:r,i=tl.reduce(function(c,d){return c[d]=Dj(d,t.rects,o),c},{}),l=i[t.placement],s=l.x,u=l.y;t.modifiersData.popperOffsets!=null&&(t.modifiersData.popperOffsets.x+=s,t.modifiersData.popperOffsets.y+=u),t.modifiersData[a]=i}var $j={name:"offset",enabled:!0,phase:"main",requires:["popperOffsets"],fn:Oj};function Rj(e){var t=e.state,n=e.name;t.modifiersData[n]=eI({reference:t.rects.reference,element:t.rects.popper,placement:t.placement})}var tI={name:"popperOffsets",enabled:!0,phase:"read",fn:Rj,data:{}};function Nj(e){return e==="x"?"y":"x"}function Bj(e){var t=e.state,n=e.options,a=e.name,r=n.mainAxis,o=r===void 0?!0:r,i=n.altAxis,l=i===void 0?!1:i,s=n.boundary,u=n.rootBoundary,c=n.altBoundary,d=n.padding,v=n.tether,f=v===void 0?!0:v,h=n.tetherOffset,m=h===void 0?0:h,g=ch(t,{boundary:s,rootBoundary:u,padding:d,altBoundary:c}),y=Bi(t.placement),b=Hd(t.placement),_=!b,w=A6(y),S=Nj(w),k=t.modifiersData.popperOffsets,T=t.rects.reference,M=t.rects.popper,E=typeof m=="function"?m(Object.assign({},t.rects,{placement:t.placement})):m,A=typeof E=="number"?{mainAxis:E,altAxis:E}:Object.assign({mainAxis:0,altAxis:0},E),I=t.modifiersData.offset?t.modifiersData.offset[t.placement]:null,P={x:0,y:0};if(k){if(o){var R,G=w==="y"?zr:Fr,K=w==="y"?Bo:Vo,H=w==="y"?"height":"width",z=k[w],B=z+g[G],W=z-g[K],N=f?-M[H]/2:0,V=b===Vd?T[H]:M[H],Y=b===Vd?-M[H]:-T[H],X=t.elements.arrow,ee=f&&X?E6(X):{width:0,height:0},re=t.modifiersData["arrow#persistent"]?t.modifiersData["arrow#persistent"].padding:jA(),ie=re[G],le=re[K],ve=Iv(0,T[H],ee[H]),he=_?T[H]/2-N-ve-ie-A.mainAxis:V-ve-ie-A.mainAxis,J=_?-T[H]/2+N+ve+le+A.mainAxis:Y+ve+le+A.mainAxis,Z=t.elements.arrow&&ep(t.elements.arrow),j=Z?w==="y"?Z.clientTop||0:Z.clientLeft||0:0,oe=(R=I==null?void 0:I[w])!=null?R:0,se=z+he-oe-j,fe=z+J-oe,ge=Iv(f?Gm(B,se):B,z,f?Yu(W,fe):W);k[w]=ge,P[w]=ge-z}if(l){var Se,De=w==="x"?zr:Fr,Te=w==="x"?Bo:Vo,Re=k[S],Ce=S==="y"?"height":"width",Le=Re+g[De],me=Re-g[Te],Be=[zr,Fr].indexOf(y)!==-1,ze=(Se=I==null?void 0:I[S])!=null?Se:0,xe=Be?Le:Re-T[Ce]-M[Ce]-ze+A.altAxis,rt=Be?Re+T[Ce]+M[Ce]-ze-A.altAxis:me,nt=f&&Be?dj(xe,Re,rt):Iv(f?xe:Le,Re,f?rt:me);k[S]=nt,P[S]=nt-Re}t.modifiersData[a]=P}}var Vj={name:"preventOverflow",enabled:!0,phase:"main",fn:Bj,requiresIfExists:["offset"]};function zj(e){return{scrollLeft:e.scrollLeft,scrollTop:e.scrollTop}}function Fj(e){return e===lo(e)||!Lo(e)?I6(e):zj(e)}function Hj(e){var t=e.getBoundingClientRect(),n=zd(t.width)/e.offsetWidth||1,a=zd(t.height)/e.offsetHeight||1;return n!==1||a!==1}function Wj(e,t,n){n===void 0&&(n=!1);var a=Lo(t),r=Lo(t)&&Hj(t),o=Gs(t),i=Fd(e,r,n),l={scrollLeft:0,scrollTop:0},s={x:0,y:0};return(a||!a&&!n)&&((qi(t)!=="body"||P6(o))&&(l=Fj(t)),Lo(t)?(s=Fd(t,!0),s.x+=t.clientLeft,s.y+=t.clientTop):o&&(s.x=L6(o))),{x:i.left+l.scrollLeft-s.x,y:i.top+l.scrollTop-s.y,width:i.width,height:i.height}}function Kj(e){var t=new Map,n=new Set,a=[];e.forEach(function(o){t.set(o.name,o)});function r(o){n.add(o.name);var i=[].concat(o.requires||[],o.requiresIfExists||[]);i.forEach(function(l){if(!n.has(l)){var s=t.get(l);s&&r(s)}}),a.push(o)}return e.forEach(function(o){n.has(o.name)||r(o)}),a}function Uj(e){var t=Kj(e);return ij.reduce(function(n,a){return n.concat(t.filter(function(r){return r.phase===a}))},[])}function Gj(e){var t;return function(){return t||(t=new Promise(function(n){Promise.resolve().then(function(){t=void 0,n(e())})})),t}}function Yj(e){var t=e.reduce(function(n,a){var r=n[a.name];return n[a.name]=r?Object.assign({},r,a,{options:Object.assign({},r.options,a.options),data:Object.assign({},r.data,a.data)}):a,n},{});return Object.keys(t).map(function(n){return t[n]})}var t5={placement:"bottom",modifiers:[],strategy:"absolute"};function n5(){for(var e=arguments.length,t=new Array(e),n=0;n{const a={name:"updateState",enabled:!0,phase:"write",fn:({state:s})=>{const u=Jj(s);Object.assign(i.value,u)},requires:["computeStyles"]},r=D(()=>{const{onFirstUpdate:s,placement:u,strategy:c,modifiers:d}=p(n);return{onFirstUpdate:s,placement:u||"bottom",strategy:c||"absolute",modifiers:[...d||[],a,{name:"applyStyles",enabled:!1}]}}),o=Ht(),i=F({styles:{popper:{position:p(r).strategy,left:"0",top:"0"},arrow:{position:"absolute"}},attributes:{}}),l=()=>{o.value&&(o.value.destroy(),o.value=void 0)};return ye(r,s=>{const u=p(o);u&&u.setOptions(s)},{deep:!0}),ye([e,t],([s,u])=>{l(),!(!s||!u)&&(o.value=Xj(s,u,p(r)))}),rn(()=>{l()}),{state:D(()=>{var s;return{...((s=p(o))==null?void 0:s.state)||{}}}),styles:D(()=>p(i).styles),attributes:D(()=>p(i).attributes),update:()=>{var s;return(s=p(o))==null?void 0:s.update()},forceUpdate:()=>{var s;return(s=p(o))==null?void 0:s.forceUpdate()},instanceRef:D(()=>p(o))}};function Jj(e){const t=Object.keys(e.elements);return{styles:Rd(t.map(n=>[n,e.styles[n]||{}])),attributes:Rd(t.map(n=>[n,e.attributes[n]]))}}const O6=e=>{if(!e)return{onClick:kt,onMousedown:kt,onMouseup:kt};let t=!1,n=!1;return{onClick:i=>{t&&n&&e(i),t=n=!1},onMousedown:i=>{t=i.target===i.currentTarget},onMouseup:i=>{n=i.target===i.currentTarget}}},Qj=(e,t=0)=>{if(t===0)return e;const n=F(_t(t)&&!!t.initVal);let a=null;const r=i=>{if(zt(i)){n.value=e.value;return}a&&clearTimeout(a),a=setTimeout(()=>{n.value=e.value},i)},o=i=>{i==="leading"?Je(t)?r(t):r(t.leading):_t(t)?r(t.trailing):n.value=!1};return mt(()=>o("leading")),ye(()=>e.value,i=>{o(i?"leading":"trailing")}),n};function a5(){let e;const t=(a,r)=>{n(),e=globalThis.setTimeout(a,r)},n=()=>{e!==void 0&&(globalThis.clearTimeout(e),e=void 0)};return Ds(()=>n()),{registerTimeout:t,cancelTimeout:n}}const d4={prefix:Math.floor(Math.random()*1e4),current:0},eq=Symbol("elIdInjection"),$6=()=>Et()?Ze(eq,d4):d4,Fa=e=>{const t=$6();!qt&&t===d4&&un("IdInjection",`Looks like you are using server rendering, you must provide a id provider to ensure the hydration process to be succeed +usage: app.provide(ID_INJECTION_KEY, { + prefix: number, + current: number, +})`);const n=C6();return EA(()=>p(e)||`${n.value}-id-${t.prefix}-${t.current++}`)};let nd=[];const r5=e=>{mn(e)===Pe.esc&&nd.forEach(t=>t(e))},tq=e=>{mt(()=>{nd.length===0&&document.addEventListener("keydown",r5),qt&&nd.push(e)}),rn(()=>{nd=nd.filter(t=>t!==e),nd.length===0&&qt&&document.removeEventListener("keydown",r5)})},nI=()=>{const e=C6(),t=$6(),n=D(()=>`${e.value}-popper-container-${t.prefix}`);return{id:n,selector:D(()=>`#${n.value}`)}},nq=e=>{const t=document.createElement("div");return t.id=e,document.body.appendChild(t),t},aq=()=>{const{id:e,selector:t}=nI();return g1(()=>{qt&&(document.body.querySelector(t.value)||nq(e.value))}),{id:e,selector:t}},rq=$e({showAfter:{type:Number,default:0},hideAfter:{type:Number,default:200},autoClose:{type:Number,default:0}}),oq={showAfter:0,hideAfter:200,autoClose:0},iq=({showAfter:e,hideAfter:t,autoClose:n,open:a,close:r})=>{const{registerTimeout:o}=a5(),{registerTimeout:i,cancelTimeout:l}=a5();return{onOpen:(c,d=p(e))=>{o(()=>{a(c);const v=p(n);Je(v)&&v>0&&i(()=>{r(c)},v)},d)},onClose:(c,d=p(t))=>{l(),o(()=>{r(c)},d)}}},aI=Symbol("elForwardRef"),lq=e=>{Ft(aI,{setForwardRef:n=>{e.value=n}})},sq=e=>({mounted(t){e(t)},updated(t){e(t)},unmounted(){e(null)}}),o5={current:0},i5=F(0),rI=2e3,l5=Symbol("elZIndexContextKey"),oI=Symbol("zIndexContextKey"),tp=e=>{const t=Et()?Ze(l5,o5):o5,n=e||(Et()?Ze(oI,void 0):void 0),a=D(()=>{const i=p(n);return Je(i)?i:rI}),r=D(()=>a.value+i5.value),o=()=>(t.current++,i5.value=t.current,r.value);return!qt&&!Ze(l5)&&un("ZIndexInjection",`Looks like you are using server rendering, you must provide a z-index provider to ensure the hydration process to be succeed +usage: app.provide(ZINDEX_INJECTION_KEY, { current: 0 })`),{initialZIndex:a,currentZIndex:r,nextZIndex:o}},Wd=Math.min,ju=Math.max,Ym=Math.round,$p=Math.floor,Sl=e=>({x:e,y:e}),uq={left:"right",right:"left",bottom:"top",top:"bottom"};function iI(e,t,n){return ju(e,Wd(t,n))}function np(e,t){return typeof e=="function"?e(t):e}function Kd(e){return e.split("-")[0]}function ap(e){return e.split("-")[1]}function lI(e){return e==="x"?"y":"x"}function R6(e){return e==="y"?"height":"width"}function ps(e){const t=e[0];return t==="t"||t==="b"?"y":"x"}function N6(e){return lI(ps(e))}function cq(e,t,n){n===void 0&&(n=!1);const a=ap(e),r=N6(e),o=R6(r);let i=r==="x"?a===(n?"end":"start")?"right":"left":a==="start"?"bottom":"top";return t.reference[o]>t.floating[o]&&(i=jm(i)),[i,jm(i)]}function dq(e){const t=jm(e);return[f4(e),t,f4(t)]}function f4(e){return e.includes("start")?e.replace("start","end"):e.replace("end","start")}const s5=["left","right"],u5=["right","left"],fq=["top","bottom"],vq=["bottom","top"];function hq(e,t,n){switch(e){case"top":case"bottom":return n?t?u5:s5:t?s5:u5;case"left":case"right":return t?fq:vq;default:return[]}}function pq(e,t,n,a){const r=ap(e);let o=hq(Kd(e),n==="start",a);return r&&(o=o.map(i=>i+"-"+r),t&&(o=o.concat(o.map(f4)))),o}function jm(e){const t=Kd(e);return uq[t]+e.slice(t.length)}function mq(e){var t,n,a,r;return{top:(t=e.top)!=null?t:0,right:(n=e.right)!=null?n:0,bottom:(a=e.bottom)!=null?a:0,left:(r=e.left)!=null?r:0}}function sI(e){return typeof e!="number"?mq(e):{top:e,right:e,bottom:e,left:e}}function qm(e){const{x:t,y:n,width:a,height:r}=e;return{width:a,height:r,top:n,left:t,right:t+a,bottom:n+r,x:t,y:n}}function c5(e,t,n){let{reference:a,floating:r}=e;const o=ps(t),i=N6(t),l=R6(i),s=Kd(t),u=o==="y",c=a.x+a.width/2-r.width/2,d=a.y+a.height/2-r.height/2,v=a[l]/2-r[l]/2;let f;switch(s){case"top":f={x:c,y:a.y-r.height};break;case"bottom":f={x:c,y:a.y+a.height};break;case"right":f={x:a.x+a.width,y:d};break;case"left":f={x:a.x-r.width,y:d};break;default:f={x:a.x,y:a.y}}const h=ap(t);return h&&(f[i]+=v*(h==="end"?1:-1)*(n&&u?-1:1)),f}async function uI(e,t){var n;t===void 0&&(t={});const{x:a,y:r,platform:o,rects:i,elements:l,strategy:s}=e,{boundary:u="clippingAncestors",rootBoundary:c="viewport",elementContext:d="floating",altBoundary:v=!1,padding:f=0}=np(t,e),h=sI(f),g=l[v?d==="floating"?"reference":"floating":d],y=qm(await o.getClippingRect({element:(n=await(o.isElement==null?void 0:o.isElement(g)))==null||n?g:g.contextElement||await(o.getDocumentElement==null?void 0:o.getDocumentElement(l.floating)),boundary:u,rootBoundary:c,strategy:s})),b=d==="floating"?{x:a,y:r,width:i.floating.width,height:i.floating.height}:i.reference,_=await(o.getOffsetParent==null?void 0:o.getOffsetParent(l.floating)),w=await(o.isElement==null?void 0:o.isElement(_))&&await(o.getScale==null?void 0:o.getScale(_))||{x:1,y:1},S=qm(o.convertOffsetParentRelativeRectToViewportRelativeRect?await o.convertOffsetParentRelativeRectToViewportRelativeRect({elements:l,rect:b,offsetParent:_,strategy:s}):b);return{top:(y.top-S.top+h.top)/w.y,bottom:(S.bottom-y.bottom+h.bottom)/w.y,left:(y.left-S.left+h.left)/w.x,right:(S.right-y.right+h.right)/w.x}}const gq=50,yq=async(e,t,n)=>{const{placement:a="bottom",strategy:r="absolute",middleware:o=[],platform:i}=n,l=i.detectOverflow?i:{...i,detectOverflow:uI},s=await(i.isRTL==null?void 0:i.isRTL(t));let u=await i.getElementRects({reference:e,floating:t,strategy:r}),{x:c,y:d}=c5(u,a,s),v=a,f=0;const h={};for(let m=0;m({name:"arrow",options:e,async fn(t){const{x:n,y:a,placement:r,rects:o,platform:i,elements:l,middlewareData:s}=t,{element:u,padding:c=0}=np(e,t)||{};if(u==null)return{};const d=sI(c),v={x:n,y:a},f=N6(r),h=R6(f),m=await i.getDimensions(u),g=f==="y",y=g?"top":"left",b=g?"bottom":"right",_=g?"clientHeight":"clientWidth",w=o.reference[h]+o.reference[f]-v[f]-o.floating[h],S=v[f]-o.reference[f],k=await(i.getOffsetParent==null?void 0:i.getOffsetParent(u));let T=k?k[_]:0;(!T||!await(i.isElement==null?void 0:i.isElement(k)))&&(T=l.floating[_]||o.floating[h]);const M=w/2-S/2,E=T/2-m[h]/2-1,A=Wd(d[y],E),I=Wd(d[b],E),P=T-m[h]-I,R=T/2-m[h]/2+M,G=iI(A,R,P),K=!s.arrow&&ap(r)!=null&&R!==G&&o.reference[h]/2-(RG<=0)){var I,P;const G=(((I=o.flip)==null?void 0:I.index)||0)+1,K=T[G];if(K&&(!(d==="alignment"?b!==ps(K):!1)||A.every(B=>ps(B.placement)===b?B.overflows[0]>0:!0)))return{data:{index:G,overflows:A},reset:{placement:K}};let H=(P=A.filter(z=>z.overflows[0]<=0).sort((z,B)=>z.overflows[1]-B.overflows[1])[0])==null?void 0:P.placement;if(!H)switch(f){case"bestFit":{var R;const z=(R=A.filter(B=>{if(k){const W=ps(B.placement);return W===b||W==="y"}return!0}).map(B=>[B.placement,B.overflows.filter(W=>W>0).reduce((W,N)=>W+N,0)]).sort((B,W)=>B[1]-W[1])[0])==null?void 0:R[0];z&&(H=z);break}case"initialPlacement":H=l;break}if(r!==H)return{reset:{placement:H}}}return{}}}},wq=new Set(["left","top"]);async function Sq(e,t){const{placement:n,platform:a,elements:r}=e,o=await(a.isRTL==null?void 0:a.isRTL(r.floating)),i=Kd(n),l=ap(n),s=ps(n)==="y",u=wq.has(i)?-1:1,c=o&&s?-1:1,d=np(t,e);let{mainAxis:v,crossAxis:f,alignmentAxis:h}=typeof d=="number"?{mainAxis:d,crossAxis:0,alignmentAxis:null}:{mainAxis:d.mainAxis||0,crossAxis:d.crossAxis||0,alignmentAxis:d.alignmentAxis};return l&&typeof h=="number"&&(f=l==="end"?h*-1:h),s?{x:f*c,y:v*u}:{x:v*u,y:f*c}}const Cq=function(e){return e===void 0&&(e=0),{name:"offset",options:e,async fn(t){var n,a;const{x:r,y:o,placement:i,middlewareData:l}=t,s=await Sq(t,e);return i===((n=l.offset)==null?void 0:n.placement)&&(a=l.arrow)!=null&&a.alignmentOffset?{}:{x:r+s.x,y:o+s.y,data:{...s,placement:i}}}}},xq=function(e){return e===void 0&&(e={}),{name:"shift",options:e,async fn(t){const{x:n,y:a,placement:r,platform:o}=t,{mainAxis:i=!0,crossAxis:l=!1,limiter:s={fn:b=>{let{x:_,y:w}=b;return{x:_,y:w}}},...u}=np(e,t),c={x:n,y:a},d=await o.detectOverflow(t,u),v=ps(r),f=lI(v);let h=c[f],m=c[v];const g=(b,_)=>iI(_+d[b==="y"?"top":"left"],_,_-d[b==="y"?"bottom":"right"]);i&&(h=g(f,h)),l&&(m=g(v,m));const y=s.fn({...t,[f]:h,[v]:m});return{...y,data:{x:y.x-n,y:y.y-a,enabled:{[f]:i,[v]:l}}}}}};function $1(){return typeof window<"u"}function vf(e){return cI(e)?(e.nodeName||"").toLowerCase():"#document"}function Hr(e){var t;return(e==null||(t=e.ownerDocument)==null?void 0:t.defaultView)||window}function zl(e){var t;return(t=(cI(e)?e.ownerDocument:e.document)||window.document)==null?void 0:t.documentElement}function cI(e){return $1()?e instanceof Node||e instanceof Hr(e).Node:!1}function Xi(e){return $1()?e instanceof Element||e instanceof Hr(e).Element:!1}function Ys(e){return $1()?e instanceof HTMLElement||e instanceof Hr(e).HTMLElement:!1}function d5(e){return!$1()||typeof ShadowRoot>"u"?!1:e instanceof ShadowRoot||e instanceof Hr(e).ShadowRoot}function R1(e){const{overflow:t,overflowX:n,overflowY:a,display:r}=Zi(e);return/auto|scroll|overlay|hidden|clip/.test(t+a+n)&&r!=="inline"&&r!=="contents"}function kq(e){return/^(table|td|th)$/.test(vf(e))}function N1(e){try{if(e.matches(":popover-open"))return!0}catch{}try{return e.matches(":modal")}catch{return!1}}const Tq=/transform|translate|scale|rotate|perspective|filter/,Mq=/paint|layout|strict|content/,tu=e=>!!e&&e!=="none";let K2;function B6(e){const t=Xi(e)?Zi(e):e;return tu(t.transform)||tu(t.translate)||tu(t.scale)||tu(t.rotate)||tu(t.perspective)||!V6()&&(tu(t.backdropFilter)||tu(t.filter))||Tq.test(t.willChange||"")||Mq.test(t.contain||"")}function Eq(e){let t=cc(e);for(;Ys(t)&&!dh(t);){if(B6(t))return t;if(N1(t))return null;t=cc(t)}return null}function V6(){return K2==null&&(K2=typeof CSS<"u"&&CSS.supports&&CSS.supports("-webkit-backdrop-filter","none")),K2}function dh(e){return/^(html|body|#document)$/.test(vf(e))}function Zi(e){return Hr(e).getComputedStyle(e)}function B1(e){return Xi(e)?{scrollLeft:e.scrollLeft,scrollTop:e.scrollTop}:{scrollLeft:e.scrollX,scrollTop:e.scrollY}}function cc(e){if(vf(e)==="html")return e;const t=e.assignedSlot||e.parentNode||d5(e)&&e.host||zl(e);return d5(t)?t.host:t}function dI(e){const t=cc(e);return dh(t)?(e.ownerDocument||e).body:Ys(t)&&R1(t)?t:dI(t)}function fh(e,t,n){var a;t===void 0&&(t=[]),n===void 0&&(n=!0);const r=dI(e),o=r===((a=e.ownerDocument)==null?void 0:a.body),i=Hr(r);if(o){const l=v4(i);return t.concat(i,i.visualViewport||[],R1(r)?r:[],l&&n?fh(l):[])}else return t.concat(r,fh(r,[],n))}function v4(e){return e.parent&&Object.getPrototypeOf(e.parent)?e.frameElement:null}function fI(e){const t=Zi(e);let n=parseFloat(t.width)||0,a=parseFloat(t.height)||0;const r=Ys(e),o=r?e.offsetWidth:n,i=r?e.offsetHeight:a,l=Ym(n)!==o||Ym(a)!==i;return l&&(n=o,a=i),{width:n,height:a,$:l}}function z6(e){return Xi(e)?e:e.contextElement}function wd(e){const t=z6(e);if(!Ys(t))return Sl(1);const n=t.getBoundingClientRect(),{width:a,height:r,$:o}=fI(t);let i=(o?Ym(n.width):n.width)/a,l=(o?Ym(n.height):n.height)/r;return(!i||!Number.isFinite(i))&&(i=1),(!l||!Number.isFinite(l))&&(l=1),{x:i,y:l}}const Aq=Sl(0);function vI(e){const t=Hr(e);return!V6()||!t.visualViewport?Aq:{x:t.visualViewport.offsetLeft,y:t.visualViewport.offsetTop}}function Iq(e,t,n){return t===void 0&&(t=!1),!!n&&t&&n===Hr(e)}function dc(e,t,n,a){t===void 0&&(t=!1),n===void 0&&(n=!1);const r=e.getBoundingClientRect(),o=z6(e);let i=Sl(1);t&&(a?Xi(a)&&(i=wd(a)):i=wd(e));const l=Iq(o,n,a)?vI(o):Sl(0);let s=(r.left+l.x)/i.x,u=(r.top+l.y)/i.y,c=r.width/i.x,d=r.height/i.y;if(o&&a){const v=Hr(o),f=Xi(a)?Hr(a):a;let h=v,m=v4(h);for(;m&&f!==h;){const g=wd(m),y=m.getBoundingClientRect(),b=Zi(m),_=y.left+(m.clientLeft+parseFloat(b.paddingLeft))*g.x,w=y.top+(m.clientTop+parseFloat(b.paddingTop))*g.y;s*=g.x,u*=g.y,c*=g.x,d*=g.y,s+=_,u+=w,h=Hr(m),m=v4(h)}}return qm({width:c,height:d,x:s,y:u})}function V1(e,t){const n=B1(e).scrollLeft;return t?t.left+n:dc(zl(e)).left+n}function hI(e,t){const n=e.getBoundingClientRect(),a=n.left+t.scrollLeft-V1(e,n),r=n.top+t.scrollTop;return{x:a,y:r}}function Lq(e){let{elements:t,rect:n,offsetParent:a,strategy:r}=e;const o=r==="fixed",i=zl(a),l=t?N1(t.floating):!1;if(a===i||l&&o)return n;let s={scrollLeft:0,scrollTop:0},u=Sl(1);const c=Sl(0),d=Ys(a);if((d||!o)&&((vf(a)!=="body"||R1(i))&&(s=B1(a)),d)){const f=dc(a);u=wd(a),c.x=f.x+a.clientLeft,c.y=f.y+a.clientTop}const v=i&&!d&&!o?hI(i,s):Sl(0);return{width:n.width*u.x,height:n.height*u.y,x:n.x*u.x-s.scrollLeft*u.x+c.x+v.x,y:n.y*u.y-s.scrollTop*u.y+c.y+v.y}}function Pq(e){return e.getClientRects?Array.from(e.getClientRects()):[]}function Dq(e){const t=B1(e),n=e.ownerDocument.body,a=ju(e.scrollWidth,e.clientWidth,n.scrollWidth,n.clientWidth),r=ju(e.scrollHeight,e.clientHeight,n.scrollHeight,n.clientHeight);let o=-t.scrollLeft+V1(e);const i=-t.scrollTop;return Zi(n).direction==="rtl"&&(o+=ju(e.clientWidth,n.clientWidth)-a),{width:a,height:r,x:o,y:i}}const Oq=25;function $q(e,t,n){n===void 0&&(n="viewport");const a=n==="layoutViewport",r=Hr(e),o=zl(e),i=r.visualViewport;let l=o.clientWidth,s=o.clientHeight,u=0,c=0;if(i){const v=!V6()||t==="fixed";a?v||(u=-i.offsetLeft,c=-i.offsetTop):(l=i.width,s=i.height,v&&(u=i.offsetLeft,c=i.offsetTop))}if(V1(o)<=0){const v=o.ownerDocument,f=v.body,h=getComputedStyle(f),m=v.compatMode==="CSS1Compat"&&parseFloat(h.marginLeft)+parseFloat(h.marginRight)||0,g=Math.abs(o.clientWidth-f.clientWidth-m),y=getComputedStyle(o).scrollbarGutter==="stable both-edges"?g/2:g;y<=Oq&&(l-=y)}return{width:l,height:s,x:u,y:c}}function Rq(e,t){const n=dc(e,!0,t==="fixed"),a=n.top+e.clientTop,r=n.left+e.clientLeft,o=wd(e),i=e.clientWidth*o.x,l=e.clientHeight*o.y,s=r*o.x,u=a*o.y;return{width:i,height:l,x:s,y:u}}function f5(e,t,n){let a;if(t==="viewport"||t==="layoutViewport")a=$q(e,n,t);else if(t==="document")a=Dq(zl(e));else if(Xi(t))a=Rq(t,n);else{const r=vI(e);a={x:t.x-r.x,y:t.y-r.y,width:t.width,height:t.height}}return qm(a)}function Nq(e,t){const n=t.get(e);if(n)return n;let a=fh(e,[],!1).filter(l=>Xi(l)&&vf(l)!=="body"),r=null;const o=Zi(e).position==="fixed";let i=o?cc(e):e;for(;Xi(i)&&!dh(i);){const l=Zi(i),s=B6(i),u=r?r.position:o?"fixed":"";!s&&(u==="fixed"||u==="absolute"&&l.position==="static")?a=a.filter(d=>d!==i):r=l,i=cc(i)}return t.set(e,a),a}function Bq(e){let{element:t,boundary:n,rootBoundary:a,strategy:r}=e;const i=[...n==="clippingAncestors"?N1(t)?[]:Nq(t,this._c):[].concat(n),a],l=f5(t,i[0],r);let s=l.top,u=l.right,c=l.bottom,d=l.left;for(let v=1;v{l(!1,1e-7)},1e3)}T=!1}try{a=new IntersectionObserver(M,{...k,root:o.ownerDocument})}catch{a=new IntersectionObserver(M,k)}a.observe(e)}const s=Hr(e),u=()=>l(n);return s.addEventListener("resize",u),l(!0),()=>{s.removeEventListener("resize",u),i()}}function Uq(e,t,n,a){a===void 0&&(a={});const{ancestorScroll:r=!0,ancestorResize:o=!0,elementResize:i=typeof ResizeObserver=="function",layoutShift:l=typeof IntersectionObserver=="function",animationFrame:s=!1}=a,u=z6(e),c=r||o?[...u?fh(u):[],...t?fh(t):[]]:[];c.forEach(y=>{r&&y.addEventListener("scroll",n),o&&y.addEventListener("resize",n)});const d=u&&l?Kq(u,n,o):null;let v=-1,f=null;i&&(f=new ResizeObserver(y=>{let[b]=y;b&&b.target===u&&f&&t&&(f.unobserve(t),cancelAnimationFrame(v),v=requestAnimationFrame(()=>{var _;(_=f)==null||_.observe(t)})),n()}),u&&!s&&f.observe(u),t&&f.observe(t));let h,m=s?dc(e):null;s&&g();function g(){const y=dc(e);m&&!mI(m,y)&&n(),m=y,h=requestAnimationFrame(g)}return n(),()=>{var y;c.forEach(b=>{r&&b.removeEventListener("scroll",n),o&&b.removeEventListener("resize",n)}),d==null||d(),(y=f)==null||y.disconnect(),f=null,s&&cancelAnimationFrame(h)}}const Gq=uI,Yq=Cq,jq=xq,qq=_q,Xq=bq,Zq=(e,t,n)=>{const a=new Map,r=n??{},o={...Wq,...r.platform,_c:a};return yq(e,t,{...r,platform:o})};function Jq(e){let t;function n(){if(e.value==null)return;const{selectionStart:r,selectionEnd:o,value:i}=e.value;r==null||o==null||(t={selectionStart:r,selectionEnd:o,value:i,beforeTxt:i.slice(0,Math.max(0,r)),afterTxt:i.slice(Math.max(0,o))})}function a(){if(e.value==null||t==null)return;const{value:r}=e.value,{beforeTxt:o,afterTxt:i,selectionStart:l}=t;if(o==null||i==null||l==null)return;let s=r.length;if(r.endsWith(i))s=r.length-i.length;else if(r.startsWith(o))s=o.length;else{const u=o[l-1],c=r.indexOf(u,l-1);c!==-1&&(s=c+1)}e.value.setSelectionRange(s,s)}return[n,a]}const Qq="utils/vue/vnode";function h4(e){return bn(e)&&e.type===Ve}function gI(e){return bn(e)&&e.type===oa}function eX(e){return bn(e)&&!h4(e)&&!gI(e)}const tX=e=>{if(!bn(e))return un(Qq,"[getNormalizedProps] must be a VNode"),{};const t=e.props||{},n=(bn(e.type)?e.type.props:void 0)||{},a={};return Object.keys(n).forEach(r=>{en(n[r],"default")&&(a[r]=n[r].default)}),Object.keys(t).forEach(r=>{a[Va(r)]=t[r]}),a},eo=e=>{const t=Ae(e)?e:[e],n=[];return t.forEach(a=>{var r;Ae(a)?n.push(...eo(a)):bn(a)&&((r=a.component)!=null&&r.subTree)?n.push(a,...eo(a.component.subTree)):bn(a)&&Ae(a.children)?n.push(...eo(a.children)):bn(a)&&a.shapeFlag===2?n.push(...eo(a.type())):n.push(a)}),n},nX=(e,t,n)=>eo(e.subTree).filter(a=>{var r;return bn(a)&&((r=a.type)==null?void 0:r.name)===t&&!!a.component}).map(a=>a.component.uid).map(a=>n[a]).filter(a=>!!a),z1=(e,t)=>{const n=Ht({}),a=Ht([]),r=new WeakMap,o=u=>{n.value[u.uid]=u,Y0(n),mt(()=>{const c=u.getVnode().el,d=c.parentNode;if(!r.has(d)){r.set(d,[]);const v=d.insertBefore.bind(d);d.insertBefore=(f,h)=>(r.get(d).some(m=>f===m||h===m)&&Y0(n),v(f,h))}r.get(d).push(c)})},i=u=>{delete n.value[u.uid],Y0(n);const c=u.getVnode().el,d=c.parentNode,v=r.get(d),f=v.indexOf(c);v.splice(f,1)},l=()=>{a.value=nX(e,t,n.value)},s=u=>u.render();return{children:a,addChild:o,removeChild:i,ChildrenSorter:q({setup(u,{slots:c}){return()=>(l(),c.default?ot(s,{render:c.default}):null)}})}},pa=si({type:String,values:ii,required:!1}),yI=Symbol("size"),bI=()=>{const e=Ze(yI,{});return D(()=>p(e.size)||"")};function Fl(e,{disabled:t,beforeFocus:n,afterFocus:a,beforeBlur:r,afterBlur:o}={}){const{emit:i}=Et(),l=Ht(),s=F(!1),u=v=>{const f=Qe(n)?n(v):!1;p(t)||s.value||f||(s.value=!0,i("focus",v),a==null||a())},c=v=>{var h;const f=Qe(r)?r(v):!1;p(t)||v.relatedTarget&&((h=l.value)!=null&&h.contains(v.relatedTarget))||f||(s.value=!1,i("blur",v),o==null||o())},d=v=>{var f,h;p(t)||sc(v.target)||(f=l.value)!=null&&f.contains(document.activeElement)&&l.value!==document.activeElement||(h=e.value)==null||h.focus()};return ye([l,()=>p(t)],([v,f])=>{v&&(f?v.removeAttribute("tabindex"):v.setAttribute("tabindex","-1"))}),on(l,"focus",u,!0),on(l,"blur",c,!0),on(l,"click",d,!0),{isFocused:s,wrapperRef:l,handleFocus:u,handleBlur:c}}function rp({afterComposition:e,emit:t}){const n=F(!1),a=l=>{t==null||t("compositionstart",l),n.value=!0},r=l=>{t==null||t("compositionupdate",l),n.value=!0},o=l=>{t==null||t("compositionend",l),n.value&&(n.value=!1,He(()=>e(l)))};return{isComposing:n,handleComposition:l=>{l.type==="compositionend"?o(l):r(l)},handleCompositionStart:a,handleCompositionUpdate:r,handleCompositionEnd:o}}const _I=Symbol("emptyValuesContextKey"),aX="use-empty-values",rX=["",void 0,null],Mc=$e({emptyValues:Array,valueOnClear:{type:ae([String,Number,Boolean,Function]),default:void 0,validator:e=>(e=Qe(e)?e():e,Ae(e)?e.every(t=>!t):!e)}}),op=(e,t)=>{const n=Et()?Ze(_I,F({})):F({}),a=D(()=>e.emptyValues||n.value.emptyValues||rX),r=D(()=>Qe(e.valueOnClear)?e.valueOnClear():e.valueOnClear!==void 0?e.valueOnClear:Qe(n.value.valueOnClear)?n.value.valueOnClear():n.value.valueOnClear!==void 0?n.value.valueOnClear:t!==void 0?t:void 0),o=i=>{let l=!0;return Ae(i)?l=a.value.some(s=>Rn(i,s)):l=a.value.includes(i),l};return o(r.value)||un(aX,"value-on-clear should be a value of empty-values"),{emptyValues:a,valueOnClear:r,isEmptyValue:o}},oX=$e({ariaLabel:String,ariaOrientation:{type:String,values:["horizontal","vertical","undefined"]},ariaControls:String}),dr=e=>ji(oX,e),wI=e=>{const t=e.props,n=Ae(t)?Rd(t.map(a=>[a,{}])):t;e.setPropsDefaults=a=>{if(n){for(const[r,o]of Object.entries(a)){const i=n[r];if(en(n,r)){if(h6(i)){n[r]={...i,default:o};continue}n[r]={type:i,default:o}}}e.props=n}}},wt=(e,t)=>{if(e.install=n=>{for(const a of[e,...Object.values(t??{})])n.component(a.name,a)},t)for(const[n,a]of Object.entries(t))e[n]=a;return wI(e),e},SI=(e,t)=>(e.install=n=>{e._context=n._context,n.config.globalProperties[t]=e},e),iX=(e,t)=>(e.install=n=>{n.directive(t,e)},e),On=e=>(e.install=kt,wI(e),e),h5="ElAffix";var lX=q({name:h5,__name:"affix",props:oY,emits:iY,setup(e,{expose:t,emit:n}){const a=e,r=n,o=ke("affix"),i=Ht(),l=Ht(),s=Ht(),{height:u}=_6(),{height:c,width:d,top:v,bottom:f,left:h,update:m}=HC(l,{windowScroll:!1}),g=HC(i),y=F(!1),b=F(0),_=F(0),w=D(()=>!a.teleported||!y.value),S=D(()=>({display:"flow-root",height:y.value?`${c.value}px`:"",width:y.value?`${d.value}px`:""})),k=D(()=>{if(!y.value)return{};const A=Vn(a.offset);return{height:`${c.value}px`,width:`${d.value}px`,top:a.position==="top"?A:"",bottom:a.position==="bottom"?A:"",left:a.teleported?`${h.value}px`:"",transform:_.value?`translateY(${_.value}px)`:"",zIndex:a.zIndex}}),T=()=>{if(!s.value)return;b.value=s.value instanceof Window?document.documentElement.scrollTop:s.value.scrollTop||0;const{position:A,target:I,offset:P}=a,R=P+c.value;if(A==="top")if(I){const G=g.bottom.value-R;y.value=P>v.value&&g.bottom.value>0,_.value=G<0?G:0}else y.value=P>v.value;else if(I){const G=u.value-g.top.value-R;y.value=u.value-Pg.top.value,_.value=G<0?-G:0}else y.value=u.value-P{if(!y.value){m();return}y.value=!1,await He(),m(),y.value=!0},E=async()=>{m(),await He(),r("scroll",{scrollTop:b.value,fixed:y.value})};return ye(y,A=>r(Ot,A)),mt(()=>{a.target?(i.value=document.querySelector(a.target)??void 0,i.value||Dn(h5,`Target does not exist: ${a.target}`)):i.value=document.documentElement,s.value=x6(l.value,!0),m()}),Wh(()=>{He(M)}),j3(()=>{y.value=!1}),on(s,"scroll",E),ur(T),t({update:T,updateRoot:M}),(A,I)=>(x(),O("div",{ref_key:"root",ref:l,class:U(p(o).b()),style:et(S.value)},[(x(),ne(sf,{disabled:w.value,to:e.appendTo},[C("div",{class:U({[p(o).m("fixed")]:y.value}),style:et(k.value)},[ce(A.$slots,"default")],6)],8,["disabled","to"]))],6))}}),sX=lX;const uX=wt(sX);/*! Element Plus Icons Vue v2.3.2 */var cX=q({name:"AddLocation",__name:"add-location",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M288 896h448q32 0 32 32t-32 32H288q-32 0-32-32t32-32"}),C("path",{fill:"currentColor",d:"M800 416a288 288 0 1 0-576 0c0 118.144 94.528 272.128 288 456.576C705.472 688.128 800 534.144 800 416M512 960C277.312 746.688 160 565.312 160 416a352 352 0 0 1 704 0c0 149.312-117.312 330.688-352 544"}),C("path",{fill:"currentColor",d:"M544 384h96a32 32 0 1 1 0 64h-96v96a32 32 0 0 1-64 0v-96h-96a32 32 0 0 1 0-64h96v-96a32 32 0 0 1 64 0z"})]))}}),dX=cX,fX=q({name:"Aim",__name:"aim",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 896a384 384 0 1 0 0-768 384 384 0 0 0 0 768m0 64a448 448 0 1 1 0-896 448 448 0 0 1 0 896"}),C("path",{fill:"currentColor",d:"M512 96a32 32 0 0 1 32 32v192a32 32 0 0 1-64 0V128a32 32 0 0 1 32-32m0 576a32 32 0 0 1 32 32v192a32 32 0 1 1-64 0V704a32 32 0 0 1 32-32M96 512a32 32 0 0 1 32-32h192a32 32 0 0 1 0 64H128a32 32 0 0 1-32-32m576 0a32 32 0 0 1 32-32h192a32 32 0 1 1 0 64H704a32 32 0 0 1-32-32"})]))}}),vX=fX,hX=q({name:"AlarmClock",__name:"alarm-clock",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 832a320 320 0 1 0 0-640 320 320 0 0 0 0 640m0 64a384 384 0 1 1 0-768 384 384 0 0 1 0 768"}),C("path",{fill:"currentColor",d:"m292.288 824.576 55.424 32-48 83.136a32 32 0 1 1-55.424-32zm439.424 0-55.424 32 48 83.136a32 32 0 1 0 55.424-32zM512 512h160a32 32 0 1 1 0 64H480a32 32 0 0 1-32-32V320a32 32 0 0 1 64 0zM90.496 312.256A160 160 0 0 1 312.32 90.496l-46.848 46.848a96 96 0 0 0-128 128L90.56 312.256zm835.264 0A160 160 0 0 0 704 90.496l46.848 46.848a96 96 0 0 1 128 128z"})]))}}),pX=hX,mX=q({name:"Apple",__name:"apple",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M599.872 203.776a189.4 189.4 0 0 1 64.384-4.672l2.624.128c31.168 1.024 51.2 4.096 79.488 16.32 37.632 16.128 74.496 45.056 111.488 89.344 96.384 115.264 82.752 372.8-34.752 521.728-7.68 9.728-32 41.6-30.72 39.936a427 427 0 0 1-30.08 35.776c-31.232 32.576-65.28 49.216-110.08 50.048-31.36.64-53.568-5.312-84.288-18.752l-6.528-2.88c-20.992-9.216-30.592-11.904-47.296-11.904-18.112 0-28.608 2.88-51.136 12.672l-6.464 2.816c-28.416 12.224-48.32 18.048-76.16 19.2-74.112 2.752-116.928-38.08-180.672-132.16-96.64-142.08-132.608-349.312-55.04-486.4 46.272-81.92 129.92-133.632 220.672-135.04 32.832-.576 60.288 6.848 99.648 22.72 27.136 10.88 34.752 13.76 37.376 14.272 16.256-20.16 27.776-36.992 34.56-50.24 13.568-26.304 27.2-59.968 40.704-100.8a32 32 0 1 1 60.8 20.224c-12.608 37.888-25.408 70.4-38.528 97.664m-51.52 78.08c-14.528 17.792-31.808 37.376-51.904 58.816a32 32 0 1 1-46.72-43.776l12.288-13.248c-28.032-11.2-61.248-26.688-95.68-26.112-70.4 1.088-135.296 41.6-171.648 105.792C121.6 492.608 176 684.16 247.296 788.992c34.816 51.328 76.352 108.992 130.944 106.944 52.48-2.112 72.32-34.688 135.872-34.688s81.28 34.688 136.96 33.536c56.448-1.088 75.776-39.04 126.848-103.872 107.904-136.768 107.904-362.752 35.776-449.088-72.192-86.272-124.672-84.096-151.68-85.12-41.472-4.288-81.6 12.544-113.664 25.152"})]))}}),gX=mX,yX=q({name:"ArrowDownBold",__name:"arrow-down-bold",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M104.704 338.752a64 64 0 0 1 90.496 0l316.8 316.8 316.8-316.8a64 64 0 0 1 90.496 90.496L557.248 791.296a64 64 0 0 1-90.496 0L104.704 429.248a64 64 0 0 1 0-90.496"})]))}}),bX=yX,_X=q({name:"ArrowDown",__name:"arrow-down",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M831.872 340.864 512 652.672 192.128 340.864a30.59 30.59 0 0 0-42.752 0 29.12 29.12 0 0 0 0 41.6L489.664 714.24a32 32 0 0 0 44.672 0l340.288-331.712a29.12 29.12 0 0 0 0-41.728 30.59 30.59 0 0 0-42.752 0z"})]))}}),ui=_X,wX=q({name:"ArrowLeftBold",__name:"arrow-left-bold",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M685.248 104.704a64 64 0 0 1 0 90.496L368.448 512l316.8 316.8a64 64 0 0 1-90.496 90.496L232.704 557.248a64 64 0 0 1 0-90.496l362.048-362.048a64 64 0 0 1 90.496 0"})]))}}),SX=wX,CX=q({name:"ArrowLeft",__name:"arrow-left",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M609.408 149.376 277.76 489.6a32 32 0 0 0 0 44.672l331.648 340.352a29.12 29.12 0 0 0 41.728 0 30.59 30.59 0 0 0 0-42.752L339.264 511.936l311.872-319.872a30.59 30.59 0 0 0 0-42.688 29.12 29.12 0 0 0-41.728 0"})]))}}),Ji=CX,xX=q({name:"ArrowRightBold",__name:"arrow-right-bold",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M338.752 104.704a64 64 0 0 0 0 90.496l316.8 316.8-316.8 316.8a64 64 0 0 0 90.496 90.496l362.048-362.048a64 64 0 0 0 0-90.496L429.248 104.704a64 64 0 0 0-90.496 0"})]))}}),kX=xX,TX=q({name:"ArrowRight",__name:"arrow-right",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M340.864 149.312a30.59 30.59 0 0 0 0 42.752L652.736 512 340.864 831.872a30.59 30.59 0 0 0 0 42.752 29.12 29.12 0 0 0 41.728 0L714.24 534.336a32 32 0 0 0 0-44.672L382.592 149.376a29.12 29.12 0 0 0-41.728 0z"})]))}}),za=TX,MX=q({name:"ArrowUpBold",__name:"arrow-up-bold",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M104.704 685.248a64 64 0 0 0 90.496 0l316.8-316.8 316.8 316.8a64 64 0 0 0 90.496-90.496L557.248 232.704a64 64 0 0 0-90.496 0L104.704 594.752a64 64 0 0 0 0 90.496"})]))}}),EX=MX,AX=q({name:"ArrowUp",__name:"arrow-up",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m488.832 344.32-339.84 356.672a32 32 0 0 0 0 44.16l.384.384a29.44 29.44 0 0 0 42.688 0l320-335.872 319.872 335.872a29.44 29.44 0 0 0 42.688 0l.384-.384a32 32 0 0 0 0-44.16L535.168 344.32a32 32 0 0 0-46.336 0"})]))}}),ip=AX,IX=q({name:"Avatar",__name:"avatar",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M628.736 528.896A416 416 0 0 1 928 928H96a415.87 415.87 0 0 1 299.264-399.104L512 704zM720 304a208 208 0 1 1-416 0 208 208 0 0 1 416 0"})]))}}),LX=IX,PX=q({name:"Back",__name:"back",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M224 480h640a32 32 0 1 1 0 64H224a32 32 0 0 1 0-64"}),C("path",{fill:"currentColor",d:"m237.248 512 265.408 265.344a32 32 0 0 1-45.312 45.312l-288-288a32 32 0 0 1 0-45.312l288-288a32 32 0 1 1 45.312 45.312z"})]))}}),CI=PX,DX=q({name:"Baseball",__name:"baseball",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M195.2 828.8a448 448 0 1 1 633.6-633.6 448 448 0 0 1-633.6 633.6m45.248-45.248a384 384 0 1 0 543.104-543.104 384 384 0 0 0-543.104 543.104"}),C("path",{fill:"currentColor",d:"M497.472 96.896c22.784 4.672 44.416 9.472 64.896 14.528a256.128 256.128 0 0 0 350.208 350.208c5.056 20.48 9.856 42.112 14.528 64.896A320.128 320.128 0 0 1 497.472 96.896M108.48 491.904a320.128 320.128 0 0 1 423.616 423.68c-23.04-3.648-44.992-7.424-65.728-11.52a256.128 256.128 0 0 0-346.496-346.432 1737 1737 0 0 1-11.392-65.728"})]))}}),OX=DX,$X=q({name:"Basketball",__name:"basketball",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M778.752 788.224a382.46 382.46 0 0 0 116.032-245.632 256.51 256.51 0 0 0-241.728-13.952 762.9 762.9 0 0 1 125.696 259.584m-55.04 44.224a699.65 699.65 0 0 0-125.056-269.632 256.13 256.13 0 0 0-56.064 331.968 382.7 382.7 0 0 0 181.12-62.336m-254.08 61.248A320.13 320.13 0 0 1 557.76 513.6a716 716 0 0 0-48.192-48.128 320.13 320.13 0 0 1-379.264 88.384 382.4 382.4 0 0 0 110.144 229.696 382.4 382.4 0 0 0 229.184 110.08zM129.28 481.088a256.13 256.13 0 0 0 331.072-56.448 699.65 699.65 0 0 0-268.8-124.352 382.66 382.66 0 0 0-62.272 180.8m106.56-235.84a762.9 762.9 0 0 1 258.688 125.056 256.51 256.51 0 0 0-13.44-241.088A382.46 382.46 0 0 0 235.84 245.248m318.08-114.944c40.576 89.536 37.76 193.92-8.448 281.344a780 780 0 0 1 66.176 66.112 320.83 320.83 0 0 1 282.112-8.128 382.4 382.4 0 0 0-110.144-229.12 382.4 382.4 0 0 0-229.632-110.208zM828.8 828.8a448 448 0 1 1-633.6-633.6 448 448 0 0 1 633.6 633.6"})]))}}),RX=$X,NX=q({name:"BellFilled",__name:"bell-filled",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M640 832a128 128 0 0 1-256 0zm192-64H134.4a38.4 38.4 0 0 1 0-76.8H192V448c0-154.88 110.08-284.16 256.32-313.6a64 64 0 1 1 127.36 0A320.13 320.13 0 0 1 832 448v243.2h57.6a38.4 38.4 0 0 1 0 76.8z"})]))}}),BX=NX,VX=q({name:"Bell",__name:"bell",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 64a64 64 0 0 1 64 64v64H448v-64a64 64 0 0 1 64-64"}),C("path",{fill:"currentColor",d:"M256 768h512V448a256 256 0 1 0-512 0zm256-640a320 320 0 0 1 320 320v384H192V448a320 320 0 0 1 320-320"}),C("path",{fill:"currentColor",d:"M96 768h832q32 0 32 32t-32 32H96q-32 0-32-32t32-32m352 128h128a64 64 0 0 1-128 0"})]))}}),zX=VX,FX=q({name:"Bicycle",__name:"bicycle",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M256 832a128 128 0 1 0 0-256 128 128 0 0 0 0 256m0 64a192 192 0 1 1 0-384 192 192 0 0 1 0 384"}),C("path",{fill:"currentColor",d:"M288 672h320q32 0 32 32t-32 32H288q-32 0-32-32t32-32"}),C("path",{fill:"currentColor",d:"M768 832a128 128 0 1 0 0-256 128 128 0 0 0 0 256m0 64a192 192 0 1 1 0-384 192 192 0 0 1 0 384"}),C("path",{fill:"currentColor",d:"M480 192a32 32 0 0 1 0-64h160a32 32 0 0 1 31.04 24.256l96 384a32 32 0 0 1-62.08 15.488L615.04 192zM96 384a32 32 0 0 1 0-64h128a32 32 0 0 1 30.336 21.888l64 192a32 32 0 1 1-60.672 20.224L200.96 384z"}),C("path",{fill:"currentColor",d:"m373.376 599.808-42.752-47.616 320-288 42.752 47.616z"})]))}}),HX=FX,WX=q({name:"BottomLeft",__name:"bottom-left",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M256 768h416a32 32 0 1 1 0 64H224a32 32 0 0 1-32-32V352a32 32 0 0 1 64 0z"}),C("path",{fill:"currentColor",d:"M246.656 822.656a32 32 0 0 1-45.312-45.312l544-544a32 32 0 0 1 45.312 45.312z"})]))}}),KX=WX,UX=q({name:"BottomRight",__name:"bottom-right",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M352 768a32 32 0 1 0 0 64h448a32 32 0 0 0 32-32V352a32 32 0 0 0-64 0v416z"}),C("path",{fill:"currentColor",d:"M777.344 822.656a32 32 0 0 0 45.312-45.312l-544-544a32 32 0 0 0-45.312 45.312z"})]))}}),GX=UX,YX=q({name:"Bottom",__name:"bottom",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M544 805.888V168a32 32 0 1 0-64 0v637.888L246.656 557.952a30.72 30.72 0 0 0-45.312 0 35.52 35.52 0 0 0 0 48.064l288 306.048a30.72 30.72 0 0 0 45.312 0l288-306.048a35.52 35.52 0 0 0 0-48 30.72 30.72 0 0 0-45.312 0L544 805.824z"})]))}}),jX=YX,qX=q({name:"Bowl",__name:"bowl",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M714.432 704a351.74 351.74 0 0 0 148.16-256H161.408a351.74 351.74 0 0 0 148.16 256zM288 766.592A415.68 415.68 0 0 1 96 416a32 32 0 0 1 32-32h768a32 32 0 0 1 32 32 415.68 415.68 0 0 1-192 350.592V832a64 64 0 0 1-64 64H352a64 64 0 0 1-64-64zM493.248 320h-90.496l254.4-254.4a32 32 0 1 1 45.248 45.248zm187.328 0h-128l269.696-155.712a32 32 0 0 1 32 55.424zM352 768v64h320v-64z"})]))}}),XX=qX,ZX=q({name:"Box",__name:"box",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M317.056 128 128 344.064V896h768V344.064L706.944 128zm-14.528-64h418.944a32 32 0 0 1 24.064 10.88l206.528 236.096A32 32 0 0 1 960 332.032V928a32 32 0 0 1-32 32H96a32 32 0 0 1-32-32V332.032a32 32 0 0 1 7.936-21.12L278.4 75.008A32 32 0 0 1 302.528 64"}),C("path",{fill:"currentColor",d:"M64 320h896v64H64z"}),C("path",{fill:"currentColor",d:"M448 327.872V640h128V327.872L526.08 128h-28.16zM448 64h128l64 256v352a32 32 0 0 1-32 32H416a32 32 0 0 1-32-32V320z"})]))}}),JX=ZX,QX=q({name:"Briefcase",__name:"briefcase",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M320 320V128h384v192h192v192H128V320zM128 576h768v320H128zm256-256h256.064V192H384z"})]))}}),eZ=QX,tZ=q({name:"BrushFilled",__name:"brush-filled",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M608 704v160a96 96 0 0 1-192 0V704h-96a128 128 0 0 1-128-128h640a128 128 0 0 1-128 128zM192 512V128.064h640V512z"})]))}}),nZ=tZ,aZ=q({name:"Brush",__name:"brush",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M896 448H128v192a64 64 0 0 0 64 64h192v192h256V704h192a64 64 0 0 0 64-64zm-770.752-64c0-47.552 5.248-90.24 15.552-128 14.72-54.016 42.496-107.392 83.2-160h417.28l-15.36 70.336L736 96h211.2c-24.832 42.88-41.92 96.256-51.2 160a664 664 0 0 0-6.144 128H960v256a128 128 0 0 1-128 128H704v160a32 32 0 0 1-32 32H352a32 32 0 0 1-32-32V768H192A128 128 0 0 1 64 640V384zm64 0h636.544c-2.048-45.824.256-91.584 6.848-137.216 4.48-30.848 10.688-59.776 18.688-86.784h-96.64l-221.12 141.248L561.92 160H256.512c-25.856 37.888-43.776 75.456-53.952 112.832-8.768 32.064-13.248 69.12-13.312 111.168"})]))}}),rZ=aZ,oZ=q({name:"Burger",__name:"burger",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M160 512a32 32 0 0 0-32 32v64a32 32 0 0 0 30.08 32H864a32 32 0 0 0 32-32v-64a32 32 0 0 0-32-32zm736-58.56A96 96 0 0 1 960 544v64a96 96 0 0 1-51.968 85.312L855.36 833.6a96 96 0 0 1-89.856 62.272H258.496A96 96 0 0 1 168.64 833.6l-52.608-140.224A96 96 0 0 1 64 608v-64a96 96 0 0 1 64-90.56V448a384 384 0 1 1 768 5.44M832 448a320 320 0 0 0-640 0zM512 704H188.352l40.192 107.136a32 32 0 0 0 29.952 20.736h507.008a32 32 0 0 0 29.952-20.736L835.648 704z"})]))}}),iZ=oZ,lZ=q({name:"Calendar",__name:"calendar",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M128 384v512h768V192H768v32a32 32 0 1 1-64 0v-32H320v32a32 32 0 0 1-64 0v-32H128v128h768v64zm192-256h384V96a32 32 0 1 1 64 0v32h160a32 32 0 0 1 32 32v768a32 32 0 0 1-32 32H96a32 32 0 0 1-32-32V160a32 32 0 0 1 32-32h160V96a32 32 0 0 1 64 0zm-32 384h64a32 32 0 0 1 0 64h-64a32 32 0 0 1 0-64m0 192h64a32 32 0 1 1 0 64h-64a32 32 0 1 1 0-64m192-192h64a32 32 0 0 1 0 64h-64a32 32 0 0 1 0-64m0 192h64a32 32 0 1 1 0 64h-64a32 32 0 1 1 0-64m192-192h64a32 32 0 1 1 0 64h-64a32 32 0 1 1 0-64m0 192h64a32 32 0 1 1 0 64h-64a32 32 0 1 1 0-64"})]))}}),xI=lZ,sZ=q({name:"CameraFilled",__name:"camera-filled",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M160 224a64 64 0 0 0-64 64v512a64 64 0 0 0 64 64h704a64 64 0 0 0 64-64V288a64 64 0 0 0-64-64H748.416l-46.464-92.672A64 64 0 0 0 644.736 96H379.328a64 64 0 0 0-57.216 35.392L275.776 224zm352 435.2a115.2 115.2 0 1 0 0-230.4 115.2 115.2 0 0 0 0 230.4m0 140.8a256 256 0 1 1 0-512 256 256 0 0 1 0 512"})]))}}),uZ=sZ,cZ=q({name:"Camera",__name:"camera",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M896 256H128v576h768zm-199.424-64-32.064-64h-304.96l-32 64zM96 192h160l46.336-92.608A64 64 0 0 1 359.552 64h304.96a64 64 0 0 1 57.216 35.328L768.192 192H928a32 32 0 0 1 32 32v640a32 32 0 0 1-32 32H96a32 32 0 0 1-32-32V224a32 32 0 0 1 32-32m416 512a160 160 0 1 0 0-320 160 160 0 0 0 0 320m0 64a224 224 0 1 1 0-448 224 224 0 0 1 0 448"})]))}}),kI=cZ,dZ=q({name:"CaretBottom",__name:"caret-bottom",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m192 384 320 384 320-384z"})]))}}),fZ=dZ,vZ=q({name:"CaretLeft",__name:"caret-left",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M672 192 288 511.936 672 832z"})]))}}),hZ=vZ,pZ=q({name:"CaretRight",__name:"caret-right",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M384 192v640l384-320.064z"})]))}}),F6=pZ,mZ=q({name:"CaretTop",__name:"caret-top",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 320 192 704h639.936z"})]))}}),TI=mZ,gZ=q({name:"Cellphone",__name:"cellphone",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M256 128a64 64 0 0 0-64 64v640a64 64 0 0 0 64 64h512a64 64 0 0 0 64-64V192a64 64 0 0 0-64-64zm0-64h512a128 128 0 0 1 128 128v640a128 128 0 0 1-128 128H256a128 128 0 0 1-128-128V192A128 128 0 0 1 256 64m128 128h256a32 32 0 1 1 0 64H384a32 32 0 0 1 0-64m128 640a64 64 0 1 1 0-128 64 64 0 0 1 0 128"})]))}}),yZ=gZ,bZ=q({name:"ChatDotRound",__name:"chat-dot-round",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m174.72 855.68 135.296-45.12 23.68 11.84C388.096 849.536 448.576 864 512 864c211.84 0 384-166.784 384-352S723.84 160 512 160 128 326.784 128 512c0 69.12 24.96 139.264 70.848 199.232l22.08 28.8-46.272 115.584zm-45.248 82.56A32 32 0 0 1 89.6 896l58.368-145.92C94.72 680.32 64 596.864 64 512 64 299.904 256 96 512 96s448 203.904 448 416-192 416-448 416a461.06 461.06 0 0 1-206.912-48.384l-175.616 58.56z"}),C("path",{fill:"currentColor",d:"M512 563.2a51.2 51.2 0 1 1 0-102.4 51.2 51.2 0 0 1 0 102.4m192 0a51.2 51.2 0 1 1 0-102.4 51.2 51.2 0 0 1 0 102.4m-384 0a51.2 51.2 0 1 1 0-102.4 51.2 51.2 0 0 1 0 102.4"})]))}}),_Z=bZ,wZ=q({name:"ChatDotSquare",__name:"chat-dot-square",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M273.536 736H800a64 64 0 0 0 64-64V256a64 64 0 0 0-64-64H224a64 64 0 0 0-64 64v570.88zM296 800 147.968 918.4A32 32 0 0 1 96 893.44V256a128 128 0 0 1 128-128h576a128 128 0 0 1 128 128v416a128 128 0 0 1-128 128z"}),C("path",{fill:"currentColor",d:"M512 499.2a51.2 51.2 0 1 1 0-102.4 51.2 51.2 0 0 1 0 102.4m192 0a51.2 51.2 0 1 1 0-102.4 51.2 51.2 0 0 1 0 102.4m-384 0a51.2 51.2 0 1 1 0-102.4 51.2 51.2 0 0 1 0 102.4"})]))}}),SZ=wZ,CZ=q({name:"ChatLineRound",__name:"chat-line-round",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m174.72 855.68 135.296-45.12 23.68 11.84C388.096 849.536 448.576 864 512 864c211.84 0 384-166.784 384-352S723.84 160 512 160 128 326.784 128 512c0 69.12 24.96 139.264 70.848 199.232l22.08 28.8-46.272 115.584zm-45.248 82.56A32 32 0 0 1 89.6 896l58.368-145.92C94.72 680.32 64 596.864 64 512 64 299.904 256 96 512 96s448 203.904 448 416-192 416-448 416a461.06 461.06 0 0 1-206.912-48.384l-175.616 58.56z"}),C("path",{fill:"currentColor",d:"M352 576h320q32 0 32 32t-32 32H352q-32 0-32-32t32-32m32-192h256q32 0 32 32t-32 32H384q-32 0-32-32t32-32"})]))}}),xZ=CZ,kZ=q({name:"ChatLineSquare",__name:"chat-line-square",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M160 826.88 273.536 736H800a64 64 0 0 0 64-64V256a64 64 0 0 0-64-64H224a64 64 0 0 0-64 64zM296 800 147.968 918.4A32 32 0 0 1 96 893.44V256a128 128 0 0 1 128-128h576a128 128 0 0 1 128 128v416a128 128 0 0 1-128 128z"}),C("path",{fill:"currentColor",d:"M352 512h320q32 0 32 32t-32 32H352q-32 0-32-32t32-32m0-192h320q32 0 32 32t-32 32H352q-32 0-32-32t32-32"})]))}}),TZ=kZ,MZ=q({name:"ChatRound",__name:"chat-round",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m174.72 855.68 130.048-43.392 23.424 11.392C382.4 849.984 444.352 864 512 864c223.744 0 384-159.872 384-352 0-192.832-159.104-352-384-352S128 319.168 128 512a341.12 341.12 0 0 0 69.248 204.288l21.632 28.8-44.16 110.528zm-45.248 82.56A32 32 0 0 1 89.6 896l56.512-141.248A405.12 405.12 0 0 1 64 512C64 299.904 235.648 96 512 96s448 203.904 448 416-173.44 416-448 416c-79.68 0-150.848-17.152-211.712-46.72l-170.88 56.96z"})]))}}),EZ=MZ,AZ=q({name:"ChatSquare",__name:"chat-square",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M273.536 736H800a64 64 0 0 0 64-64V256a64 64 0 0 0-64-64H224a64 64 0 0 0-64 64v570.88zM296 800 147.968 918.4A32 32 0 0 1 96 893.44V256a128 128 0 0 1 128-128h576a128 128 0 0 1 128 128v416a128 128 0 0 1-128 128z"})]))}}),IZ=AZ,LZ=q({name:"Check",__name:"check",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M406.656 706.944 195.84 496.256a32 32 0 1 0-45.248 45.248l256 256 512-512a32 32 0 0 0-45.248-45.248L406.592 706.944z"})]))}}),Os=LZ,PZ=q({name:"Checked",__name:"checked",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M704 192h160v736H160V192h160.064v64H704zM311.616 537.28l-45.312 45.248L447.36 763.52l316.8-316.8-45.312-45.184L447.36 673.024zM384 192V96h256v96z"})]))}}),DZ=PZ,OZ=q({name:"Cherry",__name:"cherry",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M261.056 449.6c13.824-69.696 34.88-128.96 63.36-177.728 23.744-40.832 61.12-88.64 112.256-143.872H320a32 32 0 0 1 0-64h384a32 32 0 1 1 0 64H554.752c14.912 39.168 41.344 86.592 79.552 141.76 47.36 68.48 84.8 106.752 106.304 114.304a224 224 0 1 1-84.992 14.784c-22.656-22.912-47.04-53.76-73.92-92.608-38.848-56.128-67.008-105.792-84.352-149.312-55.296 58.24-94.528 107.52-117.76 147.2-23.168 39.744-41.088 88.768-53.568 147.072a224.064 224.064 0 1 1-64.96-1.6M288 832a160 160 0 1 0 0-320 160 160 0 0 0 0 320m448-64a160 160 0 1 0 0-320 160 160 0 0 0 0 320"})]))}}),$Z=OZ,RZ=q({name:"Chicken",__name:"chicken",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M349.952 716.992 478.72 588.16a106.7 106.7 0 0 1-26.176-19.072 106.7 106.7 0 0 1-19.072-26.176L304.704 671.744c.768 3.072 1.472 6.144 2.048 9.216l2.048 31.936 31.872 1.984c3.136.64 6.208 1.28 9.28 2.112m57.344 33.152a128 128 0 1 1-216.32 114.432l-1.92-32-32-1.92a128 128 0 1 1 114.432-216.32L416.64 469.248c-2.432-101.44 58.112-239.104 149.056-330.048 107.328-107.328 231.296-85.504 316.8 0 85.44 85.44 107.328 209.408 0 316.8-91.008 90.88-228.672 151.424-330.112 149.056L407.296 750.08zm90.496-226.304c49.536 49.536 233.344-7.04 339.392-113.088 78.208-78.208 63.232-163.072 0-226.304-63.168-63.232-148.032-78.208-226.24 0C504.896 290.496 448.32 474.368 497.792 523.84M244.864 708.928a64 64 0 1 0-59.84 59.84l56.32-3.52zm8.064 127.68a64 64 0 1 0 59.84-59.84l-56.32 3.52z"})]))}}),NZ=RZ,BZ=q({name:"ChromeFilled",__name:"chrome-filled",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg","xml:space":"preserve",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M938.67 512.01c0-44.59-6.82-87.6-19.54-128H682.67a212.37 212.37 0 0 1 42.67 128c.06 38.71-10.45 76.7-30.42 109.87l-182.91 316.8c235.65-.01 426.66-191.02 426.66-426.67"}),C("path",{fill:"currentColor",d:"M576.79 401.63a127.9 127.9 0 0 0-63.56-17.6c-22.36-.22-44.39 5.43-63.89 16.38s-35.79 26.82-47.25 46.02a128 128 0 0 0-2.16 127.44l1.24 2.13a127.9 127.9 0 0 0 46.36 46.61 127.9 127.9 0 0 0 63.38 17.44c22.29.2 44.24-5.43 63.68-16.33a127.94 127.94 0 0 0 47.16-45.79v-.01l1.11-1.92a127.98 127.98 0 0 0 .29-127.46 127.96 127.96 0 0 0-46.36-46.91"}),C("path",{fill:"currentColor",d:"M394.45 333.96A213.34 213.34 0 0 1 512 298.67h369.58A426.5 426.5 0 0 0 512 85.34a425.6 425.6 0 0 0-171.74 35.98 425.6 425.6 0 0 0-142.62 102.22l118.14 204.63a213.4 213.4 0 0 1 78.67-94.21m117.56 604.72H512zm-97.25-236.73a213.3 213.3 0 0 1-89.54-86.81L142.48 298.6c-36.35 62.81-57.13 135.68-57.13 213.42 0 203.81 142.93 374.22 333.95 416.55h.04l118.19-204.71a213.3 213.3 0 0 1-122.77-21.91"})]))}}),VZ=BZ,zZ=q({name:"CircleCheckFilled",__name:"circle-check-filled",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 64a448 448 0 1 1 0 896 448 448 0 0 1 0-896m-55.808 536.384-99.52-99.584a38.4 38.4 0 1 0-54.336 54.336l126.72 126.72a38.27 38.27 0 0 0 54.336 0l262.4-262.464a38.4 38.4 0 1 0-54.272-54.336z"})]))}}),MI=zZ,FZ=q({name:"CircleCheck",__name:"circle-check",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 896a384 384 0 1 0 0-768 384 384 0 0 0 0 768m0 64a448 448 0 1 1 0-896 448 448 0 0 1 0 896"}),C("path",{fill:"currentColor",d:"M745.344 361.344a32 32 0 0 1 45.312 45.312l-288 288a32 32 0 0 1-45.312 0l-160-160a32 32 0 1 1 45.312-45.312L480 626.752z"})]))}}),Ao=FZ,HZ=q({name:"CircleCloseFilled",__name:"circle-close-filled",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 64a448 448 0 1 1 0 896 448 448 0 0 1 0-896m0 393.664L407.936 353.6a38.4 38.4 0 1 0-54.336 54.336L457.664 512 353.6 616.064a38.4 38.4 0 1 0 54.336 54.336L512 566.336 616.064 670.4a38.4 38.4 0 1 0 54.336-54.336L566.336 512 670.4 407.936a38.4 38.4 0 1 0-54.336-54.336z"})]))}}),fc=HZ,WZ=q({name:"CircleClose",__name:"circle-close",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m466.752 512-90.496-90.496a32 32 0 0 1 45.248-45.248L512 466.752l90.496-90.496a32 32 0 1 1 45.248 45.248L557.248 512l90.496 90.496a32 32 0 1 1-45.248 45.248L512 557.248l-90.496 90.496a32 32 0 0 1-45.248-45.248z"}),C("path",{fill:"currentColor",d:"M512 896a384 384 0 1 0 0-768 384 384 0 0 0 0 768m0 64a448 448 0 1 1 0-896 448 448 0 0 1 0 896"})]))}}),ci=WZ,KZ=q({name:"CirclePlusFilled",__name:"circle-plus-filled",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 64a448 448 0 1 1 0 896 448 448 0 0 1 0-896m-38.4 409.6H326.4a38.4 38.4 0 1 0 0 76.8h147.2v147.2a38.4 38.4 0 0 0 76.8 0V550.4h147.2a38.4 38.4 0 0 0 0-76.8H550.4V326.4a38.4 38.4 0 1 0-76.8 0z"})]))}}),UZ=KZ,GZ=q({name:"CirclePlus",__name:"circle-plus",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M352 480h320a32 32 0 1 1 0 64H352a32 32 0 0 1 0-64"}),C("path",{fill:"currentColor",d:"M480 672V352a32 32 0 1 1 64 0v320a32 32 0 0 1-64 0"}),C("path",{fill:"currentColor",d:"M512 896a384 384 0 1 0 0-768 384 384 0 0 0 0 768m0 64a448 448 0 1 1 0-896 448 448 0 0 1 0 896"})]))}}),YZ=GZ,jZ=q({name:"Clock",__name:"clock",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 896a384 384 0 1 0 0-768 384 384 0 0 0 0 768m0 64a448 448 0 1 1 0-896 448 448 0 0 1 0 896"}),C("path",{fill:"currentColor",d:"M480 256a32 32 0 0 1 32 32v256a32 32 0 0 1-64 0V288a32 32 0 0 1 32-32"}),C("path",{fill:"currentColor",d:"M480 512h256q32 0 32 32t-32 32H480q-32 0-32-32t32-32"})]))}}),H6=jZ,qZ=q({name:"CloseBold",__name:"close-bold",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M195.2 195.2a64 64 0 0 1 90.496 0L512 421.504 738.304 195.2a64 64 0 0 1 90.496 90.496L602.496 512 828.8 738.304a64 64 0 0 1-90.496 90.496L512 602.496 285.696 828.8a64 64 0 0 1-90.496-90.496L421.504 512 195.2 285.696a64 64 0 0 1 0-90.496"})]))}}),XZ=qZ,ZZ=q({name:"Close",__name:"close",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M764.288 214.592 512 466.88 259.712 214.592a31.936 31.936 0 0 0-45.12 45.12L466.752 512 214.528 764.224a31.936 31.936 0 1 0 45.12 45.184L512 557.184l252.288 252.288a31.936 31.936 0 0 0 45.12-45.12L557.12 512.064l252.288-252.352a31.936 31.936 0 1 0-45.12-45.184z"})]))}}),so=ZZ,JZ=q({name:"Cloudy",__name:"cloudy",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M598.4 831.872H328.192a256 256 0 0 1-34.496-510.528A352 352 0 1 1 598.4 831.872m-271.36-64h272.256a288 288 0 1 0-248.512-417.664L335.04 381.44l-34.816 3.584a192 192 0 0 0 26.88 382.848z"})]))}}),QZ=JZ,eJ=q({name:"CoffeeCup",__name:"coffee-cup",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M768 192a192 192 0 1 1-8 383.808A256.13 256.13 0 0 1 512 768H320A256 256 0 0 1 64 512V160a32 32 0 0 1 32-32h640a32 32 0 0 1 32 32zm0 64v256a128 128 0 1 0 0-256M96 832h640a32 32 0 1 1 0 64H96a32 32 0 1 1 0-64m32-640v320a192 192 0 0 0 192 192h192a192 192 0 0 0 192-192V192z"})]))}}),tJ=eJ,nJ=q({name:"Coffee",__name:"coffee",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M822.592 192h14.272a32 32 0 0 1 31.616 26.752l21.312 128A32 32 0 0 1 858.24 384h-49.344l-39.04 546.304A32 32 0 0 1 737.92 960H285.824a32 32 0 0 1-32-29.696L214.912 384H165.76a32 32 0 0 1-31.552-37.248l21.312-128A32 32 0 0 1 187.136 192h14.016l-6.72-93.696A32 32 0 0 1 226.368 64h571.008a32 32 0 0 1 31.936 34.304zm-64.128 0 4.544-64H260.736l4.544 64zm-548.16 128H820.48l-10.688-64H214.208l-10.688 64zm68.736 64 36.544 512H708.16l36.544-512z"})]))}}),aJ=nJ,rJ=q({name:"Coin",__name:"coin",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m161.92 580.736 29.888 58.88C171.328 659.776 160 681.728 160 704c0 82.304 155.328 160 352 160s352-77.696 352-160c0-22.272-11.392-44.16-31.808-64.32l30.464-58.432C903.936 615.808 928 657.664 928 704c0 129.728-188.544 224-416 224S96 833.728 96 704c0-46.592 24.32-88.576 65.92-123.264"}),C("path",{fill:"currentColor",d:"m161.92 388.736 29.888 58.88C171.328 467.84 160 489.792 160 512c0 82.304 155.328 160 352 160s352-77.696 352-160c0-22.272-11.392-44.16-31.808-64.32l30.464-58.432C903.936 423.808 928 465.664 928 512c0 129.728-188.544 224-416 224S96 641.728 96 512c0-46.592 24.32-88.576 65.92-123.264"}),C("path",{fill:"currentColor",d:"M512 544c-227.456 0-416-94.272-416-224S284.544 96 512 96s416 94.272 416 224-188.544 224-416 224m0-64c196.672 0 352-77.696 352-160S708.672 160 512 160s-352 77.696-352 160 155.328 160 352 160"})]))}}),oJ=rJ,iJ=q({name:"ColdDrink",__name:"cold-drink",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M768 64a192 192 0 1 1-69.952 370.88L480 725.376V896h96a32 32 0 1 1 0 64H320a32 32 0 1 1 0-64h96V725.376L76.8 273.536a64 64 0 0 1-12.8-38.4v-10.688a32 32 0 0 1 32-32h71.808l-65.536-83.84a32 32 0 0 1 50.432-39.424l96.256 123.264h337.728A192.06 192.06 0 0 1 768 64M656.896 192.448H800a32 32 0 0 1 32 32v10.624a64 64 0 0 1-12.8 38.4l-80.448 107.2a128 128 0 1 0-81.92-188.16v-.064zm-357.888 64 129.472 165.76a32 32 0 0 1-50.432 39.36l-160.256-205.12H144l304 404.928 304-404.928z"})]))}}),lJ=iJ,sJ=q({name:"CollectionTag",__name:"collection-tag",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M256 128v698.88l196.032-156.864a96 96 0 0 1 119.936 0L768 826.816V128zm-32-64h576a32 32 0 0 1 32 32v797.44a32 32 0 0 1-51.968 24.96L531.968 720a32 32 0 0 0-39.936 0L243.968 918.4A32 32 0 0 1 192 893.44V96a32 32 0 0 1 32-32"})]))}}),uJ=sJ,cJ=q({name:"Collection",__name:"collection",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M192 736h640V128H256a64 64 0 0 0-64 64zm64-672h608a32 32 0 0 1 32 32v672a32 32 0 0 1-32 32H160l-32 57.536V192A128 128 0 0 1 256 64"}),C("path",{fill:"currentColor",d:"M240 800a48 48 0 1 0 0 96h592v-96zm0-64h656v160a64 64 0 0 1-64 64H240a112 112 0 0 1 0-224m144-608v250.88l96-76.8 96 76.8V128zm-64-64h320v381.44a32 32 0 0 1-51.968 24.96L480 384l-108.032 86.4A32 32 0 0 1 320 445.44z"})]))}}),dJ=cJ,fJ=q({name:"Comment",__name:"comment",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M736 504a56 56 0 1 1 0-112 56 56 0 0 1 0 112m-224 0a56 56 0 1 1 0-112 56 56 0 0 1 0 112m-224 0a56 56 0 1 1 0-112 56 56 0 0 1 0 112M128 128v640h192v160l224-160h352V128z"})]))}}),vJ=fJ,hJ=q({name:"Compass",__name:"compass",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 896a384 384 0 1 0 0-768 384 384 0 0 0 0 768m0 64a448 448 0 1 1 0-896 448 448 0 0 1 0 896"}),C("path",{fill:"currentColor",d:"M725.888 315.008C676.48 428.672 624 513.28 568.576 568.64c-55.424 55.424-139.968 107.904-253.568 157.312a12.8 12.8 0 0 1-16.896-16.832c49.536-113.728 102.016-198.272 157.312-253.632 55.36-55.296 139.904-107.776 253.632-157.312a12.8 12.8 0 0 1 16.832 16.832"})]))}}),pJ=hJ,mJ=q({name:"Connection",__name:"connection",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M640 384v64H448a128 128 0 0 0-128 128v128a128 128 0 0 0 128 128h320a128 128 0 0 0 128-128V576a128 128 0 0 0-64-110.848V394.88c74.56 26.368 128 97.472 128 181.056v128a192 192 0 0 1-192 192H448a192 192 0 0 1-192-192V576a192 192 0 0 1 192-192z"}),C("path",{fill:"currentColor",d:"M384 640v-64h192a128 128 0 0 0 128-128V320a128 128 0 0 0-128-128H256a128 128 0 0 0-128 128v128a128 128 0 0 0 64 110.848v70.272A192.06 192.06 0 0 1 64 448V320a192 192 0 0 1 192-192h320a192 192 0 0 1 192 192v128a192 192 0 0 1-192 192z"})]))}}),p4=mJ,gJ=q({name:"Coordinate",__name:"coordinate",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M480 512h64v320h-64z"}),C("path",{fill:"currentColor",d:"M192 896h640a64 64 0 0 0-64-64H256a64 64 0 0 0-64 64m64-128h512a128 128 0 0 1 128 128v64H128v-64a128 128 0 0 1 128-128m256-256a192 192 0 1 0 0-384 192 192 0 0 0 0 384m0 64a256 256 0 1 1 0-512 256 256 0 0 1 0 512"})]))}}),yJ=gJ,bJ=q({name:"CopyDocument",__name:"copy-document",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M768 832a128 128 0 0 1-128 128H192A128 128 0 0 1 64 832V384a128 128 0 0 1 128-128v64a64 64 0 0 0-64 64v448a64 64 0 0 0 64 64h448a64 64 0 0 0 64-64z"}),C("path",{fill:"currentColor",d:"M384 128a64 64 0 0 0-64 64v448a64 64 0 0 0 64 64h448a64 64 0 0 0 64-64V192a64 64 0 0 0-64-64zm0-64h448a128 128 0 0 1 128 128v448a128 128 0 0 1-128 128H384a128 128 0 0 1-128-128V192A128 128 0 0 1 384 64"})]))}}),_J=bJ,wJ=q({name:"Cpu",__name:"cpu",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M320 256a64 64 0 0 0-64 64v384a64 64 0 0 0 64 64h384a64 64 0 0 0 64-64V320a64 64 0 0 0-64-64zm0-64h384a128 128 0 0 1 128 128v384a128 128 0 0 1-128 128H320a128 128 0 0 1-128-128V320a128 128 0 0 1 128-128"}),C("path",{fill:"currentColor",d:"M512 64a32 32 0 0 1 32 32v128h-64V96a32 32 0 0 1 32-32m160 0a32 32 0 0 1 32 32v128h-64V96a32 32 0 0 1 32-32m-320 0a32 32 0 0 1 32 32v128h-64V96a32 32 0 0 1 32-32m160 896a32 32 0 0 1-32-32V800h64v128a32 32 0 0 1-32 32m160 0a32 32 0 0 1-32-32V800h64v128a32 32 0 0 1-32 32m-320 0a32 32 0 0 1-32-32V800h64v128a32 32 0 0 1-32 32M64 512a32 32 0 0 1 32-32h128v64H96a32 32 0 0 1-32-32m0-160a32 32 0 0 1 32-32h128v64H96a32 32 0 0 1-32-32m0 320a32 32 0 0 1 32-32h128v64H96a32 32 0 0 1-32-32m896-160a32 32 0 0 1-32 32H800v-64h128a32 32 0 0 1 32 32m0-160a32 32 0 0 1-32 32H800v-64h128a32 32 0 0 1 32 32m0 320a32 32 0 0 1-32 32H800v-64h128a32 32 0 0 1 32 32"})]))}}),SJ=wJ,CJ=q({name:"CreditCard",__name:"credit-card",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M896 324.096c0-42.368-2.496-55.296-9.536-68.48a52.35 52.35 0 0 0-22.144-22.08c-13.12-7.04-26.048-9.536-68.416-9.536H228.096c-42.368 0-55.296 2.496-68.48 9.536a52.35 52.35 0 0 0-22.08 22.144c-7.04 13.12-9.536 26.048-9.536 68.416v375.808c0 42.368 2.496 55.296 9.536 68.48a52.35 52.35 0 0 0 22.144 22.08c13.12 7.04 26.048 9.536 68.416 9.536h567.808c42.368 0 55.296-2.496 68.48-9.536a52.35 52.35 0 0 0 22.08-22.144c7.04-13.12 9.536-26.048 9.536-68.416zm64 0v375.808c0 57.088-5.952 77.76-17.088 98.56-11.136 20.928-27.52 37.312-48.384 48.448S852.928 864 795.968 864H228.032c-57.088 0-77.76-5.952-98.56-17.088a116.3 116.3 0 0 1-48.448-48.384c-11.136-20.864-17.088-41.6-17.088-98.56V324.032c0-57.088 5.952-77.76 17.088-98.56 11.136-20.928 27.52-37.312 48.384-48.448s41.6-17.088 98.56-17.088H795.84c57.088 0 77.76 5.952 98.56 17.088 20.928 11.136 37.312 27.52 48.448 48.384s17.088 41.6 17.088 98.56z"}),C("path",{fill:"currentColor",d:"M64 320h896v64H64zm0 128h896v64H64zm128 192h256v64H192z"})]))}}),xJ=CJ,kJ=q({name:"Crop",__name:"crop",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M256 768h672a32 32 0 1 1 0 64H224a32 32 0 0 1-32-32V96a32 32 0 0 1 64 0z"}),C("path",{fill:"currentColor",d:"M832 224v704a32 32 0 1 1-64 0V256H96a32 32 0 0 1 0-64h704a32 32 0 0 1 32 32"})]))}}),TJ=kJ,MJ=q({name:"DArrowLeft",__name:"d-arrow-left",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M529.408 149.376a29.12 29.12 0 0 1 41.728 0 30.59 30.59 0 0 1 0 42.688L259.264 511.936l311.872 319.936a30.59 30.59 0 0 1-.512 43.264 29.12 29.12 0 0 1-41.216-.512L197.76 534.272a32 32 0 0 1 0-44.672zm256 0a29.12 29.12 0 0 1 41.728 0 30.59 30.59 0 0 1 0 42.688L515.264 511.936l311.872 319.936a30.59 30.59 0 0 1-.512 43.264 29.12 29.12 0 0 1-41.216-.512L453.76 534.272a32 32 0 0 1 0-44.672z"})]))}}),Al=MJ,EJ=q({name:"DArrowRight",__name:"d-arrow-right",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M452.864 149.312a29.12 29.12 0 0 1 41.728.064L826.24 489.664a32 32 0 0 1 0 44.672L494.592 874.624a29.12 29.12 0 0 1-41.728 0 30.59 30.59 0 0 1 0-42.752L764.736 512 452.864 192a30.59 30.59 0 0 1 0-42.688m-256 0a29.12 29.12 0 0 1 41.728.064L570.24 489.664a32 32 0 0 1 0 44.672L238.592 874.624a29.12 29.12 0 0 1-41.728 0 30.59 30.59 0 0 1 0-42.752L508.736 512 196.864 192a30.59 30.59 0 0 1 0-42.688"})]))}}),Il=EJ,AJ=q({name:"DCaret",__name:"d-caret",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m512 128 288 320H224zM224 576h576L512 896z"})]))}}),IJ=AJ,LJ=q({name:"DataAnalysis",__name:"data-analysis",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m665.216 768 110.848 192h-73.856L591.36 768H433.024L322.176 960H248.32l110.848-192H160a32 32 0 0 1-32-32V192H64a32 32 0 0 1 0-64h896a32 32 0 1 1 0 64h-64v544a32 32 0 0 1-32 32zM832 192H192v512h640zM352 448a32 32 0 0 1 32 32v64a32 32 0 0 1-64 0v-64a32 32 0 0 1 32-32m160-64a32 32 0 0 1 32 32v128a32 32 0 0 1-64 0V416a32 32 0 0 1 32-32m160-64a32 32 0 0 1 32 32v192a32 32 0 1 1-64 0V352a32 32 0 0 1 32-32"})]))}}),PJ=LJ,DJ=q({name:"DataBoard",__name:"data-board",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M32 128h960v64H32z"}),C("path",{fill:"currentColor",d:"M192 192v512h640V192zm-64-64h768v608a32 32 0 0 1-32 32H160a32 32 0 0 1-32-32z"}),C("path",{fill:"currentColor",d:"M322.176 960H248.32l144.64-250.56 55.424 32zm453.888 0h-73.856L576 741.44l55.424-32z"})]))}}),OJ=DJ,$J=q({name:"DataLine",__name:"data-line",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M359.168 768H160a32 32 0 0 1-32-32V192H64a32 32 0 0 1 0-64h896a32 32 0 1 1 0 64h-64v544a32 32 0 0 1-32 32H665.216l110.848 192h-73.856L591.36 768H433.024L322.176 960H248.32zM832 192H192v512h640zM342.656 534.656a32 32 0 1 1-45.312-45.312L444.992 341.76l125.44 94.08L679.04 300.032a32 32 0 1 1 49.92 39.936L581.632 524.224 451.008 426.24 342.656 534.592z"})]))}}),RJ=$J,NJ=q({name:"DeleteFilled",__name:"delete-filled",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M352 192V95.936a32 32 0 0 1 32-32h256a32 32 0 0 1 32 32V192h256a32 32 0 1 1 0 64H96a32 32 0 0 1 0-64zm64 0h192v-64H416zM192 960a32 32 0 0 1-32-32V256h704v672a32 32 0 0 1-32 32zm224-192a32 32 0 0 0 32-32V416a32 32 0 0 0-64 0v320a32 32 0 0 0 32 32m192 0a32 32 0 0 0 32-32V416a32 32 0 0 0-64 0v320a32 32 0 0 0 32 32"})]))}}),BJ=NJ,VJ=q({name:"DeleteLocation",__name:"delete-location",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M288 896h448q32 0 32 32t-32 32H288q-32 0-32-32t32-32"}),C("path",{fill:"currentColor",d:"M800 416a288 288 0 1 0-576 0c0 118.144 94.528 272.128 288 456.576C705.472 688.128 800 534.144 800 416M512 960C277.312 746.688 160 565.312 160 416a352 352 0 0 1 704 0c0 149.312-117.312 330.688-352 544"}),C("path",{fill:"currentColor",d:"M384 384h256q32 0 32 32t-32 32H384q-32 0-32-32t32-32"})]))}}),zJ=VJ,FJ=q({name:"Delete",__name:"delete",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M160 256H96a32 32 0 0 1 0-64h256V95.936a32 32 0 0 1 32-32h256a32 32 0 0 1 32 32V192h256a32 32 0 1 1 0 64h-64v672a32 32 0 0 1-32 32H192a32 32 0 0 1-32-32zm448-64v-64H416v64zM224 896h576V256H224zm192-128a32 32 0 0 1-32-32V416a32 32 0 0 1 64 0v320a32 32 0 0 1-32 32m192 0a32 32 0 0 1-32-32V416a32 32 0 0 1 64 0v320a32 32 0 0 1-32 32"})]))}}),W6=FJ,HJ=q({name:"Dessert",__name:"dessert",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M128 416v-48a144 144 0 0 1 168.64-141.888 224.128 224.128 0 0 1 430.72 0A144 144 0 0 1 896 368v48a384 384 0 0 1-352 382.72V896h-64v-97.28A384 384 0 0 1 128 416m287.104-32.064h193.792a143.81 143.81 0 0 1 58.88-132.736 160.064 160.064 0 0 0-311.552 0 143.81 143.81 0 0 1 58.88 132.8zm-72.896 0a72 72 0 1 0-140.48 0zm339.584 0h140.416a72 72 0 1 0-140.48 0zM512 736a320 320 0 0 0 318.4-288.064H193.6A320 320 0 0 0 512 736M384 896.064h256a32 32 0 1 1 0 64H384a32 32 0 1 1 0-64"})]))}}),WJ=HJ,KJ=q({name:"Discount",__name:"discount",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M224 704h576V318.336L552.512 115.84a64 64 0 0 0-81.024 0L224 318.336zm0 64v128h576V768zM593.024 66.304l259.2 212.096A32 32 0 0 1 864 303.168V928a32 32 0 0 1-32 32H192a32 32 0 0 1-32-32V303.168a32 32 0 0 1 11.712-24.768l259.2-212.096a128 128 0 0 1 162.112 0"}),C("path",{fill:"currentColor",d:"M512 448a64 64 0 1 0 0-128 64 64 0 0 0 0 128m0 64a128 128 0 1 1 0-256 128 128 0 0 1 0 256"})]))}}),UJ=KJ,GJ=q({name:"DishDot",__name:"dish-dot",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m384.064 274.56.064-50.688A128 128 0 0 1 512.128 96c70.528 0 127.68 57.152 127.68 127.68v50.752A448.19 448.19 0 0 1 955.392 768H68.544A448.19 448.19 0 0 1 384 274.56zM96 832h832a32 32 0 1 1 0 64H96a32 32 0 1 1 0-64m32-128h768a384 384 0 1 0-768 0m447.808-448v-32.32a63.68 63.68 0 0 0-63.68-63.68 64 64 0 0 0-64 63.936V256z"})]))}}),YJ=GJ,jJ=q({name:"Dish",__name:"dish",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M480 257.152V192h-96a32 32 0 0 1 0-64h256a32 32 0 1 1 0 64h-96v65.152A448 448 0 0 1 955.52 768H68.48A448 448 0 0 1 480 257.152M128 704h768a384 384 0 1 0-768 0M96 832h832a32 32 0 1 1 0 64H96a32 32 0 1 1 0-64"})]))}}),qJ=jJ,XJ=q({name:"DocumentAdd",__name:"document-add",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M832 384H576V128H192v768h640zm-26.496-64L640 154.496V320zM160 64h480l256 256v608a32 32 0 0 1-32 32H160a32 32 0 0 1-32-32V96a32 32 0 0 1 32-32m320 512V448h64v128h128v64H544v128h-64V640H352v-64z"})]))}}),ZJ=XJ,JJ=q({name:"DocumentChecked",__name:"document-checked",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M805.504 320 640 154.496V320zM832 384H576V128H192v768h640zM160 64h480l256 256v608a32 32 0 0 1-32 32H160a32 32 0 0 1-32-32V96a32 32 0 0 1 32-32m318.4 582.144 180.992-180.992L704.64 510.4 478.4 736.64 320 578.304l45.248-45.312z"})]))}}),QJ=JJ,eQ=q({name:"DocumentCopy",__name:"document-copy",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M128 320v576h576V320zm-32-64h640a32 32 0 0 1 32 32v640a32 32 0 0 1-32 32H96a32 32 0 0 1-32-32V288a32 32 0 0 1 32-32M960 96v704a32 32 0 0 1-32 32h-96v-64h64V128H384v64h-64V96a32 32 0 0 1 32-32h576a32 32 0 0 1 32 32M256 672h320v64H256zm0-192h320v64H256z"})]))}}),tQ=eQ,nQ=q({name:"DocumentDelete",__name:"document-delete",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M805.504 320 640 154.496V320zM832 384H576V128H192v768h640zM160 64h480l256 256v608a32 32 0 0 1-32 32H160a32 32 0 0 1-32-32V96a32 32 0 0 1 32-32m308.992 546.304-90.496-90.624 45.248-45.248 90.56 90.496 90.496-90.432 45.248 45.248-90.496 90.56 90.496 90.496-45.248 45.248-90.496-90.496-90.56 90.496-45.248-45.248z"})]))}}),aQ=nQ,rQ=q({name:"DocumentRemove",__name:"document-remove",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M805.504 320 640 154.496V320zM832 384H576V128H192v768h640zM160 64h480l256 256v608a32 32 0 0 1-32 32H160a32 32 0 0 1-32-32V96a32 32 0 0 1 32-32m192 512h320v64H352z"})]))}}),oQ=rQ,iQ=q({name:"Document",__name:"document",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M832 384H576V128H192v768h640zm-26.496-64L640 154.496V320zM160 64h480l256 256v608a32 32 0 0 1-32 32H160a32 32 0 0 1-32-32V96a32 32 0 0 1 32-32m160 448h384v64H320zm0-192h160v64H320zm0 384h384v64H320z"})]))}}),EI=iQ,lQ=q({name:"Download",__name:"download",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M160 832h704a32 32 0 1 1 0 64H160a32 32 0 1 1 0-64m384-253.696 236.288-236.352 45.248 45.248L508.8 704 192 387.2l45.248-45.248L480 584.704V128h64z"})]))}}),AI=lQ,sQ=q({name:"Drizzling",__name:"drizzling",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m739.328 291.328-35.2-6.592-12.8-33.408a192.064 192.064 0 0 0-365.952 23.232l-9.92 40.896-41.472 7.04a176.32 176.32 0 0 0-146.24 173.568c0 97.28 78.72 175.936 175.808 175.936h400a192 192 0 0 0 35.776-380.672M959.552 480a256 256 0 0 1-256 256h-400A239.81 239.81 0 0 1 63.744 496.192a240.32 240.32 0 0 1 199.488-236.8 256.128 256.128 0 0 1 487.872-30.976A256.064 256.064 0 0 1 959.552 480M288 800h64v64h-64zm192 0h64v64h-64zm-96 96h64v64h-64zm192 0h64v64h-64zm96-96h64v64h-64z"})]))}}),uQ=sQ,cQ=q({name:"EditPen",__name:"edit-pen",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m199.04 672.64 193.984 112 224-387.968-193.92-112-224 388.032zm-23.872 60.16 32.896 148.288 144.896-45.696zM455.04 229.248l193.92 112 56.704-98.112-193.984-112zM104.32 708.8l384-665.024 304.768 175.936L409.152 884.8h.064l-248.448 78.336zm384 254.272v-64h448v64z"})]))}}),dQ=cQ,fQ=q({name:"Edit",__name:"edit",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M832 512a32 32 0 1 1 64 0v352a32 32 0 0 1-32 32H160a32 32 0 0 1-32-32V160a32 32 0 0 1 32-32h352a32 32 0 0 1 0 64H192v640h640z"}),C("path",{fill:"currentColor",d:"m469.952 554.24 52.8-7.552L847.104 222.4a32 32 0 1 0-45.248-45.248L477.44 501.44l-7.552 52.8zm422.4-422.4a96 96 0 0 1 0 135.808l-331.84 331.84a32 32 0 0 1-18.112 9.088L436.8 623.68a32 32 0 0 1-36.224-36.224l15.104-105.6a32 32 0 0 1 9.024-18.112l331.904-331.84a96 96 0 0 1 135.744 0z"})]))}}),vQ=fQ,hQ=q({name:"ElemeFilled",__name:"eleme-filled",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M176 64h672c61.824 0 112 50.176 112 112v672a112 112 0 0 1-112 112H176A112 112 0 0 1 64 848V176c0-61.824 50.176-112 112-112m150.528 173.568c-152.896 99.968-196.544 304.064-97.408 456.96a330.69 330.69 0 0 0 456.96 96.64c9.216-5.888 17.6-11.776 25.152-18.56a18.24 18.24 0 0 0 4.224-24.32L700.352 724.8a47.55 47.55 0 0 0-65.536-14.272A234.56 234.56 0 0 1 310.592 641.6C240 533.248 271.104 387.968 379.456 316.48a234.3 234.3 0 0 1 276.352 15.168c1.664.832 2.56 2.56 3.392 4.224 5.888 8.384 3.328 19.328-5.12 25.216L456.832 489.6a47.55 47.55 0 0 0-14.336 65.472l16 24.384c5.888 8.384 16.768 10.88 25.216 5.056l308.224-199.936a19.584 19.584 0 0 0 6.72-23.488v-.896c-4.992-9.216-10.048-17.6-15.104-26.88-99.968-151.168-304.064-194.88-456.96-95.744zM786.88 504.704l-62.208 40.32c-8.32 5.888-10.88 16.768-4.992 25.216L760 632.32c5.888 8.448 16.768 11.008 25.152 5.12l31.104-20.16a55.36 55.36 0 0 0 16-76.48l-20.224-31.04a19.52 19.52 0 0 0-25.152-5.12z"})]))}}),pQ=hQ,mQ=q({name:"Eleme",__name:"eleme",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M300.032 188.8c174.72-113.28 408-63.36 522.24 109.44 5.76 10.56 11.52 20.16 17.28 30.72v.96a22.4 22.4 0 0 1-7.68 26.88l-352.32 228.48c-9.6 6.72-22.08 3.84-28.8-5.76l-18.24-27.84a54.336 54.336 0 0 1 16.32-74.88l225.6-146.88c9.6-6.72 12.48-19.2 5.76-28.8-.96-1.92-1.92-3.84-3.84-4.8a267.84 267.84 0 0 0-315.84-17.28c-123.84 81.6-159.36 247.68-78.72 371.52a268.096 268.096 0 0 0 370.56 78.72 54.336 54.336 0 0 1 74.88 16.32l17.28 26.88c5.76 9.6 3.84 21.12-4.8 27.84-8.64 7.68-18.24 14.4-28.8 21.12a377.92 377.92 0 0 1-522.24-110.4c-113.28-174.72-63.36-408 111.36-522.24m526.08 305.28a22.336 22.336 0 0 1 28.8 5.76l23.04 35.52a63.23 63.23 0 0 1-18.24 87.36l-35.52 23.04c-9.6 6.72-22.08 3.84-28.8-5.76l-46.08-71.04c-6.72-9.6-3.84-22.08 5.76-28.8z"})]))}}),gQ=mQ,yQ=q({name:"ElementPlus",__name:"element-plus",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M839.7 734.7c0 33.3-17.9 41-17.9 41S519.7 949.8 499.2 960c-10.2 5.1-20.5 5.1-30.7 0 0 0-314.9-184.3-325.1-192-5.1-5.1-10.2-12.8-12.8-20.5V368.6c0-17.9 20.5-28.2 20.5-28.2L466 158.6q19.2-7.65 38.4 0s279 161.3 309.8 179.2c17.9 7.7 28.2 25.6 25.6 46.1-.1-5-.1 317.5-.1 350.8M714.2 371.2c-64-35.8-217.6-125.4-217.6-125.4-7.7-5.1-20.5-5.1-30.7 0L217.6 389.1s-17.9 10.2-17.9 23v297c0 5.1 5.1 12.8 7.7 17.9 7.7 5.1 256 148.5 256 148.5 7.7 5.1 17.9 5.1 25.6 0 15.4-7.7 250.9-145.9 250.9-145.9s12.8-5.1 12.8-30.7v-74.2l-276.5 169v-64c0-17.9 7.7-30.7 20.5-46.1L745 535c5.1-7.7 10.2-20.5 10.2-30.7v-66.6l-279 169v-69.1c0-15.4 5.1-30.7 17.9-38.4zM919 135.7c0-5.1-5.1-7.7-7.7-7.7h-58.9V66.6c0-5.1-5.1-5.1-10.2-5.1l-30.7 5.1c-5.1 0-5.1 2.6-5.1 5.1V128h-56.3c-5.1 0-5.1 5.1-7.7 5.1v38.4h69.1v64c0 5.1 5.1 5.1 10.2 5.1l30.7-5.1c5.1 0 5.1-2.6 5.1-5.1v-56.3h64z"})]))}}),bQ=yQ,_Q=q({name:"Expand",__name:"expand",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M128 192h768v128H128zm0 256h512v128H128zm0 256h768v128H128zm576-352 192 160-192 128z"})]))}}),wQ=_Q,SQ=q({name:"Failed",__name:"failed",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m557.248 608 135.744-135.744-45.248-45.248-135.68 135.744-135.808-135.68-45.248 45.184L466.752 608l-135.68 135.68 45.184 45.312L512 653.248l135.744 135.744 45.248-45.248L557.312 608zM704 192h160v736H160V192h160v64h384zm-320 0V96h256v96z"})]))}}),CQ=SQ,xQ=q({name:"Female",__name:"female",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 640a256 256 0 1 0 0-512 256 256 0 0 0 0 512m0 64a320 320 0 1 1 0-640 320 320 0 0 1 0 640"}),C("path",{fill:"currentColor",d:"M512 640q32 0 32 32v256q0 32-32 32t-32-32V672q0-32 32-32"}),C("path",{fill:"currentColor",d:"M352 800h320q32 0 32 32t-32 32H352q-32 0-32-32t32-32"})]))}}),kQ=xQ,TQ=q({name:"Files",__name:"files",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M128 384v448h768V384zm-32-64h832a32 32 0 0 1 32 32v512a32 32 0 0 1-32 32H96a32 32 0 0 1-32-32V352a32 32 0 0 1 32-32m64-128h704v64H160zm96-128h512v64H256z"})]))}}),MQ=TQ,EQ=q({name:"Film",__name:"film",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M160 160v704h704V160zm-32-64h768a32 32 0 0 1 32 32v768a32 32 0 0 1-32 32H128a32 32 0 0 1-32-32V128a32 32 0 0 1 32-32"}),C("path",{fill:"currentColor",d:"M320 288V128h64v352h256V128h64v160h160v64H704v128h160v64H704v128h160v64H704v160h-64V544H384v352h-64V736H128v-64h192V544H128v-64h192V352H128v-64z"})]))}}),AQ=EQ,IQ=q({name:"Filter",__name:"filter",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M384 523.392V928a32 32 0 0 0 46.336 28.608l192-96A32 32 0 0 0 640 832V523.392l280.768-343.104a32 32 0 1 0-49.536-40.576l-288 352A32 32 0 0 0 576 512v300.224l-128 64V512a32 32 0 0 0-7.232-20.288L195.52 192H704a32 32 0 1 0 0-64H128a32 32 0 0 0-24.768 52.288z"})]))}}),LQ=IQ,PQ=q({name:"Finished",__name:"finished",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M280.768 753.728 691.456 167.04a32 32 0 1 1 52.416 36.672L314.24 817.472a32 32 0 0 1-45.44 7.296l-230.4-172.8a32 32 0 0 1 38.4-51.2zM736 448a32 32 0 1 1 0-64h192a32 32 0 1 1 0 64zM608 640a32 32 0 0 1 0-64h319.936a32 32 0 1 1 0 64zM480 832a32 32 0 1 1 0-64h447.936a32 32 0 1 1 0 64z"})]))}}),DQ=PQ,OQ=q({name:"FirstAidKit",__name:"first-aid-kit",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M192 256a64 64 0 0 0-64 64v448a64 64 0 0 0 64 64h640a64 64 0 0 0 64-64V320a64 64 0 0 0-64-64zm0-64h640a128 128 0 0 1 128 128v448a128 128 0 0 1-128 128H192A128 128 0 0 1 64 768V320a128 128 0 0 1 128-128"}),C("path",{fill:"currentColor",d:"M544 512h96a32 32 0 0 1 0 64h-96v96a32 32 0 0 1-64 0v-96h-96a32 32 0 0 1 0-64h96v-96a32 32 0 0 1 64 0zM352 128v64h320v-64zm-32-64h384a32 32 0 0 1 32 32v128a32 32 0 0 1-32 32H320a32 32 0 0 1-32-32V96a32 32 0 0 1 32-32"})]))}}),$Q=OQ,RQ=q({name:"Flag",__name:"flag",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M288 128h608L736 384l160 256H288v320h-96V64h96z"})]))}}),NQ=RQ,BQ=q({name:"Fold",__name:"fold",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M896 192H128v128h768zm0 256H384v128h512zm0 256H128v128h768zM320 384 128 512l192 128z"})]))}}),VQ=BQ,zQ=q({name:"FolderAdd",__name:"folder-add",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M128 192v640h768V320H485.76L357.504 192zm-32-64h287.872l128.384 128H928a32 32 0 0 1 32 32v576a32 32 0 0 1-32 32H96a32 32 0 0 1-32-32V160a32 32 0 0 1 32-32m384 416V416h64v128h128v64H544v128h-64V608H352v-64z"})]))}}),FQ=zQ,HQ=q({name:"FolderChecked",__name:"folder-checked",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M128 192v640h768V320H485.76L357.504 192zm-32-64h287.872l128.384 128H928a32 32 0 0 1 32 32v576a32 32 0 0 1-32 32H96a32 32 0 0 1-32-32V160a32 32 0 0 1 32-32m414.08 502.144 180.992-180.992L736.32 494.4 510.08 720.64l-158.4-158.336 45.248-45.312z"})]))}}),WQ=HQ,KQ=q({name:"FolderDelete",__name:"folder-delete",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M128 192v640h768V320H485.76L357.504 192zm-32-64h287.872l128.384 128H928a32 32 0 0 1 32 32v576a32 32 0 0 1-32 32H96a32 32 0 0 1-32-32V160a32 32 0 0 1 32-32m370.752 448-90.496-90.496 45.248-45.248L512 530.752l90.496-90.496 45.248 45.248L557.248 576l90.496 90.496-45.248 45.248L512 621.248l-90.496 90.496-45.248-45.248z"})]))}}),UQ=KQ,GQ=q({name:"FolderOpened",__name:"folder-opened",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M878.08 448H241.92l-96 384h636.16zM832 384v-64H485.76L357.504 192H128v448l57.92-231.744A32 32 0 0 1 216.96 384zm-24.96 512H96a32 32 0 0 1-32-32V160a32 32 0 0 1 32-32h287.872l128.384 128H864a32 32 0 0 1 32 32v96h23.04a32 32 0 0 1 31.04 39.744l-112 448A32 32 0 0 1 807.04 896"})]))}}),YQ=GQ,jQ=q({name:"FolderRemove",__name:"folder-remove",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M128 192v640h768V320H485.76L357.504 192zm-32-64h287.872l128.384 128H928a32 32 0 0 1 32 32v576a32 32 0 0 1-32 32H96a32 32 0 0 1-32-32V160a32 32 0 0 1 32-32m256 416h320v64H352z"})]))}}),qQ=jQ,XQ=q({name:"Folder",__name:"folder",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M128 192v640h768V320H485.76L357.504 192zm-32-64h287.872l128.384 128H928a32 32 0 0 1 32 32v576a32 32 0 0 1-32 32H96a32 32 0 0 1-32-32V160a32 32 0 0 1 32-32"})]))}}),ZQ=XQ,JQ=q({name:"Food",__name:"food",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M128 352.576V352a288 288 0 0 1 491.072-204.224 192 192 0 0 1 274.24 204.48 64 64 0 0 1 57.216 74.24C921.6 600.512 850.048 710.656 736 756.992V800a96 96 0 0 1-96 96H384a96 96 0 0 1-96-96v-43.008c-114.048-46.336-185.6-156.48-214.528-330.496A64 64 0 0 1 128 352.64zm64-.576h64a160 160 0 0 1 320 0h64a224 224 0 0 0-448 0m128 0h192a96 96 0 0 0-192 0m439.424 0h68.544A128.256 128.256 0 0 0 704 192c-15.36 0-29.952 2.688-43.52 7.616 11.328 18.176 20.672 37.76 27.84 58.304A64.128 64.128 0 0 1 759.424 352M672 768H352v32a32 32 0 0 0 32 32h256a32 32 0 0 0 32-32zm-342.528-64h365.056c101.504-32.64 165.76-124.928 192.896-288H136.576c27.136 163.072 91.392 255.36 192.896 288"})]))}}),QQ=JQ,eee=q({name:"Football",__name:"football",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 960a448 448 0 1 1 0-896 448 448 0 0 1 0 896m0-64a384 384 0 1 0 0-768 384 384 0 0 0 0 768"}),C("path",{fill:"currentColor",d:"M186.816 268.288c16-16.384 31.616-31.744 46.976-46.08 17.472 30.656 39.808 58.112 65.984 81.28l-32.512 56.448a386 386 0 0 1-80.448-91.648m653.696-5.312a385.9 385.9 0 0 1-83.776 96.96l-32.512-56.384a322.9 322.9 0 0 0 68.48-85.76c15.552 14.08 31.488 29.12 47.808 45.184M465.984 445.248l11.136-63.104a323.6 323.6 0 0 0 69.76 0l11.136 63.104a388 388 0 0 1-92.032 0m-62.72-12.8A381.8 381.8 0 0 1 320 396.544l32-55.424a320 320 0 0 0 62.464 27.712l-11.2 63.488zm300.8-35.84a381.8 381.8 0 0 1-83.328 35.84l-11.2-63.552A320 320 0 0 0 672 341.184l32 55.424zm-520.768 364.8a385.9 385.9 0 0 1 83.968-97.28l32.512 56.32c-26.88 23.936-49.856 52.352-67.52 84.032-16-13.44-32.32-27.712-48.96-43.072m657.536.128a1443 1443 0 0 1-49.024 43.072 321.4 321.4 0 0 0-67.584-84.16l32.512-56.32c33.216 27.456 61.696 60.352 84.096 97.408M465.92 578.752a388 388 0 0 1 92.032 0l-11.136 63.104a323.6 323.6 0 0 0-69.76 0zm-62.72 12.8 11.2 63.552a320 320 0 0 0-62.464 27.712L320 627.392a381.8 381.8 0 0 1 83.264-35.84zm300.8 35.84-32 55.424a318.3 318.3 0 0 0-62.528-27.712l11.2-63.488c29.44 8.64 57.28 20.736 83.264 35.776z"})]))}}),tee=eee,nee=q({name:"ForkSpoon",__name:"fork-spoon",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M256 410.304V96a32 32 0 0 1 64 0v314.304a96 96 0 0 0 64-90.56V96a32 32 0 0 1 64 0v223.744a160 160 0 0 1-128 156.8V928a32 32 0 1 1-64 0V476.544a160 160 0 0 1-128-156.8V96a32 32 0 0 1 64 0v223.744a96 96 0 0 0 64 90.56M672 572.48C581.184 552.128 512 446.848 512 320c0-141.44 85.952-256 192-256s192 114.56 192 256c0 126.848-69.184 232.128-160 252.48V928a32 32 0 1 1-64 0zM704 512c66.048 0 128-82.56 128-192s-61.952-192-128-192-128 82.56-128 192 61.952 192 128 192"})]))}}),aee=nee,ree=q({name:"Fries",__name:"fries",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M608 224v-64a32 32 0 0 0-64 0v336h26.88A64 64 0 0 0 608 484.096zm101.12 160A64 64 0 0 0 672 395.904V384h64V224a32 32 0 1 0-64 0v160zm74.88 0a92.928 92.928 0 0 1 91.328 110.08l-60.672 323.584A96 96 0 0 1 720.32 896H303.68a96 96 0 0 1-94.336-78.336L148.672 494.08A92.928 92.928 0 0 1 240 384h-16V224a96 96 0 0 1 188.608-25.28A95.74 95.74 0 0 1 480 197.44V160a96 96 0 0 1 188.608-25.28A96 96 0 0 1 800 224v160zM670.784 512a128 128 0 0 1-99.904 48H453.12a128 128 0 0 1-99.84-48H352v-1.536a128 128 0 0 1-9.984-14.976L314.88 448H240a28.928 28.928 0 0 0-28.48 34.304L241.088 640h541.824l29.568-157.696A28.928 28.928 0 0 0 784 448h-74.88l-27.136 47.488A132 132 0 0 1 672 510.464V512zM480 288a32 32 0 0 0-64 0v196.096A64 64 0 0 0 453.12 496H480zm-128 96V224a32 32 0 0 0-64 0v160zh-37.12A64 64 0 0 1 352 395.904zm-98.88 320 19.072 101.888A32 32 0 0 0 303.68 832h416.64a32 32 0 0 0 31.488-26.112L770.88 704z"})]))}}),oee=ree,iee=q({name:"FullScreen",__name:"full-screen",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m160 96.064 192 .192a32 32 0 0 1 0 64l-192-.192V352a32 32 0 0 1-64 0V96h64zm0 831.872V928H96V672a32 32 0 1 1 64 0v191.936l192-.192a32 32 0 1 1 0 64zM864 96.064V96h64v256a32 32 0 1 1-64 0V160.064l-192 .192a32 32 0 1 1 0-64zm0 831.872-192-.192a32 32 0 0 1 0-64l192 .192V672a32 32 0 1 1 64 0v256h-64z"})]))}}),II=iee,lee=q({name:"GobletFull",__name:"goblet-full",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M256 320h512c0-78.592-12.608-142.4-36.928-192h-434.24C269.504 192.384 256 256.256 256 320m503.936 64H264.064a256.128 256.128 0 0 0 495.872 0M544 638.4V896h96a32 32 0 1 1 0 64H384a32 32 0 1 1 0-64h96V638.4A320 320 0 0 1 192 320c0-85.632 21.312-170.944 64-256h512c42.688 64.32 64 149.632 64 256a320 320 0 0 1-288 318.4"})]))}}),see=lee,uee=q({name:"GobletSquareFull",__name:"goblet-square-full",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M256 270.912c10.048 6.72 22.464 14.912 28.992 18.624a220.16 220.16 0 0 0 114.752 30.72c30.592 0 49.408-9.472 91.072-41.152l.64-.448c52.928-40.32 82.368-55.04 132.288-54.656 55.552.448 99.584 20.8 142.72 57.408l1.536 1.28V128H256zm.96 76.288C266.368 482.176 346.88 575.872 512 576c157.44.064 237.952-85.056 253.248-209.984a952 952 0 0 1-40.192-35.712c-32.704-27.776-63.36-41.92-101.888-42.24-31.552-.256-50.624 9.28-93.12 41.6l-.576.448c-52.096 39.616-81.024 54.208-129.792 54.208-54.784 0-100.48-13.376-142.784-37.056zM480 638.848C250.624 623.424 192 442.496 192 319.68V96a32 32 0 0 1 32-32h576a32 32 0 0 1 32 32v224c0 122.816-58.624 303.68-288 318.912V896h96a32 32 0 1 1 0 64H384a32 32 0 1 1 0-64h96z"})]))}}),cee=uee,dee=q({name:"GobletSquare",__name:"goblet-square",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M544 638.912V896h96a32 32 0 1 1 0 64H384a32 32 0 1 1 0-64h96V638.848C250.624 623.424 192 442.496 192 319.68V96a32 32 0 0 1 32-32h576a32 32 0 0 1 32 32v224c0 122.816-58.624 303.68-288 318.912M256 319.68c0 149.568 80 256.192 256 256.256C688.128 576 768 469.568 768 320V128H256z"})]))}}),fee=dee,vee=q({name:"Goblet",__name:"goblet",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M544 638.4V896h96a32 32 0 1 1 0 64H384a32 32 0 1 1 0-64h96V638.4A320 320 0 0 1 192 320c0-85.632 21.312-170.944 64-256h512c42.688 64.32 64 149.632 64 256a320 320 0 0 1-288 318.4M256 320a256 256 0 1 0 512 0c0-78.592-12.608-142.4-36.928-192h-434.24C269.504 192.384 256 256.256 256 320"})]))}}),hee=vee,pee=q({name:"GoldMedal",__name:"gold-medal",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg","xml:space":"preserve",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m772.13 452.84 53.86-351.81c1.32-10.01-1.17-18.68-7.49-26.02S804.35 64 795.01 64H228.99v-.01h-.06c-9.33 0-17.15 3.67-23.49 11.01s-8.83 16.01-7.49 26.02l53.87 351.89C213.54 505.73 193.59 568.09 192 640c2 90.67 33.17 166.17 93.5 226.5S421.33 957.99 512 960c90.67-2 166.17-33.17 226.5-93.5 60.33-60.34 91.49-135.83 93.5-226.5-1.59-71.94-21.56-134.32-59.87-187.16M640.01 128h117.02l-39.01 254.02c-20.75-10.64-40.74-19.73-59.94-27.28-5.92-3-11.95-5.8-18.08-8.41V128zM576 128v198.76c-13.18-2.58-26.74-4.43-40.67-5.55-8.07-.8-15.85-1.2-23.33-1.2-10.54 0-21.09.66-31.64 1.96a360 360 0 0 0-32.36 4.79V128zm-192 0h.04v218.3c-6.22 2.66-12.34 5.5-18.36 8.56-19.13 7.54-39.02 16.6-59.66 27.16L267.01 128zm308.99 692.99c-48 48-108.33 73-180.99 75.01-72.66-2.01-132.99-27.01-180.99-75.01S258.01 712.66 256 640c2.01-72.66 27.01-132.99 75.01-180.99 19.67-19.67 41.41-35.47 65.22-47.41 38.33-15.04 71.15-23.92 98.44-26.65 5.07-.41 10.2-.7 15.39-.88.63-.01 1.28-.03 1.91-.03.66 0 1.35.03 2.02.04 5.11.17 10.15.46 15.13.86 27.4 2.71 60.37 11.65 98.91 26.79 23.71 11.93 45.36 27.69 64.96 47.29 48 48 73 108.33 75.01 180.99-2.01 72.65-27.01 132.98-75.01 180.98"}),C("path",{fill:"currentColor",d:"M544 480H416v64h64v192h-64v64h192v-64h-64z"})]))}}),mee=pee,gee=q({name:"GoodsFilled",__name:"goods-filled",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M192 352h640l64 544H128zm128 224h64V448h-64zm320 0h64V448h-64zM384 288h-64a192 192 0 1 1 384 0h-64a128 128 0 1 0-256 0"})]))}}),yee=gee,bee=q({name:"Goods",__name:"goods",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M320 288v-22.336C320 154.688 405.504 64 512 64s192 90.688 192 201.664v22.4h131.072a32 32 0 0 1 31.808 28.8l57.6 576a32 32 0 0 1-31.808 35.2H131.328a32 32 0 0 1-31.808-35.2l57.6-576a32 32 0 0 1 31.808-28.8H320zm64 0h256v-22.336C640 189.248 582.272 128 512 128s-128 61.248-128 137.664v22.4zm-64 64H217.92l-51.2 512h690.56l-51.264-512H704v96a32 32 0 1 1-64 0v-96H384v96a32 32 0 0 1-64 0z"})]))}}),_ee=bee,wee=q({name:"Grape",__name:"grape",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M544 195.2a160 160 0 0 1 96 60.8 160 160 0 1 1 146.24 254.976 160 160 0 0 1-128 224 160 160 0 1 1-292.48 0 160 160 0 0 1-128-224A160 160 0 1 1 384 256a160 160 0 0 1 96-60.8V128h-64a32 32 0 0 1 0-64h192a32 32 0 0 1 0 64h-64zM512 448a96 96 0 1 0 0-192 96 96 0 0 0 0 192m-256 0a96 96 0 1 0 0-192 96 96 0 0 0 0 192m128 224a96 96 0 1 0 0-192 96 96 0 0 0 0 192m128 224a96 96 0 1 0 0-192 96 96 0 0 0 0 192m128-224a96 96 0 1 0 0-192 96 96 0 0 0 0 192m128-224a96 96 0 1 0 0-192 96 96 0 0 0 0 192"})]))}}),See=wee,Cee=q({name:"Grid",__name:"grid",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M640 384v256H384V384zm64 0h192v256H704zm-64 512H384V704h256zm64 0V704h192v192zm-64-768v192H384V128zm64 0h192v192H704zM320 384v256H128V384zm0 512H128V704h192zm0-768v192H128V128z"})]))}}),xee=Cee,kee=q({name:"Guide",__name:"guide",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M640 608h-64V416h64zm0 160v160a32 32 0 0 1-32 32H416a32 32 0 0 1-32-32V768h64v128h128V768zM384 608V416h64v192zm256-352h-64V128H448v128h-64V96a32 32 0 0 1 32-32h192a32 32 0 0 1 32 32z"}),C("path",{fill:"currentColor",d:"m220.8 256-71.232 80 71.168 80H768V256zm-14.4-64H800a32 32 0 0 1 32 32v224a32 32 0 0 1-32 32H206.4a32 32 0 0 1-23.936-10.752l-99.584-112a32 32 0 0 1 0-42.496l99.584-112A32 32 0 0 1 206.4 192m678.784 496-71.104 80H266.816V608h547.2zm-56.768-144H234.88a32 32 0 0 0-32 32v224a32 32 0 0 0 32 32h593.6a32 32 0 0 0 23.936-10.752l99.584-112a32 32 0 0 0 0-42.496l-99.584-112A32 32 0 0 0 828.48 544z"})]))}}),Tee=kee,Mee=q({name:"Handbag",__name:"handbag",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg","xml:space":"preserve",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M887.01 264.99c-6-5.99-13.67-8.99-23.01-8.99H704c-1.34-54.68-20.01-100.01-56-136s-81.32-54.66-136-56c-54.68 1.34-100.01 20.01-136 56s-54.66 81.32-56 136H160c-9.35 0-17.02 3-23.01 8.99-5.99 6-8.99 13.67-8.99 23.01v640c0 9.35 2.99 17.02 8.99 23.01S150.66 960 160 960h704c9.35 0 17.02-2.99 23.01-8.99S896 937.34 896 928V288c0-9.35-2.99-17.02-8.99-23.01M421.5 165.5c24.32-24.34 54.49-36.84 90.5-37.5 35.99.68 66.16 13.18 90.5 37.5s36.84 54.49 37.5 90.5H384c.68-35.99 13.18-66.16 37.5-90.5M832 896H192V320h128v128h64V320h256v128h64V320h128z"})]))}}),Eee=Mee,Aee=q({name:"Headset",__name:"headset",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M896 529.152V512a384 384 0 1 0-768 0v17.152A128 128 0 0 1 320 640v128a128 128 0 1 1-256 0V512a448 448 0 1 1 896 0v256a128 128 0 1 1-256 0V640a128 128 0 0 1 192-110.848M896 640a64 64 0 0 0-128 0v128a64 64 0 0 0 128 0zm-768 0v128a64 64 0 0 0 128 0V640a64 64 0 1 0-128 0"})]))}}),Iee=Aee,Lee=q({name:"HelpFilled",__name:"help-filled",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M926.784 480H701.312A192.51 192.51 0 0 0 544 322.688V97.216A416.064 416.064 0 0 1 926.784 480m0 64A416.064 416.064 0 0 1 544 926.784V701.312A192.51 192.51 0 0 0 701.312 544zM97.28 544h225.472A192.51 192.51 0 0 0 480 701.312v225.472A416.064 416.064 0 0 1 97.216 544zm0-64A416.064 416.064 0 0 1 480 97.216v225.472A192.51 192.51 0 0 0 322.688 480H97.216z"})]))}}),Pee=Lee,Dee=q({name:"Help",__name:"help",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m759.936 805.248-90.944-91.008A254.9 254.9 0 0 1 512 768a254.9 254.9 0 0 1-156.992-53.76l-90.944 91.008A382.46 382.46 0 0 0 512 896c94.528 0 181.12-34.176 247.936-90.752m45.312-45.312A382.46 382.46 0 0 0 896 512c0-94.528-34.176-181.12-90.752-247.936l-91.008 90.944C747.904 398.4 768 452.864 768 512s-20.096 113.6-53.76 156.992zm-45.312-541.184A382.46 382.46 0 0 0 512 128c-94.528 0-181.12 34.176-247.936 90.752l90.944 91.008A254.9 254.9 0 0 1 512 256c59.136 0 113.6 20.096 156.992 53.76zm-541.184 45.312A382.46 382.46 0 0 0 128 512c0 94.528 34.176 181.12 90.752 247.936l91.008-90.944A254.9 254.9 0 0 1 256 512c0-59.136 20.096-113.6 53.76-156.992zm417.28 394.496a194.6 194.6 0 0 0 22.528-22.528C686.912 602.56 704 559.232 704 512a191.23 191.23 0 0 0-67.968-146.56A191.3 191.3 0 0 0 512 320a191.23 191.23 0 0 0-146.56 67.968C337.088 421.44 320 464.768 320 512a191.23 191.23 0 0 0 67.968 146.56C421.44 686.912 464.768 704 512 704c47.296 0 90.56-17.088 124.032-45.44M512 960a448 448 0 1 1 0-896 448 448 0 0 1 0 896"})]))}}),Oee=Dee,$ee=q({name:"Hide",__name:"hide",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M876.8 156.8c0-9.6-3.2-16-9.6-22.4s-12.8-9.6-22.4-9.6-16 3.2-22.4 9.6L736 220.8c-64-32-137.6-51.2-224-60.8-160 16-288 73.6-377.6 176S0 496 0 512s48 73.6 134.4 176c22.4 25.6 44.8 48 73.6 67.2l-86.4 89.6c-6.4 6.4-9.6 12.8-9.6 22.4s3.2 16 9.6 22.4 12.8 9.6 22.4 9.6 16-3.2 22.4-9.6l704-710.4c3.2-6.4 6.4-12.8 6.4-22.4m-646.4 528Q115.2 579.2 76.8 512q43.2-72 153.6-172.8C304 272 400 230.4 512 224c64 3.2 124.8 19.2 176 44.8l-54.4 54.4C598.4 300.8 560 288 512 288c-64 0-115.2 22.4-160 64s-64 96-64 160c0 48 12.8 89.6 35.2 124.8L256 707.2c-9.6-6.4-19.2-16-25.6-22.4m140.8-96Q352 555.2 352 512c0-44.8 16-83.2 48-112s67.2-48 112-48c28.8 0 54.4 6.4 73.6 19.2zM889.599 336c-12.8-16-28.8-28.8-41.6-41.6l-48 48c73.6 67.2 124.8 124.8 150.4 169.6q-43.2 72-153.6 172.8c-73.6 67.2-172.8 108.8-284.8 115.2-51.2-3.2-99.2-12.8-140.8-28.8l-48 48c57.6 22.4 118.4 38.4 188.8 44.8 160-16 288-73.6 377.6-176S1024 528 1024 512s-48.001-73.6-134.401-176"}),C("path",{fill:"currentColor",d:"M511.998 672c-12.8 0-25.6-3.2-38.4-6.4l-51.2 51.2c28.8 12.8 57.6 19.2 89.6 19.2 64 0 115.2-22.4 160-64 41.6-41.6 64-96 64-160 0-32-6.4-64-19.2-89.6l-51.2 51.2c3.2 12.8 6.4 25.6 6.4 38.4 0 44.8-16 83.2-48 112s-67.2 48-112 48"})]))}}),LI=$ee,Ree=q({name:"Histogram",__name:"histogram",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M416 896V128h192v768zm-288 0V448h192v448zm576 0V320h192v576z"})]))}}),Nee=Ree,Bee=q({name:"HomeFilled",__name:"home-filled",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 128 128 447.936V896h255.936V640H640v256h255.936V447.936z"})]))}}),Vee=Bee,zee=q({name:"HotWater",__name:"hot-water",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M273.067 477.867h477.866V409.6H273.067zm0 68.266v51.2A187.733 187.733 0 0 0 460.8 785.067h102.4a187.733 187.733 0 0 0 187.733-187.734v-51.2zm-34.134-204.8h546.134a34.133 34.133 0 0 1 34.133 34.134v221.866a256 256 0 0 1-256 256H460.8a256 256 0 0 1-256-256V375.467a34.133 34.133 0 0 1 34.133-34.134M512 34.133a34.133 34.133 0 0 1 34.133 34.134v170.666a34.133 34.133 0 0 1-68.266 0V68.267A34.133 34.133 0 0 1 512 34.133M375.467 102.4a34.133 34.133 0 0 1 34.133 34.133v102.4a34.133 34.133 0 0 1-68.267 0v-102.4a34.133 34.133 0 0 1 34.134-34.133m273.066 0a34.133 34.133 0 0 1 34.134 34.133v102.4a34.133 34.133 0 1 1-68.267 0v-102.4a34.133 34.133 0 0 1 34.133-34.133M170.667 921.668h682.666a34.133 34.133 0 1 1 0 68.267H170.667a34.133 34.133 0 1 1 0-68.267"})]))}}),Fee=zee,Hee=q({name:"House",__name:"house",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M192 413.952V896h640V413.952L512 147.328zM139.52 374.4l352-293.312a32 32 0 0 1 40.96 0l352 293.312A32 32 0 0 1 896 398.976V928a32 32 0 0 1-32 32H160a32 32 0 0 1-32-32V398.976a32 32 0 0 1 11.52-24.576"})]))}}),Wee=Hee,Kee=q({name:"IceCreamRound",__name:"ice-cream-round",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m308.352 489.344 226.304 226.304a32 32 0 0 0 45.248 0L783.552 512A192 192 0 1 0 512 240.448L308.352 444.16a32 32 0 0 0 0 45.248zm135.744 226.304L308.352 851.392a96 96 0 0 1-135.744-135.744l135.744-135.744-45.248-45.248a96 96 0 0 1 0-135.808L466.752 195.2A256 256 0 0 1 828.8 557.248L625.152 760.96a96 96 0 0 1-135.808 0l-45.248-45.248zM398.848 670.4 353.6 625.152 217.856 760.896a32 32 0 0 0 45.248 45.248zm248.96-384.64a32 32 0 0 1 0 45.248L466.624 512a32 32 0 1 1-45.184-45.248l180.992-181.056a32 32 0 0 1 45.248 0zm90.496 90.496a32 32 0 0 1 0 45.248L557.248 602.496A32 32 0 1 1 512 557.248l180.992-180.992a32 32 0 0 1 45.312 0"})]))}}),Uee=Kee,Gee=q({name:"IceCreamSquare",__name:"ice-cream-square",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M416 640h256a32 32 0 0 0 32-32V160a32 32 0 0 0-32-32H352a32 32 0 0 0-32 32v448a32 32 0 0 0 32 32zm192 64v160a96 96 0 0 1-192 0V704h-64a96 96 0 0 1-96-96V160a96 96 0 0 1 96-96h320a96 96 0 0 1 96 96v448a96 96 0 0 1-96 96zm-64 0h-64v160a32 32 0 1 0 64 0z"})]))}}),Yee=Gee,jee=q({name:"IceCream",__name:"ice-cream",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M128.64 448a208 208 0 0 1 193.536-191.552 224 224 0 0 1 445.248 15.488A208.13 208.13 0 0 1 894.784 448H896L548.8 983.68a32 32 0 0 1-53.248.704L128 448zm64.256 0h286.208a144 144 0 0 0-286.208 0m351.36 0h286.272a144 144 0 0 0-286.272 0m-294.848 64 271.808 396.608L778.24 512zM511.68 352.64a207.87 207.87 0 0 1 189.184-96.192 160 160 0 0 0-314.752 5.632c52.608 12.992 97.28 46.08 125.568 90.56"})]))}}),qee=jee,Xee=q({name:"IceDrink",__name:"ice-drink",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 448v128h239.68l16.064-128zm-64 0H256.256l16.064 128H448zm64-255.36V384h247.744A256.13 256.13 0 0 0 512 192.64m-64 8.064A256.45 256.45 0 0 0 264.256 384H448zm64-72.064A320.13 320.13 0 0 1 825.472 384H896a32 32 0 1 1 0 64h-64v1.92l-56.96 454.016A64 64 0 0 1 711.552 960H312.448a64 64 0 0 1-63.488-56.064L192 449.92V448h-64a32 32 0 0 1 0-64h70.528A320.38 320.38 0 0 1 448 135.04V96a96 96 0 0 1 96-96h128a32 32 0 1 1 0 64H544a32 32 0 0 0-32 32zM743.68 640H280.32l32.128 256h399.104z"})]))}}),Zee=Xee,Jee=q({name:"IceTea",__name:"ice-tea",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M197.696 259.648a320.128 320.128 0 0 1 628.608 0A96 96 0 0 1 896 352v64a96 96 0 0 1-71.616 92.864l-49.408 395.072A64 64 0 0 1 711.488 960H312.512a64 64 0 0 1-63.488-56.064l-49.408-395.072A96 96 0 0 1 128 416v-64a96 96 0 0 1 69.696-92.352M264.064 256h495.872a256.128 256.128 0 0 0-495.872 0m495.424 256H264.512l48 384h398.976zM224 448h576a32 32 0 0 0 32-32v-64a32 32 0 0 0-32-32H224a32 32 0 0 0-32 32v64a32 32 0 0 0 32 32m160 192h64v64h-64zm192 64h64v64h-64zm-128 64h64v64h-64zm64-192h64v64h-64z"})]))}}),Qee=Jee,ete=q({name:"InfoFilled",__name:"info-filled",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 64a448 448 0 1 1 0 896.064A448 448 0 0 1 512 64m67.2 275.072c33.28 0 60.288-23.104 60.288-57.344s-27.072-57.344-60.288-57.344c-33.28 0-60.16 23.104-60.16 57.344s26.88 57.344 60.16 57.344M590.912 699.2c0-6.848 2.368-24.64 1.024-34.752l-52.608 60.544c-10.88 11.456-24.512 19.392-30.912 17.28a12.99 12.99 0 0 1-8.256-14.72l87.68-276.992c7.168-35.136-12.544-67.2-54.336-71.296-44.096 0-108.992 44.736-148.48 101.504 0 6.784-1.28 23.68.064 33.792l52.544-60.608c10.88-11.328 23.552-19.328 29.952-17.152a12.8 12.8 0 0 1 7.808 16.128L388.48 728.576c-10.048 32.256 8.96 63.872 55.04 71.04 67.84 0 107.904-43.648 147.456-100.416z"})]))}}),Ud=ete,tte=q({name:"Iphone",__name:"iphone",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M224 768v96.064a64 64 0 0 0 64 64h448a64 64 0 0 0 64-64V768zm0-64h576V160a64 64 0 0 0-64-64H288a64 64 0 0 0-64 64zm32 288a96 96 0 0 1-96-96V128a96 96 0 0 1 96-96h512a96 96 0 0 1 96 96v768a96 96 0 0 1-96 96zm304-144a48 48 0 1 1-96 0 48 48 0 0 1 96 0"})]))}}),nte=tte,ate=q({name:"Key",__name:"key",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M448 456.064V96a32 32 0 0 1 32-32.064L672 64a32 32 0 0 1 0 64H512v128h160a32 32 0 0 1 0 64H512v128a256 256 0 1 1-64 8.064M512 896a192 192 0 1 0 0-384 192 192 0 0 0 0 384"})]))}}),PI=ate,rte=q({name:"KnifeFork",__name:"knife-fork",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M256 410.56V96a32 32 0 0 1 64 0v314.56A96 96 0 0 0 384 320V96a32 32 0 0 1 64 0v224a160 160 0 0 1-128 156.8V928a32 32 0 1 1-64 0V476.8A160 160 0 0 1 128 320V96a32 32 0 0 1 64 0v224a96 96 0 0 0 64 90.56m384-250.24V544h126.72c-3.328-78.72-12.928-147.968-28.608-207.744-14.336-54.528-46.848-113.344-98.112-175.872zM640 608v320a32 32 0 1 1-64 0V64h64c85.312 89.472 138.688 174.848 160 256s32 177.152 32 288z"})]))}}),ote=rte,ite=q({name:"Lightning",__name:"lightning",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M288 671.36v64.128A239.81 239.81 0 0 1 63.744 496.192a240.32 240.32 0 0 1 199.488-236.8 256.128 256.128 0 0 1 487.872-30.976A256.064 256.064 0 0 1 736 734.016v-64.768a192 192 0 0 0 3.328-377.92l-35.2-6.592-12.8-33.408a192.064 192.064 0 0 0-365.952 23.232l-9.92 40.896-41.472 7.04a176.32 176.32 0 0 0-146.24 173.568c0 91.968 70.464 167.36 160.256 175.232z"}),C("path",{fill:"currentColor",d:"M416 736a32 32 0 0 1-27.776-47.872l128-224a32 32 0 1 1 55.552 31.744L471.168 672H608a32 32 0 0 1 27.776 47.872l-128 224a32 32 0 1 1-55.68-31.744L552.96 736z"})]))}}),lte=ite,ste=q({name:"Link",__name:"link",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M715.648 625.152 670.4 579.904l90.496-90.56c75.008-74.944 85.12-186.368 22.656-248.896-62.528-62.464-173.952-52.352-248.96 22.656L444.16 353.6l-45.248-45.248 90.496-90.496c100.032-99.968 251.968-110.08 339.456-22.656 87.488 87.488 77.312 239.424-22.656 339.456l-90.496 90.496zm-90.496 90.496-90.496 90.496C434.624 906.112 282.688 916.224 195.2 828.8c-87.488-87.488-77.312-239.424 22.656-339.456l90.496-90.496 45.248 45.248-90.496 90.56c-75.008 74.944-85.12 186.368-22.656 248.896 62.528 62.464 173.952 52.352 248.96-22.656l90.496-90.496zm0-362.048 45.248 45.248L398.848 670.4 353.6 625.152z"})]))}}),DI=ste,ute=q({name:"List",__name:"list",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M704 192h160v736H160V192h160v64h384zM288 512h448v-64H288zm0 256h448v-64H288zm96-576V96h256v96z"})]))}}),cte=ute,dte=q({name:"Loading",__name:"loading",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 64a32 32 0 0 1 32 32v192a32 32 0 0 1-64 0V96a32 32 0 0 1 32-32m0 640a32 32 0 0 1 32 32v192a32 32 0 1 1-64 0V736a32 32 0 0 1 32-32m448-192a32 32 0 0 1-32 32H736a32 32 0 1 1 0-64h192a32 32 0 0 1 32 32m-640 0a32 32 0 0 1-32 32H96a32 32 0 0 1 0-64h192a32 32 0 0 1 32 32M195.2 195.2a32 32 0 0 1 45.248 0L376.32 331.008a32 32 0 0 1-45.248 45.248L195.2 240.448a32 32 0 0 1 0-45.248m452.544 452.544a32 32 0 0 1 45.248 0L828.8 783.552a32 32 0 0 1-45.248 45.248L647.744 692.992a32 32 0 0 1 0-45.248M828.8 195.264a32 32 0 0 1 0 45.184L692.992 376.32a32 32 0 0 1-45.248-45.248l135.808-135.808a32 32 0 0 1 45.248 0m-452.544 452.48a32 32 0 0 1 0 45.248L240.448 828.8a32 32 0 0 1-45.248-45.248l135.808-135.808a32 32 0 0 1 45.248 0"})]))}}),Mr=dte,fte=q({name:"LocationFilled",__name:"location-filled",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 928c23.936 0 117.504-68.352 192.064-153.152C803.456 661.888 864 535.808 864 416c0-189.632-155.84-320-352-320S160 226.368 160 416c0 120.32 60.544 246.4 159.936 359.232C394.432 859.84 488 928 512 928m0-435.2a64 64 0 1 0 0-128 64 64 0 0 0 0 128m0 140.8a204.8 204.8 0 1 1 0-409.6 204.8 204.8 0 0 1 0 409.6"})]))}}),vte=fte,hte=q({name:"LocationInformation",__name:"location-information",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M288 896h448q32 0 32 32t-32 32H288q-32 0-32-32t32-32"}),C("path",{fill:"currentColor",d:"M800 416a288 288 0 1 0-576 0c0 118.144 94.528 272.128 288 456.576C705.472 688.128 800 534.144 800 416M512 960C277.312 746.688 160 565.312 160 416a352 352 0 0 1 704 0c0 149.312-117.312 330.688-352 544"}),C("path",{fill:"currentColor",d:"M512 512a96 96 0 1 0 0-192 96 96 0 0 0 0 192m0 64a160 160 0 1 1 0-320 160 160 0 0 1 0 320"})]))}}),pte=hte,mte=q({name:"Location",__name:"location",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M800 416a288 288 0 1 0-576 0c0 118.144 94.528 272.128 288 456.576C705.472 688.128 800 534.144 800 416M512 960C277.312 746.688 160 565.312 160 416a352 352 0 0 1 704 0c0 149.312-117.312 330.688-352 544"}),C("path",{fill:"currentColor",d:"M512 512a96 96 0 1 0 0-192 96 96 0 0 0 0 192m0 64a160 160 0 1 1 0-320 160 160 0 0 1 0 320"})]))}}),gte=mte,yte=q({name:"Lock",__name:"lock",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M224 448a32 32 0 0 0-32 32v384a32 32 0 0 0 32 32h576a32 32 0 0 0 32-32V480a32 32 0 0 0-32-32zm0-64h576a96 96 0 0 1 96 96v384a96 96 0 0 1-96 96H224a96 96 0 0 1-96-96V480a96 96 0 0 1 96-96"}),C("path",{fill:"currentColor",d:"M512 544a32 32 0 0 1 32 32v192a32 32 0 1 1-64 0V576a32 32 0 0 1 32-32m192-160v-64a192 192 0 1 0-384 0v64zM512 64a256 256 0 0 1 256 256v128H256V320A256 256 0 0 1 512 64"})]))}}),bte=yte,_te=q({name:"Lollipop",__name:"lollipop",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M513.28 448a64 64 0 1 1 76.544 49.728A96 96 0 0 0 768 448h64a160 160 0 0 1-320 0zm-126.976-29.696a256 256 0 1 0 43.52-180.48A256 256 0 0 1 832 448h-64a192 192 0 0 0-381.696-29.696m105.664 249.472L285.696 874.048a96 96 0 0 1-135.68-135.744l206.208-206.272a320 320 0 1 1 135.744 135.744m-54.464-36.032a322 322 0 0 1-45.248-45.248L195.2 783.552a32 32 0 1 0 45.248 45.248l197.056-197.12z"})]))}}),wte=_te,Ste=q({name:"MagicStick",__name:"magic-stick",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 64h64v192h-64zm0 576h64v192h-64zM160 480v-64h192v64zm576 0v-64h192v64zM249.856 199.04l45.248-45.184L430.848 289.6 385.6 334.848 249.856 199.104zM657.152 606.4l45.248-45.248 135.744 135.744-45.248 45.248zM114.048 923.2 68.8 877.952l316.8-316.8 45.248 45.248zM702.4 334.848 657.152 289.6l135.744-135.744 45.248 45.248z"})]))}}),Cte=Ste,xte=q({name:"Magnet",__name:"magnet",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M832 320V192H704v320a192 192 0 1 1-384 0V192H192v128h128v64H192v128a320 320 0 0 0 640 0V384H704v-64zM640 512V128h256v384a384 384 0 1 1-768 0V128h256v384a128 128 0 1 0 256 0"})]))}}),kte=xte,Tte=q({name:"Male",__name:"male",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M399.5 849.5a225 225 0 1 0 0-450 225 225 0 0 0 0 450m0 56.25a281.25 281.25 0 1 1 0-562.5 281.25 281.25 0 0 1 0 562.5m253.125-787.5h225q28.125 0 28.125 28.125T877.625 174.5h-225q-28.125 0-28.125-28.125t28.125-28.125"}),C("path",{fill:"currentColor",d:"M877.625 118.25q28.125 0 28.125 28.125v225q0 28.125-28.125 28.125T849.5 371.375v-225q0-28.125 28.125-28.125"}),C("path",{fill:"currentColor",d:"M604.813 458.9 565.1 419.131l292.613-292.668 39.825 39.824z"})]))}}),Mte=Tte,Ete=q({name:"Management",__name:"management",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M576 128v288l96-96 96 96V128h128v768H320V128zm-448 0h128v768H128z"})]))}}),Ate=Ete,Ite=q({name:"MapLocation",__name:"map-location",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M800 416a288 288 0 1 0-576 0c0 118.144 94.528 272.128 288 456.576C705.472 688.128 800 534.144 800 416M512 960C277.312 746.688 160 565.312 160 416a352 352 0 0 1 704 0c0 149.312-117.312 330.688-352 544"}),C("path",{fill:"currentColor",d:"M512 448a64 64 0 1 0 0-128 64 64 0 0 0 0 128m0 64a128 128 0 1 1 0-256 128 128 0 0 1 0 256m345.6 192L960 960H672v-64H352v64H64l102.4-256zm-68.928 0H235.328l-76.8 192h706.944z"})]))}}),Lte=Ite,Pte=q({name:"Medal",__name:"medal",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 896a256 256 0 1 0 0-512 256 256 0 0 0 0 512m0 64a320 320 0 1 1 0-640 320 320 0 0 1 0 640"}),C("path",{fill:"currentColor",d:"M576 128H448v200a286.7 286.7 0 0 1 64-8c19.52 0 40.832 2.688 64 8zm64 0v219.648c24.448 9.088 50.56 20.416 78.4 33.92L757.44 128zm-256 0H266.624l39.04 253.568c27.84-13.504 53.888-24.832 78.336-33.92zM229.312 64h565.376a32 32 0 0 1 31.616 36.864L768 480c-113.792-64-199.104-96-256-96s-142.208 32-256 96l-58.304-379.136A32 32 0 0 1 229.312 64"})]))}}),Dte=Pte,Ote=q({name:"Memo",__name:"memo",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg","xml:space":"preserve",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M480 320h192c21.33 0 32-10.67 32-32s-10.67-32-32-32H480c-21.33 0-32 10.67-32 32s10.67 32 32 32"}),C("path",{fill:"currentColor",d:"M887.01 72.99C881.01 67 873.34 64 864 64H160c-9.35 0-17.02 3-23.01 8.99C131 78.99 128 86.66 128 96v832c0 9.35 2.99 17.02 8.99 23.01S150.66 960 160 960h704c9.35 0 17.02-2.99 23.01-8.99S896 937.34 896 928V96c0-9.35-3-17.02-8.99-23.01M192 896V128h96v768zm640 0H352V128h480z"}),C("path",{fill:"currentColor",d:"M480 512h192c21.33 0 32-10.67 32-32s-10.67-32-32-32H480c-21.33 0-32 10.67-32 32s10.67 32 32 32m0 192h192c21.33 0 32-10.67 32-32s-10.67-32-32-32H480c-21.33 0-32 10.67-32 32s10.67 32 32 32"})]))}}),$te=Ote,Rte=q({name:"Menu",__name:"menu",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M160 448a32 32 0 0 1-32-32V160.064a32 32 0 0 1 32-32h256a32 32 0 0 1 32 32V416a32 32 0 0 1-32 32zm448 0a32 32 0 0 1-32-32V160.064a32 32 0 0 1 32-32h255.936a32 32 0 0 1 32 32V416a32 32 0 0 1-32 32zM160 896a32 32 0 0 1-32-32V608a32 32 0 0 1 32-32h256a32 32 0 0 1 32 32v256a32 32 0 0 1-32 32zm448 0a32 32 0 0 1-32-32V608a32 32 0 0 1 32-32h255.936a32 32 0 0 1 32 32v256a32 32 0 0 1-32 32z"})]))}}),Nte=Rte,Bte=q({name:"MessageBox",__name:"message-box",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M288 384h448v64H288zm96-128h256v64H384zM131.456 512H384v128h256V512h252.544L721.856 192H302.144zM896 576H704v128H320V576H128v256h768zM275.776 128h472.448a32 32 0 0 1 28.608 17.664l179.84 359.552A32 32 0 0 1 960 519.552V864a32 32 0 0 1-32 32H96a32 32 0 0 1-32-32V519.552a32 32 0 0 1 3.392-14.336l179.776-359.552A32 32 0 0 1 275.776 128"})]))}}),Vte=Bte,zte=q({name:"Message",__name:"message",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M128 224v512a64 64 0 0 0 64 64h640a64 64 0 0 0 64-64V224zm0-64h768a64 64 0 0 1 64 64v512a128 128 0 0 1-128 128H192A128 128 0 0 1 64 736V224a64 64 0 0 1 64-64"}),C("path",{fill:"currentColor",d:"M904 224 656.512 506.88a192 192 0 0 1-289.024 0L120 224zm-698.944 0 210.56 240.704a128 128 0 0 0 192.704 0L818.944 224z"})]))}}),Fte=zte,Hte=q({name:"Mic",__name:"mic",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M480 704h160a64 64 0 0 0 64-64v-32h-96a32 32 0 0 1 0-64h96v-96h-96a32 32 0 0 1 0-64h96v-96h-96a32 32 0 0 1 0-64h96v-32a64 64 0 0 0-64-64H384a64 64 0 0 0-64 64v32h96a32 32 0 0 1 0 64h-96v96h96a32 32 0 0 1 0 64h-96v96h96a32 32 0 0 1 0 64h-96v32a64 64 0 0 0 64 64zm64 64v128h192a32 32 0 1 1 0 64H288a32 32 0 1 1 0-64h192V768h-96a128 128 0 0 1-128-128V192A128 128 0 0 1 384 64h256a128 128 0 0 1 128 128v448a128 128 0 0 1-128 128z"})]))}}),Wte=Hte,Kte=q({name:"Microphone",__name:"microphone",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 128a128 128 0 0 0-128 128v256a128 128 0 1 0 256 0V256a128 128 0 0 0-128-128m0-64a192 192 0 0 1 192 192v256a192 192 0 1 1-384 0V256A192 192 0 0 1 512 64m-32 832v-64a288 288 0 0 1-288-288v-32a32 32 0 0 1 64 0v32a224 224 0 0 0 224 224h64a224 224 0 0 0 224-224v-32a32 32 0 1 1 64 0v32a288 288 0 0 1-288 288v64h64a32 32 0 1 1 0 64H416a32 32 0 1 1 0-64z"})]))}}),Ute=Kte,Gte=q({name:"MilkTea",__name:"milk-tea",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M416 128V96a96 96 0 0 1 96-96h128a32 32 0 1 1 0 64H512a32 32 0 0 0-32 32v32h320a96 96 0 0 1 11.712 191.296l-39.68 581.056A64 64 0 0 1 708.224 960H315.776a64 64 0 0 1-63.872-59.648l-39.616-581.056A96 96 0 0 1 224 128zM276.48 320l39.296 576h392.448l4.8-70.784a224.064 224.064 0 0 1 30.016-439.808L747.52 320zM224 256h576a32 32 0 1 0 0-64H224a32 32 0 0 0 0 64m493.44 503.872 21.12-309.12a160 160 0 0 0-21.12 309.12"})]))}}),Yte=Gte,jte=q({name:"Minus",__name:"minus",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M128 544h768a32 32 0 1 0 0-64H128a32 32 0 0 0 0 64"})]))}}),OI=jte,qte=q({name:"Money",__name:"money",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M256 640v192h640V384H768v-64h150.976c14.272 0 19.456 1.472 24.64 4.288a29.06 29.06 0 0 1 12.16 12.096c2.752 5.184 4.224 10.368 4.224 24.64v493.952c0 14.272-1.472 19.456-4.288 24.64a29.06 29.06 0 0 1-12.096 12.16c-5.184 2.752-10.368 4.224-24.64 4.224H233.024c-14.272 0-19.456-1.472-24.64-4.288a29.06 29.06 0 0 1-12.16-12.096c-2.688-5.184-4.224-10.368-4.224-24.576V640z"}),C("path",{fill:"currentColor",d:"M768 192H128v448h640zm64-22.976v493.952c0 14.272-1.472 19.456-4.288 24.64a29.06 29.06 0 0 1-12.096 12.16c-5.184 2.752-10.368 4.224-24.64 4.224H105.024c-14.272 0-19.456-1.472-24.64-4.288a29.06 29.06 0 0 1-12.16-12.096C65.536 682.432 64 677.248 64 663.04V169.024c0-14.272 1.472-19.456 4.288-24.64a29.06 29.06 0 0 1 12.096-12.16C85.568 129.536 90.752 128 104.96 128h685.952c14.272 0 19.456 1.472 24.64 4.288a29.06 29.06 0 0 1 12.16 12.096c2.752 5.184 4.224 10.368 4.224 24.64z"}),C("path",{fill:"currentColor",d:"M448 576a160 160 0 1 1 0-320 160 160 0 0 1 0 320m0-64a96 96 0 1 0 0-192 96 96 0 0 0 0 192"})]))}}),Xte=qte,Zte=q({name:"Monitor",__name:"monitor",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M544 768v128h192a32 32 0 1 1 0 64H288a32 32 0 1 1 0-64h192V768H192A128 128 0 0 1 64 640V256a128 128 0 0 1 128-128h640a128 128 0 0 1 128 128v384a128 128 0 0 1-128 128zM192 192a64 64 0 0 0-64 64v384a64 64 0 0 0 64 64h640a64 64 0 0 0 64-64V256a64 64 0 0 0-64-64z"})]))}}),Jte=Zte,Qte=q({name:"MoonNight",__name:"moon-night",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M384 512a448 448 0 0 1 215.872-383.296A384 384 0 0 0 213.76 640h188.8A448.3 448.3 0 0 1 384 512M171.136 704a448 448 0 0 1 636.992-575.296A384 384 0 0 0 499.328 704h-328.32z"}),C("path",{fill:"currentColor",d:"M32 640h960q32 0 32 32t-32 32H32q-32 0-32-32t32-32m128 128h384a32 32 0 1 1 0 64H160a32 32 0 1 1 0-64m160 127.68 224 .256a32 32 0 0 1 32 32V928a32 32 0 0 1-32 32l-224-.384a32 32 0 0 1-32-32v-.064a32 32 0 0 1 32-32z"})]))}}),ene=Qte,tne=q({name:"Moon",__name:"moon",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M240.448 240.448a384 384 0 1 0 559.424 525.696 448 448 0 0 1-542.016-542.08 391 391 0 0 0-17.408 16.384m181.056 362.048a384 384 0 0 0 525.632 16.384A448 448 0 1 1 405.056 76.8a384 384 0 0 0 16.448 525.696"})]))}}),nne=tne,ane=q({name:"MoreFilled",__name:"more-filled",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M176 416a112 112 0 1 1 0 224 112 112 0 0 1 0-224m336 0a112 112 0 1 1 0 224 112 112 0 0 1 0-224m336 0a112 112 0 1 1 0 224 112 112 0 0 1 0-224"})]))}}),m4=ane,rne=q({name:"More",__name:"more",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M176 416a112 112 0 1 0 0 224 112 112 0 0 0 0-224m0 64a48 48 0 1 1 0 96 48 48 0 0 1 0-96m336-64a112 112 0 1 1 0 224 112 112 0 0 1 0-224m0 64a48 48 0 1 0 0 96 48 48 0 0 0 0-96m336-64a112 112 0 1 1 0 224 112 112 0 0 1 0-224m0 64a48 48 0 1 0 0 96 48 48 0 0 0 0-96"})]))}}),$I=rne,one=q({name:"MostlyCloudy",__name:"mostly-cloudy",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M737.216 357.952 704 349.824l-11.776-32a192.064 192.064 0 0 0-367.424 23.04l-8.96 39.04-39.04 8.96A192.064 192.064 0 0 0 320 768h368a207.81 207.81 0 0 0 207.808-208 208.32 208.32 0 0 0-158.592-202.048m15.168-62.208A272.32 272.32 0 0 1 959.744 560a271.81 271.81 0 0 1-271.552 272H320a256 256 0 0 1-57.536-505.536 256.128 256.128 0 0 1 489.92-30.72"})]))}}),ine=one,lne=q({name:"Mouse",__name:"mouse",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M438.144 256c-68.352 0-92.736 4.672-117.76 18.112q-30.144 16.128-46.272 46.272C260.672 345.408 256 369.792 256 438.144v275.712c0 68.352 4.672 92.736 18.112 117.76q16.128 30.144 46.272 46.272C345.408 891.328 369.792 896 438.144 896h147.712c68.352 0 92.736-4.672 117.76-18.112q30.144-16.128 46.272-46.272C763.328 806.592 768 782.208 768 713.856V438.144c0-68.352-4.672-92.736-18.112-117.76a110.46 110.46 0 0 0-46.272-46.272C678.592 260.672 654.208 256 585.856 256zm0-64h147.712c85.568 0 116.608 8.96 147.904 25.6 31.36 16.768 55.872 41.344 72.576 72.64C823.104 321.536 832 352.576 832 438.08v275.84c0 85.504-8.96 116.544-25.6 147.84a174.46 174.46 0 0 1-72.64 72.576C702.464 951.104 671.424 960 585.92 960H438.08c-85.504 0-116.544-8.96-147.84-25.6a174.46 174.46 0 0 1-72.64-72.704c-16.768-31.296-25.664-62.336-25.664-147.84v-275.84c0-85.504 8.96-116.544 25.6-147.84a174.46 174.46 0 0 1 72.768-72.576c31.232-16.704 62.272-25.6 147.776-25.6z"}),C("path",{fill:"currentColor",d:"M512 320q32 0 32 32v128q0 32-32 32t-32-32V352q0-32 32-32m32-96a32 32 0 0 1-64 0v-64a32 32 0 0 0-32-32h-96a32 32 0 0 1 0-64h96a96 96 0 0 1 96 96z"})]))}}),sne=lne,une=q({name:"Mug",__name:"mug",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M736 800V160H160v640a64 64 0 0 0 64 64h448a64 64 0 0 0 64-64m64-544h63.552a96 96 0 0 1 96 96v224a96 96 0 0 1-96 96H800v128a128 128 0 0 1-128 128H224A128 128 0 0 1 96 800V128a32 32 0 0 1 32-32h640a32 32 0 0 1 32 32zm0 64v288h63.552a32 32 0 0 0 32-32V352a32 32 0 0 0-32-32z"})]))}}),cne=une,dne=q({name:"MuteNotification",__name:"mute-notification",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m241.216 832 63.616-64H768V448c0-42.368-10.24-82.304-28.48-117.504l46.912-47.232C815.36 331.392 832 387.84 832 448v320h96a32 32 0 1 1 0 64zm-90.24 0H96a32 32 0 1 1 0-64h96V448a320.13 320.13 0 0 1 256-313.6V128a64 64 0 1 1 128 0v6.4a319.55 319.55 0 0 1 171.648 97.088l-45.184 45.44A256 256 0 0 0 256 448v278.336L151.04 832zM448 896h128a64 64 0 0 1-128 0"}),C("path",{fill:"currentColor",d:"M150.72 859.072a32 32 0 0 1-45.44-45.056l704-708.544a32 32 0 0 1 45.44 45.056z"})]))}}),fne=dne,vne=q({name:"Mute",__name:"mute",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m412.16 592.128-45.44 45.44A191.23 191.23 0 0 1 320 512V256a192 192 0 1 1 384 0v44.352l-64 64V256a128 128 0 1 0-256 0v256c0 30.336 10.56 58.24 28.16 80.128m51.968 38.592A128 128 0 0 0 640 512v-57.152l64-64V512a192 192 0 0 1-287.68 166.528zM314.88 779.968l46.144-46.08A223 223 0 0 0 480 768h64a224 224 0 0 0 224-224v-32a32 32 0 1 1 64 0v32a288 288 0 0 1-288 288v64h64a32 32 0 1 1 0 64H416a32 32 0 1 1 0-64h64v-64c-61.44 0-118.4-19.2-165.12-52.032M266.752 737.6A286.98 286.98 0 0 1 192 544v-32a32 32 0 0 1 64 0v32c0 56.832 21.184 108.8 56.064 148.288z"}),C("path",{fill:"currentColor",d:"M150.72 859.072a32 32 0 0 1-45.44-45.056l704-708.544a32 32 0 0 1 45.44 45.056z"})]))}}),hne=vne,pne=q({name:"NoSmoking",__name:"no-smoking",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M440.256 576H256v128h56.256l-64 64H224a32 32 0 0 1-32-32V544a32 32 0 0 1 32-32h280.256zm143.488 128H704V583.744L775.744 512H928a32 32 0 0 1 32 32v192a32 32 0 0 1-32 32H519.744zM768 576v128h128V576zm-29.696-207.552 45.248 45.248-497.856 497.856-45.248-45.248zM256 64h64v320h-64zM128 192h64v192h-64zM64 512h64v256H64z"})]))}}),mne=pne,gne=q({name:"Notebook",__name:"notebook",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M192 128v768h640V128zm-32-64h704a32 32 0 0 1 32 32v832a32 32 0 0 1-32 32H160a32 32 0 0 1-32-32V96a32 32 0 0 1 32-32"}),C("path",{fill:"currentColor",d:"M672 128h64v768h-64zM96 192h128q32 0 32 32t-32 32H96q-32 0-32-32t32-32m0 192h128q32 0 32 32t-32 32H96q-32 0-32-32t32-32m0 192h128q32 0 32 32t-32 32H96q-32 0-32-32t32-32m0 192h128q32 0 32 32t-32 32H96q-32 0-32-32t32-32"})]))}}),yne=gne,bne=q({name:"Notification",__name:"notification",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 128v64H256a64 64 0 0 0-64 64v512a64 64 0 0 0 64 64h512a64 64 0 0 0 64-64V512h64v256a128 128 0 0 1-128 128H256a128 128 0 0 1-128-128V256a128 128 0 0 1 128-128z"}),C("path",{fill:"currentColor",d:"M768 384a128 128 0 1 0 0-256 128 128 0 0 0 0 256m0 64a192 192 0 1 1 0-384 192 192 0 0 1 0 384"})]))}}),_ne=bne,wne=q({name:"Odometer",__name:"odometer",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 896a384 384 0 1 0 0-768 384 384 0 0 0 0 768m0 64a448 448 0 1 1 0-896 448 448 0 0 1 0 896"}),C("path",{fill:"currentColor",d:"M192 512a320 320 0 1 1 640 0 32 32 0 1 1-64 0 256 256 0 1 0-512 0 32 32 0 0 1-64 0"}),C("path",{fill:"currentColor",d:"M570.432 627.84A96 96 0 1 1 509.568 608l60.992-187.776A32 32 0 1 1 631.424 440l-60.992 187.776zM502.08 734.464a32 32 0 1 0 19.84-60.928 32 32 0 0 0-19.84 60.928"})]))}}),Sne=wne,Cne=q({name:"OfficeBuilding",__name:"office-building",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M192 128v704h384V128zm-32-64h448a32 32 0 0 1 32 32v768a32 32 0 0 1-32 32H160a32 32 0 0 1-32-32V96a32 32 0 0 1 32-32"}),C("path",{fill:"currentColor",d:"M256 256h256v64H256zm0 192h256v64H256zm0 192h256v64H256zm384-128h128v64H640zm0 128h128v64H640zM64 832h896v64H64z"}),C("path",{fill:"currentColor",d:"M640 384v448h192V384zm-32-64h256a32 32 0 0 1 32 32v512a32 32 0 0 1-32 32H608a32 32 0 0 1-32-32V352a32 32 0 0 1 32-32"})]))}}),xne=Cne,kne=q({name:"Open",__name:"open",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M329.956 257.138a254.862 254.862 0 0 0 0 509.724h364.088a254.862 254.862 0 0 0 0-509.724zm0-72.818h364.088a327.68 327.68 0 1 1 0 655.36H329.956a327.68 327.68 0 1 1 0-655.36"}),C("path",{fill:"currentColor",d:"M694.044 621.227a109.227 109.227 0 1 0 0-218.454 109.227 109.227 0 0 0 0 218.454m0 72.817a182.044 182.044 0 1 1 0-364.088 182.044 182.044 0 0 1 0 364.088"})]))}}),Tne=kne,Mne=q({name:"Operation",__name:"operation",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M389.44 768a96.064 96.064 0 0 1 181.12 0H896v64H570.56a96.064 96.064 0 0 1-181.12 0H128v-64zm192-288a96.064 96.064 0 0 1 181.12 0H896v64H762.56a96.064 96.064 0 0 1-181.12 0H128v-64zm-320-288a96.064 96.064 0 0 1 181.12 0H896v64H442.56a96.064 96.064 0 0 1-181.12 0H128v-64z"})]))}}),Ene=Mne,Ane=q({name:"Opportunity",__name:"opportunity",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M384 960v-64h192.064v64zm448-544a350.66 350.66 0 0 1-128.32 271.424C665.344 719.04 640 763.776 640 813.504V832H320v-14.336c0-48-19.392-95.36-57.216-124.992a351.55 351.55 0 0 1-128.448-344.256c25.344-136.448 133.888-248.128 269.76-276.48A352.384 352.384 0 0 1 832 416m-544 32c0-132.288 75.904-224 192-224v-64c-154.432 0-256 122.752-256 288z"})]))}}),Ine=Ane,Lne=q({name:"Orange",__name:"orange",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M544 894.72a382.34 382.34 0 0 0 215.936-89.472L577.024 622.272c-10.24 6.016-21.248 10.688-33.024 13.696v258.688zm261.248-134.784A382.34 382.34 0 0 0 894.656 544H635.968c-3.008 11.776-7.68 22.848-13.696 33.024zM894.656 480a382.34 382.34 0 0 0-89.408-215.936L622.272 446.976c6.016 10.24 10.688 21.248 13.696 33.024zm-134.72-261.248A382.34 382.34 0 0 0 544 129.344v258.688c11.776 3.008 22.848 7.68 33.024 13.696zM480 129.344a382.34 382.34 0 0 0-215.936 89.408l182.912 182.976c10.24-6.016 21.248-10.688 33.024-13.696zm-261.248 134.72A382.34 382.34 0 0 0 129.344 480h258.688c3.008-11.776 7.68-22.848 13.696-33.024zM129.344 544a382.34 382.34 0 0 0 89.408 215.936l182.976-182.912A127.2 127.2 0 0 1 388.032 544zm134.72 261.248A382.34 382.34 0 0 0 480 894.656V635.968a127.2 127.2 0 0 1-33.024-13.696zM512 960a448 448 0 1 1 0-896 448 448 0 0 1 0 896m0-384a64 64 0 1 0 0-128 64 64 0 0 0 0 128"})]))}}),Pne=Lne,Dne=q({name:"Paperclip",__name:"paperclip",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M602.496 240.448A192 192 0 1 1 874.048 512l-316.8 316.8A256 256 0 0 1 195.2 466.752L602.496 59.456l45.248 45.248L240.448 512A192 192 0 0 0 512 783.552l316.8-316.8a128 128 0 1 0-181.056-181.056L353.6 579.904a32 32 0 1 0 45.248 45.248l294.144-294.144 45.312 45.248L444.096 670.4a96 96 0 1 1-135.744-135.744z"})]))}}),One=Dne,$ne=q({name:"PartlyCloudy",__name:"partly-cloudy",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M598.4 895.872H328.192a256 256 0 0 1-34.496-510.528A352 352 0 1 1 598.4 895.872m-271.36-64h272.256a288 288 0 1 0-248.512-417.664L335.04 445.44l-34.816 3.584a192 192 0 0 0 26.88 382.848z"}),C("path",{fill:"currentColor",d:"M139.84 501.888a256 256 0 1 1 417.856-277.12c-17.728 2.176-38.208 8.448-61.504 18.816A192 192 0 1 0 189.12 460.48a6004 6004 0 0 0-49.28 41.408"})]))}}),Rne=$ne,Nne=q({name:"Pear",__name:"pear",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M542.336 258.816a443 443 0 0 0-9.024 25.088 32 32 0 1 1-60.8-20.032l1.088-3.328a162.69 162.69 0 0 0-122.048 131.392l-17.088 102.72-20.736 15.36C256.192 552.704 224 610.88 224 672c0 120.576 126.4 224 288 224s288-103.424 288-224c0-61.12-32.192-119.296-89.728-161.92l-20.736-15.424-17.088-102.72a162.69 162.69 0 0 0-130.112-133.12m-40.128-66.56c7.936-15.552 16.576-30.08 25.92-43.776 23.296-33.92 49.408-59.776 78.528-77.12a32 32 0 1 1 32.704 55.04c-20.544 12.224-40.064 31.552-58.432 58.304a317 317 0 0 0-9.792 15.104 226.69 226.69 0 0 1 164.48 181.568l12.8 77.248C819.456 511.36 864 587.392 864 672c0 159.04-157.568 288-352 288S160 831.04 160 672c0-84.608 44.608-160.64 115.584-213.376l12.8-77.248a226.624 226.624 0 0 1 213.76-189.184z"})]))}}),Bne=Nne,Vne=q({name:"PhoneFilled",__name:"phone-filled",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M199.232 125.568 90.624 379.008a32 32 0 0 0 6.784 35.2l512.384 512.384a32 32 0 0 0 35.2 6.784l253.44-108.608a32 32 0 0 0 10.048-52.032L769.6 633.92a32 32 0 0 0-36.928-5.952l-130.176 65.088-271.488-271.552 65.024-130.176a32 32 0 0 0-5.952-36.928L251.2 115.52a32 32 0 0 0-51.968 10.048"})]))}}),zne=Vne,Fne=q({name:"Phone",__name:"phone",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M79.36 432.256 591.744 944.64a32 32 0 0 0 35.2 6.784l253.44-108.544a32 32 0 0 0 9.984-52.032l-153.856-153.92a32 32 0 0 0-36.928-6.016l-69.888 34.944L358.08 394.24l35.008-69.888a32 32 0 0 0-5.952-36.928L233.152 133.568a32 32 0 0 0-52.032 10.048L72.512 397.056a32 32 0 0 0 6.784 35.2zm60.48-29.952 81.536-190.08L325.568 316.48l-24.64 49.216-20.608 41.216 32.576 32.64 271.552 271.552 32.64 32.64 41.216-20.672 49.28-24.576 104.192 104.128-190.08 81.472zM512 320v-64a256 256 0 0 1 256 256h-64a192 192 0 0 0-192-192m0-192V64a448 448 0 0 1 448 448h-64a384 384 0 0 0-384-384"})]))}}),Hne=Fne,Wne=q({name:"PictureFilled",__name:"picture-filled",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M96 896a32 32 0 0 1-32-32V160a32 32 0 0 1 32-32h832a32 32 0 0 1 32 32v704a32 32 0 0 1-32 32zm315.52-228.48-68.928-68.928a32 32 0 0 0-45.248 0L128 768.064h778.688l-242.112-290.56a32 32 0 0 0-49.216 0L458.752 665.408a32 32 0 0 1-47.232 2.112M256 384a96 96 0 1 0 192.064-.064A96 96 0 0 0 256 384"})]))}}),RI=Wne,Kne=q({name:"PictureRounded",__name:"picture-rounded",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 128a384 384 0 1 0 0 768 384 384 0 0 0 0-768m0-64a448 448 0 1 1 0 896 448 448 0 0 1 0-896"}),C("path",{fill:"currentColor",d:"M640 288q64 0 64 64t-64 64-64-64 64-64M214.656 790.656l-45.312-45.312 185.664-185.6a96 96 0 0 1 123.712-10.24l138.24 98.688a32 32 0 0 0 39.872-2.176L906.688 422.4l42.624 47.744L699.52 693.696a96 96 0 0 1-119.808 6.592l-138.24-98.752a32 32 0 0 0-41.152 3.456l-185.664 185.6z"})]))}}),Une=Kne,Gne=q({name:"Picture",__name:"picture",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M160 160v704h704V160zm-32-64h768a32 32 0 0 1 32 32v768a32 32 0 0 1-32 32H128a32 32 0 0 1-32-32V128a32 32 0 0 1 32-32"}),C("path",{fill:"currentColor",d:"M384 288q64 0 64 64t-64 64-64-64 64-64M185.408 876.992l-50.816-38.912L350.72 556.032a96 96 0 0 1 134.592-17.856l1.856 1.472 122.88 99.136a32 32 0 0 0 44.992-4.864l216-269.888 49.92 39.936-215.808 269.824-.256.32a96 96 0 0 1-135.04 14.464l-122.88-99.072-.64-.512a32 32 0 0 0-44.8 5.952z"})]))}}),NI=Gne,Yne=q({name:"PieChart",__name:"pie-chart",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M448 68.48v64.832A384.128 384.128 0 0 0 512 896a384.13 384.13 0 0 0 378.688-320h64.768A448.128 448.128 0 0 1 64 512 448.13 448.13 0 0 1 448 68.48"}),C("path",{fill:"currentColor",d:"M576 97.28V448h350.72A384.064 384.064 0 0 0 576 97.28M512 64V33.152A448 448 0 0 1 990.848 512H512z"})]))}}),jne=Yne,qne=q({name:"Place",__name:"place",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 512a192 192 0 1 0 0-384 192 192 0 0 0 0 384m0 64a256 256 0 1 1 0-512 256 256 0 0 1 0 512"}),C("path",{fill:"currentColor",d:"M512 512a32 32 0 0 1 32 32v256a32 32 0 1 1-64 0V544a32 32 0 0 1 32-32"}),C("path",{fill:"currentColor",d:"M384 649.088v64.96C269.76 732.352 192 771.904 192 800c0 37.696 139.904 96 320 96s320-58.304 320-96c0-28.16-77.76-67.648-192-85.952v-64.96C789.12 671.04 896 730.368 896 800c0 88.32-171.904 160-384 160s-384-71.68-384-160c0-69.696 106.88-128.96 256-150.912"})]))}}),Xne=qne,Zne=q({name:"Platform",__name:"platform",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M448 832v-64h128v64h192v64H256v-64zM128 704V128h768v576z"})]))}}),Jne=Zne,Qne=q({name:"Plus",__name:"plus",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M480 480V128a32 32 0 0 1 64 0v352h352a32 32 0 1 1 0 64H544v352a32 32 0 1 1-64 0V544H128a32 32 0 0 1 0-64z"})]))}}),hf=Qne,eae=q({name:"Pointer",__name:"pointer",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M511.552 128c-35.584 0-64.384 28.8-64.384 64.448v516.48L274.048 570.88a94.27 94.27 0 0 0-112.896-3.456 44.416 44.416 0 0 0-8.96 62.208L332.8 870.4A64 64 0 0 0 384 896h512V575.232a64 64 0 0 0-45.632-61.312l-205.952-61.76A96 96 0 0 1 576 360.192V192.448C576 156.8 547.2 128 511.552 128M359.04 556.8l24.128 19.2V192.448a128.448 128.448 0 1 1 256.832 0v167.744a32 32 0 0 0 22.784 30.656l206.016 61.76A128 128 0 0 1 960 575.232V896a64 64 0 0 1-64 64H384a128 128 0 0 1-102.4-51.2L101.056 668.032A108.416 108.416 0 0 1 128 512.512a158.27 158.27 0 0 1 185.984 8.32z"})]))}}),tae=eae,nae=q({name:"Position",__name:"position",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m249.6 417.088 319.744 43.072 39.168 310.272L845.12 178.88zm-129.024 47.168a32 32 0 0 1-7.68-61.44l777.792-311.04a32 32 0 0 1 41.6 41.6l-310.336 775.68a32 32 0 0 1-61.44-7.808L512 516.992z"})]))}}),aae=nae,rae=q({name:"Postcard",__name:"postcard",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M160 224a32 32 0 0 0-32 32v512a32 32 0 0 0 32 32h704a32 32 0 0 0 32-32V256a32 32 0 0 0-32-32zm0-64h704a96 96 0 0 1 96 96v512a96 96 0 0 1-96 96H160a96 96 0 0 1-96-96V256a96 96 0 0 1 96-96"}),C("path",{fill:"currentColor",d:"M704 320a64 64 0 1 1 0 128 64 64 0 0 1 0-128M288 448h256q32 0 32 32t-32 32H288q-32 0-32-32t32-32m0 128h256q32 0 32 32t-32 32H288q-32 0-32-32t32-32"})]))}}),oae=rae,iae=q({name:"Pouring",__name:"pouring",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m739.328 291.328-35.2-6.592-12.8-33.408a192.064 192.064 0 0 0-365.952 23.232l-9.92 40.896-41.472 7.04a176.32 176.32 0 0 0-146.24 173.568c0 97.28 78.72 175.936 175.808 175.936h400a192 192 0 0 0 35.776-380.672M959.552 480a256 256 0 0 1-256 256h-400A239.81 239.81 0 0 1 63.744 496.192a240.32 240.32 0 0 1 199.488-236.8 256.128 256.128 0 0 1 487.872-30.976A256.064 256.064 0 0 1 959.552 480M224 800a32 32 0 0 1 32 32v96a32 32 0 1 1-64 0v-96a32 32 0 0 1 32-32m192 0a32 32 0 0 1 32 32v96a32 32 0 1 1-64 0v-96a32 32 0 0 1 32-32m192 0a32 32 0 0 1 32 32v96a32 32 0 1 1-64 0v-96a32 32 0 0 1 32-32m192 0a32 32 0 0 1 32 32v96a32 32 0 1 1-64 0v-96a32 32 0 0 1 32-32"})]))}}),lae=iae,sae=q({name:"Present",__name:"present",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M480 896V640H192v-64h288V320H192v576zm64 0h288V320H544v256h288v64H544zM128 256h768v672a32 32 0 0 1-32 32H160a32 32 0 0 1-32-32z"}),C("path",{fill:"currentColor",d:"M96 256h832q32 0 32 32t-32 32H96q-32 0-32-32t32-32"}),C("path",{fill:"currentColor",d:"M416 256a64 64 0 1 0 0-128 64 64 0 0 0 0 128m0 64a128 128 0 1 1 0-256 128 128 0 0 1 0 256"}),C("path",{fill:"currentColor",d:"M608 256a64 64 0 1 0 0-128 64 64 0 0 0 0 128m0 64a128 128 0 1 1 0-256 128 128 0 0 1 0 256"})]))}}),uae=sae,cae=q({name:"PriceTag",__name:"price-tag",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M224 318.336V896h576V318.336L552.512 115.84a64 64 0 0 0-81.024 0zM593.024 66.304l259.2 212.096A32 32 0 0 1 864 303.168V928a32 32 0 0 1-32 32H192a32 32 0 0 1-32-32V303.168a32 32 0 0 1 11.712-24.768l259.2-212.096a128 128 0 0 1 162.112 0"}),C("path",{fill:"currentColor",d:"M512 448a64 64 0 1 0 0-128 64 64 0 0 0 0 128m0 64a128 128 0 1 1 0-256 128 128 0 0 1 0 256"})]))}}),dae=cae,fae=q({name:"Printer",__name:"printer",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M256 768H105.024c-14.272 0-19.456-1.472-24.64-4.288a29.06 29.06 0 0 1-12.16-12.096C65.536 746.432 64 741.248 64 727.04V379.072c0-42.816 4.48-58.304 12.8-73.984 8.384-15.616 20.672-27.904 36.288-36.288 15.68-8.32 31.168-12.8 73.984-12.8H256V64h512v192h68.928c42.816 0 58.304 4.48 73.984 12.8 15.616 8.384 27.904 20.672 36.288 36.288 8.32 15.68 12.8 31.168 12.8 73.984v347.904c0 14.272-1.472 19.456-4.288 24.64a29.06 29.06 0 0 1-12.096 12.16c-5.184 2.752-10.368 4.224-24.64 4.224H768v192H256zm64-192v320h384V576zm-64 128V512h512v192h128V379.072c0-29.376-1.408-36.48-5.248-43.776a23.3 23.3 0 0 0-10.048-10.048c-7.232-3.84-14.4-5.248-43.776-5.248H187.072c-29.376 0-36.48 1.408-43.776 5.248a23.3 23.3 0 0 0-10.048 10.048c-3.84 7.232-5.248 14.4-5.248 43.776V704zm64-448h384V128H320zm-64 128h64v64h-64zm128 0h64v64h-64z"})]))}}),vae=fae,hae=q({name:"Promotion",__name:"promotion",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m64 448 832-320-128 704-446.08-243.328L832 192 242.816 545.472zm256 512V657.024L512 768z"})]))}}),pae=hae,mae=q({name:"QuartzWatch",__name:"quartz-watch",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg","xml:space":"preserve",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M422.02 602.01v-.03c-6.68-5.99-14.35-8.83-23.01-8.51q-13.005.48-22.5 10.02c-6.33 6.36-9.5 13.7-9.5 22.02s3 15.82 8.99 22.5c8.68 8.68 19.02 11.35 31.01 8s19.49-10.85 22.5-22.5.51-22.15-7.49-31.49zM384 512c0-9.35-3-17.02-8.99-23.01-6-5.99-13.66-8.99-23.01-8.99s-17.02 3-23.01 8.99c-5.99 6-8.99 13.66-8.99 23.01s3 17.02 8.99 23.01c6 5.99 13.66 8.99 23.01 8.99s17.02-3 23.01-8.99c5.99-6 8.99-13.67 8.99-23.01m6.53-82.49c11.65 3.01 22.15.51 31.49-7.49h.04c5.99-6.68 8.83-14.34 8.51-23.01s-3.66-16.16-10.02-22.5c-6.36-6.33-13.7-9.5-22.02-9.5s-15.82 3-22.5 8.99c-8.68 8.69-11.35 19.02-8 31.01q5.025 17.985 22.5 22.5m242.94 0q17.505-4.545 22.02-22.02c3.01-11.65.51-22.15-7.49-31.49h.01c-6.68-5.99-14.18-8.99-22.5-8.99s-15.66 3.16-22.02 9.5q-9.54 9.51-10.02 22.5c-.32 8.66 2.52 16.33 8.51 23.01 9.32 8.02 19.82 10.52 31.49 7.49M512 640c-9.35 0-17.02 3-23.01 8.99-5.99 6-8.99 13.66-8.99 23.01s3 17.02 8.99 23.01c6 5.99 13.67 8.99 23.01 8.99 9.35 0 17.02-3 23.01-8.99 5.99-6 8.99-13.66 8.99-23.01s-3-17.02-8.99-23.01c-6-5.99-13.66-8.99-23.01-8.99m183.01-151.01c-6-5.99-13.66-8.99-23.01-8.99s-17.02 3-23.01 8.99c-5.99 6-8.99 13.66-8.99 23.01s3 17.02 8.99 23.01c6 5.99 13.66 8.99 23.01 8.99s17.02-3 23.01-8.99c5.99-6 8.99-13.67 8.99-23.01 0-9.35-3-17.02-8.99-23.01"}),C("path",{fill:"currentColor",d:"M832 512c-2-90.67-33.17-166.17-93.5-226.5-20.43-20.42-42.6-37.49-66.5-51.23V64H352v170.26c-23.9 13.74-46.07 30.81-66.5 51.24-60.33 60.33-91.49 135.83-93.5 226.5 2 90.67 33.17 166.17 93.5 226.5 20.43 20.43 42.6 37.5 66.5 51.24V960h320V789.74c23.9-13.74 46.07-30.81 66.5-51.24 60.33-60.34 91.49-135.83 93.5-226.5M416 128h192v78.69c-29.85-9.03-61.85-13.93-96-14.69-34.15.75-66.15 5.65-96 14.68zm192 768H416v-78.68c29.85 9.03 61.85 13.93 96 14.68 34.15-.75 66.15-5.65 96-14.68zm-96-128c-72.66-2.01-132.99-27.01-180.99-75.01S258.01 584.66 256 512c2.01-72.66 27.01-132.99 75.01-180.99S439.34 258.01 512 256c72.66 2.01 132.99 27.01 180.99 75.01S765.99 439.34 768 512c-2.01 72.66-27.01 132.99-75.01 180.99S584.66 765.99 512 768"}),C("path",{fill:"currentColor",d:"M512 320c-9.35 0-17.02 3-23.01 8.99-5.99 6-8.99 13.66-8.99 23.01s3 17.02 8.99 23.01c6 5.99 13.67 8.99 23.01 8.99 9.35 0 17.02-3 23.01-8.99 5.99-6 8.99-13.66 8.99-23.01s-3-17.02-8.99-23.01c-6-5.99-13.66-8.99-23.01-8.99m112.99 273.5c-8.66-.32-16.33 2.52-23.01 8.51-7.98 9.32-10.48 19.82-7.49 31.49s10.49 19.17 22.5 22.5 22.35.66 31.01-8v.04c5.99-6.68 8.99-14.18 8.99-22.5s-3.16-15.66-9.5-22.02-13.84-9.7-22.5-10.02"})]))}}),gae=mae,yae=q({name:"QuestionFilled",__name:"question-filled",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 64a448 448 0 1 1 0 896 448 448 0 0 1 0-896m23.744 191.488c-52.096 0-92.928 14.784-123.2 44.352-30.976 29.568-45.76 70.4-45.76 122.496h80.256c0-29.568 5.632-52.8 17.6-68.992 13.376-19.712 35.2-28.864 66.176-28.864 23.936 0 42.944 6.336 56.32 19.712 12.672 13.376 19.712 31.68 19.712 54.912 0 17.6-6.336 34.496-19.008 49.984l-8.448 9.856c-45.76 40.832-73.216 70.4-82.368 89.408-9.856 19.008-14.08 42.24-14.08 68.992v9.856h80.96v-9.856c0-16.896 3.52-31.68 10.56-45.76 6.336-12.672 15.488-24.64 28.16-35.2 33.792-29.568 54.208-48.576 60.544-55.616 16.896-22.528 26.048-51.392 26.048-86.592q0-64.416-42.24-101.376c-28.16-25.344-65.472-37.312-111.232-37.312m-12.672 406.208a54.27 54.27 0 0 0-38.72 14.784 49.4 49.4 0 0 0-15.488 38.016c0 15.488 4.928 28.16 15.488 38.016A54.85 54.85 0 0 0 523.072 768c15.488 0 28.16-4.928 38.72-14.784a51.52 51.52 0 0 0 16.192-38.72 51.97 51.97 0 0 0-15.488-38.016 55.94 55.94 0 0 0-39.424-14.784"})]))}}),BI=yae,bae=q({name:"Rank",__name:"rank",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m186.496 544 41.408 41.344a32 32 0 1 1-45.248 45.312l-96-96a32 32 0 0 1 0-45.312l96-96a32 32 0 1 1 45.248 45.312L186.496 480h290.816V186.432l-41.472 41.472a32 32 0 1 1-45.248-45.184l96-96.128a32 32 0 0 1 45.312 0l96 96.064a32 32 0 0 1-45.248 45.184l-41.344-41.28V480H832l-41.344-41.344a32 32 0 0 1 45.248-45.312l96 96a32 32 0 0 1 0 45.312l-96 96a32 32 0 0 1-45.248-45.312L832 544H541.312v293.44l41.344-41.28a32 32 0 1 1 45.248 45.248l-96 96a32 32 0 0 1-45.312 0l-96-96a32 32 0 1 1 45.312-45.248l41.408 41.408V544z"})]))}}),_ae=bae,wae=q({name:"ReadingLamp",__name:"reading-lamp",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M352 896h320q32 0 32 32t-32 32H352q-32 0-32-32t32-32m-44.672-768-99.52 448h608.384l-99.52-448zm-25.6-64h460.608a32 32 0 0 1 31.232 25.088l113.792 512A32 32 0 0 1 856.128 640H167.872a32 32 0 0 1-31.232-38.912l113.792-512A32 32 0 0 1 281.664 64z"}),C("path",{fill:"currentColor",d:"M672 576q32 0 32 32v128q0 32-32 32t-32-32V608q0-32 32-32m-192-.064h64V960h-64z"})]))}}),Sae=wae,Cae=q({name:"Reading",__name:"reading",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m512 863.36 384-54.848v-638.72L525.568 222.72a96 96 0 0 1-27.136 0L128 169.792v638.72zM137.024 106.432l370.432 52.928a32 32 0 0 0 9.088 0l370.432-52.928A64 64 0 0 1 960 169.792v638.72a64 64 0 0 1-54.976 63.36l-388.48 55.488a32 32 0 0 1-9.088 0l-388.48-55.488A64 64 0 0 1 64 808.512v-638.72a64 64 0 0 1 73.024-63.36"}),C("path",{fill:"currentColor",d:"M480 192h64v704h-64z"})]))}}),xae=Cae,kae=q({name:"RefreshLeft",__name:"refresh-left",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M289.088 296.704h92.992a32 32 0 0 1 0 64H232.96a32 32 0 0 1-32-32V179.712a32 32 0 0 1 64 0v50.56a384 384 0 0 1 643.84 282.88 384 384 0 0 1-383.936 384 384 384 0 0 1-384-384h64a320 320 0 1 0 640 0 320 320 0 0 0-555.712-216.448z"})]))}}),VI=kae,Tae=q({name:"RefreshRight",__name:"refresh-right",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M784.512 230.272v-50.56a32 32 0 1 1 64 0v149.056a32 32 0 0 1-32 32H667.52a32 32 0 1 1 0-64h92.992A320 320 0 1 0 524.8 833.152a320 320 0 0 0 320-320h64a384 384 0 0 1-384 384 384 384 0 0 1-384-384 384 384 0 0 1 643.712-282.88"})]))}}),zI=Tae,Mae=q({name:"Refresh",__name:"refresh",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M771.776 794.88A384 384 0 0 1 128 512h64a320 320 0 0 0 555.712 216.448H654.72a32 32 0 1 1 0-64h149.056a32 32 0 0 1 32 32v148.928a32 32 0 1 1-64 0v-50.56zM276.288 295.616h92.992a32 32 0 0 1 0 64H220.16a32 32 0 0 1-32-32V178.56a32 32 0 0 1 64 0v50.56A384 384 0 0 1 896.128 512h-64a320 320 0 0 0-555.776-216.384z"})]))}}),g4=Mae,Eae=q({name:"Refrigerator",__name:"refrigerator",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M256 448h512V160a32 32 0 0 0-32-32H288a32 32 0 0 0-32 32zm0 64v352a32 32 0 0 0 32 32h448a32 32 0 0 0 32-32V512zm32-448h448a96 96 0 0 1 96 96v704a96 96 0 0 1-96 96H288a96 96 0 0 1-96-96V160a96 96 0 0 1 96-96m32 224h64v96h-64zm0 288h64v96h-64z"})]))}}),Aae=Eae,Iae=q({name:"RemoveFilled",__name:"remove-filled",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 64a448 448 0 1 1 0 896 448 448 0 0 1 0-896M288 512a38.4 38.4 0 0 0 38.4 38.4h371.2a38.4 38.4 0 0 0 0-76.8H326.4A38.4 38.4 0 0 0 288 512"})]))}}),Lae=Iae,Pae=q({name:"Remove",__name:"remove",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M352 480h320a32 32 0 1 1 0 64H352a32 32 0 0 1 0-64"}),C("path",{fill:"currentColor",d:"M512 896a384 384 0 1 0 0-768 384 384 0 0 0 0 768m0 64a448 448 0 1 1 0-896 448 448 0 0 1 0 896"})]))}}),Dae=Pae,Oae=q({name:"Right",__name:"right",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M754.752 480H160a32 32 0 1 0 0 64h594.752L521.344 777.344a32 32 0 0 0 45.312 45.312l288-288a32 32 0 0 0 0-45.312l-288-288a32 32 0 1 0-45.312 45.312z"})]))}}),$ae=Oae,Rae=q({name:"ScaleToOriginal",__name:"scale-to-original",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M813.176 180.706a60.235 60.235 0 0 1 60.236 60.235v481.883a60.235 60.235 0 0 1-60.236 60.235H210.824a60.235 60.235 0 0 1-60.236-60.235V240.94a60.235 60.235 0 0 1 60.236-60.235h602.352zm0-60.235H210.824A120.47 120.47 0 0 0 90.353 240.94v481.883a120.47 120.47 0 0 0 120.47 120.47h602.353a120.47 120.47 0 0 0 120.471-120.47V240.94a120.47 120.47 0 0 0-120.47-120.47zm-120.47 180.705a30.12 30.12 0 0 0-30.118 30.118v301.177a30.118 30.118 0 0 0 60.236 0V331.294a30.12 30.12 0 0 0-30.118-30.118m-361.412 0a30.12 30.12 0 0 0-30.118 30.118v301.177a30.118 30.118 0 1 0 60.236 0V331.294a30.12 30.12 0 0 0-30.118-30.118M512 361.412a30.12 30.12 0 0 0-30.118 30.117v30.118a30.118 30.118 0 0 0 60.236 0V391.53A30.12 30.12 0 0 0 512 361.412M512 512a30.12 30.12 0 0 0-30.118 30.118v30.117a30.118 30.118 0 0 0 60.236 0v-30.117A30.12 30.12 0 0 0 512 512"})]))}}),FI=Rae,Nae=q({name:"School",__name:"school",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M224 128v704h576V128zm-32-64h640a32 32 0 0 1 32 32v768a32 32 0 0 1-32 32H192a32 32 0 0 1-32-32V96a32 32 0 0 1 32-32"}),C("path",{fill:"currentColor",d:"M64 832h896v64H64zm256-640h128v96H320z"}),C("path",{fill:"currentColor",d:"M384 832h256v-64a128 128 0 1 0-256 0zm128-256a192 192 0 0 1 192 192v128H320V768a192 192 0 0 1 192-192M320 384h128v96H320zm256-192h128v96H576zm0 192h128v96H576z"})]))}}),Bae=Nae,Vae=q({name:"Scissor",__name:"scissor",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m512.064 578.368-106.88 152.768a160 160 0 1 1-23.36-78.208L472.96 522.56 196.864 128.256a32 32 0 1 1 52.48-36.736l393.024 561.344a160 160 0 1 1-23.36 78.208l-106.88-152.704zm54.4-189.248 208.384-297.6a32 32 0 0 1 52.48 36.736l-221.76 316.672-39.04-55.808zm-376.32 425.856a96 96 0 1 0 110.144-157.248 96 96 0 0 0-110.08 157.248zm643.84 0a96 96 0 1 0-110.08-157.248 96 96 0 0 0 110.08 157.248"})]))}}),zae=Vae,Fae=q({name:"Search",__name:"search",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m795.904 750.72 124.992 124.928a32 32 0 0 1-45.248 45.248L750.656 795.904a416 416 0 1 1 45.248-45.248zM480 832a352 352 0 1 0 0-704 352 352 0 0 0 0 704"})]))}}),HI=Fae,Hae=q({name:"Select",__name:"select",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M77.248 415.04a64 64 0 0 1 90.496 0l226.304 226.304L846.528 188.8a64 64 0 1 1 90.56 90.496l-543.04 543.04-316.8-316.8a64 64 0 0 1 0-90.496"})]))}}),Wae=Hae,Kae=q({name:"Sell",__name:"sell",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M704 288h131.072a32 32 0 0 1 31.808 28.8L886.4 512h-64.384l-16-160H704v96a32 32 0 1 1-64 0v-96H384v96a32 32 0 0 1-64 0v-96H217.92l-51.2 512H512v64H131.328a32 32 0 0 1-31.808-35.2l57.6-576a32 32 0 0 1 31.808-28.8H320v-22.336C320 154.688 405.504 64 512 64s192 90.688 192 201.664v22.4zm-64 0v-22.336C640 189.248 582.272 128 512 128s-128 61.248-128 137.664v22.4h256zm201.408 483.84L768 698.496V928a32 32 0 1 1-64 0V698.496l-73.344 73.344a32 32 0 1 1-45.248-45.248l128-128a32 32 0 0 1 45.248 0l128 128a32 32 0 1 1-45.248 45.248"})]))}}),Uae=Kae,Gae=q({name:"SemiSelect",__name:"semi-select",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M128 448h768q64 0 64 64t-64 64H128q-64 0-64-64t64-64"})]))}}),Yae=Gae,jae=q({name:"Service",__name:"service",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M864 409.6a192 192 0 0 1-37.888 349.44A256.064 256.064 0 0 1 576 960h-96a32 32 0 1 1 0-64h96a192.06 192.06 0 0 0 181.12-128H736a32 32 0 0 1-32-32V416a32 32 0 0 1 32-32h32c10.368 0 20.544.832 30.528 2.432a288 288 0 0 0-573.056 0A193 193 0 0 1 256 384h32a32 32 0 0 1 32 32v320a32 32 0 0 1-32 32h-32a192 192 0 0 1-96-358.4 352 352 0 0 1 704 0M256 448a128 128 0 1 0 0 256zm640 128a128 128 0 0 0-128-128v256a128 128 0 0 0 128-128"})]))}}),qae=jae,Xae=q({name:"SetUp",__name:"set-up",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M224 160a64 64 0 0 0-64 64v576a64 64 0 0 0 64 64h576a64 64 0 0 0 64-64V224a64 64 0 0 0-64-64zm0-64h576a128 128 0 0 1 128 128v576a128 128 0 0 1-128 128H224A128 128 0 0 1 96 800V224A128 128 0 0 1 224 96"}),C("path",{fill:"currentColor",d:"M384 416a64 64 0 1 0 0-128 64 64 0 0 0 0 128m0 64a128 128 0 1 1 0-256 128 128 0 0 1 0 256"}),C("path",{fill:"currentColor",d:"M480 320h256q32 0 32 32t-32 32H480q-32 0-32-32t32-32m160 416a64 64 0 1 0 0-128 64 64 0 0 0 0 128m0 64a128 128 0 1 1 0-256 128 128 0 0 1 0 256"}),C("path",{fill:"currentColor",d:"M288 640h256q32 0 32 32t-32 32H288q-32 0-32-32t32-32"})]))}}),Zae=Xae,Jae=q({name:"Setting",__name:"setting",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M600.704 64a32 32 0 0 1 30.464 22.208l35.2 109.376c14.784 7.232 28.928 15.36 42.432 24.512l112.384-24.192a32 32 0 0 1 34.432 15.36L944.32 364.8a32 32 0 0 1-4.032 37.504l-77.12 85.12a357 357 0 0 1 0 49.024l77.12 85.248a32 32 0 0 1 4.032 37.504l-88.704 153.6a32 32 0 0 1-34.432 15.296L708.8 803.904c-13.44 9.088-27.648 17.28-42.368 24.512l-35.264 109.376A32 32 0 0 1 600.704 960H423.296a32 32 0 0 1-30.464-22.208L357.696 828.48a352 352 0 0 1-42.56-24.64l-112.32 24.256a32 32 0 0 1-34.432-15.36L79.68 659.2a32 32 0 0 1 4.032-37.504l77.12-85.248a357 357 0 0 1 0-48.896l-77.12-85.248A32 32 0 0 1 79.68 364.8l88.704-153.6a32 32 0 0 1 34.432-15.296l112.32 24.256c13.568-9.152 27.776-17.408 42.56-24.64l35.2-109.312A32 32 0 0 1 423.232 64H600.64zm-23.424 64H446.72l-36.352 113.088-24.512 11.968a294 294 0 0 0-34.816 20.096l-22.656 15.36-116.224-25.088-65.28 113.152 79.68 88.192-1.92 27.136a293 293 0 0 0 0 40.192l1.92 27.136-79.808 88.192 65.344 113.152 116.224-25.024 22.656 15.296a294 294 0 0 0 34.816 20.096l24.512 11.968L446.72 896h130.688l36.48-113.152 24.448-11.904a288 288 0 0 0 34.752-20.096l22.592-15.296 116.288 25.024 65.28-113.152-79.744-88.192 1.92-27.136a293 293 0 0 0 0-40.256l-1.92-27.136 79.808-88.128-65.344-113.152-116.288 24.96-22.592-15.232a288 288 0 0 0-34.752-20.096l-24.448-11.904L577.344 128zM512 320a192 192 0 1 1 0 384 192 192 0 0 1 0-384m0 64a128 128 0 1 0 0 256 128 128 0 0 0 0-256"})]))}}),Qae=Jae,ere=q({name:"Share",__name:"share",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m679.872 348.8-301.76 188.608a127.8 127.8 0 0 1 5.12 52.16l279.936 104.96a128 128 0 1 1-22.464 59.904l-279.872-104.96a128 128 0 1 1-16.64-166.272l301.696-188.608a128 128 0 1 1 33.92 54.272z"})]))}}),tre=ere,nre=q({name:"Ship",__name:"ship",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 386.88V448h405.568a32 32 0 0 1 30.72 40.768l-76.48 267.968A192 192 0 0 1 687.168 896H336.832a192 192 0 0 1-184.64-139.264L75.648 488.768A32 32 0 0 1 106.368 448H448V117.888a32 32 0 0 1 47.36-28.096l13.888 7.616L512 96v2.88l231.68 126.4a32 32 0 0 1-2.048 57.216zm0-70.272 144.768-65.792L512 171.84zM512 512H148.864l18.24 64H856.96l18.24-64zM185.408 640l28.352 99.2A128 128 0 0 0 336.832 832h350.336a128 128 0 0 0 123.072-92.8l28.352-99.2z"})]))}}),are=nre,rre=q({name:"Shop",__name:"shop",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M704 704h64v192H256V704h64v64h384zm188.544-152.192C894.528 559.616 896 567.616 896 576a96 96 0 1 1-192 0 96 96 0 1 1-192 0 96 96 0 1 1-192 0 96 96 0 1 1-192 0c0-8.384 1.408-16.384 3.392-24.192L192 128h640z"})]))}}),ore=rre,ire=q({name:"ShoppingBag",__name:"shopping-bag",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M704 320v96a32 32 0 0 1-32 32h-32V320H384v128h-32a32 32 0 0 1-32-32v-96H192v576h640V320zm-384-64a192 192 0 1 1 384 0h160a32 32 0 0 1 32 32v640a32 32 0 0 1-32 32H160a32 32 0 0 1-32-32V288a32 32 0 0 1 32-32zm64 0h256a128 128 0 1 0-256 0"}),C("path",{fill:"currentColor",d:"M192 704h640v64H192z"})]))}}),lre=ire,sre=q({name:"ShoppingCartFull",__name:"shopping-cart-full",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M432 928a48 48 0 1 1 0-96 48 48 0 0 1 0 96m320 0a48 48 0 1 1 0-96 48 48 0 0 1 0 96M96 128a32 32 0 0 1 0-64h160a32 32 0 0 1 31.36 25.728L320.64 256H928a32 32 0 0 1 31.296 38.72l-96 448A32 32 0 0 1 832 768H384a32 32 0 0 1-31.36-25.728L229.76 128zm314.24 576h395.904l82.304-384H333.44z"}),C("path",{fill:"currentColor",d:"M699.648 256 608 145.984 516.352 256zm-140.8-151.04a64 64 0 0 1 98.304 0L836.352 320H379.648z"})]))}}),ure=sre,cre=q({name:"ShoppingCart",__name:"shopping-cart",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M432 928a48 48 0 1 1 0-96 48 48 0 0 1 0 96m320 0a48 48 0 1 1 0-96 48 48 0 0 1 0 96M96 128a32 32 0 0 1 0-64h160a32 32 0 0 1 31.36 25.728L320.64 256H928a32 32 0 0 1 31.296 38.72l-96 448A32 32 0 0 1 832 768H384a32 32 0 0 1-31.36-25.728L229.76 128zm314.24 576h395.904l82.304-384H333.44z"})]))}}),dre=cre,fre=q({name:"ShoppingTrolley",__name:"shopping-trolley",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg","xml:space":"preserve",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M368 833c-13.3 0-24.5 4.5-33.5 13.5S321 866.7 321 880s4.5 24.5 13.5 33.5 20.2 13.8 33.5 14.5c13.3-.7 24.5-5.5 33.5-14.5S415 893.3 415 880s-4.5-24.5-13.5-33.5S381.3 833 368 833m439-193c7.4 0 13.8-2.2 19.5-6.5S836 623.3 838 616l112-448c2-10-.2-19.2-6.5-27.5S929 128 919 128H96c-9.3 0-17 3-23 9s-9 13.7-9 23 3 17 9 23 13.7 9 23 9h96v576h672c9.3 0 17-3 23-9s9-13.7 9-23-3-17-9-23-13.7-9-23-9H256v-64zM256 192h622l-96 384H256zm432 641c-13.3 0-24.5 4.5-33.5 13.5S641 866.7 641 880s4.5 24.5 13.5 33.5 20.2 13.8 33.5 14.5c13.3-.7 24.5-5.5 33.5-14.5S735 893.3 735 880s-4.5-24.5-13.5-33.5S701.3 833 688 833"})]))}}),vre=fre,hre=q({name:"Smoking",__name:"smoking",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M256 576v128h640V576zm-32-64h704a32 32 0 0 1 32 32v192a32 32 0 0 1-32 32H224a32 32 0 0 1-32-32V544a32 32 0 0 1 32-32"}),C("path",{fill:"currentColor",d:"M704 576h64v128h-64zM256 64h64v320h-64zM128 192h64v192h-64zM64 512h64v256H64z"})]))}}),pre=hre,mre=q({name:"Soccer",__name:"soccer",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M418.496 871.04 152.256 604.8c-16.512 94.016-2.368 178.624 42.944 224 44.928 44.928 129.344 58.752 223.296 42.24m72.32-18.176a573.06 573.06 0 0 0 224.832-137.216 573.1 573.1 0 0 0 137.216-224.832L533.888 171.84a578.56 578.56 0 0 0-227.52 138.496A567.7 567.7 0 0 0 170.432 532.48zM871.04 418.496c16.512-93.952 2.688-178.368-42.24-223.296-44.544-44.544-128.704-58.048-222.592-41.536zM149.952 874.048c-112.96-112.96-88.832-408.96 111.168-608.96C461.056 65.152 760.96 36.928 874.048 149.952c113.024 113.024 86.784 411.008-113.152 610.944s-497.92 226.112-610.944 113.152m452.544-497.792 22.656-22.656a32 32 0 0 1 45.248 45.248l-22.656 22.656 45.248 45.248A32 32 0 1 1 647.744 512l-45.248-45.248L557.248 512l45.248 45.248a32 32 0 1 1-45.248 45.248L512 557.248l-45.248 45.248L512 647.744a32 32 0 1 1-45.248 45.248l-45.248-45.248-22.656 22.656a32 32 0 1 1-45.248-45.248l22.656-22.656-45.248-45.248A32 32 0 1 1 376.256 512l45.248 45.248L466.752 512l-45.248-45.248a32 32 0 1 1 45.248-45.248L512 466.752l45.248-45.248L512 376.256a32 32 0 0 1 45.248-45.248z"})]))}}),gre=mre,yre=q({name:"SoldOut",__name:"sold-out",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M704 288h131.072a32 32 0 0 1 31.808 28.8L886.4 512h-64.384l-16-160H704v96a32 32 0 1 1-64 0v-96H384v96a32 32 0 0 1-64 0v-96H217.92l-51.2 512H512v64H131.328a32 32 0 0 1-31.808-35.2l57.6-576a32 32 0 0 1 31.808-28.8H320v-22.336C320 154.688 405.504 64 512 64s192 90.688 192 201.664v22.4zm-64 0v-22.336C640 189.248 582.272 128 512 128s-128 61.248-128 137.664v22.4h256zm201.408 476.16a32 32 0 1 1 45.248 45.184l-128 128a32 32 0 0 1-45.248 0l-128-128a32 32 0 1 1 45.248-45.248L704 837.504V608a32 32 0 1 1 64 0v229.504l73.408-73.408z"})]))}}),bre=yre,_re=q({name:"SortDown",__name:"sort-down",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M576 96v709.568L333.312 562.816A32 32 0 1 0 288 608l297.408 297.344A32 32 0 0 0 640 882.688V96a32 32 0 0 0-64 0"})]))}}),WI=_re,wre=q({name:"SortUp",__name:"sort-up",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M384 141.248V928a32 32 0 1 0 64 0V218.56l242.688 242.688A32 32 0 1 0 736 416L438.592 118.656A32 32 0 0 0 384 141.248"})]))}}),KI=wre,Sre=q({name:"Sort",__name:"sort",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M384 96a32 32 0 0 1 64 0v786.752a32 32 0 0 1-54.592 22.656L95.936 608a32 32 0 0 1 0-45.312h.128a32 32 0 0 1 45.184 0L384 805.632zm192 45.248a32 32 0 0 1 54.592-22.592L928.064 416a32 32 0 0 1 0 45.312h-.128a32 32 0 0 1-45.184 0L640 218.496V928a32 32 0 1 1-64 0z"})]))}}),Cre=Sre,xre=q({name:"Stamp",__name:"stamp",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M624 475.968V640h144a128 128 0 0 1 128 128H128a128 128 0 0 1 128-128h144V475.968a192 192 0 1 1 224 0M128 896v-64h768v64z"})]))}}),kre=xre,Tre=q({name:"StarFilled",__name:"star-filled",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M313.6 924.48a70.4 70.4 0 0 1-74.152-5.365 70.4 70.4 0 0 1-27.992-68.875l37.888-220.928L88.96 472.96a70.4 70.4 0 0 1 3.788-104.225A70.4 70.4 0 0 1 128 352.896l221.76-32.256 99.2-200.96a70.4 70.4 0 0 1 100.246-28.595 70.4 70.4 0 0 1 25.962 28.595l99.2 200.96 221.824 32.256a70.4 70.4 0 0 1 39.04 120.064L774.72 629.376l37.888 220.928a70.4 70.4 0 0 1-102.144 74.24L512 820.096l-198.4 104.32z"})]))}}),rv=Tre,Mre=q({name:"Star",__name:"star",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m512 747.84 228.16 119.936a6.4 6.4 0 0 0 9.28-6.72l-43.52-254.08 184.512-179.904a6.4 6.4 0 0 0-3.52-10.88l-255.104-37.12L517.76 147.904a6.4 6.4 0 0 0-11.52 0L392.192 379.072l-255.104 37.12a6.4 6.4 0 0 0-3.52 10.88L318.08 606.976l-43.584 254.08a6.4 6.4 0 0 0 9.28 6.72zM313.6 924.48a70.4 70.4 0 0 1-102.144-74.24l37.888-220.928L88.96 472.96A70.4 70.4 0 0 1 128 352.896l221.76-32.256 99.2-200.96a70.4 70.4 0 0 1 126.208 0l99.2 200.96 221.824 32.256a70.4 70.4 0 0 1 39.04 120.064L774.72 629.376l37.888 220.928a70.4 70.4 0 0 1-102.144 74.24L512 820.096l-198.4 104.32z"})]))}}),UI=Mre,Ere=q({name:"Stopwatch",__name:"stopwatch",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 896a384 384 0 1 0 0-768 384 384 0 0 0 0 768m0 64a448 448 0 1 1 0-896 448 448 0 0 1 0 896"}),C("path",{fill:"currentColor",d:"M672 234.88c-39.168 174.464-80 298.624-122.688 372.48-64 110.848-202.624 30.848-138.624-80C453.376 453.44 540.48 355.968 672 234.816z"})]))}}),Are=Ere,Ire=q({name:"SuccessFilled",__name:"success-filled",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 64a448 448 0 1 1 0 896 448 448 0 0 1 0-896m-55.808 536.384-99.52-99.584a38.4 38.4 0 1 0-54.336 54.336l126.72 126.72a38.27 38.27 0 0 0 54.336 0l262.4-262.464a38.4 38.4 0 1 0-54.272-54.336z"})]))}}),K6=Ire,Lre=q({name:"Sugar",__name:"sugar",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m801.728 349.184 4.48 4.48a128 128 0 0 1 0 180.992L534.656 806.144a128 128 0 0 1-181.056 0l-4.48-4.48-19.392 109.696a64 64 0 0 1-108.288 34.176L78.464 802.56a64 64 0 0 1 34.176-108.288l109.76-19.328-4.544-4.544a128 128 0 0 1 0-181.056l271.488-271.488a128 128 0 0 1 181.056 0l4.48 4.48 19.392-109.504a64 64 0 0 1 108.352-34.048l142.592 143.04a64 64 0 0 1-34.24 108.16zm-548.8 198.72h447.168v2.24l60.8-60.8a63.8 63.8 0 0 0 18.752-44.416h-426.88l-89.664 89.728a64 64 0 0 0-10.24 13.248zm0 64q4.128 7.104 10.176 13.248l135.744 135.744a64 64 0 0 0 90.496 0L638.4 611.904zm490.048-230.976L625.152 263.104a64 64 0 0 0-90.496 0L416.768 380.928zM123.712 757.312l142.976 142.976 24.32-137.6a25.6 25.6 0 0 0-29.696-29.632zm633.6-633.344-24.32 137.472a25.6 25.6 0 0 0 29.632 29.632l137.28-24.064-142.656-143.04z"})]))}}),Pre=Lre,Dre=q({name:"SuitcaseLine",__name:"suitcase-line",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg","xml:space":"preserve",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M922.5 229.5c-24.32-24.34-54.49-36.84-90.5-37.5H704v-64c-.68-17.98-7.02-32.98-19.01-44.99S658.01 64.66 640 64H384c-17.98.68-32.98 7.02-44.99 19.01S320.66 110 320 128v64H192c-35.99.68-66.16 13.18-90.5 37.5S64.66 283.99 64 320v448c.68 35.99 13.18 66.16 37.5 90.5s54.49 36.84 90.5 37.5h640c35.99-.68 66.16-13.18 90.5-37.5s36.84-54.49 37.5-90.5V320c-.68-35.99-13.18-66.16-37.5-90.5M384 128h256v64H384zM256 832h-64c-17.98-.68-32.98-7.02-44.99-19.01S128.66 786.01 128 768V448h128zm448 0H320V448h384zm192-64c-.68 17.98-7.02 32.98-19.01 44.99S850.01 831.34 832 832h-64V448h128zm0-384H128v-64c.69-17.98 7.02-32.98 19.01-44.99S173.99 256.66 192 256h640c17.98.69 32.98 7.02 44.99 19.01S895.34 301.99 896 320z"})]))}}),Ore=Dre,$re=q({name:"Suitcase",__name:"suitcase",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M128 384h768v-64a64 64 0 0 0-64-64H192a64 64 0 0 0-64 64zm0 64v320a64 64 0 0 0 64 64h640a64 64 0 0 0 64-64V448zm64-256h640a128 128 0 0 1 128 128v448a128 128 0 0 1-128 128H192A128 128 0 0 1 64 768V320a128 128 0 0 1 128-128"}),C("path",{fill:"currentColor",d:"M384 128v64h256v-64zm0-64h256a64 64 0 0 1 64 64v64a64 64 0 0 1-64 64H384a64 64 0 0 1-64-64v-64a64 64 0 0 1 64-64"})]))}}),Rre=$re,Nre=q({name:"Sunny",__name:"sunny",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 704a192 192 0 1 0 0-384 192 192 0 0 0 0 384m0 64a256 256 0 1 1 0-512 256 256 0 0 1 0 512m0-704a32 32 0 0 1 32 32v64a32 32 0 0 1-64 0V96a32 32 0 0 1 32-32m0 768a32 32 0 0 1 32 32v64a32 32 0 1 1-64 0v-64a32 32 0 0 1 32-32M195.2 195.2a32 32 0 0 1 45.248 0l45.248 45.248a32 32 0 1 1-45.248 45.248L195.2 240.448a32 32 0 0 1 0-45.248m543.104 543.104a32 32 0 0 1 45.248 0l45.248 45.248a32 32 0 0 1-45.248 45.248l-45.248-45.248a32 32 0 0 1 0-45.248M64 512a32 32 0 0 1 32-32h64a32 32 0 0 1 0 64H96a32 32 0 0 1-32-32m768 0a32 32 0 0 1 32-32h64a32 32 0 1 1 0 64h-64a32 32 0 0 1-32-32M195.2 828.8a32 32 0 0 1 0-45.248l45.248-45.248a32 32 0 0 1 45.248 45.248L240.448 828.8a32 32 0 0 1-45.248 0m543.104-543.104a32 32 0 0 1 0-45.248l45.248-45.248a32 32 0 0 1 45.248 45.248l-45.248 45.248a32 32 0 0 1-45.248 0"})]))}}),Bre=Nre,Vre=q({name:"Sunrise",__name:"sunrise",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M32 768h960a32 32 0 1 1 0 64H32a32 32 0 1 1 0-64m129.408-96a352 352 0 0 1 701.184 0h-64.32a288 288 0 0 0-572.544 0zM512 128a32 32 0 0 1 32 32v96a32 32 0 0 1-64 0v-96a32 32 0 0 1 32-32m407.296 168.704a32 32 0 0 1 0 45.248l-67.84 67.84a32 32 0 1 1-45.248-45.248l67.84-67.84a32 32 0 0 1 45.248 0m-814.592 0a32 32 0 0 1 45.248 0l67.84 67.84a32 32 0 1 1-45.248 45.248l-67.84-67.84a32 32 0 0 1 0-45.248"})]))}}),zre=Vre,Fre=q({name:"Sunset",__name:"sunset",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M82.56 640a448 448 0 1 1 858.88 0h-67.2a384 384 0 1 0-724.288 0zM32 704h960q32 0 32 32t-32 32H32q-32 0-32-32t32-32m256 128h448q32 0 32 32t-32 32H288q-32 0-32-32t32-32"})]))}}),Hre=Fre,Wre=q({name:"SwitchButton",__name:"switch-button",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M352 159.872V230.4a352 352 0 1 0 320 0v-70.528A416.128 416.128 0 0 1 512 960a416 416 0 0 1-160-800.128"}),C("path",{fill:"currentColor",d:"M512 64q32 0 32 32v320q0 32-32 32t-32-32V96q0-32 32-32"})]))}}),Kre=Wre,Ure=q({name:"SwitchFilled",__name:"switch-filled",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg","xml:space":"preserve",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M247.47 358.4v.04c.07 19.17 7.72 37.53 21.27 51.09s31.92 21.2 51.09 21.27c39.86 0 72.41-32.6 72.41-72.4s-32.6-72.36-72.41-72.36-72.36 32.55-72.36 72.36"}),C("path",{fill:"currentColor",d:"M492.38 128H324.7c-52.16 0-102.19 20.73-139.08 57.61a196.66 196.66 0 0 0-57.61 139.08V698.7c-.01 25.84 5.08 51.42 14.96 75.29s24.36 45.56 42.63 63.83 39.95 32.76 63.82 42.65a196.7 196.7 0 0 0 75.28 14.98h167.68c3.03 0 5.46-2.43 5.46-5.42V133.42c.6-2.99-1.83-5.42-5.46-5.42m-56.11 705.88H324.7c-17.76.13-35.36-3.33-51.75-10.18s-31.22-16.94-43.61-29.67c-25.3-25.35-39.81-59.1-39.81-95.32V324.69c-.13-17.75 3.33-35.35 10.17-51.74a131.7 131.7 0 0 1 29.64-43.62c25.39-25.3 59.14-39.81 95.36-39.81h111.57zm402.12-647.67a196.66 196.66 0 0 0-139.08-57.61H580.48c-3.03 0-4.82 2.43-4.82 4.82v757.16c-.6 2.99 1.79 5.42 5.42 5.42h118.23a196.7 196.7 0 0 0 139.08-57.61A196.66 196.66 0 0 0 896 699.31V325.29a196.7 196.7 0 0 0-57.61-139.08m-111.3 441.92c-42.83 0-77.82-34.99-77.82-77.82s34.98-77.82 77.82-77.82c42.83 0 77.82 34.99 77.82 77.82s-34.99 77.82-77.82 77.82"})]))}}),Gre=Ure,Yre=q({name:"Switch",__name:"switch",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M118.656 438.656a32 32 0 0 1 0-45.248L416 96l4.48-3.776A32 32 0 0 1 461.248 96l3.712 4.48a32.064 32.064 0 0 1-3.712 40.832L218.56 384H928a32 32 0 1 1 0 64H141.248a32 32 0 0 1-22.592-9.344M64 608a32 32 0 0 1 32-32h786.752a32 32 0 0 1 22.656 54.592L608 928l-4.48 3.776a32.064 32.064 0 0 1-40.832-49.024L805.632 640H96a32 32 0 0 1-32-32"})]))}}),jre=Yre,qre=q({name:"TakeawayBox",__name:"takeaway-box",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M832 384H192v448h640zM96 320h832V128H96zm800 64v480a32 32 0 0 1-32 32H160a32 32 0 0 1-32-32V384H64a32 32 0 0 1-32-32V96a32 32 0 0 1 32-32h896a32 32 0 0 1 32 32v256a32 32 0 0 1-32 32zM416 512h192a32 32 0 0 1 0 64H416a32 32 0 0 1 0-64"})]))}}),Xre=qre,Zre=q({name:"Ticket",__name:"ticket",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M640 832H64V640a128 128 0 1 0 0-256V192h576v160h64V192h256v192a128 128 0 1 0 0 256v192H704V672h-64zm0-416v192h64V416z"})]))}}),Jre=Zre,Qre=q({name:"Tickets",__name:"tickets",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M192 128v768h640V128zm-32-64h704a32 32 0 0 1 32 32v832a32 32 0 0 1-32 32H160a32 32 0 0 1-32-32V96a32 32 0 0 1 32-32m160 448h384v64H320zm0-192h192v64H320zm0 384h384v64H320z"})]))}}),eoe=Qre,toe=q({name:"Timer",__name:"timer",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 896a320 320 0 1 0 0-640 320 320 0 0 0 0 640m0 64a384 384 0 1 1 0-768 384 384 0 0 1 0 768"}),C("path",{fill:"currentColor",d:"M512 320a32 32 0 0 1 32 32l-.512 224a32 32 0 1 1-64 0L480 352a32 32 0 0 1 32-32"}),C("path",{fill:"currentColor",d:"M448 576a64 64 0 1 0 128 0 64 64 0 1 0-128 0m96-448v128h-64V128h-96a32 32 0 0 1 0-64h256a32 32 0 1 1 0 64z"})]))}}),noe=toe,aoe=q({name:"ToiletPaper",__name:"toilet-paper",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M595.2 128H320a192 192 0 0 0-192 192v576h384V352c0-90.496 32.448-171.2 83.2-224M736 64c123.712 0 224 128.96 224 288S859.712 640 736 640H576v320H64V320A256 256 0 0 1 320 64zM576 352v224h160c84.352 0 160-97.28 160-224s-75.648-224-160-224-160 97.28-160 224"}),C("path",{fill:"currentColor",d:"M736 448c-35.328 0-64-43.008-64-96s28.672-96 64-96 64 43.008 64 96-28.672 96-64 96"})]))}}),roe=aoe,ooe=q({name:"Tools",__name:"tools",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M764.416 254.72a351.7 351.7 0 0 1 86.336 149.184H960v192.064H850.752a351.7 351.7 0 0 1-86.336 149.312l54.72 94.72-166.272 96-54.592-94.72a352.64 352.64 0 0 1-172.48 0L371.136 936l-166.272-96 54.72-94.72a351.7 351.7 0 0 1-86.336-149.312H64v-192h109.248a351.7 351.7 0 0 1 86.336-149.312L204.8 160l166.208-96h.192l54.656 94.592a352.64 352.64 0 0 1 172.48 0L652.8 64h.128L819.2 160l-54.72 94.72zM704 499.968a192 192 0 1 0-384 0 192 192 0 0 0 384 0"})]))}}),ioe=ooe,loe=q({name:"TopLeft",__name:"top-left",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M256 256h416a32 32 0 1 0 0-64H224a32 32 0 0 0-32 32v448a32 32 0 0 0 64 0z"}),C("path",{fill:"currentColor",d:"M246.656 201.344a32 32 0 0 0-45.312 45.312l544 544a32 32 0 0 0 45.312-45.312z"})]))}}),soe=loe,uoe=q({name:"TopRight",__name:"top-right",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M768 256H353.6a32 32 0 1 1 0-64H800a32 32 0 0 1 32 32v448a32 32 0 0 1-64 0z"}),C("path",{fill:"currentColor",d:"M777.344 201.344a32 32 0 0 1 45.312 45.312l-544 544a32 32 0 0 1-45.312-45.312z"})]))}}),coe=uoe,doe=q({name:"Top",__name:"top",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M572.235 205.282v600.365a30.118 30.118 0 1 1-60.235 0V205.282L292.382 438.633a28.913 28.913 0 0 1-42.646 0 33.43 33.43 0 0 1 0-45.236l271.058-288.045a28.913 28.913 0 0 1 42.647 0L834.5 393.397a33.43 33.43 0 0 1 0 45.176 28.913 28.913 0 0 1-42.647 0l-219.618-233.23z"})]))}}),foe=doe,voe=q({name:"TrendCharts",__name:"trend-charts",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M128 896V128h768v768zm291.712-327.296 128 102.4 180.16-201.792-47.744-42.624-139.84 156.608-128-102.4-180.16 201.792 47.744 42.624zM816 352a48 48 0 1 0-96 0 48 48 0 0 0 96 0"})]))}}),hoe=voe,poe=q({name:"TrophyBase",__name:"trophy-base",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg","xml:space":"preserve",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M918.4 201.6c-6.4-6.4-12.8-9.6-22.4-9.6H768V96c0-9.6-3.2-16-9.6-22.4S745.6 64 736 64H288c-9.6 0-16 3.2-22.4 9.6S256 86.4 256 96v96H128c-9.6 0-16 3.2-22.4 9.6S96 217.6 96 224c3.2 108.8 25.6 185.6 64 224 34.4 34.4 77.56 55.65 127.65 61.99 10.91 20.44 24.78 39.25 41.95 56.41 40.86 40.86 91 65.47 150.4 71.9V768h-96c-9.6 0-16 3.2-22.4 9.6S352 790.4 352 800s3.2 16 9.6 22.4 12.8 9.6 22.4 9.6h256c9.6 0 16-3.2 22.4-9.6s9.6-12.8 9.6-22.4-3.2-16-9.6-22.4-12.8-9.6-22.4-9.6h-96V637.26c59.4-7.71 109.54-30.01 150.4-70.86 17.2-17.2 31.51-36.06 42.81-56.55 48.93-6.51 90.02-27.7 126.79-61.85 38.4-38.4 60.8-112 64-224 0-6.4-3.2-16-9.6-22.4M256 438.4c-19.2-6.4-35.2-19.2-51.2-35.2-22.4-22.4-35.2-70.4-41.6-147.2H256zm390.4 80C608 553.6 566.4 576 512 576s-99.2-19.2-134.4-57.6S320 438.4 320 384V128h384v256q0 81.6-57.6 134.4m172.8-115.2c-16 16-32 25.6-51.2 35.2V256h92.8c-6.4 76.8-19.2 124.8-41.6 147.2M768 896H256c-9.6 0-16 3.2-22.4 9.6S224 918.4 224 928s3.2 16 9.6 22.4 12.8 9.6 22.4 9.6h512c9.6 0 16-3.2 22.4-9.6s9.6-12.8 9.6-22.4-3.2-16-9.6-22.4-12.8-9.6-22.4-9.6"})]))}}),moe=poe,goe=q({name:"Trophy",__name:"trophy",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M480 896V702.08A256.26 256.26 0 0 1 264.064 512h-32.64a96 96 0 0 1-91.968-68.416L93.632 290.88a76.8 76.8 0 0 1 73.6-98.88H256V96a32 32 0 0 1 32-32h448a32 32 0 0 1 32 32v96h88.768a76.8 76.8 0 0 1 73.6 98.88L884.48 443.52A96 96 0 0 1 792.576 512h-32.64A256.26 256.26 0 0 1 544 702.08V896h128a32 32 0 1 1 0 64H352a32 32 0 1 1 0-64zm224-448V128H320v320a192 192 0 1 0 384 0m64 0h24.576a32 32 0 0 0 30.656-22.784l45.824-152.768A12.8 12.8 0 0 0 856.768 256H768zm-512 0V256h-88.768a12.8 12.8 0 0 0-12.288 16.448l45.824 152.768A32 32 0 0 0 231.424 448z"})]))}}),yoe=goe,boe=q({name:"TurnOff",__name:"turn-off",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M329.956 257.138a254.862 254.862 0 0 0 0 509.724h364.088a254.862 254.862 0 0 0 0-509.724zm0-72.818h364.088a327.68 327.68 0 1 1 0 655.36H329.956a327.68 327.68 0 1 1 0-655.36"}),C("path",{fill:"currentColor",d:"M329.956 621.227a109.227 109.227 0 1 0 0-218.454 109.227 109.227 0 0 0 0 218.454m0 72.817a182.044 182.044 0 1 1 0-364.088 182.044 182.044 0 0 1 0 364.088"})]))}}),_oe=boe,woe=q({name:"Umbrella",__name:"umbrella",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M320 768a32 32 0 1 1 64 0 64 64 0 0 0 128 0V512H64a448 448 0 1 1 896 0H576v256a128 128 0 1 1-256 0m570.688-320a384.128 384.128 0 0 0-757.376 0z"})]))}}),Soe=woe,Coe=q({name:"Unlock",__name:"unlock",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M224 448a32 32 0 0 0-32 32v384a32 32 0 0 0 32 32h576a32 32 0 0 0 32-32V480a32 32 0 0 0-32-32zm0-64h576a96 96 0 0 1 96 96v384a96 96 0 0 1-96 96H224a96 96 0 0 1-96-96V480a96 96 0 0 1 96-96"}),C("path",{fill:"currentColor",d:"M512 544a32 32 0 0 1 32 32v192a32 32 0 1 1-64 0V576a32 32 0 0 1 32-32m178.304-295.296A192.064 192.064 0 0 0 320 320v64h352l96 38.4V448H256V320a256 256 0 0 1 493.76-95.104z"})]))}}),xoe=Coe,koe=q({name:"UploadFilled",__name:"upload-filled",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M544 864V672h128L512 480 352 672h128v192H320v-1.6c-5.376.32-10.496 1.6-16 1.6A240 240 0 0 1 64 624c0-123.136 93.12-223.488 212.608-237.248A239.81 239.81 0 0 1 512 192a239.87 239.87 0 0 1 235.456 194.752c119.488 13.76 212.48 114.112 212.48 237.248a240 240 0 0 1-240 240c-5.376 0-10.56-1.28-16-1.6v1.6z"})]))}}),Toe=koe,Moe=q({name:"Upload",__name:"upload",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M160 832h704a32 32 0 1 1 0 64H160a32 32 0 1 1 0-64m384-578.304V704h-64V247.296L237.248 490.048 192 444.8 508.8 128l316.8 316.8-45.312 45.248z"})]))}}),Eoe=Moe,Aoe=q({name:"UserFilled",__name:"user-filled",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M288 320a224 224 0 1 0 448 0 224 224 0 1 0-448 0m544 608H160a32 32 0 0 1-32-32v-96a160 160 0 0 1 160-160h448a160 160 0 0 1 160 160v96a32 32 0 0 1-32 32z"})]))}}),Ioe=Aoe,Loe=q({name:"User",__name:"user",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 512a192 192 0 1 0 0-384 192 192 0 0 0 0 384m0 64a256 256 0 1 1 0-512 256 256 0 0 1 0 512m320 320v-96a96 96 0 0 0-96-96H288a96 96 0 0 0-96 96v96a32 32 0 1 1-64 0v-96a160 160 0 0 1 160-160h448a160 160 0 0 1 160 160v96a32 32 0 1 1-64 0"})]))}}),Poe=Loe,Doe=q({name:"Van",__name:"van",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M128.896 736H96a32 32 0 0 1-32-32V224a32 32 0 0 1 32-32h576a32 32 0 0 1 32 32v96h164.544a32 32 0 0 1 31.616 27.136l54.144 352A32 32 0 0 1 922.688 736h-91.52a144 144 0 1 1-286.272 0H415.104a144 144 0 1 1-286.272 0zm23.36-64a143.872 143.872 0 0 1 239.488 0H568.32c17.088-25.6 42.24-45.376 71.744-55.808V256H128v416zm655.488 0h77.632l-19.648-128H704v64.896A144 144 0 0 1 807.744 672m48.128-192-14.72-96H704v96zM688 832a80 80 0 1 0 0-160 80 80 0 0 0 0 160m-416 0a80 80 0 1 0 0-160 80 80 0 0 0 0 160"})]))}}),Ooe=Doe,$oe=q({name:"VideoCameraFilled",__name:"video-camera-filled",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m768 576 192-64v320l-192-64v96a32 32 0 0 1-32 32H96a32 32 0 0 1-32-32V480a32 32 0 0 1 32-32h640a32 32 0 0 1 32 32zM192 768v64h384v-64zm192-480a160 160 0 0 1 320 0 160 160 0 0 1-320 0m64 0a96 96 0 1 0 192.064-.064A96 96 0 0 0 448 288m-320 32a128 128 0 1 1 256.064.064A128 128 0 0 1 128 320m64 0a64 64 0 1 0 128 0 64 64 0 0 0-128 0"})]))}}),Roe=$oe,Noe=q({name:"VideoCamera",__name:"video-camera",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M704 768V256H128v512zm64-416 192-96v512l-192-96v128a32 32 0 0 1-32 32H96a32 32 0 0 1-32-32V224a32 32 0 0 1 32-32h640a32 32 0 0 1 32 32zm0 71.552v176.896l128 64V359.552zM192 320h192v64H192z"})]))}}),Boe=Noe,Voe=q({name:"VideoPause",__name:"video-pause",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 64a448 448 0 1 1 0 896 448 448 0 0 1 0-896m0 832a384 384 0 0 0 0-768 384 384 0 0 0 0 768m-96-544q32 0 32 32v256q0 32-32 32t-32-32V384q0-32 32-32m192 0q32 0 32 32v256q0 32-32 32t-32-32V384q0-32 32-32"})]))}}),zoe=Voe,Foe=q({name:"VideoPlay",__name:"video-play",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 64a448 448 0 1 1 0 896 448 448 0 0 1 0-896m0 832a384 384 0 0 0 0-768 384 384 0 0 0 0 768m-48-247.616L668.608 512 464 375.616zm10.624-342.656 249.472 166.336a48 48 0 0 1 0 79.872L474.624 718.272A48 48 0 0 1 400 678.336V345.6a48 48 0 0 1 74.624-39.936z"})]))}}),Hoe=Foe,Woe=q({name:"View",__name:"view",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 160c320 0 512 352 512 352S832 864 512 864 0 512 0 512s192-352 512-352m0 64c-225.28 0-384.128 208.064-436.8 288 52.608 79.872 211.456 288 436.8 288 225.28 0 384.128-208.064 436.8-288-52.608-79.872-211.456-288-436.8-288m0 64a224 224 0 1 1 0 448 224 224 0 0 1 0-448m0 64a160.19 160.19 0 0 0-160 160c0 88.192 71.744 160 160 160s160-71.808 160-160-71.744-160-160-160"})]))}}),U6=Woe,Koe=q({name:"WalletFilled",__name:"wallet-filled",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M688 512a112 112 0 1 0 0 224h208v160H128V352h768v160zm32 160h-32a48 48 0 0 1 0-96h32a48 48 0 0 1 0 96m-80-544 128 160H384z"})]))}}),Uoe=Koe,Goe=q({name:"Wallet",__name:"wallet",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M640 288h-64V128H128v704h384v32a32 32 0 0 0 32 32H96a32 32 0 0 1-32-32V96a32 32 0 0 1 32-32h512a32 32 0 0 1 32 32z"}),C("path",{fill:"currentColor",d:"M128 320v512h768V320zm-32-64h832a32 32 0 0 1 32 32v576a32 32 0 0 1-32 32H96a32 32 0 0 1-32-32V288a32 32 0 0 1 32-32"}),C("path",{fill:"currentColor",d:"M704 640a64 64 0 1 1 0-128 64 64 0 0 1 0 128"})]))}}),Yoe=Goe,joe=q({name:"WarnTriangleFilled",__name:"warn-triangle-filled",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg","xml:space":"preserve",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M928.99 755.83 574.6 203.25c-12.89-20.16-36.76-32.58-62.6-32.58s-49.71 12.43-62.6 32.58L95.01 755.83c-12.91 20.12-12.9 44.91.01 65.03 12.92 20.12 36.78 32.51 62.59 32.49h708.78c25.82.01 49.68-12.37 62.59-32.49s12.92-44.91.01-65.03M554.67 768h-85.33v-85.33h85.33zm0-426.67v298.66h-85.33V341.32z"})]))}}),qoe=joe,Xoe=q({name:"WarningFilled",__name:"warning-filled",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 64a448 448 0 1 1 0 896 448 448 0 0 1 0-896m0 192a58.43 58.43 0 0 0-58.24 63.744l23.36 256.384a35.072 35.072 0 0 0 69.76 0l23.296-256.384A58.43 58.43 0 0 0 512 256m0 512a51.2 51.2 0 1 0 0-102.4 51.2 51.2 0 0 0 0 102.4"})]))}}),$s=Xoe,Zoe=q({name:"Warning",__name:"warning",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 64a448 448 0 1 1 0 896 448 448 0 0 1 0-896m0 832a384 384 0 0 0 0-768 384 384 0 0 0 0 768m48-176a48 48 0 1 1-96 0 48 48 0 0 1 96 0m-48-464a32 32 0 0 1 32 32v288a32 32 0 0 1-64 0V288a32 32 0 0 1 32-32"})]))}}),Joe=Zoe,Qoe=q({name:"Watch",__name:"watch",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M512 768a256 256 0 1 0 0-512 256 256 0 0 0 0 512m0 64a320 320 0 1 1 0-640 320 320 0 0 1 0 640"}),C("path",{fill:"currentColor",d:"M480 352a32 32 0 0 1 32 32v160a32 32 0 0 1-64 0V384a32 32 0 0 1 32-32"}),C("path",{fill:"currentColor",d:"M480 512h128q32 0 32 32t-32 32H480q-32 0-32-32t32-32m128-256V128H416v128h-64V64h320v192zM416 768v128h192V768h64v192H352V768z"})]))}}),eie=Qoe,tie=q({name:"Watermelon",__name:"watermelon",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m683.072 600.32-43.648 162.816-61.824-16.512 53.248-198.528L576 493.248l-158.4 158.4-45.248-45.248 158.4-158.4-55.616-55.616-198.528 53.248-16.512-61.824 162.816-43.648L282.752 200A384 384 0 0 0 824 741.248zm231.552 141.056a448 448 0 1 1-632-632z"})]))}}),nie=tie,aie=q({name:"WindPower",__name:"wind-power",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"M160 64q32 0 32 32v832q0 32-32 32t-32-32V96q0-32 32-32m416 354.624 128-11.584V168.96l-128-11.52v261.12zm-64 5.824V151.552L320 134.08V160h-64V64l616.704 56.064A96 96 0 0 1 960 215.68v144.64a96 96 0 0 1-87.296 95.616L256 512V224h64v217.92zm256-23.232 98.88-8.96A32 32 0 0 0 896 360.32V215.68a32 32 0 0 0-29.12-31.872l-98.88-8.96z"})]))}}),rie=aie,oie=q({name:"ZoomIn",__name:"zoom-in",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m795.904 750.72 124.992 124.928a32 32 0 0 1-45.248 45.248L750.656 795.904a416 416 0 1 1 45.248-45.248zM480 832a352 352 0 1 0 0-704 352 352 0 0 0 0 704m-32-384v-96a32 32 0 0 1 64 0v96h96a32 32 0 0 1 0 64h-96v96a32 32 0 0 1-64 0v-96h-96a32 32 0 0 1 0-64z"})]))}}),G6=oie,iie=q({name:"ZoomOut",__name:"zoom-out",setup(e){return(t,n)=>(x(),O("svg",{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 1024 1024"},[C("path",{fill:"currentColor",d:"m795.904 750.72 124.992 124.928a32 32 0 0 1-45.248 45.248L750.656 795.904a416 416 0 1 1 45.248-45.248zM480 832a352 352 0 1 0 0-704 352 352 0 0 0 0 704M352 448h256a32 32 0 0 1 0 64H352a32 32 0 0 1 0-64"})]))}}),GI=iie;const lie=Object.freeze(Object.defineProperty({__proto__:null,AddLocation:dX,Aim:vX,AlarmClock:pX,Apple:gX,ArrowDown:ui,ArrowDownBold:bX,ArrowLeft:Ji,ArrowLeftBold:SX,ArrowRight:za,ArrowRightBold:kX,ArrowUp:ip,ArrowUpBold:EX,Avatar:LX,Back:CI,Baseball:OX,Basketball:RX,Bell:zX,BellFilled:BX,Bicycle:HX,Bottom:jX,BottomLeft:KX,BottomRight:GX,Bowl:XX,Box:JX,Briefcase:eZ,Brush:rZ,BrushFilled:nZ,Burger:iZ,Calendar:xI,Camera:kI,CameraFilled:uZ,CaretBottom:fZ,CaretLeft:hZ,CaretRight:F6,CaretTop:TI,Cellphone:yZ,ChatDotRound:_Z,ChatDotSquare:SZ,ChatLineRound:xZ,ChatLineSquare:TZ,ChatRound:EZ,ChatSquare:IZ,Check:Os,Checked:DZ,Cherry:$Z,Chicken:NZ,ChromeFilled:VZ,CircleCheck:Ao,CircleCheckFilled:MI,CircleClose:ci,CircleCloseFilled:fc,CirclePlus:YZ,CirclePlusFilled:UZ,Clock:H6,Close:so,CloseBold:XZ,Cloudy:QZ,Coffee:aJ,CoffeeCup:tJ,Coin:oJ,ColdDrink:lJ,Collection:dJ,CollectionTag:uJ,Comment:vJ,Compass:pJ,Connection:p4,Coordinate:yJ,CopyDocument:_J,Cpu:SJ,CreditCard:xJ,Crop:TJ,DArrowLeft:Al,DArrowRight:Il,DCaret:IJ,DataAnalysis:PJ,DataBoard:OJ,DataLine:RJ,Delete:W6,DeleteFilled:BJ,DeleteLocation:zJ,Dessert:WJ,Discount:UJ,Dish:qJ,DishDot:YJ,Document:EI,DocumentAdd:ZJ,DocumentChecked:QJ,DocumentCopy:tQ,DocumentDelete:aQ,DocumentRemove:oQ,Download:AI,Drizzling:uQ,Edit:vQ,EditPen:dQ,Eleme:gQ,ElemeFilled:pQ,ElementPlus:bQ,Expand:wQ,Failed:CQ,Female:kQ,Files:MQ,Film:AQ,Filter:LQ,Finished:DQ,FirstAidKit:$Q,Flag:NQ,Fold:VQ,Folder:ZQ,FolderAdd:FQ,FolderChecked:WQ,FolderDelete:UQ,FolderOpened:YQ,FolderRemove:qQ,Food:QQ,Football:tee,ForkSpoon:aee,Fries:oee,FullScreen:II,Goblet:hee,GobletFull:see,GobletSquare:fee,GobletSquareFull:cee,GoldMedal:mee,Goods:_ee,GoodsFilled:yee,Grape:See,Grid:xee,Guide:Tee,Handbag:Eee,Headset:Iee,Help:Oee,HelpFilled:Pee,Hide:LI,Histogram:Nee,HomeFilled:Vee,HotWater:Fee,House:Wee,IceCream:qee,IceCreamRound:Uee,IceCreamSquare:Yee,IceDrink:Zee,IceTea:Qee,InfoFilled:Ud,Iphone:nte,Key:PI,KnifeFork:ote,Lightning:lte,Link:DI,List:cte,Loading:Mr,Location:gte,LocationFilled:vte,LocationInformation:pte,Lock:bte,Lollipop:wte,MagicStick:Cte,Magnet:kte,Male:Mte,Management:Ate,MapLocation:Lte,Medal:Dte,Memo:$te,Menu:Nte,Message:Fte,MessageBox:Vte,Mic:Wte,Microphone:Ute,MilkTea:Yte,Minus:OI,Money:Xte,Monitor:Jte,Moon:nne,MoonNight:ene,More:$I,MoreFilled:m4,MostlyCloudy:ine,Mouse:sne,Mug:cne,Mute:hne,MuteNotification:fne,NoSmoking:mne,Notebook:yne,Notification:_ne,Odometer:Sne,OfficeBuilding:xne,Open:Tne,Operation:Ene,Opportunity:Ine,Orange:Pne,Paperclip:One,PartlyCloudy:Rne,Pear:Bne,Phone:Hne,PhoneFilled:zne,Picture:NI,PictureFilled:RI,PictureRounded:Une,PieChart:jne,Place:Xne,Platform:Jne,Plus:hf,Pointer:tae,Position:aae,Postcard:oae,Pouring:lae,Present:uae,PriceTag:dae,Printer:vae,Promotion:pae,QuartzWatch:gae,QuestionFilled:BI,Rank:_ae,Reading:xae,ReadingLamp:Sae,Refresh:g4,RefreshLeft:VI,RefreshRight:zI,Refrigerator:Aae,Remove:Dae,RemoveFilled:Lae,Right:$ae,ScaleToOriginal:FI,School:Bae,Scissor:zae,Search:HI,Select:Wae,Sell:Uae,SemiSelect:Yae,Service:qae,SetUp:Zae,Setting:Qae,Share:tre,Ship:are,Shop:ore,ShoppingBag:lre,ShoppingCart:dre,ShoppingCartFull:ure,ShoppingTrolley:vre,Smoking:pre,Soccer:gre,SoldOut:bre,Sort:Cre,SortDown:WI,SortUp:KI,Stamp:kre,Star:UI,StarFilled:rv,Stopwatch:Are,SuccessFilled:K6,Sugar:Pre,Suitcase:Rre,SuitcaseLine:Ore,Sunny:Bre,Sunrise:zre,Sunset:Hre,Switch:jre,SwitchButton:Kre,SwitchFilled:Gre,TakeawayBox:Xre,Ticket:Jre,Tickets:eoe,Timer:noe,ToiletPaper:roe,Tools:ioe,Top:foe,TopLeft:soe,TopRight:coe,TrendCharts:hoe,Trophy:yoe,TrophyBase:moe,TurnOff:_oe,Umbrella:Soe,Unlock:xoe,Upload:Eoe,UploadFilled:Toe,User:Poe,UserFilled:Ioe,Van:Ooe,VideoCamera:Boe,VideoCameraFilled:Roe,VideoPause:zoe,VideoPlay:Hoe,View:U6,Wallet:Yoe,WalletFilled:Uoe,WarnTriangleFilled:qoe,Warning:Joe,WarningFilled:$s,Watch:eie,Watermelon:nie,WindPower:rie,ZoomIn:G6,ZoomOut:GI},Symbol.toStringTag,{value:"Module"})),pn=ae([String,Object,Function]),YI={Close:so},Y6={Close:so,SuccessFilled:K6,InfoFilled:Ud,WarningFilled:$s,CircleCloseFilled:fc},Rs={primary:Ud,success:K6,warning:$s,error:fc,info:Ud},F1={validating:Mr,success:Ao,error:ci},sie=["light","dark"],uie=$e({title:{type:String,default:""},description:{type:String,default:""},type:{type:String,values:sh(Rs),default:"info"},closable:{type:Boolean,default:!0},closeText:{type:String,default:""},showIcon:Boolean,center:Boolean,effect:{type:String,values:sie,default:"light"}}),cie={close:e=>e instanceof MouseEvent},die=$e({size:{type:ae([Number,String])},color:{type:String}});var fie=q({name:"ElIcon",inheritAttrs:!1,__name:"icon",props:die,setup(e){const t=e,n=ke("icon"),a=D(()=>{const{size:r,color:o}=t,i=Vn(r);return!i&&!o?{}:{fontSize:i,"--color":o}});return(r,o)=>(x(),O("i",Tt({class:p(n).b(),style:a.value},r.$attrs),[ce(r.$slots,"default")],16))}}),vie=fie;const Xe=wt(vie);var hie=q({name:"ElAlert",__name:"alert",props:uie,emits:cie,setup(e,{emit:t}){const{Close:n}=Y6,a=e,r=t,o=Xn(),i=ke("alert"),l=F(!0),s=D(()=>Rs[a.type]),u=D(()=>{var v;if(a.description)return!0;const d=(v=o.default)==null?void 0:v.call(o);return d?eo(d).some(f=>!gI(f)):!1}),c=d=>{l.value=!1,r("close",d)};return(d,v)=>(x(),ne(Pa,{name:p(i).b("fade"),persisted:""},{default:$(()=>[st(C("div",{class:U([p(i).b(),p(i).m(e.type),p(i).is("center",e.center),p(i).is(e.effect)]),role:"alert"},[e.showIcon&&(d.$slots.icon||s.value)?(x(),ne(p(Xe),{key:0,class:U([p(i).e("icon"),p(i).is("big",u.value)])},{default:$(()=>[ce(d.$slots,"icon",{},()=>[(x(),ne(St(s.value)))])]),_:3},8,["class"])):te("v-if",!0),C("div",{class:U(p(i).e("content"))},[e.title||d.$slots.title?(x(),O("span",{key:0,class:U([p(i).e("title"),{"with-description":u.value}])},[ce(d.$slots,"title",{},()=>[ue(Q(e.title),1)])],2)):te("v-if",!0),u.value?(x(),O("p",{key:1,class:U(p(i).e("description"))},[ce(d.$slots,"default",{},()=>[ue(Q(e.description),1)])],2)):te("v-if",!0),e.closable?(x(),O(Ve,{key:2},[e.closeText?(x(),O("div",{key:0,class:U([p(i).e("close-btn"),p(i).is("customed")]),onClick:c},Q(e.closeText),3)):(x(),ne(p(Xe),{key:1,class:U(p(i).e("close-btn")),onClick:c},{default:$(()=>[L(p(n))]),_:1},8,["class"]))],64)):te("v-if",!0)],2)],2),[[Zt,l.value]])]),_:3},8,["name"]))}}),pie=hie;const mie=wt(pie),jI=["dialog","grid","group","listbox","menu","navigation","tooltip","tree"],qI=$e({role:{type:String,values:jI,default:"tooltip"}}),j6=Symbol("popper"),XI=Symbol("popperContent");var gie=q({name:"ElPopperArrow",inheritAttrs:!1,__name:"arrow",setup(e,{expose:t}){const n=ke("popper"),{arrowRef:a,arrowStyle:r}=Ze(XI,void 0);return rn(()=>{a.value=void 0}),t({arrowRef:a}),(o,i)=>(x(),O("span",{ref_key:"arrowRef",ref:a,class:U(p(n).e("arrow")),style:et(p(r)),"data-popper-arrow":""},null,6))}}),yie=gie;const ZI=$e({virtualRef:{type:ae(Object)},virtualTriggering:Boolean,onMouseenter:{type:ae(Function)},onMouseleave:{type:ae(Function)},onClick:{type:ae(Function)},onKeydown:{type:ae(Function)},onFocus:{type:ae(Function)},onBlur:{type:ae(Function)},onContextmenu:{type:ae(Function)},id:String,open:Boolean}),G2="ElOnlyChild",JI=q({name:G2,setup(e,{slots:t,attrs:n}){var r;const a=sq(((r=Ze(aI))==null?void 0:r.setForwardRef)??kt);return()=>{var s;const o=(s=t.default)==null?void 0:s.call(t,n);if(!o)return null;const[i,l]=QI(o);return i?(l>1&&un(G2,"requires exact only one valid child."),st(ei(i,n),[[a]])):(un(G2,"no valid child node found"),null)}}});function QI(e){if(!e)return[null,0];const t=e,n=t.filter(a=>a.type!==oa).length;for(const a of t){if(_t(a))switch(a.type){case oa:continue;case uf:case"svg":return[p5(a),n];case Ve:return QI(a.children);default:return[a,n]}return[p5(a),n]}return[null,0]}function p5(e){return L("span",{class:ke("only-child").e("content")},[e])}var bie=q({name:"ElPopperTrigger",inheritAttrs:!1,__name:"trigger",props:ZI,setup(e,{expose:t}){const n=e,{role:a,triggerRef:r}=Ze(j6,void 0);lq(r);const o=D(()=>l.value?n.id:void 0),i=D(()=>{if(a&&a.value==="tooltip")return n.open&&n.id?n.id:void 0}),l=D(()=>{if(a&&a.value!=="tooltip")return a.value}),s=D(()=>l.value?`${n.open}`:void 0);let u;const c=["onMouseenter","onMouseleave","onClick","onKeydown","onFocus","onBlur","onContextmenu"];return mt(()=>{ye(()=>n.virtualRef,d=>{d&&(r.value=da(d))},{immediate:!0}),ye(r,(d,v)=>{u==null||u(),u=void 0,Vr(v)&&c.forEach(f=>{const h=n[f];h&&v.removeEventListener(f.slice(2).toLowerCase(),h,["onFocus","onBlur"].includes(f))}),Vr(d)&&(c.forEach(f=>{const h=n[f];h&&d.addEventListener(f.slice(2).toLowerCase(),h,["onFocus","onBlur"].includes(f))}),sc(d)&&(u=ye([o,i,l,s],f=>{["aria-controls","aria-describedby","aria-haspopup","aria-expanded"].forEach((h,m)=>{Un(f[m])?d.removeAttribute(h):d.setAttribute(h,f[m])})},{immediate:!0}))),Vr(v)&&sc(v)&&["aria-controls","aria-describedby","aria-haspopup","aria-expanded"].forEach(f=>v.removeAttribute(f))},{immediate:!0})}),rn(()=>{if(u==null||u(),u=void 0,r.value&&Vr(r.value)){const d=r.value;c.forEach(v=>{const f=n[v];f&&d.removeEventListener(v.slice(2).toLowerCase(),f,["onFocus","onBlur"].includes(v))}),r.value=void 0}}),t({triggerRef:r}),(d,v)=>e.virtualTriggering?te("v-if",!0):(x(),ne(p(JI),Tt({key:0},d.$attrs,{"aria-controls":o.value,"aria-describedby":i.value,"aria-expanded":s.value,"aria-haspopup":l.value}),{default:$(()=>[ce(d.$slots,"default")]),_:3},16,["aria-controls","aria-describedby","aria-expanded","aria-haspopup"]))}}),_ie=bie;const eL=$e({arrowOffset:{type:Number,default:5}}),wie={arrowOffset:5},Sie=$e({boundariesPadding:{type:Number,default:0},fallbackPlacements:{type:ae(Array),default:void 0},gpuAcceleration:{type:Boolean,default:!0},offset:{type:Number,default:12},placement:{type:String,values:tl,default:"bottom"},popperOptions:{type:ae(Object),default:()=>({})},strategy:{type:String,values:["fixed","absolute"],default:"absolute"}}),tL=$e({...Sie,...eL,id:String,style:{type:ae([String,Array,Object,Boolean]),default:void 0},className:{type:ae([String,Array,Object,Boolean])},effect:{type:ae(String),default:"dark"},visible:Boolean,enterable:{type:Boolean,default:!0},pure:Boolean,focusOnShow:Boolean,trapping:Boolean,popperClass:{type:ae([String,Array,Object,Boolean]),default:void 0},popperStyle:{type:ae([String,Array,Object,Boolean]),default:void 0},referenceEl:{type:ae(Object)},triggerTargetEl:{type:ae(Object)},stopPopperMouseEvent:{type:Boolean,default:!0},virtualTriggering:Boolean,zIndex:Number,...dr(["ariaLabel"]),loop:Boolean}),Cie={boundariesPadding:0,gpuAcceleration:!0,offset:12,placement:"bottom",popperOptions:()=>({}),strategy:"absolute"},xie={...Cie,...wie,effect:"dark",enterable:!0,stopPopperMouseEvent:!0,visible:!1,pure:!1,focusOnShow:!1,trapping:!1,virtualTriggering:!1,loop:!1,style:void 0,popperStyle:void 0,popperClass:void 0},kie={mouseenter:e=>e instanceof MouseEvent,mouseleave:e=>e instanceof MouseEvent,focus:()=>!0,blur:()=>!0,close:()=>!0},Tie=$e({size:{type:String,values:ii},disabled:Boolean}),Mie=$e({...Tie,model:Object,rules:{type:ae(Object)},labelPosition:{type:String,values:["left","right","top"],default:"right"},requireAsteriskPosition:{type:String,values:["left","right"],default:"left"},labelWidth:{type:[String,Number],default:""},labelSuffix:{type:String,default:""},inline:Boolean,inlineMessage:Boolean,statusIcon:Boolean,showMessage:{type:Boolean,default:!0},validateOnRuleChange:{type:Boolean,default:!0},hideRequiredAsterisk:Boolean,scrollToError:Boolean,scrollIntoViewOptions:{type:ae([Object,Boolean]),default:!0}}),Eie={validate:(e,t,n)=>(Ae(e)||Ye(e))&&vn(t)&&Ye(n)},Ec=Symbol("formContextKey"),Qi=Symbol("formItemContextKey"),ca=(e,t={})=>{const n=F(void 0),a=t.prop?n:WA("size"),r=t.global?n:bI(),o=t.form?{size:void 0}:Ze(Ec,void 0),i=t.formItem?{size:void 0}:Ze(Qi,void 0);return D(()=>a.value||p(e)||(i==null?void 0:i.size)||(o==null?void 0:o.size)||r.value||"")},$n=e=>{const t=WA("disabled"),n=Ze(Ec,void 0);return D(()=>t.value??p(e)??(n==null?void 0:n.disabled)??!1)},Ea=()=>({form:Ze(Ec,void 0),formItem:Ze(Qi,void 0)}),Yr=(e,{formItemContext:t,disableIdGeneration:n,disableIdManagement:a})=>{n||(n=F(!1)),a||(a=F(!1));const r=Et(),o=()=>{let u=r==null?void 0:r.parent;for(;u;){if(u.type.name==="ElFormItem")return!1;if(u.type.name==="ElLabelWrap")return!0;u=u.parent}return!1},i=F();let l;const s=D(()=>{var u;return!!(!(e.label||e.ariaLabel)&&t&&t.inputIds&&((u=t.inputIds)==null?void 0:u.length)<=1)});return mt(()=>{l=ye([cn(e,"id"),n],([u,c])=>{const d=u??(c?void 0:Fa().value);d!==i.value&&(t!=null&&t.removeInputId&&!o()&&(i.value&&t.removeInputId(i.value),!(a!=null&&a.value)&&!c&&d&&t.addInputId(d)),i.value=d)},{immediate:!0})}),Us(()=>{l&&l(),t!=null&&t.removeInputId&&i.value&&t.removeInputId(i.value)}),{isLabeledByFormItem:s,inputId:i}},Aie=["","error","validating","success"],Iie=$e({label:String,labelWidth:{type:[String,Number]},labelPosition:{type:String,values:["left","right","top",""],default:""},prop:{type:ae([String,Array])},required:{type:Boolean,default:void 0},rules:{type:ae([Object,Array])},error:String,validateStatus:{type:String,values:Aie},for:String,inlineMessage:{type:Boolean,default:void 0},showMessage:{type:Boolean,default:!0},size:{type:String,values:ii}}),m5=e=>[...new Set(e)],Pf=e=>Ae(e)?e[0]:e,or=e=>!e&&e!==0?[]:Ae(e)?e:[e],Lie="ElForm";function Pie(){const e=F([]),t=D(()=>{if(!e.value.length)return"0";const o=Math.max(...e.value);return o?`${o}px`:""});function n(o){const i=e.value.indexOf(o);return i===-1&&t.value==="0"&&un(Lie,`unexpected width ${o}`),i}function a(o,i){if(o&&i){const l=n(i);e.value.splice(l,1,o)}else o&&e.value.push(o)}function r(o){const i=n(o);i>-1&&e.value.splice(i,1)}return{autoLabelWidth:t,registerLabelWidth:a,deregisterLabelWidth:r}}const Rp=(e,t)=>{const n=wa(t).map(a=>Ae(a)?a.join("."):a);return n.length>0?e.filter(a=>a.propString&&n.includes(a.propString)):e},Dc="ElForm";var Die=q({name:Dc,__name:"form",props:Mie,emits:Eie,setup(e,{expose:t,emit:n}){const a=e,r=n,o=F(),i=nn([]),l=new Map,s=ca(),u=ke("form"),c=D(()=>{const{labelPosition:T,inline:M}=a;return[u.b(),u.m(s.value||"default"),{[u.m(`label-${T}`)]:T,[u.m("inline")]:M}]}),d=T=>Rp(i,[T])[0],v=T=>{i.includes(T)||i.push(T),T.propString&&(l.has(T.propString)?T.setInitialValue(l.get(T.propString)):l.set(T.propString,Li(T.fieldValue)))},f=(T,M)=>{if(M){l.delete(M);return}const E=i.indexOf(T);E>-1&&(i.splice(E,1),T.propString&&l.set(T.propString,Li(T.getInitialValue())))},h=T=>{if(!a.model){un(Dc,"model is required for setInitialValues to work.");return}if(!T){un(Dc,"initModel is required for setInitialValues to work.");return}for(const M of l.keys())l.set(M,Li(Cs(T,M).value));i.forEach(M=>{M.prop&&M.setInitialValue(Cs(T,M.prop).value)})},m=(T=[])=>{if(!a.model){un(Dc,"model is required for resetFields to work.");return}Rp(i,T).forEach(A=>A.resetField());const M=new Set(i.map(A=>A.propString).filter(Boolean)),E=T.length>0?wa(T).map(A=>Ae(A)?A.join("."):A):[...l.keys()];for(const A of E)!M.has(A)&&l.has(A)&&(Cs(a.model,A).value=Li(l.get(A)))},g=(T=[])=>{Rp(i,T).forEach(M=>M.clearValidate())},y=D(()=>{const T=!!a.model;return T||un(Dc,"model is required for validate to work."),T}),b=T=>{if(i.length===0)return[];const M=Rp(i,T);return M.length?M:(un(Dc,"please pass correct props!"),[])},_=async T=>S(void 0,T),w=async(T=[])=>{if(!y.value)return!1;const M=b(T);if(M.length===0)return!0;let E={};for(const A of M)try{await A.validate(""),A.validateState==="error"&&!A.error&&A.resetField()}catch(I){E={...E,...I}}return Object.keys(E).length===0?!0:Promise.reject(E)},S=async(T=[],M)=>{var I;let E=!1;const A=!Qe(M);try{return E=await w(T),E===!0&&await(M==null?void 0:M(E)),E}catch(P){if(P instanceof Error)throw P;const R=P;return a.scrollToError&&o.value&&((I=o.value.querySelector(`.${u.b()}-item.is-error`))==null||I.scrollIntoView(a.scrollIntoViewOptions)),!E&&await(M==null?void 0:M(!1,R)),A&&Promise.reject(R)}},k=T=>{var E;const M=d(T);M&&((E=M.$el)==null||E.scrollIntoView(a.scrollIntoViewOptions))};return ye(()=>a.rules,()=>{a.validateOnRuleChange&&_().catch(kt)},{deep:!0,flush:"post"}),Ft(Ec,nn({...Ta(a),emit:r,resetFields:m,clearValidate:g,validateField:S,getField:d,addField:v,removeField:f,setInitialValues:h,...Pie()})),t({validate:_,validateField:S,resetFields:m,clearValidate:g,scrollToField:k,getField:d,fields:i,setInitialValues:h}),(T,M)=>(x(),O("form",{ref_key:"formRef",ref:o,class:U(c.value)},[ce(T.$slots,"default")],2))}}),Oie=Die;const g5="ElLabelWrap";var $ie=q({name:g5,props:{isAutoWidth:Boolean,updateAll:Boolean},setup(e,{slots:t}){const n=Ze(Ec,void 0),a=Ze(Qi);a||Dn(g5,"usage: ");const r=ke("form"),o=F(),i=F(0),l=()=>{var c;if((c=o.value)!=null&&c.firstElementChild){const d=window.getComputedStyle(o.value.firstElementChild).width;return Math.ceil(Number.parseFloat(d))}else return 0},s=(c="update")=>{He(()=>{t.default&&e.isAutoWidth&&(c==="update"?i.value=l():c==="remove"&&(n==null||n.deregisterLabelWidth(i.value)))})},u=()=>s("update");return mt(()=>{u()}),rn(()=>{s("remove")}),oi(()=>u()),ye(i,(c,d)=>{e.updateAll&&(n==null||n.registerLabelWidth(c,d))}),Mn(D(()=>{var c;return((c=o.value)==null?void 0:c.firstElementChild)??null}),u),()=>{var d,v;if(!t)return null;const{isAutoWidth:c}=e;if(c){const f=n==null?void 0:n.autoLabelWidth,h=a==null?void 0:a.hasLabel,m={};if(h&&f&&f!=="auto"){const g=Math.max(0,Number.parseInt(f,10)-i.value),y=(a.labelPosition||n.labelPosition)==="left"?"marginRight":"marginLeft";g&&(m[y]=`${g}px`)}return L("div",{ref:o,class:[r.be("item","label-wrap")],style:m},[(d=t.default)==null?void 0:d.call(t)])}else return L(Ve,{ref:o},[(v=t.default)==null?void 0:v.call(t)])}}});function $u(){return $u=Object.assign?Object.assign.bind():function(e){for(var t=1;t"u"||!Reflect.construct||Reflect.construct.sham)return!1;if(typeof Proxy=="function")return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){})),!0}catch{return!1}}function J0(e,t,n){return Nie()?J0=Reflect.construct.bind():J0=function(r,o,i){var l=[null];l.push.apply(l,o);var s=Function.bind.apply(r,l),u=new s;return i&&vh(u,i.prototype),u},J0.apply(null,arguments)}function Bie(e){return Function.toString.call(e).indexOf("[native code]")!==-1}function b4(e){var t=typeof Map=="function"?new Map:void 0;return b4=function(a){if(a===null||!Bie(a))return a;if(typeof a!="function")throw new TypeError("Super expression must either be null or a function");if(typeof t<"u"){if(t.has(a))return t.get(a);t.set(a,r)}function r(){return J0(a,arguments,y4(this).constructor)}return r.prototype=Object.create(a.prototype,{constructor:{value:r,enumerable:!1,writable:!0,configurable:!0}}),vh(r,a)},b4(e)}var Vie=/%[sdj%]/g,zie=function(){};function _4(e){if(!e||!e.length)return null;var t={};return e.forEach(function(n){var a=n.field;t[a]=t[a]||[],t[a].push(n)}),t}function to(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),a=1;a=o)return l;switch(l){case"%s":return String(n[r++]);case"%d":return Number(n[r++]);case"%j":try{return JSON.stringify(n[r++])}catch{return"[Circular]"}break;default:return l}});return i}return e}function Fie(e){return e==="string"||e==="url"||e==="hex"||e==="email"||e==="date"||e==="pattern"}function Da(e,t){return!!(e==null||t==="array"&&Array.isArray(e)&&!e.length||Fie(t)&&typeof e=="string"&&!e)}function Hie(e,t,n){var a=[],r=0,o=e.length;function i(l){a.push.apply(a,l||[]),r++,r===o&&n(a)}e.forEach(function(l){t(l,i)})}function y5(e,t,n){var a=0,r=e.length;function o(i){if(i&&i.length){n(i);return}var l=a;a=a+1,l()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+\.)+[a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]{2,}))$/,hex:/^#?([a-f0-9]{6}|[a-f0-9]{3})$/i},ov={integer:function(t){return ov.number(t)&&parseInt(t,10)===t},float:function(t){return ov.number(t)&&!ov.integer(t)},array:function(t){return Array.isArray(t)},regexp:function(t){if(t instanceof RegExp)return!0;try{return!!new RegExp(t)}catch{return!1}},date:function(t){return typeof t.getTime=="function"&&typeof t.getMonth=="function"&&typeof t.getYear=="function"&&!isNaN(t.getTime())},number:function(t){return isNaN(t)?!1:typeof t=="number"},object:function(t){return typeof t=="object"&&!ov.array(t)},method:function(t){return typeof t=="function"},email:function(t){return typeof t=="string"&&t.length<=320&&!!t.match(S5.email)},url:function(t){return typeof t=="string"&&t.length<=2048&&!!t.match(jie())},hex:function(t){return typeof t=="string"&&!!t.match(S5.hex)}},qie=function(t,n,a,r,o){if(t.required&&n===void 0){nL(t,n,a,r,o);return}var i=["integer","float","array","regexp","object","method","email","number","date","url","hex"],l=t.type;i.indexOf(l)>-1?ov[l](n)||r.push(to(o.messages.types[l],t.fullField,t.type)):l&&typeof n!==t.type&&r.push(to(o.messages.types[l],t.fullField,t.type))},Xie=function(t,n,a,r,o){var i=typeof t.len=="number",l=typeof t.min=="number",s=typeof t.max=="number",u=/[\uD800-\uDBFF][\uDC00-\uDFFF]/g,c=n,d=null,v=typeof n=="number",f=typeof n=="string",h=Array.isArray(n);if(v?d="number":f?d="string":h&&(d="array"),!d)return!1;h&&(c=n.length),f&&(c=n.replace(u,"_").length),i?c!==t.len&&r.push(to(o.messages[d].len,t.fullField,t.len)):l&&!s&&ct.max?r.push(to(o.messages[d].max,t.fullField,t.max)):l&&s&&(ct.max)&&r.push(to(o.messages[d].range,t.fullField,t.min,t.max))},Oc="enum",Zie=function(t,n,a,r,o){t[Oc]=Array.isArray(t[Oc])?t[Oc]:[],t[Oc].indexOf(n)===-1&&r.push(to(o.messages[Oc],t.fullField,t[Oc].join(", ")))},Jie=function(t,n,a,r,o){if(t.pattern){if(t.pattern instanceof RegExp)t.pattern.lastIndex=0,t.pattern.test(n)||r.push(to(o.messages.pattern.mismatch,t.fullField,n,t.pattern));else if(typeof t.pattern=="string"){var i=new RegExp(t.pattern);i.test(n)||r.push(to(o.messages.pattern.mismatch,t.fullField,n,t.pattern))}}},Cn={required:nL,whitespace:Yie,type:qie,range:Xie,enum:Zie,pattern:Jie},Qie=function(t,n,a,r,o){var i=[],l=t.required||!t.required&&r.hasOwnProperty(t.field);if(l){if(Da(n,"string")&&!t.required)return a();Cn.required(t,n,r,i,o,"string"),Da(n,"string")||(Cn.type(t,n,r,i,o),Cn.range(t,n,r,i,o),Cn.pattern(t,n,r,i,o),t.whitespace===!0&&Cn.whitespace(t,n,r,i,o))}a(i)},ele=function(t,n,a,r,o){var i=[],l=t.required||!t.required&&r.hasOwnProperty(t.field);if(l){if(Da(n)&&!t.required)return a();Cn.required(t,n,r,i,o),n!==void 0&&Cn.type(t,n,r,i,o)}a(i)},tle=function(t,n,a,r,o){var i=[],l=t.required||!t.required&&r.hasOwnProperty(t.field);if(l){if(n===""&&(n=void 0),Da(n)&&!t.required)return a();Cn.required(t,n,r,i,o),n!==void 0&&(Cn.type(t,n,r,i,o),Cn.range(t,n,r,i,o))}a(i)},nle=function(t,n,a,r,o){var i=[],l=t.required||!t.required&&r.hasOwnProperty(t.field);if(l){if(Da(n)&&!t.required)return a();Cn.required(t,n,r,i,o),n!==void 0&&Cn.type(t,n,r,i,o)}a(i)},ale=function(t,n,a,r,o){var i=[],l=t.required||!t.required&&r.hasOwnProperty(t.field);if(l){if(Da(n)&&!t.required)return a();Cn.required(t,n,r,i,o),Da(n)||Cn.type(t,n,r,i,o)}a(i)},rle=function(t,n,a,r,o){var i=[],l=t.required||!t.required&&r.hasOwnProperty(t.field);if(l){if(Da(n)&&!t.required)return a();Cn.required(t,n,r,i,o),n!==void 0&&(Cn.type(t,n,r,i,o),Cn.range(t,n,r,i,o))}a(i)},ole=function(t,n,a,r,o){var i=[],l=t.required||!t.required&&r.hasOwnProperty(t.field);if(l){if(Da(n)&&!t.required)return a();Cn.required(t,n,r,i,o),n!==void 0&&(Cn.type(t,n,r,i,o),Cn.range(t,n,r,i,o))}a(i)},ile=function(t,n,a,r,o){var i=[],l=t.required||!t.required&&r.hasOwnProperty(t.field);if(l){if(n==null&&!t.required)return a();Cn.required(t,n,r,i,o,"array"),n!=null&&(Cn.type(t,n,r,i,o),Cn.range(t,n,r,i,o))}a(i)},lle=function(t,n,a,r,o){var i=[],l=t.required||!t.required&&r.hasOwnProperty(t.field);if(l){if(Da(n)&&!t.required)return a();Cn.required(t,n,r,i,o),n!==void 0&&Cn.type(t,n,r,i,o)}a(i)},sle="enum",ule=function(t,n,a,r,o){var i=[],l=t.required||!t.required&&r.hasOwnProperty(t.field);if(l){if(Da(n)&&!t.required)return a();Cn.required(t,n,r,i,o),n!==void 0&&Cn[sle](t,n,r,i,o)}a(i)},cle=function(t,n,a,r,o){var i=[],l=t.required||!t.required&&r.hasOwnProperty(t.field);if(l){if(Da(n,"string")&&!t.required)return a();Cn.required(t,n,r,i,o),Da(n,"string")||Cn.pattern(t,n,r,i,o)}a(i)},dle=function(t,n,a,r,o){var i=[],l=t.required||!t.required&&r.hasOwnProperty(t.field);if(l){if(Da(n,"date")&&!t.required)return a();if(Cn.required(t,n,r,i,o),!Da(n,"date")){var s;n instanceof Date?s=n:s=new Date(n),Cn.type(t,s,r,i,o),s&&Cn.range(t,s.getTime(),r,i,o)}}a(i)},fle=function(t,n,a,r,o){var i=[],l=Array.isArray(n)?"array":typeof n;Cn.required(t,n,r,i,o,l),a(i)},Y2=function(t,n,a,r,o){var i=t.type,l=[],s=t.required||!t.required&&r.hasOwnProperty(t.field);if(s){if(Da(n,i)&&!t.required)return a();Cn.required(t,n,r,l,o,i),Da(n,i)||Cn.type(t,n,r,l,o)}a(l)},vle=function(t,n,a,r,o){var i=[],l=t.required||!t.required&&r.hasOwnProperty(t.field);if(l){if(Da(n)&&!t.required)return a();Cn.required(t,n,r,i,o)}a(i)},Pv={string:Qie,method:ele,number:tle,boolean:nle,regexp:ale,integer:rle,float:ole,array:ile,object:lle,enum:ule,pattern:cle,date:dle,url:Y2,hex:Y2,email:Y2,required:fle,any:vle};function w4(){return{default:"Validation error on field %s",required:"%s is required",enum:"%s must be one of %s",whitespace:"%s cannot be empty",date:{format:"%s date %s is invalid for format %s",parse:"%s date could not be parsed, %s is invalid ",invalid:"%s date %s is invalid"},types:{string:"%s is not a %s",method:"%s is not a %s (function)",array:"%s is not an %s",object:"%s is not an %s",number:"%s is not a %s",date:"%s is not a %s",boolean:"%s is not a %s",integer:"%s is not an %s",float:"%s is not a %s",regexp:"%s is not a valid %s",email:"%s is not a valid %s",url:"%s is not a valid %s",hex:"%s is not a valid %s"},string:{len:"%s must be exactly %s characters",min:"%s must be at least %s characters",max:"%s cannot be longer than %s characters",range:"%s must be between %s and %s characters"},number:{len:"%s must equal %s",min:"%s cannot be less than %s",max:"%s cannot be greater than %s",range:"%s must be between %s and %s"},array:{len:"%s must be exactly %s in length",min:"%s cannot be less than %s in length",max:"%s cannot be greater than %s in length",range:"%s must be between %s and %s in length"},pattern:{mismatch:"%s value %s does not match pattern %s"},clone:function(){var t=JSON.parse(JSON.stringify(this));return t.clone=this.clone,t}}}var S4=w4(),lp=function(){function e(n){this.rules=null,this._messages=S4,this.define(n)}var t=e.prototype;return t.define=function(a){var r=this;if(!a)throw new Error("Cannot configure a schema with no rules");if(typeof a!="object"||Array.isArray(a))throw new Error("Rules must be an object");this.rules={},Object.keys(a).forEach(function(o){var i=a[o];r.rules[o]=Array.isArray(i)?i:[i]})},t.messages=function(a){return a&&(this._messages=w5(w4(),a)),this._messages},t.validate=function(a,r,o){var i=this;r===void 0&&(r={}),o===void 0&&(o=function(){});var l=a,s=r,u=o;if(typeof s=="function"&&(u=s,s={}),!this.rules||Object.keys(this.rules).length===0)return u&&u(null,l),Promise.resolve(l);function c(m){var g=[],y={};function b(w){if(Array.isArray(w)){var S;g=(S=g).concat.apply(S,w)}else g.push(w)}for(var _=0;_n.labelPosition||(r==null?void 0:r.labelPosition)),y=D(()=>g.value==="top"?{}:{width:Vn(n.labelWidth??(r==null?void 0:r.labelWidth))}),b=D(()=>{if(g.value==="top"||r!=null&&r.inline)return{};if(!n.label&&!n.labelWidth&&A)return{};const J=Vn(n.labelWidth??(r==null?void 0:r.labelWidth));return!n.label&&!a.label?{marginLeft:J}:{}}),_=D(()=>[l.b(),l.m(i.value),l.is("error",c.value==="error"),l.is("validating",c.value==="validating"),l.is("success",c.value==="success"),l.is("required",K.value||n.required),l.is("no-asterisk",r==null?void 0:r.hideRequiredAsterisk),(r==null?void 0:r.requireAsteriskPosition)==="right"?"asterisk-right":"asterisk-left",{[l.m("feedback")]:r==null?void 0:r.statusIcon,[l.m(`label-${g.value}`)]:g.value}]),w=D(()=>vn(n.inlineMessage)?n.inlineMessage:(r==null?void 0:r.inlineMessage)||!1),S=D(()=>[l.e("error"),{[l.em("error","inline")]:w.value}]),k=D(()=>n.prop?Ae(n.prop)?n.prop.join("."):n.prop:""),T=D(()=>!!(n.label||a.label)),M=D(()=>n.for??(u.value.length===1?u.value[0]:void 0)),E=D(()=>!M.value&&T.value),A=!!o,I=D(()=>{const J=r==null?void 0:r.model;if(!(!J||!n.prop))return Cs(J,n.prop).value}),P=D(()=>{const{required:J}=n,Z=[];n.rules&&Z.push(...wa(n.rules));const j=r==null?void 0:r.rules;if(j&&n.prop){const oe=Cs(j,n.prop).value;oe&&Z.push(...wa(oe))}if(J!==void 0){const oe=Z.map((se,fe)=>[se,fe]).filter(([se])=>"required"in se);if(oe.length>0)for(const[se,fe]of oe)se.required!==J&&(Z[fe]={...se,required:J});else Z.push({required:J})}return Z}),R=D(()=>P.value.length>0),G=J=>P.value.filter(Z=>!Z.trigger||!J?!0:Ae(Z.trigger)?Z.trigger.includes(J):Z.trigger===J).map(({trigger:Z,...j})=>j),K=D(()=>P.value.some(J=>J.required)),H=D(()=>d.value==="error"&&n.showMessage&&((r==null?void 0:r.showMessage)??!0)),z=D(()=>`${n.label||""}${(r==null?void 0:r.labelSuffix)||""}`),B=J=>{c.value=J},W=J=>{var oe;const{errors:Z,fields:j}=J;(!Z||!j)&&console.error(J),B("error"),v.value=Z?((oe=Z==null?void 0:Z[0])==null?void 0:oe.message)??`${n.prop} is required`:"",r==null||r.emit("validate",n.prop,!1,v.value)},N=()=>{B("success"),r==null||r.emit("validate",n.prop,!0,"")},V=async J=>{const Z=k.value;return new lp({[Z]:J}).validate({[Z]:I.value},{firstFields:!0}).then(()=>(N(),!0)).catch(j=>(W(j),Promise.reject(j)))},Y=async(J,Z)=>{if(m||!n.prop)return!1;const j=Qe(Z);if(!R.value)return Z==null||Z(!1),!1;const oe=G(J);return oe.length===0?(Z==null||Z(!0),!0):(B("validating"),V(oe).then(()=>(Z==null||Z(!0),!0)).catch(se=>{const{fields:fe}=se;return Z==null||Z(!1,fe),j?!1:Promise.reject(fe)}))},X=()=>{B(""),v.value="",m=!1},ee=async()=>{const J=r==null?void 0:r.model;if(!J||!n.prop)return;const Z=Cs(J,n.prop);m=!0,Z.value=Li(h),await He(),X(),m=!1},re=J=>{u.value.includes(J)||u.value.push(J)},ie=J=>{u.value=u.value.filter(Z=>Z!==J)},le=J=>{h=Li(J)},ve=()=>h;ye(()=>n.error,J=>{v.value=J||"",B(J?"error":"")},{immediate:!0}),ye(()=>n.validateStatus,J=>B(J||""));const he=nn({...Ta(n),$el:f,size:i,validateMessage:v,validateState:c,labelId:s,inputIds:u,isGroup:E,hasLabel:T,fieldValue:I,addInputId:re,removeInputId:ie,resetField:ee,clearValidate:X,validate:Y,propString:k,setInitialValue:le,getInitialValue:ve});return Ft(Qi,he),ye(k,(J,Z)=>{!r||!Z||(r.removeField(he,Z),J&&(le(I.value),r.addField(he)))}),mt(()=>{n.prop&&(le(I.value),r==null||r.addField(he))}),rn(()=>{r==null||r.removeField(he)}),t({size:i,validateMessage:v,validateState:c,validate:Y,clearValidate:X,resetField:ee,setInitialValue:le}),(J,Z)=>{var j;return x(),O("div",{ref_key:"formItemRef",ref:f,class:U(_.value),role:E.value?"group":void 0,"aria-labelledby":E.value?p(s):void 0},[L(p($ie),{"is-auto-width":y.value.width==="auto","update-all":((j=p(r))==null?void 0:j.labelWidth)==="auto"},{default:$(()=>[e.label||J.$slots.label?(x(),ne(St(M.value?"label":"div"),{key:0,id:p(s),for:M.value,class:U(p(l).e("label")),style:et(y.value)},{default:$(()=>[ce(J.$slots,"label",{label:z.value},()=>[ue(Q(z.value),1)])]),_:3},8,["id","for","class","style"])):te("v-if",!0)]),_:3},8,["is-auto-width","update-all"]),C("div",{class:U(p(l).e("content")),style:et(b.value)},[ce(J.$slots,"default"),L(L7,{name:`${p(l).namespace.value}-zoom-in-top`},{default:$(()=>[H.value?ce(J.$slots,"error",{key:0,error:v.value},()=>[C("div",{class:U(S.value)},Q(v.value),3)]):te("v-if",!0)]),_:3},8,["name"])],6)],10,hle)}}}),aL=ple;const mle=wt(Oie,{FormItem:aL}),gle=On(aL),j2="focus-trap.focus-after-trapped",q2="focus-trap.focus-after-released",yle="focus-trap.focusout-prevented",C5={cancelable:!0,bubbles:!1},ble={cancelable:!0,bubbles:!1},x5="focusAfterTrapped",k5="focusAfterReleased",rL=Symbol("elFocusTrap"),q6=F(),H1=F(0),X6=F(0);let Bp=0;const oL=e=>{const t=[],n=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT,{acceptNode:a=>{const r=a.tagName==="INPUT"&&a.type==="hidden";return a.disabled||a.hidden||r?NodeFilter.FILTER_SKIP:a.tabIndex>=0||a===document.activeElement?NodeFilter.FILTER_ACCEPT:NodeFilter.FILTER_SKIP}});for(;n.nextNode();)t.push(n.currentNode);return t},T5=(e,t)=>{for(const n of e)if(!_le(n,t))return n},_le=(e,t)=>{if(getComputedStyle(e).visibility==="hidden")return!0;for(;e;){if(t&&e===t)return!1;if(getComputedStyle(e).display==="none")return!0;e=e.parentElement}return!1},wle=e=>{const t=oL(e);return[T5(t,e),T5(t.reverse(),e)]},Sle=e=>e instanceof HTMLInputElement&&"select"in e,os=(e,t)=>{if(e){const n=document.activeElement;Jh(e,{preventScroll:!0}),X6.value=window.performance.now(),e!==n&&Sle(e)&&t&&e.select()}};function M5(e,t){const n=[...e],a=e.indexOf(t);return a!==-1&&n.splice(a,1),n}const Cle=()=>{let e=[];return{push:a=>{const r=e[0];r&&a!==r&&r.pause(),e=M5(e,a),e.unshift(a)},remove:a=>{var r,o;e=M5(e,a),(o=(r=e[0])==null?void 0:r.resume)==null||o.call(r)}}},xle=(e,t=!1)=>{const n=document.activeElement;for(const a of e)if(os(a,t),document.activeElement!==n)return},E5=Cle(),kle=()=>H1.value>X6.value,Vp=()=>{q6.value="pointer",H1.value=window.performance.now()},A5=()=>{q6.value="keyboard",H1.value=window.performance.now()},Tle=()=>(mt(()=>{Bp===0&&(document.addEventListener("mousedown",Vp),document.addEventListener("touchstart",Vp),document.addEventListener("keydown",A5)),Bp++}),rn(()=>{Bp--,Bp<=0&&(document.removeEventListener("mousedown",Vp),document.removeEventListener("touchstart",Vp),document.removeEventListener("keydown",A5))}),{focusReason:q6,lastUserFocusTimestamp:H1,lastAutomatedFocusTimestamp:X6}),zp=e=>new CustomEvent(yle,{...ble,detail:e});var Mle=q({name:"ElFocusTrap",inheritAttrs:!1,props:{loop:Boolean,trapped:Boolean,focusTrapEl:Object,focusStartEl:{type:[Object,String],default:"first"}},emits:[x5,k5,"focusin","focusout","focusout-prevented","release-requested"],setup(e,{emit:t}){const n=F();let a,r;const{focusReason:o}=Tle();tq(h=>{e.trapped&&!i.paused&&t("release-requested",h)});const i={paused:!1,pause(){this.paused=!0},resume(){this.paused=!1}},l=h=>{if(!e.loop&&!e.trapped||i.paused)return;const{altKey:m,ctrlKey:g,metaKey:y,currentTarget:b,shiftKey:_}=h,{loop:w}=e,S=mn(h)===Pe.tab&&!m&&!g&&!y,k=document.activeElement;if(S&&k){const T=b,[M,E]=wle(T);if(M&&E){if(!_&&k===E){const A=zp({focusReason:o.value});t("focusout-prevented",A),A.defaultPrevented||(h.preventDefault(),w&&os(M,!0))}else if(_&&[M,T].includes(k)){const A=zp({focusReason:o.value});t("focusout-prevented",A),A.defaultPrevented||(h.preventDefault(),w&&os(E,!0))}}else if(k===T){const A=zp({focusReason:o.value});t("focusout-prevented",A),A.defaultPrevented||h.preventDefault()}}};Ft(rL,{focusTrapRef:n,onKeydown:l}),ye(()=>e.focusTrapEl,h=>{h&&(n.value=h)},{immediate:!0}),ye([n],([h],[m])=>{h&&(h.addEventListener("keydown",l),h.addEventListener("focusin",c),h.addEventListener("focusout",d)),m&&(m.removeEventListener("keydown",l),m.removeEventListener("focusin",c),m.removeEventListener("focusout",d))});const s=h=>{t(x5,h)},u=h=>t(k5,h),c=h=>{const m=p(n);if(!m)return;const g=h.target,y=h.relatedTarget,b=g&&m.contains(g);e.trapped||y&&m.contains(y)||(a=y),b&&t("focusin",h),!i.paused&&e.trapped&&(b?r=g:os(r,!0))},d=h=>{const m=p(n);if(!(i.paused||!m))if(e.trapped){const g=h.relatedTarget;!Un(g)&&!m.contains(g)&&setTimeout(()=>{if(!i.paused&&e.trapped){const y=zp({focusReason:o.value});t("focusout-prevented",y),y.defaultPrevented||os(r,!0)}},0)}else{const g=h.target;g&&m.contains(g)||t("focusout",h)}};async function v(){await He();const h=p(n);if(h){E5.push(i);const m=h.contains(document.activeElement)?a:document.activeElement;if(a=m,!h.contains(m)){const g=new Event(j2,C5);h.addEventListener(j2,s),h.dispatchEvent(g),g.defaultPrevented||He(()=>{let y=e.focusStartEl;Ye(y)||(os(y),document.activeElement!==y&&(y="first")),y==="first"&&xle(oL(h),!0),(document.activeElement===m||y==="container")&&os(h)})}}}function f(){const h=p(n);if(h){h.removeEventListener(j2,s);const m=new CustomEvent(q2,{...C5,detail:{focusReason:o.value}});h.addEventListener(q2,u),h.dispatchEvent(m),!m.defaultPrevented&&(o.value=="keyboard"||!kle()||h.contains(document.activeElement))&&os(a??document.body),h.removeEventListener(q2,u),E5.remove(i),a=null,r=null}}return mt(()=>{e.trapped&&v(),ye(()=>e.trapped,h=>{h?v():f()})}),rn(()=>{e.trapped&&f(),n.value&&(n.value.removeEventListener("keydown",l),n.value.removeEventListener("focusin",c),n.value.removeEventListener("focusout",d),n.value=void 0),a=null,r=null}),{onKeydown:l}}}),ya=(e,t)=>{const n=e.__vccOpts||e;for(const[a,r]of t)n[a]=r;return n};function Ele(e,t,n,a,r,o){return ce(e.$slots,"default",{handleKeydown:e.onKeydown})}var Ale=ya(Mle,[["render",Ele]]),pf=Ale;const Ile=(e,t=[])=>{const{placement:n,strategy:a,popperOptions:r}=e,o={placement:n,strategy:a,...r,modifiers:[...Ple(e),...t]};return Dle(o,r==null?void 0:r.modifiers),o},Lle=e=>{if(qt)return da(e)};function Ple(e){const{offset:t,gpuAcceleration:n,fallbackPlacements:a}=e;return[{name:"offset",options:{offset:[0,t??12]}},{name:"preventOverflow",options:{padding:{top:0,bottom:0,left:0,right:0}}},{name:"flip",options:{padding:5,fallbackPlacements:a}},{name:"computeStyles",options:{gpuAcceleration:n}}]}function Dle(e,t){t&&(e.modifiers=[...e.modifiers,...t??[]])}const Ole=0,$le=e=>{const{popperInstanceRef:t,contentRef:n,triggerRef:a,role:r}=Ze(j6,void 0),o=F(),i=D(()=>e.arrowOffset),l=D(()=>({name:"eventListeners",enabled:!!e.visible})),s=D(()=>{const b=p(o),_=p(i)??Ole;return{name:"arrow",enabled:!CA(b),options:{element:b,padding:_}}}),u=D(()=>({onFirstUpdate:()=>{h()},...Ile(e,[p(s),p(l)])})),c=D(()=>Lle(e.referenceEl)||p(a)),{attributes:d,state:v,styles:f,update:h,forceUpdate:m,instanceRef:g}=Zj(c,n,u);ye(g,b=>t.value=b,{flush:"sync"}),mt(()=>{ye(()=>{var b,_;return(_=(b=p(c))==null?void 0:b.getBoundingClientRect)==null?void 0:_.call(b)},()=>{h()})});let y;return ye(()=>e.visible,b=>{y==null||y(),y=void 0,b&&(y=Mn(n,h).stop)}),rn(()=>{t.value=void 0,y==null||y(),y=void 0}),{attributes:d,arrowRef:o,contentRef:n,instanceRef:g,state:v,styles:f,role:r,forceUpdate:m,update:h}},Rle=(e,{attributes:t,styles:n,role:a})=>{const{nextZIndex:r}=tp(),o=ke("popper"),i=D(()=>p(t).popper),l=F(Je(e.zIndex)?e.zIndex:r()),s=D(()=>[o.b(),o.is("pure",e.pure),o.is(e.effect),e.popperClass]),u=D(()=>[{zIndex:p(l)},p(n).popper,e.popperStyle||{}]),c=D(()=>a.value==="dialog"?"false":void 0),d=D(()=>p(n).arrow||{});return{ariaModal:c,arrowStyle:d,contentAttrs:i,contentClass:s,contentStyle:u,contentZIndex:l,updateZIndex:()=>{l.value=Je(e.zIndex)?e.zIndex:r()}}},Nle=(e,t)=>{const n=F(!1),a=F(),r=()=>{t("focus")},o=u=>{var c;((c=u.detail)==null?void 0:c.focusReason)!=="pointer"&&(a.value="first",t("blur"))},i=u=>{e.visible&&!n.value&&(u.target&&(a.value=u.target),n.value=!0)},l=u=>{e.trapping||(u.detail.focusReason==="pointer"&&u.preventDefault(),n.value=!1)},s=()=>{n.value=!1,t("close")};return rn(()=>{a.value=void 0}),{focusStartRef:a,trapped:n,onFocusAfterReleased:o,onFocusAfterTrapped:r,onFocusInTrap:i,onFocusoutPrevented:l,onReleaseRequested:s}};var Ble=q({name:"ElPopperContent",__name:"content",props:tL,emits:kie,setup(e,{expose:t,emit:n}){const a=n,r=e,{focusStartRef:o,trapped:i,onFocusAfterReleased:l,onFocusAfterTrapped:s,onFocusInTrap:u,onFocusoutPrevented:c,onReleaseRequested:d}=Nle(r,a),{attributes:v,arrowRef:f,contentRef:h,styles:m,instanceRef:g,role:y,update:b}=$le(r),{ariaModal:_,arrowStyle:w,contentAttrs:S,contentClass:k,contentStyle:T,updateZIndex:M}=Rle(r,{styles:m,attributes:v,role:y}),E=Ze(Qi,void 0);Ft(XI,{arrowStyle:w,arrowRef:f}),E&&Ft(Qi,{...E,addInputId:kt,removeInputId:kt});let A;const I=(R=!0)=>{b(),R&&M()},P=()=>{I(!1),r.visible&&r.focusOnShow?i.value=!0:r.visible===!1&&(i.value=!1)};return mt(()=>{ye(()=>r.triggerTargetEl,(R,G)=>{A==null||A(),A=void 0;const K=p(R||h.value),H=p(G||h.value);Vr(K)&&(A=ye([y,()=>r.ariaLabel,_,()=>r.id],z=>{["role","aria-label","aria-modal","id"].forEach((B,W)=>{Un(z[W])?K.removeAttribute(B):K.setAttribute(B,z[W])})},{immediate:!0})),H!==K&&Vr(H)&&["role","aria-label","aria-modal","id"].forEach(z=>{H.removeAttribute(z)})},{immediate:!0}),ye(()=>r.visible,P,{immediate:!0})}),rn(()=>{A==null||A(),A=void 0,h.value=void 0}),t({popperContentRef:h,popperInstanceRef:g,updatePopper:I,contentStyle:T}),(R,G)=>(x(),O("div",Tt({ref_key:"contentRef",ref:h},p(S),{style:p(T),class:p(k),tabindex:"-1",onMouseenter:G[0]||(G[0]=K=>R.$emit("mouseenter",K)),onMouseleave:G[1]||(G[1]=K=>R.$emit("mouseleave",K))}),[L(p(pf),{loop:e.loop,trapped:p(i),"trap-on-focus-in":!0,"focus-trap-el":p(h),"focus-start-el":p(o),onFocusAfterTrapped:p(s),onFocusAfterReleased:p(l),onFocusin:p(u),onFocusoutPrevented:p(c),onReleaseRequested:p(d)},{default:$(()=>[ce(R.$slots,"default")]),_:3},8,["loop","trapped","focus-trap-el","focus-start-el","onFocusAfterTrapped","onFocusAfterReleased","onFocusin","onFocusoutPrevented","onReleaseRequested"])],16))}}),Vle=Ble,zle=q({name:"ElPopper",inheritAttrs:!1,__name:"popper",props:qI,setup(e,{expose:t}){const n=e,a={triggerRef:F(),popperInstanceRef:F(),contentRef:F(),referenceRef:F(),role:D(()=>n.role)};return t(a),Ft(j6,a),(r,o)=>ce(r.$slots,"default")}}),Fle=zle;const iL=wt(Fle),Hle={...oq,...xie,content:"",visible:null,teleported:!0},Tn=$e({...rq,...tL,appendTo:{type:ae([String,Object])},content:{type:String,default:""},rawContent:Boolean,persistent:Boolean,visible:{type:ae(Boolean),default:null},transition:String,teleported:{type:Boolean,default:!0},disabled:Boolean,...dr(["ariaLabel"])}),qu=$e({...ZI,disabled:Boolean,trigger:{type:ae([String,Array]),default:"hover"},triggerKeys:{type:ae(Array),default:()=>[Pe.enter,Pe.numpadEnter,Pe.space]},focusOnTarget:Boolean}),{useModelToggleProps:Wle,useModelToggleEmits:Kle,useModelToggle:Ule}=jY("visible"),iv=$e({...qI,...Wle,...Tn,...qu,...eL,showArrow:{type:Boolean,default:!0}}),Gle=[...Kle,"before-show","before-hide","show","hide","open","close"],Z6=Symbol("elTooltip"),C4=(e,t)=>Ae(e)?e.includes(t):e===t,$c=(e,t,n)=>a=>{C4(p(e),t)&&n(a)};var Yle=q({name:"ElTooltipTrigger",__name:"trigger",props:qu,setup(e,{expose:t}){const n=e,a=ke("tooltip"),{controlled:r,id:o,open:i,onOpen:l,onClose:s,onToggle:u}=Ze(Z6,void 0),c=F(null),d=()=>{if(p(r)||n.disabled)return!0},v=cn(n,"trigger"),f=_a(d,$c(v,"hover",w=>{l(w),n.focusOnTarget&&w.target&&He(()=>{Jh(w.target,{preventScroll:!0})})})),h=_a(d,$c(v,"hover",s)),m=_a(d,$c(v,"click",w=>{w.button===0&&u(w)})),g=_a(d,$c(v,"focus",l)),y=_a(d,$c(v,"focus",s)),b=_a(d,$c(v,"contextmenu",w=>{w.preventDefault(),u(w)})),_=_a(d,w=>{const S=mn(w);n.triggerKeys.includes(S)&&(w.preventDefault(),u(w))});return t({triggerRef:c}),(w,S)=>(x(),ne(p(_ie),{id:p(o),"virtual-ref":e.virtualRef,open:p(i),"virtual-triggering":e.virtualTriggering,class:U(p(a).e("trigger")),onBlur:p(y),onClick:p(m),onContextmenu:p(b),onFocus:p(g),onMouseenter:p(f),onMouseleave:p(h),onKeydown:p(_)},{default:$(()=>[ce(w.$slots,"default")]),_:3},8,["id","virtual-ref","open","virtual-triggering","class","onBlur","onClick","onContextmenu","onFocus","onMouseenter","onMouseleave","onKeydown"]))}}),jle=Yle,qle=q({name:"ElTooltipContent",inheritAttrs:!1,__name:"content",props:Tn,setup(e,{expose:t}){const n=e,{selector:a}=nI(),r=ke("tooltip"),o=F(),i=EA(()=>{var B;return(B=o.value)==null?void 0:B.popperContentRef});let l;const{controlled:s,id:u,open:c,trigger:d,onClose:v,onOpen:f,onShow:h,onHide:m,onBeforeShow:g,onBeforeHide:y}=Ze(Z6,void 0),b=D(()=>n.transition||`${r.namespace.value}-fade-in-linear`),_=D(()=>n.persistent);rn(()=>{l==null||l()});const w=D(()=>p(_)?!0:p(c)),S=D(()=>n.disabled?!1:p(c)),k=D(()=>n.appendTo||a.value),T=D(()=>n.style??{}),M=F(!0),E=()=>{m(),z()&&Jh(document.body,{preventScroll:!0}),M.value=!0},A=()=>{if(p(s))return!0},I=_a(A,()=>{n.enterable&&C4(p(d),"hover")&&f()}),P=_a(A,()=>{C4(p(d),"hover")&&v()}),R=()=>{var B,W;(W=(B=o.value)==null?void 0:B.updatePopper)==null||W.call(B),g==null||g()},G=()=>{y==null||y()},K=()=>{h()},H=()=>{n.virtualTriggering||v()},z=B=>{var V;const W=(V=o.value)==null?void 0:V.popperContentRef,N=(B==null?void 0:B.relatedTarget)||document.activeElement;return W==null?void 0:W.contains(N)};return ye(()=>p(c),B=>{B?(M.value=!1,l=b6(i,()=>{p(s)||or(p(d)).every(W=>W!=="hover"&&W!=="focus")&&v()},{detectIframe:!0})):l==null||l()},{flush:"post"}),t({contentRef:o,isFocusInsideContent:z}),(B,W)=>(x(),ne(sf,{disabled:!e.teleported,to:k.value},[w.value||!M.value?(x(),ne(Pa,{key:0,name:b.value,appear:!_.value,onAfterLeave:E,onBeforeEnter:R,onAfterEnter:K,onBeforeLeave:G,persisted:""},{default:$(()=>[st(L(p(Vle),Tt({id:p(u),ref_key:"contentRef",ref:o},B.$attrs,{"aria-label":e.ariaLabel,"aria-hidden":M.value,"boundaries-padding":e.boundariesPadding,"fallback-placements":e.fallbackPlacements,"gpu-acceleration":e.gpuAcceleration,offset:e.offset,placement:e.placement,"popper-options":e.popperOptions,"arrow-offset":e.arrowOffset,strategy:e.strategy,effect:e.effect,enterable:e.enterable,pure:e.pure,"popper-class":e.popperClass,"popper-style":[e.popperStyle,T.value],"reference-el":e.referenceEl,"trigger-target-el":e.triggerTargetEl,visible:S.value,"z-index":e.zIndex,loop:e.loop,onMouseenter:p(I),onMouseleave:p(P),onBlur:H,onClose:p(v)}),{default:$(()=>[ce(B.$slots,"default")]),_:3},16,["id","aria-label","aria-hidden","boundaries-padding","fallback-placements","gpu-acceleration","offset","placement","popper-options","arrow-offset","strategy","effect","enterable","pure","popper-class","popper-style","reference-el","trigger-target-el","visible","z-index","loop","onMouseenter","onMouseleave","onClose"]),[[Zt,S.value]])]),_:3},8,["name","appear"])):te("v-if",!0)],8,["disabled","to"]))}}),Xle=qle;const Zle=["innerHTML"],Jle={key:1};var Qle=q({name:"ElTooltip",__name:"tooltip",props:iv,emits:Gle,setup(e,{expose:t,emit:n}){const a=e,r=n;aq();const o=ke("tooltip"),i=Fa(),l=F(),s=F(),u=()=>{var S;const w=p(l);w&&((S=w.popperInstanceRef)==null||S.update())},c=F(!1),d=F(),{show:v,hide:f,hasUpdateHandler:h}=Ule({indicator:c,toggleReason:d}),{onOpen:m,onClose:g}=iq({showAfter:cn(a,"showAfter"),hideAfter:cn(a,"hideAfter"),autoClose:cn(a,"autoClose"),open:v,close:f}),y=D(()=>vn(a.visible)&&!h.value),b=D(()=>[o.b(),a.popperClass]);Ft(Z6,{controlled:y,id:i,open:Ld(c),trigger:cn(a,"trigger"),onOpen:m,onClose:g,onToggle:w=>{p(c)?g(w):m(w)},onShow:()=>{r("show",d.value)},onHide:()=>{r("hide",d.value)},onBeforeShow:()=>{r("before-show",d.value)},onBeforeHide:()=>{r("before-hide",d.value)},updatePopper:u}),ye(()=>a.disabled,w=>{w&&c.value&&(c.value=!1),!w&&vn(a.visible)&&(c.value=a.visible)});const _=w=>{var S;return(S=s.value)==null?void 0:S.isFocusInsideContent(w)};return j3(()=>c.value&&f()),rn(()=>{d.value=void 0}),t({popperRef:l,contentRef:s,isFocusInsideContent:_,updatePopper:u,onOpen:m,onClose:g,hide:f}),(w,S)=>(x(),ne(p(iL),{ref_key:"popperRef",ref:l,role:e.role},{default:$(()=>[L(jle,{disabled:e.disabled,trigger:e.trigger,"trigger-keys":e.triggerKeys,"virtual-ref":e.virtualRef,"virtual-triggering":e.virtualTriggering,"focus-on-target":e.focusOnTarget},{default:$(()=>[w.$slots.default?ce(w.$slots,"default",{key:0}):te("v-if",!0)]),_:3},8,["disabled","trigger","trigger-keys","virtual-ref","virtual-triggering","focus-on-target"]),L(Xle,{ref_key:"contentRef",ref:s,"aria-label":e.ariaLabel,"boundaries-padding":e.boundariesPadding,content:e.content,disabled:e.disabled,effect:e.effect,enterable:e.enterable,"fallback-placements":e.fallbackPlacements,"hide-after":e.hideAfter,"gpu-acceleration":e.gpuAcceleration,offset:e.offset,persistent:e.persistent,"popper-class":b.value,"popper-style":e.popperStyle,placement:e.placement,"popper-options":e.popperOptions,"arrow-offset":e.arrowOffset,pure:e.pure,"raw-content":e.rawContent,"reference-el":e.referenceEl,"trigger-target-el":e.triggerTargetEl,"show-after":e.showAfter,strategy:e.strategy,teleported:e.teleported,transition:e.transition,"virtual-triggering":e.virtualTriggering,"z-index":e.zIndex,"append-to":e.appendTo,loop:e.loop},{default:$(()=>[ce(w.$slots,"content",{},()=>[e.rawContent?(x(),O("span",{key:0,innerHTML:e.content},null,8,Zle)):(x(),O("span",Jle,Q(e.content),1))]),e.showArrow?(x(),ne(p(yie),{key:0})):te("v-if",!0)]),_:3},8,["aria-label","boundaries-padding","content","disabled","effect","enterable","fallback-placements","hide-after","gpu-acceleration","offset","persistent","popper-class","popper-style","placement","popper-options","arrow-offset","pure","raw-content","reference-el","trigger-target-el","show-after","strategy","teleported","transition","virtual-triggering","z-index","append-to","loop"])]),_:3},8,["role"]))}}),ese=Qle;const Ma=wt(ese),Nn=e=>e,J6=$e({id:{type:String,default:void 0},size:pa,disabled:{type:Boolean,default:void 0},modelValue:{type:ae([String,Number,Object]),default:""},modelModifiers:{type:ae(Object),default:()=>({})},maxlength:{type:[String,Number]},minlength:{type:[String,Number]},type:{type:ae(String),default:"text"},resize:{type:String,values:["none","both","horizontal","vertical"]},autosize:{type:ae([Boolean,Object]),default:!1},autocomplete:{type:ae(String),default:"off"},formatter:{type:Function},parser:{type:Function},placeholder:{type:String},form:{type:String},readonly:Boolean,clearable:Boolean,clearIcon:{type:pn,default:ci},showPassword:Boolean,showWordLimit:Boolean,wordLimitPosition:{type:String,values:["inside","outside"],default:"inside"},suffixIcon:{type:pn},prefixIcon:{type:pn},containerRole:{type:String,default:void 0},tabindex:{type:[String,Number],default:0},validateEvent:{type:Boolean,default:!0},inputStyle:{type:ae([Object,Array,String,Boolean]),default:()=>Nn({})},countGraphemes:{type:ae(Function)},autofocus:Boolean,rows:{type:Number,default:2},...dr(["ariaLabel"]),inputmode:{type:ae(String),default:void 0},name:String}),tse={[bt]:e=>Ye(e),input:e=>Ye(e),change:(e,t)=>Ye(e)&&(t instanceof Event||t===void 0),focus:e=>e instanceof FocusEvent,blur:e=>e instanceof FocusEvent,clear:e=>e===void 0||e instanceof MouseEvent,mouseleave:e=>e instanceof MouseEvent,mouseenter:e=>e instanceof MouseEvent,keydown:e=>e instanceof Event,compositionstart:e=>e instanceof CompositionEvent,compositionupdate:e=>e instanceof CompositionEvent,compositionend:e=>e instanceof CompositionEvent};$i(ci);let qa;const nse={height:"0",visibility:"hidden",overflow:P1()?"":"hidden",position:"absolute","z-index":"-1000",top:"0",right:"0"},ase=["letter-spacing","line-height","padding-top","padding-bottom","font-family","font-weight","font-size","text-rendering","text-transform","width","text-indent","padding-left","padding-right","border-width","box-sizing","word-break"],I5=e=>{const t=Number.parseFloat(e);return Number.isNaN(t)?e:t};function rse(e){const t=window.getComputedStyle(e),n=t.getPropertyValue("box-sizing"),a=Number.parseFloat(t.getPropertyValue("padding-bottom"))+Number.parseFloat(t.getPropertyValue("padding-top")),r=Number.parseFloat(t.getPropertyValue("border-bottom-width"))+Number.parseFloat(t.getPropertyValue("border-top-width"));return{contextStyle:ase.map(o=>[o,t.getPropertyValue(o)]),paddingSize:a,borderSize:r,boxSizing:n}}function L5(e,t=1,n){var c;if(!qa){qa=document.createElement("textarea");let d=document.body;!P1()&&e.parentNode&&(d=e.parentNode),d.appendChild(qa)}const{paddingSize:a,borderSize:r,boxSizing:o,contextStyle:i}=rse(e);i.forEach(([d,v])=>qa==null?void 0:qa.style.setProperty(d,v)),Object.entries(nse).forEach(([d,v])=>qa==null?void 0:qa.style.setProperty(d,v,"important")),qa.value=e.value||e.placeholder||"";let l=qa.scrollHeight;const s={};o==="border-box"?l=l+r:o==="content-box"&&(l=l-a),qa.value="";const u=qa.scrollHeight-a;if(Je(t)){let d=u*t;o==="border-box"&&(d=d+a+r),l=Math.max(d,l),s.minHeight=`${d}px`}if(Je(n)){let d=u*n;o==="border-box"&&(d=d+a+r),l=Math.min(d,l)}return s.height=`${l}px`,(c=qa.parentNode)==null||c.removeChild(qa),qa=void 0,s}const ose=["id","name","minlength","maxlength","type","disabled","readonly","autocomplete","tabindex","aria-label","placeholder","form","autofocus","role","inputmode"],ise=["id","name","minlength","maxlength","tabindex","disabled","readonly","autocomplete","aria-label","placeholder","form","autofocus","rows","role","inputmode"],P5="ElInput";var lse=q({name:P5,inheritAttrs:!1,__name:"input",props:J6,emits:tse,setup(e,{expose:t,emit:n}){const a=e,r=n,o=Nl(),i=Xn(),l=D(()=>[a.type==="textarea"?y.b():g.b(),g.m(h.value),g.is("disabled",m.value),g.is("exceed",he.value),{[g.b("group")]:i.prepend||i.append,[g.m("prefix")]:i.prefix||a.prefixIcon,[g.m("suffix")]:i.suffix||a.suffixIcon||a.clearable||a.showPassword,[g.bm("suffix","password-clear")]:re.value&&ie.value,[g.b("hidden")]:a.type==="hidden"},o.class]),s=D(()=>[g.e("wrapper"),g.is("focus",G.value)]),u=A1(),c=D(()=>{var _e;return(_e=a.maxlength)==null?void 0:_e.toString()}),{form:d,formItem:v}=Ea(),{inputId:f}=Yr(a,{formItemContext:v}),h=ca(),m=$n(),g=ke("input"),y=ke("textarea"),b=Ht(),_=Ht(),w=F(!1),S=F(!1),k=F(),T=F(),M=Ht(a.inputStyle),E=F("");let A;const I=F(),P=D(()=>b.value||_.value),{wrapperRef:R,isFocused:G,handleFocus:K,handleBlur:H}=Fl(P,{disabled:m,afterFocus(){var _e;a.showPassword&&(A=(_e=P.value)==null?void 0:_e.value)},beforeBlur(_e){var Ne;if(a.showPassword){const tt=P.value;if(_e.relatedTarget&&((Ne=tt==null?void 0:tt.parentElement)!=null&&Ne.contains(_e.relatedTarget)))return;const Me=tt==null?void 0:tt.value;!Un(Me)&&!Un(A)&&Me!==A&&(tt==null||tt.dispatchEvent(new Event("change",{bubbles:!0}))),A=void 0}},afterBlur(){var _e;a.validateEvent&&((_e=v==null?void 0:v.validate)==null||_e.call(v,"blur").catch(kt))}}),z=D(()=>(d==null?void 0:d.statusIcon)??!1),B=D(()=>(v==null?void 0:v.validateState)||""),W=D(()=>B.value&&F1[B.value]),N=D(()=>S.value?U6:LI),V=D(()=>[o.style]),Y=D(()=>[a.inputStyle,M.value,{resize:a.resize},I.value?{height:I.value}:void 0]),X=D(()=>Un(a.modelValue)?"":String(a.modelValue)),ee=D(()=>a.clearable&&!m.value&&!a.readonly),re=D(()=>ee.value&&!!X.value&&(G.value||w.value)),ie=D(()=>a.showPassword&&!m.value&&!!X.value),le=D(()=>a.showWordLimit&&!!c.value&&(a.type==="text"||a.type==="textarea")&&!m.value&&!a.readonly&&!a.showPassword),ve=D(()=>a.countGraphemes&&a.showWordLimit?a.countGraphemes(X.value):X.value.length),he=D(()=>!!le.value&&ve.value>Number(c.value)),J=D(()=>!!i.suffix||!!a.suffixIcon||a.clearable||a.showPassword||le.value||!!B.value&&z.value),Z=D(()=>!!Object.keys(a.modelModifiers).length),[j,oe]=Jq(b);let se;Mn(_,_e=>{if(Se(),!le.value&&!ee.value||a.resize!=="both"&&a.resize!=="horizontal")return;const{width:Ne}=_e[0].target.getBoundingClientRect(),tt=()=>{se=void 0,k.value={right:`calc(100% - ${Ne-10}px)`},T.value={right:`calc(100% - ${Ne-11}px)`}};se&&ti(se),se=Wr(tt)});const fe=()=>{const{type:_e,autosize:Ne}=a;if(!(!qt||_e!=="textarea"||!_.value))if(Ne){const tt=_t(Ne)?Ne.minRows:void 0,Me=_t(Ne)?Ne.maxRows:void 0,Ue=L5(_.value,tt,Me);M.value={overflowY:"hidden",...Ue},He(()=>{_.value.offsetHeight,M.value=Ue})}else M.value={minHeight:L5(_.value).minHeight}},Se=(_e=>{let Ne=!1;return()=>{var tt;if(Ne||!a.autosize){a.resize!=="none"&&setTimeout(()=>{var Me;I.value=(Me=_.value)==null?void 0:Me.style.height});return}((tt=_.value)==null?void 0:tt.offsetParent)!==null&&(setTimeout(_e),Ne=!0)}})(fe),De=()=>{const _e=P.value,Ne=a.formatter?a.formatter(X.value):X.value;!_e||_e.value===Ne||a.type==="file"||(_e.value=Ne)},Te=_e=>{const{trim:Ne,number:tt}=a.modelModifiers;return Ne&&(_e=_e.trim()),tt&&(_e=`${I5(_e)}`),a.formatter&&a.parser&&(_e=a.parser(_e)),_e},Re=async _e=>{if(Le.value)return;const{lazy:Ne}=a.modelModifiers;let{value:tt}=_e.target,Me=!1;if(Ne){r(ua,tt);return}if(tt=Te(tt),a.countGraphemes&&c.value!=null){const Ue=Number(c.value),Gt=a.countGraphemes(tt),je=a.countGraphemes(E.value);if(Gt>Ue&&Gt>je)if(je>Ue)tt=E.value,Me=!0;else{const ft=E.value,ht=tt;let pt=0;for(;ptpt&&Ct>pt&&ft[It-1]===ht[Ct-1];)It--,Ct--;const $t=ht.slice(0,pt),Sn=ft.slice(pt,It),Fe=ht.slice(pt,Ct),qe=ht.slice(Ct),ut=je-a.countGraphemes(Sn),Ie=Math.max(0,Ue-ut);let Ke="";if(Ie>0)if(typeof Intl<"u"&&"Segmenter"in Intl){const Ge=new Intl.Segmenter(void 0,{granularity:"grapheme"});for(const{segment:Ee}of Ge.segment(Fe)){const de=Ke+Ee;if(a.countGraphemes(de)>Ie)break;Ke=de}}else for(const Ge of Array.from(Fe)){const Ee=Ke+Ge;if(a.countGraphemes(Ee)>Ie)break;Ke=Ee}tt=$t+Ke+qe,Me=!0}}if(String(tt)===X.value){if(a.formatter||Me){const Ue=_e.target,Gt=Ue.value,je=Ue.selectionStart,ft=Ue.selectionEnd;if(De(),Me&&P.value&&je!=null&&ft!=null){const ht=P.value.value,pt=Gt.slice(Math.max(0,ft));let It=Math.min(je,ht.length);pt&&ht.endsWith(pt)&&(It=ht.length-pt.length),P.value.setSelectionRange(It,It)}}return}E.value=tt,j(),r(bt,tt),r(ua,tt),await He(),(a.formatter&&a.parser||!Z.value)&&De(),oe()},Ce=async _e=>{let{value:Ne}=_e.target;a.showPassword&&(A=Ne),Ne=Te(Ne),a.modelModifiers.lazy&&r(bt,Ne),r(Ot,Ne,_e),await He(),De()},{isComposing:Le,handleCompositionStart:me,handleCompositionUpdate:Be,handleCompositionEnd:ze}=rp({emit:r,afterComposition:Re}),xe=()=>{S.value=!S.value},rt=()=>{var _e;return(_e=P.value)==null?void 0:_e.focus()},nt=()=>{var _e;return(_e=P.value)==null?void 0:_e.blur()},Pt=_e=>{w.value=!1,r("mouseleave",_e)},Wt=_e=>{w.value=!0,r("mouseenter",_e)},Jt=_e=>{r("keydown",_e)},Rt=()=>{var _e;(_e=P.value)==null||_e.select()},Qt=_e=>{r(bt,""),A="",r(Ot,""),r("clear",_e),r(ua,"")};return ye(()=>a.modelValue,()=>{var _e;He(()=>{fe(),a.autosize&&(I.value=void 0)}),a.validateEvent&&((_e=v==null?void 0:v.validate)==null||_e.call(v,"change").catch(kt))}),ye(()=>X.value,_e=>{E.value=_e},{immediate:!0}),ye(X,_e=>{if(!P.value)return;const{trim:Ne,number:tt}=a.modelModifiers,Me=P.value.value,Ue=(tt||a.type==="number")&&!/^0\d/.test(Me)?`${I5(Me)}`:Me;Ue!==_e&&(document.activeElement===P.value&&P.value.type!=="range"&&Ne&&Ue.trim()===_e||De())}),ye(()=>a.type,async()=>{await He(),De(),fe()}),mt(()=>{!a.formatter&&a.parser&&un(P5,"If you set the parser, you also need to set the formatter."),De(),He(fe)}),rn(()=>{se&&ti(se)}),t({input:b,textarea:_,ref:P,textareaStyle:Y,autosize:cn(a,"autosize"),isComposing:Le,passwordVisible:S,focus:rt,blur:nt,select:Rt,clear:Qt,resizeTextarea:fe}),(_e,Ne)=>(x(),O("div",{class:U([l.value,{[p(g).bm("group","append")]:_e.$slots.append,[p(g).bm("group","prepend")]:_e.$slots.prepend}]),style:et(V.value),onMouseenter:Wt,onMouseleave:Pt},[te(" input "),e.type!=="textarea"?(x(),O(Ve,{key:0},[te(" prepend slot "),_e.$slots.prepend?(x(),O("div",{key:0,class:U(p(g).be("group","prepend"))},[ce(_e.$slots,"prepend")],2)):te("v-if",!0),C("div",{ref_key:"wrapperRef",ref:R,class:U(s.value)},[te(" prefix slot "),_e.$slots.prefix||e.prefixIcon?(x(),O("span",{key:0,class:U(p(g).e("prefix"))},[C("span",{class:U(p(g).e("prefix-inner"))},[ce(_e.$slots,"prefix"),e.prefixIcon?(x(),ne(p(Xe),{key:0,class:U(p(g).e("icon"))},{default:$(()=>[(x(),ne(St(e.prefixIcon)))]),_:1},8,["class"])):te("v-if",!0)],2)],2)):te("v-if",!0),C("input",Tt({id:p(f),ref_key:"input",ref:b,class:p(g).e("inner")},p(u),{name:e.name,minlength:e.countGraphemes?void 0:e.minlength,maxlength:e.countGraphemes?void 0:c.value,type:e.showPassword?S.value?"text":"password":e.type,disabled:p(m),readonly:e.readonly,autocomplete:e.autocomplete,tabindex:e.tabindex,"aria-label":e.ariaLabel,placeholder:e.placeholder,style:e.inputStyle,form:e.form,autofocus:e.autofocus,role:e.containerRole,inputmode:e.inputmode,onCompositionstart:Ne[0]||(Ne[0]=(...tt)=>p(me)&&p(me)(...tt)),onCompositionupdate:Ne[1]||(Ne[1]=(...tt)=>p(Be)&&p(Be)(...tt)),onCompositionend:Ne[2]||(Ne[2]=(...tt)=>p(ze)&&p(ze)(...tt)),onInput:Re,onChange:Ce,onKeydown:Jt}),null,16,ose),te(" suffix slot "),J.value?(x(),O("span",{key:1,class:U(p(g).e("suffix"))},[C("span",{class:U(p(g).e("suffix-inner"))},[ee.value?(x(),ne(p(Xe),{key:0,class:U([p(g).e("icon"),p(g).e("clear")]),style:et({visibility:re.value?"visible":"hidden"}),onMousedown:at(p(kt),["prevent"]),onClick:Qt},{default:$(()=>[(x(),ne(St(e.clearIcon)))]),_:1},8,["class","style","onMousedown"])):te("v-if",!0),!re.value||!ie.value||!le.value?(x(),O(Ve,{key:1},[ce(_e.$slots,"suffix"),e.suffixIcon?(x(),ne(p(Xe),{key:0,class:U(p(g).e("icon"))},{default:$(()=>[(x(),ne(St(e.suffixIcon)))]),_:1},8,["class"])):te("v-if",!0)],64)):te("v-if",!0),ie.value?(x(),ne(p(Xe),{key:2,class:U([p(g).e("icon"),p(g).e("password")]),onClick:xe,onMousedown:at(p(kt),["prevent"]),onMouseup:at(p(kt),["prevent"])},{default:$(()=>[ce(_e.$slots,"password-icon",{visible:S.value},()=>[(x(),ne(St(N.value)))])]),_:3},8,["class","onMousedown","onMouseup"])):te("v-if",!0),le.value?(x(),O("span",{key:3,class:U([p(g).e("count"),p(g).is("outside",e.wordLimitPosition==="outside")])},[C("span",{class:U(p(g).e("count-inner"))},Q(ve.value)+" / "+Q(c.value),3)],2)):te("v-if",!0),B.value&&W.value&&z.value?(x(),ne(p(Xe),{key:4,class:U([p(g).e("icon"),p(g).e("validateIcon"),p(g).is("loading",B.value==="validating")])},{default:$(()=>[(x(),ne(St(W.value)))]),_:1},8,["class"])):te("v-if",!0)],2)],2)):te("v-if",!0)],2),te(" append slot "),_e.$slots.append?(x(),O("div",{key:1,class:U(p(g).be("group","append"))},[ce(_e.$slots,"append")],2)):te("v-if",!0)],64)):(x(),O(Ve,{key:1},[te(" textarea "),C("textarea",Tt({id:p(f),ref_key:"textarea",ref:_,class:[p(y).e("inner"),p(g).is("focus",p(G)),p(y).is("clearable",e.clearable)]},p(u),{name:e.name,minlength:e.countGraphemes?void 0:e.minlength,maxlength:e.countGraphemes?void 0:c.value,tabindex:e.tabindex,disabled:p(m),readonly:e.readonly,autocomplete:e.autocomplete,style:Y.value,"aria-label":e.ariaLabel,placeholder:e.placeholder,form:e.form,autofocus:e.autofocus,rows:e.rows,role:e.containerRole,inputmode:e.inputmode,onCompositionstart:Ne[3]||(Ne[3]=(...tt)=>p(me)&&p(me)(...tt)),onCompositionupdate:Ne[4]||(Ne[4]=(...tt)=>p(Be)&&p(Be)(...tt)),onCompositionend:Ne[5]||(Ne[5]=(...tt)=>p(ze)&&p(ze)(...tt)),onInput:Re,onFocus:Ne[6]||(Ne[6]=(...tt)=>p(K)&&p(K)(...tt)),onBlur:Ne[7]||(Ne[7]=(...tt)=>p(H)&&p(H)(...tt)),onChange:Ce,onKeydown:Jt}),null,16,ise),re.value?(x(),ne(p(Xe),{key:0,class:U([p(y).e("icon"),p(y).e("clear")]),style:et(T.value),onMousedown:at(p(kt),["prevent"]),onClick:Qt},{default:$(()=>[(x(),ne(St(e.clearIcon)))]),_:1},8,["class","style","onMousedown"])):te("v-if",!0),le.value?(x(),O("span",{key:1,style:et(k.value),class:U([p(g).e("count"),p(g).is("outside",e.wordLimitPosition==="outside")])},Q(ve.value)+" / "+Q(c.value),7)):te("v-if",!0)],64))],38))}}),sse=lse;const Ba=wt(sse),use=$e({...J6,valueKey:{type:String,default:"value"},modelValue:{type:[String,Number],default:""},debounce:{type:Number,default:300},placement:{type:ae(String),values:["top","top-start","top-end","bottom","bottom-start","bottom-end"],default:"bottom-start"},fetchSuggestions:{type:ae([Function,Array]),default:kt},popperClass:Tn.popperClass,popperStyle:Tn.popperStyle,popperOptions:Tn.popperOptions,showArrow:{type:Boolean,default:!0},triggerOnFocus:{type:Boolean,default:!0},selectWhenUnmatched:Boolean,hideLoading:Boolean,teleported:Tn.teleported,appendTo:Tn.appendTo,highlightFirstItem:Boolean,fitInputWidth:Boolean,loopNavigation:{type:Boolean,default:!0}}),cse={[bt]:e=>Ye(e)||Je(e),[ua]:e=>Ye(e)||Je(e),[Ot]:e=>Ye(e)||Je(e),focus:e=>e instanceof FocusEvent,blur:e=>e instanceof FocusEvent,clear:()=>!0,select:e=>_t(e)},dse=$e({distance:{type:Number,default:0},height:{type:[String,Number],default:""},maxHeight:{type:[String,Number],default:""},native:Boolean,wrapStyle:{type:ae([String,Object,Array,Boolean]),default:""},wrapClass:{type:ae([String,Array,Object,Boolean]),default:""},viewClass:{type:ae([String,Array,Object,Boolean]),default:""},viewStyle:{type:ae([String,Object,Array,Boolean]),default:""},noresize:Boolean,tag:{type:String,default:"div"},always:Boolean,minSize:{type:Number,default:20},tabindex:{type:[String,Number],default:void 0},id:String,role:String,...dr(["ariaLabel","ariaOrientation"])}),hh={"end-reached":e=>["left","right","top","bottom"].includes(e),scroll:({scrollTop:e,scrollLeft:t})=>[e,t].every(Je)},lL={vertical:{offset:"offsetHeight",scroll:"scrollTop",scrollSize:"scrollHeight",size:"height",key:"vertical",axis:"Y",client:"clientY",direction:"top"},horizontal:{offset:"offsetWidth",scroll:"scrollLeft",scrollSize:"scrollWidth",size:"width",key:"horizontal",axis:"X",client:"clientX",direction:"left"}},fse=({move:e,size:t,bar:n})=>({[n.size]:t,transform:`translate${n.axis}(${e}%)`}),vse=$e({vertical:Boolean,size:String,move:Number,ratio:{type:Number,required:!0},always:Boolean}),Q6=Symbol("scrollbarContextKey");function Vi(e,t,n=.03){return e-t>n}const hse=$e({always:{type:Boolean,default:!0},minSize:{type:Number,required:!0}}),pse="Thumb";var mse=q({__name:"thumb",props:vse,setup(e){const t=e,n=Ze(Q6),a=ke("scrollbar");n||Dn(pse,"can not inject scrollbar context");const r=F(),o=F(),i=F({}),l=F(!1);let s=!1,u=!1,c=0,d=0,v=qt?document.onselectstart:null;const f=D(()=>lL[t.vertical?"vertical":"horizontal"]),h=D(()=>fse({size:t.size,move:t.move,bar:f.value})),m=D(()=>r.value[f.value.offset]**2/n.wrapElement[f.value.scrollSize]/t.ratio/o.value[f.value.offset]),g=M=>{var A;if(M.stopPropagation(),M.ctrlKey||[1,2].includes(M.button))return;(A=window.getSelection())==null||A.removeAllRanges(),b(M);const E=M.currentTarget;E&&(i.value[f.value.axis]=E[f.value.offset]-(M[f.value.client]-E.getBoundingClientRect()[f.value.direction]))},y=M=>{if(!o.value||!r.value||!n.wrapElement)return;const E=(Math.abs(M.target.getBoundingClientRect()[f.value.direction]-M[f.value.client])-o.value[f.value.offset]/2)*100*m.value/r.value[f.value.offset];n.wrapElement[f.value.scroll]=E*n.wrapElement[f.value.scrollSize]/100},b=M=>{M.stopImmediatePropagation(),s=!0,c=n.wrapElement.scrollHeight,d=n.wrapElement.scrollWidth,document.addEventListener("mousemove",_),document.addEventListener("mouseup",w),v=document.onselectstart,document.onselectstart=()=>!1},_=M=>{if(!r.value||!o.value||s===!1)return;const E=i.value[f.value.axis];if(!E)return;const A=((r.value.getBoundingClientRect()[f.value.direction]-M[f.value.client])*-1-(o.value[f.value.offset]-E))*100*m.value/r.value[f.value.offset];f.value.scroll==="scrollLeft"?n.wrapElement[f.value.scroll]=A*d/100:n.wrapElement[f.value.scroll]=A*c/100},w=()=>{s=!1,i.value[f.value.axis]=0,document.removeEventListener("mousemove",_),document.removeEventListener("mouseup",w),T(),u&&(l.value=!1)},S=()=>{u=!1,l.value=!!t.size},k=()=>{u=!0,l.value=s};rn(()=>{T(),document.removeEventListener("mouseup",w)});const T=()=>{document.onselectstart!==v&&(document.onselectstart=v)};return on(cn(n,"scrollbarElement"),"mousemove",S),on(cn(n,"scrollbarElement"),"mouseleave",k),(M,E)=>(x(),ne(Pa,{name:p(a).b("fade"),persisted:""},{default:$(()=>[st(C("div",{ref_key:"instance",ref:r,class:U([p(a).e("bar"),p(a).is(f.value.key)]),onMousedown:y,onClick:E[0]||(E[0]=at(()=>{},["stop"]))},[C("div",{ref_key:"thumb",ref:o,class:U(p(a).e("thumb")),style:et(h.value),onMousedown:g},null,38)],34),[[Zt,e.always||l.value]])]),_:1},8,["name"]))}}),D5=mse,gse=q({__name:"bar",props:hse,setup(e,{expose:t}){const n=e,a=Ze(Q6),r=F(0),o=F(0),i=F(""),l=F(""),s=F(1),u=F(1);return t({handleScroll:v=>{if(v){const f=v.offsetHeight-4,h=v.offsetWidth-4;o.value=v.scrollTop*100/f*s.value,r.value=v.scrollLeft*100/h*u.value}},update:()=>{const v=a==null?void 0:a.wrapElement;if(!v)return;const f=v.offsetHeight-4,h=v.offsetWidth-4,m=f**2/v.scrollHeight,g=h**2/v.scrollWidth,y=Math.max(m,n.minSize),b=Math.max(g,n.minSize);s.value=m/(f-m)/(y/(f-y)),u.value=g/(h-g)/(b/(h-b)),l.value=y+4(x(),O(Ve,null,[L(D5,{move:r.value,ratio:u.value,size:i.value,always:e.always},null,8,["move","ratio","size","always"]),L(D5,{move:o.value,ratio:s.value,size:l.value,vertical:"",always:e.always},null,8,["move","ratio","size","always"])],64))}}),yse=gse;const bse=["tabindex"],X2="ElScrollbar";var _se=q({name:X2,__name:"scrollbar",props:dse,emits:hh,setup(e,{expose:t,emit:n}){const a=e,r=n,o=ke("scrollbar");let i,l,s,u=0,c=0,d=0,v="";const f={bottom:!1,top:!1,right:!1,left:!1},h=F(),m=F(),g=F(),y=F(),b=D(()=>{const G={},K=Vn(a.height),H=Vn(a.maxHeight);return K&&(G.height=K),H&&(G.maxHeight=H),[a.wrapStyle,G]}),_=D(()=>[a.wrapClass,o.e("wrap"),{[o.em("wrap","hidden-default")]:!a.native}]),w=D(()=>[o.e("view"),a.viewClass]),S=G=>f[G]??!1,k={top:"bottom",bottom:"top",left:"right",right:"left"},T=G=>{const K=k[v];if(!K)return;const H=G[v],z=G[K];H&&!f[v]&&(f[v]=!0),!z&&f[K]&&(f[K]=!1)},M=()=>{var G;if(m.value){(G=y.value)==null||G.handleScroll(m.value);const K=c,H=d;c=m.value.scrollTop,d=m.value.scrollLeft;const z={bottom:!Vi(m.value.scrollHeight-a.distance,m.value.clientHeight+c),top:c<=a.distance&&K!==0,right:!Vi(m.value.scrollWidth-a.distance,m.value.clientWidth+d)&&H!==d,left:d<=a.distance&&H!==0};if(r("scroll",{scrollTop:c,scrollLeft:d}),K!==c&&(v=c>K?"bottom":"top"),H!==d&&(v=d>H?"right":"left"),a.distance>0){if(S(v))return;T(z)}z[v]&&r("end-reached",v)}};function E(G,K){_t(G)?m.value.scrollTo(G):Je(G)&&Je(K)&&m.value.scrollTo(G,K)}const A=G=>{if(!Je(G)){un(X2,"value must be a number");return}m.value.scrollTop=G},I=G=>{if(!Je(G)){un(X2,"value must be a number");return}m.value.scrollLeft=G},P=()=>{var G,K;(G=y.value)==null||G.update(),f[v]=!1,m.value&&((K=y.value)==null||K.handleScroll(m.value))},R=()=>{u||(u=requestAnimationFrame(()=>{var G,K;u=0,m.value&&((G=y.value)==null||G.update(),(K=y.value)==null||K.handleScroll(m.value))}))};return ye(()=>a.noresize,G=>{G?(i==null||i(),l==null||l(),s==null||s()):({stop:i}=Mn(g,P),{stop:l}=Mn(m,P),s=on("resize",P))},{immediate:!0}),ye(()=>[a.maxHeight,a.height],()=>{a.native||He(()=>{P()})}),Ft(Q6,nn({scrollbarElement:h,wrapElement:m})),Wh(()=>{m.value&&(m.value.scrollTop=c,m.value.scrollLeft=d)}),mt(()=>{a.native||He(()=>{P()})}),oi(()=>P()),t({wrapRef:m,update:P,scrollTo:E,setScrollTop:A,setScrollLeft:I,handleScroll:M}),(G,K)=>(x(),O("div",{ref_key:"scrollbarRef",ref:h,class:U(p(o).b())},[C("div",{ref_key:"wrapRef",ref:m,class:U(_.value),style:et(b.value),tabindex:e.tabindex,onScroll:M,onTransitionend:R,onAnimationend:R},[(x(),ne(St(e.tag),{id:e.id,ref_key:"resizeRef",ref:g,class:U(w.value),style:et(e.viewStyle),role:e.role,"aria-label":e.ariaLabel,"aria-orientation":e.ariaOrientation},{default:$(()=>[ce(G.$slots,"default")]),_:3},8,["id","class","style","role","aria-label","aria-orientation"]))],46,bse),e.native?te("v-if",!0):(x(),ne(yse,{key:0,ref_key:"barRef",ref:y,always:e.always,"min-size":e.minSize},null,8,["always","min-size"]))],2))}}),wse=_se;const ni=wt(wse),Sse=["aria-expanded","aria-owns"],Cse={key:0},xse=["id","aria-selected","onClick"],O5="ElAutocomplete";var kse=q({name:O5,inheritAttrs:!1,__name:"autocomplete",props:use,emits:cse,setup(e,{expose:t,emit:n}){const a=e,r=n,o=D(()=>{const J=Ba.props??[];return ji(a,Ae(J)?J:Object.keys(J))}),i=Nl(),l=$n(),s=ke("autocomplete"),u=F(),c=F(),d=F(),v=F();let f=!1,h=!1;const m=F([]),g=F(-1),y=F(""),b=F(!1),_=F(!1),w=F(!1),S=Fa(),k=D(()=>i.style),T=D(()=>(m.value.length>0||w.value)&&b.value),M=D(()=>!a.hideLoading&&w.value),E=D(()=>u.value?Array.from(u.value.$el.querySelectorAll("input")):[]),A=()=>{T.value&&(y.value=`${u.value.$el.offsetWidth}px`)},I=()=>{g.value=-1},P=async J=>{if(_.value)return;const Z=j=>{w.value=!1,!_.value&&(Ae(j)?(m.value=j,g.value=a.highlightFirstItem?0:-1):Dn(O5,"autocomplete suggestions must be an array"))};if(w.value=!0,Ae(a.fetchSuggestions))Z(a.fetchSuggestions);else{const j=await a.fetchSuggestions(J,Z);Ae(j)&&Z(j)}},R=Xh(P,D(()=>a.debounce)),G=J=>{const Z=!!J;if(r(ua,J),r(bt,J),_.value=!1,b.value||(b.value=Z),!a.triggerOnFocus&&!J){_.value=!0,m.value=[];return}R(J)},K=J=>{var Z;l.value||(((Z=J.target)==null?void 0:Z.tagName)!=="INPUT"||E.value.includes(document.activeElement))&&(b.value=!0)},H=J=>{r(Ot,J)},z=J=>{if(h)h=!1;else{b.value=!0,r("focus",J);const Z=a.modelValue??"";a.triggerOnFocus&&!f&&(m.value=[],g.value=-1,R(String(Z)))}},B=J=>{setTimeout(()=>{var Z;if((Z=d.value)!=null&&Z.isFocusInsideContent()){h=!0;return}b.value&&Y(),r("blur",J)})},W=()=>{b.value=!1,r(bt,""),r("clear")},N=async()=>{var J;(J=u.value)!=null&&J.isComposing||(T.value&&g.value>=0&&g.value{T.value&&(J.preventDefault(),J.stopPropagation(),Y())},Y=()=>{b.value=!1},X=()=>{var J;(J=u.value)==null||J.focus()},ee=()=>{var J;(J=u.value)==null||J.blur()},re=async J=>{r(ua,J[a.valueKey]),r(bt,J[a.valueKey]),r("select",J),m.value=[],g.value=-1},ie=J=>{var Se,De;if(!T.value||w.value)return;if(J<0){if(!a.loopNavigation){g.value=-1;return}J=m.value.length-1}J>=m.value.length&&(J=a.loopNavigation?0:m.value.length-1);const[Z,j]=le(),oe=j[J],se=Z.scrollTop,{offsetTop:fe,scrollHeight:ge}=oe;fe+ge>se+Z.clientHeight&&(Z.scrollTop=fe+ge-Z.clientHeight),fe{const J=c.value.querySelector(`.${s.be("suggestion","wrap")}`);return[J,J.querySelectorAll(`.${s.be("suggestion","list")} li`)]},ve=b6(v,J=>{var j;if((j=d.value)!=null&&j.isFocusInsideContent())return;const Z=h;h=!1,T.value&&(Z?B(new FocusEvent("blur",J)):Y())}),he=J=>{switch(mn(J)){case Pe.up:J.preventDefault(),ie(g.value-1);break;case Pe.down:J.preventDefault(),ie(g.value+1);break;case Pe.enter:case Pe.numpadEnter:J.preventDefault(),N();break;case Pe.tab:Y();break;case Pe.esc:V(J);break;case Pe.home:J.preventDefault(),ie(0);break;case Pe.end:J.preventDefault(),ie(m.value.length-1);break;case Pe.pageUp:J.preventDefault(),ie(Math.max(0,g.value-10));break;case Pe.pageDown:J.preventDefault(),ie(Math.min(m.value.length-1,g.value+10));break}};return rn(()=>{ve==null||ve()}),mt(()=>{var Z;const J=(Z=u.value)==null?void 0:Z.ref;J&&([{key:"role",value:"textbox"},{key:"aria-autocomplete",value:"list"},{key:"aria-controls",value:S.value},{key:"aria-activedescendant",value:`${S.value}-item-${g.value}`}].forEach(({key:j,value:oe})=>J.setAttribute(j,oe)),f=J.hasAttribute("readonly"))}),t({highlightedIndex:g,activated:b,loading:w,inputRef:u,popperRef:d,suggestions:m,handleSelect:re,handleKeyEnter:N,focus:X,blur:ee,close:Y,highlight:ie,getData:P}),(J,Z)=>(x(),ne(p(Ma),{ref_key:"popperRef",ref:d,visible:T.value,placement:e.placement,"fallback-placements":["bottom-start","top-start"],"popper-class":[p(s).e("popper"),e.popperClass],"popper-style":e.popperStyle,"popper-options":e.popperOptions,"show-arrow":e.showArrow,teleported:e.teleported,"append-to":e.appendTo,"gpu-acceleration":!1,pure:"","manual-mode":"",effect:"light",trigger:"click",transition:`${p(s).namespace.value}-zoom-in-top`,persistent:"",role:"listbox",onBeforeShow:A,onHide:I},{content:$(()=>[C("div",{ref_key:"regionRef",ref:c,class:U([p(s).b("suggestion"),p(s).is("loading",M.value)]),style:et({[e.fitInputWidth?"width":"minWidth"]:y.value,outline:"none"}),role:"region"},[J.$slots.header?(x(),O("div",{key:0,class:U(p(s).be("suggestion","header")),onClick:Z[0]||(Z[0]=at(()=>{},["stop"]))},[ce(J.$slots,"header")],2)):te("v-if",!0),L(p(ni),{id:p(S),tag:"ul","wrap-class":p(s).be("suggestion","wrap"),"view-class":p(s).be("suggestion","list"),role:"listbox"},{default:$(()=>[M.value?(x(),O("li",Cse,[ce(J.$slots,"loading",{},()=>[L(p(Xe),{class:U(p(s).is("loading"))},{default:$(()=>[L(p(Mr))]),_:1},8,["class"])])])):(x(!0),O(Ve,{key:1},dt(m.value,(j,oe)=>(x(),O("li",{id:`${p(S)}-item-${oe}`,key:oe,class:U({highlighted:g.value===oe}),role:"option","aria-selected":g.value===oe,onClick:se=>re(j)},[ce(J.$slots,"default",{item:j},()=>[ue(Q(j[e.valueKey]),1)])],10,xse))),128))]),_:3},8,["id","wrap-class","view-class"]),J.$slots.footer?(x(),O("div",{key:1,class:U(p(s).be("suggestion","footer")),onClick:Z[1]||(Z[1]=at(()=>{},["stop"]))},[ce(J.$slots,"footer")],2)):te("v-if",!0)],6)]),default:$(()=>[C("div",{ref_key:"listboxRef",ref:v,class:U([p(s).b(),J.$attrs.class]),style:et(k.value),role:"combobox","aria-haspopup":"listbox","aria-expanded":T.value,"aria-owns":p(S)},[L(p(Ba),Tt({ref_key:"inputRef",ref:u},Tt(o.value,J.$attrs),{"model-value":e.modelValue,disabled:p(l),onInput:G,onChange:H,onFocus:z,onBlur:B,onClear:W,onKeydown:he,onMousedown:K}),xr({_:2},[J.$slots.prepend?{name:"prepend",fn:$(()=>[ce(J.$slots,"prepend")]),key:"0"}:void 0,J.$slots.append?{name:"append",fn:$(()=>[ce(J.$slots,"append")]),key:"1"}:void 0,J.$slots.prefix?{name:"prefix",fn:$(()=>[ce(J.$slots,"prefix")]),key:"2"}:void 0,J.$slots.suffix?{name:"suffix",fn:$(()=>[ce(J.$slots,"suffix")]),key:"3"}:void 0]),1040,["model-value","disabled"])],14,Sse)]),_:3},8,["visible","placement","popper-class","popper-style","popper-options","show-arrow","teleported","append-to","transition"]))}}),Tse=kse;const Mse=wt(Tse),Ese=$e({size:{type:[Number,String],values:ii,validator:e=>Je(e)},shape:{type:String,values:["circle","square"]},icon:{type:pn},src:{type:String,default:""},alt:String,srcSet:String,fit:{type:ae(String),default:"cover"}}),Ase={error:e=>e instanceof Event},sL=Symbol("avatarGroupContextKey"),Ise={size:{type:ae([Number,String]),values:ii,validator:e=>Je(e)},shape:{type:ae(String),values:["circle","square"]},collapseAvatars:Boolean,collapseAvatarsTooltip:Boolean,maxCollapseAvatars:{type:Number,default:1},effect:{type:ae(String),default:"light"},placement:{type:ae(String),values:tl,default:"top"},popperClass:Tn.popperClass,popperStyle:Tn.popperStyle,collapseClass:{type:ae([String,Array,Object,Boolean]),default:void 0},collapseStyle:{type:ae([String,Array,Object,Boolean]),default:void 0}},Lse=["src","alt","srcset"];var Pse=q({name:"ElAvatar",__name:"avatar",props:Ese,emits:Ase,setup(e,{emit:t}){const n=e,a=t,r=Ze(sL,void 0),o=ke("avatar"),i=F(!1),l=D(()=>n.size??(r==null?void 0:r.size)),s=D(()=>n.shape??(r==null?void 0:r.shape)??"circle"),u=D(()=>{const{icon:f}=n,h=[o.b()];return Ye(l.value)&&h.push(o.m(l.value)),f&&h.push(o.m("icon")),s.value&&h.push(o.m(s.value)),h}),c=D(()=>Je(l.value)?o.cssVarBlock({size:Vn(l.value)}):void 0),d=D(()=>({objectFit:n.fit}));ye(()=>[n.src,n.srcSet],()=>i.value=!1);function v(f){i.value=!0,a("error",f)}return(f,h)=>(x(),O("span",{class:U(u.value),style:et(c.value)},[(e.src||e.srcSet)&&!i.value?(x(),O("img",{key:0,src:e.src,alt:e.alt,srcset:e.srcSet,style:et(d.value),onError:v},null,44,Lse)):e.icon?(x(),ne(p(Xe),{key:1},{default:$(()=>[(x(),ne(St(e.icon)))]),_:1})):ce(f.$slots,"default",{key:2})],6))}}),uL=Pse,cL=q({name:"ElAvatarGroup",props:Ise,setup(e,{slots:t}){const n=ke("avatar-group");return Ft(sL,nn({size:cn(e,"size"),shape:cn(e,"shape")})),()=>{var o;const a=eo(((o=t.default)==null?void 0:o.call(t))??[]);let r=a;if(e.collapseAvatars&&a.length>e.maxCollapseAvatars){r=a.slice(0,e.maxCollapseAvatars);const i=a.slice(e.maxCollapseAvatars);r.push(L(Ma,{popperClass:e.popperClass,popperStyle:e.popperStyle,placement:e.placement,effect:e.effect,disabled:!e.collapseAvatarsTooltip},{default:()=>L(uL,{size:e.size,shape:e.shape,class:e.collapseClass,style:e.collapseStyle},{default:()=>[ue("+ "),i.length]}),content:()=>L("div",{class:n.e("collapse-avatars")},[i.map((l,s)=>bn(l)?ei(l,{key:l.key??s}):l)])}))}return L("div",{class:n.b()},[r])}}});const Dse=wt(uL,{AvatarGroup:cL}),Ose=On(cL),$se={visibilityHeight:{type:Number,default:200},target:{type:String,default:""},right:{type:Number,default:40},bottom:{type:Number,default:40}},Rse={click:e=>e instanceof MouseEvent},Nse=(e,t,n)=>{const a=Ht(),r=Ht(),o=F(!1),i=()=>{a.value&&(o.value=a.value.scrollTop>=e.visibilityHeight)},l=s=>{var u;(u=a.value)==null||u.scrollTo({top:0,behavior:"smooth"}),t("click",s)};return on(r,"scroll",LA(i,300,!0)),mt(()=>{r.value=document,a.value=document.documentElement,e.target&&(a.value=document.querySelector(e.target)??void 0,a.value||Dn(n,`target does not exist: ${e.target}`),r.value=a.value),i()}),{visible:o,handleClick:l}},$5="ElBacktop";var Bse=q({name:$5,__name:"backtop",props:$se,emits:Rse,setup(e,{emit:t}){const n=e,a=t,r=ke("backtop"),{handleClick:o,visible:i}=Nse(n,a,$5),l=D(()=>({right:`${n.right}px`,bottom:`${n.bottom}px`}));return(s,u)=>(x(),ne(Pa,{name:`${p(r).namespace.value}-fade-in`},{default:$(()=>[p(i)?(x(),O("div",{key:0,style:et(l.value),class:U(p(r).b()),onClick:u[0]||(u[0]=at((...c)=>p(o)&&p(o)(...c),["stop"]))},[ce(s.$slots,"default",{},()=>[L(p(Xe),{class:U(p(r).e("icon"))},{default:$(()=>[L(p(TI))]),_:1},8,["class"])])],6)):te("v-if",!0)]),_:3},8,["name"]))}}),Vse=Bse;const zse=wt(Vse),Fse=$e({value:{type:[String,Number],default:""},max:{type:Number,default:99},isDot:Boolean,hidden:Boolean,type:{type:String,values:["primary","success","warning","info","danger"],default:"danger"},showZero:{type:Boolean,default:!0},color:String,badgeStyle:{type:ae([String,Object,Array,Boolean]),default:void 0},offset:{type:ae(Array),default:()=>[0,0]},badgeClass:{type:ae([String,Array,Object,Boolean]),default:void 0}});var Hse=q({name:"ElBadge",__name:"badge",props:Fse,setup(e,{expose:t}){const n=e,a=ke("badge"),r=D(()=>n.isDot?"":Je(n.value)&&Je(n.max)?n.max[{backgroundColor:n.color,marginRight:Vn(-n.offset[0]),marginTop:Vn(n.offset[1])},n.badgeStyle??{}]);return t({content:r}),(i,l)=>(x(),O("div",{class:U(p(a).b())},[ce(i.$slots,"default"),L(Pa,{name:`${p(a).namespace.value}-zoom-in-center`},{default:$(()=>[!e.hidden&&(r.value||e.isDot||i.$slots.content)?(x(),O("sup",{key:0,class:U([p(a).e("content"),p(a).em("content",e.type),p(a).is("fixed",!!i.$slots.default),p(a).is("dot",e.isDot),p(a).is("hide-zero",!e.showZero&&e.value===0),e.badgeClass]),style:et(o.value)},[ce(i.$slots,"content",{value:r.value},()=>[ue(Q(r.value),1)])],6)):te("v-if",!0)]),_:3},8,["name"])],2))}}),Wse=Hse;const dL=wt(Wse),Kse=$e({separator:{type:String,default:"/"},separatorIcon:{type:pn}}),fL=Symbol("breadcrumbKey"),Use=$e({to:{type:ae([String,Object]),default:""},replace:Boolean}),Gse=["aria-label"];var Yse=q({name:"ElBreadcrumb",__name:"breadcrumb",props:Kse,setup(e){const{t}=Vt(),n=e,a=ke("breadcrumb"),r=F();return Ft(fL,n),mt(()=>{const o=r.value.querySelectorAll(`.${a.e("item")}`);o.length&&o[o.length-1].setAttribute("aria-current","page")}),(o,i)=>(x(),O("div",{ref_key:"breadcrumb",ref:r,class:U(p(a).b()),"aria-label":p(t)("el.breadcrumb.label"),role:"navigation"},[ce(o.$slots,"default")],10,Gse))}}),jse=Yse,qse=q({name:"ElBreadcrumbItem",__name:"breadcrumb-item",props:Use,setup(e){const t=e,n=Et(),a=Ze(fL,void 0),r=ke("breadcrumb"),o=n.appContext.config.globalProperties.$router,i=()=>{!t.to||!o||(t.replace?o.replace(t.to):o.push(t.to))};return(l,s)=>{var u,c;return x(),O("span",{class:U(p(r).e("item"))},[C("span",{class:U([p(r).e("inner"),p(r).is("link",!!e.to)]),role:"link",onClick:i},[ce(l.$slots,"default")],2),(u=p(a))!=null&&u.separatorIcon?(x(),ne(p(Xe),{key:0,class:U(p(r).e("separator"))},{default:$(()=>[(x(),ne(St(p(a).separatorIcon)))]),_:1},8,["class"])):(x(),O("span",{key:1,class:U(p(r).e("separator")),role:"presentation"},Q((c=p(a))==null?void 0:c.separator),3))],2)}}}),vL=qse;const Xse=wt(jse,{BreadcrumbItem:vL}),Zse=On(vL),x4=["default","primary","success","warning","info","danger","text",""],Jse=["button","submit","reset"],k4=$e({size:pa,disabled:{type:Boolean,default:void 0},type:{type:String,values:x4,default:""},icon:{type:pn},nativeType:{type:String,values:Jse,default:"button"},loading:Boolean,loadingIcon:{type:pn,default:()=>Mr},plain:{type:Boolean,default:void 0},text:{type:Boolean,default:void 0},link:Boolean,bg:Boolean,autofocus:Boolean,round:{type:Boolean,default:void 0},circle:Boolean,dashed:{type:Boolean,default:void 0},color:String,dark:Boolean,autoInsertSpace:{type:Boolean,default:void 0},tag:{type:ae([String,Object]),default:"button"}}),Qse={click:e=>e instanceof MouseEvent},hL=Symbol(),Xm=F();function Hl(e,t=void 0){const n=Et()?Ze(hL,Xm):Xm;return e?D(()=>{var a;return((a=n.value)==null?void 0:a[e])??t}):n}function W1(e,t){const n=Hl(),a=ke(e,D(()=>{var l;return((l=n.value)==null?void 0:l.namespace)||"el"})),r=Vt(D(()=>{var l;return(l=n.value)==null?void 0:l.locale})),o=tp(D(()=>{var s;const l=(s=n.value)==null?void 0:s.zIndex;return Un(l)||Number.isNaN(l)?rI:l})),i=D(()=>{var l;return p(t)||((l=n.value)==null?void 0:l.size)||""});return ew(D(()=>p(n)||{})),{ns:a,locale:r,zIndex:o,size:i}}const ew=(e,t,n=!1)=>{const a=!!Et(),r=a?Hl():void 0,o=(t==null?void 0:t.provide)??(a?Ft:void 0);if(!o){un("provideGlobalConfig","provideGlobalConfig() can only be used inside setup().");return}const i=D(()=>{const l=p(e);return r!=null&&r.value?eue(r.value,l):l});return o(hL,i),o(VA,D(()=>i.value.locale)),o(zA,D(()=>i.value.namespace)),o(oI,D(()=>i.value.zIndex)),o(yI,{size:D(()=>i.value.size||"")}),o(_I,D(()=>({emptyValues:i.value.emptyValues,valueOnClear:i.value.valueOnClear}))),(n||!Xm.value)&&(Xm.value=i.value),i},eue=(e,t)=>{const n=[...new Set([...sh(e),...sh(t)])],a={};for(const r of n)a[r]=t[r]!==void 0?t[r]:e[r];return a},tue=$e({a11y:{type:Boolean,default:!0},locale:{type:ae(Object)},size:pa,button:{type:ae(Object)},card:{type:ae(Object)},dialog:{type:ae(Object)},link:{type:ae(Object)},experimentalFeatures:{type:ae(Object)},keyboardNavigation:{type:Boolean,default:!0},message:{type:ae(Object)},zIndex:Number,namespace:{type:String,default:"el"},table:{type:ae(Object)},...Mc}),Qa={placement:"top"},nue=q({name:"ElConfigProvider",props:tue,setup(e,{slots:t}){const n=ew(e);return ye(()=>e.message,a=>{var r;Object.assign(Qa,((r=n==null?void 0:n.value)==null?void 0:r.message)??{},a??{})},{immediate:!0,deep:!0}),()=>ce(t,"default",{config:n==null?void 0:n.value})}}),aue=wt(nue),pL=Symbol("buttonGroupContextKey"),rue=(e,t)=>{Ri({from:"type.text",replacement:"link",version:"3.0.0",scope:"props",ref:"https://element-plus.org/en-US/component/button.html#button-attributes"},D(()=>e.type==="text"));const n=Ze(pL,void 0),a=Hl("button"),{form:r}=Ea(),o=ca(D(()=>n==null?void 0:n.size)),i=$n(),l=F(),s=Xn(),u=D(()=>{var b;return e.type||(n==null?void 0:n.type)||((b=a.value)==null?void 0:b.type)||""}),c=D(()=>{var b;return e.autoInsertSpace??((b=a.value)==null?void 0:b.autoInsertSpace)??!1}),d=D(()=>{var b;return e.plain??((b=a.value)==null?void 0:b.plain)??!1}),v=D(()=>{var b;return e.round??((b=a.value)==null?void 0:b.round)??!1}),f=D(()=>{var b;return e.text??((b=a.value)==null?void 0:b.text)??!1}),h=D(()=>{var b;return e.dashed??((b=a.value)==null?void 0:b.dashed)??!1}),m=D(()=>e.tag==="button"?{ariaDisabled:i.value||e.loading,disabled:i.value||e.loading,autofocus:e.autofocus,type:e.nativeType}:{}),g=D(()=>{var _;const b=(_=s.default)==null?void 0:_.call(s);if(c.value&&(b==null?void 0:b.length)===1){const w=b[0];if((w==null?void 0:w.type)===uf){const S=w.children;return new RegExp("^\\p{Unified_Ideograph}{2}$","u").test(S.trim())}}return!1});return{_disabled:i,_size:o,_type:u,_ref:l,_props:m,_plain:d,_round:v,_text:f,_dashed:h,shouldAddSpace:g,handleClick:b=>{if(i.value||e.loading){b.stopPropagation();return}e.nativeType==="reset"&&(r==null||r.resetFields()),t("click",b)}}};function Ya(e,t){oue(e)&&(e="100%");const n=iue(e);return e=t===360?e:Math.min(t,Math.max(0,parseFloat(e))),n&&(e=parseInt(String(e*t),10)/100),Math.abs(e-t)<1e-6?1:(t===360?e=(e<0?e%t+t:e%t)/parseFloat(String(t)):e=e%t/parseFloat(String(t)),e)}function Fp(e){return Math.min(1,Math.max(0,e))}function oue(e){return typeof e=="string"&&e.indexOf(".")!==-1&&parseFloat(e)===1}function iue(e){return typeof e=="string"&&e.indexOf("%")!==-1}function mL(e){return e=parseFloat(e),(isNaN(e)||e<0||e>1)&&(e=1),e}function Hp(e){return Number(e)<=1?`${Number(e)*100}%`:e}function Ru(e){return e.length===1?"0"+e:String(e)}function lue(e,t,n){return{r:Ya(e,255)*255,g:Ya(t,255)*255,b:Ya(n,255)*255}}function R5(e,t,n){e=Ya(e,255),t=Ya(t,255),n=Ya(n,255);const a=Math.max(e,t,n),r=Math.min(e,t,n);let o=0,i=0;const l=(a+r)/2;if(a===r)i=0,o=0;else{const s=a-r;switch(i=l>.5?s/(2-a-r):s/(a+r),a){case e:o=(t-n)/s+(t1&&(n-=1),n<1/6?e+(t-e)*(6*n):n<1/2?t:n<2/3?e+(t-e)*(2/3-n)*6:e}function sue(e,t,n){let a,r,o;if(e=Ya(e,360),t=Ya(t,100),n=Ya(n,100),t===0)r=n,o=n,a=n;else{const i=n<.5?n*(1+t):n+t-n*t,l=2*n-i;a=Z2(l,i,e+1/3),r=Z2(l,i,e),o=Z2(l,i,e-1/3)}return{r:a*255,g:r*255,b:o*255}}function N5(e,t,n){e=Ya(e,255),t=Ya(t,255),n=Ya(n,255);const a=Math.max(e,t,n),r=Math.min(e,t,n);let o=0;const i=a,l=a-r,s=a===0?0:l/a;if(a===r)o=0;else{switch(a){case e:o=(t-n)/l+(t>16,g:(e&65280)>>8,b:e&255}}const T4={aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyan:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgreen:"#006400",darkgrey:"#a9a9a9",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",goldenrod:"#daa520",gold:"#ffd700",gray:"#808080",green:"#008000",greenyellow:"#adff2f",grey:"#808080",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavenderblush:"#fff0f5",lavender:"#e6e6fa",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgreen:"#90ee90",lightgrey:"#d3d3d3",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",rebeccapurple:"#663399",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",steelblue:"#4682b4",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",tomato:"#ff6347",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"};function hue(e){let t={r:0,g:0,b:0},n=1,a=null,r=null,o=null,i=!1,l=!1;return typeof e=="string"&&(e=gue(e)),typeof e=="object"&&(qr(e.r)&&qr(e.g)&&qr(e.b)?(t=lue(e.r,e.g,e.b),i=!0,l=String(e.r).substr(-1)==="%"?"prgb":"rgb"):qr(e.h)&&qr(e.s)&&qr(e.v)?(a=Hp(e.s),r=Hp(e.v),t=uue(e.h,a,r),i=!0,l="hsv"):qr(e.h)&&qr(e.s)&&qr(e.l)?(a=Hp(e.s),o=Hp(e.l),t=sue(e.h,a,o),i=!0,l="hsl"):qr(e.c)&&qr(e.m)&&qr(e.y)&&qr(e.k)&&(t=due(e.c,e.m,e.y,e.k),i=!0,l="cmyk"),Object.prototype.hasOwnProperty.call(e,"a")&&(n=e.a)),n=mL(n),{ok:i,format:e.format||l,r:Math.min(255,Math.max(t.r,0)),g:Math.min(255,Math.max(t.g,0)),b:Math.min(255,Math.max(t.b,0)),a:n}}const pue="[-\\+]?\\d+%?",mue="[-\\+]?\\d*\\.\\d+%?",ms="(?:"+mue+")|(?:"+pue+")",J2="[\\s|\\(]+("+ms+")[,|\\s]+("+ms+")[,|\\s]+("+ms+")\\s*\\)?",Wp="[\\s|\\(]+("+ms+")[,|\\s]+("+ms+")[,|\\s]+("+ms+")[,|\\s]+("+ms+")\\s*\\)?",ho={CSS_UNIT:new RegExp(ms),rgb:new RegExp("rgb"+J2),rgba:new RegExp("rgba"+Wp),hsl:new RegExp("hsl"+J2),hsla:new RegExp("hsla"+Wp),hsv:new RegExp("hsv"+J2),hsva:new RegExp("hsva"+Wp),cmyk:new RegExp("cmyk"+Wp),hex3:/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex6:/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,hex4:/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex8:/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/};function gue(e){if(e=e.trim().toLowerCase(),e.length===0)return!1;let t=!1;if(T4[e])e=T4[e],t=!0;else if(e==="transparent")return{r:0,g:0,b:0,a:0,format:"name"};let n=ho.rgb.exec(e);return n?{r:n[1],g:n[2],b:n[3]}:(n=ho.rgba.exec(e),n?{r:n[1],g:n[2],b:n[3],a:n[4]}:(n=ho.hsl.exec(e),n?{h:n[1],s:n[2],l:n[3]}:(n=ho.hsla.exec(e),n?{h:n[1],s:n[2],l:n[3],a:n[4]}:(n=ho.hsv.exec(e),n?{h:n[1],s:n[2],v:n[3]}:(n=ho.hsva.exec(e),n?{h:n[1],s:n[2],v:n[3],a:n[4]}:(n=ho.cmyk.exec(e),n?{c:n[1],m:n[2],y:n[3],k:n[4]}:(n=ho.hex8.exec(e),n?{r:Xr(n[1]),g:Xr(n[2]),b:Xr(n[3]),a:z5(n[4]),format:t?"name":"hex8"}:(n=ho.hex6.exec(e),n?{r:Xr(n[1]),g:Xr(n[2]),b:Xr(n[3]),format:t?"name":"hex"}:(n=ho.hex4.exec(e),n?{r:Xr(n[1]+n[1]),g:Xr(n[2]+n[2]),b:Xr(n[3]+n[3]),a:z5(n[4]+n[4]),format:t?"name":"hex8"}:(n=ho.hex3.exec(e),n?{r:Xr(n[1]+n[1]),g:Xr(n[2]+n[2]),b:Xr(n[3]+n[3]),format:t?"name":"hex"}:!1))))))))))}function qr(e){return typeof e=="number"?!Number.isNaN(e):ho.CSS_UNIT.test(e)}class ea{constructor(t="",n={}){if(t instanceof ea)return t;typeof t=="number"&&(t=vue(t)),this.originalInput=t;const a=hue(t);this.originalInput=t,this.r=a.r,this.g=a.g,this.b=a.b,this.a=a.a,this.roundA=Math.round(100*this.a)/100,this.format=n.format??a.format,this.gradientType=n.gradientType,this.r<1&&(this.r=Math.round(this.r)),this.g<1&&(this.g=Math.round(this.g)),this.b<1&&(this.b=Math.round(this.b)),this.isValid=a.ok}isDark(){return this.getBrightness()<128}isLight(){return!this.isDark()}getBrightness(){const t=this.toRgb();return(t.r*299+t.g*587+t.b*114)/1e3}getLuminance(){const t=this.toRgb();let n,a,r;const o=t.r/255,i=t.g/255,l=t.b/255;return o<=.03928?n=o/12.92:n=Math.pow((o+.055)/1.055,2.4),i<=.03928?a=i/12.92:a=Math.pow((i+.055)/1.055,2.4),l<=.03928?r=l/12.92:r=Math.pow((l+.055)/1.055,2.4),.2126*n+.7152*a+.0722*r}getAlpha(){return this.a}setAlpha(t){return this.a=mL(t),this.roundA=Math.round(100*this.a)/100,this}isMonochrome(){const{s:t}=this.toHsl();return t===0}toHsv(){const t=N5(this.r,this.g,this.b);return{h:t.h*360,s:t.s,v:t.v,a:this.a}}toHsvString(){const t=N5(this.r,this.g,this.b),n=Math.round(t.h*360),a=Math.round(t.s*100),r=Math.round(t.v*100);return this.a===1?`hsv(${n}, ${a}%, ${r}%)`:`hsva(${n}, ${a}%, ${r}%, ${this.roundA})`}toHsl(){const t=R5(this.r,this.g,this.b);return{h:t.h*360,s:t.s,l:t.l,a:this.a}}toHslString(){const t=R5(this.r,this.g,this.b),n=Math.round(t.h*360),a=Math.round(t.s*100),r=Math.round(t.l*100);return this.a===1?`hsl(${n}, ${a}%, ${r}%)`:`hsla(${n}, ${a}%, ${r}%, ${this.roundA})`}toHex(t=!1){return B5(this.r,this.g,this.b,t)}toHexString(t=!1){return"#"+this.toHex(t)}toHex8(t=!1){return cue(this.r,this.g,this.b,this.a,t)}toHex8String(t=!1){return"#"+this.toHex8(t)}toHexShortString(t=!1){return this.a===1?this.toHexString(t):this.toHex8String(t)}toRgb(){return{r:Math.round(this.r),g:Math.round(this.g),b:Math.round(this.b),a:this.a}}toRgbString(){const t=Math.round(this.r),n=Math.round(this.g),a=Math.round(this.b);return this.a===1?`rgb(${t}, ${n}, ${a})`:`rgba(${t}, ${n}, ${a}, ${this.roundA})`}toPercentageRgb(){const t=n=>`${Math.round(Ya(n,255)*100)}%`;return{r:t(this.r),g:t(this.g),b:t(this.b),a:this.a}}toPercentageRgbString(){const t=n=>Math.round(Ya(n,255)*100);return this.a===1?`rgb(${t(this.r)}%, ${t(this.g)}%, ${t(this.b)}%)`:`rgba(${t(this.r)}%, ${t(this.g)}%, ${t(this.b)}%, ${this.roundA})`}toCmyk(){return{...V5(this.r,this.g,this.b)}}toCmykString(){const{c:t,m:n,y:a,k:r}=V5(this.r,this.g,this.b);return`cmyk(${t}, ${n}, ${a}, ${r})`}toName(){if(this.a===0)return"transparent";if(this.a<1)return!1;const t="#"+B5(this.r,this.g,this.b,!1);for(const[n,a]of Object.entries(T4))if(t===a)return n;return!1}toString(t){const n=!!t;t=t??this.format;let a=!1;const r=this.a<1&&this.a>=0;return!n&&r&&(t.startsWith("hex")||t==="name")?t==="name"&&this.a===0?this.toName():this.toRgbString():(t==="rgb"&&(a=this.toRgbString()),t==="prgb"&&(a=this.toPercentageRgbString()),(t==="hex"||t==="hex6")&&(a=this.toHexString()),t==="hex3"&&(a=this.toHexString(!0)),t==="hex4"&&(a=this.toHex8String(!0)),t==="hex8"&&(a=this.toHex8String()),t==="name"&&(a=this.toName()),t==="hsl"&&(a=this.toHslString()),t==="hsv"&&(a=this.toHsvString()),t==="cmyk"&&(a=this.toCmykString()),a||this.toHexString())}toNumber(){return(Math.round(this.r)<<16)+(Math.round(this.g)<<8)+Math.round(this.b)}clone(){return new ea(this.toString())}lighten(t=10){const n=this.toHsl();return n.l+=t/100,n.l=Fp(n.l),new ea(n)}brighten(t=10){const n=this.toRgb();return n.r=Math.max(0,Math.min(255,n.r-Math.round(255*-(t/100)))),n.g=Math.max(0,Math.min(255,n.g-Math.round(255*-(t/100)))),n.b=Math.max(0,Math.min(255,n.b-Math.round(255*-(t/100)))),new ea(n)}darken(t=10){const n=this.toHsl();return n.l-=t/100,n.l=Fp(n.l),new ea(n)}tint(t=10){return this.mix("white",t)}shade(t=10){return this.mix("black",t)}desaturate(t=10){const n=this.toHsl();return n.s-=t/100,n.s=Fp(n.s),new ea(n)}saturate(t=10){const n=this.toHsl();return n.s+=t/100,n.s=Fp(n.s),new ea(n)}greyscale(){return this.desaturate(100)}spin(t){const n=this.toHsl(),a=(n.h+t)%360;return n.h=a<0?360+a:a,new ea(n)}mix(t,n=50){const a=this.toRgb(),r=new ea(t).toRgb(),o=n/100,i={r:(r.r-a.r)*o+a.r,g:(r.g-a.g)*o+a.g,b:(r.b-a.b)*o+a.b,a:(r.a-a.a)*o+a.a};return new ea(i)}analogous(t=6,n=30){const a=this.toHsl(),r=360/n,o=[this];for(a.h=(a.h-(r*t>>1)+720)%360;--t;)a.h=(a.h+r)%360,o.push(new ea(a));return o}complement(){const t=this.toHsl();return t.h=(t.h+180)%360,new ea(t)}monochromatic(t=6){const n=this.toHsv(),{h:a}=n,{s:r}=n;let{v:o}=n;const i=[],l=1/t;for(;t--;)i.push(new ea({h:a,s:r,v:o})),o=(o+l)%1;return i}splitcomplement(){const t=this.toHsl(),{h:n}=t;return[this,new ea({h:(n+72)%360,s:t.s,l:t.l}),new ea({h:(n+216)%360,s:t.s,l:t.l})]}onBackground(t){const n=this.toRgb(),a=new ea(t).toRgb(),r=n.a+a.a*(1-n.a);return new ea({r:(n.r*n.a+a.r*a.a*(1-n.a))/r,g:(n.g*n.a+a.g*a.a*(1-n.a))/r,b:(n.b*n.a+a.b*a.a*(1-n.a))/r,a:r})}triad(){return this.polyad(3)}tetrad(){return this.polyad(4)}polyad(t){const n=this.toHsl(),{h:a}=n,r=[this],o=360/t;for(let i=1;i{let a={},r=e.color;if(r){const o=r.match(/var\((.*?)\)/);o&&(r=window.getComputedStyle(window.document.documentElement).getPropertyValue(o[1]));const i=new ea(r),l=e.dark?i.tint(20).toString():gi(i,20);if(e.plain)a=n.cssVarBlock({"bg-color":e.dark?gi(i,90):i.tint(90).toString(),"text-color":r,"border-color":e.dark?gi(i,50):i.tint(50).toString(),"hover-text-color":`var(${n.cssVarName("color-white")})`,"hover-bg-color":r,"hover-border-color":r,"active-bg-color":l,"active-text-color":`var(${n.cssVarName("color-white")})`,"active-border-color":l}),t.value&&(a[n.cssVarBlockName("disabled-bg-color")]=e.dark?gi(i,90):i.tint(90).toString(),a[n.cssVarBlockName("disabled-text-color")]=e.dark?gi(i,50):i.tint(50).toString(),a[n.cssVarBlockName("disabled-border-color")]=e.dark?gi(i,80):i.tint(80).toString());else if(e.link||e.text){const s=e.dark?gi(i,30):i.tint(30).toString();if(a=n.cssVarBlock({"text-color":r,"hover-text-color":s,"active-text-color":l}),e.link&&(a[n.cssVarBlockName("hover-link-text-color")]=s,a[n.cssVarBlockName("active-color")]=l),t.value){const u=e.dark?gi(i,50):i.tint(50).toString();a[n.cssVarBlockName("disabled-bg-color")]="transparent",a[n.cssVarBlockName("disabled-text-color")]=u,a[n.cssVarBlockName("disabled-border-color")]="transparent"}}else{const s=e.dark?gi(i,30):i.tint(30).toString(),u=i.isDark()?`var(${n.cssVarName("color-white")})`:`var(${n.cssVarName("color-black")})`;if(a=n.cssVarBlock({"bg-color":r,"text-color":u,"border-color":r,"hover-bg-color":s,"hover-text-color":u,"hover-border-color":s,"active-bg-color":l,"active-border-color":l}),t.value){const c=e.dark?gi(i,50):i.tint(50).toString();a[n.cssVarBlockName("disabled-bg-color")]=c,a[n.cssVarBlockName("disabled-text-color")]=e.dark?"rgba(255, 255, 255, 0.5)":`var(${n.cssVarName("color-white")})`,a[n.cssVarBlockName("disabled-border-color")]=c}}}return a})}var bue=q({name:"ElButton",__name:"button",props:k4,emits:Qse,setup(e,{expose:t,emit:n}){const a=e,r=n,o=yue(a),i=ke("button"),{_ref:l,_size:s,_type:u,_disabled:c,_props:d,_plain:v,_round:f,_text:h,_dashed:m,shouldAddSpace:g,handleClick:y}=rue(a,r),b=D(()=>[i.b(),i.m(u.value),i.m(s.value),i.is("disabled",c.value),i.is("loading",a.loading),i.is("plain",v.value),i.is("round",f.value),i.is("circle",a.circle),i.is("text",h.value),i.is("dashed",m.value),i.is("link",a.link),i.is("has-bg",a.bg)]);return t({ref:l,size:s,type:u,disabled:c,shouldAddSpace:g}),(_,w)=>(x(),ne(St(e.tag),Tt({ref_key:"_ref",ref:l},p(d),{class:b.value,style:p(o),onClick:p(y)}),{default:$(()=>[e.loading?(x(),O(Ve,{key:0},[_.$slots.loading?ce(_.$slots,"loading",{key:0}):(x(),ne(p(Xe),{key:1,class:U(p(i).is("loading"))},{default:$(()=>[(x(),ne(St(e.loadingIcon)))]),_:1},8,["class"]))],64)):e.icon||_.$slots.icon?(x(),ne(p(Xe),{key:1},{default:$(()=>[e.icon?(x(),ne(St(e.icon),{key:0})):ce(_.$slots,"icon",{key:1})]),_:3})):te("v-if",!0),_.$slots.default?(x(),O("span",{key:2,class:U({[p(i).em("text","expand")]:p(g)})},[ce(_.$slots,"default")],2)):te("v-if",!0)]),_:3},16,["class","style","onClick"]))}}),_ue=bue;const wue={size:k4.size,type:k4.type,direction:{type:ae(String),values:["horizontal","vertical"],default:"horizontal"}};var Sue=q({name:"ElButtonGroup",__name:"button-group",props:wue,setup(e){const t=e;Ft(pL,nn({size:cn(t,"size"),type:cn(t,"type")}));const n=ke("button");return(a,r)=>(x(),O("div",{class:U([p(n).b("group"),p(n).bm("group",t.direction)])},[ce(a.$slots,"default")],2))}}),gL=Sue;const xa=wt(_ue,{ButtonGroup:gL}),yL=On(gL),Cue=e=>Ae(e)&&e.length===2&&e.every(t=>Is(t)),xue=$e({modelValue:{type:Date},range:{type:ae(Array),validator:Cue},controllerType:{type:String,values:["button","select"],default:"button"},formatter:{type:ae(Function)}}),kue={[bt]:e=>Is(e),[ua]:e=>Is(e)},F5=["hours","minutes","seconds"],nl="EP_PICKER_BASE",tw="ElPopperOptions",bL=Symbol("commonPickerContextKey"),H5="HH:mm:ss",ad="YYYY-MM-DD",Tue={date:ad,dates:ad,week:"gggg[w]ww",year:"YYYY",years:"YYYY",month:"YYYY-MM",months:"YYYY-MM",datetime:`${ad} ${H5}`,monthrange:"YYYY-MM",yearrange:"YYYY",daterange:ad,datetimerange:`${ad} ${H5}`};var Wl=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{};function Kl(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}var _L={exports:{}};(function(e,t){(function(n,a){e.exports=a()})(Wl,function(){var n=1e3,a=6e4,r=36e5,o="millisecond",i="second",l="minute",s="hour",u="day",c="week",d="month",v="quarter",f="year",h="date",m="Invalid Date",g=/^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/,y=/\[([^\]]+)]|YYYY|YY|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g,b={name:"en",weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),ordinal:function(G){var K=["th","st","nd","rd"],H=G%100;return"["+G+(K[(H-20)%10]||K[H]||K[0])+"]"}},_=function(G,K,H){var z=String(G);return!z||z.length>=K?G:""+Array(K+1-z.length).join(H)+G},w={s:_,z:function(G){var K=-G.utcOffset(),H=Math.abs(K),z=Math.floor(H/60),B=H%60;return(K<=0?"+":"-")+_(z,2,"0")+":"+_(B,2,"0")},m:function G(K,H){if(K.date()1)return G(N[0])}else{var V=K.name;k[V]=K,B=V}return!z&&B&&(S=B),B||!z&&S},A=function(G,K){if(M(G))return G.clone();var H=typeof K=="object"?K:{};return H.date=G,H.args=arguments,new P(H)},I=w;I.l=E,I.i=M,I.w=function(G,K){return A(G,{locale:K.$L,utc:K.$u,x:K.$x,$offset:K.$offset})};var P=function(){function G(H){this.$L=E(H.locale,null,!0),this.parse(H),this.$x=this.$x||H.x||{},this[T]=!0}var K=G.prototype;return K.parse=function(H){this.$d=function(z){var B=z.date,W=z.utc;if(B===null)return new Date(NaN);if(I.u(B))return new Date;if(B instanceof Date)return new Date(B);if(typeof B=="string"&&!/Z$/i.test(B)){var N=B.match(g);if(N){var V=N[2]-1||0,Y=(N[7]||"0").substring(0,3);return W?new Date(Date.UTC(N[1],V,N[3]||1,N[4]||0,N[5]||0,N[6]||0,Y)):new Date(N[1],V,N[3]||1,N[4]||0,N[5]||0,N[6]||0,Y)}}return new Date(B)}(H),this.init()},K.init=function(){var H=this.$d;this.$y=H.getFullYear(),this.$M=H.getMonth(),this.$D=H.getDate(),this.$W=H.getDay(),this.$H=H.getHours(),this.$m=H.getMinutes(),this.$s=H.getSeconds(),this.$ms=H.getMilliseconds()},K.$utils=function(){return I},K.isValid=function(){return this.$d.toString()!==m},K.isSame=function(H,z){var B=A(H);return this.startOf(z)<=B&&B<=this.endOf(z)},K.isAfter=function(H,z){return A(H)[e>0?e-1:void 0,e,eArray.from(Array.from({length:e}).keys()),wL=e=>e.replace(/\W?m{1,2}|\W?ZZ/g,"").replace(/\W?h{1,2}|\W?s{1,3}|\W?a/gi,"").trim(),SL=e=>e.replace(/\W?D{1,2}|\W?Do|\W?d{1,4}|\W?M{1,4}|\W?Y{2,4}/g,"").trim(),W5=function(e,t){const n=Is(e),a=Is(t);return n&&a?e.getTime()===t.getTime():!n&&!a?e===t:!1},CL=function(e,t){const n=Ae(e),a=Ae(t);return n&&a?e.length!==t.length?!1:e.every((r,o)=>W5(r,t[o])):!n&&!a?W5(e,t):!1},K5=function(e,t,n){const a=_r(t)||t==="x"?gt(e).locale(n):gt(e,t).locale(n);return a.isValid()?a:void 0},U5=function(e,t,n){return _r(t)?e:t==="x"?+e:gt(e).locale(n).format(t)},ey=(e,t)=>{const n=[],a=t==null?void 0:t();for(let r=0;rAe(e)?e.map(t=>t.toDate()):e.toDate(),nw=$e({disabledHours:{type:ae(Function)},disabledMinutes:{type:ae(Function)},disabledSeconds:{type:ae(Function)}}),xL=$e({visible:Boolean,actualVisible:{type:Boolean,default:void 0},format:{type:String,default:""}}),aw=$e({automaticDropdown:{type:Boolean,default:!0},id:{type:ae([Array,String])},name:{type:ae([Array,String])},popperClass:Tn.popperClass,popperStyle:Tn.popperStyle,format:String,valueFormat:String,dateFormat:String,timeFormat:String,type:{type:String,default:""},clearable:{type:Boolean,default:!0},clearIcon:{type:ae([String,Object]),default:ci},editable:{type:Boolean,default:!0},saveOnBlur:{type:Boolean,default:!0},prefixIcon:{type:ae([String,Object]),default:""},size:pa,readonly:Boolean,disabled:{type:Boolean,default:void 0},placeholder:{type:String,default:""},popperOptions:{type:ae(Object),default:()=>({})},modelValue:{type:ae([Date,Array,String,Number]),default:""},rangeSeparator:{type:String,default:"-"},startPlaceholder:String,endPlaceholder:String,defaultValue:{type:ae([Date,Array])},defaultTime:{type:ae([Date,Array])},isRange:Boolean,...nw,disabledDate:{type:Function},cellClassName:{type:Function},shortcuts:{type:Array,default:()=>[]},arrowControl:Boolean,tabindex:{type:ae([String,Number]),default:0},validateEvent:{type:Boolean,default:!0},unlinkPanels:Boolean,singlePanel:Boolean,placement:{type:ae(String),values:tl,default:"bottom"},fallbackPlacements:{type:ae(Array),default:["bottom","top","right","left"]},...Mc,...dr(["ariaLabel"]),showNow:{type:Boolean,default:!0},showConfirm:{type:Boolean,default:!0},showFooter:{type:Boolean,default:!0},showWeekNumber:Boolean}),Eue=$e({id:{type:ae(Array)},name:{type:ae(Array)},modelValue:{type:ae([Array,String])},startPlaceholder:String,endPlaceholder:String,disabled:Boolean}),kL=(e,t)=>{const{lang:n}=Vt(),a=F(!1),r=F(!1),o=F(null),i=D(()=>{const{modelValue:h}=e;return!h||Ae(h)&&!h.filter(Boolean).length}),l=h=>{if(!CL(e.modelValue,h)){let m;Ae(h)?m=h.map(g=>U5(g,e.valueFormat,n.value)):h&&(m=U5(h,e.valueFormat,n.value)),t(bt,h&&m,n.value)}},s=D(()=>{let h;if(i.value?u.value.getDefaultValue&&(h=u.value.getDefaultValue()):Ae(e.modelValue)?h=e.modelValue.map(m=>K5(m,e.valueFormat,n.value)):h=K5(e.modelValue??"",e.valueFormat,n.value),u.value.getRangeAvailableTime){const m=u.value.getRangeAvailableTime(h);Rn(m,h)||(h=m,i.value||l(Q0(h)))}return Ae(h)&&h.some(m=>!m)&&(h=[]),h}),u=F({});return{parsedValue:s,pickerActualVisible:r,pickerOptions:u,pickerVisible:a,userInput:o,valueIsEmpty:i,emitInput:l,onCalendarChange:h=>{t("calendar-change",h)},onPanelChange:(h,m,g)=>{t("panel-change",h,m,g)},onPick:(h="",m=!1)=>{a.value=m;let g;Ae(h)?g=h.map(y=>y.toDate()):g=h&&h.toDate(),o.value=null,l(g)},onSetPickerOption:h=>{u.value[h[0]]=h[1],u.value.panelReady=!0}}},Aue=["id","name","placeholder","value","disabled"],Iue=["id","name","placeholder","value","disabled"];var Lue=q({name:"PickerRangeTrigger",inheritAttrs:!1,__name:"picker-range-trigger",props:Eue,emits:["mouseenter","mouseleave","click","touchstart","focus","blur","startInput","endInput","startChange","endChange"],setup(e,{expose:t,emit:n}){const a=e,r=n,{formItem:o}=Ea(),{inputId:i}=Yr(nn({id:D(()=>{var M;return(M=a.id)==null?void 0:M[0]})}),{formItemContext:o}),l=A1(),s=ke("date"),u=ke("range"),c=F(),d=F(),{wrapperRef:v,isFocused:f}=Fl(c,{disabled:D(()=>a.disabled)}),h=M=>{r("click",M)},m=M=>{r("mouseenter",M)},g=M=>{r("mouseleave",M)},y=M=>{r("touchstart",M)},b=M=>{r("startInput",M)},_=M=>{r("endInput",M)},w=M=>{r("startChange",M)},S=M=>{r("endChange",M)};return t({focus:()=>{var M;(M=c.value)==null||M.focus()},blur:()=>{var M,E;(M=c.value)==null||M.blur(),(E=d.value)==null||E.blur()}}),(M,E)=>(x(),O("div",{ref_key:"wrapperRef",ref:v,class:U([p(s).is("active",p(f)),M.$attrs.class]),style:et(M.$attrs.style),onClick:h,onMouseenter:m,onMouseleave:g,onTouchstartPassive:y},[ce(M.$slots,"prefix"),C("input",Tt(p(l),{id:p(i),ref_key:"inputRef",ref:c,name:M.name&&M.name[0],placeholder:M.startPlaceholder,value:M.modelValue&&M.modelValue[0],class:p(u).b("input"),disabled:M.disabled,onInput:b,onChange:w}),null,16,Aue),ce(M.$slots,"range-separator"),C("input",Tt(p(l),{id:M.id&&M.id[1],ref_key:"endInputRef",ref:d,name:M.name&&M.name[1],placeholder:M.endPlaceholder,value:M.modelValue&&M.modelValue[1],class:p(u).b("input"),disabled:M.disabled,onInput:_,onChange:S}),null,16,Iue),ce(M.$slots,"suffix")],38))}}),Pue=Lue,Due=q({name:"Picker",__name:"picker",props:aw,emits:[bt,Ot,"focus","blur","clear","calendar-change","panel-change","visible-change","keydown"],setup(e,{expose:t,emit:n}){const a=e,r=n,o=Nl(),i=ke("date"),l=ke("input"),s=ke("range"),{formItem:u}=Ea(),c=Ze(tw,{}),d=op(a,null),v=F(),f=F(),h=F(null);let m=!1;const g=$n(),y=kL(a,r),{parsedValue:b,pickerActualVisible:_,userInput:w,pickerVisible:S,pickerOptions:k,valueIsEmpty:T,emitInput:M,onPick:E,onSetPickerOption:A,onCalendarChange:I,onPanelChange:P}=y,{isFocused:R,handleFocus:G,handleBlur:K}=Fl(f,{disabled:g,beforeFocus(){return a.readonly},afterFocus(){a.automaticDropdown&&(S.value=!0)},beforeBlur(Me){var Ue;return!m&&((Ue=v.value)==null?void 0:Ue.isFocusInsideContent(Me))},afterBlur(){var Me,Ue;J.value&&!a.saveOnBlur?T.value||(Ue=(Me=k.value).handleCancel)==null||Ue.call(Me):ze(),S.value=!1,m=!1,a.validateEvent&&(u==null||u.validate("blur").catch(kt))}}),H=F(!1),z=D(()=>[i.b("editor"),i.bm("editor",a.type),l.e("wrapper"),i.is("disabled",g.value),i.is("active",S.value),s.b("editor"),Le?s.bm("editor",Le.value):"",o.class]),B=D(()=>[l.e("icon"),s.e("close-icon"),fe.value?"":s.em("close-icon","hidden")]);ye(S,Me=>{Me?He(()=>{Me&&(h.value=a.modelValue)}):(w.value=null,He(()=>{W(a.modelValue)}))});const W=(Me,Ue)=>{(Ue||!CL(Me,h.value))&&(r(Ot,Me),Ue&&(h.value=Me),a.validateEvent&&(u==null||u.validate("change").catch(kt)))},N=Me=>{r("keydown",Me)},V=D(()=>f.value?Array.from(f.value.$el.querySelectorAll("input")):[]),Y=(Me,Ue,Gt)=>{const je=V.value;je.length&&(!Gt||Gt==="min"?(je[0].setSelectionRange(Me,Ue),je[0].focus()):Gt==="max"&&(je[1].setSelectionRange(Me,Ue),je[1].focus()))},X=()=>{_.value=!0},ee=()=>{r("visible-change",!0)},re=()=>{_.value=!1,S.value=!1,r("visible-change",!1)},ie=()=>{S.value=!0},le=()=>{S.value=!1},ve=D(()=>{const Me=rt(b.value);return Ae(w.value)?[w.value[0]??(Me&&Me[0])??"",w.value[1]??(Me&&Me[1])??""]:w.value!==null?w.value:J.value&&T.value&&!a.saveOnBlur||!J.value&&T.value||!S.value&&T.value?"":Me?Z.value||j.value||oe.value?Me.join(", "):Me:""}),he=D(()=>a.type.includes("time")),J=D(()=>a.type.startsWith("time")),Z=D(()=>a.type==="dates"),j=D(()=>a.type==="months"),oe=D(()=>a.type==="years"),se=D(()=>a.prefixIcon||(he.value?H6:xI)),fe=D(()=>a.clearable&&!g.value&&!a.readonly&&!T.value&&(H.value||R.value)),ge=Me=>{a.readonly||g.value||(fe.value&&(Me==null||Me.stopPropagation(),k.value.handleClear?k.value.handleClear():M(d.valueOnClear.value),W(d.valueOnClear.value,!0),re()),r("clear"))},Se=async Me=>{var Ue;a.readonly||g.value||(((Ue=Me.target)==null?void 0:Ue.tagName)!=="INPUT"||R.value||!a.automaticDropdown)&&(S.value=!0)},De=()=>{a.readonly||g.value||!T.value&&a.clearable&&(H.value=!0)},Te=()=>{H.value=!1},Re=Me=>{var Ue;a.readonly||g.value||(((Ue=Me.touches[0].target)==null?void 0:Ue.tagName)!=="INPUT"||R.value||!a.automaticDropdown)&&(S.value=!0)},Ce=D(()=>a.type.includes("range")),Le=ca(),me=D(()=>{var Me,Ue;return(Ue=(Me=p(v))==null?void 0:Me.popperRef)==null?void 0:Ue.contentRef}),Be=b6(f,Me=>{const Ue=p(me),Gt=da(f);Ue&&(Me.target===Ue||Me.composedPath().includes(Ue))||Me.target===Gt||Gt&&Me.composedPath().includes(Gt)||(S.value=!1)});rn(()=>{Be==null||Be()});const ze=()=>{if(J.value&&!a.saveOnBlur)return;const Me=Ae(w.value)&&w.value.every(Ue=>Ue==="");if(w.value&&!Me){const Ue=xe(ve.value);Ue&&(nt(Ue)&&M(Q0(Ue)),w.value=null)}(w.value===""||Me)&&(M(d.valueOnClear.value),W(d.valueOnClear.value,!0),w.value=null)},xe=Me=>Me?k.value.parseUserInput(Me):null,rt=Me=>Me?Ae(Me)?Me.map(Ue=>Ue.format(a.format)):Me.format(a.format):null,nt=Me=>k.value.isValidValue(Me),Pt=async Me=>{if(a.readonly||g.value)return;const Ue=mn(Me);if(N(Me),Ue===Pe.esc){S.value===!0&&(S.value=!1,Me.preventDefault(),Me.stopPropagation());return}if(Ue===Pe.down&&(k.value.handleFocusPicker&&(Me.preventDefault(),Me.stopPropagation()),S.value===!1&&(S.value=!0,await He()),k.value.handleFocusPicker)){k.value.handleFocusPicker();return}if(Ue===Pe.tab){m=!0;return}if(Ue===Pe.enter||Ue===Pe.numpadEnter){S.value?w.value===null||w.value===""||nt(xe(ve.value))?(ze(),S.value=!1):nt(xe(ve.value))||un("Picker",`Invalid user input: ${ve.value}. Please check the format of the input.`):S.value=!0,Me.preventDefault(),Me.stopPropagation();return}if(w.value){Me.stopPropagation();return}k.value.handleKeydownInput&&k.value.handleKeydownInput(Me)},Wt=Me=>{w.value=Me,S.value||(S.value=!0)},Jt=Me=>{const Ue=Me.target;w.value?w.value=[Ue.value,w.value[1]]:w.value=[Ue.value,null]},Rt=Me=>{const Ue=Me.target;w.value?w.value=[w.value[0],Ue.value]:w.value=[null,Ue.value]},Qt=()=>{var je;const Me=w.value,Ue=xe(Me&&Me[0]),Gt=p(b);if(Ue&&Ue.isValid()){w.value=[rt(Ue),((je=ve.value)==null?void 0:je[1])||null];const ft=[Ue,Gt&&(Gt[1]||null)];nt(ft)&&(M(Q0(ft)),w.value=null)}},_e=()=>{var je;const Me=p(w),Ue=xe(Me&&Me[1]),Gt=p(b);if(Ue&&Ue.isValid()){w.value=[((je=p(ve))==null?void 0:je[0])||null,rt(Ue)];const ft=[Gt&&Gt[0],Ue];nt(ft)&&(M(Q0(ft)),w.value=null)}},Ne=()=>{var Me;(Me=f.value)==null||Me.focus()},tt=()=>{var Me;(Me=f.value)==null||Me.blur()};return Ft(nl,{props:a,emptyValues:d}),Ft(bL,y),t({focus:Ne,blur:tt,handleOpen:ie,handleClose:le,onPick:E}),(Me,Ue)=>(x(),ne(p(Ma),Tt({ref_key:"refPopper",ref:v,visible:p(S),effect:"light",pure:"",trigger:"click"},Me.$attrs,{role:"dialog",teleported:"",transition:`${p(i).namespace.value}-zoom-in-top`,"popper-class":[`${p(i).namespace.value}-picker__popper`,Me.popperClass],"popper-style":Me.popperStyle,"popper-options":p(c),"fallback-placements":Me.fallbackPlacements,"gpu-acceleration":!1,placement:Me.placement,"stop-popper-mouse-event":!1,"hide-after":0,persistent:"",onBeforeShow:X,onShow:ee,onHide:re}),{default:$(()=>[Ce.value?(x(),ne(Pue,{key:1,id:Me.id,ref_key:"inputRef",ref:f,"model-value":ve.value,name:Me.name,disabled:p(g),readonly:!Me.editable||Me.readonly,"start-placeholder":Me.startPlaceholder,"end-placeholder":Me.endPlaceholder,class:U(z.value),style:et(Me.$attrs.style),"aria-label":Me.ariaLabel,tabindex:Me.tabindex,autocomplete:"off",role:"combobox",onClick:Se,onFocus:p(G),onBlur:p(K),onStartInput:Jt,onStartChange:Qt,onEndInput:Rt,onEndChange:_e,onMousedown:Se,onMouseenter:De,onMouseleave:Te,onTouchstartPassive:Re,onKeydown:Pt},{prefix:$(()=>[se.value?(x(),ne(p(Xe),{key:0,class:U([p(l).e("icon"),p(s).e("icon")])},{default:$(()=>[(x(),ne(St(se.value)))]),_:1},8,["class"])):te("v-if",!0)]),"range-separator":$(()=>[ce(Me.$slots,"range-separator",{},()=>[C("span",{class:U(p(s).b("separator"))},Q(Me.rangeSeparator),3)])]),suffix:$(()=>[Me.clearIcon?(x(),ne(p(Xe),{key:0,class:U(B.value),onMousedown:at(p(kt),["prevent"]),onClick:ge},{default:$(()=>[(x(),ne(St(Me.clearIcon)))]),_:1},8,["class","onMousedown"])):te("v-if",!0)]),_:3},8,["id","model-value","name","disabled","readonly","start-placeholder","end-placeholder","class","style","aria-label","tabindex","onFocus","onBlur"])):(x(),ne(p(Ba),{key:0,id:Me.id,ref_key:"inputRef",ref:f,"container-role":"combobox","model-value":ve.value,name:Me.name,size:p(Le),disabled:p(g),placeholder:Me.placeholder,class:U([p(i).b("editor"),p(i).bm("editor",Me.type),p(i).is("focus",p(S)),Me.$attrs.class]),style:et(Me.$attrs.style),readonly:!Me.editable||Me.readonly||Z.value||j.value||oe.value||Me.type==="week","aria-label":Me.ariaLabel,tabindex:Me.tabindex,"validate-event":!1,onInput:Wt,onFocus:p(G),onBlur:p(K),onKeydown:Pt,onChange:ze,onMousedown:Se,onMouseenter:De,onMouseleave:Te,onTouchstartPassive:Re,onClick:Ue[0]||(Ue[0]=at(()=>{},["stop"]))},{prefix:$(()=>[se.value?(x(),ne(p(Xe),{key:0,class:U(p(l).e("icon")),onMousedown:at(Se,["prevent"]),onTouchstartPassive:Re},{default:$(()=>[(x(),ne(St(se.value)))]),_:1},8,["class"])):te("v-if",!0)]),suffix:$(()=>[fe.value&&Me.clearIcon?(x(),ne(p(Xe),{key:0,class:U(`${p(l).e("icon")} clear-icon`),onMousedown:at(p(kt),["prevent"]),onClick:ge},{default:$(()=>[(x(),ne(St(Me.clearIcon)))]),_:1},8,["class","onMousedown"])):te("v-if",!0)]),_:1},8,["id","model-value","name","size","disabled","placeholder","class","style","readonly","aria-label","tabindex","onFocus","onBlur"]))]),content:$(()=>[ce(Me.$slots,"default",{visible:p(S),actualVisible:p(_),parsedValue:p(b),format:Me.format,dateFormat:Me.dateFormat,timeFormat:Me.timeFormat,unlinkPanels:Me.unlinkPanels,type:Me.type,defaultValue:Me.defaultValue,showNow:Me.showNow,showConfirm:Me.showConfirm,showFooter:Me.showFooter,showWeekNumber:Me.showWeekNumber,singlePanel:Me.singlePanel,onPick:Ue[1]||(Ue[1]=(...Gt)=>p(E)&&p(E)(...Gt)),onSelectRange:Y,onSetPickerOption:Ue[2]||(Ue[2]=(...Gt)=>p(A)&&p(A)(...Gt)),onCalendarChange:Ue[3]||(Ue[3]=(...Gt)=>p(I)&&p(I)(...Gt)),onClear:ge,onPanelChange:Ue[4]||(Ue[4]=(...Gt)=>p(P)&&p(P)(...Gt)),onMousedown:Ue[5]||(Ue[5]=at(()=>{},["stop"]))})]),_:3},16,["visible","transition","popper-class","popper-style","popper-options","fallback-placements","placement"]))}}),TL=Due;const is=new Map;if(qt){let e;document.addEventListener("mousedown",t=>e=t),document.addEventListener("mouseup",t=>{if(e){for(const n of is.values())for(const{documentHandler:a}of n)a(t,e);e=void 0}})}function G5(e,t){let n=[];return Ae(t.arg)?n=t.arg:Vr(t.arg)&&n.push(t.arg),function(a,r){const o=t.instance.popperRef,i=a.target,l=r==null?void 0:r.target,s=!t||!t.instance,u=!i||!l,c=e.contains(i)||e.contains(l),d=e===i,v=n.length&&n.some(h=>h==null?void 0:h.contains(i))||n.length&&n.includes(l),f=o&&(o.contains(i)||o.contains(l));s||u||c||d||v||f||t.value(a,r)}}const Ns={beforeMount(e,t){is.has(e)||is.set(e,[]),is.get(e).push({documentHandler:G5(e,t),bindingFn:t.value})},updated(e,t){is.has(e)||is.set(e,[]);const n=is.get(e),a=n.findIndex(o=>o.bindingFn===t.oldValue),r={documentHandler:G5(e,t),bindingFn:t.value};a>=0?n.splice(a,1,r):n.push(r)},unmounted(e){is.delete(e)}},Kp="_RepeatClick",Zm={beforeMount(e,t){const n=t.value,{interval:a=100,delay:r=600}=Qe(n)?{}:n;let o,i;const l=()=>Qe(n)?n():n.handler(),s=()=>{i&&(clearTimeout(i),i=void 0),o&&(clearInterval(o),o=void 0)},u=c=>{c.button===0&&(s(),l(),document.addEventListener("mouseup",s,{once:!0,capture:!0}),i=setTimeout(()=>{o=setInterval(()=>{l()},a)},r))};e[Kp]={start:u,clear:s},e.addEventListener("mousedown",u)},unmounted(e){if(!e[Kp])return;const{start:t,clear:n}=e[Kp];t&&e.removeEventListener("mousedown",t),n&&(n(),document.removeEventListener("mouseup",n,!0)),e[Kp]=null}},M4="_trap-focus-children",Nu=[],Y5=e=>{if(Nu.length===0)return;const t=mn(e),n=Nu[Nu.length-1][M4];if(n.length>0&&t===Pe.tab){if(n.length===1){e.preventDefault(),document.activeElement!==n[0]&&n[0].focus();return}const a=e.shiftKey,r=e.target===n[0],o=e.target===n[n.length-1];r&&a&&(e.preventDefault(),n[n.length-1].focus()),o&&!a&&(e.preventDefault(),n[0].focus())}},Oue={beforeMount(e){e[M4]=KC(e),Nu.push(e),Nu.length<=1&&document.addEventListener("keydown",Y5)},updated(e){He(()=>{e[M4]=KC(e)})},unmounted(){Nu.shift(),Nu.length===0&&document.removeEventListener("keydown",Y5)}};var j5=!1,Lu,E4,A4,em,tm,ML,nm,I4,L4,P4,EL,D4,O4,AL,IL;function Lr(){if(!j5){j5=!0;var e=navigator.userAgent,t=/(?:MSIE.(\d+\.\d+))|(?:(?:Firefox|GranParadiso|Iceweasel).(\d+\.\d+))|(?:Opera(?:.+Version.|.)(\d+\.\d+))|(?:AppleWebKit.(\d+(?:\.\d+)?))|(?:Trident\/\d+\.\d+.*rv:(\d+\.\d+))/.exec(e),n=/(Mac OS X)|(Windows)|(Linux)/.exec(e);if(D4=/\b(iPhone|iP[ao]d)/.exec(e),O4=/\b(iP[ao]d)/.exec(e),P4=/Android/i.exec(e),AL=/FBAN\/\w+;/i.exec(e),IL=/Mobile/i.exec(e),EL=!!/Win64/.exec(e),t){Lu=t[1]?parseFloat(t[1]):t[5]?parseFloat(t[5]):NaN,Lu&&document&&document.documentMode&&(Lu=document.documentMode);var a=/(?:Trident\/(\d+.\d+))/.exec(e);ML=a?parseFloat(a[1])+4:Lu,E4=t[2]?parseFloat(t[2]):NaN,A4=t[3]?parseFloat(t[3]):NaN,em=t[4]?parseFloat(t[4]):NaN,em?(t=/(?:Chrome\/(\d+\.\d+))/.exec(e),tm=t&&t[1]?parseFloat(t[1]):NaN):tm=NaN}else Lu=E4=A4=tm=em=NaN;if(n){if(n[1]){var r=/(?:Mac OS X (\d+(?:[._]\d+)?))/.exec(e);nm=r?parseFloat(r[1].replace("_",".")):!0}else nm=!1;I4=!!n[2],L4=!!n[3]}else nm=I4=L4=!1}}var $4={ie:function(){return Lr()||Lu},ieCompatibilityMode:function(){return Lr()||ML>Lu},ie64:function(){return $4.ie()&&EL},firefox:function(){return Lr()||E4},opera:function(){return Lr()||A4},webkit:function(){return Lr()||em},safari:function(){return $4.webkit()},chrome:function(){return Lr()||tm},windows:function(){return Lr()||I4},osx:function(){return Lr()||nm},linux:function(){return Lr()||L4},iphone:function(){return Lr()||D4},mobile:function(){return Lr()||D4||O4||P4||IL},nativeApp:function(){return Lr()||AL},android:function(){return Lr()||P4},ipad:function(){return Lr()||O4}},$ue=$4,Rue=!!(typeof window<"u"&&window.document&&window.document.createElement),Nue={canUseDOM:Rue},LL=Nue,PL;LL.canUseDOM&&(PL=document.implementation&&document.implementation.hasFeature&&document.implementation.hasFeature("","")!==!0);function Bue(e,t){if(!LL.canUseDOM||t&&!("addEventListener"in document))return!1;var n="on"+e,a=n in document;if(!a){var r=document.createElement("div");r.setAttribute(n,"return;"),a=typeof r[n]=="function"}return!a&&PL&&e==="wheel"&&(a=document.implementation.hasFeature("Events.wheel","3.0")),a}var Vue=Bue,q5=10,X5=40,Z5=800;function DL(e){var t=0,n=0,a=0,r=0;return"detail"in e&&(n=e.detail),"wheelDelta"in e&&(n=-e.wheelDelta/120),"wheelDeltaY"in e&&(n=-e.wheelDeltaY/120),"wheelDeltaX"in e&&(t=-e.wheelDeltaX/120),"axis"in e&&e.axis===e.HORIZONTAL_AXIS&&(t=n,n=0),a=t*q5,r=n*q5,"deltaY"in e&&(r=e.deltaY),"deltaX"in e&&(a=e.deltaX),(a||r)&&e.deltaMode&&(e.deltaMode==1?(a*=X5,r*=X5):(a*=Z5,r*=Z5)),a&&!t&&(t=a<1?-1:1),r&&!n&&(n=r<1?-1:1),{spinX:t,spinY:n,pixelX:a,pixelY:r}}DL.getEventType=function(){return $ue.firefox()?"DOMMouseScroll":Vue("wheel")?"wheel":"mousewheel"};var zue=DL;/** +* Checks if an event is supported in the current execution environment. +* +* NOTE: This will not work correctly for non-generic events such as `change`, +* `reset`, `load`, `error`, and `select`. +* +* Borrows from Modernizr. +* +* @param {string} eventNameSuffix Event name, e.g. "click". +* @param {?boolean} capture Check if the capture phase is supported. +* @return {boolean} True if the event is supported. +* @internal +* @license Modernizr 3.0.0pre (Custom Build) | MIT +*/const R4="_Mousewheel",J5=function(e,t){if(e&&e.addEventListener){OL(e);const n=function(a){const r=zue(a);t&&Reflect.apply(t,this,[a,r])};e[R4]={wheelHandler:n},e.addEventListener("wheel",n,{passive:!0})}},OL=e=>{var t;(t=e._Mousewheel)!=null&&t.wheelHandler&&(e.removeEventListener("wheel",e[R4].wheelHandler),e[R4]=null)},Q5={beforeMount(e,t){J5(e,t.value)},unmounted(e){OL(e)},updated(e,t){t.value!==t.oldValue&&J5(e,t.value)}},Fue=$e({...xL,datetimeRole:String,parsedValue:{type:ae(Object)}}),$L=({getAvailableHours:e,getAvailableMinutes:t,getAvailableSeconds:n})=>{const a=(i,l,s,u)=>{const c={hour:e,minute:t,second:n};let d=i;return["hour","minute","second"].forEach(v=>{if(c[v]){let f;const h=c[v];switch(v){case"minute":f=h(d.hour(),l,u);break;case"second":f=h(d.hour(),d.minute(),l,u);break;default:f=h(l,u);break}if(f!=null&&f.length&&!f.includes(d[v]())){const m=s?0:f.length-1;d=d[v](f[m])}}}),d},r={};return{timePickerOptions:r,getAvailableTime:a,onSetOption:([i,l])=>{r[i]=l}}},ty=e=>{const t=(a,r)=>a||r,n=a=>a!==!0;return e.map(t).filter(n)},RL=(e,t,n)=>({getHoursList:(i,l)=>ey(24,e&&(()=>e==null?void 0:e(i,l))),getMinutesList:(i,l,s)=>ey(60,t&&(()=>t==null?void 0:t(i,l,s))),getSecondsList:(i,l,s,u)=>ey(60,n&&(()=>n==null?void 0:n(i,l,s,u)))}),NL=(e,t,n)=>{const{getHoursList:a,getMinutesList:r,getSecondsList:o}=RL(e,t,n);return{getAvailableHours:(u,c)=>ty(a(u,c)),getAvailableMinutes:(u,c,d)=>ty(r(u,c,d)),getAvailableSeconds:(u,c,d,v)=>ty(o(u,c,d,v))}},BL=(e,t)=>{const n=F(e.parsedValue);return ye(()=>e.visible,a=>{const r=ia(t.modelValue),o=ia(t.valueOnClear);if(a&&r===o){n.value=o;return}a||(n.value=e.parsedValue)}),n},Hue=$e({role:{type:String,required:!0},spinnerDate:{type:ae(Object),required:!0},showSeconds:{type:Boolean,default:!0},arrowControl:Boolean,amPmMode:{type:ae(String),default:""},...nw}),Wue=["onClick"],Kue=["onMouseenter"];var Uue=q({__name:"basic-time-spinner",props:Hue,emits:[Ot,"select-range","set-option"],setup(e,{emit:t}){const n=e,{isRange:a,format:r,saveOnBlur:o}=Ze(nl).props,i=t,l=ke("time"),{getHoursList:s,getMinutesList:u,getSecondsList:c}=RL(n.disabledHours,n.disabledMinutes,n.disabledSeconds);let d=!1;const v={hours:!1,minutes:!1,seconds:!1},f=F(),h={hours:F(),minutes:F(),seconds:F()},m=D(()=>n.showSeconds?F5:F5.slice(0,2)),g=D(()=>{const{spinnerDate:V}=n;return{hours:V.hour(),minutes:V.minute(),seconds:V.second()}}),y=D(()=>{const{hours:V,minutes:Y}=p(g),{role:X,spinnerDate:ee}=n,re=a?void 0:ee;return{hours:s(X,re),minutes:u(V,X,re),seconds:c(V,Y,X,re)}}),b=D(()=>{const{hours:V,minutes:Y,seconds:X}=p(g);return{hours:Q2(V,23),minutes:Q2(Y,59),seconds:Q2(X,59)}}),_=Yi(V=>{d=!1,k(V)},200),w=V=>{if(!n.amPmMode)return"";const Y=n.amPmMode==="A";let X=V<12?" am":" pm";return Y&&(X=X.toUpperCase()),X},S=V=>{let Y=[0,0];const X=r||"HH:mm:ss",ee=X.indexOf("HH"),re=X.indexOf("mm"),ie=X.indexOf("ss");switch(V){case"hours":ee!==-1&&(Y=[ee,ee+2]);break;case"minutes":re!==-1&&(Y=[re,re+2]);break;case"seconds":ie!==-1&&(Y=[ie,ie+2]);break}const[le,ve]=Y;i("select-range",le,ve),f.value=V},k=V=>{E(V,p(g)[V])},T=()=>{k("hours"),k("minutes"),k("seconds")},M=V=>V.querySelector(`.${l.namespace.value}-scrollbar__wrap`),E=(V,Y)=>{if(n.arrowControl)return;const X=p(h[V]);X&&X.$el&&(o||(v[V]=!0,Wr(()=>{v[V]=!1})),M(X.$el).scrollTop=Math.max(0,Y*A(V)))},A=V=>{var X;const Y=(X=p(h[V]))==null?void 0:X.$el.querySelector("li");return Y&&Number.parseFloat(ml(Y,"height"))||0},I=()=>{R(1)},P=()=>{R(-1)},R=V=>{f.value||S("hours");const Y=f.value,X=p(g)[Y],ee=G(Y,X,V,f.value==="hours"?24:60);K(Y,ee),E(Y,ee),He(()=>S(Y))},G=(V,Y,X,ee)=>{let re=(Y+X+ee)%ee;const ie=p(y)[V];for(;ie[re]&&re!==Y;)re=(re+X+ee)%ee;return re},K=(V,Y)=>{if(p(y)[V][Y])return;const{hours:X,minutes:ee,seconds:re}=p(g);let ie;switch(V){case"hours":ie=n.spinnerDate.hour(Y).minute(ee).second(re);break;case"minutes":ie=n.spinnerDate.hour(X).minute(Y).second(re);break;case"seconds":ie=n.spinnerDate.hour(X).minute(ee).second(Y);break}i(Ot,ie)},H=(V,{value:Y,disabled:X})=>{X||(K(V,Y),S(V),E(V,Y))},z=V=>{if(!o&&v[V])return;const Y=p(h[V]);Y&&(d=!0,_(V),K(V,Math.min(Math.round((M(Y.$el).scrollTop-(B(V)*.5-10)/A(V)+3)/A(V)),V==="hours"?23:59)))},B=V=>p(h[V]).$el.offsetHeight,W=()=>{const V=Y=>{const X=p(h[Y]);X&&X.$el&&(M(X.$el).onscroll=()=>{z(Y)})};V("hours"),V("minutes"),V("seconds")};mt(()=>{He(()=>{!n.arrowControl&&W(),T(),n.role==="start"&&S("hours")})});const N=(V,Y)=>{h[Y].value=V??void 0};return i("set-option",[`${n.role}_scrollDown`,R]),i("set-option",[`${n.role}_emitSelectRange`,S]),ye(()=>n.spinnerDate,()=>{d||T()}),(V,Y)=>(x(),O("div",{class:U([p(l).b("spinner"),{"has-seconds":V.showSeconds}])},[V.arrowControl?te("v-if",!0):(x(!0),O(Ve,{key:0},dt(m.value,X=>(x(),ne(p(ni),{key:X,ref_for:!0,ref:ee=>N(ee,X),class:U(p(l).be("spinner","wrapper")),"wrap-style":"max-height: inherit;","view-class":p(l).be("spinner","list"),noresize:"",tag:"ul",onMouseenter:ee=>S(X),onMousemove:ee=>k(X)},{default:$(()=>[(x(!0),O(Ve,null,dt(y.value[X],(ee,re)=>(x(),O("li",{key:re,class:U([p(l).be("spinner","item"),p(l).is("active",re===g.value[X]),p(l).is("disabled",ee)]),onClick:ie=>H(X,{value:re,disabled:ee})},[X==="hours"?(x(),O(Ve,{key:0},[ue(Q(("0"+(V.amPmMode?re%12||12:re)).slice(-2))+Q(w(re)),1)],64)):(x(),O(Ve,{key:1},[ue(Q(("0"+re).slice(-2)),1)],64))],10,Wue))),128))]),_:2},1032,["class","view-class","onMouseenter","onMousemove"]))),128)),V.arrowControl?(x(!0),O(Ve,{key:1},dt(m.value,X=>(x(),O("div",{key:X,class:U([p(l).be("spinner","wrapper"),p(l).is("arrow")]),onMouseenter:ee=>S(X)},[st((x(),ne(p(Xe),{class:U(["arrow-up",p(l).be("spinner","arrow")])},{default:$(()=>[L(p(ip))]),_:1},8,["class"])),[[p(Zm),P]]),st((x(),ne(p(Xe),{class:U(["arrow-down",p(l).be("spinner","arrow")])},{default:$(()=>[L(p(ui))]),_:1},8,["class"])),[[p(Zm),I]]),C("ul",{class:U(p(l).be("spinner","list"))},[(x(!0),O(Ve,null,dt(b.value[X],(ee,re)=>(x(),O("li",{key:re,class:U([p(l).be("spinner","item"),p(l).is("active",ee===g.value[X]),p(l).is("disabled",y.value[X][ee])])},[p(Je)(ee)?(x(),O(Ve,{key:0},[X==="hours"?(x(),O(Ve,{key:0},[ue(Q(("0"+(V.amPmMode?ee%12||12:ee)).slice(-2))+Q(w(ee)),1)],64)):(x(),O(Ve,{key:1},[ue(Q(("0"+ee).slice(-2)),1)],64))],64)):te("v-if",!0)],2))),128))],2)],42,Kue))),128)):te("v-if",!0)],2))}}),N4=Uue,Gue=q({__name:"panel-time-pick",props:Fue,emits:["pick","select-range","set-picker-option"],setup(e,{emit:t}){const n=e,a=t,r=Ze(nl),{arrowControl:o,disabledHours:i,disabledMinutes:l,disabledSeconds:s,defaultValue:u}=r.props,{getAvailableHours:c,getAvailableMinutes:d,getAvailableSeconds:v}=NL(i,l,s),f=ke("time"),{t:h,lang:m}=Vt(),g=F([0,2]),y=BL(n,{modelValue:D(()=>r.props.modelValue),valueOnClear:D(()=>r!=null&&r.emptyValues?r.emptyValues.valueOnClear.value:null)}),b=D(()=>zt(n.actualVisible)?`${f.namespace.value}-zoom-in-top`:""),_=D(()=>n.format.includes("ss")),w=D(()=>n.format.includes("A")?"A":n.format.includes("a")?"a":""),S=B=>{const W=gt(B).locale(m.value),N=K(W);return W.isSame(N)},k=()=>{const B=y.value;a("pick",B,!1),He(()=>{y.value=B})},T=(B=!1,W=!1)=>{W||a("pick",n.parsedValue,B)},M=B=>{n.visible&&a("pick",K(B).millisecond(0),!0)},E=(B,W)=>{a("select-range",B,W),g.value=[B,W]},A=B=>{const W=n.format,N=W.indexOf("HH"),V=W.indexOf("mm"),Y=W.indexOf("ss"),X=[],ee=[];N!==-1&&(X.push(N),ee.push("hours")),V!==-1&&(X.push(V),ee.push("minutes")),Y!==-1&&_.value&&(X.push(Y),ee.push("seconds"));const re=(X.indexOf(g.value[0])+B+X.length)%X.length;P.start_emitSelectRange(ee[re])},I=B=>{const W=mn(B),{left:N,right:V,up:Y,down:X}=Pe;if([N,V].includes(W)){A(W===N?-1:1),B.preventDefault();return}if([Y,X].includes(W)){const ee=W===Y?-1:1;P.start_scrollDown(ee),B.preventDefault();return}},{timePickerOptions:P,onSetOption:R,getAvailableTime:G}=$L({getAvailableHours:c,getAvailableMinutes:d,getAvailableSeconds:v}),K=B=>G(B,n.datetimeRole||"",!0),H=B=>B?gt(B,n.format).locale(m.value):null,z=()=>gt(u).locale(m.value);return a("set-picker-option",["isValidValue",S]),a("set-picker-option",["parseUserInput",H]),a("set-picker-option",["handleKeydownInput",I]),a("set-picker-option",["getRangeAvailableTime",K]),a("set-picker-option",["getDefaultValue",z]),a("set-picker-option",["handleCancel",k]),(B,W)=>(x(),ne(Pa,{name:b.value},{default:$(()=>[B.actualVisible||B.visible?(x(),O("div",{key:0,class:U(p(f).b("panel"))},[C("div",{class:U([p(f).be("panel","content"),{"has-seconds":_.value}])},[L(N4,{ref:"spinner",role:B.datetimeRole||"start","arrow-control":p(o),"show-seconds":_.value,"am-pm-mode":w.value,"spinner-date":B.parsedValue,"disabled-hours":p(i),"disabled-minutes":p(l),"disabled-seconds":p(s),onChange:M,onSetOption:p(R),onSelectRange:E},null,8,["role","arrow-control","show-seconds","am-pm-mode","spinner-date","disabled-hours","disabled-minutes","disabled-seconds","onSetOption"])],2),C("div",{class:U(p(f).be("panel","footer"))},[C("button",{type:"button",class:U([p(f).be("panel","btn"),"cancel"]),onClick:k},Q(p(h)("el.datepicker.cancel")),3),C("button",{type:"button",class:U([p(f).be("panel","btn"),"confirm"]),onClick:W[0]||(W[0]=N=>T())},Q(p(h)("el.datepicker.confirm")),3)],2)],2)):te("v-if",!0)]),_:1},8,["name"]))}}),Jm=Gue;const Yue=$e({...xL,parsedValue:{type:ae(Array)}}),jue=["disabled"];var que=q({__name:"panel-time-range",props:Yue,emits:["pick","select-range","set-picker-option"],setup(e,{emit:t}){const n=e,a=t,r=(Z,j)=>{const oe=[];for(let se=Z;se<=j;se++)oe.push(se);return oe},{t:o,lang:i}=Vt(),l=ke("time"),s=ke("picker"),u=Ze(nl),{arrowControl:c,disabledHours:d,disabledMinutes:v,disabledSeconds:f,defaultValue:h}=u.props,m=D(()=>[l.be("range-picker","body"),l.be("panel","content"),l.is("arrow",c),S.value?"has-seconds":""]),g=D(()=>[l.be("range-picker","body"),l.be("panel","content"),l.is("arrow",c),S.value?"has-seconds":""]),y=D(()=>n.parsedValue[0]),b=D(()=>n.parsedValue[1]),_=BL(n,{modelValue:D(()=>u.props.modelValue),valueOnClear:D(()=>u!=null&&u.emptyValues?u.emptyValues.valueOnClear.value:null)}),w=()=>{const Z=_.value;a("pick",Z,!1),He(()=>{_.value=Z})},S=D(()=>n.format.includes("ss")),k=D(()=>n.format.includes("A")?"A":n.format.includes("a")?"a":""),T=(Z=!1)=>{a("pick",[y.value,b.value],Z)},M=Z=>{I(Z.millisecond(0),b.value)},E=Z=>{I(y.value,Z.millisecond(0))},A=Z=>{const j=Z.map(se=>gt(se).locale(i.value)),oe=Y(j);return j[0].isSame(oe[0])&&j[1].isSame(oe[1])},I=(Z,j)=>{n.visible&&a("pick",[Z,j],!0)},P=D(()=>y.value>b.value),R=F([0,2]),G=(Z,j)=>{a("select-range",Z,j,"min"),R.value=[Z,j]},K=D(()=>S.value?11:8),H=(Z,j)=>{a("select-range",Z,j,"max");const oe=p(K);R.value=[Z+oe,j+oe]},z=Z=>{const j=S.value?[0,3,6,11,14,17]:[0,3,8,11],oe=["hours","minutes"].concat(S.value?["seconds"]:[]),se=(j.indexOf(R.value[0])+Z+j.length)%j.length,fe=j.length/2;se{const j=mn(Z),{left:oe,right:se,up:fe,down:ge}=Pe;if([oe,se].includes(j)){z(j===oe?-1:1),Z.preventDefault();return}if([fe,ge].includes(j)){const Se=j===fe?-1:1;ie[`${R.value[0]{const oe=d?d(Z):[],se=Z==="start",fe=(j||(se?b.value:y.value)).hour();return F2(oe,se?r(fe+1,23):r(0,fe-1))},N=(Z,j,oe)=>{const se=v?v(Z,j):[],fe=j==="start",ge=oe||(fe?b.value:y.value);if(Z!==ge.hour())return se;const Se=ge.minute();return F2(se,fe?r(Se+1,59):r(0,Se-1))},V=(Z,j,oe,se)=>{const fe=f?f(Z,j,oe):[],ge=oe==="start",Se=se||(ge?b.value:y.value),De=Se.hour(),Te=Se.minute();if(Z!==De||j!==Te)return fe;const Re=Se.second();return F2(fe,ge?r(Re+1,59):r(0,Re-1))},Y=([Z,j])=>[le(Z,"start",!0,j),le(j,"end",!1,Z)],{getAvailableHours:X,getAvailableMinutes:ee,getAvailableSeconds:re}=NL(W,N,V),{timePickerOptions:ie,getAvailableTime:le,onSetOption:ve}=$L({getAvailableHours:X,getAvailableMinutes:ee,getAvailableSeconds:re}),he=Z=>Z?Ae(Z)?Z.map(j=>gt(j,n.format).locale(i.value)):gt(Z,n.format).locale(i.value):null,J=()=>{if(Ae(h))return h.map(j=>gt(j).locale(i.value));const Z=gt(h).locale(i.value);return[Z,Z.add(60,"m")]};return a("set-picker-option",["parseUserInput",he]),a("set-picker-option",["isValidValue",A]),a("set-picker-option",["handleKeydownInput",B]),a("set-picker-option",["getDefaultValue",J]),a("set-picker-option",["getRangeAvailableTime",Y]),a("set-picker-option",["handleCancel",w]),(Z,j)=>Z.actualVisible?(x(),O("div",{key:0,class:U([p(l).b("range-picker"),p(s).b("panel")])},[C("div",{class:U(p(l).be("range-picker","content"))},[C("div",{class:U(p(l).be("range-picker","cell"))},[C("div",{class:U(p(l).be("range-picker","header"))},Q(p(o)("el.datepicker.startTime")),3),C("div",{class:U(m.value)},[L(N4,{ref:"minSpinner",role:"start","show-seconds":S.value,"am-pm-mode":k.value,"arrow-control":p(c),"spinner-date":y.value,"disabled-hours":W,"disabled-minutes":N,"disabled-seconds":V,onChange:M,onSetOption:p(ve),onSelectRange:G},null,8,["show-seconds","am-pm-mode","arrow-control","spinner-date","onSetOption"])],2)],2),C("div",{class:U(p(l).be("range-picker","cell"))},[C("div",{class:U(p(l).be("range-picker","header"))},Q(p(o)("el.datepicker.endTime")),3),C("div",{class:U(g.value)},[L(N4,{ref:"maxSpinner",role:"end","show-seconds":S.value,"am-pm-mode":k.value,"arrow-control":p(c),"spinner-date":b.value,"disabled-hours":W,"disabled-minutes":N,"disabled-seconds":V,onChange:E,onSetOption:p(ve),onSelectRange:H},null,8,["show-seconds","am-pm-mode","arrow-control","spinner-date","onSetOption"])],2)],2)],2),C("div",{class:U(p(l).be("panel","footer"))},[C("button",{type:"button",class:U([p(l).be("panel","btn"),"cancel"]),onClick:j[0]||(j[0]=oe=>w())},Q(p(o)("el.datepicker.cancel")),3),C("button",{type:"button",class:U([p(l).be("panel","btn"),"confirm"]),disabled:P.value,onClick:j[1]||(j[1]=oe=>T())},Q(p(o)("el.datepicker.confirm")),11,jue)],2)],2)):te("v-if",!0)}}),Xue=que,VL={exports:{}};(function(e,t){(function(n,a){e.exports=a()})(Wl,function(){var n={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"},a=/(\[[^[]*\])|([-_:/.,()\s]+)|(A|a|Q|YYYY|YY?|ww?|MM?M?M?|Do|DD?|hh?|HH?|mm?|ss?|S{1,3}|z|ZZ?)/g,r=/\d/,o=/\d\d/,i=/\d\d?/,l=/\d*[^-_:/,()\s\d]+/,s={},u=function(g){return(g=+g)+(g>68?1900:2e3)},c=function(g){return function(y){this[g]=+y}},d=[/[+-]\d\d:?(\d\d)?|Z/,function(g){(this.zone||(this.zone={})).offset=function(y){if(!y||y==="Z")return 0;var b=y.match(/([+-]|\d\d)/g),_=60*b[1]+(+b[2]||0);return _===0?0:b[0]==="+"?-_:_}(g)}],v=function(g){var y=s[g];return y&&(y.indexOf?y:y.s.concat(y.f))},f=function(g,y){var b,_=s.meridiem;if(_){for(var w=1;w<=24;w+=1)if(g.indexOf(_(w,0,y))>-1){b=w>12;break}}else b=g===(y?"pm":"PM");return b},h={A:[l,function(g){this.afternoon=f(g,!1)}],a:[l,function(g){this.afternoon=f(g,!0)}],Q:[r,function(g){this.month=3*(g-1)+1}],S:[r,function(g){this.milliseconds=100*+g}],SS:[o,function(g){this.milliseconds=10*+g}],SSS:[/\d{3}/,function(g){this.milliseconds=+g}],s:[i,c("seconds")],ss:[i,c("seconds")],m:[i,c("minutes")],mm:[i,c("minutes")],H:[i,c("hours")],h:[i,c("hours")],HH:[i,c("hours")],hh:[i,c("hours")],D:[i,c("day")],DD:[o,c("day")],Do:[l,function(g){var y=s.ordinal,b=g.match(/\d+/);if(this.day=b[0],y)for(var _=1;_<=31;_+=1)y(_).replace(/\[|\]/g,"")===g&&(this.day=_)}],w:[i,c("week")],ww:[o,c("week")],M:[i,c("month")],MM:[o,c("month")],MMM:[l,function(g){var y=v("months"),b=(v("monthsShort")||y.map(function(_){return _.slice(0,3)})).indexOf(g)+1;if(b<1)throw new Error;this.month=b%12||b}],MMMM:[l,function(g){var y=v("months").indexOf(g)+1;if(y<1)throw new Error;this.month=y%12||y}],Y:[/[+-]?\d+/,c("year")],YY:[o,function(g){this.year=u(g)}],YYYY:[/\d{4}/,c("year")],Z:d,ZZ:d};function m(g){var y,b;y=g,b=s&&s.formats;for(var _=(g=y.replace(/(\[[^\]]+])|(LTS?|l{1,4}|L{1,4})/g,function(A,I,P){var R=P&&P.toUpperCase();return I||b[P]||n[P]||b[R].replace(/(\[[^\]]+])|(MMMM|MM|DD|dddd)/g,function(G,K,H){return K||H.slice(1)})})).match(a),w=_.length,S=0;S-1)return new Date((B==="X"?1e3:1)*z);var V=m(B)(z),Y=V.year,X=V.month,ee=V.day,re=V.hours,ie=V.minutes,le=V.seconds,ve=V.milliseconds,he=V.zone,J=V.week,Z=new Date,j=ee||(Y||X?1:Z.getDate()),oe=Y||Z.getFullYear(),se=0;Y&&!X||(se=X>0?X-1:Z.getMonth());var fe,ge=re||0,Se=ie||0,De=le||0,Te=ve||0;return he?new Date(Date.UTC(oe,se,j,ge,Se,De,Te+60*he.offset*1e3)):W?new Date(Date.UTC(oe,se,j,ge,Se,De,Te)):(fe=new Date(oe,se,j,ge,Se,De,Te),J&&(fe=N(fe).week(J).toDate()),fe)}catch{return new Date("")}}(k,E,T,b),this.init(),R&&R!==!0&&(this.$L=this.locale(R).$L),P&&k!=this.format(E)&&(this.$d=new Date("")),s={}}else if(E instanceof Array)for(var G=E.length,K=1;K<=G;K+=1){M[1]=E[K-1];var H=b.apply(this,M);if(H.isValid()){this.$d=H.$d,this.$L=H.$L,this.init();break}K===G&&(this.$d=new Date(""))}else w.call(this,S)}}})})(VL);var Zue=VL.exports;const rw=Kl(Zue);gt.extend(rw);var Jue=q({name:"ElTimePicker",install:null,props:{...aw,isRange:Boolean},emits:[bt],setup(e,t){const n=F(),[a,r]=e.isRange?["timerange",Xue]:["time",Jm],o=i=>t.emit(bt,i);return Ft(tw,e.popperOptions),t.expose({focus:()=>{var i;(i=n.value)==null||i.focus()},blur:()=>{var i;(i=n.value)==null||i.blur()},handleOpen:()=>{var i;(i=n.value)==null||i.handleOpen()},handleClose:()=>{var i;(i=n.value)==null||i.handleClose()}}),()=>L(TL,Tt(e,{ref:n,type:a,format:e.format??"HH:mm:ss","onUpdate:modelValue":o}),{default:i=>L(r,i,null)})}});const Que=wt(Jue),Ll=$e({type:{type:String,values:["primary","success","info","warning","danger"],default:"primary"},closable:Boolean,disableTransitions:Boolean,hit:Boolean,color:String,size:{type:String,values:ii},effect:{type:String,values:["dark","light","plain"],default:"light"},round:Boolean}),ece={close:e=>e instanceof MouseEvent,click:e=>e instanceof MouseEvent},tce=["aria-label"],nce=["aria-label"];var ace=q({name:"ElTag",__name:"tag",props:Ll,emits:ece,setup(e,{emit:t}){const n=e,a=t,r=ca(),{t:o}=Vt(),i=ke("tag"),l=D(()=>{const{type:d,hit:v,effect:f,closable:h,round:m}=n;return[i.b(),i.is("closable",h),i.m(d||"primary"),i.m(r.value),i.m(f),i.is("hit",v),i.is("round",m)]}),s=d=>{a("close",d)},u=d=>{a("click",d)},c=d=>{var v,f,h;(h=(f=(v=d==null?void 0:d.component)==null?void 0:v.subTree)==null?void 0:f.component)!=null&&h.bum&&(d.component.subTree.component.bum=null)};return(d,v)=>e.disableTransitions?(x(),O("span",{key:0,class:U(l.value),style:et({backgroundColor:e.color}),onClick:u},[C("span",{class:U(p(i).e("content"))},[ce(d.$slots,"default")],2),e.closable?(x(),O("button",{key:0,"aria-label":p(o)("el.tag.close"),class:U(p(i).e("close")),type:"button",onClick:at(s,["stop"])},[L(p(Xe),null,{default:$(()=>[L(p(so))]),_:1})],10,tce)):te("v-if",!0)],6)):(x(),ne(Pa,{key:1,name:`${p(i).namespace.value}-zoom-in-center`,appear:"",onVnodeMounted:c},{default:$(()=>[C("span",{class:U(l.value),style:et({backgroundColor:e.color}),onClick:u},[C("span",{class:U(p(i).e("content"))},[ce(d.$slots,"default")],2),e.closable?(x(),O("button",{key:0,"aria-label":p(o)("el.tag.close"),class:U(p(i).e("close")),type:"button",onClick:at(s,["stop"])},[L(p(Xe),null,{default:$(()=>[L(p(so))]),_:1})],10,nce)):te("v-if",!0)],6)]),_:3},8,["name"]))}}),rce=ace;const Cl=wt(rce),zL=Symbol("ElSelectGroup"),sp=Symbol("ElSelect"),Qm={label:"label",value:"value",disabled:"disabled",options:"options"};function up(e){const t=F({...Qm,...e.props});let n={...e.props};return ye(()=>e.props,l=>{Rn(l,n)||(t.value={...Qm,...l},n={...l})},{deep:!0}),{aliasProps:t,getLabel:l=>na(l,t.value.label),getValue:l=>na(l,t.value.value),getDisabled:l=>na(l,t.value.disabled),getOptions:l=>na(l,t.value.options)}}const FL=$e({name:String,id:String,modelValue:{type:ae([Array,String,Number,Boolean,Object]),default:void 0},autocomplete:{type:String,default:"off"},automaticDropdown:Boolean,size:pa,effect:{type:ae(String),default:"light"},disabled:{type:Boolean,default:void 0},clearable:Boolean,filterable:Boolean,allowCreate:Boolean,loading:Boolean,popperClass:{type:ae([String,Array,Object,Boolean]),default:""},popperStyle:{type:ae([String,Array,Object,Boolean]),default:void 0},popperOptions:{type:ae(Object),default:()=>({})},remote:Boolean,debounce:{type:Number,default:300},loadingText:String,noMatchText:String,noDataText:String,remoteMethod:{type:ae(Function)},filterMethod:{type:ae(Function)},multiple:Boolean,multipleLimit:{type:Number,default:0},placeholder:{type:String},defaultFirstOption:Boolean,reserveKeyword:{type:Boolean,default:!0},valueKey:{type:String,default:"value"},collapseTags:Boolean,collapseTagsTooltip:Boolean,tagTooltip:{type:ae(Object),default:()=>({})},maxCollapseTags:{type:Number,default:1},teleported:Tn.teleported,persistent:{type:Boolean,default:!0},clearIcon:{type:pn,default:ci},fitInputWidth:Boolean,suffixIcon:{type:pn,default:ui},tagType:{...Ll.type,default:"info"},tagEffect:{...Ll.effect,default:"light"},validateEvent:{type:Boolean,default:!0},remoteShowSuffix:Boolean,showArrow:{type:Boolean,default:!0},offset:{type:Number,default:12},placement:{type:ae(String),values:tl,default:"bottom-start"},fallbackPlacements:{type:ae(Array),default:["bottom-start","top-start","right","left"]},tabindex:{type:[String,Number],default:0},appendTo:Tn.appendTo,options:{type:ae(Array)},props:{type:ae(Object),default:()=>Qm},...Mc,...dr(["ariaLabel"])}),oce={[bt]:e=>!0,[Ot]:e=>!0,"popup-scroll":hh.scroll,"end-reached":hh["end-reached"],"remove-tag":e=>!0,"visible-change":e=>!0,focus:e=>e instanceof FocusEvent,blur:e=>e instanceof FocusEvent,clear:()=>!0},B4="ElOption",ice=$e({value:{type:[String,Number,Boolean,Object],required:!0},label:{type:[String,Number]},created:Boolean,disabled:Boolean});function lce(e,t){const n=Ze(sp);n||Dn(B4,"usage: ");const a=Ze(zL,{disabled:!1}),r=D(()=>c(wa(n.props.modelValue),e.value)),o=D(()=>{if(n.props.multiple){const f=wa(n.props.modelValue??[]);return!r.value&&f.length>=n.props.multipleLimit&&n.props.multipleLimit>0}else return!1}),i=D(()=>e.label??(_t(e.value)?"":e.value)),l=D(()=>e.value||e.label||""),s=D(()=>e.disabled||t.groupDisabled||o.value),u=Et(),c=(f=[],h)=>{if(_t(e.value)){const m=n.props.valueKey;return f&&f.some(g=>wn(na(g,m))===na(h,m))}else return f&&f.includes(h)},d=()=>{s.value||(n.states.hoveringIndex=n.optionsArray.indexOf(u.proxy))},v=f=>{t.visible=new RegExp(S6(f),"i").test(String(i.value))||e.created};return ye(()=>i.value,()=>{!e.created&&!n.props.remote&&n.setSelected()}),ye(()=>e.value,(f,h)=>{const{remote:m,valueKey:g}=n.props;if((m?f!==h:!Rn(f,h))&&(n.onOptionDestroy(h,u.proxy),n.onOptionCreate(u.proxy)),!e.created&&!m){if(g&&_t(f)&&_t(h)&&f[g]===h[g])return;n.setSelected()}}),ye(()=>a.disabled,()=>{t.groupDisabled=a.disabled},{immediate:!0}),{select:n,currentLabel:i,currentValue:l,itemSelected:r,isDisabled:s,hoverItem:d,updateOption:v}}var sce=q({name:B4,componentName:B4,props:ice,setup(e){const t=ke("select"),n=Fa(),a=D(()=>[t.be("dropdown","item"),t.is("disabled",p(s)),t.is("selected",p(l)),t.is("hovering",p(f))]),r=nn({index:-1,groupDisabled:!1,visible:!0,hover:!1}),o=Nd?null:"mousemove",{currentLabel:i,itemSelected:l,isDisabled:s,select:u,hoverItem:c,updateOption:d}=lce(e,r),{visible:v,hover:f}=Ta(r),h=Et().proxy;u.onOptionCreate(h),rn(()=>{const y=h.value;He(()=>{const{selected:b}=u.states,_=b.some(w=>w.value===h.value);u.states.cachedOptions.get(y)===h&&!_&&u.states.cachedOptions.delete(y)}),u.onOptionDestroy(y,h)});function m(){s.value||u.handleOptionSelect(h)}return{ns:t,id:n,containerKls:a,currentLabel:i,itemSelected:l,isDisabled:s,select:u,visible:v,hover:f,states:r,mouseMoveEventName:o,hoverItem:c,handleMousedown:y=>{let b=y.target;const _=y.currentTarget;for(;b&&b!==_;){if(sc(b))return;b=b.parentElement}y.preventDefault()},updateOption:d,selectOptionClick:m}}});const uce=["id","aria-disabled","aria-selected"];function cce(e,t,n,a,r,o){return st((x(),O("li",Tt({id:e.id,class:e.containerKls,role:"option","aria-disabled":e.isDisabled||void 0,"aria-selected":e.itemSelected},{[Wu(e.mouseMoveEventName)]:t[0]||(t[0]=(...i)=>e.hoverItem&&e.hoverItem(...i))},{onMousedown:t[1]||(t[1]=(...i)=>e.handleMousedown&&e.handleMousedown(...i)),onClick:t[2]||(t[2]=at((...i)=>e.selectOptionClick&&e.selectOptionClick(...i),["stop"]))}),[ce(e.$slots,"default",{},()=>[C("span",null,Q(e.currentLabel),1)])],16,uce)),[[Zt,e.visible]])}var ow=ya(sce,[["render",cce]]),dce=q({name:"ElOptionGroup",componentName:"ElOptionGroup",props:{label:String,disabled:Boolean},setup(e){const t=ke("select"),n=F(),a=Et(),r=F([]);Ft(zL,nn({...Ta(e)}));const o=D(()=>r.value.some(u=>u.visible===!0)),i=u=>{var c;return u.type.name==="ElOption"&&!!((c=u.component)!=null&&c.proxy)},l=u=>{const c=wa(u),d=[];return c.forEach(v=>{var f;bn(v)&&(i(v)?d.push(v.component.proxy):Ae(v.children)&&v.children.length?d.push(...l(v.children)):(f=v.component)!=null&&f.subTree&&d.push(...l(v.component.subTree)))}),d},s=()=>{r.value=l(a.subTree)};return mt(()=>{s()}),Zh(n,s,{attributes:!0,subtree:!0,childList:!0}),{groupRef:n,visible:o,ns:t}}});function fce(e,t,n,a,r,o){return st((x(),O("ul",{ref:"groupRef",class:U(e.ns.be("group","wrap"))},[C("li",{class:U(e.ns.be("group","title"))},Q(e.label),3),C("li",null,[C("ul",{class:U(e.ns.b("group"))},[ce(e.$slots,"default")],2)])],2)),[[Zt,e.visible]])}var iw=ya(dce,[["render",fce]]),vce=q({name:"ElSelectDropdown",componentName:"ElSelectDropdown",setup(){const e=Ze(sp),t=ke("select"),n=D(()=>e.props.popperClass),a=D(()=>e.props.multiple),r=D(()=>e.props.fitInputWidth),o=F("");function i(){var s;const l=(s=e.selectRef)==null?void 0:s.offsetWidth;l?o.value=`${l-2}px`:o.value=""}return mt(()=>{i(),Mn(e.selectRef,i)}),{ns:t,minWidth:o,popperClass:n,isMultiple:a,isFitInputWidth:r}}});function hce(e,t,n,a,r,o){return x(),O("div",{class:U([e.ns.b("dropdown"),e.ns.is("multiple",e.isMultiple),e.popperClass]),style:et({[e.isFitInputWidth?"width":"minWidth"]:e.minWidth})},[e.$slots.header?(x(),O("div",{key:0,class:U(e.ns.be("dropdown","header"))},[ce(e.$slots,"header")],2)):te("v-if",!0),ce(e.$slots,"default"),e.$slots.footer?(x(),O("div",{key:1,class:U(e.ns.be("dropdown","footer"))},[ce(e.$slots,"footer")],2)):te("v-if",!0)],6)}var pce=ya(vce,[["render",hce]]);const mce=(e,t)=>{const{t:n}=Vt(),a=Xn(),r=Fa(),o=ke("select"),i=ke("input"),l=nn({inputValue:"",options:new Map,cachedOptions:new Map,optionValues:[],selected:[],selectionWidth:0,collapseItemWidth:0,selectedLabel:"",hoveringIndex:-1,previousQuery:null,inputHovering:!1,menuVisibleOnFocus:!1,isBeforeHide:!1}),s=F(),u=F(),c=F(),d=F(),v=F(),f=F(),h=F(),m=F(),g=F(),y=F(),b=F(),_=F(!1),w=F(),S=F(!1),{form:k,formItem:T}=Ea(),{inputId:M}=Yr(e,{formItemContext:T}),{valueOnClear:E,isEmptyValue:A}=op(e),{isComposing:I,handleCompositionStart:P,handleCompositionUpdate:R,handleCompositionEnd:G}=rp({afterComposition:Oe=>Wt(Oe)}),K=$n(),{wrapperRef:H,isFocused:z,handleBlur:B}=Fl(v,{disabled:K,afterFocus(){e.automaticDropdown&&!_.value&&(_.value=!0,l.menuVisibleOnFocus=!0)},beforeBlur(Oe){var lt,Bt;return((lt=c.value)==null?void 0:lt.isFocusInsideContent(Oe))||((Bt=d.value)==null?void 0:Bt.isFocusInsideContent(Oe))},afterBlur(){var Oe;_.value=!1,l.menuVisibleOnFocus=!1,e.validateEvent&&((Oe=T==null?void 0:T.validate)==null||Oe.call(T,"blur").catch(kt))}}),W=D(()=>Ae(e.modelValue)?e.modelValue.length>0:!A(e.modelValue)),N=D(()=>(k==null?void 0:k.statusIcon)??!1),V=D(()=>e.clearable&&!K.value&&W.value&&(z.value||l.inputHovering)),Y=D(()=>e.remote&&e.filterable&&!e.remoteShowSuffix?"":e.suffixIcon),X=D(()=>o.is("reverse",!!(Y.value&&_.value))),ee=D(()=>(T==null?void 0:T.validateState)||""),re=D(()=>ee.value&&F1[ee.value]),ie=D(()=>e.remote?e.debounce:0),le=D(()=>e.remote&&!l.inputValue&&l.options.size===0),ve=D(()=>e.loading?e.loadingText||n("el.select.loading"):e.filterable&&l.inputValue&&l.options.size>0&&he.value===0?e.noMatchText||n("el.select.noMatch"):l.options.size===0?e.noDataText||n("el.select.noData"):null),he=D(()=>J.value.filter(Oe=>Oe.visible).length),J=D(()=>{const Oe=Array.from(l.options.values()),lt=[];return l.optionValues.forEach(Bt=>{const _n=Oe.findIndex(Yn=>Yn.value===Bt);_n>-1&<.push(Oe[_n])}),lt.length>=Oe.length?lt:Oe}),Z=D(()=>Array.from(l.cachedOptions.values())),j=D(()=>{const Oe=J.value.filter(lt=>!lt.created).some(lt=>lt.currentLabel===l.inputValue);return e.filterable&&e.allowCreate&&l.inputValue!==""&&!Oe}),oe=()=>{e.filterable&&Qe(e.filterMethod)||e.filterable&&e.remote&&Qe(e.remoteMethod)||J.value.forEach(Oe=>{var lt;(lt=Oe.updateOption)==null||lt.call(Oe,l.inputValue)})},se=ca(),fe=D(()=>["small"].includes(se.value)?"small":"default"),ge=D({get(){return _.value&&(e.loading||!le.value||e.remote&&!!a.empty)&&(!S.value||!_r(l.previousQuery)||l.options.size>0)},set(Oe){_.value=Oe}}),Se=D(()=>{if(e.multiple&&!zt(e.modelValue))return wa(e.modelValue).length===0&&!l.inputValue;const Oe=Ae(e.modelValue)?e.modelValue[0]:e.modelValue;return e.filterable||zt(Oe)?!l.inputValue:!0}),De=D(()=>{const Oe=e.placeholder??n("el.select.placeholder");return e.multiple||!W.value?Oe:l.selectedLabel}),Te=Nd?null:"mouseenter";ye(()=>e.modelValue,(Oe,lt)=>{e.multiple&&e.filterable&&!e.reserveKeyword&&(l.inputValue="",Re("")),Le(),!Rn(Oe,lt)&&e.validateEvent&&(T==null||T.validate("change").catch(kt))},{flush:"post",deep:!0}),ye(()=>_.value,Oe=>{Oe?Re(l.inputValue):(l.inputValue="",l.previousQuery=null,l.isBeforeHide=!0,l.menuVisibleOnFocus=!1)}),ye(()=>l.options.entries(),()=>{qt&&(Le(),e.defaultFirstOption&&(e.filterable||e.remote)&&he.value&&Ce())},{flush:"post"}),ye([()=>l.hoveringIndex,J],([Oe])=>{Je(Oe)&&Oe>-1?w.value=J.value[Oe]||{}:w.value={},J.value.forEach(lt=>{lt.hover=w.value===lt})}),ur(()=>{l.isBeforeHide||oe()});const Re=Oe=>{l.previousQuery===Oe||I.value||(l.previousQuery=Oe,e.filterable&&Qe(e.filterMethod)?e.filterMethod(Oe):e.filterable&&e.remote&&Qe(e.remoteMethod)&&e.remoteMethod(Oe),e.defaultFirstOption&&(e.filterable||e.remote)&&he.value?He(Ce):He(Be))},Ce=()=>{const Oe=J.value.filter(_n=>_n.visible&&!_n.disabled&&!_n.states.groupDisabled),lt=Oe.find(_n=>_n.created),Bt=Oe[0];l.hoveringIndex=Ue(J.value.map(_n=>_n.value),lt||Bt)},Le=()=>{if(e.multiple)l.selectedLabel="";else{const lt=me(Ae(e.modelValue)?e.modelValue[0]:e.modelValue);l.selectedLabel=lt.currentLabel,l.selected=[lt];return}const Oe=[];zt(e.modelValue)||wa(e.modelValue).forEach(lt=>{Oe.push(me(lt))}),l.selected=Oe},me=Oe=>{let lt;const Bt=qv(Oe);for(let Yn=l.cachedOptions.size-1;Yn>=0;Yn--){const Zn=Z.value[Yn];if(Bt?na(Zn.value,e.valueKey)===na(Oe,e.valueKey):Zn.value===Oe){lt={index:J.value.filter(ja=>!ja.created).indexOf(Zn),value:Oe,currentLabel:Zn.currentLabel,get isDisabled(){return Zn.isDisabled}};break}}if(lt)return lt;const _n=l.selected.find(Yn=>Bt?na(Yn.value,e.valueKey)===na(Oe,e.valueKey):Yn.value===Oe);return{index:-1,value:Oe,currentLabel:Bt?Oe.label:_n?_n.currentLabel:Oe??""}},Be=()=>{const Oe=l.selected.length;if(Oe>0){const lt=l.selected[Oe-1];l.hoveringIndex=J.value.findIndex(Bt=>Ie(lt)===Ie(Bt))}else l.hoveringIndex=-1},ze=()=>{l.selectionWidth=Number.parseFloat(window.getComputedStyle(u.value).width)},xe=()=>{l.collapseItemWidth=y.value.getBoundingClientRect().width},rt=()=>{var Oe,lt;(lt=(Oe=c.value)==null?void 0:Oe.updatePopper)==null||lt.call(Oe)},nt=()=>{var Oe,lt;(lt=(Oe=d.value)==null?void 0:Oe.updatePopper)==null||lt.call(Oe)},Pt=()=>{l.inputValue.length>0&&!_.value&&(_.value=!0),Re(l.inputValue)},Wt=Oe=>{if(l.inputValue=Oe.target.value,e.remote)S.value=!0,Jt();else return Pt()},Jt=Xh(()=>{Pt(),S.value=!1},ie),Rt=Oe=>{Rn(e.modelValue,Oe)||t(Ot,Oe)},Qt=Oe=>wA(Oe,lt=>{const Bt=l.cachedOptions.get(lt);return!(Bt!=null&&Bt.disabled)&&!(Bt!=null&&Bt.states.groupDisabled)}),_e=Oe=>{const lt=mn(Oe);if(e.multiple&<!==Pe.delete&&Oe.target.value.length<=0){const Bt=wa(e.modelValue).slice(),_n=Qt(Bt);if(_n<0)return;const Yn=Bt[_n];Bt.splice(_n,1),t(bt,Bt),Rt(Bt),t("remove-tag",Yn)}},Ne=(Oe,lt)=>{const Bt=l.selected.indexOf(lt);if(Bt>-1&&!K.value){const _n=wa(e.modelValue).slice();_n.splice(Bt,1),t(bt,_n),Rt(_n),t("remove-tag",lt.value)}Oe.stopPropagation(),It()},tt=Oe=>{Oe.stopPropagation();const lt=e.multiple?[]:E.value;if(e.multiple)for(const Bt of l.selected)Bt.isDisabled&<.push(Bt.value);t(bt,lt),Rt(lt),l.hoveringIndex=-1,_.value=!1,t("clear"),It()},Me=Oe=>{if(e.multiple){const lt=wa(e.modelValue??[]).slice(),Bt=Ue(lt,Oe);Bt>-1?lt.splice(Bt,1):(e.multipleLimit<=0||lt.length{Gt(Oe)})},Ue=(Oe,lt)=>zt(lt)?-1:_t(lt.value)?Oe.findIndex(Bt=>Rn(na(Bt,e.valueKey),Ie(lt))):Oe.indexOf(lt.value),Gt=Oe=>{var _n,Yn,Zn,ja,Gl;const lt=Ae(Oe)?Oe[Oe.length-1]:Oe;let Bt=null;if(!Un(lt==null?void 0:lt.value)){const co=J.value.filter(Yl=>Yl.value===lt.value);co.length>0&&(Bt=co[0].$el)}if(c.value&&Bt){const co=(ja=(Zn=(Yn=(_n=c.value)==null?void 0:_n.popperRef)==null?void 0:Yn.contentRef)==null?void 0:Zn.querySelector)==null?void 0:ja.call(Zn,`.${o.be("dropdown","wrap")}`);co&&k6(co,Bt)}(Gl=b.value)==null||Gl.handleScroll()},je=Oe=>{l.options.set(Oe.value,Oe),l.cachedOptions.set(Oe.value,Oe)},ft=(Oe,lt)=>{l.options.get(Oe)===lt&&l.options.delete(Oe)},ht=D(()=>{var Oe,lt;return(lt=(Oe=c.value)==null?void 0:Oe.popperRef)==null?void 0:lt.contentRef}),pt=()=>{l.isBeforeHide=!1,He(()=>{var Oe;(Oe=b.value)==null||Oe.update(),Gt(l.selected)})},It=()=>{var Oe;(Oe=v.value)==null||Oe.focus()},Ct=()=>{var Oe;if(_.value){_.value=!1,He(()=>{var lt;return(lt=v.value)==null?void 0:lt.blur()});return}(Oe=v.value)==null||Oe.blur()},$t=Oe=>{tt(Oe)},Sn=Oe=>{if(_.value=!1,z.value){const lt=new FocusEvent("blur",Oe);He(()=>B(lt))}},Fe=()=>{l.inputValue.length>0?l.inputValue="":_.value=!1},qe=Oe=>{var lt;K.value||e.filterable&&_.value&&Oe&&!((lt=h.value)!=null&<.contains(Oe.target))||(Nd&&(l.inputHovering=!0),l.menuVisibleOnFocus?l.menuVisibleOnFocus=!1:_.value=!_.value)},ut=()=>{if(!_.value)qe();else{const Oe=J.value[l.hoveringIndex];Oe&&!Oe.isDisabled&&Me(Oe)}},Ie=Oe=>_t(Oe.value)?na(Oe.value,e.valueKey):Oe.value,Ke=D(()=>J.value.filter(Oe=>Oe.visible).every(Oe=>Oe.isDisabled)),Ge=D(()=>e.multiple?e.collapseTags?l.selected.slice(0,e.maxCollapseTags):l.selected:[]),Ee=D(()=>e.multiple?e.collapseTags?l.selected.slice(e.maxCollapseTags):[]:[]),de=Oe=>{if(!_.value){_.value=!0;return}if(!(l.options.size===0||he.value===0||I.value)&&!Ke.value){Oe==="next"?(l.hoveringIndex++,l.hoveringIndex===l.options.size&&(l.hoveringIndex=0)):Oe==="prev"&&(l.hoveringIndex--,l.hoveringIndex<0&&(l.hoveringIndex=l.options.size-1));const lt=J.value[l.hoveringIndex];(lt.isDisabled||!lt.visible)&&de(Oe),He(()=>Gt(w.value))}},it=(Oe,lt,Bt,_n)=>{for(let Yn=lt;Yn>=0&&Yn<_n;Yn+=Bt){const Zn=Oe[Yn];if(!(Zn!=null&&Zn.isDisabled)&&(Zn!=null&&Zn.visible))return Yn}return null},Dt=(Oe,lt)=>{const Bt=l.options.size;if(Bt===0)return;const _n=Br(Oe,0,Bt-1),Yn=J.value,Zn=lt==="up"?-1:1,ja=it(Yn,_n,Zn,Bt)??it(Yn,_n-Zn,-Zn,Bt);ja!=null&&(l.hoveringIndex=ja,He(()=>Gt(w.value)))},sn=Oe=>{const lt=mn(Oe);let Bt=!0;switch(lt){case Pe.up:de("prev");break;case Pe.down:de("next");break;case Pe.enter:case Pe.numpadEnter:I.value||ut();break;case Pe.esc:Fe();break;case Pe.backspace:Bt=!1,_e(Oe);return;case Pe.home:if(!_.value)return;Dt(0,"down");break;case Pe.end:if(!_.value)return;Dt(l.options.size-1,"up");break;case Pe.pageUp:if(!_.value)return;Dt(l.hoveringIndex-10,"up");break;case Pe.pageDown:if(!_.value)return;Dt(l.hoveringIndex+10,"down");break;default:Bt=!1;break}Bt&&(Oe.preventDefault(),Oe.stopPropagation())},gn=()=>{if(!u.value)return 0;const Oe=window.getComputedStyle(u.value);return Number.parseFloat(Oe.gap||"6px")},Aa=D(()=>{const Oe=gn(),lt=e.filterable?Oe+11:0;return{maxWidth:`${y.value&&e.maxCollapseTags===1?l.selectionWidth-l.collapseItemWidth-Oe-lt:l.selectionWidth-lt}px`}}),jr=D(()=>({maxWidth:`${l.selectionWidth}px`})),vr=Oe=>{t("popup-scroll",Oe)},Ia=Oe=>{t("end-reached",Oe)};Mn(u,ze),Mn(H,rt),Mn(g,nt),Mn(y,xe);let Oa;return ye(()=>ge.value,Oe=>{Oe?Oa=Mn(m,rt).stop:(Oa==null||Oa(),Oa=void 0),t("visible-change",Oe)}),mt(()=>{Le()}),{inputId:M,contentId:r,nsSelect:o,nsInput:i,states:l,isFocused:z,expanded:_,optionsArray:J,hoverOption:w,selectSize:se,filteredOptionsCount:he,updateTooltip:rt,updateTagTooltip:nt,debouncedOnInputChange:Jt,onInput:Wt,deletePrevTag:_e,deleteTag:Ne,deleteSelected:tt,handleOptionSelect:Me,scrollToOption:Gt,hasModelValue:W,shouldShowPlaceholder:Se,currentPlaceholder:De,mouseEnterEventName:Te,needStatusIcon:N,showClearBtn:V,iconComponent:Y,iconReverse:X,validateState:ee,validateIcon:re,showNewOption:j,updateOptions:oe,collapseTagSize:fe,setSelected:Le,selectDisabled:K,emptyText:ve,handleCompositionStart:P,handleCompositionUpdate:R,handleCompositionEnd:G,handleKeydown:sn,onOptionCreate:je,onOptionDestroy:ft,handleMenuEnter:pt,focus:It,blur:Ct,handleClearClick:$t,handleClickOutside:Sn,handleEsc:Fe,toggleMenu:qe,selectOption:ut,getValueKey:Ie,navigateOptions:de,dropdownMenuVisible:ge,showTagList:Ge,collapseTagList:Ee,popupScroll:vr,getOption:me,endReached:Ia,tagStyle:Aa,collapseTagStyle:jr,popperRef:ht,inputRef:v,tooltipRef:c,tagTooltipRef:d,prefixRef:f,suffixRef:h,selectRef:s,wrapperRef:H,selectionRef:u,scrollbarRef:b,menuRef:m,tagMenuRef:g,collapseItemRef:y}};var gce=q({name:"ElOptions",setup(e,{slots:t}){const n=Ze(sp);let a=[];return()=>{var l,s;const r=(l=t.default)==null?void 0:l.call(t),o=[];function i(u){Ae(u)&&u.forEach(c=>{var v,f,h,m;const d=(v=(c==null?void 0:c.type)||{})==null?void 0:v.name;d==="ElOptionGroup"?i(!Ye(c.children)&&!Ae(c.children)&&Qe((f=c.children)==null?void 0:f.default)?(h=c.children)==null?void 0:h.default():c.children):d==="ElOption"?o.push((m=c.props)==null?void 0:m.value):Ae(c.children)&&i(c.children)})}return r.length&&i((s=r[0])==null?void 0:s.children),Rn(o,a)||(a=o,n&&(n.states.optionValues=o)),r}}});const ex="ElSelect",ph=new WeakMap,yce=e=>(...t)=>{var r,o;const n=t[0];if(!n||n.includes('Slot "default" invoked outside of the render function')&&((r=t[2])!=null&&r.includes("ElTreeSelect")))return;const a=(o=ph.get(e))==null?void 0:o.originalWarnHandler;if(a){a(...t);return}console.warn(...t)},bce=e=>{let t=ph.get(e);return t||(t={originalWarnHandler:e.config.warnHandler,handler:yce(e),count:0},ph.set(e,t)),t};var _ce=q({name:ex,componentName:ex,components:{ElSelectMenu:pce,ElOption:ow,ElOptions:gce,ElOptionGroup:iw,ElTag:Cl,ElScrollbar:ni,ElTooltip:Ma,ElIcon:Xe},directives:{ClickOutside:Ns},props:FL,emits:oce,setup(e,{emit:t,slots:n}){const a=Et(),r=bce(a.appContext);r.count+=1,a.appContext.config.warnHandler=r.handler;const o=D(()=>{const{modelValue:b,multiple:_}=e,w=_?[]:void 0;return Ae(b)?_?b:w:_?w:b}),i=nn({...Ta(e),modelValue:o}),l=mce(i,t),{calculatorRef:s,inputStyle:u}=w6(),{getLabel:c,getValue:d,getOptions:v,getDisabled:f}=up(e),h=b=>({label:c(b),value:d(b),disabled:f(b)}),m=b=>b.reduce((_,w)=>(_.push(w),w.children&&w.children.length>0&&_.push(...m(w.children)),_),[]),g=b=>{eo(b||[]).forEach(_=>{var w;if(_t(_)&&(_.type.name==="ElOption"||_.type.name==="ElTree")){const S=_.type.name;if(S==="ElTree")m(((w=_.props)==null?void 0:w.data)||[]).forEach(k=>{k.currentLabel=k.label??(_t(k.value)?"":k.value),l.onOptionCreate(k)});else if(S==="ElOption"){const k={..._.props};k.currentLabel=k.label??(_t(k.value)?"":k.value),l.onOptionCreate(k)}}})};ye(()=>{var b;return[e.persistent||l.expanded.value||!n.default||(b=n.default)==null?void 0:b.call(n),o.value]},()=>{var b;e.persistent||l.expanded.value||n.default&&(l.states.options.clear(),g((b=n.default)==null?void 0:b.call(n)))},{immediate:!0}),Ft(sp,nn({props:i,states:l.states,selectRef:l.selectRef,optionsArray:l.optionsArray,setSelected:l.setSelected,handleOptionSelect:l.handleOptionSelect,onOptionCreate:l.onOptionCreate,onOptionDestroy:l.onOptionDestroy}));const y=D(()=>e.multiple?l.states.selected.map(b=>b.currentLabel):l.states.selectedLabel);return rn(()=>{const b=ph.get(a.appContext);b&&(b.count-=1,b.count<=0&&(a.appContext.config.warnHandler=b.originalWarnHandler,ph.delete(a.appContext)))}),{...l,modelValue:o,selectedLabel:y,calculatorRef:s,inputStyle:u,getLabel:c,getValue:d,getOptions:v,getDisabled:f,getOptionProps:h}}});const wce=["id","value","name","disabled","autocomplete","tabindex","readonly","aria-activedescendant","aria-controls","aria-expanded","aria-label"],Sce=["textContent"],Cce={key:1};function xce(e,t,n,a,r,o){const i=be("el-tag"),l=be("el-tooltip"),s=be("el-icon"),u=be("el-option"),c=be("el-option-group"),d=be("el-options"),v=be("el-scrollbar"),f=be("el-select-menu"),h=Gr("click-outside");return st((x(),O("div",Tt({ref:"selectRef",class:[e.nsSelect.b(),e.nsSelect.m(e.selectSize)]},{[Wu(e.mouseEnterEventName)]:t[11]||(t[11]=m=>e.states.inputHovering=!0)},{onMouseleave:t[12]||(t[12]=m=>e.states.inputHovering=!1)}),[L(l,{ref:"tooltipRef",visible:e.dropdownMenuVisible,placement:e.placement,teleported:e.teleported,"popper-class":[e.nsSelect.e("popper"),e.popperClass],"popper-style":e.popperStyle,"popper-options":e.popperOptions,"fallback-placements":e.fallbackPlacements,effect:e.effect,pure:"",trigger:"click",transition:`${e.nsSelect.namespace.value}-zoom-in-top`,"stop-popper-mouse-event":!1,"gpu-acceleration":!1,persistent:e.persistent,"append-to":e.appendTo,"show-arrow":e.showArrow,offset:e.offset,onBeforeShow:e.handleMenuEnter,onHide:t[10]||(t[10]=m=>e.states.isBeforeHide=!1)},{default:$(()=>{var m;return[C("div",{ref:"wrapperRef",class:U([e.nsSelect.e("wrapper"),e.nsSelect.is("focused",e.isFocused),e.nsSelect.is("hovering",e.states.inputHovering),e.nsSelect.is("filterable",e.filterable),e.nsSelect.is("disabled",e.selectDisabled)]),onClick:t[7]||(t[7]=at((...g)=>e.toggleMenu&&e.toggleMenu(...g),["prevent"]))},[e.$slots.prefix?(x(),O("div",{key:0,ref:"prefixRef",class:U(e.nsSelect.e("prefix"))},[ce(e.$slots,"prefix")],2)):te("v-if",!0),C("div",{ref:"selectionRef",class:U([e.nsSelect.e("selection"),e.nsSelect.is("near",e.multiple&&!e.$slots.prefix&&!!e.states.selected.length)])},[e.multiple?ce(e.$slots,"tag",{key:0,data:e.states.selected,deleteTag:e.deleteTag,selectDisabled:e.selectDisabled},()=>{var g,y,b,_,w,S,k,T,M,E,A,I,P;return[(x(!0),O(Ve,null,dt(e.showTagList,R=>(x(),O("div",{key:e.getValueKey(R),class:U(e.nsSelect.e("selected-item"))},[L(i,{closable:!e.selectDisabled&&!R.isDisabled,size:e.collapseTagSize,type:e.tagType,effect:e.tagEffect,"disable-transitions":"",style:et(e.tagStyle),onClose:G=>e.deleteTag(G,R)},{default:$(()=>[C("span",{class:U(e.nsSelect.e("tags-text"))},[ce(e.$slots,"label",{index:R.index,label:R.currentLabel,value:R.value},()=>[ue(Q(R.currentLabel),1)])],2)]),_:2},1032,["closable","size","type","effect","style","onClose"])],2))),128)),e.collapseTags&&e.states.selected.length>e.maxCollapseTags?(x(),ne(l,{key:0,ref:"tagTooltipRef",disabled:e.dropdownMenuVisible||!e.collapseTagsTooltip,"fallback-placements":((g=e.tagTooltip)==null?void 0:g.fallbackPlacements)??["bottom","top","right","left"],effect:((y=e.tagTooltip)==null?void 0:y.effect)??e.effect,placement:((b=e.tagTooltip)==null?void 0:b.placement)??"bottom","popper-class":((_=e.tagTooltip)==null?void 0:_.popperClass)??e.popperClass,"popper-style":((w=e.tagTooltip)==null?void 0:w.popperStyle)??e.popperStyle,teleported:((S=e.tagTooltip)==null?void 0:S.teleported)??e.teleported,"append-to":((k=e.tagTooltip)==null?void 0:k.appendTo)??e.appendTo,"popper-options":((T=e.tagTooltip)==null?void 0:T.popperOptions)??e.popperOptions,transition:(M=e.tagTooltip)==null?void 0:M.transition,"show-after":(E=e.tagTooltip)==null?void 0:E.showAfter,"hide-after":(A=e.tagTooltip)==null?void 0:A.hideAfter,"auto-close":(I=e.tagTooltip)==null?void 0:I.autoClose,offset:(P=e.tagTooltip)==null?void 0:P.offset},{default:$(()=>[C("div",{ref:"collapseItemRef",class:U(e.nsSelect.e("selected-item"))},[L(i,{closable:!1,size:e.collapseTagSize,type:e.tagType,effect:e.tagEffect,"disable-transitions":"",style:et(e.collapseTagStyle)},{default:$(()=>[C("span",{class:U(e.nsSelect.e("tags-text"))}," + "+Q(e.states.selected.length-e.maxCollapseTags),3)]),_:1},8,["size","type","effect","style"])],2)]),content:$(()=>[C("div",{ref:"tagMenuRef",class:U(e.nsSelect.e("selection"))},[(x(!0),O(Ve,null,dt(e.collapseTagList,R=>(x(),O("div",{key:e.getValueKey(R),class:U(e.nsSelect.e("selected-item"))},[L(i,{class:"in-tooltip",closable:!e.selectDisabled&&!R.isDisabled,size:e.collapseTagSize,type:e.tagType,effect:e.tagEffect,"disable-transitions":"",onClose:G=>e.deleteTag(G,R)},{default:$(()=>[C("span",{class:U(e.nsSelect.e("tags-text"))},[ce(e.$slots,"label",{index:R.index,label:R.currentLabel,value:R.value},()=>[ue(Q(R.currentLabel),1)])],2)]),_:2},1032,["closable","size","type","effect","onClose"])],2))),128))],2)]),_:3},8,["disabled","fallback-placements","effect","placement","popper-class","popper-style","teleported","append-to","popper-options","transition","show-after","hide-after","auto-close","offset"])):te("v-if",!0)]}):te("v-if",!0),C("div",{class:U([e.nsSelect.e("selected-item"),e.nsSelect.e("input-wrapper"),e.nsSelect.is("hidden",!e.filterable||e.selectDisabled||e.multiple&&!e.states.inputValue&&!e.isFocused)])},[C("input",{id:e.inputId,ref:"inputRef",value:e.states.inputValue,type:"text",name:e.name,class:U([e.nsSelect.e("input"),e.nsSelect.is(e.selectSize)]),disabled:e.selectDisabled,autocomplete:e.autocomplete,style:et(e.inputStyle),tabindex:e.tabindex,role:"combobox",readonly:!e.filterable,spellcheck:"false","aria-activedescendant":((m=e.hoverOption)==null?void 0:m.id)||"","aria-controls":e.contentId,"aria-expanded":e.dropdownMenuVisible,"aria-label":e.ariaLabel,"aria-autocomplete":"none","aria-haspopup":"listbox",onKeydown:t[0]||(t[0]=(...g)=>e.handleKeydown&&e.handleKeydown(...g)),onCompositionstart:t[1]||(t[1]=(...g)=>e.handleCompositionStart&&e.handleCompositionStart(...g)),onCompositionupdate:t[2]||(t[2]=(...g)=>e.handleCompositionUpdate&&e.handleCompositionUpdate(...g)),onCompositionend:t[3]||(t[3]=(...g)=>e.handleCompositionEnd&&e.handleCompositionEnd(...g)),onInput:t[4]||(t[4]=(...g)=>e.onInput&&e.onInput(...g)),onChange:t[5]||(t[5]=at(()=>{},["stop"])),onClick:t[6]||(t[6]=at((...g)=>e.toggleMenu&&e.toggleMenu(...g),["stop"]))},null,46,wce),e.filterable?(x(),O("span",{key:0,ref:"calculatorRef","aria-hidden":"true",class:U(e.nsSelect.e("input-calculator")),textContent:Q(e.states.inputValue)},null,10,Sce)):te("v-if",!0)],2),e.shouldShowPlaceholder?(x(),O("div",{key:1,class:U([e.nsSelect.e("selected-item"),e.nsSelect.e("placeholder"),e.nsSelect.is("transparent",!e.hasModelValue||e.expanded&&!e.states.inputValue)])},[e.hasModelValue?ce(e.$slots,"label",{key:0,index:e.getOption(e.modelValue).index,label:e.currentPlaceholder,value:e.modelValue},()=>[C("span",null,Q(e.currentPlaceholder),1)]):(x(),O("span",Cce,Q(e.currentPlaceholder),1))],2)):te("v-if",!0)],2),C("div",{ref:"suffixRef",class:U(e.nsSelect.e("suffix"))},[e.iconComponent&&!e.showClearBtn?(x(),ne(s,{key:0,class:U([e.nsSelect.e("caret"),e.nsSelect.e("icon"),e.iconReverse])},{default:$(()=>[(x(),ne(St(e.iconComponent)))]),_:1},8,["class"])):te("v-if",!0),e.showClearBtn&&e.clearIcon?(x(),ne(s,{key:1,class:U([e.nsSelect.e("caret"),e.nsSelect.e("icon"),e.nsSelect.e("clear")]),onClick:e.handleClearClick},{default:$(()=>[(x(),ne(St(e.clearIcon)))]),_:1},8,["class","onClick"])):te("v-if",!0),e.validateState&&e.validateIcon&&e.needStatusIcon?(x(),ne(s,{key:2,class:U([e.nsInput.e("icon"),e.nsInput.e("validateIcon"),e.nsInput.is("loading",e.validateState==="validating")])},{default:$(()=>[(x(),ne(St(e.validateIcon)))]),_:1},8,["class"])):te("v-if",!0)],2)],2)]}),content:$(()=>[L(f,{ref:"menuRef"},{default:$(()=>[e.$slots.header?(x(),O("div",{key:0,class:U(e.nsSelect.be("dropdown","header")),onClick:t[8]||(t[8]=at(()=>{},["stop"]))},[ce(e.$slots,"header")],2)):te("v-if",!0),st(L(v,{id:e.contentId,ref:"scrollbarRef",tag:"ul","wrap-class":e.nsSelect.be("dropdown","wrap"),"view-class":e.nsSelect.be("dropdown","list"),class:U([e.nsSelect.is("empty",e.filteredOptionsCount===0)]),role:"listbox","aria-label":e.ariaLabel,"aria-orientation":"vertical",onScroll:e.popupScroll,onEndReached:e.endReached},{default:$(()=>[e.showNewOption?(x(),ne(u,{key:0,value:e.states.inputValue,created:!0},null,8,["value"])):te("v-if",!0),L(d,null,{default:$(()=>[ce(e.$slots,"default",{},()=>[(x(!0),O(Ve,null,dt(e.options,(m,g)=>{var y;return x(),O(Ve,{key:g},[(y=e.getOptions(m))!=null&&y.length?(x(),ne(c,{key:0,label:e.getLabel(m),disabled:e.getDisabled(m)},{default:$(()=>[(x(!0),O(Ve,null,dt(e.getOptions(m),b=>(x(),ne(u,Tt({key:e.getValue(b)},{ref_for:!0},e.getOptionProps(b)),null,16))),128))]),_:2},1032,["label","disabled"])):(x(),ne(u,Tt({key:1,ref_for:!0},e.getOptionProps(m)),null,16))],64)}),128))])]),_:3})]),_:3},8,["id","wrap-class","view-class","class","aria-label","onScroll","onEndReached"]),[[Zt,e.states.options.size>0&&!e.loading]]),e.$slots.loading&&e.loading?(x(),O("div",{key:1,class:U(e.nsSelect.be("dropdown","loading"))},[ce(e.$slots,"loading")],2)):e.loading||e.filteredOptionsCount===0?(x(),O("div",{key:2,class:U(e.nsSelect.be("dropdown","empty"))},[ce(e.$slots,"empty",{},()=>[C("span",null,Q(e.emptyText),1)])],2)):te("v-if",!0),e.$slots.footer?(x(),O("div",{key:3,class:U(e.nsSelect.be("dropdown","footer")),onClick:t[9]||(t[9]=at(()=>{},["stop"]))},[ce(e.$slots,"footer")],2)):te("v-if",!0)]),_:3},512)]),_:3},8,["visible","placement","teleported","popper-class","popper-style","popper-options","fallback-placements","effect","transition","persistent","append-to","show-arrow","offset","onBeforeShow"])],16)),[[h,e.handleClickOutside,e.popperRef]])}var kce=ya(_ce,[["render",xce]]);const Bs=wt(kce,{Option:ow,OptionGroup:iw}),eg=On(ow),Tce=On(iw),Mce=(e,t)=>{const n=e.subtract(1,"month").endOf("month").date();return xs(t).map((a,r)=>n-(t-r-1))},Ece=e=>xs(e.daysInMonth()).map((t,n)=>n+1),Ace=e=>xs(e.length/7).map(t=>{const n=t*7;return e.slice(n,n+7)}),Ice=$e({selectedDay:{type:ae(Object)},range:{type:ae(Array)},date:{type:ae(Object),required:!0},hideHeader:{type:Boolean}}),Lce={pick:e=>_t(e)};var HL={exports:{}};(function(e,t){(function(n,a){e.exports=a()})(Wl,function(){return function(n,a,r){var o=a.prototype,i=function(d){return d&&(d.indexOf?d:d.s)},l=function(d,v,f,h,m){var g=d.name?d:d.$locale(),y=i(g[v]),b=i(g[f]),_=y||b.map(function(S){return S.slice(0,h)});if(!m)return _;var w=g.weekStart;return _.map(function(S,k){return _[(k+(w||0))%7]})},s=function(){return r.Ls[r.locale()]},u=function(d,v){return d.formats[v]||function(f){return f.replace(/(\[[^\]]+])|(MMMM|MM|DD|dddd)/g,function(h,m,g){return m||g.slice(1)})}(d.formats[v.toUpperCase()])},c=function(){var d=this;return{months:function(v){return v?v.format("MMMM"):l(d,"months")},monthsShort:function(v){return v?v.format("MMM"):l(d,"monthsShort","months",3)},firstDayOfWeek:function(){return d.$locale().weekStart||0},weekdays:function(v){return v?v.format("dddd"):l(d,"weekdays")},weekdaysMin:function(v){return v?v.format("dd"):l(d,"weekdaysMin","weekdays",2)},weekdaysShort:function(v){return v?v.format("ddd"):l(d,"weekdaysShort","weekdays",3)},longDateFormat:function(v){return u(d.$locale(),v)},meridiem:this.$locale().meridiem,ordinal:this.$locale().ordinal}};o.localeData=function(){return c.bind(this)()},r.localeData=function(){var d=s();return{firstDayOfWeek:function(){return d.weekStart||0},weekdays:function(){return r.weekdays()},weekdaysShort:function(){return r.weekdaysShort()},weekdaysMin:function(){return r.weekdaysMin()},months:function(){return r.months()},monthsShort:function(){return r.monthsShort()},longDateFormat:function(v){return u(d,v)},meridiem:d.meridiem,ordinal:d.ordinal}},r.months=function(){return l(s(),"months")},r.monthsShort=function(){return l(s(),"monthsShort","months",3)},r.weekdays=function(d){return l(s(),"weekdays",null,null,d)},r.weekdaysShort=function(d){return l(s(),"weekdaysShort","weekdays",3,d)},r.weekdaysMin=function(d){return l(s(),"weekdaysMin","weekdays",2,d)}}})})(HL);var Pce=HL.exports;const WL=Kl(Pce),Dce=(e,t)=>{gt.extend(WL);const n=gt.localeData().firstDayOfWeek(),{t:a,lang:r}=Vt(),o=gt().locale(r.value),i=D(()=>!!e.range&&!!e.range.length),l=D(()=>{let v=[];if(i.value){const[f,h]=e.range,m=xs(h.date()-f.date()+1).map(b=>({text:f.date()+b,type:"current"}));let g=m.length%7;g=g===0?0:7-g;const y=xs(g).map((b,_)=>({text:_+1,type:"next"}));v=m.concat(y)}else{const f=e.date.startOf("month").day(),h=Mce(e.date,(f-n+7)%7).map(y=>({text:y,type:"prev"})),m=Ece(e.date).map(y=>({text:y,type:"current"}));v=[...h,...m];const g=xs(7-(v.length%7||7)).map((y,b)=>({text:b+1,type:"next"}));v=v.concat(g)}return Ace(v)}),s=D(()=>{const v=n;return v===0?R2.map(f=>a(`el.datepicker.weeks.${f}`)):R2.slice(v).concat(R2.slice(0,v)).map(f=>a(`el.datepicker.weeks.${f}`))}),u=(v,f)=>{switch(f){case"prev":return e.date.startOf("month").subtract(1,"month").date(v);case"next":return e.date.startOf("month").add(1,"month").date(v);case"current":return e.date.date(v)}};return{now:o,isInRange:i,rows:l,weekDays:s,getFormattedDate:u,handlePickDay:({text:v,type:f})=>{t("pick",u(v,f))},getSlotData:({text:v,type:f})=>{const h=u(v,f);return{isSelected:h.isSame(e.selectedDay),type:`${f}-month`,day:h.format(ad),date:h.toDate()}}}},Oce={key:0},$ce=["onClick"];var Rce=q({name:"DateTable",__name:"date-table",props:Ice,emits:Lce,setup(e,{expose:t,emit:n}){const a=e,{isInRange:r,now:o,rows:i,weekDays:l,getFormattedDate:s,handlePickDay:u,getSlotData:c}=Dce(a,n),d=ke("calendar-table"),v=ke("calendar-day"),f=({text:h,type:m})=>{const g=[m];if(m==="current"){const y=s(h,m);y.isSame(a.selectedDay,"day")&&g.push(v.is("selected")),y.isSame(o,"day")&&g.push(v.is("today"))}return g};return t({getFormattedDate:s}),(h,m)=>(x(),O("table",{class:U([p(d).b(),p(d).is("range",p(r))]),cellspacing:"0",cellpadding:"0"},[e.hideHeader?te("v-if",!0):(x(),O("thead",Oce,[C("tr",null,[(x(!0),O(Ve,null,dt(p(l),g=>(x(),O("th",{key:g,scope:"col"},Q(g),1))),128))])])),C("tbody",null,[(x(!0),O(Ve,null,dt(p(i),(g,y)=>(x(),O("tr",{key:y,class:U({[p(d).e("row")]:!0,[p(d).em("row","hide-border")]:y===0&&e.hideHeader})},[(x(!0),O(Ve,null,dt(g,(b,_)=>(x(),O("td",{key:_,class:U(f(b)),onClick:w=>p(u)(b)},[C("div",{class:U(p(v).b())},[ce(h.$slots,"date-cell",{data:p(c)(b)},()=>[C("span",null,Q(b.text),1)])],2)],10,$ce))),128))],2))),128))])],2))}}),tx=Rce;const Nce=(e,t)=>{const n=e.endOf("month"),a=t.startOf("month"),r=n.isSame(a,"week")?a.add(1,"week"):a;return[[e,n],[r.startOf("week"),t]]},Bce=(e,t)=>{const n=e.endOf("month"),a=e.add(1,"month").startOf("month"),r=n.isSame(a,"week")?a.add(1,"week"):a,o=r.endOf("month"),i=t.startOf("month"),l=o.isSame(i,"week")?i.add(1,"week"):i;return[[e,n],[r.startOf("week"),o],[l.startOf("week"),t]]},Vce=(e,t,n)=>{const{lang:a}=Vt(),r=F(),o=gt().locale(a.value),i=D({get(){return e.modelValue?s.value:r.value},set(y){if(!y)return;r.value=y;const b=y.toDate();t(ua,b),t(bt,b)}}),l=D(()=>{if(!e.range||!Ae(e.range)||e.range.length!==2||e.range.some(_=>!Is(_)))return[];const[y,b]=e.range.map(_=>gt(_).locale(a.value));return y.isAfter(b)?(un(n,"end time should be greater than start time"),[]):y.isSame(b,"month")||y.add(1,"month").isSame(b,"month")?f(y,b):(un(n,"start time and end time interval must not exceed two months"),[])}),s=D(()=>e.modelValue?gt(e.modelValue).locale(a.value):i.value||(l.value.length?l.value[0][0]:o)),u=D(()=>s.value.subtract(1,"month").date(1)),c=D(()=>s.value.add(1,"month").date(1)),d=D(()=>s.value.subtract(1,"year").date(1)),v=D(()=>s.value.add(1,"year").date(1)),f=(y,b)=>{const _=y.startOf("week"),w=b.endOf("week"),S=_.get("month"),k=w.get("month");return S===k?[[_,w]]:(S+1)%12===k?Nce(_,w):S+2===k||(S+1)%11===k?Bce(_,w):(un(n,"start time and end time interval must not exceed two months"),[])},h=y=>{i.value=y},m=y=>{const b={"prev-month":u.value,"next-month":c.value,"prev-year":d.value,"next-year":v.value,today:o}[y];b.isSame(s.value,"day")||h(b)};return{calculateValidatedDateRange:f,date:s,realSelectedDay:i,pickDay:h,selectDate:m,validatedRange:l,handleDateChange:y=>{y==="today"?m("today"):h(y)}}},zce=$e({date:{type:ae(Object),required:!0},formatter:{type:ae(Function)}}),Fce={"date-change":e=>_t(e)||Ye(e)};var Hce=q({name:"SelectController",__name:"select-controller",props:zce,emits:Fce,setup(e,{emit:t}){const n=e,a=t,r=ke("calendar-select"),{t:o,lang:i}=Vt(),l=Array.from({length:12},(h,m)=>{const g=m+1;return{value:g,label:Qe(n.formatter)?n.formatter(g,"month"):g}}),s=D(()=>n.date.year()),u=D(()=>n.date.month()+1),c=D(()=>{const h=[];for(let m=-10;m<10;m++){const g=s.value+m;if(g>0){const y=Qe(n.formatter)?n.formatter(g,"year"):g;h.push({value:g,label:y})}}return h}),d=h=>{a("date-change",gt(new Date(h,u.value-1,1)).locale(i.value))},v=h=>{a("date-change",gt(new Date(s.value,h-1,1)).locale(i.value))},f=()=>{a("date-change","today")};return(h,m)=>(x(),O(Ve,null,[L(p(Bs),{"model-value":s.value,size:"small",class:U(p(r).e("year")),"validate-event":!1,options:c.value,onChange:d},null,8,["model-value","class","options"]),L(p(Bs),{"model-value":u.value,size:"small",class:U(p(r).e("month")),"validate-event":!1,options:p(l),onChange:v},null,8,["model-value","class","options"]),L(p(xa),{size:"small",onClick:f},{default:$(()=>[ue(Q(p(o)("el.datepicker.today")),1)]),_:1})],64))}}),Wce=Hce;const nx="ElCalendar";var Kce=q({name:nx,__name:"calendar",props:xue,emits:kue,setup(e,{expose:t,emit:n}){const a=ke("calendar"),{calculateValidatedDateRange:r,date:o,pickDay:i,realSelectedDay:l,selectDate:s,validatedRange:u,handleDateChange:c}=Vce(e,n,nx),{t:d}=Vt(),v=D(()=>{const f=`el.datepicker.month${o.value.format("M")}`;return`${o.value.year()} ${d("el.datepicker.year")} ${d(f)}`});return t({selectedDay:l,pickDay:i,selectDate:s,calculateValidatedDateRange:r}),(f,h)=>(x(),O("div",{class:U(p(a).b())},[C("div",{class:U(p(a).e("header"))},[ce(f.$slots,"header",{date:v.value},()=>[C("div",{class:U(p(a).e("title"))},Q(v.value),3),p(u).length===0&&e.controllerType==="button"?(x(),O("div",{key:0,class:U(p(a).e("button-group"))},[L(p(yL),null,{default:$(()=>[L(p(xa),{size:"small",onClick:h[0]||(h[0]=m=>p(s)("prev-month"))},{default:$(()=>[ue(Q(p(d)("el.datepicker.prevMonth")),1)]),_:1}),L(p(xa),{size:"small",onClick:h[1]||(h[1]=m=>p(s)("today"))},{default:$(()=>[ue(Q(p(d)("el.datepicker.today")),1)]),_:1}),L(p(xa),{size:"small",onClick:h[2]||(h[2]=m=>p(s)("next-month"))},{default:$(()=>[ue(Q(p(d)("el.datepicker.nextMonth")),1)]),_:1})]),_:1})],2)):p(u).length===0&&e.controllerType==="select"?(x(),O("div",{key:1,class:U(p(a).e("select-controller"))},[L(Wce,{date:p(o),formatter:e.formatter,onDateChange:p(c)},null,8,["date","formatter","onDateChange"])],2)):te("v-if",!0)])],2),p(u).length===0?(x(),O("div",{key:0,class:U(p(a).e("body"))},[L(tx,{date:p(o),"selected-day":p(l),onPick:p(i)},xr({_:2},[f.$slots["date-cell"]?{name:"date-cell",fn:$(m=>[ce(f.$slots,"date-cell",bl(wl(m)))]),key:"0"}:void 0]),1032,["date","selected-day","onPick"])],2)):(x(),O("div",{key:1,class:U(p(a).e("body"))},[(x(!0),O(Ve,null,dt(p(u),(m,g)=>(x(),ne(tx,{key:g,date:m[0],"selected-day":p(l),range:m,"hide-header":g!==0,onPick:p(i)},xr({_:2},[f.$slots["date-cell"]?{name:"date-cell",fn:$(y=>[ce(f.$slots,"date-cell",Tt({ref_for:!0},y))]),key:"0"}:void 0]),1032,["date","selected-day","range","hide-header","onPick"]))),128))],2))],2))}}),Uce=Kce;const Gce=wt(Uce),Yce=$e({header:{type:String,default:""},footer:{type:String,default:""},bodyStyle:{type:ae([String,Object,Array,Boolean]),default:""},headerClass:{type:ae([String,Array,Object,Boolean]),default:void 0},bodyClass:{type:ae([String,Array,Object,Boolean]),default:void 0},footerClass:{type:ae([String,Array,Object,Boolean]),default:void 0},shadow:{type:String,values:["always","hover","never"],default:void 0}});var jce=q({name:"ElCard",__name:"card",props:Yce,setup(e){const t=Hl("card"),n=ke("card");return(a,r)=>{var o;return x(),O("div",{class:U([p(n).b(),p(n).is(`${e.shadow||((o=p(t))==null?void 0:o.shadow)||"always"}-shadow`)])},[a.$slots.header||e.header?(x(),O("div",{key:0,class:U([p(n).e("header"),e.headerClass])},[ce(a.$slots,"header",{},()=>[ue(Q(e.header),1)])],2)):te("v-if",!0),C("div",{class:U([p(n).e("body"),e.bodyClass]),style:et(e.bodyStyle)},[ce(a.$slots,"default")],6),a.$slots.footer||e.footer?(x(),O("div",{key:1,class:U([p(n).e("footer"),e.footerClass])},[ce(a.$slots,"footer",{},()=>[ue(Q(e.footer),1)])],2)):te("v-if",!0)],2)}}}),qce=jce;const Xce=wt(qce),Zce=$e({initialIndex:{type:Number,default:0},height:{type:String,default:""},trigger:{type:String,values:["hover","click"],default:"hover"},autoplay:{type:Boolean,default:!0},interval:{type:Number,default:3e3},indicatorPosition:{type:String,values:["","none","outside"],default:""},arrow:{type:String,values:["always","hover","never"],default:"hover"},type:{type:String,values:["","card"],default:""},cardScale:{type:Number,default:.83},loop:{type:Boolean,default:!0},direction:{type:String,values:["horizontal","vertical"],default:"horizontal"},pauseOnHover:{type:Boolean,default:!0},motionBlur:Boolean}),Jce={change:(e,t)=>[e,t].every(Je)},KL=Symbol("carouselContextKey"),tg="ElCarouselItem",Qce=$e({name:{type:String,default:""},label:{type:[String,Number],default:""}}),ax=300,ede=(e,t,n)=>{const{children:a,addChild:r,removeChild:o,ChildrenSorter:i}=z1(Et(),tg),l=Xn(),s=F(-1),u=F(null),c=F(!1),d=F(),v=F(0),f=F(!0),h=D(()=>e.arrow!=="never"&&!p(y)),m=D(()=>a.value.some(re=>re.props.label.toString().length>0)),g=D(()=>e.type==="card"),y=D(()=>e.direction==="vertical"),b=D(()=>e.height!=="auto"?{height:e.height}:{height:`${v.value}px`,overflow:"hidden"}),_=hs(re=>{E(re)},ax,{trailing:!0}),w=hs(re=>{z(re)},ax),S=re=>f.value?s.value<=1?re<=1:re>1:!0;function k(){u.value&&(clearInterval(u.value),u.value=null)}function T(){e.interval<=0||!e.autoplay||u.value||(u.value=setInterval(()=>M(),e.interval))}const M=()=>{s.valuehe.props.name===re);ve.length>0&&(re=a.value.indexOf(ve[0]))}if(re=Number(re),Number.isNaN(re)||re!==Math.floor(re)){un(n,"index must be integer.");return}const ie=a.value.length,le=s.value;re<0?s.value=e.loop?ie-1:0:re>=ie?s.value=e.loop?0:ie-1:s.value=re,le===s.value&&A(le),N()}function A(re){a.value.forEach((ie,le)=>{ie.translateItem(le,s.value,re)})}function I(re,ie){var ge,Se,De,Te;const le=p(a),ve=le.length;if(ve===0||!re.states.inStage)return!1;const he=ie+1,J=ie-1,Z=ve-1,j=le[Z].states.active,oe=le[0].states.active,se=(Se=(ge=le[he])==null?void 0:ge.states)==null?void 0:Se.active,fe=(Te=(De=le[J])==null?void 0:De.states)==null?void 0:Te.active;return ie===Z&&oe||se?"left":ie===0&&j||fe?"right":!1}function P(){c.value=!0,e.pauseOnHover&&k()}function R(){c.value=!1,T()}function G(re){p(y)||a.value.forEach((ie,le)=>{re===I(ie,le)&&(ie.states.hover=!0)})}function K(){p(y)||a.value.forEach(re=>{re.states.hover=!1})}function H(re){s.value=re}function z(re){e.trigger==="hover"&&re!==s.value&&(s.value=re)}function B(){E(s.value-1)}function W(){E(s.value+1)}function N(){k(),(!e.pauseOnHover||!c.value)&&T()}function V(re){e.height==="auto"&&(v.value=re)}function Y(){var le;const re=(le=l.default)==null?void 0:le.call(l);if(!re)return null;const ie=eo(re).filter(ve=>bn(ve)&&ve.type.name==="ElCarouselItem");return(ie==null?void 0:ie.length)===2&&e.loop&&!g.value?(f.value=!0,ie):(f.value=!1,null)}ye(()=>s.value,(re,ie)=>{A(ie),f.value&&(re=re%2,ie=ie%2),ie>-1&&t(Ot,re,ie)});const X=D({get:()=>f.value?s.value%2:s.value,set:re=>s.value=re});ye(()=>e.autoplay,re=>{re?T():k()}),ye(()=>e.loop,()=>{E(s.value)}),ye(()=>e.interval,()=>{N()});const ee=Ht();return mt(()=>{ye(()=>a.value,()=>{a.value.length>0&&E(e.initialIndex)},{immediate:!0}),ee.value=Mn(d.value,()=>{A()}),T()}),rn(()=>{k(),d.value&&ee.value&&ee.value.stop()}),Ft(KL,{root:d,isCardType:g,isVertical:y,items:a,loop:e.loop,cardScale:e.cardScale,addItem:r,removeItem:o,setActiveItem:E,setContainerHeight:V}),{root:d,activeIndex:s,exposeActiveIndex:X,arrowDisplay:h,hasLabel:m,hover:c,isCardType:g,items:a,isVertical:y,containerStyle:b,isItemsTwoLength:f,handleButtonEnter:G,handleButtonLeave:K,handleIndicatorClick:H,handleMouseEnter:P,handleMouseLeave:R,setActiveItem:E,prev:B,next:W,PlaceholderItem:Y,isTwoLengthShow:S,ItemsSorter:i,throttledArrowClick:_,throttledIndicatorHover:w}},tde=["aria-label"],nde=["aria-label"],ade=["onMouseenter","onClick"],rde=["aria-label"],ode={key:0},ide={key:2,xmlns:"http://www.w3.org/2000/svg",version:"1.1",style:{display:"none"}},rx="ElCarousel";var lde=q({name:rx,__name:"carousel",props:Zce,emits:Jce,setup(e,{expose:t,emit:n}){const a=e,{root:r,activeIndex:o,exposeActiveIndex:i,arrowDisplay:l,hasLabel:s,hover:u,isCardType:c,items:d,isVertical:v,containerStyle:f,handleButtonEnter:h,handleButtonLeave:m,handleIndicatorClick:g,handleMouseEnter:y,handleMouseLeave:b,setActiveItem:_,prev:w,next:S,PlaceholderItem:k,isTwoLengthShow:T,ItemsSorter:M,throttledArrowClick:E,throttledIndicatorHover:A}=ede(a,n,rx),I=ke("carousel"),{t:P}=Vt(),R=D(()=>{const z=[I.b(),I.m(a.direction)];return p(c)&&z.push(I.m("card")),z.push(I.is("vertical-outside",p(v)&&a.indicatorPosition==="outside")),z}),G=D(()=>{const z=[I.e("indicators"),I.em("indicators",a.direction)];return p(s)&&z.push(I.em("indicators","labels")),a.indicatorPosition==="outside"&&z.push(I.em("indicators","outside")),p(v)&&z.push(I.em("indicators","right")),z});function K(z){if(!a.motionBlur)return;const B=p(v)?`${I.namespace.value}-transitioning-vertical`:`${I.namespace.value}-transitioning`;z.currentTarget.classList.add(B)}function H(z){if(!a.motionBlur)return;const B=p(v)?`${I.namespace.value}-transitioning-vertical`:`${I.namespace.value}-transitioning`;z.currentTarget.classList.remove(B)}return t({activeIndex:i,setActiveItem:_,prev:w,next:S}),(z,B)=>(x(),O("div",{ref_key:"root",ref:r,class:U(R.value),onMouseenter:B[6]||(B[6]=at((...W)=>p(y)&&p(y)(...W),["stop"])),onMouseleave:B[7]||(B[7]=at((...W)=>p(b)&&p(b)(...W),["stop"]))},[p(l)?(x(),ne(Pa,{key:0,name:"carousel-arrow-left",persisted:""},{default:$(()=>[st(C("button",{type:"button",class:U([p(I).e("arrow"),p(I).em("arrow","left")]),"aria-label":p(P)("el.carousel.leftArrow"),onMouseenter:B[0]||(B[0]=W=>p(h)("left")),onMouseleave:B[1]||(B[1]=(...W)=>p(m)&&p(m)(...W)),onClick:B[2]||(B[2]=at(W=>p(E)(p(o)-1),["stop"]))},[L(p(Xe),null,{default:$(()=>[L(p(Ji))]),_:1})],42,tde),[[Zt,(e.arrow==="always"||p(u))&&(e.loop||p(o)>0)]])]),_:1})):te("v-if",!0),p(l)?(x(),ne(Pa,{key:1,name:"carousel-arrow-right",persisted:""},{default:$(()=>[st(C("button",{type:"button",class:U([p(I).e("arrow"),p(I).em("arrow","right")]),"aria-label":p(P)("el.carousel.rightArrow"),onMouseenter:B[3]||(B[3]=W=>p(h)("right")),onMouseleave:B[4]||(B[4]=(...W)=>p(m)&&p(m)(...W)),onClick:B[5]||(B[5]=at(W=>p(E)(p(o)+1),["stop"]))},[L(p(Xe),null,{default:$(()=>[L(p(za))]),_:1})],42,nde),[[Zt,(e.arrow==="always"||p(u))&&(e.loop||p(o)[e.indicatorPosition!=="none"?(x(),O("ul",{key:0,class:U(G.value)},[(x(!0),O(Ve,null,dt(p(d),(W,N)=>st((x(),O("li",{key:N,class:U([p(I).e("indicator"),p(I).em("indicator",e.direction),p(I).is("active",N===p(o))]),onMouseenter:V=>p(A)(N),onClick:at(V=>p(g)(N),["stop"])},[C("button",{class:U(p(I).e("button")),"aria-label":p(P)("el.carousel.indicator",{index:N+1})},[p(s)?(x(),O("span",ode,Q(W.props.label),1)):te("v-if",!0)],10,rde)],42,ade)),[[Zt,p(T)(N)]])),128))],2)):te("v-if",!0)]),_:1}),e.motionBlur?(x(),O("svg",ide,[...B[8]||(B[8]=[C("defs",null,[C("filter",{id:"elCarouselHorizontal"},[C("feGaussianBlur",{in:"SourceGraphic",stdDeviation:"12,0"})]),C("filter",{id:"elCarouselVertical"},[C("feGaussianBlur",{in:"SourceGraphic",stdDeviation:"0,10"})])],-1)])])):te("v-if",!0)],34))}}),sde=lde;const ude=e=>{const t=Ze(KL),n=Et();t||un(tg,"usage: "),n||un(tg,"compositional hook can only be invoked inside setups");const a=F(),r=F(!1),o=F(0),i=F(1),l=F(!1),s=F(!1),u=F(!1),c=F(!1),{isCardType:d,isVertical:v,cardScale:f}=t;function h(w,S,k){const T=k-1,M=S-1,E=S+1,A=k/2;return S===0&&w===T?-1:S===T&&w===0?k:w=A?k+1:w>E&&w-S>=A?-2:w}function m(w,S){var T,M;const k=p(v)?((T=t.root.value)==null?void 0:T.offsetHeight)||0:((M=t.root.value)==null?void 0:M.offsetWidth)||0;return u.value?k*((2-f)*(w-S)+1)/4:w{const T=p(d),M=t.items.value.length??NaN,E=w===S;!T&&!zt(k)&&(c.value=E||w===k),!E&&M>2&&t.loop&&(w=h(w,S,M));const A=p(v);l.value=E,T?(u.value=Math.round(Math.abs(w-S))<=1,o.value=m(w,S),i.value=p(l)?1:f):o.value=g(w,S,A),s.value=!0,E&&a.value&&t.setContainerHeight(a.value.offsetHeight)};function b(){if(t&&p(d)){const w=t.items.value.findIndex(({uid:S})=>S===n.uid);t.setActiveItem(w)}}const _={props:e,states:nn({hover:r,translate:o,scale:i,active:l,ready:s,inStage:u,animating:c}),uid:n.uid,getVnode:()=>n.vnode,translateItem:y};return t.addItem(_),rn(()=>{t.removeItem(_)}),{carouselItemRef:a,active:l,animating:c,hover:r,inStage:u,isVertical:v,translate:o,isCardType:d,scale:i,ready:s,handleItemClick:b}};var cde=q({name:tg,__name:"carousel-item",props:Qce,setup(e){const t=e,n=ke("carousel"),{carouselItemRef:a,active:r,animating:o,hover:i,inStage:l,isVertical:s,translate:u,isCardType:c,scale:d,ready:v,handleItemClick:f}=ude(t),h=D(()=>[n.e("item"),n.is("active",r.value),n.is("in-stage",l.value),n.is("hover",i.value),n.is("animating",o.value),{[n.em("item","card")]:c.value,[n.em("item","card-vertical")]:c.value&&s.value}]),m=D(()=>({transform:[`${`translate${p(s)?"Y":"X"}`}(${p(u)}px)`,`scale(${p(d)})`].join(" ")}));return(g,y)=>st((x(),O("div",{ref_key:"carouselItemRef",ref:a,class:U(h.value),style:et(m.value),onClick:y[0]||(y[0]=(...b)=>p(f)&&p(f)(...b))},[p(c)?st((x(),O("div",{key:0,class:U(p(n).e("mask"))},null,2)),[[Zt,!p(r)]]):te("v-if",!0),ce(g.$slots,"default")],6)),[[Zt,p(v)]])}}),UL=cde;const dde=wt(sde,{CarouselItem:UL}),fde=On(UL),GL=$e({modelValue:{type:ae([Number,String,Array,Object])},options:{type:ae(Array),default:()=>[]},props:{type:ae(Object),default:()=>({})},virtualScroll:Boolean,itemSize:{type:Number,default:34},height:{type:Number,default:204}}),vde={expandTrigger:"click",multiple:!1,checkStrictly:!1,emitPath:!0,lazy:!1,lazyLoad:kt,value:"value",label:"label",children:"children",leaf:"leaf",disabled:"disabled",hoverThreshold:500,checkOnClickNode:!1,checkOnClickLeaf:!0,showPrefix:!0},hde=$e({...GL,border:{type:Boolean,default:!0},renderLabel:{type:Function}}),ox=e=>!0,pde={[bt]:ox,[Ot]:ox,close:()=>!0,"expand-change":e=>e},mde=e=>D(()=>({...vde,...e.props})),ng="itemRendered",ag="scroll",ny="end-reached",rg="forward",gde="backward",Io="auto",mh="start",Pi="center",yde="horizontal",YL="vertical",gh="negative",lw="positive-ascending",sw="positive-descending",bde={[yde]:"left",[YL]:"top"},V4=si({type:ae([Number,Function]),required:!0}),z4=si({type:Number}),F4=si({type:Number,default:2}),_de=si({type:String,values:["ltr","rtl"],default:"ltr"}),H4=si({type:Number,default:0}),og=si({type:Number,required:!0}),jL=si({type:String,values:["horizontal","vertical"],default:YL}),qL=$e({className:{type:ae([String,Array,Object,Boolean]),default:""},containerElement:{type:ae([String,Object]),default:"div"},data:{type:ae(Array),default:()=>Nn([])},direction:_de,height:{type:[String,Number],required:!0},innerElement:{type:[String,Object],default:"div"},innerProps:{type:ae(Object),default:()=>({})},style:{type:ae([Object,String,Array,Boolean]),default:void 0},useIsScrolling:Boolean,width:{type:[Number,String],required:!1},innerWidth:{type:[Number,String],required:!1},perfMode:{type:Boolean,default:!0},scrollbarAlwaysOn:Boolean}),XL=$e({cache:F4,estimatedItemSize:z4,layout:jL,initScrollOffset:H4,total:og,itemSize:V4,...qL}),W4={type:Number,default:6},ZL={type:Number,default:0},JL={type:Number,default:2},Xu=$e({columnCache:F4,columnWidth:V4,estimatedColumnWidth:z4,estimatedRowHeight:z4,initScrollLeft:H4,initScrollTop:H4,itemKey:{type:ae(Function),default:({columnIndex:e,rowIndex:t})=>`${t}:${e}`},rowCache:F4,rowHeight:V4,totalColumn:og,totalRow:og,hScrollbarSize:W4,vScrollbarSize:W4,scrollbarStartGap:ZL,scrollbarEndGap:JL,role:String,...qL}),QL=$e({alwaysOn:Boolean,class:{type:ae([String,Array,Object,Boolean]),default:""},layout:jL,total:og,ratio:{type:Number,required:!0},clientSize:{type:Number,required:!0},scrollFrom:{type:Number,required:!0},scrollbarSize:W4,startGap:ZL,endGap:JL,visible:Boolean}),lv=(e,t)=>ee==="ltr"||e==="rtl"||e==="horizontal",ix=e=>e==="rtl";let Rc=null;function ig(e=!1){if(Rc===null||e){const t=document.createElement("div"),n=t.style;n.width="50px",n.height="50px",n.overflow="scroll",n.direction="rtl";const a=document.createElement("div"),r=a.style;return r.width="100px",r.height="100px",t.appendChild(a),document.body.appendChild(t),t.scrollLeft>0?Rc=sw:(t.scrollLeft=1,t.scrollLeft===0?Rc=gh:Rc=lw),document.body.removeChild(t),Rc}return Rc}function wde({move:e,size:t,bar:n},a){const r={},o=`translate${n.axis}(${e}px)`;return r[n.size]=t,r.transform=o,a==="horizontal"?r.height="100%":r.width="100%",r}var lx=Number.isNaN||function(t){return typeof t=="number"&&t!==t};function Sde(e,t){return!!(e===t||lx(e)&&lx(t))}function Cde(e,t){if(e.length!==t.length)return!1;for(var n=0;n{const e=Et().proxy.$props;return D(()=>{const t=(n,a,r)=>({});return e.perfMode?k1(t):xde(t)})},tP=({atEndEdge:e,atStartEdge:t,layout:n},a)=>{let r,o=0;const i=s=>s<0&&t.value||s>0&&e.value;return{hasReachedEdge:i,onWheel:s=>{ti(r);let{deltaX:u,deltaY:c}=s;s.shiftKey&&c!==0&&(u=c,c=0);const d=n.value==="horizontal"?u:c;i(d)||(o+=d,!P1()&&d!==0&&s.preventDefault(),r=Wr(()=>{a(o),o=0}))}}},K4=q({name:"ElVirtualScrollBar",props:QL,emits:["scroll","start-move","stop-move"],setup(e,{emit:t}){const n=D(()=>e.startGap+e.endGap),a=ke("virtual-scrollbar"),r=ke("scrollbar"),o=F(),i=F();let l=null,s=null;const u=nn({isDragging:!1,traveled:0}),c=D(()=>lL[e.layout]),d=D(()=>e.clientSize-p(n)),v=D(()=>({position:"absolute",width:`${e.layout==="horizontal"?d.value:e.scrollbarSize}px`,height:`${e.layout==="horizontal"?e.scrollbarSize:d.value}px`,[bde[e.layout]]:"2px",right:"2px",bottom:"2px",borderRadius:"4px"})),f=D(()=>{const k=e.ratio;if(k>=100)return Number.POSITIVE_INFINITY;if(k>=50)return k*d.value/100;const T=d.value/3;return Math.floor(Math.min(Math.max(k*d.value/100,20),T))}),h=D(()=>{if(!Number.isFinite(f.value))return{display:"none"};const k=`${f.value}px`;return wde({bar:c.value,size:k,move:u.traveled},e.layout)}),m=D(()=>Math.ceil(e.clientSize-f.value-p(n))),g=()=>{window.addEventListener("mousemove",w),window.addEventListener("mouseup",_);const k=p(i);k&&(s=document.onselectstart,document.onselectstart=()=>!1,k.addEventListener("touchmove",w,{passive:!0}),k.addEventListener("touchend",_))},y=()=>{window.removeEventListener("mousemove",w),window.removeEventListener("mouseup",_),document.onselectstart=s,s=null;const k=p(i);k&&(k.removeEventListener("touchmove",w),k.removeEventListener("touchend",_))},b=k=>{k.stopImmediatePropagation(),!(k.ctrlKey||[1,2].includes(k.button))&&(u.isDragging=!0,u[c.value.axis]=k.currentTarget[c.value.offset]-(k[c.value.client]-k.currentTarget.getBoundingClientRect()[c.value.direction]),t("start-move"),g())},_=()=>{u.isDragging=!1,u[c.value.axis]=0,t("stop-move"),y()},w=k=>{const{isDragging:T}=u;if(!T||!i.value||!o.value)return;const M=u[c.value.axis];if(!M)return;ti(l);const E=(o.value.getBoundingClientRect()[c.value.direction]-k[c.value.client])*-1-(i.value[c.value.offset]-M);l=Wr(()=>{u.traveled=Math.max(0,Math.min(E,m.value)),t("scroll",E,m.value)})},S=k=>{const T=Math.abs(k.target.getBoundingClientRect()[c.value.direction]-k[c.value.client])-i.value[c.value.offset]/2;u.traveled=Math.max(0,Math.min(T,m.value)),t("scroll",T,m.value)};return ye(()=>e.scrollFrom,k=>{u.isDragging||(u.traveled=Math.ceil(k*m.value))}),rn(()=>{y()}),()=>ot("div",{role:"presentation",ref:o,class:[a.b(),e.class,(e.alwaysOn||u.isDragging)&&"always-on"],style:v.value,onMousedown:at(S,["stop","prevent"]),onTouchstartPrevent:b},ot("div",{ref:i,class:r.e("thumb"),style:h.value,onMousedown:b},[]))}}),nP=({name:e,getOffset:t,getItemSize:n,getItemOffset:a,getEstimatedTotalSize:r,getStartIndexForOffset:o,getStopIndexForStartIndex:i,initCache:l,clearCache:s,validateProps:u})=>q({name:e??"ElVirtualList",props:XL,emits:[ng,ag,ny],setup(c,{emit:d,expose:v}){u(c);const f=Et(),h=ke("vl"),m=F(l(c,f)),g=eP(),y=F(),b=F(),_=F(),w=F({isScrolling:!1,scrollDir:rg,scrollOffset:Je(c.initScrollOffset)?c.initScrollOffset:0,updateRequested:!1,isScrollbarDragging:!1}),S=D(()=>{const{total:j,cache:oe}=c,{isScrolling:se,scrollDir:fe,scrollOffset:ge}=p(w);if(j===0)return[0,0,0,0];const Se=o(c,ge,p(m)),De=i(c,Se,ge,p(m)),Te=!se||fe==="backward"?Math.max(1,oe):1,Re=!se||fe==="forward"?Math.max(1,oe):1;return[Math.max(0,Se-Te),Math.max(0,Math.min(j-1,De+Re)),Se,De]}),k=D(()=>r(c,p(m))),T=D(()=>yh(c.layout)),M=D(()=>[{position:"relative",[`overflow-${T.value?"x":"y"}`]:"scroll",WebkitOverflowScrolling:"touch",willChange:"transform"},{direction:c.direction,height:Je(c.height)?`${c.height}px`:c.height,width:Je(c.width)?`${c.width}px`:c.width},c.style]),E=D(()=>{const j=p(k),oe=p(T),se=c.innerWidth;return{height:oe?"100%":`${j}px`,pointerEvents:p(w).isScrolling?"none":void 0,width:oe?`${j}px`:se!==void 0?Je(se)?`${se}px`:se:"100%",margin:0,boxSizing:"border-box"}}),A=D(()=>T.value?c.width:c.height),I=D(()=>Math.max(0,k.value-A.value)),P=j=>I1(j,0,I.value),R=1,G=j=>({start:!Vi(j,0,R),end:!Vi(I.value,j,R)}),K=D(()=>P(w.value.scrollOffset)),H=D(()=>G(K.value));let z=H.value;const{onWheel:B}=tP({atStartEdge:D(()=>H.value.start),atEndEdge:D(()=>H.value.end),layout:D(()=>c.layout)},j=>{var oe,se;(se=(oe=_.value).onMouseUp)==null||se.call(oe),ie(Math.min(w.value.scrollOffset+j,I.value))});on(y,"wheel",B,{passive:!1});const W=()=>{const{total:j}=c;if(j>0){const[ge,Se,De,Te]=p(S);d(ng,ge,Se,De,Te)}const{scrollDir:oe,scrollOffset:se,updateRequested:fe}=p(w);d(ag,oe,se,fe)},N=(j,oe)=>{const se=G(oe),fe=c.direction==="rtl"?"left":"right",ge=c.direction==="rtl"?"right":"left";j==="forward"&&se.end&&!z.end&&d(ny,T.value?fe:"bottom"),j==="backward"&&se.start&&!z.start&&d(ny,T.value?ge:"top"),z=se},V=(j,{isScrolling:oe,updateRequested:se})=>{const fe=p(w),ge=Math.max(j,0);if(ge===fe.scrollOffset)return;const Se=lv(fe.scrollOffset,ge);w.value={...fe,isScrolling:oe,scrollDir:Se,scrollOffset:ge,updateRequested:se},N(Se,P(ge)),He(he)},Y=j=>{const{clientHeight:oe,scrollHeight:se,scrollTop:fe}=j.currentTarget;p(w).scrollOffset!==fe&&V(Math.min(fe,se-oe),{isScrolling:!0,updateRequested:!1})},X=j=>{const{clientWidth:oe,scrollLeft:se,scrollWidth:fe}=j.currentTarget;if(p(w).scrollOffset===se)return;const{direction:ge}=c;let Se=se;if(ge==="rtl")switch(ig()){case gh:Se=-se;break;case sw:Se=fe-oe-se;break}V(Math.min(Se,fe-oe),{isScrolling:!0,updateRequested:!1})},ee=j=>{p(T)?X(j):Y(j),W()},re=(j,oe)=>{const se=I.value/oe*j;ie(Math.min(I.value,se))},ie=j=>{V(j,{isScrolling:p(w).isScrolling,updateRequested:!0})},le=(j,oe=Io)=>{const{scrollOffset:se}=p(w);j=Math.max(0,Math.min(j,c.total-1)),ie(t(c,j,oe,se,p(m)))},ve=j=>{const{direction:oe,itemSize:se,layout:fe}=c,ge=g.value(s&&se,s&&fe,s&&oe);let Se;if(en(ge,String(j)))Se=ge[j];else{const De=a(c,j,p(m)),Te=n(c,j,p(m)),Re=p(T),Ce=oe==="rtl",Le=Re?De:0;ge[j]=Se={position:"absolute",left:Ce?void 0:`${Le}px`,right:Ce?`${Le}px`:void 0,top:Re?0:`${De}px`,height:Re?"100%":`${Te}px`,width:Re?`${Te}px`:"100%"}}return Se},he=()=>{w.value.isScrolling=!1,He(()=>{g.value(-1,null,null)})},J=()=>{const j=y.value;j&&(j.scrollTop=0)};mt(()=>{if(!qt)return;const{initScrollOffset:j}=c,oe=p(y);Je(j)&&oe&&(p(T)?oe.scrollLeft=j:oe.scrollTop=j),W()}),oi(()=>{const{direction:j,layout:oe}=c,{scrollOffset:se,updateRequested:fe}=p(w),ge=p(y);if(fe&&ge)if(oe==="horizontal")if(j==="rtl")switch(ig()){case gh:ge.scrollLeft=-se;break;case lw:ge.scrollLeft=se;break;default:{const{clientWidth:Se,scrollWidth:De}=ge;ge.scrollLeft=De-Se-se;break}}else ge.scrollLeft=se;else ge.scrollTop=se}),Wh(()=>{p(y).scrollTop=p(w).scrollOffset}),ye(I,()=>{z=H.value});const Z={ns:h,clientSize:A,estimatedTotalSize:k,windowStyle:M,windowRef:y,innerRef:b,innerStyle:E,itemsToRender:S,scrollbarRef:_,states:w,getItemStyle:ve,onScroll:ee,onScrollbarScroll:re,onWheel:B,scrollTo:ie,scrollToItem:le,resetScrollTop:J};return v({windowRef:y,innerRef:b,getItemStyleCache:g,scrollTo:ie,scrollToItem:le,resetScrollTop:J,states:w}),Z},render(c){var V;const{$slots:d,className:v,clientSize:f,containerElement:h,data:m,getItemStyle:g,innerElement:y,itemsToRender:b,innerStyle:_,layout:w,scrollbarAlwaysOn:S,total:k,onScroll:T,onScrollbarScroll:M,states:E,useIsScrolling:A,windowStyle:I,ns:P}=c,[R,G]=b,K=St(h),H=St(y),z=[];if(k>0)for(let Y=R;Y<=G;Y++)z.push(ot(Ve,{key:Y},(V=d.default)==null?void 0:V.call(d,{data:m,index:Y,isScrolling:A?E.isScrolling:void 0,style:g(Y)})));const B=[ot(H,Tt(c.innerProps,{style:_,ref:"innerRef"}),Ye(H)?z:{default:()=>z})],W=ot(K4,{ref:"scrollbarRef",clientSize:f,layout:w,onScroll:M,ratio:f*100/this.estimatedTotalSize,scrollFrom:E.scrollOffset/(this.estimatedTotalSize-f),total:k,alwaysOn:S}),N=ot(K,{class:[P.e("window"),v],style:I,onScroll:T,ref:"windowRef",key:0},Ye(K)?[B]:{default:()=>[B]});return ot("div",{key:0,class:[P.e("wrapper"),S?"always-on":""]},[N,W])}}),cp=nP({name:"ElFixedSizeList",getItemOffset:({itemSize:e},t)=>t*e,getItemSize:({itemSize:e})=>e,getEstimatedTotalSize:({total:e,itemSize:t})=>t*e,getOffset:({height:e,total:t,itemSize:n,layout:a,width:r},o,i,l)=>{const s=yh(a)?r:e;Ye(s)&&Dn("[ElVirtualList]",` + You should set + width/height + to number when your layout is + horizontal/vertical + `);const u=Math.max(0,t*n-s),c=Math.min(u,o*n),d=Math.max(0,(o+1)*n-s);switch(i==="smart"&&(l>=d-s&&l<=c+s?i=Io:i=Pi),i){case mh:return c;case"end":return d;case Pi:{const v=Math.round(d+(c-d)/2);return vu+Math.floor(s/2)?u:v}case Io:default:return l>=d&&l<=c?l:lMath.max(0,Math.min(e-1,Math.floor(n/t))),getStopIndexForStartIndex:({height:e,total:t,itemSize:n,layout:a,width:r},o,i)=>{const l=o*n,s=yh(a)?r:e,u=Math.ceil((s+i-l)/n);return Math.max(0,Math.min(t-1,o+u-1))},initCache(){},clearCache:!0,validateProps(){}}),kde="ElDynamicSizeList",id=(e,t,n)=>{const{itemSize:a}=e,{items:r,lastVisitedIndex:o}=n;if(t>o){let i=0;if(o>=0){const l=r[o];i=l.offset+l.size}for(let l=o+1;l<=t;l++){const s=a(l);r[l]={offset:i,size:s},i+=s}n.lastVisitedIndex=t}return r[t]},Tde=(e,t,n)=>{const{items:a,lastVisitedIndex:r}=t;return(r>0?a[r].offset:0)>=n?aP(e,t,0,r,n):Mde(e,t,Math.max(0,r),n)},aP=(e,t,n,a,r)=>{for(;n<=a;){const o=n+Math.floor((a-n)/2),i=id(e,o,t).offset;if(i===r)return o;ir&&(a=o-1)}return Math.max(0,n-1)},Mde=(e,t,n,a)=>{const{total:r}=e;let o=1;for(;n{let r=0;if(a>=e&&(a=e-1),a>=0){const i=t[a];r=i.offset+i.size}const o=(e-a-1)*n;return r+o},Ede=nP({name:"ElDynamicSizeList",getItemOffset:(e,t,n)=>id(e,t,n).offset,getItemSize:(e,t,{items:n})=>n[t].size,getEstimatedTotalSize:sx,getOffset:(e,t,n,a,r)=>{const{height:o,layout:i,width:l}=e,s=yh(i)?l:o,u=id(e,t,r),c=sx(e,r),d=Math.max(0,Math.min(c-s,u.offset)),v=Math.max(0,u.offset-s+u.size);switch(n==="smart"&&(a>=v-s&&a<=d+s?n=Io:n=Pi),n){case mh:return d;case"end":return v;case Pi:return Math.round(v+(d-v)/2);case Io:default:return a>=v&&a<=d?a:aTde(e,n,t),getStopIndexForStartIndex:(e,t,n,a)=>{const{height:r,total:o,layout:i,width:l}=e,s=yh(i)?l:r,u=id(e,t,a),c=n+s;let d=u.offset+u.size,v=t;for(;v{var o,i;n.lastVisitedIndex=Math.min(n.lastVisitedIndex,a-1),(o=t.exposed)==null||o.getItemStyleCache(-1),r&&((i=t.proxy)==null||i.$forceUpdate())},n},clearCache:!1,validateProps:({itemSize:e})=>{typeof e!="function"&&Dn(kde,` + itemSize is required as function, but the given value was ${typeof e} + `)}}),Ade=({atXEndEdge:e,atXStartEdge:t,atYEndEdge:n,atYStartEdge:a},r)=>{let o=null,i=0,l=0;const s=(c,d)=>{const v=c<0&&t.value||c>0&&e.value,f=d<0&&a.value||d>0&&n.value;return v||f};return{hasReachedEdge:s,onWheel:c=>{ti(o);let d=c.deltaX,v=c.deltaY;if(Math.abs(d)>Math.abs(v)?v=0:d=0,c.shiftKey&&v!==0&&(d=v,v=0),s(d,v)){c.deltaX!==0&&d===0&&c.preventDefault();return}i+=d,l+=v,c.preventDefault(),o=Wr(()=>{r(i,l),i=0,l=0})}}},Ide=(e,t,n,a,r,o,i)=>{const l=F(0),s=F(0);let u,c=0,d=0;const v=h=>{ti(u),l.value=h.touches[0].clientX,s.value=h.touches[0].clientY,c=0,d=0},f=h=>{h.preventDefault(),ti(u),c+=l.value-h.touches[0].clientX,d+=s.value-h.touches[0].clientY,l.value=h.touches[0].clientX,s.value=h.touches[0].clientY,u=Wr(()=>{const m=a.value-p(o),g=r.value-p(i);n({scrollLeft:Math.min(t.value.scrollLeft+c,m),scrollTop:Math.min(t.value.scrollTop+d,g)}),c=0,d=0})};return on(e,"touchstart",v,{passive:!0}),on(e,"touchmove",f,{passive:!1}),{touchStartX:l,touchStartY:s,handleTouchStart:v,handleTouchMove:f}},rP=({name:e,clearCache:t,getColumnPosition:n,getColumnStartIndexForOffset:a,getColumnStopIndexForStartIndex:r,getEstimatedTotalHeight:o,getEstimatedTotalWidth:i,getColumnOffset:l,getRowOffset:s,getRowPosition:u,getRowStartIndexForOffset:c,getRowStopIndexForStartIndex:d,initCache:v,injectToInstance:f,validateProps:h})=>q({name:e??"ElVirtualList",props:Xu,emits:[ng,ag],setup(m,{emit:g,expose:y,slots:b}){const _=ke("vl");h(m);const w=Et(),S=F(v(m,w));f==null||f(w,S);const k=F(),T=F(),M=F(),E=F(),A=F({isScrolling:!1,scrollLeft:Je(m.initScrollLeft)?m.initScrollLeft:0,scrollTop:Je(m.initScrollTop)?m.initScrollTop:0,updateRequested:!1,xAxisScrollDir:rg,yAxisScrollDir:rg}),I=eP(),P=D(()=>Number.parseInt(`${m.height}`,10)),R=D(()=>Number.parseInt(`${m.width}`,10)),G=D(()=>{const{totalColumn:Ce,totalRow:Le,columnCache:me}=m,{isScrolling:Be,xAxisScrollDir:ze,scrollLeft:xe}=p(A);if(Ce===0||Le===0)return[0,0,0,0];const rt=a(m,xe,p(S)),nt=r(m,rt,xe,p(S)),Pt=!Be||ze==="backward"?Math.max(1,me):1,Wt=!Be||ze==="forward"?Math.max(1,me):1;return[Math.max(0,rt-Pt),Math.max(0,Math.min(Ce-1,nt+Wt)),rt,nt]}),K=D(()=>{const{totalColumn:Ce,totalRow:Le,rowCache:me}=m,{isScrolling:Be,yAxisScrollDir:ze,scrollTop:xe}=p(A);if(Ce===0||Le===0)return[0,0,0,0];const rt=c(m,xe,p(S)),nt=d(m,rt,xe,p(S)),Pt=!Be||ze==="backward"?Math.max(1,me):1,Wt=!Be||ze==="forward"?Math.max(1,me):1;return[Math.max(0,rt-Pt),Math.max(0,Math.min(Le-1,nt+Wt)),rt,nt]}),H=D(()=>o(m,p(S))),z=D(()=>i(m,p(S))),B=D(()=>[{position:"relative",overflow:"hidden",WebkitOverflowScrolling:"touch",willChange:"transform"},{direction:m.direction,height:Je(m.height)?`${m.height}px`:m.height,width:Je(m.width)?`${m.width}px`:m.width},m.style??{}]),W=D(()=>{const Ce=`${p(z)}px`;return{height:`${p(H)}px`,pointerEvents:p(A).isScrolling?"none":void 0,width:Ce,margin:0,boxSizing:"border-box"}}),N=()=>{const{totalColumn:Ce,totalRow:Le}=m;if(Ce>0&&Le>0){const[nt,Pt,Wt,Jt]=p(G),[Rt,Qt,_e,Ne]=p(K);g(ng,{columnCacheStart:nt,columnCacheEnd:Pt,rowCacheStart:Rt,rowCacheEnd:Qt,columnVisibleStart:Wt,columnVisibleEnd:Jt,rowVisibleStart:_e,rowVisibleEnd:Ne})}const{scrollLeft:me,scrollTop:Be,updateRequested:ze,xAxisScrollDir:xe,yAxisScrollDir:rt}=p(A);g(ag,{xAxisScrollDir:xe,scrollLeft:me,yAxisScrollDir:rt,scrollTop:Be,updateRequested:ze})},V=Ce=>{const{clientHeight:Le,clientWidth:me,scrollHeight:Be,scrollLeft:ze,scrollTop:xe,scrollWidth:rt}=Ce.currentTarget,nt=p(A);if(nt.scrollTop===xe&&nt.scrollLeft===ze)return;let Pt=ze;if(ix(m.direction))switch(ig()){case gh:Pt=-ze;break;case sw:Pt=rt-me-ze;break}A.value={...nt,isScrolling:!0,scrollLeft:Pt,scrollTop:Math.max(0,Math.min(xe,Be-Le)),updateRequested:!0,xAxisScrollDir:lv(nt.scrollLeft,Pt),yAxisScrollDir:lv(nt.scrollTop,xe)},He(()=>j()),oe(),N()},Y=(Ce,Le)=>{const me=p(P),Be=(H.value-me)/Le*Ce;re({scrollTop:Math.min(H.value-me,Be)})},X=(Ce,Le)=>{const me=p(R),Be=(z.value-me)/Le*Ce;re({scrollLeft:Math.min(z.value-me,Be)})},{onWheel:ee}=Ade({atXStartEdge:D(()=>A.value.scrollLeft<=0),atXEndEdge:D(()=>A.value.scrollLeft>=z.value-p(R)),atYStartEdge:D(()=>A.value.scrollTop<=0),atYEndEdge:D(()=>A.value.scrollTop>=H.value-p(P))},(Ce,Le)=>{var ze,xe,rt,nt;(xe=(ze=T.value)==null?void 0:ze.onMouseUp)==null||xe.call(ze),(nt=(rt=M.value)==null?void 0:rt.onMouseUp)==null||nt.call(rt);const me=p(R),Be=p(P);re({scrollLeft:Math.min(A.value.scrollLeft+Ce,z.value-me),scrollTop:Math.min(A.value.scrollTop+Le,H.value-Be)})});on(k,"wheel",ee,{passive:!1});const re=({scrollLeft:Ce=A.value.scrollLeft,scrollTop:Le=A.value.scrollTop})=>{Ce=Math.max(Ce,0),Le=Math.max(Le,0);const me=p(A);Le===me.scrollTop&&Ce===me.scrollLeft||(A.value={...me,xAxisScrollDir:lv(me.scrollLeft,Ce),yAxisScrollDir:lv(me.scrollTop,Le),scrollLeft:Ce,scrollTop:Le,updateRequested:!0},He(()=>j()),oe(),N())},{touchStartX:ie,touchStartY:le,handleTouchStart:ve,handleTouchMove:he}=Ide(k,A,re,z,H,R,P),J=(Ce=0,Le=0,me=Io)=>{const Be=p(A);Le=Math.max(0,Math.min(Le,m.totalColumn-1)),Ce=Math.max(0,Math.min(Ce,m.totalRow-1));const ze=FA(_.namespace.value),xe=p(S),rt=o(m,xe),nt=i(m,xe);re({scrollLeft:l(m,Le,me,Be.scrollLeft,xe,nt>m.width?ze:0),scrollTop:s(m,Ce,me,Be.scrollTop,xe,rt>m.height?ze:0)})},Z=(Ce,Le)=>{const{columnWidth:me,direction:Be,rowHeight:ze}=m,xe=I.value(t&&me,t&&ze,t&&Be),rt=`${Ce},${Le}`;if(en(xe,rt))return xe[rt];{const[,nt]=n(m,Le,p(S)),Pt=p(S),Wt=ix(Be),[Jt,Rt]=u(m,Ce,Pt),[Qt]=n(m,Le,Pt);return xe[rt]={position:"absolute",left:Wt?void 0:`${nt}px`,right:Wt?`${nt}px`:void 0,top:`${Rt}px`,height:`${Jt}px`,width:`${Qt}px`},xe[rt]}},j=()=>{A.value.isScrolling=!1,He(()=>{I.value(-1,null,null)})};mt(()=>{if(!qt)return;const{initScrollLeft:Ce,initScrollTop:Le}=m,me=p(k);me&&(Je(Ce)&&(me.scrollLeft=Ce),Je(Le)&&(me.scrollTop=Le)),N()});const oe=()=>{const{direction:Ce}=m,{scrollLeft:Le,scrollTop:me,updateRequested:Be}=p(A),ze=p(k);if(Be&&ze){if(Ce==="rtl")switch(ig()){case gh:ze.scrollLeft=-Le;break;case lw:ze.scrollLeft=Le;break;default:{const{clientWidth:xe,scrollWidth:rt}=ze;ze.scrollLeft=rt-xe-Le;break}}else ze.scrollLeft=Math.max(0,Le);ze.scrollTop=Math.max(0,me)}},{resetAfterColumnIndex:se,resetAfterRowIndex:fe,resetAfter:ge}=w.proxy;y({windowRef:k,innerRef:E,getItemStyleCache:I,touchStartX:ie,touchStartY:le,handleTouchStart:ve,handleTouchMove:he,scrollTo:re,scrollToItem:J,states:A,resetAfterColumnIndex:se,resetAfterRowIndex:fe,resetAfter:ge});const Se=()=>{const{scrollbarAlwaysOn:Ce,scrollbarStartGap:Le,scrollbarEndGap:me,totalColumn:Be,totalRow:ze}=m,xe=p(R),rt=p(P),nt=p(z),Pt=p(H),{scrollLeft:Wt,scrollTop:Jt}=p(A);return{horizontalScrollbar:ot(K4,{ref:T,alwaysOn:Ce,startGap:Le,endGap:me,class:_.e("horizontal"),clientSize:xe,layout:"horizontal",onScroll:X,ratio:xe*100/nt,scrollFrom:Wt/(nt-xe),total:ze,visible:!0}),verticalScrollbar:ot(K4,{ref:M,alwaysOn:Ce,startGap:Le,endGap:me,class:_.e("vertical"),clientSize:rt,layout:"vertical",onScroll:Y,ratio:rt*100/Pt,scrollFrom:Jt/(Pt-rt),total:Be,visible:!0})}},De=()=>{var Jt;const[Ce,Le]=p(G),[me,Be]=p(K),{data:ze,totalColumn:xe,totalRow:rt,useIsScrolling:nt,itemKey:Pt}=m,Wt=[];if(rt>0&&xe>0)for(let Rt=me;Rt<=Be;Rt++)for(let Qt=Ce;Qt<=Le;Qt++){const _e=Pt({columnIndex:Qt,data:ze,rowIndex:Rt});Wt.push(ot(Ve,{key:_e},(Jt=b.default)==null?void 0:Jt.call(b,{columnIndex:Qt,data:ze,isScrolling:nt?p(A).isScrolling:void 0,style:Z(Rt,Qt),rowIndex:Rt})))}return Wt},Te=()=>{const Ce=St(m.innerElement),Le=De();return[ot(Ce,Tt(m.innerProps,{style:p(W),ref:E}),Ye(Ce)?Le:{default:()=>Le})]};return()=>{const Ce=St(m.containerElement),{horizontalScrollbar:Le,verticalScrollbar:me}=Se(),Be=Te();return ot("div",{key:0,class:_.e("wrapper"),role:m.role},[ot(Ce,{class:m.className,style:p(B),onScroll:V,ref:k},Ye(Ce)?Be:{default:()=>Be}),Le,me])}}}),ux="ElFixedSizeGrid",Lde=rP({name:"ElFixedSizeGrid",getColumnPosition:({columnWidth:e},t)=>[e,t*e],getRowPosition:({rowHeight:e},t)=>[e,t*e],getEstimatedTotalHeight:({totalRow:e,rowHeight:t})=>t*e,getEstimatedTotalWidth:({totalColumn:e,columnWidth:t})=>t*e,getColumnOffset:({totalColumn:e,columnWidth:t,width:n},a,r,o,i,l)=>{n=Number(n);const s=Math.max(0,e*t-n),u=Math.min(s,a*t),c=Math.max(0,a*t-n+l+t);switch(r==="smart"&&(o>=c-n&&o<=u+n?r=Io:r=Pi),r){case mh:return u;case"end":return c;case Pi:{const d=Math.round(c+(u-c)/2);return ds+Math.floor(n/2)?s:d}case Io:default:return o>=c&&o<=u?o:c>u||o{t=Number(t);const s=Math.max(0,n*e-t),u=Math.min(s,a*e),c=Math.max(0,a*e-t+l+e);switch(r==="smart"&&(o>=c-t&&o<=u+t?r=Io:r=Pi),r){case mh:return u;case"end":return c;case Pi:{const d=Math.round(c+(u-c)/2);return ds+Math.floor(t/2)?s:d}case Io:default:return o>=c&&o<=u?o:c>u||oMath.max(0,Math.min(t-1,Math.floor(n/e))),getColumnStopIndexForStartIndex:({columnWidth:e,totalColumn:t,width:n},a,r)=>{const o=a*e,i=Math.ceil((n+r-o)/e);return Math.max(0,Math.min(t-1,a+i-1))},getRowStartIndexForOffset:({rowHeight:e,totalRow:t},n)=>Math.max(0,Math.min(t-1,Math.floor(n/e))),getRowStopIndexForStartIndex:({rowHeight:e,totalRow:t,height:n},a,r)=>{const o=a*e,i=Math.ceil((n+r-o)/e);return Math.max(0,Math.min(t-1,a+i-1))},initCache:()=>{},clearCache:!0,validateProps:({columnWidth:e,rowHeight:t})=>{Je(e)||Dn(ux,` + "columnWidth" must be passed as number, + instead ${typeof e} was given. + `),Je(t)||Dn(ux,` + "columnWidth" must be passed as number, + instead ${typeof t} was given. + `)}}),{max:lg,min:oP,floor:iP}=Math,cx="ElDynamicSizeGrid",Pde={column:"columnWidth",row:"rowHeight"},U4={column:"lastVisitedColumnIndex",row:"lastVisitedRowIndex"},vl=(e,t,n,a)=>{const[r,o,i]=[n[a],e[Pde[a]],n[U4[a]]];if(t>i){let l=0;if(i>=0){const s=r[i];l=s.offset+s.size}for(let s=i+1;s<=t;s++){const u=o(s);r[s]={offset:l,size:u},l+=u}n[U4[a]]=t}return r[t]},lP=(e,t,n,a,r,o)=>{for(;n<=a;){const i=n+iP((a-n)/2),l=vl(e,i,t,o).offset;if(l===r)return i;l{const o=r==="column"?e.totalColumn:e.totalRow;let i=1;for(;n{const[r,o]=[t[a],t[U4[a]]];return(o>0?r[o].offset:0)>=n?lP(e,t,0,o,n,a):Dde(e,t,lg(0,o),n,a)},sP=({totalRow:e},{estimatedRowHeight:t,lastVisitedRowIndex:n,row:a})=>{let r=0;if(n>=e&&(n=e-1),n>=0){const i=a[n];r=i.offset+i.size}const o=(e-n-1)*t;return r+o},uP=({totalColumn:e},{column:t,estimatedColumnWidth:n,lastVisitedColumnIndex:a})=>{let r=0;if(a>e&&(a=e-1),a>=0){const i=t[a];r=i.offset+i.size}const o=(e-a-1)*n;return r+o},Ode={column:uP,row:sP},fx=(e,t,n,a,r,o,i)=>{const[l,s]=[o==="row"?e.height:e.width,Ode[o]],u=vl(e,t,r,o),c=lg(0,oP(s(e,r)-l,u.offset)),d=lg(0,u.offset-l+i+u.size);switch(n==="smart"&&(a>=d-l&&a<=c+l?n=Io:n=Pi),n){case mh:return c;case"end":return d;case Pi:return Math.round(d+(c-d)/2);case Io:default:return a>=d&&a<=c?a:d>c||a{const a=vl(e,t,n,"column");return[a.size,a.offset]},getRowPosition:(e,t,n)=>{const a=vl(e,t,n,"row");return[a.size,a.offset]},getColumnOffset:(e,t,n,a,r,o)=>fx(e,t,n,a,r,"column",o),getRowOffset:(e,t,n,a,r,o)=>fx(e,t,n,a,r,"row",o),getColumnStartIndexForOffset:(e,t,n)=>dx(e,n,t,"column"),getColumnStopIndexForStartIndex:(e,t,n,a)=>{const r=vl(e,t,a,"column"),o=n+e.width;let i=r.offset+r.size,l=t;for(;ldx(e,n,t,"row"),getRowStopIndexForStartIndex:(e,t,n,a)=>{const{totalRow:r,height:o}=e,i=vl(e,t,a,"row"),l=n+o;let s=i.size+i.offset,u=t;for(;u{const n=({columnIndex:o,rowIndex:i},l)=>{var s,u;l=zt(l)?!0:l,Je(o)&&(t.value.lastVisitedColumnIndex=Math.min(t.value.lastVisitedColumnIndex,o-1)),Je(i)&&(t.value.lastVisitedRowIndex=Math.min(t.value.lastVisitedRowIndex,i-1)),(s=e.exposed)==null||s.getItemStyleCache.value(-1,null,null),l&&((u=e.proxy)==null||u.$forceUpdate())},a=(o,i)=>{n({columnIndex:o},i)},r=(o,i)=>{n({rowIndex:o},i)};Object.assign(e.proxy,{resetAfterColumnIndex:a,resetAfterRowIndex:r,resetAfter:n})},initCache:({estimatedColumnWidth:e=50,estimatedRowHeight:t=50})=>({column:{},estimatedColumnWidth:e,estimatedRowHeight:t,lastVisitedColumnIndex:-1,lastVisitedRowIndex:-1,row:{}}),clearCache:!1,validateProps:({columnWidth:e,rowHeight:t})=>{Qe(e)||Dn(cx,` + "columnWidth" must be passed as function, + instead ${typeof e} was given. + `),Qe(t)||Dn(cx,` + "rowHeight" must be passed as function, + instead ${typeof t} was given. + `)}}),cP={modelValue:{type:[Number,String,Boolean],default:void 0},label:{type:[String,Boolean,Number,Object],default:void 0},value:{type:[String,Boolean,Number,Object],default:void 0},indeterminate:Boolean,disabled:{type:Boolean,default:void 0},checked:Boolean,name:{type:String,default:void 0},trueValue:{type:[String,Number],default:void 0},falseValue:{type:[String,Number],default:void 0},trueLabel:{type:[String,Number],default:void 0},falseLabel:{type:[String,Number],default:void 0},id:{type:String,default:void 0},border:Boolean,size:pa,tabindex:[String,Number],validateEvent:{type:Boolean,default:!0},ariaLabel:String,...dr(["ariaControls"])},dP={[bt]:e=>Ye(e)||Je(e)||vn(e),change:e=>Ye(e)||Je(e)||vn(e)},mf=Symbol("checkboxGroupContextKey"),Rde=$e({modelValue:{type:ae(Array),default:()=>[]},disabled:{type:Boolean,default:void 0},min:Number,max:Number,size:pa,fill:String,textColor:String,tag:{type:String,default:"div"},validateEvent:{type:Boolean,default:!0},options:{type:ae(Array)},props:{type:ae(Object),default:()=>fP},type:{type:String,values:["checkbox","button"],default:"checkbox"},...dr(["ariaLabel"])}),Nde={[bt]:e=>Ae(e),change:e=>Ae(e)},fP={label:"label",value:"value",disabled:"disabled"},Bde=({model:e,isChecked:t})=>{const n=Ze(mf,void 0),a=Ze(Ec,void 0),r=D(()=>{var l,s;const o=(l=n==null?void 0:n.max)==null?void 0:l.value,i=(s=n==null?void 0:n.min)==null?void 0:s.value;return!zt(o)&&e.value.length>=o&&!t.value||!zt(i)&&e.value.length<=i&&t.value});return{isDisabled:$n(D(()=>{var o;return n===void 0?(a==null?void 0:a.disabled)??r.value:((o=n.disabled)==null?void 0:o.value)||r.value})),isLimitDisabled:r}},Vde=(e,{model:t,isLimitExceeded:n,hasOwnLabel:a,isDisabled:r,isLabeledByFormItem:o})=>{const i=Ze(mf,void 0),{formItem:l}=Ea(),{emit:s}=Et();function u(h){return[!0,e.trueValue,e.trueLabel].includes(h)?e.trueValue??e.trueLabel??!0:e.falseValue??e.falseLabel??!1}function c(h,m){s(Ot,u(h),m)}function d(h){if(n.value)return;const m=h.target;s(Ot,u(m.checked),h)}async function v(h){n.value||!a.value&&!r.value&&o.value&&(h.composedPath().some(m=>m.tagName==="LABEL")||(t.value=u([!1,e.falseValue,e.falseLabel].includes(t.value)),await He(),c(t.value,h)))}const f=D(()=>(i==null?void 0:i.validateEvent)||e.validateEvent);return ye(()=>e.modelValue,()=>{f.value&&(l==null||l.validate("change").catch(kt))}),{handleChange:d,onClickRoot:v}},zde=e=>{const t=F(!1),{emit:n,vnode:a}=Et(),r=Ze(mf,void 0),o=D(()=>zt(r)===!1),i=F(!1),l=D(()=>{const u=a.props??{};return"modelValue"in u||"model-value"in u}),s=D({get(){var u;return o.value?(u=r==null?void 0:r.modelValue)==null?void 0:u.value:l.value?e.modelValue:t.value},set(u){var c,d;o.value&&Ae(u)?(i.value=((c=r==null?void 0:r.max)==null?void 0:c.value)!==void 0&&u.length>(r==null?void 0:r.max.value)&&u.length>s.value.length,i.value===!1&&((d=r==null?void 0:r.changeEvent)==null||d.call(r,u))):(n(bt,u),t.value=u)}});return{model:s,isGroup:o,isLimitExceeded:i}},Fde=(e,t,{model:n})=>{const a=Ze(mf,void 0),r=F(!1),o=D(()=>Qr(e.value)?e.label:e.value),i=D(()=>{const l=n.value;return vn(l)?l:Ae(l)?_t(o.value)?l.map(wn).some(s=>Rn(s,o.value)):l.map(wn).includes(o.value):l!=null?l===e.trueValue||l===e.trueLabel:!!l});return{checkboxButtonSize:ca(D(()=>{var l;return(l=a==null?void 0:a.size)==null?void 0:l.value}),{prop:!0}),isChecked:i,isFocused:r,checkboxSize:ca(D(()=>{var l;return(l=a==null?void 0:a.size)==null?void 0:l.value})),hasOwnLabel:D(()=>!!t.default||!Qr(o.value)),actualValue:o}},vP=(e,t)=>{const{formItem:n}=Ea(),{model:a,isGroup:r,isLimitExceeded:o}=zde(e),{isFocused:i,isChecked:l,checkboxButtonSize:s,checkboxSize:u,hasOwnLabel:c,actualValue:d}=Fde(e,t,{model:a}),{isDisabled:v}=Bde({model:a,isChecked:l}),{inputId:f,isLabeledByFormItem:h}=Yr(e,{formItemContext:n,disableIdGeneration:c,disableIdManagement:r}),{handleChange:m,onClickRoot:g}=Vde(e,{model:a,isLimitExceeded:o,hasOwnLabel:c,isDisabled:v,isLabeledByFormItem:h});return(()=>{function b(){Ae(a.value)&&!a.value.includes(d.value)?a.value.push(d.value):a.value=e.trueValue??e.trueLabel??!0}e.checked&&b()})(),Ri({from:"label act as value",replacement:"value",version:"3.0.0",scope:"el-checkbox",ref:"https://element-plus.org/en-US/component/checkbox.html"},D(()=>r.value&&Qr(e.value))),Ri({from:"true-label",replacement:"true-value",version:"3.0.0",scope:"el-checkbox",ref:"https://element-plus.org/en-US/component/checkbox.html"},D(()=>!!e.trueLabel)),Ri({from:"false-label",replacement:"false-value",version:"3.0.0",scope:"el-checkbox",ref:"https://element-plus.org/en-US/component/checkbox.html"},D(()=>!!e.falseLabel)),{inputId:f,isLabeledByFormItem:h,isChecked:l,isDisabled:v,isFocused:i,checkboxButtonSize:s,checkboxSize:u,hasOwnLabel:c,model:a,actualValue:d,handleChange:m,onClickRoot:g}},Hde=["id","indeterminate","name","tabindex","disabled"];var Wde=q({name:"ElCheckbox",__name:"checkbox",props:cP,emits:dP,setup(e){const t=e,{inputId:n,isLabeledByFormItem:a,isChecked:r,isDisabled:o,isFocused:i,checkboxSize:l,hasOwnLabel:s,model:u,actualValue:c,handleChange:d,onClickRoot:v}=vP(t,Xn()),f=D(()=>t.trueValue||t.falseValue||t.trueLabel||t.falseLabel?{"true-value":t.trueValue??t.trueLabel??!0,"false-value":t.falseValue??t.falseLabel??!1}:{value:c.value}),h=ke("checkbox"),m=D(()=>[h.b(),h.m(l.value),h.is("disabled",o.value),h.is("bordered",t.border),h.is("checked",r.value)]),g=D(()=>[h.e("input"),h.is("disabled",o.value),h.is("checked",r.value),h.is("indeterminate",t.indeterminate),h.is("focus",i.value)]);return(y,b)=>(x(),ne(St(!p(s)&&p(a)?"span":"label"),{for:!p(s)&&p(a)?null:p(n),class:U(m.value),"aria-controls":e.indeterminate?e.ariaControls:null,"aria-checked":e.indeterminate?"mixed":void 0,"aria-label":e.ariaLabel,onClick:p(v)},{default:$(()=>[C("span",{class:U(g.value)},[st(C("input",Tt({id:p(n),"onUpdate:modelValue":b[0]||(b[0]=_=>xn(u)?u.value=_:null),class:p(h).e("original"),type:"checkbox",indeterminate:e.indeterminate,name:e.name,tabindex:e.tabindex,disabled:p(o)},f.value,{onChange:b[1]||(b[1]=(..._)=>p(d)&&p(d)(..._)),onFocus:b[2]||(b[2]=_=>i.value=!0),onBlur:b[3]||(b[3]=_=>i.value=!1),onClick:b[4]||(b[4]=at(()=>{},["stop"]))}),null,16,Hde),[[O7,p(u)]]),C("span",{class:U(p(h).e("inner"))},null,2)],2),p(s)?(x(),O("span",{key:0,class:U(p(h).e("label"))},[ce(y.$slots,"default"),y.$slots.default?te("v-if",!0):(x(),O(Ve,{key:0},[ue(Q(e.label),1)],64))],2)):te("v-if",!0)]),_:3},8,["for","class","aria-controls","aria-checked","aria-label","onClick"]))}}),hP=Wde;const Kde=["name","tabindex","disabled"];var Ude=q({name:"ElCheckboxButton",__name:"checkbox-button",props:cP,emits:dP,setup(e){const t=e,{isFocused:n,isChecked:a,isDisabled:r,checkboxButtonSize:o,model:i,actualValue:l,handleChange:s}=vP(t,Xn()),u=D(()=>t.trueValue||t.falseValue||t.trueLabel||t.falseLabel?{"true-value":t.trueValue??t.trueLabel??!0,"false-value":t.falseValue??t.falseLabel??!1}:{value:l.value}),c=Ze(mf,void 0),d=ke("checkbox"),v=D(()=>{var m,g;const h=((m=c==null?void 0:c.fill)==null?void 0:m.value)??"";return{backgroundColor:h,borderColor:h,color:((g=c==null?void 0:c.textColor)==null?void 0:g.value)??"",boxShadow:h?`-1px 0 0 0 ${h}`:void 0}}),f=D(()=>[d.b("button"),d.bm("button",o.value),d.is("disabled",r.value),d.is("checked",a.value),d.is("focus",n.value)]);return(h,m)=>(x(),O("label",{class:U(f.value)},[st(C("input",Tt({"onUpdate:modelValue":m[0]||(m[0]=g=>xn(i)?i.value=g:null),class:p(d).be("button","original"),type:"checkbox",name:e.name,tabindex:e.tabindex,disabled:p(r)},u.value,{onChange:m[1]||(m[1]=(...g)=>p(s)&&p(s)(...g)),onFocus:m[2]||(m[2]=g=>n.value=!0),onBlur:m[3]||(m[3]=g=>n.value=!1),onClick:m[4]||(m[4]=at(()=>{},["stop"]))}),null,16,Kde),[[O7,p(i)]]),h.$slots.default||e.label?(x(),O("span",{key:0,class:U(p(d).be("button","inner")),style:et(p(a)?v.value:void 0)},[ce(h.$slots,"default",{},()=>[ue(Q(e.label),1)])],6)):te("v-if",!0)],2))}}),uw=Ude,Gde=q({name:"ElCheckboxGroup",__name:"checkbox-group",props:Rde,emits:Nde,setup(e,{emit:t}){const n=e,a=t,r=ke("checkbox"),o=$n(),{formItem:i}=Ea(),{inputId:l,isLabeledByFormItem:s}=Yr(n,{formItemContext:i}),u=async h=>{a(bt,h),await He(),a(Ot,h)},c=D({get(){return n.modelValue},set(h){u(h)}}),d=D(()=>({...fP,...n.props})),v=h=>{const{label:m,value:g,disabled:y}=d.value,b={label:h[m],value:h[g],disabled:h[y]};return{...Tc(h,[m,g,y]),...b}},f=D(()=>n.type==="button"?uw:hP);return Ft(mf,{...ji(Ta(n),["size","min","max","validateEvent","fill","textColor"]),disabled:o,modelValue:c,changeEvent:u}),ye(()=>n.modelValue,(h,m)=>{n.validateEvent&&!Rn(h,m)&&(i==null||i.validate("change").catch(kt))}),(h,m)=>{var g;return x(),ne(St(e.tag),{id:p(l),class:U(p(r).b("group")),role:"group","aria-label":p(s)?void 0:e.ariaLabel||"checkbox-group","aria-labelledby":p(s)?(g=p(i))==null?void 0:g.labelId:void 0},{default:$(()=>[ce(h.$slots,"default",{},()=>[(x(!0),O(Ve,null,dt(e.options,(y,b)=>(x(),ne(St(f.value),Tt({key:b},{ref_for:!0},v(y)),null,16))),128))])]),_:3},8,["id","class","aria-label","aria-labelledby"])}}}),pP=Gde;const Zo=wt(hP,{CheckboxButton:uw,CheckboxGroup:pP}),Yde=On(uw),cw=On(pP),mP=$e({modelValue:{type:[String,Number,Boolean],default:void 0},size:pa,disabled:{type:Boolean,default:void 0},label:{type:[String,Number,Boolean],default:void 0},value:{type:[String,Number,Boolean],default:void 0},name:{type:String,default:void 0}}),jde=$e({...mP,border:Boolean}),gP={[bt]:e=>Ye(e)||Je(e)||vn(e),[Ot]:e=>Ye(e)||Je(e)||vn(e)},yP=Symbol("radioGroupKey"),qde=$e({...mP}),bP={label:"label",value:"value",disabled:"disabled"},Xde=$e({id:{type:String,default:void 0},size:pa,disabled:{type:Boolean,default:void 0},modelValue:{type:[String,Number,Boolean],default:void 0},fill:{type:String,default:""},textColor:{type:String,default:""},name:{type:String,default:void 0},validateEvent:{type:Boolean,default:!0},options:{type:ae(Array)},props:{type:ae(Object),default:()=>bP},type:{type:String,values:["radio","button"],default:"radio"},...dr(["ariaLabel"])}),Zde=gP,_P=(e,t)=>{const n=F(),a=Ze(yP,void 0),r=D(()=>!!a),o=D(()=>Qr(e.value)?e.label:e.value),i=D({get(){return r.value?a.modelValue:e.modelValue},set(d){r.value?a.changeEvent(d):t&&t("update:modelValue",d),n.value.checked=e.modelValue===o.value}}),l=ca(D(()=>a==null?void 0:a.size)),s=$n(D(()=>a==null?void 0:a.disabled)),u=F(!1),c=D(()=>s.value||r.value&&i.value!==o.value?-1:0);return Ri({from:"label act as value",replacement:"value",version:"3.0.0",scope:"el-radio",ref:"https://element-plus.org/en-US/component/radio.html"},D(()=>r.value&&Qr(e.value))),{radioRef:n,isGroup:r,radioGroup:a,focus:u,size:l,disabled:s,tabIndex:c,modelValue:i,actualValue:o}},Jde=["value","name","disabled","checked"];var Qde=q({name:"ElRadio",__name:"radio",props:jde,emits:gP,setup(e,{emit:t}){const n=e,a=t,r=ke("radio"),{radioRef:o,radioGroup:i,focus:l,size:s,disabled:u,modelValue:c,actualValue:d}=_P(n,a);function v(){He(()=>a(Ot,c.value))}return(f,h)=>{var m;return x(),O("label",{class:U([p(r).b(),p(r).is("disabled",p(u)),p(r).is("focus",p(l)),p(r).is("bordered",e.border),p(r).is("checked",p(c)===p(d)),p(r).m(p(s))])},[C("span",{class:U([p(r).e("input"),p(r).is("disabled",p(u)),p(r).is("checked",p(c)===p(d))])},[st(C("input",{ref_key:"radioRef",ref:o,"onUpdate:modelValue":h[0]||(h[0]=g=>xn(c)?c.value=g:null),class:U(p(r).e("original")),value:p(d),name:e.name||((m=p(i))==null?void 0:m.name),disabled:p(u),checked:p(c)===p(d),type:"radio",onFocus:h[1]||(h[1]=g=>l.value=!0),onBlur:h[2]||(h[2]=g=>l.value=!1),onChange:v,onClick:h[3]||(h[3]=at(()=>{},["stop"]))},null,42,Jde),[[$7,p(c)]]),C("span",{class:U(p(r).e("inner"))},null,2)],2),C("span",{class:U(p(r).e("label")),onKeydown:h[4]||(h[4]=at(()=>{},["stop"]))},[ce(f.$slots,"default",{},()=>[ue(Q(e.label),1)])],34)],2)}}}),wP=Qde;const efe=["value","name","disabled"];var tfe=q({name:"ElRadioButton",__name:"radio-button",props:qde,setup(e){const t=e,n=ke("radio"),{radioRef:a,focus:r,size:o,disabled:i,modelValue:l,radioGroup:s,actualValue:u}=_P(t),c=D(()=>({backgroundColor:(s==null?void 0:s.fill)||"",borderColor:(s==null?void 0:s.fill)||"",boxShadow:s!=null&&s.fill?`-1px 0 0 0 ${s.fill}`:"",color:(s==null?void 0:s.textColor)||""}));return(d,v)=>{var f;return x(),O("label",{class:U([p(n).b("button"),p(n).is("active",p(l)===p(u)),p(n).is("disabled",p(i)),p(n).is("focus",p(r)),p(n).bm("button",p(o))])},[st(C("input",{ref_key:"radioRef",ref:a,"onUpdate:modelValue":v[0]||(v[0]=h=>xn(l)?l.value=h:null),class:U(p(n).be("button","original-radio")),value:p(u),type:"radio",name:e.name||((f=p(s))==null?void 0:f.name),disabled:p(i),onFocus:v[1]||(v[1]=h=>r.value=!0),onBlur:v[2]||(v[2]=h=>r.value=!1),onClick:v[3]||(v[3]=at(()=>{},["stop"]))},null,42,efe),[[$7,p(l)]]),C("span",{class:U(p(n).be("button","inner")),style:et(p(l)===p(u)?c.value:{}),onKeydown:v[4]||(v[4]=at(()=>{},["stop"]))},[ce(d.$slots,"default",{},()=>[ue(Q(e.label),1)])],38)],2)}}}),dw=tfe;const nfe=["id","aria-label","aria-labelledby"];var afe=q({name:"ElRadioGroup",__name:"radio-group",props:Xde,emits:Zde,setup(e,{emit:t}){const n=e,a=t,r=ke("radio"),o=Fa(),i=F(),{formItem:l}=Ea(),{inputId:s,isLabeledByFormItem:u}=Yr(n,{formItemContext:l}),c=m=>{a(bt,m),He(()=>a(Ot,m))};mt(()=>{const m=i.value.querySelectorAll("[type=radio]"),g=m[0];!Array.from(m).some(y=>y.checked)&&g&&(g.tabIndex=0)});const d=D(()=>n.name||o.value),v=D(()=>({...bP,...n.props})),f=m=>{const{label:g,value:y,disabled:b}=v.value,_={label:m[g],value:m[y],disabled:m[b]};return{...Tc(m,[g,y,b]),..._}},h=D(()=>n.type==="button"?dw:wP);return Ft(yP,nn({...Ta(n),changeEvent:c,name:d})),ye(()=>n.modelValue,(m,g)=>{n.validateEvent&&!Rn(m,g)&&(l==null||l.validate("change").catch(kt))}),(m,g)=>(x(),O("div",{id:p(s),ref_key:"radioGroupRef",ref:i,class:U(p(r).b("group")),role:"radiogroup","aria-label":p(u)?void 0:e.ariaLabel||"radio-group","aria-labelledby":p(u)?p(l).labelId:void 0},[ce(m.$slots,"default",{},()=>[(x(!0),O(Ve,null,dt(e.options,(y,b)=>(x(),ne(St(h.value),Tt({key:b},{ref_for:!0},f(y)),null,16))),128))])],10,nfe))}}),SP=afe;const CP=wt(wP,{RadioButton:dw,RadioGroup:SP}),rfe=On(SP),ofe=On(dw),K1=Symbol();function ife(e){return!!(Ae(e)?e.every(({type:t})=>t===oa):(e==null?void 0:e.type)===oa)}var lfe=q({name:"NodeContent",props:{node:{type:Object,required:!0}},setup(e){const t=ke("cascader-node"),{renderLabelFn:n}=Ze(K1),{node:a}=e,{data:r,label:o}=a,i=()=>{const l=n==null?void 0:n({node:a,data:r});return ife(l)?o:l??o};return()=>L("span",{class:t.e("label")},[i()])}});const sfe=["id","aria-haspopup","aria-owns","aria-expanded","tabindex"];var ufe=q({name:"ElCascaderNode",__name:"node",props:{node:{type:Object,required:!0},menuId:{type:String,required:!1}},emits:["expand"],setup(e,{emit:t}){const n=e,a=t,r=Ze(K1),o=ke("cascader-node"),i=D(()=>r.isHoverMenu),l=D(()=>r.config.multiple),s=D(()=>r.config.checkStrictly),u=D(()=>r.config.showPrefix),c=D(()=>{var E;return(E=r.checkedNodes[0])==null?void 0:E.uid}),d=D(()=>n.node.isDisabled),v=D(()=>n.node.isLeaf),f=D(()=>s.value&&!v.value||!d.value),h=D(()=>g(r.expandingNode)),m=D(()=>s.value&&r.checkedNodes.some(g)),g=E=>{var P;const{level:A,uid:I}=n.node;return((P=E==null?void 0:E.pathNodes[A-1])==null?void 0:P.uid)===I},y=()=>{h.value||r.expandNode(n.node)},b=E=>{const{node:A}=n;E!==A.checked&&r.handleCheckChange(A,E)},_=()=>{r.lazyLoad(n.node,()=>{v.value||y()})},w=E=>{i.value&&(S(),!v.value&&a("expand",E))},S=()=>{const{node:E}=n;!f.value||E.loading||(E.loaded?y():_())},k=()=>{v.value&&!d.value&&!s.value&&!l.value?M(!0):(r.config.checkOnClickNode&&(l.value||s.value)||v.value&&r.config.checkOnClickLeaf)&&!d.value?T(!n.node.checked):i.value||S()},T=E=>{s.value?(b(E),n.node.loaded&&y()):M(E)},M=E=>{n.node.loaded?(b(E),!s.value&&y()):_()};return(E,A)=>(x(),O("li",{id:`${e.menuId}-${e.node.uid}`,role:"menuitem","aria-haspopup":!v.value,"aria-owns":v.value?void 0:e.menuId,"aria-expanded":h.value,tabindex:f.value?-1:void 0,class:U([p(o).b(),p(o).is("selectable",s.value),p(o).is("active",e.node.checked),p(o).is("disabled",!f.value),h.value&&"in-active-path",m.value&&"in-checked-path"]),onMouseenter:w,onFocus:w,onClick:k},[te(" prefix "),l.value&&u.value?(x(),ne(p(Zo),{key:0,"model-value":e.node.checked,indeterminate:e.node.indeterminate,disabled:d.value,onClick:A[0]||(A[0]=at(()=>{},["stop"])),"onUpdate:modelValue":T},null,8,["model-value","indeterminate","disabled"])):s.value&&u.value?(x(),ne(p(CP),{key:1,"model-value":c.value,label:e.node.uid,disabled:d.value,"onUpdate:modelValue":T,onClick:A[1]||(A[1]=at(()=>{},["stop"]))},{default:$(()=>[te(` + Add an empty element to avoid render label, + do not use empty fragment here for https://github.com/vuejs/vue-next/pull/2485 + `),A[2]||(A[2]=C("span",null,null,-1))]),_:1},8,["model-value","label","disabled"])):v.value&&e.node.checked?(x(),ne(p(Xe),{key:2,class:U(p(o).e("prefix"))},{default:$(()=>[L(p(Os))]),_:1},8,["class"])):te("v-if",!0),te(" content "),L(p(lfe),{node:e.node},null,8,["node"]),te(" postfix "),v.value?te("v-if",!0):(x(),O(Ve,{key:3},[e.node.loading?(x(),ne(p(Xe),{key:0,class:U([p(o).is("loading"),p(o).e("postfix")])},{default:$(()=>[L(p(Mr))]),_:1},8,["class"])):(x(),ne(p(Xe),{key:1,class:U(["arrow-right",p(o).e("postfix")])},{default:$(()=>[L(p(za))]),_:1},8,["class"]))],64))],42,sfe))}}),vx=ufe,cfe=q({name:"ElCascaderMenu",__name:"menu",props:{nodes:{type:Array,required:!0},index:{type:Number,required:!0},virtualScroll:{type:Boolean,required:!1,default:!1},itemSize:{type:Number,required:!1,default:34},height:{type:Number,required:!1,default:204}},setup(e,{expose:t}){const n=e,a=Et(),r=ke("cascader-menu"),{t:o}=Vt(),i=Fa();let l,s;const u=Ze(K1),c=F(),d=F(),v=D(()=>!n.nodes.length),f=D(()=>!u.initialLoaded),h=D(()=>`${i.value}-${n.index}`),m=()=>{var M,E,A,I;let T;if(u.expandingNode){const{level:P,pathNodes:R}=u.expandingNode;n.index0&&(T=(A=(E=u.checkedNodes[0])==null?void 0:E.pathNodes[n.index])==null?void 0:A.uid)}else u.checkedNodes.length>0&&n.indexP.uid===T):-1},g=T=>T?n.nodes.findIndex(M=>`${h.value}-${M.uid}`===T):-1,y=T=>{var E;const M=Br(T,0,n.nodes.length-1);(E=d.value)==null||E.scrollToItem(M)},b=T=>{if(!n.nodes.length)return;const M=Br(T,0,n.nodes.length-1);y(M),He(()=>{var A;const E=(A=a.vnode.el)==null?void 0:A.querySelector(`#${h.value}-${n.nodes[M].uid}`);E&&_d(E)})},_=T=>{l=T.target},w=T=>{var M,E,A;if(!(!u.isHoverMenu||!l||!c.value))if(l.contains(T.target)){S();const I=a.vnode.el,{left:P}=I.getBoundingClientRect(),{offsetWidth:R,offsetHeight:G}=I,K=T.clientX-P,H=l.offsetTop,z=H+l.offsetHeight,B=n.virtualScroll?((E=(M=d.value)==null?void 0:M.states)==null?void 0:E.scrollOffset)||0:((A=I.querySelector(`.${r.e("wrap")}`))==null?void 0:A.scrollTop)||0;c.value.innerHTML=` + + + `}else s||(s=window.setTimeout(k,u.config.hoverThreshold))},S=()=>{s&&(clearTimeout(s),s=void 0)},k=()=>{c.value&&(c.value.innerHTML="",S())};return t({getActiveNodeIndex:m,getNodeIndexById:g,scrollToItem:y,focusNodeAt:b,virtualListRef:d,get $el(){return a.vnode.el}}),(T,M)=>{var E;return e.virtualScroll?(x(),O("div",{key:h.value,class:U(p(r).b()),onMousemove:w,onMouseleave:k},[L(p(cp),{ref_key:"virtualListRef",ref:d,height:e.height,"item-size":e.itemSize,data:e.nodes,total:e.nodes.length,"class-name":p(r).e("list"),"inner-element":"ul","inner-props":{role:"menu",class:p(r).is("empty",v.value)}},{default:$(({data:A,index:I,style:P})=>[(x(),ne(vx,{key:A[I].uid,node:A[I],"menu-id":h.value,style:et(P),onExpand:_},null,8,["node","menu-id","style"]))]),_:1},8,["height","item-size","data","total","class-name","inner-props"]),f.value?(x(),O("div",{key:0,class:U(p(r).e("empty-text"))},[L(p(Xe),{size:14,class:U(p(r).is("loading"))},{default:$(()=>[L(p(Mr))]),_:1},8,["class"]),ue(" "+Q(p(o)("el.cascader.loading")),1)],2)):v.value?(x(),O("div",{key:1,class:U(p(r).e("empty-text"))},[ce(T.$slots,"empty",{},()=>[ue(Q(p(o)("el.cascader.noData")),1)])],2)):(E=p(u))!=null&&E.isHoverMenu?(x(),O(Ve,{key:2},[te(" eslint-disable vue/html-self-closing "),(x(),O("svg",{ref_key:"hoverZone",ref:c,class:U(p(r).e("hover-zone"))},null,2))],2112)):te("v-if",!0),te(" eslint-enable vue/html-self-closing ")],34)):(x(),ne(p(ni),{key:h.value,tag:"ul",role:"menu",class:U(p(r).b()),"wrap-class":p(r).e("wrap"),"view-class":[p(r).e("list"),p(r).is("empty",v.value)],onMousemove:w,onMouseleave:k},{default:$(()=>{var A;return[(x(!0),O(Ve,null,dt(e.nodes,I=>(x(),ne(vx,{key:I.uid,node:I,"menu-id":h.value,onExpand:_},null,8,["node","menu-id"]))),128)),f.value?(x(),O("div",{key:0,class:U(p(r).e("empty-text"))},[L(p(Xe),{size:14,class:U(p(r).is("loading"))},{default:$(()=>[L(p(Mr))]),_:1},8,["class"]),ue(" "+Q(p(o)("el.cascader.loading")),1)],2)):v.value?(x(),O("div",{key:1,class:U(p(r).e("empty-text"))},[ce(T.$slots,"empty",{},()=>[ue(Q(p(o)("el.cascader.noData")),1)])],2)):(A=p(u))!=null&&A.isHoverMenu?(x(),O(Ve,{key:2},[te(" eslint-disable vue/html-self-closing "),(x(),O("svg",{ref_key:"hoverZone",ref:c,class:U(p(r).e("hover-zone"))},null,2))],2112)):te("v-if",!0),te(" eslint-enable vue/html-self-closing ")]}),_:3},8,["class","wrap-class","view-class"]))}}}),dfe=cfe;let ffe=0;const vfe=e=>{const t=[e];let{parent:n}=e;for(;n;)t.unshift(n),n=n.parent;return t};var G4=class Y4{constructor(t,n,a,r=!1){this.data=t,this.config=n,this.parent=a,this.root=r,this.uid=ffe++,this.checked=!1,this.indeterminate=!1,this.loading=!1;const{value:o,label:i,children:l}=n,s=t[l],u=vfe(this);this.level=r?0:a?a.level+1:1,this.value=t[o],this.label=t[i],this.pathNodes=u,this.pathValues=u.map(c=>c.value),this.pathLabels=u.map(c=>c.label),this.childrenData=s,this.children=(s||[]).map(c=>new Y4(c,n,this)),this.loaded=!n.lazy||this.isLeaf||!_r(s),this.text=""}get isDisabled(){const{data:t,parent:n,config:a}=this,{disabled:r,checkStrictly:o}=a;return(Qe(r)?r(t,this):!!t[r])||!o&&!!(n!=null&&n.isDisabled)}get isLeaf(){const{data:t,config:n,childrenData:a,loaded:r}=this,{lazy:o,leaf:i}=n,l=Qe(i)?i(t,this):t[i];return zt(l)?o&&!r?!1:!(Ae(a)&&a.length):!!l}get valueByOption(){return this.config.emitPath?this.pathValues:this.value}appendChild(t){const{childrenData:n,children:a}=this,r=new Y4(t,this.config,this);return Ae(n)?n.push(t):this.childrenData=[t],a.push(r),r}calcText(t,n){const a=t?this.pathLabels.join(n):this.label;return this.text=a,a}broadcast(t){this.children.forEach(n=>{var a;n&&(n.broadcast(t),(a=n.onParentCheck)==null||a.call(n,t))})}emit(){var n;const{parent:t}=this;t&&((n=t.onChildCheck)==null||n.call(t),t.emit())}onParentCheck(t){this.isDisabled||this.setCheckState(t)}onChildCheck(){const{children:t}=this,n=t.filter(r=>!r.isDisabled),a=n.length?n.every(r=>r.checked):!1;this.setCheckState(a)}setCheckState(t){const n=this.children.length,a=this.children.reduce((r,o)=>r+(o.checked?1:o.indeterminate?.5:0),0);this.checked=this.loaded&&this.children.filter(r=>!r.isDisabled).every(r=>r.loaded&&r.checked)&&t,this.indeterminate=this.loaded&&a!==n&&a>0}doCheck(t){if(this.checked===t)return;const{checkStrictly:n,multiple:a}=this.config;n||!a?this.checked=t:(this.broadcast(t),this.setCheckState(t),this.emit())}};const j4=(e,t)=>e.reduce((n,a)=>(a.isLeaf?n.push(a):(!t&&n.push(a),n=n.concat(j4(a.children,t))),n),[]);var hx=class{constructor(e,t){this.config=t;const n=(e||[]).map(a=>new G4(a,this.config));this.nodes=n,this.allNodes=j4(n,!1),this.leafNodes=j4(n,!0)}getNodes(){return this.nodes}getFlattedNodes(e){return e?this.leafNodes:this.allNodes}appendNode(e,t){const n=t?t.appendChild(e):new G4(e,this.config);t||this.nodes.push(n),this.appendAllNodesAndLeafNodes(n)}appendNodes(e,t){e.length>0?e.forEach(n=>this.appendNode(n,t)):t&&t.isLeaf&&this.leafNodes.push(t)}appendAllNodesAndLeafNodes(e){this.allNodes.push(e),e.isLeaf&&this.leafNodes.push(e),e.children&&e.children.forEach(t=>{this.appendAllNodesAndLeafNodes(t)})}getNodeByValue(e,t=!1){return Qr(e)?null:this.getFlattedNodes(t).find(n=>Rn(n.value,e)||Rn(n.pathValues,e))||null}getSameNode(e){return e&&this.getFlattedNodes(!1).find(({value:t,level:n})=>Rn(e.value,t)&&e.level===n)||null}};const ay=e=>{if(!e)return 0;const t=e.id.split("-");return Number(t[t.length-2])},hfe=e=>{if(!e)return;const t=e.querySelector("input");t?t.click():DA(e)&&e.click()},pfe=(e,t)=>{const n=t.slice(0),a=n.map(o=>o.uid),r=e.reduce((o,i)=>{const l=a.indexOf(i.uid);return l>-1&&(o.push(i),n.splice(l,1),a.splice(l,1)),o},[]);return r.push(...n),r};var mfe=q({name:"ElCascaderPanel",__name:"index",props:hde,emits:pde,setup(e,{expose:t,emit:n}){const a=e,r=n;let o=!1;const i=ke("cascader"),l=mde(a),s=Xn();let u;const c=F(!0),d=F(!1),v=F([]),f=F(),h=F([]),m=F(),g=F([]),y=D(()=>l.value.expandTrigger==="hover"),b=D(()=>a.renderLabel||s.default),_=D(()=>a.virtualScroll),w=D(()=>a.itemSize),S=D(()=>a.height),k=()=>{const{options:N}=a,V=l.value;o=!1,u=new hx(N,V),h.value=[u.getNodes()],V.lazy&&_r(a.options)?(c.value=!1,T(void 0,Y=>{Y&&(u=new hx(Y,V),h.value=[u.getNodes()]),c.value=!0,K(!1,!0)})):K(!1,!0)},T=(N,V)=>{const Y=l.value;N=N||new G4({},Y,void 0,!0),N.loading=!0;const X=re=>{const ie=N,le=ie.root?null:ie;ie.loading=!1,ie.loaded=!0,ie.childrenData=ie.childrenData||[],re&&(u==null||u.appendNodes(re,le)),re&&(V==null||V(re)),N.level===0&&(d.value=!0)},ee=()=>{N.loading=!1,N.loaded=!1,N.level===0&&(c.value=!0)};Y.lazyLoad(N,X,ee)},M=(N,V)=>{var re;const{level:Y}=N,X=h.value.slice(0,Y);let ee;N.isLeaf?ee=N.pathNodes[Y-2]:(ee=N,X.push(N.children)),((re=m.value)==null?void 0:re.uid)!==(ee==null?void 0:ee.uid)&&(m.value=N,h.value=X,!V&&r("expand-change",(N==null?void 0:N.pathValues)||[]))},E=(N,V,Y=!0)=>{const{checkStrictly:X,multiple:ee}=l.value,re=g.value[0];o=!0,!ee&&(re==null||re.doCheck(!1)),N.doCheck(V),G(),Y&&!ee&&!X&&r("close"),!Y&&!ee&&A(N)},A=N=>{N&&(N=N.parent,A(N),N&&M(N))},I=N=>u==null?void 0:u.getFlattedNodes(N),P=N=>{var V;return(V=I(N))==null?void 0:V.filter(({checked:Y})=>Y!==!1)},R=()=>{g.value.forEach(N=>N.doCheck(!1)),G(),h.value=h.value.slice(0,1),m.value=void 0,r("expand-change",[])},G=()=>{const{checkStrictly:N,multiple:V}=l.value,Y=g.value,X=pfe(Y,P(!N)),ee=X.map(re=>re.valueByOption);g.value=X,f.value=V?ee:ee[0]??null},K=(N=!1,V=!1)=>{const{modelValue:Y}=a,{lazy:X,multiple:ee,checkStrictly:re}=l.value,ie=!re;if(!(!c.value||o||!V&&Rn(Y,f.value)))if(X&&!N){const le=m5(VG(or(Y))).map(ve=>u==null?void 0:u.getNodeByValue(ve)).filter(ve=>!!ve&&!ve.loaded&&!ve.loading);le.length?le.forEach(ve=>{T(ve,()=>K(!1,V))}):K(!0,V)}else H(m5((ee?or(Y):[Y]).map(le=>u==null?void 0:u.getNodeByValue(le,ie))),V),f.value=Li(Y??void 0)},H=(N,V=!0)=>{const{checkStrictly:Y}=l.value,X=g.value,ee=N.filter(le=>!!le&&(Y||le.isLeaf)),re=u==null?void 0:u.getSameNode(m.value),ie=V&&re||ee[0];ie?ie.pathNodes.forEach(le=>M(le,!0)):m.value=void 0,X.forEach(le=>le.doCheck(!1)),nn(ee).forEach(le=>le.doCheck(!0)),g.value=ee,He(z)},z=()=>{qt&&v.value.forEach(N=>{var Y,X;const V=N==null?void 0:N.$el;if(V)if(_.value){const ee=(Y=N==null?void 0:N.getActiveNodeIndex)==null?void 0:Y.call(N);ee!==void 0&&ee>=0&&((X=N==null?void 0:N.scrollToItem)==null||X.call(N,ee))}else{const ee=V.querySelector(`.${i.namespace.value}-scrollbar__wrap`);let re=V.querySelector(`.${i.b("node")}.in-active-path`);if(!re){const ie=V.querySelectorAll(`.${i.b("node")}.${i.is("active")}`);re=ie[ie.length-1]}k6(ee,re)}})},B=N=>{var X,ee;const V=N.target,Y=mn(N);switch(Y){case Pe.up:case Pe.down:{N.preventDefault();const re=Y===Pe.up?-1:1;if(_.value){const ie=ay(V),le=v.value[ie];if(le){const ve=le.getNodeIndexById(V.id);if(ve>=0){const he=h.value[ie]??[],J=he.length;let Z=ve+re;for(;Z>=0&&Z{Rn(N,V)||k()},{immediate:!0}),ye(()=>a.options,k,{deep:!0}),ye(()=>a.modelValue,()=>{o=!1,K()},{deep:!0}),ye(()=>f.value,N=>{Rn(N,a.modelValue)||(r(bt,N),r(Ot,N))});const W=()=>{d.value||!c.value||k()};return q3(()=>v.value=[]),mt(()=>!_r(a.modelValue)&&K()),t({menuList:v,menus:h,checkedNodes:g,handleKeyDown:B,handleCheckChange:E,getFlattedNodes:I,getCheckedNodes:P,clearCheckedNodes:R,calculateCheckedValue:G,scrollToExpandingNode:z,loadLazyRootNodes:W}),(N,V)=>(x(),O("div",{class:U([p(i).b("panel"),p(i).is("bordered",e.border)]),onKeydown:B},[(x(!0),O(Ve,null,dt(h.value,(Y,X)=>(x(),ne(dfe,{key:X,ref_for:!0,ref:ee=>v.value[X]=ee,index:X,nodes:[...Y],"virtual-scroll":_.value,"item-size":w.value,height:S.value},{empty:$(()=>[ce(N.$slots,"empty")]),_:3},8,["index","nodes","virtual-scroll","item-size","height"]))),128))],34))}}),gfe=mfe;const xP=wt(gfe),yfe=$e({...GL,size:pa,placeholder:String,disabled:{type:Boolean,default:void 0},clearable:Boolean,clearIcon:{type:pn,default:ci},filterable:Boolean,filterMethod:{type:ae(Function),default:(e,t)=>e.text.includes(t)},separator:{type:String,default:" / "},showAllLevels:{type:Boolean,default:!0},collapseTags:Boolean,maxCollapseTags:{type:Number,default:1},collapseTagsTooltip:Boolean,maxCollapseTagsTooltipHeight:{type:[String,Number]},debounce:{type:Number,default:300},beforeFilter:{type:ae(Function),default:()=>!0},placement:{type:ae(String),values:tl,default:"bottom-start"},fallbackPlacements:{type:ae(Array),default:["bottom-start","bottom","top-start","top","right","left"]},popperClass:Tn.popperClass,popperStyle:Tn.popperStyle,teleported:Tn.teleported,effect:{type:ae(String),default:"light"},tagType:{...Ll.type,default:"info"},tagEffect:{...Ll.effect,default:"light"},validateEvent:{type:Boolean,default:!0},persistent:{type:Boolean,default:!0},showCheckedStrategy:{type:String,values:["parent","child"],default:"child"},checkOnClickNode:Boolean,showPrefix:{type:Boolean,default:!0},fitInputWidth:{type:[Boolean,Number],default:!1},...Mc}),px=e=>!0,bfe={[bt]:px,[Ot]:px,focus:e=>e instanceof FocusEvent,blur:e=>e instanceof FocusEvent,clear:()=>!0,visibleChange:e=>vn(e),expandChange:e=>!!e,removeTag:e=>!!e},_fe=["placeholder"],wfe=["onClick"],Sfe=["id","data-suggestion-index","onClick"],Cfe=34;var xfe=q({name:"ElCascader",__name:"cascader",props:yfe,emits:bfe,setup(e,{expose:t,emit:n}){const a={modifiers:[{name:"arrowPosition",enabled:!0,phase:"main",fn:({state:Ie})=>{const{modifiersData:Ke,placement:Ge}=Ie;["right","left","bottom","top"].includes(Ge)||Ke.arrow&&(Ke.arrow.x=35)},requires:["arrow"]}]},r=e,o=n,i=Nl(),l=Xn();let s=0,u=0;const c=ke("cascader"),d=ke("input"),v={small:7,default:11,large:15},{t:f}=Vt(),{formItem:h}=Ea(),m=$n(),{valueOnClear:g}=op(r),{isComposing:y,handleComposition:b}=rp({afterComposition(Ie){var Ge;const Ke=(Ge=Ie.target)==null?void 0:Ge.value;Sn(Ke)}}),_=F(),w=F(),S=F(),k=F(),T=F(),M=F(),E=F(),A=F(!1),I=F(!1),P=F(!1),R=F(""),G=F(""),K=F([]),H=F([]),z=F("100%"),B=D(()=>!!l["suggestion-item"]),W=D(()=>Br(H.value.length*r.itemSize,r.itemSize,r.height)),N=D(()=>r.props.multiple?r.collapseTags?K.value.slice(0,r.maxCollapseTags):K.value:[]),V=D(()=>r.props.multiple?r.collapseTags?K.value.slice(r.maxCollapseTags):[]:[]),Y=D(()=>i.style),X=D(()=>r.placeholder??f("el.cascader.placeholder")),ee=D(()=>G.value||K.value.length>0||y.value?"":X.value),re=ca(),ie=D(()=>re.value==="small"?"small":"default"),le=D(()=>!!r.props.multiple),ve=D(()=>!r.filterable||le.value),he=D(()=>le.value?G.value:R.value),J=D(()=>{var Ie;return((Ie=T.value)==null?void 0:Ie.checkedNodes)||[]}),{wrapperRef:Z,isFocused:j,handleBlur:oe}=Fl(S,{disabled:m,beforeBlur(Ie){var Ke,Ge;return((Ke=_.value)==null?void 0:Ke.isFocusInsideContent(Ie))||((Ge=w.value)==null?void 0:Ge.isFocusInsideContent(Ie))},afterBlur(){var Ie;r.validateEvent&&((Ie=h==null?void 0:h.validate)==null||Ie.call(h,"blur").catch(kt))}}),se=D(()=>!r.clearable||m.value||P.value||!I.value&&!j.value?!1:!!J.value.length),fe=D(()=>{const{showAllLevels:Ie,separator:Ke}=r,Ge=J.value;return Ge.length?le.value?"":Ge[0].calcText(Ie,Ke):""}),ge=D(()=>(h==null?void 0:h.validateState)||""),Se=D({get(){return Li(r.modelValue)},set(Ie){const Ke=Ie??g.value;o(bt,Ke),o(Ot,Ke),r.validateEvent&&(h==null||h.validate("change").catch(kt))}}),De=D(()=>[c.b(),c.m(re.value),c.is("disabled",m.value),i.class]),Te=D(()=>[d.e("icon"),"icon-arrow-down",c.is("reverse",A.value)]),Re=D(()=>c.is("focus",j.value)),Ce=D(()=>{var Ie,Ke;return(Ke=(Ie=_.value)==null?void 0:Ie.popperRef)==null?void 0:Ke.contentRef}),Le=Ie=>{j.value&&oe(new FocusEvent("blur",Ie)),me(!1)},me=Ie=>{var Ke,Ge;m.value||(Ie=Ie??!A.value,Ie!==A.value&&(A.value=Ie,(Ge=(Ke=S.value)==null?void 0:Ke.input)==null||Ge.setAttribute("aria-expanded",`${Ie}`),Ie?(Be(),T.value&&He(T.value.scrollToExpandingNode)):r.filterable&&ft(),o("visibleChange",Ie)))},Be=()=>{He(()=>{var Ie;(Ie=_.value)==null||Ie.updatePopper()})},ze=()=>{P.value=!1},xe=Ie=>{const{showAllLevels:Ke,separator:Ge}=r;return{node:Ie,key:Ie.uid,text:Ie.calcText(Ke,Ge),hitState:!1,closable:!m.value&&!Ie.isDisabled}},rt=Ie=>{var Ge;const Ke=Ie.node;Ke.doCheck(!1),(Ge=T.value)==null||Ge.calculateCheckedValue(),o("removeTag",Ke.valueByOption)},nt=()=>{switch(r.showCheckedStrategy){case"child":return J.value;case"parent":{const Ie=Me(!1),Ke=Ie.map(Ge=>Ge.value);return Ie.filter(Ge=>!Ge.parent||!Ke.includes(Ge.parent.value))}default:return[]}},Pt=()=>{if(!le.value)return;const Ie=nt(),Ke=[];Ie.forEach(Ge=>Ke.push(xe(Ge))),K.value=Ke},Wt=()=>{var de,it;const{filterMethod:Ie,showAllLevels:Ke,separator:Ge}=r,Ee=(it=(de=T.value)==null?void 0:de.getFlattedNodes(!r.props.checkStrictly))==null?void 0:it.filter(Dt=>Dt.isDisabled?!1:(Dt.calcText(Ke,Ge),Ie(Dt,he.value)));le.value&&K.value.forEach(Dt=>{Dt.hitState=!1}),P.value=!0,H.value=Ee,He(()=>{var Dt;r.virtualScroll&&H.value.length>0&&((Dt=E.value)==null||Dt.scrollToItem(0)),Ne()}),Be()},Jt=Ie=>{const Ke=M.value;return Ie?(Ke==null?void 0:Ke.querySelector(Ie))??void 0:Ke},Rt=()=>{var Ke;let Ie;P.value&&M.value?Ie=Jt(`.${c.e("suggestion-item")}`):Ie=(Ke=T.value)==null?void 0:Ke.$el.querySelector(`.${c.b("node")}[tabindex="-1"]`),Ie&&(Ie.focus(),!P.value&&Ie.getAttribute("aria-haspopup")==="true"&&Ie.click())},Qt=Ie=>{const Ke=Jt();if(!Ke)return;const Ge=Je(r.fitInputWidth)?`${r.fitInputWidth}px`:`${Ie}px`,Ee=it=>{r.fitInputWidth!==!1?(it.style.width=Ge,it.style.minWidth=""):(it.style.width="",it.style.minWidth=Ge)};if(Ee(Ke),r.virtualScroll){z.value=r.fitInputWidth!==!1?Ge:B.value?`${Ie}px`:`${Math.max(Ie,tt())}px`;return}const de=Jt(`.${c.e("suggestion-list")}`);de&&Ee(de)},_e=()=>{var Ge;if(!l.prefix)return 0;const Ie=(Ge=S.value)==null?void 0:Ge.$el.querySelector(`.${d.e("prefix")}`);if(!Ie)return 0;const Ke=Ie.getBoundingClientRect().width;return Ke<=0?0:Ke+v[re.value||"default"]},Ne=()=>{var Ee,de;const Ie=(Ee=S.value)==null?void 0:Ee.input,Ke=(de=S.value)==null?void 0:de.$el;if(!qt||!Ie||!Ke)return;M.value&&Qt(Ke.getBoundingClientRect().width);const Ge=k.value;if(Ge){const it=K.value.length>0?`${Math.max(Ge.offsetHeight,s)-2}px`:`${s}px`;Ie.style.height=it,Ge.style.left=`${_e()}px`,Be()}},tt=()=>{if(B.value||!H.value.length)return 0;const Ie=document.createElement("canvas").getContext("2d");if(!Ie)return 0;const Ke=Jt(`.${c.e("suggestion-item")}`);if(!Ke||!qt)return 0;const Ge=getComputedStyle(Ke),Ee=Number.parseFloat(Ge.paddingLeft)+Number.parseFloat(Ge.paddingRight);Ie.font=`bold ${Ge.fontSize} ${Ge.fontFamily}`;let de=0,it=!1;for(const Dt of H.value){const sn=Dt.text||"",gn=Ie.measureText(sn);de=Math.max(de,gn.width),Dt.checked&&!it&&(it=!0)}return de+Ee+(it?Cfe:0)},Me=Ie=>{var Ke;return(Ke=T.value)==null?void 0:Ke.getCheckedNodes(Ie)},Ue=Ie=>{Be(),o("expandChange",Ie)},Gt=Ie=>{if(!y.value)switch(mn(Ie)){case Pe.enter:case Pe.numpadEnter:me();break;case Pe.down:me(!0),He(Rt),Ie.preventDefault();break;case Pe.esc:A.value===!0&&(Ie.preventDefault(),Ie.stopPropagation(),me(!1));break;case Pe.tab:me(!1);break}},je=()=>{var Ie;(Ie=T.value)==null||Ie.clearCheckedNodes(),!A.value&&r.filterable&&ft(),me(!1),o("clear")},ft=()=>{const{value:Ie}=fe;R.value=Ie,G.value=Ie},ht=Ie=>{var Ge,Ee;const{checked:Ke}=Ie;le.value?(Ge=T.value)==null||Ge.handleCheckChange(Ie,!Ke,!1):(!Ke&&((Ee=T.value)==null||Ee.handleCheckChange(Ie,!0,!1)),me(!1))},pt=Ie=>{var Ee;const Ke=(Ee=Ie.closest("[data-suggestion-index]"))==null?void 0:Ee.dataset.suggestionIndex;if(!Ke)return-1;const Ge=Number.parseInt(Ke,10);return Number.isNaN(Ge)||Ge<0||Ge>=H.value.length?-1:Ge},It=Ie=>{const Ke=Ie.target,Ge=mn(Ie);switch(Ge){case Pe.up:case Pe.down:{Ie.preventDefault();const Ee=Ge===Pe.up?-1:1;if(r.virtualScroll&&E.value){const de=pt(Ke);if(de>=0){const it=H.value.length,Dt=(de+Ee+it)%it;E.value.scrollToItem(Dt),He(()=>{const sn=Jt(`#suggestion-${H.value[Dt].uid}`);sn&&_d(sn)});return}}_d(OA(Ke,Ee,`.${c.e("suggestion-item")}[tabindex="-1"]`));break}case Pe.enter:case Pe.numpadEnter:Ke.click();break}},Ct=()=>{const Ie=K.value[K.value.length-1];u=G.value?0:u+1,!(!Ie||!u||r.collapseTags&&K.value.length>1)&&(Ie.hitState?rt(Ie):Ie.hitState=!0)},$t=Xh(()=>{const{value:Ie}=he;if(!Ie)return;const Ke=r.beforeFilter(Ie);Ls(Ke)?Ke.then(Wt).catch(()=>{}):Ke!==!1?Wt():ze()},D(()=>r.debounce)),Sn=(Ie,Ke)=>{if(!A.value&&me(!0),!(Ke!=null&&Ke.isComposing))if(Ie)$t();else{const Ge=r.beforeFilter("");Ls(Ge)&&Ge.catch(()=>{}),ze()}},Fe=Ie=>Number.parseFloat(IY(d.cssVarName("input-height"),Ie).value)-2,qe=()=>{var Ie;(Ie=S.value)==null||Ie.focus()},ut=()=>{var Ie;(Ie=S.value)==null||Ie.blur()};return ye(P,Be),ye([J,m,()=>r.collapseTags,()=>r.maxCollapseTags],Pt),ye(K,()=>{He(()=>Ne())}),ye(re,async()=>{await He();const Ie=S.value.input;s=Fe(Ie)||s,Ne()}),ye(fe,ft,{immediate:!0}),ye(()=>A.value,Ie=>{var Ke;Ie&&r.props.lazy&&r.props.lazyLoad&&((Ke=T.value)==null||Ke.loadLazyRootNodes())}),mt(()=>{const Ie=S.value.input,Ke=S.value.$el,Ge=Fe(Ie);s=Ie.offsetHeight||Ge,Mn(Ke,Ne)}),t({getCheckedNodes:Me,cascaderPanelRef:T,togglePopperVisible:me,contentRef:Ce,presentText:fe,focus:qe,blur:ut}),(Ie,Ke)=>(x(),ne(p(Ma),{ref_key:"tooltipRef",ref:_,visible:A.value,teleported:e.teleported,"popper-class":[p(c).e("dropdown"),e.popperClass],"popper-style":e.popperStyle,"popper-options":a,"fallback-placements":e.fallbackPlacements,"stop-popper-mouse-event":!1,"gpu-acceleration":!1,placement:e.placement,transition:`${p(c).namespace.value}-zoom-in-top`,effect:e.effect,pure:"",persistent:e.persistent,onHide:ze},{default:$(()=>[st((x(),O("div",{ref_key:"wrapperRef",ref:Z,class:U(De.value),style:et(Y.value),onClick:Ke[8]||(Ke[8]=()=>me(ve.value?void 0:!0)),onKeydown:Gt,onMouseenter:Ke[9]||(Ke[9]=Ge=>I.value=!0),onMouseleave:Ke[10]||(Ke[10]=Ge=>I.value=!1)},[L(p(Ba),{ref_key:"inputRef",ref:S,modelValue:R.value,"onUpdate:modelValue":Ke[1]||(Ke[1]=Ge=>R.value=Ge),placeholder:ee.value,readonly:ve.value,disabled:p(m),"validate-event":!1,size:p(re),class:U(Re.value),tabindex:le.value&&e.filterable&&!p(m)?-1:void 0,onCompositionstart:p(b),onCompositionupdate:p(b),onCompositionend:p(b),onInput:Sn},xr({suffix:$(()=>[se.value?(x(),ne(p(Xe),{key:"clear",class:U([p(d).e("icon"),"icon-circle-close"]),onClick:at(je,["stop"])},{default:$(()=>[(x(),ne(St(e.clearIcon)))]),_:1},8,["class"])):(x(),ne(p(Xe),{key:"arrow-down",class:U(Te.value),onClick:Ke[0]||(Ke[0]=at(Ge=>me(),["stop"]))},{default:$(()=>[L(p(ui))]),_:1},8,["class"]))]),_:2},[Ie.$slots.prefix?{name:"prefix",fn:$(()=>[ce(Ie.$slots,"prefix")]),key:"0"}:void 0]),1032,["modelValue","placeholder","readonly","disabled","size","class","tabindex","onCompositionstart","onCompositionupdate","onCompositionend"]),le.value?(x(),O("div",{key:0,ref_key:"tagWrapper",ref:k,class:U([p(c).e("tags"),p(c).is("validate",!!ge.value)])},[ce(Ie.$slots,"tag",{data:K.value,deleteTag:rt},()=>[(x(!0),O(Ve,null,dt(N.value,Ge=>(x(),ne(p(Cl),{key:Ge.key,type:e.tagType,size:ie.value,effect:e.tagEffect,hit:Ge.hitState,closable:Ge.closable,"disable-transitions":"",onClose:Ee=>rt(Ge)},{default:$(()=>[C("span",null,Q(Ge.text),1)]),_:2},1032,["type","size","effect","hit","closable","onClose"]))),128))]),e.collapseTags&&K.value.length>e.maxCollapseTags?(x(),ne(p(Ma),{key:0,ref_key:"tagTooltipRef",ref:w,disabled:A.value||!e.collapseTagsTooltip,"fallback-placements":["bottom","top","right","left"],placement:"bottom","popper-class":e.popperClass,"popper-style":e.popperStyle,effect:e.effect,persistent:e.persistent},{default:$(()=>[L(p(Cl),{closable:!1,size:ie.value,type:e.tagType,effect:e.tagEffect,"disable-transitions":""},{default:$(()=>[C("span",{class:U(p(c).e("tags-text"))}," + "+Q(K.value.length-e.maxCollapseTags),3)]),_:1},8,["size","type","effect"])]),content:$(()=>[L(p(ni),{"max-height":e.maxCollapseTagsTooltipHeight},{default:$(()=>[C("div",{class:U(p(c).e("collapse-tags"))},[(x(!0),O(Ve,null,dt(V.value,(Ge,Ee)=>(x(),O("div",{key:Ee,class:U(p(c).e("collapse-tag"))},[(x(),ne(p(Cl),{key:Ge.key,class:"in-tooltip",type:e.tagType,size:ie.value,effect:e.tagEffect,hit:Ge.hitState,closable:Ge.closable,"disable-transitions":"",onClose:de=>rt(Ge)},{default:$(()=>[C("span",null,Q(Ge.text),1)]),_:2},1032,["type","size","effect","hit","closable","onClose"]))],2))),128))],2)]),_:1},8,["max-height"])]),_:1},8,["disabled","popper-class","popper-style","effect","persistent"])):te("v-if",!0),e.filterable&&!p(m)?st((x(),O("input",{key:1,"onUpdate:modelValue":Ke[2]||(Ke[2]=Ge=>G.value=Ge),type:"text",class:U(p(c).e("search-input")),placeholder:fe.value?"":X.value,onInput:Ke[3]||(Ke[3]=Ge=>Sn(G.value,Ge)),onClick:Ke[4]||(Ke[4]=at(Ge=>me(!0),["stop"])),onKeydown:kn(Ct,["delete"]),onCompositionstart:Ke[5]||(Ke[5]=(...Ge)=>p(b)&&p(b)(...Ge)),onCompositionupdate:Ke[6]||(Ke[6]=(...Ge)=>p(b)&&p(b)(...Ge)),onCompositionend:Ke[7]||(Ke[7]=(...Ge)=>p(b)&&p(b)(...Ge))},null,42,_fe)),[[D7,G.value]]):te("v-if",!0)],2)):te("v-if",!0)],38)),[[p(Ns),Le,Ce.value]])]),content:$(()=>[Ie.$slots.header?(x(),O("div",{key:0,class:U(p(c).e("header")),onClick:Ke[11]||(Ke[11]=at(()=>{},["stop"]))},[ce(Ie.$slots,"header")],2)):te("v-if",!0),st(L(p(xP),{ref_key:"cascaderPanelRef",ref:T,modelValue:Se.value,"onUpdate:modelValue":Ke[12]||(Ke[12]=Ge=>Se.value=Ge),options:e.options,props:r.props,border:!1,"render-label":Ie.$slots.default,"virtual-scroll":e.virtualScroll,"item-size":e.itemSize,height:e.height,onExpandChange:Ue,onClose:Ke[13]||(Ke[13]=Ge=>Ie.$nextTick(()=>me(!1)))},{empty:$(()=>[ce(Ie.$slots,"empty")]),_:3},8,["modelValue","options","props","render-label","virtual-scroll","item-size","height"]),[[Zt,!P.value]]),e.filterable?(x(),O(Ve,{key:1},[e.virtualScroll?st((x(),O("div",{key:1,ref_key:"suggestionPanel",ref:M,class:U(p(c).e("suggestion-panel")),onKeydown:It},[st(L(p(cp),{ref_key:"suggestionVirtualListRef",ref:E,height:W.value,"item-size":e.itemSize,data:H.value,total:H.value.length,"class-name":p(c).e("suggestion-list"),"inner-element":"ul","inner-width":z.value},{default:$(({data:Ge,index:Ee,style:de})=>[(x(),O("li",{id:`suggestion-${Ge[Ee].uid}`,key:Ge[Ee].uid,"data-suggestion-index":Ee,class:U([p(c).e("suggestion-item"),p(c).is("checked",Ge[Ee].checked)]),tabindex:-1,style:et(de),onClick:it=>ht(Ge[Ee])},[ce(Ie.$slots,"suggestion-item",{item:Ge[Ee]},()=>[C("span",null,Q(Ge[Ee].text),1),Ge[Ee].checked?(x(),ne(p(Xe),{key:0},{default:$(()=>[L(p(Os))]),_:1})):te("v-if",!0)])],14,Sfe))]),_:3},8,["height","item-size","data","total","class-name","inner-width"]),[[Zt,H.value.length]]),H.value.length?te("v-if",!0):ce(Ie.$slots,"empty",{key:0},()=>[C("ul",{class:U(p(c).e("suggestion-list"))},[C("li",{class:U(p(c).e("empty-text"))},Q(p(f)("el.cascader.noMatch")),3)],2)])],34)),[[Zt,P.value]]):st((x(),ne(p(ni),{key:0,ref:Ge=>M.value=Ge==null?void 0:Ge.$el,tag:"ul",class:U(p(c).e("suggestion-panel")),"wrap-class":p(c).e("suggestion-wrap"),"view-class":p(c).e("suggestion-list"),onKeydown:It},{default:$(()=>[H.value.length?(x(!0),O(Ve,{key:0},dt(H.value,Ge=>(x(),O("li",{key:Ge.uid,class:U([p(c).e("suggestion-item"),p(c).is("checked",Ge.checked)]),tabindex:-1,onClick:Ee=>ht(Ge)},[ce(Ie.$slots,"suggestion-item",{item:Ge},()=>[C("span",null,Q(Ge.text),1),Ge.checked?(x(),ne(p(Xe),{key:0},{default:$(()=>[L(p(Os))]),_:1})):te("v-if",!0)])],10,wfe))),128)):ce(Ie.$slots,"empty",{key:1},()=>[C("li",{class:U(p(c).e("empty-text"))},Q(p(f)("el.cascader.noMatch")),3)])]),_:3},8,["class","wrap-class","view-class"])),[[Zt,P.value]])],64)):te("v-if",!0),Ie.$slots.footer?(x(),O("div",{key:2,class:U(p(c).e("footer")),onClick:Ke[14]||(Ke[14]=at(()=>{},["stop"]))},[ce(Ie.$slots,"footer")],2)):te("v-if",!0)]),_:3},8,["visible","teleported","popper-class","popper-style","fallback-placements","placement","transition","effect","persistent"]))}}),kfe=xfe;const Tfe=wt(kfe),Mfe=$e({checked:Boolean,disabled:Boolean,type:{type:String,values:["primary","success","info","warning","danger"],default:"primary"}}),Efe={"update:checked":e=>vn(e),[Ot]:e=>vn(e)};var Afe=q({name:"ElCheckTag",__name:"check-tag",props:Mfe,emits:Efe,setup(e,{emit:t}){const n=e,a=t,r=ke("check-tag"),o=D(()=>[r.b(),r.is("checked",n.checked),r.is("disabled",n.disabled),r.m(n.type||"primary")]),i=()=>{if(n.disabled)return;const l=!n.checked;a(Ot,l),a("update:checked",l)};return(l,s)=>(x(),O("span",{class:U(o.value),onClick:i},[ce(l.$slots,"default")],2))}}),Ife=Afe;const Lfe=wt(Ife),Pfe=$e({tag:{type:String,default:"div"},span:{type:Number,default:24},offset:{type:Number,default:0},pull:{type:Number,default:0},push:{type:Number,default:0},xs:{type:ae([Number,Object]),default:()=>Nn({})},sm:{type:ae([Number,Object]),default:()=>Nn({})},md:{type:ae([Number,Object]),default:()=>Nn({})},lg:{type:ae([Number,Object]),default:()=>Nn({})},xl:{type:ae([Number,Object]),default:()=>Nn({})}}),Dfe=["start","center","end","space-around","space-between","space-evenly"],Ofe=["top","middle","bottom"],$fe=$e({tag:{type:String,default:"div"},gutter:{type:Number,default:0},justify:{type:String,values:Dfe,default:"start"},align:{type:String,values:Ofe}}),kP=Symbol("rowContextKey");var Rfe=q({name:"ElRow",__name:"row",props:$fe,setup(e){const t=e,n=ke("row");Ft(kP,{gutter:D(()=>t.gutter)});const a=D(()=>{const o={};return t.gutter&&(o.marginRight=o.marginLeft=`-${t.gutter/2}px`),o}),r=D(()=>[n.b(),n.is(`justify-${t.justify}`,t.justify!=="start"),n.is(`align-${t.align}`,!!t.align)]);return(o,i)=>(x(),ne(St(e.tag),{class:U(r.value),style:et(a.value)},{default:$(()=>[ce(o.$slots,"default")]),_:3},8,["class","style"]))}}),Nfe=Rfe;const Bfe=wt(Nfe);var Vfe=q({name:"ElCol",__name:"col",props:Pfe,setup(e){const t=e,{gutter:n}=Ze(kP,{gutter:D(()=>0)}),a=ke("col"),r=D(()=>{const i={};return n.value&&(i.paddingLeft=i.paddingRight=`${n.value/2}px`),i}),o=D(()=>{const i=[];return["span","offset","pull","push"].forEach(l=>{const s=t[l];Je(s)&&(l==="span"?i.push(a.b(`${t[l]}`)):s>0&&i.push(a.b(`${l}-${t[l]}`)))}),["xs","sm","md","lg","xl"].forEach(l=>{Je(t[l])?i.push(a.b(`${l}-${t[l]}`)):_t(t[l])&&Object.entries(t[l]).forEach(([s,u])=>{i.push(s!=="span"?a.b(`${l}-${s}-${u}`):a.b(`${l}-${u}`))})}),n.value&&i.push(a.is("guttered")),[a.b(),i]});return(i,l)=>(x(),ne(St(e.tag),{class:U(o.value),style:et(r.value)},{default:$(()=>[ce(i.$slots,"default")]),_:3},8,["class","style"]))}}),zfe=Vfe;const Ffe=wt(zfe),mx=e=>Je(e)||Ye(e)||Ae(e),Hfe=$e({accordion:Boolean,modelValue:{type:ae([Array,String,Number]),default:()=>Nn([])},expandIconPosition:{type:ae([String]),default:"right"},beforeCollapse:{type:ae(Function)}}),Wfe={[bt]:mx,[Ot]:mx},TP=Symbol("collapseContextKey"),Kfe=$e({title:{type:String,default:""},name:{type:ae([String,Number]),default:void 0},icon:{type:pn,default:za},disabled:Boolean});var Ufe=q({name:"ElCollapseTransition",__name:"collapse-transition",setup(e){const t=ke("collapse-transition"),n=r=>{r.style.maxHeight="",r.style.overflow=r.dataset.oldOverflow,r.style.paddingTop=r.dataset.oldPaddingTop,r.style.paddingBottom=r.dataset.oldPaddingBottom},a={beforeEnter(r){r.dataset||(r.dataset={}),r.dataset.oldPaddingTop=r.style.paddingTop,r.dataset.oldPaddingBottom=r.style.paddingBottom,r.style.height&&(r.dataset.elExistsHeight=r.style.height),r.style.maxHeight=0,r.style.paddingTop=0,r.style.paddingBottom=0},enter(r){requestAnimationFrame(()=>{r.dataset.oldOverflow=r.style.overflow,r.dataset.elExistsHeight?r.style.maxHeight=r.dataset.elExistsHeight:r.scrollHeight!==0?r.style.maxHeight=`${r.scrollHeight}px`:r.style.maxHeight=0,r.style.paddingTop=r.dataset.oldPaddingTop,r.style.paddingBottom=r.dataset.oldPaddingBottom,r.style.overflow="hidden"})},afterEnter(r){r.style.maxHeight="",r.style.overflow=r.dataset.oldOverflow},enterCancelled(r){n(r)},beforeLeave(r){r.dataset||(r.dataset={}),r.dataset.oldPaddingTop=r.style.paddingTop,r.dataset.oldPaddingBottom=r.style.paddingBottom,r.dataset.oldOverflow=r.style.overflow,r.style.maxHeight=`${r.scrollHeight}px`,r.style.overflow="hidden"},leave(r){r.scrollHeight!==0&&(r.style.maxHeight=0,r.style.paddingTop=0,r.style.paddingBottom=0)},afterLeave(r){n(r)},leaveCancelled(r){n(r)}};return(r,o)=>(x(),ne(Pa,Tt({name:p(t).b()},fz(a)),{default:$(()=>[ce(r.$slots,"default")]),_:3},16,["name"]))}}),Gfe=Ufe;const U1=wt(Gfe),gx="ElCollapse",Yfe=(e,t)=>{const n=F(wa(e.modelValue)),a=i=>{n.value=i;const l=e.accordion?n.value[0]:n.value;t(bt,l),t(Ot,l)},r=i=>{if(e.accordion)a([n.value[0]===i?"":i]);else{const l=[...n.value],s=l.indexOf(i);s>-1?l.splice(s,1):l.push(i),a(l)}},o=async i=>{const{beforeCollapse:l}=e;if(!l){r(i);return}const s=l(i);[Ls(s),vn(s)].includes(!0)||Dn(gx,"beforeCollapse must return type `Promise` or `boolean`"),Ls(s)?s.then(u=>{u!==!1&&r(i)}).catch(u=>{un(gx,`some error occurred: ${u}`)}):s&&r(i)};return ye(()=>e.modelValue,()=>n.value=wa(e.modelValue),{deep:!0}),Ft(TP,{activeNames:n,handleItemClick:o}),{activeNames:n,setActiveNames:a}},jfe=e=>{const t=ke("collapse");return{rootKls:D(()=>[t.b(),t.b(`icon-position-${e.expandIconPosition}`)])}};var qfe=q({name:"ElCollapse",__name:"collapse",props:Hfe,emits:Wfe,setup(e,{expose:t,emit:n}){const a=e,{activeNames:r,setActiveNames:o}=Yfe(a,n),{rootKls:i}=jfe(a);return t({activeNames:r,setActiveNames:o}),(l,s)=>(x(),O("div",{class:U(p(i))},[ce(l.$slots,"default")],2))}}),Xfe=qfe;const Zfe=e=>{const t=Ze(TP),{namespace:n}=ke("collapse"),a=F(!1),r=F(!1),o=$6(),i=D(()=>o.current++),l=D(()=>e.name??`${n.value}-id-${o.prefix}-${p(i)}`),s=D(()=>t==null?void 0:t.activeNames.value.includes(p(l)));return{focusing:a,id:i,isActive:s,handleFocus:()=>{setTimeout(()=>{r.value?r.value=!1:a.value=!0},50)},handleHeaderClick:v=>{var f;e.disabled||(f=v.target)!=null&&f.closest("input, textarea, select")||(t==null||t.handleItemClick(p(l)),a.value=!1,r.value=!0)},handleEnterClick:v=>{var f;(f=v.target)!=null&&f.closest("input, textarea, select")||(v.preventDefault(),t==null||t.handleItemClick(p(l)))}}},Jfe=(e,{focusing:t,isActive:n,id:a})=>{const r=ke("collapse"),o=D(()=>[r.b("item"),r.is("active",p(n)),r.is("disabled",e.disabled)]),i=D(()=>[r.be("item","header"),r.is("active",p(n)),{focusing:p(t)&&!e.disabled}]),l=D(()=>[r.be("item","arrow"),r.is("active",p(n))]);return{itemTitleKls:D(()=>[r.be("item","title")]),arrowKls:l,headKls:i,rootKls:o,itemWrapperKls:D(()=>r.be("item","wrap")),itemContentKls:D(()=>r.be("item","content")),scopedContentId:D(()=>r.b(`content-${p(a)}`)),scopedHeadId:D(()=>r.b(`head-${p(a)}`))}},Qfe=["id","aria-expanded","aria-controls","aria-describedby","tabindex","aria-disabled"],eve=["id","aria-hidden","aria-labelledby"];var tve=q({name:"ElCollapseItem",__name:"collapse-item",props:Kfe,setup(e,{expose:t}){const n=e,{focusing:a,id:r,isActive:o,handleFocus:i,handleHeaderClick:l,handleEnterClick:s}=Zfe(n),{arrowKls:u,headKls:c,rootKls:d,itemTitleKls:v,itemWrapperKls:f,itemContentKls:h,scopedContentId:m,scopedHeadId:g}=Jfe(n,{focusing:a,isActive:o,id:r});return t({isActive:o}),(y,b)=>(x(),O("div",{class:U(p(d))},[C("div",{id:p(g),class:U(p(c)),"aria-expanded":p(o),"aria-controls":p(m),"aria-describedby":p(m),tabindex:e.disabled?void 0:0,"aria-disabled":e.disabled,role:"button",onClick:b[0]||(b[0]=(..._)=>p(l)&&p(l)(..._)),onKeydown:b[1]||(b[1]=kn(at((..._)=>p(s)&&p(s)(..._),["stop"]),["space","enter"])),onFocus:b[2]||(b[2]=(..._)=>p(i)&&p(i)(..._)),onBlur:b[3]||(b[3]=_=>a.value=!1)},[C("span",{class:U(p(v))},[ce(y.$slots,"title",{isActive:p(o)},()=>[ue(Q(e.title),1)])],2),ce(y.$slots,"icon",{isActive:p(o)},()=>[L(p(Xe),{class:U(p(u))},{default:$(()=>[(x(),ne(St(e.icon)))]),_:1},8,["class"])])],42,Qfe),L(p(U1),null,{default:$(()=>[st(C("div",{id:p(m),role:"region",class:U(p(f)),"aria-hidden":!p(o),"aria-labelledby":p(g)},[C("div",{class:U(p(h))},[ce(y.$slots,"default")],2)],10,eve),[[Zt,p(o)]])]),_:3})],2))}}),MP=tve;const nve=wt(Xfe,{CollapseItem:MP}),ave=On(MP),EP=$e({modelValue:{type:ae(String),default:void 0},border:{type:Boolean,default:!0},showAlpha:Boolean,colorFormat:{type:ae(String)},disabled:Boolean,predefine:{type:ae(Array)},validateEvent:{type:Boolean,default:!0},hueSliderClass:{type:ae([String,Array,Object,Boolean]),default:void 0},hueSliderStyle:{type:ae([String,Array,Object,Boolean]),default:void 0}}),rve={[bt]:e=>Ye(e)||Un(e)},AP=Symbol("colorCommonPickerKey"),IP=Symbol("colorPickerPanelContextKey"),LP=$e({color:{type:ae(Object),required:!0},vertical:Boolean,disabled:Boolean}),ove=LP,yx=e=>{let t=0,n=e;for(;n;)t+=n.offsetTop,n=n.offsetParent;return t},q4=(e,t)=>Math.abs(yx(e)-yx(t)),sg=e=>{let t,n;return e.type==="touchend"?(n=e.changedTouches[0].clientY,t=e.changedTouches[0].clientX):e.type.startsWith("touch")?(n=e.touches[0].clientY,t=e.touches[0].clientX):(n=e.clientY,t=e.clientX),{clientX:t,clientY:n}};let ry=!1;function X4(e,t){if(!qt)return;const n=function(o){var i;(i=t.drag)==null||i.call(t,o)},a=function(o){var i;document.removeEventListener("mousemove",n),document.removeEventListener("mouseup",a),document.removeEventListener("touchmove",n),document.removeEventListener("touchend",a),document.onselectstart=null,document.ondragstart=null,ry=!1,(i=t.end)==null||i.call(t,o)},r=function(o){var i;ry||(document.onselectstart=()=>!1,document.ondragstart=()=>!1,document.addEventListener("mousemove",n),document.addEventListener("mouseup",a),document.addEventListener("touchmove",n),document.addEventListener("touchend",a),ry=!0,(i=t.start)==null||i.call(t,o))};e.addEventListener("mousedown",r),e.addEventListener("touchstart",r,{passive:!1})}const PP=(e,{key:t,minValue:n,maxValue:a})=>{const r=Et(),o=Ht(),i=Ht(),l=D(()=>e.color.get(t));function s(v){var f;e.disabled||(v.target!==o.value&&u(v),(f=o.value)==null||f.focus())}function u(v){if(!i.value||!o.value||e.disabled)return;const f=r.vnode.el.getBoundingClientRect(),{clientX:h,clientY:m}=sg(v);let g;if(e.vertical){let y=m-f.top;y=Math.max(o.value.offsetHeight/2,y),y=Math.min(y,f.height-o.value.offsetHeight/2),g=Math.round((y-o.value.offsetHeight/2)/(f.height-o.value.offsetHeight)*a)}else{let y=h-f.left;y=Math.max(o.value.offsetWidth/2,y),y=Math.min(y,f.width-o.value.offsetWidth/2),g=Math.round((y-o.value.offsetWidth/2)/(f.width-o.value.offsetWidth)*a)}e.color.set(t,g)}function c(v){if(e.disabled)return;const{shiftKey:f}=v,h=mn(v),m=f?10:1,g=t==="hue"?-1:1;let y=!0;switch(h){case Pe.left:case Pe.down:d(-m*g);break;case Pe.right:case Pe.up:d(m*g);break;case Pe.home:e.color.set(t,t==="hue"?a:n);break;case Pe.end:e.color.set(t,t==="hue"?n:a);break;case Pe.pageDown:d(-4*g);break;case Pe.pageUp:d(4*g);break;default:y=!1;break}y&&v.preventDefault()}function d(v){let f=l.value+v;f=fa?a:f,e.color.set(t,f)}return{thumb:o,bar:i,currentValue:l,handleDrag:u,handleClick:s,handleKeydown:c}},DP=(e,{namespace:t,maxValue:n,bar:a,thumb:r,currentValue:o,handleDrag:i,getBackground:l})=>{const s=Et(),u=ke(t),c=F(0),d=F(0),v=F();function f(){if(!r.value||e.vertical)return 0;const _=s.vnode.el,w=o.value;return _?Math.round(w*(_.offsetWidth-r.value.offsetWidth/2)/n):0}function h(){if(!r.value)return 0;const _=s.vnode.el;if(!e.vertical)return 0;const w=o.value;return _?Math.round(w*(_.offsetHeight-r.value.offsetHeight/2)/n):0}function m(){c.value=f(),d.value=h(),v.value=l==null?void 0:l()}mt(()=>{if(!a.value||!r.value)return;const _={drag:w=>{i(w)},end:w=>{i(w)}};X4(a.value,_),X4(r.value,_),m()}),ye(o,()=>m()),ye(()=>e.color.value,()=>m());const g=D(()=>[u.b(),u.is("vertical",e.vertical),u.is("disabled",e.disabled)]),y=D(()=>u.e("bar")),b=D(()=>u.e("thumb"));return{rootKls:g,barKls:y,barStyle:D(()=>({background:v.value})),thumbKls:b,thumbStyle:D(()=>({left:Vn(c.value),top:Vn(d.value)})),thumbLeft:c,thumbTop:d,update:m}},ive=["aria-label","aria-valuenow","aria-valuetext","aria-orientation","tabindex","aria-disabled"],bx=0,oy=100;var lve=q({name:"ElColorAlphaSlider",__name:"alpha-slider",props:LP,setup(e,{expose:t}){const n=e,{currentValue:a,bar:r,thumb:o,handleDrag:i,handleClick:l,handleKeydown:s}=PP(n,{key:"alpha",minValue:bx,maxValue:oy}),{rootKls:u,barKls:c,barStyle:d,thumbKls:v,thumbStyle:f,update:h}=DP(n,{namespace:"color-alpha-slider",maxValue:oy,currentValue:a,bar:r,thumb:o,handleDrag:i,getBackground:b}),{t:m}=Vt(),g=D(()=>m("el.colorpicker.alphaLabel")),y=D(()=>m("el.colorpicker.alphaDescription",{alpha:a.value,color:n.color.value}));function b(){if(n.color&&n.color.value){const{r:_,g:w,b:S}=n.color.toRgb();return`linear-gradient(to right, rgba(${_}, ${w}, ${S}, 0) 0%, rgba(${_}, ${w}, ${S}, 1) 100%)`}return""}return t({update:h,bar:r,thumb:o}),(_,w)=>(x(),O("div",{class:U(p(u))},[C("div",{ref_key:"bar",ref:r,class:U(p(c)),style:et(p(d)),onClick:w[0]||(w[0]=(...S)=>p(l)&&p(l)(...S))},null,6),C("div",{ref_key:"thumb",ref:o,class:U(p(v)),style:et(p(f)),"aria-label":g.value,"aria-valuenow":p(a),"aria-valuetext":y.value,"aria-orientation":e.vertical?"vertical":"horizontal","aria-valuemin":bx,"aria-valuemax":oy,role:"slider",tabindex:e.disabled?void 0:0,"aria-disabled":e.disabled,onKeydown:w[1]||(w[1]=(...S)=>p(s)&&p(s)(...S))},null,46,ive)],2))}}),sve=lve;const uve=["aria-label","aria-valuenow","aria-valuetext","aria-orientation","tabindex","aria-disabled"],_x=0,iy=360;var cve=q({name:"ElColorHueSlider",__name:"hue-slider",props:ove,setup(e,{expose:t}){const n=e,{currentValue:a,bar:r,thumb:o,handleDrag:i,handleClick:l,handleKeydown:s}=PP(n,{key:"hue",minValue:_x,maxValue:iy}),{rootKls:u,barKls:c,thumbKls:d,thumbStyle:v,thumbTop:f,update:h}=DP(n,{namespace:"color-hue-slider",maxValue:iy,currentValue:a,bar:r,thumb:o,handleDrag:i}),{t:m}=Vt(),g=D(()=>m("el.colorpicker.hueLabel")),y=D(()=>m("el.colorpicker.hueDescription",{hue:a.value,color:n.color.value}));return t({bar:r,thumb:o,thumbTop:f,update:h}),(b,_)=>(x(),O("div",{class:U(p(u))},[C("div",{ref_key:"bar",ref:r,class:U(p(c)),onClick:_[0]||(_[0]=(...w)=>p(l)&&p(l)(...w))},null,2),C("div",{ref_key:"thumb",ref:o,class:U(p(d)),style:et(p(v)),"aria-label":g.value,"aria-valuenow":p(a),"aria-valuetext":y.value,"aria-orientation":e.vertical?"vertical":"horizontal","aria-valuemin":_x,"aria-valuemax":iy,role:"slider",tabindex:e.disabled?void 0:0,"aria-disabled":e.disabled,onKeydown:_[1]||(_[1]=(...w)=>p(s)&&p(s)(...w))},null,46,uve)],2))}}),dve=cve;const fve=$e({colors:{type:ae(Array),required:!0},color:{type:ae(Object),required:!0},enableAlpha:{type:Boolean,required:!0},disabled:Boolean});var ug=class{constructor(e={}){this._hue=0,this._saturation=100,this._value=100,this._alpha=100,this._tiny=new ea,this._isValid=!1,this.enableAlpha=!1,this.format="",this.value="";for(const t in e)en(e,t)&&(this[t]=e[t]);e.value?this.fromString(e.value):this.doOnChange()}set(e,t){if(arguments.length===1&&typeof e=="object"){for(const n in e)en(e,n)&&this.set(n,e[n]);return}this[`_${e}`]=t,this._isValid=!0,this.doOnChange()}get(e){return["hue","saturation","value","alpha"].includes(e)?Math.round(this[`_${e}`]):this[`_${e}`]}toRgb(){return this._isValid?this._tiny.toRgb():{r:255,g:255,b:255,a:0}}fromString(e){const t=new ea(e);if(this._isValid=t.isValid,t.isValid){const{h:n,s:a,v:r,a:o}=t.toHsv();this._hue=n,this._saturation=a*100,this._value=r*100,this._alpha=o*100}else this._hue=0,this._saturation=100,this._value=100,this._alpha=100;this.doOnChange()}clear(){this._isValid=!1,this.value="",this._hue=0,this._saturation=100,this._value=100,this._alpha=100}compare(e){const t=new ea({h:e._hue,s:e._saturation/100,v:e._value/100,a:e._alpha/100});return this._tiny.equals(t)}doOnChange(){const{_hue:e,_saturation:t,_value:n,_alpha:a,format:r,enableAlpha:o}=this;let i=r||(o?"rgb":"hex");r==="hex"&&o&&(i="hex8"),this._tiny=new ea({h:e,s:t/100,v:n/100,a:a/100}),this.value=this._isValid?this._tiny.toString(i):""}};const vve=e=>{const{currentColor:t}=Ze(IP),n=F(r(e.colors,e.color));ye(()=>t.value,o=>{const i=new ug({value:o,enableAlpha:e.enableAlpha});n.value.forEach(l=>{l.selected=i.compare(l)})}),ur(()=>{n.value=r(e.colors,e.color)});function a(o){e.color.fromString(e.colors[o])}function r(o,i){return o.map(l=>{const s=new ug({value:l,enableAlpha:e.enableAlpha});return s.selected=s.compare(i),s})}return{rgbaColors:n,handleSelect:a}},hve=e=>{const t=ke("color-predefine"),n=D(()=>[t.b(),t.is("disabled",e.disabled)]),a=D(()=>t.e("colors"));function r(o){return[t.e("color-selector"),t.is("alpha",o.get("alpha")<100),{selected:o.selected}]}return{rootKls:n,colorsKls:a,colorSelectorKls:r}},pve=["disabled","aria-label","onClick"];var mve=q({name:"ElColorPredefine",__name:"predefine",props:fve,setup(e){const t=e,{rgbaColors:n,handleSelect:a}=vve(t),{rootKls:r,colorsKls:o,colorSelectorKls:i}=hve(t),{t:l}=Vt(),s=u=>l("el.colorpicker.predefineDescription",{value:u});return(u,c)=>(x(),O("div",{class:U(p(r))},[C("div",{class:U(p(o))},[(x(!0),O(Ve,null,dt(p(n),(d,v)=>(x(),O("button",{key:e.colors[v],type:"button",disabled:e.disabled,"aria-label":s(d.value),class:U(p(i)(d)),onClick:f=>p(a)(v)},[C("div",{style:et({backgroundColor:d.value})},null,4)],10,pve))),128))],2)],2))}}),gve=mve;const yve=$e({color:{type:ae(Object),required:!0},disabled:Boolean}),bve=e=>{const t=Et(),n=F(),a=F(0),r=F(0),o=F("hsl(0, 100%, 50%)"),i=D(()=>e.color.get("saturation")),l=D(()=>e.color.get("value")),s=D(()=>e.color.get("hue"));function u(h){var m;e.disabled||(h.target!==n.value&&c(h),(m=n.value)==null||m.focus({preventScroll:!0}))}function c(h){if(e.disabled)return;const m=t.vnode.el.getBoundingClientRect(),{clientX:g,clientY:y}=sg(h);let b=g-m.left,_=y-m.top;b=Math.max(0,b),b=Math.min(b,m.width),_=Math.max(0,_),_=Math.min(_,m.height),r.value=b,a.value=_,e.color.set({saturation:b/m.width*100,value:100-_/m.height*100})}function d(h){if(e.disabled)return;const{shiftKey:m}=h,g=mn(h),y=m?10:1;let b=!0;switch(g){case Pe.left:v(-y);break;case Pe.right:v(y);break;case Pe.up:f(y);break;case Pe.down:f(-y);break;default:b=!1;break}b&&h.preventDefault()}function v(h){let m=i.value+h;m=m<0?0:m>100?100:m,e.color.set("saturation",m)}function f(h){let m=l.value+h;m=m<0?0:m>100?100:m,e.color.set("value",m)}return{cursorRef:n,cursorTop:a,cursorLeft:r,background:o,saturation:i,brightness:l,hue:s,handleClick:u,handleDrag:c,handleKeydown:d}},_ve=(e,{cursorTop:t,cursorLeft:n,background:a,handleDrag:r})=>{const o=Et(),i=ke("color-svpanel");function l(){const s=e.color.get("saturation"),u=e.color.get("value"),{clientWidth:c,clientHeight:d}=o.vnode.el;n.value=s*c/100,t.value=(100-u)*d/100,a.value=`hsl(${e.color.get("hue")}, 100%, 50%)`}return mt(()=>{X4(o.vnode.el,{drag:s=>{r(s)},end:s=>{r(s)}}),l()}),ye([()=>e.color.get("hue"),()=>e.color.get("value"),()=>e.color.value],()=>l()),{rootKls:D(()=>i.b()),cursorKls:D(()=>i.e("cursor")),rootStyle:D(()=>({backgroundColor:a.value})),cursorStyle:D(()=>({top:Vn(t.value),left:Vn(n.value)})),update:l}},wve=["tabindex","aria-disabled","aria-label","aria-valuenow","aria-valuetext"];var Sve=q({name:"ElSvPanel",__name:"sv-panel",props:yve,setup(e,{expose:t}){const n=e,{cursorRef:a,cursorTop:r,cursorLeft:o,background:i,saturation:l,brightness:s,handleClick:u,handleDrag:c,handleKeydown:d}=bve(n),{rootKls:v,cursorKls:f,rootStyle:h,cursorStyle:m,update:g}=_ve(n,{cursorTop:r,cursorLeft:o,background:i,handleDrag:c}),{t:y}=Vt(),b=D(()=>y("el.colorpicker.svLabel")),_=D(()=>y("el.colorpicker.svDescription",{saturation:l.value,brightness:s.value,color:n.color.value}));return t({update:g}),(w,S)=>(x(),O("div",{class:U(p(v)),style:et(p(h)),onClick:S[1]||(S[1]=(...k)=>p(u)&&p(u)(...k))},[C("div",{ref_key:"cursorRef",ref:a,class:U(p(f)),style:et(p(m)),tabindex:e.disabled?void 0:0,"aria-disabled":e.disabled,role:"slider","aria-valuemin":"0,0","aria-valuemax":"100,100","aria-label":b.value,"aria-valuenow":`${p(l)},${p(s)}`,"aria-valuetext":_.value,onKeydown:S[0]||(S[0]=(...k)=>p(d)&&p(d)(...k))},null,46,wve)],6))}}),Cve=Sve;const OP=(e,t)=>{const n=nn(new ug({enableAlpha:e.showAlpha,format:e.colorFormat||"",value:e.modelValue}));return ye(()=>[e.colorFormat,e.showAlpha],()=>{n.enableAlpha=e.showAlpha,n.format=e.colorFormat||n.format,n.doOnChange(),t(bt,n.value)}),{color:n}};var xve=q({name:"ElColorPickerPanel",__name:"color-picker-panel",props:EP,emits:rve,setup(e,{expose:t,emit:n}){const a=e,r=n,o=ke("color-picker-panel"),{formItem:i}=Ea(),l=$n(),s=F(),u=F(),c=F(),d=F(),v=F(""),{color:f}=Ze(AP,()=>OP(a,r),!0);function h(){f.fromString(v.value),f.value!==v.value&&(v.value=f.value)}function m(){var y;a.validateEvent&&((y=i==null?void 0:i.validate)==null||y.call(i,"blur").catch(kt))}function g(){var y,b,_;(y=s.value)==null||y.update(),(b=u.value)==null||b.update(),(_=c.value)==null||_.update()}return mt(()=>{a.modelValue&&(v.value=f.value),He(g)}),ye(()=>a.modelValue,y=>{y!==f.value&&(y?f.fromString(y):f.clear())}),ye(()=>f.value,y=>{r(bt,y),v.value=y,a.validateEvent&&(i==null||i.validate("change").catch(kt))}),Ft(IP,{currentColor:D(()=>f.value)}),t({color:f,inputRef:d,update:g}),(y,b)=>(x(),O("div",{class:U([p(o).b(),p(o).is("disabled",p(l)),p(o).is("border",e.border)]),onFocusout:m},[C("div",{class:U(p(o).e("wrapper"))},[L(dve,{ref_key:"hueRef",ref:s,color:p(f),vertical:"",disabled:p(l),class:U(["hue-slider",e.hueSliderClass]),style:et(e.hueSliderStyle)},null,8,["color","disabled","class","style"]),L(Cve,{ref_key:"svRef",ref:u,color:p(f),disabled:p(l)},null,8,["color","disabled"])],2),e.showAlpha?(x(),ne(sve,{key:0,ref_key:"alphaRef",ref:c,color:p(f),disabled:p(l)},null,8,["color","disabled"])):te("v-if",!0),e.predefine?(x(),ne(gve,{key:1,ref:"predefine","enable-alpha":e.showAlpha,color:p(f),colors:e.predefine,disabled:p(l)},null,8,["enable-alpha","color","colors","disabled"])):te("v-if",!0),C("div",{class:U(p(o).e("footer"))},[L(p(Ba),{ref_key:"inputRef",ref:d,modelValue:v.value,"onUpdate:modelValue":b[0]||(b[0]=_=>v.value=_),"validate-event":!1,size:"small",disabled:p(l),onChange:h},null,8,["modelValue","disabled"]),ce(y.$slots,"footer")],2)],34))}}),kve=xve;const $P=wt(kve),Tve=$e({persistent:{type:Boolean,default:!0},modelValue:{type:ae(String),default:void 0},id:String,showAlpha:Boolean,colorFormat:{type:ae(String)},disabled:{type:Boolean,default:void 0},clearable:{type:Boolean,default:!0},size:pa,popperClass:Tn.popperClass,popperStyle:Tn.popperStyle,tabindex:{type:[String,Number],default:0},teleported:Tn.teleported,appendTo:Tn.appendTo,predefine:{type:ae(Array)},validateEvent:{type:Boolean,default:!0},...Mc,...dr(["ariaLabel"])}),Mve={[bt]:e=>Ye(e)||Un(e),[Ot]:e=>Ye(e)||Un(e),activeChange:e=>Ye(e)||Un(e),focus:e=>e instanceof FocusEvent,blur:e=>e instanceof FocusEvent,clear:()=>!0},Eve=["id","aria-label","aria-labelledby","aria-description","aria-disabled","tabindex"];var Ave=q({name:"ElColorPicker",__name:"color-picker",props:Tve,emits:Mve,setup(e,{expose:t,emit:n}){const a=e,r=n,{t:o}=Vt(),i=ke("color"),{formItem:l}=Ea(),s=ca(),u=$n(),{valueOnClear:c,isEmptyValue:d}=op(a,null),v=OP(a,r),{inputId:f,isLabeledByFormItem:h}=Yr(a,{formItemContext:l}),m=F(),g=F(),y=F(),b=F(!1),_=F(!1);let w=!0;const{isFocused:S,handleFocus:k,handleBlur:T}=Fl(g,{disabled:u,beforeBlur(J){var Z;return(Z=m.value)==null?void 0:Z.isFocusInsideContent(J)},afterBlur(){var J;H(!1),N(),a.validateEvent&&((J=l==null?void 0:l.validate)==null||J.call(l,"blur").catch(kt))}}),M=yY(()=>{var J;return((J=y.value)==null?void 0:J.color)??v.color}),E=D(()=>ji(a,Object.keys(EP))),A=D(()=>!a.modelValue&&!_.value?"transparent":K(M,a.showAlpha)),I=D(()=>!a.modelValue&&!_.value?"":M.value),P=D(()=>h.value?void 0:a.ariaLabel||o("el.colorpicker.defaultLabel")),R=D(()=>h.value?l==null?void 0:l.labelId:void 0),G=D(()=>[i.b("picker"),i.is("disabled",u.value),i.bm("picker",s.value),i.is("focused",S.value)]);function K(J,Z){const{r:j,g:oe,b:se,a:fe}=J.toRgb();return Z?`rgba(${j}, ${oe}, ${se}, ${fe})`:`rgb(${j}, ${oe}, ${se})`}function H(J){b.value=J}const z=Yi(H,100,{leading:!0});function B(){u.value||H(!0)}function W(){z(!1),N()}function N(){He(()=>{a.modelValue?M.fromString(a.modelValue):(M.value="",He(()=>{_.value=!1}))})}function V(){u.value||(b.value&&N(),z(!b.value))}function Y(){const J=d(M.value)?c.value:M.value;r(bt,J),r(Ot,J),a.validateEvent&&(l==null||l.validate("change").catch(kt)),z(!1),He(()=>{const Z=new ug({enableAlpha:a.showAlpha,format:a.colorFormat||"",value:a.modelValue});M.compare(Z)||N()})}function X(){z(!1),r(bt,c.value),r(Ot,c.value),a.modelValue!==c.value&&a.validateEvent&&(l==null||l.validate("change").catch(kt)),N(),r("clear")}function ee(){var J,Z;(Z=(J=y==null?void 0:y.value)==null?void 0:J.inputRef)==null||Z.focus()}function re(){b.value&&(W(),S.value&&ve())}function ie(J){J.preventDefault(),J.stopPropagation(),H(!1),N()}function le(J){switch(mn(J)){case Pe.enter:case Pe.numpadEnter:case Pe.space:J.preventDefault(),J.stopPropagation(),B();break;case Pe.esc:ie(J);break}}function ve(){g.value.focus()}function he(){g.value.blur()}return ye(()=>I.value,J=>{w&&r("activeChange",J),w=!0}),ye(()=>M.value,()=>{!a.modelValue&&!_.value&&(_.value=!0)}),ye(()=>a.modelValue,J=>{J?J&&J!==M.value&&(w=!1,M.fromString(J)):_.value=!1}),ye(()=>b.value,()=>{y.value&&He(y.value.update)}),Ft(AP,v),t({color:M,show:B,hide:W,focus:ve,blur:he}),(J,Z)=>(x(),ne(p(Ma),{ref_key:"popper",ref:m,visible:b.value,"show-arrow":!1,"fallback-placements":["bottom","top","right","left"],offset:0,"gpu-acceleration":!1,"popper-class":[p(i).be("picker","panel"),e.popperClass],"popper-style":e.popperStyle,"stop-popper-mouse-event":!1,pure:"",loop:"",role:"dialog",effect:"light",trigger:"click",teleported:e.teleported,transition:`${p(i).namespace.value}-zoom-in-top`,persistent:e.persistent,"append-to":e.appendTo,onShow:ee,onHide:Z[2]||(Z[2]=j=>H(!1))},{content:$(()=>[st((x(),ne(p($P),Tt({ref_key:"pickerPanelRef",ref:y},E.value,{border:!1,"validate-event":!1,onKeydown:kn(ie,["esc"])}),{footer:$(()=>[C("div",null,[e.clearable?(x(),ne(p(xa),{key:0,class:U(p(i).be("footer","link-btn")),text:"",size:"small",onClick:X},{default:$(()=>[ue(Q(p(o)("el.colorpicker.clear")),1)]),_:1},8,["class"])):te("v-if",!0),L(p(xa),{plain:"",size:"small",class:U(p(i).be("footer","btn")),onClick:Y},{default:$(()=>[ue(Q(p(o)("el.colorpicker.confirm")),1)]),_:1},8,["class"])])]),_:1},16)),[[p(Ns),re,g.value]])]),default:$(()=>[C("div",Tt({id:p(f),ref_key:"triggerRef",ref:g},J.$attrs,{class:G.value,role:"button","aria-label":P.value,"aria-labelledby":R.value,"aria-description":p(o)("el.colorpicker.description",{color:e.modelValue||""}),"aria-disabled":p(u),tabindex:p(u)?void 0:e.tabindex,onKeydown:le,onFocus:Z[0]||(Z[0]=(...j)=>p(k)&&p(k)(...j)),onBlur:Z[1]||(Z[1]=(...j)=>p(T)&&p(T)(...j))}),[C("div",{class:U(p(i).be("picker","trigger")),onClick:V},[C("span",{class:U([p(i).be("picker","color"),p(i).is("alpha",e.showAlpha)])},[C("span",{class:U(p(i).be("picker","color-inner")),style:et({backgroundColor:A.value})},[st(L(p(Xe),{class:U([p(i).be("picker","icon"),p(i).is("icon-arrow-down")])},{default:$(()=>[L(p(ui))]),_:1},8,["class"]),[[Zt,e.modelValue||_.value]]),st(L(p(Xe),{class:U([p(i).be("picker","empty"),p(i).is("icon-close")])},{default:$(()=>[L(p(so))]),_:1},8,["class"]),[[Zt,!e.modelValue&&!_.value]])],6)],2)],2)],16,Eve)]),_:1},8,["visible","popper-class","popper-style","teleported","transition","persistent","append-to"]))}}),Ive=Ave;const Lve=wt(Ive);var Pve=q({name:"ElContainer",__name:"container",props:{direction:{type:String,required:!1}},setup(e){const t=e,n=Xn(),a=ke("container"),r=D(()=>t.direction==="vertical"?!0:t.direction==="horizontal"?!1:n&&n.default?n.default().some(o=>{const i=o.type.name;return i==="ElHeader"||i==="ElFooter"}):!1);return(o,i)=>(x(),O("section",{class:U([p(a).b(),p(a).is("vertical",r.value)])},[ce(o.$slots,"default")],2))}}),Dve=Pve,Ove=q({name:"ElAside",__name:"aside",props:{width:{type:[String,null],required:!1,default:null}},setup(e){const t=e,n=ke("aside"),a=D(()=>t.width?n.cssVarBlock({width:t.width}):{});return(r,o)=>(x(),O("aside",{class:U(p(n).b()),style:et(a.value)},[ce(r.$slots,"default")],6))}}),RP=Ove,$ve=q({name:"ElFooter",__name:"footer",props:{height:{type:[String,null],required:!1,default:null}},setup(e){const t=e,n=ke("footer"),a=D(()=>t.height?n.cssVarBlock({height:t.height}):{});return(r,o)=>(x(),O("footer",{class:U(p(n).b()),style:et(a.value)},[ce(r.$slots,"default")],6))}}),NP=$ve,Rve=q({name:"ElHeader",__name:"header",props:{height:{type:[String,null],required:!1,default:null}},setup(e){const t=e,n=ke("header"),a=D(()=>t.height?n.cssVarBlock({height:t.height}):{});return(r,o)=>(x(),O("header",{class:U(p(n).b()),style:et(a.value)},[ce(r.$slots,"default")],6))}}),BP=Rve,Nve=q({name:"ElMain",__name:"main",setup(e){const t=ke("main");return(n,a)=>(x(),O("main",{class:U(p(t).b())},[ce(n.$slots,"default")],2))}}),VP=Nve;const Bve=wt(Dve,{Aside:RP,Footer:NP,Header:BP,Main:VP}),Vve=On(RP),zve=On(NP),Fve=On(BP),Hve=On(VP),Wve=$e({format:{type:String,default:"HH:mm:ss"},prefix:String,suffix:String,title:String,value:{type:ae([Number,Object]),default:0},valueStyle:{type:ae([String,Object,Array,Boolean]),default:void 0}}),Kve={finish:()=>!0,[Ot]:e=>Je(e)},Uve=$e({decimalSeparator:{type:String,default:"."},groupSeparator:{type:String,default:","},precision:{type:Number,default:0},formatter:Function,value:{type:ae([Number,Object]),default:0},prefix:String,suffix:String,title:String,valueStyle:{type:ae([String,Object,Array,Boolean]),default:void 0}});var Gve=q({name:"ElStatistic",__name:"statistic",props:Uve,setup(e,{expose:t}){const n=e,a=ke("statistic"),r=D(()=>{const{value:o,formatter:i,precision:l,decimalSeparator:s,groupSeparator:u}=n;if(Qe(i))return i(o);if(!Je(o)||Number.isNaN(o))return o;let[c,d=""]=String(o).split(".");return d=d.padEnd(l,"0").slice(0,l>0?l:0),c=c.replace(/\B(?=(\d{3})+(?!\d))/g,u),[c,d].join(d?s:"")});return t({displayValue:r}),(o,i)=>(x(),O("div",{class:U(p(a).b())},[o.$slots.title||e.title?(x(),O("div",{key:0,class:U(p(a).e("head"))},[ce(o.$slots,"title",{},()=>[ue(Q(e.title),1)])],2)):te("v-if",!0),C("div",{class:U(p(a).e("content"))},[o.$slots.prefix||e.prefix?(x(),O("div",{key:0,class:U(p(a).e("prefix"))},[ce(o.$slots,"prefix",{},()=>[C("span",null,Q(e.prefix),1)])],2)):te("v-if",!0),C("span",{class:U(p(a).e("number")),style:et(e.valueStyle)},Q(r.value),7),o.$slots.suffix||e.suffix?(x(),O("div",{key:1,class:U(p(a).e("suffix"))},[ce(o.$slots,"suffix",{},()=>[C("span",null,Q(e.suffix),1)])],2)):te("v-if",!0)],2)],2))}}),Yve=Gve;const zP=wt(Yve),jve=[["Y",1e3*60*60*24*365],["M",1e3*60*60*24*30],["D",1e3*60*60*24],["H",1e3*60*60],["m",1e3*60],["s",1e3],["S",1]],Sx=e=>Je(e)?new Date(e).getTime():e.valueOf(),Cx=(e,t)=>{let n=e;return jve.reduce((a,[r,o])=>{const i=new RegExp(`${r}+(?![^\\[\\]]*\\])`,"g");if(i.test(a)){const l=Math.floor(n/o);return n-=l*o,a.replace(i,s=>String(l).padStart(s.length,"0"))}return a},t).replace(/\[([^\]]*)]/g,"$1")};var qve=q({name:"ElCountdown",__name:"countdown",props:Wve,emits:Kve,setup(e,{expose:t,emit:n}){const a=e,r=n;let o;const i=F(0),l=D(()=>Cx(i.value,a.format)),s=d=>Cx(d,a.format),u=()=>{o&&(ti(o),o=void 0)},c=()=>{const d=Sx(a.value),v=()=>{let f=d-Date.now();r(Ot,f),f<=0?(f=0,u(),r("finish")):o=Wr(v),i.value=f};o=Wr(v)};return mt(()=>{i.value=Sx(a.value)-Date.now(),ye(()=>[a.value,a.format],()=>{u(),c()},{immediate:!0})}),rn(()=>{u()}),t({displayValue:l}),(d,v)=>(x(),ne(p(zP),{value:i.value,title:e.title,prefix:e.prefix,suffix:e.suffix,"value-style":e.valueStyle,formatter:s},xr({_:2},[dt(d.$slots,(f,h)=>({name:h,fn:$(()=>[ce(d.$slots,h)])}))]),1032,["value","title","prefix","suffix","value-style"]))}}),Xve=qve;const Zve=wt(Xve),Jve=$e({valueFormat:String,dateFormat:String,timeFormat:String,disabled:{type:Boolean,default:void 0},modelValue:{type:ae([Date,Array,String,Number]),default:""},defaultValue:{type:ae([Date,Array])},defaultTime:{type:ae([Date,Array])},isRange:Boolean,...nw,disabledDate:{type:Function},cellClassName:{type:Function},shortcuts:{type:Array,default:()=>[]},arrowControl:Boolean,unlinkPanels:Boolean,showNow:{type:Boolean,default:!0},showConfirm:Boolean,showFooter:Boolean,showWeekNumber:Boolean,type:{type:ae(String),default:"date"},clearable:{type:Boolean,default:!0},border:{type:Boolean,default:!0},editable:{type:Boolean,default:!0}}),fw=Symbol("rootPickerContextKey"),Qve=["date","dates","year","years","month","months","week","range"],vw=$e({cellClassName:{type:ae(Function)},disabledDate:{type:ae(Function)},date:{type:ae(Object),required:!0},minDate:{type:ae(Object)},maxDate:{type:ae(Object)},parsedValue:{type:ae([Object,Array])},rangeState:{type:ae(Object),default:()=>({endDate:null,selecting:!1})},disabled:Boolean}),FP=$e({type:{type:ae(String),required:!0,values:PF},dateFormat:String,timeFormat:String,showNow:{type:Boolean,default:!0},showConfirm:Boolean,showFooter:{type:Boolean,default:!0},showWeekNumber:Boolean,border:Boolean,disabled:Boolean,editable:{type:Boolean,default:!0}}),hw=$e({unlinkPanels:Boolean,visible:{type:Boolean,default:!0},showConfirm:Boolean,showFooter:{type:Boolean,default:!0},border:Boolean,disabled:Boolean,parsedValue:{type:ae(Array)},singlePanel:Boolean}),pw=e=>({type:String,values:Qve,default:e}),ehe=$e({...FP,parsedValue:{type:ae([Object,Array])},visible:{type:Boolean,default:!0},format:{type:String,default:""}}),bh=e=>{if(!Ae(e))return!1;const[t,n]=e;return gt.isDayjs(t)&>.isDayjs(n)&>(t).isValid()&>(n).isValid()&&t.isSameOrBefore(n)},G1=(e,{lang:t,step:n=1,unit:a,unlinkPanels:r})=>{let o;if(Ae(e)){let[i,l]=e.map(s=>gt(s).locale(t));return r||(l=i.add(n,a)),[i,l]}else e?o=gt(e):o=gt();return o=o.locale(t),[o,o.add(n,a)]},the=(e,t,{columnIndexOffset:n,startDate:a,nextEndDate:r,now:o,unit:i,relativeDateGetter:l,setCellMetadata:s,setRowMetadata:u})=>{for(let c=0;c{const r=gt().locale(a).startOf("month").month(n).year(t).hour(e.hour()).minute(e.minute()).second(e.second());return xs(r.daysInMonth()).map(o=>r.add(o,"day").toDate())},Gd=(e,t,n,a,r)=>{const o=gt().year(t).month(n).startOf("month").hour(e.hour()).minute(e.minute()).second(e.second()),i=cg(e,t,n,a).find(l=>!(r!=null&&r(l)));return i?gt(i).locale(a):o.locale(a)},dg=(e,t,n)=>{const a=e.year();if(!(n!=null&&n(e.toDate())))return e.locale(t);const r=e.month();if(!cg(e,a,r,t).every(n))return Gd(e,a,r,t,n);for(let o=0;o<12;o++)if(!cg(e,a,o,t).every(n))return Gd(e,a,o,t,n);return e},Yd=(e,t,n)=>{if(Ae(e))return e.map(a=>Yd(a,t,n));if(Ye(e)){let a=gt(e,t);if(a.isValid()||(a=gt(e)),!a.isValid())return a;const r=a.format(t);return gt(r,t,!0).isValid()?gt(r,t).locale(n):gt("")}return gt(e,t).locale(n)},nhe=$e({...vw,showWeekNumber:Boolean,selectionMode:pw("date")}),ahe=["changerange","pick","select"],fg=(e="")=>["normal","today"].includes(e),rhe=(e,t)=>{const{lang:n}=Vt(),a=F(),r=F(),o=F(),i=F(),l=F([[],[],[],[],[],[]]);let s=!1;const u=e.date.$locale().weekStart||7,c=e.date.locale("en").localeData().weekdaysShort().map(W=>W.toLowerCase()),d=D(()=>u>3?7-u:-u),v=D(()=>{const W=e.date.startOf("month");return W.subtract(W.day()||7,"day")}),f=D(()=>c.concat(c).slice(u,u+7)),h=D(()=>Wm(p(w)).some(W=>W.isCurrent)),m=D(()=>{const W=e.date.startOf("month");return{startOfMonthDay:W.day()||7,dateCountOfMonth:W.daysInMonth(),dateCountOfLastMonth:W.subtract(1,"month").daysInMonth()}}),g=D(()=>e.selectionMode==="dates"?or(e.parsedValue):[]),y=(W,{count:N,rowIndex:V,columnIndex:Y})=>{const{startOfMonthDay:X,dateCountOfMonth:ee,dateCountOfLastMonth:re}=p(m),ie=p(d);if(V>=0&&V<=1){const le=X+ie<0?7+X+ie:X+ie;if(Y+V*7>=le)return W.text=N,!0;W.text=re-(le-Y%7)+1+V*7,W.type="prev-month"}else return N<=ee?W.text=N:(W.text=N-ee,W.type="next-month"),!0;return!1},b=(W,{columnIndex:N,rowIndex:V},Y)=>{const{disabledDate:X,cellClassName:ee}=e,re=p(g),ie=y(W,{count:Y,rowIndex:V,columnIndex:N}),le=W.dayjs.toDate();return W.selected=re.find(ve=>ve.isSame(W.dayjs,"day")),W.isSelected=!!W.selected,W.isCurrent=k(W),W.disabled=X==null?void 0:X(le),W.customClass=ee==null?void 0:ee(le),ie},_=W=>{if(e.selectionMode==="week"){const[N,V]=e.showWeekNumber?[1,7]:[0,6],Y=B(W[N+1]);W[N].inRange=Y,W[N].start=Y,W[V].inRange=Y,W[V].end=Y}},w=D(()=>{const{minDate:W,maxDate:N,rangeState:V,showWeekNumber:Y}=e,X=p(d),ee=p(l),re="day";let ie=1;if(the({row:6,column:7},ee,{startDate:W,columnIndexOffset:Y?1:0,nextEndDate:V.endDate||N||V.selecting&&W||null,now:gt().locale(p(n)).startOf(re),unit:re,relativeDateGetter:le=>p(v).add(le-X,re),setCellMetadata:(...le)=>{b(...le,ie)&&(ie+=1)},setRowMetadata:_}),Y)for(let le=0;le<6;le++)ee[le][1].dayjs&&(ee[le][0]={type:"week",text:ee[le][1].dayjs.week()});return ee});ye(()=>e.date,async()=>{var W;(W=p(a))!=null&&W.contains(document.activeElement)&&(await He(),await S())});const S=async()=>{var W;return(W=p(r))==null?void 0:W.focus()},k=W=>e.selectionMode==="date"&&fg(W.type)&&T(W,e.parsedValue),T=(W,N)=>N?gt(N).locale(p(n)).isSame(e.date.date(Number(W.text)),"day"):!1,M=(W,N)=>{const V=p(m).startOfMonthDay,Y=p(d),X=V+Y<0?7+V+Y:V+Y,ee=W*7+(N-(e.showWeekNumber?1:0));return e.date.startOf("month").subtract(X,"day").add(ee,"day")},E=W=>{var X;if(!e.rangeState.selecting)return;let N=W.target;if(N.tagName==="SPAN"&&(N=(X=N.parentNode)==null?void 0:X.parentNode),N.tagName==="DIV"&&(N=N.parentNode),N.tagName!=="TD")return;const V=N.parentNode.rowIndex-1,Y=N.cellIndex;p(w)[V][Y].disabled||(V!==p(o)||Y!==p(i))&&(o.value=V,i.value=Y,t("changerange",{selecting:!0,endDate:M(V,Y)}))},A=W=>!p(h)&&(W==null?void 0:W.text)===1&&fg(W.type)||W.isCurrent,I=W=>{s||p(h)||e.selectionMode!=="date"||z(W,!0)},P=W=>{W.target.closest("td")&&(s=!0)},R=W=>{W.target.closest("td")&&(s=!1)},G=W=>{!e.rangeState.selecting||!e.minDate?(t("pick",{minDate:W,maxDate:null}),t("select",!0)):(W>=e.minDate?t("pick",{minDate:e.minDate,maxDate:W}):t("pick",{minDate:W,maxDate:e.minDate}),t("select",!1))},K=W=>{const N=W.week(),V=`${W.year()}w${N}`;t("pick",{year:W.year(),week:N,value:V,date:W.startOf("week")})},H=(W,N)=>{t("pick",N?or(e.parsedValue).filter(V=>(V==null?void 0:V.valueOf())!==W.valueOf()):or(e.parsedValue).concat([W]))},z=(W,N=!1)=>{if(e.disabled)return;const V=W.target.closest("td");if(!V)return;const Y=V.parentNode.rowIndex-1,X=V.cellIndex,ee=p(w)[Y][X];if(ee.disabled||ee.type==="week")return;const re=M(Y,X);switch(e.selectionMode){case"range":G(re);break;case"date":t("pick",re,N);break;case"week":K(re);break;case"dates":H(re,!!ee.selected);break}},B=W=>{if(e.selectionMode!=="week")return!1;let N=e.date.startOf("day");if(W.type==="prev-month"&&(N=N.subtract(1,"month")),W.type==="next-month"&&(N=N.add(1,"month")),N=N.date(Number.parseInt(W.text,10)),e.parsedValue&&!Ae(e.parsedValue)){const V=(e.parsedValue.day()-u+7)%7-1;return e.parsedValue.subtract(V,"day").isSame(N,"day")}return!1};return{WEEKS:f,rows:w,tbodyRef:a,currentCellRef:r,focus:S,isCurrent:k,isWeekActive:B,isSelectedCell:A,handlePickDate:z,handleMouseUp:R,handleMouseDown:P,handleMouseMove:E,handleFocus:I}},ohe=(e,{isCurrent:t,isWeekActive:n})=>{const a=ke("date-table"),{t:r}=Vt(),o=D(()=>[a.b(),a.is("week-mode",e.selectionMode==="week"&&!e.disabled)]),i=D(()=>r("el.datepicker.dateTablePrompt")),l=u=>{const c=[];return fg(u.type)&&!u.disabled?(c.push("available"),u.type==="today"&&c.push("today")):c.push(u.type),t(u)&&c.push("current"),u.inRange&&(fg(u.type)||e.selectionMode==="week")&&(c.push("in-range"),u.start&&c.push("start-date"),u.end&&c.push("end-date")),(u.disabled||e.disabled)&&c.push("disabled"),u.selected&&c.push("selected"),u.customClass&&c.push(u.customClass),c.join(" ")},s=u=>[a.e("row"),{current:n(u)}];return{tableKls:o,tableLabel:i,weekHeaderClass:a.e("week-header"),getCellClasses:l,getRowKls:s,t:r}},ihe=$e({cell:{type:ae(Object)}});var mw=q({name:"ElDatePickerCell",props:ihe,setup(e){const t=ke("date-table-cell"),{slots:n}=Ze(fw);return()=>{const{cell:a}=e;return ce(n,"default",{...a},()=>[L("div",{class:t.b()},[L("span",{class:t.e("text")},[(a==null?void 0:a.renderText)??(a==null?void 0:a.text)])])])}}});const lhe=["aria-label"],she=["aria-label"],uhe=["aria-current","aria-selected","tabindex","aria-disabled"];var che=q({__name:"basic-date-table",props:nhe,emits:ahe,setup(e,{expose:t,emit:n}){const a=e,{WEEKS:r,rows:o,tbodyRef:i,currentCellRef:l,focus:s,isCurrent:u,isWeekActive:c,isSelectedCell:d,handlePickDate:v,handleMouseUp:f,handleMouseDown:h,handleMouseMove:m,handleFocus:g}=rhe(a,n),{tableLabel:y,tableKls:b,getCellClasses:_,getRowKls:w,weekHeaderClass:S,t:k}=ohe(a,{isCurrent:u,isWeekActive:c});let T=!1;return rn(()=>{T=!0}),t({focus:s}),(M,E)=>(x(),O("table",{"aria-label":p(y),class:U(p(b)),cellspacing:"0",cellpadding:"0",role:"grid",onClick:E[1]||(E[1]=(...A)=>p(v)&&p(v)(...A)),onMousemove:E[2]||(E[2]=(...A)=>p(m)&&p(m)(...A)),onMousedown:E[3]||(E[3]=(...A)=>p(h)&&p(h)(...A)),onMouseup:E[4]||(E[4]=(...A)=>p(f)&&p(f)(...A))},[C("tbody",{ref_key:"tbodyRef",ref:i},[C("tr",null,[M.showWeekNumber?(x(),O("th",{key:0,scope:"col",class:U(p(S))},null,2)):te("v-if",!0),(x(!0),O(Ve,null,dt(p(r),(A,I)=>(x(),O("th",{key:I,"aria-label":p(k)("el.datepicker.weeksFull."+A),scope:"col"},Q(p(k)("el.datepicker.weeks."+A)),9,she))),128))]),(x(!0),O(Ve,null,dt(p(o),(A,I)=>(x(),O("tr",{key:I,class:U(p(w)(M.showWeekNumber?A[2]:A[1]))},[(x(!0),O(Ve,null,dt(A,(P,R)=>(x(),O("td",{key:`${I}.${R}`,ref_for:!0,ref:G=>!p(T)&&p(d)(P)&&(l.value=G),class:U(p(_)(P)),"aria-current":P.isCurrent?"date":void 0,"aria-selected":P.isCurrent,tabindex:M.disabled?void 0:p(d)(P)?0:-1,"aria-disabled":M.disabled,onFocus:E[0]||(E[0]=(...G)=>p(g)&&p(g)(...G))},[L(p(mw),{cell:P},null,8,["cell"])],42,uhe))),128))],2))),128))],512)],42,lhe))}}),Z4=che;const dhe=$e({...vw,selectionMode:pw("month")}),fhe=["aria-label"],vhe=["aria-selected","aria-label","tabindex","onKeydown"];var hhe=q({__name:"basic-month-table",props:dhe,emits:["changerange","pick","select"],setup(e,{expose:t,emit:n}){const a=e,r=n,o=ke("month-table"),{t:i,lang:l}=Vt(),s=F(),u=F(),c=F(a.date.locale("en").localeData().monthsShort().map(w=>w.toLowerCase())),d=F([[],[],[]]),v=F(),f=F(),h=D(()=>{var k,T,M;const w=d.value,S=gt().locale(l.value).startOf("month");for(let E=0;E<3;E++){const A=w[E];for(let I=0;I<4;I++){const P=A[I]||(A[I]={row:E,column:I,type:"normal",inRange:!1,start:!1,end:!1,text:-1,disabled:!1,isSelected:!1,customClass:void 0,date:void 0,dayjs:void 0,isCurrent:void 0,selected:void 0,renderText:void 0,timestamp:void 0});P.type="normal";const R=E*4+I,G=a.date.startOf("year").month(R),K=a.rangeState.endDate||a.maxDate||a.rangeState.selecting&&a.minDate||null;P.inRange=!!(a.minDate&&G.isSameOrAfter(a.minDate,"month")&&K&&G.isSameOrBefore(K,"month"))||!!(a.minDate&&G.isSameOrBefore(a.minDate,"month")&&K&&G.isSameOrAfter(K,"month")),(k=a.minDate)!=null&&k.isSameOrAfter(K)?(P.start=!!(K&&G.isSame(K,"month")),P.end=a.minDate&&G.isSame(a.minDate,"month")):(P.start=!!(a.minDate&&G.isSame(a.minDate,"month")),P.end=!!(K&&G.isSame(K,"month"))),S.isSame(G)&&(P.type="today");const H=G.toDate();P.text=R,P.disabled=((T=a.disabledDate)==null?void 0:T.call(a,H))||!1,P.date=H,P.customClass=(M=a.cellClassName)==null?void 0:M.call(a,H),P.dayjs=G,P.timestamp=G.valueOf(),P.isSelected=y(P)}}return w}),m=()=>{var w;(w=u.value)==null||w.focus()},g=w=>{const S={},k=a.date.year(),T=new Date,M=w.text;return S.disabled=a.disabled||(a.disabledDate?cg(a.date,k,M,l.value).every(a.disabledDate):!1),S.current=or(a.parsedValue).some(E=>gt.isDayjs(E)&&E.year()===k&&E.month()===M),S.today=T.getFullYear()===k&&T.getMonth()===M,w.customClass&&(S[w.customClass]=!0),w.inRange&&(S["in-range"]=!0,w.start&&(S["start-date"]=!0),w.end&&(S["end-date"]=!0)),S},y=w=>{const S=a.date.year(),k=w.text;return or(a.date).some(T=>T.year()===S&&T.month()===k)},b=w=>{var M;if(!a.rangeState.selecting)return;let S=w.target;if(S.tagName==="SPAN"&&(S=(M=S.parentNode)==null?void 0:M.parentNode),S.tagName==="DIV"&&(S=S.parentNode),S.tagName!=="TD")return;const k=S.parentNode.rowIndex,T=S.cellIndex;h.value[k][T].disabled||(k!==v.value||T!==f.value)&&(v.value=k,f.value=T,r("changerange",{selecting:!0,endDate:a.date.startOf("year").month(k*4+T)}))},_=w=>{var E;if(a.disabled)return;const S=(E=w.target)==null?void 0:E.closest("td");if((S==null?void 0:S.tagName)!=="TD"||Ni(S,"disabled"))return;const k=S.cellIndex,T=S.parentNode.rowIndex*4+k,M=a.date.startOf("year").month(T);if(a.selectionMode==="months"){if(w.type==="keydown"){r("pick",or(a.parsedValue),!1);return}const A=Gd(a.date,a.date.year(),T,l.value,a.disabledDate);r("pick",Ni(S,"current")?or(a.parsedValue).filter(I=>(I==null?void 0:I.year())!==A.year()||(I==null?void 0:I.month())!==A.month()):or(a.parsedValue).concat([gt(A)]))}else a.selectionMode==="range"?a.rangeState.selecting?(a.minDate&&M>=a.minDate?r("pick",{minDate:a.minDate,maxDate:M}):r("pick",{minDate:M,maxDate:a.minDate}),r("select",!1)):(r("pick",{minDate:M,maxDate:null}),r("select",!0)):r("pick",T)};return ye(()=>a.date,async()=>{var w,S;(w=s.value)!=null&&w.contains(document.activeElement)&&(await He(),(S=u.value)==null||S.focus())}),t({focus:m}),(w,S)=>(x(),O("table",{role:"grid","aria-label":p(i)("el.datepicker.monthTablePrompt"),class:U(p(o).b()),onClick:_,onMousemove:b},[C("tbody",{ref_key:"tbodyRef",ref:s},[(x(!0),O(Ve,null,dt(h.value,(k,T)=>(x(),O("tr",{key:T},[(x(!0),O(Ve,null,dt(k,(M,E)=>(x(),O("td",{key:E,ref_for:!0,ref:A=>M.isSelected&&(u.value=A),class:U(g(M)),"aria-selected":!!M.isSelected,"aria-label":p(i)(`el.datepicker.month${+M.text+1}`),tabindex:M.isSelected?0:-1,onKeydown:[kn(at(_,["prevent","stop"]),["space"]),kn(at(_,["prevent","stop"]),["enter"])]},[L(p(mw),{cell:{...M,renderText:p(i)("el.datepicker.months."+c.value[M.text])}},null,8,["cell"])],42,vhe))),128))]))),128))],512)],42,fhe))}}),_h=hhe;const phe=$e({...vw,selectionMode:pw("year")}),mhe=["aria-label"],ghe=["aria-selected","aria-label","tabindex","onKeydown"];var yhe=q({__name:"basic-year-table",props:phe,emits:["changerange","pick","select"],setup(e,{expose:t,emit:n}){const a=(S,k)=>{const T=gt(String(S)).locale(k).startOf("year");return xs(T.endOf("year").dayOfYear()).map(M=>T.add(M,"day").toDate())},r=e,o=n,i=ke("year-table"),{t:l,lang:s}=Vt(),u=F(),c=F(),d=D(()=>Math.floor(r.date.year()/10)*10),v=F([[],[],[]]),f=F(),h=F(),m=D(()=>{var T,M,E;const S=v.value,k=gt().locale(s.value).startOf("year");for(let A=0;A<3;A++){const I=S[A];for(let P=0;P<4&&!(A*4+P>=10);P++){let R=I[P];R||(R={row:A,column:P,type:"normal",inRange:!1,start:!1,end:!1,text:-1,disabled:!1,isSelected:!1,customClass:void 0,date:void 0,dayjs:void 0,isCurrent:void 0,selected:void 0,renderText:void 0,timestamp:void 0}),R.type="normal";const G=A*4+P+d.value,K=gt().year(G),H=r.rangeState.endDate||r.maxDate||r.rangeState.selecting&&r.minDate||null;R.inRange=!!(r.minDate&&K.isSameOrAfter(r.minDate,"year")&&H&&K.isSameOrBefore(H,"year"))||!!(r.minDate&&K.isSameOrBefore(r.minDate,"year")&&H&&K.isSameOrAfter(H,"year")),(T=r.minDate)!=null&&T.isSameOrAfter(H)?(R.start=!!(H&&K.isSame(H,"year")),R.end=!!(r.minDate&&K.isSame(r.minDate,"year"))):(R.start=!!(r.minDate&&K.isSame(r.minDate,"year")),R.end=!!(H&&K.isSame(H,"year"))),k.isSame(K)&&(R.type="today"),R.text=G;const z=K.toDate();R.disabled=((M=r.disabledDate)==null?void 0:M.call(r,z))||!1,R.date=z,R.customClass=(E=r.cellClassName)==null?void 0:E.call(r,z),R.dayjs=K,R.timestamp=K.valueOf(),R.isSelected=b(R),I[P]=R}}return S}),g=()=>{var S;(S=c.value)==null||S.focus()},y=S=>{const k={},T=gt().locale(s.value),M=S.text;return k.disabled=r.disabled||(r.disabledDate?a(M,s.value).every(r.disabledDate):!1),k.today=T.year()===M,k.current=or(r.parsedValue).some(E=>E.year()===M),S.customClass&&(k[S.customClass]=!0),S.inRange&&(k["in-range"]=!0,S.start&&(k["start-date"]=!0),S.end&&(k["end-date"]=!0)),k},b=S=>{const k=S.text;return or(r.date).some(T=>T.year()===k)},_=S=>{var A;if(r.disabled)return;const k=(A=S.target)==null?void 0:A.closest("td");if(!k||!k.textContent||Ni(k,"disabled"))return;const T=k.cellIndex,M=k.parentNode.rowIndex*4+T+d.value,E=gt().year(M);if(r.selectionMode==="range")r.rangeState.selecting?(r.minDate&&E>=r.minDate?o("pick",{minDate:r.minDate,maxDate:E}):o("pick",{minDate:E,maxDate:r.minDate}),o("select",!1)):(o("pick",{minDate:E,maxDate:null}),o("select",!0));else if(r.selectionMode==="years"){if(S.type==="keydown"){o("pick",or(r.parsedValue),!1);return}const I=dg(E.startOf("year"),s.value,r.disabledDate);o("pick",Ni(k,"current")?or(r.parsedValue).filter(P=>(P==null?void 0:P.year())!==M):or(r.parsedValue).concat([I]))}else o("pick",M)},w=S=>{var E;if(!r.rangeState.selecting)return;const k=(E=S.target)==null?void 0:E.closest("td");if(!k)return;const T=k.parentNode.rowIndex,M=k.cellIndex;m.value[T][M].disabled||(T!==f.value||M!==h.value)&&(f.value=T,h.value=M,o("changerange",{selecting:!0,endDate:gt().year(d.value).add(T*4+M,"year")}))};return ye(()=>r.date,async()=>{var S,k;(S=u.value)!=null&&S.contains(document.activeElement)&&(await He(),(k=c.value)==null||k.focus())}),t({focus:g}),(S,k)=>(x(),O("table",{role:"grid","aria-label":p(l)("el.datepicker.yearTablePrompt"),class:U(p(i).b()),onClick:_,onMousemove:w},[C("tbody",{ref_key:"tbodyRef",ref:u},[(x(!0),O(Ve,null,dt(m.value,(T,M)=>(x(),O("tr",{key:M},[(x(!0),O(Ve,null,dt(T,(E,A)=>(x(),O("td",{key:`${M}_${A}`,ref_for:!0,ref:I=>E.isSelected&&(c.value=I),class:U(["available",y(E)]),"aria-selected":E.isSelected,"aria-label":String(E.text),tabindex:E.isSelected?0:-1,onKeydown:[kn(at(_,["prevent","stop"]),["space"]),kn(at(_,["prevent","stop"]),["enter"])]},[L(p(mw),{cell:E},null,8,["cell"])],42,ghe))),128))]))),128))],512)],42,mhe))}}),wh=yhe;const bhe=["disabled","onClick"],_he=["aria-label","disabled"],whe=["aria-label","disabled"],She=["tabindex","aria-disabled"],Che=["tabindex","aria-disabled"],xhe=["aria-label","disabled"],khe=["aria-label","disabled"];var The=q({__name:"panel-date-pick",props:ehe,emits:["pick","set-picker-option","panel-change"],setup(e,{emit:t}){const n=(_e,Ne,tt)=>!0,a=e,r=t,o=ke("picker-panel"),i=ke("date-picker"),l=Nl(),s=Xn(),{t:u,lang:c}=Vt(),d=Ze(nl),{shortcuts:v,disabledDate:f,cellClassName:h,defaultTime:m}=d.props,g=cn(d.props,"defaultValue"),y=F(),b=F(gt().locale(c.value)),_=F(!1);let w=!1;const S=D(()=>gt(m).locale(c.value)),k=D(()=>b.value.month()),T=D(()=>b.value.year()),M=F([]),E=F(null),A=F(null),I=_e=>M.value.length>0?n(_e,M.value,a.format||"HH:mm:ss"):!0,P=_e=>m&&!Se.value&&!_.value&&!w?S.value.year(_e.year()).month(_e.month()).date(_e.date()):ve.value?_e.millisecond(0):_e.startOf("day"),R=(_e,...Ne)=>{_e?Ae(_e)?r("pick",_e.map(P),...Ne):r("pick",P(_e),...Ne):r("pick",_e,...Ne),E.value=null,A.value=null,_.value=!1,w=!1},G=async(_e,Ne)=>{if(N.value==="date"&>.isDayjs(_e)){const tt=Pf(a.parsedValue);let Me=tt?tt.year(_e.year()).month(_e.month()).date(_e.date()):_e;I(Me),b.value=Me,R(Me,ve.value||Ne)}else N.value==="week"?R(_e.date):N.value==="dates"&&R(_e,!0)},K=_e=>{const Ne=_e?"add":"subtract";b.value=b.value[Ne](1,"month"),Qt("month")},H=_e=>{const Ne=b.value,tt=_e?"add":"subtract";b.value=z.value==="year"?Ne[tt](10,"year"):Ne[tt](1,"year"),Qt("year")},z=F("date"),B=D(()=>{const _e=u("el.datepicker.year");if(z.value==="year"){const Ne=Math.floor(T.value/10)*10;return _e?`${Ne} ${_e} - ${Ne+9} ${_e}`:`${Ne} - ${Ne+9}`}return`${T.value} ${_e}`}),W=_e=>{const Ne=Qe(_e.value)?_e.value():_e.value;if(Ne){w=!0,R(gt(Ne).locale(c.value));return}_e.onClick&&_e.onClick({attrs:l,slots:s,emit:r})},N=D(()=>{const{type:_e}=a;return["week","month","months","year","years","dates"].includes(_e)?_e:"date"}),V=D(()=>N.value==="dates"||N.value==="months"||N.value==="years"),Y=D(()=>N.value==="date"?z.value:N.value),X=D(()=>!!v.length),ee=async(_e,Ne)=>{N.value==="month"?(b.value=Gd(b.value,b.value.year(),_e,c.value,f),R(b.value,!1)):N.value==="months"?R(_e,Ne??!0):(b.value=Gd(b.value,b.value.year(),_e,c.value,f),z.value="date",["month","year","date","week"].includes(N.value)&&(R(b.value,!0),await He(),Pt())),Qt("month")},re=async(_e,Ne)=>{N.value==="year"?(b.value=dg(b.value.startOf("year").year(_e),c.value,f),R(b.value,!1)):N.value==="years"?R(_e,Ne??!0):(b.value=dg(b.value.year(_e),c.value,f),z.value="month",["month","year","date","week"].includes(N.value)&&(R(b.value,!0),await He(),Pt())),Qt("year")},ie=$n(),le=async _e=>{ie.value||(z.value=_e,await He(),Pt())},ve=D(()=>a.type==="datetime"||a.type==="datetimerange"),he=D(()=>{const _e=ve.value||N.value==="dates",Ne=N.value==="years",tt=N.value==="months",Me=z.value==="date",Ue=z.value==="year",Gt=z.value==="month";return _e&&Me||Ne&&Ue||tt&&Gt}),J=D(()=>!V.value&&a.showNow||a.showConfirm),Z=D(()=>f?a.parsedValue?Ae(a.parsedValue)?f(a.parsedValue[0].toDate()):f(a.parsedValue.toDate()):!0:!1),j=()=>{if(V.value)R(a.parsedValue);else{let _e=Pf(a.parsedValue);if(!_e){const Ne=gt(m).locale(c.value),tt=nt();_e=Ne.year(tt.year()).month(tt.month()).date(tt.date())}b.value=_e,R(_e)}},oe=D(()=>f?f(gt().locale(c.value).toDate()):!1),se=()=>{const _e=gt().locale(c.value).toDate();_.value=!0,(!f||!f(_e))&&I(_e)&&(b.value=gt().locale(c.value),R(b.value))},fe=D(()=>a.timeFormat||SL(a.format)||"HH:mm:ss"),ge=D(()=>a.dateFormat||wL(a.format)||"YYYY-MM-DD"),Se=D(()=>{if(A.value)return A.value;if(!(!a.parsedValue&&!g.value))return(Pf(a.parsedValue)||b.value).format(fe.value)}),De=D(()=>{if(E.value)return E.value;if(!(!a.parsedValue&&!g.value))return(Pf(a.parsedValue)||b.value).format(ge.value)}),Te=F(!1),Re=()=>{Te.value=!0},Ce=()=>{Te.value=!1},Le=_e=>({hour:_e.hour(),minute:_e.minute(),second:_e.second(),year:_e.year(),month:_e.month(),date:_e.date()}),me=(_e,Ne,tt)=>{const{hour:Me,minute:Ue,second:Gt}=Le(_e),je=Pf(a.parsedValue);b.value=je?je.hour(Me).minute(Ue).second(Gt):_e,R(b.value,!0),tt||(Te.value=Ne)},Be=_e=>{const Ne=gt(_e,fe.value).locale(c.value);if(Ne.isValid()&&I(Ne)){const{year:tt,month:Me,date:Ue}=Le(b.value);b.value=Ne.year(tt).month(Me).date(Ue),A.value=null,Te.value=!1,R(b.value,!0)}},ze=_e=>{const Ne=Yd(_e,ge.value,c.value);if(Ne.isValid()){if(f&&f(Ne.toDate()))return;const{hour:tt,minute:Me,second:Ue}=Le(b.value);b.value=Ne.hour(tt).minute(Me).second(Ue),E.value=null,R(b.value,!0)}},xe=_e=>gt.isDayjs(_e)&&_e.isValid()&&(f?!f(_e.toDate()):!0),rt=_e=>Yd(_e,a.format,c.value),nt=()=>{const _e=gt(g.value).locale(c.value);if(!g.value){const Ne=S.value;return gt().hour(Ne.hour()).minute(Ne.minute()).second(Ne.second()).locale(c.value)}return _e},Pt=()=>{var _e;["week","month","year","date"].includes(N.value)&&((_e=y.value)==null||_e.focus())},Wt=()=>{Pt(),N.value==="week"&&Rt(Pe.down)},Jt=_e=>{const Ne=mn(_e);[Pe.up,Pe.down,Pe.left,Pe.right,Pe.home,Pe.end,Pe.pageUp,Pe.pageDown].includes(Ne)&&(Rt(Ne),_e.stopPropagation(),_e.preventDefault()),[Pe.enter,Pe.space,Pe.numpadEnter].includes(Ne)&&E.value===null&&A.value===null&&(_e.preventDefault(),R(b.value,!1))},Rt=_e=>{const{up:Ne,down:tt,left:Me,right:Ue,home:Gt,end:je,pageUp:ft,pageDown:ht}=Pe,pt={year:{[Ne]:-4,[tt]:4,[Me]:-1,[Ue]:1,offset:(Ct,$t)=>Ct.setFullYear(Ct.getFullYear()+$t)},month:{[Ne]:-4,[tt]:4,[Me]:-1,[Ue]:1,offset:(Ct,$t)=>Ct.setMonth(Ct.getMonth()+$t)},week:{[Ne]:-1,[tt]:1,[Me]:-1,[Ue]:1,offset:(Ct,$t)=>Ct.setDate(Ct.getDate()+$t*7)},date:{[Ne]:-7,[tt]:7,[Me]:-1,[Ue]:1,[Gt]:Ct=>-Ct.getDay(),[je]:Ct=>-Ct.getDay()+6,[ft]:Ct=>-new Date(Ct.getFullYear(),Ct.getMonth(),0).getDate(),[ht]:Ct=>new Date(Ct.getFullYear(),Ct.getMonth()+1,0).getDate(),offset:(Ct,$t)=>Ct.setDate(Ct.getDate()+$t)}},It=b.value.toDate();for(;Math.abs(b.value.diff(It,"year",!0))<1;){const Ct=pt[Y.value];if(!Ct)return;if(Ct.offset(It,Qe(Ct[_e])?Ct[_e](It):Ct[_e]??0),f&&f(It))break;const $t=gt(It).locale(c.value);b.value=$t,r("pick",$t,!0);break}},Qt=_e=>{r("panel-change",b.value.toDate(),_e,z.value)};return ye(()=>N.value,_e=>{if(["month","year"].includes(_e)){z.value=_e;return}else if(_e==="years"){z.value="year";return}else if(_e==="months"){z.value="month";return}z.value="date"},{immediate:!0}),ye(()=>g.value,_e=>{_e&&(b.value=nt())},{immediate:!0}),ye(()=>a.parsedValue,_e=>{if(_e){if(V.value||Ae(_e))return;b.value=_e}else b.value=nt()},{immediate:!0}),r("set-picker-option",["isValidValue",xe]),r("set-picker-option",["parseUserInput",rt]),r("set-picker-option",["handleFocusPicker",Wt]),(_e,Ne)=>(x(),O("div",{class:U([p(o).b(),p(i).b(),p(o).is("border",_e.border),p(o).is("disabled",p(ie)),{"has-sidebar":_e.$slots.sidebar||X.value,"has-time":ve.value}])},[C("div",{class:U(p(o).e("body-wrapper"))},[ce(_e.$slots,"sidebar",{class:U(p(o).e("sidebar"))}),X.value?(x(),O("div",{key:0,class:U(p(o).e("sidebar"))},[(x(!0),O(Ve,null,dt(p(v),(tt,Me)=>(x(),O("button",{key:Me,type:"button",disabled:p(ie),class:U(p(o).e("shortcut")),onClick:Ue=>W(tt)},Q(tt.text),11,bhe))),128))],2)):te("v-if",!0),C("div",{class:U(p(o).e("body"))},[ve.value?(x(),O("div",{key:0,class:U(p(i).e("time-header"))},[C("span",{class:U(p(i).e("editor-wrap"))},[L(p(Ba),{placeholder:p(u)("el.datepicker.selectDate"),"model-value":De.value,size:"small","validate-event":!1,disabled:p(ie),readonly:!_e.editable,onInput:Ne[0]||(Ne[0]=tt=>E.value=tt),onChange:ze},null,8,["placeholder","model-value","disabled","readonly"])],2),st((x(),O("span",{class:U(p(i).e("editor-wrap"))},[L(p(Ba),{placeholder:p(u)("el.datepicker.selectTime"),"model-value":Se.value,size:"small","validate-event":!1,disabled:p(ie),readonly:!_e.editable,onFocus:Re,onInput:Ne[1]||(Ne[1]=tt=>A.value=tt),onChange:Be},null,8,["placeholder","model-value","disabled","readonly"]),L(p(Jm),{visible:Te.value,format:fe.value,"parsed-value":b.value,onPick:me},null,8,["visible","format","parsed-value"])],2)),[[p(Ns),Ce]])],2)):te("v-if",!0),st(C("div",{class:U([p(i).e("header"),(z.value==="year"||z.value==="month")&&p(i).em("header","bordered")])},[C("span",{class:U(p(i).e("prev-btn"))},[C("button",{type:"button","aria-label":p(u)("el.datepicker.prevYear"),class:U(["d-arrow-left",p(o).e("icon-btn")]),disabled:p(ie),onClick:Ne[2]||(Ne[2]=tt=>H(!1))},[ce(_e.$slots,"prev-year",{},()=>[L(p(Xe),null,{default:$(()=>[L(p(Al))]),_:1})])],10,_he),st(C("button",{type:"button","aria-label":p(u)("el.datepicker.prevMonth"),class:U([p(o).e("icon-btn"),"arrow-left"]),disabled:p(ie),onClick:Ne[3]||(Ne[3]=tt=>K(!1))},[ce(_e.$slots,"prev-month",{},()=>[L(p(Xe),null,{default:$(()=>[L(p(Ji))]),_:1})])],10,whe),[[Zt,z.value==="date"]])],2),C("span",{role:"button",class:U(p(i).e("header-label")),"aria-live":"polite",tabindex:_e.disabled?void 0:0,"aria-disabled":_e.disabled,onKeydown:Ne[4]||(Ne[4]=kn(tt=>le("year"),["enter"])),onClick:Ne[5]||(Ne[5]=tt=>le("year"))},Q(B.value),43,She),st(C("span",{role:"button","aria-live":"polite",tabindex:_e.disabled?void 0:0,"aria-disabled":_e.disabled,class:U([p(i).e("header-label"),{active:z.value==="month"}]),onKeydown:Ne[6]||(Ne[6]=kn(tt=>le("month"),["enter"])),onClick:Ne[7]||(Ne[7]=tt=>le("month"))},Q(p(u)(`el.datepicker.month${k.value+1}`)),43,Che),[[Zt,z.value==="date"]]),C("span",{class:U(p(i).e("next-btn"))},[st(C("button",{type:"button","aria-label":p(u)("el.datepicker.nextMonth"),class:U([p(o).e("icon-btn"),"arrow-right"]),disabled:p(ie),onClick:Ne[8]||(Ne[8]=tt=>K(!0))},[ce(_e.$slots,"next-month",{},()=>[L(p(Xe),null,{default:$(()=>[L(p(za))]),_:1})])],10,xhe),[[Zt,z.value==="date"]]),C("button",{type:"button","aria-label":p(u)("el.datepicker.nextYear"),class:U([p(o).e("icon-btn"),"d-arrow-right"]),disabled:p(ie),onClick:Ne[9]||(Ne[9]=tt=>H(!0))},[ce(_e.$slots,"next-year",{},()=>[L(p(Xe),null,{default:$(()=>[L(p(Il))]),_:1})])],10,khe)],2)],2),[[Zt,z.value!=="time"]]),C("div",{class:U(p(o).e("content")),onKeydown:Jt},[z.value==="date"?(x(),ne(Z4,{key:0,ref_key:"currentViewRef",ref:y,"selection-mode":N.value,date:b.value,"parsed-value":_e.parsedValue,"disabled-date":p(f),disabled:p(ie),"cell-class-name":p(h),"show-week-number":_e.showWeekNumber,onPick:G},null,8,["selection-mode","date","parsed-value","disabled-date","disabled","cell-class-name","show-week-number"])):te("v-if",!0),z.value==="year"?(x(),ne(wh,{key:1,ref_key:"currentViewRef",ref:y,"selection-mode":N.value,date:b.value,"disabled-date":p(f),disabled:p(ie),"parsed-value":_e.parsedValue,"cell-class-name":p(h),onPick:re},null,8,["selection-mode","date","disabled-date","disabled","parsed-value","cell-class-name"])):te("v-if",!0),z.value==="month"?(x(),ne(_h,{key:2,ref_key:"currentViewRef",ref:y,"selection-mode":N.value,date:b.value,"parsed-value":_e.parsedValue,"disabled-date":p(f),disabled:p(ie),"cell-class-name":p(h),onPick:ee},null,8,["selection-mode","date","parsed-value","disabled-date","disabled","cell-class-name"])):te("v-if",!0)],34)],2)],2),_e.showFooter&&he.value&&J.value?(x(),O("div",{key:0,class:U(p(o).e("footer"))},[st(L(p(xa),{text:"",size:"small",class:U(p(o).e("link-btn")),disabled:oe.value,onClick:se},{default:$(()=>[ue(Q(p(u)("el.datepicker.now")),1)]),_:1},8,["class","disabled"]),[[Zt,!V.value&&_e.showNow]]),_e.showConfirm?(x(),ne(p(xa),{key:0,plain:"",size:"small",class:U(p(o).e("link-btn")),disabled:Z.value,onClick:j},{default:$(()=>[ue(Q(p(u)("el.datepicker.confirm")),1)]),_:1},8,["class","disabled"])):te("v-if",!0)],2)):te("v-if",!0)],2))}}),Mhe=The;const Ehe=$e({...FP,...hw}),Ahe=e=>{const{emit:t}=Et(),n=Nl(),a=Xn();return o=>{const i=Qe(o.value)?o.value():o.value;if(i){t("pick",[gt(i[0]).locale(e.value),gt(i[1]).locale(e.value)]);return}o.onClick&&o.onClick({attrs:n,slots:a,emit:t})}},gw=(e,{defaultValue:t,defaultTime:n,leftDate:a,rightDate:r,step:o,unit:i,sortDates:l})=>{const{emit:s}=Et(),{pickerNs:u}=Ze(fw),c=ke("date-range-picker"),{t:d,lang:v}=Vt(),f=Ahe(v),h=F(),m=F(),g=F({endDate:null,selecting:!1}),y=k=>{g.value=k},b=(k=!1)=>{const T=p(h),M=p(m);bh([T,M])&&s("pick",[T,M],k)},_=k=>{g.value.selecting=k,k||(g.value.endDate=null)},w=k=>{if(Ae(k)&&k.length===2){const[T,M]=k;h.value=T,a.value=T,m.value=M,l(p(h),p(m))}else S()},S=()=>{let[k,T]=G1(p(t),{lang:p(v),step:o,unit:i,unlinkPanels:e.unlinkPanels});const M=A=>A.diff(A.startOf("d"),"ms"),E=p(n);if(E){let A=0,I=0;if(Ae(E)){const[P,R]=E.map(gt);A=M(P),I=M(R)}else{const P=M(gt(E));A=P,I=P}k=k.startOf("d").add(A,"ms"),T=T.startOf("d").add(I,"ms")}h.value=void 0,m.value=void 0,a.value=k,r.value=T};return ye(t,k=>{k&&S()},{immediate:!0}),ye(()=>e.parsedValue,k=>{(!(k!=null&&k.length)||!Rn(k,[h.value,m.value]))&&w(k)},{immediate:!0}),ye(()=>e.visible,()=>{e.visible&&w(e.parsedValue)},{immediate:!0}),{minDate:h,maxDate:m,rangeState:g,lang:v,ppNs:u,drpNs:c,handleChangeRange:y,handleRangeConfirm:b,handleShortcutClick:f,onSelect:_,parseValue:w,t:d}},Ihe=(e,t,n,a)=>{const r=F("date"),o=F(),i=F("date"),l=F(),{disabledDate:s}=Ze(nl).props,{t:u,lang:c}=Vt(),d=D(()=>n.value.year()),v=D(()=>n.value.month()),f=D(()=>a.value.year()),h=D(()=>a.value.month());function m(S,k){const T=u("el.datepicker.year");if(S.value==="year"){const M=Math.floor(k.value/10)*10;return T?`${M} ${T} - ${M+9} ${T}`:`${M} - ${M+9}`}return`${k.value} ${T}`}function g(S){S==null||S.focus()}async function y(S,k){if(e.disabled)return;const T=S==="left"?r:i,M=S==="left"?o:l;T.value=k,await He(),g(M.value)}async function b(S,k,T){if(e.disabled)return;const M=k==="left",E=M?n:a,A=M?a:n,I=M?r:i,P=M?o:l;S==="year"&&(E.value=dg(E.value.year(T),c.value,s)),S==="month"&&(E.value=Gd(E.value,E.value.year(),T,c.value,s)),e.unlinkPanels||(A.value=k==="left"?E.value.add(1,"month"):E.value.subtract(1,"month")),I.value=S==="year"?"month":"date",await He(),g(P.value),_(S)}function _(S){t("panel-change",[n.value.toDate(),a.value.toDate()],S)}function w(S,k,T){const M=T?"add":"subtract";return S==="year"?k[M](10,"year"):k[M](1,"year")}return{leftCurrentView:r,rightCurrentView:i,leftCurrentViewRef:o,rightCurrentViewRef:l,leftYear:d,rightYear:f,leftMonth:v,rightMonth:h,leftYearLabel:D(()=>m(r,d)),rightYearLabel:D(()=>m(i,f)),showLeftPicker:S=>y("left",S),showRightPicker:S=>y("right",S),handleLeftYearPick:S=>b("year","left",S),handleRightYearPick:S=>b("year","right",S),handleLeftMonthPick:S=>b("month","left",S),handleRightMonthPick:S=>b("month","right",S),handlePanelChange:_,adjustDateByView:w}},Lhe=["disabled","onClick"],Phe=["aria-label","disabled"],Dhe=["aria-label","disabled"],Ohe=["disabled","aria-label"],$he=["disabled","aria-label"],Rhe=["tabindex","aria-disabled"],Nhe=["tabindex","aria-disabled"],Bhe=["disabled","aria-label"],Vhe=["disabled","aria-label"],zhe=["aria-label","disabled"],Fhe=["disabled","aria-label"],Hhe=["tabindex","aria-disabled"],Whe=["tabindex","aria-disabled"],Up="month";var Khe=q({__name:"panel-date-range",props:Ehe,emits:["pick","set-picker-option","calendar-change","panel-change","clear"],setup(e,{emit:t}){const n=e,a=t,r=Ze(nl),{disabledDate:o,cellClassName:i,defaultTime:l,clearable:s}=r.props,u=cn(r.props,"format"),c=cn(r.props,"shortcuts"),d=cn(r.props,"defaultValue"),{lang:v}=Vt(),f=F(gt().locale(v.value)),h=F(gt().locale(v.value).add(1,Up)),{minDate:m,maxDate:g,rangeState:y,ppNs:b,drpNs:_,handleChangeRange:w,handleRangeConfirm:S,handleShortcutClick:k,onSelect:T,parseValue:M,t:E}=gw(n,{defaultValue:d,defaultTime:l,leftDate:f,rightDate:h,unit:Up,sortDates:Sn});ye(()=>n.visible,Fe=>{!Fe&&y.value.selecting&&(M(n.parsedValue),T(!1))});const A=F({min:null,max:null}),I=F({min:null,max:null}),{leftCurrentView:P,rightCurrentView:R,leftCurrentViewRef:G,rightCurrentViewRef:K,leftYear:H,rightYear:z,leftMonth:B,rightMonth:W,leftYearLabel:N,rightYearLabel:V,showLeftPicker:Y,showRightPicker:X,handleLeftYearPick:ee,handleRightYearPick:re,handleLeftMonthPick:ie,handleRightMonthPick:le,handlePanelChange:ve,adjustDateByView:he}=Ihe(n,a,f,h),J=D(()=>!!c.value.length),Z=D(()=>A.value.min!==null?A.value.min:m.value?m.value.format(ge.value):""),j=D(()=>A.value.max!==null?A.value.max:g.value||m.value?(g.value||m.value).format(ge.value):""),oe=D(()=>I.value.min!==null?I.value.min:m.value?m.value.format(fe.value):""),se=D(()=>I.value.max!==null?I.value.max:g.value||m.value?(g.value||m.value).format(fe.value):""),fe=D(()=>n.timeFormat||SL(u.value||"")||"HH:mm:ss"),ge=D(()=>n.dateFormat||wL(u.value||"")||"YYYY-MM-DD"),Se=Fe=>bh(Fe)&&(o?!o(Fe[0].toDate())&&!o(Fe[1].toDate()):!0),De=()=>{f.value=he(P.value,f.value,!1),n.unlinkPanels||(h.value=f.value.add(1,"month")),ve("year")},Te=()=>{f.value=f.value.subtract(1,"month"),n.unlinkPanels||(h.value=f.value.add(1,"month")),ve("month")},Re=()=>{n.unlinkPanels?h.value=he(R.value,h.value,!0):(f.value=he(R.value,f.value,!0),h.value=f.value.add(1,"month")),ve("year")},Ce=()=>{n.unlinkPanels?h.value=h.value.add(1,"month"):(f.value=f.value.add(1,"month"),h.value=f.value.add(1,"month")),ve("month")},Le=()=>{f.value=he(P.value,f.value,!0),ve("year")},me=()=>{f.value=f.value.add(1,"month"),ve("month")},Be=()=>{h.value=he(R.value,h.value,!1),ve("year")},ze=()=>{h.value=h.value.subtract(1,"month"),ve("month")},xe=D(()=>{const Fe=(B.value+1)%12,qe=B.value+1>=12?1:0;return n.singlePanel||n.unlinkPanels&&new Date(H.value+qe,Fe)n.singlePanel||n.unlinkPanels&&z.value*12+W.value-(H.value*12+B.value+1)>=12),nt=$n(),Pt=D(()=>!(m.value&&g.value&&!y.value.selecting&&bh([m.value,g.value])&&!nt.value)),Wt=D(()=>n.type==="datetime"||n.type==="datetimerange"),Jt=(Fe,qe)=>{if(Fe)return l?gt(l[qe]||l).locale(v.value).year(Fe.year()).month(Fe.month()).date(Fe.date()):Fe},Rt=(Fe,qe=!0)=>{const ut=Fe.minDate,Ie=Fe.maxDate,Ke=Jt(ut,0),Ge=Jt(Ie,1);g.value===Ge&&m.value===Ke||(a("calendar-change",[ut.toDate(),Ie&&Ie.toDate()]),g.value=Ge,m.value=Ke,!Wt.value&&qe&&(qe=!Ke||!Ge),S(qe))},Qt=F(!1),_e=F(!1),Ne=()=>{Qt.value=!1},tt=()=>{_e.value=!1},Me=(Fe,qe)=>{if(!o||!o(Fe.toDate()))return Fe;const ut=Fe.isBefore(qe);let Ie=Fe;for(;ut?Ie.isBefore(qe):Ie.isAfter(qe);)if(Ie=ut?Ie.add(1,"day"):Ie.subtract(1,"day"),!o(Ie.toDate()))return Ie;return Fe},Ue=(Fe,qe)=>{A.value[qe]=Fe;const ut=gt(Fe,ge.value).locale(v.value);if(ut.isValid()){if(o&&o(ut.toDate()))return;if(qe==="min"){if(f.value=ut,m.value=(m.value||f.value).year(ut.year()).month(ut.month()).date(ut.date()),!n.unlinkPanels&&!g.value){const Ie=Me(m.value.add(1,"month"),m.value);h.value=Ie,g.value=Ie}}else if(h.value=ut,g.value=(g.value||h.value).year(ut.year()).month(ut.month()).date(ut.date()),!n.unlinkPanels&&!m.value){const Ie=Me(g.value.subtract(1,"month"),g.value);f.value=Ie,m.value=Ie}Sn(m.value,g.value),S(!0)}},Gt=(Fe,qe)=>{if(A.value[qe]=null,qe==="min"){if(!n.unlinkPanels&&g.value&&m.value&&g.value.isBefore(m.value)){const ut=Me(m.value.add(1,"month"),m.value);h.value=ut,g.value=ut}}else if(!n.unlinkPanels&&m.value&&g.value&&m.value.isAfter(g.value)){const ut=Me(g.value.subtract(1,"month"),g.value);f.value=ut,m.value=ut}Sn(m.value,g.value),S(!0)},je=(Fe,qe)=>{I.value[qe]=Fe;const ut=gt(Fe,fe.value).locale(v.value);ut.isValid()&&(qe==="min"?(Qt.value=!0,m.value=(m.value||f.value).hour(ut.hour()).minute(ut.minute()).second(ut.second()),f.value=m.value):(_e.value=!0,g.value=(g.value||h.value).hour(ut.hour()).minute(ut.minute()).second(ut.second()),h.value=g.value))},ft=(Fe,qe)=>{I.value[qe]=null,qe==="min"?(f.value=m.value,Qt.value=!1,(!g.value||g.value.isBefore(m.value))&&(g.value=m.value)):(h.value=g.value,_e.value=!1,g.value&&g.value.isBefore(m.value)&&(m.value=g.value)),S(!0)},ht=(Fe,qe,ut)=>{I.value.min||(Fe&&(m.value=(m.value||f.value).hour(Fe.hour()).minute(Fe.minute()).second(Fe.second())),ut||(Qt.value=qe),(!g.value||g.value.isBefore(m.value))&&(g.value=m.value,h.value=Fe,He(()=>{M(n.parsedValue)})),S(!0))},pt=(Fe,qe,ut)=>{I.value.max||(Fe&&(g.value=(g.value||h.value).hour(Fe.hour()).minute(Fe.minute()).second(Fe.second())),ut||(_e.value=qe),g.value&&g.value.isBefore(m.value)&&(m.value=g.value),S(!0))},It=()=>{Ct(),a("clear")},Ct=()=>{let Fe=null;r!=null&&r.emptyValues&&(Fe=r.emptyValues.valueOnClear.value),f.value=G1(p(d),{lang:p(v),unit:"month",unlinkPanels:n.unlinkPanels})[0],h.value=f.value.add(1,"month"),g.value=void 0,m.value=void 0,S(!0),a("pick",Fe)},$t=Fe=>Yd(Fe,u.value||"",v.value);function Sn(Fe,qe){if(n.unlinkPanels&&qe){const ut=(Fe==null?void 0:Fe.year())||0,Ie=(Fe==null?void 0:Fe.month())||0,Ke=qe.year(),Ge=qe.month();h.value=ut===Ke&&Ie===Ge?qe.add(1,Up):qe}else h.value=f.value.add(1,Up),qe&&(h.value=h.value.hour(qe.hour()).minute(qe.minute()).second(qe.second()))}return a("set-picker-option",["isValidValue",Se]),a("set-picker-option",["parseUserInput",$t]),a("set-picker-option",["handleClear",Ct]),(Fe,qe)=>(x(),O("div",{class:U([p(b).b(),p(_).b(),p(b).is("border",Fe.border),p(b).is("disabled",p(nt)),{"has-sidebar":Fe.$slots.sidebar||J.value,"has-time":Wt.value,"single-panel":Fe.singlePanel}])},[C("div",{class:U(p(b).e("body-wrapper"))},[ce(Fe.$slots,"sidebar",{class:U(p(b).e("sidebar"))}),J.value?(x(),O("div",{key:0,class:U(p(b).e("sidebar"))},[(x(!0),O(Ve,null,dt(c.value,(ut,Ie)=>(x(),O("button",{key:Ie,type:"button",disabled:p(nt),class:U(p(b).e("shortcut")),onClick:Ke=>p(k)(ut)},Q(ut.text),11,Lhe))),128))],2)):te("v-if",!0),C("div",{class:U(p(b).e("body"))},[Wt.value?(x(),O("div",{key:0,class:U(p(_).e("time-header"))},[C("span",{class:U(p(_).e("editors-wrap"))},[C("span",{class:U(p(_).e("time-picker-wrap"))},[L(p(Ba),{size:"small",disabled:p(y).selecting||p(nt),placeholder:p(E)("el.datepicker.startDate"),class:U(p(_).e("editor")),"model-value":Z.value,"validate-event":!1,readonly:!Fe.editable,onInput:qe[0]||(qe[0]=ut=>Ue(ut,"min")),onChange:qe[1]||(qe[1]=ut=>Gt(ut,"min"))},null,8,["disabled","placeholder","class","model-value","readonly"])],2),st((x(),O("span",{class:U(p(_).e("time-picker-wrap"))},[L(p(Ba),{size:"small",class:U(p(_).e("editor")),disabled:p(y).selecting||p(nt),placeholder:p(E)("el.datepicker.startTime"),"model-value":oe.value,"validate-event":!1,readonly:!Fe.editable,onFocus:qe[2]||(qe[2]=ut=>Qt.value=!0),onInput:qe[3]||(qe[3]=ut=>je(ut,"min")),onChange:qe[4]||(qe[4]=ut=>ft(ut,"min"))},null,8,["class","disabled","placeholder","model-value","readonly"]),L(p(Jm),{visible:Qt.value,format:fe.value,"datetime-role":"start","parsed-value":p(m)||f.value,onPick:ht},null,8,["visible","format","parsed-value"])],2)),[[p(Ns),Ne]])],2),C("span",null,[L(p(Xe),null,{default:$(()=>[L(p(za))]),_:1})]),C("span",{class:U([p(_).e("editors-wrap"),"is-right"])},[C("span",{class:U(p(_).e("time-picker-wrap"))},[L(p(Ba),{size:"small",class:U(p(_).e("editor")),disabled:p(y).selecting||p(nt),placeholder:p(E)("el.datepicker.endDate"),"model-value":j.value,readonly:!p(m)||!Fe.editable,"validate-event":!1,onInput:qe[5]||(qe[5]=ut=>Ue(ut,"max")),onChange:qe[6]||(qe[6]=ut=>Gt(ut,"max"))},null,8,["class","disabled","placeholder","model-value","readonly"])],2),st((x(),O("span",{class:U(p(_).e("time-picker-wrap"))},[L(p(Ba),{size:"small",class:U(p(_).e("editor")),disabled:p(y).selecting||p(nt),placeholder:p(E)("el.datepicker.endTime"),"model-value":se.value,readonly:!p(m)||!Fe.editable,"validate-event":!1,onFocus:qe[7]||(qe[7]=ut=>p(m)&&(_e.value=!0)),onInput:qe[8]||(qe[8]=ut=>je(ut,"max")),onChange:qe[9]||(qe[9]=ut=>ft(ut,"max"))},null,8,["class","disabled","placeholder","model-value","readonly"]),L(p(Jm),{"datetime-role":"end",visible:_e.value,format:fe.value,"parsed-value":p(g)||h.value,onPick:pt},null,8,["visible","format","parsed-value"])],2)),[[p(Ns),tt]])],2)],2)):te("v-if",!0),C("div",{class:U([p(b).e("content"),p(_).e("content"),p(_).is("left",!Fe.singlePanel)])},[C("div",{class:U(p(_).e("header"))},[C("button",{type:"button",class:U([p(b).e("icon-btn"),"d-arrow-left"]),"aria-label":p(E)("el.datepicker.prevYear"),disabled:p(nt),onClick:De},[ce(Fe.$slots,"prev-year",{},()=>[L(p(Xe),null,{default:$(()=>[L(p(Al))]),_:1})])],10,Phe),st(C("button",{type:"button",class:U([p(b).e("icon-btn"),"arrow-left"]),"aria-label":p(E)("el.datepicker.prevMonth"),disabled:p(nt),onClick:Te},[ce(Fe.$slots,"prev-month",{},()=>[L(p(Xe),null,{default:$(()=>[L(p(Ji))]),_:1})])],10,Dhe),[[Zt,p(P)==="date"]]),Fe.unlinkPanels||Fe.singlePanel?(x(),O("button",{key:0,type:"button",disabled:!rt.value||p(nt),class:U([[p(b).e("icon-btn"),p(b).is("disabled",!rt.value||p(nt))],"d-arrow-right"]),"aria-label":p(E)("el.datepicker.nextYear"),onClick:Le},[ce(Fe.$slots,"next-year",{},()=>[L(p(Xe),null,{default:$(()=>[L(p(Il))]),_:1})])],10,Ohe)):te("v-if",!0),Fe.unlinkPanels&&p(P)==="date"||Fe.singlePanel?(x(),O("button",{key:1,type:"button",disabled:!xe.value||p(nt),class:U([[p(b).e("icon-btn"),p(b).is("disabled",!xe.value||p(nt))],"arrow-right"]),"aria-label":p(E)("el.datepicker.nextMonth"),onClick:me},[ce(Fe.$slots,"next-month",{},()=>[L(p(Xe),null,{default:$(()=>[L(p(za))]),_:1})])],10,$he)):te("v-if",!0),C("div",null,[C("span",{role:"button",class:U(p(_).e("header-label")),"aria-live":"polite",tabindex:Fe.disabled?void 0:0,"aria-disabled":Fe.disabled,onKeydown:qe[10]||(qe[10]=kn(ut=>p(Y)("year"),["enter"])),onClick:qe[11]||(qe[11]=ut=>p(Y)("year"))},Q(p(N)),43,Rhe),st(C("span",{role:"button","aria-live":"polite",tabindex:Fe.disabled?void 0:0,"aria-disabled":Fe.disabled,class:U([p(_).e("header-label"),{active:p(P)==="month"}]),onKeydown:qe[12]||(qe[12]=kn(ut=>p(Y)("month"),["enter"])),onClick:qe[13]||(qe[13]=ut=>p(Y)("month"))},Q(p(E)(`el.datepicker.month${f.value.month()+1}`)),43,Nhe),[[Zt,p(P)==="date"]])])],2),p(P)==="date"?(x(),ne(Z4,{key:0,ref_key:"leftCurrentViewRef",ref:G,"selection-mode":"range",date:f.value,"min-date":p(m),"max-date":p(g),"range-state":p(y),"disabled-date":p(o),"cell-class-name":p(i),"show-week-number":Fe.showWeekNumber,disabled:p(nt),onChangerange:p(w),onPick:Rt,onSelect:p(T)},null,8,["date","min-date","max-date","range-state","disabled-date","cell-class-name","show-week-number","disabled","onChangerange","onSelect"])):te("v-if",!0),p(P)==="year"?(x(),ne(wh,{key:1,ref_key:"leftCurrentViewRef",ref:G,"selection-mode":"year",date:f.value,"disabled-date":p(o),"parsed-value":Fe.parsedValue,disabled:p(nt),onPick:p(ee)},null,8,["date","disabled-date","parsed-value","disabled","onPick"])):te("v-if",!0),p(P)==="month"?(x(),ne(_h,{key:2,ref_key:"leftCurrentViewRef",ref:G,"selection-mode":"month",date:f.value,"parsed-value":Fe.parsedValue,"disabled-date":p(o),disabled:p(nt),onPick:p(ie)},null,8,["date","parsed-value","disabled-date","disabled","onPick"])):te("v-if",!0)],2),Fe.singlePanel?te("v-if",!0):(x(),O("div",{key:1,class:U([[p(b).e("content"),p(_).e("content")],"is-right"])},[C("div",{class:U(p(_).e("header"))},[Fe.unlinkPanels?(x(),O("button",{key:0,type:"button",disabled:!rt.value||p(nt),class:U([[p(b).e("icon-btn"),p(b).is("disabled",!rt.value||p(nt))],"d-arrow-left"]),"aria-label":p(E)("el.datepicker.prevYear"),onClick:Be},[ce(Fe.$slots,"prev-year",{},()=>[L(p(Xe),null,{default:$(()=>[L(p(Al))]),_:1})])],10,Bhe)):te("v-if",!0),Fe.unlinkPanels&&p(R)==="date"?(x(),O("button",{key:1,type:"button",disabled:!xe.value||p(nt),class:U([[p(b).e("icon-btn"),p(b).is("disabled",!xe.value||p(nt))],"arrow-left"]),"aria-label":p(E)("el.datepicker.prevMonth"),onClick:ze},[ce(Fe.$slots,"prev-month",{},()=>[L(p(Xe),null,{default:$(()=>[L(p(Ji))]),_:1})])],10,Vhe)):te("v-if",!0),C("button",{type:"button","aria-label":p(E)("el.datepicker.nextYear"),class:U([p(b).e("icon-btn"),"d-arrow-right"]),disabled:p(nt),onClick:Re},[ce(Fe.$slots,"next-year",{},()=>[L(p(Xe),null,{default:$(()=>[L(p(Il))]),_:1})])],10,zhe),st(C("button",{type:"button",class:U([p(b).e("icon-btn"),"arrow-right"]),disabled:p(nt),"aria-label":p(E)("el.datepicker.nextMonth"),onClick:Ce},[ce(Fe.$slots,"next-month",{},()=>[L(p(Xe),null,{default:$(()=>[L(p(za))]),_:1})])],10,Fhe),[[Zt,p(R)==="date"]]),C("div",null,[C("span",{role:"button",class:U(p(_).e("header-label")),"aria-live":"polite",tabindex:Fe.disabled?void 0:0,"aria-disabled":Fe.disabled,onKeydown:qe[14]||(qe[14]=kn(ut=>p(X)("year"),["enter"])),onClick:qe[15]||(qe[15]=ut=>p(X)("year"))},Q(p(V)),43,Hhe),st(C("span",{role:"button","aria-live":"polite",tabindex:Fe.disabled?void 0:0,"aria-disabled":Fe.disabled,class:U([p(_).e("header-label"),{active:p(R)==="month"}]),onKeydown:qe[16]||(qe[16]=kn(ut=>p(X)("month"),["enter"])),onClick:qe[17]||(qe[17]=ut=>p(X)("month"))},Q(p(E)(`el.datepicker.month${h.value.month()+1}`)),43,Whe),[[Zt,p(R)==="date"]])])],2),p(R)==="date"?(x(),ne(Z4,{key:0,ref_key:"rightCurrentViewRef",ref:K,"selection-mode":"range",date:h.value,"min-date":p(m),"max-date":p(g),"range-state":p(y),"disabled-date":p(o),"cell-class-name":p(i),"show-week-number":Fe.showWeekNumber,disabled:p(nt),onChangerange:p(w),onPick:Rt,onSelect:p(T)},null,8,["date","min-date","max-date","range-state","disabled-date","cell-class-name","show-week-number","disabled","onChangerange","onSelect"])):te("v-if",!0),p(R)==="year"?(x(),ne(wh,{key:1,ref_key:"rightCurrentViewRef",ref:K,"selection-mode":"year",date:h.value,"disabled-date":p(o),"parsed-value":Fe.parsedValue,disabled:p(nt),onPick:p(re)},null,8,["date","disabled-date","parsed-value","disabled","onPick"])):te("v-if",!0),p(R)==="month"?(x(),ne(_h,{key:2,ref_key:"rightCurrentViewRef",ref:K,"selection-mode":"month",date:h.value,"parsed-value":Fe.parsedValue,"disabled-date":p(o),disabled:p(nt),onPick:p(le)},null,8,["date","parsed-value","disabled-date","disabled","onPick"])):te("v-if",!0)],2))],2)],2),Fe.showFooter&&Wt.value&&(Fe.showConfirm||p(s))?(x(),O("div",{key:0,class:U(p(b).e("footer"))},[p(s)?(x(),ne(p(xa),{key:0,text:"",size:"small",class:U(p(b).e("link-btn")),onClick:It},{default:$(()=>[ue(Q(p(E)("el.datepicker.clear")),1)]),_:1},8,["class"])):te("v-if",!0),Fe.showConfirm?(x(),ne(p(xa),{key:1,plain:"",size:"small",class:U(p(b).e("link-btn")),disabled:Pt.value,onClick:qe[18]||(qe[18]=ut=>p(S)(!1))},{default:$(()=>[ue(Q(p(E)("el.datepicker.confirm")),1)]),_:1},8,["class","disabled"])):te("v-if",!0)],2)):te("v-if",!0)],2))}}),Uhe=Khe;const Ghe=$e({...hw}),Yhe=["pick","set-picker-option","calendar-change"],jhe=({unlinkPanels:e,leftDate:t,rightDate:n})=>{const{t:a}=Vt();return{leftPrevYear:()=>{t.value=t.value.subtract(1,"year"),e.value||(n.value=n.value.subtract(1,"year"))},rightNextYear:()=>{e.value||(t.value=t.value.add(1,"year")),n.value=n.value.add(1,"year")},leftNextYear:()=>{t.value=t.value.add(1,"year")},rightPrevYear:()=>{n.value=n.value.subtract(1,"year")},leftLabel:D(()=>`${t.value.year()} ${a("el.datepicker.year")}`),rightLabel:D(()=>`${n.value.year()} ${a("el.datepicker.year")}`),leftYear:D(()=>t.value.year()),rightYear:D(()=>n.value.year()===t.value.year()?t.value.year()+1:n.value.year())}},qhe=["disabled","onClick"],Xhe=["disabled"],Zhe=["disabled"],Jhe=["disabled"],Qhe=["disabled"],Gp="year";var epe=q({name:"DatePickerMonthRange",__name:"panel-month-range",props:Ghe,emits:Yhe,setup(e,{emit:t}){const n=e,a=t,{lang:r}=Vt(),o=Ze(nl),{shortcuts:i,disabledDate:l,cellClassName:s}=o.props,u=cn(o.props,"format"),c=cn(o.props,"defaultValue"),d=F(gt().locale(r.value)),v=F(gt().locale(r.value).add(1,Gp)),{minDate:f,maxDate:h,rangeState:m,ppNs:g,drpNs:y,handleChangeRange:b,handleRangeConfirm:_,handleShortcutClick:w,onSelect:S,parseValue:k}=gw(n,{defaultValue:c,leftDate:d,rightDate:v,unit:Gp,sortDates:N}),T=D(()=>!!i.length),{leftPrevYear:M,rightNextYear:E,leftNextYear:A,rightPrevYear:I,leftLabel:P,rightLabel:R,leftYear:G,rightYear:K}=jhe({unlinkPanels:cn(n,"unlinkPanels"),leftDate:d,rightDate:v}),H=D(()=>n.singlePanel||n.unlinkPanels&&K.value>G.value+1),z=(Y,X=!0)=>{const ee=Y.minDate,re=Y.maxDate;h.value===re&&f.value===ee||(a("calendar-change",[ee.toDate(),re&&re.toDate()]),h.value=re,f.value=ee,X&&_())},B=()=>{let Y=null;o!=null&&o.emptyValues&&(Y=o.emptyValues.valueOnClear.value),d.value=G1(p(c),{lang:p(r),unit:"year",unlinkPanels:n.unlinkPanels})[0],v.value=d.value.add(1,"year"),a("pick",Y)},W=Y=>Yd(Y,u.value,r.value);function N(Y,X){n.unlinkPanels&&X?v.value=((Y==null?void 0:Y.year())||0)===X.year()?X.add(1,Gp):X:v.value=d.value.add(1,Gp)}const V=$n();return ye(()=>n.visible,Y=>{!Y&&m.value.selecting&&(k(n.parsedValue),S(!1))}),a("set-picker-option",["isValidValue",bh]),a("set-picker-option",["parseUserInput",W]),a("set-picker-option",["handleClear",B]),(Y,X)=>(x(),O("div",{class:U([p(g).b(),p(y).b(),p(g).is("border",Y.border),p(g).is("disabled",p(V)),{"has-sidebar":!!Y.$slots.sidebar||T.value,"single-panel":Y.singlePanel}])},[C("div",{class:U(p(g).e("body-wrapper"))},[ce(Y.$slots,"sidebar",{class:U(p(g).e("sidebar"))}),T.value?(x(),O("div",{key:0,class:U(p(g).e("sidebar"))},[(x(!0),O(Ve,null,dt(p(i),(ee,re)=>(x(),O("button",{key:re,type:"button",class:U(p(g).e("shortcut")),disabled:p(V),onClick:ie=>p(w)(ee)},Q(ee.text),11,qhe))),128))],2)):te("v-if",!0),C("div",{class:U(p(g).e("body"))},[C("div",{class:U([p(g).e("content"),p(y).e("content"),p(y).is("left",!Y.singlePanel)])},[C("div",{class:U(p(y).e("header"))},[C("button",{type:"button",class:U([p(g).e("icon-btn"),"d-arrow-left"]),disabled:p(V),onClick:X[0]||(X[0]=(...ee)=>p(M)&&p(M)(...ee))},[ce(Y.$slots,"prev-year",{},()=>[L(p(Xe),null,{default:$(()=>[L(p(Al))]),_:1})])],10,Xhe),Y.unlinkPanels||Y.singlePanel?(x(),O("button",{key:0,type:"button",disabled:!H.value||p(V),class:U([[p(g).e("icon-btn"),p(g).is("disabled",!H.value||p(V))],"d-arrow-right"]),onClick:X[1]||(X[1]=(...ee)=>p(A)&&p(A)(...ee))},[ce(Y.$slots,"next-year",{},()=>[L(p(Xe),null,{default:$(()=>[L(p(Il))]),_:1})])],10,Zhe)):te("v-if",!0),C("div",null,Q(p(P)),1)],2),L(_h,{"selection-mode":"range",date:d.value,"min-date":p(f),"max-date":p(h),"range-state":p(m),"disabled-date":p(l),disabled:p(V),"cell-class-name":p(s),onChangerange:p(b),onPick:z,onSelect:p(S)},null,8,["date","min-date","max-date","range-state","disabled-date","disabled","cell-class-name","onChangerange","onSelect"])],2),Y.singlePanel?te("v-if",!0):(x(),O("div",{key:0,class:U([[p(g).e("content"),p(y).e("content")],"is-right"])},[C("div",{class:U(p(y).e("header"))},[Y.unlinkPanels?(x(),O("button",{key:0,type:"button",disabled:!H.value||p(V),class:U([[p(g).e("icon-btn"),p(g).is("disabled",!H.value||p(V))],"d-arrow-left"]),onClick:X[2]||(X[2]=(...ee)=>p(I)&&p(I)(...ee))},[ce(Y.$slots,"prev-year",{},()=>[L(p(Xe),null,{default:$(()=>[L(p(Al))]),_:1})])],10,Jhe)):te("v-if",!0),C("button",{type:"button",class:U([p(g).e("icon-btn"),"d-arrow-right"]),disabled:p(V),onClick:X[3]||(X[3]=(...ee)=>p(E)&&p(E)(...ee))},[ce(Y.$slots,"next-year",{},()=>[L(p(Xe),null,{default:$(()=>[L(p(Il))]),_:1})])],10,Qhe),C("div",null,Q(p(R)),1)],2),L(_h,{"selection-mode":"range",date:v.value,"min-date":p(f),"max-date":p(h),"range-state":p(m),"disabled-date":p(l),disabled:p(V),"cell-class-name":p(s),onChangerange:p(b),onPick:z,onSelect:p(S)},null,8,["date","min-date","max-date","range-state","disabled-date","disabled","cell-class-name","onChangerange","onSelect"])],2))],2)],2)],2))}}),tpe=epe;const npe=$e({...hw}),ape=["pick","set-picker-option","calendar-change"],rpe=({unlinkPanels:e,leftDate:t,rightDate:n})=>({leftPrevYear:()=>{t.value=t.value.subtract(10,"year"),e.value||(n.value=n.value.subtract(10,"year"))},rightNextYear:()=>{e.value||(t.value=t.value.add(10,"year")),n.value=n.value.add(10,"year")},leftNextYear:()=>{t.value=t.value.add(10,"year")},rightPrevYear:()=>{n.value=n.value.subtract(10,"year")},leftLabel:D(()=>{const l=Math.floor(t.value.year()/10)*10;return`${l}-${l+9}`}),rightLabel:D(()=>{const l=Math.floor(n.value.year()/10)*10;return`${l}-${l+9}`}),leftYear:D(()=>Math.floor(t.value.year()/10)*10+9),rightYear:D(()=>Math.floor(n.value.year()/10)*10)}),ope=["disabled","onClick"],ipe=["disabled"],lpe=["disabled"],spe=["disabled"],upe=["disabled"],Nc=10,Df="year";var cpe=q({name:"DatePickerYearRange",__name:"panel-year-range",props:npe,emits:ape,setup(e,{emit:t}){const n=e,a=t,{lang:r}=Vt(),o=F(gt().locale(r.value)),i=F(gt().locale(r.value).add(Nc,Df)),l=Ze(nl),{shortcuts:s,disabledDate:u,cellClassName:c}=l.props,d=cn(l.props,"format"),v=cn(l.props,"defaultValue"),{minDate:f,maxDate:h,rangeState:m,ppNs:g,drpNs:y,handleChangeRange:b,handleRangeConfirm:_,handleShortcutClick:w,onSelect:S,parseValue:k}=gw(n,{defaultValue:v,leftDate:o,rightDate:i,step:Nc,unit:Df,sortDates:re}),{leftPrevYear:T,rightNextYear:M,leftNextYear:E,rightPrevYear:A,leftLabel:I,rightLabel:P,leftYear:R,rightYear:G}=rpe({unlinkPanels:cn(n,"unlinkPanels"),leftDate:o,rightDate:i}),K=$n(),H=D(()=>!!s.length),z=D(()=>[g.b(),y.b(),g.is("border",n.border),g.is("disabled",K.value),{"has-sidebar":!!Xn().sidebar||H.value,"single-panel":n.singlePanel}]),B=D(()=>({content:[g.e("content"),y.e("content"),y.is("left",!n.singlePanel)],arrowLeftBtn:[g.e("icon-btn"),"d-arrow-left"],arrowRightBtn:[g.e("icon-btn"),g.is("disabled",!N.value||K.value),"d-arrow-right"]})),W=D(()=>({content:[g.e("content"),y.e("content"),"is-right"],arrowLeftBtn:[g.e("icon-btn"),g.is("disabled",!N.value||K.value),"d-arrow-left"],arrowRightBtn:[g.e("icon-btn"),"d-arrow-right"]})),N=D(()=>n.singlePanel||n.unlinkPanels&&G.value>R.value+1),V=(ie,le=!0)=>{const ve=ie.minDate,he=ie.maxDate;h.value===he&&f.value===ve||(a("calendar-change",[ve.toDate(),he&&he.toDate()]),h.value=he,f.value=ve,le&&_())},Y=ie=>Yd(ie,d.value,r.value),X=ie=>bh(ie)&&(u?!u(ie[0].toDate())&&!u(ie[1].toDate()):!0),ee=()=>{let ie=null;l!=null&&l.emptyValues&&(ie=l.emptyValues.valueOnClear.value);const le=G1(p(v),{lang:p(r),step:Nc,unit:Df,unlinkPanels:n.unlinkPanels});o.value=le[0],i.value=le[1],a("pick",ie)};function re(ie,le){if(n.unlinkPanels&&le){const ve=(ie==null?void 0:ie.year())||0,he=le.year();i.value=ve+Nc>he?le.add(Nc,Df):le}else i.value=o.value.add(Nc,Df)}return ye(()=>n.visible,ie=>{!ie&&m.value.selecting&&(k(n.parsedValue),S(!1))}),a("set-picker-option",["isValidValue",X]),a("set-picker-option",["parseUserInput",Y]),a("set-picker-option",["handleClear",ee]),(ie,le)=>(x(),O("div",{class:U(z.value)},[C("div",{class:U(p(g).e("body-wrapper"))},[ce(ie.$slots,"sidebar",{class:U(p(g).e("sidebar"))}),H.value?(x(),O("div",{key:0,class:U(p(g).e("sidebar"))},[(x(!0),O(Ve,null,dt(p(s),(ve,he)=>(x(),O("button",{key:he,type:"button",class:U(p(g).e("shortcut")),disabled:p(K),onClick:J=>p(w)(ve)},Q(ve.text),11,ope))),128))],2)):te("v-if",!0),C("div",{class:U(p(g).e("body"))},[C("div",{class:U(B.value.content)},[C("div",{class:U(p(y).e("header"))},[C("button",{type:"button",class:U(B.value.arrowLeftBtn),disabled:p(K),onClick:le[0]||(le[0]=(...ve)=>p(T)&&p(T)(...ve))},[ce(ie.$slots,"prev-year",{},()=>[L(p(Xe),null,{default:$(()=>[L(p(Al))]),_:1})])],10,ipe),ie.unlinkPanels||ie.singlePanel?(x(),O("button",{key:0,type:"button",disabled:!N.value||p(K),class:U(B.value.arrowRightBtn),onClick:le[1]||(le[1]=(...ve)=>p(E)&&p(E)(...ve))},[ce(ie.$slots,"next-year",{},()=>[L(p(Xe),null,{default:$(()=>[L(p(Il))]),_:1})])],10,lpe)):te("v-if",!0),C("div",null,Q(p(I)),1)],2),L(wh,{"selection-mode":"range",date:o.value,"min-date":p(f),"max-date":p(h),"range-state":p(m),"disabled-date":p(u),disabled:p(K),"cell-class-name":p(c),onChangerange:p(b),onPick:V,onSelect:p(S)},null,8,["date","min-date","max-date","range-state","disabled-date","disabled","cell-class-name","onChangerange","onSelect"])],2),ie.singlePanel?te("v-if",!0):(x(),O("div",{key:0,class:U(W.value.content)},[C("div",{class:U(p(y).e("header"))},[ie.unlinkPanels?(x(),O("button",{key:0,type:"button",disabled:!N.value||p(K),class:U(W.value.arrowLeftBtn),onClick:le[2]||(le[2]=(...ve)=>p(A)&&p(A)(...ve))},[ce(ie.$slots,"prev-year",{},()=>[L(p(Xe),null,{default:$(()=>[L(p(Al))]),_:1})])],10,spe)):te("v-if",!0),C("button",{type:"button",class:U(W.value.arrowRightBtn),disabled:p(K),onClick:le[3]||(le[3]=(...ve)=>p(M)&&p(M)(...ve))},[ce(ie.$slots,"next-year",{},()=>[L(p(Xe),null,{default:$(()=>[L(p(Il))]),_:1})])],10,upe),C("div",null,Q(p(P)),1)],2),L(wh,{"selection-mode":"range",date:i.value,"min-date":p(f),"max-date":p(h),"range-state":p(m),"disabled-date":p(u),disabled:p(K),"cell-class-name":p(c),onChangerange:p(b),onPick:V,onSelect:p(S)},null,8,["date","min-date","max-date","range-state","disabled-date","disabled","cell-class-name","onChangerange","onSelect"])],2))],2)],2)],2))}}),dpe=cpe;const fpe=function(e){switch(e){case"daterange":case"datetimerange":return Uhe;case"monthrange":return tpe;case"yearrange":return dpe;default:return Mhe}};var HP={exports:{}};(function(e,t){(function(n,a){e.exports=a()})(Wl,function(){return function(n,a){var r=a.prototype,o=r.format;r.format=function(i){var l=this,s=this.$locale();if(!this.isValid())return o.bind(this)(i);var u=this.$utils(),c=(i||"YYYY-MM-DDTHH:mm:ssZ").replace(/\[([^\]]+)]|Q|wo|ww|w|WW|W|zzz|z|gggg|GGGG|Do|X|x|k{1,2}|S/g,function(d){switch(d){case"Q":return Math.ceil((l.$M+1)/3);case"Do":return s.ordinal(l.$D);case"gggg":return l.weekYear();case"GGGG":return l.isoWeekYear();case"wo":return s.ordinal(l.week(),"W");case"w":case"ww":return u.s(l.week(),d==="w"?1:2,"0");case"W":case"WW":return u.s(l.isoWeek(),d==="W"?1:2,"0");case"k":case"kk":return u.s(String(l.$H===0?24:l.$H),d==="k"?1:2,"0");case"X":return Math.floor(l.$d.getTime()/1e3);case"x":return l.$d.getTime();case"z":return"["+l.offsetName()+"]";case"zzz":return"["+l.offsetName("long")+"]";default:return d}});return o.bind(this)(c)}}})})(HP);var vpe=HP.exports;const hpe=Kl(vpe);var WP={exports:{}};(function(e,t){(function(n,a){e.exports=a()})(Wl,function(){var n="week",a="year";return function(r,o,i){var l=o.prototype;l.week=function(s){if(s===void 0&&(s=null),s!==null)return this.add(7*(s-this.week()),"day");var u=this.$locale().yearStart||1;if(this.month()===11&&this.date()>25){var c=i(this).startOf(a).add(1,a).date(u),d=i(this).endOf(n);if(c.isBefore(d))return 1}var v=i(this).startOf(a).date(u).startOf(n).subtract(1,"millisecond"),f=this.diff(v,n,!0);return f<0?i(this).startOf("week").week():Math.ceil(f)},l.weeks=function(s){return s===void 0&&(s=null),this.week(s)}}})})(WP);var ppe=WP.exports;const mpe=Kl(ppe);var KP={exports:{}};(function(e,t){(function(n,a){e.exports=a()})(Wl,function(){return function(n,a){a.prototype.weekYear=function(){var r=this.month(),o=this.week(),i=this.year();return o===1&&r===11?i+1:r===0&&o>=52?i-1:i}}})})(KP);var gpe=KP.exports;const ype=Kl(gpe);var UP={exports:{}};(function(e,t){(function(n,a){e.exports=a()})(Wl,function(){return function(n,a,r){a.prototype.dayOfYear=function(o){var i=Math.round((r(this).startOf("day")-r(this).startOf("year"))/864e5)+1;return o==null?i:this.add(o-i,"day")}}})})(UP);var bpe=UP.exports;const _pe=Kl(bpe);var GP={exports:{}};(function(e,t){(function(n,a){e.exports=a()})(Wl,function(){return function(n,a){a.prototype.isSameOrAfter=function(r,o){return this.isSame(r,o)||this.isAfter(r,o)}}})})(GP);var wpe=GP.exports;const Spe=Kl(wpe);var YP={exports:{}};(function(e,t){(function(n,a){e.exports=a()})(Wl,function(){return function(n,a){a.prototype.isSameOrBefore=function(r,o){return this.isSame(r,o)||this.isBefore(r,o)}}})})(YP);var Cpe=YP.exports;const xpe=Kl(Cpe);function kpe(e){return typeof e=="function"||Object.prototype.toString.call(e)==="[object Object]"&&!bn(e)}gt.extend(WL);gt.extend(hpe);gt.extend(rw);gt.extend(mpe);gt.extend(ype);gt.extend(_pe);gt.extend(Spe);gt.extend(xpe);var Tpe=q({name:"ElDatePickerPanel",install:null,inheritAttrs:!1,props:Jve,emits:[bt,"calendar-change","panel-change","visible-change","clear"],setup(e,{slots:t,emit:n,attrs:a}){const r=ke("picker-panel");zt(Ze("EP_PICKER_BASE",void 0))&&Ft(nl,{props:nn({...Ta(e)})}),Ft(fw,{slots:t,pickerNs:r});const{parsedValue:o,onCalendarChange:i,onPanelChange:l,onSetPickerOption:s,onPick:u}=Ze(bL,()=>kL(e,n),!0);return()=>L(fpe(e.type),Tt(Tc(a,"onPick"),e,{parsedValue:o.value,"onSet-picker-option":s,"onCalendar-change":i,"onPanel-change":l,onClear:()=>n("clear"),onPick:u}),kpe(t)?t:{default:()=>[t]})}});const jP=wt(Tpe),Mpe=$e({...aw,type:{type:ae(String),default:"date"}});function Epe(e){return typeof e=="function"||Object.prototype.toString.call(e)==="[object Object]"&&!bn(e)}var Ape=q({name:"ElDatePicker",install:null,props:Mpe,emits:[bt],setup(e,{expose:t,emit:n,slots:a}){Ft(tw,nn(cn(e,"popperOptions")));const r=F();t({focus:()=>{var i;(i=r.value)==null||i.focus()},blur:()=>{var i;(i=r.value)==null||i.blur()},handleOpen:()=>{var i;(i=r.value)==null||i.handleOpen()},handleClose:()=>{var i;(i=r.value)==null||i.handleClose()}});const o=i=>{n(bt,i)};return()=>L(TL,Tt(e,{format:e.format??(Tue[e.type]||"YYYY-MM-DD"),type:e.type,ref:r,"onUpdate:modelValue":o}),{default:i=>L(jP,Tt({disabled:e.disabled,editable:e.editable,border:!1},i),Epe(a)?a:{default:()=>[a]}),"range-separator":a["range-separator"]})}});const Ipe=wt(Ape),Lpe=$e({border:Boolean,column:{type:Number,default:3},direction:{type:String,values:["horizontal","vertical"],default:"horizontal"},size:pa,title:{type:String,default:""},extra:{type:String,default:""},labelWidth:{type:[String,Number]}}),qP="ElDescriptionsItem",Ppe=$e({label:{type:String,default:""},span:{type:Number,default:1},rowspan:{type:Number,default:1},width:{type:[String,Number],default:""},minWidth:{type:[String,Number],default:""},labelWidth:{type:[String,Number]},align:{type:String,values:cC,default:"left"},labelAlign:{type:String,values:cC},className:{type:String,default:""},labelClassName:{type:String,default:""}}),XP=q({name:qP,props:Ppe}),yw=Symbol("elDescriptions"),Dpe=$e({row:{type:ae(Array),default:()=>[]}});var Of=q({name:"ElDescriptionsCell",props:{cell:{type:Object},tag:{type:String,default:"td"},type:{type:String}},setup(){return{descriptions:Ze(yw,{})}},render(){var m;const e=tX(this.cell),t=(((m=this.cell)==null?void 0:m.dirs)||[]).map(g=>{const{dir:y,arg:b,modifiers:_,value:w}=g;return[y,w,b,_]}),{border:n,direction:a}=this.descriptions,r=a==="vertical",o=()=>{var g,y,b;return((b=(y=(g=this.cell)==null?void 0:g.children)==null?void 0:y.label)==null?void 0:b.call(y))||e.label},i=()=>{var g,y,b;return(b=(y=(g=this.cell)==null?void 0:g.children)==null?void 0:y.default)==null?void 0:b.call(y)},l=e.span,s=e.rowspan,u=e.align?`is-${e.align}`:"",c=e.labelAlign?`is-${e.labelAlign}`:u,d=e.className,v=e.labelClassName,f={width:Vn(this.type==="label"?e.labelWidth??this.descriptions.labelWidth??e.width:e.width),minWidth:Vn(e.minWidth)},h=ke("descriptions");switch(this.type){case"label":return st(ot(this.tag,{style:f,class:[h.e("cell"),h.e("label"),h.is("bordered-label",n),h.is("vertical-label",r),c,v],colSpan:r?l:1,rowspan:r?1:s},o()),t);case"content":return st(ot(this.tag,{style:f,class:[h.e("cell"),h.e("content"),h.is("bordered-content",n),h.is("vertical-content",r),u,d],colSpan:r?l:l*2-1,rowspan:r?s*2-1:s},i()),t);default:{const g=o(),y={},b=Vn(e.labelWidth??this.descriptions.labelWidth);return b&&(y.width=b,y.display="inline-block"),st(ot("td",{style:f,class:[h.e("cell"),u],colSpan:l,rowspan:s},[Un(g)?void 0:ot("span",{style:y,class:[h.e("label"),v]},g),ot("span",{class:[h.e("content"),d]},i())]),t)}}}});const Ope={key:1};var $pe=q({name:"ElDescriptionsRow",__name:"descriptions-row",props:Dpe,setup(e){const t=Ze(yw,{});return(n,a)=>p(t).direction==="vertical"?(x(),O(Ve,{key:0},[C("tr",null,[(x(!0),O(Ve,null,dt(e.row,(r,o)=>(x(),ne(p(Of),{key:`tr1-${o}`,cell:r,tag:"th",type:"label"},null,8,["cell"]))),128))]),C("tr",null,[(x(!0),O(Ve,null,dt(e.row,(r,o)=>(x(),ne(p(Of),{key:`tr2-${o}`,cell:r,tag:"td",type:"content"},null,8,["cell"]))),128))])],64)):(x(),O("tr",Ope,[(x(!0),O(Ve,null,dt(e.row,(r,o)=>(x(),O(Ve,{key:`tr3-${o}`},[p(t).border?(x(),O(Ve,{key:0},[L(p(Of),{cell:r,tag:"td",type:"label"},null,8,["cell"]),L(p(Of),{cell:r,tag:"td",type:"content"},null,8,["cell"])],64)):(x(),ne(p(Of),{key:1,cell:r,tag:"td",type:"both"},null,8,["cell"]))],64))),128))]))}}),Rpe=$pe,Npe=q({name:"ElDescriptions",__name:"description",props:Lpe,setup(e){const t=e,n=ke("descriptions"),a=ca(),r=Xn();Ft(yw,t);const o=D(()=>[n.b(),n.m(a.value)]),i=(s,u,c,d=!1)=>(s.props||(s.props={}),u>c&&(s.props.span=c),d&&(s.props.span=u),s),l=()=>{if(!r.default)return[];const s=eo(r.default()).filter(h=>{var m;return((m=h==null?void 0:h.type)==null?void 0:m.name)===qP}),u=[];let c=[],d=t.column,v=0;const f=[];return s.forEach((h,m)=>{var _,w,S;const g=((_=h.props)==null?void 0:_.span)||1,y=((w=h.props)==null?void 0:w.rowspan)||1,b=u.length;if(f[b]||(f[b]=0),y>1)for(let k=1;k0&&(d-=f[b],f[b]=0),md?d:g),m===s.length-1){const k=t.column-v%t.column;c.push(i(h,k,d,!0)),u.push(c);return}g(x(),O("div",{class:U(o.value)},[e.title||e.extra||s.$slots.title||s.$slots.extra?(x(),O("div",{key:0,class:U(p(n).e("header"))},[C("div",{class:U(p(n).e("title"))},[ce(s.$slots,"title",{},()=>[ue(Q(e.title),1)])],2),C("div",{class:U(p(n).e("extra"))},[ce(s.$slots,"extra",{},()=>[ue(Q(e.extra),1)])],2)],2)):te("v-if",!0),C("div",{class:U(p(n).e("body"))},[C("table",{class:U([p(n).e("table"),p(n).is("bordered",e.border)])},[C("tbody",null,[(x(!0),O(Ve,null,dt(l(),(c,d)=>(x(),ne(Rpe,{key:d,row:c},null,8,["row"]))),128))])],2)],2)],2))}}),Bpe=Npe;const Vpe=wt(Bpe,{DescriptionsItem:XP}),zpe=On(XP),ZP=$e({center:Boolean,alignCenter:{type:Boolean,default:void 0},closeIcon:{type:pn},draggable:{type:Boolean,default:void 0},overflow:{type:Boolean,default:void 0},fullscreen:Boolean,headerClass:{type:ae([String,Array,Object,Boolean]),default:void 0},bodyClass:{type:ae([String,Array,Object,Boolean]),default:void 0},footerClass:{type:ae([String,Array,Object,Boolean]),default:void 0},showClose:{type:Boolean,default:!0},title:{type:String,default:""},ariaLevel:{type:String,default:"2"}}),Fpe={close:()=>!0},JP=$e({...ZP,appendToBody:Boolean,appendTo:{type:ae([String,Object]),default:"body"},beforeClose:{type:ae(Function)},destroyOnClose:Boolean,closeOnClickModal:{type:Boolean,default:!0},closeOnPressEscape:{type:Boolean,default:!0},lockScroll:{type:Boolean,default:!0},modal:{type:Boolean,default:!0},modalPenetrable:Boolean,openDelay:{type:Number,default:0},closeDelay:{type:Number,default:0},top:{type:String},modelValue:Boolean,modalClass:{type:ae([String,Array,Object,Boolean]),default:void 0},width:{type:[String,Number]},zIndex:{type:Number},trapFocus:Boolean,headerAriaLevel:{type:String,default:"2"},transition:{type:ae([String,Object]),default:void 0}}),QP={open:()=>!0,opened:()=>!0,close:()=>!0,closed:()=>!0,[bt]:e=>vn(e),openAutoFocus:()=>!0,closeAutoFocus:()=>!0},Hpe=$e({mask:{type:Boolean,default:!0},customMaskEvent:Boolean,overlayClass:{type:ae([String,Array,Object,Boolean]),default:void 0},zIndex:{type:ae([String,Number])}}),Wpe={click:e=>e instanceof MouseEvent},Kpe="overlay";var Upe=q({name:"ElOverlay",props:Hpe,emits:Wpe,setup(e,{slots:t,emit:n}){const a=ke(Kpe),r=s=>{n("click",s)},{onClick:o,onMousedown:i,onMouseup:l}=O6(e.customMaskEvent?void 0:r);return()=>e.mask?L("div",{class:[a.b(),e.overlayClass],style:{zIndex:e.zIndex},onClick:o,onMousedown:i,onMouseup:l},[ce(t,"default")],14,["onClick","onMouseup","onMousedown"]):ot("div",{class:e.overlayClass,style:{zIndex:e.zIndex,position:"fixed",top:"0px",right:"0px",bottom:"0px",left:"0px"}},[ce(t,"default")])}});const bw=Upe,eD=Symbol("dialogInjectionKey"),xx="dialog-fade",Gpe="ElDialog",tD=(e,t)=>{const n=Et().emit,{nextZIndex:a}=tp();let r="";const o=Fa(),i=Fa(),l=F(!1),s=F(!1),u=F(!1),c=F(e.zIndex??a()),d=F(!1);let v,f;const h=Hl(),m=D(()=>{var Y;return((Y=h.value)==null?void 0:Y.namespace)??"el"}),g=D(()=>{var Y;return(Y=h.value)==null?void 0:Y.dialog}),y=D(()=>{const Y={},X=`--${m.value}-dialog`;if(!e.fullscreen){e.top&&(Y[`${X}-margin-top`]=e.top);const ee=Vn(e.width);ee&&(Y[`${X}-width`]=ee)}return Y}),b=D(()=>{var Y;return(e.draggable??((Y=g.value)==null?void 0:Y.draggable)??!1)&&!e.fullscreen}),_=D(()=>{var Y;return e.alignCenter??((Y=g.value)==null?void 0:Y.alignCenter)??!1}),w=D(()=>{var Y;return e.overflow??((Y=g.value)==null?void 0:Y.overflow)??!1}),S=D(()=>e.modalPenetrable&&!e.modal&&!e.fullscreen),k=D(()=>_.value?{display:"flex"}:{}),T=D(()=>{var ee;const Y=e.transition??((ee=g.value)==null?void 0:ee.transition)??"dialog-fade",X={name:Y,onAfterEnter:M,onBeforeLeave:A,onAfterLeave:E};if(_t(Y)){const re={...Y},ie=(le,ve)=>he=>{Ae(le)?le.forEach(J=>{Qe(J)&&J(he)}):Qe(le)&&le(he),ve()};return re.onAfterEnter=ie(re.onAfterEnter,M),re.onBeforeLeave=ie(re.onBeforeLeave,A),re.onAfterLeave=ie(re.onAfterLeave,E),re.name||(re.name=xx,un(Gpe,`transition.name is missing when using object syntax, fallback to '${xx}'`)),re}return X});function M(){n("opened")}function E(){n("closed"),n(bt,!1),e.destroyOnClose&&(u.value=!1),d.value=!1}function A(){d.value=!0,n("close")}function I(){f==null||f(),v==null||v(),e.openDelay&&e.openDelay>0?{stop:v}=Bd(()=>K(),e.openDelay):K()}function P(){v==null||v(),f==null||f(),e.closeDelay&&e.closeDelay>0?{stop:f}=Bd(()=>H(),e.closeDelay):H()}function R(){function Y(X){X||(s.value=!0,l.value=!1)}e.beforeClose?e.beforeClose(Y):P()}function G(){e.closeOnClickModal&&R()}function K(){qt&&(l.value=!0)}function H(){l.value=!1}function z(){n("openAutoFocus")}function B(){n("closeAutoFocus")}function W(Y){var X;((X=Y.detail)==null?void 0:X.focusReason)==="pointer"&&Y.preventDefault()}e.lockScroll&&D1(l);function N(){e.closeOnPressEscape&&R()}function V(){!l.value||!S.value||e.zIndex!==void 0||(c.value=a())}return ye(()=>e.zIndex,()=>{c.value=e.zIndex??a()}),ye(()=>e.modelValue,Y=>{Y?(s.value=!1,d.value=!1,I(),u.value=!0,c.value=e.zIndex??a(),He(()=>{n("open"),t.value&&(t.value.parentElement.scrollTop=0,t.value.parentElement.scrollLeft=0,t.value.scrollTop=0)})):l.value&&P()}),ye(()=>e.fullscreen,Y=>{t.value&&(Y?(r=t.value.style.transform,t.value.style.transform=""):t.value.style.transform=r)}),mt(()=>{e.modelValue&&(l.value=!0,u.value=!0,I())}),{afterEnter:M,afterLeave:E,beforeLeave:A,handleClose:R,onModalClick:G,close:P,doClose:H,onOpenAutoFocus:z,onCloseAutoFocus:B,onCloseRequested:N,onFocusoutPrevented:W,bringToFront:V,titleId:o,bodyId:i,closed:s,style:y,overlayDialogStyle:k,rendered:u,visible:l,zIndex:c,transitionConfig:T,_draggable:b,_alignCenter:_,_overflow:w,closing:d,penetrable:S}},_w=(...e)=>t=>{e.forEach(n=>{n.value=t})},Ype=["aria-level"],jpe=["aria-label"],qpe=["id"];var Xpe=q({name:"ElDialogContent",__name:"dialog-content",props:ZP,emits:Fpe,setup(e,{expose:t}){const{t:n}=Vt(),{Close:a}=YI,r=e,{dialogRef:o,headerRef:i,bodyId:l,ns:s,style:u}=Ze(eD),{focusTrapRef:c}=Ze(rL),d=_w(c,o),v=D(()=>!!r.draggable),{resetPosition:f,updatePosition:h,isDragging:m}=BA(o,i,v,D(()=>!!r.overflow)),g=D(()=>[s.b(),s.is("fullscreen",r.fullscreen),s.is("draggable",v.value),s.is("dragging",m.value),s.is("align-center",!!r.alignCenter),{[s.m("center")]:r.center}]);return t({resetPosition:f,updatePosition:h}),(y,b)=>(x(),O("div",{ref:p(d),class:U(g.value),style:et(p(u)),tabindex:"-1"},[C("header",{ref_key:"headerRef",ref:i,class:U([p(s).e("header"),e.headerClass,{"show-close":e.showClose}])},[ce(y.$slots,"header",{},()=>[C("span",{role:"heading","aria-level":e.ariaLevel,class:U(p(s).e("title"))},Q(e.title),11,Ype)]),e.showClose?(x(),O("button",{key:0,"aria-label":p(n)("el.dialog.close"),class:U(p(s).e("headerbtn")),type:"button",onClick:b[0]||(b[0]=_=>y.$emit("close"))},[L(p(Xe),{class:U(p(s).e("close"))},{default:$(()=>[(x(),ne(St(e.closeIcon||p(a))))]),_:1},8,["class"])],10,jpe)):te("v-if",!0)],2),C("div",{id:p(l),class:U([p(s).e("body"),e.bodyClass])},[ce(y.$slots,"default")],10,qpe),y.$slots.footer?(x(),O("footer",{key:0,class:U([p(s).e("footer"),e.footerClass])},[ce(y.$slots,"footer")],2)):te("v-if",!0)],6))}}),Zpe=Xpe;const Jpe=["aria-label","aria-labelledby","aria-describedby"];var Qpe=q({name:"ElDialog",inheritAttrs:!1,__name:"dialog",props:JP,emits:QP,setup(e,{expose:t}){const n=e,a=Xn();Ri({scope:"el-dialog",from:"the title slot",replacement:"the header slot",version:"3.0.0",ref:"https://element-plus.org/en-US/component/dialog.html#slots"},D(()=>!!a.title));const r=ke("dialog"),o=F(),i=F(),l=F(),{visible:s,titleId:u,bodyId:c,style:d,overlayDialogStyle:v,rendered:f,transitionConfig:h,zIndex:m,_draggable:g,_alignCenter:y,_overflow:b,penetrable:_,handleClose:w,onModalClick:S,onOpenAutoFocus:k,onCloseAutoFocus:T,onCloseRequested:M,onFocusoutPrevented:E,bringToFront:A,closing:I}=tD(n,o);Ft(eD,{dialogRef:o,headerRef:i,bodyId:c,ns:r,rendered:f,style:d});const P=O6(S);return t({visible:s,dialogContentRef:l,resetPosition:()=>{var G;(G=l.value)==null||G.resetPosition()},handleClose:w}),(G,K)=>(x(),ne(sf,{to:e.appendTo,disabled:e.appendTo!=="body"?!1:!e.appendToBody},[L(Pa,Tt(p(h),{persisted:""}),{default:$(()=>[st(L(p(bw),{"custom-mask-event":"",mask:e.modal,"overlay-class":[e.modalClass??"",`${p(r).namespace.value}-modal-dialog`,p(r).is("penetrable",p(_))],"z-index":p(m)},{default:$(()=>[C("div",{role:"dialog","aria-modal":"true","aria-label":e.title||void 0,"aria-labelledby":e.title?void 0:p(u),"aria-describedby":p(c),class:U([`${p(r).namespace.value}-overlay-dialog`,p(r).is("closing",p(I))]),style:et(p(v)),onClick:K[0]||(K[0]=(...H)=>p(P).onClick&&p(P).onClick(...H)),onMousedown:K[1]||(K[1]=(...H)=>p(P).onMousedown&&p(P).onMousedown(...H)),onMouseup:K[2]||(K[2]=(...H)=>p(P).onMouseup&&p(P).onMouseup(...H))},[L(p(pf),{loop:"",trapped:p(s),"focus-start-el":"container",onFocusAfterTrapped:p(k),onFocusAfterReleased:p(T),onFocusoutPrevented:p(E),onReleaseRequested:p(M)},{default:$(()=>[p(f)?(x(),ne(Zpe,Tt({key:0,ref_key:"dialogContentRef",ref:l},G.$attrs,{center:e.center,"align-center":p(y),"close-icon":e.closeIcon,draggable:p(g),overflow:p(b),fullscreen:e.fullscreen,"header-class":e.headerClass,"body-class":e.bodyClass,"footer-class":e.footerClass,"show-close":e.showClose,title:e.title,"aria-level":e.headerAriaLevel,onClose:p(w),onMousedown:p(A)}),xr({header:$(()=>[G.$slots.title?ce(G.$slots,"title",{key:1}):ce(G.$slots,"header",{key:0,close:p(w),titleId:p(u),titleClass:p(r).e("title")})]),default:$(()=>[ce(G.$slots,"default")]),_:2},[G.$slots.footer?{name:"footer",fn:$(()=>[ce(G.$slots,"footer")]),key:"0"}:void 0]),1040,["center","align-center","close-icon","draggable","overflow","fullscreen","header-class","body-class","footer-class","show-close","title","aria-level","onClose","onMousedown"])):te("v-if",!0)]),_:3},8,["trapped","onFocusAfterTrapped","onFocusAfterReleased","onFocusoutPrevented","onReleaseRequested"])],46,Jpe)]),_:3},8,["mask","overlay-class","z-index"]),[[Zt,p(s)]])]),_:3},16)],8,["to","disabled"]))}}),e0e=Qpe;const t0e=wt(e0e),n0e=$e({direction:{type:String,values:["horizontal","vertical"],default:"horizontal"},contentPosition:{type:String,values:["left","center","right"],default:"center"},borderStyle:{type:ae(String),default:"solid"}});var a0e=q({name:"ElDivider",__name:"divider",props:n0e,setup(e){const t=e,n=ke("divider"),a=D(()=>n.cssVar({"border-style":t.borderStyle}));return(r,o)=>(x(),O("div",{class:U([p(n).b(),p(n).m(e.direction)]),style:et(a.value),role:"separator"},[r.$slots.default&&e.direction!=="vertical"?(x(),O("div",{key:0,class:U([p(n).e("text"),p(n).is(e.contentPosition)])},[ce(r.$slots,"default")],2)):te("v-if",!0)],6))}}),r0e=a0e;const nD=wt(r0e),o0e=$e({...JP,direction:{type:String,default:"rtl",values:["ltr","rtl","ttb","btt"]},resizable:Boolean,size:{type:[String,Number],default:"30%"},withHeader:{type:Boolean,default:!0},modalFade:{type:Boolean,default:!0},headerAriaLevel:{type:String,default:"2"}}),i0e={...QP,"resize-start":(e,t)=>e instanceof MouseEvent&&typeof t=="number",resize:(e,t)=>e instanceof MouseEvent&&typeof t=="number","resize-end":(e,t)=>e instanceof MouseEvent&&typeof t=="number"};function l0e(e,t,n){const{width:a,height:r}=_6(),o=D(()=>["ltr","rtl"].includes(e.direction)),i=D(()=>["ltr","ttb"].includes(e.direction)?1:-1),l=D(()=>o.value?a.value:r.value),s=D(()=>I1(u.value+i.value*c.value,4,l.value)),u=F(0),c=F(0),d=F(!1),v=F(!1);let f=[],h=[];const m=()=>{var S;const w=(S=t.value)==null?void 0:S.closest('[aria-modal="true"]');return w?o.value?w.offsetWidth:w.offsetHeight:100};ye(()=>[e.size,e.resizable],()=>{v.value=!1,u.value=0,c.value=0,b()});const g=w=>{e.resizable&&(v.value||(u.value=m(),v.value=!0),f=[w.pageX,w.pageY],d.value=!0,n("resize-start",w,u.value),h.push(on(window,"mouseup",b),on(window,"mousemove",y)))},y=w=>{const{pageX:S,pageY:k}=w,T=S-f[0],M=k-f[1];c.value=o.value?T:M,n("resize",w,s.value)},b=w=>{d.value&&(f=[],u.value=s.value,c.value=0,d.value=!1,h.forEach(S=>S==null?void 0:S()),h=[],w&&n("resize-end",w,u.value))},_=on(t,"mousedown",g);return rn(()=>{_(),b()}),{size:D(()=>v.value?`${s.value}px`:Vn(e.size)),isResizing:d,isHorizontal:o}}const s0e=["aria-label","aria-labelledby","aria-describedby"],u0e=["id","aria-level"],c0e=["aria-label"],d0e=["id"];var f0e=q({name:"ElDrawer",inheritAttrs:!1,__name:"drawer",props:o0e,emits:i0e,setup(e,{expose:t,emit:n}){const a=e,r=n,o=Xn();Ri({scope:"el-drawer",from:"the title slot",replacement:"the header slot",version:"3.0.0",ref:"https://element-plus.org/en-US/component/drawer.html#slots"},D(()=>!!o.title));const i=F(),l=F(),s=F(),u=ke("drawer"),{t:c}=Vt(),{afterEnter:d,afterLeave:v,beforeLeave:f,visible:h,rendered:m,titleId:g,bodyId:y,zIndex:b,onModalClick:_,onOpenAutoFocus:w,onCloseAutoFocus:S,onFocusoutPrevented:k,onCloseRequested:T,handleClose:M}=tD(a,i),{isHorizontal:E,size:A,isResizing:I}=l0e(a,s,r),P=D(()=>a.modalPenetrable&&!a.modal);return t({handleClose:M}),(R,G)=>(x(),ne(sf,{to:e.appendTo,disabled:e.appendTo!=="body"?!1:!e.appendToBody},[L(Pa,{name:p(u).b("fade"),onAfterEnter:p(d),onAfterLeave:p(v),onBeforeLeave:p(f),persisted:""},{default:$(()=>[st(L(p(bw),{mask:e.modal,"overlay-class":[p(u).is("drawer"),e.modalClass??"",`${p(u).namespace.value}-modal-drawer`,p(u).is("penetrable",P.value)],"z-index":p(b),onClick:p(_)},{default:$(()=>[L(p(pf),{loop:"",trapped:p(h),"focus-trap-el":i.value,"focus-start-el":l.value,onFocusAfterTrapped:p(w),onFocusAfterReleased:p(S),onFocusoutPrevented:p(k),onReleaseRequested:p(T)},{default:$(()=>[C("div",Tt({ref_key:"drawerRef",ref:i,"aria-modal":"true","aria-label":e.title||void 0,"aria-labelledby":e.title?void 0:p(g),"aria-describedby":p(y)},R.$attrs,{class:[p(u).b(),e.direction,p(h)&&"open",p(u).is("dragging",p(I))],style:{[p(E)?"width":"height"]:p(A)},role:"dialog",onClick:G[1]||(G[1]=at(()=>{},["stop"]))}),[C("span",{ref_key:"focusStartRef",ref:l,class:U(p(u).e("sr-focus")),tabindex:"-1"},null,2),e.withHeader?(x(),O("header",{key:0,class:U([p(u).e("header"),e.headerClass])},[R.$slots.title?ce(R.$slots,"title",{key:1},()=>[te(" DEPRECATED SLOT ")]):ce(R.$slots,"header",{key:0,close:p(M),titleId:p(g),titleClass:p(u).e("title")},()=>[C("span",{id:p(g),role:"heading","aria-level":e.headerAriaLevel,class:U(p(u).e("title"))},Q(e.title),11,u0e)]),e.showClose?(x(),O("button",{key:2,"aria-label":p(c)("el.drawer.close"),class:U(p(u).e("close-btn")),type:"button",onClick:G[0]||(G[0]=(...K)=>p(M)&&p(M)(...K))},[L(p(Xe),{class:U(p(u).e("close"))},{default:$(()=>[L(p(so))]),_:1},8,["class"])],10,c0e)):te("v-if",!0)],2)):te("v-if",!0),p(m)?(x(),O("div",{key:1,id:p(y),class:U([p(u).e("body"),e.bodyClass])},[ce(R.$slots,"default")],10,d0e)):te("v-if",!0),R.$slots.footer?(x(),O("div",{key:2,class:U([p(u).e("footer"),e.footerClass])},[ce(R.$slots,"footer")],2)):te("v-if",!0),e.resizable?(x(),O("div",{key:3,ref_key:"draggerRef",ref:s,style:et({zIndex:p(b)}),class:U(p(u).e("dragger"))},null,6)):te("v-if",!0)],16,s0e)]),_:3},8,["trapped","focus-trap-el","focus-start-el","onFocusAfterTrapped","onFocusAfterReleased","onFocusoutPrevented","onReleaseRequested"])]),_:3},8,["mask","overlay-class","z-index","onClick"]),[[Zt,p(h)]])]),_:3},8,["name","onAfterEnter","onAfterLeave","onBeforeLeave"])],8,["to","disabled"]))}}),v0e=f0e;const h0e=wt(v0e),J4=$e({trigger:{...qu.trigger,type:ae([String,Array])},triggerKeys:{type:ae(Array),default:()=>[Pe.enter,Pe.numpadEnter,Pe.space,Pe.down]},virtualTriggering:qu.virtualTriggering,virtualRef:qu.virtualRef,effect:{...Tn.effect,default:"light"},type:{type:ae(String)},placement:{type:ae(String),default:"bottom"},popperOptions:{type:ae(Object),default:()=>({})},id:String,size:{type:String,default:""},splitButton:Boolean,hideOnClick:{type:Boolean,default:!0},loop:{type:Boolean,default:!0},showArrow:{type:Boolean,default:!0},showTimeout:{type:Number,default:150},hideTimeout:{type:Number,default:150},tabindex:{type:ae([Number,String]),default:0},maxHeight:{type:ae([Number,String]),default:""},popperClass:Tn.popperClass,popperStyle:Tn.popperStyle,disabled:Boolean,role:{type:String,values:jI,default:"menu"},buttonProps:{type:ae(Object)},teleported:Tn.teleported,appendTo:Tn.appendTo,persistent:{type:Boolean,default:!0}}),aD=$e({command:{type:[Object,String,Number],default:()=>({})},disabled:Boolean,divided:Boolean,textValue:String,icon:{type:pn}}),p0e=$e({onKeydown:{type:ae(Function)}}),Y1=Symbol("elDropdown"),rD="elDropdown";var m0e=q({inheritAttrs:!1});function g0e(e,t,n,a,r,o){return ce(e.$slots,"default")}var y0e=ya(m0e,[["render",g0e]]),b0e=q({name:"ElCollectionItem",inheritAttrs:!1});function _0e(e,t,n,a,r,o){return ce(e.$slots,"default")}var w0e=ya(b0e,[["render",_0e]]);const oD="data-el-collection-item",S0e=e=>{const t=`El${e}Collection`,n=`${t}Item`,a=Symbol(t),r=Symbol(n);return{COLLECTION_INJECTION_KEY:a,COLLECTION_ITEM_INJECTION_KEY:r,ElCollection:Object.assign({},y0e,{name:t,setup(){const o=F(),i=new Map;Ft(a,{itemMap:i,getItems:()=>{const s=p(o);if(!s)return[];const u=Array.from(s.querySelectorAll(`[${oD}]`));return[...i.values()].sort((c,d)=>u.indexOf(c.ref)-u.indexOf(d.ref))},collectionRef:o})}}),ElCollectionItem:Object.assign({},w0e,{name:n,setup(o,{attrs:i}){const l=F(),s=Ze(a,void 0);Ft(r,{collectionItemRef:l}),mt(()=>{const u=p(l);u&&s.itemMap.set(u,{ref:u,...i})}),rn(()=>{const u=p(l);s.itemMap.delete(u)})}})}},C0e=$e({style:{type:ae([String,Array,Object,Boolean]),default:void 0},currentTabId:{type:ae(String)},defaultCurrentTabId:String,loop:Boolean,dir:{type:String,values:["ltr","rtl"],default:"ltr"},orientation:{type:ae(String)},onBlur:Function,onFocus:Function,onMousedown:Function}),{ElCollection:x0e,ElCollectionItem:k0e,COLLECTION_INJECTION_KEY:iD,COLLECTION_ITEM_INJECTION_KEY:T0e}=S0e("RovingFocusGroup"),ww=Symbol("elRovingFocusGroup"),lD=Symbol("elRovingFocusGroupItem"),M0e={ArrowLeft:"prev",ArrowUp:"prev",ArrowRight:"next",ArrowDown:"next",PageUp:"first",Home:"first",PageDown:"last",End:"last"},E0e=(e,t)=>e,A0e=(e,t,n)=>{const a=E0e(mn(e));return M0e[a]},I0e=(e,t)=>e.map((n,a)=>e[(a+t)%e.length]),kx=e=>{const{activeElement:t}=document;for(const n of e)if(n===t||(n.focus(),t!==document.activeElement))return},Tx="currentTabIdChange",Mx="rovingFocusGroup.entryFocus",L0e={bubbles:!1,cancelable:!0};var P0e=q({name:"ElRovingFocusGroupImpl",inheritAttrs:!1,props:C0e,emits:[Tx,"entryFocus"],setup(e,{emit:t}){const n=F((e.currentTabId||e.defaultCurrentTabId)??null),a=F(!1),r=F(!1),o=F(),{getItems:i}=Ze(iD,void 0),l=D(()=>[{outline:"none"},e.style]),s=m=>{t(Tx,m)},u=()=>{a.value=!0},c=_a(m=>{var g;(g=e.onMousedown)==null||g.call(e,m)},()=>{r.value=!0}),d=_a(m=>{var g;(g=e.onFocus)==null||g.call(e,m)},m=>{const g=!p(r),{target:y,currentTarget:b}=m;if(y===b&&g&&!p(a)){const _=new Event(Mx,L0e);if(b==null||b.dispatchEvent(_),!_.defaultPrevented){const w=i().filter(S=>S.focusable);kx([w.find(S=>S.active),w.find(S=>S.id===p(n)),...w].filter(Boolean).map(S=>S.ref))}}r.value=!1}),v=_a(m=>{var g;(g=e.onBlur)==null||g.call(e,m)},()=>{a.value=!1}),f=(...m)=>{t("entryFocus",...m)},h=m=>{const g=A0e(m);if(g){m.preventDefault();let y=i().filter(b=>b.focusable).map(b=>b.ref);switch(g){case"last":y.reverse();break;case"prev":case"next":{g==="prev"&&y.reverse();const b=y.indexOf(m.currentTarget);y=e.loop?I0e(y,b+1):y.slice(b+1);break}}He(()=>{kx(y)})}};Ft(ww,{currentTabbedId:Ld(n),loop:cn(e,"loop"),tabIndex:D(()=>p(a)?-1:0),rovingFocusGroupRef:o,rovingFocusGroupRootStyle:l,orientation:cn(e,"orientation"),dir:cn(e,"dir"),onItemFocus:s,onItemShiftTab:u,onBlur:v,onFocus:d,onMousedown:c,onKeydown:h}),ye(()=>e.currentTabId,m=>{n.value=m??null}),on(o,Mx,f)}});function D0e(e,t,n,a,r,o){return ce(e.$slots,"default")}var O0e=ya(P0e,[["render",D0e]]),$0e=q({name:"ElRovingFocusGroup",components:{ElFocusGroupCollection:x0e,ElRovingFocusGroupImpl:O0e}});function R0e(e,t,n,a,r,o){const i=be("el-roving-focus-group-impl"),l=be("el-focus-group-collection");return x(),ne(l,null,{default:$(()=>[L(i,bl(wl(e.$attrs)),{default:$(()=>[ce(e.$slots,"default")]),_:3},16)]),_:3})}var N0e=ya($0e,[["render",R0e]]),B0e=q({components:{ElRovingFocusCollectionItem:k0e},props:{focusable:{type:Boolean,default:!0},active:Boolean},emits:["mousedown","focus","keydown"],setup(e,{emit:t}){const{currentTabbedId:n,onItemFocus:a,onItemShiftTab:r,onKeydown:o}=Ze(ww,void 0),i=Fa(),l=F(),s=_a(v=>{t("mousedown",v)},v=>{e.focusable?a(p(i)):v.preventDefault()}),u=_a(v=>{t("focus",v)},()=>{a(p(i))}),c=_a(v=>{t("keydown",v)},v=>{const{shiftKey:f,target:h,currentTarget:m}=v;if(mn(v)===Pe.tab&&f){r();return}h===m&&o(v)}),d=D(()=>n.value===p(i));return Ft(lD,{rovingFocusGroupItemRef:l,tabIndex:D(()=>p(d)?0:-1),handleMousedown:s,handleFocus:u,handleKeydown:c}),{id:i,handleKeydown:c,handleFocus:u,handleMousedown:s}}});function V0e(e,t,n,a,r,o){const i=be("el-roving-focus-collection-item");return x(),ne(i,{id:e.id,focusable:e.focusable,active:e.active},{default:$(()=>[ce(e.$slots,"default")]),_:3},8,["id","focusable","active"])}var z0e=ya(B0e,[["render",V0e]]),F0e=N0e;const{ButtonGroup:H0e}=xa;var W0e=q({name:"ElDropdown",components:{ElButton:xa,ElButtonGroup:H0e,ElScrollbar:ni,ElTooltip:Ma,ElRovingFocusGroup:F0e,ElOnlyChild:JI,ElIcon:Xe,ArrowDown:ui},props:J4,emits:["visible-change","click","command"],setup(e,{emit:t}){const n=Et(),a=ke("dropdown"),{t:r}=Vt(),o=F(),i=F(),l=F(),s=F(),u=F(null),c=F(null),d=F(!1),v=D(()=>({maxHeight:Vn(e.maxHeight)})),f=D(()=>[a.m(w.value)]),h=D(()=>wa(e.trigger)),m=Fa().value,g=D(()=>e.id||m);function y(){var R;(R=l.value)==null||R.onClose(void 0,0)}function b(){var R;(R=l.value)==null||R.onClose()}function _(){var R;(R=l.value)==null||R.onOpen()}const w=ca();function S(...R){t("command",...R)}function k(){}function T(){const R=p(s);h.value.includes("hover")&&(R==null||R.focus({preventScroll:!0})),c.value=null}function M(R){c.value=R}function E(){t("visible-change",!0)}function A(R){var G;d.value=(R==null?void 0:R.type)==="keydown",(G=s.value)==null||G.focus()}function I(){t("visible-change",!1)}return Ft(Y1,{contentRef:s,role:D(()=>e.role),triggerId:g,isUsingKeyboard:d,onItemEnter:k,onItemLeave:T,handleClose:b}),Ft(rD,{instance:n,dropdownSize:w,handleClick:y,commandHandler:S,trigger:cn(e,"trigger"),hideOnClick:cn(e,"hideOnClick")}),{t:r,ns:a,scrollbar:u,wrapStyle:v,dropdownTriggerKls:f,dropdownSize:w,triggerId:g,currentTabId:c,handleCurrentTabIdChange:M,handlerMainButtonClick:R=>{t("click",R)},handleClose:b,handleOpen:_,handleBeforeShowTooltip:E,handleShowTooltip:A,handleBeforeHideTooltip:I,popperRef:l,contentRef:s,triggeringElementRef:o,referenceElementRef:i}}});function K0e(e,t,n,a,r,o){const i=be("el-roving-focus-group"),l=be("el-scrollbar"),s=be("el-only-child"),u=be("el-tooltip"),c=be("el-button"),d=be("arrow-down"),v=be("el-icon"),f=be("el-button-group");return x(),O("div",{class:U([e.ns.b(),e.ns.is("disabled",e.disabled)])},[L(u,{ref:"popperRef",role:e.role,effect:e.effect,"fallback-placements":["bottom","top"],"popper-options":e.popperOptions,"gpu-acceleration":!1,placement:e.placement,"popper-class":[e.ns.e("popper"),e.popperClass],"popper-style":e.popperStyle,trigger:e.trigger,"trigger-keys":e.triggerKeys,"trigger-target-el":e.contentRef,"show-arrow":e.showArrow,"show-after":e.trigger==="hover"?e.showTimeout:0,"hide-after":e.trigger==="hover"?e.hideTimeout:0,"virtual-ref":e.virtualRef??e.triggeringElementRef,"virtual-triggering":e.virtualTriggering||e.splitButton,disabled:e.disabled,transition:`${e.ns.namespace.value}-zoom-in-top`,teleported:e.teleported,"append-to":e.appendTo,pure:"","focus-on-target":"",persistent:e.persistent,onBeforeShow:e.handleBeforeShowTooltip,onShow:e.handleShowTooltip,onBeforeHide:e.handleBeforeHideTooltip},xr({content:$(()=>[L(l,{ref:"scrollbar","wrap-style":e.wrapStyle,tag:"div","view-class":e.ns.e("list")},{default:$(()=>[L(i,{loop:e.loop,"current-tab-id":e.currentTabId,orientation:"horizontal",onCurrentTabIdChange:e.handleCurrentTabIdChange},{default:$(()=>[ce(e.$slots,"dropdown")]),_:3},8,["loop","current-tab-id","onCurrentTabIdChange"])]),_:3},8,["wrap-style","view-class"])]),_:2},[e.splitButton?void 0:{name:"default",fn:$(()=>[L(s,{id:e.triggerId,ref:"triggeringElementRef",role:"button",tabindex:e.tabindex},{default:$(()=>[ce(e.$slots,"default")]),_:3},8,["id","tabindex"])]),key:"0"}]),1032,["role","effect","popper-options","placement","popper-class","popper-style","trigger","trigger-keys","trigger-target-el","show-arrow","show-after","hide-after","virtual-ref","virtual-triggering","disabled","transition","teleported","append-to","persistent","onBeforeShow","onShow","onBeforeHide"]),e.splitButton?(x(),ne(f,{key:0},{default:$(()=>[L(c,Tt({ref:"referenceElementRef"},e.buttonProps,{size:e.dropdownSize,type:e.type,disabled:e.disabled,tabindex:e.tabindex,onClick:e.handlerMainButtonClick}),{default:$(()=>[ce(e.$slots,"default")]),_:3},16,["size","type","disabled","tabindex","onClick"]),L(c,Tt({id:e.triggerId,ref:"triggeringElementRef"},e.buttonProps,{role:"button",size:e.dropdownSize,type:e.type,class:e.ns.e("caret-button"),disabled:e.disabled,tabindex:e.tabindex,"aria-label":e.t("el.dropdown.toggleDropdown")}),{default:$(()=>[L(v,{class:U(e.ns.e("icon"))},{default:$(()=>[L(d)]),_:1},8,["class"])]),_:1},16,["id","size","type","class","disabled","tabindex","aria-label"])]),_:3})):te("v-if",!0)],2)}var U0e=ya(W0e,[["render",K0e]]),G0e=q({name:"DropdownItemImpl",components:{ElIcon:Xe},props:aD,emits:["pointermove","pointerleave","click","clickimpl"],setup(e,{emit:t}){const n=ke("dropdown"),{role:a}=Ze(Y1,void 0),{collectionItemRef:r}=Ze(T0e,void 0),{rovingFocusGroupItemRef:o,tabIndex:i,handleFocus:l,handleKeydown:s,handleMousedown:u}=Ze(lD,void 0),c=_w(r,o),d=D(()=>a.value==="menu"?"menuitem":a.value==="navigation"?"link":"button"),v=_a(f=>{const h=mn(f);if([Pe.enter,Pe.numpadEnter,Pe.space].includes(h))return f.preventDefault(),f.stopImmediatePropagation(),t("clickimpl",f),!0},s);return{ns:n,itemRef:c,dataset:{[oD]:""},role:d,tabIndex:i,handleFocus:l,handleKeydown:v,handleMousedown:u}}});const Y0e=["aria-disabled","tabindex","role"];function j0e(e,t,n,a,r,o){const i=be("el-icon");return x(),O(Ve,null,[e.divided?(x(),O("li",{key:0,role:"separator",class:U(e.ns.bem("menu","item","divided"))},null,2)):te("v-if",!0),C("li",Tt({ref:e.itemRef},{...e.dataset,...e.$attrs},{"aria-disabled":e.disabled,class:[e.ns.be("menu","item"),e.ns.is("disabled",e.disabled)],tabindex:e.tabIndex,role:e.role,onClick:t[0]||(t[0]=l=>e.$emit("clickimpl",l)),onFocus:t[1]||(t[1]=(...l)=>e.handleFocus&&e.handleFocus(...l)),onKeydown:t[2]||(t[2]=at((...l)=>e.handleKeydown&&e.handleKeydown(...l),["self"])),onMousedown:t[3]||(t[3]=(...l)=>e.handleMousedown&&e.handleMousedown(...l)),onPointermove:t[4]||(t[4]=l=>e.$emit("pointermove",l)),onPointerleave:t[5]||(t[5]=l=>e.$emit("pointerleave",l))}),[e.icon||e.$slots.icon?(x(),ne(i,{key:0},{default:$(()=>[ce(e.$slots,"icon",{},()=>[(x(),ne(St(e.icon)))])]),_:3})):te("v-if",!0),ce(e.$slots,"default")],16,Y0e)],64)}var q0e=ya(G0e,[["render",j0e]]);const sD=()=>{const e=Ze(rD,{});return{elDropdown:e,_elDropdownSize:D(()=>e==null?void 0:e.dropdownSize)}};var X0e=q({name:"ElDropdownItem",components:{ElRovingFocusItem:z0e,ElDropdownItemImpl:q0e},inheritAttrs:!1,props:aD,emits:["pointermove","pointerleave","click"],setup(e,{emit:t,attrs:n}){const{elDropdown:a}=sD(),r=Et(),{onItemEnter:o,onItemLeave:i}=Ze(Y1,void 0),l=_a(u=>(t("pointermove",u),u.defaultPrevented),YC(u=>{if(e.disabled){i(u);return}const c=u.currentTarget;c===document.activeElement||c.contains(document.activeElement)||(o(u),u.defaultPrevented||c==null||c.focus({preventScroll:!0}))})),s=_a(u=>(t("pointerleave",u),u.defaultPrevented),YC(i));return{handleClick:_a(u=>{if(!e.disabled)return t("click",u),u.type!=="keydown"&&u.defaultPrevented},u=>{var c,d,v;if(e.disabled){u.stopImmediatePropagation();return}(c=a==null?void 0:a.hideOnClick)!=null&&c.value&&((d=a.handleClick)==null||d.call(a)),(v=a.commandHandler)==null||v.call(a,e.command,r,u)}),handlePointerMove:l,handlePointerLeave:s,propsAndAttrs:D(()=>({...e,...n}))}}});function Z0e(e,t,n,a,r,o){const i=be("el-dropdown-item-impl"),l=be("el-roving-focus-item");return x(),ne(l,{focusable:!e.disabled},{default:$(()=>[L(i,Tt(e.propsAndAttrs,{onPointerleave:e.handlePointerLeave,onPointermove:e.handlePointerMove,onClickimpl:e.handleClick}),xr({default:$(()=>[ce(e.$slots,"default")]),_:2},[e.$slots.icon?{name:"icon",fn:$(()=>[ce(e.$slots,"icon")]),key:"0"}:void 0]),1040,["onPointerleave","onPointermove","onClickimpl"])]),_:3},8,["focusable"])}var uD=ya(X0e,[["render",Z0e]]),J0e=q({name:"ElDropdownMenu",props:p0e,setup(e){const t=ke("dropdown"),{_elDropdownSize:n}=sD(),a=n.value,{contentRef:r,role:o,triggerId:i,isUsingKeyboard:l,handleClose:s}=Ze(Y1,void 0),{rovingFocusGroupRef:u,rovingFocusGroupRootStyle:c,onBlur:d,onFocus:v,onKeydown:f,onMousedown:h}=Ze(ww,void 0),{collectionRef:m}=Ze(iD,void 0),g=D(()=>[t.b("menu"),t.bm("menu",a==null?void 0:a.value)]),y=_w(r,u,m),b=_a(w=>{var S;(S=e.onKeydown)==null||S.call(e,w)},w=>{const{currentTarget:S,target:k}=w,T=mn(w);if(S.contains(k),Pe.tab===T)return s();f(w)});function _(w){l.value&&v(w)}return{size:a,rovingFocusGroupRootStyle:c,dropdownKls:g,role:o,triggerId:i,dropdownListWrapperRef:y,handleKeydown:b,onBlur:d,handleFocus:_,onMousedown:h}}});const Q0e=["role","aria-labelledby"];function eme(e,t,n,a,r,o){return x(),O("ul",{ref:e.dropdownListWrapperRef,class:U(e.dropdownKls),style:et(e.rovingFocusGroupRootStyle),tabindex:-1,role:e.role,"aria-labelledby":e.triggerId,onFocusin:t[0]||(t[0]=(...i)=>e.handleFocus&&e.handleFocus(...i)),onFocusout:t[1]||(t[1]=(...i)=>e.onBlur&&e.onBlur(...i)),onKeydown:t[2]||(t[2]=at((...i)=>e.handleKeydown&&e.handleKeydown(...i),["self"])),onMousedown:t[3]||(t[3]=at((...i)=>e.onMousedown&&e.onMousedown(...i),["self"]))},[ce(e.$slots,"default")],46,Q0e)}var cD=ya(J0e,[["render",eme]]);const tme=wt(U0e,{DropdownItem:uD,DropdownMenu:cD}),nme=On(uD),ame=On(cD),rme=$e({image:{type:String,default:""},imageSize:Number,description:{type:String,default:""}}),ome={viewBox:"0 0 79 86",version:"1.1",xmlns:"http://www.w3.org/2000/svg","xmlns:xlink":"http://www.w3.org/1999/xlink"},ime=["id"],lme=["stop-color"],sme=["stop-color"],ume=["id"],cme=["stop-color"],dme=["stop-color"],fme=["id"],vme={stroke:"none","stroke-width":"1",fill:"none","fill-rule":"evenodd"},hme={transform:"translate(-1268.000000, -535.000000)"},pme={transform:"translate(1268.000000, 535.000000)"},mme=["fill"],gme=["fill"],yme={transform:"translate(34.500000, 31.500000) scale(-1, 1) rotate(-25.000000) translate(-34.500000, -31.500000) translate(7.000000, 10.000000)"},bme=["fill"],_me=["fill"],wme=["fill"],Sme=["fill"],Cme=["fill"],xme={transform:"translate(53.000000, 45.000000)"},kme=["fill","xlink:href"],Tme=["fill","mask"],Mme=["fill"];var Eme=q({name:"ImgEmpty",__name:"img-empty",setup(e){const t=ke("empty"),n=Fa();return(a,r)=>(x(),O("svg",ome,[C("defs",null,[C("linearGradient",{id:`linearGradient-1-${p(n)}`,x1:"38.8503086%",y1:"0%",x2:"61.1496914%",y2:"100%"},[C("stop",{"stop-color":`var(${p(t).cssVarBlockName("fill-color-1")})`,offset:"0%"},null,8,lme),C("stop",{"stop-color":`var(${p(t).cssVarBlockName("fill-color-4")})`,offset:"100%"},null,8,sme)],8,ime),C("linearGradient",{id:`linearGradient-2-${p(n)}`,x1:"0%",y1:"9.5%",x2:"100%",y2:"90.5%"},[C("stop",{"stop-color":`var(${p(t).cssVarBlockName("fill-color-1")})`,offset:"0%"},null,8,cme),C("stop",{"stop-color":`var(${p(t).cssVarBlockName("fill-color-6")})`,offset:"100%"},null,8,dme)],8,ume),C("rect",{id:`path-3-${p(n)}`,x:"0",y:"0",width:"17",height:"36"},null,8,fme)]),C("g",vme,[C("g",hme,[C("g",pme,[C("path",{d:"M39.5,86 C61.3152476,86 79,83.9106622 79,81.3333333 C79,78.7560045 57.3152476,78 35.5,78 C13.6847524,78 0,78.7560045 0,81.3333333 C0,83.9106622 17.6847524,86 39.5,86 Z",fill:`var(${p(t).cssVarBlockName("fill-color-3")})`},null,8,mme),C("polygon",{fill:`var(${p(t).cssVarBlockName("fill-color-7")})`,transform:"translate(27.500000, 51.500000) scale(1, -1) translate(-27.500000, -51.500000) ",points:"13 58 53 58 42 45 2 45"},null,8,gme),C("g",yme,[C("polygon",{fill:`var(${p(t).cssVarBlockName("fill-color-7")})`,transform:"translate(11.500000, 5.000000) scale(1, -1) translate(-11.500000, -5.000000) ",points:"2.84078316e-14 3 18 3 23 7 5 7"},null,8,bme),C("polygon",{fill:`var(${p(t).cssVarBlockName("fill-color-5")})`,points:"-3.69149156e-15 7 38 7 38 43 -3.69149156e-15 43"},null,8,_me),C("rect",{fill:`url(#linearGradient-1-${p(n)})`,transform:"translate(46.500000, 25.000000) scale(-1, 1) translate(-46.500000, -25.000000) ",x:"38",y:"7",width:"17",height:"36"},null,8,wme),C("polygon",{fill:`var(${p(t).cssVarBlockName("fill-color-2")})`,transform:"translate(39.500000, 3.500000) scale(-1, 1) translate(-39.500000, -3.500000) ",points:"24 7 41 7 55 -3.63806207e-12 38 -3.63806207e-12"},null,8,Sme)]),C("rect",{fill:`url(#linearGradient-2-${p(n)})`,x:"13",y:"45",width:"40",height:"36"},null,8,Cme),C("g",xme,[C("use",{fill:`var(${p(t).cssVarBlockName("fill-color-8")})`,transform:"translate(8.500000, 18.000000) scale(-1, 1) translate(-8.500000, -18.000000) ","xlink:href":`#path-3-${p(n)}`},null,8,kme),C("polygon",{fill:`var(${p(t).cssVarBlockName("fill-color-9")})`,mask:`url(#mask-4-${p(n)})`,transform:"translate(12.000000, 9.000000) scale(-1, 1) translate(-12.000000, -9.000000) ",points:"7 0 24 0 20 18 7 16.5"},null,8,Tme)]),C("polygon",{fill:`var(${p(t).cssVarBlockName("fill-color-2")})`,transform:"translate(66.000000, 51.500000) scale(-1, 1) translate(-66.000000, -51.500000) ",points:"62 45 79 45 70 58 53 58"},null,8,Mme)])])])]))}}),Ame=Eme;const Ime=["src"],Lme={key:1};var Pme=q({name:"ElEmpty",__name:"empty",props:rme,setup(e){const t=e,{t:n}=Vt(),a=ke("empty"),r=D(()=>t.description||n("el.table.emptyText")),o=D(()=>({width:Vn(t.imageSize)}));return(i,l)=>(x(),O("div",{class:U(p(a).b())},[C("div",{class:U(p(a).e("image")),style:et(o.value)},[e.image?(x(),O("img",{key:0,src:e.image,ondragstart:"return false"},null,8,Ime)):ce(i.$slots,"image",{key:1},()=>[L(Ame)])],6),C("div",{class:U(p(a).e("description"))},[i.$slots.description?ce(i.$slots,"description",{key:0}):(x(),O("p",Lme,Q(r.value),1))],2),i.$slots.default?(x(),O("div",{key:0,class:U(p(a).e("bottom"))},[ce(i.$slots,"default")],2)):te("v-if",!0)],2))}}),Dme=Pme;const dD=wt(Dme),Ome=$e({hideOnClickModal:Boolean,src:{type:String,default:""},fit:{type:String,values:["","contain","cover","fill","none","scale-down"],default:""},loading:{type:String,values:["eager","lazy"]},lazy:Boolean,scrollContainer:{type:ae([String,Object])},previewSrcList:{type:ae(Array),default:()=>Nn([])},previewTeleported:Boolean,zIndex:{type:Number},initialIndex:{type:Number,default:0},infinite:{type:Boolean,default:!0},closeOnPressEscape:{type:Boolean,default:!0},zoomRate:{type:Number,default:1.2},scale:{type:Number,default:1},minScale:{type:Number,default:.2},maxScale:{type:Number,default:7},showProgress:Boolean,crossorigin:{type:ae(String)}}),$me={load:e=>e instanceof Event,error:e=>e instanceof Event,switch:e=>Je(e),close:()=>!0,show:()=>!0},Rme=$e({urlList:{type:ae(Array),default:()=>Nn([])},zIndex:{type:Number},initialIndex:{type:Number,default:0},infinite:{type:Boolean,default:!0},hideOnClickModal:Boolean,teleported:Boolean,closeOnPressEscape:{type:Boolean,default:!0},zoomRate:{type:Number,default:1.2},scale:{type:Number,default:1},minScale:{type:Number,default:.2},maxScale:{type:Number,default:7},showProgress:Boolean,crossorigin:{type:ae(String)}}),Nme={close:()=>!0,error:e=>e instanceof Event,switch:e=>Je(e),rotate:e=>Je(e)},Bme=["src","crossorigin"];var Vme=q({name:"ElImageViewer",__name:"image-viewer",props:Rme,emits:Nme,setup(e,{expose:t,emit:n}){const a={CONTAIN:{name:"contain",icon:$i(II)},ORIGINAL:{name:"original",icon:$i(FI)}},r=e,o=n;let i;const{t:l}=Vt(),s=ke("image-viewer"),{nextZIndex:u}=tp(),c=F(),d=F(),v=SV(),f=D(()=>{const{scale:he,minScale:J,maxScale:Z}=r;return I1(he,J,Z)}),h=F(!0),m=F(!1),g=F(!1),y=F(r.initialIndex),b=Ht(a.CONTAIN),_=F({scale:f.value,deg:0,offsetX:0,offsetY:0,enableTransition:!1}),w=F(r.zIndex??u());D1(g,{ns:s});const S=D(()=>{const{urlList:he}=r;return he.length<=1}),k=D(()=>y.value===0),T=D(()=>y.value===r.urlList.length-1),M=D(()=>r.urlList[y.value]),E=D(()=>[s.e("btn"),s.e("prev"),s.is("disabled",!r.infinite&&k.value)]),A=D(()=>[s.e("btn"),s.e("next"),s.is("disabled",!r.infinite&&T.value)]),I=D(()=>{const{scale:he,deg:J,offsetX:Z,offsetY:j,enableTransition:oe}=_.value;let se=Z/he,fe=j/he;const ge=J*Math.PI/180,Se=Math.cos(ge),De=Math.sin(ge);se=se*Se+fe*De,fe=fe*Se-Z/he*De;const Te={transform:`scale(${he}) rotate(${J}deg) translate(${se}px, ${fe}px)`,transition:oe?"transform .3s":""};return b.value.name===a.CONTAIN.name&&(Te.maxWidth=Te.maxHeight="100%"),Te}),P=D(()=>`${y.value+1} / ${r.urlList.length}`);function R(){K(),i==null||i(),g.value=!1,o("close")}function G(){const he=hs(Z=>{switch(mn(Z)){case Pe.esc:r.closeOnPressEscape&&R();break;case Pe.space:V();break;case Pe.left:X();break;case Pe.up:re("zoomIn");break;case Pe.right:ee();break;case Pe.down:re("zoomOut");break}}),J=hs(Z=>{re((Z.deltaY||Z.deltaX)<0?"zoomIn":"zoomOut",{zoomRate:r.zoomRate,enableTransition:!1})});v.run(()=>{on(document,"keydown",he),on(c,"wheel",J)})}function K(){v.stop()}function H(){h.value=!1}function z(he){m.value=!0,h.value=!1,o("error",he),he.target.alt=l("el.image.error")}function B(he){if(h.value||he.button!==0||!c.value)return;_.value.enableTransition=!1;const{offsetX:J,offsetY:Z}=_.value,j=he.pageX,oe=he.pageY,se=hs(Se=>{_.value={..._.value,offsetX:J+Se.pageX-j,offsetY:Z+Se.pageY-oe}}),fe=on(document,"mousemove",se),ge=on(document,"mouseup",()=>{fe(),ge()});he.preventDefault()}function W(he){if(h.value||!c.value||he.touches.length!==1)return;_.value.enableTransition=!1;const{offsetX:J,offsetY:Z}=_.value,{pageX:j,pageY:oe}=he.touches[0],se=hs(Se=>{const De=Se.touches[0];_.value={..._.value,offsetX:J+De.pageX-j,offsetY:Z+De.pageY-oe}}),fe=on(document,"touchmove",se),ge=on(document,"touchend",()=>{fe(),ge()});he.preventDefault()}function N(){_.value={scale:f.value,deg:0,offsetX:0,offsetY:0,enableTransition:!1}}function V(){if(h.value||m.value)return;const he=sh(a),J=Object.values(a),Z=b.value.name;b.value=a[he[(J.findIndex(j=>j.name===Z)+1)%he.length]],N()}function Y(he){m.value=!1;const J=r.urlList.length;y.value=(he+J)%J}function X(){k.value&&!r.infinite||Y(y.value-1)}function ee(){T.value&&!r.infinite||Y(y.value+1)}function re(he,J={}){if(h.value||m.value)return;const{minScale:Z,maxScale:j}=r,{zoomRate:oe,rotateDeg:se,enableTransition:fe}={zoomRate:r.zoomRate,rotateDeg:90,enableTransition:!0,...J};switch(he){case"zoomOut":_.value.scale>Z&&(_.value.scale=Number.parseFloat((_.value.scale/oe).toFixed(3)));break;case"zoomIn":_.value.scale0)return he.preventDefault(),!1}}return ye(()=>f.value,he=>{_.value.scale=he}),ye(M,()=>{He(()=>{var he;(he=d.value)!=null&&he.complete||(h.value=!0)})}),ye(y,he=>{N(),o("switch",he)}),mt(()=>{g.value=!0,G(),i=on("wheel",ve,{passive:!1})}),t({setActiveItem:Y}),(he,J)=>(x(),ne(sf,{to:"body",disabled:!e.teleported},[L(Pa,{name:"viewer-fade",appear:""},{default:$(()=>[C("div",{ref_key:"wrapper",ref:c,tabindex:-1,class:U(p(s).e("wrapper")),style:et({zIndex:w.value})},[L(p(pf),{loop:"",trapped:"","focus-trap-el":c.value,"focus-start-el":"container",onFocusoutPrevented:ie,onReleaseRequested:le},{default:$(()=>[C("div",{class:U(p(s).e("mask")),onClick:J[0]||(J[0]=at(Z=>e.hideOnClickModal&&R(),["self"]))},null,2),te(" CLOSE "),C("span",{class:U([p(s).e("btn"),p(s).e("close")]),onClick:R},[L(p(Xe),null,{default:$(()=>[L(p(so))]),_:1})],2),te(" ARROW "),S.value?te("v-if",!0):(x(),O(Ve,{key:0},[C("span",{class:U(E.value),onClick:X},[L(p(Xe),null,{default:$(()=>[L(p(Ji))]),_:1})],2),C("span",{class:U(A.value),onClick:ee},[L(p(Xe),null,{default:$(()=>[L(p(za))]),_:1})],2)],64)),he.$slots.progress||e.showProgress?(x(),O("div",{key:1,class:U([p(s).e("btn"),p(s).e("progress")])},[ce(he.$slots,"progress",{activeIndex:y.value,total:e.urlList.length},()=>[ue(Q(P.value),1)])],2)):te("v-if",!0),te(" ACTIONS "),C("div",{class:U([p(s).e("btn"),p(s).e("actions")])},[C("div",{class:U(p(s).e("actions__inner"))},[ce(he.$slots,"toolbar",{actions:re,prev:X,next:ee,reset:V,activeIndex:y.value,setActiveItem:Y},()=>[L(p(Xe),{onClick:J[1]||(J[1]=Z=>re("zoomOut"))},{default:$(()=>[L(p(GI))]),_:1}),L(p(Xe),{onClick:J[2]||(J[2]=Z=>re("zoomIn"))},{default:$(()=>[L(p(G6))]),_:1}),C("i",{class:U(p(s).e("actions__divider"))},null,2),L(p(Xe),{onClick:V},{default:$(()=>[(x(),ne(St(b.value.icon)))]),_:1}),C("i",{class:U(p(s).e("actions__divider"))},null,2),L(p(Xe),{onClick:J[3]||(J[3]=Z=>re("anticlockwise"))},{default:$(()=>[L(p(VI))]),_:1}),L(p(Xe),{onClick:J[4]||(J[4]=Z=>re("clockwise"))},{default:$(()=>[L(p(zI))]),_:1})])],2)],2),te(" CANVAS "),C("div",{class:U(p(s).e("canvas"))},[m.value&&he.$slots["viewer-error"]?ce(he.$slots,"viewer-error",{key:0,activeIndex:y.value,src:M.value}):(x(),O("img",{ref_key:"imgRef",ref:d,key:M.value,src:M.value,style:et(I.value),class:U(p(s).e("img")),crossorigin:e.crossorigin,onLoad:H,onError:z,onMousedown:B,onTouchstart:W},null,46,Bme))],2),ce(he.$slots,"default")]),_:3},8,["focus-trap-el"])],6)]),_:3})],8,["disabled"]))}}),zme=Vme;const fD=wt(zme),Fme=["src","loading","crossorigin"],Hme={key:0};var Wme=q({name:"ElImage",inheritAttrs:!1,__name:"image",props:Ome,emits:$me,setup(e,{expose:t,emit:n}){const a=e,r=n,{t:o}=Vt(),i=ke("image"),l=Nl(),s=D(()=>Rd(Object.entries(l).filter(([z])=>/^(data-|on[A-Z])/i.test(z)||["id","style"].includes(z)))),u=A1({excludeListeners:!0,excludeKeys:D(()=>Object.keys(s.value))}),c=F(),d=F(!1),v=F(!0),f=F(!1),h=F(),m=F(),g=qt&&"loading"in HTMLImageElement.prototype;let y;const b=D(()=>[i.e("inner"),w.value&&i.e("preview"),v.value&&i.is("loading")]),_=D(()=>{const{fit:z}=a;return qt&&z?{objectFit:z}:{}}),w=D(()=>{const{previewSrcList:z}=a;return Ae(z)&&z.length>0}),S=D(()=>{const{previewSrcList:z,initialIndex:B}=a;let W=B;return B>z.length-1&&(W=0),W}),k=D(()=>a.loading==="eager"?!1:!g&&a.loading==="lazy"||a.lazy),T=()=>{qt&&(v.value=!0,d.value=!1,c.value=a.src)};function M(z){v.value=!1,d.value=!1,r("load",z)}function E(z){v.value=!1,d.value=!0,r("error",z)}function A(z){z&&(T(),R())}const I=LA(A,200,!0);async function P(){if(!qt)return;await He();const{scrollContainer:z}=a;if(Vr(z))m.value=z;else if(Ye(z)&&z!=="")m.value=document.querySelector(z)??void 0;else if(h.value){const W=x6(h.value);m.value=qh(W)?void 0:W}const{stop:B}=PY(h,([W])=>{I(W.isIntersecting)},{root:m});y=B}function R(){!qt||!I||(y==null||y(),m.value=void 0,y=void 0)}function G(){w.value&&(f.value=!0,r("show"))}function K(){f.value=!1,r("close")}function H(z){r("switch",z)}return ye(()=>a.src,()=>{k.value?(v.value=!0,d.value=!1,R(),P()):T()}),mt(()=>{k.value?P():T()}),t({showPreview:G}),(z,B)=>(x(),O("div",Tt({ref_key:"container",ref:h},s.value,{class:[p(i).b(),z.$attrs.class]}),[d.value?ce(z.$slots,"error",{key:0},()=>[C("div",{class:U(p(i).e("error"))},Q(p(o)("el.image.error")),3)]):(x(),O(Ve,{key:1},[c.value!==void 0?(x(),O("img",Tt({key:0},p(u),{src:c.value,loading:e.loading,style:_.value,class:b.value,crossorigin:e.crossorigin,onClick:G,onLoad:M,onError:E}),null,16,Fme)):te("v-if",!0),v.value?(x(),O("div",{key:1,class:U(p(i).e("wrapper"))},[ce(z.$slots,"placeholder",{},()=>[C("div",{class:U(p(i).e("placeholder"))},null,2)])],2)):te("v-if",!0)],64)),w.value?(x(),O(Ve,{key:2},[f.value?(x(),ne(p(fD),{key:0,"z-index":e.zIndex,"initial-index":S.value,infinite:e.infinite,"zoom-rate":e.zoomRate,"min-scale":e.minScale,"max-scale":e.maxScale,"show-progress":e.showProgress,"url-list":e.previewSrcList,scale:e.scale,crossorigin:e.crossorigin,"hide-on-click-modal":e.hideOnClickModal,teleported:e.previewTeleported,"close-on-press-escape":e.closeOnPressEscape,onClose:K,onSwitch:H},xr({toolbar:$(W=>[ce(z.$slots,"toolbar",bl(wl(W)))]),default:$(()=>[z.$slots.viewer?(x(),O("div",Hme,[ce(z.$slots,"viewer")])):te("v-if",!0)]),_:2},[z.$slots.progress?{name:"progress",fn:$(W=>[ce(z.$slots,"progress",bl(wl(W)))]),key:"0"}:void 0,z.$slots["viewer-error"]?{name:"viewer-error",fn:$(W=>[ce(z.$slots,"viewer-error",bl(wl(W)))]),key:"1"}:void 0]),1032,["z-index","initial-index","infinite","zoom-rate","min-scale","max-scale","show-progress","url-list","scale","crossorigin","hide-on-click-modal","teleported","close-on-press-escape"])):te("v-if",!0)],64)):te("v-if",!0)],16))}}),Kme=Wme;const Ume=wt(Kme),Gme=$e({id:{type:String,default:void 0},step:{type:Number,default:1},stepStrictly:Boolean,max:{type:Number,default:Number.MAX_SAFE_INTEGER},min:{type:Number,default:Number.MIN_SAFE_INTEGER},modelValue:{type:[Number,null]},readonly:Boolean,disabled:{type:Boolean,default:void 0},size:pa,controls:{type:Boolean,default:!0},controlsPosition:{type:String,default:"",values:["","right"]},valueOnClear:{type:ae([String,Number,null]),validator:e=>e===null||Je(e)||["min","max"].includes(e),default:null},name:String,placeholder:String,precision:{type:Number,validator:e=>e>=0&&e===Number.parseInt(`${e}`,10)},validateEvent:{type:Boolean,default:!0},...dr(["ariaLabel"]),inputmode:{type:ae(String),default:void 0},align:{type:ae(String),default:"center"},disabledScientific:Boolean,formatter:{type:Function},parser:{type:Function},tabindex:{type:[String,Number],default:0}}),Yme={[Ot]:(e,t)=>t!==e,blur:e=>e instanceof FocusEvent,focus:e=>e instanceof FocusEvent,[ua]:e=>Je(e)||Un(e),[bt]:e=>Je(e)||Un(e)},jme=["aria-label"],qme=["aria-label"];var Xme=q({name:"ElInputNumber",__name:"input-number",props:Gme,emits:Yme,setup(e,{expose:t,emit:n}){const a=e,r=n,{t:o}=Vt(),i=ke("input-number"),l=F(),s=nn({currentValue:a.modelValue,userInput:null}),{formItem:u}=Ea(),c=D(()=>Je(a.modelValue)&&a.modelValue<=a.min),d=D(()=>Je(a.modelValue)&&a.modelValue>=a.max),v=D(()=>{const z=b(a.step);return zt(a.precision)?Math.max(b(a.modelValue),z):(z>a.precision&&un("InputNumber","precision should not be less than the decimal places of step"),a.precision)}),f=D(()=>a.controls&&a.controlsPosition==="right"),h=ca(),m=$n(),g=D(()=>{if(s.userInput!==null)return s.userInput;let z=s.currentValue;if(Un(z))return"";if(Je(z)){if(Number.isNaN(z))return"";zt(a.precision)||(z=z.toFixed(a.precision))}return z}),y=(z,B)=>{if(zt(B)&&(B=v.value),B===0)return Math.round(z);let W=String(z);const N=W.indexOf(".");if(N===-1||!W.replace(".","").split("")[N+B])return z;const V=W.length;return W.charAt(V-1)==="5"&&(W=`${W.slice(0,Math.max(0,V-1))}6`),Number.parseFloat(Number(W).toFixed(B))},b=z=>{if(Un(z))return 0;const B=z.toString(),W=B.indexOf(".");let N=0;return W!==-1&&(N=B.length-W-1),N},_=(z,B=1)=>Je(z)?z>=Number.MAX_SAFE_INTEGER&&B===1?(un("InputNumber","The value has reached the maximum safe integer limit."),z):z<=Number.MIN_SAFE_INTEGER&&B===-1?(un("InputNumber","The value has reached the minimum safe integer limit."),z):y(z+a.step*B):s.currentValue,w=z=>{const B=mn(z),W=HA(z);if(a.disabledScientific&&["e","E"].includes(W)){z.preventDefault();return}switch(B){case Pe.up:z.preventDefault(),S();break;case Pe.down:z.preventDefault(),k();break}},S=()=>{a.readonly||m.value||d.value||(M(_(Number(g.value)||0)),r(ua,s.currentValue),K())},k=()=>{a.readonly||m.value||c.value||(M(_(Number(g.value)||0,-1)),r(ua,s.currentValue),K())},T=(z,B)=>{const{max:W,min:N,step:V,precision:Y,stepStrictly:X,valueOnClear:ee}=a;WW||reW?W:N,B&&r("update:modelValue",re)),re},M=(z,B=!0)=>{var V;const W=s.currentValue,N=T(z);if(!B){r(bt,N);return}s.userInput=null,!(W===N&&z)&&(r(bt,N),W!==N&&r(Ot,N,W),a.validateEvent&&((V=u==null?void 0:u.validate)==null||V.call(u,"change").catch(kt)),s.currentValue=N)},E=z=>{s.userInput=z;let B=z===""?null:Number.parseFloat(z);Number.isNaN(B)&&(B=null),r(ua,B),M(B,!1)},A=z=>{const B=z!==""?Number.parseFloat(z):"";(Je(B)&&!Number.isNaN(B)||a.formatter&&Number.isNaN(B)||B==="")&&M(B),K(),s.userInput=null},I=()=>{var z,B;(B=(z=l.value)==null?void 0:z.focus)==null||B.call(z)},P=()=>{var z,B;(B=(z=l.value)==null?void 0:z.blur)==null||B.call(z)},R=z=>{r("focus",z)},G=z=>{var B,W,N;s.userInput=null,s.currentValue===null&&((B=l.value)!=null&&B.input)&&(l.value.input.value=((W=a.formatter)==null?void 0:W.call(a,""))??""),r("blur",z),a.validateEvent&&((N=u==null?void 0:u.validate)==null||N.call(u,"blur").catch(kt))},K=()=>{s.currentValue!==a.modelValue&&(s.currentValue=a.modelValue)},H=z=>{document.activeElement===z.target&&z.preventDefault()};return ye(()=>a.modelValue,(z,B)=>{const W=T(z,!0);s.userInput===null&&W!==B&&(s.currentValue=W)},{immediate:!0}),ye(()=>a.precision,()=>{s.currentValue=T(a.modelValue)}),mt(()=>{var V;const{min:z,max:B,modelValue:W}=a,N=(V=l.value)==null?void 0:V.input;if(N.setAttribute("role","spinbutton"),Number.isFinite(B)?N.setAttribute("aria-valuemax",String(B)):N.removeAttribute("aria-valuemax"),Number.isFinite(z)?N.setAttribute("aria-valuemin",String(z)):N.removeAttribute("aria-valuemin"),N.setAttribute("aria-valuenow",s.currentValue||s.currentValue===0?String(s.currentValue):""),N.setAttribute("aria-disabled",String(m.value)),!Je(W)&&W!=null){let Y=Number(W);Number.isNaN(Y)&&(Y=null),r(bt,Y)}N.addEventListener("wheel",H,{passive:!1})}),oi(()=>{var z,B;(B=(z=l.value)==null?void 0:z.input)==null||B.setAttribute("aria-valuenow",`${s.currentValue??""}`)}),t({focus:I,blur:P}),(z,B)=>(x(),O("div",{class:U([p(i).b(),p(i).m(p(h)),p(i).is("disabled",p(m)),p(i).is("without-controls",!e.controls),p(i).is("controls-right",f.value),p(i).is(e.align,!!e.align)]),onDragstart:B[0]||(B[0]=at(()=>{},["prevent"]))},[e.controls?st((x(),O("span",{key:0,role:"button","aria-label":p(o)("el.inputNumber.decrease"),class:U([p(i).e("decrease"),p(i).is("disabled",c.value)]),onKeydown:kn(k,["enter"])},[ce(z.$slots,"decrease-icon",{},()=>[L(p(Xe),null,{default:$(()=>[f.value?(x(),ne(p(ui),{key:0})):(x(),ne(p(OI),{key:1}))]),_:1})])],42,jme)),[[p(Zm),k]]):te("v-if",!0),e.controls?st((x(),O("span",{key:1,role:"button","aria-label":p(o)("el.inputNumber.increase"),class:U([p(i).e("increase"),p(i).is("disabled",d.value)]),onKeydown:kn(S,["enter"])},[ce(z.$slots,"increase-icon",{},()=>[L(p(Xe),null,{default:$(()=>[f.value?(x(),ne(p(ip),{key:0})):(x(),ne(p(hf),{key:1}))]),_:1})])],42,qme)),[[p(Zm),S]]):te("v-if",!0),L(p(Ba),{id:e.id,ref_key:"input",ref:l,type:e.formatter?"text":"number",step:e.step,"model-value":g.value,placeholder:e.placeholder,readonly:e.readonly,disabled:p(m),size:p(h),max:e.max,min:e.min,name:e.name,"aria-label":e.ariaLabel,"validate-event":!1,inputmode:e.inputmode,formatter:e.formatter,parser:e.parser,tabindex:e.tabindex,onKeydown:w,onBlur:G,onFocus:R,onInput:E,onChange:A},xr({_:2},[z.$slots.prefix?{name:"prefix",fn:$(()=>[ce(z.$slots,"prefix")]),key:"0"}:void 0,z.$slots.suffix?{name:"suffix",fn:$(()=>[ce(z.$slots,"suffix")]),key:"1"}:void 0]),1032,["id","type","step","model-value","placeholder","readonly","disabled","size","max","min","name","aria-label","inputmode","formatter","parser","tabindex"])],34))}}),Zme=Xme;const vD=wt(Zme),Jme=$e({modelValue:{type:ae(Array)},max:Number,tagType:{...Ll.type,default:"info"},tagEffect:Ll.effect,effect:{type:ae(String),default:"light"},trigger:{type:ae(String),default:Pe.enter},draggable:Boolean,delimiter:{type:[String,RegExp],default:""},size:pa,clearable:Boolean,clearIcon:{type:pn,default:ci},disabled:{type:Boolean,default:void 0},validateEvent:{type:Boolean,default:!0},readonly:Boolean,autofocus:Boolean,id:{type:String,default:void 0},tabindex:{type:[String,Number],default:0},maxlength:{type:[String,Number]},minlength:{type:[String,Number]},placeholder:String,autocomplete:{type:ae(String),default:"off"},saveOnBlur:{type:Boolean,default:!0},collapseTags:Boolean,collapseTagsTooltip:Boolean,maxCollapseTags:{type:Number,default:1},ariaLabel:String}),Qme={[bt]:e=>Ae(e)||zt(e),[Ot]:e=>Ae(e)||zt(e),[ua]:e=>Ye(e),"add-tag":e=>Ye(e)||Ae(e),"remove-tag":(e,t)=>Ye(e)&&Je(t),"drag-tag":(e,t,n)=>Je(e)&&Je(t)&&Ye(n),focus:e=>e instanceof FocusEvent,blur:e=>e instanceof FocusEvent,clear:()=>!0};function ege({wrapperRef:e,handleDragged:t,afterDragged:n}){const a=ke("input-tag"),r=Ht(),o=F(!1);let i,l,s,u;function c(h){return`.${a.e("inner")} .${a.namespace.value}-tag:nth-child(${h+1})`}function d(h,m){i=m,l=e.value.querySelector(c(m)),l&&(l.style.opacity="0.5"),h.dataTransfer.effectAllowed="move"}function v(h,m){if(s=m,h.preventDefault(),h.dataTransfer.dropEffect="move",zt(i)||i===m){o.value=!1;return}const g=e.value.querySelector(c(m)).getBoundingClientRect(),y=i+1!==m,b=i-1!==m,_=h.clientX-g.left,w=y?b?.5:1:-1,S=b?y?.5:0:1;_<=g.width*w?u="before":_>g.width*S?u="after":u=void 0;const k=e.value.querySelector(`.${a.e("inner")}`),T=k.getBoundingClientRect(),M=Number.parseFloat(ml(k,"gap"))/2,E=g.top-T.top;let A=-9999;if(u==="before")A=Math.max(g.left-T.left-M,Math.floor(-M/2));else if(u==="after"){const I=g.right-T.left;A=I+(T.width===I?Math.floor(M/2):M)}NA(r.value,{top:`${E}px`,left:`${A}px`}),o.value=!!u}function f(h){h.preventDefault(),l&&(l.style.opacity=""),u&&!zt(i)&&!zt(s)&&i!==s&&t(i,s,u),o.value=!1,i=void 0,l=null,s=void 0,u=void 0,n==null||n()}return{dropIndicatorRef:r,showDropIndicator:o,handleDragStart:d,handleDragOver:v,handleDragEnd:f}}function tge(){const e=F(!1);return{hovering:e,handleMouseEnter:()=>{e.value=!0},handleMouseLeave:()=>{e.value=!1}}}function nge({props:e,emit:t,formItem:n}){const a=$n(),r=ca(),o=Ht(),i=F(),l=F(),s=D(()=>["small"].includes(r.value)?"small":"default"),u=D(()=>{var H;return(H=e.modelValue)!=null&&H.length?void 0:e.placeholder}),c=D(()=>!(e.readonly||a.value)),d=D(()=>{var H;return zt(e.max)?!1:(((H=e.modelValue)==null?void 0:H.length)??0)>=e.max}),v=D(()=>{var H;return e.collapseTags?(H=e.modelValue)==null?void 0:H.slice(0,e.maxCollapseTags):e.modelValue}),f=D(()=>{var H;return e.collapseTags?(H=e.modelValue)==null?void 0:H.slice(e.maxCollapseTags):[]}),h=H=>{const z=[...e.modelValue??[],...wa(H)];t(bt,z),t(Ot,z),t("add-tag",H),i.value=void 0},m=H=>{var W;const z=H.split(e.delimiter),B=z.length>1?z.map(N=>N.trim()).filter(Boolean):[];if(e.max){const N=e.max-(((W=e.modelValue)==null?void 0:W.length)??0);B.splice(N)}return B.length===1?B[0]:B},g=H=>{var X;const z=(X=H.clipboardData)==null?void 0:X.getData("text");if(e.readonly||d.value||!e.delimiter||!z)return;const{selectionStart:B=0,selectionEnd:W=0,value:N}=H.target,V=N.slice(0,B)+z+N.slice(W),Y=m(V);Y.length&&(h(Y),t(ua,V),H.preventDefault())},y=H=>{if(d.value){i.value=void 0;return}if(!P.value){if(e.delimiter&&i.value){const z=m(i.value);z.length&&h(z)}t(ua,H.target.value)}},b=H=>{var z;if(!P.value)switch(mn(H)){case e.trigger:H.preventDefault(),H.stopPropagation(),w();break;case Pe.numpadEnter:e.trigger===Pe.enter&&(H.preventDefault(),H.stopPropagation(),w());break;case Pe.backspace:!i.value&&((z=e.modelValue)!=null&&z.length)&&(H.preventDefault(),H.stopPropagation(),S(e.modelValue.length-1));break}},_=H=>{if(!(P.value||!$A()))switch(mn(H)){case Pe.space:e.trigger===Pe.space&&(H.preventDefault(),H.stopPropagation(),w());break}},w=()=>{var z;const H=(z=i.value)==null?void 0:z.trim();!H||d.value||h(H)},S=H=>{const z=(e.modelValue??[]).slice(),[B]=z.splice(H,1);t(bt,z),t(Ot,z),t("remove-tag",B,H)},k=()=>{i.value=void 0,t(bt,void 0),t(Ot,void 0),t("clear")},T=(H,z,B)=>{const W=(e.modelValue??[]).slice(),[N]=W.splice(H,1),V=z>H&&B==="before"?-1:z{var H;(H=o.value)==null||H.focus()},E=()=>{var H;(H=o.value)==null||H.blur()},{wrapperRef:A,isFocused:I}=Fl(o,{disabled:a,beforeBlur(H){var z;return(z=l.value)==null?void 0:z.isFocusInsideContent(H)},afterBlur(){var H;e.saveOnBlur?w():i.value=void 0,e.validateEvent&&((H=n==null?void 0:n.validate)==null||H.call(n,"blur").catch(kt))}}),{isComposing:P,handleCompositionStart:R,handleCompositionUpdate:G,handleCompositionEnd:K}=rp({afterComposition:y});return ye(()=>e.modelValue,()=>{var H;e.validateEvent&&((H=n==null?void 0:n.validate)==null||H.call(n,Ot).catch(kt))}),{inputRef:o,wrapperRef:A,tagTooltipRef:l,isFocused:I,isComposing:P,inputValue:i,size:r,tagSize:s,placeholder:u,closable:c,disabled:a,inputLimit:d,showTagList:v,collapseTagList:f,handleDragged:T,handlePaste:g,handleInput:y,handleKeydown:b,handleKeyup:_,handleAddTag:w,handleRemoveTag:S,handleClear:k,handleCompositionStart:R,handleCompositionUpdate:G,handleCompositionEnd:K,focus:M,blur:E}}function age({props:e,isFocused:t,hovering:n,disabled:a,inputValue:r,size:o,validateState:i,validateIcon:l,needStatusIcon:s}){const u=Nl(),c=Xn(),d=ke("input-tag"),v=ke("input"),f=F(),h=F(),m=D(()=>[d.b(),d.is("focused",t.value),d.is("hovering",n.value),d.is("disabled",a.value),d.m(o.value),d.e("wrapper"),u.class]),g=D(()=>[u.style]),y=D(()=>{var E,A;return[d.e("inner"),d.is("draggable",e.draggable),d.is("left-space",!((E=e.modelValue)!=null&&E.length)&&!c.prefix),d.is("right-space",!((A=e.modelValue)!=null&&A.length)&&!_.value)]}),b=D(()=>{var E;return e.clearable&&!a.value&&!e.readonly&&(((E=e.modelValue)==null?void 0:E.length)||r.value)&&(t.value||n.value)}),_=D(()=>c.suffix||b.value||i.value&&l.value&&s.value),w=nn({innerWidth:0,collapseItemWidth:0}),S=()=>{if(!h.value)return 0;const E=window.getComputedStyle(h.value);return Number.parseFloat(E.gap||"6px")},k=()=>{w.innerWidth=Number.parseFloat(window.getComputedStyle(h.value).width)},T=()=>{w.collapseItemWidth=f.value.getBoundingClientRect().width},M=D(()=>{if(!e.collapseTags)return{};const E=S(),A=E+11,I=f.value&&e.maxCollapseTags===1?w.innerWidth-w.collapseItemWidth-E-A:w.innerWidth-A;return{maxWidth:`${Math.max(I,0)}px`}});return Mn(h,k),Mn(f,T),{ns:d,nsInput:v,containerKls:m,containerStyle:g,innerKls:y,showClear:b,showSuffix:_,tagStyle:M,collapseItemRef:f,innerRef:h}}const rge=["id","minlength","maxlength","disabled","readonly","autocomplete","tabindex","placeholder","autofocus","ariaLabel"],oge=["textContent"];var ige=q({name:"ElInputTag",inheritAttrs:!1,__name:"input-tag",props:Jme,emits:Qme,setup(e,{expose:t,emit:n}){const a=e,r=n,o=A1(),i=Xn(),{form:l,formItem:s}=Ea(),{inputId:u}=Yr(a,{formItemContext:s}),c=D(()=>(l==null?void 0:l.statusIcon)??!1),d=D(()=>(s==null?void 0:s.validateState)||""),v=D(()=>d.value&&F1[d.value]),{inputRef:f,wrapperRef:h,tagTooltipRef:m,isFocused:g,inputValue:y,size:b,tagSize:_,placeholder:w,closable:S,disabled:k,showTagList:T,collapseTagList:M,handleDragged:E,handlePaste:A,handleInput:I,handleKeydown:P,handleKeyup:R,handleRemoveTag:G,handleClear:K,handleCompositionStart:H,handleCompositionUpdate:z,handleCompositionEnd:B,focus:W,blur:N}=nge({props:a,emit:r,formItem:s}),{hovering:V,handleMouseEnter:Y,handleMouseLeave:X}=tge(),{calculatorRef:ee,inputStyle:re}=w6(),{dropIndicatorRef:ie,showDropIndicator:le,handleDragStart:ve,handleDragOver:he,handleDragEnd:J}=ege({wrapperRef:h,handleDragged:E,afterDragged:W}),{ns:Z,nsInput:j,containerKls:oe,containerStyle:se,innerKls:fe,showClear:ge,showSuffix:Se,tagStyle:De,collapseItemRef:Te,innerRef:Re}=age({props:a,hovering:V,isFocused:g,inputValue:y,disabled:k,size:b,validateState:d,validateIcon:v,needStatusIcon:c});return t({focus:W,blur:N}),(Ce,Le)=>(x(),O("div",{ref_key:"wrapperRef",ref:h,class:U(p(oe)),style:et(p(se)),onMouseenter:Le[9]||(Le[9]=(...me)=>p(Y)&&p(Y)(...me)),onMouseleave:Le[10]||(Le[10]=(...me)=>p(X)&&p(X)(...me))},[p(i).prefix?(x(),O("div",{key:0,class:U(p(Z).e("prefix"))},[ce(Ce.$slots,"prefix")],2)):te("v-if",!0),C("div",{ref_key:"innerRef",ref:Re,class:U(p(fe))},[(x(!0),O(Ve,null,dt(p(T),(me,Be)=>(x(),ne(p(Cl),{key:Be,size:p(_),closable:p(S),type:e.tagType,effect:e.tagEffect,draggable:p(S)&&e.draggable,style:et(p(De)),"disable-transitions":"",onClose:ze=>p(G)(Be),onDragstart:ze=>p(ve)(ze,Be),onDragover:ze=>p(he)(ze,Be),onDragend:p(J),onDrop:Le[0]||(Le[0]=at(()=>{},["stop"]))},{default:$(()=>[ce(Ce.$slots,"tag",{value:me,index:Be},()=>[ue(Q(me),1)])]),_:2},1032,["size","closable","type","effect","draggable","style","onClose","onDragstart","onDragover","onDragend"]))),128)),e.collapseTags&&e.modelValue&&e.modelValue.length>e.maxCollapseTags?(x(),ne(p(Ma),{key:0,ref_key:"tagTooltipRef",ref:m,disabled:!e.collapseTagsTooltip,"fallback-placements":["bottom","top","right","left"],effect:e.effect,placement:"bottom"},{default:$(()=>[C("div",{ref_key:"collapseItemRef",ref:Te,class:U(p(Z).e("collapse-tag"))},[L(p(Cl),{closable:!1,size:p(_),type:e.tagType,effect:e.tagEffect,"disable-transitions":""},{default:$(()=>[ue(" + "+Q(e.modelValue.length-e.maxCollapseTags),1)]),_:1},8,["size","type","effect"])],2)]),content:$(()=>[C("div",{class:U(p(Z).e("input-tag-list"))},[(x(!0),O(Ve,null,dt(p(M),(me,Be)=>(x(),ne(p(Cl),{key:Be,size:p(_),closable:p(S),type:e.tagType,effect:e.tagEffect,"disable-transitions":"",onClose:ze=>p(G)(Be+e.maxCollapseTags)},{default:$(()=>[ce(Ce.$slots,"tag",{value:me,index:Be+e.maxCollapseTags},()=>[ue(Q(me),1)])]),_:2},1032,["size","closable","type","effect","onClose"]))),128))],2)]),_:3},8,["disabled","effect"])):te("v-if",!0),C("div",{class:U(p(Z).e("input-wrapper"))},[st(C("input",Tt({id:p(u),ref_key:"inputRef",ref:f,"onUpdate:modelValue":Le[1]||(Le[1]=me=>xn(y)?y.value=me:null)},p(o),{type:"text",minlength:e.minlength,maxlength:e.maxlength,disabled:p(k),readonly:e.readonly,autocomplete:e.autocomplete,tabindex:e.tabindex,placeholder:p(w),autofocus:e.autofocus,ariaLabel:e.ariaLabel,class:p(Z).e("input"),style:p(re),onCompositionstart:Le[2]||(Le[2]=(...me)=>p(H)&&p(H)(...me)),onCompositionupdate:Le[3]||(Le[3]=(...me)=>p(z)&&p(z)(...me)),onCompositionend:Le[4]||(Le[4]=(...me)=>p(B)&&p(B)(...me)),onPaste:Le[5]||(Le[5]=(...me)=>p(A)&&p(A)(...me)),onInput:Le[6]||(Le[6]=(...me)=>p(I)&&p(I)(...me)),onKeydown:Le[7]||(Le[7]=(...me)=>p(P)&&p(P)(...me)),onKeyup:Le[8]||(Le[8]=(...me)=>p(R)&&p(R)(...me))}),null,16,rge),[[D7,p(y)]]),C("span",{ref_key:"calculatorRef",ref:ee,"aria-hidden":"true",class:U(p(Z).e("input-calculator")),textContent:Q(p(y))},null,10,oge)],2),st(C("div",{ref_key:"dropIndicatorRef",ref:ie,class:U(p(Z).e("drop-indicator"))},null,2),[[Zt,p(le)]])],2),p(Se)?(x(),O("div",{key:1,class:U(p(Z).e("suffix"))},[ce(Ce.$slots,"suffix"),p(ge)?(x(),ne(p(Xe),{key:0,class:U([p(Z).e("icon"),p(Z).e("clear")]),onMousedown:at(p(kt),["prevent"]),onClick:p(K)},{default:$(()=>[(x(),ne(St(e.clearIcon)))]),_:1},8,["class","onMousedown","onClick"])):te("v-if",!0),d.value&&v.value&&c.value?(x(),ne(p(Xe),{key:1,class:U([p(j).e("icon"),p(j).e("validateIcon"),p(j).is("loading",d.value==="validating")])},{default:$(()=>[(x(),ne(St(v.value)))]),_:1},8,["class"])):te("v-if",!0)],2)):te("v-if",!0)],38))}}),lge=ige;const sge=wt(lge),uge={[bt]:e=>Ye(e),[Ot]:e=>Ye(e),finish:e=>Ye(e),focus:e=>e instanceof FocusEvent,blur:e=>e instanceof FocusEvent},cge=["id","aria-label","aria-labelledby"],dge=["value","type","disabled","readonly","inputmode","aria-label","onClick","onKeydown","onInput"];var fge=q({name:"ElInputOtp",__name:"input-otp",props:{modelValue:{type:[String,Number],required:!1},length:{type:Number,required:!1,default:6},validator:{type:Function,required:!1,default:()=>!0},inputmode:{type:null,required:!1},type:{type:String,required:!1,default:"outlined"},size:{type:null,required:!1},mask:{type:Boolean,required:!1},disabled:{type:Boolean,required:!1,default:void 0},readonly:{type:Boolean,required:!1},id:{type:String,required:!1},validateEvent:{type:Boolean,required:!1,default:!0},separator:{type:[String,Object,Function],required:!1},ariaLabel:{type:String,required:!1}},emits:uge,setup(e,{expose:t,emit:n}){const a=e,r=n,o=D(()=>{const I=String(a.modelValue??"");return Array.from({length:a.length},(P,R)=>I.charAt(R))}),i=D(()=>{const{separator:I}=a,P=Qe(I)?I:()=>I;return Array.from({length:a.length-1},(R,G)=>P(G))}),l=F(o.value),s=F(!1),u=F([]),c=ke("input-otp"),{t:d}=Vt(),{formItem:v}=Ea(),{inputId:f,isLabeledByFormItem:h}=Yr(a,{formItemContext:v}),m=ca(),g=$n();let y=a.modelValue;const b=I=>{const P=l.value.findIndex((R,G)=>!R&&G<=I);return P===-1?I:P},_=I=>{var P;(P=u.value)!=null&&P.includes(I.relatedTarget)||(s.value=!0,r("focus",I))},w=I=>{var P,R;(P=u.value)!=null&&P.includes(I.relatedTarget)||(s.value=!1,r("blur",I),a.validateEvent&&((R=v==null?void 0:v.validate)==null||R.call(v,"blur").catch(kt)))},S=(I=!0)=>{const P=l.value.join("").slice(0,a.length);P!==a.modelValue&&(r(bt,P),I&&P.length===a.length&&r("finish",P))},k=(I,P)=>{const R=mn(I);let G=!0;switch(R){case Pe.backspace:if(a.readonly)break;l.value[P]="",E(P-1),S();break;case Pe.delete:if(a.readonly)break;l.value[P]="",E(P),S();break;case Pe.up:case Pe.left:E(P-1);break;case Pe.down:case Pe.right:E(P+1);break;default:G=!1}G&&I.preventDefault()},T=(I,P)=>{const R=I.target,G=b(P);let K=G+1,H=R.value;if(H.length>1){const z=M(H,G);R.value=l.value[P]??"",z.forEach((B,W)=>l.value[G+W]=B),E(G+z.length),S();return}a.validator(H,G)||(R.value=l.value[P]??"",H=R.value,K=G),l.value[G]=H,G!==P&&(R.value=l.value[P]??""),E(K),S()},M=(I,P=0)=>{const R=`${I??""}`.split(""),G=[];for(const K of R){if(G.length+P>=a.length)break;a.validator(K,G.length+P)&&G.push(K)}return G},E=(I=0)=>{var G;const P=I1(I,0,a.length-1),R=(G=u.value)==null?void 0:G[P];document.activeElement!==R&&(R==null||R.focus()),Wr(()=>{!a.readonly&&document.activeElement===R&&(R==null||R.select())})},A=()=>{var I,P;(P=(I=u.value)==null?void 0:I.find(R=>document.activeElement===R))==null||P.blur()};return ye(()=>a.modelValue,()=>{var I;l.value=o.value,a.validateEvent&&((I=v==null?void 0:v.validate)==null||I.call(v,"change").catch(kt))}),ye(()=>a.length,()=>{l.value=o.value,S(!1)}),ye(s,I=>{if(I){y=a.modelValue;return}y!==a.modelValue&&r(Ot,a.modelValue)}),t({inputRefs:u,focus:E,blur:A}),(I,P)=>{var R;return x(),O("div",{id:p(f),class:U([p(c).b(),p(c).m(p(m)),p(c).m(e.type),p(c).is("disabled",p(g))]),role:"group","aria-label":p(h)?void 0:e.ariaLabel||p(d)("el.inputOTP.groupLabel"),"aria-labelledby":p(h)?(R=p(v))==null?void 0:R.labelId:void 0},[(x(!0),O(Ve,null,dt(e.length,(G,K)=>(x(),O(Ve,{key:K},[C("label",{class:U(p(c).e("input-field"))},[C("input",{ref_for:!0,ref_key:"inputRefs",ref:u,value:l.value[K],class:U(p(c).e("input")),type:e.mask?"password":"text",disabled:p(g),readonly:e.readonly,inputmode:e.inputmode,autocomplete:"one-time-code","aria-label":p(d)("el.inputOTP.defaultLabel",{index:K+1}),onFocus:_,onBlur:w,onClick:H=>E(K),onKeydown:H=>k(H,K),onInput:H=>T(H,K)},null,42,dge)],2),(I.$slots.separator||i.value[K])&&K[(x(),ne(St(()=>i.value[K])))]):te("v-if",!0)],64))),128))],10,cge)}}}),vge=fge;const hge=wt(vge),pge=$e({type:{type:String,values:["primary","success","warning","info","danger","default"],default:void 0},underline:{type:[Boolean,String],values:[!0,!1,"always","never","hover"],default:void 0},disabled:Boolean,href:{type:String,default:""},target:{type:String,default:"_self"},icon:{type:pn}}),mge={click:e=>e instanceof MouseEvent},gge=["href","target"];var yge=q({name:"ElLink",__name:"link",props:pge,emits:mge,setup(e,{emit:t}){const n=e,a=t,r=Hl("link");Ri({scope:"el-link",from:"The underline option (boolean)",replacement:"'always' | 'hover' | 'never'",version:"3.0.0",ref:"https://element-plus.org/en-US/component/link.html#underline"},D(()=>vn(n.underline)));const o=ke("link"),i=D(()=>{var u;return[o.b(),o.m(n.type??((u=r.value)==null?void 0:u.type)??"default"),o.is("disabled",n.disabled),o.is("underline",l.value==="always"),o.is("hover-underline",l.value==="hover"&&!n.disabled)]}),l=D(()=>{var u;return vn(n.underline)?n.underline?"hover":"never":n.underline??((u=r.value)==null?void 0:u.underline)??"hover"});function s(u){n.disabled||a("click",u)}return(u,c)=>(x(),O("a",{class:U(i.value),href:e.disabled||!e.href?void 0:e.href,target:e.disabled||!e.href?void 0:e.target,onClick:s},[e.icon?(x(),ne(p(Xe),{key:0},{default:$(()=>[(x(),ne(St(e.icon)))]),_:1})):te("v-if",!0),u.$slots.default?(x(),O("span",{key:1,class:U(p(o).e("inner"))},[ce(u.$slots,"default")],2)):te("v-if",!0),u.$slots.icon?ce(u.$slots,"icon",{key:2}):te("v-if",!0)],10,gge))}}),bge=yge;const _ge=wt(bge),Sw="rootMenu",vg="subMenu:";function hD(e,t){const n=D(()=>{let a=e.parent;const r=[t.value];for(;a.type.name!=="ElMenu";)a.props.index&&r.unshift(a.props.index),a=a.parent;return r});return{parentMenu:D(()=>{let a=e.parent;for(;a&&!["ElMenu","ElSubMenu"].includes(a.type.name);)a=a.parent;return a}),indexPath:n}}function wge(e){return D(()=>{const t=e.backgroundColor;return t?new ea(t).shade(20).toString():""})}const pD=(e,t)=>{const n=ke("menu");return D(()=>n.cssVarBlock({"text-color":e.textColor||"","hover-text-color":e.textColor||"","bg-color":e.backgroundColor||"","hover-bg-color":wge(e).value||"","active-color":e.activeTextColor||"",level:`${t}`}))},Sge=$e({index:{type:String,required:!0},showTimeout:Number,hideTimeout:Number,popperClass:{type:ae([String,Array,Object,Boolean]),default:void 0},popperStyle:{type:ae([String,Array,Object,Boolean]),default:void 0},disabled:Boolean,teleported:{type:Boolean,default:void 0},popperOffset:Number,expandCloseIcon:{type:pn},expandOpenIcon:{type:pn},collapseCloseIcon:{type:pn},collapseOpenIcon:{type:pn}}),ly="ElSubMenu";var Cw=q({name:ly,props:Sge,setup(e,{slots:t,expose:n}){const a=Et(),{indexPath:r,parentMenu:o}=hD(a,D(()=>e.index)),i=ke("menu"),l=ke("sub-menu"),s=Ze(Sw);s||Dn(ly,"can not inject root menu");const u=Ze(`${vg}${o.value.uid}`);u||Dn(ly,"can not inject sub menu");const c=F({}),d=F({});let v;const f=F(!1),h=F(),m=F(),g=D(()=>u.level===0),y=D(()=>M.value==="horizontal"&&g.value?"bottom-start":"right-start"),b=D(()=>M.value==="horizontal"&&g.value||M.value==="vertical"&&!s.props.collapse?e.expandCloseIcon&&e.expandOpenIcon?k.value?e.expandOpenIcon:e.expandCloseIcon:ui:e.collapseCloseIcon&&e.collapseOpenIcon?k.value?e.collapseOpenIcon:e.collapseCloseIcon:za),_=D(()=>{const Y=e.teleported;return zt(Y)?g.value:Y}),w=D(()=>s.props.collapse?`${i.namespace.value}-zoom-in-left`:`${i.namespace.value}-zoom-in-top`),S=D(()=>M.value==="horizontal"&&g.value?["bottom-start","bottom-end","top-start","top-end","right-start","left-start"]:["right-start","right","right-end","left-start","bottom-start","bottom-end","top-start","top-end"]),k=D(()=>s.openedMenus.includes(e.index)),T=D(()=>[...Object.values(c.value),...Object.values(d.value)].some(({active:Y})=>Y)),M=D(()=>s.props.mode),E=D(()=>s.props.persistent),A=nn({index:e.index,indexPath:r,active:T}),I=pD(s.props,u.level+1),P=D(()=>e.popperOffset??s.props.popperOffset),R=D(()=>e.popperClass??s.props.popperClass),G=D(()=>e.popperStyle??s.props.popperStyle),K=D(()=>e.showTimeout??s.props.showTimeout),H=D(()=>e.hideTimeout??s.props.hideTimeout),z=()=>{var Y,X,ee;return(ee=(X=(Y=m.value)==null?void 0:Y.popperRef)==null?void 0:X.popperInstanceRef)==null?void 0:ee.destroy()},B=Y=>{Y||z()},W=()=>{s.props.menuTrigger==="hover"&&s.props.mode==="horizontal"||s.props.collapse&&s.props.mode==="vertical"||e.disabled||s.handleSubMenuClick({index:e.index,indexPath:r.value,active:T.value})},N=(Y,X=K.value)=>{var ee;if(Y.type!=="focus"){if(s.props.menuTrigger==="click"&&s.props.mode==="horizontal"||!s.props.collapse&&s.props.mode==="vertical"||e.disabled){u.mouseInChild.value=!0;return}u.mouseInChild.value=!0,v==null||v(),{stop:v}=Bd(()=>{s.openMenu(e.index,r.value)},X),_.value&&((ee=o.value.vnode.el)==null||ee.dispatchEvent(new MouseEvent("mouseenter"))),Y.type==="mouseenter"&&Y.target&&He(()=>{Jh(Y.target,{preventScroll:!0})})}},V=(Y=!1)=>{var X;if(s.props.menuTrigger==="click"&&s.props.mode==="horizontal"||!s.props.collapse&&s.props.mode==="vertical"){u.mouseInChild.value=!1;return}v==null||v(),u.mouseInChild.value=!1,{stop:v}=Bd(()=>!f.value&&s.closeMenu(e.index,r.value),H.value),_.value&&Y&&((X=u.handleMouseleave)==null||X.call(u,!0))};ye(()=>s.props.collapse,Y=>B(!!Y));{const Y=ee=>{d.value[ee.index]=ee},X=ee=>{delete d.value[ee.index]};Ft(`${vg}${a.uid}`,{addSubMenu:Y,removeSubMenu:X,handleMouseleave:V,mouseInChild:f,level:u.level+1})}return n({opened:k}),mt(()=>{s.addSubMenu(A),u.addSubMenu(A)}),rn(()=>{u.removeSubMenu(A),s.removeSubMenu(A)}),()=>{var ee;const Y=[(ee=t.title)==null?void 0:ee.call(t),ot(Xe,{class:l.e("icon-arrow"),style:{transform:k.value?e.expandCloseIcon&&e.expandOpenIcon||e.collapseCloseIcon&&e.collapseOpenIcon&&s.props.collapse?"none":"rotateZ(180deg)":"none"}},{default:()=>Ye(b.value)?ot(a.appContext.components[b.value]):ot(b.value)})],X=s.isMenuPopup?ot(Ma,{ref:m,visible:k.value,effect:"light",pure:!0,offset:P.value,showArrow:!1,persistent:E.value,popperClass:R.value,popperStyle:G.value,placement:y.value,teleported:_.value,fallbackPlacements:S.value,transition:w.value,gpuAcceleration:!1},{content:()=>{var re;return ot("div",{class:[i.m(M.value),i.m("popup-container"),R.value],onMouseenter:ie=>N(ie,100),onMouseleave:()=>V(!0),onFocus:ie=>N(ie,100)},[ot("ul",{class:[i.b(),i.m("popup"),i.m(`popup-${y.value}`)],style:I.value},[(re=t.default)==null?void 0:re.call(t)])])},default:()=>ot("div",{class:l.e("title"),onClick:W},Y)}):ot(Ve,{},[ot("div",{class:l.e("title"),ref:h,onClick:W},Y),ot(U1,{},{default:()=>{var re;return st(ot("ul",{role:"menu",class:[i.b(),i.m("inline")],style:I.value},[(re=t.default)==null?void 0:re.call(t)]),[[Zt,k.value]])}})]);return ot("li",{class:[l.b(),l.is("active",T.value),l.is("opened",k.value),l.is("disabled",e.disabled)],role:"menuitem",ariaHaspopup:!0,ariaExpanded:k.value,onMouseenter:N,onMouseleave:()=>V(),onFocus:N},[X])}}}),Cge=class{constructor(e,t){this.parent=e,this.domNode=t,this.subIndex=0,this.subMenuItems=this.domNode.querySelectorAll("li"),this.addListeners()}gotoSubIndex(e){e===this.subMenuItems.length?e=0:e<0&&(e=this.subMenuItems.length-1),this.subMenuItems[e].focus(),this.subIndex=e}addListeners(){const e=this.parent.domNode;this.subMenuItems.forEach(t=>{t.addEventListener("keydown",n=>{const a=mn(n);let r=!1;switch(a){case Pe.down:this.gotoSubIndex(this.subIndex+1),r=!0;break;case Pe.up:this.gotoSubIndex(this.subIndex-1),r=!0;break;case Pe.tab:X0(e,"mouseleave");break;case Pe.enter:case Pe.numpadEnter:case Pe.space:r=!0,n.currentTarget.click();break}return r&&(n.preventDefault(),n.stopPropagation()),!1})})}},xge=class{constructor(e,t){this.domNode=e,this.submenu=null,this.init(t)}init(e){this.domNode.setAttribute("tabindex","0");const t=this.domNode.querySelector(`.${e}-menu`);t&&(this.submenu=new Cge(this,t)),this.addListeners()}addListeners(){this.domNode.addEventListener("keydown",e=>{var a,r;const t=mn(e);let n=!1;switch(t){case Pe.down:X0(e.currentTarget,"mouseenter"),(a=this.submenu)==null||a.gotoSubIndex(0),n=!0;break;case Pe.up:X0(e.currentTarget,"mouseenter"),(r=this.submenu)==null||r.gotoSubIndex(this.submenu.subMenuItems.length-1),n=!0;break;case Pe.tab:X0(e.currentTarget,"mouseleave");break;case Pe.enter:case Pe.numpadEnter:case Pe.space:n=!0,e.currentTarget.click();break}n&&e.preventDefault()})}},kge=class{constructor(e,t){this.domNode=e,this.init(t)}init(e){const t=this.domNode.childNodes;Array.from(t).forEach(n=>{n.nodeType===1&&new xge(n,e)})}},Tge=q({name:"ElMenuCollapseTransition",__name:"menu-collapse-transition",setup(e){const t=ke("menu"),n={onBeforeEnter:a=>a.style.opacity="0.2",onEnter(a,r){Eo(a,`${t.namespace.value}-opacity-transition`),a.style.opacity="1",r()},onAfterEnter(a){ir(a,`${t.namespace.value}-opacity-transition`),a.style.opacity=""},onBeforeLeave(a){a.dataset||(a.dataset={}),Ni(a,t.m("collapse"))?(ir(a,t.m("collapse")),a.dataset.oldOverflow=a.style.overflow,a.dataset.scrollWidth=a.clientWidth.toString(),Eo(a,t.m("collapse"))):(Eo(a,t.m("collapse")),a.dataset.oldOverflow=a.style.overflow,a.dataset.scrollWidth=a.clientWidth.toString(),ir(a,t.m("collapse"))),a.style.width=`${a.scrollWidth}px`,a.style.overflow="hidden"},onLeave(a){Eo(a,"horizontal-collapse-transition"),a.style.width=`${a.dataset.scrollWidth}px`}};return(a,r)=>(x(),ne(Pa,Tt({mode:"out-in"},n),{default:$(()=>[ce(a.$slots,"default")]),_:3},16))}}),Mge=Tge;const Ege=$e({mode:{type:String,values:["horizontal","vertical"],default:"vertical"},defaultActive:{type:String,default:""},defaultOpeneds:{type:ae(Array),default:()=>Nn([])},uniqueOpened:Boolean,router:Boolean,menuTrigger:{type:String,values:["hover","click"],default:"hover"},collapse:Boolean,backgroundColor:String,textColor:String,activeTextColor:String,closeOnClickOutside:Boolean,collapseTransition:{type:Boolean,default:!0},ellipsis:{type:Boolean,default:!0},popperOffset:{type:Number,default:6},ellipsisIcon:{type:pn,default:()=>$I},popperEffect:{type:ae(String),default:"dark"},popperClass:{type:ae([String,Array,Object,Boolean]),default:void 0},popperStyle:{type:ae([String,Array,Object,Boolean]),default:void 0},showTimeout:{type:Number,default:300},hideTimeout:{type:Number,default:300},persistent:{type:Boolean,default:!0}}),sy=e=>Ae(e)&&e.every(t=>Ye(t)),Age={close:(e,t)=>Ye(e)&&sy(t),open:(e,t)=>Ye(e)&&sy(t),select:(e,t,n,a)=>Ye(e)&&sy(t)&&_t(n)&&(zt(a)||a instanceof Promise)},Ex=64;var Ige=q({name:"ElMenu",props:Ege,emits:Age,setup(e,{emit:t,slots:n,expose:a}){const r=Et(),o=r.appContext.config.globalProperties.$router,i=F(),l=F(),s=ke("menu"),u=ke("sub-menu");let c=Ex;const d=F(-1),v=F(e.defaultOpeneds&&!e.collapse?e.defaultOpeneds.slice(0):[]),f=F(e.defaultActive),h=F({}),m=F({}),g=D(()=>e.mode==="horizontal"||e.mode==="vertical"&&e.collapse),y=()=>{const z=f.value&&h.value[f.value];!z||e.mode==="horizontal"||e.collapse||z.indexPath.forEach(B=>{const W=m.value[B];W&&b(B,W.indexPath)})},b=(z,B)=>{v.value.includes(z)||(e.uniqueOpened&&(v.value=v.value.filter(W=>B.includes(W))),v.value.push(z),t("open",z,B))},_=z=>{const B=v.value.indexOf(z);B!==-1&&v.value.splice(B,1)},w=(z,B)=>{_(z),t("close",z,B)},S=({index:z,indexPath:B})=>{v.value.includes(z)?w(z,B):b(z,B)},k=z=>{(e.mode==="horizontal"||e.collapse)&&(v.value=[]);const{index:B,indexPath:W}=z;if(!(Un(B)||Un(W)))if(e.router&&o){const N=z.route||B,V=o.push(N).then(Y=>(Y||(f.value=B),Y));t("select",B,W,{index:B,indexPath:W,route:N},V)}else f.value=B,t("select",B,W,{index:B,indexPath:W})},T=z=>{var W;const B=h.value;f.value=((W=B[z]||f.value&&B[f.value]||B[e.defaultActive])==null?void 0:W.index)??z},M=z=>{const B=getComputedStyle(z),W=Number.parseInt(B.marginLeft,10),N=Number.parseInt(B.marginRight,10);return z.offsetWidth+W+N||0},E=()=>{if(!i.value)return-1;const z=Array.from(i.value.childNodes).filter(ee=>ee.nodeName!=="#comment"&&(ee.nodeName!=="#text"||ee.nodeValue)),B=getComputedStyle(i.value),W=Number.parseInt(B.paddingLeft,10),N=Number.parseInt(B.paddingRight,10),V=i.value.clientWidth-W-N;let Y=0,X=0;return z.forEach((ee,re)=>{Y+=M(ee),Y<=V-c&&(X=re+1)}),X===z.length?-1:X},A=z=>m.value[z].indexPath,I=(z,B=33.34)=>{let W;return()=>{W&&clearTimeout(W),W=setTimeout(()=>{z()},B)}};let P=!0;const R=()=>{const z=da(l);if(z&&(c=M(z)||Ex),d.value===E())return;const B=()=>{d.value=-1,He(()=>{d.value=E()})};P?B():I(B)(),P=!1};ye(()=>e.defaultActive,z=>{h.value[z]||(f.value=""),T(z)}),ye(()=>e.collapse,z=>{z&&(v.value=[])}),ye(h.value,y);let G;ur(()=>{e.mode==="horizontal"&&e.ellipsis?G=Mn(i,R).stop:G==null||G()});const K=F(!1);{const z=V=>{m.value[V.index]=V},B=V=>{delete m.value[V.index]};Ft(Sw,nn({props:e,openedMenus:v,items:h,subMenus:m,activeIndex:f,isMenuPopup:g,addMenuItem:V=>{h.value[V.index]=V},removeMenuItem:V=>{delete h.value[V.index]},addSubMenu:z,removeSubMenu:B,openMenu:b,closeMenu:w,handleMenuItemClick:k,handleSubMenuClick:S})),Ft(`${vg}${r.uid}`,{addSubMenu:z,removeSubMenu:B,mouseInChild:K,level:0})}mt(()=>{e.mode==="horizontal"&&new kge(r.vnode.el,s.namespace.value)}),a({open:B=>{const{indexPath:W}=m.value[B];W.forEach(N=>b(N,W))},close:_,updateActiveIndex:T,handleResize:R});const H=pD(e,0);return()=>{var V;let z=((V=n.default)==null?void 0:V.call(n))??[];const B=[];if(e.mode==="horizontal"&&i.value){const Y=eo(z).filter(re=>(re==null?void 0:re.shapeFlag)!==8),X=d.value===-1?Y:Y.slice(0,d.value),ee=d.value===-1?[]:Y.slice(d.value);ee!=null&&ee.length&&e.ellipsis&&(z=X,B.push(ot(Cw,{ref:l,index:"sub-menu-more",class:u.e("hide-arrow"),popperOffset:e.popperOffset},{title:()=>ot(Xe,{class:u.e("icon-more")},{default:()=>ot(e.ellipsisIcon)}),default:()=>ee})))}const W=e.closeOnClickOutside?[[Ns,()=>{v.value.length&&(K.value||(v.value.forEach(Y=>t("close",Y,A(Y))),v.value=[]))}]]:[],N=st(ot("ul",{key:String(e.collapse),role:"menubar",ref:i,style:H.value,class:{[s.b()]:!0,[s.m(e.mode)]:!0,[s.m("collapse")]:e.collapse}},[...z,...B]),W);return e.collapseTransition&&e.mode==="vertical"?ot(Mge,()=>N):N}}});const Lge=$e({index:{type:String,required:!0},route:{type:ae([String,Object])},disabled:Boolean}),Pge={click:e=>Ye(e.index)&&Ae(e.indexPath)},Dge={title:String},uy="ElMenuItem";var Oge=q({name:uy,__name:"menu-item",props:Lge,emits:Pge,setup(e,{expose:t,emit:n}){const a=e,r=n,o=Et(),i=Ze(Sw),l=ke("menu"),s=ke("menu-item");i||Dn(uy,"can not inject root menu");const{parentMenu:u,indexPath:c}=hD(o,cn(a,"index")),d=Ze(`${vg}${u.value.uid}`);d||Dn(uy,"can not inject sub menu");const v=D(()=>a.index===i.activeIndex),f=nn({index:a.index,indexPath:c,active:v}),h=()=>{a.disabled||(i.handleMenuItemClick({index:a.index,indexPath:c.value,route:a.route}),r("click",f))};return mt(()=>{d.addSubMenu(f),i.addMenuItem(f)}),rn(()=>{d.removeSubMenu(f),i.removeMenuItem(f)}),t({parentMenu:u,rootMenu:i,active:v,nsMenu:l,nsMenuItem:s,handleClick:h}),(m,g)=>(x(),O("li",{class:U([p(s).b(),p(s).is("active",v.value),p(s).is("disabled",e.disabled)]),role:"menuitem",tabindex:"-1",onClick:h},[p(u).type.name==="ElMenu"&&p(i).props.collapse&&m.$slots.title?(x(),ne(p(Ma),{key:0,effect:p(i).props.popperEffect,placement:"right","fallback-placements":["left"],"popper-class":p(i).props.popperClass,"popper-style":p(i).props.popperStyle,persistent:p(i).props.persistent,"focus-on-target":""},{content:$(()=>[ce(m.$slots,"title")]),default:$(()=>[C("div",{class:U(p(l).be("tooltip","trigger"))},[ce(m.$slots,"default")],2)]),_:3},8,["effect","popper-class","popper-style","persistent"])):(x(),O(Ve,{key:1},[ce(m.$slots,"default"),ce(m.$slots,"title")],64))],2))}}),mD=Oge,$ge=q({name:"ElMenuItemGroup",__name:"menu-item-group",props:Dge,setup(e){const t=ke("menu-item-group");return(n,a)=>(x(),O("li",{class:U(p(t).b())},[C("div",{class:U(p(t).e("title"))},[n.$slots.title?ce(n.$slots,"title",{key:1}):(x(),O(Ve,{key:0},[ue(Q(e.title),1)],64))],2),C("ul",null,[ce(n.$slots,"default")])],2))}}),gD=$ge;const Rge=wt(Ige,{MenuItem:mD,MenuItemGroup:gD,SubMenu:Cw}),Nge=On(mD),Bge=On(gD),Vge=On(Cw),zge=$e({icon:{type:pn,default:()=>CI},title:String,content:{type:String,default:""}}),Fge={back:()=>!0},Hge=["aria-label"];var Wge=q({name:"ElPageHeader",__name:"page-header",props:zge,emits:Fge,setup(e,{emit:t}){const n=t,{t:a}=Vt(),r=ke("page-header");function o(){n("back")}return(i,l)=>(x(),O("div",{class:U([p(r).b(),p(r).is("contentful",!!i.$slots.default),{[p(r).m("has-breadcrumb")]:!!i.$slots.breadcrumb,[p(r).m("has-extra")]:!!i.$slots.extra}])},[i.$slots.breadcrumb?(x(),O("div",{key:0,class:U(p(r).e("breadcrumb"))},[ce(i.$slots,"breadcrumb")],2)):te("v-if",!0),C("div",{class:U(p(r).e("header"))},[C("div",{class:U(p(r).e("left"))},[C("div",{class:U(p(r).e("back")),role:"button",tabindex:"0",onClick:o},[e.icon||i.$slots.icon?(x(),O("div",{key:0,"aria-label":e.title||p(a)("el.pageHeader.title"),class:U(p(r).e("icon"))},[ce(i.$slots,"icon",{},()=>[e.icon?(x(),ne(p(Xe),{key:0},{default:$(()=>[(x(),ne(St(e.icon)))]),_:1})):te("v-if",!0)])],10,Hge)):te("v-if",!0),C("div",{class:U(p(r).e("title"))},[ce(i.$slots,"title",{},()=>[ue(Q(e.title||p(a)("el.pageHeader.title")),1)])],2)],2),L(p(nD),{direction:"vertical"}),C("div",{class:U(p(r).e("content"))},[ce(i.$slots,"content",{},()=>[ue(Q(e.content),1)])],2)],2),i.$slots.extra?(x(),O("div",{key:0,class:U(p(r).e("extra"))},[ce(i.$slots,"extra")],2)):te("v-if",!0)],2),i.$slots.default?(x(),O("div",{key:1,class:U(p(r).e("main"))},[ce(i.$slots,"default")],2)):te("v-if",!0)],2))}}),Kge=Wge;const Uge=wt(Kge),yD=Symbol("elPaginationKey"),Gge=$e({disabled:Boolean,currentPage:{type:Number,default:1},prevText:{type:String},prevIcon:{type:pn}}),Yge={click:e=>e instanceof MouseEvent},jge=["disabled","aria-label","aria-disabled"],qge={key:0};var Xge=q({name:"ElPaginationPrev",__name:"prev",props:Gge,emits:Yge,setup(e){const t=e,{t:n}=Vt(),a=D(()=>t.disabled||t.currentPage<=1);return(r,o)=>(x(),O("button",{type:"button",class:"btn-prev",disabled:a.value,"aria-label":r.prevText||p(n)("el.pagination.prev"),"aria-disabled":a.value,onClick:o[0]||(o[0]=i=>r.$emit("click",i))},[r.prevText?(x(),O("span",qge,Q(r.prevText),1)):(x(),ne(p(Xe),{key:1},{default:$(()=>[(x(),ne(St(r.prevIcon)))]),_:1}))],8,jge))}}),Zge=Xge;const Jge=$e({disabled:Boolean,currentPage:{type:Number,default:1},pageCount:{type:Number,default:50},nextText:{type:String},nextIcon:{type:pn}}),Qge=["disabled","aria-label","aria-disabled"],e1e={key:0};var t1e=q({name:"ElPaginationNext",__name:"next",props:Jge,emits:["click"],setup(e){const t=e,{t:n}=Vt(),a=D(()=>t.disabled||t.currentPage===t.pageCount||t.pageCount===0);return(r,o)=>(x(),O("button",{type:"button",class:"btn-next",disabled:a.value,"aria-label":r.nextText||p(n)("el.pagination.next"),"aria-disabled":a.value,onClick:o[0]||(o[0]=i=>r.$emit("click",i))},[r.nextText?(x(),O("span",e1e,Q(r.nextText),1)):(x(),ne(p(Xe),{key:1},{default:$(()=>[(x(),ne(St(r.nextIcon)))]),_:1}))],8,Qge))}}),n1e=t1e;const xw=()=>Ze(yD,{}),a1e=$e({pageSize:{type:Number,required:!0},pageSizes:{type:ae(Array),default:()=>Nn([10,20,30,40,50,100])},popperClass:{type:ae([String,Array,Object,Boolean]),default:void 0},popperStyle:{type:ae([String,Array,Object,Boolean]),default:void 0},disabled:Boolean,teleported:Boolean,size:{type:String,values:ii},appendSizeTo:String});var r1e=q({name:"ElPaginationSizes",__name:"sizes",props:a1e,emits:["page-size-change"],setup(e,{emit:t}){const n=e,a=t,{t:r}=Vt(),o=ke("pagination"),i=xw(),l=F(n.pageSize);ye(()=>n.pageSizes,(c,d)=>{Rn(c,d)||Ae(c)&&a("page-size-change",c.includes(n.pageSize)?n.pageSize:n.pageSizes[0])}),ye(()=>n.pageSize,c=>{l.value=c});const s=D(()=>n.pageSizes);function u(c){var d;c!==l.value&&(l.value=c,(d=i.handleSizeChange)==null||d.call(i,Number(c)))}return(c,d)=>(x(),O("span",{class:U(p(o).e("sizes"))},[L(p(Bs),{"model-value":l.value,disabled:c.disabled,"popper-class":c.popperClass,"popper-style":c.popperStyle,size:c.size,teleported:c.teleported,"validate-event":!1,"append-to":c.appendSizeTo,onChange:u},{default:$(()=>[(x(!0),O(Ve,null,dt(s.value,v=>(x(),ne(p(eg),{key:v,value:v,label:v+p(r)("el.pagination.pagesize")},null,8,["value","label"]))),128))]),_:1},8,["model-value","disabled","popper-class","popper-style","size","teleported","append-to"])],2))}}),o1e=r1e;const i1e=$e({size:{type:String,values:ii}}),l1e=["disabled"];var s1e=q({name:"ElPaginationJumper",__name:"jumper",props:i1e,setup(e){const{t}=Vt(),n=ke("pagination"),{pageCount:a,disabled:r,currentPage:o,changeEvent:i}=xw(),l=F(),s=D(()=>l.value??(o==null?void 0:o.value));function u(d){l.value=d?+d:""}function c(d){d=Math.trunc(+d),i==null||i(d),l.value=void 0}return(d,v)=>(x(),O("span",{class:U(p(n).e("jump")),disabled:p(r)},[C("span",{class:U([p(n).e("goto")])},Q(p(t)("el.pagination.goto")),3),L(p(Ba),{size:d.size,class:U([p(n).e("editor"),p(n).is("in-pagination")]),min:1,max:p(a),disabled:p(r),"model-value":s.value,"validate-event":!1,"aria-label":p(t)("el.pagination.page"),type:"number","onUpdate:modelValue":u,onChange:c},null,8,["size","class","max","disabled","model-value","aria-label"]),C("span",{class:U([p(n).e("classifier")])},Q(p(t)("el.pagination.pageClassifier")),3)],10,l1e))}}),u1e=s1e;const c1e=$e({total:{type:Number,default:1e3}}),d1e=["disabled"];var f1e=q({name:"ElPaginationTotal",__name:"total",props:c1e,setup(e){const{t}=Vt(),n=ke("pagination"),{disabled:a}=xw();return(r,o)=>(x(),O("span",{class:U(p(n).e("total")),disabled:p(a)},Q(p(t)("el.pagination.total",{total:r.total})),11,d1e))}}),v1e=f1e;const h1e=$e({currentPage:{type:Number,default:1},pageCount:{type:Number,required:!0},pagerCount:{type:Number,default:7},disabled:Boolean}),p1e=["aria-current","aria-label","tabindex"],m1e=["tabindex","aria-label"],g1e=["aria-current","aria-label","tabindex"],y1e=["tabindex","aria-label"],b1e=["aria-current","aria-label","tabindex"];var _1e=q({name:"ElPaginationPager",__name:"pager",props:h1e,emits:[Ot],setup(e,{emit:t}){const n=e,a=t,r=ke("pager"),o=ke("icon"),{t:i}=Vt(),l=F(!1),s=F(!1),u=F(!1),c=F(!1),d=F(!1),v=F(!1),f=D(()=>{const S=n.pagerCount,k=(S-1)/2,T=Number(n.currentPage),M=Number(n.pageCount);let E=!1,A=!1;M>S&&(T>S-k&&(E=!0),T["more","btn-quickprev",o.b(),r.is("disabled",n.disabled)]),m=D(()=>["more","btn-quicknext",o.b(),r.is("disabled",n.disabled)]),g=D(()=>n.disabled?-1:0);ye(()=>[n.pageCount,n.pagerCount,n.currentPage],([S,k,T])=>{const M=(k-1)/2;let E=!1,A=!1;S>k&&(E=T>k-M,A=TM&&(T=M)),T!==E&&a(Ot,T)}return(S,k)=>(x(),O("ul",{class:U(p(r).b()),onClick:w,onKeyup:kn(_,["enter"])},[S.pageCount>0?(x(),O("li",{key:0,class:U([[p(r).is("active",S.currentPage===1),p(r).is("disabled",S.disabled)],"number"]),"aria-current":S.currentPage===1,"aria-label":p(i)("el.pagination.currentPage",{pager:1}),tabindex:g.value}," 1 ",10,p1e)):te("v-if",!0),l.value?(x(),O("li",{key:1,class:U(h.value),tabindex:g.value,"aria-label":p(i)("el.pagination.prevPages",{pager:S.pagerCount-2}),onMouseenter:k[0]||(k[0]=T=>y(!0)),onMouseleave:k[1]||(k[1]=T=>u.value=!1),onFocus:k[2]||(k[2]=T=>b(!0)),onBlur:k[3]||(k[3]=T=>d.value=!1)},[(u.value||d.value)&&!S.disabled?(x(),ne(p(Al),{key:0})):(x(),ne(p(m4),{key:1}))],42,m1e)):te("v-if",!0),(x(!0),O(Ve,null,dt(f.value,T=>(x(),O("li",{key:T,class:U([[p(r).is("active",S.currentPage===T),p(r).is("disabled",S.disabled)],"number"]),"aria-current":S.currentPage===T,"aria-label":p(i)("el.pagination.currentPage",{pager:T}),tabindex:g.value},Q(T),11,g1e))),128)),s.value?(x(),O("li",{key:2,class:U(m.value),tabindex:g.value,"aria-label":p(i)("el.pagination.nextPages",{pager:S.pagerCount-2}),onMouseenter:k[4]||(k[4]=T=>y()),onMouseleave:k[5]||(k[5]=T=>c.value=!1),onFocus:k[6]||(k[6]=T=>b()),onBlur:k[7]||(k[7]=T=>v.value=!1)},[(c.value||v.value)&&!S.disabled?(x(),ne(p(Il),{key:0})):(x(),ne(p(m4),{key:1}))],42,y1e)):te("v-if",!0),S.pageCount>1?(x(),O("li",{key:3,class:U([[p(r).is("active",S.currentPage===S.pageCount),p(r).is("disabled",S.disabled)],"number"]),"aria-current":S.currentPage===S.pageCount,"aria-label":p(i)("el.pagination.currentPage",{pager:S.pageCount}),tabindex:g.value},Q(S.pageCount),11,b1e)):te("v-if",!0)],34))}}),w1e=_1e;const hr=e=>typeof e!="number",S1e=$e({pageSize:Number,defaultPageSize:Number,total:Number,pageCount:Number,pagerCount:{type:Number,validator:e=>Je(e)&&Math.trunc(e)===e&&e>4&&e<22&&e%2===1,default:7},currentPage:Number,defaultCurrentPage:Number,layout:{type:String,default:["prev","pager","next","jumper","->","total"].join(", ")},pageSizes:{type:ae(Array),default:()=>Nn([10,20,30,40,50,100])},popperClass:{type:ae([String,Array,Object,Boolean]),default:""},popperStyle:{type:ae([String,Array,Object,Boolean]),default:void 0},prevText:{type:String,default:""},prevIcon:{type:pn,default:()=>Ji},nextText:{type:String,default:""},nextIcon:{type:pn,default:()=>za},teleported:{type:Boolean,default:!0},small:Boolean,size:pa,background:Boolean,disabled:Boolean,hideOnSinglePage:Boolean,appendSizeTo:String}),C1e={"update:current-page":e=>Je(e),"update:page-size":e=>Je(e),"size-change":e=>Je(e),change:(e,t)=>Je(e)&&Je(t),"current-change":e=>Je(e),"prev-click":e=>Je(e),"next-click":e=>Je(e)},Ax="ElPagination";var x1e=q({name:Ax,props:S1e,emits:C1e,setup(e,{emit:t,slots:n}){const{t:a}=Vt(),r=ke("pagination"),o=Et().vnode.props||{},i=bI(),l=D(()=>e.small?"small":e.size??i.value);Ri({from:"small",replacement:"size",version:"3.0.0",scope:"el-pagination",ref:"https://element-plus.org/zh-CN/component/pagination.html"},D(()=>!!e.small));const s="onUpdate:currentPage"in o||"onUpdate:current-page"in o||"onCurrentChange"in o,u="onUpdate:pageSize"in o||"onUpdate:page-size"in o||"onSizeChange"in o,c=D(()=>{if(hr(e.total)&&hr(e.pageCount)||!hr(e.currentPage)&&!s)return!1;if(e.layout.includes("sizes")){if(hr(e.pageCount)){if(!hr(e.total)&&!hr(e.pageSize)&&!u)return!1}else if(!u)return!1}return!0}),d=F(hr(e.defaultPageSize)?10:e.defaultPageSize),v=F(hr(e.defaultCurrentPage)?1:e.defaultCurrentPage),f=D({get(){return hr(e.pageSize)?d.value:e.pageSize},set(S){hr(e.pageSize)&&(d.value=S),u&&(t("update:page-size",S),t("size-change",S))}}),h=D(()=>{let S=0;return hr(e.pageCount)?hr(e.total)||(S=Math.max(1,Math.ceil(e.total/f.value))):S=e.pageCount,S}),m=D({get(){return hr(e.currentPage)?v.value:e.currentPage},set(S){let k=S;S<1?k=1:S>h.value&&(k=h.value),hr(e.currentPage)&&(v.value=k),s&&(t("update:current-page",k),t("current-change",k))}});ye(h,S=>{m.value>S&&(m.value=S)}),ye([m,f],S=>{t(Ot,...S)},{flush:"post"});function g(S){m.value=S}function y(S){f.value=S;const k=h.value;m.value>k&&(m.value=k)}function b(){e.disabled||(m.value-=1,t("prev-click",m.value))}function _(){e.disabled||(m.value+=1,t("next-click",m.value))}function w(S,k){S&&(S.props||(S.props={}),S.props.class=[S.props.class,k].join(" "))}return Ft(yD,{pageCount:h,disabled:D(()=>e.disabled),currentPage:m,changeEvent:g,handleSizeChange:y}),()=>{var I;if(!c.value)return un(Ax,a("el.pagination.deprecationWarning")),null;if(!e.layout||e.hideOnSinglePage&&h.value<=1)return null;const S=[],k=[],T=ot("div",{class:r.e("rightwrapper")},k),M={prev:ot(Zge,{disabled:e.disabled,currentPage:m.value,prevText:e.prevText,prevIcon:e.prevIcon,onClick:b}),jumper:ot(u1e,{size:l.value}),pager:ot(w1e,{currentPage:m.value,pageCount:h.value,pagerCount:e.pagerCount,onChange:g,disabled:e.disabled}),next:ot(n1e,{disabled:e.disabled,currentPage:m.value,pageCount:h.value,nextText:e.nextText,nextIcon:e.nextIcon,onClick:_}),sizes:ot(o1e,{pageSize:f.value,pageSizes:e.pageSizes,popperClass:e.popperClass,popperStyle:e.popperStyle,disabled:e.disabled,teleported:e.teleported,size:l.value,appendSizeTo:e.appendSizeTo}),slot:((I=n==null?void 0:n.default)==null?void 0:I.call(n))??null,total:ot(v1e,{total:hr(e.total)?0:e.total})},E=e.layout.split(",").map(P=>P.trim());let A=!1;return E.forEach(P=>{if(P==="->"){A=!0;return}A?k.push(M[P]):S.push(M[P])}),w(S[0],r.is("first")),w(S[S.length-1],r.is("last")),A&&k.length>0&&(w(k[0],r.is("first")),w(k[k.length-1],r.is("last")),S.push(T)),ot("div",{class:[r.b(),r.is("background",e.background),r.m(l.value)]},S)}}});const k1e=wt(x1e),T1e=$e({title:String,confirmButtonText:String,cancelButtonText:String,confirmButtonType:{type:String,values:x4,default:"primary"},cancelButtonType:{type:String,values:x4,default:"text"},icon:{type:pn,default:()=>BI},iconColor:{type:String,default:"#f90"},hideIcon:Boolean,hideAfter:{type:Number,default:200},effect:{...Tn.effect,default:"light"},teleported:Tn.teleported,persistent:Tn.persistent,width:{type:[String,Number],default:150},virtualTriggering:qu.virtualTriggering,virtualRef:qu.virtualRef}),M1e={confirm:e=>e instanceof MouseEvent,cancel:e=>e instanceof MouseEvent};var E1e=q({name:"ElPopconfirm",__name:"popconfirm",props:T1e,emits:M1e,setup(e,{expose:t,emit:n}){const a=e,r=n,{t:o}=Vt(),i=ke("popconfirm"),l=F(),s=F(),u=D(()=>{var y;return(y=p(l))==null?void 0:y.popperRef}),c=()=>{var y,b;(b=(y=s.value)==null?void 0:y.focus)==null||b.call(y)},d=()=>{var y,b;(b=(y=l.value)==null?void 0:y.onClose)==null||b.call(y)},v=D(()=>({width:Vn(a.width)})),f=y=>{r("confirm",y),d()},h=y=>{r("cancel",y),d()},m=D(()=>a.confirmButtonText||o("el.popconfirm.confirmButtonText")),g=D(()=>a.cancelButtonText||o("el.popconfirm.cancelButtonText"));return t({popperRef:u,hide:d}),(y,b)=>(x(),ne(p(Ma),Tt({ref_key:"tooltipRef",ref:l,trigger:"click",effect:e.effect},y.$attrs,{"virtual-triggering":e.virtualTriggering,"virtual-ref":e.virtualRef,"popper-class":`${p(i).namespace.value}-popover`,"popper-style":v.value,teleported:e.teleported,"fallback-placements":["bottom","top","right","left"],"hide-after":e.hideAfter,persistent:e.persistent,loop:"",onShow:c}),{content:$(()=>[C("div",{ref_key:"rootRef",ref:s,tabindex:"-1",class:U(p(i).b())},[C("div",{class:U(p(i).e("main"))},[!e.hideIcon&&e.icon?(x(),ne(p(Xe),{key:0,class:U(p(i).e("icon")),style:et({color:e.iconColor})},{default:$(()=>[(x(),ne(St(e.icon)))]),_:1},8,["class","style"])):te("v-if",!0),ue(" "+Q(e.title),1)],2),C("div",{class:U(p(i).e("action"))},[ce(y.$slots,"actions",{confirm:f,cancel:h},()=>[L(p(xa),{size:"small",type:e.cancelButtonType==="text"?"":e.cancelButtonType,text:e.cancelButtonType==="text",onClick:h},{default:$(()=>[ue(Q(g.value),1)]),_:1},8,["type","text"]),L(p(xa),{size:"small",type:e.confirmButtonType==="text"?"":e.confirmButtonType,text:e.confirmButtonType==="text",onClick:f},{default:$(()=>[ue(Q(m.value),1)]),_:1},8,["type","text"])])],2)],2)]),default:$(()=>[y.$slots.reference?ce(y.$slots,"reference",{key:0}):te("v-if",!0)]),_:3},16,["effect","virtual-triggering","virtual-ref","popper-class","popper-style","teleported","hide-after","persistent"]))}}),A1e=E1e;const I1e=wt(A1e),L1e=$e({type:{type:String,default:"line",values:["line","circle","dashboard"]},percentage:{type:Number,default:0,validator:e=>e>=0&&e<=100},status:{type:String,default:"",values:["","success","exception","warning"]},indeterminate:Boolean,duration:{type:Number,default:3},strokeWidth:{type:Number,default:6},strokeLinecap:{type:ae(String),default:"round"},textInside:Boolean,width:{type:Number,default:126},showText:{type:Boolean,default:!0},color:{type:ae([String,Array,Function]),default:""},striped:Boolean,stripedFlow:Boolean,format:{type:ae(Function),default:e=>`${e}%`}}),P1e=["aria-valuenow"],D1e={viewBox:"0 0 100 100"},O1e=["d","stroke","stroke-linecap","stroke-width"],$1e=["d","stroke","opacity","stroke-linecap","stroke-width"],R1e={key:0};var N1e=q({name:"ElProgress",__name:"progress",props:L1e,setup(e){const t={success:"#13ce66",exception:"#ff4949",warning:"#e6a23c",default:"#20a0ff"},n=e,a=ke("progress"),r=D(()=>{const _={width:`${n.percentage}%`,animationDuration:`${n.duration}s`},w=b(n.percentage);return w.includes("gradient")?_.background=w:_.backgroundColor=w,_}),o=D(()=>(n.strokeWidth/n.width*100).toFixed(1)),i=D(()=>["circle","dashboard"].includes(n.type)?Number.parseInt(`${50-Number.parseFloat(o.value)/2}`,10):0),l=D(()=>{const _=i.value,w=n.type==="dashboard";return` + M 50 50 + m 0 ${w?"":"-"}${_} + a ${_} ${_} 0 1 1 0 ${w?"-":""}${_*2} + a ${_} ${_} 0 1 1 0 ${w?"":"-"}${_*2} + `}),s=D(()=>2*Math.PI*i.value),u=D(()=>n.type==="dashboard"?.75:1),c=D(()=>`${-1*s.value*(1-u.value)/2}px`),d=D(()=>({strokeDasharray:`${s.value*u.value}px, ${s.value}px`,strokeDashoffset:c.value})),v=D(()=>({strokeDasharray:`${s.value*u.value*(n.percentage/100)}px, ${s.value}px`,strokeDashoffset:c.value,transition:"stroke-dasharray 0.6s ease 0s, stroke 0.6s ease, opacity ease 0.6s"})),f=D(()=>{let _;return n.color?_=b(n.percentage):_=t[n.status]||t.default,_}),h=D(()=>n.status==="warning"?$s:n.type==="line"?n.status==="success"?Ao:ci:n.status==="success"?Os:so),m=D(()=>n.type==="line"?12+n.strokeWidth*.4:n.width*.111111+2),g=D(()=>n.format(n.percentage));function y(_){const w=100/_.length;return _.map((S,k)=>Ye(S)?{color:S,percentage:(k+1)*w}:S).sort((S,k)=>S.percentage-k.percentage)}const b=_=>{var S;const{color:w}=n;if(Qe(w))return w(_);if(Ye(w))return w;{const k=y(w);for(const T of k)if(T.percentage>_)return T.color;return(S=k[k.length-1])==null?void 0:S.color}};return(_,w)=>(x(),O("div",{class:U([p(a).b(),p(a).m(e.type),p(a).is(e.status),{[p(a).m("without-text")]:!e.showText,[p(a).m("text-inside")]:e.textInside}]),role:"progressbar","aria-valuenow":e.percentage,"aria-valuemin":"0","aria-valuemax":"100"},[e.type==="line"?(x(),O("div",{key:0,class:U(p(a).b("bar"))},[C("div",{class:U(p(a).be("bar","outer")),style:et({height:`${e.strokeWidth}px`})},[C("div",{class:U([p(a).be("bar","inner"),{[p(a).bem("bar","inner","indeterminate")]:e.indeterminate},{[p(a).bem("bar","inner","striped")]:e.striped},{[p(a).bem("bar","inner","striped-flow")]:e.stripedFlow}]),style:et(r.value)},[(e.showText||_.$slots.default)&&e.textInside?(x(),O("div",{key:0,class:U(p(a).be("bar","innerText"))},[ce(_.$slots,"default",{percentage:e.percentage},()=>[C("span",null,Q(g.value),1)])],2)):te("v-if",!0)],6)],6)],2)):(x(),O("div",{key:1,class:U(p(a).b("circle")),style:et({height:`${e.width}px`,width:`${e.width}px`})},[(x(),O("svg",D1e,[C("path",{class:U(p(a).be("circle","track")),d:l.value,stroke:`var(${p(a).cssVarName("fill-color-light")}, #e5e9f2)`,"stroke-linecap":e.strokeLinecap,"stroke-width":o.value,fill:"none",style:et(d.value)},null,14,O1e),C("path",{class:U(p(a).be("circle","path")),d:l.value,stroke:f.value,fill:"none",opacity:e.percentage?1:0,"stroke-linecap":e.strokeLinecap,"stroke-width":o.value,style:et(v.value)},null,14,$1e)]))],6)),(e.showText||_.$slots.default)&&!e.textInside?(x(),O("div",{key:2,class:U(p(a).e("text")),style:et({fontSize:`${m.value}px`})},[ce(_.$slots,"default",{percentage:e.percentage},()=>[e.status?(x(),ne(p(Xe),{key:1},{default:$(()=>[(x(),ne(St(h.value)))]),_:1})):(x(),O("span",R1e,Q(g.value),1))])],6)):te("v-if",!0)],10,P1e))}}),B1e=N1e;const kw=wt(B1e),V1e=$e({modelValue:{type:Number,default:0},id:{type:String,default:void 0},lowThreshold:{type:Number,default:2},highThreshold:{type:Number,default:4},max:{type:Number,default:5},colors:{type:ae([Array,Object]),default:()=>Nn(["","",""])},voidColor:{type:String,default:""},disabledVoidColor:{type:String,default:""},icons:{type:ae([Array,Object]),default:()=>[rv,rv,rv]},voidIcon:{type:pn,default:()=>UI},disabledVoidIcon:{type:pn,default:()=>rv},disabled:{type:Boolean,default:void 0},allowHalf:Boolean,showText:Boolean,showScore:Boolean,textColor:{type:String,default:""},texts:{type:ae(Array),default:()=>Nn(["Extremely bad","Disappointed","Fair","Satisfied","Surprise"])},scoreTemplate:{type:String,default:"{value}"},size:pa,clearable:Boolean,...dr(["ariaLabel"])}),z1e={[Ot]:e=>Je(e),[bt]:e=>Je(e)},F1e=["id","aria-label","aria-labelledby","aria-valuenow","aria-valuetext","aria-valuemax","tabindex","aria-disabled"],H1e=["onMousemove","onClick"];var W1e=q({name:"ElRate",__name:"rate",props:V1e,emits:z1e,setup(e,{expose:t,emit:n}){function a(B,W){const N=Y=>_t(Y),V=W[Object.keys(W).map(Y=>+Y).filter(Y=>{const X=W[Y];return N(X)&&X.excluded?BY-X)[0]];return N(V)&&V.value||V}const r=e,o=n,i=Ze(Qi,void 0),l=ca(),s=ke("rate"),{inputId:u,isLabeledByFormItem:c}=Yr(r,{formItemContext:i}),d=F(Br(r.modelValue,0,r.max)),v=F(-1),f=F(!0),h=F([]),m=D(()=>h.value.map(B=>B.$el.clientWidth)),g=D(()=>[s.b(),s.m(l.value)]),y=$n(),b=D(()=>s.cssVarBlock({"void-color":r.voidColor,"disabled-void-color":r.disabledVoidColor,"fill-color":k.value})),_=D(()=>{let B="";return r.showScore?B=r.scoreTemplate.replace(/\{\s*value\s*\}/,y.value?`${r.modelValue}`:`${d.value}`):r.showText&&(B=r.texts[Math.ceil(d.value)-1]),B}),w=D(()=>r.modelValue*100-Math.floor(r.modelValue)*100),S=D(()=>Ae(r.colors)?{[r.lowThreshold]:r.colors[0],[r.highThreshold]:{value:r.colors[1],excluded:!0},[r.max]:r.colors[2]}:r.colors),k=D(()=>{const B=a(d.value,S.value);return _t(B)?"":B}),T=D(()=>{let B="";return y.value?B=`${w.value}%`:r.allowHalf&&(B="50%"),{color:k.value,width:B}}),M=D(()=>{let B=Ae(r.icons)?[...r.icons]:{...r.icons};return B=$i(B),Ae(B)?{[r.lowThreshold]:B[0],[r.highThreshold]:{value:B[1],excluded:!0},[r.max]:B[2]}:B}),E=D(()=>a(r.modelValue,M.value)),A=D(()=>y.value?Ye(r.disabledVoidIcon)?r.disabledVoidIcon:$i(r.disabledVoidIcon):Ye(r.voidIcon)?r.voidIcon:$i(r.voidIcon)),I=D(()=>a(d.value,M.value));function P(B){const W=y.value&&w.value>0&&B-1r.modelValue,N=r.allowHalf&&f.value&&B-.5<=d.value&&B>d.value;return W||N}function R(B){r.clearable&&B===r.modelValue&&(B=0),o(bt,B),r.modelValue!==B&&o(Ot,B)}function G(B){y.value||(r.allowHalf&&f.value?R(d.value):R(B))}function K(B){if(y.value)return;const W=mn(B),N=r.allowHalf?.5:1;let V=d.value;switch(W){case Pe.up:case Pe.right:V+=N;break;case Pe.left:case Pe.down:V-=N;break}if(V=Br(V,0,r.max),V!==d.value)return B.stopPropagation(),B.preventDefault(),o(bt,V),o(Ot,V),V}function H(B,W){y.value||(r.allowHalf&&W?(f.value=W.offsetX*2<=m.value[B-1],d.value=f.value?B-.5:B):d.value=B,v.value=B)}function z(){y.value||(r.allowHalf&&(f.value=r.modelValue!==Math.floor(r.modelValue)),d.value=Br(r.modelValue,0,r.max),v.value=-1)}return ye(()=>r.modelValue,B=>{d.value=Br(B,0,r.max),f.value=r.modelValue!==Math.floor(r.modelValue)}),r.modelValue||o(bt,0),t({setCurrentValue:H,resetCurrentValue:z}),(B,W)=>{var N;return x(),O("div",{id:p(u),class:U([g.value,p(s).is("disabled",p(y))]),role:"slider","aria-label":p(c)?void 0:e.ariaLabel||"rating","aria-labelledby":p(c)?(N=p(i))==null?void 0:N.labelId:void 0,"aria-valuenow":d.value,"aria-valuetext":_.value||void 0,"aria-valuemin":"0","aria-valuemax":e.max,style:et(b.value),tabindex:p(y)?void 0:0,"aria-disabled":p(y),onKeydown:K},[(x(!0),O(Ve,null,dt(e.max,(V,Y)=>(x(),O("span",{key:Y,class:U(p(s).e("item")),onMousemove:X=>H(V,X),onMouseleave:z,onClick:X=>G(V)},[L(p(Xe),{ref_for:!0,ref_key:"iconRefs",ref:h,class:U([p(s).e("icon"),{hover:v.value===V},p(s).is("active",V<=d.value),p(s).is("focus-visible",V===Math.ceil(d.value||1))])},{default:$(()=>[st((x(),ne(St(I.value),null,null,512)),[[Zt,!P(V)&&V<=d.value]]),st((x(),ne(St(A.value),null,null,512)),[[Zt,!P(V)&&V>d.value]]),st((x(),ne(St(A.value),{class:U([p(s).em("decimal","box")])},null,8,["class"])),[[Zt,P(V)]]),st(L(p(Xe),{style:et(T.value),class:U([p(s).e("icon"),p(s).e("decimal")])},{default:$(()=>[(x(),ne(St(E.value)))]),_:1},8,["style","class"]),[[Zt,P(V)]])]),_:2},1032,["class"])],42,H1e))),128)),e.showText||e.showScore?(x(),O("span",{key:0,class:U(p(s).e("text")),style:et({color:e.textColor})},Q(_.value),7)):te("v-if",!0)],46,F1e)}}}),K1e=W1e;const U1e=wt(K1e),Pu={primary:"icon-primary",success:"icon-success",warning:"icon-warning",error:"icon-error",info:"icon-info"},Ix={[Pu.primary]:Ud,[Pu.success]:MI,[Pu.warning]:$s,[Pu.error]:fc,[Pu.info]:Ud},G1e=$e({title:{type:String,default:""},subTitle:{type:String,default:""},icon:{type:String,values:["primary","success","warning","info","error"],default:"info"}});var Y1e=q({name:"ElResult",__name:"result",props:G1e,setup(e){const t=e,n=ke("result"),a=D(()=>{const r=t.icon,o=r&&Pu[r]?Pu[r]:"icon-info";return{class:o,component:Ix[o]||Ix["icon-info"]}});return(r,o)=>(x(),O("div",{class:U(p(n).b())},[C("div",{class:U(p(n).e("icon"))},[ce(r.$slots,"icon",{},()=>[a.value.component?(x(),ne(St(a.value.component),{key:0,class:U(a.value.class)},null,8,["class"])):te("v-if",!0)])],2),e.title||r.$slots.title?(x(),O("div",{key:0,class:U(p(n).e("title"))},[ce(r.$slots,"title",{},()=>[C("p",null,Q(e.title),1)])],2)):te("v-if",!0),e.subTitle||r.$slots["sub-title"]?(x(),O("div",{key:1,class:U(p(n).e("subtitle"))},[ce(r.$slots,"sub-title",{},()=>[C("p",null,Q(e.subTitle),1)])],2)):te("v-if",!0),r.$slots.extra?(x(),O("div",{key:2,class:U(p(n).e("extra"))},[ce(r.$slots,"extra")],2)):te("v-if",!0)],2))}}),j1e=Y1e;const q1e=wt(j1e),Tw=Symbol("ElSelectV2Injection"),X1e=$e({allowCreate:Boolean,autocomplete:{type:ae(String),default:"none"},automaticDropdown:Boolean,clearable:Boolean,clearIcon:{type:pn,default:ci},effect:{type:ae(String),default:"light"},collapseTags:Boolean,collapseTagsTooltip:Boolean,tagTooltip:{type:ae(Object),default:()=>({})},maxCollapseTags:{type:Number,default:1},defaultFirstOption:Boolean,disabled:{type:Boolean,default:void 0},estimatedOptionHeight:{type:Number,default:void 0},filterable:Boolean,filterMethod:{type:ae(Function)},height:{type:Number,default:274},itemHeight:{type:Number,default:34},id:String,loading:Boolean,loadingText:String,modelValue:{type:ae([Array,String,Number,Boolean,Object]),default:void 0},multiple:Boolean,multipleLimit:{type:Number,default:0},name:String,noDataText:String,noMatchText:String,remoteMethod:{type:ae(Function)},reserveKeyword:{type:Boolean,default:!0},options:{type:ae(Array),required:!0},placeholder:{type:String},teleported:Tn.teleported,persistent:{type:Boolean,default:!0},popperClass:Tn.popperClass,popperStyle:Tn.popperStyle,popperOptions:{type:ae(Object),default:()=>({})},remote:Boolean,debounce:{type:Number,default:300},size:pa,props:{type:ae(Object),default:()=>Qm},valueKey:{type:String,default:"value"},scrollbarAlwaysOn:Boolean,validateEvent:{type:Boolean,default:!0},offset:{type:Number,default:12},remoteShowSuffix:Boolean,showArrow:{type:Boolean,default:!0},placement:{type:ae(String),values:tl,default:"bottom-start"},fallbackPlacements:{type:ae(Array),default:["bottom-start","top-start","right","left"]},tagType:{...Ll.type,default:"info"},tagEffect:{...Ll.effect,default:"light"},tabindex:{type:[String,Number],default:0},appendTo:Tn.appendTo,fitInputWidth:{type:[Boolean,Number],default:!0,validator(e){return vn(e)||Je(e)}},suffixIcon:{type:pn,default:ui},...Mc,...dr(["ariaLabel"])}),Z1e=$e({data:Array,disabled:Boolean,hovering:Boolean,item:{type:ae(Object),required:!0},index:Number,style:Object,selected:Boolean,created:Boolean}),J1e={[bt]:e=>!0,[Ot]:e=>!0,"end-reached":hh["end-reached"],"remove-tag":e=>!0,"visible-change":e=>!0,focus:e=>e instanceof FocusEvent,blur:e=>e instanceof FocusEvent,clear:()=>!0},Q1e={hover:e=>Je(e),select:(e,t)=>!0};var e2e=q({props:{item:{type:Object,required:!0},style:{type:Object},height:Number},setup(){return{ns:ke("select")}}});function t2e(e,t,n,a,r,o){return x(),O("div",{class:U(e.ns.be("group","title")),style:et({...e.style,lineHeight:`${e.height}px`})},Q(e.item.label),7)}var n2e=ya(e2e,[["render",t2e]]);function a2e(e,{emit:t}){return{hoverItem:()=>{e.disabled||t("hover",e.index)},selectOptionClick:()=>{e.disabled||t("select",e.item,e.index)}}}var r2e=q({props:Z1e,emits:Q1e,setup(e,{emit:t}){const n=Ze(Tw),a=ke("select"),r=Nd?null:"mousemove",{hoverItem:o,selectOptionClick:i}=a2e(e,{emit:t}),{getLabel:l}=up(n.props),s=n.contentId;return{ns:a,contentId:s,mouseMoveEventName:r,hoverItem:o,handleMousedown:c=>{let d=c.target;const v=c.currentTarget;for(;d&&d!==v;){if(sc(d))return;d=d.parentElement}c.preventDefault()},selectOptionClick:i,getLabel:l}}});const o2e=["id","aria-selected","aria-disabled"];function i2e(e,t,n,a,r,o){return x(),O("li",Tt({id:`${e.contentId}-${e.index}`,role:"option","aria-selected":e.selected,"aria-disabled":e.disabled||void 0,style:e.style,class:[e.ns.be("dropdown","item"),e.ns.is("selected",e.selected),e.ns.is("disabled",e.disabled),e.ns.is("created",e.created),e.ns.is("hovering",e.hovering)]},{[Wu(e.mouseMoveEventName)]:t[0]||(t[0]=(...i)=>e.hoverItem&&e.hoverItem(...i))},{onMousedown:t[1]||(t[1]=(...i)=>e.handleMousedown&&e.handleMousedown(...i)),onClick:t[2]||(t[2]=at((...i)=>e.selectOptionClick&&e.selectOptionClick(...i),["stop"]))}),[ce(e.$slots,"default",{item:e.item,index:e.index,disabled:e.disabled},()=>[C("span",null,Q(e.getLabel(e.item)),1)])],16,o2e)}var l2e=ya(r2e,[["render",i2e]]),s2e=q({name:"ElSelectDropdown",props:{loading:Boolean,data:{type:Array,required:!0},hoveringIndex:Number,width:Number,id:String,ariaLabel:String},emits:{"end-reached":hh["end-reached"]},setup(e,{slots:t,expose:n,emit:a}){const r=Ze(Tw),o=ke("select"),{getLabel:i,getValue:l,getDisabled:s}=up(r.props),u=F([]),c=F(),d=D(()=>e.data.length);ye(()=>d.value,()=>{var P,R;(R=(P=r.tooltipRef.value)==null?void 0:P.updatePopper)==null||R.call(P)});const v=D(()=>zt(r.props.estimatedOptionHeight)),f=D(()=>v.value?{itemSize:r.props.itemHeight}:{estimatedSize:r.props.estimatedOptionHeight,itemSize:P=>u.value[P]}),h=(P=[],R)=>{const{props:{valueKey:G}}=r;return _t(R)?P&&P.some(K=>wn(na(K,G))===na(R,G)):P.includes(R)},m=(P,R)=>{if(_t(R)){const{valueKey:G}=r.props;return na(P,G)===na(R,G)}else return P===R},g=(P,R)=>r.props.multiple?h(P,l(R)):m(P,l(R)),y=(P,R)=>{const{disabled:G,multiple:K,multipleLimit:H}=r.props;return G||!R&&(K?H>0&&P.length>=H:!1)},b=P=>e.hoveringIndex===P;n({listRef:c,isSized:v,isItemDisabled:y,isItemHovering:b,isItemSelected:g,scrollToItem:P=>{const R=c.value;R&&R.scrollToItem(P)},resetScrollTop:()=>{const P=c.value;P&&P.resetScrollTop()}});const S=P=>{const{index:R,data:G,style:K}=P,H=p(v),{itemSize:z,estimatedSize:B}=p(f),{modelValue:W}=r.props,{onSelect:N,onHover:V}=r,Y=G[R];if(Y.type==="Group")return L(n2e,{item:Y,style:K,height:H?z:B},null);const X=g(W,Y),ee=y(W,X),re=b(R);return L(l2e,Tt(P,{selected:X,disabled:s(Y)||ee,created:!!Y.created,hovering:re,item:Y,onSelect:N,onHover:V}),{default:ie=>{var le;return((le=t.default)==null?void 0:le.call(t,ie))||L("span",null,[i(Y)])}})},{onKeyboardNavigate:k,onKeyboardSelect:T}=r,M=()=>{k("forward")},E=()=>{k("backward")},A=P=>{const R=mn(P),{tab:G,esc:K,down:H,up:z,enter:B,numpadEnter:W}=Pe;switch([K,H,z,B,W].includes(R)&&(P.preventDefault(),P.stopPropagation()),R){case G:case K:break;case H:M();break;case z:E();break;case B:case W:T();break}},I=P=>{a("end-reached",P)};return()=>{var W,N,V,Y;const{data:P,width:R}=e,{height:G,multiple:K,scrollbarAlwaysOn:H}=r.props,z=Nd?!0:H,B=p(v)?cp:Ede;return L("div",{class:[o.b("dropdown"),o.is("multiple",K)],style:{width:`${R}px`}},[(W=t.header)==null?void 0:W.call(t),((N=t.loading)==null?void 0:N.call(t))||((V=t.empty)==null?void 0:V.call(t))||L(B,Tt({ref:c},p(f),{className:o.be("dropdown","list"),scrollbarAlwaysOn:z,data:P,height:G,width:R,total:P.length,innerElement:"ul",innerProps:{id:e.id,role:"listbox","aria-label":e.ariaLabel,"aria-orientation":"vertical"},onEndReached:I,onKeydown:A}),{default:X=>L(S,X,null)}),(Y=t.footer)==null?void 0:Y.call(t)])}}});function u2e(e,t){const{aliasProps:n,getLabel:a,getValue:r}=up(e),o=F(0),i=F(),l=D(()=>e.allowCreate&&e.filterable);ye(()=>e.options,f=>{const h=new Set(f.map(m=>a(m)));t.createdOptions=t.createdOptions.filter(m=>!h.has(a(m)))});function s(f){const h=m=>a(m)===f;return e.options&&e.options.some(h)||t.createdOptions.some(h)}function u(f){l.value&&(e.multiple&&f.created?o.value++:i.value=f)}function c(f){if(l.value)if(f&&f.length>0){if(s(f)){t.createdOptions=t.createdOptions.filter(m=>a(m)!==t.previousQuery);return}const h={[n.value.value]:f,[n.value.label]:f,created:!0,[n.value.disabled]:!1};t.createdOptions.length>=o.value?t.createdOptions[o.value]=h:t.createdOptions.push(h)}else if(e.multiple)t.createdOptions.length=o.value;else{const h=i.value;t.createdOptions.length=0,h&&h.created&&t.createdOptions.push(h)}}function d(f){if(!l.value||!f||!f.created||f.created&&e.reserveKeyword&&t.inputValue===a(f))return;const h=t.createdOptions.findIndex(m=>r(m)===r(f));~h&&(t.createdOptions.splice(h,1),o.value--)}function v(){l.value&&(t.createdOptions.length=0,o.value=0)}return{createNewOption:c,removeNewOption:d,selectNewOption:u,clearAllNewOption:v}}const c2e=(e,t)=>{const{t:n}=Vt(),a=Xn(),r=ke("select"),o=ke("input"),{form:i,formItem:l}=Ea(),{inputId:s}=Yr(e,{formItemContext:l}),{aliasProps:u,getLabel:c,getValue:d,getDisabled:v,getOptions:f}=up(e),{valueOnClear:h,isEmptyValue:m}=op(e),g=nn({inputValue:"",cachedOptions:[],createdOptions:[],hoveringIndex:-1,inputHovering:!1,selectionWidth:0,collapseItemWidth:0,previousQuery:null,previousValue:void 0,selectedLabel:"",menuVisibleOnFocus:!1,isBeforeHide:!1}),y=F(-1),b=F(!1),_=F(),w=F(),S=F(),k=F(),T=F(),M=F(),E=F(),A=F(),I=F(),P=F(),{isComposing:R,handleCompositionStart:G,handleCompositionEnd:K,handleCompositionUpdate:H}=rp({afterComposition:pe=>_n(pe)}),z=$n(),{wrapperRef:B,isFocused:W,handleBlur:N}=Fl(T,{disabled:z,afterFocus(){e.automaticDropdown&&!ee.value&&(ee.value=!0,g.menuVisibleOnFocus=!0)},beforeBlur(pe){var ct,Nt;return((ct=S.value)==null?void 0:ct.isFocusInsideContent(pe))||((Nt=k.value)==null?void 0:Nt.isFocusInsideContent(pe))},afterBlur(){var pe;ee.value=!1,g.menuVisibleOnFocus=!1,e.validateEvent&&((pe=l==null?void 0:l.validate)==null||pe.call(l,"blur").catch(kt))}}),V=D(()=>De("")),Y=D(()=>e.loading?!1:e.options.length>0||g.createdOptions.length>0),X=F([]),ee=F(!1),re=D(()=>(i==null?void 0:i.statusIcon)??!1),ie=D(()=>{const pe=X.value.length*e.itemHeight;return pe>e.height?e.height:pe}),le=D(()=>e.multiple?Ae(e.modelValue)&&e.modelValue.length>0:!m(e.modelValue)),ve=D(()=>e.clearable&&!z.value&&le.value&&(W.value||g.inputHovering)),he=D(()=>e.remote&&e.filterable&&!e.remoteShowSuffix?"":e.suffixIcon),J=D(()=>he.value&&r.is("reverse",ee.value)),Z=D(()=>(l==null?void 0:l.validateState)||""),j=D(()=>{if(Z.value)return F1[Z.value]}),oe=D(()=>e.remote?e.debounce:0),se=D(()=>e.remote&&!g.inputValue&&!Y.value),fe=D(()=>e.loading?e.loadingText||n("el.select.loading"):e.filterable&&g.inputValue&&Y.value&&X.value.length===0?e.noMatchText||n("el.select.noMatch"):Y.value?null:e.noDataText||n("el.select.noData")),ge=D(()=>e.filterable&&Qe(e.filterMethod)),Se=D(()=>e.filterable&&e.remote&&Qe(e.remoteMethod)),De=pe=>{const ct=new RegExp(S6(pe),"i"),Nt=Kt=>ge.value||Se.value?!0:pe?ct.test(c(Kt)||""):!0;return e.loading?[]:[...g.createdOptions,...e.options].reduce((Kt,$a)=>{const Ir=f($a);if(Ae(Ir)){const kf=Ir.filter(Nt);kf.length>0&&Kt.push({label:c($a),type:"Group"},...kf)}else(e.remote||Nt($a))&&Kt.push($a);return Kt},[])},Te=()=>{X.value=De(g.inputValue)},Re=D(()=>{const pe=new Map;return V.value.forEach((ct,Nt)=>{pe.set(qe(d(ct)),{option:ct,index:Nt})}),pe}),Ce=D(()=>{const pe=new Map;return X.value.forEach((ct,Nt)=>{pe.set(qe(d(ct)),{option:ct,index:Nt})}),pe}),Le=D(()=>X.value.every(pe=>v(pe))),me=ca(),Be=D(()=>me.value==="small"?"small":"default"),ze=()=>{var ct;if(Je(e.fitInputWidth)){y.value=e.fitInputWidth;return}const pe=((ct=_.value)==null?void 0:ct.offsetWidth)||200;!e.fitInputWidth&&Y.value?He(()=>{y.value=Math.max(pe,xe())}):y.value=pe},xe=()=>{var Ir,kf;const pe=document.createElement("canvas").getContext("2d"),ct=r.be("dropdown","item"),Nt=(((kf=(Ir=A.value)==null?void 0:Ir.listRef)==null?void 0:kf.innerRef)||document).querySelector(`.${ct}`);if(Nt===null||pe===null)return 0;const Kt=getComputedStyle(Nt),$a=Number.parseFloat(Kt.paddingLeft)+Number.parseFloat(Kt.paddingRight);return pe.font=`bold ${Kt.font.replace(new RegExp(`\\b${Kt.fontWeight}\\b`),"")}`,X.value.reduce((iV,lV)=>{const sV=pe.measureText(c(lV));return Math.max(sV.width,iV)},0)+$a},rt=()=>{if(!w.value)return 0;const pe=window.getComputedStyle(w.value);return Number.parseFloat(pe.gap||"6px")},nt=D(()=>{const pe=rt(),ct=e.filterable?pe+11:0;return{maxWidth:`${P.value&&e.maxCollapseTags===1?g.selectionWidth-g.collapseItemWidth-pe-ct:g.selectionWidth-ct}px`}}),Pt=D(()=>({maxWidth:`${g.selectionWidth}px`})),Wt=D(()=>Ae(e.modelValue)?e.modelValue.length===0&&!g.inputValue:e.filterable?!g.inputValue:!0),Jt=D(()=>{const pe=e.placeholder??n("el.select.placeholder");return e.multiple||!le.value?pe:g.selectedLabel}),Rt=D(()=>{var pe,ct;return(ct=(pe=S.value)==null?void 0:pe.popperRef)==null?void 0:ct.contentRef}),Qt=D(()=>{if(e.multiple){const pe=e.modelValue.length;if(pe>0&&Ce.value.has(e.modelValue[pe-1])){const{index:ct}=Ce.value.get(e.modelValue[pe-1]);return ct}}else if(!m(e.modelValue)&&Ce.value.has(e.modelValue)){const{index:pe}=Ce.value.get(e.modelValue);return pe}return-1}),_e=D({get(){return ee.value&&(e.loading||!se.value||e.remote&&!!a.empty)&&(!b.value||!_r(g.previousQuery)||Y.value)},set(pe){ee.value=pe}}),Ne=D(()=>e.multiple?e.collapseTags?g.cachedOptions.slice(0,e.maxCollapseTags):g.cachedOptions:[]),tt=D(()=>e.multiple?e.collapseTags?g.cachedOptions.slice(e.maxCollapseTags):[]:[]),{createNewOption:Me,removeNewOption:Ue,selectNewOption:Gt,clearAllNewOption:je}=u2e(e,g),ft=pe=>{var ct;z.value||e.filterable&&ee.value&&pe&&!((ct=E.value)!=null&&ct.contains(pe.target))||(g.menuVisibleOnFocus?g.menuVisibleOnFocus=!1:ee.value=!ee.value)},ht=()=>{g.inputValue.length>0&&!ee.value&&(ee.value=!0),Me(g.inputValue),He(()=>{It(g.inputValue)})},pt=Xh(()=>{ht(),b.value=!1},oe),It=pe=>{g.previousQuery===pe||R.value||(g.previousQuery=pe,e.filterable&&Qe(e.filterMethod)?e.filterMethod(pe):e.filterable&&e.remote&&Qe(e.remoteMethod)&&e.remoteMethod(pe),e.defaultFirstOption&&(e.filterable||e.remote)&&X.value.length?He(Ct):He(Bt))},Ct=()=>{const pe=X.value.filter(Kt=>!Kt.disabled&&Kt.type!=="Group"),ct=pe.find(Kt=>Kt.created),Nt=pe[0];g.hoveringIndex=Fe(X.value,ct||Nt)},$t=pe=>{Rn(e.modelValue,pe)||t(Ot,pe)},Sn=pe=>{t(bt,pe),$t(pe),g.previousValue=e.multiple?String(pe):pe,He(()=>{if(e.multiple&&Ae(e.modelValue)){const ct=g.cachedOptions.slice(),Nt=e.modelValue.map(Kt=>Gl(Kt,ct));Rn(g.cachedOptions,Nt)||(g.cachedOptions=Nt)}else Yl(!0)})},Fe=(pe=[],ct)=>{if(!_t(ct))return pe.indexOf(ct);const Nt=e.valueKey;let Kt=-1;return pe.some(($a,Ir)=>na($a,Nt)===na(ct,Nt)?(Kt=Ir,!0):!1),Kt},qe=pe=>_t(pe)?na(pe,e.valueKey):pe,ut=()=>{ze()},Ie=pe=>{t("end-reached",pe)},Ke=()=>{g.selectionWidth=Number.parseFloat(window.getComputedStyle(w.value).width)},Ge=()=>{g.collapseItemWidth=P.value.getBoundingClientRect().width},Ee=()=>{var pe,ct;(ct=(pe=S.value)==null?void 0:pe.updatePopper)==null||ct.call(pe)},de=()=>{var pe,ct;(ct=(pe=k.value)==null?void 0:pe.updatePopper)==null||ct.call(pe)},it=pe=>{const ct=d(pe);if(e.multiple){let Nt=e.modelValue.slice();const Kt=Fe(Nt,ct);Kt>-1?(Nt=[...Nt.slice(0,Kt),...Nt.slice(Kt+1)],g.cachedOptions.splice(Kt,1),Ue(pe)):(e.multipleLimit<=0||Nt.length{let Nt=e.modelValue.slice();const Kt=Fe(Nt,d(ct));Kt>-1&&!z.value&&(Nt=[...e.modelValue.slice(0,Kt),...e.modelValue.slice(Kt+1)],g.cachedOptions.splice(Kt,1),Sn(Nt),t("remove-tag",d(ct)),Ue(ct)),pe.stopPropagation(),sn()},sn=()=>{var pe;(pe=T.value)==null||pe.focus()},gn=()=>{var pe;if(ee.value){ee.value=!1,He(()=>{var ct;return(ct=T.value)==null?void 0:ct.blur()});return}(pe=T.value)==null||pe.blur()},Aa=()=>{g.inputValue.length>0?g.inputValue="":ee.value=!1},jr=pe=>wA(pe,ct=>!g.cachedOptions.some(Nt=>d(Nt)===ct&&v(Nt))),vr=pe=>{const ct=mn(pe);if(e.multiple&&ct!==Pe.delete&&g.inputValue.length===0){pe.preventDefault();const Nt=e.modelValue.slice(),Kt=jr(Nt);if(Kt<0)return;const $a=Nt[Kt];Nt.splice(Kt,1);const Ir=g.cachedOptions[Kt];g.cachedOptions.splice(Kt,1),Ue(Ir),Sn(Nt),t("remove-tag",$a)}},Ia=()=>{let pe;Ae(e.modelValue)?pe=[]:pe=h.value,g.selectedLabel="",ee.value=!1,Sn(pe),t("clear"),je(),sn()},Oa=(pe,ct=void 0)=>{const Nt=X.value;if(!["forward","backward"].includes(pe)||z.value||Nt.length<=0||Le.value||R.value)return;if(!ee.value)return ft();zt(ct)&&(ct=g.hoveringIndex);let Kt=-1;pe==="forward"?(Kt=ct+1,Kt>=Nt.length&&(Kt=0)):pe==="backward"&&(Kt=ct-1,(Kt<0||Kt>=Nt.length)&&(Kt=Nt.length-1));const $a=Nt[Kt];if(v($a)||$a.type==="Group")return Oa(pe,Kt);g.hoveringIndex=Kt,ja(Kt)},Oe=()=>{if(ee.value)~g.hoveringIndex&&X.value[g.hoveringIndex]&&it(X.value[g.hoveringIndex]);else return ft()},lt=pe=>{g.hoveringIndex=pe??-1},Bt=()=>{if(!e.multiple)g.hoveringIndex=X.value.findIndex(pe=>qe(d(pe))===qe(e.modelValue));else{const pe=e.modelValue.length;if(pe>0){const ct=e.modelValue[pe-1];g.hoveringIndex=X.value.findIndex(Nt=>qe(ct)===qe(d(Nt)))}else g.hoveringIndex=-1}},_n=pe=>{if(g.inputValue=pe.target.value,e.remote)b.value=!0,pt();else return ht()},Yn=pe=>{ee.value=!1,W.value&&N(new FocusEvent("blur",pe))},Zn=()=>(g.isBeforeHide=!1,He(()=>{~Qt.value&&ja(Qt.value)})),ja=pe=>{A.value.scrollToItem(pe)},Gl=(pe,ct)=>{const Nt=qe(pe);if(Re.value.has(Nt)){const{option:Kt}=Re.value.get(Nt);return Kt}if(ct&&ct.length){const Kt=ct.find($a=>qe(d($a))===Nt);if(Kt)return Kt}return{[u.value.value]:pe,[u.value.label]:pe}},co=pe=>{var ct;return((ct=Re.value.get(d(pe)))==null?void 0:ct.index)??-1},Yl=(pe=!1)=>{if(e.multiple)if(e.modelValue.length>0){const ct=g.cachedOptions.slice();g.cachedOptions.length=0,g.previousValue=e.modelValue.toString();for(const Nt of e.modelValue){const Kt=Gl(Nt,ct);g.cachedOptions.push(Kt)}}else g.cachedOptions=[],g.previousValue=void 0;else if(le.value){g.previousValue=e.modelValue;const ct=X.value,Nt=ct.findIndex(Kt=>qe(d(Kt))===qe(e.modelValue));~Nt?g.selectedLabel=c(ct[Nt]):(!g.selectedLabel||pe)&&(g.selectedLabel=qe(e.modelValue))}else g.selectedLabel="",g.previousValue=void 0;je(),ze()};ye(()=>e.fitInputWidth,()=>{ze()}),ye(ee,pe=>{pe?(e.persistent||ze(),It("")):(g.inputValue="",g.previousQuery=null,g.isBeforeHide=!0,g.menuVisibleOnFocus=!1,Me(""))}),ye(()=>e.modelValue,(pe,ct)=>{var Nt;(!pe||Ae(pe)&&pe.length===0||e.multiple&&!Rn(pe.toString(),g.previousValue)||!e.multiple&&qe(pe)!==qe(g.previousValue))&&Yl(!0),!Rn(pe,ct)&&e.validateEvent&&((Nt=l==null?void 0:l.validate)==null||Nt.call(l,"change").catch(kt))},{deep:!0}),ye(()=>e.options,()=>{const pe=T.value;(!pe||pe&&document.activeElement!==pe)&&Yl()},{deep:!0,flush:"post"}),ye(()=>X.value,()=>(ze(),A.value&&He(A.value.resetScrollTop))),ur(()=>{g.isBeforeHide||Te()}),ur(()=>{const{valueKey:pe,options:ct}=e,Nt=new Map;for(const Kt of ct){const $a=d(Kt);let Ir=$a;if(_t(Ir)&&(Ir=na($a,pe)),Nt.get(Ir)){un("ElSelectV2","The option values you provided seem to be duplicated, which may cause some problems, please check.");break}else Nt.set(Ir,!0)}}),mt(()=>{Yl()}),Mn(_,ut),Mn(w,Ke),Mn(B,Ee),Mn(I,de),Mn(P,Ge);let Xs;return ye(()=>_e.value,pe=>{pe?Xs=Mn(A,Ee).stop:(Xs==null||Xs(),Xs=void 0),t("visible-change",pe)}),{inputId:s,collapseTagSize:Be,currentPlaceholder:Jt,expanded:ee,emptyText:fe,popupHeight:ie,debounce:oe,allOptions:V,allOptionsValueMap:Re,filteredOptions:X,iconComponent:he,iconReverse:J,tagStyle:nt,collapseTagStyle:Pt,popperSize:y,dropdownMenuVisible:_e,hasModelValue:le,shouldShowPlaceholder:Wt,selectDisabled:z,selectSize:me,needStatusIcon:re,showClearBtn:ve,states:g,isFocused:W,nsSelect:r,nsInput:o,inputRef:T,menuRef:A,tagMenuRef:I,tooltipRef:S,tagTooltipRef:k,selectRef:_,wrapperRef:B,selectionRef:w,prefixRef:M,suffixRef:E,collapseItemRef:P,popperRef:Rt,validateState:Z,validateIcon:j,showTagList:Ne,collapseTagList:tt,debouncedOnInputChange:pt,deleteTag:Dt,getLabel:c,getValue:d,getDisabled:v,getValueKey:qe,getIndex:co,handleClear:Ia,handleClickOutside:Yn,handleDel:vr,handleEsc:Aa,focus:sn,blur:gn,handleMenuEnter:Zn,handleResize:ut,resetSelectionWidth:Ke,updateTooltip:Ee,updateTagTooltip:de,updateOptions:Te,toggleMenu:ft,scrollTo:ja,onInput:_n,onKeyboardNavigate:Oa,onKeyboardSelect:Oe,onEndReached:Ie,onSelect:it,onHover:lt,handleCompositionStart:G,handleCompositionEnd:K,handleCompositionUpdate:H}};var d2e=q({name:"ElSelectV2",components:{ElSelectMenu:s2e,ElTag:Cl,ElTooltip:Ma,ElIcon:Xe},directives:{ClickOutside:Ns},props:X1e,emits:J1e,setup(e,{emit:t}){const n=D(()=>{const{modelValue:s,multiple:u}=e,c=u?[]:void 0;return Ae(s)?u?s:c:u?c:s}),a=c2e(nn({...Ta(e),modelValue:n}),t),{calculatorRef:r,inputStyle:o}=w6(),i=Fa();Ft(Tw,{props:nn({...Ta(e),height:a.popupHeight,modelValue:n}),expanded:a.expanded,tooltipRef:a.tooltipRef,contentId:i,onSelect:a.onSelect,onHover:a.onHover,onKeyboardNavigate:a.onKeyboardNavigate,onKeyboardSelect:a.onKeyboardSelect});const l=D(()=>e.multiple?a.states.cachedOptions.map(s=>a.getLabel(s)):a.states.selectedLabel);return{...a,modelValue:n,selectedLabel:l,calculatorRef:r,inputStyle:o,contentId:i,BORDER_HORIZONTAL_WIDTH:2}}});const f2e=["id","value","autocomplete","tabindex","aria-expanded","aria-label","disabled","aria-controls","aria-activedescendant","readonly","name"],v2e=["textContent"],h2e={key:1};function p2e(e,t,n,a,r,o){const i=be("el-tag"),l=be("el-tooltip"),s=be("el-icon"),u=be("el-select-menu"),c=Gr("click-outside");return st((x(),O("div",{ref:"selectRef",class:U([e.nsSelect.b(),e.nsSelect.m(e.selectSize)]),onMouseenter:t[15]||(t[15]=d=>e.states.inputHovering=!0),onMouseleave:t[16]||(t[16]=d=>e.states.inputHovering=!1)},[L(l,{ref:"tooltipRef",visible:e.dropdownMenuVisible,teleported:e.teleported,"popper-class":[e.nsSelect.e("popper"),e.popperClass],"popper-style":e.popperStyle,"gpu-acceleration":!1,"stop-popper-mouse-event":!1,"popper-options":e.popperOptions,"fallback-placements":e.fallbackPlacements,effect:e.effect,placement:e.placement,pure:"",transition:`${e.nsSelect.namespace.value}-zoom-in-top`,trigger:"click",persistent:e.persistent,"append-to":e.appendTo,"show-arrow":e.showArrow,offset:e.offset,onBeforeShow:e.handleMenuEnter,onHide:t[14]||(t[14]=d=>e.states.isBeforeHide=!1)},{default:$(()=>{var d;return[C("div",{ref:"wrapperRef",class:U([e.nsSelect.e("wrapper"),e.nsSelect.is("focused",e.isFocused),e.nsSelect.is("hovering",e.states.inputHovering),e.nsSelect.is("filterable",e.filterable),e.nsSelect.is("disabled",e.selectDisabled)]),onClick:t[11]||(t[11]=at((...v)=>e.toggleMenu&&e.toggleMenu(...v),["prevent"]))},[e.$slots.prefix?(x(),O("div",{key:0,ref:"prefixRef",class:U(e.nsSelect.e("prefix"))},[ce(e.$slots,"prefix")],2)):te("v-if",!0),C("div",{ref:"selectionRef",class:U([e.nsSelect.e("selection"),e.nsSelect.is("near",e.multiple&&!e.$slots.prefix&&!!e.modelValue.length)])},[e.multiple?ce(e.$slots,"tag",{key:0,data:e.states.cachedOptions,deleteTag:e.deleteTag,selectDisabled:e.selectDisabled},()=>{var v,f,h,m,g,y,b,_,w,S,k,T,M;return[(x(!0),O(Ve,null,dt(e.showTagList,E=>(x(),O("div",{key:e.getValueKey(e.getValue(E)),class:U(e.nsSelect.e("selected-item"))},[L(i,{closable:!e.selectDisabled&&!e.getDisabled(E),size:e.collapseTagSize,type:e.tagType,effect:e.tagEffect,"disable-transitions":"",style:et(e.tagStyle),onClose:A=>e.deleteTag(A,E)},{default:$(()=>[C("span",{class:U(e.nsSelect.e("tags-text"))},[ce(e.$slots,"label",{index:e.getIndex(E),label:e.getLabel(E),value:e.getValue(E)},()=>[ue(Q(e.getLabel(E)),1)])],2)]),_:2},1032,["closable","size","type","effect","style","onClose"])],2))),128)),e.collapseTags&&e.states.cachedOptions.length>e.maxCollapseTags?(x(),ne(l,{key:0,ref:"tagTooltipRef",disabled:e.dropdownMenuVisible||!e.collapseTagsTooltip,"fallback-placements":((v=e.tagTooltip)==null?void 0:v.fallbackPlacements)??["bottom","top","right","left"],effect:((f=e.tagTooltip)==null?void 0:f.effect)??e.effect,placement:((h=e.tagTooltip)==null?void 0:h.placement)??"bottom","popper-class":((m=e.tagTooltip)==null?void 0:m.popperClass)??e.popperClass,"popper-style":((g=e.tagTooltip)==null?void 0:g.popperStyle)??e.popperStyle,teleported:((y=e.tagTooltip)==null?void 0:y.teleported)??e.teleported,"append-to":((b=e.tagTooltip)==null?void 0:b.appendTo)??e.appendTo,"popper-options":((_=e.tagTooltip)==null?void 0:_.popperOptions)??e.popperOptions,transition:(w=e.tagTooltip)==null?void 0:w.transition,"show-after":(S=e.tagTooltip)==null?void 0:S.showAfter,"hide-after":(k=e.tagTooltip)==null?void 0:k.hideAfter,"auto-close":(T=e.tagTooltip)==null?void 0:T.autoClose,offset:(M=e.tagTooltip)==null?void 0:M.offset},{default:$(()=>[C("div",{ref:"collapseItemRef",class:U(e.nsSelect.e("selected-item"))},[L(i,{closable:!1,size:e.collapseTagSize,type:e.tagType,effect:e.tagEffect,style:et(e.collapseTagStyle),"disable-transitions":""},{default:$(()=>[C("span",{class:U(e.nsSelect.e("tags-text"))}," + "+Q(e.states.cachedOptions.length-e.maxCollapseTags),3)]),_:1},8,["size","type","effect","style"])],2)]),content:$(()=>[C("div",{ref:"tagMenuRef",class:U(e.nsSelect.e("selection"))},[(x(!0),O(Ve,null,dt(e.collapseTagList,E=>(x(),O("div",{key:e.getValueKey(e.getValue(E)),class:U(e.nsSelect.e("selected-item"))},[L(i,{class:"in-tooltip",closable:!e.selectDisabled&&!e.getDisabled(E),size:e.collapseTagSize,type:e.tagType,effect:e.tagEffect,"disable-transitions":"",onClose:A=>e.deleteTag(A,E)},{default:$(()=>[C("span",{class:U(e.nsSelect.e("tags-text"))},[ce(e.$slots,"label",{index:e.getIndex(E),label:e.getLabel(E),value:e.getValue(E)},()=>[ue(Q(e.getLabel(E)),1)])],2)]),_:2},1032,["closable","size","type","effect","onClose"])],2))),128))],2)]),_:3},8,["disabled","fallback-placements","effect","placement","popper-class","popper-style","teleported","append-to","popper-options","transition","show-after","hide-after","auto-close","offset"])):te("v-if",!0)]}):te("v-if",!0),C("div",{class:U([e.nsSelect.e("selected-item"),e.nsSelect.e("input-wrapper"),e.nsSelect.is("hidden",!e.filterable||e.selectDisabled||e.multiple&&!e.states.inputValue&&!e.isFocused)])},[C("input",{id:e.inputId,ref:"inputRef",value:e.states.inputValue,style:et(e.inputStyle),autocomplete:e.autocomplete,tabindex:e.tabindex,"aria-autocomplete":"none","aria-haspopup":"listbox",autocapitalize:"off","aria-expanded":e.expanded,"aria-label":e.ariaLabel,class:U([e.nsSelect.e("input"),e.nsSelect.is(e.selectSize)]),disabled:e.selectDisabled,role:"combobox","aria-controls":e.contentId,"aria-activedescendant":e.states.hoveringIndex>=0?`${e.contentId}-${e.states.hoveringIndex}`:"",readonly:!e.filterable,spellcheck:"false",type:"text",name:e.name,onInput:t[0]||(t[0]=(...v)=>e.onInput&&e.onInput(...v)),onChange:t[1]||(t[1]=at(()=>{},["stop"])),onCompositionstart:t[2]||(t[2]=(...v)=>e.handleCompositionStart&&e.handleCompositionStart(...v)),onCompositionupdate:t[3]||(t[3]=(...v)=>e.handleCompositionUpdate&&e.handleCompositionUpdate(...v)),onCompositionend:t[4]||(t[4]=(...v)=>e.handleCompositionEnd&&e.handleCompositionEnd(...v)),onKeydown:[t[5]||(t[5]=kn(at(v=>e.onKeyboardNavigate("backward"),["stop","prevent"]),["up"])),t[6]||(t[6]=kn(at(v=>e.onKeyboardNavigate("forward"),["stop","prevent"]),["down"])),t[7]||(t[7]=kn(at((...v)=>e.onKeyboardSelect&&e.onKeyboardSelect(...v),["stop","prevent"]),["enter"])),t[8]||(t[8]=kn(at((...v)=>e.handleEsc&&e.handleEsc(...v),["stop","prevent"]),["esc"])),t[9]||(t[9]=kn(at((...v)=>e.handleDel&&e.handleDel(...v),["stop"]),["delete"]))],onClick:t[10]||(t[10]=at((...v)=>e.toggleMenu&&e.toggleMenu(...v),["stop"]))},null,46,f2e),e.filterable?(x(),O("span",{key:0,ref:"calculatorRef","aria-hidden":"true",class:U(e.nsSelect.e("input-calculator")),textContent:Q(e.states.inputValue)},null,10,v2e)):te("v-if",!0)],2),e.shouldShowPlaceholder?(x(),O("div",{key:1,class:U([e.nsSelect.e("selected-item"),e.nsSelect.e("placeholder"),e.nsSelect.is("transparent",!e.hasModelValue||e.expanded&&!e.states.inputValue)])},[e.hasModelValue?ce(e.$slots,"label",{key:0,index:((d=e.allOptionsValueMap.get(e.modelValue))==null?void 0:d.index)??-1,label:e.currentPlaceholder,value:e.modelValue},()=>[C("span",null,Q(e.currentPlaceholder),1)]):(x(),O("span",h2e,Q(e.currentPlaceholder),1))],2)):te("v-if",!0)],2),C("div",{ref:"suffixRef",class:U(e.nsSelect.e("suffix"))},[e.iconComponent?st((x(),ne(s,{key:0,class:U([e.nsSelect.e("caret"),e.nsInput.e("icon"),e.iconReverse])},{default:$(()=>[(x(),ne(St(e.iconComponent)))]),_:1},8,["class"])),[[Zt,!e.showClearBtn]]):te("v-if",!0),e.showClearBtn&&e.clearIcon?(x(),ne(s,{key:1,class:U([e.nsSelect.e("caret"),e.nsInput.e("icon"),e.nsSelect.e("clear")]),onClick:at(e.handleClear,["prevent","stop"])},{default:$(()=>[(x(),ne(St(e.clearIcon)))]),_:1},8,["class","onClick"])):te("v-if",!0),e.validateState&&e.validateIcon&&e.needStatusIcon?(x(),ne(s,{key:2,class:U([e.nsInput.e("icon"),e.nsInput.e("validateIcon"),e.nsInput.is("loading",e.validateState==="validating")])},{default:$(()=>[(x(),ne(St(e.validateIcon)))]),_:1},8,["class"])):te("v-if",!0)],2)],2)]}),content:$(()=>[L(u,{id:e.contentId,ref:"menuRef",data:e.filteredOptions,width:e.popperSize-e.BORDER_HORIZONTAL_WIDTH,"hovering-index":e.states.hoveringIndex,"scrollbar-always-on":e.scrollbarAlwaysOn,"aria-label":e.ariaLabel,onEndReached:e.onEndReached},xr({default:$(d=>[ce(e.$slots,"default",bl(wl(d)))]),_:2},[e.$slots.header?{name:"header",fn:$(()=>[C("div",{class:U(e.nsSelect.be("dropdown","header")),onClick:t[12]||(t[12]=at(()=>{},["stop"]))},[ce(e.$slots,"header")],2)]),key:"0"}:void 0,e.$slots.loading&&e.loading?{name:"loading",fn:$(()=>[C("div",{class:U(e.nsSelect.be("dropdown","loading"))},[ce(e.$slots,"loading")],2)]),key:"1"}:e.loading||e.filteredOptions.length===0?{name:"empty",fn:$(()=>[C("div",{class:U(e.nsSelect.be("dropdown","empty"))},[ce(e.$slots,"empty",{},()=>[C("span",null,Q(e.emptyText),1)])],2)]),key:"2"}:void 0,e.$slots.footer?{name:"footer",fn:$(()=>[C("div",{class:U(e.nsSelect.be("dropdown","footer")),onClick:t[13]||(t[13]=at(()=>{},["stop"]))},[ce(e.$slots,"footer")],2)]),key:"3"}:void 0]),1032,["id","data","width","hovering-index","scrollbar-always-on","aria-label","onEndReached"])]),_:3},8,["visible","teleported","popper-class","popper-style","popper-options","fallback-placements","effect","placement","transition","persistent","append-to","show-arrow","offset","onBeforeShow"])],34)),[[c,e.handleClickOutside,e.popperRef]])}var m2e=ya(d2e,[["render",p2e]]);const g2e=wt(m2e),y2e=$e({animated:Boolean,count:{type:Number,default:1},rows:{type:Number,default:3},loading:{type:Boolean,default:!0},throttle:{type:ae([Number,Object])}}),b2e=$e({variant:{type:String,values:["circle","rect","h1","h3","text","caption","p","image","button"],default:"text"}});var _2e=q({name:"ElSkeletonItem",__name:"skeleton-item",props:b2e,setup(e){const t=ke("skeleton");return(n,a)=>(x(),O("div",{class:U([p(t).e("item"),p(t).e(e.variant)])},[e.variant==="image"?(x(),ne(p(RI),{key:0})):te("v-if",!0)],2))}}),hg=_2e,w2e=q({name:"ElSkeleton",__name:"skeleton",props:y2e,setup(e,{expose:t}){const n=e,a=ke("skeleton"),r=Qj(cn(n,"loading"),n.throttle);return t({uiLoading:r}),(o,i)=>p(r)?(x(),O("div",Tt({key:0,class:[p(a).b(),p(a).is("animated",e.animated)]},o.$attrs),[(x(!0),O(Ve,null,dt(e.count,l=>(x(),O(Ve,{key:l},[p(r)?ce(o.$slots,"template",{key:l},()=>[L(hg,{class:U(p(a).is("first")),variant:"p"},null,8,["class"]),(x(!0),O(Ve,null,dt(e.rows,s=>(x(),ne(hg,{key:s,class:U([p(a).e("paragraph"),p(a).is("last",s===e.rows&&e.rows>1)]),variant:"p"},null,8,["class"]))),128))]):te("v-if",!0)],64))),128))],16)):ce(o.$slots,"default",bl(Tt({key:1},o.$attrs)))}}),S2e=w2e;const C2e=wt(S2e,{SkeletonItem:hg}),x2e=On(hg),bD=Symbol("sliderContextKey"),k2e=$e({modelValue:{type:ae([Number,Array]),default:0},id:{type:String,default:void 0},min:{type:Number,default:0},max:{type:Number,default:100},step:{type:ae([Number,String]),default:1},showInput:Boolean,showInputControls:{type:Boolean,default:!0},size:pa,inputSize:pa,showStops:Boolean,showTooltip:{type:Boolean,default:!0},formatTooltip:{type:ae(Function),default:void 0},disabled:{type:Boolean,default:void 0},range:Boolean,vertical:Boolean,height:String,rangeStartLabel:{type:String,default:void 0},rangeEndLabel:{type:String,default:void 0},formatValueText:{type:ae(Function),default:void 0},tooltipClass:{type:ae([String,Array,Object,Boolean]),default:void 0},placement:{type:String,values:tl,default:"top"},marks:{type:ae(Object)},validateEvent:{type:Boolean,default:!0},persistent:{type:Boolean,default:!0},...dr(["ariaLabel"])}),cy=e=>Je(e)||Ae(e)&&e.every(Je),T2e={[bt]:cy,[ua]:cy,[Ot]:cy},M2e=(e,t,n)=>{const a=F();return mt(async()=>{e.range?(Ae(e.modelValue)?(t.firstValue=Math.max(e.min,e.modelValue[0]),t.secondValue=Math.min(e.max,e.modelValue[1])):(t.firstValue=e.min,t.secondValue=e.max),t.oldValue=[t.firstValue,t.secondValue]):(!Je(e.modelValue)||Number.isNaN(e.modelValue)?t.firstValue=e.min:t.firstValue=Math.min(e.max,Math.max(e.min,e.modelValue)),t.oldValue=t.firstValue),on(window,"resize",n),await He(),n()}),{sliderWrapper:a}},E2e=e=>{const t=D(()=>e.marks?Object.keys(e.marks).map(Number.parseFloat).sort((n,a)=>n-a).filter(n=>n<=e.max&&n>=e.min).map(n=>({point:n,position:(n-e.min)*100/(e.max-e.min),mark:e.marks[n]})):[]);return ur(()=>{if(e.step==="mark"&&!e.marks&&un("ElSlider","marks prop must be provided when step is mark"),e.marks){const n=Object.keys(e.marks),a=t.value.map(o=>o.point),r=n.filter(o=>{const i=Number.parseFloat(o);return Number.isNaN(i)||!a.includes(i)});r.length>0&&un("ElSlider",`Some marks keys are invalid (not a number or out of [min, max]): [${r.map(o=>`'${o}'`).join(", ")}] and will be ignored.`)}}),t},A2e=(e,t,n)=>{const{formItem:a}=Ea(),r=Ht(),o=F(),i=F(),l={firstButton:o,secondButton:i},s=$n(),u=D(()=>Math.min(t.firstValue,t.secondValue)),c=D(()=>Math.max(t.firstValue,t.secondValue)),d=D(()=>e.range?`${100*(c.value-u.value)/(e.max-e.min)}%`:`${100*(t.firstValue-e.min)/(e.max-e.min)}%`),v=D(()=>e.range?`${100*(u.value-e.min)/(e.max-e.min)}%`:"0%"),f=D(()=>e.vertical?{height:e.height}:{}),h=D(()=>e.vertical?{height:d.value,bottom:v.value}:{width:d.value,left:v.value}),m=()=>{r.value&&(t.sliderSize=r.value.getBoundingClientRect()[e.vertical?"height":"width"])},g=I=>{const P=e.min+I*(e.max-e.min)/100;if(!e.range)return o;let R;return Math.abs(u.value-P)t.secondValue?"firstButton":"secondButton",l[R]},y=I=>{const P=g(I);return P.value.setPosition(I),P},b=I=>{t.firstValue=I??e.min,w(e.range?[u.value,c.value]:I??e.min)},_=I=>{t.secondValue=I,e.range&&w([u.value,c.value])},w=I=>{n(bt,I),n(ua,I)},S=async()=>{await He(),n(Ot,e.range?[u.value,c.value]:e.modelValue)},k=I=>{var R,G,K,H;if(s.value||t.dragging)return;m();let P=0;if(e.vertical){const z=((G=(R=I.touches)==null?void 0:R.item(0))==null?void 0:G.clientY)??I.clientY;P=(r.value.getBoundingClientRect().bottom-z)/t.sliderSize*100}else P=((((H=(K=I.touches)==null?void 0:K.item(0))==null?void 0:H.clientX)??I.clientX)-r.value.getBoundingClientRect().left)/t.sliderSize*100;if(!(P<0||P>100))return y(P)};return{elFormItem:a,slider:r,firstButton:o,secondButton:i,sliderDisabled:s,minValue:u,maxValue:c,runwayStyle:f,barStyle:h,resetSize:m,setPosition:y,emitChange:S,onSliderWrapperPrevent:I=>{var P,R;((P=l.firstButton.value)!=null&&P.dragging||(R=l.secondButton.value)!=null&&R.dragging)&&I.preventDefault()},onSliderClick:I=>{k(I)&&S()},onSliderDown:async I=>{const P=k(I);P&&(await He(),P.value.onButtonDown(I))},onSliderMarkerDown:I=>{s.value||t.dragging||y(I)&&S()},setFirstValue:b,setSecondValue:_}},I2e=(e,t,n,a)=>({stops:D(()=>{if(!e.showStops||e.min>e.max)return[];if(e.step==="mark"||e.step===0)return e.step===0&&un("ElSlider","step should not be 0."),[];const i=Math.ceil((e.max-e.min)/e.step),l=100*e.step/(e.max-e.min),s=Array.from({length:i-1}).map((u,c)=>(c+1)*l);return e.range?s.filter(u=>u<100*(n.value-e.min)/(e.max-e.min)||u>100*(a.value-e.min)/(e.max-e.min)):s.filter(u=>u>100*(t.firstValue-e.min)/(e.max-e.min))}),getStopStyle:i=>e.vertical?{bottom:`${i}%`}:{left:`${i}%`}}),L2e=(e,t,n,a,r,o)=>{const i=u=>{r(bt,u),r(ua,u)},l=()=>e.range?![n.value,a.value].every((u,c)=>u===t.oldValue[c]):e.modelValue!==t.oldValue,s=()=>{var c,d;e.min>e.max&&Dn("Slider","min should not be greater than max.");const u=e.modelValue;e.range&&Ae(u)?u[1]e.max?i([e.max,e.max]):u[0]e.max?i([u[0],e.max]):(t.firstValue=u[0],t.secondValue=u[1],l()&&(e.validateEvent&&((c=o==null?void 0:o.validate)==null||c.call(o,"change").catch(kt)),t.oldValue=u.slice())):!e.range&&Je(u)&&!Number.isNaN(u)&&(ue.max?i(e.max):(t.firstValue=u,l()&&(e.validateEvent&&((d=o==null?void 0:o.validate)==null||d.call(o,"change").catch(kt)),t.oldValue=u)))};s(),ye(()=>t.dragging,u=>{u||s()}),ye(()=>e.modelValue,(u,c)=>{t.dragging||Ae(u)&&Ae(c)&&u.every((d,v)=>d===c[v])&&t.firstValue===u[0]&&t.secondValue===u[1]||s()},{deep:!0}),ye(()=>[e.min,e.max],()=>{s()})},P2e=(e,t,n)=>{const a=F(),r=F(!1),o=D(()=>t.value instanceof Function);return{tooltip:a,tooltipVisible:r,formatValue:D(()=>o.value&&t.value(e.modelValue)||e.modelValue),displayTooltip:Yi(()=>{n.value&&(r.value=!0)},50),hideTooltip:Yi(()=>{n.value&&(r.value=!1)},50)}},D2e=(e,t,n)=>{const{disabled:a,min:r,max:o,step:i,showTooltip:l,persistent:s,precision:u,sliderSize:c,formatTooltip:d,emitChange:v,resetSize:f,updateDragging:h,markList:m}=Ze(bD),{tooltip:g,tooltipVisible:y,formatValue:b,displayTooltip:_,hideTooltip:w}=P2e(e,d,l),S=F(),k=D(()=>`${(e.modelValue-r.value)/(o.value-r.value)*100}%`),T=D(()=>e.vertical?{bottom:k.value}:{left:k.value}),M=D(()=>i.value==="mark"&&m.value.length>0),E=()=>{t.hovering=!0,_()},A=()=>{t.hovering=!1,t.dragging||w()},I=ie=>{a.value||(ie.preventDefault(),Y(ie),window.addEventListener("mousemove",X),window.addEventListener("touchmove",X),window.addEventListener("mouseup",ee),window.addEventListener("touchend",ee),window.addEventListener("contextmenu",ee),S.value.focus())},P=ie=>{a.value||(t.newPosition=Number.parseFloat(k.value)+ie/(o.value-r.value)*100,re(t.newPosition),v())},R=ie=>{if(a.value||!m.value.length)return;const le=e.modelValue,ve=Number.EPSILON,he=Math.abs(ie);let J;if(ie>0){const Z=m.value.findIndex(j=>j.point>le+ve);if(Z!==-1){const j=Math.min(Z+he-1,m.value.length-1);J=m.value[j].point}}else{let Z=-1;for(let j=m.value.length-1;j>=0;j--)if(m.value[j].point{M.value?R(-1):Je(i.value)&&P(-i.value)},K=()=>{M.value?R(1):Je(i.value)&&P(i.value)},H=()=>{M.value?R(-4):Je(i.value)&&P(-i.value*4)},z=()=>{M.value?R(4):Je(i.value)&&P(i.value*4)},B=()=>{a.value||(re(0),v())},W=()=>{a.value||(re(100),v())},N=ie=>{const le=mn(ie);let ve=!0;switch(le){case Pe.left:case Pe.down:G();break;case Pe.right:case Pe.up:K();break;case Pe.home:B();break;case Pe.end:W();break;case Pe.pageDown:H();break;case Pe.pageUp:z();break;default:ve=!1;break}ve&&ie.preventDefault()},V=ie=>{let le,ve;return ie.type.startsWith("touch")?(ve=ie.touches[0].clientY,le=ie.touches[0].clientX):(ve=ie.clientY,le=ie.clientX),{clientX:le,clientY:ve}},Y=ie=>{t.dragging=!0,t.isClick=!0;const{clientX:le,clientY:ve}=V(ie);e.vertical?t.startY=ve:t.startX=le,t.startPosition=Number.parseFloat(k.value),t.newPosition=t.startPosition},X=ie=>{if(t.dragging){t.isClick=!1,_(),f();let le;const{clientX:ve,clientY:he}=V(ie);e.vertical?(t.currentY=he,le=(t.startY-t.currentY)/c.value*100):(t.currentX=ve,le=(t.currentX-t.startX)/c.value*100),t.newPosition=t.startPosition+le,re(t.newPosition)}},ee=()=>{t.dragging&&(setTimeout(()=>{t.dragging=!1,t.hovering||w(),t.isClick||re(t.newPosition),v()},0),window.removeEventListener("mousemove",X),window.removeEventListener("touchmove",X),window.removeEventListener("mouseup",ee),window.removeEventListener("touchend",ee),window.removeEventListener("contextmenu",ee))},re=async ie=>{if(ie===null||Number.isNaN(+ie))return;ie=Br(ie,0,100);let le;if(i.value==="mark")m.value.length===0?le=ie<=50?r.value:o.value:le=m.value.reduce((ve,he)=>Math.abs(he.position-ie)t.dragging,ie=>{h(ie)}),on(S,"touchstart",I,{passive:!1}),{disabled:a,button:S,tooltip:g,tooltipVisible:y,showTooltip:l,persistent:s,wrapperStyle:T,formatValue:b,handleMouseEnter:E,handleMouseLeave:A,onButtonDown:I,onKeyDown:N,setPosition:re}},O2e=$e({modelValue:{type:Number,default:0},vertical:Boolean,tooltipClass:{type:ae([String,Array,Object,Boolean]),default:void 0},placement:{type:String,values:tl,default:"top"}}),$2e={[bt]:e=>Je(e)},R2e=["tabindex"];var N2e=q({name:"ElSliderButton",__name:"button",props:O2e,emits:$2e,setup(e,{expose:t,emit:n}){const a=e,r=n,o=ke("slider"),i=nn({hovering:!1,dragging:!1,isClick:!1,startX:0,currentX:0,startY:0,currentY:0,startPosition:0,newPosition:0,oldValue:a.modelValue}),l=D(()=>d.value?v.value:!1),{disabled:s,button:u,tooltip:c,showTooltip:d,persistent:v,tooltipVisible:f,wrapperStyle:h,formatValue:m,handleMouseEnter:g,handleMouseLeave:y,onButtonDown:b,onKeyDown:_,setPosition:w}=D2e(a,i,r),{hovering:S,dragging:k}=Ta(i);return t({onButtonDown:b,onKeyDown:_,setPosition:w,hovering:S,dragging:k}),(T,M)=>(x(),O("div",{ref_key:"button",ref:u,class:U([p(o).e("button-wrapper"),{hover:p(S),dragging:p(k)}]),style:et(p(h)),tabindex:p(s)?void 0:0,onMouseenter:M[0]||(M[0]=(...E)=>p(g)&&p(g)(...E)),onMouseleave:M[1]||(M[1]=(...E)=>p(y)&&p(y)(...E)),onMousedown:M[2]||(M[2]=(...E)=>p(b)&&p(b)(...E)),onFocus:M[3]||(M[3]=(...E)=>p(g)&&p(g)(...E)),onBlur:M[4]||(M[4]=(...E)=>p(y)&&p(y)(...E)),onKeydown:M[5]||(M[5]=(...E)=>p(_)&&p(_)(...E))},[L(p(Ma),{ref_key:"tooltip",ref:c,visible:p(f),placement:T.placement,"fallback-placements":["top","bottom","right","left"],"stop-popper-mouse-event":!1,"popper-class":T.tooltipClass,disabled:!p(d),persistent:l.value},{content:$(()=>[C("span",null,Q(p(m)),1)]),default:$(()=>[C("div",{class:U([p(o).e("button"),{hover:p(S),dragging:p(k)}])},null,2)]),_:1},8,["visible","placement","popper-class","disabled","persistent"])],46,R2e))}}),Lx=N2e;const B2e=$e({mark:{type:ae([String,Object]),default:void 0}});var V2e=q({name:"ElSliderMarker",props:B2e,setup(e){const t=ke("slider"),n=D(()=>Ye(e.mark)?e.mark:e.mark.label),a=D(()=>Ye(e.mark)?void 0:e.mark.style);return()=>ot("div",{class:t.e("marks-text"),style:a.value},n.value)}});const z2e=["id","role","aria-label","aria-labelledby"],F2e={key:1};var H2e=q({name:"ElSlider",__name:"slider",props:k2e,emits:T2e,setup(e,{expose:t,emit:n}){const a=e,r=n,o=ke("slider"),{t:i}=Vt(),l=nn({firstValue:0,secondValue:0,oldValue:0,dragging:!1,sliderSize:1}),{elFormItem:s,slider:u,firstButton:c,secondButton:d,sliderDisabled:v,minValue:f,maxValue:h,runwayStyle:m,barStyle:g,resetSize:y,emitChange:b,onSliderWrapperPrevent:_,onSliderClick:w,onSliderDown:S,onSliderMarkerDown:k,setFirstValue:T,setSecondValue:M}=A2e(a,l,r),{stops:E,getStopStyle:A}=I2e(a,l,f,h),{inputId:I,isLabeledByFormItem:P}=Yr(a,{formItemContext:s}),R=ca(),G=D(()=>a.inputSize||R.value),K=D(()=>a.showInput&&!a.range&&a.step!=="mark"),H=D(()=>a.ariaLabel||i("el.slider.defaultLabel",{min:a.min,max:a.max})),z=D(()=>a.range?a.rangeStartLabel||i("el.slider.defaultRangeStartLabel"):H.value),B=D(()=>a.formatValueText?a.formatValueText(ie.value):`${ie.value}`),W=D(()=>a.rangeEndLabel||i("el.slider.defaultRangeEndLabel")),N=D(()=>a.formatValueText?a.formatValueText(le.value):`${le.value}`),V=D(()=>[o.b(),o.m(R.value),o.is("vertical",a.vertical),{[o.m("with-input")]:K.value}]),Y=E2e(a);L2e(a,l,f,h,r,s);const X=D(()=>Je(a.step)?a.step:1),ee=D(()=>{const J=Je(a.step)?a.step:1,Z=[a.min,a.max,J].map(j=>{const oe=`${j}`.split(".")[1];return oe?oe.length:0});return Math.max.apply(null,Z)}),{sliderWrapper:re}=M2e(a,l,y),{firstValue:ie,secondValue:le,sliderSize:ve}=Ta(l),he=J=>{l.dragging=J};return on(re,"touchstart",_,{passive:!1}),on(re,"touchmove",_,{passive:!1}),Ft(bD,{...Ta(a),sliderSize:ve,disabled:v,precision:ee,markList:Y,emitChange:b,resetSize:y,updateDragging:he}),t({onSliderClick:w}),(J,Z)=>{var j,oe;return x(),O("div",{id:J.range?p(I):void 0,ref_key:"sliderWrapper",ref:re,class:U(V.value),role:J.range?"group":void 0,"aria-label":J.range&&!p(P)?H.value:void 0,"aria-labelledby":J.range&&p(P)?(j=p(s))==null?void 0:j.labelId:void 0},[C("div",{ref_key:"slider",ref:u,class:U([p(o).e("runway"),{"show-input":K.value},p(o).is("disabled",p(v))]),style:et(p(m)),onMousedown:Z[0]||(Z[0]=(...se)=>p(S)&&p(S)(...se)),onTouchstartPassive:Z[1]||(Z[1]=(...se)=>p(S)&&p(S)(...se))},[C("div",{class:U(p(o).e("bar")),style:et(p(g))},null,6),L(Lx,{id:J.range?void 0:p(I),ref_key:"firstButton",ref:c,"model-value":p(ie),vertical:J.vertical,"tooltip-class":J.tooltipClass,placement:J.placement,role:"slider","aria-label":J.range||!p(P)?z.value:void 0,"aria-labelledby":!J.range&&p(P)?(oe=p(s))==null?void 0:oe.labelId:void 0,"aria-valuemin":J.min,"aria-valuemax":J.range?p(le):J.max,"aria-valuenow":p(ie),"aria-valuetext":B.value,"aria-orientation":J.vertical?"vertical":"horizontal","aria-disabled":p(v),"onUpdate:modelValue":p(T)},null,8,["id","model-value","vertical","tooltip-class","placement","aria-label","aria-labelledby","aria-valuemin","aria-valuemax","aria-valuenow","aria-valuetext","aria-orientation","aria-disabled","onUpdate:modelValue"]),J.range?(x(),ne(Lx,{key:0,ref_key:"secondButton",ref:d,"model-value":p(le),vertical:J.vertical,"tooltip-class":J.tooltipClass,placement:J.placement,role:"slider","aria-label":W.value,"aria-valuemin":p(ie),"aria-valuemax":J.max,"aria-valuenow":p(le),"aria-valuetext":N.value,"aria-orientation":J.vertical?"vertical":"horizontal","aria-disabled":p(v),"onUpdate:modelValue":p(M)},null,8,["model-value","vertical","tooltip-class","placement","aria-label","aria-valuemin","aria-valuemax","aria-valuenow","aria-valuetext","aria-orientation","aria-disabled","onUpdate:modelValue"])):te("v-if",!0),J.showStops?(x(),O("div",F2e,[(x(!0),O(Ve,null,dt(p(E),(se,fe)=>(x(),O("div",{key:fe,class:U(p(o).e("stop")),style:et(p(A)(se))},null,6))),128))])):te("v-if",!0),p(Y).length>0?(x(),O(Ve,{key:2},[C("div",null,[(x(!0),O(Ve,null,dt(p(Y),(se,fe)=>(x(),O("div",{key:fe,style:et(p(A)(se.position)),class:U([p(o).e("stop"),p(o).e("marks-stop")])},null,6))),128))]),C("div",{class:U(p(o).e("marks"))},[(x(!0),O(Ve,null,dt(p(Y),(se,fe)=>(x(),ne(p(V2e),{key:fe,mark:se.mark,style:et(p(A)(se.position)),onMousedown:at(ge=>p(k)(se.position),["stop"])},null,8,["mark","style","onMousedown"]))),128))],2)],64)):te("v-if",!0)],38),K.value?(x(),ne(p(vD),{key:0,ref:"input","model-value":p(ie),class:U(p(o).e("input")),step:X.value,disabled:p(v),controls:J.showInputControls,min:J.min,max:J.max,precision:ee.value,size:G.value,"onUpdate:modelValue":p(T),onChange:p(b)},null,8,["model-value","class","step","disabled","controls","min","max","precision","size","onUpdate:modelValue","onChange"])):te("v-if",!0)],10,z2e)}}}),W2e=H2e;const K2e=wt(W2e),U2e=$e({prefixCls:{type:String}}),Px=q({name:"ElSpaceItem",props:U2e,setup(e,{slots:t}){const n=ke("space"),a=D(()=>`${e.prefixCls||n.b()}__item`);return()=>ot("div",{class:a.value},ce(t,"default"))}}),Dx={small:8,default:12,large:16};function G2e(e){const t=ke("space"),n=D(()=>[t.b(),t.m(e.direction),e.class]),a=F(0),r=F(0),o=D(()=>[e.wrap||e.fill?{flexWrap:"wrap"}:{},{alignItems:e.alignment},{rowGap:`${r.value}px`,columnGap:`${a.value}px`},e.style]),i=D(()=>e.fill?{flexGrow:1,minWidth:`${e.fillRatio}%`}:{});return ur(()=>{const{size:l="small",wrap:s,direction:u,fill:c}=e;if(Ae(l)){const[d=0,v=0]=l;a.value=d,r.value=v}else{let d;Je(l)?d=l:d=Dx[l||"small"]||Dx.small,(s||c)&&u==="horizontal"?a.value=r.value=d:u==="horizontal"?(a.value=d,r.value=0):(r.value=d,a.value=0)}}),{classes:n,containerStyle:o,itemStyle:i}}const Y2e=$e({direction:{type:String,values:["horizontal","vertical"],default:"horizontal"},class:{type:ae([String,Object,Array,Boolean]),default:""},style:{type:ae([String,Array,Object,Boolean]),default:""},alignment:{type:ae(String),default:"center"},prefixCls:{type:String},spacer:{type:ae([Object,String,Number,Array]),default:null,validator:e=>bn(e)||Je(e)||Ye(e)},wrap:Boolean,fill:Boolean,fillRatio:{type:Number,default:100},size:{type:[String,Array,Number],values:ii,validator:e=>Je(e)||Ae(e)&&e.length===2&&e.every(Je)}}),j2e=q({name:"ElSpace",props:Y2e,setup(e,{slots:t}){const{classes:n,containerStyle:a,itemStyle:r}=G2e(e);function o(i,l="",s=[]){const{prefixCls:u}=e;return i.forEach((c,d)=>{h4(c)?Ae(c.children)&&c.children.forEach((v,f)=>{h4(v)&&Ae(v.children)?o(v.children,`${l+f}-`,s):bn(v)&&(v==null?void 0:v.type)===oa?s.push(v):s.push(L(Px,{style:r.value,prefixCls:u,key:`nested-${l+f}`},{default:()=>[v]},12,["style","prefixCls"]))}):eX(c)&&s.push(L(Px,{style:r.value,prefixCls:u,key:`LoopKey${l+d}`},{default:()=>[c]},12,["style","prefixCls"]))}),s}return()=>{const{spacer:i,direction:l}=e,s=ce(t,"default",{key:0},()=>[]);if((s.children??[]).length===0)return null;if(Ae(s.children)){let u=o(s.children);if(i){const c=u.length-1;u=u.reduce((d,v,f)=>{const h=[...d,v];return f!==c&&h.push(L("span",{style:[r.value,l==="vertical"?"width: 100%":null],key:f},[bn(i)?ei(i):ue(i,1)],4)),h},[])}return L("div",{class:n.value,style:a.value},u,6)}return s.children}}}),q2e=wt(j2e),X2e=$e({space:{type:[Number,String],default:""},active:{type:Number,default:0},direction:{type:String,default:"horizontal",values:["horizontal","vertical"]},alignCenter:{type:Boolean},simple:{type:Boolean},finishStatus:{type:String,values:["wait","process","finish","error","success"],default:"finish"},processStatus:{type:String,values:["wait","process","finish","error","success"],default:"process"}}),Z2e={[Ot]:(e,t)=>[e,t].every(Je)},_D="ElSteps",J2e=$e({title:{type:String,default:""},icon:{type:pn},description:{type:String,default:""},status:{type:String,values:["","wait","process","finish","error","success"],default:""}});var Q2e=q({name:"ElSteps",__name:"steps",props:X2e,emits:Z2e,setup(e,{emit:t}){const n=e,a=t,r=ke("steps"),{children:o,addChild:i,removeChild:l,ChildrenSorter:s}=z1(Et(),"ElStep");return ye(o,()=>{o.value.forEach((u,c)=>{u.setIndex(c)})}),Ft(_D,{props:n,steps:o,addStep:i,removeStep:l}),ye(()=>n.active,(u,c)=>{a(Ot,u,c)}),(u,c)=>(x(),O("div",{class:U([p(r).b(),p(r).m(e.simple?"simple":e.direction)])},[ce(u.$slots,"default"),L(p(s))],2))}}),eye=Q2e,tye=q({name:"ElStep",__name:"item",props:J2e,setup(e){const t=e,n=ke("step"),a=F(-1),r=F({}),o=F(""),i=Ze(_D),l=Et();let s=0,u=0;mt(()=>{ye([()=>i.props.active,()=>i.props.processStatus,()=>i.props.finishStatus],([M],[E])=>{u=E||0,s=M-u,k(M)},{immediate:!0})});const c=D(()=>t.status||o.value),d=D(()=>{const M=i.steps.value[a.value-1];return M?M.internalStatus.value:"wait"}),v=D(()=>i.props.alignCenter),f=D(()=>i.props.direction==="vertical"),h=D(()=>i.props.simple),m=D(()=>i.steps.value.length),g=D(()=>{var M;return((M=i.steps.value[m.value-1])==null?void 0:M.uid)===l.uid}),y=D(()=>h.value?"":i.props.space),b=D(()=>[n.b(),n.is(h.value?"simple":i.props.direction),n.is("flex",g.value&&!y.value&&!v.value),n.is("center",v.value&&!f.value&&!h.value)]),_=D(()=>{const M={flexBasis:Je(y.value)?`${y.value}px`:y.value?y.value:`${100/(m.value-(v.value?0:1))}%`};return f.value||g.value&&(M.maxWidth=`${100/m.value}%`),M}),w=M=>{a.value=M},S=M=>{const E=M==="wait",A={transitionDelay:`${Math.abs(s)===1?0:s>0?(a.value+1-u)*150:-(a.value+1-i.props.active)*150}ms`},I=M===i.props.processStatus||E?0:100;A.borderWidth=I&&!h.value?"1px":0,A[i.props.direction==="vertical"?"height":"width"]=`${I}%`,r.value=A},k=M=>{M>a.value?o.value=i.props.finishStatus:M===a.value&&d.value!=="error"?o.value=i.props.processStatus:o.value="wait";const E=i.steps.value[a.value-1];E&&E.calcProgress(o.value)},T={uid:l.uid,getVnode:()=>l.vnode,currentStatus:c,internalStatus:o,setIndex:w,calcProgress:S};return i.addStep(T),rn(()=>{i.removeStep(T)}),(M,E)=>(x(),O("div",{style:et(_.value),class:U(b.value)},[te(" icon & line "),C("div",{class:U([p(n).e("head"),p(n).is(c.value)])},[h.value?te("v-if",!0):(x(),O("div",{key:0,class:U(p(n).e("line"))},[C("i",{class:U(p(n).e("line-inner")),style:et(r.value)},null,6)],2)),C("div",{class:U([p(n).e("icon"),p(n).is(e.icon||M.$slots.icon?"icon":"text")])},[ce(M.$slots,"icon",{},()=>[e.icon?(x(),ne(p(Xe),{key:0,class:U(p(n).e("icon-inner"))},{default:$(()=>[(x(),ne(St(e.icon)))]),_:1},8,["class"])):c.value==="success"?(x(),ne(p(Xe),{key:1,class:U([p(n).e("icon-inner"),p(n).is("status")])},{default:$(()=>[L(p(Os))]),_:1},8,["class"])):c.value==="error"?(x(),ne(p(Xe),{key:2,class:U([p(n).e("icon-inner"),p(n).is("status")])},{default:$(()=>[L(p(so))]),_:1},8,["class"])):h.value?te("v-if",!0):(x(),O("div",{key:3,class:U(p(n).e("icon-inner"))},Q(a.value+1),3))])],2)],2),te(" title & description "),C("div",{class:U(p(n).e("main"))},[C("div",{class:U([p(n).e("title"),p(n).is(c.value)])},[ce(M.$slots,"title",{},()=>[ue(Q(e.title),1)])],2),h.value?(x(),O("div",{key:0,class:U(p(n).e("arrow"))},null,2)):(x(),O("div",{key:1,class:U([p(n).e("description"),p(n).is(c.value)])},[ce(M.$slots,"description",{},()=>[ue(Q(e.description),1)])],2))],2)],6))}}),wD=tye;const nye=wt(eye,{Step:wD}),aye=On(wD),SD=e=>["",...ii].includes(e),rye=$e({modelValue:{type:[Boolean,String,Number],default:!1},disabled:{type:Boolean,default:void 0},loading:Boolean,size:{type:String,validator:SD},width:{type:[String,Number],default:""},inlinePrompt:Boolean,inactiveActionIcon:{type:pn},activeActionIcon:{type:pn},activeIcon:{type:pn},inactiveIcon:{type:pn},activeText:{type:String,default:""},inactiveText:{type:String,default:""},activeValue:{type:[Boolean,String,Number],default:!0},inactiveValue:{type:[Boolean,String,Number],default:!1},name:{type:String,default:""},validateEvent:{type:Boolean,default:!0},beforeChange:{type:ae(Function)},id:String,tabindex:{type:[String,Number]},...dr(["ariaLabel"])}),oye={[bt]:e=>vn(e)||Ye(e)||Je(e),[Ot]:e=>vn(e)||Ye(e)||Je(e),[ua]:e=>vn(e)||Ye(e)||Je(e)},iye=["id","aria-checked","aria-disabled","aria-label","name","true-value","false-value","disabled","tabindex"],lye=["aria-hidden"],sye={key:1},uye={key:1},cye=["aria-hidden"],Yp="ElSwitch";var dye=q({name:Yp,__name:"switch",props:rye,emits:oye,setup(e,{expose:t,emit:n}){const a=e,r=n,{formItem:o}=Ea(),i=ca(),l=ke("switch"),{inputId:s}=Yr(a,{formItemContext:o}),u=$n(D(()=>{if(a.loading)return!0})),c=F(a.modelValue!==!1),d=Ht(),v=D(()=>[l.b(),l.m(i.value),l.is("disabled",u.value),l.is("checked",y.value)]),f=D(()=>[l.e("label"),l.em("label","left"),l.is("active",!y.value)]),h=D(()=>[l.e("label"),l.em("label","right"),l.is("active",y.value)]),m=D(()=>({width:Vn(a.width)}));ye(()=>a.modelValue,()=>{c.value=!0});const g=D(()=>c.value?a.modelValue:!1),y=D(()=>g.value===a.activeValue);[a.activeValue,a.inactiveValue].includes(g.value)||(un(Yp,"model-value must be active-value or inactive-value"),r(bt,a.inactiveValue),r(Ot,a.inactiveValue),r(ua,a.inactiveValue)),ye(y,S=>{var k;d.value.checked=S,a.validateEvent&&((k=o==null?void 0:o.validate)==null||k.call(o,"change").catch(kt))});const b=()=>{const S=y.value?a.inactiveValue:a.activeValue;r(bt,S),r(Ot,S),r(ua,S),He(()=>{d.value.checked=y.value})},_=()=>{if(u.value)return;const{beforeChange:S}=a;if(!S){b();return}const k=S();[Ls(k),vn(k)].includes(!0)||Dn(Yp,"beforeChange must return type `Promise` or `boolean`"),Ls(k)?k.then(T=>{T&&b()}).catch(T=>{un(Yp,`some error occurred: ${T}`)}):k&&b()},w=()=>{var S,k;(k=(S=d.value)==null?void 0:S.focus)==null||k.call(S)};return mt(()=>{d.value.checked=y.value}),t({focus:w,checked:y}),(S,k)=>(x(),O("div",{class:U(v.value),onClick:at(_,["prevent"])},[C("input",{id:p(s),ref_key:"input",ref:d,class:U(p(l).e("input")),type:"checkbox",role:"switch","aria-checked":y.value,"aria-disabled":p(u),"aria-label":e.ariaLabel,name:e.name,"true-value":e.activeValue,"false-value":e.inactiveValue,disabled:p(u),tabindex:e.tabindex,onChange:b,onKeydown:kn(_,["enter"])},null,42,iye),!e.inlinePrompt&&(e.inactiveIcon||e.inactiveText||S.$slots.inactive)?(x(),O("span",{key:0,class:U(f.value)},[ce(S.$slots,"inactive",{},()=>[e.inactiveIcon?(x(),ne(p(Xe),{key:0},{default:$(()=>[(x(),ne(St(e.inactiveIcon)))]),_:1})):te("v-if",!0),!e.inactiveIcon&&e.inactiveText?(x(),O("span",{key:1,"aria-hidden":y.value},Q(e.inactiveText),9,lye)):te("v-if",!0)])],2)):te("v-if",!0),C("span",{class:U(p(l).e("core")),style:et(m.value)},[e.inlinePrompt?(x(),O("div",{key:0,class:U(p(l).e("inner"))},[y.value?(x(),O("div",{key:1,class:U(p(l).e("inner-wrapper"))},[ce(S.$slots,"active",{},()=>[e.activeIcon?(x(),ne(p(Xe),{key:0},{default:$(()=>[(x(),ne(St(e.activeIcon)))]),_:1})):te("v-if",!0),!e.activeIcon&&e.activeText?(x(),O("span",uye,Q(e.activeText),1)):te("v-if",!0)])],2)):(x(),O("div",{key:0,class:U(p(l).e("inner-wrapper"))},[ce(S.$slots,"inactive",{},()=>[e.inactiveIcon?(x(),ne(p(Xe),{key:0},{default:$(()=>[(x(),ne(St(e.inactiveIcon)))]),_:1})):te("v-if",!0),!e.inactiveIcon&&e.inactiveText?(x(),O("span",sye,Q(e.inactiveText),1)):te("v-if",!0)])],2))],2)):te("v-if",!0),C("div",{class:U(p(l).e("action"))},[e.loading?(x(),ne(p(Xe),{key:0,class:U(p(l).is("loading"))},{default:$(()=>[L(p(Mr))]),_:1},8,["class"])):y.value?ce(S.$slots,"active-action",{key:1},()=>[e.activeActionIcon?(x(),ne(p(Xe),{key:0},{default:$(()=>[(x(),ne(St(e.activeActionIcon)))]),_:1})):te("v-if",!0)]):y.value?te("v-if",!0):ce(S.$slots,"inactive-action",{key:2},()=>[e.inactiveActionIcon?(x(),ne(p(Xe),{key:0},{default:$(()=>[(x(),ne(St(e.inactiveActionIcon)))]),_:1})):te("v-if",!0)])],2)],6),!e.inlinePrompt&&(e.activeIcon||e.activeText||S.$slots.active)?(x(),O("span",{key:1,class:U(h.value)},[ce(S.$slots,"active",{},()=>[e.activeIcon?(x(),ne(p(Xe),{key:0},{default:$(()=>[(x(),ne(St(e.activeIcon)))]),_:1})):te("v-if",!0),!e.activeIcon&&e.activeText?(x(),O("span",{key:1,"aria-hidden":!y.value},Q(e.activeText),9,cye)):te("v-if",!0)])],2)):te("v-if",!0)],2))}}),fye=dye;const vye=wt(fye),hye={data:{type:Array,default:()=>[]},size:pa,width:[String,Number],height:[String,Number],maxHeight:[String,Number],fit:{type:Boolean,default:!0},stripe:Boolean,border:Boolean,rowKey:[String,Function],showHeader:{type:Boolean,default:!0},showSummary:Boolean,sumText:String,summaryMethod:Function,rowClassName:[String,Function],rowStyle:[Object,Function],cellClassName:[String,Function],cellStyle:[Object,Function],headerRowClassName:[String,Function],headerRowStyle:[Object,Function],headerCellClassName:[String,Function],headerCellStyle:[Object,Function],highlightCurrentRow:Boolean,currentRowKey:[String,Number],emptyText:String,expandRowKeys:Array,defaultExpandAll:Boolean,rowExpandable:{type:Function},defaultSort:Object,tooltipEffect:String,tooltipOptions:Object,spanMethod:Function,selectOnIndeterminate:{type:Boolean,default:!0},indent:{type:Number,default:16},treeProps:{type:Object,default:()=>({hasChildren:"hasChildren",children:"children",checkStrictly:!1})},lazy:Boolean,load:Function,style:{type:[String,Object,Array,Boolean],default:()=>({})},className:{type:String,default:""},tableLayout:{type:String,default:"fixed"},scrollbarAlwaysOn:Boolean,flexible:Boolean,showOverflowTooltip:{type:[Boolean,Object],default:void 0},tooltipFormatter:Function,appendFilterPanelTo:String,scrollbarTabindex:{type:[Number,String],default:void 0},allowDragLastColumn:{type:Boolean,default:!0},preserveExpandedContent:Boolean,nativeScrollbar:Boolean};let pye=1,mye=1;const gye=e=>`${e}-table_${pye++}`,yye=e=>`${e}_column_${mye++}`,dy=function(e){var t;return(t=e.target)==null?void 0:t.closest("td")},bye=function(e,t,n,a,r){if(!t&&!a&&(!r||Ae(r)&&!r.length))return e;Ye(n)?n=n==="descending"?-1:1:n=n&&n<0?-1:1;const o=a?null:function(l,s){return r?SA(wa(r),u=>Ye(u)?na(l,u):u(l,s,e)):(t!=="$key"&&_t(l)&&"$value"in l&&(l=l.$value),[_t(l)?t?na(l,t):null:l])},i=function(l,s){var u,c,d,v,f;if(a)return a(l.value,s.value);for(let h=0,m=((u=l.key)==null?void 0:u.length)??0;h((f=s.key)==null?void 0:f[h]))return 1}return 0};return e.map((l,s)=>({value:l,index:s,key:o?o(l,s):null})).sort((l,s)=>{let u=i(l,s);return u||(u=l.index-s.index),u*+n}).map(l=>l.value)},CD=function(e,t){let n=null;return e.columns.forEach(a=>{a.id===t&&(n=a)}),n},_ye=function(e,t){let n=null;for(let a=0;a{if(!e)throw new Error("Row is required when get row identity");if(Ye(t)){if(!t.includes("."))return`${e[t]}`;const n=t.split(".");let a=e;for(const r of n)a=a[r];return`${a}`}else if(Qe(t))return t.call(null,e);return""},Sd=function(e,t,n=!1,a="children"){const r=e||[],o={};return r.forEach((i,l)=>{if(o[ra(i,t)]={row:i,index:l},n){const s=i[a];Ae(s)&&Object.assign(o,Sd(s,t,!0,a))}}),o};function wye(e,t){const n={};let a;for(a in e)n[a]=e[a];for(a in t)if(en(t,a)){const r=t[a];zt(r)||(n[a]=r)}return n}function Mw(e){return e===""||zt(e)||(e=Number.parseInt(e,10),Number.isNaN(e)&&(e="")),e}function xD(e){return e===""||zt(e)||(e=Mw(e),Number.isNaN(e)&&(e=80)),e}function Sye(e){return Je(e)?e:Ye(e)?/^\d+(?:px)?$/.test(e)?Number.parseInt(e,10):e:null}function Cye(...e){return e.length===0?t=>t:e.length===1?e[0]:e.reduce((t,n)=>(...a)=>t(n(...a)))}function ld(e,t,n,a,r,o,i){let l=o??0,s=!1;const c=(()=>{if(!i)return e.indexOf(t);const m=ra(t,i);return e.findIndex(g=>ra(g,i)===m)})(),d=c!==-1,v=r==null?void 0:r.call(null,t,l),f=m=>{m==="add"?e.push(t):e.splice(c,1),s=!0},h=m=>{let g=0;const y=(a==null?void 0:a.children)&&m[a.children];return y&&Ae(y)&&(g+=y.length,y.forEach(b=>{g+=h(b)})),g};return(!r||v)&&(vn(n)?n&&!d?f("add"):!n&&d&&f("remove"):f(d?"remove":"add")),!(a!=null&&a.checkStrictly)&&(a!=null&&a.children)&&Ae(t[a.children])&&t[a.children].forEach(m=>{const g=ld(e,m,n??!d,a,r,l+1,i);l+=h(m)+1,g&&(s=g)}),s}function xye(e,t,n="children",a="hasChildren",r=!1){const o=l=>!(Ae(l)&&l.length);function i(l,s,u){t(l,s,u),s.forEach(c=>{if(c[a]&&r){t(c,null,u+1);return}const d=c[n];o(d)||i(c,d,u+1)})}e.forEach(l=>{if(l[a]&&r){t(l,null,0);return}const s=l[n];o(s)||i(l,s,0)})}const kye=(e,t,n,a)=>{const r=Qe(a==null?void 0:a.tooltipFormatter)?a.tooltipFormatter({row:n,column:a,cellValue:Cs(n,a.property).value}):void 0;return bn(r)?{slotContent:r,content:null,strategy:"fixed",...e}:{slotContent:null,content:r??t,strategy:"fixed",...e}};let Wn=null;function Tye(e,t,n,a,r,o){var f;const i=kye(e,t,n,a),l={...i,slotContent:void 0};if((Wn==null?void 0:Wn.trigger)===r){const h=(f=Wn.vm)==null?void 0:f.component;xA(h==null?void 0:h.props,l),h&&i.slotContent&&(h.slots.content=()=>[i.slotContent]);return}Wn==null||Wn();const s=o==null?void 0:o.refs.tableWrapper,u=s==null?void 0:s.dataset.prefix,c=L(Ma,{virtualTriggering:!0,virtualRef:r,appendTo:s,placement:"top",transition:"none",offset:0,hideAfter:0,...l},i.slotContent?{content:()=>i.slotContent}:void 0);c.appContext={...o.appContext,...o};const d=document.createElement("div");Ps(c,d),c.component.exposed.onOpen();const v=s==null?void 0:s.querySelector(`.${u}-scrollbar__wrap`);Wn=()=>{var m,g;(g=(m=c.component)==null?void 0:m.exposed)!=null&&g.onClose&&c.component.exposed.onClose(),Ps(null,d);const h=Wn;v==null||v.removeEventListener("scroll",h),h.trigger=void 0,h.vm=void 0,Wn=null},Wn.trigger=r??void 0,Wn.vm=c,v==null||v.addEventListener("scroll",Wn)}function kD(e){return e.children?SA(e.children,kD):[e]}function $x(e,t){return e+t.colSpan}const TD=(e,t,n,a)=>{let r=0,o=e;const i=n.states.columns.value;if(a){const s=kD(a[e]);r=i.slice(0,i.indexOf(s[0])).reduce($x,0),o=r+s.reduce($x,0)-1}else r=e;let l;switch(t){case"left":o=i.length-n.states.rightFixedLeafColumnsLength.value&&(l="right");break;default:o=i.length-n.states.rightFixedLeafColumnsLength.value&&(l="right")}return l?{direction:l,start:r,after:o}:{}},Ew=(e,t,n,a,r,o=0)=>{const i=[],{direction:l,start:s,after:u}=TD(t,n,a,r);if(l){const c=l==="left";i.push(`${e}-fixed-column--${l}`),c&&u+o===a.states.fixedLeafColumnsLength.value-1?i.push("is-last-column"):!c&&s-o===a.states.columns.value.length-a.states.rightFixedLeafColumnsLength.value&&i.push("is-first-column")}return i};function Rx(e,t){return e+(E1(t.realWidth)||Number.isNaN(t.realWidth)?Number(t.width):t.realWidth)}const Aw=(e,t,n,a)=>{const{direction:r,start:o=0,after:i=0}=TD(e,t,n,a);if(!r)return;const l={},s=r==="left",u=n.states.columns.value;return s?l.left=u.slice(0,o).reduce(Rx,0):l.right=u.slice(i+1).reverse().reduce(Rx,0),l},jd=(e,t)=>{e&&(Number.isNaN(e[t])||(e[t]=`${e[t]}px`))};function MD(e){return e.some(t=>bn(t)?!(t.type===oa||t.type===Ve&&!MD(t.children)):!0)?e:null}function Mye(e){const t=Et(),n=F(!1),a=F([]),r=(u,c)=>{const d=t.store.states.rowExpandable.value;return(d==null?void 0:d(u,c))??!0};return{updateExpandRows:()=>{const u=e.data.value||[],c=e.rowKey.value;if(n.value)a.value=t.store.states.rowExpandable.value?u.filter(r):u.slice();else if(c){const d=Sd(a.value,c);a.value=u.filter((v,f)=>!!d[ra(v,c)]&&r(v,f))}else a.value=[]},toggleRowExpansion:(u,c)=>{const d=(e.data.value||[]).indexOf(u);d>-1&&!r(u,d)||ld(a.value,u,c,void 0,void 0,void 0,e.rowKey.value)&&t.emit("expand-change",u,a.value.slice())},setExpandRowKeys:u=>{t.store.assertRowKey();const c=e.data.value||[],d=e.rowKey.value,v=Sd(c,d);a.value=u.reduce((f,h)=>{const m=v[h];return m&&r(m.row,m.index)&&f.push(m.row),f},[])},isRowExpanded:u=>{const c=e.rowKey.value;return c?!!Sd(a.value,c)[ra(u,c)]:a.value.includes(u)},states:{expandRows:a,defaultExpandAll:n}}}function Eye(e){const t=Et(),n=F(null),a=F(null),r=u=>{t.store.assertRowKey(),n.value=u,i(u)},o=()=>{n.value=null},i=u=>{const{data:c,rowKey:d}=e,v=a.value;let f=null;d.value&&(f=(p(c)||[]).find(h=>ra(h,d.value)===u)??null),a.value=f??null,t.emit("current-change",a.value,v)};return{setCurrentRowKey:r,restoreCurrentRowKey:o,setCurrentRowByKey:i,updateCurrentRow:u=>{const c=a.value;if(u&&u!==c){a.value=u,t.emit("current-change",a.value,c);return}!u&&c&&(a.value=null,t.emit("current-change",null,c))},updateCurrentRowData:()=>{const u=e.rowKey.value,c=e.data.value||[],d=a.value;d&&!c.includes(d)?u?i(ra(d,u)):(a.value=null,t.emit("current-change",null,d)):n.value&&(i(n.value),o())},states:{_currentRowKey:n,currentRow:a}}}function Aye(e){const t=F([]),n=F({}),a=F(16),r=F(!1),o=F({}),i=F("hasChildren"),l=F("children"),s=F(!1),u=Et(),c=D(()=>e.rowKey.value?v(e.data.value||[]):{}),d=D(()=>{const w=e.rowKey.value,S=Object.keys(o.value),k={};return S.length&&S.forEach(T=>{if(o.value[T].length){const M={children:[]};o.value[T].forEach(E=>{const A=ra(E,w);M.children.push(A),E[i.value]&&!k[A]&&(k[A]={children:[]})}),k[T]=M}}),k}),v=w=>{const S=e.rowKey.value,k={};return xye(w,(T,M,E)=>{const A=ra(T,S);Ae(M)?k[A]={children:M.map(I=>ra(I,S)),level:E}:r.value&&(k[A]={children:[],lazy:!0,level:E})},l.value,i.value,r.value),k},f=(w=!1,S)=>{var A,I;S||(S=(A=u.store)==null?void 0:A.states.defaultExpandAll.value);const k=c.value,T=d.value,M=Object.keys(k),E={};if(M.length){const P=p(n),R=[],G=(H,z)=>{if(w)return t.value?S||t.value.includes(z):!!(S||H!=null&&H.expanded);{const B=S||t.value&&t.value.includes(z);return!!(H!=null&&H.expanded||B)}};M.forEach(H=>{const z=P[H],B={...k[H]};if(B.expanded=G(z,H),B.lazy){const{loaded:W=!1,loading:N=!1}=z||{};B.loaded=!!W,B.loading=!!N,R.push(H)}E[H]=B});const K=Object.keys(T);r.value&&K.length&&R.length&&K.forEach(H=>{var W;const z=P[H],B=T[H].children;if(R.includes(H)){if(((W=E[H].children)==null?void 0:W.length)!==0)throw new Error("[ElTable]children must be an empty array.");E[H].children=B}else{const{loaded:N=!1,loading:V=!1}=z||{};E[H]={lazy:!0,loaded:!!N,loading:!!V,expanded:G(z,H),children:B,level:void 0}}})}n.value=E,(I=u.store)==null||I.updateTableScrollY()};ye(()=>t.value,()=>{f(!0)},{deep:!0}),ye(()=>c.value,()=>{f()}),ye(()=>d.value,()=>{f()});const h=w=>{t.value=w,f()},m=w=>r.value&&w&&"loaded"in w&&!w.loaded,g=(w,S)=>{u.store.assertRowKey();const k=e.rowKey.value,T=ra(w,k),M=T&&n.value[T];if(T&&M&&"expanded"in M){const E=M.expanded;S=zt(S)?!M.expanded:S,n.value[T].expanded=S,E!==S&&u.emit("expand-change",w,S),S&&m(M)&&b(w,T,M),u.store.updateTableScrollY()}},y=w=>{u.store.assertRowKey();const S=e.rowKey.value,k=ra(w,S),T=n.value[k];m(T)?b(w,k,T):g(w,void 0)},b=(w,S,k)=>{const{load:T}=u.props;T&&!n.value[S].loaded&&(n.value[S].loading=!0,T(w,k,M=>{if(!Ae(M))throw new TypeError("[ElTable] data must be an array");n.value[S].loading=!1,n.value[S].loaded=!0,n.value[S].expanded=!0,M.length&&(o.value={...o.value,[S]:M}),u.emit("expand-change",w,!0)}))};return{loadData:b,loadOrToggle:y,toggleTreeExpansion:g,updateTreeExpandKeys:h,updateTreeData:f,updateKeyChildren:(w,S)=>{const{lazy:k,rowKey:T}=u.props;if(k){if(!T)throw new Error("[Table] rowKey is required in updateKeyChild");o.value[w]&&(o.value={...o.value,[w]:S})}},normalize:v,states:{expandRowKeys:t,treeData:n,indent:a,lazy:r,lazyTreeNodeMap:o,lazyColumnIdentifier:i,childrenColumnName:l,checkStrictly:s}}}const Iye=(e,t)=>{const n=t.sortingColumn;return!n||Ye(n.sortable)?e:bye(e,t.sortProp,t.sortOrder,n.sortMethod,n.sortBy)},am=e=>{const t=[];return e.forEach(n=>{n.children&&n.children.length>0?t.push.apply(t,am(n.children)):t.push(n)}),t};function Lye(){var Gt;const e=Et(),{size:t}=Ta((Gt=e.proxy)==null?void 0:Gt.$props),n=F(null),a=F([]),r=F([]),o=F(!1),i=F([]),l=F([]),s=F([]),u=F([]),c=F([]),d=F([]),v=F([]),f=F([]),h=[],m=F(0),g=F(0),y=F(0),b=F(!1),_=F([]),w=F({}),S=F(!1),k=F(!1),T=F(null),M=F(null),E=F({}),A=F(null),I=F(null),P=F(null),R=F(null),G=F(null);let K=[];const H=D(()=>n.value?Sd(_.value,n.value):void 0),z=je=>{var Ct;const{childrenColumnName:ft,lazyTreeNodeMap:ht}=e.store.states,pt=je[ft.value]??[];if(!n.value)return pt;const It=ra(je,n.value);return[...((Ct=ht.value)==null?void 0:Ct[It])??[],...pt]};ye(a,()=>{var je;e.state&&(V(!1),e.props.tableLayout==="auto"&&((je=e.refs.tableHeaderRef)==null||je.updateFixedColumnStyle()))},{deep:!0});const B=()=>{if(!n.value)throw new Error("[ElTable] prop row-key is required")},W=je=>{var ft;(ft=je.children)==null||ft.forEach(ht=>{ht.fixed=je.fixed,W(ht)})},N=()=>{i.value.forEach($t=>{W($t)}),u.value=i.value.filter($t=>[!0,"left"].includes($t.fixed));const je=i.value.find($t=>$t.type==="selection");let ft;je&&je.fixed!=="right"&&!u.value.includes(je)&&i.value.indexOf(je)===0&&u.value.length&&(u.value.unshift(je),ft=!0),c.value=i.value.filter($t=>$t.fixed==="right");const ht=i.value.filter($t=>(ft?$t.type!=="selection":!0)&&!$t.fixed);l.value=Array.from(u.value).concat(ht).concat(c.value);const pt=am(ht),It=am(u.value),Ct=am(c.value);m.value=pt.length,g.value=It.length,y.value=Ct.length,s.value=Array.from(It).concat(pt).concat(Ct),o.value=u.value.length>0||c.value.length>0},V=(je,ft=!1)=>{je&&N(),ft?e.state.doLayout():e.state.debouncedUpdateLayout()},Y=je=>H.value?!!H.value[ra(je,n.value)]:_.value.includes(je),X=D(()=>{const je=new Map;if(!n.value||!T.value)return je;let ft=0;const ht=pt=>{Ae(pt)&&pt.forEach(It=>{const Ct=ra(It,n.value);je.set(Ct,ft),ft+=1;const $t=z(It);$t.length&&ht($t)})};return ht(a.value||[]),je}),ee=(je={})=>{const{emitChange:ft=!0}=je;if(Rt.checkStrictly.value||!n.value){w.value={},K=[];return}const ht=n.value,pt=je.rowIndexMap??X.value,It=T.value,Ct=new WeakMap,$t=Ee=>{const de=Ct.get(Ee);if(de)return de;const it=ra(Ee,ht);return Ct.set(Ee,it),it},Sn={},Fe=[],qe=new Set(_.value.map(Ee=>$t(Ee))),ut=[];let Ie=!1;const Ke=(Ee,de,it)=>{const Dt=qe.has(de);it&&!Dt?(ut.push(Ee),qe.add(de),Ie=!0):!it&&Dt&&(qe.delete(de),Ie=!0)},Ge=Ee=>{let de=0,it=0;return Ae(Ee)?(Ee.forEach(Dt=>{const sn=$t(Dt),gn=z(Dt);let Aa=0,jr=0;if(gn.length){const Ia=Ge(gn);Aa=Ia.selectedCount,jr=Ia.selectableCount}const vr=It?It.call(null,Dt,pt.get(sn)??0):!0;if(vr&&jr>0){const Ia=Aa===jr;!Ia&&Aa!==0&&(Sn[sn]=!0,Fe.push(Dt)),Ke(Dt,sn,Ia)}vr&&(it+=1,qe.has(sn)&&(de+=1)),de+=Aa,it+=jr}),{selectedCount:de,selectableCount:it}):{selectedCount:de,selectableCount:it}};if(Ge(a.value||[]),Ie){const Ee=_.value.filter(de=>qe.has($t(de)));ut.forEach(de=>{qe.has($t(de))&&Ee.push(de)}),_.value=Ee}w.value=Sn,K=Fe,Ie&&ft&&e.emit("selection-change",_.value?_.value.slice():[])},re=()=>{b.value=!1;const je=_.value;_.value=[],w.value={},K=[],je.length&&e.emit("selection-change",[])},ie=()=>{var ft,ht;let je;if(n.value){je=[];const pt=(ht=(ft=e==null?void 0:e.store)==null?void 0:ft.states)==null?void 0:ht.childrenColumnName.value,It=Sd(a.value,n.value,!0,pt),{lazyTreeNodeMap:Ct}=e.store.states;Ct.value&&Object.entries(Ct.value).forEach(([$t,Sn])=>{It[$t]&&Sn.forEach(Fe=>{const qe=ra(Fe,n.value);It[qe]||(It[qe]={row:Fe,index:-1})})});for(const $t in H.value)en(H.value,$t)&&!It[$t]&&je.push(H.value[$t].row)}else je=_.value.filter(pt=>!a.value.includes(pt));if(je.length){const pt=_.value.filter(It=>!je.includes(It));_.value=pt,ee({emitChange:!1}),e.emit("selection-change",[...pt])}},le=()=>(_.value||[]).slice(),ve=()=>K.slice(),he=(je,ft,ht)=>{var qe;if(!n.value||Rt.checkStrictly.value||!Rt.lazy.value)return;const{lazyTreeNodeMap:pt,childrenColumnName:It}=e.store.states,Ct=ra(je,n.value),$t=((qe=pt.value)==null?void 0:qe[Ct])??[],Sn=je[It.value]??[],Fe={children:It.value,checkStrictly:!1};for(const ut of $t){const Ie=ht.get(ra(ut,n.value))??0;ld(_.value,ut,ft,Fe,T.value,Ie,n.value),he(ut,ft,ht)}for(const ut of Sn)he(ut,ft,ht)},J=(je,ft,ht=!0,pt=!1)=>{var Ct,$t,Sn,Fe;const It={children:($t=(Ct=e==null?void 0:e.store)==null?void 0:Ct.states)==null?void 0:$t.childrenColumnName.value,checkStrictly:(Fe=(Sn=e==null?void 0:e.store)==null?void 0:Sn.states)==null?void 0:Fe.checkStrictly.value};if(ld(_.value,je,ft,It,pt?void 0:T.value,a.value.indexOf(je),n.value)){Rt.lazy.value&&!Rt.checkStrictly.value?(he(je,ft??Y(je),X.value),ee({emitChange:!1,rowIndexMap:X.value})):ee({emitChange:!1});const qe=(_.value||[]).slice();ht&&e.emit("select",qe,je),e.emit("selection-change",qe)}},Z=()=>{var Sn,Fe;const je=k.value?!b.value:!(b.value||_.value.length);b.value=je;let ft=!1,ht=0;const pt=(Fe=(Sn=e==null?void 0:e.store)==null?void 0:Sn.states)==null?void 0:Fe.rowKey.value,{childrenColumnName:It}=e.store.states,Ct={children:It.value,checkStrictly:!1};a.value.forEach((qe,ut)=>{const Ie=ut+ht;ld(_.value,qe,je,Ct,T.value,Ie,pt)&&(ft=!0),ht+=se(ra(qe,pt))});const $t=X.value;if(Rt.lazy.value&&!Rt.checkStrictly.value&&pt)for(const qe of Object.values(Rt.lazyTreeNodeMap.value))for(const ut of qe){const Ie=$t.get(ra(ut,pt))??0;ld(_.value,ut,je,Ct,T.value,Ie,pt)&&(ft=!0),he(ut,je,$t)}ee({emitChange:!1,rowIndexMap:$t}),ft&&e.emit("selection-change",_.value?[..._.value]:[]),e.emit("select-all",(_.value||[]).slice())},j=()=>{var It;if(((It=a.value)==null?void 0:It.length)===0){b.value=!1;return}let je=0,ft=0;const ht=Ct=>{for(const $t of Ct){const Sn=T.value&&T.value.call(null,$t,je);if(Y($t))ft++;else if(!T.value||Sn)return!1;je++;const Fe=z($t);if(Fe.length&&!ht(Fe))return!1}return!0},pt=ht(a.value||[]);b.value=ft===0?!1:pt},oe=je=>{if(!n.value)return!1;const ft=ra(je,n.value);return!!w.value[ft]},se=je=>{var It;if(!e||!e.store)return 0;const{treeData:ft}=e.store.states;let ht=0;const pt=(It=ft.value[je])==null?void 0:It.children;return pt&&(ht+=pt.length,pt.forEach(Ct=>{ht+=se(Ct)})),ht},fe=(je,ft)=>{const ht={};return wa(je).forEach(pt=>{E.value[pt.id]=ft,ht[pt.columnKey||pt.id]=ft}),ht},ge=(je,ft,ht)=>{I.value&&I.value!==je&&(I.value.order=null),I.value=je,P.value=ft,R.value=ht},Se=()=>{let je=p(r);Object.keys(E.value).forEach(ft=>{const ht=E.value[ft];if(!ht||ht.length===0)return;const pt=CD({columns:s.value},ft);pt&&pt.filterMethod&&(je=je.filter(It=>ht.some(Ct=>pt.filterMethod.call(null,Ct,It,pt))))}),A.value=je},De=()=>{a.value=Iye(A.value??[],{sortingColumn:I.value,sortProp:P.value,sortOrder:R.value})},Te=(je=void 0)=>{je!=null&&je.filter||Se(),De()},Re=je=>{const{tableHeaderRef:ft}=e.refs;if(!ft)return;const ht=Object.assign({},ft.filterPanels),pt=Object.keys(ht);if(pt.length)if(Ye(je)&&(je=[je]),Ae(je)){const It=je.map(Ct=>_ye({columns:s.value},Ct));pt.forEach(Ct=>{const $t=It.find(Sn=>Sn.id===Ct);$t&&($t.filteredValue=[])}),e.store.commit("filterChange",{column:It,values:[],silent:!0,multi:!0})}else pt.forEach(It=>{const Ct=s.value.find($t=>$t.id===It);Ct&&(Ct.filteredValue=[])}),E.value={},e.store.commit("filterChange",{column:{},values:[],silent:!0})},Ce=()=>{I.value&&(ge(null,null,null),e.store.commit("changeSortCondition",{silent:!0}))},{setExpandRowKeys:Le,toggleRowExpansion:me,updateExpandRows:Be,states:ze,isRowExpanded:xe}=Mye({data:a,rowKey:n}),{updateTreeExpandKeys:rt,toggleTreeExpansion:nt,updateTreeData:Pt,updateKeyChildren:Wt,loadOrToggle:Jt,states:Rt}=Aye({data:a,rowKey:n}),{updateCurrentRowData:Qt,updateCurrentRow:_e,setCurrentRowKey:Ne,states:tt}=Eye({data:a,rowKey:n}),Me=je=>{Le(je),rt(je)},Ue=(je,ft)=>{s.value.some(({type:ht})=>ht==="expand")?me(je,ft):nt(je,ft)};return ye(()=>Rt.checkStrictly.value,je=>{je?(w.value={},K=[]):ee({emitChange:!1}),j()}),ye(()=>Rt.lazyTreeNodeMap.value,()=>{var pt;if(!Rt.lazy.value||Rt.checkStrictly.value||!n.value)return;const je=X.value,ft=_.value.length;for(const It of Object.keys(Rt.lazyTreeNodeMap.value))(pt=H.value)!=null&&pt[It]&&he(H.value[It].row,!0,je);const ht=_.value.length!==ft;ee({emitChange:!ht,rowIndexMap:je}),j(),ht&&e.emit("selection-change",[..._.value])}),{assertRowKey:B,updateColumns:N,scheduleLayout:V,isSelected:Y,clearSelection:re,cleanSelection:ie,getSelectionRows:le,getHalfSelectionRows:ve,toggleRowSelection:J,_toggleAllSelection:Z,toggleAllSelection:null,updateAllSelected:j,updateSelectionByChildren:ee,getRowIndeterminate:oe,updateFilters:fe,updateCurrentRow:_e,updateSort:ge,execFilter:Se,execSort:De,execQuery:Te,clearFilter:Re,clearSort:Ce,toggleRowExpansion:me,setExpandRowKeysAdapter:Me,setCurrentRowKey:Ne,toggleRowExpansionAdapter:Ue,isRowExpanded:xe,updateExpandRows:Be,updateCurrentRowData:Qt,loadOrToggle:Jt,updateTreeData:Pt,updateKeyChildren:Wt,states:{tableSize:t,rowKey:n,data:a,_data:r,isComplex:o,_columns:i,originColumns:l,columns:s,fixedColumns:u,rightFixedColumns:c,leafColumns:d,fixedLeafColumns:v,rightFixedLeafColumns:f,updateOrderFns:h,leafColumnsLength:m,fixedLeafColumnsLength:g,rightFixedLeafColumnsLength:y,isAllSelected:b,selection:_,selectionIndeterminate:w,reserveSelection:S,selectOnIndeterminate:k,selectable:T,rowExpandable:M,filters:E,filteredData:A,sortingColumn:I,sortProp:P,sortOrder:R,hoverRow:G,...ze,...Rt,...tt}}}function Q4(e,t){return e.map(n=>{var a;return n.id===t.id?t:((a=n.children)!=null&&a.length&&(n.children=Q4(n.children,t)),n)})}function e_(e){e.forEach(t=>{var n,a;t.no=(n=t.getColumnIndex)==null?void 0:n.call(t),(a=t.children)!=null&&a.length&&e_(t.children)}),e.sort((t,n)=>t.no-n.no)}function Pye(){const e=Et(),t=Lye(),n=ke("table"),{t:a}=Vt();return{ns:n,t:a,...t,mutations:{setData(l,s){const u=p(l._data)!==s;l.data.value=s,l._data.value=s,e.store.execQuery(),e.store.updateCurrentRowData(),e.store.updateExpandRows(),e.store.updateTreeData(e.store.states.defaultExpandAll.value),p(l.reserveSelection)?e.store.assertRowKey():u?e.store.clearSelection():e.store.cleanSelection(),e.store.updateSelectionByChildren({emitChange:!1}),e.store.updateAllSelected(),e.$ready&&e.store.scheduleLayout()},insertColumn(l,s,u,c){var f;const d=p(l._columns);let v=[];u?(u&&!u.children&&(u.children=[]),(f=u.children)==null||f.push(s),v=Q4(d,u)):(d.push(s),v=d),e_(v),l._columns.value=v,l.updateOrderFns.push(c),s.type==="selection"&&(l.selectable.value=s.selectable,l.reserveSelection.value=s.reserveSelection),e.$ready&&(e.store.updateColumns(),e.store.scheduleLayout())},updateColumnOrder(l,s){var u;((u=s.getColumnIndex)==null?void 0:u.call(s))!==s.no&&(e_(l._columns.value),e.$ready&&e.store.updateColumns())},removeColumn(l,s,u,c){var f;const d=p(l._columns)||[];if(u)(f=u.children)==null||f.splice(u.children.findIndex(h=>h.id===s.id),1),He(()=>{var h;((h=u.children)==null?void 0:h.length)===0&&delete u.children}),l._columns.value=Q4(d,u);else{const h=d.indexOf(s);h>-1&&(d.splice(h,1),l._columns.value=d)}const v=l.updateOrderFns.indexOf(c);v>-1&&l.updateOrderFns.splice(v,1),e.$ready&&(e.store.updateColumns(),e.store.scheduleLayout())},sort(l,s){const{prop:u,order:c,init:d}=s;if(u){const v=p(l.columns).find(f=>f.property===u);v&&(v.order=c,e.store.updateSort(v,u,c),e.store.commit("changeSortCondition",{init:d}))}},changeSortCondition(l,s){const{sortingColumn:u,sortProp:c,sortOrder:d}=l,v=p(u),f=p(c),h=p(d);E1(h)&&(l.sortingColumn.value=null,l.sortProp.value=null),e.store.execQuery({filter:!0}),(!s||!(s.silent||s.init))&&e.emit("sort-change",{column:v,prop:f,order:h}),e.store.updateTableScrollY()},filterChange(l,s){const{column:u,values:c,silent:d}=s,v=e.store.updateFilters(u,c);e.store.execQuery(),d||e.emit("filter-change",v),e.store.updateTableScrollY()},toggleAllSelection(){var l,s;(s=(l=e.store).toggleAllSelection)==null||s.call(l)},rowSelectedChanged(l,s){e.store.toggleRowSelection(s),e.store.updateAllSelected()},setHoverRow(l,s){l.hoverRow.value=s},setCurrentRow(l,s){e.store.updateCurrentRow(s)}},commit:function(l,...s){const u=e.store.mutations;if(u[l])u[l].apply(e,[e.store.states,...s]);else throw new Error(`Action not found: ${l}`)},updateTableScrollY:function(){He(()=>e.layout.updateScrollY.apply(e.layout))}}}const Iw={rowKey:"rowKey",defaultExpandAll:"defaultExpandAll",rowExpandable:"rowExpandable",selectOnIndeterminate:"selectOnIndeterminate",indent:"indent",lazy:"lazy","treeProps.hasChildren":{key:"lazyColumnIdentifier",default:"hasChildren"},"treeProps.children":{key:"childrenColumnName",default:"children"},"treeProps.checkStrictly":{key:"checkStrictly",default:!1}};function Dye(e,t){if(!e)throw new Error("Table is required.");const n=Pye();return n.toggleAllSelection=Yi(n._toggleAllSelection,10),Object.keys(Iw).forEach(a=>{ED(AD(t,a),a,n)}),Oye(n,t),n}function Oye(e,t){Object.keys(Iw).forEach(n=>{ye(()=>AD(t,n),a=>{ED(a,n,e)})})}function ED(e,t,n){let a=e,r=Iw[t];_t(r)&&(a=a||r.default,r=r.key),n.states[r].value=a}function AD(e,t){if(t.includes(".")){const n=t.split(".");let a=e;return n.forEach(r=>{a=a[r]}),a}else return e[t]}var $ye=class{constructor(e){this.observers=[],this.table=null,this.store=null,this.columns=[],this.fit=!0,this.showHeader=!0,this.heightMap={},this.height=F(null),this.scrollX=F(!1),this.scrollY=F(!1),this.bodyWidth=F(null),this.fixedWidth=F(null),this.rightFixedWidth=F(null),this.gutterWidth=0;for(const t in e)en(e,t)&&(xn(this[t])?this[t].value=e[t]:this[t]=e[t]);if(!this.table)throw new Error("Table is required for Table Layout");if(!this.store)throw new Error("Store is required for Table Layout")}updateScrollY(){const e=this.height.value;if(E1(e))return!1;const t=this.table.refs.scrollBarRef;if(this.table.vnode.el&&(t!=null&&t.wrapRef)){let n=!0;const a=this.scrollY.value;return n=t.wrapRef.scrollHeight>t.wrapRef.clientHeight,this.scrollY.value=n,a!==n}return!1}setHeight(e,t="height"){if(!qt)return;const n=this.table.vnode.el;if(e=Sye(e),this.height.value=Number(e),this.heightMap[t]=e,!n&&(e||e===0)){He(()=>{this.heightMap[t]===e&&this.setHeight(e,t)});return}n&&Je(e)?(n.style[t]=`${e}px`,this.updateElsHeight()):n&&Ye(e)&&(n.style[t]=e,this.updateElsHeight())}setMaxHeight(e){this.setHeight(e,"max-height")}getFlattenColumns(){const e=[];return this.table.store.states.columns.value.forEach(t=>{t.isColumnGroup?e.push.apply(e,t.columns):e.push(t)}),e}updateElsHeight(){this.updateScrollY(),this.notifyObservers("scrollable")}headerDisplayNone(e){if(!e)return!0;let t=e;for(;t.tagName!=="DIV";){if(getComputedStyle(t).display==="none")return!0;t=t.parentElement}return!1}updateColumnsWidth(){var l;if(!qt)return;const e=this.fit,t=(l=this.table.vnode.el)==null?void 0:l.clientWidth;let n=0;const a=this.getFlattenColumns(),r=a.filter(s=>!Je(s.width));if(a.forEach(s=>{Je(s.width)&&s.realWidth&&(s.realWidth=null)}),r.length>0&&e){if(a.forEach(s=>{n+=Number(s.width||s.minWidth||80)}),n<=t){this.scrollX.value=!1;const s=t-n;if(r.length===1)r[0].realWidth=Number(r[0].minWidth||80)+s;else{const u=s/r.reduce((d,v)=>d+Number(v.minWidth||80),0);let c=0;r.forEach((d,v)=>{if(v===0)return;const f=Math.floor(Number(d.minWidth||80)*u);c+=f,d.realWidth=Number(d.minWidth||80)+f}),r[0].realWidth=Number(r[0].minWidth||80)+s-c}}else this.scrollX.value=!0,r.forEach(s=>{s.realWidth=Number(s.minWidth)});this.bodyWidth.value=Math.max(n,t),this.table.state.resizeState.value.width=this.bodyWidth.value}else a.forEach(s=>{!s.width&&!s.minWidth?s.realWidth=80:s.realWidth=Number(s.width||s.minWidth),n+=s.realWidth}),this.scrollX.value=n>t,this.bodyWidth.value=n;const o=this.store.states.fixedColumns.value;if(o.length>0){let s=0;o.forEach(u=>{s+=Number(u.realWidth||u.width)}),this.fixedWidth.value=s}const i=this.store.states.rightFixedColumns.value;if(i.length>0){let s=0;i.forEach(u=>{s+=Number(u.realWidth||u.width)}),this.rightFixedWidth.value=s}this.notifyObservers("columns")}addObserver(e){this.observers.push(e)}removeObserver(e){const t=this.observers.indexOf(e);t!==-1&&this.observers.splice(t,1)}notifyObservers(e){this.observers.forEach(t=>{var n,a;switch(e){case"columns":(n=t.state)==null||n.onColumnsChange(this);break;case"scrollable":(a=t.state)==null||a.onScrollableChange(this);break;default:throw new Error(`Table Layout don't have event ${e}.`)}})}};const di=Symbol("ElTable"),ID=e=>{const t=[];return e.forEach(n=>{n.children?(t.push(n),t.push.apply(t,ID(n.children))):t.push(n)}),t},LD=e=>{let t=1;const n=(r,o)=>{if(o&&(r.level=o.level+1,t{n(l,r),i+=l.colSpan}),r.colSpan=i}else r.colSpan=1};e.forEach(r=>{r.level=1,n(r,void 0)});const a=[];for(let r=0;r{r.children?(r.rowSpan=1,r.children.forEach(o=>o.isSubColumn=!0)):r.rowSpan=t-r.level+1,a[r.level-1].push(r)}),a};function Rye(e){const t=Ze(di),n=D(()=>LD(e.store.states.originColumns.value));return{isGroup:D(()=>{const o=n.value.length>1;return o&&t&&(t.state.isGroup.value=!0),o}),toggleAllSelection:o=>{o.stopPropagation(),t==null||t.store.commit("toggleAllSelection")},columnRows:n}}var Nye=q({name:"ElTableFilterPanel",components:{ElCheckbox:Zo,ElCheckboxGroup:cw,ElScrollbar:ni,ElTooltip:Ma,ElIcon:Xe,ArrowDown:ui,ArrowUp:ip},props:{placement:{type:String,default:"bottom-start"},store:{type:Object},column:{type:Object},upDataColumn:{type:Function},appendTo:Tn.appendTo},setup(e){const t=Et(),{t:n}=Vt(),a=ke("table-filter"),r=t==null?void 0:t.parent;e.column&&!r.filterPanels.value[e.column.id]&&(r.filterPanels.value[e.column.id]=t);const o=F(null),i=F(null),l=F(0),s=D(()=>e.column&&e.column.filters),u=D(()=>e.column&&e.column.filterClassName?`${a.b()} ${e.column.filterClassName}`:a.b()),c=D({get:()=>{var T;return(((T=e.column)==null?void 0:T.filteredValue)||[])[0]},set:T=>{var M;if(e.column){const E=[...e.column.filteredValue??[]];Qr(T)?E.splice(0,1):E.splice(0,1,T),(M=e.upDataColumn)==null||M.call(e,"filteredValue",E)}}}),d=D({get(){return e.column?e.column.filteredValue||[]:[]},set(T){var M;e.column&&((M=e.upDataColumn)==null||M.call(e,"filteredValue",T))}}),v=D(()=>e.column?e.column.filterMultiple:!0),f=T=>T.value===c.value,h=()=>{var T;(T=o.value)==null||T.onClose()},m=()=>{b(d.value),h()},g=()=>{d.value=[],b(d.value),h()},y=(T,M)=>{c.value=T,l.value=M,Qr(T)?b([]):b(d.value),h()},b=T=>{var M,E;(M=e.store)==null||M.commit("filterChange",{column:e.column,values:T}),(E=e.store)==null||E.updateAllSelected()},_=()=>{var T,M;(T=i.value)==null||T.focus(),!v.value&&S(),e.column&&((M=e.upDataColumn)==null||M.call(e,"filterOpened",!0))},w=()=>{var T;e.column&&((T=e.upDataColumn)==null||T.call(e,"filterOpened",!1))},S=()=>{if(Qr(c)){l.value=0;return}const T=(s.value||[]).findIndex(M=>M.value===c.value);l.value=T>=0?T+1:0};return{multiple:v,filterClassName:u,filteredValue:d,filterValue:c,filters:s,handleConfirm:m,handleReset:g,handleSelect:y,isPropAbsent:Qr,isActive:f,t:n,ns:a,tooltipRef:o,rootRef:i,checkedIndex:l,handleShowTooltip:_,handleHideTooltip:w,handleKeydown:T=>{var P,R;const M=mn(T),E=(s.value?s.value.length:0)+1;let A=l.value,I=!0;switch(M){case Pe.down:case Pe.right:A=(A+1)%E;break;case Pe.up:case Pe.left:A=(A-1+E)%E;break;case Pe.tab:h(),I=!1;break;case Pe.enter:case Pe.space:if(A===0)y(null,0);else{const G=(s.value||[])[A-1];G.value&&y(G.value,A)}break;default:I=!1;break}I&&T.preventDefault(),l.value=A,(R=(P=i.value)==null?void 0:P.querySelector(`.${a.e("list-item")}:nth-child(${A+1})`))==null||R.focus()}}}});const Bye=["disabled"],Vye=["tabindex","aria-checked"],zye=["tabindex","aria-checked","onClick"],Fye=["aria-label"];function Hye(e,t,n,a,r,o){const i=be("el-checkbox"),l=be("el-checkbox-group"),s=be("el-scrollbar"),u=be("arrow-up"),c=be("arrow-down"),d=be("el-icon"),v=be("el-tooltip");return x(),ne(v,{ref:"tooltipRef",offset:0,placement:e.placement,"show-arrow":!1,trigger:"click",role:"dialog",teleported:"",effect:"light",pure:"",loop:"","popper-class":e.filterClassName,persistent:"","append-to":e.appendTo,onShow:e.handleShowTooltip,onHide:e.handleHideTooltip},{content:$(()=>[e.multiple?(x(),O("div",{key:0,ref:"rootRef",tabindex:"-1",class:U(e.ns.e("multiple"))},[C("div",{class:U(e.ns.e("content"))},[L(s,{"wrap-class":e.ns.e("wrap")},{default:$(()=>[L(l,{modelValue:e.filteredValue,"onUpdate:modelValue":t[0]||(t[0]=f=>e.filteredValue=f),class:U(e.ns.e("checkbox-group"))},{default:$(()=>[(x(!0),O(Ve,null,dt(e.filters,f=>(x(),ne(i,{key:f.value,value:f.value},{default:$(()=>[ue(Q(f.text),1)]),_:2},1032,["value"]))),128))]),_:1},8,["modelValue","class"])]),_:1},8,["wrap-class"])],2),C("div",{class:U(e.ns.e("bottom"))},[C("button",{class:U(e.ns.is("disabled",e.filteredValue.length===0)),disabled:e.filteredValue.length===0,type:"button",onClick:t[1]||(t[1]=(...f)=>e.handleConfirm&&e.handleConfirm(...f))},Q(e.t("el.table.confirmFilter")),11,Bye),C("button",{type:"button",onClick:t[2]||(t[2]=(...f)=>e.handleReset&&e.handleReset(...f))},Q(e.t("el.table.resetFilter")),1)],2)],2)):(x(),O("ul",{key:1,ref:"rootRef",tabindex:"-1",role:"radiogroup",class:U(e.ns.e("list")),onKeydown:t[4]||(t[4]=(...f)=>e.handleKeydown&&e.handleKeydown(...f))},[C("li",{role:"radio",class:U([e.ns.e("list-item"),e.ns.is("active",e.isPropAbsent(e.filterValue))]),tabindex:e.checkedIndex===0?0:-1,"aria-checked":e.isPropAbsent(e.filterValue),onClick:t[3]||(t[3]=f=>e.handleSelect(null,0))},Q(e.t("el.table.clearFilter")),11,Vye),(x(!0),O(Ve,null,dt(e.filters,(f,h)=>(x(),O("li",{key:f.value,role:"radio",class:U([e.ns.e("list-item"),e.ns.is("active",e.isActive(f))]),tabindex:e.checkedIndex===h+1?0:-1,"aria-checked":e.isActive(f),onClick:m=>e.handleSelect(f.value,h+1)},Q(f.text),11,zye))),128))],34))]),default:$(()=>{var f;return[C("button",{type:"button",class:U(`${e.ns.namespace.value}-table__column-filter-trigger`),"aria-label":e.t("el.table.filterLabel",{column:((f=e.column)==null?void 0:f.label)||""})},[L(d,null,{default:$(()=>[ce(e.$slots,"filter-icon",{},()=>{var h;return[(h=e.column)!=null&&h.filterOpened?(x(),ne(u,{key:0})):(x(),ne(c,{key:1}))]})]),_:3})],10,Fye)]}),_:3},8,["placement","popper-class","append-to","onShow","onHide"])}var Wye=ya(Nye,[["render",Hye]]);function Lw(e){const t=Et();g1(()=>{n.value.addObserver(t)}),mt(()=>{a(n.value),r(n.value)}),oi(()=>{a(n.value),r(n.value)}),Us(()=>{n.value.removeObserver(t)});const n=D(()=>{const o=e.layout;if(!o)throw new Error("Can not find table layout.");return o}),a=o=>{var u;const i=((u=e.vnode.el)==null?void 0:u.querySelectorAll("colgroup > col"))||[];if(!i.length)return;const l=o.getFlattenColumns(),s={};l.forEach(c=>{s[c.id]=c});for(let c=0,d=i.length;c{var s,u;const i=((s=e.vnode.el)==null?void 0:s.querySelectorAll("colgroup > col[name=gutter]"))||[];for(let c=0,d=i.length;c{m.stopPropagation()},o=(m,g)=>{!g.filters&&g.sortable?h(m,g,!1):g.filterable&&!g.sortable&&r(m),a==null||a.emit("header-click",g,m)},i=(m,g)=>{a==null||a.emit("header-contextmenu",g,m)},l=F(null),s=F(!1),u=F(),c=(m,g)=>{var y,b,_;if(qt&&!(g.children&&g.children.length>0)&&l.value&&e.border&&l.value.id===g.id){s.value=!0;const w=a;t("set-drag-visible",!0);const S=(y=w==null?void 0:w.vnode.el)==null?void 0:y.getBoundingClientRect().left,k=(_=(b=n==null?void 0:n.vnode)==null?void 0:b.el)==null?void 0:_.querySelector(`th.${g.id}`),T=k.getBoundingClientRect(),M=T.left-S+30;Eo(k,"noclick"),u.value={startMouseLeft:m.clientX,startLeft:T.right-S,startColumnLeft:T.left-S,tableLeft:S};const E=w==null?void 0:w.refs.resizeProxy;E.style.left=`${u.value.startLeft}px`,document.onselectstart=function(){return!1},document.ondragstart=function(){return!1};const A=P=>{const R=P.clientX-u.value.startMouseLeft,G=u.value.startLeft+R;E.style.left=`${Math.max(M,G)}px`},I=()=>{if(s.value){const{startColumnLeft:P,startLeft:R}=u.value;g.width=g.realWidth=Number.parseInt(E.style.left,10)-P,w==null||w.emit("header-dragend",g.width,R-P,g,m),requestAnimationFrame(()=>{e.store.scheduleLayout(!1,!0)}),document.body.style.cursor="",s.value=!1,l.value=null,u.value=void 0,t("set-drag-visible",!1)}document.removeEventListener("mousemove",A),document.removeEventListener("mouseup",I),document.onselectstart=null,document.ondragstart=null,setTimeout(()=>{ir(k,"noclick")},0)};document.addEventListener("mousemove",A),document.addEventListener("mouseup",I)}},d=(m,g)=>{var E;if(!e.border||g.children&&g.children.length>0)return;const y=m.target,b=Vr(y)?y.closest("th"):null;if(!b)return;const _=Ni(b,"is-sortable");if(_){const A=s.value?"col-resize":"";b.style.cursor=A;const I=b.querySelector(".caret-wrapper");I&&(I.style.cursor=A)}if(!g.resizable||s.value){l.value=null;return}const w=b.getBoundingClientRect(),S=((E=b.parentNode)==null?void 0:E.lastElementChild)===b,k=e.allowDragLastColumn||!S,T=w.width>12&&w.right-m.clientX<8&&k,M=T?"col-resize":"";document.body.style.cursor=M,l.value=T?g:null,_&&(b.style.cursor=M)},v=()=>{!qt||s.value||(document.body.style.cursor="")},f=({order:m,sortOrders:g})=>{if(m==="")return g[0];const y=g.indexOf(m||null);return g[y>g.length-2?0:y+1]},h=(m,g,y)=>{var E;m.stopPropagation();const b=g.order===y?null:y||f(g),_=(E=m.target)==null?void 0:E.closest("th");if(_&&Ni(_,"noclick")){ir(_,"noclick");return}if(!g.sortable)return;const w=m.currentTarget;if(["ascending","descending"].some(A=>Ni(w,A)&&!g.sortOrders.includes(A)))return;const S=e.store.states;let k=S.sortProp.value,T;const M=S.sortingColumn.value;(M!==g||M===g&&E1(M.order))&&(M&&(M.order=null),S.sortingColumn.value=g,k=g.property??null),b?T=g.order=b:T=g.order=null,S.sortProp.value=k,S.sortOrder.value=T,a==null||a.store.commit("changeSortCondition")};return{handleHeaderClick:o,handleHeaderContextMenu:i,handleMouseDown:c,handleMouseMove:d,handleMouseOut:v,handleSortClick:h,handleFilterClick:r}}function Uye(e){const t=Ze(di),n=ke("table");return{getHeaderRowStyle:l=>{const s=t==null?void 0:t.props.headerRowStyle;return Qe(s)?s.call(null,{rowIndex:l}):s},getHeaderRowClass:l=>{const s=[],u=t==null?void 0:t.props.headerRowClassName;return Ye(u)?s.push(u):Qe(u)&&s.push(u.call(null,{rowIndex:l})),s.join(" ")},getHeaderCellStyle:(l,s,u,c)=>{let d=(t==null?void 0:t.props.headerCellStyle)??{};Qe(d)&&(d=d.call(null,{rowIndex:l,columnIndex:s,row:u,column:c}));const v=Aw(s,c.fixed,e.store,u);return jd(v,"left"),jd(v,"right"),Object.assign({},d,v)},getHeaderCellClass:(l,s,u,c)=>{const d=Ew(n.b(),s,c.fixed,e.store,u),v=[c.id,c.order,c.headerAlign,c.className,c.labelClassName,...d];c.children||v.push("is-leaf"),c.sortable&&v.push("is-sortable");const f=t==null?void 0:t.props.headerCellClassName;return Ye(f)?v.push(f):Qe(f)&&v.push(f.call(null,{rowIndex:l,columnIndex:s,row:u,column:c})),v.push(n.e("cell")),v.filter(h=>!!h).join(" ")}}}var Nx=q({name:"ElTableHeader",components:{ElCheckbox:Zo},props:{fixed:{type:String,default:""},store:{required:!0,type:Object},border:Boolean,defaultSort:{type:Object,default:()=>({prop:"",order:""})},appendFilterPanelTo:{type:String},allowDragLastColumn:{type:Boolean}},setup(e,{emit:t}){const n=Et(),a=Ze(di),r=ke("table"),o=F({}),{onColumnsChange:i,onScrollableChange:l}=Lw(a),s=(a==null?void 0:a.props.tableLayout)==="auto",u=nn(new Map),c=F();let d;const v=()=>{d&&(clearTimeout(d),d=void 0),d=setTimeout(()=>{d=void 0;const P=c.value;P&&u.size>0&&(u.forEach((R,G)=>{const K=P.querySelector(`.${G.replace(/\s/g,".")}`);K&&(R.width=K.getBoundingClientRect().width||R.width)}),u.clear())})};ye(u,v),rn(()=>{d&&(clearTimeout(d),d=void 0)}),mt(async()=>{await He(),await He();const{prop:P,order:R}=e.defaultSort;a==null||a.store.commit("sort",{prop:P,order:R,init:!0}),v()});const{handleHeaderClick:f,handleHeaderContextMenu:h,handleMouseDown:m,handleMouseMove:g,handleMouseOut:y,handleSortClick:b,handleFilterClick:_}=Kye(e,t),{getHeaderRowStyle:w,getHeaderRowClass:S,getHeaderCellStyle:k,getHeaderCellClass:T}=Uye(e),{isGroup:M,toggleAllSelection:E,columnRows:A}=Rye(e),{t:I}=Vt();return n.state={onColumnsChange:i,onScrollableChange:l},n.filterPanels=o,{ns:r,t:I,filterPanels:o,onColumnsChange:i,onScrollableChange:l,columnRows:A,getHeaderRowClass:S,getHeaderRowStyle:w,getHeaderCellClass:T,getHeaderCellStyle:k,handleHeaderClick:f,handleHeaderContextMenu:h,handleMouseDown:m,handleMouseMove:g,handleMouseOut:y,handleSortClick:b,handleFilterClick:_,isGroup:M,toggleAllSelection:E,saveIndexSelection:u,isTableLayoutAuto:s,theadRef:c,updateFixedColumnStyle:v}},render(){const{ns:e,t,isGroup:n,columnRows:a,getHeaderCellStyle:r,getHeaderCellClass:o,getHeaderRowClass:i,getHeaderRowStyle:l,handleHeaderClick:s,handleHeaderContextMenu:u,handleMouseDown:c,handleMouseMove:d,handleSortClick:v,handleMouseOut:f,store:h,$parent:m,saveIndexSelection:g,isTableLayoutAuto:y}=this;let b=1;return ot("thead",{ref:"theadRef",class:e.is("group",n)},a.map((_,w)=>ot("tr",{class:i(w),key:w,style:l(w)},_.map((S,k)=>{S.rowSpan>b&&(b=S.rowSpan);const T=o(w,k,_,S);return y&&S.fixed&&g.set(T,S),ot("th",{class:T,colspan:S.colSpan,key:`${S.id}-thead`,rowspan:S.rowSpan,scope:S.colSpan>1?"colgroup":"col",ariaSort:S.sortable?S.order:void 0,style:r(w,k,_,S),onClick:M=>{var E;(E=M.currentTarget)!=null&&E.classList.contains("noclick")||s(M,S)},onContextmenu:M=>u(M,S),onMousedown:M=>c(M,S),onMousemove:M=>d(M,S),onMouseout:f},[ot("div",{class:["cell",S.filteredValue&&S.filteredValue.length>0?"highlight":""]},[S.renderHeader?S.renderHeader({column:S,$index:k,store:h,_self:m}):S.label,S.sortable&&ot("button",{type:"button",class:"caret-wrapper","aria-label":t("el.table.sortLabel",{column:S.label||""}),onClick:M=>v(M,S)},[ot("i",{onClick:M=>v(M,S,"ascending"),class:"sort-caret ascending"}),ot("i",{onClick:M=>v(M,S,"descending"),class:"sort-caret descending"})]),S.filterable&&ot(Wye,{store:h,placement:S.filterPlacement||"bottom-start",appendTo:m==null?void 0:m.appendFilterPanelTo,column:S,upDataColumn:(M,E)=>{S[M]=E}},{"filter-icon":()=>S.renderFilterIcon?S.renderFilterIcon({filterOpened:S.filterOpened}):null})])])}))))}});function Gye(e){const t=Ze(di),n=F(""),a=F(ot("div")),r=(h,m,g)=>{var S,k;const y=t,b=dy(h);let _=null;const w=(S=y==null?void 0:y.vnode.el)==null?void 0:S.dataset.prefix;b&&(_=Ox({columns:((k=e.store)==null?void 0:k.states.columns.value)??[]},b,w),_&&(y==null||y.emit(`cell-${g}`,m,_,b,h))),y==null||y.emit(`row-${g}`,m,_,h)},o=(h,m)=>{r(h,m,"dblclick")},i=(h,m)=>{var g;(g=e.store)==null||g.commit("setCurrentRow",m),r(h,m,"click")},l=(h,m)=>{r(h,m,"contextmenu")},s=Yi(h=>{var m;(m=e.store)==null||m.commit("setHoverRow",h)},30),u=Yi(()=>{var h;(h=e.store)==null||h.commit("setHoverRow",null)},30),c=h=>{const m=window.getComputedStyle(h,null);return{left:Number.parseInt(m.paddingLeft,10)||0,right:Number.parseInt(m.paddingRight,10)||0,top:Number.parseInt(m.paddingTop,10)||0,bottom:Number.parseInt(m.paddingBottom,10)||0}},d=(h,m,g)=>{var b;let y=(b=m==null?void 0:m.target)==null?void 0:b.parentNode;for(;h>1&&(y=y==null?void 0:y.nextSibling,!(!y||y.nodeName!=="TR"));)g(y,"hover-row hover-fixed-row"),h--};return{handleDoubleClick:o,handleClick:i,handleContextMenu:l,handleMouseEnter:s,handleMouseLeave:u,handleCellMouseEnter:(h,m,g)=>{var z,B,W;if(!t)return;const y=t,b=dy(h),_=(z=y==null?void 0:y.vnode.el)==null?void 0:z.dataset.prefix;let w=null;if(b){if(w=Ox({columns:((B=e.store)==null?void 0:B.states.columns.value)??[]},b,_),!w)return;b.rowSpan>1&&d(b.rowSpan,h,Eo);const N=y.hoverState={cell:b,column:w,row:m};y==null||y.emit("cell-mouse-enter",N.row,N.column,N.cell,h)}if(!g){(Wn==null?void 0:Wn.trigger)===b&&(Wn==null||Wn());return}const S=h.target.querySelector(".cell");if(!(Ni(S,`${_}-tooltip`)&&S.childNodes.length&&((W=S.textContent)!=null&&W.trim())))return;const k=document.createRange();k.setStart(S,0),k.setEnd(S,S.childNodes.length);const{width:T,height:M}=k.getBoundingClientRect(),{width:E,height:A}=S.getBoundingClientRect(),{top:I,left:P,right:R,bottom:G}=c(S),K=P+R,H=I+G;Vi(T+K,E)||Vi(M+H,A)||Vi(S.scrollWidth,E)?Tye(g,((b==null?void 0:b.innerText)||(b==null?void 0:b.textContent))??"",m,w,b,y):(Wn==null?void 0:Wn.trigger)===b&&(Wn==null||Wn())},handleCellMouseLeave:h=>{const m=dy(h);if(!m)return;m.rowSpan>1&&d(m.rowSpan,h,ir);const g=t==null?void 0:t.hoverState;t==null||t.emit("cell-mouse-leave",g==null?void 0:g.row,g==null?void 0:g.column,g==null?void 0:g.cell,h)},tooltipContent:n,tooltipTrigger:a}}function Yye(e){const t=Ze(di),n=ke("table");return{getRowStyle:(u,c)=>{const d=t==null?void 0:t.props.rowStyle;return Qe(d)?d.call(null,{row:u,rowIndex:c}):d||null},getRowClass:(u,c,d)=>{var h;const v=[n.e("row")];t!=null&&t.props.highlightCurrentRow&&u===((h=e.store)==null?void 0:h.states.currentRow.value)&&v.push("current-row"),e.stripe&&d%2===1&&v.push(n.em("row","striped"));const f=t==null?void 0:t.props.rowClassName;return Ye(f)?v.push(f):Qe(f)&&v.push(f.call(null,{row:u,rowIndex:c})),v},getCellStyle:(u,c,d,v)=>{const f=t==null?void 0:t.props.cellStyle;let h=f??{};Qe(f)&&(h=f.call(null,{rowIndex:u,columnIndex:c,row:d,column:v}));const m=Aw(c,e==null?void 0:e.fixed,e.store);return jd(m,"left"),jd(m,"right"),Object.assign({},h,m)},getCellClass:(u,c,d,v,f)=>{const h=Ew(n.b(),c,e==null?void 0:e.fixed,e.store,void 0,f),m=[v.id,v.align,v.className,...h],g=t==null?void 0:t.props.cellClassName;return Ye(g)?m.push(g):Qe(g)&&m.push(g.call(null,{rowIndex:u,columnIndex:c,row:d,column:v})),m.push(n.e("cell")),m.filter(y=>!!y).join(" ")},getSpan:(u,c,d,v)=>{let f=1,h=1;const m=t==null?void 0:t.props.spanMethod;if(Qe(m)){const g=m({row:u,column:c,rowIndex:d,columnIndex:v});Ae(g)?(f=g[0],h=g[1]):_t(g)&&(f=g.rowspan,h=g.colspan)}return{rowspan:f,colspan:h}},getColspanRealWidth:(u,c,d)=>{if(c<1)return u[d].realWidth;const v=u.map(({realWidth:f,width:h})=>f||h).slice(d,d+c);return Number(v.reduce((f,h)=>Number(f)+Number(h),-1))}}}const jye=["colspan","rowspan"];var qye=q({name:"TableTdWrapper",__name:"td-wrapper",props:{colspan:{type:Number,default:1},rowspan:{type:Number,default:1}},setup(e){return(t,n)=>(x(),O("td",{colspan:e.colspan,rowspan:e.rowspan},[ce(t.$slots,"default")],8,jye))}}),Xye=qye;function Zye(e){const t=Ze(di),n=ke("table"),{handleDoubleClick:a,handleClick:r,handleContextMenu:o,handleMouseEnter:i,handleMouseLeave:l,handleCellMouseEnter:s,handleCellMouseLeave:u,tooltipContent:c,tooltipTrigger:d}=Gye(e),{getRowStyle:v,getRowClass:f,getCellStyle:h,getCellClass:m,getSpan:g,getColspanRealWidth:y}=Yye(e);let b=-1;const _=D(()=>{var M;return(M=e.store)==null?void 0:M.states.columns.value.findIndex(({type:E})=>E==="default")}),w=(M,E)=>{var I;const A=(I=t==null?void 0:t.props)==null?void 0:I.rowKey;return A?ra(M,A):E},S=(M,E,A,I=!1)=>{const{tooltipEffect:P,tooltipOptions:R,store:G}=e,{indent:K,columns:H}=G.states,z=[];let B=!0;return A&&(z.push(n.em("row",`level-${A.level}`)),B=!!A.display),E===0&&(b=-1),e.stripe&&B&&b++,z.push(...f(M,E,b)),ot("tr",{style:[B?null:{display:"none"},v(M,E)],class:z,key:w(M,E),onDblclick:W=>a(W,M),onClick:W=>r(W,M),onContextmenu:W=>o(W,M),onMouseenter:()=>i(E),onMouseleave:l},H.value.map((W,N)=>{const{rowspan:V,colspan:Y}=g(M,W,E,N);if(!V||!Y)return null;const X=Object.assign({},W);X.realWidth=y(H.value,Y,N);const ee={store:G,_self:e.context||t,column:X,row:M,$index:E,cellIndex:N,expanded:I};N===_.value&&A&&(ee.treeNode={indent:A.level&&A.level*K.value,level:A.level},vn(A.expanded)&&(ee.treeNode.expanded=A.expanded,"loading"in A&&(ee.treeNode.loading=A.loading),"noLazyChildren"in A&&(ee.treeNode.noLazyChildren=A.noLazyChildren)));const re=`${w(M,E)},${N}`,ie=X.columnKey||X.rawColumnKey||"",le=W.showOverflowTooltip&&xA({effect:P},R,W.showOverflowTooltip);return ot(Xye,{style:h(E,N,M,W),class:m(E,N,M,W,Y-1),key:`${ie}${re}`,rowspan:V,colspan:Y,onMouseenter:ve=>s(ve,M,le),onMouseleave:u},{default:()=>k(N,W,ee)})}))},k=(M,E,A)=>E.renderCell(A);return{wrappedRowRender:(M,E)=>{const A=e.store,{isRowExpanded:I,assertRowKey:P}=A,{treeData:R,lazyTreeNodeMap:G,childrenColumnName:K,rowKey:H}=A.states,z=A.states.columns.value;if(z.some(({type:B})=>B==="expand")){const B=I(M),W=S(M,E,void 0,B),N=t==null?void 0:t.renderExpanded;if(!N)return console.error("[Element Error]renderExpanded is required."),W;const V=[[W]];return(t.props.preserveExpandedContent||B)&&V[0].push(ot("tr",{key:`expanded-row__${W.key}`,style:{display:B?"":"none"}},[ot("td",{colspan:z.length,class:`${n.e("cell")} ${n.e("expanded-cell")}`},[N({row:M,$index:E,store:A,expanded:B})])])),V}else if(Object.keys(R.value).length){P();const B=ra(M,H.value);let W=R.value[B],N=null;W&&(N={expanded:W.expanded,level:W.level,display:!0,noLazyChildren:void 0,loading:void 0},vn(W.lazy)&&(N&&vn(W.loaded)&&W.loaded&&(N.noLazyChildren=!(W.children&&W.children.length)),N.loading=W.loading));const V=[S(M,E,N??void 0)];if(W){let Y=0;const X=(ee,re)=>{ee&&ee.length&&re&&ee.forEach(ie=>{const le={display:re.display&&re.expanded,level:re.level+1,expanded:!1,noLazyChildren:!1,loading:!1},ve=ra(ie,H.value);if(Qr(ve))throw new Error("For nested data item, row-key is required.");W={...R.value[ve]},W&&(le.expanded=W.expanded,W.level=W.level||le.level,W.display=!!(W.expanded&&le.display),vn(W.lazy)&&(vn(W.loaded)&&W.loaded&&(le.noLazyChildren=!(W.children&&W.children.length)),le.loading=W.loading)),Y++,V.push(S(ie,E+Y,le)),W&&X(G.value[ve]||ie[K.value],W)})};W.display=!0,X(G.value[B]||M[K.value],W)}return V}else return S(M,E,void 0)},tooltipContent:c,tooltipTrigger:d}}const Jye={store:{required:!0,type:Object},stripe:Boolean,tooltipEffect:String,tooltipOptions:{type:Object},context:{default:()=>({}),type:Object},rowClassName:[String,Function],rowStyle:[Object,Function],fixed:{type:String,default:""},highlight:Boolean};var Qye=q({name:"ElTableBody",props:Jye,setup(e){var c;const t=Et(),n=Ze(di),a=ke("table"),{wrappedRowRender:r,tooltipContent:o,tooltipTrigger:i}=Zye(e),{onColumnsChange:l,onScrollableChange:s}=Lw(n),u=[];return ye((c=e.store)==null?void 0:c.states.hoverRow,(d,v)=>{var y,b;const f=t==null?void 0:t.vnode.el,h=Array.from((f==null?void 0:f.children)||[]).filter(_=>_==null?void 0:_.classList.contains(`${a.e("row")}`));let m=d;const g=(y=h[m])==null?void 0:y.childNodes;if(g!=null&&g.length){let _=0;Array.from(g).reduce((w,S,k)=>{var T,M;return((T=g[k])==null?void 0:T.colSpan)>1&&(_=(M=g[k])==null?void 0:M.colSpan),S.nodeName!=="TD"&&_===0&&w.push(k),_>0&&_--,w},[]).forEach(w=>{var S;for(m=d;m>0;){const k=(S=h[m-1])==null?void 0:S.childNodes;if(k[w]&&k[w].nodeName==="TD"&&k[w].rowSpan>1){Eo(k[w],"hover-cell"),u.push(k[w]);break}m--}})}else u.forEach(_=>ir(_,"hover-cell")),u.length=0;!((b=e.store)!=null&&b.states.isComplex.value)||!qt||Wr(()=>{const _=h[v],w=h[d];_&&!_.classList.contains("hover-fixed-row")&&ir(_,"hover-row"),w&&Eo(w,"hover-row")})}),Us(()=>{Wn==null||Wn()}),{ns:a,onColumnsChange:l,onScrollableChange:s,wrappedRowRender:r,tooltipContent:o,tooltipTrigger:i}},render(){const{wrappedRowRender:e,store:t}=this;return ot("tbody",{tabIndex:-1},[((t==null?void 0:t.states.data.value)||[]).reduce((n,a)=>n.concat(e(a,n.length)),[])])}});function ebe(){var t;const e=(t=Ze(di))==null?void 0:t.store;return{leftFixedLeafCount:D(()=>(e==null?void 0:e.states.fixedLeafColumnsLength.value)??0),rightFixedLeafCount:D(()=>(e==null?void 0:e.states.rightFixedColumns.value.length)??0),columnsCount:D(()=>(e==null?void 0:e.states.columns.value.length)??0),leftFixedCount:D(()=>(e==null?void 0:e.states.fixedColumns.value.length)??0),rightFixedCount:D(()=>(e==null?void 0:e.states.rightFixedColumns.value.length)??0),columns:D(()=>(e==null?void 0:e.states.columns.value)??[])}}function tbe(e){const{columns:t}=ebe(),n=ke("table");return{getCellClasses:(o,i)=>{const l=o[i],s=[n.e("cell"),l.id,l.align,l.labelClassName,...Ew(n.b(),i,l.fixed,e.store)];return l.className&&s.push(l.className),l.children||s.push(n.is("leaf")),s},getCellStyles:(o,i)=>{const l=Aw(i,o.fixed,e.store);return jd(l,"left"),jd(l,"right"),l},columns:t}}var Bx=q({name:"ElTableFooter",props:{fixed:{type:String,default:""},store:{required:!0,type:Object},summaryMethod:Function,sumText:String,border:Boolean,defaultSort:{type:Object,default:()=>({prop:"",order:""})}},setup(e){const t=Ze(di),n=ke("table"),{getCellClasses:a,getCellStyles:r,columns:o}=tbe(e),{onScrollableChange:i,onColumnsChange:l}=Lw(t);return{ns:n,onScrollableChange:i,onColumnsChange:l,getCellClasses:a,getCellStyles:r,columns:o}},render(){const{columns:e,getCellStyles:t,getCellClasses:n,summaryMethod:a,sumText:r}=this,o=this.store.states.data.value;let i=[];return a?i=a({columns:e,data:o}):e.forEach((l,s)=>{if(s===0){i[s]=r;return}const u=o.map(f=>Number(f[l.property])),c=[];let d=!0;u.forEach(f=>{if(!Number.isNaN(+f)){d=!1;const h=`${f}`.split(".")[1];c.push(h?h.length:0)}});const v=Math.max.apply(null,c);d?i[s]="":i[s]=u.reduce((f,h)=>{const m=Number(h);return Number.isNaN(+m)?f:Number.parseFloat((f+h).toFixed(Math.min(v,20)))},0)}),ot(ot("tfoot",[ot("tr",{},[...e.map((l,s)=>ot("td",{key:s,colspan:l.colSpan,rowspan:l.rowSpan,class:n(e,s),style:t(l,s)},[ot("div",{class:["cell",l.labelClassName]},[i[s]])]))])]))}});function nbe(e){return{setCurrentRow:v=>{e.commit("setCurrentRow",v)},getSelectionRows:()=>e.getSelectionRows(),getHalfSelectionRows:()=>e.getHalfSelectionRows(),toggleRowSelection:(v,f,h=!0)=>{e.toggleRowSelection(v,f,!1,h),e.updateAllSelected()},clearSelection:()=>{e.clearSelection()},clearFilter:v=>{e.clearFilter(v)},toggleAllSelection:()=>{e.commit("toggleAllSelection")},toggleRowExpansion:(v,f)=>{e.toggleRowExpansionAdapter(v,f)},clearSort:()=>{e.clearSort()},sort:(v,f)=>{e.commit("sort",{prop:v,order:f})},updateKeyChildren:(v,f)=>{e.updateKeyChildren(v,f)}}}function abe(e,t,n,a){const r=F(!1),o=F(null),i=F(!1),l=K=>{i.value=K},s=F({width:null,height:null,headerHeight:null}),u=F(!1),c={display:"inline-block",verticalAlign:"middle"},d=F(),v=F(0),f=F(0),h=F(0),m=F(0),g=F(0);ye(()=>e.height,K=>{t.setHeight(K??null)},{immediate:!0}),ye(()=>e.maxHeight,K=>{t.setMaxHeight(K??null)},{immediate:!0}),ye(()=>[e.currentRowKey,n.states.rowKey],([K,H])=>{!p(H)||!p(K)||n.setCurrentRowKey(`${K}`)},{immediate:!0}),ye(()=>e.data,K=>{a.store.commit("setData",K)},{immediate:!0,deep:!0}),ur(()=>{e.expandRowKeys&&n.setExpandRowKeysAdapter(e.expandRowKeys)});const y=()=>{a.store.commit("setHoverRow",null),a.hoverState&&(a.hoverState=null)},b=(K,H)=>{const{pixelX:z,pixelY:B}=H;Math.abs(z)>=Math.abs(B)&&(a.refs.bodyWrapper.scrollLeft+=H.pixelX/5)},_=D(()=>e.height||e.maxHeight||n.states.fixedColumns.value.length>0||n.states.rightFixedColumns.value.length>0),w=D(()=>({width:t.bodyWidth.value?`${t.bodyWidth.value}px`:""})),S=()=>{_.value&&t.updateElsHeight(),t.updateColumnsWidth(),!(typeof window>"u")&&requestAnimationFrame(E)};mt(async()=>{await He(),n.updateColumns(),A(),requestAnimationFrame(S);const K=a.vnode.el,H=a.refs.headerWrapper;e.flexible&&K&&K.parentElement&&(K.parentElement.style.minWidth="0"),s.value={width:d.value=K.offsetWidth,height:K.offsetHeight,headerHeight:e.showHeader&&H?H.offsetHeight:null},n.states.columns.value.forEach(z=>{z.filteredValue&&z.filteredValue.length&&a.store.commit("filterChange",{column:z,values:z.filteredValue,silent:!0})}),a.$ready=!0});const k=(K,H)=>{if(!K)return;const z=Array.from(K.classList).filter(B=>!B.startsWith("is-scrolling-"));z.push(t.scrollX.value?H:"is-scrolling-none"),K.className=z.join(" ")},T=K=>{const{tableWrapper:H}=a.refs;k(H,K)},M=K=>{const{tableWrapper:H}=a.refs;return!!(H&&H.classList.contains(K))},E=function(){if(!a.refs.scrollBarRef)return;if(!t.scrollX.value){const V="is-scrolling-none";M(V)||T(V);return}const K=a.refs.scrollBarRef.wrapRef;if(!K)return;const{scrollLeft:H,offsetWidth:z,scrollWidth:B}=K,{headerWrapper:W,footerWrapper:N}=a.refs;W&&(W.scrollLeft=H),N&&(N.scrollLeft=H),H>=B-z-1?T("is-scrolling-right"):T(H===0?"is-scrolling-left":"is-scrolling-middle")},A=()=>{a.refs.scrollBarRef&&(a.refs.scrollBarRef.wrapRef&&on(a.refs.scrollBarRef.wrapRef,"scroll",E,{passive:!0}),e.fit?Mn(a.vnode.el,I):on(window,"resize",I),Mn(a.refs.tableInnerWrapper,()=>{var K,H;I(),(H=(K=a.refs)==null?void 0:K.scrollBarRef)==null||H.update()}))},I=()=>{var X,ee,re,ie;const K=a.vnode.el;if(!a.$ready||!K)return;let H=!1;const{width:z,height:B,headerHeight:W}=s.value,N=d.value=K.offsetWidth;z!==N&&(H=!0);const V=K.offsetHeight;(e.height||_.value)&&B!==V&&(H=!0);const Y=e.tableLayout==="fixed"?a.refs.headerWrapper:(X=a.refs.tableHeaderRef)==null?void 0:X.$el;e.showHeader&&(Y==null?void 0:Y.offsetHeight)!==W&&(H=!0),v.value=((ee=a.refs.tableWrapper)==null?void 0:ee.scrollHeight)||0,h.value=(Y==null?void 0:Y.scrollHeight)||0,m.value=((re=a.refs.footerWrapper)==null?void 0:re.offsetHeight)||0,g.value=((ie=a.refs.appendWrapper)==null?void 0:ie.offsetHeight)||0,f.value=v.value-h.value-m.value-g.value,H&&(s.value={width:N,height:V,headerHeight:e.showHeader&&(Y==null?void 0:Y.offsetHeight)||0},S())},P=ca(),R=D(()=>{const{bodyWidth:K,scrollY:H,gutterWidth:z}=t;return K.value?`${K.value-(H.value?z:0)}px`:""}),G=D(()=>e.maxHeight?"fixed":e.tableLayout);return{isHidden:r,renderExpanded:o,setDragVisible:l,isGroup:u,handleMouseLeave:y,handleHeaderFooterMousewheel:b,tableSize:P,emptyBlockStyle:D(()=>{if(e.data&&e.data.length)return;let K="100%";e.height&&f.value&&(K=`${f.value}px`);const H=d.value;return{width:H?`${H}px`:"",height:K}}),resizeProxyVisible:i,bodyWidth:R,resizeState:s,doLayout:S,tableBodyStyles:w,tableLayout:G,scrollbarViewStyle:c,scrollbarStyle:D(()=>e.height?{height:"100%"}:e.maxHeight?Number.isNaN(Number(e.maxHeight))?{maxHeight:`calc(${e.maxHeight} - ${h.value+m.value}px)`}:{maxHeight:`${+e.maxHeight-h.value-m.value}px`}:{})}}function rbe(e){let t;const n=()=>{const a=e.vnode.el.querySelector(".hidden-columns"),r={childList:!0,subtree:!0},o=e.store.states.updateOrderFns;t=new MutationObserver(()=>{o.forEach(i=>i())}),t.observe(a,r)};mt(()=>{n()}),Us(()=>{t==null||t.disconnect()})}function rm(e){const t=e.tableLayout==="auto";let n=e.columns||[];t&&n.every(({width:r})=>zt(r))&&(n=[]);const a=r=>{const o={key:`${e.tableLayout}_${r.id}`,style:{},name:void 0};return t?o.style={width:`${r.width}px`}:o.name=r.id,o};return ot("colgroup",{},n.map(r=>ot("col",a(r))))}rm.props=["columns","tableLayout"];const obe=()=>{const e=F(),t=(o,i)=>{const l=e.value;l&&l.scrollTo(o,i)},n=(o,i)=>{const l=e.value;l&&Je(i)&&["Top","Left"].includes(o)&&l[`setScroll${o}`](i)};return{scrollBarRef:e,scrollTo:t,setScrollTop:o=>n("Top",o),setScrollLeft:o=>n("Left",o)}},ibe=["data-prefix"],lbe={ref:"hiddenColumns",class:"hidden-columns"};var sbe=q({name:"ElTable",__name:"table",props:hye,emits:["select","select-all","selection-change","cell-mouse-enter","cell-mouse-leave","cell-dblclick","cell-contextmenu","cell-click","row-dblclick","row-click","row-contextmenu","header-click","header-contextmenu","sort-change","filter-change","current-change","header-dragend","expand-change","scroll"],setup(e,{expose:t}){const n=e,{t:a}=Vt(),r=ke("table"),o=Hl("table"),i=Et();Ft(di,i);const l=Dye(i,n);i.store=l;const s=new $ye({store:i.store,table:i,fit:n.fit,showHeader:n.showHeader});i.layout=s;const u=D(()=>(l.states.data.value||[]).length===0),{setCurrentRow:c,getSelectionRows:d,getHalfSelectionRows:v,toggleRowSelection:f,clearSelection:h,clearFilter:m,toggleAllSelection:g,toggleRowExpansion:y,clearSort:b,sort:_,updateKeyChildren:w}=nbe(l),{isHidden:S,renderExpanded:k,setDragVisible:T,isGroup:M,handleMouseLeave:E,handleHeaderFooterMousewheel:A,tableSize:I,emptyBlockStyle:P,resizeProxyVisible:R,bodyWidth:G,resizeState:K,doLayout:H,tableBodyStyles:z,tableLayout:B,scrollbarViewStyle:W,scrollbarStyle:N}=abe(n,s,l,i),{scrollBarRef:V,scrollTo:Y,setScrollLeft:X,setScrollTop:ee}=obe(),re=Yi(H,50),ie=gye(r.namespace.value),le=i;i.tableId=ie,i.state={isGroup:M,resizeState:K,doLayout:H,debouncedUpdateLayout:re};const ve=D(()=>n.sumText??a("el.table.sumText")),he=D(()=>n.emptyText??a("el.table.emptyText")),J=D(()=>{var oe;return n.tooltipEffect??((oe=o.value)==null?void 0:oe.tooltipEffect)}),Z=D(()=>{var oe;return n.tooltipOptions??((oe=o.value)==null?void 0:oe.tooltipOptions)}),j=D(()=>LD(l.states.originColumns.value)[0]);return rbe(i),rn(()=>{re.cancel()}),t({ns:r,layout:s,store:l,columns:j,handleHeaderFooterMousewheel:A,handleMouseLeave:E,tableId:ie,tableSize:I,isHidden:S,isEmpty:u,renderExpanded:k,resizeProxyVisible:R,resizeState:K,isGroup:M,bodyWidth:G,tableBodyStyles:z,emptyBlockStyle:P,debouncedUpdateLayout:re,setCurrentRow:c,getSelectionRows:d,getHalfSelectionRows:v,toggleRowSelection:f,clearSelection:h,clearFilter:m,toggleAllSelection:g,toggleRowExpansion:y,clearSort:b,doLayout:H,sort:_,updateKeyChildren:w,t:a,setDragVisible:T,context:le,computedSumText:ve,computedEmptyText:he,computedTooltipEffect:J,computedTooltipOptions:Z,tableLayout:B,scrollbarViewStyle:W,scrollbarStyle:N,scrollBarRef:V,scrollTo:Y,setScrollLeft:X,setScrollTop:ee,allowDragLastColumn:n.allowDragLastColumn}),(oe,se)=>(x(),O("div",{ref:"tableWrapper",class:U([{[p(r).m("fit")]:e.fit,[p(r).m("striped")]:e.stripe,[p(r).m("border")]:e.border||p(M),[p(r).m("hidden")]:p(S),[p(r).m("group")]:p(M),[p(r).m("fluid-height")]:e.maxHeight,[p(r).m("scrollable-x")]:p(s).scrollX.value,[p(r).m("scrollable-y")]:p(s).scrollY.value,[p(r).m("enable-row-hover")]:!p(l).states.isComplex.value,[p(r).m("enable-row-transition")]:(p(l).states.data.value||[]).length!==0&&(p(l).states.data.value||[]).length<100,"has-footer":e.showSummary},p(r).m(p(I)),e.className,p(r).b(),p(r).m(`layout-${p(B)}`)]),style:et(e.style),"data-prefix":p(r).namespace.value,onMouseleave:se[1]||(se[1]=(...fe)=>p(E)&&p(E)(...fe))},[C("div",{ref:"tableInnerWrapper",class:U(p(r).e("inner-wrapper"))},[C("div",lbe,[ce(oe.$slots,"default")],512),e.showHeader&&p(B)==="fixed"?st((x(),O("div",{key:0,ref:"headerWrapper",class:U(p(r).e("header-wrapper"))},[C("table",{ref:"tableHeader",class:U(p(r).e("header")),style:et(p(z)),border:"0",cellpadding:"0",cellspacing:"0"},[L(p(rm),{columns:p(l).states.columns.value,"table-layout":p(B)},null,8,["columns","table-layout"]),L(p(Nx),{ref:"tableHeaderRef",border:e.border,"default-sort":e.defaultSort,store:p(l),"append-filter-panel-to":e.appendFilterPanelTo,"allow-drag-last-column":e.allowDragLastColumn,onSetDragVisible:p(T)},null,8,["border","default-sort","store","append-filter-panel-to","allow-drag-last-column","onSetDragVisible"])],6)],2)),[[p(Q5),p(A)]]):te("v-if",!0),C("div",{ref:"bodyWrapper",class:U(p(r).e("body-wrapper"))},[L(p(ni),{ref_key:"scrollBarRef",ref:V,"view-style":p(W),"wrap-style":p(N),always:e.scrollbarAlwaysOn,tabindex:e.scrollbarTabindex,native:e.nativeScrollbar,onScroll:se[0]||(se[0]=fe=>oe.$emit("scroll",fe))},{default:$(()=>[C("table",{ref:"tableBody",class:U(p(r).e("body")),cellspacing:"0",cellpadding:"0",border:"0",style:et({width:p(G),tableLayout:p(B)})},[L(p(rm),{columns:p(l).states.columns.value,"table-layout":p(B)},null,8,["columns","table-layout"]),e.showHeader&&p(B)==="auto"?(x(),ne(p(Nx),{key:0,ref:"tableHeaderRef",class:U(p(r).e("body-header")),border:e.border,"default-sort":e.defaultSort,store:p(l),"append-filter-panel-to":e.appendFilterPanelTo,onSetDragVisible:p(T)},null,8,["class","border","default-sort","store","append-filter-panel-to","onSetDragVisible"])):te("v-if",!0),L(p(Qye),{context:p(le),highlight:e.highlightCurrentRow,"row-class-name":e.rowClassName,"tooltip-effect":J.value,"tooltip-options":Z.value,"row-style":e.rowStyle,store:p(l),stripe:e.stripe},null,8,["context","highlight","row-class-name","tooltip-effect","tooltip-options","row-style","store","stripe"]),e.showSummary&&p(B)==="auto"?(x(),ne(p(Bx),{key:1,class:U(p(r).e("body-footer")),border:e.border,"default-sort":e.defaultSort,store:p(l),"sum-text":ve.value,"summary-method":e.summaryMethod},null,8,["class","border","default-sort","store","sum-text","summary-method"])):te("v-if",!0)],6),u.value?(x(),O("div",{key:0,ref:"emptyBlock",style:et(p(P)),class:U(p(r).e("empty-block"))},[C("span",{class:U(p(r).e("empty-text"))},[ce(oe.$slots,"empty",{},()=>[ue(Q(he.value),1)])],2)],6)):te("v-if",!0),oe.$slots.append?(x(),O("div",{key:1,ref:"appendWrapper",class:U(p(r).e("append-wrapper"))},[ce(oe.$slots,"append")],2)):te("v-if",!0)]),_:3},8,["view-style","wrap-style","always","tabindex","native"])],2),e.showSummary&&p(B)==="fixed"?st((x(),O("div",{key:1,ref:"footerWrapper",class:U(p(r).e("footer-wrapper"))},[C("table",{class:U(p(r).e("footer")),cellspacing:"0",cellpadding:"0",border:"0",style:et(p(z))},[L(p(rm),{columns:p(l).states.columns.value,"table-layout":p(B)},null,8,["columns","table-layout"]),L(p(Bx),{border:e.border,"default-sort":e.defaultSort,store:p(l),"sum-text":ve.value,"summary-method":e.summaryMethod},null,8,["border","default-sort","store","sum-text","summary-method"])],6)],2)),[[Zt,!u.value],[p(Q5),p(A)]]):te("v-if",!0),e.border||p(M)?(x(),O("div",{key:2,class:U(p(r).e("border-left-patch"))},null,2)):te("v-if",!0)],2),st(C("div",{ref:"resizeProxy",class:U(p(r).e("column-resize-proxy"))},null,2),[[Zt,p(R)]])],46,ibe))}}),ube=sbe;const cbe={type:{type:String,default:"default"},label:String,className:String,labelClassName:String,property:String,prop:String,width:{type:[String,Number],default:""},minWidth:{type:[String,Number],default:""},renderHeader:Function,sortable:{type:[Boolean,String],default:!1},sortMethod:Function,sortBy:[String,Function,Array],resizable:{type:Boolean,default:!0},columnKey:String,align:String,headerAlign:String,showOverflowTooltip:{type:[Boolean,Object],default:void 0},tooltipFormatter:Function,fixed:[Boolean,String],formatter:Function,selectable:Function,reserveSelection:Boolean,filterMethod:Function,filteredValue:Array,filters:Array,filterPlacement:String,filterMultiple:{type:Boolean,default:!0},filterClassName:String,index:[Number,Function],sortOrders:{type:Array,default:()=>["ascending","descending",null],validator:e=>e.every(t=>["ascending","descending",null].includes(t))}},dbe={selection:"table-column--selection",expand:"table__expand-column"},fbe={default:{order:""},selection:{width:48,minWidth:48,realWidth:48,order:""},expand:{width:48,minWidth:48,realWidth:48,order:""},index:{width:48,minWidth:48,realWidth:48,order:""}},vbe=e=>dbe[e]||"",hbe={selection:{renderHeader({store:e}){function t(){return e.states.data.value&&e.states.data.value.length===0}return ot(Zo,{disabled:t(),size:e.states.tableSize.value,indeterminate:e.states.selection.value.length>0&&!e.states.isAllSelected.value,"onUpdate:modelValue":e.toggleAllSelection??void 0,modelValue:e.states.isAllSelected.value,ariaLabel:e.t("el.table.selectAllLabel")})},renderCell({row:e,column:t,store:n,$index:a}){return ot(Zo,{disabled:t.selectable?!t.selectable.call(null,e,a):!1,size:n.states.tableSize.value,onChange:()=>{n.commit("rowSelectedChanged",e)},onClick:r=>r.stopPropagation(),modelValue:n.isSelected(e),indeterminate:n.getRowIndeterminate(e),ariaLabel:n.t("el.table.selectRowLabel")})},sortable:!1,resizable:!1},index:{renderHeader({column:e}){return e.label||"#"},renderCell({column:e,$index:t}){let n=t+1;const a=e.index;return Je(a)?n=t+a:Qe(a)&&(n=a(t)),ot("div",{},[n])},sortable:!1},expand:{renderHeader({column:e}){return e.label||""},renderCell({column:e,row:t,store:n,expanded:a,$index:r}){var u,c;const{ns:o}=n,i=[o.e("expand-icon")];!e.renderExpand&&a&&i.push(o.em("expand-icon","expanded"));const l=function(d){d.stopPropagation(),n.toggleRowExpansion(t)},s=((c=(u=n.states.rowExpandable).value)==null?void 0:c.call(u,t,r))??!0;return s||i.push(o.is("disabled")),ot("button",{type:"button",disabled:!s,"aria-label":n.t(a?"el.table.collapseRowLabel":"el.table.expandRowLabel"),"aria-expanded":a,class:i,onClick:l},{default:()=>e.renderExpand?[e.renderExpand({expanded:a,expandable:s})]:[ot(Xe,null,{default:()=>[ot(za)]})]})},sortable:!1,resizable:!1}};function pbe({row:e,column:t,$index:n}){var o;const a=t.property,r=a&&Cs(e,a).value;return t&&t.formatter?t.formatter(e,t,r,n):((o=r==null?void 0:r.toString)==null?void 0:o.call(r))||""}function mbe({row:e,treeNode:t,store:n},a=!1){const{ns:r}=n;if(!t)return a?[ot("span",{class:r.e("placeholder")})]:null;const o=[],i=function(l){l.stopPropagation(),!t.loading&&n.loadOrToggle(e)};if(t.indent&&o.push(ot("span",{class:r.e("indent"),style:{"padding-left":`${t.indent}px`}})),vn(t.expanded)&&!t.noLazyChildren){const l=[r.e("expand-icon"),t.expanded?r.em("expand-icon","expanded"):""];let s=za;t.loading&&(s=Mr),o.push(ot("button",{type:"button","aria-label":n.t(t.expanded?"el.table.collapseRowLabel":"el.table.expandRowLabel"),"aria-expanded":t.expanded,class:l,onClick:i},{default:()=>[ot(Xe,{class:r.is("loading",t.loading)},{default:()=>[ot(s)]})]}))}else o.push(ot("span",{class:r.e("placeholder")}));return o}function Vx(e,t){return e.reduce((n,a)=>(n[a]=a,n),t)}function gbe(e,t){const n=Et();return{registerComplexWatchers:()=>{const o=["fixed"],i={realWidth:"width",realMinWidth:"minWidth"},l=Vx(o,i);Object.keys(l).forEach(s=>{const u=i[s];en(t,u)&&ye(()=>t[u],c=>{let d=c;u==="width"&&s==="realWidth"&&(d=Mw(c)),u==="minWidth"&&s==="realMinWidth"&&(d=xD(c)),n.columnConfig.value[u]=d,n.columnConfig.value[s]=d;const v=u==="fixed";e.value.store.scheduleLayout(v)})})},registerNormalWatchers:()=>{const o=["label","filters","filterMultiple","filteredValue","sortable","index","formatter","className","labelClassName","filterClassName","showOverflowTooltip","tooltipFormatter","resizable"],i=["showOverflowTooltip"],l={property:"prop",align:"realAlign",headerAlign:"realHeaderAlign"},s=Vx(o,l);Object.keys(s).forEach(c=>{const d=l[c];en(t,d)&&ye(()=>t[d],v=>{n.columnConfig.value[c]=v,(c==="filters"||c==="filterMethod")&&(n.columnConfig.value.filterable=!!(n.columnConfig.value.filters||n.columnConfig.value.filterMethod))})}),i.forEach(c=>{en(e.value.props,c)&&ye(()=>e.value.props[c],d=>{n.columnConfig.value.type!=="selection"&&zt(t[c])&&(n.columnConfig.value[c]=d)})});const u=Hl("table");u.value&&en(u.value,"showOverflowTooltip")&&ye(()=>{var c;return(c=u.value)==null?void 0:c.showOverflowTooltip},c=>{n.columnConfig.value.type!=="selection"&&(!zt(t.showOverflowTooltip)||!zt(e.value.props.showOverflowTooltip)||(n.columnConfig.value.showOverflowTooltip=c))})}}}function ybe(e,t,n){const a=Et(),r=F(""),o=F(!1),i=F(),l=F(),s=ke("table");ur(()=>{i.value=e.align?`is-${e.align}`:null,i.value}),ur(()=>{l.value=e.headerAlign?`is-${e.headerAlign}`:i.value,l.value});const u=D(()=>{let w=a.vnode.vParent||a.parent;for(;w&&!w.tableId&&!w.columnId;)w=w.vnode.vParent||w.parent;return w}),c=D(()=>{const{store:w}=a.parent;if(!w)return!1;const{treeData:S}=w.states,k=S.value;return k&&Object.keys(k).length>0}),d=F(Mw(e.width)),v=F(xD(e.minWidth)),f=w=>(d.value&&(w.width=d.value),v.value&&(w.minWidth=v.value),!d.value&&v.value&&(w.width=void 0),w.minWidth||(w.minWidth=80),w.realWidth=Number(zt(w.width)?w.minWidth:w.width),w),h=w=>{const S=w.type,k=hbe[S]||{};Object.keys(k).forEach(M=>{const E=k[M];M!=="className"&&!zt(E)&&(w[M]=E)});const T=vbe(S);if(T){const M=`${p(s.namespace)}-${T}`;w.className=w.className?`${w.className} ${M}`:M}return w},m=w=>{Ae(w)?w.forEach(k=>S(k)):S(w);function S(k){var T;((T=k==null?void 0:k.type)==null?void 0:T.name)==="ElTableColumn"&&(k.vParent=a)}};return{columnId:r,realAlign:i,isSubColumn:o,realHeaderAlign:l,columnOrTableParent:u,setColumnWidth:f,setColumnForcedProps:h,setColumnRenders:w=>{e.renderHeader?un("TableColumn","Comparing to render-header, scoped-slot header is easier to use. We recommend users to use scoped-slot header."):w.type!=="selection"&&(w.renderHeader=k=>{if(a.columnConfig.value.label,t.header){const T=t.header(k);if(MD(T))return ot(Ve,T)}return ue(w.label)}),t["filter-icon"]&&(w.renderFilterIcon=k=>ce(t,"filter-icon",k)),t.expand&&(w.renderExpand=k=>ce(t,"expand",k));let S=w.renderCell;return w.type==="expand"?(w.renderCell=k=>ot("div",{class:"cell"},[S(k)]),n.value.renderExpanded=k=>t.default?t.default(k):t.default):(S=S||pbe,w.renderCell=k=>{let T=null;if(t.default){const P=t.default(k);T=P.some(R=>R.type!==oa)?P:S(k)}else T=S(k);const{columns:M}=n.value.store.states,E=M.value.findIndex(P=>P.type==="default"),A=mbe(k,c.value&&k.cellIndex===E),I={class:"cell",style:{}};return w.showOverflowTooltip&&(I.class=`${I.class} ${p(s.namespace)}-tooltip`,I.style={width:`${(k.column.realWidth||Number(k.column.width))-1}px`}),m(T),ot("div",I,[A,T])}),w},getPropsData:(...w)=>w.reduce((S,k)=>(Ae(k)&&k.forEach(T=>{S[T]=e[T]}),S),{}),getColumnElIndex:(w,S)=>Array.prototype.indexOf.call(w,S),updateColumnOrder:()=>{n.value.store.commit("updateColumnOrder",a.columnConfig.value)}}}var bbe=q({name:"ElTableColumn",__name:"index",props:cbe,setup(e){const t=e,n=Xn(),a=Et(),r=Hl("table"),o=F({}),i=D(()=>{let k=a.parent;for(;k&&!k.tableId;)k=k.parent;return k}),{registerNormalWatchers:l,registerComplexWatchers:s}=gbe(i,t),{columnId:u,isSubColumn:c,realHeaderAlign:d,columnOrTableParent:v,setColumnWidth:f,setColumnForcedProps:h,setColumnRenders:m,getPropsData:g,getColumnElIndex:y,realAlign:b,updateColumnOrder:_}=ybe(t,n,i),w=v.value;u.value=yye("tableId"in w?w.tableId:w.columnId),g1(()=>{var P,R;c.value=i.value!==w;const k=t.type||"default",T=t.sortable===""?!0:t.sortable,M=k==="selection"?!1:zt(t.showOverflowTooltip)?w.props.showOverflowTooltip??((P=r.value)==null?void 0:P.showOverflowTooltip):t.showOverflowTooltip,E=zt(t.tooltipFormatter)?w.props.tooltipFormatter??((R=r.value)==null?void 0:R.tooltipFormatter):t.tooltipFormatter,A={...fbe[k],id:u.value,type:k,property:t.prop||t.property,align:b,headerAlign:d,showOverflowTooltip:M,tooltipFormatter:E,filterable:t.filters||t.filterMethod,filteredValue:[],filterPlacement:"",filterClassName:"",isColumnGroup:!1,isSubColumn:!1,filterOpened:!1,sortable:T,index:t.index,rawColumnKey:a.vnode.key};let I=g(["columnKey","label","className","labelClassName","type","renderHeader","formatter","fixed","resizable"],["sortMethod","sortBy","sortOrders"],["selectable","reserveSelection"],["filterMethod","filters","filterMultiple","filterOpened","filteredValue","filterPlacement","filterClassName"]);I=wye(A,I),I=Cye(m,f,h)(I),o.value=I,l(),s()}),mt(()=>{var E,A;const k=v.value,T=c.value?(E=k.vnode.el)==null?void 0:E.children:(A=k.refs.hiddenColumns)==null?void 0:A.children,M=()=>y(T||[],a.vnode.el);o.value.getColumnIndex=M,M()>-1&&i.value.store.commit("insertColumn",o.value,c.value?"columnConfig"in k&&k.columnConfig.value:null,_)}),rn(()=>{const k=o.value.getColumnIndex;(k?k():-1)>-1&&i.value.store.commit("removeColumn",o.value,c.value?"columnConfig"in w&&w.columnConfig.value:null,_)}),a.columnId=u.value,a.columnConfig=o;const S=()=>{var k,T;try{const M=(k=n.default)==null?void 0:k.call(n,{row:{},column:{},$index:-1}),E=[];if(Ae(M))for(const A of M)((T=A.type)==null?void 0:T.name)==="ElTableColumn"||A.shapeFlag&2?E.push(A):A.type===Ve&&Ae(A.children)&&A.children.forEach(I=>{(I==null?void 0:I.patchFlag)!==1024&&!Ye(I==null?void 0:I.children)&&E.push(I)});return ot("div",E)}catch{return ot("div",[])}};return(k,T)=>(x(),ne(S))}}),_be=bbe,wbe=_be,PD=wbe;const Sbe=wt(ube,{TableColumn:PD}),Cbe=On(PD),t_={asc:"desc",desc:"asc"},Sh=Symbol("placeholder"),DD=String,dp={type:ae(Array),required:!0},Pw={type:ae(Array)},OD={...Pw,required:!0},xbe=String,zx={type:ae(Array),default:()=>Nn([])},Du={type:Number,required:!0},$D={type:ae([String,Number,Symbol]),default:"id"},Fx={type:ae(Object)},Bu=$e({class:String,columns:dp,columnsStyles:{type:ae(Object),required:!0},depth:Number,expandColumnKey:xbe,estimatedRowHeight:{...Xu.estimatedRowHeight,default:void 0},isScrolling:Boolean,onRowExpand:{type:ae(Function)},onRowHover:{type:ae(Function)},onRowHeightChange:{type:ae(Function)},rowData:{type:ae(Object),required:!0},rowEventHandlers:{type:ae(Object)},rowIndex:{type:Number,required:!0},rowKey:$D,style:{type:ae(Object)}}),fy={type:Number,required:!0},Dw=$e({class:String,columns:dp,fixedHeaderData:{type:ae(Array)},headerData:{type:ae(Array),required:!0},headerHeight:{type:ae([Number,Array]),default:50},rowWidth:fy,rowHeight:{type:Number,default:50},height:fy,width:fy}),om=$e({columns:dp,data:OD,fixedData:Pw,estimatedRowHeight:Bu.estimatedRowHeight,width:Du,height:Du,headerWidth:Du,headerHeight:Dw.headerHeight,bodyWidth:Du,rowHeight:Du,cache:XL.cache,useIsScrolling:Boolean,scrollbarAlwaysOn:Xu.scrollbarAlwaysOn,scrollbarStartGap:Xu.scrollbarStartGap,scrollbarEndGap:Xu.scrollbarEndGap,class:DD,style:Fx,containerStyle:Fx,getRowHeight:{type:ae(Function),required:!0},rowKey:Bu.rowKey,onRowsRendered:{type:ae(Function)},onScroll:{type:ae(Function)}}),kbe=$e({cache:om.cache,estimatedRowHeight:Bu.estimatedRowHeight,rowKey:$D,headerClass:{type:ae([String,Function])},headerProps:{type:ae([Object,Function])},headerCellProps:{type:ae([Object,Function])},headerHeight:Dw.headerHeight,footerHeight:{type:Number,default:0},rowClass:{type:ae([String,Function])},rowProps:{type:ae([Object,Function])},rowHeight:{type:Number,default:50},cellProps:{type:ae([Object,Function])},columns:dp,data:OD,dataGetter:{type:ae(Function)},fixedData:Pw,expandColumnKey:Bu.expandColumnKey,expandedRowKeys:zx,defaultExpandedRowKeys:zx,class:DD,fixed:Boolean,style:{type:ae(Object)},width:Du,height:Du,maxHeight:Number,useIsScrolling:Boolean,indentSize:{type:Number,default:12},iconSize:{type:Number,default:12},hScrollbarSize:Xu.hScrollbarSize,vScrollbarSize:Xu.vScrollbarSize,scrollbarAlwaysOn:QL.alwaysOn,sortBy:{type:ae(Object),default:()=>({})},sortState:{type:ae(Object),default:void 0},onColumnSort:{type:ae(Function)},onExpandedRowsChange:{type:ae(Function)},onEndReached:{type:ae(Function)},onRowExpand:Bu.onRowExpand,onScroll:om.onScroll,onRowsRendered:om.onRowsRendered,rowEventHandlers:Bu.rowEventHandlers}),Tbe=(e,t,n)=>{const a={flexGrow:0,flexShrink:0,...n?{}:{flexGrow:e.flexGrow??0,flexShrink:e.flexShrink??1}},r={...e.style??{},...a,flexBasis:"auto",width:e.width};return t||(e.maxWidth&&(r.maxWidth=e.maxWidth),e.minWidth&&(r.minWidth=e.minWidth)),r};function Mbe(e,t,n){const a=D(()=>p(t).map((g,y)=>({...g,key:g.key??g.dataKey??y}))),r=D(()=>p(a).filter(g=>!g.hidden)),o=D(()=>p(r).filter(g=>g.fixed==="left"||g.fixed===!0)),i=D(()=>p(r).filter(g=>g.fixed==="right")),l=D(()=>p(r).filter(g=>!g.fixed)),s=D(()=>{const g=[];return p(o).forEach(y=>{g.push({...y,placeholderSign:Sh})}),p(l).forEach(y=>{g.push(y)}),p(i).forEach(y=>{g.push({...y,placeholderSign:Sh})}),g}),u=D(()=>p(o).length||p(i).length),c=D(()=>p(a).reduce((g,y)=>(g[y.key]=Tbe(y,p(n),e.fixed),g),{})),d=D(()=>p(r).reduce((g,y)=>g+y.width,0)),v=g=>p(a).find(y=>y.key===g),f=g=>p(c)[g],h=(g,y)=>{g.width=y};function m(g){var S;const{key:y}=g.currentTarget.dataset;if(!y)return;const{sortState:b,sortBy:_}=e;let w="asc";_t(b)?w=t_[b[y]]:w=t_[_.order],(S=e.onColumnSort)==null||S.call(e,{column:v(y),key:y,order:w})}return{columns:a,columnsStyles:c,columnsTotalWidth:d,fixedColumnsOnLeft:o,fixedColumnsOnRight:i,hasFixedColumns:u,mainColumns:s,normalColumns:l,visibleColumns:r,getColumn:v,getColumnStyle:f,updateColumnWidth:h,onColumnSorted:m}}const Ebe=(e,{mainTableRef:t,leftTableRef:n,rightTableRef:a,onMaybeEndReached:r})=>{const o=F({scrollLeft:0,scrollTop:0});function i(f){var m,g,y;const{scrollTop:h}=f;(m=t.value)==null||m.scrollTo(f),(g=n.value)==null||g.scrollToTop(h),(y=a.value)==null||y.scrollToTop(h)}function l(f){o.value=f,i(f)}function s(f){o.value.scrollTop=f,i(p(o))}function u(f){var h,m;o.value.scrollLeft=f,(m=(h=t.value)==null?void 0:h.scrollTo)==null||m.call(h,p(o))}function c(f){var h;l(f),(h=e.onScroll)==null||h.call(e,f)}function d({scrollTop:f}){const{scrollTop:h}=p(o);f!==h&&s(f)}function v(f,h="auto"){var m;(m=t.value)==null||m.scrollToRow(f,h)}return ye(()=>p(o).scrollTop,(f,h)=>{f>h&&r()}),{scrollPos:o,scrollTo:l,scrollToLeft:u,scrollToTop:s,scrollToRow:v,onScroll:c,onVerticalScroll:d}},Abe=(e,{mainTableRef:t,leftTableRef:n,rightTableRef:a,tableInstance:r,ns:o,isScrolling:i})=>{const l=Et(),{emit:s}=l,u=Ht(!1),c=F(e.defaultExpandedRowKeys||[]),d=F(-1),v=Ht(null),f=F({}),h=F({}),m=Ht({}),g=Ht({}),y=Ht({}),b=D(()=>Je(e.estimatedRowHeight));function _(A){var I;(I=e.onRowsRendered)==null||I.call(e,A),A.rowCacheEnd>p(d)&&(d.value=A.rowCacheEnd)}function w({hovered:A,rowKey:I}){i.value||r.vnode.el.querySelectorAll(`[rowkey="${String(I)}"]`).forEach(P=>{A?P.classList.add(o.is("hovered")):P.classList.remove(o.is("hovered"))})}function S({expanded:A,rowData:I,rowIndex:P,rowKey:R}){var H,z;const G=[...p(c)],K=G.indexOf(R);A?K===-1&&G.push(R):K>-1&&G.splice(K,1),c.value=G,s("update:expandedRowKeys",G),(H=e.onRowExpand)==null||H.call(e,{expanded:A,rowData:I,rowIndex:P,rowKey:R}),(z=e.onExpandedRowsChange)==null||z.call(e,G),r.vnode.el.querySelector(`.${o.is("hovered")}[rowkey="${String(R)}"]`)&&He(()=>w({hovered:!0,rowKey:R}))}const k=Yi(()=>{var A,I,P,R;u.value=!0,f.value={...p(f),...p(h)},T(p(v),!1),h.value={},v.value=null,(A=t.value)==null||A.forceUpdate(),(I=n.value)==null||I.forceUpdate(),(P=a.value)==null||P.forceUpdate(),(R=l.proxy)==null||R.$forceUpdate(),u.value=!1},0);function T(A,I=!1){p(b)&&[t,n,a].forEach(P=>{const R=p(P);R&&R.resetAfterRowIndex(A,I)})}function M(A,I,P){const R=p(v);(R===null||R>P)&&(v.value=P),h.value[A]=I}function E({rowKey:A,height:I,rowIndex:P},R){R?R==="right"?y.value[A]=I:m.value[A]=I:g.value[A]=I;const G=Math.max(...[m,y,g].map(K=>K.value[A]||0));p(f)[A]!==G&&(M(A,G,P),k())}return{expandedRowKeys:c,lastRenderedRowIndex:d,isDynamic:b,isResetting:u,rowHeights:f,resetAfterIndex:T,onRowExpanded:S,onRowHovered:w,onRowsRendered:_,onRowHeightChange:E}},Ibe=(e,{expandedRowKeys:t,lastRenderedRowIndex:n,resetAfterIndex:a})=>{const r=F({}),o=D(()=>{const l={},{data:s,rowKey:u}=e,c=p(t);if(!c||!c.length)return s;const d=[],v=new Set;c.forEach(h=>v.add(h));let f=s.slice();for(f.forEach(h=>l[h[u]]=0);f.length>0;){const h=f.shift();d.push(h),v.has(h[u])&&Ae(h.children)&&h.children.length>0&&(f=[...h.children,...f],h.children.forEach(m=>l[m[u]]=l[h[u]]+1))}return r.value=l,d}),i=D(()=>{const{data:l,expandColumnKey:s}=e;return s?p(o):l});return ye(i,(l,s)=>{l!==s&&(n.value=-1,a(0,!0))}),{data:i,depthMap:r}},Lbe=(e,t)=>e+t,im=e=>Ae(e)?e.reduce(Lbe,0):e,vc=(e,t,n={})=>Qe(e)?e(t):e??n,gs=e=>(["width","maxWidth","minWidth","height"].forEach(t=>{e[t]=Vn(e[t])}),e),RD=e=>bn(e)?t=>ot(e,t):e,Pbe=(e,{columnsTotalWidth:t,rowsHeight:n,fixedColumnsOnLeft:a,fixedColumnsOnRight:r})=>{const o=D(()=>{const{fixed:m,width:g,vScrollbarSize:y}=e,b=g-y;return m?Math.max(Math.round(p(t)),b):b}),i=D(()=>{const{height:m=0,maxHeight:g=0,footerHeight:y,hScrollbarSize:b}=e;if(g>0){const _=p(v),w=p(n),S=p(d)+_+w+b;return Math.min(S,g-y)}return m-y}),l=D(()=>{const{maxHeight:m}=e,g=p(i);if(Je(m)&&m>0)return g;const y=p(n)+p(d)+p(v);return Math.min(g,y)}),s=m=>m.width,u=D(()=>im(p(a).map(s))),c=D(()=>im(p(r).map(s))),d=D(()=>im(e.headerHeight)),v=D(()=>{var m;return(((m=e.fixedData)==null?void 0:m.length)||0)*e.rowHeight}),f=D(()=>p(i)-p(d)-p(v)),h=D(()=>{const{style:m={},height:g,width:y}=e;return gs({...m,height:g,width:y})});return{bodyWidth:o,fixedTableHeight:l,mainTableHeight:i,leftTableWidth:u,rightTableWidth:c,windowHeight:f,footerHeight:D(()=>gs({height:e.footerHeight})),emptyStyle:D(()=>({top:Vn(p(d)),bottom:Vn(e.footerHeight),width:Vn(e.width)})),rootStyle:h,headerHeight:d}};function Dbe(e){const t=F(),n=F(),a=F(),{columns:r,columnsStyles:o,columnsTotalWidth:i,fixedColumnsOnLeft:l,fixedColumnsOnRight:s,hasFixedColumns:u,mainColumns:c,onColumnSorted:d}=Mbe(e,cn(e,"columns"),cn(e,"fixed")),{scrollTo:v,scrollToLeft:f,scrollToTop:h,scrollToRow:m,onScroll:g,onVerticalScroll:y,scrollPos:b}=Ebe(e,{mainTableRef:t,leftTableRef:n,rightTableRef:a,onMaybeEndReached:oe}),_=ke("table-v2"),w=Et(),S=Ht(!1),{expandedRowKeys:k,lastRenderedRowIndex:T,isDynamic:M,isResetting:E,rowHeights:A,resetAfterIndex:I,onRowExpanded:P,onRowHeightChange:R,onRowHovered:G,onRowsRendered:K}=Abe(e,{mainTableRef:t,leftTableRef:n,rightTableRef:a,tableInstance:w,ns:_,isScrolling:S}),{data:H,depthMap:z}=Ibe(e,{expandedRowKeys:k,lastRenderedRowIndex:T,resetAfterIndex:I}),B=D(()=>{const{estimatedRowHeight:se,rowHeight:fe}=e,ge=p(H);return Je(se)?Object.values(p(A)).reduce((Se,De)=>Se+De,0):ge.length*fe}),{bodyWidth:W,fixedTableHeight:N,mainTableHeight:V,leftTableWidth:Y,rightTableWidth:X,windowHeight:ee,footerHeight:re,emptyStyle:ie,rootStyle:le,headerHeight:ve}=Pbe(e,{columnsTotalWidth:i,fixedColumnsOnLeft:l,fixedColumnsOnRight:s,rowsHeight:B}),he=F(),J=D(()=>{const se=p(H).length===0;return Ae(e.fixedData)?e.fixedData.length===0&&se:se});function Z(se){const{estimatedRowHeight:fe,rowHeight:ge,rowKey:Se}=e;return fe?p(A)[p(H)[se][Se]]||fe:ge}const j=F(!1);function oe(){const{onEndReached:se}=e;if(!se)return;const{scrollTop:fe}=p(b),ge=p(B),Se=ge-(fe+p(ee))+e.hScrollbarSize;!j.value&&p(T)>=0&&ge<=fe+p(V)-p(ve)?(j.value=!0,se(Se)):j.value=!1}return ye(()=>p(B),()=>j.value=!1),ye(()=>e.expandedRowKeys,se=>k.value=se,{deep:!0}),{columns:r,containerRef:he,mainTableRef:t,leftTableRef:n,rightTableRef:a,isDynamic:M,isResetting:E,isScrolling:S,hasFixedColumns:u,columnsStyles:o,columnsTotalWidth:i,data:H,expandedRowKeys:k,depthMap:z,fixedColumnsOnLeft:l,fixedColumnsOnRight:s,mainColumns:c,bodyWidth:W,emptyStyle:ie,rootStyle:le,footerHeight:re,mainTableHeight:V,fixedTableHeight:N,leftTableWidth:Y,rightTableWidth:X,showEmpty:J,getRowHeight:Z,onColumnSorted:d,onRowHovered:G,onRowExpanded:P,onRowsRendered:K,onRowHeightChange:R,scrollTo:v,scrollToLeft:f,scrollToTop:h,scrollToRow:m,onScroll:g,onVerticalScroll:y}}const Ow=Symbol("tableV2"),ND="tableV2GridScrollLeft",Obe=q({name:"ElTableV2Header",props:Dw,setup(e,{slots:t,expose:n}){const a=ke("table-v2"),r=Ze(ND),o=F(),i=D(()=>gs({width:e.width,height:e.height})),l=D(()=>gs({width:e.rowWidth,height:e.height})),s=D(()=>wa(p(e.headerHeight))),u=v=>{const f=p(o);He(()=>{f!=null&&f.scroll&&f.scroll({left:v})})},c=()=>{const v=a.e("fixed-header-row"),{columns:f,fixedHeaderData:h,rowHeight:m}=e;return h==null?void 0:h.map((g,y)=>{var _;const b=gs({height:m,width:"100%"});return(_=t.fixed)==null?void 0:_.call(t,{class:v,columns:f,rowData:g,rowIndex:-(y+1),style:b})})},d=()=>{const v=a.e("dynamic-header-row"),{columns:f}=e;return p(s).map((h,m)=>{var y;const g=gs({width:"100%",height:h});return(y=t.dynamic)==null?void 0:y.call(t,{class:v,columns:f,headerIndex:m,style:g})})};return oi(()=>{r!=null&&r.value&&u(r.value)}),n({scrollToLeft:u}),()=>{if(!(e.height<=0))return L("div",{ref:o,class:e.class,style:p(i),role:"rowgroup"},[L("div",{style:p(l),class:a.e("header")},[d(),c()])])}}}),$be="ElTableV2Grid",Rbe=e=>{const t=F(),n=F(),a=F(0),r=D(()=>{const{data:g,rowHeight:y,estimatedRowHeight:b}=e;if(!b)return g.length*y}),o=D(()=>{const{fixedData:g,rowHeight:y}=e;return((g==null?void 0:g.length)||0)*y}),i=D(()=>im(e.headerHeight)),l=D(()=>{const{height:g}=e;return Math.max(0,g-p(i)-p(o))}),s=D(()=>p(i)+p(o)>0),u=({data:g,rowIndex:y})=>g[y][e.rowKey];function c({rowCacheStart:g,rowCacheEnd:y,rowVisibleStart:b,rowVisibleEnd:_}){var w;(w=e.onRowsRendered)==null||w.call(e,{rowCacheStart:g,rowCacheEnd:y,rowVisibleStart:b,rowVisibleEnd:_})}function d(g,y){var b;(b=n.value)==null||b.resetAfterRowIndex(g,y)}function v(g,y){const b=p(t),_=p(n);_t(g)?(b==null||b.scrollToLeft(g.scrollLeft),a.value=g.scrollLeft,_==null||_.scrollTo(g)):(b==null||b.scrollToLeft(g),a.value=g,_==null||_.scrollTo({scrollLeft:g,scrollTop:y}))}function f(g){var y;(y=p(n))==null||y.scrollTo({scrollTop:g})}function h(g,y){const b=p(n);if(!b)return;const _=a.value;b.scrollToItem(g,0,y),_&&v({scrollLeft:_})}function m(){var g,y;(g=p(n))==null||g.$forceUpdate(),(y=p(t))==null||y.$forceUpdate()}return ye(()=>e.bodyWidth,()=>{var g;Je(e.estimatedRowHeight)&&((g=n.value)==null||g.resetAfter({columnIndex:0},!1))}),{bodyRef:n,forceUpdate:m,fixedRowHeight:o,gridHeight:l,hasHeader:s,headerHeight:i,headerRef:t,totalHeight:r,itemKey:u,onItemRendered:c,resetAfterRowIndex:d,scrollTo:v,scrollToTop:f,scrollToRow:h,scrollLeft:a}},$w=q({name:$be,props:om,setup(e,{slots:t,expose:n}){const{ns:a}=Ze(Ow),{bodyRef:r,fixedRowHeight:o,gridHeight:i,hasHeader:l,headerRef:s,headerHeight:u,totalHeight:c,forceUpdate:d,itemKey:v,onItemRendered:f,resetAfterRowIndex:h,scrollTo:m,scrollToTop:g,scrollToRow:y,scrollLeft:b}=Rbe(e);Ft(ND,b),Wh(async()=>{var S;await He();const w=(S=r.value)==null?void 0:S.states.scrollTop;w&&g(Math.round(w)+1)}),n({forceUpdate:d,totalHeight:c,scrollTo:m,scrollToTop:g,scrollToRow:y,resetAfterRowIndex:h});const _=()=>e.bodyWidth;return()=>{const{cache:w,columns:S,data:k,fixedData:T,useIsScrolling:M,scrollbarAlwaysOn:E,scrollbarEndGap:A,scrollbarStartGap:I,style:P,rowHeight:R,bodyWidth:G,estimatedRowHeight:K,headerWidth:H,height:z,width:B,getRowHeight:W,onScroll:N}=e,V=Je(K),Y=V?$de:Lde,X=p(u);return L("div",{role:"table",class:[a.e("table"),e.class],style:P},[L(Y,{ref:r,data:k,useIsScrolling:M,itemKey:v,columnCache:0,columnWidth:V?_:G,totalColumn:1,totalRow:k.length,rowCache:w,rowHeight:V?W:R,width:B,height:p(i),class:a.e("body"),role:"rowgroup",scrollbarStartGap:I,scrollbarEndGap:A,scrollbarAlwaysOn:E,onScroll:N,onItemRendered:f,perfMode:!1},{default:ee=>{var ie;const re=k[ee.rowIndex];return(ie=t.row)==null?void 0:ie.call(t,{...ee,columns:S,rowData:re})}}),p(l)&&L(Obe,{ref:s,class:a.e("header-wrapper"),columns:S,headerData:k,headerHeight:e.headerHeight,fixedHeaderData:T,rowWidth:H,rowHeight:R,width:B,height:Math.min(X+p(o),z)},{dynamic:t.header,fixed:t.row})])}}});function Nbe(e){return typeof e=="function"||Object.prototype.toString.call(e)==="[object Object]"&&!bn(e)}const Bbe=(e,{slots:t})=>{const{mainTableRef:n,...a}=e;return L($w,Tt({ref:n},a),Nbe(t)?t:{default:()=>[t]})};function Vbe(e){return typeof e=="function"||Object.prototype.toString.call(e)==="[object Object]"&&!bn(e)}const zbe=(e,{slots:t})=>{if(!e.columns.length)return;const{leftTableRef:n,...a}=e;return L($w,Tt({ref:n},a),Vbe(t)?t:{default:()=>[t]})};function Fbe(e){return typeof e=="function"||Object.prototype.toString.call(e)==="[object Object]"&&!bn(e)}const Hbe=(e,{slots:t})=>{if(!e.columns.length)return;const{rightTableRef:n,...a}=e;return L($w,Tt({ref:n},a),Fbe(t)?t:{default:()=>[t]})},Wbe=e=>{const{isScrolling:t}=Ze(Ow),n=F(!1),a=F(),r=D(()=>Je(e.estimatedRowHeight)&&e.rowIndex>=0),o=(s=!1)=>{const u=p(a);if(!u)return;const{columns:c,onRowHeightChange:d,rowKey:v,rowIndex:f,style:h}=e,{height:m}=u.getBoundingClientRect();n.value=!0,He(()=>{if(s||m!==Number.parseInt(h.height)){const g=c[0],y=(g==null?void 0:g.placeholderSign)===Sh;d==null||d({rowKey:v,height:m,rowIndex:f},g&&!y&&g.fixed)}})},i=D(()=>{const{rowData:s,rowIndex:u,rowKey:c,onRowHover:d}=e,v=e.rowEventHandlers||{},f={};return Object.entries(v).forEach(([h,m])=>{Qe(m)&&(f[h]=g=>{m({event:g,rowData:s,rowIndex:u,rowKey:c})})}),d&&[{name:"onMouseleave",hovered:!1},{name:"onMouseenter",hovered:!0}].forEach(({name:h,hovered:m})=>{const g=f[h];f[h]=y=>{d({event:y,hovered:m,rowData:s,rowIndex:u,rowKey:c}),g==null||g(y)}}),f}),l=s=>{const{onRowExpand:u,rowData:c,rowIndex:d,rowKey:v}=e;u==null||u({expanded:s,rowData:c,rowIndex:d,rowKey:v})};return mt(()=>{p(r)&&o(!0)}),{isScrolling:t,measurable:r,measured:n,rowRef:a,eventHandlers:i,onExpand:l}},Kbe=q({name:"ElTableV2TableRow",props:Bu,setup(e,{expose:t,slots:n,attrs:a}){const{eventHandlers:r,isScrolling:o,measurable:i,measured:l,rowRef:s,onExpand:u}=Wbe(e);return t({onExpand:u}),()=>{const{columns:c,columnsStyles:d,expandColumnKey:v,depth:f,rowData:h,rowIndex:m,style:g}=e;let y=c.map((b,_)=>{const w=Ae(h.children)&&h.children.length>0&&b.key===v;return n.cell({column:b,columns:c,columnIndex:_,depth:f,style:d[b.key],rowData:h,rowIndex:m,isScrolling:p(o),expandIconProps:w?{rowData:h,rowIndex:m,onExpand:u}:void 0})});if(n.row&&(y=n.row({cells:y.map(b=>Ae(b)&&b.length===1?b[0]:b),style:g,columns:c,depth:f,rowData:h,rowIndex:m,isScrolling:p(o)})),p(i)){const{height:b,..._}=g||{},w=p(l);return L("div",Tt({ref:s,class:e.class,style:w?g:_,role:"row"},a,p(r)),[y])}return L("div",Tt(a,{ref:s,class:e.class,style:g,role:"row"},p(r)),[y])}}});function Ube(e){return typeof e=="function"||Object.prototype.toString.call(e)==="[object Object]"&&!bn(e)}const Gbe=(e,{slots:t})=>{const{columns:n,columnsStyles:a,depthMap:r,expandColumnKey:o,expandedRowKeys:i,estimatedRowHeight:l,hasFixedColumns:s,rowData:u,rowIndex:c,style:d,isScrolling:v,rowProps:f,rowClass:h,rowKey:m,rowEventHandlers:g,ns:y,onRowHovered:b,onRowExpanded:_}=e,w=vc(h,{columns:n,rowData:u,rowIndex:c},""),S=vc(f,{columns:n,rowData:u,rowIndex:c}),k=u[m],T=r[k]||0,M=!!o,E=c<0,A=[y.e("row"),w,y.is("expanded",M&&i.includes(k)),y.is("fixed",!T&&E),y.is("customized",!!t.row),{[y.e(`row-depth-${T}`)]:M&&c>=0}],I=s?b:void 0,P={...S,columns:n,columnsStyles:a,class:A,depth:T,expandColumnKey:o,estimatedRowHeight:E?void 0:l,isScrolling:v,rowIndex:c,rowData:u,rowKey:k,rowEventHandlers:g,style:d};return L(Kbe,Tt(P,{onRowExpand:_,onMouseenter:K=>{I==null||I({hovered:!0,rowKey:k,event:K,rowData:u,rowIndex:c})},onMouseleave:K=>{I==null||I({hovered:!1,rowKey:k,event:K,rowData:u,rowIndex:c})},rowkey:k}),Ube(t)?t:{default:()=>[t]})},Rw=(e,{slots:t})=>{var i;const{cellData:n,style:a}=e,r=((i=n==null?void 0:n.toString)==null?void 0:i.call(n))||"",o=ce(t,"default",e,()=>[r]);return L("div",{class:e.class,title:r,style:a},[o])};Rw.displayName="ElTableV2Cell";Rw.inheritAttrs=!1;const BD=e=>{const{expanded:t,expandable:n,onExpand:a,style:r,size:o,ariaLabel:i}=e;return L("button",Tt({onClick:n?()=>a(!t):void 0,ariaLabel:i,ariaExpanded:t,class:e.class},{type:"button"}),[L(Xe,{size:o,style:r},{default:()=>[L(za,null,null)]})])};BD.inheritAttrs=!1;const n_=({columns:e,column:t,columnIndex:n,depth:a,expandIconProps:r,isScrolling:o,rowData:i,rowIndex:l,style:s,expandedRowKeys:u,ns:c,t:d,cellProps:v,expandColumnKey:f,indentSize:h,iconSize:m,rowKey:g},{slots:y})=>{const b=gs(s);if(t.placeholderSign===Sh)return L("div",{class:c.em("row-cell","placeholder"),style:b},null);const{cellRenderer:_,dataKey:w,dataGetter:S}=t,k=Qe(S)?S({columns:e,column:t,columnIndex:n,rowData:i,rowIndex:l}):na(i,w??""),T=vc(v,{cellData:k,columns:e,column:t,columnIndex:n,rowIndex:l,rowData:i}),M={class:c.e("cell-text"),columns:e,column:t,columnIndex:n,cellData:k,isScrolling:o,rowData:i,rowIndex:l},E=RD(_),A=E?E(M):ce(y,"default",M,()=>[L(Rw,M,null)]),I=[c.e("row-cell"),t.class,t.align==="center"&&c.is("align-center"),t.align==="right"&&c.is("align-right")],P=l>=0&&f&&t.key===f,R=l>=0&&u.includes(i[g]);let G;const K=`margin-inline-start: ${a*h}px;`;return P&&(_t(r)?G=L(BD,Tt(r,{class:[c.e("expand-icon"),c.is("expanded",R)],size:m,expanded:R,ariaLabel:d(R?"el.table.collapseRowLabel":"el.table.expandRowLabel"),style:K,expandable:!0}),null):G=L("div",{style:[K,`width: ${m}px; height: ${m}px;`].join(" ")},null)),L("div",Tt({class:I,style:b},T,{role:"cell"}),[G,A])};n_.inheritAttrs=!1;const Ybe=$e({class:String,columns:dp,columnsStyles:{type:ae(Object),required:!0},headerIndex:Number,style:{type:ae(Object)}}),jbe=q({name:"ElTableV2HeaderRow",props:Ybe,setup(e,{slots:t}){return()=>{const{columns:n,columnsStyles:a,headerIndex:r,style:o}=e;let i=n.map((l,s)=>t.cell({columns:n,column:l,columnIndex:s,headerIndex:r,style:a[l.key]}));return t.header&&(i=t.header({cells:i.map(l=>Ae(l)&&l.length===1?l[0]:l),columns:n,headerIndex:r})),L("div",{class:e.class,style:o,role:"row"},[i])}}});function qbe(e){return typeof e=="function"||Object.prototype.toString.call(e)==="[object Object]"&&!bn(e)}const Xbe=({columns:e,columnsStyles:t,headerIndex:n,style:a,headerClass:r,headerProps:o,ns:i},{slots:l})=>{const s={columns:e,headerIndex:n},u=[i.e("header-row"),vc(r,s,""),i.is("customized",!!l.header)];return L(jbe,{...vc(o,s),columnsStyles:t,class:u,columns:e,headerIndex:n,style:a},qbe(l)?l:{default:()=>[l]})},Nw=(e,{slots:t})=>ce(t,"default",e,()=>{var n,a;return[L("div",{class:e.class,title:(n=e.column)==null?void 0:n.title},[(a=e.column)==null?void 0:a.title])]});Nw.displayName="ElTableV2HeaderCell";Nw.inheritAttrs=!1;const Zbe=e=>{const{sortOrder:t}=e;return L("button",{type:"button","aria-label":e.ariaLabel,class:e.class},[L(Xe,{size:14},{default:()=>[L(t==="asc"?KI:WI,null,null)]})])},Hx=(e,{slots:t})=>{const{column:n,ns:a,t:r,style:o,onColumnSorted:i}=e,l=gs(o);if(n.placeholderSign===Sh)return L("div",{class:a.em("header-row-cell","placeholder"),style:l},null);const{headerCellRenderer:s,headerClass:u,sortable:c}=n,d={...e,class:a.e("header-cell-text")},v=RD(s),f=v?v(d):ce(t,"default",d,()=>[L(Nw,d,null)]),{sortBy:h,sortState:m,headerCellProps:g}=e;let y,b,_;if(m){const S=m[n.key];y=!!t_[S],b=y?S:"asc"}else y=n.key===h.key,b=y?h.order:"asc";b==="asc"?_="ascending":b==="desc"?_="descending":_=void 0;const w=[a.e("header-cell"),vc(u,e,""),n.align==="center"&&a.is("align-center"),n.align==="right"&&a.is("align-right"),c&&a.is("sortable")];return L("div",Tt({...vc(g,e),onClick:n.sortable?i:void 0,ariaSort:c?_:void 0,class:w,style:l,"data-key":n.key},{role:"columnheader"}),[f,c&&L(Zbe,{class:[a.e("sort-icon"),y&&a.is("sorting")],sortOrder:b,ariaLabel:r("el.table.sortLabel",{column:n.title||""})},null)])},VD=(e,{slots:t})=>{var n;return L("div",{class:e.class,style:e.style},[(n=t.default)==null?void 0:n.call(t)])};VD.displayName="ElTableV2Footer";const zD=(e,{slots:t})=>{const n=ce(t,"default",{},()=>[L(dD,null,null)]);return L("div",{class:e.class,style:e.style},[n])};zD.displayName="ElTableV2Empty";const FD=(e,{slots:t})=>{var n;return L("div",{class:e.class,style:e.style},[(n=t.default)==null?void 0:n.call(t)])};FD.displayName="ElTableV2Overlay";function $f(e){return typeof e=="function"||Object.prototype.toString.call(e)==="[object Object]"&&!bn(e)}const Jbe=q({name:"ElTableV2",props:kbe,slots:Object,setup(e,{slots:t,expose:n}){const a=ke("table-v2"),{t:r}=Vt(),{columnsStyles:o,fixedColumnsOnLeft:i,fixedColumnsOnRight:l,mainColumns:s,mainTableHeight:u,fixedTableHeight:c,leftTableWidth:d,rightTableWidth:v,data:f,depthMap:h,expandedRowKeys:m,hasFixedColumns:g,mainTableRef:y,leftTableRef:b,rightTableRef:_,isDynamic:w,isResetting:S,isScrolling:k,bodyWidth:T,emptyStyle:M,rootStyle:E,footerHeight:A,showEmpty:I,scrollTo:P,scrollToLeft:R,scrollToTop:G,scrollToRow:K,getRowHeight:H,onColumnSorted:z,onRowHeightChange:B,onRowHovered:W,onRowExpanded:N,onRowsRendered:V,onScroll:Y,onVerticalScroll:X}=Dbe(e);return n({scrollTo:P,scrollToLeft:R,scrollToTop:G,scrollToRow:K}),Ft(Ow,{ns:a,isResetting:S,isScrolling:k}),()=>{const{cache:ee,cellProps:re,estimatedRowHeight:ie,expandColumnKey:le,fixedData:ve,headerHeight:he,headerClass:J,headerProps:Z,headerCellProps:j,sortBy:oe,sortState:se,rowHeight:fe,rowClass:ge,rowEventHandlers:Se,rowKey:De,rowProps:Te,scrollbarAlwaysOn:Re,indentSize:Ce,iconSize:Le,useIsScrolling:me,vScrollbarSize:Be,width:ze}=e,xe=p(f),rt={cache:ee,class:a.e("main"),columns:p(s),data:xe,fixedData:ve,estimatedRowHeight:ie,bodyWidth:p(T),headerHeight:he,headerWidth:p(T),height:p(u),mainTableRef:y,rowKey:De,rowHeight:fe,scrollbarAlwaysOn:Re,scrollbarStartGap:2,scrollbarEndGap:Be,useIsScrolling:me,width:ze,getRowHeight:H,onRowsRendered:V,onScroll:Y},nt=p(d),Pt=p(c),Wt={cache:ee,class:a.e("left"),columns:p(i),data:xe,fixedData:ve,estimatedRowHeight:ie,leftTableRef:b,rowHeight:fe,bodyWidth:nt,headerWidth:nt,headerHeight:he,height:Pt,rowKey:De,scrollbarAlwaysOn:Re,scrollbarStartGap:2,scrollbarEndGap:Be,useIsScrolling:me,width:nt,getRowHeight:H,onScroll:X},Jt=p(v),Rt={cache:ee,class:a.e("right"),columns:p(l),data:xe,fixedData:ve,estimatedRowHeight:ie,rightTableRef:_,rowHeight:fe,bodyWidth:Jt,headerWidth:Jt,headerHeight:he,height:Pt,rowKey:De,scrollbarAlwaysOn:Re,scrollbarStartGap:2,scrollbarEndGap:Be,width:Jt,style:`${a.cssVarName("table-scrollbar-size")}: ${Be}px`,useIsScrolling:me,getRowHeight:H,onScroll:X},Qt=p(o),_e={ns:a,depthMap:p(h),columnsStyles:Qt,expandColumnKey:le,expandedRowKeys:p(m),estimatedRowHeight:ie,hasFixedColumns:p(g),rowProps:Te,rowClass:ge,rowKey:De,rowEventHandlers:Se,onRowHovered:W,onRowExpanded:N,onRowHeightChange:B},Ne={cellProps:re,expandColumnKey:le,indentSize:Ce,iconSize:Le,rowKey:De,expandedRowKeys:p(m),ns:a,t:r},tt={ns:a,headerClass:J,headerProps:Z,columnsStyles:Qt},Me={ns:a,t:r,sortBy:oe,sortState:se,headerCellProps:j,onColumnSorted:z},Ue={row:ft=>L(Gbe,Tt(ft,_e),{row:t.row,cell:ht=>{let pt;return t.cell?L(n_,Tt(ht,Ne,{style:Qt[ht.column.key]}),$f(pt=t.cell(ht))?pt:{default:()=>[pt]}):L(n_,Tt(ht,Ne,{style:Qt[ht.column.key]}),null)}}),header:ft=>L(Xbe,Tt(ft,tt),{header:t.header,cell:ht=>{let pt;return t["header-cell"]?L(Hx,Tt(ht,Me,{style:Qt[ht.column.key]}),$f(pt=t["header-cell"](ht))?pt:{default:()=>[pt]}):L(Hx,Tt(ht,Me,{style:Qt[ht.column.key]}),null)}})},Gt=[e.class,a.b(),a.e("root"),a.is("dynamic",p(w))],je={class:a.e("footer"),style:p(A)};return L("div",{class:Gt,style:p(E)},[L(Bbe,rt,$f(Ue)?Ue:{default:()=>[Ue]}),L(zbe,Wt,$f(Ue)?Ue:{default:()=>[Ue]}),L(Hbe,Rt,$f(Ue)?Ue:{default:()=>[Ue]}),t.footer&&L(VD,je,{default:t.footer}),p(I)&&L(zD,{class:a.e("empty"),style:p(M)},{default:t.empty}),t.overlay&&L(FD,{class:a.e("overlay")},{default:t.overlay})])}}}),Qbe=$e({disableWidth:Boolean,disableHeight:Boolean,onResize:{type:ae(Function)}}),e4e=e=>{const t=F(),n=F(0),a=F(0);let r;return mt(()=>{r=Mn(t,([o])=>{const{width:i,height:l}=o.contentRect,{paddingLeft:s,paddingRight:u,paddingTop:c,paddingBottom:d}=getComputedStyle(o.target),v=Number.parseInt(s)||0,f=Number.parseInt(u)||0,h=Number.parseInt(c)||0,m=Number.parseInt(d)||0;n.value=i-v-f,a.value=l-h-m}).stop}),rn(()=>{r==null||r()}),ye([n,a],([o,i])=>{var l;(l=e.onResize)==null||l.call(e,{width:o,height:i})}),{sizer:t,width:n,height:a}},t4e=q({name:"ElAutoResizer",props:Qbe,setup(e,{slots:t}){const n=ke("auto-resizer"),{height:a,width:r,sizer:o}=e4e(e),i={width:"100%",height:"100%"};return()=>{var l;return L("div",{ref:o,class:n.b(),style:i},[(l=t.default)==null?void 0:l.call(t,{height:a.value,width:r.value})])}}}),n4e=wt(Jbe),a4e=wt(t4e),j1=Symbol("tabsRootContextKey"),r4e=$e({tabs:{type:ae(Array),default:()=>Nn([])},tabRefs:{type:ae(Object),default:()=>Nn({})}}),Wx="ElTabBar";var o4e=q({name:Wx,__name:"tab-bar",props:r4e,setup(e,{expose:t}){const n=e,a=Ze(j1);a||Dn(Wx,"");const r=ke("tabs"),o=F(),i=F(),l=D(()=>{var f;return zt(a.props.defaultValue)||!!((f=i.value)!=null&&f.transform)}),s=()=>{let f=0,h=0;const m=["top","bottom"].includes(a.props.tabPosition)?"width":"height",g=m==="width"?"x":"y",y=g==="x"?"left":"top";return n.tabs.every(b=>{if(zt(b.paneName))return!1;const _=n.tabRefs[b.paneName];if(!_)return!1;if(!b.active)return!0;f=_[`offset${W2(y)}`],h=_[`client${W2(m)}`];const w=window.getComputedStyle(_);return m==="width"&&(h-=Number.parseFloat(w.paddingLeft)+Number.parseFloat(w.paddingRight),f+=Number.parseFloat(w.paddingLeft)),!1}),{[m]:`${h}px`,transform:`translate${W2(g)}(${f}px)`}},u=()=>i.value=s(),c=[],d=()=>{c.forEach(f=>f.stop()),c.length=0,Object.values(n.tabRefs).forEach(f=>{c.push(Mn(f,u))})};ye(()=>n.tabs,async()=>{await He(),u(),d()},{immediate:!0});const v=Mn(o,()=>u());return rn(()=>{c.forEach(f=>f.stop()),c.length=0,v.stop()}),t({ref:o,update:u}),(f,h)=>l.value?(x(),O("div",{key:0,ref_key:"barRef",ref:o,class:U([p(r).e("active-bar"),p(r).is(p(a).props.tabPosition)]),style:et(i.value)},null,6)):te("v-if",!0)}}),i4e=o4e;const l4e=5,s4e=({scrollable:e,navOffset:t,navSize:n,navContainerSize:a,isHorizontal:r})=>{const o=F(!1),i=D(()=>Math.max(n.value-a.value,0));let l,s;const u=v=>{if(!e.value||v.touches.length!==1)return;const{clientX:f,clientY:h}=sg(v);l={startX:f,startY:h,startOffset:t.value},s=void 0},c=v=>{if(!l||!e.value)return;if(v.touches.length!==1){d();return}const{clientX:f,clientY:h}=sg(v),m=l.startX-f,g=l.startY-h,y=r.value?m:g,b=r.value?g:m,_=Math.abs(y),w=Math.abs(b);if(CA(s)){if(Math.max(_,w)<=l4e)return;s=_>w}if(!s)return;const S=Br(l.startOffset+y,0,i.value);i.value<=0||S===t.value||!v.cancelable||(v.preventDefault(),o.value=!0,t.value=S)},d=()=>{l=void 0,s=void 0,o.value=!1};return{isTouchScrolling:o,handleTouchStart:u,handleTouchMove:c,handleTouchEnd:d}},u4e=$e({panes:{type:ae(Array),default:()=>Nn([])},currentName:{type:[String,Number],default:""},editable:Boolean,type:{type:String,values:["card","border-card",""],default:""},stretch:Boolean,tabindex:{type:[String,Number],default:void 0}}),c4e={tabClick:(e,t,n)=>n instanceof Event,tabRemove:(e,t)=>t instanceof Event},Kx="ElTabNav",d4e=q({name:Kx,props:u4e,emits:c4e,setup(e,{expose:t,emit:n}){const a=Ze(j1);a||Dn(Kx,"");const r=ke("tabs"),o=LY(),i=OY(),l=F(),s=F(),u=F(),c=F({}),d=F(),v=F(!1),f=F(0),h=F(!1),m=F(!0),g=F(!1),y=Ht(),b=D(()=>["top","bottom"].includes(a.props.tabPosition)),_=D(()=>b.value?"width":"height"),w=D(()=>{const ie=_.value==="width"?"X":"Y";return{transition:g.value||R.value?"none":void 0,transform:`translate${ie}(-${f.value}px)`}}),{width:S,height:k}=Um(l),{width:T,height:M}=Um(s,{width:0,height:0},{box:"border-box"}),E=D(()=>b.value?S.value:k.value),A=D(()=>b.value?T.value:M.value),{onWheel:I}=tP({atStartEdge:D(()=>f.value<=0),atEndEdge:D(()=>A.value-f.value<=E.value),layout:D(()=>b.value?"horizontal":"vertical")},ie=>{f.value=Br(f.value+ie,0,A.value-E.value)}),P=ie=>{g.value=!0,I(ie),Wr(()=>{g.value=!1})},{isTouchScrolling:R,handleTouchStart:G,handleTouchMove:K,handleTouchEnd:H}=s4e({scrollable:v,navOffset:f,navSize:A,navContainerSize:E,isHorizontal:b}),z=()=>{if(!l.value)return;const ie=l.value.getBoundingClientRect()[_.value],le=f.value;le&&(f.value=le>ie?le-ie:0)},B=()=>{if(!l.value||!s.value)return;const ie=s.value.getBoundingClientRect()[_.value],le=l.value.getBoundingClientRect()[_.value],ve=f.value;Vi(ie-ve,le)&&(f.value=ie-ve>le*2?ve+le:ie-le)},W=async()=>{const ie=s.value;if(!v.value||!u.value||!l.value||!ie)return;await He();const le=c.value[e.currentName];if(!le)return;const ve=l.value,he=le.getBoundingClientRect(),J=ve.getBoundingClientRect(),Z=J.left+1,j=J.right-1,oe=ie.getBoundingClientRect(),se=b.value?oe.width-J.width:oe.height-J.height,fe=f.value;let ge=fe;b.value?(he.leftj&&(ge=fe+he.right-j)):(he.topJ.bottom&&(ge=fe+(he.bottom-J.bottom))),ge=Math.max(ge,0),f.value=Math.min(ge,se)},N=()=>{var he;if(!s.value||!l.value)return;e.stretch&&((he=d.value)==null||he.update());const ie=s.value.getBoundingClientRect()[_.value],le=l.value.getBoundingClientRect()[_.value],ve=f.value;le0&&(f.value=0))},V=ie=>{const le=mn(ie);let ve=0;switch(le){case Pe.left:case Pe.up:ve=-1;break;case Pe.right:case Pe.down:ve=1;break;default:return}const he=Array.from(ie.currentTarget.querySelectorAll("[role=tab]:not(.is-disabled)"));let J=he.indexOf(ie.target)+ve;J<0?J=he.length-1:J>=he.length&&(J=0),he[J].focus({preventScroll:!0}),he[J].click(),Y()},Y=()=>{m.value&&(h.value=!0)},X=()=>h.value=!1,ee=(ie,le)=>{c.value[le]=ie},re=async()=>{var ie;await He(),(ie=c.value[e.currentName])==null||ie.focus({preventScroll:!0})};return ye(o,ie=>{ie==="hidden"?m.value=!1:ie==="visible"&&setTimeout(()=>m.value=!0,50)}),ye(i,ie=>{ie?setTimeout(()=>m.value=!0,50):m.value=!1}),Mn(u,()=>{Wr(N)}),mt(()=>setTimeout(()=>W(),0)),oi(()=>N()),t({scrollToActiveTab:W,removeFocus:X,focusActiveTab:re,tabListRef:s,tabBarRef:d,scheduleRender:()=>Y0(y)}),()=>{const ie=v.value?[L("span",{class:[r.e("nav-prev"),r.is("disabled",!v.value.prev)],onClick:z},[L(Xe,null,{default:()=>[L(Ji,null,null)]})]),L("span",{class:[r.e("nav-next"),r.is("disabled",!v.value.next)],onClick:B},[L(Xe,null,{default:()=>[L(za,null,null)]})])]:null,le=e.panes.map((ve,he)=>{var Se,De;const J=ve.uid,Z=ve.props.disabled,j=ve.props.name??ve.index??`${he}`,oe=!Z&&(ve.isClosable||ve.props.closable!==!1&&e.editable);ve.index=`${he}`;const se=oe?L(Xe,{class:"is-icon-close",onClick:Te=>n("tabRemove",ve,Te)},{default:()=>[L(so,null,null)]}):null,fe=((De=(Se=ve.slots).label)==null?void 0:De.call(Se))||ve.props.label,ge=!Z&&ve.active?e.tabindex??a.props.tabindex:-1;return L("div",{ref:Te=>ee(Te,j),class:[r.e("item"),r.is(a.props.tabPosition),r.is("active",ve.active),r.is("disabled",Z),r.is("closable",oe),r.is("focus",h.value)],id:`tab-${j}`,key:`tab-${J}`,"aria-controls":`pane-${j}`,role:"tab","aria-selected":ve.active,tabindex:ge,onFocus:()=>Y(),onBlur:()=>X(),onClick:Te=>{X(),n("tabClick",ve,j,Te)},onKeydown:Te=>{const Re=mn(Te);oe&&(Re===Pe.delete||Re===Pe.backspace)&&n("tabRemove",ve,Te)}},[fe,se])});return y.value,L("div",{ref:u,class:[r.e("nav-wrap"),r.is("scrollable",!!v.value),r.is(a.props.tabPosition)]},[ie,L("div",{class:r.e("nav-scroll"),ref:l},[e.panes.length>0?L("div",{class:[r.e("nav"),r.is(a.props.tabPosition),r.is("stretch",e.stretch&&["top","bottom"].includes(a.props.tabPosition))],ref:s,style:w.value,role:"tablist",onKeydown:V,onWheel:P,onTouchstart:G,onTouchmove:K,onTouchend:H,onTouchcancel:H},[e.type?null:L(i4e,{ref:d,tabs:[...e.panes],tabRefs:c.value},null),le]):null])])}}}),f4e=$e({type:{type:String,values:["card","border-card",""],default:""},closable:Boolean,addable:Boolean,modelValue:{type:[String,Number]},defaultValue:{type:[String,Number]},editable:Boolean,tabPosition:{type:String,values:["top","right","bottom","left"],default:"top"},beforeLeave:{type:ae(Function),default:()=>!0},stretch:Boolean,tabindex:{type:[String,Number],default:0}}),vy=e=>Ye(e)||Je(e),v4e={[bt]:e=>vy(e),tabClick:(e,t)=>t instanceof Event,tabChange:e=>vy(e),edit:(e,t)=>["remove","add"].includes(t),tabRemove:e=>vy(e),tabAdd:()=>!0},h4e=q({name:"ElTabs",props:f4e,emits:v4e,setup(e,{emit:t,slots:n,expose:a}){const r=ke("tabs"),o=D(()=>["left","right"].includes(e.tabPosition)),{children:i,addChild:l,removeChild:s,ChildrenSorter:u}=z1(Et(),"ElTabPane"),c=F(),d=F((zt(e.modelValue)?e.defaultValue:e.modelValue)??"0"),v=async(b,_=!1)=>{var w,S,k,T;if(!(d.value===b||zt(b)))try{let M;if(e.beforeLeave){const E=e.beforeLeave(b,d.value);M=E instanceof Promise?await E:E}else M=!0;if(M!==!1){const E=(w=i.value.find(A=>A.paneName===d.value))==null?void 0:w.isFocusInsidePane();d.value=b,_&&(t(bt,b),t("tabChange",b)),(k=(S=c.value)==null?void 0:S.removeFocus)==null||k.call(S),E&&((T=c.value)==null||T.focusActiveTab())}}catch{}},f=(b,_,w)=>{b.props.disabled||(t("tabClick",b,w),v(_,!0))},h=(b,_)=>{b.props.disabled||zt(b.props.name)||(_.stopPropagation(),t("edit",b.props.name,"remove"),t("tabRemove",b.props.name))},m=()=>{t("edit",void 0,"add"),t("tabAdd")},g=b=>{const _=mn(b);[Pe.enter,Pe.numpadEnter].includes(_)&&m()},y=b=>{const _=b.el.firstChild,w=["bottom","right"].includes(e.tabPosition)?b.children[0].el:b.children[1].el;_!==w&&_.before(w)};return ye(()=>e.modelValue,b=>v(b)),ye(d,async()=>{var b;await He(),(b=c.value)==null||b.scrollToActiveTab()}),Ft(j1,{props:e,currentName:d,registerPane:l,unregisterPane:s,nav$:c}),a({currentName:d,get tabNavRef(){return Tc(c.value,["scheduleRender"])}}),()=>{const b=n["add-icon"],_=e.editable||e.addable?L("div",{class:[r.e("new-tab"),o.value&&r.e("new-tab-vertical")],tabindex:e.tabindex,onClick:m,onKeydown:g},[b?ce(n,"add-icon"):L(Xe,{class:r.is("icon-plus")},{default:()=>[L(hf,null,null)]})]):null,w=()=>L(d4e,{ref:c,currentName:d.value,editable:e.editable,type:e.type,panes:i.value,stretch:e.stretch,onTabClick:f,onTabRemove:h},null),S=L("div",{class:[r.e("header"),o.value&&r.e("header-vertical"),r.is(e.tabPosition)]},[L(u,null,{default:w,$stable:!0}),_]),k=L("div",{class:r.e("content")},[ce(n,"default")]);return L("div",{class:[r.b(),r.m(e.tabPosition),{[r.m("card")]:e.type==="card",[r.m("border-card")]:e.type==="border-card"}],onVnodeMounted:y,onVnodeUpdated:y},[k,S])}}}),p4e=$e({label:{type:String,default:""},name:{type:[String,Number]},closable:{type:Boolean,default:void 0},disabled:Boolean,lazy:Boolean}),m4e=["id","aria-hidden","aria-labelledby"],Ux="ElTabPane";var g4e=q({name:Ux,__name:"tab-pane",props:p4e,setup(e){const t=e,n=Et(),a=Xn(),r=Ze(j1);r||Dn(Ux,"usage: ");const o=ke("tab-pane"),i=F(),l=F(),s=D(()=>t.closable??r.props.closable),u=D(()=>r.currentName.value===(t.name??l.value)),c=F(u.value),d=D(()=>t.name??l.value),v=D(()=>!t.lazy||c.value||u.value),f=()=>{var m;return(m=i.value)==null?void 0:m.contains(document.activeElement)};ye(u,m=>{m&&(c.value=!0)});const h=nn({uid:n.uid,getVnode:()=>n.vnode,slots:a,props:t,paneName:d,active:u,index:l,isClosable:s,isFocusInsidePane:f});return r.registerPane(h),rn(()=>{r.unregisterPane(h)}),q3(()=>{var m;a.label&&((m=r.nav$.value)==null||m.scheduleRender())}),(m,g)=>v.value?st((x(),O("div",{key:0,id:`pane-${d.value}`,ref_key:"paneRef",ref:i,class:U(p(o).b()),role:"tabpanel","aria-hidden":!u.value,"aria-labelledby":`tab-${d.value}`},[ce(m.$slots,"default")],10,m4e)),[[Zt,u.value]]):te("v-if",!0)}}),HD=g4e;const y4e=wt(h4e,{TabPane:HD}),b4e=On(HD),_4e=$e({type:{type:String,values:["primary","success","info","warning","danger",""],default:""},size:{type:String,values:ii,default:""},truncated:Boolean,lineClamp:{type:[String,Number]},tag:{type:String,default:"span"}});var w4e=q({name:"ElText",__name:"text",props:_4e,setup(e){const t=e,n=Nl(),a=F(),r=ca(),o=ke("text"),i=D(()=>[o.b(),o.m(t.type),o.m(r.value),o.is("truncated",t.truncated),o.is("line-clamp",!zt(t.lineClamp))]),l=async()=>{var c,d,v,f,h,m,g;if(await He(),n.title)return;let s=!1;const u=((c=a.value)==null?void 0:c.textContent)||"";if(t.truncated){const y=(d=a.value)==null?void 0:d.offsetWidth,b=(v=a.value)==null?void 0:v.scrollWidth;y&&b&&b>y&&(s=!0)}else if(!zt(t.lineClamp)){const y=(f=a.value)==null?void 0:f.offsetHeight,b=(h=a.value)==null?void 0:h.scrollHeight;y&&b&&b>y&&(s=!0)}s?(m=a.value)==null||m.setAttribute("title",u):(g=a.value)==null||g.removeAttribute("title")};return mt(l),oi(l),(s,u)=>(x(),ne(St(e.tag),{ref_key:"textRef",ref:a,class:U(i.value),style:et({"-webkit-line-clamp":e.lineClamp})},{default:$(()=>[ce(s.$slots,"default")]),_:3},8,["class","style"]))}}),S4e=w4e;const Bw=wt(S4e),WD="09:00",KD="18:00",UD="00:30",C4e=$e({format:{type:String,default:"HH:mm"},modelValue:{type:ae(String)},disabled:{type:Boolean,default:void 0},editable:{type:Boolean,default:!0},effect:{type:ae(String),default:"light"},clearable:{type:Boolean,default:!0},size:pa,placeholder:String,start:{type:String,default:WD},end:{type:String,default:KD},step:{type:String,default:UD},minTime:{type:ae(String)},maxTime:{type:ae(String)},includeEndTime:Boolean,name:String,prefixIcon:{type:ae([String,Object]),default:()=>H6},clearIcon:{type:ae([String,Object]),default:()=>ci},popperClass:{type:ae([String,Array,Object,Boolean]),default:""},popperStyle:{type:ae([String,Array,Object,Boolean]),default:void 0},...Mc}),Zu=e=>{const t=(e||"").split(":");if(t.length>=2){let n=Number.parseInt(t[0],10);const a=Number.parseInt(t[1],10);if(Number.isNaN(n)||Number.isNaN(a))return null;const r=e.toUpperCase();return r.includes("AM")&&n===12?n=0:r.includes("PM")&&n!==12&&(n+=12),{hours:n,minutes:a}}return null},x4e=e=>!!e&&e.hours>=0&&e.minutes>=0&&!Number.isNaN(e.hours)&&!Number.isNaN(e.minutes),hy=(e,t)=>{const n=Zu(e);if(!n)return-1;const a=Zu(t);if(!a)return-1;const r=n.minutes+n.hours*60,o=a.minutes+a.hours*60;return r===o?0:r>o?1:-1},Gx=e=>`${e}`.padStart(2,"0"),lm=e=>`${Gx(e.hours)}:${Gx(e.minutes)}`,k4e=(e,t)=>{const n=Zu(e);if(!n)return"";const a=Zu(t);if(!a)return"";const r={hours:n.hours,minutes:n.minutes};return r.minutes+=a.minutes,r.hours+=a.hours,r.hours+=Math.floor(r.minutes/60),r.minutes=r.minutes%60,lm(r)};var T4e=q({name:"ElTimeSelect",__name:"time-select",props:C4e,emits:[Ot,"blur","focus","clear",bt],setup(e,{expose:t}){gt.extend(rw);const{Option:n}=Bs,a=e,r=ke("input"),o=F(),i=$n(),{lang:l}=Vt(),s=(b,_,w,S=!0)=>{const k=Zu(b);return!x4e(k)||!S&&k.hours===0&&k.minutes===0?(un("ElTimeSelect",`invalid ${_}, fallback to default ${_} (${w}).`),w):lm(k)},u=D(()=>a.modelValue),c=D(()=>s(a.start,"start",WD)),d=D(()=>s(a.end,"end",KD)),v=D(()=>{const b=Zu(a.minTime||"");return b?lm(b):null}),f=D(()=>{const b=Zu(a.maxTime||"");return b?lm(b):null}),h=D(()=>s(a.step,"step",UD,!1)),m=D(()=>{var S;const b=[],_=(k,T)=>{b.push({value:k,rawValue:T,disabled:hy(T,v.value||"-1:-1")<=0||hy(T,f.value||"100:100")>=0})};let w=c.value;for(;hy(w,d.value)<=0;)_(gt(w,"HH:mm").locale(l.value).format(a.format),w),w=k4e(w,h.value);return a.includeEndTime&&((S=b[b.length-1])==null?void 0:S.rawValue)!==d.value&&_(gt(d.value,"HH:mm").locale(l.value).format(a.format),d.value),b});return t({blur:()=>{var b,_;(_=(b=o.value)==null?void 0:b.blur)==null||_.call(b)},focus:()=>{var b,_;(_=(b=o.value)==null?void 0:b.focus)==null||_.call(b)}}),(b,_)=>(x(),ne(p(Bs),{ref_key:"select",ref:o,name:e.name,"model-value":u.value,disabled:p(i),clearable:e.clearable,"clear-icon":e.clearIcon,size:e.size,effect:e.effect,placeholder:e.placeholder,"default-first-option":"",filterable:e.editable,"empty-values":e.emptyValues,"value-on-clear":e.valueOnClear,"popper-class":e.popperClass,"popper-style":e.popperStyle,"onUpdate:modelValue":_[0]||(_[0]=w=>b.$emit(p("update:modelValue"),w)),onChange:_[1]||(_[1]=w=>b.$emit(p("change"),w)),onBlur:_[2]||(_[2]=w=>b.$emit("blur",w)),onFocus:_[3]||(_[3]=w=>b.$emit("focus",w)),onClear:_[4]||(_[4]=()=>b.$emit("clear"))},{prefix:$(()=>[e.prefixIcon?(x(),ne(p(Xe),{key:0,class:U(p(r).e("prefix-icon"))},{default:$(()=>[(x(),ne(St(e.prefixIcon)))]),_:1},8,["class"])):te("v-if",!0)]),default:$(()=>[(x(!0),O(Ve,null,dt(m.value,w=>(x(),ne(p(n),{key:w.value,label:w.value,value:w.value,disabled:w.disabled},null,8,["label","value","disabled"]))),128))]),_:1},8,["name","model-value","disabled","clearable","clear-icon","size","effect","placeholder","filterable","empty-values","value-on-clear","popper-class","popper-style"]))}}),M4e=T4e;const E4e=wt(M4e),GD="timeline",A4e=$e({mode:{type:String,values:["start","alternate","alternate-reverse","end"],default:"start"},reverse:Boolean}),I4e=q({name:"ElTimeline",props:A4e,setup(e,{slots:t}){const n=ke("timeline");Ft(GD,{props:e,slots:t});const a=D(()=>[n.b(),n.is(e.mode)]);return()=>{var o;const r=eo(((o=t.default)==null?void 0:o.call(t))??[]);return ot("ul",{class:a.value},e.reverse?r.reverse():r)}}}),L4e=$e({timestamp:{type:String,default:""},hideTimestamp:Boolean,center:Boolean,placement:{type:String,values:["top","bottom"],default:"bottom"},type:{type:String,values:["primary","success","warning","danger","info"],default:""},color:{type:String,default:""},size:{type:String,values:["normal","large"],default:"normal"},icon:{type:pn},hollow:Boolean});var P4e=q({name:"ElTimelineItem",__name:"timeline-item",props:L4e,setup(e){const t=e,{props:n}=Ze(GD),a=ke("timeline-item"),r=D(()=>[a.e("node"),a.em("node",t.size||""),a.em("node",t.type||""),a.is("hollow",t.hollow)]),o=D(()=>[a.b(),{[a.e("center")]:t.center},a.is(n.mode)]);return(i,l)=>(x(),O("li",{class:U(o.value)},[C("div",{class:U(p(a).e("tail"))},null,2),i.$slots.dot?te("v-if",!0):(x(),O("div",{key:0,class:U(r.value),style:et({backgroundColor:e.color})},[e.icon?(x(),ne(p(Xe),{key:0,class:U(p(a).e("icon"))},{default:$(()=>[(x(),ne(St(e.icon)))]),_:1},8,["class"])):te("v-if",!0)],6)),i.$slots.dot?(x(),O("div",{key:1,class:U(p(a).e("dot"))},[ce(i.$slots,"dot")],2)):te("v-if",!0),C("div",{class:U(p(a).e("wrapper"))},[!e.hideTimestamp&&e.placement==="top"?(x(),O("div",{key:0,class:U([p(a).e("timestamp"),p(a).is("top")])},Q(e.timestamp),3)):te("v-if",!0),C("div",{class:U(p(a).e("content"))},[ce(i.$slots,"default")],2),!e.hideTimestamp&&e.placement==="bottom"?(x(),O("div",{key:1,class:U([p(a).e("timestamp"),p(a).is("bottom")])},Q(e.timestamp),3)):te("v-if",!0)],2)],2))}}),YD=P4e;const D4e=wt(I4e,{TimelineItem:YD}),O4e=On(YD),jD="left-check-change",qD="right-check-change",ls=$e({data:{type:ae(Array),default:()=>[]},titles:{type:ae(Array),default:()=>[]},buttonTexts:{type:ae(Array),default:()=>[]},filterPlaceholder:String,filterMethod:{type:ae(Function)},leftDefaultChecked:{type:ae(Array),default:()=>[]},rightDefaultChecked:{type:ae(Array),default:()=>[]},renderContent:{type:ae(Function)},modelValue:{type:ae(Array),default:()=>[]},format:{type:ae(Object),default:()=>({})},filterable:Boolean,props:{type:ae(Object),default:()=>Nn({label:"label",key:"key",disabled:"disabled"})},targetOrder:{type:String,values:["original","push","unshift"],default:"original"},validateEvent:{type:Boolean,default:!0},virtualScroll:Boolean,itemSize:{type:Number,default:30}}),a_=(e,t)=>[e,t].every(Ae)||Ae(e)&&Un(t),$4e={[Ot]:(e,t,n)=>[e,n].every(Ae)&&["left","right"].includes(t),[bt]:e=>Ae(e),[jD]:a_,[qD]:a_},fp=e=>{const t={label:"label",key:"key",disabled:"disabled"};return D(()=>({...t,...e.props}))},R4e=(e,t)=>({onSourceCheckedChange:(r,o)=>{e.leftChecked=r,o&&t(jD,r,o)},onTargetCheckedChange:(r,o)=>{e.rightChecked=r,o&&t(qD,r,o)}}),N4e=e=>{const t=fp(e),n=D(()=>e.data.reduce((a,r)=>(a[r[t.value.key]]=r,a),{}));return{sourceData:D(()=>e.data.filter(a=>!e.modelValue.includes(a[t.value.key]))),targetData:D(()=>e.targetOrder==="original"?e.data.filter(a=>e.modelValue.includes(a[t.value.key])):e.modelValue.reduce((a,r)=>{const o=n.value[r];return o&&a.push(o),a},[]))}},B4e=(e,t,n)=>{const a=fp(e),r=(l,s,u)=>{n(bt,l),n(Ot,l,s,u)};return{addToLeft:()=>{const l=e.modelValue.slice();t.rightChecked.forEach(s=>{const u=l.indexOf(s);u>-1&&l.splice(u,1)}),r(l,"left",t.rightChecked)},addToRight:()=>{let l=e.modelValue.slice();const s=e.data.filter(u=>{const c=u[a.value.key];return t.leftChecked.includes(c)&&!e.modelValue.includes(c)}).map(u=>u[a.value.key]);l=e.targetOrder==="unshift"?s.concat(l):l.concat(s),e.targetOrder==="original"&&(l=e.data.filter(u=>l.includes(u[a.value.key])).map(u=>u[a.value.key])),r(l,"right",t.leftChecked)}}},r_="checked-change",V4e=$e({data:ls.data,optionRender:{type:ae(Function)},placeholder:String,title:String,filterable:Boolean,format:ls.format,filterMethod:ls.filterMethod,defaultChecked:ls.leftDefaultChecked,props:ls.props,virtualScroll:ls.virtualScroll,itemSize:ls.itemSize}),z4e={[r_]:a_},F4e=(e,t,n)=>{const a=fp(e),r=D(()=>e.data.filter(f=>Qe(e.filterMethod)?e.filterMethod(t.query,f):String(f[a.value.label]||f[a.value.key]).toLowerCase().includes(t.query.toLowerCase()))),o=D(()=>r.value.filter(f=>!f[a.value.disabled])),i=D(()=>{const f=t.checked.length,h=e.data.length,{noChecked:m,hasChecked:g}=e.format;return m&&g?f>0?g.replace(/\${checked}/g,f.toString()).replace(/\${total}/g,h.toString()):m.replace(/\${total}/g,h.toString()):`${f}/${h}`}),l=D(()=>{const f=t.checked.length;return f>0&&f{const f=o.value.map(h=>h[a.value.key]);t.allChecked=f.length>0&&f.every(h=>t.checked.includes(h))},u=f=>{t.checked=f?o.value.map(h=>h[a.value.key]):[]},c=F(),{height:d}=Um(D(()=>{var f;return(f=c.value)==null?void 0:f.$el})),v=F();return ye(()=>t.checked,(f,h)=>{s(),t.checkChangeByUser?n(r_,f,f.concat(h).filter(m=>!f.includes(m)||!h.includes(m))):(n(r_,f),t.checkChangeByUser=!0)}),ye(o,()=>{s()}),ye(()=>e.data,()=>{const f=[],h=r.value.map(m=>m[a.value.key]);t.checked.forEach(m=>{h.includes(m)&&f.push(m)}),t.checkChangeByUser=!1,t.checked=f}),ye(()=>e.defaultChecked,(f,h)=>{if(h&&f.length===h.length&&f.every(y=>h.includes(y)))return;const m=[],g=o.value.map(y=>y[a.value.key]);f.forEach(y=>{g.includes(y)&&m.push(y)}),t.checkChangeByUser=!1,t.checked=m},{immediate:!0}),ye(()=>t.query,()=>{var f;e.virtualScroll&&((f=v.value)==null||f.scrollToItem(0))},{flush:"post"}),{filteredData:r,checkableData:o,checkedSummary:i,virtualListRef:v,isIndeterminate:l,checkboxGroupRef:c,virtualListHeight:d,updateAllChecked:s,handleAllCheckedChange:u}};var H4e=q({name:"ElTransferPanel",__name:"transfer-panel",props:V4e,emits:z4e,setup(e,{expose:t,emit:n}){const a=e,r=n,o=Xn(),i=({option:T})=>T,{t:l}=Vt(),s=ke("transfer"),u=nn({checked:[],allChecked:!1,query:"",checkChangeByUser:!0}),c=fp(a),{filteredData:d,checkedSummary:v,virtualListRef:f,isIndeterminate:h,checkboxGroupRef:m,virtualListHeight:g,handleAllCheckedChange:y}=F4e(a,u,r),b=D(()=>!_r(u.query)&&_r(d.value)),_=D(()=>!_r(o.default()[0].children)),{checked:w,allChecked:S,query:k}=Ta(u);return t({query:k}),(T,M)=>(x(),O("div",{class:U(p(s).b("panel"))},[C("p",{class:U(p(s).be("panel","header"))},[L(p(Zo),{modelValue:p(S),"onUpdate:modelValue":M[0]||(M[0]=E=>xn(S)?S.value=E:null),indeterminate:p(h),"validate-event":!1,onChange:p(y)},{default:$(()=>[C("span",{class:U(p(s).be("panel","header-title"))},Q(e.title),3),C("span",{class:U(p(s).be("panel","header-count"))},Q(p(v)),3)]),_:1},8,["modelValue","indeterminate","onChange"])],2),C("div",{class:U([p(s).be("panel","body"),p(s).is("with-footer",_.value)])},[e.filterable?(x(),ne(p(Ba),{key:0,modelValue:p(k),"onUpdate:modelValue":M[1]||(M[1]=E=>xn(k)?k.value=E:null),class:U(p(s).be("panel","filter")),size:"default",placeholder:e.placeholder,"prefix-icon":p(HI),clearable:"","validate-event":!1},null,8,["modelValue","class","placeholder","prefix-icon"])):te("v-if",!0),st(L(p(cw),{ref_key:"checkboxGroupRef",ref:m,modelValue:p(w),"onUpdate:modelValue":M[2]||(M[2]=E=>xn(w)?w.value=E:null),"validate-event":!1,class:U([p(s).is("filterable",e.filterable),p(s).be("panel","list")])},{default:$(()=>[e.virtualScroll?(x(),ne(p(cp),{key:1,ref_key:"virtualListRef",ref:f,data:p(d),total:p(d).length,"item-size":e.itemSize,height:p(g)},{default:$(({data:E,index:A,style:I})=>[(x(),O("div",{key:E[A][p(c).key],style:et(I)},[L(p(Zo),{class:U(p(s).be("panel","item")),value:E[A][p(c).key],disabled:E[A][p(c).disabled],"validate-event":!1},{default:$(()=>{var P;return[L(i,{option:(P=e.optionRender)==null?void 0:P.call(e,E[A])},null,8,["option"])]}),_:2},1032,["class","value","disabled"])],4))]),_:1},8,["data","total","item-size","height"])):(x(!0),O(Ve,{key:0},dt(p(d),E=>(x(),ne(p(Zo),{key:E[p(c).key],class:U(p(s).be("panel","item")),value:E[p(c).key],disabled:E[p(c).disabled],"validate-event":!1},{default:$(()=>{var A;return[L(i,{option:(A=e.optionRender)==null?void 0:A.call(e,E)},null,8,["option"])]}),_:2},1032,["class","value","disabled"]))),128))]),_:1},8,["modelValue","class"]),[[Zt,!b.value&&!p(_r)(e.data)]]),st(C("div",{class:U(p(s).be("panel","empty"))},[ce(T.$slots,"empty",{},()=>[ue(Q(b.value?p(l)("el.transfer.noMatch"):p(l)("el.transfer.noData")),1)])],2),[[Zt,b.value||p(_r)(e.data)]])],2),_.value?(x(),O("p",{key:0,class:U(p(s).be("panel","footer"))},[ce(T.$slots,"default")],2)):te("v-if",!0)],2))}}),Yx=H4e;const W4e={key:0},K4e={key:0};var U4e=q({name:"ElTransfer",__name:"transfer",props:ls,emits:$4e,setup(e,{expose:t,emit:n}){const a=e,r=n,o=Xn(),{t:i}=Vt(),l=ke("transfer"),{formItem:s}=Ea(),u=nn({leftChecked:[],rightChecked:[]}),c=fp(a),{sourceData:d,targetData:v}=N4e(a),{onSourceCheckedChange:f,onTargetCheckedChange:h}=R4e(u,r),{addToLeft:m,addToRight:g}=B4e(a,u,r),y=F(),b=F(),_=E=>{switch(E){case"left":y.value.query="";break;case"right":b.value.query="";break}},w=D(()=>a.buttonTexts.length===2),S=D(()=>a.titles[0]||i("el.transfer.titles.0")),k=D(()=>a.titles[1]||i("el.transfer.titles.1")),T=D(()=>a.filterPlaceholder||i("el.transfer.filterPlaceholder"));ye(()=>a.modelValue,()=>{var E;a.validateEvent&&((E=s==null?void 0:s.validate)==null||E.call(s,"change").catch(kt))});const M=D(()=>E=>{var I;if(a.renderContent)return a.renderContent(ot,E);const A=(((I=o.default)==null?void 0:I.call(o,{option:E}))||[]).filter(P=>P.type!==oa);return A.length?A:ot("span",E[c.value.label]||E[c.value.key])});return t({clearQuery:_,leftPanel:y,rightPanel:b}),(E,A)=>(x(),O("div",{class:U(p(l).b())},[L(Yx,{ref_key:"leftPanel",ref:y,data:p(d),"option-render":M.value,placeholder:T.value,title:S.value,filterable:e.filterable,format:e.format,"filter-method":e.filterMethod,"default-checked":e.leftDefaultChecked,props:a.props,"virtual-scroll":e.virtualScroll,"item-size":e.itemSize,onCheckedChange:p(f)},{empty:$(()=>[ce(E.$slots,"left-empty")]),default:$(()=>[ce(E.$slots,"left-footer")]),_:3},8,["data","option-render","placeholder","title","filterable","format","filter-method","default-checked","props","virtual-scroll","item-size","onCheckedChange"]),C("div",{class:U(p(l).e("buttons"))},[L(p(xa),{type:"primary",class:U([p(l).e("button"),p(l).is("with-texts",w.value)]),disabled:p(_r)(u.rightChecked),onClick:p(m)},{default:$(()=>[L(p(Xe),null,{default:$(()=>[L(p(Ji))]),_:1}),p(zt)(e.buttonTexts[0])?te("v-if",!0):(x(),O("span",W4e,Q(e.buttonTexts[0]),1))]),_:1},8,["class","disabled","onClick"]),L(p(xa),{type:"primary",class:U([p(l).e("button"),p(l).is("with-texts",w.value)]),disabled:p(_r)(u.leftChecked),onClick:p(g)},{default:$(()=>[p(zt)(e.buttonTexts[1])?te("v-if",!0):(x(),O("span",K4e,Q(e.buttonTexts[1]),1)),L(p(Xe),null,{default:$(()=>[L(p(za))]),_:1})]),_:1},8,["class","disabled","onClick"])],2),L(Yx,{ref_key:"rightPanel",ref:b,data:p(v),"option-render":M.value,placeholder:T.value,filterable:e.filterable,format:e.format,"filter-method":e.filterMethod,title:k.value,"default-checked":e.rightDefaultChecked,props:a.props,"virtual-scroll":e.virtualScroll,"item-size":e.itemSize,onCheckedChange:p(h)},{empty:$(()=>[ce(E.$slots,"right-empty")]),default:$(()=>[ce(E.$slots,"right-footer")]),_:3},8,["data","option-render","placeholder","filterable","format","filter-method","title","default-checked","props","virtual-scroll","item-size","onCheckedChange"])],2))}}),G4e=U4e;const Y4e=wt(G4e),Vw="RootTree",XD="NodeInstance",jx="TreeNodeMap",ZD=$e({data:{type:ae(Array),default:()=>[]},emptyText:{type:String},renderAfterExpand:{type:Boolean,default:!0},nodeKey:String,checkStrictly:Boolean,defaultExpandAll:Boolean,expandOnClickNode:{type:Boolean,default:!0},checkOnClickNode:Boolean,checkOnClickLeaf:{type:Boolean,default:!0},checkDescendants:Boolean,autoExpandParent:{type:Boolean,default:!0},defaultCheckedKeys:{type:Array},defaultExpandedKeys:{type:Array},currentNodeKey:{type:[String,Number]},renderContent:{type:ae(Function)},showCheckbox:Boolean,draggable:Boolean,allowDrag:{type:ae(Function)},allowDrop:{type:ae(Function)},props:{type:Object,default:()=>({children:"children",label:"label",disabled:"disabled"})},lazy:Boolean,highlightCurrent:Boolean,load:{type:Function},filterNodeMethod:{type:Function},accordion:Boolean,indent:{type:Number,default:18},icon:{type:pn}}),j4e={"check-change":(e,t,n)=>e&&vn(t)&&vn(n),"current-change":(e,t)=>!0,"node-click":(e,t,n,a)=>e&&t&&a instanceof Event,"node-contextmenu":(e,t,n,a)=>e instanceof Event&&t&&n,"node-collapse":(e,t,n)=>e&&t,"node-expand":(e,t,n)=>e&&t,check:(e,t)=>e&&t,"node-drag-start":(e,t)=>e&&t,"node-drag-end":(e,t,n,a)=>e&&a,"node-drop":(e,t,n,a)=>e&&t&&a,"node-drag-leave":(e,t,n)=>e&&t&&n,"node-drag-enter":(e,t,n)=>e&&t&&n,"node-drag-over":(e,t,n)=>e&&t&&n},JD="$treeNodeId",qx=function(e,t){!t||t.$treeNodeId||Object.defineProperty(t,JD,{value:e.id,enumerable:!1,configurable:!1,writable:!1})},zw=(e,t)=>t==null?void 0:t[e||"$treeNodeId"],o_=(e,t,n)=>{const a=e.value.currentNode;n();const r=e.value.currentNode;a!==r&&t("current-change",r?r.data:null,r)},QD=e=>{let t=!0,n=!0,a=!0,r=!0;for(let o=0,i=e.length;o{n.canFocus=t,i_(n.childNodes,t)})};let q4e=0;var l_=class sm{constructor(t){this.isLeafByUser=void 0,this.isLeaf=void 0,this.isEffectivelyChecked=!1,this.id=q4e++,this.text=null,this.checked=!1,this.indeterminate=!1,this.data=null,this.expanded=!1,this.parent=null,this.visible=!0,this.isCurrent=!1,this.canFocus=!1;for(const n in t)en(t,n)&&(this[n]=t[n]);this.level=0,this.loaded=!1,this.childNodes=[],this.loading=!1,this.parent&&(this.level=this.parent.level+1)}initialize(){var o;const t=this.store;if(!t)throw new Error("[Node]store is required!");t.registerNode(this);const n=t.props;if(n&&typeof n.isLeaf<"u"){const i=jp(this,"isLeaf");vn(i)&&(this.isLeafByUser=i)}if(t.lazy!==!0&&this.data?(this.setData(this.data),t.defaultExpandAll&&(this.expanded=!0,this.canFocus=!0)):this.level>0&&t.lazy&&t.defaultExpandAll&&!this.isLeafByUser&&this.expand(),Ae(this.data)||qx(this,this.data),!this.data)return;const a=t.defaultExpandedKeys,r=t.key;r&&!Un(this.key)&&a&&a.includes(this.key)&&this.expand(null,t.autoExpandParent),r&&t.currentNodeKey!==void 0&&this.key===t.currentNodeKey&&(t.currentNode&&(t.currentNode.isCurrent=!1),t.currentNode=this,t.currentNode.isCurrent=!0),t.lazy&&t._initDefaultCheckedNode(this),this.updateLeafState(),(this.level===1||((o=this.parent)==null?void 0:o.expanded)===!0)&&(this.canFocus=!0)}setData(t){Ae(t)||qx(this,t),this.data=t,this.childNodes=[];let n;this.level===0&&Ae(this.data)?n=this.data:n=jp(this,"children")||[];for(let a=0,r=n.length;a-1)return t.childNodes[n+1]}return null}get previousSibling(){const t=this.parent;if(t){const n=t.childNodes.indexOf(this);if(n>-1)return n>0?t.childNodes[n-1]:null}return null}contains(t,n=!0){return(this.childNodes||[]).some(a=>a===t||n&&a.contains(t))}remove(){const t=this.parent;t&&t.removeChild(this)}insertChild(t,n,a){if(!t)throw new Error("InsertChild error: child is required.");if(!(t instanceof sm)){if(!a){const r=this.getChildren(!0);r!=null&&r.includes(t.data)||(zt(n)||n<0?r==null||r.push(t.data):r==null||r.splice(n,0,t.data))}Object.assign(t,{parent:this,store:this.store}),t=nn(new sm(t)),t instanceof sm&&t.initialize()}t.level=this.level+1,zt(n)||n<0?this.childNodes.push(t):this.childNodes.splice(n,0,t),this.updateLeafState()}insertBefore(t,n){let a;n&&(a=this.childNodes.indexOf(n)),this.insertChild(t,a)}insertAfter(t,n){let a;n&&(a=this.childNodes.indexOf(n),a!==-1&&(a+=1)),this.insertChild(t,a)}removeChild(t){const n=this.getChildren()||[],a=n.indexOf(t.data);a>-1&&n.splice(a,1);const r=this.childNodes.indexOf(t);r>-1&&(this.store&&this.store.deregisterNode(t),t.parent=null,this.childNodes.splice(r,1)),this.updateLeafState()}removeChildByData(t){const n=this.childNodes.find(a=>a.data===t);n&&this.removeChild(n)}expand(t,n){const a=()=>{if(n){let r=this.parent;for(;r&&r.level>0;)r.expanded=!0,r=r.parent}this.expanded=!0,t&&t(),i_(this.childNodes,!0)};this.shouldLoadData()?this.loadData(r=>{Ae(r)&&(this.store.checkStrictly||(this.checked?this.setChecked(!0,!0):sv(this)),a())}):a()}doCreateChildren(t,n={}){t.forEach(a=>{this.insertChild(Object.assign({data:a},n),void 0,!0)})}collapse(){this.expanded=!1,i_(this.childNodes,!1)}shouldLoadData(){return!!(this.store.lazy===!0&&this.store.load&&!this.loaded)}updateLeafState(){if(this.store.lazy===!0&&this.loaded!==!0&&typeof this.isLeafByUser<"u"){this.isLeaf=this.isLeafByUser,this.isEffectivelyChecked=this.isLeaf&&this.disabled;return}const t=this.childNodes;if(!this.store.lazy||this.store.lazy===!0&&this.loaded===!0){this.isLeaf=!t||t.length===0,this.isEffectivelyChecked=this.isLeaf&&this.disabled;return}this.isLeaf=!1}setChecked(t,n,a,r){if(this.indeterminate=t==="half",this.checked=t===!0,this.isEffectivelyChecked=!this.childNodes.length&&(this.disabled||this.checked),this.store.checkStrictly&&!n)return;if(!(this.shouldLoadData()&&!this.store.checkDescendants)){const i=()=>{if(n){const l=this.childNodes;for(let d=0,v=l.length;d{i(),sv(this)},{checked:t!==!1});return}else i()}const o=this.parent;!o||o.level===0||!a&&!this.store.checkStrictly&&sv(o)}getChildren(t=!1){if(this.level===0)return this.data;const n=this.data;if(!n)return null;const a=this.store.props;let r="children";return a&&(r=a.children||"children"),zt(n[r])&&(n[r]=null),t&&!n[r]&&(n[r]=[]),n[r]}updateChildren(){const t=this.getChildren()||[],n=this.childNodes.map(o=>o.data),a={},r=[];t.forEach((o,i)=>{const l=o[JD];l&&n.some(s=>(s==null?void 0:s.$treeNodeId)===l)?a[l]={index:i,data:o}:r.push({index:i,data:o})}),this.store.lazy||n.forEach(o=>{a[o==null?void 0:o.$treeNodeId]||this.removeChildByData(o)}),r.forEach(({index:o,data:i})=>{this.insertChild({data:i},o)}),this.updateLeafState()}loadData(t,n={}){if(this.store.lazy===!0&&this.store.load&&!this.loaded&&(!this.loading||Object.keys(n).length)){this.loading=!0;const a=o=>{this.childNodes=[],this.doCreateChildren(o,n),this.loaded=!0,this.loading=!1,this.updateLeafState(),t&&t.call(this,o)},r=()=>{this.loading=!1};this.store.load(this,a,r)}else t&&t.call(this)}eachNode(t){const n=[this];for(;n.length;){const a=n.shift();n.unshift(...a.childNodes),t(a)}}reInitChecked(){this.store.checkStrictly||sv(this)}},X4e=class{constructor(e){this.lazy=!1,this.checkStrictly=!1,this.autoExpandParent=!1,this.defaultExpandAll=!1,this.checkDescendants=!1,this.currentNode=null,this.currentNodeKey=null;for(const t in e)en(e,t)&&(this[t]=e[t]);this.nodesMap={}}initialize(){if(this.root=new l_({data:this.data,store:this}),this.root.initialize(),this.lazy&&this.load){const e=this.load;e(this.root,t=>{this.root.doCreateChildren(t),this._initDefaultCheckedNodes()},kt)}else this._initDefaultCheckedNodes()}filter(e){const t=this.filterNodeMethod,n=this.lazy,a=async function(r){const o=r.root?r.root.childNodes:r.childNodes;for(const[i,l]of o.entries())l.visible=!!(t!=null&&t.call(l,e,l.data,l)),i%80===0&&i>0&&await He(),await a(l);if(!r.visible&&o.length){let i=!0;i=!o.some(l=>l.visible),r.root?r.root.visible=i===!1:r.visible=i===!1}e&&r.visible&&!r.isLeaf&&(!n||r.loaded)&&r.expand()};a(this)}setData(e){e!==this.root.data?(this.nodesMap={},this.root.setData(e),this._initDefaultCheckedNodes(),this.setCurrentNodeKey(this.currentNodeKey)):this.root.updateChildren()}getNode(e){if(e instanceof l_)return e;const t=_t(e)?zw(this.key,e):e;return this.nodesMap[t]||null}insertBefore(e,t){var a;const n=this.getNode(t);(a=n.parent)==null||a.insertBefore({data:e},n)}insertAfter(e,t){var a;const n=this.getNode(t);(a=n.parent)==null||a.insertAfter({data:e},n)}remove(e){const t=this.getNode(e);t&&t.parent&&(t===this.currentNode&&(this.currentNode=null),t.parent.removeChild(t))}append(e,t){const n=Qr(t)?this.root:this.getNode(t);n&&n.insertChild({data:e})}_initDefaultCheckedNodes(){const e=this.defaultCheckedKeys||[],t=this.nodesMap;e.forEach(n=>{const a=t[n];a&&a.setChecked(!0,!this.checkStrictly)})}_initDefaultCheckedNode(e){const t=this.defaultCheckedKeys||[];!Un(e.key)&&t.includes(e.key)&&e.setChecked(!0,!this.checkStrictly)}setDefaultCheckedKey(e){e!==this.defaultCheckedKeys&&(this.defaultCheckedKeys=e,this._initDefaultCheckedNodes())}registerNode(e){const t=this.key;if(!(!e||!e.data))if(!t)this.nodesMap[e.id]=e;else{const n=e.key;Un(n)||(this.nodesMap[n]=e)}}deregisterNode(e){!this.key||!e||!e.data||(e.childNodes.forEach(t=>{this.deregisterNode(t)}),delete this.nodesMap[e.key])}getCheckedNodes(e=!1,t=!1){const n=[],a=function(r){(r.root?r.root.childNodes:r.childNodes).forEach(o=>{(o.checked||t&&o.indeterminate)&&(!e||e&&o.isLeaf)&&n.push(o.data),a(o)})};return a(this),n}getCheckedKeys(e=!1){return this.getCheckedNodes(e).map(t=>(t||{})[this.key])}getHalfCheckedNodes(){const e=[],t=function(n){(n.root?n.root.childNodes:n.childNodes).forEach(a=>{a.indeterminate&&e.push(a.data),t(a)})};return t(this),e}getHalfCheckedKeys(){return this.getHalfCheckedNodes().map(e=>(e||{})[this.key])}_getAllNodes(){const e=[],t=this.nodesMap;for(const n in t)en(t,n)&&e.push(t[n]);return e}updateChildren(e,t){const n=this.nodesMap[e];if(!n)return;const a=n.childNodes;for(let r=a.length-1;r>=0;r--){const o=a[r];this.remove(o.data)}for(let r=0,o=t.length;rl.level-s.level),r=Object.create(null),o=Object.keys(n);a.forEach(l=>l.setChecked(!1,!1));const i=l=>{l.childNodes.forEach(s=>{var u;r[s.data[e]]=!0,(u=s.childNodes)!=null&&u.length&&i(s)})};for(let l=0,s=a.length;l{f.isLeaf||f.setChecked(!1,!1,!0),d(f)}),v.reInitChecked()};d(u)}}}setCheckedNodes(e,t=!1){const n=this.key,a={};e.forEach(r=>{a[(r||{})[n]]=!0}),this._setCheckedKeys(n,t,a)}setCheckedKeys(e,t=!1){this.defaultCheckedKeys=e;const n=this.key,a={};e.forEach(r=>{a[r]=!0}),this._setCheckedKeys(n,t,a)}setDefaultExpandedKeys(e){e=e||[],this.defaultExpandedKeys=e,e.forEach(t=>{const n=this.getNode(t);n&&n.expand(null,this.autoExpandParent)})}setChecked(e,t,n){const a=this.getNode(e);a&&a.setChecked(!!t,n)}getCurrentNode(){return this.currentNode}setCurrentNode(e){const t=this.currentNode;t&&(t.isCurrent=!1),this.currentNode=e,this.currentNode.isCurrent=!0}setUserCurrentNode(e,t=!0){var r;const n=e[this.key],a=this.nodesMap[n];this.setCurrentNode(a),t&&this.currentNode&&this.currentNode.level>1&&((r=this.currentNode.parent)==null||r.expand(null,!0))}setCurrentNodeKey(e,t=!0){var a;if(this.currentNodeKey=e,Qr(e)){this.currentNode&&(this.currentNode.isCurrent=!1),this.currentNode=null;return}const n=this.getNode(e);n&&(this.setCurrentNode(n),t&&this.currentNode&&this.currentNode.level>1&&((a=this.currentNode.parent)==null||a.expand(null,!0)))}};function eO(e){const t=Ze(jx,null);let n={treeNodeExpand:a=>{var r;e.node!==a&&((r=e.node)==null||r.collapse())},children:new Set};return t&&t.children.add(n),rn(()=>{t&&t.children.delete(n),n=null}),Ft(jx,n),{broadcastExpanded:a=>{if(e.accordion)for(const r of n.children)r.treeNodeExpand(a)}}}const tO=Symbol("dragEvents");function Z4e({props:e,ctx:t,el$:n,dropIndicator$:a,store:r}){const o=ke("tree"),i=F({showDropIndicator:!1,draggingNode:null,dropNode:null,allowDrop:!0,dropType:null});return Ft(tO,{treeNodeDragStart:({event:c,treeNode:d})=>{if(c.dataTransfer){if(Qe(e.allowDrag)&&!e.allowDrag(d.node))return c.preventDefault(),!1;c.dataTransfer.effectAllowed="move";try{c.dataTransfer.setData("text/plain","")}catch{}i.value.draggingNode=d,t.emit("node-drag-start",d.node,c)}},treeNodeDragOver:({event:c,treeNode:d})=>{if(!c.dataTransfer)return;const v=d,f=i.value.dropNode;f&&f.node.id!==v.node.id&&ir(f.$el,o.is("drop-inner"));const h=i.value.draggingNode;if(!h||!v)return;let m=!0,g=!0,y=!0,b=!0;Qe(e.allowDrop)&&(m=e.allowDrop(h.node,v.node,"prev"),b=g=e.allowDrop(h.node,v.node,"inner"),y=e.allowDrop(h.node,v.node,"next")),c.dataTransfer.dropEffect=g||m||y?"move":"none",(m||g||y)&&(f==null?void 0:f.node.id)!==v.node.id&&(f&&t.emit("node-drag-leave",h.node,f.node,c),t.emit("node-drag-enter",h.node,v.node,c)),m||g||y?i.value.dropNode=v:i.value.dropNode=null,v.node.nextSibling===h.node&&(y=!1),v.node.previousSibling===h.node&&(m=!1),v.node.contains(h.node,!1)&&(g=!1),(h.node===v.node||h.node.contains(v.node))&&(m=!1,g=!1,y=!1);const _=v.$el,w=_.querySelector(`.${o.be("node","content")}`).getBoundingClientRect(),S=n.value.getBoundingClientRect(),k=n.value.scrollTop;let T;const M=m?g?.25:y?.45:1:Number.NEGATIVE_INFINITY,E=y?g?.75:m?.55:0:Number.POSITIVE_INFINITY;let A=-9999;const I=c.clientY-w.top;Iw.height*E?T="after":g?T="inner":T="none";const P=_.querySelector(`.${o.be("node","expand-icon")}`).getBoundingClientRect(),R=a.value;T==="before"?A=P.top-S.top+k:T==="after"&&(A=P.bottom-S.top+k),R.style.top=`${A}px`,R.style.left=`${P.right-S.left}px`,T==="inner"?Eo(_,o.is("drop-inner")):ir(_,o.is("drop-inner")),i.value.showDropIndicator=T==="before"||T==="after",i.value.allowDrop=i.value.showDropIndicator||b,i.value.dropType=T,t.emit("node-drag-over",h.node,v.node,c)},treeNodeDragEnd:c=>{var h,m;const{draggingNode:d,dropType:v,dropNode:f}=i.value;if(c.preventDefault(),c.dataTransfer&&(c.dataTransfer.dropEffect="move"),d!=null&&d.node.data&&f){const g={data:d.node.data};v!=="none"&&d.node.remove(),v==="before"?(h=f.node.parent)==null||h.insertBefore(g,f.node):v==="after"?(m=f.node.parent)==null||m.insertAfter(g,f.node):v==="inner"&&f.node.insertChild(g),v!=="none"&&(r.value.registerNode(g),r.value.key&&d.node.eachNode(y=>{var b;(b=r.value.nodesMap[y.data[r.value.key]])==null||b.setChecked(y.checked,!r.value.checkStrictly)})),ir(f.$el,o.is("drop-inner")),t.emit("node-drag-end",d.node,f.node,v,c),v!=="none"&&t.emit("node-drop",d.node,f.node,v,c)}d&&!f&&t.emit("node-drag-end",d.node,null,v,c),i.value.showDropIndicator=!1,i.value.draggingNode=null,i.value.dropNode=null,i.value.allowDrop=!0}}),{dragState:i}}var J4e=q({name:"ElTreeNodeContent",props:{node:{type:Object,required:!0},renderContent:Function},setup(e){const t=ke("tree"),n=Ze(XD),a=Ze(Vw);return()=>{const r=e.node,{data:o,store:i}=r;return e.renderContent?e.renderContent(ot,{_self:n,node:r,data:o,store:i}):ce(a.ctx.slots,"default",{node:r,data:o},()=>[ot(Bw,{tag:"span",truncated:!0,class:t.be("node","label")},()=>[r.label])])}}}),Q4e=J4e,e_e=q({name:"ElTreeNode",components:{ElCollapseTransition:U1,ElCheckbox:Zo,NodeContent:Q4e,ElIcon:Xe,Loading:Mr},props:{node:{type:l_,default:()=>({})},props:{type:Object,default:()=>({})},accordion:Boolean,renderContent:Function,renderAfterExpand:Boolean,showCheckbox:Boolean},emits:["node-expand"],setup(e,t){const n=ke("tree"),{broadcastExpanded:a}=eO(e),r=Ze(Vw),o=F(!1),i=F(!1),l=F(),s=F(),u=F(),c=Ze(tO),d=Et();Ft(XD,d),r||un("Tree","Can not find node's tree."),e.node.expanded&&(o.value=!0,i.value=!0);const v=r.props.props.children||"children";ye(()=>{var A;const E=(A=e.node.data)==null?void 0:A[v];return E&&[...E]},()=>{e.node.updateChildren()}),ye(()=>e.node.indeterminate,E=>{m(e.node.checked,E)}),ye(()=>e.node.checked,E=>{m(E,e.node.indeterminate)}),ye(()=>e.node.childNodes.length,()=>e.node.reInitChecked()),ye(()=>e.node.expanded,E=>{He(()=>o.value=E),E&&(i.value=!0)});const f=E=>r.props.nodeKey?zw(r.props.nodeKey,E.data):E.id,h=E=>{const A=e.props.class;if(!A)return{};let I;if(Qe(A)){const{data:P}=E;I=A(P,E)}else I=A;return Ye(I)?{[I]:!0}:I},m=(E,A)=>{(l.value!==E||s.value!==A)&&r.ctx.emit("check-change",e.node.data,E,A),l.value=E,s.value=A},g=E=>{o_(r.store,r.ctx.emit,()=>{var A;if((A=r==null?void 0:r.props)!=null&&A.nodeKey){const I=f(e.node);r.store.value.setCurrentNodeKey(I)}else r.store.value.setCurrentNode(e.node)}),r.currentNode.value=e.node,r.props.expandOnClickNode&&b(),(r.props.checkOnClickNode||e.node.isLeaf&&r.props.checkOnClickLeaf&&e.showCheckbox)&&!e.node.disabled&&_(!e.node.checked),r.ctx.emit("node-click",e.node.data,e.node,d,E)},y=E=>{var A;(A=r.instance.vnode.props)!=null&&A.onNodeContextmenu&&(E.stopPropagation(),E.preventDefault()),r.ctx.emit("node-contextmenu",E,e.node.data,e.node,d)},b=()=>{e.node.isLeaf||(o.value?(r.ctx.emit("node-collapse",e.node.data,e.node,d),e.node.collapse()):e.node.expand(()=>{t.emit("node-expand",e.node.data,e.node,d)}))},_=E=>{const A=r==null?void 0:r.props.checkStrictly,I=e.node.childNodes;!A&&I.length&&(E=I.some(P=>!P.isEffectivelyChecked)),e.node.setChecked(E,!A),He(()=>{const P=r.store.value;r.ctx.emit("check",e.node.data,{checkedNodes:P.getCheckedNodes(),checkedKeys:P.getCheckedKeys(),halfCheckedNodes:P.getHalfCheckedNodes(),halfCheckedKeys:P.getHalfCheckedKeys()})})};return{ns:n,node$:u,tree:r,expanded:o,childNodeRendered:i,oldChecked:l,oldIndeterminate:s,getNodeKey:f,getNodeClass:h,handleSelectChange:m,handleClick:g,handleContextMenu:y,handleExpandIconClick:b,handleCheckChange:_,handleChildNodeExpand:(E,A,I)=>{a(A),r.ctx.emit("node-expand",E,A,I)},handleDragStart:E=>{r.props.draggable&&c.treeNodeDragStart({event:E,treeNode:e})},handleDragOver:E=>{E.preventDefault(),r.props.draggable&&c.treeNodeDragOver({event:E,treeNode:{$el:u.value,node:e.node}})},handleDrop:E=>{E.preventDefault()},handleDragEnd:E=>{r.props.draggable&&c.treeNodeDragEnd(E)},CaretRight:F6}}});const t_e=["aria-expanded","aria-disabled","aria-checked","draggable","data-key"],n_e=["aria-expanded"];function a_e(e,t,n,a,r,o){const i=be("el-icon"),l=be("el-checkbox"),s=be("loading"),u=be("node-content"),c=be("el-tree-node"),d=be("el-collapse-transition");return st((x(),O("div",{ref:"node$",class:U([e.ns.b("node"),e.ns.is("expanded",e.expanded),e.ns.is("current",e.node.isCurrent),e.ns.is("hidden",!e.node.visible),e.ns.is("focusable",!e.node.disabled),e.ns.is("checked",!e.node.disabled&&e.node.checked),e.getNodeClass(e.node)]),role:"treeitem",tabindex:"-1","aria-expanded":e.expanded,"aria-disabled":e.node.disabled,"aria-checked":e.node.checked,draggable:e.tree.props.draggable,"data-key":e.getNodeKey(e.node),onClick:t[2]||(t[2]=at((...v)=>e.handleClick&&e.handleClick(...v),["stop"])),onContextmenu:t[3]||(t[3]=(...v)=>e.handleContextMenu&&e.handleContextMenu(...v)),onDragstart:t[4]||(t[4]=at((...v)=>e.handleDragStart&&e.handleDragStart(...v),["stop"])),onDragover:t[5]||(t[5]=at((...v)=>e.handleDragOver&&e.handleDragOver(...v),["stop"])),onDragend:t[6]||(t[6]=at((...v)=>e.handleDragEnd&&e.handleDragEnd(...v),["stop"])),onDrop:t[7]||(t[7]=at((...v)=>e.handleDrop&&e.handleDrop(...v),["stop"]))},[C("div",{class:U(e.ns.be("node","content")),style:et({paddingLeft:(e.node.level-1)*e.tree.props.indent+"px"})},[e.tree.props.icon||e.CaretRight?(x(),ne(i,{key:0,class:U([e.ns.be("node","expand-icon"),e.ns.is("leaf",e.node.isLeaf),{expanded:!e.node.isLeaf&&e.expanded}]),onClick:at(e.handleExpandIconClick,["stop"])},{default:$(()=>[(x(),ne(St(e.tree.props.icon||e.CaretRight)))]),_:1},8,["class","onClick"])):te("v-if",!0),e.showCheckbox?(x(),ne(l,{key:1,"model-value":e.node.checked,indeterminate:e.node.indeterminate,disabled:!!e.node.disabled,onClick:t[0]||(t[0]=at(()=>{},["stop"])),onChange:e.handleCheckChange},null,8,["model-value","indeterminate","disabled","onChange"])):te("v-if",!0),e.node.loading?(x(),ne(i,{key:2,class:U([e.ns.be("node","loading-icon"),e.ns.is("loading")])},{default:$(()=>[L(s)]),_:1},8,["class"])):te("v-if",!0),L(u,{node:e.node,"render-content":e.renderContent},null,8,["node","render-content"])],6),L(d,null,{default:$(()=>[!e.renderAfterExpand||e.childNodeRendered?st((x(),O("div",{key:0,class:U(e.ns.be("node","children")),role:"group","aria-expanded":e.expanded,onClick:t[1]||(t[1]=at(()=>{},["stop"]))},[(x(!0),O(Ve,null,dt(e.node.childNodes,v=>(x(),ne(c,{key:e.getNodeKey(v),"render-content":e.renderContent,"render-after-expand":e.renderAfterExpand,"show-checkbox":e.showCheckbox,node:v,accordion:e.accordion,props:e.props,onNodeExpand:e.handleChildNodeExpand},null,8,["render-content","render-after-expand","show-checkbox","node","accordion","props","onNodeExpand"]))),128))],10,n_e)),[[Zt,e.expanded]]):te("v-if",!0)]),_:1})],42,t_e)),[[Zt,e.node.visible]])}var r_e=ya(e_e,[["render",a_e]]);function o_e({el$:e},t){const n=ke("tree");mt(()=>{o()}),oi(()=>{var i;(i=e.value)==null||i.querySelectorAll("input[type=checkbox]").forEach(l=>{l.setAttribute("tabindex","-1")})});function a(i,l){var u,c;const s=t.value.getNode(i[l].dataset.key);return s.canFocus&&s.visible&&(((u=s.parent)==null?void 0:u.expanded)||((c=s.parent)==null?void 0:c.level)===0)}on(e,"keydown",i=>{const l=i.target;if(!l.className.includes(n.b("node")))return;const s=mn(i),u=Array.from(e.value.querySelectorAll(`.${n.is("focusable")}[role=treeitem]`)),c=u.indexOf(l);let d;if([Pe.up,Pe.down].includes(s)){if(i.preventDefault(),s===Pe.up){d=c===-1?0:c!==0?c-1:u.length-1;const f=d;for(;!a(u,d);){if(d--,d===f){d=-1;break}d<0&&(d=u.length-1)}}else{d=c===-1?0:c=u.length&&(d=0)}}d!==-1&&u[d].focus()}[Pe.left,Pe.right].includes(s)&&(i.preventDefault(),l.click());const v=l.querySelector('[type="checkbox"]');[Pe.enter,Pe.numpadEnter,Pe.space].includes(s)&&v&&(i.preventDefault(),v.click())});const o=()=>{var s;if(!e.value)return;const i=Array.from(e.value.querySelectorAll(`.${n.is("focusable")}[role=treeitem]`));Array.from(e.value.querySelectorAll("input[type=checkbox]")).forEach(u=>{u.setAttribute("tabindex","-1")});const l=e.value.querySelectorAll(`.${n.is("checked")}[role=treeitem]`);if(l.length){l[0].setAttribute("tabindex","0");return}(s=i[0])==null||s.setAttribute("tabindex","0")}}var i_e=q({name:"ElTree",components:{ElTreeNode:r_e},props:ZD,emits:j4e,setup(e,t){const{t:n}=Vt(),a=ke("tree"),r=F(new X4e({key:e.nodeKey,data:e.data,lazy:e.lazy,props:e.props,load:e.load,currentNodeKey:e.currentNodeKey,checkStrictly:e.checkStrictly,checkDescendants:e.checkDescendants,defaultCheckedKeys:e.defaultCheckedKeys,defaultExpandedKeys:e.defaultExpandedKeys,autoExpandParent:e.autoExpandParent,defaultExpandAll:e.defaultExpandAll,filterNodeMethod:e.filterNodeMethod}));r.value.initialize();const o=F(r.value.root),i=F(null),l=F(null),s=F(null),{broadcastExpanded:u}=eO(e),{dragState:c}=Z4e({props:e,ctx:t,el$:l,dropIndicator$:s,store:r});o_e({el$:l},r);const d=Et(),v=D(()=>{let N=d==null?void 0:d.parent;for(;N;){if(N.type.name==="ElTreeSelect")return!0;N=N.parent}return!1}),f=D(()=>{const{childNodes:N}=o.value;return(!N||N.length===0||N.every(({visible:V})=>!V))&&!v.value});ye(()=>e.currentNodeKey,N=>{r.value.setCurrentNodeKey(N??null)}),ye(()=>e.defaultCheckedKeys,(N,V)=>{Rn(N,V)||r.value.setDefaultCheckedKey(N??[])}),ye(()=>e.defaultExpandedKeys,N=>{r.value.setDefaultExpandedKeys(N??[])}),ye(()=>e.data,N=>{r.value.setData(N)},{deep:!0}),ye(()=>e.checkStrictly,N=>{r.value.checkStrictly=N});const h=N=>{if(!e.filterNodeMethod)throw new Error("[Tree] filterNodeMethod is required when filter");r.value.filter(N)},m=N=>e.nodeKey?zw(e.nodeKey,N.data):N.id,g=N=>{if(!e.nodeKey)throw new Error(`[Tree] nodeKey is required in ${N}`)},y=N=>{g("getNodePath");const V=r.value.getNode(N);if(!V)return[];const Y=[V.data];let X=V.parent;for(;X&&X!==o.value;)Y.push(X.data),X=X.parent;return Y.reverse()},b=(N,V)=>r.value.getCheckedNodes(N,V),_=N=>r.value.getCheckedKeys(N),w=()=>{const N=r.value.getCurrentNode();return N?N.data:null},S=()=>{g("getCurrentKey");const N=w();return N?N[e.nodeKey]:null},k=(N,V)=>{g("setCheckedNodes"),r.value.setCheckedNodes(N,V)},T=(N,V)=>{g("setCheckedKeys"),r.value.setCheckedKeys(N,V)},M=(N,V,Y=!1)=>{r.value.setChecked(N,V,Y)},E=()=>r.value.getHalfCheckedNodes(),A=()=>r.value.getHalfCheckedKeys(),I=(N,V=!0)=>{g("setCurrentNode"),o_(r,t.emit,()=>{u(N),r.value.setUserCurrentNode(N,V)})},P=(N=null,V=!0)=>{g("setCurrentKey"),o_(r,t.emit,()=>{u(),r.value.setCurrentNodeKey(N,V)})},R=N=>r.value.getNode(N),G=N=>{r.value.remove(N)},K=(N,V)=>{r.value.append(N,V)},H=(N,V)=>{r.value.insertBefore(N,V)},z=(N,V)=>{r.value.insertAfter(N,V)},B=(N,V,Y)=>{u(V),t.emit("node-expand",N,V,Y)},W=(N,V)=>{g("updateKeyChildren"),r.value.updateChildren(N,V)};return Ft(Vw,{ctx:t,props:e,store:r,root:o,currentNode:i,instance:d}),Ft(Qi,void 0),{ns:a,store:r,root:o,currentNode:i,dragState:c,el$:l,dropIndicator$:s,isEmpty:f,filter:h,getNodeKey:m,getNodePath:y,getCheckedNodes:b,getCheckedKeys:_,getCurrentNode:w,getCurrentKey:S,setCheckedNodes:k,setCheckedKeys:T,setChecked:M,getHalfCheckedNodes:E,getHalfCheckedKeys:A,setCurrentNode:I,setCurrentKey:P,t:n,getNode:R,remove:G,append:K,insertBefore:H,insertAfter:z,handleNodeExpand:B,updateKeyChildren:W}}});function l_e(e,t,n,a,r,o){const i=be("el-tree-node");return x(),O("div",{ref:"el$",class:U([e.ns.b(),e.ns.is("dragging",!!e.dragState.draggingNode),e.ns.is("drop-not-allow",!e.dragState.allowDrop),e.ns.is("drop-inner",e.dragState.dropType==="inner"),{[e.ns.m("highlight-current")]:e.highlightCurrent}]),role:"tree"},[(x(!0),O(Ve,null,dt(e.root.childNodes,l=>(x(),ne(i,{key:e.getNodeKey(l),node:l,props:e.props,accordion:e.accordion,"render-after-expand":e.renderAfterExpand,"show-checkbox":e.showCheckbox,"render-content":e.renderContent,onNodeExpand:e.handleNodeExpand},null,8,["node","props","accordion","render-after-expand","show-checkbox","render-content","onNodeExpand"]))),128)),e.isEmpty?(x(),O("div",{key:0,class:U(e.ns.e("empty-block"))},[ce(e.$slots,"empty",{},()=>[C("span",{class:U(e.ns.e("empty-text"))},Q(e.emptyText??e.t("el.tree.emptyText")),3)])],2)):te("v-if",!0),st(C("div",{ref:"dropIndicator$",class:U(e.ns.e("drop-indicator"))},null,2),[[Zt,e.dragState.showDropIndicator]])],2)}var s_e=ya(i_e,[["render",l_e]]);const Fw=wt(s_e),u_e=(e,{attrs:t,emit:n},{select:a,tree:r,key:o})=>{const i=ke("tree-select");ye(()=>e.data,()=>{e.filterable&&He(()=>{var s,u;(u=r.value)==null||u.filter((s=a.value)==null?void 0:s.states.inputValue)})},{flush:"post"});const l=s=>{var c,d;const u=s.at(-1);if(u.expanded&&u.childNodes.at(-1))l([u.childNodes.at(-1)]);else{(d=(c=r.value.el$)==null?void 0:c.querySelector(`[data-key="${s.at(-1).key}"]`))==null||d.focus({preventScroll:!0});return}};return mt(()=>{on(()=>{var s;return(s=a.value)==null?void 0:s.$el},"keydown",async s=>{const u=mn(s),{dropdownMenuVisible:c}=a.value;[Pe.down,Pe.up].includes(u)&&c&&(await He(),setTimeout(()=>{var d,v,f;if(Pe.up===u){const h=r.value.store.root.childNodes;l(h);return}(f=(v=(d=a.value.optionsArray[a.value.states.hoveringIndex].$el)==null?void 0:d.parentNode)==null?void 0:v.parentNode)==null||f.focus({preventScroll:!0})}))},{capture:!0})}),{...ji(Ta(e),Object.keys(Bs.props)),...t,class:D(()=>t.class),style:D(()=>t.style),"onUpdate:modelValue":s=>n(bt,s),valueKey:o,popperClass:D(()=>[i.e("popper"),e.popperClass]),filterMethod:(s="")=>{var u;e.filterMethod?e.filterMethod(s):e.remoteMethod?e.remoteMethod(s):(u=r.value)==null||u.filter(s)}}},c_e=q({extends:eg,setup(e,t){const n=eg.setup(e,t);delete n.selectOptionClick;const a=Et().proxy;return He(()=>{n.select.states.cachedOptions.get(a.value)||n.select.onOptionCreate(a)}),ye(()=>t.attrs.visible,r=>{He(()=>{n.states.visible=r})},{immediate:!0}),n},methods:{selectOptionClick(){this.$el.parentElement.click()}}});function s_(e){return e||e===0}function Hw(e){return Ae(e)&&e.length}function Bc(e){return Ae(e)?e:s_(e)?[e]:[]}function um(e,t,n,a,r){for(let o=0;o{ye([()=>e.modelValue,o],()=>{e.showCheckbox&&He(()=>{const h=o.value;h&&!Rn(h.getCheckedKeys(),Bc(e.modelValue))&&h.setCheckedKeys(Bc(e.modelValue))})},{immediate:!0,deep:!0});const l=D(()=>({value:i.value,label:"label",children:"children",disabled:"disabled",isLeaf:"isLeaf",...e.props})),s=(h,m)=>{var y;const g=l.value[h];return Qe(g)?g(m,(y=o.value)==null?void 0:y.getNode(s("value",m))):m[g]},u=Bc(e.modelValue).map(h=>um(e.data||[],m=>s("value",m)===h,m=>s("children",m),(m,g,y,b)=>b&&s("value",b))).filter(h=>s_(h)),c=D(()=>{if(!e.renderAfterExpand&&!e.lazy)return[];const h=[];return cm(e.data.concat(e.cacheData),m=>{const g=s("value",m);h.push({value:g,currentLabel:s("label",m),isDisabled:s("disabled",m)})},m=>s("children",m)),h}),d=()=>{var h;return(h=o.value)==null?void 0:h.getCheckedKeys().filter(m=>{var y;const g=(y=o.value)==null?void 0:y.getNode(m);return!Un(g)&&_r(g.childNodes)})},v=h=>{Rn(e.modelValue,h)||a(Ot,h)};function f(h){a(bt,h),v(h)}return{...ji(Ta(e),Object.keys(Fw.props)),...t,nodeKey:i,expandOnClickNode:D(()=>!e.checkStrictly&&e.expandOnClickNode),defaultExpandedKeys:D(()=>e.defaultExpandedKeys?e.defaultExpandedKeys.concat(u):u),renderContent:(h,{node:m,data:g,store:y})=>h(c_e,{value:s("value",g),label:s("label",g),disabled:s("disabled",g),visible:m.visible},e.renderContent?()=>e.renderContent(h,{node:m,data:g,store:y}):n.default?()=>n.default({node:m,data:g,store:y}):void 0),filterNodeMethod:(h,m,g)=>e.filterNodeMethod?e.filterNodeMethod(h,m,g):h?new RegExp(S6(h),"i").test(s("label",m)||""):!0,onNodeClick:(h,m,g)=>{var y,b,_;if((y=t.onNodeClick)==null||y.call(t,h,m,g),!(e.showCheckbox&&e.checkOnClickNode))if(!e.showCheckbox&&(e.checkStrictly||m.isLeaf)){if(!s("disabled",h)){const w=(b=r.value)==null?void 0:b.states.options.get(s("value",h));(_=r.value)==null||_.handleOptionSelect(w)}}else e.expandOnClickNode&&g.proxy.handleExpandIconClick()},onCheck:(h,m)=>{var S;if(!e.showCheckbox)return;const g=s("value",h),y={};cm([o.value.store.root],k=>y[k.key]=k,k=>k.childNodes);const b=m.checkedKeys,_=e.multiple?Bc(e.modelValue).filter(k=>!(k in y)&&!b.includes(k)):[],w=_.concat(b);if(e.checkStrictly)f(e.multiple?w:w.includes(g)?g:void 0);else if(e.multiple){const k=d();f(_.concat(k))}else{const k=um([h],E=>!Hw(s("children",E))&&!s("disabled",E),E=>s("children",E)),T=k?s("value",k):void 0,M=s_(e.modelValue)&&!!um([h],E=>s("value",E)===e.modelValue,E=>s("children",E));f(T===e.modelValue||M?void 0:T)}He(()=>{var T;const k=Bc(e.modelValue);o.value.setCheckedKeys(k),(T=t.onCheck)==null||T.call(t,h,{checkedKeys:o.value.getCheckedKeys(),checkedNodes:o.value.getCheckedNodes(),halfCheckedKeys:o.value.getHalfCheckedKeys(),halfCheckedNodes:o.value.getHalfCheckedNodes()})}),(S=r.value)==null||S.focus()},onNodeExpand:(h,m,g)=>{var y;(y=t.onNodeExpand)==null||y.call(t,h,m,g),He(()=>{if(!e.checkStrictly&&e.lazy&&e.multiple&&m.checked){const b={},_=o.value.getCheckedKeys();cm([o.value.store.root],k=>b[k.key]=k,k=>k.childNodes);const w=Bc(e.modelValue).filter(k=>!(k in b)&&!_.includes(k)),S=d();f(w.concat(S))}})},cacheOptions:c}};var f_e=q({props:{data:{type:Array,default:()=>[]}},setup(e){const t=Ze(sp);return ye(()=>e.data,()=>{var a;e.data.forEach(r=>{t.states.cachedOptions.has(r.value)||t.states.cachedOptions.set(r.value,r)});const n=((a=t.selectRef)==null?void 0:a.querySelectorAll("input"))||[];qt&&!Array.from(n).includes(document.activeElement)&&t.setSelected()},{flush:"post",immediate:!0}),()=>{}}}),v_e=q({name:"ElTreeSelect",inheritAttrs:!1,props:{...FL,...ZD,cacheData:{type:Array,default:()=>[]}},setup(e,t){const{slots:n,expose:a,emit:r,attrs:o}=t,i={...o,onChange:void 0},l=F(),s=F(),u=D(()=>e.nodeKey||e.valueKey||"value"),c=u_e(e,{attrs:o,emit:r},{select:l,tree:s,key:u}),{cacheOptions:d,...v}=d_e(e,{attrs:i,slots:n,emit:r},{select:l,tree:s,key:u}),f=nn({});return a(f),mt(()=>{Object.assign(f,{...ji(s.value,["filter","updateKeyChildren","getCheckedNodes","setCheckedNodes","getCheckedKeys","setCheckedKeys","setChecked","getHalfCheckedNodes","getHalfCheckedKeys","getCurrentKey","getCurrentNode","setCurrentKey","setCurrentNode","getNode","remove","append","insertBefore","insertAfter"]),...ji(l.value,["focus","blur","selectedLabel"]),treeRef:s.value,selectRef:l.value})}),()=>ot(Bs,nn({...c,ref:h=>l.value=h}),{...n,default:()=>[ot(f_e,{data:d.value}),ot(Fw,nn({...v,ref:h=>s.value=h}))]})}}),h_e=v_e;const p_e=wt(h_e),Ww=Symbol(),m_e={key:-1,level:-1,data:{}},nO={type:Number,default:26},g_e=$e({data:{type:ae(Array),default:()=>Nn([])},emptyText:{type:String},height:{type:Number,default:200},props:{type:ae(Object),default:()=>Nn({children:"children",label:"label",disabled:"disabled",value:"id",class:""})},highlightCurrent:Boolean,showCheckbox:Boolean,defaultCheckedKeys:{type:ae(Array),default:()=>Nn([])},checkStrictly:Boolean,defaultExpandedKeys:{type:ae(Array),default:()=>Nn([])},indent:{type:Number,default:16},itemSize:nO,icon:{type:pn},expandOnClickNode:{type:Boolean,default:!0},checkOnClickNode:Boolean,checkOnClickLeaf:{type:Boolean,default:!0},currentNodeKey:{type:ae([String,Number])},accordion:Boolean,filterMethod:{type:ae(Function)},perfMode:{type:Boolean,default:!0},scrollbarAlwaysOn:Boolean}),y_e=$e({node:{type:ae(Object),default:()=>Nn(m_e)},expanded:Boolean,checked:Boolean,indeterminate:Boolean,showCheckbox:Boolean,disabled:Boolean,current:Boolean,hiddenExpandIcon:Boolean,itemSize:nO}),b_e=$e({node:{type:ae(Object),required:!0}}),aO="node-click",rO="node-drop",oO="node-expand",iO="node-collapse",lO="current-change",sO="check",uO="check-change",cO="node-contextmenu",__e={[aO]:(e,t,n)=>e&&t&&n,[rO]:(e,t,n)=>e&&t&&n,[oO]:(e,t)=>e&&t,[iO]:(e,t)=>e&&t,[lO]:(e,t)=>e&&t,[sO]:(e,t)=>e&&t,[uO]:(e,t)=>e&&vn(t),[cO]:(e,t,n)=>e&&t&&n},w_e={click:(e,t)=>!!(e&&t),drop:(e,t)=>!!(e&&t),toggle:e=>!!e,check:(e,t)=>e&&vn(t)};function S_e(e,t){const n=F(new Set),a=F(new Set),{emit:r}=Et();ye([()=>t.value,()=>e.defaultCheckedKeys],()=>He(()=>{b(e.defaultCheckedKeys)}),{immediate:!0});const o=(_=!1)=>{if(!t.value||!e.showCheckbox||e.checkStrictly&&!_)return;const{levelTreeNodeMap:w,maxLevel:S}=t.value,k=n.value,T=new Set;for(let M=S;M>=1;--M){const E=w.get(M);E&&E.forEach(A=>{const I=A.children;let P=!A.isLeaf||A.disabled||k.has(A.key);if(I){let R=!0,G=!1;for(const K of I){const H=K.key;if(K.isEffectivelyChecked||(P=!1),k.has(H))G=!0;else if(T.has(H)){R=!1,G=!0;break}else R=!1}R?k.add(A.key):G?(T.add(A.key),k.delete(A.key)):(k.delete(A.key),T.delete(A.key))}A.isEffectivelyChecked=P})}a.value=T},i=_=>n.value.has(_.key),l=_=>a.value.has(_.key),s=(_,w,S=!0,k=!0,T=!1)=>{const M=n.value,E=_.children;(!e.checkStrictly||T)&&S&&(E!=null&&E.length)&&(w=E.some(I=>!I.isEffectivelyChecked));const A=(I,P)=>{M[P?"add":"delete"](I.key);const R=I.children;(!e.checkStrictly||T)&&R&&R.forEach(G=>{(!G.disabled||G.children)&&A(G,P)})};A(_,w),k&&o(),S&&u(_,w)},u=(_,w)=>{const{checkedNodes:S,checkedKeys:k}=h(),{halfCheckedNodes:T,halfCheckedKeys:M}=m();r(sO,_.data,{checkedKeys:k,checkedNodes:S,halfCheckedKeys:M,halfCheckedNodes:T}),r(uO,_.data,w)};function c(_=!1){return h(_).checkedKeys}function d(_=!1){return h(_).checkedNodes}function v(){return m().halfCheckedKeys}function f(){return m().halfCheckedNodes}function h(_=!1){const w=[],S=[];if(t!=null&&t.value&&e.showCheckbox){const{treeNodeMap:k}=t.value;n.value.forEach(T=>{const M=k.get(T);M&&(!_||_&&M.isLeaf)&&(S.push(T),w.push(M.data))})}return{checkedKeys:S,checkedNodes:w}}function m(){const _=[],w=[];if(t!=null&&t.value&&e.showCheckbox){const{treeNodeMap:S}=t.value;a.value.forEach(k=>{const T=S.get(k);T&&(w.push(k),_.push(T.data))})}return{halfCheckedNodes:_,halfCheckedKeys:w}}function g(_){n.value.clear(),a.value.clear(),He(()=>{b(_)})}function y(_,w,S){if(t!=null&&t.value&&e.showCheckbox){const k=t.value.treeNodeMap.get(_);k&&s(k,w,!1,void 0,S)}}function b(_){if(t!=null&&t.value){const{treeNodeMap:w}=t.value;if(e.showCheckbox&&w&&(_==null?void 0:_.length)>0){for(const S of _){const k=w.get(S);k&&!i(k)&&s(k,!0,!1,!1)}o()}}}return{updateCheckedKeys:o,toggleCheckbox:s,isChecked:i,isIndeterminate:l,getCheckedKeys:c,getCheckedNodes:d,getHalfCheckedKeys:v,getHalfCheckedNodes:f,setChecked:y,setCheckedKeys:g}}function C_e(e,t){const n=F(new Set([])),a=F(new Set([])),r=D(()=>Qe(e.filterMethod));function o(l){var m;if(!r.value)return;const s=new Set,u=a.value,c=n.value,d=[],v=((m=t.value)==null?void 0:m.treeNodes)||[],f=e.filterMethod;c.clear();function h(g){g.forEach(y=>{d.push(y),f!=null&&f(l,y.data,y)?d.forEach(_=>{s.add(_.key),_.expanded=!0}):(y.expanded=!1,y.isLeaf&&c.add(y.key));const b=y.children;if(b&&h(b),!y.isLeaf){if(!s.has(y.key))c.add(y.key);else if(b){let _=!0;for(const w of b)if(!c.has(w.key)){_=!1;break}_?u.add(y.key):u.delete(y.key)}}d.pop()})}return h(v),s}function i(l){return a.value.has(l.key)}return{hiddenExpandIconKeySet:a,hiddenNodeKeySet:n,doFilter:o,isForceHiddenExpandIcon:i}}function x_e(e,t){const n=F(new Set),a=F(),r=Ht(),o=F(),{isIndeterminate:i,isChecked:l,toggleCheckbox:s,getCheckedKeys:u,getCheckedNodes:c,getHalfCheckedKeys:d,getHalfCheckedNodes:v,setChecked:f,setCheckedKeys:h}=S_e(e,r),{doFilter:m,hiddenNodeKeySet:g,isForceHiddenExpandIcon:y}=C_e(e,r),b=D(()=>{var Z;return((Z=e.props)==null?void 0:Z.value)||"id"}),_=D(()=>{var Z;return((Z=e.props)==null?void 0:Z.children)||"children"}),w=D(()=>{var Z;return((Z=e.props)==null?void 0:Z.disabled)||"disabled"}),S=D(()=>{var Z;return((Z=e.props)==null?void 0:Z.label)||"label"}),k=D(()=>{var ge;const Z=n.value,j=g.value,oe=[],se=((ge=r.value)==null?void 0:ge.treeNodes)||[],fe=[];for(let Se=se.length-1;Se>=0;--Se)fe.push(se[Se]);for(;fe.length;){const Se=fe.pop();if(!j.has(Se.key)&&(oe.push(Se),Se.children&&Z.has(Se.key)))for(let De=Se.children.length-1;De>=0;--De)fe.push(Se.children[De])}return oe}),T=D(()=>k.value.length>0);function M(Z){const j=new Map,oe=new Map;let se=1;function fe(Se,De=1,Te=void 0){var Ce;const Re=[];for(const Le of Se){const me=I(Le),Be={level:De,key:me,data:Le};Be.label=R(Le),Be.parent=Te;const ze=A(Le);Be.disabled=P(Le),Be.isLeaf=!ze||ze.length===0,Be.expanded=n.value.has(me),ze&&ze.length&&(Be.children=fe(ze,De+1,Be)),Re.push(Be),j.set(me,Be),oe.has(De)||oe.set(De,[]),(Ce=oe.get(De))==null||Ce.push(Be)}return De>se&&(se=De),Re}const ge=fe(Z);return{treeNodeMap:j,levelTreeNodeMap:oe,maxLevel:se,treeNodes:ge}}function E(Z){const j=m(Z);j&&(n.value=j)}function A(Z){return Z[_.value]}function I(Z){return Z?Z[b.value]:""}function P(Z){return Z[w.value]}function R(Z){return Z[S.value]}function G(Z){n.value.has(Z.key)?V(Z):N(Z)}function K(Z){const j=new Set,oe=r.value.treeNodeMap;n.value.forEach(se=>{const fe=oe.get(se);fe&&(fe.expanded=!1)}),Z.forEach(se=>{let fe=oe.get(se);for(;fe&&!j.has(fe.key);)j.add(fe.key),fe.expanded=!0,fe=fe.parent}),n.value=j}function H(Z,j){B(Z),t(aO,Z.data,Z,j),e.expandOnClickNode&&G(Z),e.showCheckbox&&(e.checkOnClickNode||Z.isLeaf&&e.checkOnClickLeaf)&&!Z.disabled&&s(Z,!l(Z),!0)}function z(Z,j){t(rO,Z.data,Z,j)}function B(Z){X(Z)||(a.value=Z.key,t(lO,Z.data,Z))}function W(Z,j){s(Z,j)}function N(Z){const j=n.value;if(r.value&&e.accordion){const{treeNodeMap:se}=r.value;j.forEach(fe=>{const ge=se.get(fe);Z&&Z.level===(ge==null?void 0:ge.level)&&(j.delete(fe),ge.expanded=!1)})}j.add(Z.key);const oe=ve(Z.key);oe&&(oe.expanded=!0,t(oO,oe.data,oe))}function V(Z){n.value.delete(Z.key);const j=ve(Z.key);j&&(j.expanded=!1,t(iO,j.data,j))}function Y(Z){return!!Z.disabled}function X(Z){const j=a.value;return j!==void 0&&j===Z.key}function ee(){var Z,j;if(a.value!==void 0)return(j=(Z=r.value)==null?void 0:Z.treeNodeMap.get(a.value))==null?void 0:j.data}function re(){return a.value}function ie(Z){a.value=Z}function le(Z){r.value=M(Z)}function ve(Z){var oe;const j=_t(Z)?I(Z):Z;return(oe=r.value)==null?void 0:oe.treeNodeMap.get(j)}function he(Z,j="auto"){const oe=ve(Z);oe&&o.value&&o.value.scrollToItem(k.value.indexOf(oe),j)}function J(Z){var j;(j=o.value)==null||j.scrollTo(Z)}return ye(()=>e.currentNodeKey,Z=>{a.value=Z},{immediate:!0}),ye(()=>e.defaultExpandedKeys,Z=>{K(Z||[])}),ye(()=>e.data,Z=>{le(Z),K(e.defaultExpandedKeys||[])},{immediate:!0}),{tree:r,flattenTree:k,isNotEmpty:T,listRef:o,getKey:I,getChildren:A,toggleExpand:G,toggleCheckbox:s,isChecked:l,isIndeterminate:i,isDisabled:Y,isCurrent:X,isForceHiddenExpandIcon:y,handleNodeClick:H,handleNodeDrop:z,handleNodeCheck:W,getCurrentNode:ee,getCurrentKey:re,setCurrentKey:ie,getCheckedKeys:u,getCheckedNodes:c,getHalfCheckedKeys:d,getHalfCheckedNodes:v,setChecked:f,setCheckedKeys:h,filter:E,setData:le,getNode:ve,expandNode:N,collapseNode:V,setExpandedKeys:K,scrollToNode:he,scrollTo:J}}var k_e=q({name:"ElTreeNodeContent",props:b_e,setup(e){const t=Ze(Ww),n=ke("tree");return()=>{const a=e.node,{data:r}=a;return t!=null&&t.ctx.slots.default?t.ctx.slots.default({node:a,data:r}):ot(Bw,{tag:"span",truncated:!0,class:n.be("node","label")},()=>[a==null?void 0:a.label])}}});const T_e=["aria-expanded","aria-disabled","aria-checked","data-key"];var M_e=q({name:"ElTreeNode",__name:"tree-node",props:y_e,emits:w_e,setup(e,{emit:t}){const n=e,a=t,r=Ze(Ww),o=ke("tree"),i=D(()=>(r==null?void 0:r.props.indent)??16),l=D(()=>(r==null?void 0:r.props.icon)??F6),s=h=>{var y;const m=(y=r==null?void 0:r.props.props)==null?void 0:y.class;if(!m)return{};let g;if(Qe(m)){const{data:b}=h;g=m(b,h)}else g=m;return Ye(g)?{[g]:!0}:g},u=h=>{a("click",n.node,h)},c=h=>{a("drop",n.node,h)},d=()=>{a("toggle",n.node)},v=h=>{a("check",n.node,h)},f=h=>{var m,g,y,b;(y=(g=(m=r==null?void 0:r.instance)==null?void 0:m.vnode)==null?void 0:g.props)!=null&&y.onNodeContextmenu&&(h.stopPropagation(),h.preventDefault()),r==null||r.ctx.emit(cO,h,(b=n.node)==null?void 0:b.data,n.node)};return(h,m)=>{var g,y,b;return x(),O("div",{ref:"node$",class:U([p(o).b("node"),p(o).is("expanded",e.expanded),p(o).is("current",e.current),p(o).is("focusable",!e.disabled),p(o).is("checked",!e.disabled&&e.checked),s(e.node)]),role:"treeitem",tabindex:"-1","aria-expanded":e.expanded,"aria-disabled":e.disabled,"aria-checked":e.checked,"data-key":(g=e.node)==null?void 0:g.key,onClick:at(u,["stop"]),onContextmenu:f,onDragover:m[1]||(m[1]=at(()=>{},["prevent"])),onDragenter:m[2]||(m[2]=at(()=>{},["prevent"])),onDrop:at(c,["stop"])},[C("div",{class:U(p(o).be("node","content")),style:et({paddingLeft:`${(e.node.level-1)*i.value}px`,height:e.itemSize+"px"})},[l.value?(x(),ne(p(Xe),{key:0,class:U([p(o).is("leaf",!!((y=e.node)!=null&&y.isLeaf)),p(o).is("hidden",e.hiddenExpandIcon),{expanded:!((b=e.node)!=null&&b.isLeaf)&&e.expanded},p(o).be("node","expand-icon")]),onClick:at(d,["stop"])},{default:$(()=>[(x(),ne(St(l.value)))]),_:1},8,["class"])):te("v-if",!0),e.showCheckbox?(x(),ne(p(Zo),{key:1,"model-value":e.checked,indeterminate:e.indeterminate,disabled:e.disabled,onChange:v,onClick:m[0]||(m[0]=at(()=>{},["stop"]))},null,8,["model-value","indeterminate","disabled"])):te("v-if",!0),L(p(k_e),{node:{...e.node,expanded:e.expanded}},null,8,["node"])],6)],42,T_e)}}}),E_e=M_e,A_e=q({name:"ElTreeV2",__name:"tree",props:g_e,emits:__e,setup(e,{expose:t,emit:n}){const a=e,r=n,o=Xn(),i=D(()=>a.itemSize);Ft(Ww,{ctx:{emit:r,slots:o},props:a,instance:Et()}),Ft(Qi,void 0);const{t:l}=Vt(),s=ke("tree"),{flattenTree:u,isNotEmpty:c,listRef:d,toggleExpand:v,isIndeterminate:f,isChecked:h,isDisabled:m,isCurrent:g,isForceHiddenExpandIcon:y,handleNodeClick:b,handleNodeDrop:_,handleNodeCheck:w,toggleCheckbox:S,getCurrentNode:k,getCurrentKey:T,setCurrentKey:M,getCheckedKeys:E,getCheckedNodes:A,getHalfCheckedKeys:I,getHalfCheckedNodes:P,setChecked:R,setCheckedKeys:G,filter:K,setData:H,getNode:z,expandNode:B,collapseNode:W,setExpandedKeys:N,scrollToNode:V,scrollTo:Y}=x_e(a,r);return t({toggleCheckbox:S,getCurrentNode:k,getCurrentKey:T,setCurrentKey:M,getCheckedKeys:E,getCheckedNodes:A,getHalfCheckedKeys:I,getHalfCheckedNodes:P,setChecked:R,setCheckedKeys:G,filter:K,setData:H,getNode:z,expandNode:B,collapseNode:W,setExpandedKeys:N,scrollToNode:V,scrollTo:Y}),(X,ee)=>(x(),O("div",{class:U([p(s).b(),{[p(s).m("highlight-current")]:e.highlightCurrent}]),role:"tree"},[p(c)?(x(),ne(p(cp),{key:0,ref_key:"listRef",ref:d,"class-name":p(s).b("virtual-list"),data:p(u),total:p(u).length,height:e.height,"item-size":i.value,"perf-mode":e.perfMode,"scrollbar-always-on":e.scrollbarAlwaysOn},{default:$(({data:re,index:ie,style:le})=>[(x(),ne(E_e,{key:re[ie].key,style:et(le),node:re[ie],expanded:re[ie].expanded,"show-checkbox":e.showCheckbox,checked:p(h)(re[ie]),indeterminate:p(f)(re[ie]),"item-size":i.value,disabled:p(m)(re[ie]),current:p(g)(re[ie]),"hidden-expand-icon":p(y)(re[ie]),onClick:p(b),onToggle:p(v),onCheck:p(w),onDrop:p(_)},null,8,["style","node","expanded","show-checkbox","checked","indeterminate","item-size","disabled","current","hidden-expand-icon","onClick","onToggle","onCheck","onDrop"]))]),_:1},8,["class-name","data","total","height","item-size","perf-mode","scrollbar-always-on"])):(x(),O("div",{key:1,class:U(p(s).e("empty-block"))},[ce(X.$slots,"empty",{},()=>[C("span",{class:U(p(s).e("empty-text"))},Q(e.emptyText??p(l)("el.tree.emptyText")),3)])],2))],2))}}),I_e=A_e;const L_e=wt(I_e),P_e="ElUpload";var D_e=class extends Error{constructor(e,t,n,a){super(e),this.name="UploadAjaxError",this.status=t,this.method=n,this.url=a}};function Xx(e,t,n){let a;return n.response?a=`${n.response.error||n.response}`:n.responseText?a=`${n.responseText}`:a=`fail to ${t.method} ${e} ${n.status}`,new D_e(a,n.status,t.method,e)}function O_e(e){const t=e.responseText||e.response;if(!t)return t;try{return JSON.parse(t)}catch{return t}}const $_e=e=>{typeof XMLHttpRequest>"u"&&Dn(P_e,"XMLHttpRequest is undefined");const t=new XMLHttpRequest,n=e.action;t.upload&&t.upload.addEventListener("progress",o=>{const i=o;i.percent=o.total>0?o.loaded/o.total*100:0,e.onProgress(i)});const a=new FormData;if(e.data)for(const[o,i]of Object.entries(e.data))Ae(i)?i.length===2&&i[0]instanceof Blob&&Ye(i[1])?a.append(o,i[0],i[1]):i.forEach(l=>{a.append(o,l)}):a.append(o,i);a.append(e.filename,e.file,e.file.name),t.addEventListener("error",()=>{e.onError(Xx(n,e,t))}),t.addEventListener("load",()=>{if(t.status<200||t.status>=300)return e.onError(Xx(n,e,t));e.onSuccess(O_e(t))}),t.open(e.method,n,!0),e.withCredentials&&"withCredentials"in t&&(t.withCredentials=!0);const r=e.headers||{};if(r instanceof Headers)r.forEach((o,i)=>t.setRequestHeader(i,o));else for(const[o,i]of Object.entries(r))Un(i)||t.setRequestHeader(o,String(i));return t.send(a),t},dO=["text","picture","picture-card"];let R_e=1;const u_=()=>Date.now()+R_e++,fO=$e({action:{type:String,default:"#"},headers:{type:ae(Object)},method:{type:String,default:"post"},data:{type:ae([Object,Function,Promise]),default:()=>Nn({})},multiple:Boolean,name:{type:String,default:"file"},drag:Boolean,withCredentials:Boolean,showFileList:{type:Boolean,default:!0},accept:{type:String,default:""},fileList:{type:ae(Array),default:()=>Nn([])},autoUpload:{type:Boolean,default:!0},listType:{type:String,values:dO,default:"text"},httpRequest:{type:ae(Function),default:$_e},disabled:{type:Boolean,default:void 0},limit:Number,directory:Boolean}),N_e=$e({...fO,beforeUpload:{type:ae(Function),default:kt},beforeRemove:{type:ae(Function)},onRemove:{type:ae(Function),default:kt},onChange:{type:ae(Function),default:kt},onPreview:{type:ae(Function),default:kt},onSuccess:{type:ae(Function),default:kt},onProgress:{type:ae(Function),default:kt},onError:{type:ae(Function),default:kt},onExceed:{type:ae(Function),default:kt},crossorigin:{type:ae(String)}}),vO=Symbol("uploadContextKey"),B_e=$e({files:{type:ae(Array),default:()=>Nn([])},disabled:{type:Boolean,default:void 0},handlePreview:{type:ae(Function),default:kt},listType:{type:String,values:dO,default:"text"},crossorigin:{type:ae(String)}}),V_e={remove:e=>!!e},z_e=$e({...fO,beforeUpload:{type:ae(Function),default:kt},onRemove:{type:ae(Function),default:kt},onStart:{type:ae(Function),default:kt},onSuccess:{type:ae(Function),default:kt},onProgress:{type:ae(Function),default:kt},onError:{type:ae(Function),default:kt},onExceed:{type:ae(Function),default:kt}}),F_e=$e({disabled:{type:Boolean,default:void 0},directory:Boolean}),H_e={file:e=>Ae(e)},W_e=["tabindex","aria-disabled","onKeydown"],K_e=["src","crossorigin"],U_e=["onClick"],G_e=["title"],Y_e=["onClick"],j_e=["onClick"];var q_e=q({name:"ElUploadList",__name:"upload-list",props:B_e,emits:V_e,setup(e,{emit:t}){const n=e,a=t,{t:r}=Vt(),o=ke("upload"),i=ke("icon"),l=ke("list"),s=$n(),u=F(!1),c=D(()=>[o.b("list"),o.bm("list",n.listType),o.is("disabled",s.value)]),d=async f=>{const h=f.relatedTarget;await He(),h!=null&&h.classList.contains(o.be("list","item"))||(u.value=!1)},v=f=>{a("remove",f)};return(f,h)=>(x(),ne(L7,{tag:"ul",class:U(c.value),name:p(l).b()},{default:$(()=>[(x(!0),O(Ve,null,dt(e.files,(m,g)=>(x(),O("li",{key:m.uid||m.name,class:U([p(o).be("list","item"),p(o).is(m.status),{focusing:u.value}]),tabindex:p(s)?void 0:0,"aria-disabled":p(s),role:"button",onKeydown:kn(y=>!p(s)&&v(m),["delete"]),onFocus:h[0]||(h[0]=y=>u.value=!0),onBlur:d,onClick:h[1]||(h[1]=y=>u.value=!1)},[ce(f.$slots,"default",{file:m,index:g},()=>[e.listType==="picture"||m.status!=="uploading"&&e.listType==="picture-card"?(x(),O("img",{key:0,class:U(p(o).be("list","item-thumbnail")),src:m.url,crossorigin:e.crossorigin,alt:""},null,10,K_e)):te("v-if",!0),m.status==="uploading"||e.listType!=="picture-card"?(x(),O("div",{key:1,class:U(p(o).be("list","item-info"))},[C("a",{class:U(p(o).be("list","item-name")),onClick:at(y=>e.handlePreview(m),["prevent"])},[L(p(Xe),{class:U(p(i).m("document"))},{default:$(()=>[L(p(EI))]),_:1},8,["class"]),C("span",{class:U(p(o).be("list","item-file-name")),title:m.name},Q(m.name),11,G_e)],10,U_e),m.status==="uploading"?(x(),ne(p(kw),{key:0,type:e.listType==="picture-card"?"circle":"line","stroke-width":e.listType==="picture-card"?6:2,percentage:Number(m.percentage),style:et(e.listType==="picture-card"?"":"margin-top: 0.5rem")},null,8,["type","stroke-width","percentage","style"])):te("v-if",!0)],2)):te("v-if",!0),C("label",{class:U(p(o).be("list","item-status-label"))},[e.listType==="text"?(x(),ne(p(Xe),{key:0,class:U([p(i).m("upload-success"),p(i).m("circle-check")])},{default:$(()=>[L(p(Ao))]),_:1},8,["class"])):["picture-card","picture"].includes(e.listType)?(x(),ne(p(Xe),{key:1,class:U([p(i).m("upload-success"),p(i).m("check")])},{default:$(()=>[L(p(Os))]),_:1},8,["class"])):te("v-if",!0)],2),p(s)?te("v-if",!0):(x(),ne(p(Xe),{key:2,class:U(p(i).m("close")),"aria-label":p(r)("el.upload.delete"),role:"button",tabindex:"0",onClick:y=>v(m),onKeydown:kn(at(y=>v(m),["prevent"]),["enter","space"])},{default:$(()=>[L(p(so))]),_:1},8,["class","aria-label","onClick","onKeydown"])),p(s)?te("v-if",!0):(x(),O("i",{key:3,class:U(p(i).m("close-tip"))},Q(p(r)("el.upload.deleteTip")),3)),e.listType==="picture-card"?(x(),O("span",{key:4,class:U(p(o).be("list","item-actions"))},[C("span",{class:U(p(o).be("list","item-preview")),onClick:y=>e.handlePreview(m)},[L(p(Xe),{class:U(p(i).m("zoom-in"))},{default:$(()=>[L(p(G6))]),_:1},8,["class"])],10,Y_e),p(s)?te("v-if",!0):(x(),O("span",{key:0,class:U(p(o).be("list","item-delete")),onClick:y=>v(m)},[L(p(Xe),{class:U(p(i).m("delete"))},{default:$(()=>[L(p(W6))]),_:1},8,["class"])],10,j_e))],2)):te("v-if",!0)])],42,W_e))),128)),ce(f.$slots,"append")]),_:3},8,["class","name"]))}}),Zx=q_e;const Jx="ElUploadDrag";var X_e=q({name:Jx,__name:"upload-dragger",props:F_e,emits:H_e,setup(e,{emit:t}){const n=e,a=t;Ze(vO)||Dn(Jx,"usage: ");const r=ke("upload"),o=F(!1),i=$n(),l=v=>new Promise((f,h)=>v.file(f,h)),s=async v=>{try{if(v.isFile){const f=await l(v);return f.isDirectory=!1,[f]}if(v.isDirectory){const f=v.createReader(),h=()=>new Promise((b,_)=>f.readEntries(b,_)),m=[];let g=await h();for(;g.length>0;)m.push(...g),g=await h();const y=m.map(b=>s(b).catch(()=>[]));return Wm(await Promise.all(y))}}catch{return[]}return[]},u=async v=>{if(i.value)return;o.value=!1,v.stopPropagation();const f=Array.from(v.dataTransfer.files),h=v.dataTransfer.items||[];if(n.directory){const m=Array.from(h).map(g=>{var y;return(y=g==null?void 0:g.webkitGetAsEntry)==null?void 0:y.call(g)}).filter(g=>g);a("file",Wm(await Promise.all(m.map(s))));return}f.forEach((m,g)=>{var b,_;const y=(_=(b=h[g])==null?void 0:b.webkitGetAsEntry)==null?void 0:_.call(b);y&&(m.isDirectory=y.isDirectory)}),a("file",f)},c=()=>{i.value||(o.value=!0)},d=v=>{v.currentTarget.contains(v.relatedTarget)||(o.value=!1)};return(v,f)=>(x(),O("div",{class:U([p(r).b("dragger"),p(r).is("dragover",o.value)]),onDrop:at(u,["prevent"]),onDragover:at(c,["prevent"]),onDragleave:at(d,["prevent"])},[ce(v.$slots,"default")],34))}}),Z_e=X_e;const J_e=["tabindex","aria-disabled","onKeydown"],Q_e=["name","disabled","multiple","accept","webkitdirectory"];var e3e=q({name:"ElUploadContent",inheritAttrs:!1,__name:"upload-content",props:z_e,setup(e,{expose:t}){const n=e,a=ke("upload"),r=$n(),o=Ht({}),i=Ht(),l=m=>{if(m.length===0)return;const{autoUpload:g,limit:y,fileList:b,multiple:_,onStart:w,onExceed:S}=n;if(y&&b.length+m.length>y){S(m,b);return}_||(m=m.slice(0,1));for(const k of m){const T=k;T.uid=u_(),w(T),g&&s(T)}},s=async m=>{if(i.value.value="",!n.beforeUpload)return c(m);let g,y={};try{const _=n.data,w=n.beforeUpload(m);y=qv(n.data)?Li(n.data):n.data,g=await w,qv(n.data)&&Rn(_,y)&&(y=Li(n.data))}catch{g=!1}if(g===!1){n.onRemove(m);return}let b=m;g instanceof Blob&&(g instanceof File?b=g:b=new File([g],m.name,{type:m.type})),c(Object.assign(b,{uid:m.uid}),y)},u=async(m,g)=>Qe(m)?m(g):m,c=async(m,g)=>{const{headers:y,data:b,method:_,withCredentials:w,name:S,action:k,onProgress:T,onSuccess:M,onError:E,httpRequest:A}=n;try{g=await u(g??b,m)}catch{n.onRemove(m);return}const{uid:I}=m,P={headers:y||{},withCredentials:w,file:m,data:g,method:_,filename:S,action:k,onProgress:G=>{T(G,m)},onSuccess:G=>{M(G,m),delete o.value[I]},onError:G=>{E(G,m),delete o.value[I]}},R=A(P);o.value[I]=R,R instanceof Promise&&R.then(P.onSuccess,P.onError)},d=m=>{const g=m.target.files;g&&l(Array.from(g))},v=()=>{r.value||(i.value.value="",i.value.click())},f=()=>{v()};return t({abort:m=>{TA(o.value).filter(m?([g])=>String(m.uid)===g:()=>!0).forEach(([g,y])=>{y instanceof XMLHttpRequest&&y.abort(),delete o.value[g]})},upload:s}),(m,g)=>(x(),O("div",{class:U([p(a).b(),p(a).m(e.listType),p(a).is("drag",e.drag),p(a).is("disabled",p(r))]),tabindex:p(r)?void 0:0,"aria-disabled":p(r),role:"button",onClick:v,onKeydown:kn(at(f,["self"]),["enter","space"])},[e.drag?(x(),ne(Z_e,{key:0,disabled:p(r),directory:e.directory,onFile:l},{default:$(()=>[ce(m.$slots,"default")]),_:3},8,["disabled","directory"])):ce(m.$slots,"default",{key:1}),C("input",{ref_key:"inputRef",ref:i,class:U(p(a).e("input")),name:e.name,disabled:p(r),multiple:e.multiple,accept:e.accept,webkitdirectory:e.directory||void 0,type:"file",onChange:d,onClick:g[0]||(g[0]=at(()=>{},["stop"]))},null,42,Q_e)],42,J_e))}}),Qx=e3e;const ek="ElUpload",tk=e=>{var t;(t=e.url)!=null&&t.startsWith("blob:")&&URL.revokeObjectURL(e.url)},t3e=(e,t)=>{const n=DY(e,"fileList",void 0,{passive:!0}),a=h=>n.value.find(m=>m.uid===h.uid);function r(h){var m;(m=t.value)==null||m.abort(h)}function o(h=["ready","uploading","success","fail"]){n.value=n.value.filter(m=>!h.includes(m.status))}function i(h){n.value=n.value.filter(m=>m.uid!==h.uid)}const l=h=>{He(()=>e.onChange(h,n.value))},s=(h,m)=>{const g=a(m);g&&(console.error(h),g.status="fail",i(g),e.onError(h,g,n.value),l(g))},u=(h,m)=>{const g=a(m);g&&(e.onProgress(h,g,n.value),g.status="uploading",g.percentage=Math.round(h.percent))},c=(h,m)=>{const g=a(m);g&&(g.status="success",g.response=h,e.onSuccess(h,g,n.value),l(g))},d=h=>{Un(h.uid)&&(h.uid=u_());const m={name:h.name,percentage:0,status:"ready",size:h.size,raw:h,uid:h.uid};if(e.listType==="picture-card"||e.listType==="picture")try{m.url=URL.createObjectURL(h)}catch(g){un(ek,g.message),e.onError(g,m,n.value)}n.value=[...n.value,m],l(m)},v=async h=>{const m=h instanceof File?a(h):h;m||Dn(ek,"file to be removed not found");const g=y=>{r(y),i(y),e.onRemove(y,n.value),tk(y)};e.beforeRemove?await e.beforeRemove(m,n.value)!==!1&&g(m):g(m)};function f(){n.value.filter(({status:h})=>h==="ready").forEach(({raw:h})=>{var m;return h&&((m=t.value)==null?void 0:m.upload(h))})}return ye(()=>e.listType,h=>{h!=="picture-card"&&h!=="picture"||(n.value=n.value.map(m=>{const{raw:g,url:y}=m;if(!y&&g)try{m.url=URL.createObjectURL(g)}catch(b){e.onError(b,m,n.value)}return m}))}),ye(n,h=>{for(const m of h)m.uid||(m.uid=u_()),m.status||(m.status="success")},{immediate:!0,deep:!0}),{uploadFiles:n,abort:r,clearFiles:o,handleError:s,handleProgress:u,handleStart:d,handleSuccess:c,handleRemove:v,submit:f,revokeFileObjectURL:tk}};var n3e=q({name:"ElUpload",__name:"upload",props:N_e,setup(e,{expose:t}){const n=e,a=$n(),r=Ht(),{abort:o,submit:i,clearFiles:l,uploadFiles:s,handleStart:u,handleError:c,handleRemove:d,handleSuccess:v,handleProgress:f,revokeFileObjectURL:h}=t3e(n,r),m=D(()=>n.listType==="picture-card"),g=D(()=>({...n,fileList:s.value,onStart:u,onProgress:f,onSuccess:v,onError:c,onRemove:d}));return rn(()=>{s.value.forEach(h)}),Ft(vO,{accept:cn(n,"accept")}),t({abort:o,submit:i,clearFiles:l,handleStart:u,handleRemove:d}),(y,b)=>(x(),O("div",null,[m.value&&e.showFileList?(x(),ne(Zx,{key:0,disabled:p(a),"list-type":e.listType,files:p(s),crossorigin:e.crossorigin,"handle-preview":e.onPreview,onRemove:p(d)},xr({append:$(()=>[L(Qx,Tt({ref_key:"uploadRef",ref:r},g.value),{default:$(()=>[y.$slots.trigger?ce(y.$slots,"trigger",{key:0}):te("v-if",!0),!y.$slots.trigger&&y.$slots.default?ce(y.$slots,"default",{key:1}):te("v-if",!0)]),_:3},16)]),_:2},[y.$slots.file?{name:"default",fn:$(({file:_,index:w})=>[ce(y.$slots,"file",{file:_,index:w})]),key:"0"}:void 0]),1032,["disabled","list-type","files","crossorigin","handle-preview","onRemove"])):te("v-if",!0),!m.value||m.value&&!e.showFileList?(x(),ne(Qx,Tt({key:1,ref_key:"uploadRef",ref:r},g.value),{default:$(()=>[y.$slots.trigger?ce(y.$slots,"trigger",{key:0}):te("v-if",!0),!y.$slots.trigger&&y.$slots.default?ce(y.$slots,"default",{key:1}):te("v-if",!0)]),_:3},16)):te("v-if",!0),y.$slots.trigger?ce(y.$slots,"default",{key:2}):te("v-if",!0),ce(y.$slots,"tip"),!m.value&&e.showFileList?(x(),ne(Zx,{key:3,disabled:p(a),"list-type":e.listType,files:p(s),crossorigin:e.crossorigin,"handle-preview":e.onPreview,onRemove:p(d)},xr({_:2},[y.$slots.file?{name:"default",fn:$(({file:_,index:w})=>[ce(y.$slots,"file",{file:_,index:w})]),key:"0"}:void 0]),1032,["disabled","list-type","files","crossorigin","handle-preview","onRemove"])):te("v-if",!0)]))}}),a3e=n3e;const r3e=wt(a3e),o3e=$e({zIndex:{type:Number,default:9},rotate:{type:Number,default:-22},width:Number,height:Number,image:String,content:{type:ae([String,Array]),default:"Element Plus"},font:{type:ae(Object)},gap:{type:ae(Array),default:()=>[100,100]},offset:{type:ae(Array)}});function i3e(e){return e.replace(/([A-Z])/g,"-$1").toLowerCase()}function l3e(e){return Object.keys(e).map(t=>`${i3e(t)}: ${e[t]};`).join(" ")}function s3e(){return window.devicePixelRatio||1}const u3e=(e,t)=>{let n=!1;return e.removedNodes.length&&t&&(n=Array.from(e.removedNodes).includes(t)),e.type==="attributes"&&e.target===t&&(n=!0),n},c3e={left:[0,.5],start:[0,.5],center:[.5,0],right:[1,-.5],end:[1,-.5]};function py(e,t,n=1){const a=document.createElement("canvas"),r=a.getContext("2d"),o=e*n,i=t*n;return a.setAttribute("width",`${o}px`),a.setAttribute("height",`${i}px`),r.save(),[r,a,o,i]}function d3e(){function e(t,n,a,r,o,i,l,s,u){const[c,d,v,f]=py(r,o,a);let h=0;if(t instanceof HTMLImageElement)c.drawImage(t,0,0,v,f);else{const{color:Y,fontSize:X,fontStyle:ee,fontWeight:re,fontFamily:ie,textAlign:le,textBaseline:ve}=i,he=Number(X)*a;c.font=`${ee} normal ${re} ${he}px/${o}px ${ie}`,c.fillStyle=Y,c.textAlign=le,c.textBaseline=ve;const J=Ae(t)?t:[t];if(ve!=="top"&&J[0]){const Z=c.measureText(J[0]);c.textBaseline="top";const j=c.measureText(J[0]);h=Z.actualBoundingBoxAscent-j.actualBoundingBoxAscent}J==null||J.forEach((Z,j)=>{const[oe,se]=c3e[le];c.fillText(Z??"",v*oe+u*se,j*(he+i.fontGap*a))})}const m=Math.PI/180*Number(n),g=Math.max(r,o),[y,b,_]=py(g,g,a);y.translate(_/2,_/2),y.rotate(m),v>0&&f>0&&y.drawImage(d,-v/2,-f/2);function w(Y,X){return[Y*Math.cos(m)-X*Math.sin(m),Y*Math.sin(m)+X*Math.cos(m)]}let S=0,k=0,T=0,M=0;const E=v/2,A=f/2;[[0-E,0-A],[0+E,0-A],[0+E,0+A],[0-E,0+A]].forEach(([Y,X])=>{const[ee,re]=w(Y,X);S=Math.min(S,ee),k=Math.max(k,ee),T=Math.min(T,re),M=Math.max(M,re)});const I=S+_/2,P=T+_/2,R=k-S,G=M-T,K=l*a,H=s*a,z=(R+K)*2,B=G+H,[W,N]=py(z,B);function V(Y=0,X=0){W.drawImage(b,I,P,R,G,Y,X+h,R,G)}return V(),V(R+K,-G/2-H/2),V(R+K,+G/2+H/2),[N.toDataURL(),z/a,B/a]}return e}var f3e=q({name:"ElWatermark",__name:"watermark",props:o3e,setup(e){const t={position:"relative"},n=e,a=D(()=>{var I;return((I=n.font)==null?void 0:I.fontGap)??3}),r=D(()=>{var I;return((I=n.font)==null?void 0:I.color)??"rgba(0,0,0,.15)"}),o=D(()=>{var I;return((I=n.font)==null?void 0:I.fontSize)??16}),i=D(()=>{var I;return((I=n.font)==null?void 0:I.fontWeight)??"normal"}),l=D(()=>{var I;return((I=n.font)==null?void 0:I.fontStyle)??"normal"}),s=D(()=>{var I;return((I=n.font)==null?void 0:I.fontFamily)??"sans-serif"}),u=D(()=>{var I;return((I=n.font)==null?void 0:I.textAlign)??"center"}),c=D(()=>{var I;return((I=n.font)==null?void 0:I.textBaseline)??"hanging"}),d=D(()=>n.gap[0]),v=D(()=>n.gap[1]),f=D(()=>d.value/2),h=D(()=>v.value/2),m=D(()=>{var I;return((I=n.offset)==null?void 0:I[0])??f.value}),g=D(()=>{var I;return((I=n.offset)==null?void 0:I[1])??h.value}),y=()=>{const I={zIndex:n.zIndex,position:"absolute",left:0,top:0,width:"100%",height:"100%",pointerEvents:"none",backgroundRepeat:"repeat"};let P=m.value-f.value,R=g.value-h.value;return P>0&&(I.left=`${P}px`,I.width=`calc(100% - ${P}px)`,P=0),R>0&&(I.top=`${R}px`,I.height=`calc(100% - ${R}px)`,R=0),I.backgroundPosition=`${P}px ${R}px`,I},b=Ht(null),_=Ht(),w=F(!1),S=()=>{_.value&&(_.value.remove(),_.value=void 0)},k=(I,P)=>{var R;b.value&&_.value&&(w.value=!0,_.value.setAttribute("style",l3e({...y(),backgroundImage:`url('${I}')`,backgroundSize:`${Math.floor(P)}px`})),(R=b.value)==null||R.append(_.value),setTimeout(()=>{w.value=!1}))},T=I=>{let P=120,R=64,G=0;const{image:K,content:H,width:z,height:B,rotate:W}=n;if(!K&&I.measureText){I.font=`${Number(o.value)}px ${s.value}`;const N=Ae(H)?H:[H];let V=0,Y=0;N.forEach(ee=>{const{width:re,fontBoundingBoxAscent:ie,fontBoundingBoxDescent:le,actualBoundingBoxAscent:ve,actualBoundingBoxDescent:he}=I.measureText(ee),J=zt(ie)?ve+he:ie+le;re>V&&(V=Math.ceil(re)),J>Y&&(Y=Math.ceil(J))}),P=V,R=Y*N.length+(N.length-1)*a.value;const X=Math.PI/180*Number(W);G=Math.ceil(Math.abs(Math.sin(X)*R)/2),P+=G}return[z??P,B??R,G]},M=d3e(),E=()=>{const I=document.createElement("canvas").getContext("2d"),P=n.image,R=n.content,G=n.rotate;if(I){_.value||(_.value=document.createElement("div"));const K=s3e(),[H,z,B]=T(I),W=N=>{const[V,Y]=M(N||"",G,K,H,z,{color:r.value,fontSize:o.value,fontStyle:l.value,fontWeight:i.value,fontFamily:s.value,fontGap:a.value,textAlign:u.value,textBaseline:c.value},d.value,v.value,B);k(V,Y)};if(P){const N=new Image;N.onload=()=>{W(N)},N.onerror=()=>{W(R)},N.crossOrigin="anonymous",N.referrerPolicy="no-referrer",N.src=P}else W(R)}};return mt(()=>{E()}),ye(()=>n,()=>{E()},{deep:!0,flush:"post"}),rn(()=>{S()}),Zh(b,I=>{w.value||I.forEach(P=>{u3e(P,_.value)&&(S(),E())})},{attributes:!0,subtree:!0,childList:!0}),(I,P)=>(x(),O("div",{ref_key:"containerRef",ref:b,style:et([t])},[ce(I.$slots,"default")],4))}}),v3e=f3e;const h3e=wt(v3e),p3e=["absolute","fixed"],m3e=["top-start","top-end","top","bottom-start","bottom-end","bottom","left-start","left-end","left","right-start","right-end","right"],Kw=$e({placement:{type:ae(String),values:m3e,default:"bottom"},reference:{type:ae(Object),default:null},strategy:{type:ae(String),values:p3e,default:"absolute"},offset:{type:Number,default:10},showArrow:Boolean,zIndex:{type:Number,default:2001}}),g3e={close:()=>!0},y3e=$e({modelValue:Boolean,current:{type:Number},showArrow:{type:Boolean,default:!0},showClose:{type:Boolean,default:!0},closeIcon:{type:pn},placement:Kw.placement,contentStyle:{type:ae([Object])},mask:{type:ae([Boolean,Object]),default:!0},gap:{type:ae(Object),default:()=>({offset:6,radius:2})},zIndex:{type:Number},scrollIntoViewOptions:{type:ae([Boolean,Object]),default:()=>({block:"center"})},type:{type:ae(String)},appendTo:{type:ae([String,Object]),default:"body"},closeOnPressEscape:{type:Boolean,default:!0},targetAreaClickable:{type:Boolean,default:!0}}),b3e={[bt]:e=>vn(e),"update:current":e=>Je(e),close:e=>Je(e),finish:()=>!0,change:e=>Je(e)},_3e=$e({target:{type:ae([String,Object,Function])},title:String,description:String,showClose:{type:Boolean,default:void 0},closeIcon:{type:pn},showArrow:{type:Boolean,default:void 0},placement:Kw.placement,mask:{type:ae([Boolean,Object]),default:void 0},contentStyle:{type:ae([Object])},prevButtonProps:{type:ae(Object)},nextButtonProps:{type:ae(Object)},scrollIntoViewOptions:{type:ae([Boolean,Object]),default:void 0},type:{type:ae(String)}}),w3e={close:()=>!0},S3e=(e,t,n,a,r)=>{const o=F(null),i=()=>{let c;return Ye(e.value)?c=document.querySelector(e.value):Qe(e.value)?c=e.value():c=e.value,c},l=()=>{const c=i();if(!c||!t.value){o.value=null;return}C3e(c)||c.scrollIntoView(r.value);const{left:d,top:v,width:f,height:h}=c.getBoundingClientRect();o.value={left:d,top:v,width:f,height:h,radius:0}};mt(()=>{ye([t,e],()=>{l()},{immediate:!0}),window.addEventListener("resize",l)}),rn(()=>{window.removeEventListener("resize",l)});const s=c=>(Ae(n.value.offset)?n.value.offset[c]:n.value.offset)??6,u=D(()=>{var f;if(!o.value)return o.value;const c=s(0),d=s(1),v=((f=n.value)==null?void 0:f.radius)||2;return{left:o.value.left-c,top:o.value.top-d,width:o.value.width+c*2,height:o.value.height+d*2,radius:v}});return{mergedPosInfo:u,triggerTarget:D(()=>{const c=i();return!a.value||!c||!window.DOMRect?c||void 0:{getBoundingClientRect(){var d,v,f,h;return window.DOMRect.fromRect({width:((d=u.value)==null?void 0:d.width)||0,height:((v=u.value)==null?void 0:v.height)||0,x:((f=u.value)==null?void 0:f.left)||0,y:((h=u.value)==null?void 0:h.top)||0})}}})}},q1=Symbol("ElTour");function C3e(e){const t=window.innerWidth||document.documentElement.clientWidth,n=window.innerHeight||document.documentElement.clientHeight,{top:a,right:r,bottom:o,left:i}=e.getBoundingClientRect();return a>=0&&i>=0&&r<=t&&o<=n}const x3e=(e,t,n,a,r,o,i,l)=>{const s=F(),u=F(),c=F({}),d={x:s,y:u,placement:a,strategy:r,middlewareData:c},v=D(()=>{const y=[Yq(p(o)),qq(),jq(),k3e()];return p(l)&&p(n)&&y.push(Xq({element:p(n)})),y}),f=async()=>{if(!qt)return;const y=p(e),b=p(t);if(!y||!b)return;const _=await Zq(y,b,{placement:p(a),strategy:p(r),middleware:p(v)});sh(d).forEach(w=>{d[w].value=_[w]})},h=D(()=>{if(!p(e))return{position:"fixed",top:"50%",left:"50%",transform:"translate3d(-50%, -50%, 0)",maxWidth:"100vw",zIndex:p(i)};const{overflow:y}=p(c);return{position:p(r),zIndex:p(i),top:p(u)!=null?`${p(u)}px`:"",left:p(s)!=null?`${p(s)}px`:"",maxWidth:y!=null&&y.maxWidth?`${y==null?void 0:y.maxWidth}px`:""}}),m=D(()=>{if(!p(l))return{};const{arrow:y}=p(c);return{left:(y==null?void 0:y.x)!=null?`${y==null?void 0:y.x}px`:"",top:(y==null?void 0:y.y)!=null?`${y==null?void 0:y.y}px`:""}});let g;return mt(()=>{const y=p(e),b=p(t);y&&b&&(g=Uq(y,b,f)),ur(()=>{f()})}),rn(()=>{g&&g()}),{update:f,contentStyle:h,arrowStyle:m}},k3e=()=>({name:"overflow",async fn(e){const t=await Gq(e);let n=0;return t.left>0&&(n=t.left),t.right>0&&(n=t.right),{data:{maxWidth:e.rects.floating.width-n}}}}),T3e=$e({zIndex:{type:Number,default:1001},visible:Boolean,fill:{type:String,default:"rgba(0,0,0,0.5)"},pos:{type:ae(Object)},targetAreaClickable:{type:Boolean,default:!0}}),M3e={style:{width:"100%",height:"100%"}},E3e=["d"];var A3e=q({name:"ElTourMask",inheritAttrs:!1,__name:"mask",props:T3e,setup(e){const t=e,{ns:n}=Ze(q1),a=D(()=>{var c;return((c=t.pos)==null?void 0:c.radius)??2}),r=D(()=>{const c=a.value,d=`a${c},${c} 0 0 1`;return{topRight:`${d} ${c},${c}`,bottomRight:`${d} ${-c},${c}`,bottomLeft:`${d} ${-c},${-c}`,topLeft:`${d} ${c},${-c}`}}),{width:o,height:i}=_6(),l=D(()=>{const c=o.value,d=i.value,v=r.value,f=`M${c},0 L0,0 L0,${d} L${c},${d} L${c},0 Z`,h=a.value;return t.pos?`${f} M${t.pos.left+h},${t.pos.top} h${t.pos.width-h*2} ${v.topRight} v${t.pos.height-h*2} ${v.bottomRight} h${-t.pos.width+h*2} ${v.bottomLeft} v${-t.pos.height+h*2} ${v.topLeft} z`:f}),s=D(()=>({position:"fixed",left:0,right:0,top:0,bottom:0,zIndex:t.zIndex,pointerEvents:t.pos&&t.targetAreaClickable?"none":"auto"})),u=D(()=>({fill:t.fill,pointerEvents:"auto",cursor:"auto"}));return D1(cn(t,"visible"),{ns:n}),(c,d)=>e.visible?(x(),O("div",Tt({key:0,class:p(n).e("mask"),style:s.value},c.$attrs),[(x(),O("svg",M3e,[C("path",{class:U(p(n).e("hollow")),style:et(u.value),d:l.value},null,14,E3e)]))],16)):te("v-if",!0)}}),I3e=A3e;const L3e=["data-side"];var P3e=q({name:"ElTourContent",__name:"content",props:Kw,emits:g3e,setup(e,{emit:t}){const n=e,a=t,r=F(n.placement),o=F(n.strategy),i=F(null),l=F(null);ye(()=>n.placement,()=>{r.value=n.placement});const{contentStyle:s,arrowStyle:u}=x3e(cn(n,"reference"),i,l,r,o,cn(n,"offset"),cn(n,"zIndex"),cn(n,"showArrow")),c=D(()=>r.value.split("-")[0]),{ns:d}=Ze(q1),v=()=>{a("close")},f=h=>{h.detail.focusReason==="pointer"&&h.preventDefault()};return(h,m)=>(x(),O("div",{ref_key:"contentRef",ref:i,style:et(p(s)),class:U(p(d).e("content")),"data-side":c.value,tabindex:"-1"},[L(p(pf),{loop:"",trapped:"","focus-start-el":"container","focus-trap-el":i.value||void 0,onReleaseRequested:v,onFocusoutPrevented:f},{default:$(()=>[ce(h.$slots,"default")]),_:3},8,["focus-trap-el"]),e.showArrow?(x(),O("span",{key:0,ref_key:"arrowRef",ref:l,style:et(p(u)),class:U(p(d).e("arrow"))},null,6)):te("v-if",!0)],14,L3e))}}),D3e=P3e,O3e=q({name:"ElTourSteps",props:{current:{type:Number,default:0}},emits:["update-total"],setup(e,{slots:t,emit:n}){let a=0;return()=>{var s,u;const r=(s=t.default)==null?void 0:s.call(t),o=[];let i=0;function l(c){Ae(c)&&c.forEach(d=>{var v;((v=(d==null?void 0:d.type)||{})==null?void 0:v.name)==="ElTourStep"&&(o.push(d),i+=1)})}return r.length&&l(eo((u=r[0])==null?void 0:u.children)),a!==i&&(a=i,n("update-total",i)),o.length?o[e.current]:null}}}),$3e=q({name:"ElTour",inheritAttrs:!1,__name:"tour",props:y3e,emits:b3e,setup(e,{emit:t}){const n=e,a=t,r=ke("tour"),o=F(0),i=F(),l=D(()=>!zt(n.current)),s=F(n.current??0),u=D({get(){return zt(n.current)?s.value:n.current},set(P){(l.value?n.current:s.value)!==P&&(l.value||(s.value=P),a("update:current",P))}}),c=D(()=>{var P;return(P=i.value)==null?void 0:P.target}),d=D(()=>[r.b(),_.value==="primary"?r.m("primary"):""]),v=D(()=>{var P;return((P=i.value)==null?void 0:P.placement)||n.placement}),f=D(()=>{var P;return((P=i.value)==null?void 0:P.contentStyle)??n.contentStyle}),h=D(()=>{var P;return((P=i.value)==null?void 0:P.mask)??n.mask}),m=D(()=>!!h.value&&n.modelValue),g=D(()=>vn(h.value)?void 0:h.value),y=D(()=>{var P;return!!c.value&&(((P=i.value)==null?void 0:P.showArrow)??n.showArrow)}),b=D(()=>{var P;return((P=i.value)==null?void 0:P.scrollIntoViewOptions)??n.scrollIntoViewOptions}),_=D(()=>{var P;return((P=i.value)==null?void 0:P.type)??n.type}),{nextZIndex:w}=tp(),S=w(),k=D(()=>n.zIndex??S),{mergedPosInfo:T,triggerTarget:M}=S3e(c,cn(n,"modelValue"),cn(n,"gap"),h,b);ye(()=>n.current,P=>!zt(P)&&(s.value=P)),ye(u,(P,R)=>{!n.modelValue||P===R||a(Ot,P)},{flush:"post"}),ye(()=>n.modelValue,P=>{!P&&u.value!==0&&(u.value=0)});const E=()=>{n.closeOnPressEscape&&(a(bt,!1),a("close",u.value))},A=P=>{o.value=P},I=Xn();return Ft(q1,{currentStep:i,current:u,total:o,showClose:cn(n,"showClose"),closeIcon:cn(n,"closeIcon"),mergedType:_,ns:r,slots:I,updateModelValue(P){a(bt,P)},onClose(){a("close",u.value)},onFinish(){a("finish")}}),(P,R)=>{var G,K;return x(),O(Ve,null,[(x(),ne(sf,{to:e.appendTo},[C("div",Tt({class:d.value},P.$attrs),[L(I3e,{visible:m.value,fill:(G=g.value)==null?void 0:G.color,style:et((K=g.value)==null?void 0:K.style),pos:p(T),"z-index":k.value,"target-area-clickable":e.targetAreaClickable},null,8,["visible","fill","style","pos","z-index","target-area-clickable"]),e.modelValue?(x(),ne(D3e,{key:u.value,reference:p(M),placement:v.value,"show-arrow":y.value,"z-index":k.value,style:et(f.value),onClose:E},{default:$(()=>[L(p(O3e),{current:u.value,onUpdateTotal:A},{default:$(()=>[ce(P.$slots,"default")]),_:3},8,["current"])]),_:3},8,["reference","placement","show-arrow","z-index","style"])):te("v-if",!0)],16)],8,["to"])),te(" just for IDE "),te("v-if",!0)],64)}}}),R3e=$3e;const N3e=["aria-label"];var B3e=q({name:"ElTourStep",__name:"step",props:_3e,emits:w3e,setup(e,{emit:t}){const n=e,a=t,{Close:r}=YI,{t:o}=Vt(),{currentStep:i,current:l,total:s,showClose:u,closeIcon:c,mergedType:d,ns:v,slots:f,updateModelValue:h,onClose:m,onFinish:g}=Ze(q1);ye(n,E=>{i.value=E},{immediate:!0});const y=D(()=>n.showClose??u.value),b=D(()=>n.closeIcon??c.value??r),_=E=>{if(E)return Tc(E,["children","onClick"])},w=()=>{var E,A;l.value-=1,(E=n.prevButtonProps)!=null&&E.onClick&&((A=n.prevButtonProps)==null||A.onClick())},S=()=>{var E;l.value>=s.value-1?k():l.value+=1,(E=n.nextButtonProps)!=null&&E.onClick&&n.nextButtonProps.onClick()},k=()=>{T(),g()},T=()=>{h(!1),m(),a("close")},M=E=>{var A;if(!((A=E.target)!=null&&A.isContentEditable))switch(mn(E)){case Pe.left:E.preventDefault(),l.value>0&&w();break;case Pe.right:E.preventDefault(),S();break}};return mt(()=>{window.addEventListener("keydown",M)}),rn(()=>{window.removeEventListener("keydown",M)}),(E,A)=>(x(),O(Ve,null,[y.value?(x(),O("button",{key:0,"aria-label":p(o)("el.tour.close"),class:U(p(v).e("closebtn")),type:"button",onClick:T},[L(p(Xe),{class:U(p(v).e("close"))},{default:$(()=>[(x(),ne(St(b.value)))]),_:1},8,["class"])],10,N3e)):te("v-if",!0),C("header",{class:U([p(v).e("header"),{"show-close":p(u)}])},[ce(E.$slots,"header",{},()=>[C("span",{role:"heading",class:U(p(v).e("title"))},Q(e.title),3)])],2),C("div",{class:U(p(v).e("body"))},[ce(E.$slots,"default",{},()=>[C("span",null,Q(e.description),1)])],2),C("footer",{class:U(p(v).e("footer"))},[C("div",{class:U(p(v).b("indicators"))},[p(f).indicators?(x(),ne(St(p(f).indicators),{key:0,current:p(l),total:p(s)},null,8,["current","total"])):(x(!0),O(Ve,{key:1},dt(p(s),(I,P)=>(x(),O("span",{key:I,class:U([p(v).b("indicator"),p(v).is("active",P===p(l))])},null,2))),128))],2),C("div",{class:U(p(v).b("buttons"))},[p(l)>0?(x(),ne(p(xa),Tt({key:0,size:"small",type:p(d)},_(e.prevButtonProps),{onClick:w}),{default:$(()=>{var I;return[ue(Q(((I=e.prevButtonProps)==null?void 0:I.children)??p(o)("el.tour.previous")),1)]}),_:1},16,["type"])):te("v-if",!0),p(l)<=p(s)-1?(x(),ne(p(xa),Tt({key:1,size:"small",type:p(d)==="primary"?"default":"primary"},_(e.nextButtonProps),{onClick:S}),{default:$(()=>{var I;return[ue(Q(((I=e.nextButtonProps)==null?void 0:I.children)??(p(l)===p(s)-1?p(o)("el.tour.finish"):p(o)("el.tour.next"))),1)]}),_:1},16,["type"])):te("v-if",!0)],2)],2)],64))}}),hO=B3e;const V3e=wt(R3e,{TourStep:hO}),z3e=On(hO),F3e=$e({container:{type:ae([String,Object])},offset:{type:Number,default:0},bound:{type:Number,default:15},duration:{type:Number,default:300},marker:{type:Boolean,default:!0},type:{type:ae(String),default:"default"},direction:{type:ae(String),default:"vertical"},selectScrollTop:Boolean}),H3e={change:e=>Ye(e),click:(e,t)=>e instanceof MouseEvent&&(Ye(t)||zt(t))},qp=e=>{if(!qt||e==="")return null;if(Ye(e))try{return document.querySelector(e)}catch{return null}return e};function W3e(e){let t=0;const n=(...a)=>{t&&ti(t),t=Wr(()=>{e(...a),t=0})};return n.cancel=()=>{ti(t),t=0},n}const pO=Symbol("anchor");var K3e=q({name:"ElAnchor",__name:"anchor",props:F3e,emits:H3e,setup(e,{expose:t,emit:n}){const a=e,r=n,o=Xn(),i=F(""),l=F({}),s=F(null),u=F(null),c=F(),d={};let v=!1,f=0;const h=ke("anchor"),m=D(()=>[h.b(),a.type==="underline"?h.m("underline"):"",h.m(a.direction)]),g=P=>{d[P.href]=P.el},y=P=>{delete d[P]},b=P=>{i.value!==P&&(i.value=P,r(Ot,P))};let _=null,w="";const S=P=>{if(!c.value)return;const R=qp(P);if(!R)return;if(_){if(w===P)return;_()}w=P,v=!0;const G=UC(R,c.value),K=q4(R,G),H=G.scrollHeight-G.clientHeight,z=Math.min(K-a.offset,H);_=UY(c.value,f,z,a.duration,()=>{setTimeout(()=>{v=!1,w=""},20)})},k=P=>{P&&(b(P),S(P))},T=(P,R)=>{r("click",P,R),k(R)},M=W3e(()=>{c.value&&(f=GC(c.value));const P=E();v||zt(P)||b(P)}),E=()=>{if(!c.value)return;const P=GC(c.value),R=[];for(const G of Object.keys(d)){const K=qp(G);if(!K)continue;const H=q4(K,UC(K,c.value));R.push({top:H-a.offset-a.bound,href:G})}R.sort((G,K)=>G.top-K.top);for(let G=0;GP))return K.href}},A=()=>{const P=qp(a.container);!P||qh(P)?c.value=window:c.value=P};on(c,"scroll",M);const I=()=>{He(()=>{if(!s.value||!u.value||!i.value){l.value={};return}const P=d[i.value];if(!P){l.value={};return}const R=s.value.getBoundingClientRect(),G=u.value.getBoundingClientRect(),K=P.getBoundingClientRect();a.direction==="horizontal"?l.value={left:`${K.left-R.left}px`,width:`${K.width}px`,opacity:1}:l.value={top:`${K.top-R.top+(K.height-G.height)/2}px`,opacity:1}})};return ye(i,I),ye(()=>{var P;return(P=o.default)==null?void 0:P.call(o)},I),mt(()=>{A();const P=decodeURIComponent(window.location.hash);qp(P)?k(P):M()}),ye(()=>a.container,()=>{A()}),Ft(pO,{ns:h,direction:a.direction,currentAnchor:i,addLink:g,removeLink:y,handleClick:T}),t({scrollTo:k}),(P,R)=>(x(),O("div",{ref_key:"anchorRef",ref:s,class:U(m.value)},[e.marker?(x(),O("div",{key:0,ref_key:"markerRef",ref:u,class:U(p(h).e("marker")),style:et(l.value)},null,6)):te("v-if",!0),C("div",{class:U(p(h).e("list"))},[ce(P.$slots,"default")],2)],2))}}),U3e=K3e;const G3e=$e({title:String,href:String}),Y3e=["href"];var j3e=q({name:"ElAnchorLink",__name:"anchor-link",props:G3e,setup(e){const t=e,n=F(null),{ns:a,direction:r,currentAnchor:o,addLink:i,removeLink:l,handleClick:s}=Ze(pO),u=D(()=>[a.e("link"),a.is("active",o.value===t.href)]),c=d=>{s(d,t.href)};return ye(()=>t.href,(d,v)=>{He(()=>{v&&l(v),d&&i({href:d,el:n.value})})}),mt(()=>{const{href:d}=t;d&&i({href:d,el:n.value})}),rn(()=>{const{href:d}=t;d&&l(d)}),(d,v)=>(x(),O("div",{class:U(p(a).e("item"))},[C("a",{ref_key:"linkRef",ref:n,class:U(u.value),href:e.href,onClick:c},[ce(d.$slots,"default",{},()=>[ue(Q(e.title),1)])],10,Y3e),d.$slots["sub-link"]&&p(r)==="vertical"?(x(),O("div",{key:0,class:U(p(a).e("list"))},[ce(d.$slots,"sub-link")],2)):te("v-if",!0)],2))}}),mO=j3e;const q3e=wt(U3e,{AnchorLink:mO}),X3e=On(mO),gO={label:"label",value:"value",disabled:"disabled"},Z3e=$e({direction:{type:ae(String),default:"horizontal"},options:{type:ae(Array),default:()=>[]},modelValue:{type:[String,Number,Boolean],default:void 0},props:{type:ae(Object),default:()=>gO},block:Boolean,size:pa,disabled:{type:Boolean,default:void 0},validateEvent:{type:Boolean,default:!0},id:String,name:String,...dr(["ariaLabel"])}),J3e={[bt]:e=>Ye(e)||Je(e)||vn(e),[Ot]:e=>Ye(e)||Je(e)||vn(e)},Q3e=["id","aria-label","aria-labelledby"],e6e=["name","disabled","checked","onChange"];var t6e=q({name:"ElSegmented",__name:"segmented",props:Z3e,emits:J3e,setup(e,{emit:t}){const n=e,a=t,r=ke("segmented"),o=Fa(),i=ca(),l=$n(),{formItem:s}=Ea(),{inputId:u,isLabeledByFormItem:c}=Yr(n,{formItemContext:s}),d=F(null),v=kY(),f=nn({isInit:!1,width:0,height:0,translateX:0,translateY:0,focusVisible:!1}),h=(I,P)=>{const R=g(P);a(bt,R),a(Ot,R),I.target.checked=R===n.modelValue},m=D(()=>({...gO,...n.props})),g=I=>_t(I)?I[m.value.value]:I,y=I=>_t(I)?I[m.value.label]:I,b=I=>!!(l.value||_t(I)&&I[m.value.disabled]),_=I=>n.modelValue===g(I),w=I=>n.options.find(P=>g(P)===I),S=I=>[r.e("item"),r.is("selected",_(I)),r.is("disabled",b(I))],k=()=>{if(!d.value)return;const I=d.value.querySelector(".is-selected"),P=d.value.querySelector(".is-selected input");if(!I||!P){f.width=0,f.height=0,f.translateX=0,f.translateY=0,f.focusVisible=!1;return}f.isInit=!0,n.direction==="vertical"?(f.height=I.offsetHeight,f.translateY=I.offsetTop):(f.width=I.offsetWidth,f.translateX=I.offsetLeft);try{f.focusVisible=P.matches(":focus-visible")}catch{}},T=D(()=>[r.b(),r.m(i.value),r.is("block",n.block)]),M=D(()=>({width:n.direction==="vertical"?"100%":`${f.width}px`,height:n.direction==="vertical"?`${f.height}px`:"100%",transform:n.direction==="vertical"?`translateY(${f.translateY}px)`:`translateX(${f.translateX}px)`,display:f.isInit?"block":"none"})),E=D(()=>[r.e("item-selected"),r.is("disabled",b(w(n.modelValue))),r.is("focus-visible",f.focusVisible)]),A=D(()=>n.name||o.value);return Mn(d,k),ye(v,k),ye(()=>n.options,k,{deep:!0,flush:"post"}),ye(()=>n.modelValue,()=>{var I;k(),n.validateEvent&&((I=s==null?void 0:s.validate)==null||I.call(s,"change").catch(kt))},{flush:"post"}),(I,P)=>e.options.length?(x(),O("div",{key:0,id:p(u),ref_key:"segmentedRef",ref:d,class:U(T.value),role:"radiogroup","aria-label":p(c)?void 0:e.ariaLabel||"segmented","aria-labelledby":p(c)?p(s).labelId:void 0},[C("div",{class:U([p(r).e("group"),p(r).m(e.direction)])},[C("div",{style:et(M.value),class:U(E.value)},null,6),(x(!0),O(Ve,null,dt(e.options,(R,G)=>(x(),O("label",{key:G,class:U(S(R))},[C("input",{class:U(p(r).e("item-input")),type:"radio",name:A.value,disabled:b(R),checked:_(R),onChange:K=>h(K,R)},null,42,e6e),C("div",{class:U(p(r).e("item-label"))},[ce(I.$slots,"default",{item:R},()=>[ue(Q(y(R)),1)])],2)],2))),128))],2)],10,Q3e)):te("v-if",!0)}}),n6e=t6e;const a6e=wt(n6e),r6e=(e,t)=>{const n=e.toLowerCase();return(t.label||t.value||"").toLowerCase().includes(n)},o6e=(e,t,n)=>{const{selectionEnd:a}=e;if(a===null)return;const r=e.value,o=wa(t);let i=-1,l;for(let s=a-1;s>=0;--s){const u=r[s];if(i===-1&&(u===n||u===` +`||u==="\r")){i=s;continue}if(o.includes(u)){const c=i===-1?a:i;l={pattern:r.slice(s+1,c),start:s+1,end:c,prefix:u,prefixIndex:s,splitIndex:i,selectionEnd:a};break}}return l},i6e=(e,t={debug:!1,useSelectionEnd:!1})=>{const n=e.selectionStart!==null?e.selectionStart:0,a=e.selectionEnd!==null?e.selectionEnd:0,r=t.useSelectionEnd?a:n,o=["direction","boxSizing","width","height","overflowX","overflowY","borderTopWidth","borderRightWidth","borderBottomWidth","borderLeftWidth","borderStyle","paddingTop","paddingRight","paddingBottom","paddingLeft","fontStyle","fontVariant","fontWeight","fontStretch","fontSize","fontSizeAdjust","lineHeight","fontFamily","textAlign","textTransform","textIndent","textDecoration","letterSpacing","wordSpacing","tabSize","MozTabSize"];if(t.debug){const v=document.querySelector("#input-textarea-caret-position-mirror-div");v!=null&&v.parentNode&&v.parentNode.removeChild(v)}const i=document.createElement("div");i.id="input-textarea-caret-position-mirror-div",document.body.appendChild(i);const l=i.style,s=window.getComputedStyle(e),u=e.nodeName==="INPUT";l.whiteSpace=u?"nowrap":"pre-wrap",u||(l.wordWrap="break-word"),l.position="absolute",t.debug||(l.visibility="hidden"),o.forEach(v=>{if(u&&v==="lineHeight")if(s.boxSizing==="border-box"){const f=Number.parseInt(s.height),h=Number.parseInt(s.paddingTop)+Number.parseInt(s.paddingBottom)+Number.parseInt(s.borderTopWidth)+Number.parseInt(s.borderBottomWidth),m=h+Number.parseInt(s.lineHeight);f>m?l.lineHeight=`${f-h}px`:f===m?l.lineHeight=s.lineHeight:l.lineHeight="0"}else l.lineHeight=s.height;else l[v]=s[v]}),P1()?e.scrollHeight>Number.parseInt(s.height)&&(l.overflowY="scroll"):l.overflow="hidden",i.textContent=e.value.slice(0,Math.max(0,r)),u&&i.textContent&&(i.textContent=i.textContent.replace(/\s/g," "));const c=document.createElement("span");c.textContent=e.value.slice(Math.max(0,r))||".",c.style.position="relative",c.style.left=`${-e.scrollLeft}px`,c.style.top=`${-e.scrollTop}px`,i.appendChild(c);const d={top:c.offsetTop+Number.parseInt(s.borderTopWidth),left:c.offsetLeft+Number.parseInt(s.borderLeftWidth),height:Number.parseInt(s.fontSize)*1.5};return t.debug?c.style.backgroundColor="#aaa":document.body.removeChild(i),d.left>=e.clientWidth&&(d.left=e.clientWidth),d},l6e=$e({...J6,options:{type:ae(Array),default:()=>[]},prefix:{type:ae([String,Array]),default:"@",validator:e=>Ye(e)?e.length===1:e.every(t=>Ye(t)&&t.length===1)},split:{type:String,default:" ",validator:e=>e.length===1},filterOption:{type:ae([Boolean,Function]),default:()=>r6e,validator:e=>e===!1?!0:Qe(e)},placement:{type:ae(String),default:"bottom"},showArrow:Boolean,offset:{type:Number,default:0},whole:Boolean,checkIsWhole:{type:ae(Function)},modelValue:String,loading:Boolean,popperClass:Tn.popperClass,popperStyle:Tn.popperStyle,popperOptions:{type:ae(Object),default:()=>({})},props:{type:ae(Object),default:()=>yO}}),s6e={[bt]:e=>Ye(e),"whole-remove":(e,t)=>Ye(e)&&Ye(t),input:e=>Ye(e),search:(e,t)=>Ye(e)&&Ye(t),select:(e,t)=>_t(e)&&Ye(t),focus:e=>e instanceof FocusEvent,blur:e=>e instanceof FocusEvent},yO={value:"value",label:"label",disabled:"disabled"},u6e=$e({options:{type:ae(Array),default:()=>[]},loading:Boolean,disabled:Boolean,contentId:String,ariaLabel:String}),c6e={select:e=>Ye(e.value)},d6e=["id","aria-disabled","aria-selected","onMousemove","onClick"];var f6e=q({name:"ElMentionDropdown",__name:"mention-dropdown",props:u6e,emits:c6e,setup(e,{expose:t,emit:n}){const a=e,r=n,o=ke("mention"),{t:i}=Vt(),l=F(-1),s=F(),u=F(),c=F(),d=(w,S)=>[o.be("dropdown","item"),o.is("hovering",l.value===S),o.is("disabled",w.disabled||a.disabled)],v=w=>{w.disabled||a.disabled||r("select",w)},f=w=>{l.value=w},h=D(()=>a.disabled||a.options.every(w=>w.disabled)),m=D(()=>a.options[l.value]),g=()=>{!m.value||m.value.disabled||a.disabled||r("select",m.value)},y=w=>{const{options:S}=a;if(S.length===0||h.value)return;w==="next"?(l.value++,l.value===S.length&&(l.value=0)):w==="prev"&&(l.value--,l.value<0&&(l.value=S.length-1));const k=S[l.value];if(k.disabled){y(w);return}He(()=>b(k))},b=w=>{var M,E,A,I;const{options:S}=a,k=S.findIndex(P=>P.value===w.value),T=(M=u.value)==null?void 0:M[k];if(T){const P=(A=(E=c.value)==null?void 0:E.querySelector)==null?void 0:A.call(E,`.${o.be("dropdown","wrap")}`);P&&k6(P,T)}(I=s.value)==null||I.handleScroll()};return ye(()=>a.options,()=>{h.value||a.options.length===0?l.value=-1:l.value=a.options.findIndex(w=>!w.disabled)},{immediate:!0}),t({hoveringIndex:l,navigateOptions:y,selectHoverOption:g,hoverOption:m}),(w,S)=>(x(),O("div",{ref_key:"dropdownRef",ref:c,class:U(p(o).b("dropdown"))},[w.$slots.header?(x(),O("div",{key:0,class:U(p(o).be("dropdown","header"))},[ce(w.$slots,"header")],2)):te("v-if",!0),st(L(p(ni),{id:e.contentId,ref_key:"scrollbarRef",ref:s,tag:"ul","wrap-class":p(o).be("dropdown","wrap"),"view-class":p(o).be("dropdown","list"),role:"listbox","aria-label":e.ariaLabel,"aria-orientation":"vertical"},{default:$(()=>[(x(!0),O(Ve,null,dt(e.options,(k,T)=>(x(),O("li",{id:`${e.contentId}-${T}`,ref_for:!0,ref_key:"optionRefs",ref:u,key:T,class:U(d(k,T)),role:"option","aria-disabled":k.disabled||e.disabled||void 0,"aria-selected":l.value===T,onMousemove:M=>f(T),onClick:at(M=>v(k),["stop"])},[ce(w.$slots,"label",{item:k,index:T},()=>[C("span",null,Q(k.label??k.value),1)])],42,d6e))),128))]),_:3},8,["id","wrap-class","view-class","aria-label"]),[[Zt,e.options.length>0&&!e.loading]]),e.loading?(x(),O("div",{key:1,class:U(p(o).be("dropdown","loading"))},[ce(w.$slots,"loading",{},()=>[ue(Q(p(i)("el.mention.loading")),1)])],2)):te("v-if",!0),w.$slots.footer?(x(),O("div",{key:2,class:U(p(o).be("dropdown","footer"))},[ce(w.$slots,"footer")],2)):te("v-if",!0)],2))}}),v6e=f6e,h6e=q({name:"ElMention",inheritAttrs:!1,__name:"mention",props:l6e,emits:s6e,setup(e,{expose:t,emit:n}){const a=e,r=n,o=D(()=>{const z=Ba.props??[];return ji(a,Ae(z)?z:Object.keys(z))}),i=ke("mention"),l=$n(),s=Fa(),u=F(),c=F(),d=F(),v=F(!1),f=F(),h=F(),m=D(()=>a.showArrow?a.placement:`${a.placement}-start`),g=D(()=>a.showArrow?["bottom","top"]:["bottom-start","top-start"]),y=D(()=>({...yO,...a.props})),b=z=>{const B={label:z[y.value.label],value:z[y.value.value],disabled:z[y.value.disabled]};return{...z,...B}},_=D(()=>a.options.map(b)),w=D(()=>{const{filterOption:z}=a;return!h.value||!z?_.value:_.value.filter(B=>z(h.value.pattern,B))}),S=D(()=>v.value&&(!!w.value.length||a.loading)),k=D(()=>{var z;return`${s.value}-${(z=d.value)==null?void 0:z.hoveringIndex}`}),T=z=>{r(bt,z),r(ua,z),G()},M=z=>{var W,N,V,Y;if((W=u.value)!=null&&W.isComposing)return;const B=mn(z);switch(B){case Pe.left:case Pe.right:G();break;case Pe.up:case Pe.down:if(!v.value)return;z.preventDefault(),(N=d.value)==null||N.navigateOptions(B===Pe.up?"prev":"next");break;case Pe.enter:case Pe.numpadEnter:if(!v.value){a.type!=="textarea"&&G();return}z.preventDefault(),(V=d.value)!=null&&V.hoverOption?(Y=d.value)==null||Y.selectHoverOption():v.value=!1;break;case Pe.esc:if(!v.value)return;z.preventDefault(),v.value=!1;break;case Pe.backspace:if(a.whole&&h.value){const{splitIndex:X,selectionEnd:ee,pattern:re,prefixIndex:ie,prefix:le}=h.value,ve=R();if(!ve)return;const he=ve.value,J=_.value.find(Z=>Z.value===re);if((Qe(a.checkIsWhole)?a.checkIsWhole(re,le):J)&&X!==-1&&X+1===ee){z.preventDefault();const Z=he.slice(0,ie)+he.slice(X+1);r(bt,Z),r(ua,Z),r("whole-remove",re,le);const j=ie;He(()=>{ve.selectionStart=j,ve.selectionEnd=j,H()})}}}},{wrapperRef:E}=Fl(u,{disabled:l,afterFocus(){G()},beforeBlur(z){var B;return(B=c.value)==null?void 0:B.isFocusInsideContent(z)},afterBlur(){v.value=!1}}),A=()=>{G()},I=z=>a.options.find(B=>z.value===B[y.value.value]),P=z=>{if(!h.value)return;const B=R();if(!B)return;const W=B.value,{split:N}=a,V=W.slice(h.value.end),Y=V.startsWith(N),X=`${z.value}${Y?"":N}`,ee=W.slice(0,h.value.start)+X+V;r(bt,ee),r(ua,ee),r("select",I(z),h.value.prefix);const re=h.value.start+X.length+(Y?1:0);He(()=>{B.selectionStart=re,B.selectionEnd=re,B.focus(),H()})},R=()=>{var z,B;return a.type==="textarea"?(z=u.value)==null?void 0:z.textarea:(B=u.value)==null?void 0:B.input},G=()=>{setTimeout(()=>{K(),H(),He(()=>{var z;return(z=c.value)==null?void 0:z.updatePopper()})},0)},K=()=>{const z=R();if(!z)return;const B=i6e(z),W=z.getBoundingClientRect(),N=E.value.getBoundingClientRect();f.value={position:"absolute",width:0,height:`${B.height}px`,left:`${B.left+W.left-N.left}px`,top:`${B.top+W.top-N.top}px`}},H=()=>{const z=R();if(document.activeElement!==z){v.value=!1;return}const{prefix:B,split:W}=a;if(h.value=o6e(z,B,W),h.value&&h.value.splitIndex===-1){v.value=!0,r("search",h.value.pattern,h.value.prefix);return}v.value=!1};return t({input:u,tooltip:c,dropdownVisible:S}),(z,B)=>(x(),O("div",{ref_key:"wrapperRef",ref:E,class:U(p(i).b())},[L(p(Ba),Tt(Tt(o.value,z.$attrs),{ref_key:"elInputRef",ref:u,"model-value":e.modelValue,disabled:p(l),role:S.value?"combobox":void 0,"aria-activedescendant":S.value?k.value||"":void 0,"aria-controls":S.value?p(s):void 0,"aria-expanded":S.value||void 0,"aria-label":e.ariaLabel,"aria-autocomplete":S.value?"none":void 0,"aria-haspopup":S.value?"listbox":void 0,onInput:T,onKeydown:M,onMousedown:A}),xr({_:2},[dt(z.$slots,(W,N)=>({name:N,fn:$(V=>[ce(z.$slots,N,bl(wl(V)))])}))]),1040,["model-value","disabled","role","aria-activedescendant","aria-controls","aria-expanded","aria-label","aria-autocomplete","aria-haspopup"]),L(p(Ma),{ref_key:"tooltipRef",ref:c,visible:S.value,"popper-class":[p(i).e("popper"),e.popperClass],"popper-style":e.popperStyle,"popper-options":e.popperOptions,placement:m.value,"fallback-placements":g.value,effect:"light",pure:"",offset:e.offset,"show-arrow":e.showArrow},{default:$(()=>[C("div",{style:et(f.value)},null,4)]),content:$(()=>[L(v6e,{ref_key:"dropdownRef",ref:d,options:w.value,disabled:p(l),loading:e.loading,"content-id":p(s),"aria-label":e.ariaLabel,onSelect:P,onClick:B[0]||(B[0]=at(W=>{var N;return(N=u.value)==null?void 0:N.focus()},["stop"]))},xr({_:2},[dt(z.$slots,(W,N)=>({name:N,fn:$(V=>[ce(z.$slots,N,bl(wl(V)))])}))]),1032,["options","disabled","loading","content-id","aria-label"])]),_:3},8,["visible","popper-class","popper-style","popper-options","placement","fallback-placements","offset","show-arrow"])],2))}}),p6e=h6e;const m6e=wt(p6e),g6e=$e({layout:{type:String,default:"horizontal",values:["horizontal","vertical"]},lazy:Boolean}),y6e={resizeStart:(e,t)=>!0,resize:(e,t)=>!0,resizeEnd:(e,t)=>!0,collapse:(e,t,n)=>!0},b6e=$e({min:{type:[String,Number]},max:{type:[String,Number]},size:{type:[String,Number]},resizable:{type:Boolean,default:!0},collapsible:Boolean}),_6e={"update:size":e=>typeof e=="number"||typeof e=="string"};function w6e(e){const t=F(),{width:n,height:a}=Um(t);return{containerEl:t,containerSize:D(()=>e.value==="horizontal"?n.value:a.value)}}function Uw(e){return Number(e.slice(0,-1))/100}function Gw(e){return Number(e.slice(0,-2))}function Yw(e){return Ye(e)&&e.endsWith("%")}function jw(e){return Ye(e)&&e.endsWith("px")}function S6e(e,t){const n=D(()=>e.value.map(i=>i.size)),a=D(()=>e.value.length),r=F([]);ye([n,a,t],()=>{var u;let i=[],l=0;for(let c=0;cc+(d||0),0);if(s>1||!l){const c=1/s;i=i.map(d=>d===void 0?0:d*c)}else{const c=(1-s)/l;i=i.map(d=>d===void 0?c:d)}r.value=i});const o=i=>i*t.value;return{percentSizes:r,pxSizes:D(()=>r.value.map(o))}}function C6e(e,t,n,a){const r=g=>g*t.value||0;function o(g,y){return Yw(g)?r(Uw(g)):jw(g)?Gw(g):g??y}const i=F(0),l=F(null);let s=[],u=kt;const c=D(()=>e.value.map(g=>[g.min,g.max]));ye(a,()=>{if(i.value){const g=new MouseEvent("mouseup",{bubbles:!0});window.dispatchEvent(g)}});const d=g=>{i.value=0,l.value={index:g,confirmed:!1},s=n.value},v=(g,y)=>{var I;let b=null;if((!l.value||!l.value.confirmed)&&y!==0){if(y>0)b=g,l.value={index:g,confirmed:!0};else for(let P=g;P>=0;P-=1)if(s[P]>0){b=P,l.value={index:P,confirmed:!0};break}}const _=b??((I=l.value)==null?void 0:I.index)??g,w=[...s],S=_+1,k=o(c.value[_][0],0),T=o(c.value[S][0],0),M=o(c.value[_][1],t.value||0),E=o(c.value[S][1],t.value||0);let A=y;w[_]+AM&&(A=M-w[_]),w[S]-A>E&&(A=w[S]-E),w[_]+=A,w[S]-=A,i.value=A,u=()=>{e.value.forEach((P,R)=>{P.size=w[R]}),u=kt},a.value||u()},f=()=>{a.value&&u(),i.value=0,l.value=null,s=[]},h=[];return{lazyOffset:i,onMoveStart:d,onMoving:v,onMoveEnd:f,movingIndex:l,onCollapse:(g,y)=>{h.length||h.push(...n.value.map((T,M)=>{var E;return T<=0?o((E=c.value[M])==null?void 0:E[0],0):T}));const b=n.value,_=y==="start"?g:g+1,w=y==="start"?g+1:g,S=b[_],k=b[w];if(S!==0&&k!==0)b[_]=0,b[w]+=S,h[g]=S;else{const T=S+k,M=Br(h[g],0,T),E=T-M;b[w]=M,b[_]=E}e.value.forEach((T,M)=>{T.size=b[M]})}}}const bO=Symbol("splitterRootContextKey");var x6e=q({name:"ElSplitter",__name:"splitter",props:g6e,emits:y6e,setup(e,{emit:t}){const n=ke("splitter"),a=t,r=e,o=cn(r,"layout"),i=cn(r,"lazy"),{containerEl:l,containerSize:s}=w6e(o),{removeChild:u,children:c,addChild:d,ChildrenSorter:v}=z1(Et(),"ElSplitterPanel");ye(c,()=>{g.value=null,c.value.forEach((A,I)=>{A.setIndex(I)})});const{percentSizes:f,pxSizes:h}=S6e(c,s),{lazyOffset:m,movingIndex:g,onMoveStart:y,onMoving:b,onMoveEnd:_,onCollapse:w}=C6e(c,s,h,i),S=D(()=>({[n.cssVarBlockName("bar-offset")]:i.value?`${m.value}px`:void 0}));return Ft(bO,nn({panels:c,percentSizes:f,pxSizes:h,layout:o,lazy:i,movingIndex:g,containerSize:s,onMoveStart:A=>{y(A),a("resizeStart",A,h.value)},onMoving:(A,I)=>{b(A,I),i.value||a("resize",A,h.value)},onMoveEnd:async A=>{_(),await He(),a("resizeEnd",A,h.value)},onCollapse:(A,I)=>{w(A,I),a("collapse",A,I,h.value)},registerPanel:d,unregisterPanel:u})),(A,I)=>(x(),O("div",{ref_key:"containerEl",ref:l,class:U([p(n).b(),p(n).e(o.value)]),style:et(S.value)},[ce(A.$slots,"default"),L(p(v)),te(" Prevent iframe touch events from breaking "),p(g)?(x(),O("div",{key:0,class:U([p(n).e("mask"),p(n).e(`mask-${o.value}`)])},null,2)):te("v-if",!0)],6))}}),k6e=x6e;function T6e(e){return e&&_t(e)?e:{start:!!e,end:!!e}}function nk(e,t,n,a){return!!(e!=null&&e.collapsible.end&&t>0||n!=null&&n.collapsible.start&&a===0&&t>0)}var M6e=q({name:"ElSplitterBar",__name:"split-bar",props:{index:{type:Number,required:!0},layout:{type:String,values:["horizontal","vertical"],default:"horizontal"},resizable:{type:Boolean,default:!0},lazy:Boolean,startCollapsible:Boolean,endCollapsible:Boolean},emits:["moveStart","moving","moveEnd","collapse"],setup(e,{emit:t}){const n=ke("splitter-bar"),a=e,r=t,o=D(()=>a.layout==="horizontal"),i=D(()=>o.value?{width:0}:{height:0}),l=D(()=>({width:o.value?"16px":"100%",height:o.value?"100%":"16px",cursor:a.resizable?o.value?"ew-resize":"ns-resize":"auto",touchAction:"none"})),s=D(()=>{const b=n.e("dragger");return{[`${b}-horizontal`]:o.value,[`${b}-vertical`]:!o.value,[`${b}-active`]:!!u.value}}),u=F(null),c=b=>{a.resizable&&(u.value=[b.pageX,b.pageY],r("moveStart",a.index),window.addEventListener("mouseup",h),window.addEventListener("mousemove",v))},d=b=>{if(a.resizable&&b.touches.length===1){b.preventDefault();const _=b.touches[0];u.value=[_.pageX,_.pageY],r("moveStart",a.index),window.addEventListener("touchend",m),window.addEventListener("touchmove",f)}},v=b=>{const{pageX:_,pageY:w}=b,S=_-u.value[0],k=w-u.value[1],T=o.value?S:k;r("moving",a.index,T)},f=b=>{if(b.touches.length===1){b.preventDefault();const _=b.touches[0],w=_.pageX-u.value[0],S=_.pageY-u.value[1],k=o.value?w:S;r("moving",a.index,k)}},h=()=>{u.value=null,window.removeEventListener("mouseup",h),window.removeEventListener("mousemove",v),r("moveEnd",a.index)},m=()=>{u.value=null,window.removeEventListener("touchend",m),window.removeEventListener("touchmove",f),r("moveEnd",a.index)},g=D(()=>o.value?Ji:ip),y=D(()=>o.value?za:ui);return(b,_)=>(x(),O("div",{class:U([p(n).b()]),style:et(i.value)},[e.startCollapsible?(x(),O("div",{key:0,class:U([p(n).e("collapse-icon"),p(n).e(`${e.layout}-collapse-icon-start`)]),onClick:_[0]||(_[0]=w=>r("collapse",e.index,"start"))},[ce(b.$slots,"start-collapsible",{},()=>[(x(),ne(St(g.value),{style:{width:"12px",height:"12px"}}))])],2)):te("v-if",!0),C("div",{class:U([p(n).e("dragger"),s.value,p(n).is("disabled",!e.resizable),p(n).is("lazy",e.resizable&&e.lazy)]),style:et(l.value),onMousedown:c,onTouchstart:d},null,38),e.endCollapsible?(x(),O("div",{key:1,class:U([p(n).e("collapse-icon"),p(n).e(`${e.layout}-collapse-icon-end`)]),onClick:_[1]||(_[1]=w=>r("collapse",e.index,"end"))},[ce(b.$slots,"end-collapsible",{},()=>[(x(),ne(St(y.value),{style:{width:"12px",height:"12px"}}))])],2)):te("v-if",!0)],6))}}),E6e=M6e;const ak="ElSplitterPanel";var A6e=q({name:ak,__name:"split-panel",props:b6e,emits:_6e,setup(e,{expose:t,emit:n}){const a=ke("splitter-panel"),r=e,o=n,i=Ze(bO);i||Dn(ak,"usage: ");const{panels:l,layout:s,lazy:u,containerSize:c,pxSizes:d}=Ta(i),{registerPanel:v,unregisterPanel:f,onCollapse:h,onMoveEnd:m,onMoveStart:g,onMoving:y}=i,b=F(),_=Et(),w=_.uid,S=F(0),k=D(()=>l.value[S.value]),T=B=>{S.value=B},M=D(()=>k.value?d.value[S.value]??0:0),E=D(()=>k.value?d.value[S.value+1]??0:0),A=D(()=>k.value?l.value[S.value+1]:null),I=D(()=>{var B;return A.value?r.resizable&&((B=A.value)==null?void 0:B.resizable)&&(M.value!==0||!r.min)&&(E.value!==0||!A.value.min):!1}),P=D(()=>k.value?S.value!==l.value.length-1:!1),R=D(()=>nk(k.value,M.value,A.value,E.value)),G=D(()=>nk(A.value,E.value,k.value,M.value));function K(B){return Yw(B)?Uw(B)*c.value||0:jw(B)?Gw(B):B??0}let H=!1;ye(()=>r.size,()=>{if(!H&&k.value){if(!c.value){k.value.size=r.size;return}const B=K(r.size),W=K(r.max),N=K(r.min),V=Math.min(Math.max(B,N||0),W||B);V!==B&&o("update:size",V),k.value.size=V}}),ye(()=>{var B;return(B=k.value)==null?void 0:B.size},B=>{B!==r.size&&(H=!0,o("update:size",B),He(()=>H=!1))}),ye(()=>r.resizable,B=>{k.value&&(k.value.resizable=B)});const z=nn({uid:w,getVnode:()=>_.vnode,setIndex:T,...r,collapsible:D(()=>T6e(r.collapsible))});return v(z),rn(()=>f(z)),t({splitterPanelRef:b}),(B,W)=>(x(),O(Ve,null,[C("div",Tt({ref_key:"panelEl",ref:b,class:[p(a).b()],style:{flexBasis:`${M.value}px`}},B.$attrs),[ce(B.$slots,"default")],16),P.value?(x(),ne(E6e,{key:0,index:S.value,layout:p(s),lazy:p(u),resizable:I.value,"start-collapsible":R.value,"end-collapsible":G.value,onMoveStart:p(g),onMoving:p(y),onMoveEnd:p(m),onCollapse:p(h)},{"start-collapsible":$(()=>[ce(B.$slots,"start-collapsible")]),"end-collapsible":$(()=>[ce(B.$slots,"end-collapsible")]),_:3},8,["index","layout","lazy","resizable","start-collapsible","end-collapsible","onMoveStart","onMoving","onMoveEnd","onCollapse"])):te("v-if",!0)],64))}}),_O=A6e;const I6e=wt(k6e,{SplitPanel:_O}),L6e=On(_O),jo="ElInfiniteScroll",P6e={delay:{type:Number,default:200},distance:{type:Number,default:0},disabled:{type:Boolean,default:!1},immediate:{type:Boolean,default:!0}},qw=(e,t)=>Object.entries(P6e).reduce((n,[a,r])=>{const{type:o,default:i}=r,l=e.getAttribute(`infinite-scroll-${a}`);let s=t[l]??l??i;return s=s==="false"?!1:s,s=o(s),n[a]=Number.isNaN(s)?i:s,n},{}),wO=e=>{const{observer:t}=e[jo];t&&(t.disconnect(),delete e[jo].observer)},D6e=(e,t)=>{const{container:n,containerEl:a,instance:r,observer:o,lastScrollTop:i}=e[jo],{disabled:l,distance:s}=qw(e,r),{clientHeight:u,scrollHeight:c,scrollTop:d}=a,v=d-i;if(e[jo].lastScrollTop=d,o||l||v<0)return;let f=!1;if(n===e)f=c-(u+d)<=s;else{const{clientTop:h,scrollHeight:m}=e,g=q4(e,a);f=d+u>=g+h+m-s}f&&t.call(r)};function my(e,t){const{containerEl:n,instance:a}=e[jo],{disabled:r}=qw(e,a);r||n.clientHeight===0||(n.scrollHeight<=n.clientHeight?t.call(a):wO(e))}const O6e={async mounted(e,t){const{instance:n,value:a}=t;Ri({scope:jo,from:"the directive v-infinite-scroll",replacement:"the el-scrollbar infinite scroll",version:"3.0.0",ref:"https://element-plus.org/en-US/component/scrollbar#infinite-scroll"},!0),Qe(a)||Dn(jo,"'v-infinite-scroll' binding value must be a function"),await He();const{delay:r,immediate:o}=qw(e,n),i=x6(e,!0),l=i===window?document.documentElement:i,s=hs(D6e.bind(null,e,a),r);if(i){if(e[jo]={instance:n,container:i,containerEl:l,delay:r,cb:a,onScroll:s,lastScrollTop:l.scrollTop},o){const u=new MutationObserver(hs(my.bind(null,e,a),50));e[jo].observer=u,u.observe(e,{childList:!0,subtree:!0}),my(e,a)}i.addEventListener("scroll",s)}},unmounted(e){if(!e.ElInfiniteScroll)return;const{container:t,onScroll:n}=e[jo];t==null||t.removeEventListener("scroll",n),wO(e)},async updated(e){if(!e.ElInfiniteScroll)await He();else{const{containerEl:t,cb:n,observer:a}=e[jo];t.clientHeight&&a&&my(e,n)}}},c_=O6e;c_.install=e=>{e.directive("InfiniteScroll",c_)};const $6e=c_;function R6e(e,t){let n;const a=F(!1),r=nn({...e,originalPosition:"",originalOverflow:"",visible:!1});function o(v){r.text=v}function i(){const v=r.parent,f=d.ns;if(!v.vLoadingAddClassList){let h=v.getAttribute("loading-number");h=Number.parseInt(h)-1,h?v.setAttribute("loading-number",h.toString()):(ir(v,f.bm("parent","relative")),v.removeAttribute("loading-number")),ir(v,f.bm("parent","hidden"))}l(),c.unmount()}function l(){var v,f;(f=(v=d.$el)==null?void 0:v.parentNode)==null||f.removeChild(d.$el)}function s(){var v;e.beforeClose&&!e.beforeClose()||(a.value=!0,clearTimeout(n),n=setTimeout(u,400),r.visible=!1,(v=e.closed)==null||v.call(e))}function u(){if(!a.value)return;const v=r.parent;a.value=!1,v.vLoadingAddClassList=void 0,i()}const c=V7(q({name:"ElLoading",setup(v,{expose:f}){const{ns:h,zIndex:m}=W1("loading");return f({ns:h,zIndex:m}),()=>{const g=r.spinner||r.svg,y=ot("svg",{class:"circular",viewBox:r.svgViewBox?r.svgViewBox:"0 0 50 50",...g?{innerHTML:g}:{}},g?void 0:[ot("circle",{class:"path",cx:"25",cy:"25",r:"20",fill:"none"})]),b=r.text?ot("p",{class:h.b("text")},[r.text]):void 0;return ot(Pa,{name:h.b("fade"),onAfterLeave:u},{default:$(()=>[st(L("div",{style:{backgroundColor:r.background||""},class:[h.b("mask"),r.customClass,h.is("fullscreen",r.fullscreen)]},[ot("div",{class:h.b("spinner")},[y,b])]),[[Zt,r.visible]])])})}}}));Object.assign(c._context,t??{});const d=c.mount(document.createElement("div"));return{...Ta(r),setText:o,removeElLoadingChild:l,close:s,handleAfterLeave:u,vm:d,get $el(){return d.$el}}}let Xp;const Cd=function(e={},t){if(!qt)return;const n=N6e(e);if(n.fullscreen&&Xp)return Xp;const a=R6e({...n,closed:()=>{var o;(o=n.closed)==null||o.call(n),n.fullscreen&&(Xp=void 0)}},t??Cd._context);B6e(n,n.parent,a),rk(n,n.parent,a),n.parent.vLoadingAddClassList=()=>rk(n,n.parent,a);let r=n.parent.getAttribute("loading-number");return r?r=`${Number.parseInt(r)+1}`:r="1",n.parent.setAttribute("loading-number",r),n.parent.appendChild(a.$el),He(()=>a.visible.value=n.visible),n.fullscreen&&(Xp=a),a},N6e=e=>{let t;return Ye(e.target)?t=document.querySelector(e.target)??document.body:t=e.target||document.body,{parent:t===document.body||e.body?document.body:t,background:e.background||"",svg:e.svg||"",svgViewBox:e.svgViewBox||"",spinner:e.spinner||!1,text:e.text||"",fullscreen:t===document.body&&(e.fullscreen??!0),lock:e.lock??!1,customClass:e.customClass||"",visible:e.visible??!0,beforeClose:e.beforeClose,closed:e.closed,target:t}},B6e=async(e,t,n)=>{const{nextZIndex:a}=n.vm.zIndex||n.vm._.exposed.zIndex,r={};if(e.fullscreen)n.originalPosition.value=ml(document.body,"position"),n.originalOverflow.value=ml(document.body,"overflow"),r.zIndex=a();else if(e.parent===document.body){n.originalPosition.value=ml(document.body,"position"),await He();for(const o of["top","left"]){const i=o==="top"?"scrollTop":"scrollLeft";r[o]=`${e.target.getBoundingClientRect()[o]+document.body[i]+document.documentElement[i]-Number.parseInt(ml(document.body,`margin-${o}`),10)}px`}for(const o of["height","width"])r[o]=`${e.target.getBoundingClientRect()[o]}px`}else n.originalPosition.value=ml(t,"position");for(const[o,i]of Object.entries(r))n.$el.style[o]=i},rk=(e,t,n)=>{const a=n.vm.ns||n.vm._.exposed.ns;["absolute","fixed","sticky"].includes(n.originalPosition.value)?ir(t,a.bm("parent","relative")):Eo(t,a.bm("parent","relative")),e.fullscreen&&e.lock?Eo(t,a.bm("parent","hidden")):ir(t,a.bm("parent","hidden"))};Cd._context=null;const uv=Symbol("ElLoading"),Eu=e=>`element-loading-${$l(e)}`,ok=(e,t)=>{const n=t.instance,a=u=>_t(t.value)?t.value[u]:void 0,r=u=>F(Ye(u)&&(n==null?void 0:n[u])||u),o=u=>r(a(u)||e.getAttribute(Eu(u))),i=a("fullscreen")??t.modifiers.fullscreen,l={text:o("text"),svg:o("svg"),svgViewBox:o("svgViewBox"),spinner:o("spinner"),background:o("background"),customClass:o("customClass"),fullscreen:i,target:a("target")??(i?void 0:e),body:a("body")??t.modifiers.body,lock:a("lock")??t.modifiers.lock},s=Cd(l);s._context=Dv._context,e[uv]={options:l,instance:s}},V6e=(e,t)=>{for(const n of Object.keys(e))xn(e[n])&&(e[n].value=t[n])},Dv={mounted(e,t){t.value&&ok(e,t)},updated(e,t){const n=e[uv];if(!t.value){n==null||n.instance.close(),e[uv]=null;return}n?V6e(n.options,_t(t.value)?t.value:{text:e.getAttribute(Eu("text")),svg:e.getAttribute(Eu("svg")),svgViewBox:e.getAttribute(Eu("svgViewBox")),spinner:e.getAttribute(Eu("spinner")),background:e.getAttribute(Eu("background")),customClass:e.getAttribute(Eu("customClass"))}):ok(e,t)},unmounted(e){var t;(t=e[uv])==null||t.instance.close(),e[uv]=null}};Dv._context=null;const z6e={install(e){Cd._context=e._context,Dv._context=e._context,e.directive("loading",Dv),e.config.globalProperties.$loading=Cd},directive:Dv,service:Cd},SO=["primary","success","info","warning","error"],CO=["top","top-left","top-right","bottom","bottom-left","bottom-right"],Ja=Nn({customClass:"",dangerouslyUseHTMLString:!1,duration:3e3,icon:void 0,id:"",message:"",onClose:void 0,showClose:!1,type:"info",plain:!1,offset:16,placement:void 0,zIndex:0,grouping:!1,repeatNum:1,appendTo:qt?document.body:void 0}),F6e=$e({customClass:{type:ae([String,Array,Object,Boolean]),default:Ja.customClass},dangerouslyUseHTMLString:{type:Boolean,default:Ja.dangerouslyUseHTMLString},duration:{type:Number,default:Ja.duration},icon:{type:pn,default:Ja.icon},id:{type:String,default:Ja.id},message:{type:ae([String,Object,Function]),default:Ja.message},onClose:{type:ae(Function),default:Ja.onClose},showClose:{type:Boolean,default:Ja.showClose},type:{type:String,values:SO,default:Ja.type},plain:{type:Boolean,default:Ja.plain},offset:{type:Number,default:Ja.offset},placement:{type:String,values:CO,default:Ja.placement},zIndex:{type:Number,default:Ja.zIndex},grouping:{type:Boolean,default:Ja.grouping},repeatNum:{type:Number,default:Ja.repeatNum}}),H6e={destroy:()=>!0},Jo=W3({}),W6e=e=>(Jo[e]||(Jo[e]=W3([])),Jo[e]),K6e=(e,t)=>{const n=Jo[t]||[],a=n.findIndex(i=>i.id===e),r=n[a];let o;return a>0&&(o=n[a-1]),{current:r,prev:o}},U6e=(e,t)=>{const{prev:n}=K6e(e,t);return n?n.vm.exposed.bottom.value:0},G6e=(e,t,n)=>(Jo[n]||[]).findIndex(a=>a.id===e)>0?16:t,Y6e=["id"],j6e=["innerHTML"];var q6e=q({name:"ElMessage",__name:"message",props:F6e,emits:H6e,setup(e,{expose:t,emit:n}){const{Close:a}=Y6,r=e,o=n,i=F(!1),{ns:l,zIndex:s}=W1("message"),{currentZIndex:u,nextZIndex:c}=s,d=F(),v=F(!1),f=F(0);let h;const m=D(()=>r.type?r.type==="error"?"danger":r.type:"info"),g=D(()=>{const R=r.type;return{[l.bm("icon",R)]:R&&Rs[R]}}),y=D(()=>r.icon||Rs[r.type]||""),b=D(()=>r.placement||"top"),_=D(()=>U6e(r.id,b.value)),w=D(()=>Math.max(G6e(r.id,r.offset,b.value)+_.value,r.offset)),S=D(()=>f.value+w.value),k=D(()=>b.value.includes("left")?l.is("left"):b.value.includes("right")?l.is("right"):l.is("center")),T=D(()=>b.value.startsWith("top")?"top":"bottom"),M=D(()=>({[T.value]:`${w.value}px`,zIndex:u.value}));function E(){r.duration!==0&&({stop:h}=Bd(()=>{I()},r.duration))}function A(){h==null||h()}function I(){v.value=!1,He(()=>{var R;i.value||((R=r.onClose)==null||R.call(r),o("destroy"))})}function P(R){mn(R)===Pe.esc&&I()}return mt(()=>{E(),c(),v.value=!0}),ye(()=>r.repeatNum,()=>{A(),E()}),on(document,"keydown",P),Mn(d,()=>{f.value=d.value.getBoundingClientRect().height}),t({visible:v,bottom:S,close:I}),(R,G)=>(x(),ne(Pa,{name:p(l).b("fade"),onBeforeEnter:G[0]||(G[0]=K=>i.value=!0),onBeforeLeave:e.onClose,onAfterLeave:G[1]||(G[1]=K=>R.$emit("destroy")),persisted:""},{default:$(()=>[st(C("div",{id:e.id,ref_key:"messageRef",ref:d,class:U([p(l).b(),{[p(l).m(e.type)]:e.type},p(l).is("closable",e.showClose),p(l).is("plain",e.plain),p(l).is("bottom",T.value==="bottom"),k.value,e.customClass]),style:et(M.value),role:"alert",onMouseenter:A,onMouseleave:E},[e.repeatNum>1?(x(),ne(p(dL),{key:0,value:e.repeatNum,type:m.value,class:U(p(l).e("badge"))},null,8,["value","type","class"])):te("v-if",!0),y.value?(x(),ne(p(Xe),{key:1,class:U([p(l).e("icon"),g.value])},{default:$(()=>[(x(),ne(St(y.value)))]),_:1},8,["class"])):te("v-if",!0),!e.dangerouslyUseHTMLString||R.$slots.default?(x(),O("p",{key:2,class:U(p(l).e("content"))},[ce(R.$slots,"default",{},()=>[ue(Q(e.message),1)])],2)):(x(),O(Ve,{key:3},[te(" Caution here, message could've been compromised, never use user's input as message "),C("p",{class:U(p(l).e("content")),innerHTML:e.message},null,10,j6e)],2112)),e.showClose?(x(),ne(p(Xe),{key:4,class:U(p(l).e("closeBtn")),onClick:at(I,["stop"])},{default:$(()=>[L(p(a))]),_:1},8,["class"])):te("v-if",!0)],46,Y6e),[[Zt,v.value]])]),_:3},8,["name","onBeforeLeave"]))}}),X6e=q6e;let Z6e=1;const J6e=e=>{if(!e.appendTo)e.appendTo=document.body;else if(Ye(e.appendTo)){let t=document.querySelector(e.appendTo);Vr(t)||(un("ElMessage","the appendTo option is not an HTMLElement. Falling back to document.body."),t=document.body),e.appendTo=t}},Q6e=e=>{!e.placement&&Ye(Qa.placement)&&Qa.placement&&(e.placement=Qa.placement),e.placement||(e.placement="top"),CO.includes(e.placement)||(un("ElMessage",`Invalid placement: ${e.placement}. Falling back to 'top'.`),e.placement="top")},xO=e=>{const t=!e||Ye(e)||bn(e)||Qe(e)?{message:e}:e,n={...Ja,...t};return J6e(n),Q6e(n),vn(Qa.grouping)&&!n.grouping&&(n.grouping=Qa.grouping),Je(Qa.duration)&&n.duration===3e3&&(n.duration=Qa.duration),Je(Qa.offset)&&n.offset===16&&(n.offset=Qa.offset),vn(Qa.showClose)&&!n.showClose&&(n.showClose=Qa.showClose),vn(Qa.plain)&&!n.plain&&(n.plain=Qa.plain),n},ewe=e=>{const t=Jo[e.props.placement||"top"],n=t.indexOf(e);if(n===-1)return;t.splice(n,1);const{handler:a}=e;a.close()},twe=({appendTo:e,...t},n)=>{const a=`message_${Z6e++}`,r=t.onClose,o=document.createElement("div"),i={...t,id:a,onClose:()=>{r==null||r(),ewe(u)},onDestroy:()=>{Ps(null,o)}},l=L(X6e,i,Qe(i.message)||bn(i.message)?{default:Qe(i.message)?i.message:()=>i.message}:null);l.appContext=n||hc._context,Ps(l,o),e.appendChild(o.firstElementChild);const s=l.component,u={id:a,vnode:l,vm:s,handler:{close:()=>{s.exposed.close()}},props:l.component.props};return u},hc=(e={},t)=>{if(!qt)return{close:()=>{}};const n=xO(e),a=W6e(n.placement||"top");if(n.grouping&&a.length){const o=a.find(({vnode:i})=>{var l;return((l=i.props)==null?void 0:l.message)===n.message});if(o)return o.props.repeatNum+=1,o.props.type=n.type,o.handler}if(Je(Qa.max)&&a.length>=Qa.max)return{close:()=>{}};const r=twe(n,t);return a.push(r),r.handler};SO.forEach(e=>{hc[e]=(t={},n)=>hc({...xO(t),type:e},n)});function nwe(e){for(const t in Jo)if(en(Jo,t)){const n=[...Jo[t]];for(const a of n)(!e||e===a.props.type)&&a.handler.close()}}function awe(e){Jo[e]&&[...Jo[e]].forEach(t=>t.handler.close())}hc.closeAll=nwe;hc.closeAllByPlacement=awe;hc._context=null;const We=SI(hc,"$message");var rwe=q({name:"ElMessageBox",directives:{TrapFocus:Oue},components:{ElButton:xa,ElFocusTrap:pf,ElInput:Ba,ElOverlay:bw,ElIcon:Xe,...Y6},inheritAttrs:!1,props:{buttonSize:{type:String,validator:SD},modal:{type:Boolean,default:!0},lockScroll:{type:Boolean,default:!0},showClose:{type:Boolean,default:!0},closeOnClickModal:{type:Boolean,default:!0},closeOnPressEscape:{type:Boolean,default:!0},closeOnHashChange:{type:Boolean,default:!0},center:Boolean,draggable:Boolean,overflow:Boolean,roundButton:Boolean,container:{type:String,default:"body"},boxType:{type:String,default:""}},emits:["vanish","action"],setup(e,{emit:t}){const{locale:n,zIndex:a,ns:r,size:o}=W1("message-box",D(()=>e.buttonSize)),{t:i}=n,{nextZIndex:l}=a,s=F(!1),u=nn({autofocus:!0,beforeClose:null,callback:null,cancelButtonText:"",cancelButtonClass:"",confirmButtonText:"",confirmButtonClass:"",cancelButtonType:"",confirmButtonType:"primary",customClass:"",customStyle:{},dangerouslyUseHTMLString:!1,distinguishCancelAndClose:!1,icon:"",closeIcon:"",inputPattern:null,inputPlaceholder:"",inputType:"text",inputValue:"",inputValidator:void 0,inputErrorMessage:"",message:"",modalFade:!0,modalClass:"",showCancelButton:!1,showConfirmButton:!0,type:"",title:void 0,showInput:!1,action:"",confirmButtonLoading:!1,cancelButtonLoading:!1,confirmButtonLoadingIcon:$i(Mr),cancelButtonLoadingIcon:$i(Mr),confirmButtonDisabled:!1,editorErrorMessage:"",validateError:!1,zIndex:l()}),c=D(()=>{const K=u.type;return{[r.bm("icon",K)]:K&&Rs[K]}}),d=Fa(),v=Fa(),f=D(()=>{const K=u.type;return u.icon||K&&Rs[K]||""}),h=D(()=>!!u.message),m=F(),g=F(),y=F(),b=F(),_=F(),w=D(()=>u.confirmButtonClass);ye(()=>u.inputValue,async K=>{await He(),e.boxType==="prompt"&&K&&I()},{immediate:!0}),ye(()=>s.value,K=>{var H;K&&(e.boxType!=="prompt"&&(u.autofocus?y.value=((H=_.value)==null?void 0:H.$el)??m.value:y.value=m.value),u.zIndex=l()),e.boxType==="prompt"&&(K?He().then(()=>{b.value&&b.value.$el&&(u.autofocus?y.value=P()??m.value:y.value=m.value)}):(u.editorErrorMessage="",u.validateError=!1))});const{isDragging:S}=BA(m,g,D(()=>e.draggable),D(()=>e.overflow));mt(async()=>{await He(),e.closeOnHashChange&&window.addEventListener("hashchange",k)}),rn(()=>{e.closeOnHashChange&&window.removeEventListener("hashchange",k)});function k(){s.value&&(s.value=!1,He(()=>{u.action&&t("action",u.action)}))}const T=()=>{e.closeOnClickModal&&A(u.distinguishCancelAndClose?"close":"cancel")},M=O6(T),E=K=>{var H;if(u.inputType!=="textarea"&&!((H=b.value)!=null&&H.isComposing))return K.preventDefault(),A("confirm")},A=K=>{var H;e.boxType==="prompt"&&K==="confirm"&&!I()||(u.action=K,u.beforeClose?(H=u.beforeClose)==null||H.call(u,K,u,k):k())},I=()=>{if(e.boxType==="prompt"){const K=u.inputPattern;if(K&&!K.test(u.inputValue||""))return u.editorErrorMessage=u.inputErrorMessage||i("el.messagebox.error"),u.validateError=!0,!1;const H=u.inputValidator;if(Qe(H)){const z=H(u.inputValue);if(z===!1)return u.editorErrorMessage=u.inputErrorMessage||i("el.messagebox.error"),u.validateError=!0,!1;if(Ye(z))return u.editorErrorMessage=z,u.validateError=!0,!1}}return u.editorErrorMessage="",u.validateError=!1,!0},P=()=>{var H;const K=(H=b.value)==null?void 0:H.$refs;return(K==null?void 0:K.input)??(K==null?void 0:K.textarea)},R=()=>{A("close")},G=()=>{e.closeOnPressEscape&&R()};return e.lockScroll&&D1(s,{ns:r}),{...Ta(u),ns:r,overlayEvent:M,visible:s,hasMessage:h,typeClass:c,contentId:d,inputId:v,btnSize:o,iconComponent:f,confirmButtonClasses:w,rootRef:m,focusStartRef:y,headerRef:g,inputRef:b,isDragging:S,confirmRef:_,doClose:k,handleClose:R,onCloseRequested:G,handleWrapperClick:T,handleInputEnter:E,handleAction:A,t:i}}});const owe=["aria-label","aria-describedby"],iwe=["aria-label"],lwe=["id"];function swe(e,t,n,a,r,o){const i=be("el-icon"),l=be("el-input"),s=be("el-button"),u=be("el-focus-trap"),c=be("el-overlay");return x(),ne(Pa,{name:"fade-in-linear",onAfterLeave:t[11]||(t[11]=d=>e.$emit("vanish")),persisted:""},{default:$(()=>[st(L(c,{"z-index":e.zIndex,"overlay-class":[e.ns.is("message-box"),e.modalClass],mask:e.modal},{default:$(()=>[C("div",{role:"dialog","aria-label":e.title,"aria-modal":"true","aria-describedby":e.showInput?void 0:e.contentId,class:U(`${e.ns.namespace.value}-overlay-message-box`),onClick:t[8]||(t[8]=(...d)=>e.overlayEvent.onClick&&e.overlayEvent.onClick(...d)),onMousedown:t[9]||(t[9]=(...d)=>e.overlayEvent.onMousedown&&e.overlayEvent.onMousedown(...d)),onMouseup:t[10]||(t[10]=(...d)=>e.overlayEvent.onMouseup&&e.overlayEvent.onMouseup(...d))},[L(u,{loop:"",trapped:e.visible,"focus-trap-el":e.rootRef,"focus-start-el":e.focusStartRef,onReleaseRequested:e.onCloseRequested},{default:$(()=>[C("div",{ref:"rootRef",class:U([e.ns.b(),e.customClass,e.ns.is("draggable",e.draggable),e.ns.is("dragging",e.isDragging),{[e.ns.m("center")]:e.center}]),style:et(e.customStyle),tabindex:"-1",onClick:t[7]||(t[7]=at(()=>{},["stop"]))},[e.title!==null&&e.title!==void 0?(x(),O("div",{key:0,ref:"headerRef",class:U([e.ns.e("header"),{"show-close":e.showClose}])},[C("div",{class:U(e.ns.e("title"))},[e.iconComponent&&e.center?(x(),ne(i,{key:0,class:U([e.ns.e("status"),e.typeClass])},{default:$(()=>[(x(),ne(St(e.iconComponent)))]),_:1},8,["class"])):te("v-if",!0),C("span",null,Q(e.title),1)],2),e.showClose?(x(),O("button",{key:0,type:"button",class:U(e.ns.e("headerbtn")),"aria-label":e.t("el.messagebox.close"),onClick:t[0]||(t[0]=d=>e.handleAction(e.distinguishCancelAndClose?"close":"cancel")),onKeydown:t[1]||(t[1]=kn(at(d=>e.handleAction(e.distinguishCancelAndClose?"close":"cancel"),["prevent"]),["enter"]))},[L(i,{class:U(e.ns.e("close"))},{default:$(()=>[(x(),ne(St(e.closeIcon||"close")))]),_:1},8,["class"])],42,iwe)):te("v-if",!0)],2)):te("v-if",!0),C("div",{id:e.contentId,class:U(e.ns.e("content"))},[C("div",{class:U(e.ns.e("container"))},[e.iconComponent&&!e.center&&e.hasMessage?(x(),ne(i,{key:0,class:U([e.ns.e("status"),e.typeClass])},{default:$(()=>[(x(),ne(St(e.iconComponent)))]),_:1},8,["class"])):te("v-if",!0),e.hasMessage?(x(),O("div",{key:1,class:U(e.ns.e("message"))},[ce(e.$slots,"default",{},()=>[e.dangerouslyUseHTMLString?(x(),ne(St(e.showInput?"label":"p"),{key:1,for:e.showInput?e.inputId:void 0,innerHTML:e.message},null,8,["for","innerHTML"])):(x(),ne(St(e.showInput?"label":"p"),{key:0,for:e.showInput?e.inputId:void 0,textContent:Q(e.message)},null,8,["for","textContent"]))])],2)):te("v-if",!0)],2),st(C("div",{class:U(e.ns.e("input"))},[L(l,{id:e.inputId,ref:"inputRef",modelValue:e.inputValue,"onUpdate:modelValue":t[2]||(t[2]=d=>e.inputValue=d),type:e.inputType,placeholder:e.inputPlaceholder,"aria-invalid":e.validateError,class:U({invalid:e.validateError}),onKeydown:kn(e.handleInputEnter,["enter"])},null,8,["id","modelValue","type","placeholder","aria-invalid","class","onKeydown"]),C("div",{class:U(e.ns.e("errormsg")),style:et({visibility:e.editorErrorMessage?"visible":"hidden"})},Q(e.editorErrorMessage),7)],2),[[Zt,e.showInput]])],10,lwe),e.showCancelButton||e.showConfirmButton?(x(),O("div",{key:1,class:U(e.ns.e("btns"))},[e.showCancelButton?(x(),ne(s,{key:0,type:e.cancelButtonType==="text"?"":e.cancelButtonType,text:e.cancelButtonType==="text",loading:e.cancelButtonLoading,"loading-icon":e.cancelButtonLoadingIcon,class:U([e.cancelButtonClass]),round:e.roundButton,size:e.btnSize,onClick:t[3]||(t[3]=d=>e.handleAction("cancel")),onKeydown:t[4]||(t[4]=kn(at(d=>e.handleAction("cancel"),["prevent"]),["enter"]))},{default:$(()=>[ue(Q(e.cancelButtonText||e.t("el.messagebox.cancel")),1)]),_:1},8,["type","text","loading","loading-icon","class","round","size"])):te("v-if",!0),st(L(s,{ref:"confirmRef",type:e.confirmButtonType==="text"?"":e.confirmButtonType,text:e.confirmButtonType==="text",loading:e.confirmButtonLoading,"loading-icon":e.confirmButtonLoadingIcon,class:U([e.confirmButtonClasses]),round:e.roundButton,disabled:e.confirmButtonDisabled,size:e.btnSize,onClick:t[5]||(t[5]=d=>e.handleAction("confirm")),onKeydown:t[6]||(t[6]=kn(at(d=>e.handleAction("confirm"),["prevent"]),["enter"]))},{default:$(()=>[ue(Q(e.confirmButtonText||e.t("el.messagebox.confirm")),1)]),_:1},8,["type","text","loading","loading-icon","class","round","disabled","size"]),[[Zt,e.showConfirmButton]])],2)):te("v-if",!0)],6)]),_:3},8,["trapped","focus-trap-el","focus-start-el","onReleaseRequested"])],42,owe)]),_:3},8,["z-index","overlay-class","mask"]),[[Zt,e.visible]])]),_:3})}var uwe=ya(rwe,[["render",swe]]);const Ch=new Map,cwe=e=>{let t=document.body;return e.appendTo&&(Ye(e.appendTo)&&(t=document.querySelector(e.appendTo)),Vr(e.appendTo)&&(t=e.appendTo),Vr(t)||(un("ElMessageBox","the appendTo option is not an HTMLElement. Falling back to document.body."),t=document.body)),t},gy=(e,t)=>{var a;const n=(a=e.component)==null?void 0:a.proxy;return()=>n.handleAction(t)},dwe=(e,t,n=null)=>{const a=L(uwe,e,Qe(e.message)||bn(e.message)?{default:Qe(e.message)?()=>e.message({confirm:gy(a,"confirm"),cancel:gy(a,"cancel"),close:gy(a,"close")}):()=>e.message}:null);return a.appContext=n,Ps(a,t),cwe(e).appendChild(t.firstElementChild),a.component},fwe=()=>document.createElement("div"),vwe=(e,t)=>{const n=fwe();e.onVanish=()=>{Ps(null,n),Ch.delete(r)},e.onAction=o=>{const i=Ch.get(r);let l;e.showInput?l={value:r.inputValue,action:o}:l=o,e.callback?e.callback(l,a.proxy):o==="cancel"||o==="close"?e.distinguishCancelAndClose&&o!=="cancel"?i.reject("close"):i.reject("cancel"):i.resolve(l)};const a=dwe(e,n,t),r=a.proxy;for(const o in e)en(e,o)&&!en(r.$props,o)&&(o==="closeIcon"&&_t(e[o])?r[o]=$i(e[o]):r[o]=e[o]);return r.visible=!0,r};function gf(e,t=null){if(!qt)return Promise.reject();let n;return Ye(e)||bn(e)?e={message:e}:n=e.callback,new Promise((a,r)=>{const o=vwe(e,t??gf._context);Ch.set(o,{options:e,callback:n,resolve:a,reject:r})})}const hwe=["alert","confirm","prompt"],pwe={alert:{closeOnPressEscape:!1,closeOnClickModal:!1},confirm:{showCancelButton:!0},prompt:{showCancelButton:!0,showInput:!0}};hwe.forEach(e=>{gf[e]=mwe(e)});function mwe(e){return(t,n,a,r)=>{let o="";return _t(n)?(a=n,o=""):zt(n)?o="":o=n,gf(Object.assign({title:o,message:t,type:"",...pwe[e]},a,{boxType:e}),r)}}gf.close=()=>{Ch.forEach((e,t)=>{t.doClose()}),Ch.clear()};gf._context=null;const ss=gf;ss.install=e=>{ss._context=e._context,e.config.globalProperties.$msgbox=ss,e.config.globalProperties.$messageBox=ss,e.config.globalProperties.$alert=ss.alert,e.config.globalProperties.$confirm=ss.confirm,e.config.globalProperties.$prompt=ss.prompt};const er=ss,kO=["primary","success","info","warning","error"],gwe=$e({customClass:{type:ae([String,Array,Object,Boolean]),default:""},dangerouslyUseHTMLString:Boolean,duration:{type:Number,default:4500},icon:{type:pn},id:{type:String,default:""},message:{type:ae([String,Object,Function]),default:""},offset:{type:Number,default:0},onClick:{type:ae(Function),default:()=>{}},onClose:{type:ae(Function),required:!0},position:{type:String,values:["top-right","top-left","bottom-right","bottom-left"],default:"top-right"},showClose:{type:Boolean,default:!0},title:{type:String,default:""},type:{type:String,values:[...kO,""],default:""},zIndex:Number,closeIcon:{type:pn,default:so},progress:{type:ae([Boolean,Object]),default:!1},pauseOnHover:{type:Boolean,default:!0}}),ywe={destroy:()=>!0},bwe=["id"],_we=["textContent"],wwe={key:0},Swe=["innerHTML"];var Cwe=q({name:"ElNotification",__name:"notification",props:gwe,emits:ywe,setup(e,{expose:t}){const n=e,{ns:a,zIndex:r}=W1("notification"),{nextZIndex:o,currentZIndex:i}=r,l=F(!1);let s;const u=F(100);let c=0,d=0;const v=D(()=>{const R=n.type;return R&&Rs[n.type]?a.m(R):""}),f=D(()=>!!n.progress&&n.duration>0),h=D(()=>{switch(n.type){case"success":return"success";case"warning":return"warning";case"error":return"exception";default:return}}),m=D(()=>({status:h.value,strokeWidth:3,showText:!1,...typeof n.progress=="object"?n.progress:void 0})),g=D(()=>n.type&&Rs[n.type]||n.icon),y=D(()=>n.position.endsWith("right")?"right":"left"),b=D(()=>n.position.startsWith("top")?"top":"bottom"),_=D(()=>({[b.value]:`${n.offset}px`,zIndex:n.zIndex??i.value})),{pause:w,resume:S}=_Y(()=>{u.value=Math.max(0,100-(c+Date.now()-d)/n.duration*100)},100,{immediate:!1});function k(){if(n.duration<=0)return;s==null||s(),w(),d=Date.now();const R=Math.max(0,n.duration-c);({stop:s}=Bd(()=>{l.value&&I()},R)),f.value&&(u.value=R/n.duration*100,S())}function T(){s==null||s(),w(),d>0&&(c+=Date.now()-d,d=0)}let M=!1;function E(){M=!0,n.pauseOnHover&&T()}function A(){M=!1,n.pauseOnHover&&k()}function I(){l.value=!1,T()}function P(R){switch(mn(R)){case Pe.delete:case Pe.backspace:T();break;case Pe.esc:l.value&&I();break;default:if(M&&n.pauseOnHover)break;T(),k();break}}return mt(()=>{k(),o(),l.value=!0}),on(document,"keydown",P),t({visible:l,close:I}),(R,G)=>(x(),ne(Pa,{name:p(a).b("fade"),onBeforeLeave:e.onClose,onAfterLeave:G[1]||(G[1]=K=>R.$emit("destroy")),persisted:""},{default:$(()=>[st(C("div",{id:e.id,class:U([p(a).b(),e.customClass,y.value]),style:et(_.value),role:"alert",onMouseenter:E,onMouseleave:A,onClick:G[0]||(G[0]=(...K)=>e.onClick&&e.onClick(...K))},[g.value?(x(),ne(p(Xe),{key:0,class:U([p(a).e("icon"),v.value])},{default:$(()=>[(x(),ne(St(g.value)))]),_:1},8,["class"])):te("v-if",!0),C("div",{class:U(p(a).e("group"))},[C("h2",{class:U(p(a).e("title")),textContent:Q(e.title)},null,10,_we),st(C("div",{class:U(p(a).e("content")),style:et(e.title?void 0:{margin:0})},[ce(R.$slots,"default",{},()=>[e.dangerouslyUseHTMLString?(x(),O(Ve,{key:1},[te(" Caution here, message could've been compromised, never use user's input as message "),C("p",{innerHTML:e.message},null,8,Swe)],2112)):(x(),O("p",wwe,Q(e.message),1))])],6),[[Zt,e.message]]),e.showClose?(x(),ne(p(Xe),{key:0,class:U(p(a).e("closeBtn")),onClick:at(I,["stop"])},{default:$(()=>[(x(),ne(St(e.closeIcon)))]),_:1},8,["class"])):te("v-if",!0)],2),f.value?(x(),O("div",{key:1,class:U(p(a).e("progress")),"aria-hidden":"true"},[L(p(kw),Tt(m.value,{percentage:u.value}),null,16,["percentage"])],2)):te("v-if",!0)],46,bwe),[[Zt,l.value]])]),_:3},8,["name","onBeforeLeave"]))}}),xwe=Cwe;const qd={"top-left":[],"top-right":[],"bottom-left":[],"bottom-right":[]},pg=16;let kwe=1;const pc=function(e={},t){if(!qt)return{close:()=>{}};(Ye(e)||bn(e))&&(e={message:e});const n=e.position||"top-right";let a=e.offset||0;qd[n].forEach(({vm:c})=>{var d;a+=(((d=c.el)==null?void 0:d.offsetHeight)||0)+pg}),a+=pg;const r=`notification_${kwe++}`,o=e.onClose,i={...e,offset:a,id:r,onClose:()=>{Twe(r,n,o)}};let l=document.body;Vr(e.appendTo)?l=e.appendTo:Ye(e.appendTo)&&(l=document.querySelector(e.appendTo)),Vr(l)||(un("ElNotification","the appendTo option is not an HTMLElement. Falling back to document.body."),l=document.body);const s=document.createElement("div"),u=L(xwe,i,Qe(i.message)?i.message:bn(i.message)?()=>i.message:null);return u.appContext=zt(t)?pc._context:t,u.props.onDestroy=()=>{Ps(null,s)},Ps(u,s),qd[n].push({vm:u}),l.appendChild(s.firstElementChild),{close:()=>{u.component.exposed.close()}}};kO.forEach(e=>{pc[e]=(t={},n)=>((Ye(t)||bn(t))&&(t={message:t}),pc({...t,type:e},n))});function Twe(e,t,n){const a=qd[t],r=a.findIndex(({vm:u})=>{var c;return((c=u.component)==null?void 0:c.props.id)===e});if(r===-1)return;const{vm:o}=a[r];if(!o)return;n==null||n(o);const i=o.el.offsetHeight,l=t.split("-")[0];a.splice(r,1);const s=a.length;if(!(s<1))for(let u=r;u{t.component.exposed.close()})}function Ewe(e="top-right"){var n,a,r,o;let t=((r=(a=(n=qd[e][0])==null?void 0:n.vm.component)==null?void 0:a.props)==null?void 0:r.offset)||0;for(const{vm:i}of qd[e])i.component.props.offset=t,t+=(((o=i.el)==null?void 0:o.offsetHeight)||0)+pg}pc.closeAll=Mwe;pc.updateOffsets=Ewe;pc._context=null;const Awe=SI(pc,"$notify"),Iwe=$e({...Tc(iv,["ariaLabel","gpuAcceleration","rawContent"]),placement:J4.placement,tabindex:J4.tabindex,effect:{...iv.effect,default:"light"},title:String,width:{type:[String,Number],default:150},offset:{...iv.offset,default:void 0},persistent:{...iv.persistent,default:!0}}),Lwe={"update:visible":e=>vn(e),"before-enter":()=>!0,"before-leave":()=>!0,"after-enter":()=>!0,"after-leave":()=>!0};({...Tc(Hle,["gpuAcceleration"])});var Pwe=q({name:"ElPopover",__name:"popover",props:Iwe,emits:Lwe,setup(e,{expose:t,emit:n}){const a=e,r=n,o=D(()=>{const y=Ma.props;return ji(a,Ae(y)?y:Object.keys(y))}),i=ke("popover"),l=F(),s=D(()=>{var y;return(y=p(l))==null?void 0:y.popperRef}),u=D(()=>[{width:Vn(a.width)},a.popperStyle]),c=D(()=>[i.b(),a.popperClass,{[i.m("plain")]:!!a.content}]),d=D(()=>a.transition===`${i.namespace.value}-fade-in-linear`),v=()=>{var y;(y=l.value)==null||y.hide()},f=()=>{r("before-enter")},h=()=>{r("before-leave")},m=()=>{r("after-enter")},g=()=>{r("update:visible",!1),r("after-leave")};return t({popperRef:s,hide:v}),(y,b)=>(x(),ne(p(Ma),Tt({ref_key:"tooltipRef",ref:l},o.value,{"aria-label":e.title,"popper-class":c.value,"popper-style":u.value,"gpu-acceleration":d.value,onBeforeShow:f,onBeforeHide:h,onShow:m,onHide:g}),{content:$(()=>[e.title?(x(),O("div",{key:0,class:U(p(i).e("title")),role:"title"},Q(e.title),3)):te("v-if",!0),ce(y.$slots,"default",{hide:v},()=>[ue(Q(e.content),1)])]),default:$(()=>[y.$slots.reference?ce(y.$slots,"reference",{key:0}):te("v-if",!0)]),_:3},16,["aria-label","popper-class","popper-style","gpu-acceleration"]))}}),Dwe=Pwe;const ik=(e,t)=>{var a;const n=(a=t.arg||t.value)==null?void 0:a.popperRef;n&&(n.triggerRef=e)};var Owe={mounted(e,t){ik(e,t)},updated(e,t){ik(e,t)}};const $we="popover",TO=iX(Owe,$we),Rwe=wt(Dwe,{directive:TO}),Nwe="2.14.4",Bwe=(e=[])=>({version:Nwe,install:(n,a)=>{n[uC]||(n[uC]=!0,e.forEach(r=>n.use(r)),a&&ew(a,n,!0))}});var Vwe=[uX,mie,Mse,a4e,Dse,Ose,zse,dL,Xse,Zse,xa,yL,Gce,Xce,dde,fde,Tfe,xP,Lfe,Zo,Yde,cw,Ffe,nve,ave,U1,$P,Lve,aue,Bve,Vve,zve,Fve,Hve,Ipe,jP,Vpe,zpe,t0e,nD,h0e,tme,nme,ame,dD,mle,gle,Xe,Ume,fD,Ba,vD,sge,hge,_ge,Rge,Nge,Bge,Vge,Uge,k1e,I1e,Rwe,iL,kw,CP,ofe,rfe,U1e,q1e,Bfe,ni,Bs,eg,Tce,g2e,C2e,x2e,K2e,q2e,zP,Zve,nye,aye,vye,Sbe,Cbe,n4e,y4e,b4e,Cl,Bw,Que,E4e,D4e,O4e,Ma,Y4e,Fw,p_e,L_e,r3e,h3e,V3e,z3e,q3e,X3e,a6e,m6e,I6e,L6e],zwe=[$6e,z6e,We,er,Awe,TO],Xw=Bwe([...Vwe,...zwe]);Xw.install;Xw.version;var Fwe=Xw,Hwe={name:"zh-cn",el:{breadcrumb:{label:"面包屑"},colorpicker:{confirm:"确定",clear:"清空",defaultLabel:"颜色选择器",description:"当前颜色 {color},按 Enter 键选择新颜色",alphaLabel:"选择透明度的值",alphaDescription:"透明度 {alpha}, 当前颜色 {color}",hueLabel:"选择色相值",hueDescription:"色相 {hue}, 当前颜色 {color}",svLabel:"选择饱和度与明度的值",svDescription:"饱和度 {saturation}, 明度 {brightness}, 当前颜色 {color}",predefineDescription:"选择 {value} 作为颜色"},datepicker:{now:"此刻",today:"今天",cancel:"取消",clear:"清空",confirm:"确定",dateTablePrompt:"使用方向键与 Enter 键可选择日期",monthTablePrompt:"使用方向键与 Enter 键可选择月份",yearTablePrompt:"使用方向键与 Enter 键可选择年份",selectedDate:"已选日期",selectDate:"选择日期",selectTime:"选择时间",startDate:"开始日期",startTime:"开始时间",endDate:"结束日期",endTime:"结束时间",prevYear:"前一年",nextYear:"后一年",prevMonth:"上个月",nextMonth:"下个月",year:"年",month1:"1 月",month2:"2 月",month3:"3 月",month4:"4 月",month5:"5 月",month6:"6 月",month7:"7 月",month8:"8 月",month9:"9 月",month10:"10 月",month11:"11 月",month12:"12 月",weeks:{sun:"日",mon:"一",tue:"二",wed:"三",thu:"四",fri:"五",sat:"六"},weeksFull:{sun:"星期日",mon:"星期一",tue:"星期二",wed:"星期三",thu:"星期四",fri:"星期五",sat:"星期六"},months:{jan:"一月",feb:"二月",mar:"三月",apr:"四月",may:"五月",jun:"六月",jul:"七月",aug:"八月",sep:"九月",oct:"十月",nov:"十一月",dec:"十二月"}},inputNumber:{decrease:"减少数值",increase:"增加数值"},select:{loading:"加载中",noMatch:"无匹配数据",noData:"无数据",placeholder:"请选择"},mention:{loading:"加载中"},dropdown:{toggleDropdown:"切换下拉选项"},cascader:{noMatch:"无匹配数据",loading:"加载中",placeholder:"请选择",noData:"暂无数据"},pagination:{goto:"前往",pagesize:"条/页",total:"共 {total} 条",pageClassifier:"页",page:"页",prev:"上一页",next:"下一页",currentPage:"第 {pager} 页",prevPages:"向前 {pager} 页",nextPages:"向后 {pager} 页",deprecationWarning:"你使用了一些已被废弃的用法,请参考 el-pagination 的官方文档"},dialog:{close:"关闭此对话框"},drawer:{close:"关闭此对话框"},messagebox:{title:"提示",confirm:"确定",cancel:"取消",error:"输入的数据不合法!",close:"关闭此对话框"},upload:{deleteTip:"按 Delete 键可删除",delete:"删除",preview:"查看图片",continue:"继续上传"},slider:{defaultLabel:"滑块介于 {min} 至 {max}",defaultRangeStartLabel:"选择起始值",defaultRangeEndLabel:"选择结束值"},table:{emptyText:"暂无数据",confirmFilter:"筛选",resetFilter:"重置",clearFilter:"全部",sumText:"合计",selectAllLabel:"选择所有行",selectRowLabel:"选择当前行",expandRowLabel:"展开当前行",collapseRowLabel:"收起当前行",sortLabel:"按 {column} 排序",filterLabel:"按 {column} 过滤"},tag:{close:"关闭此标签"},tour:{next:"下一步",previous:"上一步",finish:"结束导览",close:"关闭此对话框"},tree:{emptyText:"暂无数据"},transfer:{noMatch:"无匹配数据",noData:"无数据",titles:["列表 1","列表 2"],filterPlaceholder:"请输入搜索内容",noCheckedFormat:"共 {total} 项",hasCheckedFormat:"已选 {checked}/{total} 项"},image:{error:"加载失败"},pageHeader:{title:"返回"},popconfirm:{confirmButtonText:"确定",cancelButtonText:"取消"},carousel:{leftArrow:"上一张幻灯片",rightArrow:"下一张幻灯片",indicator:"幻灯片切换至索引 {index}"},inputOTP:{groupLabel:"一次性密码输入框",defaultLabel:"请输入第 {index} 位 OTP 字符"}}};const fr=(e,t)=>{const n=e.__vccOpts||e;for(const[a,r]of t)n[a]=r;return n},Wwe={class:"login-page"},Kwe={class:"login-card"},Uwe={class:"login-header"},Gwe={class:"login-sub"},Ywe={key:0,class:"login-error"},jwe={key:0,class:"login-error"},qwe={__name:"Login",emits:["login-success"],setup(e,{emit:t}){const n=t,a=nn({username:"",password:""}),r=F(!1),o=F(""),i=F(!1);mt(async()=>{var u;try{const d=await(await fetch("/api/auth/setup-status")).json();i.value=((u=d.data)==null?void 0:u.setup_needed)??!1}catch{}});async function l(){if(!a.username||!a.password){o.value="请输入用户名和密码";return}r.value=!0,o.value="";try{const u=await fetch("/api/auth/login",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(a)}),c=await u.json();if(!u.ok){o.value=c.error||"登录失败";return}sessionStorage.setItem("admin_token",c.data.token),sessionStorage.setItem("admin_username",a.username),We.success("登录成功"),n("login-success")}catch(u){o.value="网络错误: "+u.message}finally{r.value=!1}}async function s(){if(!a.username||a.username.length<3){o.value="用户名至少 3 位";return}if(!a.password||a.password.length<8){o.value="密码至少 8 位";return}r.value=!0,o.value="";try{const u=await fetch("/api/auth/register",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({username:a.username,password:a.password})}),c=await u.json();if(!u.ok){o.value=c.error||"注册失败";return}We.success("管理员创建成功"),i.value=!1,r.value=!1,await l()}catch(u){o.value="网络错误: "+u.message}finally{r.value=!1}}return(u,c)=>{const d=be("el-alert"),v=be("el-input"),f=be("el-form-item"),h=be("el-button"),m=be("el-icon"),g=be("el-form");return x(),O("div",Wwe,[C("div",Kwe,[C("div",Uwe,[c[4]||(c[4]=_7('

PayGateway

',2)),C("p",Gwe,Q(i.value?"首次启动 · 初始化管理员":"支付网关管理控制台"),1)]),i.value?(x(),ne(g,{key:0,onSubmit:at(s,["prevent"]),model:a,size:"large"},{default:$(()=>[L(d,{type:"info",closable:!1,class:"mb12",style:{"font-size":"12px"},title:"首次启动:设置管理员账号与密码。凭据经 bcrypt 哈希加密后存入数据库,登录后台后可随时管理。"}),L(f,null,{default:$(()=>[L(v,{modelValue:a.username,"onUpdate:modelValue":c[0]||(c[0]=y=>a.username=y),placeholder:"管理员用户名(3-32 位)","prefix-icon":"User",onKeyup:kn(s,["enter"]),size:"large",class:"login-input"},null,8,["modelValue"])]),_:1}),L(f,null,{default:$(()=>[L(v,{modelValue:a.password,"onUpdate:modelValue":c[1]||(c[1]=y=>a.password=y),type:"password",placeholder:"管理员密码(至少 8 位)","prefix-icon":"Lock","show-password":"",onKeyup:kn(s,["enter"]),size:"large",class:"login-input"},null,8,["modelValue"])]),_:1}),L(f,{style:{"margin-bottom":"0","margin-top":"4px"}},{default:$(()=>[L(h,{type:"primary",loading:r.value,class:"login-btn",onClick:s,style:{width:"100%"},size:"large"},{default:$(()=>[ue(Q(r.value?"初始化中...":"创建管理员并登录"),1)]),_:1},8,["loading"])]),_:1}),o.value?(x(),O("div",Ywe,[L(m,{size:13},{default:$(()=>[L(p(fc))]),_:1}),ue(" "+Q(o.value),1)])):te("",!0)]),_:1},8,["model"])):(x(),ne(g,{key:1,onSubmit:at(l,["prevent"]),model:a,size:"large"},{default:$(()=>[L(f,null,{default:$(()=>[L(v,{modelValue:a.username,"onUpdate:modelValue":c[2]||(c[2]=y=>a.username=y),placeholder:"用户名","prefix-icon":"User",onKeyup:kn(l,["enter"]),size:"large",class:"login-input"},null,8,["modelValue"])]),_:1}),L(f,null,{default:$(()=>[L(v,{modelValue:a.password,"onUpdate:modelValue":c[3]||(c[3]=y=>a.password=y),type:"password",placeholder:"密码","prefix-icon":"Lock","show-password":"",onKeyup:kn(l,["enter"]),size:"large",class:"login-input"},null,8,["modelValue"])]),_:1}),L(f,{style:{"margin-bottom":"0","margin-top":"4px"}},{default:$(()=>[L(h,{type:"primary",loading:r.value,class:"login-btn",onClick:l,style:{width:"100%"},size:"large"},{default:$(()=>[ue(Q(r.value?"登录中...":"登 录"),1)]),_:1},8,["loading"])]),_:1}),o.value?(x(),O("div",jwe,[L(m,{size:13},{default:$(()=>[L(p(fc))]),_:1}),ue(" "+Q(o.value),1)])):te("",!0)]),_:1},8,["model"]))])])}}},Xwe=fr(qwe,[["__scopeId","data-v-40e3134b"]]);/*! ***************************************************************************** +Copyright (c) Microsoft Corporation. + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR +OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. +***************************************************************************** */var d_=function(e,t){return d_=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(n,a){n.__proto__=a}||function(n,a){for(var r in a)Object.prototype.hasOwnProperty.call(a,r)&&(n[r]=a[r])},d_(e,t)};function At(e,t){if(typeof t!="function"&&t!==null)throw new TypeError("Class extends value "+String(t)+" is not a constructor or null");d_(e,t);function n(){this.constructor=e}e.prototype=t===null?Object.create(t):(n.prototype=t.prototype,new n)}var Zwe=function(){function e(){this.firefox=!1,this.ie=!1,this.edge=!1,this.newEdge=!1,this.weChat=!1}return e}(),Jwe=function(){function e(){this.browser=new Zwe,this.node=!1,this.wxa=!1,this.worker=!1,this.svgSupported=!1,this.touchEventsSupported=!1,this.pointerEventsSupported=!1,this.domSupported=!1,this.transformSupported=!1,this.transform3dSupported=!1,this.hasGlobalWindow=typeof window<"u"}return e}(),hn=new Jwe;typeof wx=="object"&&typeof wx.getSystemInfoSync=="function"?(hn.wxa=!0,hn.touchEventsSupported=!0):typeof document>"u"&&typeof self<"u"?hn.worker=!0:!hn.hasGlobalWindow||"Deno"in window||typeof navigator<"u"&&typeof navigator.userAgent=="string"&&navigator.userAgent.indexOf("Node.js")>-1?(hn.node=!0,hn.svgSupported=!0):Qwe(navigator.userAgent,hn);function Qwe(e,t){var n=t.browser,a=e.match(/Firefox\/([\d.]+)/),r=e.match(/MSIE\s([\d.]+)/)||e.match(/Trident\/.+?rv:(([\d.]+))/),o=e.match(/Edge?\/([\d.]+)/),i=/micromessenger/i.test(e);a&&(n.firefox=!0,n.version=a[1]),r&&(n.ie=!0,n.version=r[1]),o&&(n.edge=!0,n.version=o[1],n.newEdge=+o[1].split(".")[0]>18),i&&(n.weChat=!0),t.svgSupported=typeof SVGRect<"u",t.touchEventsSupported="ontouchstart"in window&&!n.ie&&!n.edge,t.pointerEventsSupported="onpointerdown"in window&&(n.edge||n.ie&&+n.version>=11);var l=t.domSupported=typeof document<"u";if(l){var s=document.documentElement.style;t.transform3dSupported=(n.ie&&"transition"in s||n.edge||"WebKitCSSMatrix"in window&&"m11"in new WebKitCSSMatrix||"MozPerspective"in s)&&!("OTransition"in s),t.transformSupported=t.transform3dSupported||n.ie&&+n.version>=9}}var Zw=12,e8e="sans-serif",Vs=Zw+"px "+e8e,t8e=20,n8e=100,a8e="007LLmW'55;N0500LLLLLLLLLL00NNNLzWW\\\\WQb\\0FWLg\\bWb\\WQ\\WrWWQ000CL5LLFLL0LL**F*gLLLL5F0LF\\FFF5.5N";function r8e(e){var t={};if(typeof JSON>"u")return t;for(var n=0;n=0)l=i*n.length;else for(var s=0;s=u8e&&(yy=0),yy++}function e8(){for(var e=[],t=0;t>1)%2;l.cssText=["position: absolute","visibility: hidden","padding: 0","margin: 0","border-width: 0","user-select: none","width:0","height:0",a[s]+":0",r[u]+":0",a[1-s]+":auto",r[1-u]+":auto",""].join("!important;"),e.appendChild(i),n.push(i)}return t.clearMarkers=function(){we(n,function(c){c.parentNode&&c.parentNode.removeChild(c)})},n}function L8e(e,t,n){for(var a=n?"invTrans":"trans",r=t[a],o=t.srcCoords,i=[],l=[],s=!0,u=0;u<4;u++){var c=e[u].getBoundingClientRect(),d=2*u,v=c.left,f=c.top;i.push(v,f),s=s&&o&&v===o[d]&&f===o[d+1],l.push(e[u].offsetLeft,e[u].offsetTop)}return s&&r?r:(t.srcCoords=i,t[a]=n?ck(l,i):ck(i,l))}function DO(e){return e.nodeName.toUpperCase()==="CANVAS"}var P8e=/([&<>"'])/g,D8e={"&":"&","<":"<",">":">",'"':""","'":"'"};function Or(e){return e==null?"":(e+"").replace(P8e,function(t,n){return D8e[n]})}var O8e=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Cy=[],$8e=hn.browser.firefox&&+hn.browser.version.split(".")[0]<39;function y_(e,t,n,a){return n=n||{},a?dk(e,t,n):$8e&&t.layerX!=null&&t.layerX!==t.offsetX?(n.zrX=t.layerX,n.zrY=t.layerY):t.offsetX!=null?(n.zrX=t.offsetX,n.zrY=t.offsetY):dk(e,t,n),n}function dk(e,t,n){if(hn.domSupported&&e.getBoundingClientRect){var a=t.clientX,r=t.clientY;if(DO(e)){var o=e.getBoundingClientRect();n.zrX=a-o.left,n.zrY=r-o.top;return}else if(g_(Cy,e,a,r)){n.zrX=Cy[0],n.zrY=Cy[1];return}}n.zrX=n.zrY=0}function a8(e){return e||window.event}function go(e,t,n){if(t=a8(t),t.zrX!=null)return t;var a=t.type,r=a&&a.indexOf("touch")>=0;if(r){var i=a!=="touchend"?t.targetTouches[0]:t.changedTouches[0];i&&y_(e,i,t,n)}else{y_(e,t,t,n);var o=R8e(t);t.zrDelta=o?o/120:-(t.detail||0)/3}var l=t.button;return t.which==null&&l!==void 0&&O8e.test(t.type)&&(t.which=l&1?1:l&2?3:l&4?2:0),t}function R8e(e){var t=e.wheelDelta;if(t)return t;var n=e.deltaX,a=e.deltaY;if(n==null||a==null)return t;var r=Math.abs(a!==0?a:n),o=a>0?-1:a<0?1:n>0?-1:1;return 3*r*o}function N8e(e,t,n,a){e.addEventListener(t,n,a)}function B8e(e,t,n,a){e.removeEventListener(t,n,a)}var OO=function(e){e.preventDefault(),e.stopPropagation(),e.cancelBubble=!0},V8e=function(){function e(){this._track=[]}return e.prototype.recognize=function(t,n,a){return this._doTrack(t,n,a),this._recognize(t)},e.prototype.clear=function(){return this._track.length=0,this},e.prototype._doTrack=function(t,n,a){var r=t.touches;if(r){for(var o={points:[],touches:[],target:n,event:t},i=0,l=r.length;i1&&a&&a.length>1){var o=fk(a)/fk(r);!isFinite(o)&&(o=1),t.pinchScale=o;var i=z8e(a);return t.pinchX=i[0],t.pinchY=i[1],{type:"pinch",target:e[0].target,event:t}}}}};function zi(){return[1,0,0,1,0,0]}function hp(e){return e[0]=1,e[1]=0,e[2]=0,e[3]=1,e[4]=0,e[5]=0,e}function r8(e,t){return e[0]=t[0],e[1]=t[1],e[2]=t[2],e[3]=t[3],e[4]=t[4],e[5]=t[5],e}function $v(e,t,n){var a=t[0]*n[0]+t[2]*n[1],r=t[1]*n[0]+t[3]*n[1],o=t[0]*n[2]+t[2]*n[3],i=t[1]*n[2]+t[3]*n[3],l=t[0]*n[4]+t[2]*n[5]+t[4],s=t[1]*n[4]+t[3]*n[5]+t[5];return e[0]=a,e[1]=r,e[2]=o,e[3]=i,e[4]=l,e[5]=s,e}function b_(e,t,n){return e[0]=t[0],e[1]=t[1],e[2]=t[2],e[3]=t[3],e[4]=t[4]+n[0],e[5]=t[5]+n[1],e}function o8(e,t,n,a){a===void 0&&(a=[0,0]);var r=t[0],o=t[2],i=t[4],l=t[1],s=t[3],u=t[5],c=Math.sin(n),d=Math.cos(n);return e[0]=r*d+l*c,e[1]=-r*c+l*d,e[2]=o*d+s*c,e[3]=-o*c+d*s,e[4]=d*(i-a[0])+c*(u-a[1])+a[0],e[5]=d*(u-a[1])-c*(i-a[0])+a[1],e}function F8e(e,t,n){var a=n[0],r=n[1];return e[0]=t[0]*a,e[1]=t[1]*r,e[2]=t[2]*a,e[3]=t[3]*r,e[4]=t[4]*a,e[5]=t[5]*r,e}function pp(e,t){var n=t[0],a=t[2],r=t[4],o=t[1],i=t[3],l=t[5],s=n*i-o*a;return s?(s=1/s,e[0]=i*s,e[1]=-o*s,e[2]=-a*s,e[3]=n*s,e[4]=(a*l-i*r)*s,e[5]=(o*r-n*l)*s,e):null}var fn=function(){function e(t,n){this.x=t||0,this.y=n||0}return e.prototype.copy=function(t){return this.x=t.x,this.y=t.y,this},e.prototype.clone=function(){return new e(this.x,this.y)},e.prototype.set=function(t,n){return this.x=t,this.y=n,this},e.prototype.equal=function(t){return t.x===this.x&&t.y===this.y},e.prototype.add=function(t){return this.x+=t.x,this.y+=t.y,this},e.prototype.scale=function(t){this.x*=t,this.y*=t},e.prototype.scaleAndAdd=function(t,n){this.x+=t.x*n,this.y+=t.y*n},e.prototype.sub=function(t){return this.x-=t.x,this.y-=t.y,this},e.prototype.dot=function(t){return this.x*t.x+this.y*t.y},e.prototype.len=function(){return Math.sqrt(this.x*this.x+this.y*this.y)},e.prototype.lenSquare=function(){return this.x*this.x+this.y*this.y},e.prototype.normalize=function(){var t=this.len();return this.x/=t,this.y/=t,this},e.prototype.distance=function(t){var n=this.x-t.x,a=this.y-t.y;return Math.sqrt(n*n+a*a)},e.prototype.distanceSquare=function(t){var n=this.x-t.x,a=this.y-t.y;return n*n+a*a},e.prototype.negate=function(){return this.x=-this.x,this.y=-this.y,this},e.prototype.transform=function(t){if(t){var n=this.x,a=this.y;return this.x=t[0]*n+t[2]*a+t[4],this.y=t[1]*n+t[3]*a+t[5],this}},e.prototype.toArray=function(t){return t[0]=this.x,t[1]=this.y,t},e.prototype.fromArray=function(t){this.x=t[0],this.y=t[1]},e.set=function(t,n,a){t.x=n,t.y=a},e.copy=function(t,n){t.x=n.x,t.y=n.y},e.len=function(t){return Math.sqrt(t.x*t.x+t.y*t.y)},e.lenSquare=function(t){return t.x*t.x+t.y*t.y},e.dot=function(t,n){return t.x*n.x+t.y*n.y},e.add=function(t,n,a){t.x=n.x+a.x,t.y=n.y+a.y},e.sub=function(t,n,a){t.x=n.x-a.x,t.y=n.y-a.y},e.scale=function(t,n,a){t.x=n.x*a,t.y=n.y*a},e.scaleAndAdd=function(t,n,a,r){t.x=n.x+a.x*r,t.y=n.y+a.y*r},e.lerp=function(t,n,a,r){var o=1-r;t.x=o*n.x+r*a.x,t.y=o*n.y+r*a.y},e}(),Vu=Math.min,cd=Math.max,__=Math.abs,vk=["x","y"],H8e=["width","height"],nu=new fn,au=new fn,ru=new fn,ou=new fn,Zr=$O(),cv=Zr.minTv,w_=Zr.maxTv,Rv=[0,0],ln=function(){function e(t,n,a,r){ky(this,t,n,a,r)}return e.set=function(t,n,a,r,o){return r<0&&(n=n+r,r=-r),o<0&&(a=a+o,o=-o),t.x=n,t.y=a,t.width=r,t.height=o,t},e.prototype.union=function(t){var n=Vu(t.x,this.x),a=Vu(t.y,this.y);isFinite(this.x)&&isFinite(this.width)?this.width=cd(t.x+t.width,this.x+this.width)-n:this.width=t.width,isFinite(this.y)&&isFinite(this.height)?this.height=cd(t.y+t.height,this.y+this.height)-a:this.height=t.height,this.x=n,this.y=a},e.prototype.applyTransform=function(t){e.applyTransform(this,this,t)},e.prototype.calculateTransform=function(t){return W8e(zi(),this,t)},e.prototype.intersect=function(t,n,a){return e.intersect(this,t,n,a)},e.intersect=function(t,n,a,r){a&&fn.set(a,0,0);var o=r&&r.outIntersectRect||null,i=r&&r.clamp;if(o&&(o.x=o.y=o.width=o.height=NaN),!t||!n)return!1;t instanceof e||(t=ky(K8e,t.x,t.y,t.width,t.height)),n instanceof e||(n=ky(U8e,n.x,n.y,n.width,n.height));var l=!!a;Zr.reset(r,l);var s=Zr.touchThreshold,u=t.x+s,c=t.x+t.width-s,d=t.y+s,v=t.y+t.height-s,f=n.x+s,h=n.x+n.width-s,m=n.y+s,g=n.y+n.height-s;if(u>c||d>v||f>h||m>g)return!1;var y=!(c=t.x&&n<=t.x+t.width&&a>=t.y&&a<=t.y+t.height},e.prototype.contain=function(t,n){return e.contain(this,t,n)},e.prototype.clone=function(){return new e(this.x,this.y,this.width,this.height)},e.prototype.copy=function(t){hk(this,t)},e.prototype.plain=function(){return{x:this.x,y:this.y,width:this.width,height:this.height}},e.prototype.isFinite=function(){return isFinite(this.x)&&isFinite(this.y)&&isFinite(this.width)&&isFinite(this.height)},e.prototype.isZero=function(){return this.width===0||this.height===0},e.create=function(t){return new e(t?t.x:0,t?t.y:0,t?t.width:0,t?t.height:0)},e.copy=function(t,n){return t.x=n.x,t.y=n.y,t.width=n.width,t.height=n.height,t},e.applyTransform=function(t,n,a){if(!a){t!==n&&hk(t,n);return}if(a[1]<1e-5&&a[1]>-1e-5&&a[2]<1e-5&&a[2]>-1e-5){var r=a[0],o=a[3],i=a[4],l=a[5];t.x=n.x*r+i,t.y=n.y*o+l,t.width=n.width*r,t.height=n.height*o,t.width<0&&(t.x+=t.width,t.width=-t.width),t.height<0&&(t.y+=t.height,t.height=-t.height);return}nu.x=ru.x=n.x,nu.y=ou.y=n.y,au.x=ou.x=n.x+n.width,au.y=ru.y=n.y+n.height,nu.transform(a),ou.transform(a),au.transform(a),ru.transform(a),t.x=Vu(nu.x,au.x,ru.x,ou.x),t.y=Vu(nu.y,au.y,ru.y,ou.y);var s=cd(nu.x,au.x,ru.x,ou.x),u=cd(nu.y,au.y,ru.y,ou.y);t.width=s-t.x,t.height=u-t.y},e.calculateTransform=function(t,n,a){var r=a.width/n.width,o=a.height/n.height;return t=hp(t||[]),b_(t,t,by(Ty,-n.x,-n.y)),F8e(t,t,by(Ty,r,o)),b_(t,t,by(Ty,a.x,a.y)),t},e}();ln.create;var ky=ln.set,hk=ln.copy,W8e=ln.calculateTransform;ln.applyTransform;ln.contain;var K8e=new ln(0,0,0,0),U8e=new ln(0,0,0,0),Ty=[];function pk(e,t,n,a,r,o,i,l){var s=__(t-n),u=__(a-e),c=Vu(s,u),d=vk[r],v=vk[1-r],f=H8e[r];t=u||!Zr.bidirectional)&&(cv[d]=-u,cv[v]=0,Zr.useDir&&Zr.calcDirMTV())))}function $O(){var e=0,t=new fn,n=new fn,a={minTv:new fn,maxTv:new fn,useDir:!1,dirMinTv:new fn,touchThreshold:0,bidirectional:!0,negativeSize:!1,reset:function(o,i){a.touchThreshold=0,o&&o.touchThreshold!=null&&(a.touchThreshold=cd(0,o.touchThreshold)),a.negativeSize=!1,i&&(a.minTv.set(1/0,1/0),a.maxTv.set(0,0),a.useDir=!1,o&&o.direction!=null&&(a.useDir=!0,a.dirMinTv.copy(a.minTv),n.copy(a.minTv),e=o.direction,a.bidirectional=o.bidirectional==null||!!o.bidirectional,a.bidirectional||t.set(Math.cos(e),Math.sin(e))))},calcDirMTV:function(){var o=a.minTv,i=a.dirMinTv,l=o.y*o.y+o.x*o.x,s=Math.sin(e),u=Math.cos(e),c=s*o.y+u*o.x;if(r(c)){r(o.x)&&r(o.y)&&i.set(0,0);return}if(n.x=l*u/c,n.y=l*s/c,r(n.x)&&r(n.y)){i.set(0,0);return}(a.bidirectional||t.dot(n)>0)&&n.len()=0;d--){var v=o[d];v!==r&&!v.ignore&&!v.ignoreCoarsePointer&&(!v.parent||!v.parent.ignoreCoarsePointer)&&(My.copy(v.getBoundingRect()),v.transform&&My.applyTransform(v.transform),My.intersect(c)&&l.push(v))}if(l.length)for(var f=4,h=Math.PI/12,m=Math.PI*2,g=0;g4)return;this._downPoint=null}this.dispatchToElement(o,e,t)}});function X8e(e,t,n){if(e[e.rectHover?"rectContain":"contain"](t,n)){for(var a=e,r=void 0,o=!1;a;){if(a.ignoreClip&&(o=!0),!o){var i=a.getClipPath();if(i&&!i.contain(t,n))return!1}a.silent&&(r=!0);var l=a.__hostTarget;a=l?a.ignoreHostSilent?null:l:a.parent}return r?RO:!0}return!1}function mk(e,t,n,a,r){for(var o=e.length-1;o>=0;o--){var i=e[o],l=void 0;if(i!==r&&!i.ignore&&(l=X8e(i,n,a))&&(!t.topTarget&&(t.topTarget=i),l!==RO)){t.target=i;break}}}function BO(e,t,n){var a=e.painter;return t<0||t>a.getWidth()||n<0||n>a.getHeight()}var VO=32,Nf=7;function Z8e(e){for(var t=0;e>=VO;)t|=e&1,e>>=1;return e+t}function gk(e,t,n,a){var r=t+1;if(r===n)return 1;if(a(e[r++],e[t])<0){for(;r=0;)r++;return r-t}function J8e(e,t,n){for(n--;t>>1,r(o,e[s])<0?l=s:i=s+1;var u=a-i;switch(u){case 3:e[i+3]=e[i+2];case 2:e[i+2]=e[i+1];case 1:e[i+1]=e[i];break;default:for(;u>0;)e[i+u]=e[i+u-1],u--}e[i]=o}}function Ey(e,t,n,a,r,o){var i=0,l=0,s=1;if(o(e,t[n+r])>0){for(l=a-r;s0;)i=s,s=(s<<1)+1,s<=0&&(s=l);s>l&&(s=l),i+=r,s+=r}else{for(l=r+1;sl&&(s=l);var u=i;i=r-s,s=r-u}for(i++;i>>1);o(e,t[n+c])>0?i=c+1:s=c}return s}function Ay(e,t,n,a,r,o){var i=0,l=0,s=1;if(o(e,t[n+r])<0){for(l=r+1;sl&&(s=l);var u=i;i=r-s,s=r-u}else{for(l=a-r;s=0;)i=s,s=(s<<1)+1,s<=0&&(s=l);s>l&&(s=l),i+=r,s+=r}for(i++;i>>1);o(e,t[n+c])<0?s=c:i=c+1}return s}function Q8e(e,t){var n=Nf,a,r,o=0,i=[];a=[],r=[];function l(f,h){a[o]=f,r[o]=h,o+=1}function s(){for(;o>1;){var f=o-2;if(f>=1&&r[f-1]<=r[f]+r[f+1]||f>=2&&r[f-2]<=r[f]+r[f-1])r[f-1]r[f+1])break;c(f)}}function u(){for(;o>1;){var f=o-2;f>0&&r[f-1]=Nf||T>=Nf);if(M)break;S<0&&(S=0),S+=2}if(n=S,n<1&&(n=1),h===1){for(y=0;y=0;y--)e[k+y]=e[S+y];e[w]=i[_];return}for(var T=n;;){var M=0,E=0,A=!1;do if(t(i[_],e[b])<0){if(e[w--]=e[b--],M++,E=0,--h===0){A=!0;break}}else if(e[w--]=i[_--],E++,M=0,--g===1){A=!0;break}while((M|E)=0;y--)e[k+y]=e[S+y];if(h===0){A=!0;break}}if(e[w--]=i[_--],--g===1){A=!0;break}if(E=g-Ey(e[b],i,0,g,g-1,t),E!==0){for(w-=E,_-=E,g-=E,k=w+1,S=_+1,y=0;y=Nf||E>=Nf);if(A)break;T<0&&(T=0),T+=2}if(n=T,n<1&&(n=1),g===1){for(w-=h,b-=h,k=w+1,S=b+1,y=h-1;y>=0;y--)e[k+y]=e[S+y];e[w]=i[_]}else{if(g===0)throw new Error;for(S=w-(g-1),y=0;yl&&(s=l),yk(e,n,n+s,n+o,t),o=s}i.pushRun(n,o),i.mergeRuns(),r-=o,n+=o}while(r!==0);i.forceMergeRuns()}}var Nr=1,dv=2,rd=4,bk=!1;function Iy(){bk||(bk=!0,console.warn("z / z2 / zlevel of displayable is invalid, which may cause unexpected errors"))}function _k(e,t){return e.zlevel===t.zlevel?e.z===t.z?e.z2-t.z2:e.z-t.z:e.zlevel-t.zlevel}var eSe=function(){function e(){this._roots=[],this._displayList=[],this._displayListLen=0,this.displayableSortFunc=_k}return e.prototype.traverse=function(t,n){for(var a=0;a=0&&this._roots.splice(r,1)},e.prototype.delAllRoots=function(){this._roots=[],this._displayList=[],this._displayListLen=0},e.prototype.getRoots=function(){return this._roots},e.prototype.dispose=function(){this._displayList=null,this._roots=null},e}(),yg;yg=hn.hasGlobalWindow&&(window.requestAnimationFrame&&window.requestAnimationFrame.bind(window)||window.msRequestAnimationFrame&&window.msRequestAnimationFrame.bind(window)||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame)||function(e){return setTimeout(e,16)};var Nv={linear:function(e){return e},quadraticIn:function(e){return e*e},quadraticOut:function(e){return e*(2-e)},quadraticInOut:function(e){return(e*=2)<1?.5*e*e:-.5*(--e*(e-2)-1)},cubicIn:function(e){return e*e*e},cubicOut:function(e){return--e*e*e+1},cubicInOut:function(e){return(e*=2)<1?.5*e*e*e:.5*((e-=2)*e*e+2)},quarticIn:function(e){return e*e*e*e},quarticOut:function(e){return 1- --e*e*e*e},quarticInOut:function(e){return(e*=2)<1?.5*e*e*e*e:-.5*((e-=2)*e*e*e-2)},quinticIn:function(e){return e*e*e*e*e},quinticOut:function(e){return--e*e*e*e*e+1},quinticInOut:function(e){return(e*=2)<1?.5*e*e*e*e*e:.5*((e-=2)*e*e*e*e+2)},sinusoidalIn:function(e){return 1-Math.cos(e*Math.PI/2)},sinusoidalOut:function(e){return Math.sin(e*Math.PI/2)},sinusoidalInOut:function(e){return .5*(1-Math.cos(Math.PI*e))},exponentialIn:function(e){return e===0?0:Math.pow(1024,e-1)},exponentialOut:function(e){return e===1?1:1-Math.pow(2,-10*e)},exponentialInOut:function(e){return e===0?0:e===1?1:(e*=2)<1?.5*Math.pow(1024,e-1):.5*(-Math.pow(2,-10*(e-1))+2)},circularIn:function(e){return 1-Math.sqrt(1-e*e)},circularOut:function(e){return Math.sqrt(1- --e*e)},circularInOut:function(e){return(e*=2)<1?-.5*(Math.sqrt(1-e*e)-1):.5*(Math.sqrt(1-(e-=2)*e)+1)},elasticIn:function(e){var t,n=.1,a=.4;return e===0?0:e===1?1:(!n||n<1?(n=1,t=a/4):t=a*Math.asin(1/n)/(2*Math.PI),-(n*Math.pow(2,10*(e-=1))*Math.sin((e-t)*(2*Math.PI)/a)))},elasticOut:function(e){var t,n=.1,a=.4;return e===0?0:e===1?1:(!n||n<1?(n=1,t=a/4):t=a*Math.asin(1/n)/(2*Math.PI),n*Math.pow(2,-10*e)*Math.sin((e-t)*(2*Math.PI)/a)+1)},elasticInOut:function(e){var t,n=.1,a=.4;return e===0?0:e===1?1:(!n||n<1?(n=1,t=a/4):t=a*Math.asin(1/n)/(2*Math.PI),(e*=2)<1?-.5*(n*Math.pow(2,10*(e-=1))*Math.sin((e-t)*(2*Math.PI)/a)):n*Math.pow(2,-10*(e-=1))*Math.sin((e-t)*(2*Math.PI)/a)*.5+1)},backIn:function(e){var t=1.70158;return e*e*((t+1)*e-t)},backOut:function(e){var t=1.70158;return--e*e*((t+1)*e+t)+1},backInOut:function(e){var t=2.5949095;return(e*=2)<1?.5*(e*e*((t+1)*e-t)):.5*((e-=2)*e*((t+1)*e+t)+2)},bounceIn:function(e){return 1-Nv.bounceOut(1-e)},bounceOut:function(e){return e<1/2.75?7.5625*e*e:e<2/2.75?7.5625*(e-=1.5/2.75)*e+.75:e<2.5/2.75?7.5625*(e-=2.25/2.75)*e+.9375:7.5625*(e-=2.625/2.75)*e+.984375},bounceInOut:function(e){return e<.5?Nv.bounceIn(e*2)*.5:Nv.bounceOut(e*2-1)*.5+.5}},Jp=Math.pow,ks=Math.sqrt,bg=1e-8,zO=1e-4,wk=ks(3),Qp=1/3,Ei=yf(),wo=yf(),kd=yf();function ys(e){return e>-bg&&ebg||e<-bg}function Ua(e,t,n,a,r){var o=1-r;return o*o*(o*e+3*r*t)+r*r*(r*a+3*o*n)}function Sk(e,t,n,a,r){var o=1-r;return 3*(((t-e)*o+2*(n-t)*r)*o+(a-n)*r*r)}function _g(e,t,n,a,r,o){var i=a+3*(t-n)-e,l=3*(n-t*2+e),s=3*(t-e),u=e-r,c=l*l-3*i*s,d=l*s-9*i*u,v=s*s-3*l*u,f=0;if(ys(c)&&ys(d))if(ys(l))o[0]=0;else{var h=-s/l;h>=0&&h<=1&&(o[f++]=h)}else{var m=d*d-4*c*v;if(ys(m)){var g=d/c,h=-l/i+g,y=-g/2;h>=0&&h<=1&&(o[f++]=h),y>=0&&y<=1&&(o[f++]=y)}else if(m>0){var b=ks(m),_=c*l+1.5*i*(-d+b),w=c*l+1.5*i*(-d-b);_<0?_=-Jp(-_,Qp):_=Jp(_,Qp),w<0?w=-Jp(-w,Qp):w=Jp(w,Qp);var h=(-l-(_+w))/(3*i);h>=0&&h<=1&&(o[f++]=h)}else{var S=(2*c*l-3*i*d)/(2*ks(c*c*c)),k=Math.acos(S)/3,T=ks(c),M=Math.cos(k),h=(-l-2*T*M)/(3*i),y=(-l+T*(M+wk*Math.sin(k)))/(3*i),E=(-l+T*(M-wk*Math.sin(k)))/(3*i);h>=0&&h<=1&&(o[f++]=h),y>=0&&y<=1&&(o[f++]=y),E>=0&&E<=1&&(o[f++]=E)}}return f}function HO(e,t,n,a,r){var o=6*n-12*t+6*e,i=9*t+3*a-3*e-9*n,l=3*t-3*e,s=0;if(ys(i)){if(FO(o)){var u=-l/o;u>=0&&u<=1&&(r[s++]=u)}}else{var c=o*o-4*i*l;if(ys(c))r[0]=-o/(2*i);else if(c>0){var d=ks(c),u=(-o+d)/(2*i),v=(-o-d)/(2*i);u>=0&&u<=1&&(r[s++]=u),v>=0&&v<=1&&(r[s++]=v)}}return s}function wg(e,t,n,a,r,o){var i=(t-e)*r+e,l=(n-t)*r+t,s=(a-n)*r+n,u=(l-i)*r+i,c=(s-l)*r+l,d=(c-u)*r+u;o[0]=e,o[1]=i,o[2]=u,o[3]=d,o[4]=d,o[5]=c,o[6]=s,o[7]=a}function tSe(e,t,n,a,r,o,i,l,s,u,c){var d,v=.005,f=1/0,h,m,g,y;Ei[0]=s,Ei[1]=u;for(var b=0;b<1;b+=.05)wo[0]=Ua(e,n,r,i,b),wo[1]=Ua(t,a,o,l,b),g=xd(Ei,wo),g=0&&g=0&&u<=1&&(r[s++]=u)}}else{var c=i*i-4*o*l;if(ys(c)){var u=-i/(2*o);u>=0&&u<=1&&(r[s++]=u)}else if(c>0){var d=ks(c),u=(-i+d)/(2*o),v=(-i-d)/(2*o);u>=0&&u<=1&&(r[s++]=u),v>=0&&v<=1&&(r[s++]=v)}}return s}function WO(e,t,n){var a=e+n-2*t;return a===0?.5:(e-t)/a}function Sg(e,t,n,a,r){var o=(t-e)*a+e,i=(n-t)*a+t,l=(i-o)*a+o;r[0]=e,r[1]=o,r[2]=l,r[3]=l,r[4]=i,r[5]=n}function rSe(e,t,n,a,r,o,i,l,s){var u,c=.005,d=1/0;Ei[0]=i,Ei[1]=l;for(var v=0;v<1;v+=.05){wo[0]=$r(e,n,r,v),wo[1]=$r(t,a,o,v);var f=xd(Ei,wo);f=0&&f=1?1:_g(0,a,o,1,s,l)&&Ua(0,r,i,1,l[0])}}}var lSe=function(){function e(t){this._inited=!1,this._startTime=0,this._pausedTime=0,this._paused=!1,this._life=t.life||1e3,this._delay=t.delay||0,this.loop=t.loop||!1,this.onframe=t.onframe||lr,this.ondestroy=t.ondestroy||lr,this.onrestart=t.onrestart||lr,t.easing&&this.setEasing(t.easing)}return e.prototype.step=function(t,n){if(this._inited||(this._startTime=t+this._delay,this._inited=!0),this._paused){this._pausedTime+=n;return}var a=this._life,r=t-this._startTime-this._pausedTime,o=r/a;o<0&&(o=0),o=Math.min(o,1);var i=this.easingFunc,l=i?i(o):o;if(this.onframe(l),o===1)if(this.loop){var s=r%a;this._startTime=t-s,this._pausedTime=0,this.onrestart()}else return!0;return!1},e.prototype.pause=function(){this._paused=!0},e.prototype.resume=function(){this._paused=!1},e.prototype.setEasing=function(t){this.easing=t,this.easingFunc=dn(t)?t:Nv[t]||KO(t)},e}(),UO=function(){function e(t){this.value=t}return e}(),sSe=function(){function e(){this._len=0}return e.prototype.insert=function(t){var n=new UO(t);return this.insertEntry(n),n},e.prototype.insertEntry=function(t){this.head?(this.tail.next=t,t.prev=this.tail,t.next=null,this.tail=t):this.head=this.tail=t,this._len++},e.prototype.remove=function(t){var n=t.prev,a=t.next;n?n.next=a:this.head=a,a?a.prev=n:this.tail=n,t.next=t.prev=null,this._len--},e.prototype.len=function(){return this._len},e.prototype.clear=function(){this.head=this.tail=null,this._len=0},e}(),Xd=function(){function e(t){this._list=new sSe,this._maxSize=10,this._map={},this._maxSize=t}return e.prototype.put=function(t,n){var a=this._list,r=this._map,o=null;if(r[t]==null){var i=a.len(),l=this._lastRemovedEntry;if(i>=this._maxSize&&i>0){var s=a.head;a.remove(s),delete r[s.key],o=s.value,this._lastRemovedEntry=s}l?l.value=n:l=new UO(n),l.key=t,a.insertEntry(l),r[t]=l}return o},e.prototype.get=function(t){var n=this._map[t],a=this._list;if(n!=null)return n!==a.tail&&(a.remove(n),a.insertEntry(n)),n.value},e.prototype.clear=function(){this._list.clear(),this._map={}},e.prototype.len=function(){return this._list.len()},e}(),xk={transparent:[0,0,0,0],aliceblue:[240,248,255,1],antiquewhite:[250,235,215,1],aqua:[0,255,255,1],aquamarine:[127,255,212,1],azure:[240,255,255,1],beige:[245,245,220,1],bisque:[255,228,196,1],black:[0,0,0,1],blanchedalmond:[255,235,205,1],blue:[0,0,255,1],blueviolet:[138,43,226,1],brown:[165,42,42,1],burlywood:[222,184,135,1],cadetblue:[95,158,160,1],chartreuse:[127,255,0,1],chocolate:[210,105,30,1],coral:[255,127,80,1],cornflowerblue:[100,149,237,1],cornsilk:[255,248,220,1],crimson:[220,20,60,1],cyan:[0,255,255,1],darkblue:[0,0,139,1],darkcyan:[0,139,139,1],darkgoldenrod:[184,134,11,1],darkgray:[169,169,169,1],darkgreen:[0,100,0,1],darkgrey:[169,169,169,1],darkkhaki:[189,183,107,1],darkmagenta:[139,0,139,1],darkolivegreen:[85,107,47,1],darkorange:[255,140,0,1],darkorchid:[153,50,204,1],darkred:[139,0,0,1],darksalmon:[233,150,122,1],darkseagreen:[143,188,143,1],darkslateblue:[72,61,139,1],darkslategray:[47,79,79,1],darkslategrey:[47,79,79,1],darkturquoise:[0,206,209,1],darkviolet:[148,0,211,1],deeppink:[255,20,147,1],deepskyblue:[0,191,255,1],dimgray:[105,105,105,1],dimgrey:[105,105,105,1],dodgerblue:[30,144,255,1],firebrick:[178,34,34,1],floralwhite:[255,250,240,1],forestgreen:[34,139,34,1],fuchsia:[255,0,255,1],gainsboro:[220,220,220,1],ghostwhite:[248,248,255,1],gold:[255,215,0,1],goldenrod:[218,165,32,1],gray:[128,128,128,1],green:[0,128,0,1],greenyellow:[173,255,47,1],grey:[128,128,128,1],honeydew:[240,255,240,1],hotpink:[255,105,180,1],indianred:[205,92,92,1],indigo:[75,0,130,1],ivory:[255,255,240,1],khaki:[240,230,140,1],lavender:[230,230,250,1],lavenderblush:[255,240,245,1],lawngreen:[124,252,0,1],lemonchiffon:[255,250,205,1],lightblue:[173,216,230,1],lightcoral:[240,128,128,1],lightcyan:[224,255,255,1],lightgoldenrodyellow:[250,250,210,1],lightgray:[211,211,211,1],lightgreen:[144,238,144,1],lightgrey:[211,211,211,1],lightpink:[255,182,193,1],lightsalmon:[255,160,122,1],lightseagreen:[32,178,170,1],lightskyblue:[135,206,250,1],lightslategray:[119,136,153,1],lightslategrey:[119,136,153,1],lightsteelblue:[176,196,222,1],lightyellow:[255,255,224,1],lime:[0,255,0,1],limegreen:[50,205,50,1],linen:[250,240,230,1],magenta:[255,0,255,1],maroon:[128,0,0,1],mediumaquamarine:[102,205,170,1],mediumblue:[0,0,205,1],mediumorchid:[186,85,211,1],mediumpurple:[147,112,219,1],mediumseagreen:[60,179,113,1],mediumslateblue:[123,104,238,1],mediumspringgreen:[0,250,154,1],mediumturquoise:[72,209,204,1],mediumvioletred:[199,21,133,1],midnightblue:[25,25,112,1],mintcream:[245,255,250,1],mistyrose:[255,228,225,1],moccasin:[255,228,181,1],navajowhite:[255,222,173,1],navy:[0,0,128,1],oldlace:[253,245,230,1],olive:[128,128,0,1],olivedrab:[107,142,35,1],orange:[255,165,0,1],orangered:[255,69,0,1],orchid:[218,112,214,1],palegoldenrod:[238,232,170,1],palegreen:[152,251,152,1],paleturquoise:[175,238,238,1],palevioletred:[219,112,147,1],papayawhip:[255,239,213,1],peachpuff:[255,218,185,1],peru:[205,133,63,1],pink:[255,192,203,1],plum:[221,160,221,1],powderblue:[176,224,230,1],purple:[128,0,128,1],red:[255,0,0,1],rosybrown:[188,143,143,1],royalblue:[65,105,225,1],saddlebrown:[139,69,19,1],salmon:[250,128,114,1],sandybrown:[244,164,96,1],seagreen:[46,139,87,1],seashell:[255,245,238,1],sienna:[160,82,45,1],silver:[192,192,192,1],skyblue:[135,206,235,1],slateblue:[106,90,205,1],slategray:[112,128,144,1],slategrey:[112,128,144,1],snow:[255,250,250,1],springgreen:[0,255,127,1],steelblue:[70,130,180,1],tan:[210,180,140,1],teal:[0,128,128,1],thistle:[216,191,216,1],tomato:[255,99,71,1],turquoise:[64,224,208,1],violet:[238,130,238,1],wheat:[245,222,179,1],white:[255,255,255,1],whitesmoke:[245,245,245,1],yellow:[255,255,0,1],yellowgreen:[154,205,50,1]};function Ts(e){return e=Math.round(e),e<0?0:e>255?255:e}function S_(e){return e<0?0:e>1?1:e}function Ly(e){var t=e;return t.length&&t.charAt(t.length-1)==="%"?Ts(parseFloat(t)/100*255):Ts(parseInt(t,10))}function Qu(e){var t=e;return t.length&&t.charAt(t.length-1)==="%"?S_(parseFloat(t)/100):S_(parseFloat(t))}function Py(e,t,n){return n<0?n+=1:n>1&&(n-=1),n*6<1?e+(t-e)*n*6:n*2<1?t:n*3<2?e+(t-e)*(2/3-n)*6:e}function e0(e,t,n){return e+(t-e)*n}function po(e,t,n,a,r){return e[0]=t,e[1]=n,e[2]=a,e[3]=r,e}function C_(e,t){return e[0]=t[0],e[1]=t[1],e[2]=t[2],e[3]=t[3],e}var GO=new Xd(20),t0=null;function zc(e,t){t0&&C_(t0,t),t0=GO.put(e,t0||t.slice())}function Fi(e,t){if(e){t=t||[];var n=GO.get(e);if(n)return C_(t,n);e=e+"";var a=e.replace(/ /g,"").toLowerCase();if(a in xk)return C_(t,xk[a]),zc(e,t),t;var r=a.length;if(a.charAt(0)==="#"){if(r===4||r===5){var o=parseInt(a.slice(1,4),16);if(!(o>=0&&o<=4095)){po(t,0,0,0,1);return}return po(t,(o&3840)>>4|(o&3840)>>8,o&240|(o&240)>>4,o&15|(o&15)<<4,r===5?parseInt(a.slice(4),16)/15:1),zc(e,t),t}else if(r===7||r===9){var o=parseInt(a.slice(1,7),16);if(!(o>=0&&o<=16777215)){po(t,0,0,0,1);return}return po(t,(o&16711680)>>16,(o&65280)>>8,o&255,r===9?parseInt(a.slice(7),16)/255:1),zc(e,t),t}return}var i=a.indexOf("("),l=a.indexOf(")");if(i!==-1&&l+1===r){var s=a.substr(0,i),u=a.substr(i+1,l-(i+1)).split(","),c=1;switch(s){case"rgba":if(u.length!==4)return u.length===3?po(t,+u[0],+u[1],+u[2],1):po(t,0,0,0,1);c=Qu(u.pop());case"rgb":if(u.length>=3)return po(t,Ly(u[0]),Ly(u[1]),Ly(u[2]),u.length===3?c:Qu(u[3])),zc(e,t),t;po(t,0,0,0,1);return;case"hsla":if(u.length!==4){po(t,0,0,0,1);return}return u[3]=Qu(u[3]),x_(u,t),zc(e,t),t;case"hsl":if(u.length!==3){po(t,0,0,0,1);return}return x_(u,t),zc(e,t),t;default:return}}po(t,0,0,0,1)}}function x_(e,t){var n=(parseFloat(e[0])%360+360)%360/360,a=Qu(e[1]),r=Qu(e[2]),o=r<=.5?r*(a+1):r+a-r*a,i=r*2-o;return t=t||[],po(t,Ts(Py(i,o,n+1/3)*255),Ts(Py(i,o,n)*255),Ts(Py(i,o,n-1/3)*255),1),e.length===4&&(t[3]=e[3]),t}function uSe(e){if(e){var t=e[0]/255,n=e[1]/255,a=e[2]/255,r=Math.min(t,n,a),o=Math.max(t,n,a),i=o-r,l=(o+r)/2,s,u;if(i===0)s=0,u=0;else{l<.5?u=i/(o+r):u=i/(2-o-r);var c=((o-t)/6+i/2)/i,d=((o-n)/6+i/2)/i,v=((o-a)/6+i/2)/i;t===o?s=v-d:n===o?s=1/3+c-v:a===o&&(s=2/3+d-c),s<0&&(s+=1),s>1&&(s-=1)}var f=[s*360,u,l];return e[3]!=null&&f.push(e[3]),f}}function kk(e,t){var n=Fi(e);if(n){for(var a=0;a<3;a++)n[a]=n[a]*(1-t)|0,n[a]>255?n[a]=255:n[a]<0&&(n[a]=0);return mp(n,n.length===4?"rgba":"rgb")}}function cSe(e,t,n){if(!(!(t&&t.length)||!(e>=0&&e<=1))){var a=e*(t.length-1),r=Math.floor(a),o=Math.ceil(a),i=Fi(t[r]),l=Fi(t[o]),s=a-r,u=mp([Ts(e0(i[0],l[0],s)),Ts(e0(i[1],l[1],s)),Ts(e0(i[2],l[2],s)),S_(e0(i[3],l[3],s))],"rgba");return n?{color:u,leftIndex:r,rightIndex:o,value:a}:u}}function k_(e,t,n,a){var r=Fi(e);if(e)return r=uSe(r),n!=null&&(r[1]=Qu(dn(n)?n(r[1]):n)),a!=null&&(r[2]=Qu(dn(a)?a(r[2]):a)),mp(x_(r),"rgba")}function mp(e,t){if(!(!e||!e.length)){var n=e[0]+","+e[1]+","+e[2];return(t==="rgba"||t==="hsva"||t==="hsla")&&(n+=","+e[3]),t+"("+n+")"}}function Cg(e,t){var n=Fi(e);return n?(.299*n[0]+.587*n[1]+.114*n[2])*n[3]/255+(1-n[3])*t:0}var Tk=new Xd(100);function Mk(e){if(Ut(e)){var t=Tk.get(e);return t||(t=kk(e,-.1),Tk.put(e,t)),t}else if(Z1(e)){var n=vt({},e);return n.colorStops=Xt(e.colorStops,function(a){return{offset:a.offset,color:kk(a.color,-.1)}}),n}return e}function dSe(e){return e.type==="linear"}function fSe(e){return e.type==="radial"}(function(){return typeof Buffer<"u"&&typeof Buffer.from=="function"?function(e){return Buffer.from(e).toString("base64")}:typeof btoa=="function"&&typeof unescape=="function"&&typeof encodeURIComponent=="function"?function(e){return btoa(unescape(encodeURIComponent(e)))}:function(e){return null}})();var T_=Array.prototype.slice;function dl(e,t,n){return(t-e)*n+e}function Dy(e,t,n,a){for(var r=t.length,o=0;oa?t:e,o=Math.min(n,a),i=r[o-1]||{color:[0,0,0,0],offset:0},l=o;li;if(l)a.length=i;else for(var s=o;s=1},e.prototype.getAdditiveTrack=function(){return this._additiveTrack},e.prototype.addKeyframe=function(t,n,a){this._needsSort=!0;var r=this.keyframes,o=r.length,i=!1,l=Ak,s=n;if(Kr(n)){var u=mSe(n);l=u,(u===1&&!Gn(n[0])||u===2&&!Gn(n[0][0]))&&(i=!0)}else if(Gn(n)&&!kh(n))l=a0;else if(Ut(n))if(!isNaN(+n))l=a0;else{var c=Fi(n);c&&(s=c,l=fv)}else if(Z1(n)){var d=vt({},s);d.colorStops=Xt(n.colorStops,function(f){return{offset:f.offset,color:Fi(f.color)}}),dSe(n)?l=M_:fSe(n)&&(l=E_),s=d}o===0?this.valType=l:(l!==this.valType||l===Ak)&&(i=!0),this.discrete=this.discrete||i;var v={time:t,value:s,rawValue:n,percent:0};return a&&(v.easing=a,v.easingFunc=dn(a)?a:Nv[a]||KO(a)),r.push(v),v},e.prototype.prepare=function(t,n){var a=this.keyframes;this._needsSort&&a.sort(function(m,g){return m.time-g.time});for(var r=this.valType,o=a.length,i=a[o-1],l=this.discrete,s=r0(r),u=Ik(r),c=0;c=0&&!(i[c].percent<=n);c--);c=v(c,l-2)}else{for(c=d;cn);c++);c=v(c-1,l-2)}h=i[c+1],f=i[c]}if(f&&h){this._lastFr=c,this._lastFrP=n;var g=h.percent-f.percent,y=g===0?1:v((n-f.percent)/g,1);h.easingFunc&&(y=h.easingFunc(y));var b=a?this._additiveValue:u?Bf:t[s];if((r0(o)||u)&&!b&&(b=this._additiveValue=[]),this.discrete)t[s]=y<1?f.rawValue:h.rawValue;else if(r0(o))o===hm?Dy(b,f[r],h[r],y):vSe(b,f[r],h[r],y);else if(Ik(o)){var _=f[r],w=h[r],S=o===M_;t[s]={type:S?"linear":"radial",x:dl(_.x,w.x,y),y:dl(_.y,w.y,y),colorStops:Xt(_.colorStops,function(T,M){var E=w.colorStops[M];return{offset:dl(T.offset,E.offset,y),color:vm(Dy([],T.color,E.color,y))}}),global:w.global},S?(t[s].x2=dl(_.x2,w.x2,y),t[s].y2=dl(_.y2,w.y2,y)):t[s].r=dl(_.r,w.r,y)}else if(u)Dy(b,f[r],h[r],y),a||(t[s]=vm(b));else{var k=dl(f[r],h[r],y);a?this._additiveValue=k:t[s]=k}a&&this._addToTarget(t)}}},e.prototype._addToTarget=function(t){var n=this.valType,a=this.propName,r=this._additiveValue;n===a0?t[a]=t[a]+r:n===fv?(Fi(t[a],Bf),n0(Bf,Bf,r,1),t[a]=vm(Bf)):n===hm?n0(t[a],t[a],r,1):n===YO&&Ek(t[a],t[a],r,1)},e}(),i8=function(){function e(t,n,a,r){if(this._tracks={},this._trackKeys=[],this._maxTime=0,this._started=0,this._clip=null,this._target=t,this._loop=n,n&&r){e8("Can' use additive animation on looped animation.");return}this._additiveAnimators=r,this._allowDiscrete=a}return e.prototype.getMaxTime=function(){return this._maxTime},e.prototype.getDelay=function(){return this._delay},e.prototype.getLoop=function(){return this._loop},e.prototype.getTarget=function(){return this._target},e.prototype.changeTarget=function(t){this._target=t},e.prototype.when=function(t,n,a){return this.whenWithKeys(t,n,la(n),a)},e.prototype.whenWithKeys=function(t,n,a,r){for(var o=this._tracks,i=0;i0&&s.addKeyframe(0,fm(u),r),this._trackKeys.push(l)}s.addKeyframe(t,fm(n[l]),r)}return this._maxTime=Math.max(this._maxTime,t),this},e.prototype.pause=function(){this._clip.pause(),this._paused=!0},e.prototype.resume=function(){this._clip.resume(),this._paused=!1},e.prototype.isPaused=function(){return!!this._paused},e.prototype.duration=function(t){return this._maxTime=t,this._force=!0,this},e.prototype._doneCallback=function(){this._setTracksFinished(),this._clip=null;var t=this._doneCbs;if(t)for(var n=t.length,a=0;a0)){this._started=1;for(var n=this,a=[],r=this._maxTime||0,o=0;o1){var l=i.pop();o.addKeyframe(l.time,t[r]),o.prepare(this._maxTime,o.getAdditiveTrack())}}}},e}();function dd(){return new Date().getTime()}var ySe=function(e){At(t,e);function t(n){var a=e.call(this)||this;return a._running=!1,a._time=0,a._pausedTime=0,a._pauseStart=0,a._paused=!1,n=n||{},a.stage=n.stage||{},a}return t.prototype.addClip=function(n){n.animation&&this.removeClip(n),this._head?(this._tail.next=n,n.prev=this._tail,n.next=null,this._tail=n):this._head=this._tail=n,n.animation=this},t.prototype.addAnimator=function(n){n.animation=this;var a=n.getClip();a&&this.addClip(a)},t.prototype.removeClip=function(n){if(n.animation){var a=n.prev,r=n.next;a?a.next=r:this._head=r,r?r.prev=a:this._tail=a,n.next=n.prev=n.animation=null}},t.prototype.removeAnimator=function(n){var a=n.getClip();a&&this.removeClip(a),n.animation=null},t.prototype.update=function(n){for(var a=dd()-this._pausedTime,r=a-this._time,o=this._head;o;){var i=o.next,l=o.step(a,r);l&&(o.ondestroy(),this.removeClip(o)),o=i}this._time=a,n||(this.trigger("frame",r),this.stage.update&&this.stage.update())},t.prototype._startLoop=function(){var n=this;this._running=!0;function a(){n._running&&(yg(a),!n._paused&&n.update())}yg(a)},t.prototype.start=function(){this._running||(this._time=dd(),this._pausedTime=0,this._startLoop())},t.prototype.stop=function(){this._running=!1},t.prototype.pause=function(){this._paused||(this._pauseStart=dd(),this._paused=!0)},t.prototype.resume=function(){this._paused&&(this._pausedTime+=dd()-this._pauseStart,this._paused=!1)},t.prototype.clear=function(){for(var n=this._head;n;){var a=n.next;n.prev=n.next=n.animation=null,n=a}this._head=this._tail=null},t.prototype.isFinished=function(){return this._head==null},t.prototype.animate=function(n,a){a=a||{},this.start();var r=new i8(n,a.loop);return this.addAnimator(r),r},t}(rl),bSe=300,Oy=hn.domSupported,$y=function(){var e=["click","dblclick","mousewheel","wheel","mouseout","mouseup","mousedown","mousemove","contextmenu"],t=["touchstart","touchend","touchmove"],n={pointerdown:1,pointerup:1,pointermove:1,pointerout:1},a=Xt(e,function(r){var o=r.replace("mouse","pointer");return n.hasOwnProperty(o)?o:r});return{mouse:e,touch:t,pointer:a}}(),Lk={mouse:["mousemove","mouseup"],pointer:["pointermove","pointerup"]},Pk=!1;function A_(e){var t=e.pointerType;return t==="pen"||t==="touch"}function _Se(e){e.touching=!0,e.touchTimer!=null&&(clearTimeout(e.touchTimer),e.touchTimer=null),e.touchTimer=setTimeout(function(){e.touching=!1,e.touchTimer=null},700)}function Ry(e){e&&(e.zrByTouch=!0)}function wSe(e,t){return go(e.dom,new SSe(e,t),!0)}function jO(e,t){for(var n=t,a=!1;n&&n.nodeType!==9&&!(a=n.domBelongToZr||n!==t&&n===e.painterRoot);)n=n.parentNode;return a}var SSe=function(){function e(t,n){this.stopPropagation=lr,this.stopImmediatePropagation=lr,this.preventDefault=lr,this.type=n.type,this.target=this.currentTarget=t.dom,this.pointerType=n.pointerType,this.clientX=n.clientX,this.clientY=n.clientY}return e}(),Go={mousedown:function(e){e=go(this.dom,e),this.__mayPointerCapture=[e.zrX,e.zrY],this.trigger("mousedown",e)},mousemove:function(e){e=go(this.dom,e);var t=this.__mayPointerCapture;t&&(e.zrX!==t[0]||e.zrY!==t[1])&&this.__togglePointerCapture(!0),this.trigger("mousemove",e)},mouseup:function(e){e=go(this.dom,e),this.__togglePointerCapture(!1),this.trigger("mouseup",e)},mouseout:function(e){e=go(this.dom,e);var t=e.toElement||e.relatedTarget;jO(this,t)||(this.__pointerCapturing&&(e.zrEventControl="no_globalout"),this.trigger("mouseout",e))},wheel:function(e){Pk=!0,e=go(this.dom,e),this.trigger("mousewheel",e)},mousewheel:function(e){Pk||(e=go(this.dom,e),this.trigger("mousewheel",e))},touchstart:function(e){e=go(this.dom,e),Ry(e),this.__lastTouchMoment=new Date,this.handler.processGesture(e,"start"),Go.mousemove.call(this,e),Go.mousedown.call(this,e)},touchmove:function(e){e=go(this.dom,e),Ry(e),this.handler.processGesture(e,"change"),Go.mousemove.call(this,e)},touchend:function(e){e=go(this.dom,e),Ry(e),this.handler.processGesture(e,"end"),Go.mouseup.call(this,e),+new Date-+this.__lastTouchMoment$k||e<-$k}var lu=[],Fc=[],By=zi(),Vy=Math.abs,gp=function(){function e(){}return e.prototype.getLocalTransform=function(t){return MSe(this,t)},e.prototype.setPosition=function(t){this.x=t[0],this.y=t[1]},e.prototype.setScale=function(t){this.scaleX=t[0],this.scaleY=t[1]},e.prototype.setSkew=function(t){this.skewX=t[0],this.skewY=t[1]},e.prototype.setOrigin=function(t){this.originX=t[0],this.originY=t[1]},e.prototype.needLocalTransform=function(){return iu(this.rotation)||iu(this.x)||iu(this.y)||iu(this.scaleX-1)||iu(this.scaleY-1)||iu(this.skewX)||iu(this.skewY)},e.prototype.updateTransform=function(){var t=this.parent&&this.parent.transform,n=this.needLocalTransform(),a=this.transform;if(!(n||t)){a&&(Ok(a),this.invTransform=null);return}a=a||zi(),n?this.getLocalTransform(a):Ok(a),t&&(n?$v(a,t,a):r8(a,t)),this.transform=a,this._resolveGlobalScaleRatio(a),this.invTransform=this.invTransform||zi(),pp(this.invTransform,a)},e.prototype._resolveGlobalScaleRatio=function(t){var n=this.globalScaleRatio;if(n!=null&&n!==1){this.getGlobalScale(lu);var a=lu[0]<0?-1:1,r=lu[1]<0?-1:1,o=((lu[0]-a)*n+a)/lu[0]||0,i=((lu[1]-r)*n+r)/lu[1]||0;t[0]*=o,t[1]*=o,t[2]*=i,t[3]*=i}},e.prototype.getComputedTransform=function(){for(var t=this,n=[];t;)n.push(t),t=t.parent;for(;t=n.pop();)t.updateTransform();return this.transform},e.prototype.setLocalTransform=function(t){if(t){var n=t[0]*t[0]+t[1]*t[1],a=t[2]*t[2]+t[3]*t[3],r=Math.atan2(t[1],t[0]),o=Math.PI/2+r-Math.atan2(t[3],t[2]);a=Math.sqrt(a)*Math.cos(o),n=Math.sqrt(n),this.skewX=o,this.skewY=0,this.rotation=-r,this.x=+t[4],this.y=+t[5],this.scaleX=n,this.scaleY=a,this.originX=0,this.originY=0}},e.prototype.decomposeTransform=function(){if(this.transform){var t=this.parent,n=this.transform;t&&t.transform&&(t.invTransform=t.invTransform||zi(),$v(Fc,t.invTransform,n),n=Fc);var a=this.originX,r=this.originY;(a||r)&&(By[4]=a,By[5]=r,$v(Fc,n,By),Fc[4]-=a,Fc[5]-=r,n=Fc),this.setLocalTransform(n)}},e.prototype.getGlobalScale=function(t){var n=this.transform;return t=t||[],n?(t[0]=Math.sqrt(n[0]*n[0]+n[1]*n[1]),t[1]=Math.sqrt(n[2]*n[2]+n[3]*n[3]),n[0]<0&&(t[0]=-t[0]),n[3]<0&&(t[1]=-t[1]),t):(t[0]=1,t[1]=1,t)},e.prototype.transformCoordToLocal=function(t,n){var a=[t,n],r=this.invTransform;return r&&Po(a,a,r),a},e.prototype.transformCoordToGlobal=function(t,n){var a=[t,n],r=this.transform;return r&&Po(a,a,r),a},e.prototype.getLineScale=function(){var t=this.transform;return t&&Vy(t[0]-1)>1e-10&&Vy(t[3]-1)>1e-10?Math.sqrt(Vy(t[0]*t[3]-t[2]*t[1])):1},e.prototype.copyTransform=function(t){kg(this,t)},e.getLocalTransform=function(t,n){n=n||[];var a=t.originX||0,r=t.originY||0,o=t.scaleX,i=t.scaleY,l=t.anchorX,s=t.anchorY,u=t.rotation||0,c=t.x,d=t.y,v=t.skewX?Math.tan(t.skewX):0,f=t.skewY?Math.tan(-t.skewY):0;if(a||r||l||s){var h=a+l,m=r+s;n[4]=-h*o-v*m*i,n[5]=-m*i-f*h*o}else n[4]=n[5]=0;return n[0]=o,n[3]=i,n[1]=f*o,n[2]=v*i,u&&o8(n,n,u),n[4]+=a+c,n[5]+=r+d,n},e.initDefaultProps=function(){var t=e.prototype;t.scaleX=t.scaleY=t.globalScaleRatio=1,t.x=t.y=t.originX=t.originY=t.skewX=t.skewY=t.rotation=t.anchorX=t.anchorY=0}(),e}(),MSe=gp.getLocalTransform,Q1=["x","y","originX","originY","anchorX","anchorY","rotation","scaleX","scaleY","skewX","skewY"];function kg(e,t){return c8e(e,t,Q1)}function Hi(e){o0||(o0=new Xd(100)),e=e||Vs;var t=o0.get(e);return t||(t={font:e,strWidthCache:new Xd(500),asciiWidthMap:null,asciiWidthMapTried:!1,stWideCharWidth:oo.measureText("国",e).width,asciiCharWidth:oo.measureText("a",e).width},o0.put(e,t)),t}var o0;function ESe(e){if(!(zy>=Rk)){e=e||Vs;for(var t=[],n=+new Date,a=0;a<=127;a++)t[a]=oo.measureText(String.fromCharCode(a),e).width;var r=+new Date-n;return r>16?zy=Rk:r>2&&zy++,t}}var zy=0,Rk=5;function XO(e,t){return e.asciiWidthMapTried||(e.asciiWidthMap=ESe(e.font),e.asciiWidthMapTried=!0),0<=t&&t<=127?e.asciiWidthMap!=null?e.asciiWidthMap[t]:e.asciiCharWidth:e.stWideCharWidth}function Wi(e,t){var n=e.strWidthCache,a=n.get(t);return a==null&&(a=oo.measureText(t,e.font).width,n.put(t,a)),a}function Nk(e,t,n,a){var r=Wi(Hi(t),e),o=e2(t),i=Zd(0,r,n),l=ec(0,o,a),s=new ln(i,l,r,o);return s}function ZO(e,t,n,a){var r=((e||"")+"").split(` +`),o=r.length;if(o===1)return Nk(r[0],t,n,a);for(var i=new ln(0,0,0,0),l=0;l=0?parseFloat(e)/100*t:parseFloat(e):e}function Tg(e,t,n){var a=t.position||"inside",r=t.distance!=null?t.distance:5,o=n.height,i=n.width,l=o/2,s=n.x,u=n.y,c="left",d="top";if(a instanceof Array)s+=mc(a[0],n.width),u+=mc(a[1],n.height),c=null,d=null;else switch(a){case"left":s-=r,u+=l,c="right",d="middle";break;case"right":s+=r+i,u+=l,d="middle";break;case"top":s+=i/2,u-=r,c="center",d="bottom";break;case"bottom":s+=i/2,u+=o+r,c="center";break;case"inside":s+=i/2,u+=l,c="center",d="middle";break;case"insideLeft":s+=r,u+=l,d="middle";break;case"insideRight":s+=i-r,u+=l,c="right",d="middle";break;case"insideTop":s+=i/2,u+=r,c="center";break;case"insideBottom":s+=i/2,u+=o-r,c="center",d="bottom";break;case"insideTopLeft":s+=r,u+=r;break;case"insideTopRight":s+=i-r,u+=r,c="right";break;case"insideBottomLeft":s+=r,u+=o-r,d="bottom";break;case"insideBottomRight":s+=i-r,u+=o-r,c="right",d="bottom";break}return e=e||{},e.x=s,e.y=u,e.align=c,e.verticalAlign=d,e}var Fy="__zr_normal__",Hy=Q1.concat(["ignore"]),ASe=zs(Q1,function(e,t){return e[t]=!0,e},{ignore:!1}),Hc={},ISe=new ln(0,0,0,0),i0=[],mm=0,t2=1,n2=function(){function e(t){this.id=AO(),this.animators=[],this.currentStates=[],this.states={},this._init(t)}return e.prototype._init=function(t){this.attr(t)},e.prototype.drift=function(t,n,a){switch(this.draggable){case"horizontal":n=0;break;case"vertical":t=0;break}var r=this.transform;r||(r=this.transform=[1,0,0,1,0,0]),r[4]+=t,r[5]+=n,this.decomposeTransform(),this.markRedraw()},e.prototype.beforeUpdate=function(){},e.prototype.afterUpdate=function(){},e.prototype.update=function(){this.updateTransform(),this.__dirty&&this.updateInnerText()},e.prototype.updateInnerText=function(t){var n=this._textContent;if(n&&(!n.ignore||t)){this.textConfig||(this.textConfig={});var a=this.textConfig,r=a.local,o=n.innerTransformable,i=void 0,l=void 0,s=!1;o.parent=r?this:null;var u=!1;o.copyTransform(n);var c=a.position!=null,d=a.autoOverflowArea,v=void 0;if((d||c)&&(v=ISe,a.layoutRect?v.copy(a.layoutRect):v.copy(this.getBoundingRect()),r||v.applyTransform(this.transform)),c){this.calculateTextPosition?this.calculateTextPosition(Hc,a,v):Tg(Hc,a,v),o.x=Hc.x,o.y=Hc.y,i=Hc.align,l=Hc.verticalAlign;var f=a.origin;if(f&&a.rotation!=null){var h=void 0,m=void 0;f==="center"?(h=v.width*.5,m=v.height*.5):(h=mc(f[0],v.width),m=mc(f[1],v.height)),u=!0,o.originX=-o.x+h+(r?0:v.x),o.originY=-o.y+m+(r?0:v.y)}}a.rotation!=null&&(o.rotation=a.rotation);var g=a.offset;g&&(o.x+=g[0],o.y+=g[1],u||(o.originX=-g[0],o.originY=-g[1]));var y=this._innerTextDefaultStyle||(this._innerTextDefaultStyle={});if(d){var b=y.overflowRect=y.overflowRect||new ln(0,0,0,0);o.getLocalTransform(i0),pp(i0,i0),ln.copy(b,v),b.applyTransform(i0)}else y.overflowRect=null;var _=a.inside==null?typeof a.position=="string"&&a.position.indexOf("inside")>=0:a.inside,w=void 0,S=void 0,k=void 0;_&&this.canBeInsideText()?(w=a.insideFill,S=a.insideStroke,(w==null||w==="auto")&&(w=this.getInsideTextFill()),(S==null||S==="auto")&&(S=this.getInsideTextStroke(w),k=!0)):(w=a.outsideFill,S=a.outsideStroke,(w==null||w==="auto")&&(w=this.getOutsideFill()),(S==null||S==="auto")&&(S=this.getOutsideStroke(w),k=!0)),w=w||"#000",(w!==y.fill||S!==y.stroke||k!==y.autoStroke||i!==y.align||l!==y.verticalAlign)&&(s=!0,y.fill=w,y.stroke=S,y.autoStroke=k,y.align=i,y.verticalAlign=l,n.setDefaultTextStyle(y)),n.__dirty|=Nr,s&&n.dirtyStyle(!0)}},e.prototype.canBeInsideText=function(){return!0},e.prototype.getInsideTextFill=function(){return"#fff"},e.prototype.getInsideTextStroke=function(t){return"#000"},e.prototype.getOutsideFill=function(){return this.__zr&&this.__zr.isDarkMode()?D_:P_},e.prototype.getOutsideStroke=function(t){var n=this.__zr&&this.__zr.getBackgroundColor(),a=typeof n=="string"&&Fi(n);a||(a=[255,255,255,1]);for(var r=a[3],o=this.__zr.isDarkMode(),i=0;i<3;i++)a[i]=a[i]*r+(o?0:255)*(1-r);return a[3]=1,mp(a,"rgba")},e.prototype.traverse=function(t,n){},e.prototype.attrKV=function(t,n){t==="textConfig"?this.setTextConfig(n):t==="textContent"?this.setTextContent(n):t==="clipPath"?this.setClipPath(n):t==="extra"?(this.extra=this.extra||{},vt(this.extra,n)):this[t]=n},e.prototype.hide=function(){this.ignore=!0,this.markRedraw()},e.prototype.show=function(){this.ignore=!1,this.markRedraw()},e.prototype.attr=function(t,n){if(typeof t=="string")this.attrKV(t,n);else if(Yt(t))for(var a=t,r=la(a),o=0;o0},e.prototype.getState=function(t){return this.states[t]},e.prototype.ensureState=function(t){var n=this.states;return n[t]||(n[t]={}),n[t]},e.prototype.clearStates=function(t){this.useState(Fy,!1,t)},e.prototype.useState=function(t,n,a,r){var o=t===Fy,i=this.hasState();if(!(!i&&o)){var l=this.currentStates,s=this.stateTransition;if(!(Pn(l,t)>=0&&(n||l.length===1))){var u;if(this.stateProxy&&!o&&(u=this.stateProxy(t)),u||(u=this.states&&this.states[t]),!u&&!o){e8("State "+t+" not exists.");return}o||this.saveCurrentToNormalState(u);var c=this._textContent,d=Bk(this,c,u,r);d&&!this.__inHover&&(this.__inHover=d),this._applyStateObj(t,u,this._normalState,n,zk(this,a,s),s);var v=this._textGuide;return c&&c.useState(t,n,a,!!d),v&&v.useState(t,n,a,!!d),o?(this.currentStates=[],this._normalState={}):n?this.currentStates.push(t):this.currentStates=[t],this._updateAnimationTargets(),this.markRedraw(),!d&&this.__inHover&&(this.__inHover=mm,this.__dirty&=~Nr),u}}},e.prototype.useStates=function(t,n,a){if(!t.length)this.clearStates();else{var r=[],o=this.currentStates,i=t.length,l=i===o.length;if(l){for(var s=0;s=0){var a=this.currentStates.slice();a.splice(n,1),this.useStates(a)}},e.prototype.replaceState=function(t,n,a){var r=this.currentStates.slice(),o=Pn(r,t),i=Pn(r,n)>=0;o>=0?i?r.splice(o,1):r[o]=n:a&&!i&&r.push(n),this.useStates(r)},e.prototype.toggleState=function(t,n){n?this.useState(t,!0):this.removeState(t)},e.prototype._mergeStates=function(t){for(var n={},a,r=0;r=0&&o.splice(i,1)}),this.animators.push(t),a&&a.animation.addAnimator(t),a&&a.wakeUp()},e.prototype.updateDuringAnimation=function(t){this.markRedraw()},e.prototype.stopAnimation=function(t,n){for(var a=this.animators,r=a.length,o=[],i=0;i0&&n.during&&o[0].during(function(h,m){n.during(m)});for(var v=0;v0||r.force&&!i.length){var M=void 0,E=void 0,A=void 0;if(l){E={},v&&(M={});for(var w=0;w<_;w++){var y=m[w];E[y]=n[y],v?M[y]=a[y]:n[y]=a[y]}}else if(v){A={};for(var w=0;w<_;w++){var y=m[w];A[y]=fm(n[y]),PSe(n,a,y)}}var S=new i8(n,!1,!1,d?ar(h,function(P){return P.targetName===t}):null);S.targetName=t,r.scope&&(S.scope=r.scope),v&&M&&S.whenWithKeys(0,M,m),A&&S.whenWithKeys(0,A,m),S.whenWithKeys(u??500,l?E:a,m).delay(c||0),e.addAnimator(S,t),i.push(S)}}function Bk(e,t,n,a){return!(n&&n.hoverLayer||a)||Vk(e)||t&&Vk(t)?mm:t2}function Vk(e){return e.type==="text"||e.type==="tspan"}function zk(e,t,n){return!t&&!e.__inHover&&n&&n.duration>0}var Ha=function(e){At(t,e);function t(n){var a=e.call(this)||this;return a.isGroup=!0,a._children=[],a.attr(n),a}return t.prototype.childrenRef=function(){return this._children},t.prototype.children=function(){return this._children.slice()},t.prototype.childAt=function(n){return this._children[n]},t.prototype.childOfName=function(n){for(var a=this._children,r=0;r=0&&(r.splice(o,0,n),this._doAdd(n))}return this},t.prototype.replace=function(n,a){var r=Pn(this._children,n);return r>=0&&this.replaceAt(a,r),this},t.prototype.replaceAt=function(n,a){var r=this._children,o=r[a];if(n&&n!==this&&n.parent!==this&&n!==o){r[a]=n,o.parent=null;var i=this.__zr;i&&o.removeSelfFromZr(i),this._doAdd(n)}return this},t.prototype._doAdd=function(n){n.parent&&n.parent.remove(n),n.parent=this;var a=this.__zr;a&&a!==n.__zr&&n.addSelfToZr(a),a&&a.refresh()},t.prototype.remove=function(n){var a=this.__zr,r=this._children,o=Pn(r,n);return o<0?this:(r.splice(o,1),n.parent=null,a&&n.removeSelfFromZr(a),a&&a.refresh(),this)},t.prototype.removeAll=function(){for(var n=this._children,a=this.__zr,r=0;r0&&(this._stillFrameAccum++,this._stillFrameAccum>this._sleepAfterStill&&this.animation.stop())},e.prototype.setSleepAfterStill=function(t){this._sleepAfterStill=t},e.prototype.wakeUp=function(){this._disposed||(this.animation.start(),this._stillFrameAccum=0)},e.prototype.refreshHover=function(){this._needsRefreshHover=!0},e.prototype.refreshHoverImmediately=function(){this._disposed||this._refresh({animUpdate:!1,refresh:!1,refreshHover:!0})},e.prototype.resize=function(t){this._disposed||(t=t||{},this.painter.resize(t.width,t.height),this.handler.resize())},e.prototype.clearAnimation=function(){this._disposed||this.animation.clear()},e.prototype.getWidth=function(){if(!this._disposed)return this.painter.getWidth()},e.prototype.getHeight=function(){if(!this._disposed)return this.painter.getHeight()},e.prototype.setCursorStyle=function(t){this._disposed||this.handler.setCursorStyle(t)},e.prototype.findHover=function(t,n){if(!this._disposed)return this.handler.findHover(t,n)},e.prototype.on=function(t,n,a){return this._disposed||this.handler.on(t,n,a),this},e.prototype.off=function(t,n){this._disposed||this.handler.off(t,n)},e.prototype.trigger=function(t,n){this._disposed||this.handler.trigger(t,n)},e.prototype.clear=function(){if(!this._disposed){for(var t=this.storage.getRoots(),n=0;n0){if(e<=r)return i;if(e>=o)return l}else{if(e>=r)return i;if(e<=o)return l}else{if(e===r)return i;if(e===o)return l}return(e-r)/s*u+i}var sa=HSe;function HSe(e,t,n){switch(e){case"center":case"middle":e="50%";break;case"left":case"top":e="0%";break;case"right":case"bottom":e="100%";break}return R_(e,t,n)}function R_(e,t,n){return Ut(e)?WSe(e)?parseFloat(e)/100*t+(n||0):parseFloat(e):e==null?NaN:+e}function WSe(e){return!!VSe(e).match(/%$/)}function En(e,t,n){return isNaN(t)?n?""+e:+e:(t=sr(An(0,t),e$),e=(+e).toFixed(t),n?e:+e)}function l8(e){return e.sort(function(t,n){return t-n}),e}function bs(e){if(e=+e,isNaN(e))return 0;if(e>1e-14){for(var t=1,n=0;n<15;n++,t*=10)if(Dl(e*t)/t===e)return n}return KSe(e)}function KSe(e){var t=e.toString().toLowerCase(),n=t.indexOf("e"),a=n>0?+t.slice(n+1):0,r=n>0?n:t.length,o=t.indexOf("."),i=o<0?0:r-1-o;return An(0,i-a)}function USe(e,t,n){var a=Ca(e[1]-e[0]);if(!isFinite(a)||a===0)return NaN;var r=Th(2*Ca(n||1)*Ca(a))/O_,o=Th(Ca(t))/O_,i=An(0,yp(-r+o));return isFinite(i)||(i=NaN),i}function GSe(e,t){var n=zs(e,function(f,h){return f+(isNaN(h)?0:h)},0);if(n===0)return[];for(var a=Ac(10,t),r=Xt(e,function(f){return(isNaN(f)?0:f)/n*a*100}),o=a*100,i=Xt(r,function(f){return gc(f)}),l=zs(i,function(f,h){return f+h},0),s=Xt(r,function(f,h){return f-i[h]});lu&&(u=s[d],c=d);++i[c],s[c]=0,++l}return Xt(i,function(f){return f/a})}function YSe(e,t){var n=An(bs(e),bs(t)),a=e+t;return n>e$?a:En(a,n)}var Wk=Ac(2,53)-1;function t$(e){var t=zSe*2;return(e%t+t)%t}function Mg(e){return e>-Hk&&e=10&&t++,t}var a$=2;function u8(e,t){var n=s8(e),a=Ac(10,n),r=e/a,o;return t===a$?o=1:t?r<1.5?o=1:r<2.5?o=2:r<4?o=3:r<7?o=5:o=10:r<1?o=1:r<2?o=2:r<3?o=3:r<5?o=5:o=10,e=o*a,En(e,-n)}function Eg(e){var t=parseFloat(e);return t==e&&(t!==0||!Ut(e)||e.indexOf("x")<=0)?t:NaN}function qSe(e){return!isNaN(Eg(e))}function c8(){return Dl(FSe()*9)}function r$(e,t){return t===0?e:r$(t,e%t)}function Kk(e,t){return e==null?t:t==null?e:e*t/r$(e,t)}function zo(e){return e!=null&&isFinite(e)}var XSe="[ECharts] ",ZSe=typeof console<"u"&&console.warn&&console.log;function JSe(e,t,n){ZSe&&console[e](XSe+t)}function o$(e,t){JSe("error",e)}function Rr(e){throw new Error(e)}function Uk(e,t,n){return(t-e)*n+e}var i$="series\0",QSe="\0_ec_\0";function Cr(e){return e instanceof Array?e:e==null?[]:[e]}function N_(e,t,n){if(e){e[t]=e[t]||{},e.emphasis=e.emphasis||{},e.emphasis[t]=e.emphasis[t]||{};for(var a=0,r=n.length;ae[1]&&(e[1]=t))}function u$(e,t){Ol(t)&&te[1]&&(e[1]=t)}function pCe(e,t){Jd(t[0],t[1])&&(t[0]e[1]&&(e[1]=t[1]))}function Ol(e){return e!=null&&isFinite(e)}function Jd(e,t){return Ol(e)&&Ol(t)&&e<=t}function mCe(e){var t=e[1]-e[0];return isFinite(t)&&t>=0}function gCe(e){Jd(e[0],e[1])&&e[0]>e[1]&&(e[0]=e[1])}function d$(){var e="__ec_once_"+yCe++;return function(t,n){Sr(t,e)||(t[e]=1,n())}}var yCe=c8();function v8(e,t,n){var a=an(),r=0;we(e,function(o){var i=t(o),l=a.get(i)||0;n&&n(o,l),!l&&!n&&(e[r++]=o),a.set(i,l+1)}),n||(e.length=r)}function bCe(e){return e.value+""}function _Ce(e){return e+""}function wCe(e,t){return jt(t,!0)?e.seriesIndex+2:0}function f$(e,t,n){var a=e.getData().count();return{progressiveRender:n.progressiveEnabled&&t.incrementalPrepareRender&&a>=n.threshold,large:e.get("large")&&a>=e.get("largeThreshold"),modDataCount:e.get("progressiveChunkMode")==="mod"?e.getData().count():null}}function SCe(e,t){return{seriesType:e,overallReset:t}}function h8(e){return{overallReset:e}}var CCe=".",su="___EC__COMPONENT__CONTAINER___",v$="___EC__EXTENDED_CLASS___";function Oi(e){var t={main:"",sub:""};if(e){var n=e.split(CCe);t.main=n[0]||"",t.sub=n[1]||""}return t}function xCe(e){Pl(/^[a-zA-Z0-9_]+([.][a-zA-Z0-9_]+)?$/.test(e),'componentType "'+e+'" illegal')}function kCe(e){return!!(e&&e[v$])}function p8(e,t){e.$constructor=e,e.extend=function(n){var a=this,r;return TCe(a)?r=function(o){At(i,o);function i(){return o.apply(this,arguments)||this}return i}(a):(r=function(){(n.$constructor||a).apply(this,arguments)},d8e(r,this)),vt(r.prototype,n),r[v$]=!0,r.extend=this.extend,r.superCall=ACe,r.superApply=ICe,r.superClass=a,r}}function TCe(e){return dn(e)&&/^class\s/.test(Function.prototype.toString.call(e))}function h$(e,t){e.extend=t.extend}var MCe=Math.round(Math.random()*10);function ECe(e){var t=["__\0is_clz",MCe++].join("_");e.prototype[t]=!0,e.isInstance=function(n){return!!(n&&n[t])}}function ACe(e,t){for(var n=[],a=2;a=0||o&&Pn(o,s)<0)){var u=a.getShallow(s,t);u!=null&&(i[e[l][0]]=u)}}return i}}var LCe=[["fill","color"],["shadowBlur"],["shadowOffsetX"],["shadowOffsetY"],["opacity"],["shadowColor"]],PCe=Eh(LCe),DCe=function(){function e(){}return e.prototype.getAreaStyle=function(t,n){return PCe(this,t,n)},e}(),V_=new Xd(50);function OCe(e){if(typeof e=="string"){var t=V_.get(e);return t&&t.image}else return e}function p$(e,t,n,a,r){if(e)if(typeof e=="string"){if(t&&t.__zrImageSrc===e||!n)return t;var o=V_.get(e),i={hostEl:n,cb:a,cbPayload:r};return o?(t=o.image,!r2(t)&&o.pending.push(i)):(t=oo.loadImage(e,Yk,Yk),t.__zrImageSrc=e,V_.put(e,t.__cachedImgObj={image:t,pending:[i]})),t}else return e;else return t}function Yk(){var e=this.__cachedImgObj;this.onload=this.onerror=this.__cachedImgObj=null;for(var t=0;t=l;u++)s-=l;var c=Wi(i,n);return c>s&&(n="",c=0),s=e-c,r.ellipsis=n,r.ellipsisWidth=c,r.contentWidth=s,r.containerWidth=e,r}function g$(e,t,n){var a=n.containerWidth,r=n.contentWidth,o=n.fontMeasureInfo;if(!a){e.textLine="",e.isTruncated=!1;return}var i=Wi(o,t);if(i<=a){e.textLine=t,e.isTruncated=!1;return}for(var l=0;;l++){if(i<=r||l>=n.maxIterations){t+=n.ellipsis;break}var s=l===0?RCe(t,r,o):i>0?Math.floor(t.length*r/i):0;t=t.substr(0,s),i=Wi(o,t)}t===""&&(t=n.placeholder),e.textLine=t,e.isTruncated=!0}function RCe(e,t,n){for(var a=0,r=0,o=e.length;rg&&f){var _=Math.floor(g/v);h=h||y.length>_,y=y.slice(0,_),b=y.length*v}if(r&&c&&m!=null)for(var w=m$(m,u,t.ellipsis,{minChar:t.truncateMinChar,placeholder:t.placeholder}),S={},k=0;kh&&Yy(o,i.substring(h,g),t,f),Yy(o,m[2],t,f,m[1]),h=Gy.lastIndex}hd){var W=o.lines.length;P>0?(E.tokens=E.tokens.slice(0,P),T(E,I,A),o.lines=o.lines.slice(0,M+1)):o.lines=o.lines.slice(0,M),o.isTruncated=o.isTruncated||o.lines.length0&&h+a.accumWidth>a.width&&(c=t.split(` +`),u=!0),a.accumWidth=h}else{var m=y$(t,s,a.width,a.breakAll,a.accumWidth);a.accumWidth=m.accumWidth+f,d=m.linesWidths,c=m.lines}}c||(c=t.split(` +`));for(var g=Hi(s),y=0;y=32&&t<=591||t>=880&&t<=4351||t>=4608&&t<=5119||t>=7680&&t<=8303}var HCe=zs(",&?/;] ".split(""),function(e,t){return e[t]=!0,e},{});function WCe(e){return FCe(e)?!!HCe[e]:!0}function y$(e,t,n,a,r){for(var o=[],i=[],l="",s="",u=0,c=0,d=Hi(t),v=0;vn:r+c+h>n){c?(l||s)&&(m?(l||(l=s,s="",u=0,c=u),o.push(l),i.push(c-u),s+=f,u+=h,l="",c=u):(s&&(l+=s,s="",u=0),o.push(l),i.push(c),l=f,c=h)):m?(o.push(s),i.push(u),s=f,u=h):(o.push(f),i.push(h));continue}c+=h,m?(s+=f,u+=h):(s&&(l+=s,s="",u=0),l+=f)}return s&&(l+=s),l&&(o.push(l),i.push(c)),o.length===1&&(c+=r),{accumWidth:c,lines:o,linesWidths:i}}function qk(e,t,n,a,r,o){if(e.baseX=n,e.baseY=a,e.outerWidth=e.outerHeight=null,!!t){var i=t.width*2,l=t.height*2;ln.set(Xk,Zd(n,i,r),ec(a,l,o),i,l),ln.intersect(t,Xk,null,Zk);var s=Zk.outIntersectRect;e.outerWidth=s.width,e.outerHeight=s.height,e.baseX=Zd(s.x,s.width,r,!0),e.baseY=ec(s.y,s.height,o,!0)}}var Xk=new ln(0,0,0,0),Zk={outIntersectRect:{},clamp:!0};function m8(e){return e!=null?e+="":e=""}function KCe(e){var t=m8(e.text),n=e.font,a=Wi(Hi(n),t),r=e2(n);return z_(e,a,r,null)}function z_(e,t,n,a){var r=new ln(Zd(e.x||0,t,e.textAlign),ec(e.y||0,n,e.textBaseline),t,n),o=a??(b$(e)?e.lineWidth:0);return o>0&&(r.x-=o/2,r.y-=o/2,r.width+=o,r.height+=o),r}function b$(e){var t=e.stroke;return t!=null&&t!=="none"&&e.lineWidth>0}var F_="__zr_style_"+Math.round(Math.random()*10),tc={shadowBlur:0,shadowOffsetX:0,shadowOffsetY:0,shadowColor:"#000",opacity:1,blend:"source-over"},o2={style:{shadowBlur:!0,shadowOffsetX:!0,shadowOffsetY:!0,shadowColor:!0,opacity:!0}};tc[F_]=!0;var Jk=["z","z2","invisible"],UCe=["invisible"],wp=function(e){At(t,e);function t(n){return e.call(this,n)||this}return t.prototype._init=function(n){for(var a=la(n),r=0;r1e-4){l[0]=e-n,l[1]=t-a,s[0]=e+n,s[1]=t+a;return}if(l0[0]=Zy(r)*n+e,l0[1]=Xy(r)*a+t,s0[0]=Zy(o)*n+e,s0[1]=Xy(o)*a+t,u(l,l0,s0),c(s,l0,s0),r=r%uu,r<0&&(r=r+uu),o=o%uu,o<0&&(o=o+uu),r>o&&!i?o+=uu:rr&&(u0[0]=Zy(f)*n+e,u0[1]=Xy(f)*a+t,u(l,u0,l),c(s,u0,s))}var Ln={M:1,L:2,C:3,Q:4,A:5,Z:6,R:7},cu=[],du=[],yi=[],ql=[],bi=[],_i=[],Jy=Math.min,Qy=Math.max,fu=Math.cos,vu=Math.sin,il=Math.abs,H_=Math.PI,us=H_*2,eb=typeof Float32Array<"u",Vf=[];function tb(e){var t=Math.round(e/H_*1e8)/1e8;return t%2*H_}function _$(e,t){var n=tb(e[0]);n<0&&(n+=us);var a=n-e[0],r=e[1];r+=a,!t&&r-n>=us?r=n+us:t&&n-r>=us?r=n-us:!t&&n>r?r=n+(us-tb(n-r)):t&&n0&&(this._ux=il(a/xg/t)||0,this._uy=il(a/xg/n)||0)},e.prototype.setDPR=function(t){this.dpr=t},e.prototype.setContext=function(t){this._ctx=t},e.prototype.getContext=function(){return this._ctx},e.prototype.beginPath=function(){return this._ctx&&this._ctx.beginPath(),this.reset(),this},e.prototype.reset=function(){this._saveData&&(this._len=0),this._pathSegLen&&(this._pathSegLen=null,this._pathLen=0),this._version++},e.prototype.moveTo=function(t,n){return this._drawPendingPt(),this.addData(Ln.M,t,n),this._ctx&&this._ctx.moveTo(t,n),this._x0=t,this._y0=n,this._xi=t,this._yi=n,this},e.prototype.lineTo=function(t,n){var a=il(t-this._xi),r=il(n-this._yi),o=a>this._ux||r>this._uy;if(this.addData(Ln.L,t,n),this._ctx&&o&&this._ctx.lineTo(t,n),o)this._xi=t,this._yi=n,this._pendingPtDist=0;else{var i=a*a+r*r;i>this._pendingPtDist&&(this._pendingPtX=t,this._pendingPtY=n,this._pendingPtDist=i)}return this},e.prototype.bezierCurveTo=function(t,n,a,r,o,i){return this._drawPendingPt(),this.addData(Ln.C,t,n,a,r,o,i),this._ctx&&this._ctx.bezierCurveTo(t,n,a,r,o,i),this._xi=o,this._yi=i,this},e.prototype.quadraticCurveTo=function(t,n,a,r){return this._drawPendingPt(),this.addData(Ln.Q,t,n,a,r),this._ctx&&this._ctx.quadraticCurveTo(t,n,a,r),this._xi=a,this._yi=r,this},e.prototype.arc=function(t,n,a,r,o,i){this._drawPendingPt(),Vf[0]=r,Vf[1]=o,_$(Vf,i),r=Vf[0],o=Vf[1];var l=o-r;return this.addData(Ln.A,t,n,a,a,r,l,0,i?0:1),this._ctx&&this._ctx.arc(t,n,a,r,o,i),this._xi=fu(o)*a+t,this._yi=vu(o)*a+n,this},e.prototype.arcTo=function(t,n,a,r,o){return this._drawPendingPt(),this._ctx&&this._ctx.arcTo(t,n,a,r,o),this},e.prototype.rect=function(t,n,a,r){return this._drawPendingPt(),this._ctx&&this._ctx.rect(t,n,a,r),this.addData(Ln.R,t,n,a,r),this},e.prototype.closePath=function(){this._drawPendingPt(),this.addData(Ln.Z);var t=this._ctx,n=this._x0,a=this._y0;return t&&t.closePath(),this._xi=n,this._yi=a,this},e.prototype.fill=function(t){t&&t.fill(),this.toStatic()},e.prototype.stroke=function(t){t&&t.stroke(),this.toStatic()},e.prototype.len=function(){return this._len},e.prototype.setData=function(t){if(this._saveData){var n=t.length;!(this.data&&this.data.length===n)&&eb&&(this.data=new Float32Array(n));for(var a=0;a0&&i))for(var l=0;lc.length&&(this._expandData(),c=this.data);for(var d=0;d0&&(this._ctx&&this._ctx.lineTo(this._pendingPtX,this._pendingPtY),this._pendingPtDist=0)},e.prototype._expandData=function(){if(!(this.data instanceof Array)){for(var t=[],n=0;n11&&(this.data=new Float32Array(t)))}},e.prototype.getBoundingRect=function(){yi[0]=yi[1]=bi[0]=bi[1]=Number.MAX_VALUE,ql[0]=ql[1]=_i[0]=_i[1]=-Number.MAX_VALUE;var t=this.data,n=0,a=0,r=0,o=0,i;for(i=0;ia||il(_)>r||v===n-1)&&(m=Math.sqrt(b*b+_*_),o=g,i=y);break}case Ln.C:{var w=t[v++],S=t[v++],g=t[v++],y=t[v++],k=t[v++],T=t[v++];m=nSe(o,i,w,S,g,y,k,T,10),o=k,i=T;break}case Ln.Q:{var w=t[v++],S=t[v++],g=t[v++],y=t[v++];m=oSe(o,i,w,S,g,y,10),o=g,i=y;break}case Ln.A:var M=t[v++],E=t[v++],A=t[v++],I=t[v++],P=t[v++],R=t[v++],G=R+P;v+=1,h&&(l=fu(P)*A+M,s=vu(P)*I+E),m=Qy(A,I)*Jy(us,Math.abs(R)),o=fu(G)*A+M,i=vu(G)*I+E;break;case Ln.R:{l=o=t[v++],s=i=t[v++];var K=t[v++],H=t[v++];m=K*2+H*2;break}case Ln.Z:{var b=l-o,_=s-i;m=Math.sqrt(b*b+_*_),o=l,i=s;break}}m>=0&&(u[d++]=m,c+=m)}return this._pathLen=c,c},e.prototype.rebuildPath=function(t,n){var a=this.data,r=this._ux,o=this._uy,i=this._len,l,s,u,c,d,v,f=n<1,h,m,g=0,y=0,b,_=0,w,S;if(!(f&&(this._pathSegLen||this._calculateLength(),h=this._pathSegLen,m=this._pathLen,b=n*m,!b)))e:for(var k=0;k0&&(t.lineTo(w,S),_=0),T){case Ln.M:l=u=a[k++],s=c=a[k++],t.moveTo(u,c);break;case Ln.L:{d=a[k++],v=a[k++];var E=il(d-u),A=il(v-c);if(E>r||A>o){if(f){var I=h[y++];if(g+I>b){var P=(b-g)/I;t.lineTo(u*(1-P)+d*P,c*(1-P)+v*P);break e}g+=I}t.lineTo(d,v),u=d,c=v,_=0}else{var R=E*E+A*A;R>_&&(w=d,S=v,_=R)}break}case Ln.C:{var G=a[k++],K=a[k++],H=a[k++],z=a[k++],B=a[k++],W=a[k++];if(f){var I=h[y++];if(g+I>b){var P=(b-g)/I;wg(u,G,H,B,P,cu),wg(c,K,z,W,P,du),t.bezierCurveTo(cu[1],du[1],cu[2],du[2],cu[3],du[3]);break e}g+=I}t.bezierCurveTo(G,K,H,z,B,W),u=B,c=W;break}case Ln.Q:{var G=a[k++],K=a[k++],H=a[k++],z=a[k++];if(f){var I=h[y++];if(g+I>b){var P=(b-g)/I;Sg(u,G,H,P,cu),Sg(c,K,z,P,du),t.quadraticCurveTo(cu[1],du[1],cu[2],du[2]);break e}g+=I}t.quadraticCurveTo(G,K,H,z),u=H,c=z;break}case Ln.A:var N=a[k++],V=a[k++],Y=a[k++],X=a[k++],ee=a[k++],re=a[k++],ie=a[k++],le=!a[k++],ve=Y>X?Y:X,he=il(Y-X)>.001,J=ee+re,Z=!1;if(f){var I=h[y++];g+I>b&&(J=ee+re*(b-g)/I,Z=!0),g+=I}if(he&&t.ellipse?t.ellipse(N,V,Y,X,ie,ee,J,le):t.arc(N,V,ve,ee,J,le),Z)break e;M&&(l=fu(ee)*Y+N,s=vu(ee)*X+V),u=fu(J)*Y+N,c=vu(J)*X+V;break;case Ln.R:l=u=a[k],s=c=a[k+1],d=a[k++],v=a[k++];var j=a[k++],oe=a[k++];if(f){var I=h[y++];if(g+I>b){var se=b-g;t.moveTo(d,v),t.lineTo(d+Jy(se,j),v),se-=j,se>0&&t.lineTo(d+j,v+Jy(se,oe)),se-=oe,se>0&&t.lineTo(d+Qy(j-se,0),v+oe),se-=j,se>0&&t.lineTo(d,v+Qy(oe-se,0));break e}g+=I}t.rect(d,v,j,oe);break;case Ln.Z:if(f){var I=h[y++];if(g+I>b){var P=(b-g)/I;t.lineTo(u*(1-P)+l*P,c*(1-P)+s*P);break e}g+=I}t.closePath(),u=l,c=s}}},e.prototype.clone=function(){var t=new e,n=this.data;return t.data=n.slice?n.slice():Array.prototype.slice.call(n),t._len=this._len,t},e.prototype.canSave=function(){return!!this._saveData},e.CMD=Ln,e.initDefaultProps=function(){var t=e.prototype;t._saveData=!0,t._ux=0,t._uy=0,t._pendingPtDist=0,t._version=0}(),e}();function Wc(e,t,n,a,r,o,i){if(r===0)return!1;var l=r,s=0,u=e;if(i>t+l&&i>a+l||ie+l&&o>n+l||ot+d&&c>a+d&&c>o+d&&c>l+d||ce+d&&u>n+d&&u>r+d&&u>i+d||ut+u&&s>a+u&&s>o+u||se+u&&l>n+u&&l>r+u||ln||c+ur&&(r+=zf);var v=Math.atan2(s,l);return v<0&&(v+=zf),v>=a&&v<=r||v+zf>=a&&v+zf<=r}function hu(e,t,n,a,r,o){if(o>t&&o>a||or?l:0}var Xl=bc.CMD,pu=Math.PI*2,QCe=1e-4;function e5e(e,t){return Math.abs(e-t)t&&u>a&&u>o&&u>l||u1&&t5e(),f=Ua(t,a,o,l,bo[0]),v>1&&(h=Ua(t,a,o,l,bo[1]))),v===2?gt&&l>a&&l>o||l=0&&u<=1){for(var c=0,d=$r(t,a,o,u),v=0;vn||l<-n)return 0;var s=Math.sqrt(n*n-l*l);gr[0]=-s,gr[1]=s;var u=Math.abs(a-r);if(u<1e-4)return 0;if(u>=pu-1e-4){a=0,r=pu;var c=o?1:-1;return i>=gr[0]+e&&i<=gr[1]+e?c:0}if(a>r){var d=a;a=r,r=d}a<0&&(a+=pu,r+=pu);for(var v=0,f=0;f<2;f++){var h=gr[f];if(h+e>i){var m=Math.atan2(l,h),c=o?1:-1;m<0&&(m=pu+m),(m>=a&&m<=r||m+pu>=a&&m+pu<=r)&&(m>Math.PI/2&&m1&&(n||(l+=hu(s,u,c,d,a,r))),g&&(s=o[h],u=o[h+1],c=s,d=u),m){case Xl.M:c=o[h++],d=o[h++],s=c,u=d;break;case Xl.L:if(n){if(Wc(s,u,o[h],o[h+1],t,a,r))return!0}else l+=hu(s,u,o[h],o[h+1],a,r)||0;s=o[h++],u=o[h++];break;case Xl.C:if(n){if(XCe(s,u,o[h++],o[h++],o[h++],o[h++],o[h],o[h+1],t,a,r))return!0}else l+=n5e(s,u,o[h++],o[h++],o[h++],o[h++],o[h],o[h+1],a,r)||0;s=o[h++],u=o[h++];break;case Xl.Q:if(n){if(ZCe(s,u,o[h++],o[h++],o[h],o[h+1],t,a,r))return!0}else l+=a5e(s,u,o[h++],o[h++],o[h],o[h+1],a,r)||0;s=o[h++],u=o[h++];break;case Xl.A:var y=o[h++],b=o[h++],_=o[h++],w=o[h++],S=o[h++],k=o[h++];h+=1;var T=!!(1-o[h++]);v=Math.cos(S)*_+y,f=Math.sin(S)*w+b,g?(c=v,d=f):l+=hu(s,u,v,f,a,r);var M=(a-y)*w/_+y;if(n){if(JCe(y,b,w,S,S+k,T,t,M,r))return!0}else l+=r5e(y,b,w,S,S+k,T,M,r);s=Math.cos(S+k)*_+y,u=Math.sin(S+k)*w+b;break;case Xl.R:c=s=o[h++],d=u=o[h++];var E=o[h++],A=o[h++];if(v=c+E,f=d+A,n){if(Wc(c,d,v,d,t,a,r)||Wc(v,d,v,f,t,a,r)||Wc(v,f,c,f,t,a,r)||Wc(c,f,c,d,t,a,r))return!0}else l+=hu(v,d,v,f,a,r),l+=hu(c,f,c,d,a,r);break;case Xl.Z:if(n){if(Wc(s,u,c,d,t,a,r))return!0}else l+=hu(s,u,c,d,a,r);s=c,u=d;break}}return!n&&!e5e(u,d)&&(l+=hu(s,u,c,d,a,r)||0),l!==0}function o5e(e,t,n){return w$(e,0,!1,t,n)}function i5e(e,t,n,a){return w$(e,t,!0,n,a)}var S$=zn({fill:"#000",stroke:null,strokePercent:1,fillOpacity:1,strokeOpacity:1,lineDashOffset:0,lineWidth:1,lineCap:"butt",miterLimit:10,strokeNoScale:!1,strokeFirst:!1},tc),l5e={style:zn({fill:!0,stroke:!0,strokePercent:!0,fillOpacity:!0,strokeOpacity:!0,lineDashOffset:!0,lineWidth:!0,miterLimit:!0},o2.style)},nb=Q1.concat(["invisible","culling","z","z2","zlevel","parent"]),Fn=function(e){At(t,e);function t(n){return e.call(this,n)||this}return t.prototype.update=function(){var n=this;e.prototype.update.call(this);var a=this.style;if(a.decal){var r=this._decalEl=this._decalEl||new t;r.buildPath===t.prototype.buildPath&&(r.buildPath=function(s){n.buildPath(s,n.shape)}),r.silent=!0;var o=r.style;for(var i in a)o[i]!==a[i]&&(o[i]=a[i]);o.fill=a.fill?a.decal:null,o.decal=null,o.shadowColor=null,a.strokeFirst&&(o.stroke=null);for(var l=0;l.5?P_:a>.2?TSe:D_}else if(n)return D_}return P_},t.prototype.getInsideTextStroke=function(n){var a=this.style.fill;if(Ut(a)){var r=this.__zr,o=!!(r&&r.isDarkMode()),i=Cg(n,0)0))},t.prototype.hasFill=function(){var n=this.style,a=n.fill;return a!=null&&a!=="none"},t.prototype.getBoundingRect=function(){var n=this._rect,a=this.style,r=!n;if(r){var o=!1;this.path||(o=!0,this.createPathProxy());var i=this.path;(o||this.__dirty&rd)&&(i.beginPath(),this.buildPath(i,this.shape,!1),this.pathUpdated()),n=i.getBoundingRect()}if(this._rect=n,this.hasStroke()&&this.path&&this.path.len()>0){var l=this._rectStroke||(this._rectStroke=n.clone());if(this.__dirty||r){l.copy(n);var s=a.strokeNoScale?this.getLineScale():1,u=a.lineWidth;if(!this.hasFill()){var c=this.strokeContainThreshold;u=Math.max(u,c??4)}s>1e-10&&(l.width+=u/s,l.height+=u/s,l.x-=u/s/2,l.y-=u/s/2)}return l}return n},t.prototype.contain=function(n,a){var r=this.transformCoordToLocal(n,a),o=this.getBoundingRect(),i=this.style;if(n=r[0],a=r[1],o.contain(n,a)){var l=this.path;if(this.hasStroke()){var s=i.lineWidth,u=i.strokeNoScale?this.getLineScale():1;if(u>1e-10&&(this.hasFill()||(s=Math.max(s,this.strokeContainThreshold)),i5e(l,s/u,n,a)))return!0}if(this.hasFill())return o5e(l,n,a)}return!1},t.prototype.dirtyShape=function(){this.__dirty|=rd,this._rect&&(this._rect=null),this._decalEl&&this._decalEl.dirtyShape(),this.markRedraw()},t.prototype.dirty=function(){this.dirtyStyle(),this.dirtyShape()},t.prototype.animateShape=function(n){return this.animate("shape",n)},t.prototype.updateDuringAnimation=function(n){n==="style"?this.dirtyStyle():n==="shape"?this.dirtyShape():this.markRedraw()},t.prototype.attrKV=function(n,a){n==="shape"?this.setShape(a):e.prototype.attrKV.call(this,n,a)},t.prototype.setShape=function(n,a){var r=this.shape;return r||(r=this.shape={}),typeof n=="string"?r[n]=a:vt(r,n),this.dirtyShape(),this},t.prototype.shapeChanged=function(){return!!(this.__dirty&rd)},t.prototype.createStyle=function(n){return J1(S$,n)},t.prototype._innerSaveToNormal=function(n){e.prototype._innerSaveToNormal.call(this,n);var a=this._normalState;n.shape&&!a.shape&&(a.shape=vt({},this.shape))},t.prototype._applyStateObj=function(n,a,r,o,i,l){if(e.prototype._applyStateObj.call(this,n,a,r,o,i,l),this.__inHover!==t2){var s=!(a&&o),u;if(a&&a.shape?i?o?u=a.shape:(u=vt({},r.shape),vt(u,a.shape)):(u=vt({},o?this.shape:r.shape),vt(u,a.shape)):s&&(u=r.shape),u)if(i){this.shape=vt({},this.shape);for(var c={},d=la(u),v=0;vr&&(d=l+s,l*=r/d,s*=r/d),u+c>r&&(d=u+c,u*=r/d,c*=r/d),s+u>o&&(d=s+u,s*=o/d,u*=o/d),l+c>o&&(d=l+c,l*=o/d,c*=o/d),e.moveTo(n+l,a),e.lineTo(n+r-s,a),s!==0&&e.arc(n+r-s,a+s,s,-Math.PI/2,0),e.lineTo(n+r,a+o-u),u!==0&&e.arc(n+r-u,a+o-u,u,0,Math.PI/2),e.lineTo(n+c,a+o),c!==0&&e.arc(n+c,a+o-c,c,Math.PI/2,Math.PI),e.lineTo(n,a+l),l!==0&&e.arc(n+l,a+l,l,Math.PI,Math.PI*1.5),e.closePath()}var fd=Math.round;function C$(e,t,n){if(t){var a=t.x1,r=t.x2,o=t.y1,i=t.y2;e.x1=a,e.x2=r,e.y1=o,e.y2=i;var l=n&&n.lineWidth;return l&&(fd(a*2)===fd(r*2)&&(e.x1=e.x2=zu(a,l,!0)),fd(o*2)===fd(i*2)&&(e.y1=e.y2=zu(o,l,!0))),e}}function x$(e,t,n){if(t){var a=t.x,r=t.y,o=t.width,i=t.height;e.x=a,e.y=r,e.width=o,e.height=i;var l=n&&n.lineWidth;return l&&(e.x=zu(a,l,!0),e.y=zu(r,l,!0),e.width=Math.max(zu(a+o,l,!1)-e.x,o===0?0:1),e.height=Math.max(zu(r+i,l,!1)-e.y,i===0?0:1)),e}}function zu(e,t,n){if(!t)return e;var a=fd(e*2);return(a+fd(t))%2===0?a/2:(a+(n?1:-1))/2}var v5e=function(){function e(){this.x=0,this.y=0,this.width=0,this.height=0}return e}(),h5e={},ga=function(e){At(t,e);function t(n){return e.call(this,n)||this}return t.prototype.getDefaultShape=function(){return new v5e},t.prototype.buildPath=function(n,a){var r,o,i,l;if(this.subPixelOptimize){var s=x$(h5e,a,this.style);r=s.x,o=s.y,i=s.width,l=s.height,s.r=a.r,a=s}else r=a.x,o=a.y,i=a.width,l=a.height;a.r?f5e(n,a):n.rect(r,o,i,l)},t.prototype.isZeroArea=function(){return!this.shape.width||!this.shape.height},t}(Fn);ga.prototype.type="rect";var a9={fill:"#000"},r9=2,wi={},p5e={style:zn({fill:!0,stroke:!0,fillOpacity:!0,strokeOpacity:!0,lineWidth:!0,fontSize:!0,lineHeight:!0,width:!0,height:!0,textShadowColor:!0,textShadowBlur:!0,textShadowOffsetX:!0,textShadowOffsetY:!0,backgroundColor:!0,padding:!0,borderColor:!0,borderWidth:!0,borderRadius:!0},o2.style)},Er=function(e){At(t,e);function t(n){var a=e.call(this)||this;return a.type="text",a._children=[],a._defaultStyle=a9,a.attr(n),a}return t.prototype.childrenRef=function(){return this._children},t.prototype.update=function(){e.prototype.update.call(this),this.styleChanged()&&this._updateSubTexts();for(var n=0;n0,P=0;P=0&&(G=k[R],G.align==="right");)this._placeToken(G,n,M,y,P,"right",_),E-=G.width,P-=G.width,R--;for(I+=(c-(I-g)-(b-P)-E)/2;A<=R;)G=k[A],this._placeToken(G,n,M,y,I+G.width/2,"center",_),I+=G.width,A++;y+=M}},t.prototype._placeToken=function(n,a,r,o,i,l,s){var u=a.rich[n.styleName]||{};u.text=n.text;var c=n.verticalAlign,d=o+r/2;c==="top"?d=o+n.height/2:c==="bottom"&&(d=o+r-n.height/2);var v=!n.isLineHolder&&ab(u);v&&this._renderBackground(u,a,l==="right"?i-n.width:l==="center"?i-n.width/2:i,d-n.height/2,n.width,n.height);var f=!!u.backgroundColor,h=n.textPadding;h&&(i=c9(i,l,h),d-=n.height/2-h[0]-n.innerHeight/2);var m=this._getOrCreateChild(Ag),g=m.createStyle();m.useStyle(g);var y=this._defaultStyle,b=!1,_=0,w=!1,S=u9("fill"in u?u.fill:"fill"in a?a.fill:(b=!0,y.fill)),k=s9("stroke"in u?u.stroke:"stroke"in a?a.stroke:!f&&!s&&(!y.autoStroke||b)?(_=r9,w=!0,y.stroke):null),T=u.textShadowBlur>0||a.textShadowBlur>0;g.text=n.text,g.x=i,g.y=d,T&&(g.shadowBlur=u.textShadowBlur||a.textShadowBlur||0,g.shadowColor=u.textShadowColor||a.textShadowColor||"transparent",g.shadowOffsetX=u.textShadowOffsetX||a.textShadowOffsetX||0,g.shadowOffsetY=u.textShadowOffsetY||a.textShadowOffsetY||0),g.textAlign=l,g.textBaseline="middle",g.font=n.font||Vs,g.opacity=Ju(u.opacity,a.opacity,1),i9(g,u),k&&(g.lineWidth=Ju(u.lineWidth,a.lineWidth,_),g.lineDash=jt(u.lineDash,a.lineDash),g.lineDashOffset=a.lineDashOffset||0,g.stroke=k),S&&(g.fill=S),m.setBoundingRect(z_(g,n.contentWidth,n.contentHeight,w?0:null))},t.prototype._renderBackground=function(n,a,r,o,i,l){var s=n.backgroundColor,u=n.borderWidth,c=n.borderColor,d=s&&s.image,v=s&&!d,f=n.borderRadius,h=this,m,g;if(v||n.lineHeight||u&&c){m=this._getOrCreateChild(ga),m.useStyle(m.createStyle()),m.style.fill=null;var y=m.shape;y.x=r,y.y=o,y.width=i,y.height=l,y.r=f,m.dirtyShape()}if(v){var b=m.style;b.fill=s||null,b.fillOpacity=jt(n.fillOpacity,1)}else if(d){g=this._getOrCreateChild(js),g.onload=function(){h.dirtyStyle()};var _=g.style;_.image=s.image,_.x=r,_.y=o,_.width=i,_.height=l}if(u&&c){var b=m.style;b.lineWidth=u,b.stroke=c,b.strokeOpacity=jt(n.strokeOpacity,1),b.lineDash=n.borderDash,b.lineDashOffset=n.borderDashOffset||0,m.strokeContainThreshold=0,m.hasFill()&&m.hasStroke()&&(b.strokeFirst=!0,b.lineWidth*=2)}var w=(m||g).style;w.shadowBlur=n.shadowBlur||0,w.shadowColor=n.shadowColor||"transparent",w.shadowOffsetX=n.shadowOffsetX||0,w.shadowOffsetY=n.shadowOffsetY||0,w.opacity=Ju(n.opacity,a.opacity,1)},t.makeFont=function(n){var a="";return b5e(n)&&(a=[n.fontStyle,n.fontWeight,y5e(n.fontSize),n.fontFamily||"sans-serif"].join(" ")),a&&Di(a)||n.textFont||n.font},t}(wp),m5e={left:!0,right:1,center:1},g5e={top:1,bottom:1,middle:1},o9=["fontStyle","fontWeight","fontSize","fontFamily"];function y5e(e){return typeof e=="string"&&(e.indexOf("px")!==-1||e.indexOf("rem")!==-1||e.indexOf("em")!==-1)?e:isNaN(+e)?Zw+"px":e+"px"}function i9(e,t){for(var n=0;n=0,o=!1;if(e instanceof Fn){var i=A$(e),l=r&&i.selectFill||i.normalFill,s=r&&i.selectStroke||i.normalStroke;if(Kc(l)||Kc(s)){a=a||{};var u=a.style||{};u.fill==="inherit"?(o=!0,a=vt({},a),u=vt({},u),u.fill=l):!Kc(u.fill)&&Kc(l)?(o=!0,a=vt({},a),u=vt({},u),u.fill=Mk(l)):!Kc(u.stroke)&&Kc(s)&&(o||(a=vt({},a),u=vt({},u)),u.stroke=Mk(s)),a.style=u}}if(a&&a.z2==null){o||(a=vt({},a));var c=e.z2EmphasisLift;a.z2=e.z2+(c??x5e)}return a}function I5e(e,t,n){if(n&&n.z2==null){n=vt({},n);var a=e.z2SelectLift;n.z2=e.z2+(a??k5e)}return n}function L5e(e,t,n){var a=Pn(e.currentStates,t)>=0,r=e.style.opacity,o=a?null:E5e(e,["opacity"],t,{opacity:1});n=n||{};var i=n.style||{};return i.opacity==null&&(n=vt({},n),i=vt({opacity:a?r:o.opacity*.1},i),n.style=i),n}function rb(e,t){var n=this.states[e];if(this.style){if(e==="emphasis")return A5e(this,e,t,n);if(e==="blur")return L5e(this,e,n);if(e==="select")return I5e(this,e,n)}return n}function P5e(e){e.stateProxy=rb;var t=e.getTextContent(),n=e.getTextGuideLine();t&&(t.stateProxy=rb),n&&(n.stateProxy=rb)}function m9(e,t){!R$(e,t)&&!e.__highByOuter&&Ul(e,I$)}function g9(e,t){!R$(e,t)&&!e.__highByOuter&&Ul(e,L$)}function Dg(e,t){e.__highByOuter|=1<<(t||0),Ul(e,I$)}function Og(e,t){!(e.__highByOuter&=~(1<<(t||0)))&&Ul(e,L$)}function D5e(e){Ul(e,_8)}function D$(e){Ul(e,P$)}function O$(e){Ul(e,T5e)}function $$(e){Ul(e,M5e)}function R$(e,t){return e.__highDownSilentOnTouch&&t.zrByTouch}function N$(e){var t=e.getModel(),n=[],a=[];t.eachComponent(function(r,o){var i=g8(o),l=C5e(e,o),s=r==="series";!s&&a.push(l),i.isBlured&&(l.group.traverse(function(u){P$(u)}),s&&n.push(o)),i.isBlured=!1}),we(a,function(r){r&&r.toggleBlurSeries&&r.toggleBlurSeries(n,!1,t)})}function K_(e,t,n,a){var r=a.getModel();n=n||"coordinateSystem";function o(u,c){for(var d=0;d0){var l={dataIndex:i,seriesIndex:n.seriesIndex};o!=null&&(l.dataType=o),t.push(l)}})}),t}function G_(e,t,n){B$(e,!0),Ul(e,P5e),z5e(e,t,n)}function V5e(e){B$(e,!1)}function Ah(e,t,n,a){a?V5e(e):G_(e,t,n)}function z5e(e,t,n){var a=In(e);t!=null?(a.focus=t,a.blurScope=n):a.focus&&(a.focus=null)}var b9=["emphasis","blur","select"],F5e={itemStyle:"getItemStyle",lineStyle:"getLineStyle",areaStyle:"getAreaStyle"};function $g(e,t,n,a){n=n||"itemStyle";for(var r=0;r1&&(i*=ob(h),l*=ob(h));var m=(r===o?-1:1)*ob((i*i*(l*l)-i*i*(f*f)-l*l*(v*v))/(i*i*(f*f)+l*l*(v*v)))||0,g=m*i*f/l,y=m*-l*v/i,b=(e+n)/2+f0(d)*g-d0(d)*y,_=(t+a)/2+d0(d)*g+f0(d)*y,w=C9([1,0],[(v-g)/i,(f-y)/l]),S=[(v-g)/i,(f-y)/l],k=[(-1*v-g)/i,(-1*f-y)/l],T=C9(S,k);if(q_(S,k)<=-1&&(T=Ff),q_(S,k)>=1&&(T=0),T<0){var M=Math.round(T/Ff*1e6)/1e6;T=Ff*2+M%2*Ff}c.addData(u,b,_,i,l,w,T,d,o)}var Y5e=/([mlvhzcqtsa])([^mlvhzcqtsa]*)/ig,j5e=/-?([0-9]*\.)?[0-9]+([eE]-?[0-9]+)?/g;function q5e(e){var t=new bc;if(!e)return t;var n=0,a=0,r=n,o=a,i,l=bc.CMD,s=e.match(Y5e);if(!s)return t;for(var u=0;uG*G+K*K&&(M=A,E=I),{cx:M,cy:E,x0:-c,y0:-d,x1:M*(r/S-1),y1:E*(r/S-1)}}function axe(e){var t;if(Lt(e)){var n=e.length;if(!n)return e;n===1?t=[e[0],e[0],0,0]:n===2?t=[e[0],e[0],e[1],e[1]]:n===3?t=e.concat(e[2]):t=e}else t=[e,e,e,e];return t}function rxe(e,t){var n,a=vv(t.r,0),r=vv(t.r0||0,0),o=a>0,i=r>0;if(!(!o&&!i)){if(o||(a=r,r=0),r>a){var l=a;a=r,r=l}var s=t.startAngle,u=t.endAngle;if(!(isNaN(s)||isNaN(u))){var c=t.cx,d=t.cy,v=!!t.clockwise,f=k9(u-s),h=f>ib&&f%ib;if(h>Uo&&(f=h),!(a>Uo))e.moveTo(c,d);else if(f>ib-Uo)e.moveTo(c+a*Gc(s),d+a*mu(s)),e.arc(c,d,a,s,u,!v),r>Uo&&(e.moveTo(c+r*Gc(u),d+r*mu(u)),e.arc(c,d,r,u,s,v));else{var m=void 0,g=void 0,y=void 0,b=void 0,_=void 0,w=void 0,S=void 0,k=void 0,T=void 0,M=void 0,E=void 0,A=void 0,I=void 0,P=void 0,R=void 0,G=void 0,K=a*Gc(s),H=a*mu(s),z=r*Gc(u),B=r*mu(u),W=f>Uo;if(W){var N=t.cornerRadius;N&&(n=axe(N),m=n[0],g=n[1],y=n[2],b=n[3]);var V=k9(a-r)/2;if(_=Si(V,y),w=Si(V,b),S=Si(V,m),k=Si(V,g),E=T=vv(_,w),A=M=vv(S,k),(T>Uo||M>Uo)&&(I=a*Gc(u),P=a*mu(u),R=r*Gc(s),G=r*mu(s),fUo){var he=Si(y,E),J=Si(b,E),Z=v0(R,G,K,H,a,he,v),j=v0(I,P,z,B,a,J,v);e.moveTo(c+Z.cx+Z.x0,d+Z.cy+Z.y0),E0&&e.arc(c+Z.cx,d+Z.cy,he,Xa(Z.y0,Z.x0),Xa(Z.y1,Z.x1),!v),e.arc(c,d,a,Xa(Z.cy+Z.y1,Z.cx+Z.x1),Xa(j.cy+j.y1,j.cx+j.x1),!v),J>0&&e.arc(c+j.cx,d+j.cy,J,Xa(j.y1,j.x1),Xa(j.y0,j.x0),!v))}else e.moveTo(c+K,d+H),e.arc(c,d,a,s,u,!v);if(!(r>Uo)||!W)e.lineTo(c+z,d+B);else if(A>Uo){var he=Si(m,A),J=Si(g,A),Z=v0(z,B,I,P,r,-J,v),j=v0(K,H,R,G,r,-he,v);e.lineTo(c+Z.cx+Z.x0,d+Z.cy+Z.y0),A0&&e.arc(c+Z.cx,d+Z.cy,J,Xa(Z.y0,Z.x0),Xa(Z.y1,Z.x1),!v),e.arc(c,d,r,Xa(Z.cy+Z.y1,Z.cx+Z.x1),Xa(j.cy+j.y1,j.cx+j.x1),v),he>0&&e.arc(c+j.cx,d+j.cy,he,Xa(j.y1,j.x1),Xa(j.y0,j.x0),!v))}else e.lineTo(c+z,d+B),e.arc(c,d,r,u,s,v)}e.closePath()}}}var oxe=function(){function e(){this.cx=0,this.cy=0,this.r0=0,this.r=0,this.startAngle=0,this.endAngle=Math.PI*2,this.clockwise=!0,this.cornerRadius=0}return e}(),qs=function(e){At(t,e);function t(n){return e.call(this,n)||this}return t.prototype.getDefaultShape=function(){return new oxe},t.prototype.buildPath=function(n,a){rxe(n,a)},t.prototype.isZeroArea=function(){return this.shape.startAngle===this.shape.endAngle||this.shape.r===this.shape.r0},t}(Fn);qs.prototype.type="sector";var ixe=function(){function e(){this.cx=0,this.cy=0,this.r=0,this.r0=0}return e}(),C8=function(e){At(t,e);function t(n){return e.call(this,n)||this}return t.prototype.getDefaultShape=function(){return new ixe},t.prototype.buildPath=function(n,a){var r=a.cx,o=a.cy,i=Math.PI*2;n.moveTo(r+a.r,o),n.arc(r,o,a.r,0,i,!1),n.moveTo(r+a.r0,o),n.arc(r,o,a.r0,0,i,!0)},t}(Fn);C8.prototype.type="ring";function lxe(e,t,n,a){var r=[],o=[],i=[],l=[],s,u,c,d;if(a){c=[1/0,1/0],d=[-1/0,-1/0];for(var v=0,f=e.length;v=2){if(a){var o=lxe(r,a,n,t.smoothConstraint);e.moveTo(r[0][0],r[0][1]);for(var i=r.length,l=0;l<(n?i:i-1);l++){var s=o[l*2],u=o[l*2+1],c=r[(l+1)%i];e.bezierCurveTo(s[0],s[1],u[0],u[1],c[0],c[1])}}else{e.moveTo(r[0][0],r[0][1]);for(var l=1,d=r.length;lyu[1]){if(o=!1,Na.negativeSize||a)return o;var s=h0(yu[0]-gu[1]),u=h0(gu[0]-yu[1]);lb(s,u)>m0.len()&&(s=u||!Na.bidirectional)&&(fn.scale(p0,l,-u*r),Na.useDir&&Na.calcDirMTV()))}}return o},e.prototype._getProjMinMaxOnAxis=function(t,n,a){for(var r=this._axes[t],o=this._origin,i=n[0].dot(r)+o[t],l=i,s=i,u=1;u0){var d=c.duration,v=c.delay,f=c.easing,h={duration:d,delay:v||0,easing:f,done:o,force:!!o||!!i,setToFinal:!u,scope:e,during:i};l?t.animateFrom(n,h):t.animateTo(n,h)}else t.stopAnimation(),!l&&t.attr(n),i&&i(1),o&&o()}function Ar(e,t,n,a,r,o){T8("update",e,t,n,a,r,o)}function Do(e,t,n,a,r,o){T8("enter",e,t,n,a,r,o)}function zv(e){if(!e.__zr)return!0;for(var t=0;tCa(o[1])?o[0]>0?"right":"left":o[1]>0?"bottom":"top"}function E9(e){return!e.isGroup}function Dxe(e){return e.shape!=null}function Q$(e,t,n){if(!e||!t)return;function a(i){var l={};return i.traverse(function(s){E9(s)&&s.anid&&(l[s.anid]=s)}),l}function r(i){var l={x:i.x,y:i.y,rotation:i.rotation};return Dxe(i)&&(l.shape=yn(i.shape)),l}var o=a(e);t.traverse(function(i){if(E9(i)&&i.anid){var l=o[i.anid];if(l){var s=r(i);i.attr(r(l)),Ar(i,s,n,In(i).dataIndex)}}})}function Oxe(e,t){return Xt(e,function(n){var a=n[0];a=An(a,t.x),a=sr(a,t.x+t.width);var r=n[1];return r=An(r,t.y),r=sr(r,t.y+t.height),[a,r]})}function $xe(e,t){var n=An(e.x,t.x),a=sr(e.x+e.width,t.x+t.width),r=An(e.y,t.y),o=sr(e.y+e.height,t.y+t.height);if(a>=n&&o>=r)return{x:n,y:r,width:a-n,height:o-r}}function L8(e,t,n){var a=vt({rectHover:!0},t),r=a.style={strokeNoScale:!0};if(n=n||{x:-1,y:-1,width:2,height:2},e)return e.indexOf("image://")===0?(r.image=e.slice(8),zn(r,n),new js(a)):A8(e.replace("path://",""),a,n,"center")}function Rxe(e,t,n,a,r){for(var o=0,i=r[r.length-1];o1)return!1;var g=sb(f,h,c,d)/v;return!(g<0||g>1)}function sb(e,t,n,a){return e*a-n*t}function Nxe(e){return e<=1e-6&&e>=-1e-6}function Ng(e,t,n,a,r){return t==null||(Gn(t)?Jn[0]=Jn[1]=Jn[2]=Jn[3]=t:(Jn[0]=t[0],Jn[1]=t[1],Jn[2]=t[2],Jn[3]=t[3]),a&&(Jn[0]=An(0,Jn[0]),Jn[1]=An(0,Jn[1]),Jn[2]=An(0,Jn[2]),Jn[3]=An(0,Jn[3])),n&&(Jn[0]=-Jn[0],Jn[1]=-Jn[1],Jn[2]=-Jn[2],Jn[3]=-Jn[3]),A9(e,Jn,"x","width",3,1,r&&r[0]||0),A9(e,Jn,"y","height",0,2,r&&r[1]||0)),e}var Jn=[0,0,0,0];function A9(e,t,n,a,r,o,i){var l=t[o]+t[r],s=e[a];e[a]+=l,i=An(0,sr(i,s)),e[a]=0?-t[r]:t[o]>=0?s+t[o]:Ca(l)>1e-8?(s-i)*t[r]/l:0):e[n]-=t[r]}function d2(e){var t=e.itemTooltipOption,n=e.componentModel,a=e.itemName,r=Ut(t)?{formatter:t}:t,o=n.mainType,i=n.componentIndex,l={componentType:o,name:a,$vars:["name"]};l[o+"Index"]=i;var s=e.formatterParamsExtra;s&&we(la(s),function(c){Sr(l,c)||(l[c]=s[c],l.$vars.push(c))});var u=In(e.el);u.componentMainType=o,u.componentIndex=i,u.tooltipConfig={name:a,option:zn({content:a,encodeHTMLContent:!0,formatterParams:l},r)}}function Z_(e,t){var n;e.isGroup&&(n=t(e)),n||e.traverse(t)}function f2(e,t){if(e)if(Lt(e))for(var n=0;nt&&(t=i),it&&(n=t=0),{min:n,max:t}}function nR(e,t,n){aR(e,t,n,-1/0)}function aR(e,t,n,a){if(e.ignoreModelZ)return a;var r=e.getTextContent(),o=e.getTextGuideLine(),i=e.isGroup;if(i)for(var l=e.childrenRef(),s=0;s=0&&l.push(s)}),l}}function iR(e,t){return Bn(Bn({},e,!0),t,!0)}const ake={time:{month:["January","February","March","April","May","June","July","August","September","October","November","December"],monthAbbr:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],dayOfWeek:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dayOfWeekAbbr:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"]},legend:{selector:{all:"All",inverse:"Inv"}},toolbox:{brush:{title:{rect:"Box Select",polygon:"Lasso Select",lineX:"Horizontally Select",lineY:"Vertically Select",keep:"Keep Selections",clear:"Clear Selections"}},dataView:{title:"Data View",lang:["Data View","Close","Refresh"]},dataZoom:{title:{zoom:"Zoom",back:"Zoom Reset"}},magicType:{title:{line:"Switch to Line Chart",bar:"Switch to Bar Chart",stack:"Stack",tiled:"Tile"}},restore:{title:"Restore"},saveAsImage:{title:"Save as Image",lang:["Right Click to Save Image"]}},series:{typeNames:{pie:"Pie chart",bar:"Bar chart",line:"Line chart",scatter:"Scatter plot",effectScatter:"Ripple scatter plot",radar:"Radar chart",tree:"Tree",treemap:"Treemap",boxplot:"Boxplot",candlestick:"Candlestick",k:"K line chart",heatmap:"Heat map",map:"Map",parallel:"Parallel coordinate map",lines:"Line graph",graph:"Relationship graph",sankey:"Sankey diagram",funnel:"Funnel chart",gauge:"Gauge",pictorialBar:"Pictorial bar",themeRiver:"Theme River Map",sunburst:"Sunburst",custom:"Custom chart",chart:"Chart"}},aria:{general:{withTitle:'This is a chart about "{title}"',withoutTitle:"This is a chart"},series:{single:{prefix:"",withName:" with type {seriesType} named {seriesName}.",withoutName:" with type {seriesType}."},multiple:{prefix:". It consists of {seriesCount} series count.",withName:" The {seriesId} series is a {seriesType} representing {seriesName}.",withoutName:" The {seriesId} series is a {seriesType}.",separator:{middle:"",end:""}}},data:{allData:"The data is as follows: ",partialData:"The first {displayCnt} items are: ",withName:"the data for {name} is {value}",withoutName:"{value}",separator:{middle:", ",end:". "}}}},rke={time:{month:["一月","二月","三月","四月","五月","六月","七月","八月","九月","十月","十一月","十二月"],monthAbbr:["1月","2月","3月","4月","5月","6月","7月","8月","9月","10月","11月","12月"],dayOfWeek:["星期日","星期一","星期二","星期三","星期四","星期五","星期六"],dayOfWeekAbbr:["日","一","二","三","四","五","六"]},legend:{selector:{all:"全选",inverse:"反选"}},toolbox:{brush:{title:{rect:"矩形选择",polygon:"圈选",lineX:"横向选择",lineY:"纵向选择",keep:"保持选择",clear:"清除选择"}},dataView:{title:"数据视图",lang:["数据视图","关闭","刷新"]},dataZoom:{title:{zoom:"区域缩放",back:"区域缩放还原"}},magicType:{title:{line:"切换为折线图",bar:"切换为柱状图",stack:"切换为堆叠",tiled:"切换为平铺"}},restore:{title:"还原"},saveAsImage:{title:"保存为图片",lang:["右键另存为图片"]}},series:{typeNames:{pie:"饼图",bar:"柱状图",line:"折线图",scatter:"散点图",effectScatter:"涟漪散点图",radar:"雷达图",tree:"树图",treemap:"矩形树图",boxplot:"箱型图",candlestick:"K线图",k:"K线图",heatmap:"热力图",map:"地图",parallel:"平行坐标图",lines:"线图",graph:"关系图",sankey:"桑基图",funnel:"漏斗图",gauge:"仪表盘图",pictorialBar:"象形柱图",themeRiver:"主题河流图",sunburst:"旭日图",custom:"自定义图表",chart:"图表"}},aria:{general:{withTitle:"这是一个关于“{title}”的图表。",withoutTitle:"这是一个图表,"},series:{single:{prefix:"",withName:"图表类型是{seriesType},表示{seriesName}。",withoutName:"图表类型是{seriesType}。"},multiple:{prefix:"它由{seriesCount}个图表系列组成。",withName:"第{seriesId}个系列是一个表示{seriesName}的{seriesType},",withoutName:"第{seriesId}个系列是一个{seriesType},",separator:{middle:";",end:"。"}}},data:{allData:"其数据是——",partialData:"其中,前{displayCnt}项是——",withName:"{name}的数据是{value}",withoutName:"{value}",separator:{middle:",",end:""}}}};var Bg="ZH",O8="EN",Td=O8,_m={},$8={},lR=hn.domSupported?function(){var e=(document.documentElement.lang||navigator.language||navigator.browserLanguage||Td).toUpperCase();return e.indexOf(Bg)>-1?Bg:Td}():Td;function sR(e,t){e=e.toUpperCase(),$8[e]=new fa(t),_m[e]=t}function oke(e){if(Ut(e)){var t=_m[e.toUpperCase()]||{};return e===Bg||e===O8?yn(t):Bn(yn(t),yn(_m[Td]),!1)}else return Bn(yn(e),yn(_m[Td]),!1)}function ike(e){return $8[e]}function lke(){return $8[Td]}sR(O8,ake);sR(Bg,rke);var ske=null;function m2(){return ske}function uR(e,t){t.breakOption;var n=t.breakParsed;return n}function R8(e){var t=e.brk;return t?t.breaks:[]}function Vg(e){var t=e.brk;return t?t.hasBreaks():!1}var N8=1e3,B8=N8*60,Hv=B8*60,To=Hv*24,R9=To*365,uke={year:/({yyyy}|{yy})/,month:/({MMMM}|{MMM}|{MM}|{M})/,day:/({dd}|{d})/,hour:/({HH}|{H}|{hh}|{h})/,minute:/({mm}|{m})/,second:/({ss}|{s})/,millisecond:/({SSS}|{S})/},wm={year:"{yyyy}",month:"{MMM}",day:"{d}",hour:"{HH}:{mm}",minute:"{HH}:{mm}",second:"{HH}:{mm}:{ss}",millisecond:"{HH}:{mm}:{ss} {SSS}"},cke="{yyyy}-{MM}-{dd} {HH}:{mm}:{ss} {SSS}",y0="{yyyy}-{MM}-{dd}",N9={year:"{yyyy}",month:"{yyyy}-{MM}",day:y0,hour:y0+" "+wm.hour,minute:y0+" "+wm.minute,second:y0+" "+wm.second,millisecond:cke},ac=["year","month","day","hour","minute","second","millisecond"],dke=["year","half-year","quarter","month","week","half-week","day","half-day","quarter-day","hour","minute","second","millisecond"];function fke(e){return!Ut(e)&&!dn(e)?vke(e):e}function vke(e){e=e||{};var t={},n=!0;return we(ac,function(a){n&&(n=e[a]==null)}),we(ac,function(a,r){var o=e[a];t[a]={};for(var i=null,l=r;l>=0;l--){var s=ac[l],u=Yt(o)&&!Lt(o)?o[s]:o,c=void 0;Lt(u)?(c=u.slice(),i=c[0]||""):Ut(u)?(i=u,c=[i]):(i==null?i=wm[a]:uke[s].test(i)||(i=t[s][s][0]+" "+i),c=[i],n&&(c[1]="{primary|"+i+"}")),t[a][s]=c}}),t}function Zl(e,t){return e+="","0000".substr(0,t-e.length)+e}function Wv(e){switch(e){case"half-year":case"quarter":return"month";case"week":case"half-week":return"day";case"half-day":case"quarter-day":return"hour";default:return e}}function hke(e){return e===Wv(e)}function pke(e){switch(e){case"year":case"month":return"day";case"millisecond":return"millisecond";default:return"second"}}function g2(e,t,n,a){var r=bf(e),o=r[cR(n)](),i=r[V8(n)]()+1,l=Math.floor((i-1)/3)+1,s=r[z8(n)](),u=r["get"+(n?"UTC":"")+"Day"](),c=r[F8(n)](),d=(c-1)%12+1,v=r[H8(n)](),f=r[W8(n)](),h=r[K8(n)](),m=c>=12?"pm":"am",g=m.toUpperCase(),y=a instanceof fa?a:ike(a||lR)||lke(),b=y.getModel("time"),_=b.get("month"),w=b.get("monthAbbr"),S=b.get("dayOfWeek"),k=b.get("dayOfWeekAbbr");return(t||"").replace(/{a}/g,m+"").replace(/{A}/g,g+"").replace(/{yyyy}/g,o+"").replace(/{yy}/g,Zl(o%100+"",2)).replace(/{Q}/g,l+"").replace(/{MMMM}/g,_[i-1]).replace(/{MMM}/g,w[i-1]).replace(/{MM}/g,Zl(i,2)).replace(/{M}/g,i+"").replace(/{dd}/g,Zl(s,2)).replace(/{d}/g,s+"").replace(/{eeee}/g,S[u]).replace(/{ee}/g,k[u]).replace(/{e}/g,u+"").replace(/{HH}/g,Zl(c,2)).replace(/{H}/g,c+"").replace(/{hh}/g,Zl(d+"",2)).replace(/{h}/g,d+"").replace(/{mm}/g,Zl(v,2)).replace(/{m}/g,v+"").replace(/{ss}/g,Zl(f,2)).replace(/{s}/g,f+"").replace(/{SSS}/g,Zl(h,3)).replace(/{S}/g,h+"")}function mke(e,t,n,a,r){var o=null;if(Ut(n))o=n;else if(dn(n)){var i={time:e.time,level:e.time?e.time.level:0},l=m2();l&&l.makeAxisLabelFormatterParamBreak(i,e.break),o=n(e.value,t,i)}else{var s=e.time;if(s){var u=n[s.lowerTimeUnit][s.upperTimeUnit];o=u[Math.min(s.level,u.length-1)]||""}else{var c=Sm(e.value,r);o=n[c][c][0]}}return g2(new Date(e.value),o,r,a)}function Sm(e,t){var n=bf(e),a=n[V8(t)]()+1,r=n[z8(t)](),o=n[F8(t)](),i=n[H8(t)](),l=n[W8(t)](),s=n[K8(t)](),u=s===0,c=u&&l===0,d=c&&i===0,v=d&&o===0,f=v&&r===1,h=f&&a===1;return h?"year":f?"month":v?"day":d?"hour":c?"minute":u?"second":"millisecond"}function J_(e,t,n){switch(t){case"year":e[dR(n)](0);case"month":e[fR(n)](1);case"day":e[vR(n)](0);case"hour":e[hR(n)](0);case"minute":e[pR(n)](0);case"second":e[mR(n)](0)}return e}function cR(e){return e?"getUTCFullYear":"getFullYear"}function V8(e){return e?"getUTCMonth":"getMonth"}function z8(e){return e?"getUTCDate":"getDate"}function F8(e){return e?"getUTCHours":"getHours"}function H8(e){return e?"getUTCMinutes":"getMinutes"}function W8(e){return e?"getUTCSeconds":"getSeconds"}function K8(e){return e?"getUTCMilliseconds":"getMilliseconds"}function gke(e){return e?"setUTCFullYear":"setFullYear"}function dR(e){return e?"setUTCMonth":"setMonth"}function fR(e){return e?"setUTCDate":"setDate"}function vR(e){return e?"setUTCHours":"setHours"}function hR(e){return e?"setUTCMinutes":"setMinutes"}function pR(e){return e?"setUTCSeconds":"setSeconds"}function mR(e){return e?"setUTCMilliseconds":"setMilliseconds"}function gR(e){if(!qSe(e))return Ut(e)?e:"-";var t=(e+"").split(".");return t[0].replace(/(\d{1,3})(?=(?:\d{3})+(?!\d))/g,"$1,")+(t.length>1?"."+t[1]:"")}function yR(e,t){return e=(e||"").toLowerCase().replace(/-(.)/g,function(n,a){return a.toUpperCase()}),t&&e&&(e=e.charAt(0).toUpperCase()+e.slice(1)),e}var y2=n8;function Q_(e,t,n){var a="{yyyy}-{MM}-{dd} {HH}:{mm}:{ss}";function r(c){return c&&Di(c)?c:"-"}function o(c){return zo(c)}var i=t==="time",l=e instanceof Date;if(i||l){var s=i?bf(e):e;if(isNaN(+s)){if(l)return"-"}else return g2(s,a,n)}if(t==="ordinal")return f_(e)?r(e):Gn(e)&&o(e)?e+"":"-";var u=Eg(e);return o(u)?gR(u):f_(e)?r(e):typeof e=="boolean"?e+"":"-"}var B9=["a","b","c","d","e","f","g"],db=function(e,t){return"{"+e+(t??"")+"}"};function bR(e,t,n){Lt(t)||(t=[t]);var a=t.length;if(!a)return"";for(var r=t[0].$vars||[],o=0;o':'';var i=n.markerId||"markerX";return{renderMode:o,content:"{"+i+"|} ",style:r==="subItem"?{width:4,height:4,borderRadius:2,backgroundColor:a}:{width:10,height:10,borderRadius:5,backgroundColor:a}}}function _c(e,t){return t=t||"transparent",Ut(e)?e:Yt(e)&&e.colorStops&&(e.colorStops[0]||{}).color||t}var Cm={},fb={},U8=function(){function e(){this._normalMasterList=[],this._nonSeriesBoxMasterList=[]}return e.prototype.create=function(t,n){this._nonSeriesBoxMasterList=a(Cm),this._normalMasterList=a(fb);function a(r,o){var i=[];return we(r,function(l,s){var u=l.create(t,n);i=i.concat(u||[])}),i}},e.prototype.update=function(t,n){we(this._normalMasterList,function(a){a.update&&a.update(t,n)})},e.prototype.getCoordinateSystems=function(){return this._normalMasterList.concat(this._nonSeriesBoxMasterList)},e.register=function(t,n){if(t==="matrix"||t==="calendar"){Cm[t]=n;return}fb[t]=n},e.get=function(t){return fb[t]||Cm[t]},e}();function bke(e){return!!Cm[e]}var _ke=1,_R=2;function wke(e){wR.set(e.fullType,{getCoord2:void 0}).getCoord2=e.getCoord2}var wR=an();function SR(e){var t=e.getShallow("coord",!0),n=_ke;if(t==null){var a=wR.get(e.type);a&&a.getCoord2&&(n=_R,t=a.getCoord2(e))}return{coord:t,from:n}}var Md=0,xm=1,Ske=2;function Cke(e,t){var n=e.getShallow("coordinateSystem"),a=e.getShallow("coordinateSystemUsage",!0),r=Md;if(n){var o=e.mainType==="series";a==null&&(a=o?"data":"box"),a==="data"?(r=xm,o||(r=Md)):a==="box"&&(r=Ske,!o&&!bke(n)&&(r=Md))}return{coordSysType:n,kind:r}}function xke(e){var t=e.targetModel,n=e.coordSysType,a=e.coordSysProvider,r=e.isDefaultDataCoordSys,o=Cke(t),i=o.kind,l=o.coordSysType;if(r&&i!==xm&&(i=xm,l=n),i===Md||l!==n)return Md;var s=a(n,t);return s?(i===xm?t.coordinateSystem=s:t.boxCoordinateSystem=s,i):Md}var km=we,kke=["left","right","top","bottom","width","height"],b0=[["width","left","right"],["height","top","bottom"]];function G8(e,t,n,a,r){var o=0,i=0;a==null&&(a=1/0),r==null&&(r=1/0);var l=0;t.eachChild(function(s,u){var c=s.getBoundingRect(),d=t.childAt(u+1),v=d&&d.getBoundingRect(),f,h;if(e==="horizontal"){var m=c.width+(v?-v.x+c.x:0);f=o+m,f>a||s.newline?(o=0,f=m,i+=l+n,l=c.height):l=Math.max(l,c.height)}else{var g=c.height+(v?-v.y+c.y:0);h=i+g,h>r||s.newline?(o+=l+n,i=0,h=g,l=c.width):l=Math.max(l,c.width)}s.newline||(s.x=o,s.y=i,s.markRedraw(),e==="horizontal"?o=f+n:i=h+n)})}var Kv=G8;Sa(G8,"vertical");Sa(G8,"horizontal");function Tke(e,t){return{left:e.getShallow("left",t),top:e.getShallow("top",t),right:e.getShallow("right",t),bottom:e.getShallow("bottom",t),width:e.getShallow("width",t),height:e.getShallow("height",t)}}function Mke(e,t){var n=Y8(e,t,{enableLayoutOnlyByCenter:!0}),a=e.getBoxLayoutParams(),r,o;if(n.type===pv.point)o=n.refPoint,r=wc(a,{width:t.getWidth(),height:t.getHeight()});else{var i=e.get("center"),l=Lt(i)?i:[i,i];r=wc(a,n.refContainer),o=n.boxCoordFrom===_R?n.refPoint:[sa(l[0],r.width)+r.x,sa(l[1],r.height)+r.y]}return{viewRect:r,center:o}}function Eke(e,t){var n=Mke(e,t),a=n.viewRect,r=n.center,o=e.get("radius");Lt(o)||(o=[0,o]);var i=sa(a.width,t.getWidth()),l=sa(a.height,t.getHeight()),s=Math.min(i,l),u=sa(o[0],s/2),c=sa(o[1],s/2);return{cx:r[0],cy:r[1],r0:u,r:c,viewRect:a}}function wc(e,t,n){n=y2(n||0);var a=t.width,r=t.height,o=sa(e.left,a),i=sa(e.top,r),l=sa(e.right,a),s=sa(e.bottom,r),u=sa(e.width,a),c=sa(e.height,r),d=n[2]+n[0],v=n[1]+n[3],f=e.aspect;switch(isNaN(u)&&(u=a-l-v-o),isNaN(c)&&(c=r-s-d-i),f!=null&&(isNaN(u)&&isNaN(c)&&(f>a/r?u=a*.8:c=r*.8),isNaN(u)&&(u=f*c),isNaN(c)&&(c=u/f)),isNaN(o)&&(o=a-l-u-v),isNaN(i)&&(i=r-s-c-d),e.left||e.right){case"center":o=a/2-u/2-n[3];break;case"right":o=a-u-v;break}switch(e.top||e.bottom){case"middle":case"center":i=r/2-c/2-n[0];break;case"bottom":i=r-c-d;break}o=o||0,i=i||0,isNaN(u)&&(u=a-v-o-(l||0)),isNaN(c)&&(c=r-d-i-(s||0));var h=new ln((t.x||0)+o+n[3],(t.y||0)+i+n[0],u,c);return h.margin=n,h}var pv={rect:1,point:2};function Y8(e,t,n){var a,r,o,i=e.boxCoordinateSystem,l;if(i){var s=SR(e),u=s.coord,c=s.from;if(i.dataToLayout){o=pv.rect,l=c;var d=i.dataToLayout(u);a=d.contentRect||d.rect}else n&&n.enableLayoutOnlyByCenter&&i.dataToPoint&&(o=pv.point,l=c,r=i.dataToPoint(u))}return o==null&&(o=pv.rect),o===pv.rect&&(a||(a={x:0,y:0,width:t.getWidth(),height:t.getHeight()}),r=[a.x+a.width/2,a.y+a.height/2]),{type:o,refContainer:a,refPoint:r,boxCoordFrom:l}}function Ph(e){var t=e.layoutMode||e.constructor.layoutMode;return Yt(t)?t:t?{type:t}:null}function Hs(e,t,n){var a=n&&n.ignoreSize;!Lt(a)&&(a=[a,a]);var r=i(b0[0],0),o=i(b0[1],1);s(b0[0],e,r),s(b0[1],e,o);function i(u,c){var d={},v=0,f={},h=0,m=2;if(km(u,function(b){f[b]=e[b]}),km(u,function(b){Sr(t,b)&&(d[b]=f[b]=t[b]),l(d,b)&&v++,l(f,b)&&h++}),a[c])return l(t,u[1])?f[u[2]]=null:l(t,u[2])&&(f[u[1]]=null),f;if(h===m||!v)return f;if(v>=m)return d;for(var g=0;g=0;s--)l=Bn(l,r[s],!0);a.defaultOption=l}return a.defaultOption},t.prototype.getReferringComponents=function(n,a){var r=n+"Index",o=n+"Id";return _p(this.ecModel,n,{index:this.get(r,!0),id:this.get(o,!0)},a)},t.prototype.getBoxLayoutParams=function(){return Tke(this,!1)},t.prototype.getZLevelKey=function(){return""},t.prototype.setZLevel=function(n){this.option.zlevel=n},t.protoInitialize=function(){var n=t.prototype;n.type="component",n.id="",n.name="",n.mainType="",n.subType="",n.componentIndex=0}(),t}(fa);h$(Kn,fa);a2(Kn);tke(Kn);nke(Kn,Lke);function Lke(e){var t=[];return we(Kn.getClassesByMainType(e),function(n){t=t.concat(n.dependencies||n.prototype.dependencies||[])}),t=Xt(t,function(n){return Oi(n).main}),e!=="dataset"&&Pn(t,"dataset")<=0&&t.unshift("dataset"),t}var tn={color:{},darkColor:{},size:{}},va=tn.color={theme:["#5070dd","#b6d634","#505372","#ff994d","#0ca8df","#ffd10a","#fb628b","#785db0","#3fbe95"],neutral00:"#fff",neutral05:"#f4f7fd",neutral10:"#e8ebf0",neutral15:"#dbdee4",neutral20:"#cfd2d7",neutral25:"#c3c5cb",neutral30:"#b7b9be",neutral35:"#aaacb2",neutral40:"#9ea0a5",neutral45:"#929399",neutral50:"#86878c",neutral55:"#797b7f",neutral60:"#6d6e73",neutral65:"#616266",neutral70:"#54555a",neutral75:"#48494d",neutral80:"#3c3c41",neutral85:"#303034",neutral90:"#232328",neutral95:"#17171b",neutral99:"#000",accent05:"#eff1f9",accent10:"#e0e4f2",accent15:"#d0d6ec",accent20:"#c0c9e6",accent25:"#b1bbdf",accent30:"#a1aed9",accent35:"#91a0d3",accent40:"#8292cc",accent45:"#7285c6",accent50:"#6578ba",accent55:"#5c6da9",accent60:"#536298",accent65:"#4a5787",accent70:"#404c76",accent75:"#374165",accent80:"#2e3654",accent85:"#252b43",accent90:"#1b2032",accent95:"#121521",transparent:"rgba(0,0,0,0)",highlight:"rgba(255,231,130,0.8)"};vt(va,{primary:va.neutral80,secondary:va.neutral70,tertiary:va.neutral60,quaternary:va.neutral50,disabled:va.neutral20,border:va.neutral30,borderTint:va.neutral20,borderShade:va.neutral40,background:va.neutral05,backgroundTint:"rgba(234,237,245,0.5)",backgroundTransparent:"rgba(255,255,255,0)",backgroundShade:va.neutral10,shadow:"rgba(0,0,0,0.2)",shadowTint:"rgba(129,130,136,0.2)",axisLine:va.neutral70,axisLineTint:va.neutral40,axisTick:va.neutral70,axisTickMinor:va.neutral60,axisLabel:va.neutral70,axisSplitLine:va.neutral15,axisMinorSplitLine:va.neutral05});for(var bu in va)if(va.hasOwnProperty(bu)){var V9=va[bu];bu==="theme"?tn.darkColor.theme=va.theme.slice():bu==="highlight"?tn.darkColor.highlight="rgba(255,231,130,0.4)":bu.indexOf("accent")===0?tn.darkColor[bu]=k_(V9,null,function(e){return e*.5},function(e){return Math.min(1,1.3-e)}):tn.darkColor[bu]=k_(V9,null,function(e){return e*.9},function(e){return 1-Math.pow(e,1.5)})}tn.size={xxs:2,xs:5,s:10,m:15,l:20,xl:30,xxl:40,xxxl:50};var CR="";typeof navigator<"u"&&(CR=navigator.platform||"");var Yc="rgba(0, 0, 0, 0.2)",xR=tn.color.theme[0],Pke=k_(xR,null,null,.9);const kR={darkMode:"auto",colorBy:"series",color:tn.color.theme,gradientColor:[Pke,xR],aria:{decal:{decals:[{color:Yc,dashArrayX:[1,0],dashArrayY:[2,5],symbolSize:1,rotation:Math.PI/6},{color:Yc,symbol:"circle",dashArrayX:[[8,8],[0,8,8,0]],dashArrayY:[6,0],symbolSize:.8},{color:Yc,dashArrayX:[1,0],dashArrayY:[4,3],rotation:-Math.PI/4},{color:Yc,dashArrayX:[[6,6],[0,6,6,0]],dashArrayY:[6,0]},{color:Yc,dashArrayX:[[1,0],[1,6]],dashArrayY:[1,0,6,0],rotation:Math.PI/4},{color:Yc,symbol:"triangle",dashArrayX:[[9,9],[0,9,9,0]],dashArrayY:[7,2],symbolSize:.75}]}},textStyle:{fontFamily:CR.match(/^Win/)?"Microsoft YaHei":"sans-serif",fontSize:12,fontStyle:"normal",fontWeight:"normal"},blendMode:null,stateAnimation:{duration:300,easing:"cubicOut"},animation:"auto",animationDuration:1e3,animationDurationUpdate:500,animationEasing:"cubicInOut",animationEasingUpdate:"cubicInOut",animationThreshold:2e3,progressiveThreshold:3e3,progressive:400,hoverLayerThreshold:3e3,useUTC:!1};var Wa={Must:1,Might:2,Not:3},TR=Hn();function Dke(e){TR(e).datasetMap=an()}function Oke(e,t,n){var a={},r=j8(t);if(!r||!e)return a;var o=[],i=[],l=t.ecModel,s=TR(l).datasetMap,u=r.uid+"_"+n.seriesLayoutBy,c,d;e=e.slice(),we(e,function(m,g){var y=Yt(m)?m:e[g]={name:m};y.type==="ordinal"&&c==null&&(c=g,d=h(y)),a[y.name]=[]});var v=s.get(u)||s.set(u,{categoryWayDim:d,valueWayDim:0});we(e,function(m,g){var y=m.name,b=h(m);if(c==null){var _=v.valueWayDim;f(a[y],_,b),f(i,_,b),v.valueWayDim+=b}else if(c===g)f(a[y],0,b),f(o,0,b);else{var _=v.categoryWayDim;f(a[y],_,b),f(i,_,b),v.categoryWayDim+=b}});function f(m,g,y){for(var b=0;bt)return e[a];return e[n-1]}function zke(e,t,n,a,r,o,i){o=o||e;var l=t(o),s=l.paletteIdx||0,u=l.paletteNameMap=l.paletteNameMap||{};if(u.hasOwnProperty(r))return u[r];var c=i==null||!a?n:Vke(a,i);if(c=c||n,!(!c||!c.length)){var d=c[s];return r&&(u[r]=d),l.paletteIdx=(s+1)%c.length,d}}function Fke(e,t){t(e).paletteIdx=0,t(e).paletteNameMap={}}var _0,Hf,F9,H9="\0_ec_inner",Hke=1,X8=function(e){At(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.init=function(n,a,r,o,i,l){o=o||{},this.option=null,this._theme=new fa(o),this._locale=new fa(i),this._optionManager=l},t.prototype.setOption=function(n,a,r){var o=U9(a);this._optionManager.setOption(n,r,o),this._resetOption(null,o)},t.prototype.resetOption=function(n,a){return this._resetOption(n,U9(a))},t.prototype._resetOption=function(n,a){var r=!1,o=this._optionManager;if(!n||n==="recreate"){var i=o.mountOption(n==="recreate");!this.option||n==="recreate"?F9(this,i):(this.restoreData(),this._mergeOption(i,a)),r=!0}if((n==="timeline"||n==="media")&&this.restoreData(),!n||n==="recreate"||n==="timeline"){var l=o.getTimelineOption(this);l&&(r=!0,this._mergeOption(l,a))}if(!n||n==="recreate"||n==="media"){var s=o.getMediaOption(this);s.length&&we(s,function(u){r=!0,this._mergeOption(u,a)},this)}return r},t.prototype.mergeOption=function(n){this._mergeOption(n,null)},t.prototype._mergeOption=function(n,a){var r=this.option,o=this._componentsMap,i=this._componentsCount,l=[],s=an(),u=a&&a.replaceMergeMainTypeMap;Dke(this),we(n,function(d,v){d!=null&&(Kn.hasClass(v)?v&&(l.push(v),s.set(v,!0)):r[v]=r[v]==null?yn(d):Bn(r[v],d,!0))}),u&&u.each(function(d,v){Kn.hasClass(v)&&!s.get(v)&&(l.push(v),s.set(v,!0))}),Kn.topologicalTravel(l,Kn.getAllClassMainTypes(),c,this);function c(d){var v=Bke(this,d,Cr(n[d])),f=o.get(d),h=f?u&&u.get(d)?"replaceMerge":"normalMerge":"replaceAll",m=tCe(f,v,h);sCe(m,d,Kn),r[d]=null,o.set(d,null),i.set(d,0);var g=[],y=[],b=0,_;we(m,function(w,S){var k=w.existing,T=w.newOption;if(!T)k&&(k.mergeOption({},this),k.optionUpdated({},!1));else{var M=d==="series",E=Kn.getClass(d,w.keyInfo.subType,!M);if(!E)return;if(d==="tooltip"){if(_)return;_=!0}if(k&&k.constructor===E)k.name=w.keyInfo.name,k.mergeOption(T,this),k.optionUpdated(T,!1);else{var A=vt({componentIndex:S},w.keyInfo);k=new E(T,this,this,A),vt(k,A),w.brandNew&&(k.__requireNewView=!0),k.init(T,this,this),k.optionUpdated(null,!0)}}k?(g.push(k.option),y.push(k),b++):(g.push(void 0),y.push(void 0))},this),r[d]=g,o.set(d,y),i.set(d,b),d==="series"&&_0(this)}this._seriesIndices||_0(this)},t.prototype.getOption=function(){var n=yn(this.option);return we(n,function(a,r){if(Kn.hasClass(r)){for(var o=Cr(a),i=o.length,l=!1,s=i-1;s>=0;s--)o[s]&&!Mh(o[s])?l=!0:(o[s]=null,!l&&i--);o.length=i,n[r]=o}}),delete n[H9],n},t.prototype.setTheme=function(n){this._theme=new fa(n),this._resetOption("recreate",null)},t.prototype.getTheme=function(){return this._theme},t.prototype.getLocaleModel=function(){return this._locale},t.prototype.setUpdatePayload=function(n){this._payload=n},t.prototype.getUpdatePayload=function(){return this._payload},t.prototype.getComponent=function(n,a){var r=this._componentsMap.get(n);if(r){var o=r[a||0];if(o)return o;if(a==null){for(var i=0;i=t:n==="max"?e<=t:e===t}function Xke(e,t){return e.join(",")===t.join(",")}var Ko=we,Dh=Yt,G9=["areaStyle","lineStyle","nodeStyle","linkStyle","chordStyle","label","labelLine"];function vb(e){var t=e&&e.itemStyle;if(t)for(var n=0,a=G9.length;n0?n[i-1].seriesModel:null)}),i9e(n)}})}function i9e(e){we(e,function(t,n){var a=[],r=[NaN,NaN],o=[t.stackResultDimension,t.stackedOverDimension],i=t.data,l=t.isStackedByIndex,s=t.seriesModel.get("stackStrategy")||"samesign";i.modify(o,function(u,c,d){var v=i.get(t.stackedDimension,d);if(isNaN(v))return r;var f,h;l?h=i.getRawIndex(d):f=i.get(t.stackedByDimension,d);for(var m=NaN,g=n-1;g>=0;g--){var y=e[g];if(l||(h=y.data.rawIndexOf(y.stackedByDimension,f)),h>=0){var b=y.data.getByRawIndex(y.stackResultDimension,h);if(s==="all"||s==="positive"&&b>0||s==="negative"&&b<0||s==="samesign"&&v>=0&&b>0||s==="samesign"&&v<=0&&b<0){v=YSe(v,b),m=b;break}}}return a[0]=v,a[1]=m,a})})}var b2=function(){function e(t){this.data=t.data||(t.sourceFormat===fi?{}:[]),this.sourceFormat=t.sourceFormat||M$,this.seriesLayoutBy=t.seriesLayoutBy||Ui,this.startIndex=t.startIndex||0,this.dimensionsDetectedCount=t.dimensionsDetectedCount,this.metaRawOption=t.metaRawOption;var n=this.dimensionsDefine=t.dimensionsDefine;if(n)for(var a=0;am&&(m=_)}f[0]=h,f[1]=m}},r=function(){return this._data?this._data.length/this._dimSize:0};Q9=(t={},t[cr+"_"+Ui]={pure:!0,appendData:o},t[cr+"_"+Ic]={pure:!0,appendData:function(){throw new Error('Do not support appendData when set seriesLayoutBy: "row".')}},t[Wo]={pure:!0,appendData:o},t[fi]={pure:!0,appendData:function(i){var l=this._data;we(i,function(s,u){for(var c=l[u]||(l[u]=[]),d=0;d<(s||[]).length;d++)c.push(s[d])})}},t[uo]={appendData:o},t[Ms]={persistent:!1,pure:!0,appendData:function(i){this._data=i},clean:function(){this._offset+=this.count(),this._data=null}},t);function o(i){for(var l=0;l=0&&(m=i.interpolatedValue[g])}return m!=null?m+"":""})}},e.prototype.getRawValue=function(t,n){return tf(this.getData(n),t)},e.prototype.formatTooltip=function(t,n,a){},e}();function aT(e){var t,n;return Yt(e)?e.type&&(n=e):t=e,{text:t,frag:n}}function Uv(e){return new p9e(e)}var p9e=function(){function e(t){t=t||{},this._reset=t.reset,this._plan=t.plan,this._count=t.count,this._onDirty=t.onDirty,this._dirty=!0}return e.prototype.perform=function(t){var n=this._upstream,a=t&&t.skip;if(this._dirty&&n){var r=this.context;r.data=r.outputData=n.context.outputData}this.__pipeline&&(this.__pipeline.currentTask=this);var o;this._plan&&!a&&(o=this._plan(this.context));var i=c(this._modBy),l=this._modDataCount||0,s=c(t&&t.modBy),u=t&&t.modDataCount||0;(i!==s||l!==u)&&(o="reset");function c(b){return!(b>=1)&&(b=1),b}var d;(this._dirty||o==="reset")&&(this._dirty=!1,d=this._doReset(a)),this._modBy=s,this._modDataCount=u;var v=t&&t.step;if(n?this._dueEnd=n._outputDueEnd:this._dueEnd=this._count?this._count(this.context):1/0,this._progress){var f=this._dueIndex,h=Math.min(v!=null?this._dueIndex+v:1/0,this._dueEnd);if(!a&&(d||f1&&a>0?l:i}};return o;function i(){return t=e?null:sr?-this._resultLT:0},e}();function BR(e){var t="",n=-1/0,a=-1/0,r=1/0,o=1/0;return e&&(e.g!=null&&(t+="G"+e.g,n=e.g),e.ge!=null&&(t+="GE"+e.ge,a=e.ge),e.l!=null&&(t+="L"+e.l,r=e.l),e.le!=null&&(t+="LE"+e.le,o=e.le)),{key:t,g:n,ge:a,l:r,le:o}}function VR(e,t){return t>e.g&&t>=e.ge&&t65535?T9e:M9e}function E9e(e){var t=e.constructor;return t===Array?e.slice():new t(e)}function iT(e,t,n,a,r){var o=HR[n||"float"];if(r){var i=e[t],l=i&&i.length;if(l!==a){for(var s=new o(a),u=0;ug[1]&&(g[1]=m)}return this._rawCount=this._count=s,{start:l,end:s}},e.prototype._initDataFromProvider=function(t,n,a){for(var r=this._provider,o=this._chunks,i=this._dimensions,l=i.length,s=this._rawExtent,u=Xt(i,function(b){return b.property}),c=0;cy[1]&&(y[1]=g)}}!r.persistent&&r.clean&&r.clean(),this._rawCount=this._count=n,this._extent=[]},e.prototype.count=function(){return this._count},e.prototype.get=function(t,n){if(!(n>=0&&n=0&&n=this._rawCount||t<0)return-1;if(!this._indices)return t;var n=this._indices,a=n[t];if(a!=null&&at)o=i-1;else return i}return-1},e.prototype.getIndices=function(){var t,n=this._indices;if(n){var a=n.constructor,r=this._count;if(a===Array){t=new a(r);for(var o=0;o=d&&b<=v||isNaN(b))&&(s[u++]=m),m++}h=!0}else if(o===2){for(var g=f[r[0]],_=f[r[1]],w=t[r[1]][0],S=t[r[1]][1],y=0;y=d&&b<=v||isNaN(b))&&(k>=w&&k<=S||isNaN(k))&&(s[u++]=m),m++}h=!0}}if(!h)if(o===1)for(var y=0;y=d&&b<=v||isNaN(b))&&(s[u++]=T)}else for(var y=0;yt[A][1])&&(M=!1)}M&&(s[u++]=n.getRawIndex(y))}return uy[1]&&(y[1]=g)}}}},e.prototype.lttbDownSample=function(t,n){var a=this.clone([t],!0),r=a._chunks,o=r[t],i=this.count(),l=0,s=Math.floor(1/n),u=this.getRawIndex(0),c,d,v,f=new(jc(this._rawCount))(Math.min((Math.ceil(i/s)+2)*2,i));f[l++]=u;for(var h=1;hc&&(c=d,v=w)}I>0&&Il&&(m=l-c);for(var g=0;gh&&(h=b,f=c+g)}var _=this.getRawIndex(d),w=this.getRawIndex(f);dc-h&&(s=c-h,l.length=s);for(var m=0;md[1]&&(d[1]=y),v[f++]=b}return o._count=f,o._indices=v,o._updateGetRawIdx(),o},e.prototype.each=function(t,n){if(this._count)for(var a=t.length,r=this._chunks,o=0,i=this.count();of&&(f=g))}return s[c]=[v,f]},e.prototype.getRawDataItem=function(t){var n=this.getRawIndex(t);if(this._provider.persistent)return this._provider.getItem(n);for(var a=[],r=this._chunks,o=0;o=0?this._indices[t]:-1},e.prototype._updateGetRawIdx=function(){this.getRawIndex=this._indices?this._getRawIdx:this._getRawIdxIdentity},e.internalField=function(){function t(n,a,r,o){return Tm(n[o],this._dimensions[o])}mb={arrayRows:t,objectRows:function(n,a,r,o){return Tm(n[a],this._dimensions[o])},keyedColumns:t,original:function(n,a,r,o){var i=n&&(n.value==null?n:n.value);return Tm(i instanceof Array?i[o]:i,this._dimensions[o])},typedArray:function(n,a,r,o){return n[o]}}}(),e}(),A9e=function(){function e(t){this._sourceList=[],this._storeList=[],this._upstreamSignList=[],this._versionSignBase=0,this._dirty=!0,this._sourceHost=t}return e.prototype.dirty=function(){this._setLocalSource([],[]),this._storeList=[],this._dirty=!0},e.prototype._setLocalSource=function(t,n){this._sourceList=t,this._upstreamSignList=n,this._versionSignBase++,this._versionSignBase>9e10&&(this._versionSignBase=0)},e.prototype._getVersionSign=function(){return this._sourceHost.uid+"_"+this._versionSignBase},e.prototype.prepareSource=function(){this._isDirty()&&(this._createSource(),this._dirty=!1)},e.prototype._createSource=function(){this._setLocalSource([],[]);var t=this._sourceHost,n=this._getUpstreamSourceManagers(),a=!!n.length,r,o;if(S0(t)){var i=t,l=void 0,s=void 0,u=void 0;if(a){var c=n[0];c.prepareSource(),u=c.getSource(),l=u.data,s=u.sourceFormat,o=[c._getVersionSign()]}else l=i.get("data",!0),s=Ur(l)?Ms:uo,o=[];var d=this._getSourceMetaRawOption()||{},v=u&&u.metaRawOption||{},f=jt(d.seriesLayoutBy,v.seriesLayoutBy)||null,h=jt(d.sourceHeader,v.sourceHeader),m=jt(d.dimensions,v.dimensions),g=f!==v.seriesLayoutBy||!!h!=!!v.sourceHeader||m;r=g?[e3(l,{seriesLayoutBy:f,sourceHeader:h,dimensions:m},s)]:[]}else{var y=t;if(a){var b=this._applyTransform(n);r=b.sourceList,o=b.upstreamSignList}else{var _=y.get("source",!0);r=[e3(_,this._getSourceMetaRawOption(),null)],o=[]}}this._setLocalSource(r,o)},e.prototype._applyTransform=function(t){var n=this._sourceHost,a=n.get("transform",!0),r=n.get("fromTransformResult",!0);if(r!=null){var o="";t.length!==1&&lT(o)}var i,l=[],s=[];return we(t,function(u){u.prepareSource();var c=u.getSource(r||0),d="";r!=null&&!c&&lT(d),l.push(c),s.push(u._getVersionSign())}),a?i=x9e(a,l,{datasetIndex:n.componentIndex}):r!=null&&(i=[l9e(l[0])]),{sourceList:i,upstreamSignList:s}},e.prototype._isDirty=function(){if(this._dirty)return!0;for(var t=this._getUpstreamSourceManagers(),n=0;n1||n>0&&!e.noHeader;return we(e.blocks,function(r){var o=GR(r);o>=t&&(t=o+ +(a&&(!o||n3(r)&&!r.noHeader)))}),t}return 0}function D9e(e,t,n,a){var r=t.noHeader,o=$9e(GR(t)),i=[],l=t.blocks||[];Pl(!l||Lt(l)),l=l||[];var s=e.orderMode;if(t.sortBlocks&&s){l=l.slice();var u={valueAsc:"asc",valueDesc:"desc"};if(Sr(u,s)){var c=new m9e(u[s],null);l.sort(function(m,g){return c.evaluate(m.sortParam,g.sortParam)})}else s==="seriesDesc"&&l.reverse()}we(l,function(m,g){var y=t.valueFormatter,b=UR(m)(y?vt(vt({},e),{valueFormatter:y}):e,m,g>0?o.html:0,a);b!=null&&i.push(b)});var d=e.renderMode==="richText"?i.join(o.richText):a3(a,i.join(""),r?n:o.html);if(r)return d;var v=Q_(t.header,"ordinal",e.useUTC),f=KR(a,e.renderMode).nameStyle,h=WR(a);return e.renderMode==="richText"?YR(e,v,f)+o.richText+d:a3(a,'
'+Or(v)+"
"+d,n)}function O9e(e,t,n,a){var r=e.renderMode,o=t.noName,i=t.noValue,l=!t.markerType,s=t.name,u=e.useUTC,c=t.valueFormatter||e.valueFormatter||function(w){return w=Lt(w)?w:[w],Xt(w,function(S,k){return Q_(S,Lt(f)?f[k]:f,u)})};if(!(o&&i)){var d=l?"":e.markupStyleCreator.makeTooltipMarker(t.markerType,t.markerColor||tn.color.secondary,r),v=o?"":Q_(s,"ordinal",u),f=t.valueType,h=i?[]:c(t.value,t.rawDataIndex),m=!l||!o,g=!l&&o,y=KR(a,r),b=y.nameStyle,_=y.valueStyle;return r==="richText"?(l?"":d)+(o?"":YR(e,v,b))+(i?"":B9e(e,h,m,g,_)):a3(a,(l?"":d)+(o?"":R9e(v,!l,b))+(i?"":N9e(h,m,g,_)),n)}}function sT(e,t,n,a,r,o){if(e){var i=UR(e),l={useUTC:r,renderMode:n,orderMode:a,markupStyleCreator:t,valueFormatter:e.valueFormatter};return i(l,e,0,o)}}function $9e(e){return{html:L9e[e],richText:P9e[e]}}function a3(e,t,n){var a='
',r="margin: "+n+"px 0 0",o=WR(e);return'
'+t+a+"
"}function R9e(e,t,n){var a=t?"margin-left:2px":"";return''+Or(e)+""}function N9e(e,t,n,a){var r=n?"10px":"20px",o=t?"float:right;margin-left:"+r:"";return e=Lt(e)?e:[e],''+Xt(e,function(i){return Or(i)}).join("  ")+""}function YR(e,t,n){return e.markupStyleCreator.wrapRichTextStyle(t,n)}function B9e(e,t,n,a,r){var o=[r],i=a?10:20;return n&&o.push({padding:[0,0,0,i],align:"right"}),e.markupStyleCreator.wrapRichTextStyle(Lt(t)?t.join(" "):t,o)}function V9e(e,t){var n=e.getData().getItemVisual(t,"style"),a=n[e.visualDrawType];return _c(a)}function jR(e,t){var n=e.get("padding");return n??(t==="richText"?[8,10]:10)}var gb=function(){function e(){this.richTextStyles={},this._nextStyleNameId=c8()}return e.prototype._generateStyleName=function(){return"__EC_aUTo_"+this._nextStyleNameId++},e.prototype.makeTooltipMarker=function(t,n,a){var r=a==="richText"?this._generateStyleName():null,o=yke({color:n,type:t,renderMode:a,markerId:r});return Ut(o)?o:(this.richTextStyles[r]=o.style,o.content)},e.prototype.wrapRichTextStyle=function(t,n){var a={};Lt(n)?we(n,function(o){return vt(a,o)}):vt(a,n);var r=this._generateStyleName();return this.richTextStyles[r]=a,"{"+r+"|"+t+"}"},e}();function z9e(e){var t=e.series,n=e.dataIndex,a=e.multipleSeries,r=t.getData(),o=r.mapDimensionsAll("defaultedTooltip"),i=o.length,l=t.getRawValue(n),s=Lt(l),u=V9e(t,n),c,d,v,f;if(i>1||s&&!i){var h=F9e(l,t,n,o,u);c=h.inlineValues,d=h.inlineValueTypes,v=h.blocks,f=h.inlineValues[0]}else if(i){var m=r.getDimensionInfo(o[0]);f=c=tf(r,n,o[0]),d=m.type}else f=c=s?l[0]:l;var g=d8(t),y=g&&t.name||"",b=r.getName(n),_=a?y:b;return Oh("section",{header:y,noHeader:a||!g,sortParam:f,blocks:[Oh("nameValue",{markerType:"item",markerColor:u,name:_,noName:!Di(_),value:c,valueType:d,rawDataIndex:r.getRawIndex(n)})].concat(v||[])})}function F9e(e,t,n,a,r){var o=t.getData(),i=zs(e,function(d,v,f){var h=o.getDimensionInfo(f);return d=d||h&&h.tooltip!==!1&&h.displayName!=null},!1),l=[],s=[],u=[];a.length?we(a,function(d){c(tf(o,n,d),d)}):we(e,c);function c(d,v){var f=o.getDimensionInfo(v);!f||f.otherDims.tooltip===!1||(i?u.push(Oh("nameValue",{markerType:"subItem",markerColor:r,name:f.displayName,value:d,valueType:f.type})):(l.push(d),s.push(f.type)))}return{inlineValues:l,inlineValueTypes:s,blocks:u}}var Jl=Hn();function C0(e,t){return e.getName(t)||e.getId(t)}var H9e="__universalTransitionEnabled",ai=function(e){At(t,e);function t(){var n=e!==null&&e.apply(this,arguments)||this;return n._selectedDataIndicesMap={},n}return t.prototype.init=function(n,a,r){this.seriesIndex=this.componentIndex,this.dataTask=Uv({count:K9e,reset:U9e}),this.dataTask.context={model:this},this.mergeDefaultAndTheme(n,r);var o=Jl(this).sourceManager=new A9e(this);o.prepareSource();var i=this.getInitialData(n,r);cT(i,this),this.dataTask.context.data=i,Jl(this).dataBeforeProcessed=i,uT(this),this._initSelectedMapFromData(i)},t.prototype.mergeDefaultAndTheme=function(n,a){var r=Ph(this),o=r?kp(n):{},i=this.subType;Kn.hasClass(i)&&(i+="Series"),Bn(n,a.getTheme().get(this.subType)),Bn(n,this.getDefaultOption()),N_(n,"label",["show"]),this.fillDataTextStyle(n.data),r&&Hs(n,o,r)},t.prototype.mergeOption=function(n,a){n=Bn(this.option,n,!0),this.fillDataTextStyle(n.data);var r=Ph(this);r&&Hs(this.option,n,r);var o=Jl(this).sourceManager;o.dirty(),o.prepareSource();var i=this.getInitialData(n,a);cT(i,this),this.dataTask.dirty(),this.dataTask.context.data=i,Jl(this).dataBeforeProcessed=i,uT(this),this._initSelectedMapFromData(i)},t.prototype.fillDataTextStyle=function(n){if(n&&!Ur(n))for(var a=["show"],r=0;r=0&&v<0)&&(d=S,v=w,f=0),w===v&&(c[f++]=g))}return c.length=f,c},t.prototype.formatTooltip=function(n,a,r){return z9e({series:this,dataIndex:n,multipleSeries:a})},t.prototype.isAnimationEnabled=function(){var n=this.ecModel;if(hn.node&&!(n&&n.ssr))return!1;var a=this.getShallow("animation");return a&&this.getData().count()>this.getShallow("animationThreshold")&&(a=!1),!!a},t.prototype.restoreData=function(){this.dataTask.dirty()},t.prototype.getColorFromPalette=function(n,a,r){var o=this.ecModel,i=q8.prototype.getColorFromPalette.call(this,n,a,r);return i||(i=o.getColorFromPalette(n,a,r)),i},t.prototype.coordDimToDataDim=function(n){return this.getRawData().mapDimensionsAll(n)},t.prototype.getProgressive=function(){return this.get("progressive")},t.prototype.getProgressiveThreshold=function(){return this.get("progressiveThreshold")},t.prototype.select=function(n,a){this._innerSelect(this.getData(a),n)},t.prototype.unselect=function(n,a){var r=this.option.selectedMap;if(r){var o=this.option.selectedMode,i=this.getData(a);if(o==="series"||r==="all"){this.option.selectedMap={},this._selectedDataIndicesMap={};return}for(var l=0;l=0&&r.push(i)}return r},t.prototype.isSelected=function(n,a){var r=this.option.selectedMap;if(!r)return!1;var o=this.getData(a);return(r==="all"||r[C0(o,n)])&&!o.getItemModel(n).get(["select","disabled"])},t.prototype.isUniversalTransitionEnabled=function(){if(this[H9e])return!0;var n=this.option.universalTransition;return n?n===!0?!0:n&&n.enabled:!1},t.prototype._innerSelect=function(n,a){var r,o,i=this.option,l=i.selectedMode,s=a.length;if(!(!l||!s)){if(l==="series")i.selectedMap="all";else if(l==="multiple"){Yt(i.selectedMap)||(i.selectedMap={});for(var u=i.selectedMap,c=0;c0&&this._innerSelect(n,a)}},t.registerClass=function(n){return Kn.registerClass(n)},t.protoInitialize=function(){var n=t.prototype;n.type="series.__base__",n.seriesIndex=0,n.ignoreStyleOnData=!1,n.hasSymbolVisual=!1,n.defaultSymbol="circle",n.visualStyleAccessPath="itemStyle",n.visualDrawType="fill"}(),t}(Kn);al(ai,h9e);al(ai,q8);h$(ai,Kn);function uT(e){var t=e.name;d8(e)||(e.name=W9e(e)||t)}function W9e(e){var t=e.getRawData(),n=t.mapDimensionsAll("seriesName"),a=[];return we(n,function(r){var o=t.getDimensionInfo(r);o.displayName&&a.push(o.displayName)}),a.join(" ")}function K9e(e){return e.model.getRawData().count()}function U9e(e){var t=e.model;return t.setData(t.getRawData().cloneShallow()),G9e}function G9e(e,t){t.outputData&&e.end>t.outputData.count()&&t.model.getRawData().cloneShallow(t.outputData)}function cT(e,t){we(y8e(e.CHANGABLE_METHODS,e.DOWNSAMPLE_METHODS),function(n){e.wrapMethod(n,Sa(Y9e,t))})}function Y9e(e,t){var n=r3(e);return n&&n.setOutputEnd((t||this).count()),t}function r3(e){var t=(e.ecModel||{}).scheduler,n=t&&t.getPipeline(e.uid);if(n){var a=n.currentTask;if(a){var r=a.agentStubMap;r&&(a=r.get(e.uid))}return a}}var el=function(){function e(){this.group=new Ha,this.uid=p2("viewComponent")}return e.prototype.init=function(t,n){},e.prototype.render=function(t,n,a,r){},e.prototype.dispose=function(t,n){},e.prototype.updateView=function(t,n,a,r){},e.prototype.updateLayout=function(t,n,a,r){},e.prototype.updateVisual=function(t,n,a,r){},e.prototype.toggleBlurSeries=function(t,n,a){},e.prototype.eachRendered=function(t){var n=this.group;n&&n.traverse(t)},e}();p8(el);a2(el);function eS(){var e=Hn();return function(t){var n=e(t),a=t.pipelineContext,r=!!n.large,o=!!n.progressiveRender,i=n.large=!!(a&&a.large),l=n.progressiveRender=!!(a&&a.progressiveRender);return(r!==i||o!==l)&&"reset"}}var qR=Hn(),j9e=eS(),Oo=function(){function e(){this.group=new Ha,this.uid=p2("viewChart"),this.renderTask=Uv({plan:q9e,reset:X9e}),this.renderTask.context={view:this}}return e.prototype.init=function(t,n){},e.prototype.render=function(t,n,a,r){},e.prototype.highlight=function(t,n,a,r){var o=t.getData(r&&r.dataType);o&&fT(o,r,"emphasis")},e.prototype.downplay=function(t,n,a,r){var o=t.getData(r&&r.dataType);o&&fT(o,r,"normal")},e.prototype.remove=function(t,n){this.group.removeAll()},e.prototype.dispose=function(t,n){},e.prototype.updateView=function(t,n,a,r){this.render(t,n,a,r)},e.prototype.updateVisual=function(t,n,a,r){this.render(t,n,a,r)},e.prototype.eachRendered=function(t){f2(this.group,t)},e.markUpdateMethod=function(t,n){qR(t).updateMethod=n},e.protoInitialize=function(){var t=e.prototype;t.type="chart"}(),e}();function dT(e,t,n){e&&Y_(e)&&(t==="emphasis"?Dg:Og)(e,n)}function fT(e,t,n){var a=yc(e,t),r=t&&t.highlightKey!=null?H5e(t.highlightKey):null;a!=null?we(Cr(a),function(o){dT(e.getItemGraphicEl(o),n,r)}):e.eachItemGraphicEl(function(o){dT(o,n,r)})}p8(Oo);a2(Oo);function q9e(e){return j9e(e.model)}function X9e(e){var t=e.model,n=e.ecModel,a=e.api,r=e.payload,o=t.pipelineContext.progressiveRender,i=e.view,l=r&&qR(r).updateMethod,s=o?"incrementalPrepareRender":l&&i[l]?l:"render";return s!=="render"&&i[s](t,n,a,r),Z9e[s]}var Z9e={incrementalPrepareRender:{progress:function(e,t){t.view.incrementalRender(e,t.model,t.ecModel,t.api,t.payload)}},render:{forceFirstProgress:!0,progress:function(e,t){t.view.render(t.model,t.ecModel,t.api,t.payload)}}},zg="\0__throttleOriginMethod",vT="\0__throttleRate",hT="\0__throttleType";function tS(e,t,n){var a,r=0,o=0,i=null,l,s,u,c;t=t||0;function d(){o=new Date().getTime(),i=null,e.apply(s,u||[])}var v=function(){for(var f=[],h=0;h=0?d():i=setTimeout(d,-l),r=a};return v.clear=function(){i&&(clearTimeout(i),i=null)},v.debounceNextCall=function(f){c=f},v}function XR(e,t,n,a){var r=e[t];if(r){var o=r[zg]||r,i=r[hT],l=r[vT];if(l!==n||i!==a){if(n==null||!a)return e[t]=o;r=e[t]=tS(o,n,a==="debounce"),r[zg]=o,r[hT]=a,r[vT]=n}return r}}function o3(e,t){var n=e[t];n&&n[zg]&&(n.clear&&n.clear(),e[t]=n[zg])}var pT=Hn(),mT={itemStyle:Eh(oR,!0),lineStyle:Eh(rR,!0)},J9e={lineStyle:"stroke",itemStyle:"fill"};function ZR(e,t){var n=e.visualStyleMapper||mT[t];return n||(console.warn("Unknown style type '"+t+"'."),mT.itemStyle)}function JR(e,t){var n=e.visualDrawType||J9e[t];return n||(console.warn("Unknown style type '"+t+"'."),"fill")}var Q9e={createOnAllSeries:!0,performRawSeries:!0,reset:function(e,t){var n=e.getData(),a=e.visualStyleAccessPath||"itemStyle",r=e.getModel(a),o=ZR(e,a),i=o(r),l=r.getShallow("decal");l&&(n.setVisual("decal",l),l.dirty=!0);var s=JR(e,a),u=i[s],c=dn(u)?u:null,d=i.fill==="auto"||i.stroke==="auto";if(!i[s]||c||d){var v=e.getColorFromPalette(e.name,null,t.getSeriesCount());i[s]||(i[s]=v,n.setVisual("colorFromPalette",!0)),i.fill=i.fill==="auto"||dn(i.fill)?v:i.fill,i.stroke=i.stroke==="auto"||dn(i.stroke)?v:i.stroke}if(n.setVisual("style",i),n.setVisual("drawType",s),!t.isSeriesFiltered(e)&&c)return n.setVisual("colorFromPalette",!1),{dataEach:function(f,h){var m=e.getDataParams(h),g=vt({},i);g[s]=c(m),f.setItemVisual(h,"style",g)}}}},Kf=new fa,eTe={createOnAllSeries:!0,reset:function(e,t){if(!e.ignoreStyleOnData){var n=e.getData(),a=e.visualStyleAccessPath||"itemStyle",r=ZR(e,a),o=n.getVisual("drawType");return{dataEach:n.hasItemOption?function(i,l){var s=i.getRawDataItem(l);if(s&&s[a]){Kf.option=s[a];var u=r(Kf),c=i.ensureUniqueItemVisual(l,"style");vt(c,u),Kf.option.decal&&(i.setItemVisual(l,"decal",Kf.option.decal),Kf.option.decal.dirty=!0),o in u&&i.setItemVisual(l,"colorFromPalette",!1)}}:null}}}},tTe={performRawSeries:!0,overallReset:function(e){var t=an();e.eachSeries(function(n){if(!n.isColorBySeries()){var a=n.type+"-"+n.getColorBy();pT(n).scope=t.get(a)||t.set(a,{})}}),e.eachSeries(function(n){if(!n.isColorBySeries()){var a=n.getRawData(),r={},o=n.getData(),i=pT(n).scope,l=n.visualStyleAccessPath||"itemStyle",s=JR(n,l);o.each(function(u){var c=o.getRawIndex(u);r[c]=u}),a.each(function(u){var c=r[u],d=o.getItemVisual(c,"colorFromPalette");if(d){var v=o.ensureUniqueItemVisual(c,"style"),f=a.getName(u)||u+"",h=a.count();v[s]=n.getColorFromPalette(f,i,h)}})}})}},x0=Math.PI;function nTe(e,t){t=t||{},zn(t,{text:"loading",textColor:tn.color.primary,fontSize:12,fontWeight:"normal",fontStyle:"normal",fontFamily:"sans-serif",maskColor:"rgba(255,255,255,0.8)",showSpinner:!0,color:tn.color.theme[0],spinnerRadius:10,lineWidth:5,zlevel:0});var n=new Ha,a=new ga({style:{fill:t.maskColor},zlevel:t.zlevel,z:1e4});n.add(a);var r=new Er({style:{text:t.text,fill:t.textColor,fontSize:t.fontSize,fontWeight:t.fontWeight,fontStyle:t.fontStyle,fontFamily:t.fontFamily},zlevel:t.zlevel,z:10001}),o=new ga({style:{fill:"none"},textContent:r,textConfig:{position:"right",distance:10},zlevel:t.zlevel,z:10001});n.add(o);var i;return t.showSpinner&&(i=new c2({shape:{startAngle:-x0/2,endAngle:-x0/2+.1,r:t.spinnerRadius},style:{stroke:t.color,lineCap:"round",lineWidth:t.lineWidth},zlevel:t.zlevel,z:10001}),i.animateShape(!0).when(1e3,{endAngle:x0*3/2}).start("circularInOut"),i.animateShape(!0).when(1e3,{startAngle:x0*3/2}).delay(300).start("circularInOut"),n.add(i)),n.resize=function(){var l=r.getBoundingRect().width,s=t.showSpinner?t.spinnerRadius:0,u=(e.getWidth()-s*2-(t.showSpinner&&l?10:0)-l)/2-(t.showSpinner&&l?0:5+l/2)+(t.showSpinner?0:l/2)+(l?0:s),c=e.getHeight()/2;t.showSpinner&&i.setShape({cx:u,cy:c}),o.setShape({x:u-s,y:c-s,width:s*2,height:s*2}),a.setShape({x:0,y:0,width:e.getWidth(),height:e.getHeight()})},n.resize(),n}var QR=function(){function e(t,n,a,r){this._stageTaskMap=an(),this.ecInstance=t,this.api=n,a=this._dataProcessorHandlers=a.slice(),r=this._visualHandlers=r.slice(),this._allHandlers=a.concat(r)}return e.prototype.restoreData=function(t,n){t.restoreData(n),this._stageTaskMap.each(function(a){var r=a.overallTask;r&&r.dirty()})},e.prototype.getPerformArgs=function(t,n){if(t.__pipeline){var a=this._pipelineMap.get(t.__pipeline.id),r=a.context,o=!n&&a.progressiveEnabled&&(!r||r.progressiveRender)&&t.__idxInPipeline>a.blockIndex,i=o?a.step:null,l=r&&r.modDataCount,s=l!=null?Math.ceil(l/i):null;return{step:i,modBy:s,modDataCount:l}}},e.prototype.getPipeline=function(t){return this._pipelineMap.get(t)},e.prototype.updateStreamModes=function(t,n){var a=this._pipelineMap.get(t.uid),r=t.__preparePipelineContext?t.__preparePipelineContext(n,a):f$(t,n,a);t.pipelineContext=a.context=r},e.prototype.restorePipelines=function(t,n){var a=this,r=a._pipelineMap=an();n.eachSeries(function(o){var i=t.painter.type==="canvas"&&o.getProgressive(),l=o.uid;r.set(l,{id:l,head:null,tail:null,threshold:o.getProgressiveThreshold(),progressiveEnabled:i&&!(o.preventIncremental&&o.preventIncremental()),blockIndex:-1,step:Math.round(i||700),count:0}),a._pipe(o,o.dataTask)})},e.prototype.prepareStageTasks=function(){var t=this._stageTaskMap,n=this.api.getModel(),a=this.api;we(this._allHandlers,function(r){var o=t.get(r.uid)||t.set(r.uid,{}),i="";Pl(!(r.reset&&r.overallReset),i),r.reset&&this._createSeriesStageTask(r,o,n,a),r.overallReset&&this._createOverallStageTask(r,o,n,a)},this)},e.prototype.prepareView=function(t,n,a,r){var o=t.renderTask,i=o.context;i.model=n,i.ecModel=a,i.api=r,o.__block=!t.incrementalPrepareRender,this._pipe(n,o)},e.prototype.performDataProcessorTasks=function(t,n){this._performStageTasks(this._dataProcessorHandlers,t,n,{block:!0})},e.prototype.performVisualTasks=function(t,n,a){this._performStageTasks(this._visualHandlers,t,n,a)},e.prototype._performStageTasks=function(t,n,a,r){r=r||{};var o=!1,i=this;we(t,function(s,u){if(!(r.visualType&&r.visualType!==s.visualType)){var c=i._stageTaskMap.get(s.uid),d=c.seriesTaskMap,v=c.overallTask;if(v){var f,h=v.agentStubMap;h.each(function(g){l(r,g)&&(g.dirty(),f=!0)}),f&&v.dirty(),i.updatePayload(v,a);var m=i.getPerformArgs(v,r.block);h.each(function(g){g.perform(m)}),v.perform(m)&&(o=!0)}else d&&d.each(function(g,y){l(r,g)&&g.dirty();var b=i.getPerformArgs(g,r.block);b.skip=!s.performRawSeries&&n.isSeriesFiltered(g.context.model),i.updatePayload(g,a),g.perform(b)&&(o=!0)})}});function l(s,u){return s.setDirty&&(!s.dirtyMap||s.dirtyMap.get(u.__pipeline.id))}this.unfinished=o||this.unfinished},e.prototype.performSeriesTasks=function(t){var n;t.eachSeries(function(a){n=a.dataTask.perform()||n}),this.unfinished=n||this.unfinished},e.prototype.plan=function(){this._pipelineMap.each(function(t){var n=t.tail;do{if(n.__block){t.blockIndex=n.__idxInPipeline;break}n=n.getUpstream()}while(n)})},e.prototype.updatePayload=function(t,n){n!=="remain"&&(t.context.payload=n)},e.prototype._createSeriesStageTask=function(t,n,a,r){var o=this,i=n.seriesTaskMap,l=n.seriesTaskMap=an(),s=t.seriesType,u=t.getTargetSeries;t.createOnAllSeries?a.eachRawSeries(c):s?a.eachRawSeriesByType(s,c):u&&u(a,r).each(c);function c(d){var v=d.uid,f=l.set(v,i&&i.get(v)||Uv({plan:lTe,reset:sTe,count:cTe}));f.context={model:d,ecModel:a,api:r,useClearVisual:t.isVisual&&!t.isLayout,plan:t.plan,reset:t.reset,scheduler:o},o._pipe(d,f)}},e.prototype._createOverallStageTask=function(t,n,a,r){var o=this,i=n.overallTask=n.overallTask||Uv({reset:aTe});i.context={ecModel:a,api:r,overallReset:t.overallReset,scheduler:o};var l=i.agentStubMap,s=i.agentStubMap=an(),u=t.seriesType,c=t.getTargetSeries,d=t.dirtyOnOverallProgress,v=!1,f="";Pl(!t.createOnAllSeries,f),u?a.eachRawSeriesByType(u,h):c?c(a,r).each(h):we(a.getSeries(),h);function h(m){var g=m.uid,y=s.set(g,l&&l.get(g)||(v=!0,Uv({reset:rTe,onDirty:iTe})));y.context={model:m,dirtyOnOverallProgress:d},y.agent=i,y.__block=d,o._pipe(m,y)}v&&i.dirty()},e.prototype._pipe=function(t,n){var a=t.uid,r=this._pipelineMap.get(a);!r.head&&(r.head=n),r.tail&&r.tail.pipe(n),r.tail=n,n.__idxInPipeline=r.count++,n.__pipeline=r},e.wrapStageHandler=function(t,n){return dn(t)&&(t={overallReset:t,seriesType:dTe(t)}),t.uid=p2("stageHandler"),n&&(t.visualType=n),t},e}();function aTe(e){e.overallReset(e.ecModel,e.api,e.payload)}function rTe(e){return e.dirtyOnOverallProgress&&oTe}function oTe(){this.agent.dirty(),this.getDownstream().dirty()}function iTe(){this.agent&&this.agent.dirty()}function lTe(e){return e.plan?e.plan(e.model,e.ecModel,e.api,e.payload):null}function sTe(e){e.useClearVisual&&e.data.clearAllVisual();var t=e.resetDefines=Cr(e.reset(e.model,e.ecModel,e.api,e.payload));return t.length>1?Xt(t,function(n,a){return eN(a)}):uTe}var uTe=eN(0);function eN(e){return function(t,n){var a=n.data,r=n.resetDefines[e];if(r&&r.dataEach)for(var o=t.start;o0&&f===u.length-v.length){var h=u.slice(0,f);h!=="data"&&(n.mainType=h,n[v.toLowerCase()]=s,c=!0)}}l.hasOwnProperty(u)&&(a[u]=s,c=!0),c||(r[u]=s)})}return{cptQuery:n,dataQuery:a,otherQuery:r}},e.prototype.filter=function(t,n){var a=this.eventInfo;if(!a)return!0;var r=a.targetEl,o=a.packedEvent,i=a.model,l=a.view;if(!i||!l)return!0;var s=n.cptQuery,u=n.dataQuery;return c(s,i,"mainType")&&c(s,i,"subType")&&c(s,i,"index","componentIndex")&&c(s,i,"name")&&c(s,i,"id")&&c(u,o,"name")&&c(u,o,"dataIndex")&&c(u,o,"dataType")&&(!l.filterForExposedEvent||l.filterForExposedEvent(t,n.otherQuery,r,o));function c(d,v,f,h){return d[f]==null||v[h||f]===d[f]}},e.prototype.afterTrigger=function(){this.eventInfo=null},e}(),i3=["symbol","symbolSize","symbolRotate","symbolOffset"],bT=i3.concat(["symbolKeepAspect"]),vTe={createOnAllSeries:!0,performRawSeries:!0,reset:function(e,t){var n=e.getData();if(e.legendIcon&&n.setVisual("legendIcon",e.legendIcon),!e.hasSymbolVisual)return;for(var a={},r={},o=!1,i=0;i=0&&Fu(s)?s:.5;var u=e.createRadialGradient(i,l,0,i,l,s);return u}function s3(e,t,n){for(var a=t.type==="radial"?NTe(e,t,n):RTe(e,t,n),r=t.colorStops,o=0;o0)?null:e==="dashed"?[4*t,2*t]:e==="dotted"?[t]:Gn(e)?[e]:Lt(e)?e:null}function iN(e){var t=e.style,n=t.lineDash&&t.lineWidth>0&&VTe(t.lineDash,t.lineWidth),a=t.lineDashOffset;if(n){var r=t.strokeNoScale&&e.getLineScale?e.getLineScale():1;r&&r!==1&&(n=Xt(n,function(o){return o/r}),a/=r)}return[n,a]}var zTe=new bc(!0);function Hg(e){var t=e.stroke;return!(t==null||t==="none"||!(e.lineWidth>0))}function _T(e){return typeof e=="string"&&e!=="none"}function Wg(e){var t=e.fill;return t!=null&&t!=="none"}function wT(e,t){if(t.fillOpacity!=null&&t.fillOpacity!==1){var n=e.globalAlpha;e.globalAlpha=t.fillOpacity*t.opacity,e.fill(),e.globalAlpha=n}else e.fill()}function ST(e,t){if(t.strokeOpacity!=null&&t.strokeOpacity!==1){var n=e.globalAlpha;e.globalAlpha=t.strokeOpacity*t.opacity,e.stroke(),e.globalAlpha=n}else e.stroke()}function u3(e,t,n){var a=p$(t.image,t.__image,n);if(r2(a)){var r=e.createPattern(a,t.repeat||"repeat");if(typeof DOMMatrix=="function"&&r&&r.setTransform){var o=new DOMMatrix;o.translateSelf(t.x||0,t.y||0),o.rotateSelf(0,0,(t.rotation||0)*b8e),o.scaleSelf(t.scaleX||1,t.scaleY||1),r.setTransform(o)}return r}}function FTe(e,t,n,a,r){var o,i=Hg(n),l=Wg(n),s=n.strokePercent,u=s<1,c=!t.path;(!t.silent||u)&&c&&t.createPathProxy();var d=t.path||zTe,v=t.__dirty;if(!a){var f=n.fill,h=n.stroke,m=l&&!!f.colorStops,g=i&&!!h.colorStops,y=l&&!!f.image,b=i&&!!h.image,_=void 0,w=void 0,S=void 0,k=void 0,T=void 0;(m||g)&&(T=t.getBoundingRect()),m&&(_=v?s3(e,f,T):t.__canvasFillGradient,t.__canvasFillGradient=_),g&&(w=v?s3(e,h,T):t.__canvasStrokeGradient,t.__canvasStrokeGradient=w),y&&(S=v||!t.__canvasFillPattern?u3(e,f,t):t.__canvasFillPattern,t.__canvasFillPattern=S),b&&(k=v||!t.__canvasStrokePattern?u3(e,h,t):t.__canvasStrokePattern,t.__canvasStrokePattern=k),m?e.fillStyle=_:y&&(S?e.fillStyle=S:l=!1),g?e.strokeStyle=w:b&&(k?e.strokeStyle=k:i=!1)}var M=t.getGlobalScale();d.setScale(M[0],M[1],t.segmentIgnoreThreshold);var E,A;e.setLineDash&&n.lineDash&&(o=iN(t),E=o[0],A=o[1]);var I=!0;(c||v&rd)&&(d.setDPR(e.dpr),u?d.setContext(null):(d.setContext(e),I=!1),d.reset(),t.buildPath(d,t.shape,a),d.toStatic(),t.pathUpdated()),I&&d.rebuildPath(e,u?s:1),E&&(e.setLineDash(E),e.lineDashOffset=A),a?(r.batchFill=l,r.batchStroke=i):n.strokeFirst?(i&&ST(e,n),l&&wT(e,n)):(l&&wT(e,n),i&&ST(e,n)),E&&e.setLineDash([])}function HTe(e,t,n){var a=t.__image=p$(n.image,t.__image,t,t.onload);if(!(!a||!r2(a))){var r=n.x||0,o=n.y||0,i=t.getWidth(),l=t.getHeight(),s=a.width/a.height;if(i==null&&l!=null?i=l*s:l==null&&i!=null?l=i/s:i==null&&l==null&&(i=a.width,l=a.height),n.sWidth&&n.sHeight){var u=n.sx||0,c=n.sy||0;e.drawImage(a,u,c,n.sWidth,n.sHeight,r,o,i,l)}else if(n.sx&&n.sy){var u=n.sx,c=n.sy,d=i-u,v=l-c;e.drawImage(a,u,c,d,v,r,o,i,l)}else e.drawImage(a,r,o,i,l)}}function WTe(e,t,n){var a,r=n.text;if(r!=null&&(r+=""),r){e.font=n.font||Vs,e.textAlign=n.textAlign,e.textBaseline=n.textBaseline;var o=void 0,i=void 0;e.setLineDash&&n.lineDash&&(a=iN(t),o=a[0],i=a[1]),o&&(e.setLineDash(o),e.lineDashOffset=i),n.strokeFirst?(Hg(n)&&e.strokeText(r,n.x,n.y),Wg(n)&&e.fillText(r,n.x,n.y)):(Wg(n)&&e.fillText(r,n.x,n.y),Hg(n)&&e.strokeText(r,n.x,n.y)),o&&e.setLineDash([])}}var CT=["shadowBlur","shadowOffsetX","shadowOffsetY"],xT=[["lineCap","butt"],["lineJoin","miter"],["miterLimit",10]];function lN(e,t,n,a,r){var o=!1;if(!a&&(n=n||{},t===n))return!1;if(a||t.opacity!==n.opacity){wr(e,r),o=!0;var i=Math.max(Math.min(t.opacity,1),0);e.globalAlpha=isNaN(i)?tc.opacity:i}(a||t.blend!==n.blend)&&(o||(wr(e,r),o=!0),e.globalCompositeOperation=t.blend||tc.blend);for(var l=0;l0&&n.unfinished);n.unfinished||this._zr.flush()}}},t.prototype.getDom=function(){return this._dom},t.prototype.getId=function(){return this.id},t.prototype.getZr=function(){return this._zr},t.prototype.isSSR=function(){return this._ssr},t.prototype.setOption=function(n,a,r){if(!this[ba]){if(this._disposed){this.id;return}var o,i,l;if(Yt(a)&&(r=a.lazyUpdate,o=a.silent,i=a.replaceMerge,l=a.transition,a=a.notMerge),this[ba]=!0,Jc(this),!this._model||a){var s=new Gke(this._api),u=this._theme,c=this._model=new X8;c.scheduler=this._scheduler,c.ssr=this._ssr,c.init(null,null,null,u,this._locale,s)}this._model.setOption(n,{replaceMerge:i},h3);var d={seriesTransition:l,optionChanged:!0};if(r)this[Ra]={silent:o,updateParams:d},this[ba]=!1,this.getZr().wakeUp();else{try{xu(this),sl.update.call(this,null,d)}catch(v){throw this[Ra]=null,this[ba]=!1,v}this._ssr||this._zr.flush(),this[Ra]=null,this[ba]=!1,Xc.call(this,o),Zc.call(this,o)}}},t.prototype.setTheme=function(n,a){if(!this[ba]){if(this._disposed){this.id;return}var r=this._model;if(r){var o=a&&a.silent,i=null;this[Ra]&&(o==null&&(o=this[Ra].silent),i=this[Ra].updateParams,this[Ra]=null),this[ba]=!0,Jc(this);try{this._updateTheme(n),r.setTheme(this._theme),xu(this),sl.update.call(this,{type:"setTheme"},i)}catch(l){throw this[ba]=!1,l}this[ba]=!1,Xc.call(this,o),Zc.call(this,o)}}},t.prototype._updateTheme=function(n){Ut(n)&&(n=CN[n]),n&&(n=yn(n),n&&IR(n,!0),this._theme=n)},t.prototype.getModel=function(){return this._model},t.prototype.getOption=function(){return this._model&&this._model.getOption()},t.prototype.getWidth=function(){return this._zr.getWidth()},t.prototype.getHeight=function(){return this._zr.getHeight()},t.prototype.getDevicePixelRatio=function(){return this._zr.painter.dpr||hn.hasGlobalWindow&&window.devicePixelRatio||1},t.prototype.getRenderedCanvas=function(n){return this.renderToCanvas(n)},t.prototype.renderToCanvas=function(n){n=n||{};var a=this._zr.painter;return a.getRenderedCanvas({backgroundColor:n.backgroundColor||this._model.get("backgroundColor"),pixelRatio:n.pixelRatio||this.getDevicePixelRatio()})},t.prototype.renderToSVGString=function(n){n=n||{};var a=this._zr.painter;return a.renderToString({useViewBox:n.useViewBox})},t.prototype.getSvgDataURL=function(){var n=this._zr,a=n.storage.getDisplayList();return we(a,function(r){r.stopAnimation(null,!0)}),n.painter.toDataURL()},t.prototype.getDataURL=function(n){if(this._disposed){this.id;return}n=n||{};var a=n.excludeComponents,r=this._model,o=[],i=this;we(a,function(s){r.eachComponent({mainType:s},function(u){var c=i._componentsMap[u.__viewId];c.group.ignore||(o.push(c),c.group.ignore=!0)})});var l=this._zr.painter.getType()==="svg"?this.getSvgDataURL():this.renderToCanvas(n).toDataURL("image/"+(n&&n.type||"png"));return we(o,function(s){s.group.ignore=!1}),l},t.prototype.getConnectedDataURL=function(n){if(this._disposed){this.id;return}var a=n.type==="svg",r=this.group,o=Math.min,i=Math.max,l=1/0;if(zT[r]){var s=l,u=l,c=-l,d=-l,v=[],f=n&&n.pixelRatio||this.getDevicePixelRatio();we(Gv,function(_,w){if(_.group===r){var S=a?_.getZr().painter.getSvgDom().innerHTML:_.renderToCanvas(yn(n)),k=_.getDom().getBoundingClientRect();s=o(k.left,s),u=o(k.top,u),c=i(k.right,c),d=i(k.bottom,d),v.push({dom:S,left:k.left,top:k.top})}}),s*=f,u*=f,c*=f,d*=f;var h=c-s,m=d-u,g=oo.createCanvas(),y=Fk(g,{renderer:a?"svg":"canvas"});if(y.resize({width:h,height:m}),a){var b="";return we(v,function(_){var w=_.left-s,S=_.top-u;b+=''+_.dom+""}),y.painter.getSvgRoot().innerHTML=b,n.connectedBackgroundColor&&y.painter.setBackgroundColor(n.connectedBackgroundColor),y.refreshImmediately(),y.painter.toDataURL()}else return n.connectedBackgroundColor&&y.add(new ga({shape:{x:0,y:0,width:h,height:m},style:{fill:n.connectedBackgroundColor}})),we(v,function(_){var w=new js({style:{x:_.left*f-s,y:_.top*f-u,image:_.dom}});y.add(w)}),y.refreshImmediately(),g.toDataURL("image/"+(n&&n.type||"png"))}else return this.getDataURL(n)},t.prototype.convertToPixel=function(n,a,r){return A0(this,"convertToPixel",n,a,r)},t.prototype.convertToLayout=function(n,a,r){return A0(this,"convertToLayout",n,a,r)},t.prototype.convertFromPixel=function(n,a,r){return A0(this,"convertFromPixel",n,a,r)},t.prototype.containPixel=function(n,a){if(this._disposed){this.id;return}var r=this._model,o,i=Uy(r,n);return we(i,function(l,s){s.indexOf("Models")>=0&&we(l,function(u){var c=u.coordinateSystem;if(c&&c.containPoint)o=o||!!c.containPoint(a);else if(s==="seriesModels"){var d=this._chartsMap[u.__viewId];d&&d.containPoint&&(o=o||d.containPoint(a,u))}},this)},this),!!o},t.prototype.getVisual=function(n,a){var r=this._model,o=Uy(r,n,{defaultMainType:"series"}),i=o.seriesModel,l=i.getData(),s=o.hasOwnProperty("dataIndexInside")?o.dataIndexInside:o.hasOwnProperty("dataIndex")?l.indexOfRawIndex(o.dataIndex):null;return s!=null?pTe(l,s,a):mTe(l,a)},t.prototype.getViewOfComponentModel=function(n){return this._componentsMap[n.__viewId]},t.prototype.getViewOfSeriesModel=function(n){return this._chartsMap[n.__viewId]},t.prototype._initEvents=function(){var n=this;we(vMe,function(r){var o=function(i){var l=n.getModel(),s=i.target,u,c=r==="globalout";if(c?u={}:s&&gv(s,function(m){var g=In(m);if(g&&g.dataIndex!=null){var y=g.dataModel||l.getSeriesByIndex(g.seriesIndex);return u=y&&y.getDataParams(g.dataIndex,g.dataType,s)||{},!0}else if(g.eventData)return u=vt({},g.eventData),!0},!0),u){var d=u.componentType,v=u.componentIndex;(d==="markLine"||d==="markPoint"||d==="markArea")&&(d="series",v=u.seriesIndex);var f=d&&v!=null&&l.getComponent(d,v),h=f&&n[f.mainType==="series"?"_chartsMap":"_componentsMap"][f.__viewId];u.event=i,u.type=r,n._$eventProcessor.eventInfo={targetEl:s,packedEvent:u,model:f,view:h},n.trigger(r,u)}};o.zrEventfulCallAtLast=!0,n._zr.on(r,o,n)});var a=this._messageCenter;we(f3,function(r,o){a.on(o,function(i){n.trigger(o,i)})}),yTe(a,this,this._api)},t.prototype.isDisposed=function(){return this._disposed},t.prototype.clear=function(){if(this._disposed){this.id;return}this.setOption({series:[]},!0)},t.prototype.dispose=function(){if(this._disposed){this.id;return}this._disposed=!0;var n=this.getDom();n&&s$(this.getDom(),rS,"");var a=this,r=a._api,o=a._model;we(a._componentsViews,function(i){i.dispose(o,r)}),we(a._chartsViews,function(i){i.dispose(o,r)}),a._zr.dispose(),a._dom=a._model=a._chartsMap=a._componentsMap=a._chartsViews=a._componentsViews=a._scheduler=a._api=a._zr=a._throttledZrFlush=a._theme=a._coordSysMgr=a._messageCenter=null,delete Gv[a.id]},t.prototype.resize=function(n){if(!this[ba]){if(this._disposed){this.id;return}this._zr.resize(n);var a=this._model;if(this._loadingFX&&this._loadingFX.resize(),!!a){var r=a.resetOption("media"),o=n&&n.silent;this[Ra]&&(o==null&&(o=this[Ra].silent),r=!0,this[Ra]=null),this[ba]=!0,Jc(this);try{r&&xu(this),sl.update.call(this,{type:"resize",animation:vt({duration:0},n&&n.animation)})}catch(i){throw this[ba]=!1,i}this[ba]=!1,Xc.call(this,o),Zc.call(this,o)}}},t.prototype.showLoading=function(n,a){if(this._disposed){this.id;return}if(Yt(n)&&(a=n,n=""),n=n||"default",this.hideLoading(),!!p3[n]){var r=p3[n](this._api,a),o=this._zr;this._loadingFX=r,o.add(r)}},t.prototype.hideLoading=function(){if(this._disposed){this.id;return}this._loadingFX&&this._zr.remove(this._loadingFX),this._loadingFX=null},t.prototype.makeActionFromEvent=function(n){var a=vt({},n);return a.type=d3[n.type],a},t.prototype.dispatchAction=function(n,a){if(this._disposed){this.id;return}if(Yt(a)||(a={silent:!!a}),!!Kg[n.type]&&this._model){if(this[ba]){this._pendingActions.push(n);return}var r=a.silent;Cb.call(this,n,r);var o=a.flush;o?this._zr.flush():o!==!1&&hn.browser.weChat&&this._throttledZrFlush(),Xc.call(this,r),Zc.call(this,r)}},t.prototype.updateLabelLayout=function(){mo.trigger("series:layoutlabels",this._model,this._api,{updatedSeries:[]})},t.prototype.appendData=function(n){if(this._disposed){this.id;return}var a=n.seriesIndex,r=this.getModel(),o=r.getSeriesByIndex(a);o.appendData(n),this._scheduler.unfinished=!0,this.getZr().wakeUp()},t.internalField=function(){xu=function(d){wTe(d._model);var v=d._scheduler;v.restorePipelines(d._zr,d._model),v.prepareStageTasks(),wb(d,!0),wb(d,!1),v.plan()},wb=function(d,v){for(var f=d._model,h=d._scheduler,m=v?d._componentsViews:d._chartsViews,g=v?d._componentsMap:d._chartsMap,y=d._zr,b=d._api,_=0;_jt(v.get("hoverLayerThreshold"),kR.hoverLayerThreshold)&&!hn.node&&!hn.worker;(d._usingTHL||g)&&(v.eachSeries(function(y){if(!y.preventUsingHoverLayer){var b=d._chartsMap[y.__viewId];b.__alive&&b.eachRendered(function(_){var w=_.states.emphasis;w&&w.hoverLayer!==E8&&(w.hoverLayer=g?q$:j$)})}}),d._usingTHL=g)}}function l(d,v){var f=d.get("blendMode")||null;v.eachRendered(function(h){h.isGroup||(h.style.blend=f)})}function s(d,v){if(!d.preventAutoZ){var f=tR(d);v.eachRendered(function(h){return nR(h,f.z,f.zlevel),!0})}}function u(d,v){v.eachRendered(function(f){if(!zv(f)){var h=f.getTextContent(),m=f.getTextGuideLine();f.stateTransition&&(f.stateTransition=null),h&&h.stateTransition&&(h.stateTransition=null),m&&m.stateTransition&&(m.stateTransition=null),f.hasState()?(f.prevStates=f.currentStates,f.clearStates()):f.prevStates&&(f.prevStates=null)}})}function c(d,v){var f=d.getModel("stateAnimation"),h=d.isAnimationEnabled(),m=f.get("duration"),g=m>0?{duration:m,delay:f.get("delay"),easing:f.get("easing")}:null;v.eachRendered(function(y){if(y.states&&y.states.emphasis){if(zv(y))return;if(y instanceof Fn&&W5e(y),y.__dirty){var b=y.prevStates;b&&y.useStates(b)}if(h){y.stateTransition=g;var _=y.getTextContent(),w=y.getTextGuideLine();_&&(_.stateTransition=g),w&&(w.stateTransition=g)}y.__dirty&&o(y)}})}BT=function(d){return new(function(v){At(f,v);function f(){return v!==null&&v.apply(this,arguments)||this}return f.prototype.getCoordinateSystems=function(){return d._coordSysMgr.getCoordinateSystems()},f.prototype.getComponentByElement=function(h){for(;h;){var m=h.__ecComponentInfo;if(m!=null)return d._model.getComponent(m.mainType,m.index);h=h.parent}},f.prototype.enterEmphasis=function(h,m){Dg(h,m),vo(d)},f.prototype.leaveEmphasis=function(h,m){Og(h,m),vo(d)},f.prototype.enterBlur=function(h){D5e(h),vo(d)},f.prototype.leaveBlur=function(h){D$(h),vo(d)},f.prototype.enterSelect=function(h){O$(h),vo(d)},f.prototype.leaveSelect=function(h){$$(h),vo(d)},f.prototype.getModel=function(){return d.getModel()},f.prototype.getViewOfComponentModel=function(h){return d.getViewOfComponentModel(h)},f.prototype.getViewOfSeriesModel=function(h){return d.getViewOfSeriesModel(h)},f.prototype.getECUpdateCycleVersion=function(){return d[M0]},f.prototype.usingTHL=function(){return d._usingTHL},f}(E$))(d)},wN=function(d){function v(f,h){for(var m=0;m=0)){FT.push(n);var i=QR.wrapStageHandler(n,r);i.__prio=t,i.__raw=n,e.push(i)}}function MN(e,t){p3[e]=t}function bMe(e,t,n){var a=_Te("registerMap");a&&a(e,t,n)}var _Me=C9e;Lc(nS,Q9e);Lc(w2,eTe);Lc(w2,tTe);Lc(nS,vTe);Lc(w2,hTe);Lc(pN,ZTe);kN(IR);TN(tMe,r9e);MN("default",nTe);Sf({type:nc,event:nc,update:nc},lr);Sf({type:ym,event:ym,update:ym},lr);Sf({type:Lg,event:b8,update:Lg,action:lr,refineEvent:lS,publishNonRefinedEvent:!0});Sf({type:W_,event:b8,update:W_,action:lr,refineEvent:lS,publishNonRefinedEvent:!0});Sf({type:Pg,event:b8,update:Pg,action:lr,refineEvent:lS,publishNonRefinedEvent:!0});function lS(e,t,n,a){return{eventContent:{selected:B5e(n),isFromClick:t.isFromClick||!1}}}xN("default",{});xN("dark",aN);function Gf(e){return e==null?0:e.length||1}function HT(e){return e}var wMe=function(){function e(t,n,a,r,o,i){this._old=t,this._new=n,this._oldKeyGetter=a||HT,this._newKeyGetter=r||HT,this.context=o,this._diffModeMultiple=i==="multiple"}return e.prototype.add=function(t){return this._add=t,this},e.prototype.update=function(t){return this._update=t,this},e.prototype.updateManyToOne=function(t){return this._updateManyToOne=t,this},e.prototype.updateOneToMany=function(t){return this._updateOneToMany=t,this},e.prototype.updateManyToMany=function(t){return this._updateManyToMany=t,this},e.prototype.remove=function(t){return this._remove=t,this},e.prototype.execute=function(){this[this._diffModeMultiple?"_executeMultiple":"_executeOneToOne"]()},e.prototype._executeOneToOne=function(){var t=this._old,n=this._new,a={},r=new Array(t.length),o=new Array(n.length);this._initIndexMap(t,null,r,"_oldKeyGetter"),this._initIndexMap(n,a,o,"_newKeyGetter");for(var i=0;i1){var c=s.shift();s.length===1&&(a[l]=s[0]),this._update&&this._update(c,i)}else u===1?(a[l]=null,this._update&&this._update(s,i)):this._remove&&this._remove(i)}this._performRestAdd(o,a)},e.prototype._executeMultiple=function(){var t=this._old,n=this._new,a={},r={},o=[],i=[];this._initIndexMap(t,a,o,"_oldKeyGetter"),this._initIndexMap(n,r,i,"_newKeyGetter");for(var l=0;l1&&v===1)this._updateManyToOne&&this._updateManyToOne(c,u),r[s]=null;else if(d===1&&v>1)this._updateOneToMany&&this._updateOneToMany(c,u),r[s]=null;else if(d===1&&v===1)this._update&&this._update(c,u),r[s]=null;else if(d>1&&v>1)this._updateManyToMany&&this._updateManyToMany(c,u),r[s]=null;else if(d>1)for(var f=0;f1)for(var l=0;l30}var Yf=Yt,Ql=Xt,EMe=typeof Int32Array>"u"?Array:Int32Array,AMe="e\0\0",WT=-1,IMe=["hasItemOption","_nameList","_idList","_invertedIndicesMap","_dimSummary","userOutput","_rawData","_dimValueGetter","_nameDimIdx","_idDimIdx","_nameRepeatCount"],LMe=["_approximateExtent"],KT,P0,jf,qf,Tb,Xf,Mb,PN=function(){function e(t,n){this.type="list",this._dimOmitted=!1,this._nameList=[],this._idList=[],this._visual={},this._layout={},this._itemVisuals=[],this._itemLayouts=[],this._graphicEls=[],this._approximateExtent={},this._calculationInfo={},this.hasItemOption=!1,this.TRANSFERABLE_METHODS=["cloneShallow","downSample","minmaxDownSample","lttbDownSample","map"],this.CHANGABLE_METHODS=["filterSelf","selectRange"],this.DOWNSAMPLE_METHODS=["downSample","minmaxDownSample","lttbDownSample"];var a,r=!1;AN(t)?(a=t.dimensions,this._dimOmitted=t.isDimensionOmitted(),this._schema=t):(r=!0,a=t),a=a||["x","y"];for(var o={},i=[],l={},s=!1,u={},c=0;c=n)){var a=this._store,r=a.getProvider();this._updateOrdinalMeta();var o=this._nameList,i=this._idList,l=r.getSource().sourceFormat,s=l===uo;if(s&&!r.pure)for(var u=[],c=t;c0},e.prototype.ensureUniqueItemVisual=function(t,n){var a=this._itemVisuals,r=a[t];r||(r=a[t]={});var o=r[n];return o==null&&(o=this.getVisual(n),Lt(o)?o=o.slice():Yf(o)&&(o=vt({},o)),r[n]=o),o},e.prototype.setItemVisual=function(t,n,a){var r=this._itemVisuals[t]||{};this._itemVisuals[t]=r,Yf(n)?vt(r,n):r[n]=a},e.prototype.clearAllVisual=function(){this._visual={},this._itemVisuals=[]},e.prototype.setLayout=function(t,n){Yf(t)?vt(this._layout,t):this._layout[t]=n},e.prototype.getLayout=function(t){return this._layout[t]},e.prototype.getItemLayout=function(t){return this._itemLayouts[t]},e.prototype.setItemLayout=function(t,n,a){this._itemLayouts[t]=a?vt(this._itemLayouts[t]||{},n):n},e.prototype.clearItemLayouts=function(){this._itemLayouts.length=0},e.prototype.setItemGraphicEl=function(t,n){var a=this.hostModel&&this.hostModel.seriesIndex;w5e(a,this.dataType,t,n),this._graphicEls[t]=n},e.prototype.getItemGraphicEl=function(t){return this._graphicEls[t]},e.prototype.eachItemGraphicEl=function(t,n){we(this._graphicEls,function(a,r){a&&t&&t.call(n,a,r)})},e.prototype.cloneShallow=function(t){return t||(t=new e(this._schema?this._schema:Ql(this.dimensions,this._getDimInfo,this),this.hostModel)),Tb(t,this),t._store=this._store,t},e.prototype.wrapMethod=function(t,n){var a=this[t];dn(a)&&(this.__wrappedMethods=this.__wrappedMethods||[],this.__wrappedMethods.push(t),this[t]=function(){var r=a.apply(this,arguments);return n.apply(this,[r].concat(t8(arguments)))})},e.internalField=function(){KT=function(t){var n=t._invertedIndicesMap;we(n,function(a,r){var o=t._dimInfos[r],i=o.ordinalMeta,l=t._store;if(i){a=n[r]=new EMe(i.categories.length);for(var s=0;s1&&(s+="__ec__"+c),r[n]=s}}}(),e}();function DN(e,t){Z8(e)||(e=LR(e)),t=t||{};var n=t.coordDimensions||[],a=t.dimensionsDefine||e.dimensionsDefine||[],r=an(),o=[],i=PMe(e,n,a,t.dimensionsCount),l=t.canOmitUnusedDimensions&&LN(i),s=a===e.dimensionsDefine,u=s?IN(e):sS(a),c=t.encodeDefine;!c&&t.encodeDefaulter&&(c=t.encodeDefaulter(e,i));for(var d=an(c),v=new FR(i),f=0;f0&&(E.name=E.name+(A-1))}),new EN({source:e,dimensions:o,fullDimensionCount:i,dimensionOmitted:l})}function PMe(e,t,n,a){var r=Math.max(e.dimensionsDetectedCount||1,t.length,n.length,a||0);return we(t,function(o){var i;Yt(o)&&(i=o.dimsDef)&&(r=Math.max(r,i.length))}),r}function DMe(e,t,n){if(n||t.hasKey(e)){for(var a=0;t.hasKey(e+a);)a++;e+=a}return t.set(e,!0),e}var OMe=function(){function e(t){this.coordSysDims=[],this.axisMap=an(),this.categoryAxisMap=an(),this.coordSysName=t}return e}();function $Me(e){var t=e.get("coordinateSystem"),n=new OMe(t),a=RMe[t];if(a)return a(e,n,n.axisMap,n.categoryAxisMap),n}var RMe={cartesian2d:function(e,t,n,a){var r=e.getReferringComponents("xAxis",ko).models[0],o=e.getReferringComponents("yAxis",ko).models[0];t.coordSysDims=["x","y"],n.set("x",r),n.set("y",o),Qc(r)&&(a.set("x",r),t.firstCategoryDimIndex=0),Qc(o)&&(a.set("y",o),t.firstCategoryDimIndex==null&&(t.firstCategoryDimIndex=1))},singleAxis:function(e,t,n,a){var r=e.getReferringComponents("singleAxis",ko).models[0];t.coordSysDims=["single"],n.set("single",r),Qc(r)&&(a.set("single",r),t.firstCategoryDimIndex=0)},polar:function(e,t,n,a){var r=e.getReferringComponents("polar",ko).models[0],o=r.findAxisModel("radiusAxis"),i=r.findAxisModel("angleAxis");t.coordSysDims=["radius","angle"],n.set("radius",o),n.set("angle",i),Qc(o)&&(a.set("radius",o),t.firstCategoryDimIndex=0),Qc(i)&&(a.set("angle",i),t.firstCategoryDimIndex==null&&(t.firstCategoryDimIndex=1))},geo:function(e,t,n,a){t.coordSysDims=["lng","lat"]},parallel:function(e,t,n,a){var r=e.ecModel,o=r.getComponent("parallel",e.get("parallelIndex")),i=t.coordSysDims=o.dimensions.slice();we(o.parallelAxisIndex,function(l,s){var u=r.getComponent("parallelAxis",l),c=i[s];n.set(c,u),Qc(u)&&(a.set(c,u),t.firstCategoryDimIndex==null&&(t.firstCategoryDimIndex=s))})},matrix:function(e,t,n,a){var r=e.getReferringComponents("matrix",ko).models[0];t.coordSysDims=["x","y"];var o=r.getDimensionModel("x"),i=r.getDimensionModel("y");n.set("x",o),n.set("y",i),a.set("x",o),a.set("y",i)}};function Qc(e){return e.get("type")==="category"}function NMe(e,t,n){n=n||{};var a=n.byIndex,r=n.stackedCoordDimension,o,i,l;BMe(t)?o=t:(i=t.schema,o=i.dimensions,l=t.store);var s=!!(e&&e.get("stack")),u,c,d,v,f=!0;function h(w){return w.type!=="ordinal"&&w.type!=="time"}if(we(o,function(w,S){Ut(w)&&(o[S]=w={name:w}),h(w)||(f=!1)}),we(o,function(w,S){s&&!w.isExtraCoord&&(!a&&!u&&w.ordinalMeta&&(u=w),!c&&h(w)&&(!f||w.coordDim!=="x"&&w.coordDim!=="angle")&&(!r||r===w.coordDim)&&(c=w))}),c&&!a&&!u&&(a=!0),c){d="__\0ecstackresult_"+e.id,v="__\0ecstackedover_"+e.id,u&&(u.createInvertedIndices=!0);var m=c.coordDim,g=c.type,y=0;we(o,function(w){w.coordDim===m&&y++});var b={name:d,coordDim:m,coordDimIndex:y,type:g,isExtraCoord:!0,isCalculationCoord:!0,storeDimIndex:o.length},_={name:v,coordDim:v,coordDimIndex:y+1,type:g,isExtraCoord:!0,isCalculationCoord:!0,storeDimIndex:o.length+1};i?(l&&(b.storeDimIndex=l.ensureCalculationDimension(v,g),_.storeDimIndex=l.ensureCalculationDimension(d,g)),i.appendCalculationDimension(b),i.appendCalculationDimension(_)):(o.push(b),o.push(_))}return{stackedDimension:c&&c.name,stackedByDimension:u&&u.name,isStackedByIndex:a,stackedOverDimension:v,stackResultDimension:d}}function BMe(e){return!AN(e.schema)}function af(e,t){return!!t&&t===e.getCalculationInfo("stackedDimension")}function VMe(e,t){return af(e,t)?e.getCalculationInfo("stackResultDimension"):t}function zMe(e,t){var n=e.get("coordinateSystem"),a=U8.get(n),r;return t&&t.coordSysDims&&(r=Xt(t.coordSysDims,function(o){var i={name:o},l=t.axisMap.get(o);if(l){var s=l.get("type");i.type=xMe(s)}return i})),r||(r=a&&(a.getDimensionsInfo?a.getDimensionsInfo():a.dimensions.slice())||["x","y"]),r}function FMe(e,t,n){var a,r;return n&&we(e,function(o,i){var l=o.coordDim,s=n.categoryAxisMap.get(l);s&&(a==null&&(a=i),o.ordinalMeta=s.getOrdinalMeta(),t&&(o.createInvertedIndices=!0)),o.otherDims.itemName!=null&&(r=!0)}),!r&&a!=null&&(e[a].otherDims.itemName=0),a}function uS(e,t,n){n=n||{};var a=t.getSourceManager(),r,o=!1;r=a.getSource(),o=r.sourceFormat===uo;var i=$Me(t),l=zMe(t,i),s=n.useEncodeDefaulter,u=dn(s)?s:s?Sa(Oke,l,t):null,c={coordDimensions:l,generateCoord:n.generateCoord,encodeDefine:t.getEncode(),encodeDefaulter:u,canOmitUnusedDimensions:!o},d=DN(r,c),v=FMe(d.dimensions,n.createInvertedIndices,i),f=o?null:a.getSharedDataStore(d),h=NMe(t,{schema:d,store:f}),m=new PN(d,t);m.setCalculationInfo(h);var g=v!=null&&HMe(r)?function(y,b,_,w){return w===v?_:this.defaultDimValueGetter(y,b,_,w)}:null;return m.hasItemOption=!1,m.initData(o?r:f,null,g),m}function HMe(e){if(e.sourceFormat===uo){var t=WMe(e.data||[]);return!Lt(bp(t))}}function WMe(e){for(var t=0;t=t[0]&&e<=t[1]},getExtent:function(){return this._extents[Mo].slice()},getExtentUnsafe:function(e){return this._extents[e]},setExtent:function(e,t){UT(this._extents,Mo,e,t)},setExtent2:function(e,t,n){var a=this._extents;a[e]||(a[e]=a[Mo].slice()),UT(a,e,t,n)},freeze:function(){}};function UT(e,t,n,a){Jd(n,a)&&(e[t][0]=n,e[t][1]=a)}function NN(e){return jg(e)||rf(e)}function jg(e){return e.type==="interval"}function dS(e){return e.type==="time"}function rf(e){return e.type==="log"}function pi(e){return e.type==="ordinal"}function ZMe(e){var t=s8(e),n=Ac(10,t),a=Dl(e/n);return a?a===2?a=3:a===3?a=5:a*=2:a=1,En(a*n,-t)}function Sc(e){return bs(e)+2}function D0(e,t){return Th(e)/Th(t)}function Eb(e,t,n){var a=n&&n.lookup;if(a){for(var r=0;r1&&o/i>2&&(r=Math.round(Math.ceil(r/i)*i)),r!==a[0]&&s(a[0],!0,!0);for(var l=r;l<=a[1];l+=i)s(l,!1,l===a[0]||l===a[1]);l-i!==a[1]&&s(a[1],!0,!0);function s(u,c,d){n({value:u,offInterval:c},d)}}var zN=function(e){At(t,e);function t(n){var a=e.call(this)||this;a.type="ordinal",a.parse=t.parse,$N(a,t.decoratedMethods);var r=n.ordinalMeta;r||(r=new m3({})),Lt(r)&&(r=new m3({categories:Xt(r,function(i){return Yt(i)?i.value:i})})),a._ordinalMeta=r;var o=cS(null,null,n.extent||[0,r.categories.length-1]);return a._mapper=o.mapper,RN(a),a}return t.parse=function(n){return n==null?n=NaN:Ut(n)?(n=this._ordinalMeta.getOrdinal(n),n==null&&(n=NaN)):n=Dl(n),n},t.prototype.getTicks=function(){var n=[];return VN(this,0,function(a){n.push(a)}),n},t.prototype.getMinorTicks=function(n){},t.prototype.setSortInfo=function(n){if(n==null){this._ordinalNumbersByTick=this._ticksByOrdinalNumber=null;return}for(var a=n.ordinalNumbers,r=this._ordinalNumbersByTick=[],o=this._ticksByOrdinalNumber=[],i=0,l=this._ordinalMeta.categories.length,s=sr(l,a.length);i=0&&n=0&&n=0&&ni[0]&&mi[1]||!isFinite(m)||!isFinite(i[1]))break}else{if(g>h)break;m=sr(m,i[1]),g===h&&(m=i[1])}if(d.push({value:m}),m=En(m+r,l),u){var y=u.calcNiceTickMultiple(m,f);y>=0&&(m=En(m+y*r,l))}if(d.length>0&&m===d[d.length-1].value)break;if(d.length>v)return[]}var b=d.length?d[d.length-1].value:i[1];return o[1]>b&&d.push({value:n.expandToNicedExtent?En(b+r,l):o[1]}),d},t.prototype.getMinorTicks=function(n){return vS(this,n,R8(this),this._cfg.interval)},t.prototype.getLabel=function(n,a){if(n==null)return"";var r=a&&a.precision;r==null?r=bs(n.value)||0:r==="auto"&&(r=this._cfg.intervalPrecision);var o=En(n.value,r,!0);return gR(o)},t.type="interval",t}(hi);hi.registerClass(Ad);var QMe=function(e,t,n,a){for(;n>>1;e[r][1]16?16:e>7.5?7:e>3.5?4:e>1.5?2:1}function nEe(e){var t=30*To;return e/=t,e>6?6:e>3?3:e>2?2:1}function aEe(e){return e/=Hv,e>12?12:e>6?6:e>3.5?4:e>2?2:1}function GT(e,t){return e/=t?B8:N8,e>30?30:e>20?20:e>15?15:e>10?10:e>5?5:e>2?2:1}function rEe(e){return An(u8(e,!0),1)}function oEe(e,t,n){var a=Math.max(0,Pn(ac,t)-1);return J_(new Date(e),ac[a],n).getTime()}function iEe(e,t){var n=new Date(0);n[e](1);var a=n.getTime();n[e](1+t);var r=n.getTime()-a;return function(o,i){return Math.max(0,Math.round((i-o)/r))}}function lEe(e,t,n,a,r,o){var i=3e3,l=dke,s=0;function u(H,z,B,W,N,V,Y){for(var X=iEe(N,H),ee=z,re=new Date(ee);eei));)if(re[N](re[W]()+H),ee=re.getTime(),o){var ie=o.calcNiceTickMultiple(ee,X);ie>0&&(re[N](re[W]()+ie*H),ee=re.getTime())}Y.push({value:ee,notAdd:ee>a[1]})}function c(H,z,B){var W=[],N=!z.length;if(!eEe(Wv(H),a[0],a[1],n)){N&&(z=[{value:oEe(a[0],H,n)},{value:a[1]}]);for(var V=0;V=a[0]&&Y<=a[1]&&u(ee,Y,X,re,ie,le,W),H==="year"&&B.length>1&&V===0&&B.unshift({value:B[0].value-ee})}}for(var V=0;V=a[0]&&w<=a[1]&&f++)}var S=r/t;if(f>S*1.5&&h>S/1.5||(d.push(b),f>S||e===l[m]))break}v=[]}}}for(var k=ar(Xt(d,function(H){return ar(H,function(z){return z.value>=a[0]&&z.value<=a[1]&&!z.notAdd})}),function(H){return H.length>0}),T=k.length-1,M=[],m=0;ma[0])&&M.unshift({value:a[0],time:{level:0,upperTimeUnit:G,lowerTimeUnit:G},notNice:!0}),(!R||R.valuel&&(o=l);var s=O0.length,u=Math.min(QMe(O0,o,0,s),s-1),c=O0[u][1],d=O0[Math.max(u-1,0)][0];e.setTimeInterval({approxInterval:o,interval:c,minLevelUnit:d})};hi.registerClass(FN);var $0=0,R0=1,HN=function(e){At(t,e);function t(n){var a=e.call(this)||this;a.type="log",a.parse=Ad.parse,a.base=n.logBase||10;var r=[],o=[];a._lookup={from:r,to:o},r[$0]=r[R0]=o[$0]=o[R0]=NaN,$N(a,t.mapperMethods),n.breakOption;var i={};return a.powStub=new Ad({breakParsed:i.original}),a.intervalStub=new Ad({breakParsed:i.transformed}),RN(a,a.intervalStub),a}return t.prototype.getTicks=function(n){var a=this.base,r=this.powStub,o=this.intervalStub,i=o.getExtent(),l=r.getExtent(),s={lookup:{from:i,to:l}};return Xt(o.getTicks(n||{}),function(u){var c=u.value,d=Eb(c,a,s),v;return{value:d,break:v}},this)},t.prototype.getMinorTicks=function(n){return vS(this,n,R8(this.powStub),this.intervalStub.getConfig().interval)},t.prototype.getLabel=function(n,a){return this.intervalStub.getLabel(n,a)},t.type="log",t.mapperMethods={needTransform:function(){return!0},normalize:function(n){return this.intervalStub.normalize(D0(n,this.base))},scale:function(n){return Eb(this.intervalStub.scale(n),this.base,null)},transformIn:function(n,a){return n=D0(n,this.base),a&&a.depth===Gg?n:this.intervalStub.transformIn(n,a)},transformOut:function(n,a){var r=a?a.depth:null;return YT.depth=r,jT.lookup=this._lookup,Eb(r===Gg?n:this.intervalStub.transformOut(n,YT),this.base,jT)},contain:function(n){return this.powStub.contain(n)},setExtent:function(n,a){this.setExtent2(Mo,n,a)},setExtent2:function(n,a,r){if(!(!Jd(a,r)||a<=0||r<=0)){var o=qT,i=qT;if(n===Mo){var l=this._lookup;o=l.to,i=l.from}this.powStub.setExtent2(n,o[$0]=a,o[R0]=r);var s=this.base;this.intervalStub.setExtent2(n,i[$0]=D0(a,s),i[R0]=D0(r,s))}},getFilter:function(){return{g:0}},sanitize:function(n,a){return Jd(a[0],a[1])&&zo(n)&&n<=0&&(n=a[0]),n},getDefaultStartValue:function(){return 1},getExtent:function(){return this.powStub.getExtent()},getExtentUnsafe:function(n,a){return a===null?this.powStub.getExtentUnsafe(n,null):this.intervalStub.getExtentUnsafe(n,a)}},t}(hi);hi.registerClass(HN);var YT={},jT={},qT=[],WN={value:1,category:1,time:1,log:1},KN=Hn();function uEe(e){var t=e.get("type");return(t==null||!Sr(WN,t)&&!hi.getClass(t))&&(t="value"),t}function cEe(e,t,n){var a;switch(t){case"category":return new zN({ordinalMeta:e.getOrdinalMeta?e.getOrdinalMeta():e.getCategories(),extent:So()});case"time":return new FN({locale:e.ecModel.getLocaleModel(),useUTC:e.ecModel.get("useUTC"),breakOption:a});case"log":return new HN({logBase:e.get("logBase"),breakOption:a});case"value":return new Ad({breakOption:a});default:return new(hi.getClass(t)||Ad)({})}}function dEe(e,t,n){var a=e.getExtentUnsafe(Mo,null),r=a[0],o=a[1];return Jd(r,o)?r===t||o===t?vEe:rt?fEe:g3:g3}var fEe=1,vEe=2,g3=3;function hEe(e){KN(e).noOnMyZero=!0}function pEe(e){return KN(e).noOnMyZero}function C2(e){var t=e.getLabelModel().get("formatter");if(e.type==="time"){var n=fke(t);return function(r,o){return e.scale.getFormattedLabel(r,o,n)}}else{if(Ut(t))return function(r){var o=e.scale.getLabel(r),i=t.replace("{value}",o??"");return i};if(dn(t)){if(e.type==="category")return function(r,o){return t(qg(e,r),r.value-e.scale.getExtent()[0],null)};var a=m2();return function(r,o){var i=null;return a&&(i=a.makeAxisLabelFormatterParamBreak(i,r.break)),t(qg(e,r),o,i)}}else return function(r){return e.scale.getLabel(r)}}}function qg(e,t){var n=e.scale;return pi(n)?n.getLabel(t):t.value}function hS(e){var t=e.get("interval");return t??"auto"}function mEe(e){return e.type==="category"&&hS(e.getLabelModel())===0}function gEe(e,t){var n={};return we(e.mapDimensionsAll(t),function(a){n[VMe(e,a)]=!0}),la(n)}function of(e){return e==="middle"||e==="center"}function Bh(e){return e.getShallow("show")}function yEe(e,t,n){var a=e.get("breaks",!0);a==null}function UN(e,t,n,a,r,o){var i=rf(e),l=i?e.intervalStub:e;if(l.setExtent(a[0],a[1]),i){var s=e.powStub,u={depth:Gg},c=e.transformOut(a[0],u),d=e.transformOut(a[1],u),v=JMe(n,a);t[0]&&!v[0]&&(c=r[0]),t[1]&&!v[1]&&(d=r[1]),s.setExtent(c,d)}l.setConfig(o)}function Tp(e,t){return pi(e)?e.getRawOrdinalNumber(t.value):t.value}function GN(e,t){return pi(e)&&!!t.get("boundaryGap")}var bEe=function(){function e(){}return e.prototype.needIncludeZero=function(){return!this.option.scale},e.prototype.getCoordSysModel=function(){},e}(),_Ee=d$(),y3="|&",Cf=Hn(),YN=-2,wEe=-1,SEe=Hn();function jN(e,t){var n=e.model,a=Cf(wf(n.ecModel)).keyed,r=a&&a.get(t);return r&&r.get(n.uid)}function CEe(e,t){return XN(jN(e,t))}function xEe(e,t){var n=[];return qN(e.model.ecModel,function(a){for(var r=0;r0&&d[1]>0&&!v[0]&&(d[0]=0),d[0]<0&&d[1]<0&&!v[1]&&(d[1]=0));var w=!1;d[0]>d[1]&&(d.reverse(),w=!0);var S=Zf(t,n.get("startValue",!0)),k=S!=null;!zo(S)&&r&&(S=t.getDefaultStartValue?t.getDefaultStartValue():0),zo(S)&&(k||!b||_)&&(Sd[1]&&!v[1]&&(d[1]=S,v[1]=!0));var T=this._i={scale:t,dataMM:c,noZoomEffMM:d,zoomMM:[],fixMM:v,zoomFixMM:[!1,!1],startValue:S,isBlank:y,incl0:_,tggAxInv:w,ctnShp:o};ZT(T,d)}return e.prototype.makeNoZoom=function(){return this._i.noZoomEffMM.slice()},e.prototype.makeFinal=function(){var t=this._i,n=t.zoomMM,a=t.noZoomEffMM,r=t.zoomFixMM,o=t.fixMM,i={fixMM:o,zoomFixMM:r,isBlank:t.isBlank,incl0:t.incl0,tggAxInv:t.tggAxInv,ctnShp:t.ctnShp,effMM:a.slice()},l=i.effMM;return n[0]!=null&&(l[0]=n[0],o[0]=r[0]=!0),n[1]!=null&&(l[1]=n[1],o[1]=r[1]=!0),ZT(t,l),i},e.prototype.makeRenderInfo=function(){return{startValue:this._i.startValue}},e.prototype.setZoomMM=function(t,n){this._i.zoomMM[t]=n},e}();function ZT(e,t){var n=e.scale,a=e.dataMM;n.sanitize&&(t[0]=n.sanitize(t[0],a),t[1]=n.sanitize(t[1],a),gCe(t))}function Zf(e,t){return t==null?null:kh(t)?NaN:e.parse(t)}function DEe(e,t){var n;if(pi(e))n=[0,0];else{var a=t.get("boundaryGap");typeof a=="boolean"&&(a=null),n=Lt(a)?a:[a,a]}return[JT(n[0]),JT(n[1])]}function JT(e){return mc(typeof e=="boolean"?0:e,1)||0}function tB(e){var t=IEe(e.scale);return t.extent||(t.extent=So()),t}function OEe(e,t){tB(e).dimIdxInCoord=t.get(e.dim)}function $Ee(e,t){var n=e.scale,a=e.model,r=e.dim;n.rawExtentInfo||REe(n,e,r,a,t)}function REe(e,t,n,a,r){var o=tB(t),i=o.extent,l=!1;kEe(t,function(c){if(c.boxCoordinateSystem){var d=SR(c).coord,v=o.dimIdxInCoord;if(v>=0){if(Lt(d)){var f=d[v];f!=null&&!Lt(f)&&B_(i,e.parse(f))}}}else if(c.coordinateSystem){var h=c.getData();if(h){var m=e.getFilter?e.getFilter():null;we(gEe(h,n),function(g){pCe(i,h.getApproximateExtent(g,m))})}c.__requireStartValue&&c.__requireStartValue(t)&&(l=!0)}});var s=VEe(e,t,a),u=new eB(e,a,i,l,s);nB(e,u,r),o.extent=null}function NEe(e,t){var n=e.scale;nB(n,new eB(n,e.model,t,!1,!1),PEe)}function nB(e,t,n){e.rawExtentInfo=t,t.from=n}function BEe(e,t){mS.set(e,t)}var mS=an();function aB(e,t,n,a,r){e.rawExtentInfo||NEe({scale:e,model:t},So());var o=e.rawExtentInfo.makeFinal(),i=o.effMM;return e.setExtent(i[0],i[1]),e.setBlank(o.isBlank),a&&o.tggAxInv&&n&&!n.get("legacyMinMaxDontInverseAxis")&&(a.inverse=!a.inverse),o}function VEe(e,t,n){var a=GN(e,n),r=n.get("containShape",!0);if(r==null&&!a&&(r=!0),!r)return!1;var o=!1;return JN(t,function(i){o=!!mS.get(i)||o}),o}function zEe(e,t,n,a){if(n.ctnShp){var r;if(JN(e,function(l){var s=mS.get(l);if(s){var u=s(e,a);u&&(r=r||[0,0],u$(r,u[0]),c$(r,u[1]),hEe(e))}}),!!r){var o=t.getExtent();if(pi(t))e.onBand||t.setExtent2(Rh,sr(o[0],o[0]+r[0]),An(o[1],o[1]+r[1]));else{var i=o.slice();n.zoomFixMM[0]||(i[0]=sr(i[0],t.transformOut(t.transformIn(i[0],null)+r[0],null))),n.zoomFixMM[1]||(i[1]=An(i[1],t.transformOut(t.transformIn(i[1],null)+r[1],null))),(i[0]o[1])&&t.setExtent2(Rh,i[0],i[1])}}}}function QT(e,t){var n=rf(e),a=n?e.intervalStub:e,r=t.fixMinMax||[],o=n?e.getExtent():null,i=a.getExtent(),l=BN(i,r,t.rawExtentResult);a.setExtent(l[0],l[1]),l=a.getExtent();var s=n?HEe(a,t):FEe(a,t),u=s.intervalPrecision,c=s.interval,d=t.userInterval;d!=null&&(s.interval=d,s.intervalPrecision=Sc(d)),r[0]||(l[0]=En(gc(l[0]/c)*c,u)),r[1]||(l[1]=En(yp(l[1]/c)*c,u)),d!=null&&(s.niceExtent=l.slice()),UN(e,r,i,l,o,s)}function FEe(e,t){var n=fS(t.splitNumber,5),a=S2(e),r=t.minInterval,o=t.maxInterval,i=u8(a/n,!0);r!=null&&io&&(i=o);var l=Sc(i),s=e.getExtent(),u=[En(yp(s[0]/i)*i,l),En(gc(s[1]/i)*i,l)];return{interval:i,intervalPrecision:l,niceExtent:u}}function HEe(e,t){var n=fS(t.splitNumber,10),a=e.getExtent(),r=S2(e),o=An(n$(r),1),i=n/r*o;i<=.5&&(o*=10);var l=Sc(o),s=[En(yp(a[0]/o)*o,l),En(gc(a[1]/o)*o,l)];return{intervalPrecision:l,interval:o,niceExtent:s}}function eM(e){var t=e.scale,n=e.model,a=n.axis,r=n.ecModel;WEe(t,n,a,r)}function WEe(e,t,n,a,r){var o=aB(e,t,a,n),i=jg(e)||dS(e);KEe(e,{splitNumber:t.get("splitNumber"),fixMinMax:o.fixMM,userInterval:t.get("interval"),minInterval:i?t.get("minInterval"):null,maxInterval:i?t.get("maxInterval"):null,rawExtentResult:o}),n&&a&&zEe(n,e,o,a)}function KEe(e,t){UEe[e.type](e,t)}var UEe={interval:QT,log:QT,time:sEe,ordinal:lr},tM=[],GEe={registerPreprocessor:kN,registerProcessor:TN,registerPostInit:pMe,registerPostUpdate:mMe,registerUpdateLifecycle:oS,registerAction:Sf,registerCoordinateSystem:gMe,registerLayout:yMe,registerVisual:Lc,registerTransform:_Me,registerLoading:MN,registerMap:bMe,registerImpl:bTe,PRIORITY:cMe,ComponentModel:Kn,ComponentView:el,SeriesModel:ai,ChartView:Oo,registerComponentModel:function(e){Kn.registerClass(e)},registerComponentView:function(e){el.registerClass(e)},registerSeriesModel:function(e){ai.registerClass(e)},registerChartView:function(e){Oo.registerClass(e)},registerCustomSeries:function(e,t){},registerSubTypeDefaulter:function(e,t){Kn.registerSubTypeDefaulter(e,t)},registerPainter:function(e,t){BSe(e,t)}};function Ws(e){if(Lt(e)){we(e,function(t){Ws(t)});return}Pn(tM,e)>=0||(tM.push(e),dn(e)&&(e={install:e}),e.install(GEe))}var YEe=Hn(),Yv=Hn(),ri={estimate:1,determine:2};function Xg(e){return{out:{noPxChangeTryDetermine:[]},kind:e}}function jEe(e,t){var n=e.getLabelModel().get("customValues");if(n){var a=e.scale;return{labels:Xt(rB(n,a),function(r,o){return{formattedLabel:C2(e)(r,o),rawLabel:a.getLabel(r),tick:r}})}}return e.type==="category"?XEe(e,t):JEe(e)}function qEe(e,t,n){var a=e.scale,r=e.getTickModel().get("customValues");return r?{ticks:rB(r,a)}:e.type==="category"?ZEe(e,t):{ticks:a.getTicks(n)}}function rB(e,t){var n=t.getExtent(),a=[];return we(e,function(r){r=t.parse(r),r>=n[0]&&r<=n[1]&&a.push(r)}),v8(a,_Ce,null),l8(a),Xt(a,function(r){return{value:r}})}function XEe(e,t){var n=e.getLabelModel(),a=oB(e,n,t);return!n.get("show")||e.scale.isBlank()?{labels:[]}:a}function oB(e,t,n){var a=e7e(e),r=hS(t),o=n.kind===ri.estimate;if(!o){var i=lB(a,r);if(i)return i}var l,s;dn(r)?l=Zg(e,r,!1):(s=r==="auto"?t7e(e,n):r,l=Zg(e,s,!1));var u={labels:l,labelCategoryInterval:s};return o?n.out.noPxChangeTryDetermine.push(function(){return w3(a,r,u),!0}):w3(a,r,u),u}function ZEe(e,t){var n=QEe(e),a=hS(t),r=lB(n,a);if(r)return r;var o,i;if((!t.get("show")||e.scale.isBlank())&&(o=[]),dn(a))o=Zg(e,a,!0);else if(a==="auto"){var l=oB(e,e.getLabelModel(),Xg(ri.determine));i=l.labelCategoryInterval,o=Xt(l.labels,function(s){return s.tick})}else i=a,o=Zg(e,i,!0);return w3(n,a,{ticks:o,tickCategoryInterval:i})}function JEe(e){var t=e.scale.getTicks(),n=C2(e);return{labels:Xt(t,function(a,r){return{formattedLabel:n(a,r),rawLabel:e.scale.getLabel(a),tick:a}})}}var QEe=iB("axisTick"),e7e=iB("axisLabel");function iB(e){return function(n){return Yv(n)[e]||(Yv(n)[e]={list:[]})}}function lB(e,t){for(var n=0;nc&&(u=Math.max(1,Math.floor(s/c)));for(var d=l[0],v=e.dataToCoord(d+1)-e.dataToCoord(d),f=Math.abs(v*Math.cos(o)),h=Math.abs(v*Math.sin(o)),m=0,g=0;d<=l[1];d+=u){var y=0,b=0,_=ZO(r({value:d}),a.font,"center","top");y=_.width*1.3,b=_.height*1.3,m=Math.max(m,y,7),g=Math.max(g,b,7)}var w=m/f,S=g/h;isNaN(w)&&(w=1/0),isNaN(S)&&(S=1/0);var k=Math.max(0,Math.floor(Math.min(w,S)));if(n===ri.estimate)return t.out.noPxChangeTryDetermine.push(qn(a7e,null,e,k,s)),k;var T=sB(e,k,s);return T??k}function a7e(e,t,n){return sB(e,t,n)==null}function sB(e,t,n){var a=YEe(e.model),r=e.getExtent(),o=a.lastAutoInterval,i=a.lastTickCount;if(o!=null&&i!=null&&Math.abs(o-t)<=1&&Math.abs(i-n)<=1&&o>t&&a.axisExtent0===r[0]&&a.axisExtent1===r[1])return o;a.lastTickCount=n,a.lastAutoInterval=t,a.axisExtent0=r[0],a.axisExtent1=r[1]}function r7e(e){var t=e.getLabelModel();return{axisRotate:e.getRotate?e.getRotate():e.isHorizontal&&!e.isHorizontal()?90:0,labelRotate:t.get("rotate")||0,font:t.getFont()}}function Zg(e,t,n){var a=C2(e),r=e.scale,o=[],i=dn(t);return VN(r,i?0:t,function(l,s){var u=r.getLabel(l);if(i){var c=!!t(l.value,u);if(l.offInterval=!c,!c&&!s)return}o.push(n?l:{formattedLabel:a(l),rawLabel:u,tick:l})}),o}var o7e=.8;function xf(e,t){t=t||{};var n={w:NaN,w2:NaN},a=e.scale,r=t.fromStat,o=t.min,i=jMe(a);zo(i)||(i=NaN);var l=e.getExtent(),s=Ca(l[1]-l[0]);return pi(a)?i7e(n,e,i,s):r&&l7e(n,e,i,s,r),o!=null&&(n.w=zo(n.w)?An(o,n.w):o),n}function i7e(e,t,n,a){var r=t.onBand,o=n+(r?1:0);o===0&&(o=1),e.w=a/o,!r&&n&&a&&(e.w2=e.w*n/a)}function l7e(e,t,n,a,r){var o=!1,i=-1/0;we(r.key?[CEe(t,r.key)]:xEe(t,r.sers||[]),function(l){var s=l.liPosMinGap;s!=null&&(s>0?(s>i&&(i=s),o=!1):s===YN&&(o=!0))}),zo(n)&&n>0&&zo(i)?(e.w=a/n*i,e.w2=i):o&&(e.w=a*o7e,e.w2=e.w*n/a)}var nM=[0,1],s7e=function(){function e(t,n,a){this.onBand=!1,this.inverse=!1,this.dim=t,this.scale=n,this._extent=a||[0,0]}return e.prototype.contain=function(t){var n=this._extent,a=Math.min(n[0],n[1]),r=Math.max(n[0],n[1]);return t>=a&&t<=r},e.prototype.containData=function(t){return this.scale.contain(this.scale.parse(t))},e.prototype.getExtent=function(){return this._extent.slice()},e.prototype.setExtent=function(t,n){var a=this._extent;a[0]=t,a[1]=n},e.prototype.dataToCoord=function(t,n){var a=this.scale;return t=a.normalize(a.parse(t)),$_(t,nM,aM(this),n)},e.prototype.coordToData=function(t,n){var a=$_(t,aM(this),nM,n);return this.scale.scale(a)},e.prototype.pointToData=function(t,n){},e.prototype.getTicksCoords=function(t){t=t||{};var n=t.tickModel||this.getTickModel(),a=qEe(this,n,{breakTicks:t.breakTicks,pruneByBreak:t.pruneByBreak}),r=Xt(a.ticks,function(l){return{coord:this.dataToCoord(Tp(this.scale,l)),tick:l}},this),o=n.get("alignWithLabel"),i=u7e(this,r,o);return Xt(r,function(l){return{coord:l.coord,tickValue:l.tick.value,onBand:i}})},e.prototype.getMinorTicksCoords=function(){if(pi(this.scale))return[];var t=this.model.getModel("minorTick"),n=t.get("splitNumber");n>0&&n<100||(n=5);var a=this.scale.getMinorTicks(n),r=Xt(a,function(o){return Xt(o,function(i){return{coord:this.dataToCoord(i),tickValue:i}},this)},this);return r},e.prototype.getViewLabels=function(t){return t=t||Xg(ri.determine),jEe(this,t).labels},e.prototype.getLabelModel=function(){return this.model.getModel("axisLabel")},e.prototype.getTickModel=function(){return this.model.getModel("axisTick")},e.prototype.getBandWidth=function(){return xf(this,{min:1}).w},e.prototype.calculateCategoryInterval=function(t){return t=t||Xg(ri.determine),n7e(this,t)},e}();function aM(e){var t=e.getExtent();if(e.onBand){var n=t[1]-t[0],a=n/e.scale.count()/2;t[0]+=a,t[1]-=a}return t}function u7e(e,t,n){var a=t.length;if(!e.onBand||n||!a)return!1;var r=xf(e).w;if(!r)return!1;we(t,function(l){l.coord-=r/2});var o=e.scale.getExtent(),i=t[a-1];return i.tick.offInterval&&t.pop(),t.push({coord:i.coord+r,tick:{value:o[1]+1}}),!0}function uB(e,t,n,a,r,o,i,l){var s=r-e,u=o-t,c=n-e,d=a-t,v=Math.sqrt(c*c+d*d);c/=v,d/=v;var f=s*c+u*d,h=f/v;h*=v;var m=i[0]=e+h*c,g=i[1]=t+h*d;return Math.sqrt((m-r)*(m-r)+(g-o)*(g-o))}var _s=new fn,ha=new fn,Ka=new fn,ws=new fn,Ai=new fn,Jg=[],yr=new fn;function c7e(e,t){if(t<=180&&t>0){t=t/180*Math.PI,_s.fromArray(e[0]),ha.fromArray(e[1]),Ka.fromArray(e[2]),fn.sub(ws,_s,ha),fn.sub(Ai,Ka,ha);var n=ws.len(),a=Ai.len();if(!(n<.001||a<.001)){ws.scale(1/n),Ai.scale(1/a);var r=ws.dot(Ai),o=Math.cos(t);if(o1&&fn.copy(yr,Ka),yr.toArray(e[1])}}}}function d7e(e,t,n){if(n<=180&&n>0){n=n/180*Math.PI,_s.fromArray(e[0]),ha.fromArray(e[1]),Ka.fromArray(e[2]),fn.sub(ws,ha,_s),fn.sub(Ai,Ka,ha);var a=ws.len(),r=Ai.len();if(!(a<.001||r<.001)){ws.scale(1/a),Ai.scale(1/r);var o=ws.dot(t),i=Math.cos(n);if(o=s)fn.copy(yr,Ka);else{yr.scaleAndAdd(Ai,l/Math.tan(Math.PI/2-c));var d=Ka.x!==ha.x?(yr.x-ha.x)/(Ka.x-ha.x):(yr.y-ha.y)/(Ka.y-ha.y);if(isNaN(d))return;d<0?fn.copy(yr,ha):d>1&&fn.copy(yr,Ka)}yr.toArray(e[1])}}}}function Ab(e,t,n,a){var r=n==="normal",o=r?e:e.ensureState(n);o.ignore=t;var i=a.get("smooth");i=i===!0?.3:Math.max(+i,0)||0,o.shape=o.shape||{},o.shape.smooth=i;var l=a.getModel("lineStyle").getLineStyle();r?e.useStyle(l):o.style=l}function f7e(e,t){var n=t.smooth,a=t.points;if(a)if(e.moveTo(a[0][0],a[0][1]),n>0&&a.length>=3){var r=p_(a[0],a[1]),o=p_(a[1],a[2]);if(!r||!o){e.lineTo(a[1][0],a[1][1]),e.lineTo(a[2][0],a[2][1]);return}var i=Math.min(r,o)*n,l=wy([],a[1],a[0],i/r),s=wy([],a[1],a[2],i/o),u=wy([],l,s,.5);e.bezierCurveTo(l[0],l[1],l[0],l[1],u[0],u[1]),e.bezierCurveTo(s[0],s[1],s[0],s[1],a[2][0],a[2][1])}else for(var c=1;c0){w(A*E,0,o);var I=A+T;I<0&&S(-I*E,1)}else S(-T*E,1)}}function w(T,M,E){T!==0&&(c=!0);for(var A=M;A0)for(var I=0;I0;I--){var K=E[I-1]*G;w(-K,I,o)}}}function k(T){var M=T<0?-1:1;T=Math.abs(T);for(var E=Math.ceil(T/(o-1)),A=0;A0?w(E,0,A+1):w(-E,o-A-1,o),T-=E,T<=0)return}return c}function b7e(e){var t=[];e.sort(function(u,c){return(c.suggestIgnore?1:0)-(u.suggestIgnore?1:0)||c.priority-u.priority});function n(u){if(!u.ignore){var c=u.ensureState("emphasis");c.ignore==null&&(c.ignore=!1)}u.ignore=!0}for(var a=0;a-1&&(u.style.stroke=u.style.fill,u.style.fill=tn.color.neutral00,u.style.lineWidth=2),a},t.type="series.line",t.dependencies=["grid","polar"],t.defaultOption={z:3,coordinateSystem:"cartesian2d",legendHoverLink:!0,clip:!0,label:{position:"top"},endLabel:{show:!1,valueAnimation:!0,distance:8},lineStyle:{width:2,type:"solid"},emphasis:{scale:!0},step:!1,smooth:!1,smoothMonotone:null,symbol:"emptyCircle",symbolSize:6,symbolRotate:null,showSymbol:!0,showAllSymbol:"auto",connectNulls:!1,sampling:"none",animationEasing:"linear",progressive:0,hoverLayerThreshold:1/0,universalTransition:{divideShape:"clone"},triggerLineEvent:!1,triggerEvent:!1},t}(ai);function yS(e,t){var n=e.mapDimensionsAll("defaultedLabel"),a=n.length;if(a===1){var r=tf(e,t,n[0]);return r!=null?r+"":null}else if(a){for(var o=[],i=0;i=0&&a.push(t[o])}return a.join(" ")}var bS=function(e){At(t,e);function t(n,a,r,o){var i=e.call(this)||this;return i.updateData(n,a,r,o),i}return t.prototype._createSymbol=function(n,a,r,o,i,l){this.removeAll();var s=nf(n,-1,-1,2,2,null,l);s.attr({z2:jt(i,100),culling:!0,scaleX:o[0]/2,scaleY:o[1]/2}),s.drift=w7e,this._symbolType=n,this.add(s)},t.prototype.stopSymbolAnimation=function(n){this.childAt(0).stopAnimation(null,n)},t.prototype.getSymbolType=function(){return this._symbolType},t.prototype.getSymbolPath=function(){return this.childAt(0)},t.prototype.highlight=function(){Dg(this.childAt(0))},t.prototype.downplay=function(){Og(this.childAt(0))},t.prototype.setZ=function(n,a){var r=this.childAt(0);r.zlevel=n,r.z=a},t.prototype.setDraggable=function(n,a){var r=this.childAt(0);r.draggable=n,r.cursor=!a&&n?"move":r.cursor},t.prototype.updateData=function(n,a,r,o){this.silent=!1;var i=n.getItemVisual(a,"symbol")||"circle",l=n.hostModel,s=t.getSymbolSize(n,a),u=t.getSymbolZ2(n,a),c=i!==this._symbolType,d=o&&o.disableAnimation;if(c){var v=n.getItemVisual(a,"symbolKeepAspect");this._createSymbol(i,n,a,s,u,v)}else{var f=this.childAt(0);f.silent=!1;var h={scaleX:s[0]/2,scaleY:s[1]/2};d?f.attr(h):Ar(f,h,l,a),M8(f)}if(this._updateCommon(n,a,s,r,o),c){var f=this.childAt(0);if(!d){var h={scaleX:this._sizeX,scaleY:this._sizeY,style:{opacity:f.style.opacity}};f.scaleX=f.scaleY=0,f.style.opacity=0,Do(f,h,l,a)}}d&&this.childAt(0).stopAnimation("leave")},t.prototype._updateCommon=function(n,a,r,o,i){var l=this.childAt(0),s=n.hostModel,u,c,d,v,f,h,m,g,y;if(o&&(u=o.emphasisItemStyle,c=o.blurItemStyle,d=o.selectItemStyle,v=o.focus,f=o.blurScope,m=o.labelStatesModels,g=o.hoverScale,y=o.cursorStyle,h=o.emphasisDisabled),!o||n.hasItemOption){var b=o&&o.itemModel?o.itemModel:n.getItemModel(a),_=b.getModel("emphasis");u=_.getModel("itemStyle").getItemStyle(),d=b.getModel(["select","itemStyle"]).getItemStyle(),c=b.getModel(["blur","itemStyle"]).getItemStyle(),v=_.get("focus"),f=_.get("blurScope"),h=_.get("disabled"),m=xp(b),g=_.getShallow("scale"),y=b.getShallow("cursor")}var w=n.getItemVisual(a,"symbolRotate");l.attr("rotation",(w||0)*Math.PI/180||0);var S=oN(n.getItemVisual(a,"symbolOffset"),r);S&&(l.x=S[0],l.y=S[1]),y&&l.attr("cursor",y);var k=n.getItemVisual(a,"style"),T=k.fill;if(l instanceof js){var M=l.style;l.useStyle(vt({image:M.image,x:M.x,y:M.y,width:M.width,height:M.height},k))}else l.__isEmptyBrush?l.useStyle(vt({},k)):l.useStyle(k),l.style.decal=null,l.setColor(T,i&&i.symbolInnerColor),l.style.strokeNoScale=!0;var E=n.getItemVisual(a,"liftZ"),A=this._z2;E!=null?A==null&&(this._z2=l.z2,l.z2+=E):A!=null&&(l.z2=A,this._z2=null);var I=i&&i.useNameLabel;Cp(l,m,{labelFetcher:s,labelDataIndex:a,defaultText:P,inheritColor:T,defaultOpacity:k.opacity});function P(K){return I?n.getName(K):yS(n,K)}this._sizeX=r[0]/2,this._sizeY=r[1]/2;var R=l.ensureState("emphasis");R.style=u,l.ensureState("select").style=d,l.ensureState("blur").style=c;var G=g==null||g===!0?Math.max(1.1,3/this._sizeY):isFinite(g)&&g>0?+g:1;R.scaleX=this._sizeX*G,R.scaleY=this._sizeY*G,this.setSymbolScale(1),Ah(this,v,f,h)},t.prototype.setSymbolScale=function(n){this.scaleX=this.scaleY=n},t.prototype.fadeOut=function(n,a,r){var o=this.childAt(0),i=In(this).dataIndex,l=r&&r.animation;if(this.silent=o.silent=!0,r&&r.fadeLabel){var s=o.getTextContent();s&&Rg(s,{style:{opacity:0}},a,{dataIndex:i,removeOpt:l,cb:function(){o.removeTextContent()}})}else o.removeTextContent();Rg(o,{style:{opacity:0},scaleX:0,scaleY:0},a,{dataIndex:i,cb:n,removeOpt:l})},t.getSymbolSize=function(n,a){return $Te(n.getItemVisual(a,"symbolSize"))},t.getSymbolZ2=function(n,a){return n.getItemVisual(a,"z2")},t}(Ha);function w7e(e,t){this.parent.drift(e,t)}function N0(e,t,n,a){return t&&!isNaN(t[0])&&!isNaN(t[1])&&!(a&&a.isIgnore&&a.isIgnore(n))&&!(a&&a.clipShape&&!a.clipShape.contain(t[0],t[1]))&&e.getItemVisual(n,"symbol")!=="none"}function lM(e){return e!=null&&!Yt(e)&&(e={isIgnore:e}),e||{}}function sM(e){var t=e.hostModel,n=t.getModel("emphasis");return{emphasisItemStyle:n.getModel("itemStyle").getItemStyle(),blurItemStyle:t.getModel(["blur","itemStyle"]).getItemStyle(),selectItemStyle:t.getModel(["select","itemStyle"]).getItemStyle(),focus:n.get("focus"),blurScope:n.get("blurScope"),emphasisDisabled:n.get("disabled"),hoverScale:n.get("scale"),labelStatesModels:xp(t),cursorStyle:t.get("cursor")}}function uM(e,t,n,a,r,o,i){var l=new e(t,n,a,r);return l.setPosition(o),t.setItemGraphicEl(n,l),i.add(l),l}var S7e=function(){function e(t){this.group=new Ha,this._SymbolCtor=t||bS}return e.prototype.updateData=function(t,n){this._progressiveEls=null,n=lM(n);var a=this.group,r=t.hostModel,o=this._data,i=this._SymbolCtor,l=n.disableAnimation,s=this._seriesScope=sM(t),u={disableAnimation:l},c=n.getSymbolPoint||function(d){return t.getItemLayout(d)};o||a.removeAll(),t.diff(o).add(function(d){var v=c(d);N0(t,v,d,n)&&uM(i,t,d,s,u,v,a)}).update(function(d,v){var f=o.getItemGraphicEl(v),h=c(d);if(!N0(t,h,d,n)){a.remove(f);return}var m=t.getItemVisual(d,"symbol")||"circle",g=f&&f.getSymbolType&&f.getSymbolType();if(!f||g&&g!==m)a.remove(f),f=new i(t,d,s,u),f.setPosition(h);else{f.updateData(t,d,s,u);var y={x:h[0],y:h[1]};l?f.attr(y):Ar(f,y,r)}a.add(f),t.setItemGraphicEl(d,f)}).remove(function(d){var v=o.getItemGraphicEl(d);v&&v.fadeOut(function(){a.remove(v)},r)}).execute(),this._getSymbolPoint=c,this._data=t},e.prototype.updateLayout=function(t){var n=this._data;if(n)for(var a=this,r=n.getStore(),o=0,i=r.count();o0?n=a[0]:a[1]<0&&(n=a[1]),n}function pB(e,t,n,a){var r=NaN;e.stacked&&(r=n.get(n.getCalculationInfo("stackedOverDimension"),a)),isNaN(r)&&(r=e.valueStart);var o=e.baseDataOffset,i=[];return i[o]=n.get(e.baseDim,a),i[1-o]=r,t.dataToPoint(i)}function $o(e,t){return!isFinite(e)||!isFinite(t)}var x7e=typeof Float32Array!==_f?Float32Array:void 0,k7e=typeof Float64Array!==_f?Float64Array:void 0;function gl(e){return _S({ctor:x7e},e).arr}function _S(e,t){var n=e.arr,a=e.ctor;if(t>Wk&&(t=Wk),!n||e.typed&&n.length=r||m<0)break;if($o(y,b)){if(s){m+=o;continue}break}if(m===n)e[o>0?"moveTo":"lineTo"](y,b),d=y,v=b;else{var _=y-u,w=b-c;if(_*_+w*w<.5){m+=o;continue}if(i>0){for(var S=m+o,k=t[S*2],T=t[S*2+1];k===y&&T===b&&g=a||$o(k,T))f=y,h=b;else{A=k-u,I=T-c;var G=y-u,K=k-y,H=b-c,z=T-b,B=void 0,W=void 0;if(l==="x"){B=Math.abs(G),W=Math.abs(K);var N=A>0?1:-1;f=y-N*B*i,h=b,P=y+N*W*i,R=b}else if(l==="y"){B=Math.abs(H),W=Math.abs(z);var V=I>0?1:-1;f=y,h=b-V*B*i,P=y,R=b+V*W*i}else B=Math.sqrt(G*G+H*H),W=Math.sqrt(K*K+z*z),E=W/(W+B),f=y-A*i*(1-E),h=b-I*i*(1-E),P=y+A*i*E,R=b+I*i*E,P=es(P,ts(k,y)),R=es(R,ts(T,b)),P=ts(P,es(k,y)),R=ts(R,es(T,b)),A=P-y,I=R-b,f=y-A*B/W,h=b-I*B/W,f=es(f,ts(u,y)),h=es(h,ts(c,b)),f=ts(f,es(u,y)),h=ts(h,es(c,b)),A=y-f,I=b-h,P=y+A*W/B,R=b+I*W/B}e.bezierCurveTo(d,v,f,h,y,b),d=P,v=R}else e.lineTo(y,b)}u=y,c=b,m+=o}return g}var mB=function(){function e(){this.smooth=0,this.smoothConstraint=!0}return e}(),E7e=function(e){At(t,e);function t(n){var a=e.call(this,n)||this;return a.type="ec-polyline",a}return t.prototype.getDefaultStyle=function(){return{stroke:tn.color.neutral99,fill:null}},t.prototype.getDefaultShape=function(){return new mB},t.prototype.buildPath=function(n,a){var r=a.points,o=0,i=r.length/2;if(a.connectNulls){for(;i>0&&$o(r[i*2-2],r[i*2-1]);i--);for(;o=0){var w=u?(h-s)*_+s:(f-l)*_+l;return u?[n,w]:[w,n]}l=f,s=h;break;case i.C:f=o[d++],h=o[d++],m=o[d++],g=o[d++],y=o[d++],b=o[d++];var S=u?_g(l,f,m,y,n,c):_g(s,h,g,b,n,c);if(S>0)for(var k=0;k=0){var w=u?Ua(s,h,g,b,T):Ua(l,f,m,y,T);return u?[n,w]:[w,n]}}l=y,s=b;break}}},t}(Fn),A7e=function(e){At(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t}(mB),I7e=function(e){At(t,e);function t(n){var a=e.call(this,n)||this;return a.type="ec-polygon",a}return t.prototype.getDefaultShape=function(){return new A7e},t.prototype.buildPath=function(n,a){var r=a.points,o=a.stackedOnPoints,i=0,l=r.length/2,s=a.smoothMonotone;if(a.connectNulls){for(;l>0&&$o(r[l*2-2],r[l*2-1]);l--);for(;it){o?n.push(i(o,s,t)):r&&n.push(i(r,s,0),i(r,s,t));break}else r&&(n.push(i(r,s,0)),r=null),n.push(s),o=s}return n}function O7e(e,t,n){var a=e.getVisual("visualMeta");if(!(!a||!a.length||!e.count())&&t.type==="cartesian2d"){for(var r,o,i=a.length-1;i>=0;i--){var l=e.getDimensionInfo(a[i].dimension);if(r=l&&l.coordDim,r==="x"||r==="y"){o=a[i];break}}if(o){var s=t.getAxis(r),u=Xt(o.stops,function(_){return{coord:s.toGlobalCoord(s.dataToCoord(_.value)),color:_.color}}),c=u.length,d=o.outerColors.slice();c&&u[0].coord>u[c-1].coord&&(u.reverse(),d.reverse());var v=D7e(u,r==="x"?n.getWidth():n.getHeight()),f=v.length;if(!f&&c)return u[0].coord<0?d[1]?d[1]:u[c-1].color:d[0]?d[0]:u[0].color;var h=10,m=v[0].coord-h,g=v[f-1].coord+h,y=g-m;if(y<.001)return"transparent";we(v,function(_){_.offset=(_.coord-m)/y}),v.push({offset:f?v[f-1].offset:.5,color:d[1]||"transparent"}),v.unshift({offset:f?v[0].offset:.5,color:d[0]||"transparent"});var b=new U$(0,0,0,0,v,!0);return b[r]=m,b[r+"2"]=g,b}}}function $7e(e,t,n){var a=e.get("showAllSymbol"),r=a==="auto";if(!(a&&!r)){var o=n.getAxesByScale("ordinal")[0];if(o&&!(r&&R7e(o,t))){var i=t.mapDimension(o.dim),l={};return we(o.getViewLabels(),function(s){s.tick.offInterval||(l[Tp(o.scale,s.tick)]=1)}),function(s){return!l.hasOwnProperty(t.get(i,s))}}}}function R7e(e,t){var n=e.getExtent(),a=Math.abs(n[1]-n[0])/e.scale.count();isNaN(a)&&(a=0);for(var r=t.count(),o=Math.max(1,Math.round(r/5)),i=0;ia)return!1;return!0}function N7e(e){for(var t=e.length/2;t>0&&$o(e[t*2-2],e[t*2-1]);t--);return t-1}function hM(e,t){return[e[t*2],e[t*2+1]]}function B7e(e,t,n){for(var a=e.length/2,r=n==="x"?0:1,o,i,l=0,s=-1,u=0;u=t||o>=t&&i<=t){s=u;break}l=u,o=i}return{range:[l,s],t:(t-o)/(i-o)}}function _B(e){if(e.get(["endLabel","show"]))return!0;for(var t=0;t0&&n.get(["emphasis","lineStyle","width"])==="bolder"){var W=h.getState("emphasis").style;W.lineWidth=+h.style.lineWidth+1}In(h).seriesIndex=n.seriesIndex,Ah(h,H,z,B);var N=vM(n.get("smooth")),V=n.get("smoothMonotone");if(h.setShape({smooth:N,smoothMonotone:V,connectNulls:T}),m){var Y=l.getCalculationInfo("stackedOnSeries"),X=0;m.useStyle(zn(u.getAreaStyle(),{fill:P,opacity:.7,lineJoin:"bevel",decal:l.getVisual("style").decal})),Y&&(X=vM(Y.get("smooth"))),m.setShape({smooth:N,stackedOnSmooth:X,smoothMonotone:V,connectNulls:T}),$g(m,n,"areaStyle"),In(m).seriesIndex=n.seriesIndex,Ah(m,H,z,B)}var ee=this._changePolyState;l.eachItemGraphicEl(function(he){he&&(he.onHoverStateChange=ee)}),this._polyline.onHoverStateChange=ee,this._data=l,this._coordSys=o,this._stackedOnPoints=S,this._points=c,this._step=A,this._valueOrigin=_;var re=n.get("triggerEvent"),ie=n.get("triggerLineEvent"),le=ie===!0||re===!0||re==="line",ve=ie===!0||re===!0||re==="area";this.packEventData(n,h,le),m&&this.packEventData(n,m,ve)},t.prototype.packEventData=function(n,a,r){In(a).eventData=r?{componentType:"series",componentSubType:"line",componentIndex:n.componentIndex,seriesIndex:n.seriesIndex,seriesName:n.name,seriesType:"line",selfType:a===this._polygon?"area":"line"}:null},t.prototype.highlight=function(n,a,r,o){var i=n.getData(),l=yc(i,o);if(this._changePolyState("emphasis"),!(l instanceof Array)&&l!=null&&l>=0){var s=i.getLayout("points"),u=i.getItemGraphicEl(l);if(!u){var c=s[l*2],d=s[l*2+1];if($o(c,d)||this._clipShapeForSymbol&&!this._clipShapeForSymbol.contain(c,d))return;var v=n.get("zlevel")||0,f=n.get("z")||0;u=new bS(i,l),u.x=c,u.y=d,u.setZ(v,f);var h=u.getSymbolPath().getTextContent();h&&(h.zlevel=v,h.z=f,h.z2=this._polyline.z2+1),u.__temp=!0,i.setItemGraphicEl(l,u),u.stopSymbolAnimation(!0),this.group.add(u)}u.highlight()}else Oo.prototype.highlight.call(this,n,a,r,o)},t.prototype.downplay=function(n,a,r,o){var i=n.getData(),l=yc(i,o);if(this._changePolyState("normal"),l!=null&&l>=0){var s=i.getItemGraphicEl(l);s&&(s.__temp?(i.setItemGraphicEl(l,null),this.group.remove(s)):s.downplay())}else Oo.prototype.downplay.call(this,n,a,r,o)},t.prototype._changePolyState=function(n){var a=this._polygon;p9(this._polyline,n),a&&p9(a,n)},t.prototype._newPolyline=function(n){var a=this._polyline;return a&&this._lineGroup.remove(a),a=new E7e({shape:{points:n},segmentIgnoreThreshold:2,z2:10}),this._lineGroup.add(a),this._polyline=a,a},t.prototype._newPolygon=function(n,a){var r=this._polygon;return r&&this._lineGroup.remove(r),r=new I7e({shape:{points:n,stackedOnPoints:a},segmentIgnoreThreshold:2}),this._lineGroup.add(r),this._polygon=r,r},t.prototype._initSymbolLabelAnimation=function(n,a,r){var o,i,l=a.getBaseAxis(),s=l.inverse;a.type==="cartesian2d"?(o=l.isHorizontal(),i=!1):a.type==="polar"&&(o=l.dim==="angle",i=!0);var u=n.hostModel,c=u.get("animationDuration");dn(c)&&(c=c(null));var d=u.get("animationDelay")||0,v=dn(d)?d(null):d;n.eachItemGraphicEl(function(f,h){var m=f;if(m){var g=[f.x,f.y],y=void 0,b=void 0,_=void 0;if(r)if(i){var w=r,S=a.pointToCoord(g);o?(y=w.startAngle,b=w.endAngle,_=-S[1]/180*Math.PI):(y=w.r0,b=w.r,_=S[0])}else{var k=r;o?(y=k.x,b=k.x+k.width,_=f.x):(y=k.y+k.height,b=k.y,_=f.y)}var T=b===y?0:(_-y)/(b-y);s&&(T=1-T);var M=dn(d)?d(h):c*T+v,E=m.getSymbolPath(),A=E.getTextContent();m.attr({scaleX:0,scaleY:0}),m.animateTo({scaleX:1,scaleY:1},{duration:200,setToFinal:!0,delay:M}),A&&A.animateFrom({style:{opacity:0}},{duration:300,delay:M}),E.disableLabelAnimation=!0}})},t.prototype._initOrUpdateEndLabel=function(n,a,r){var o=n.getModel("endLabel");if(_B(n)){var i=n.getData(),l=this._polyline,s=i.getLayout("points");if(!s){l.removeTextContent(),this._endLabel=null;return}var u=this._endLabel;u||(u=this._endLabel=new Er({z2:200}),u.ignoreClip=!0,l.setTextContent(this._endLabel),l.disableLabelAnimation=!0);var c=N7e(s);c>=0&&(Cp(l,xp(n,"endLabel"),{inheritColor:r,labelFetcher:n,labelDataIndex:c,defaultText:function(d,v,f){return f!=null?vB(i,f):yS(i,d)},enableTextSetter:!0},V7e(o,a)),l.textConfig.position=null)}else this._endLabel&&(this._polyline.removeTextContent(),this._endLabel=null)},t.prototype._endLabelOnDuring=function(n,a,r,o,i,l,s){var u=this._endLabel,c=this._polyline;if(u){n<1&&o.originalX==null&&(o.originalX=u.x,o.originalY=u.y);var d=r.getLayout("points"),v=r.hostModel,f=v.get("connectNulls"),h=l.get("precision"),m=l.get("distance")||0,g=s.getBaseAxis(),y=g.isHorizontal(),b=g.inverse,_=a.shape,w=b?y?_.x:_.y+_.height:y?_.x+_.width:_.y,S=(y?m:0)*(b?-1:1),k=(y?0:-m)*(b?-1:1),T=y?"x":"y",M=B7e(d,w,T),E=M.range,A=E[1]-E[0],I=void 0;if(A>=1){if(A>1&&!f){var P=hM(d,E[0]);u.attr({x:P[0]+S,y:P[1]+k}),i&&(I=v.getRawValue(E[0]))}else{var P=c.getPointOn(w,T);P&&u.attr({x:P[0]+S,y:P[1]+k});var R=v.getRawValue(E[0]),G=v.getRawValue(E[1]);i&&(I=hCe(r,h,R,G,M.t))}o.lastFrameIndex=E[0]}else{var K=n===1||o.lastFrameIndex>0?E[0]:0,P=hM(d,K);i&&(I=v.getRawValue(K)),u.attr({x:P[0]+S,y:P[1]+k})}if(i){var H=h2(u);typeof H.setLabelText=="function"&&H.setLabelText(I)}}},t.prototype._doUpdateAnimation=function(n,a,r,o,i,l,s){var u=this._polyline,c=this._polygon,d=n.hostModel,v=M7e(this._data,n,this._stackedOnPoints,a,this._coordSys,r,this._valueOrigin),f=v.current,h=v.stackedOnCurrent,m=v.next,g=v.stackedOnNext;if(i&&(h=ns(v.stackedOnCurrent,v.current,r,i,s),f=ns(v.current,null,r,i,s),g=ns(v.stackedOnNext,v.next,r,i,s),m=ns(v.next,null,r,i,s)),fM(f,m)>3e3||c&&fM(h,g)>3e3){u.stopAnimation(),u.setShape({points:m}),c&&(c.stopAnimation(),c.setShape({points:m,stackedOnPoints:g}));return}u.shape.__points=v.current,u.shape.points=f;var y={shape:{points:m}};v.current!==f&&(y.shape.__points=v.next),u.stopAnimation(),Ar(u,y,d),c&&(c.setShape({points:f,stackedOnPoints:h}),c.stopAnimation(),Ar(c,{shape:{stackedOnPoints:g}},d),u.shape.points!==c.shape.points&&(c.shape.points=u.shape.points));for(var b=[],_=v.status,w=0;w<_.length;w++){var S=_[w].cmd;if(S==="="){var k=n.getItemGraphicEl(_[w].idx1);k&&b.push({el:k,ptIdx:w})}}u.animators&&u.animators.length&&u.animators[0].during(function(){c&&c.dirtyShape();for(var T=u.shape.__points,M=0;Mt&&(t=e[n]);return isFinite(t)?t:NaN},min:function(e){for(var t=1/0,n=0;n10&&i.type==="cartesian2d"&&o){var s=i.getBaseAxis(),u=i.getOtherAxis(s),c=s.getExtent(),d=a.getDevicePixelRatio(),v=Math.abs(c[1]-c[0])*(d||1),f=Math.round(l/v);if(isFinite(f)&&f>1){o==="lttb"?t.setData(r.lttbDownSample(r.mapDimension(u.dim),1/f)):o==="minmax"&&t.setData(r.minmaxDownSample(r.mapDimension(u.dim),1/f));var h=void 0;Ut(o)?h=H7e[o]:dn(o)&&(h=o),h&&t.setData(r.downSample(r.mapDimension(u.dim),1/f,h,W7e))}}}}}function K7e(e){e.registerChartView(z7e),e.registerSeriesModel(_7e),e.registerLayout(F7e("line")),e.registerVisual({seriesType:"line",reset:function(t){var n=t.getData(),a=t.getModel("lineStyle").getLineStyle();a&&!a.stroke&&(a.stroke=n.getVisual("style").fill),n.setVisual("legendLineStyle",a)}}),e.registerProcessor(e.PRIORITY.PROCESSOR.STATISTIC,wB("line"))}var U7e=function(e){At(t,e);function t(n,a,r,o,i){var l=e.call(this,n,a,r)||this;return l.index=0,l.type=o||"value",l.position=i||"bottom",l}return t.prototype.isHorizontal=function(){var n=this.position;return n==="top"||n==="bottom"},t.prototype.getGlobalExtent=function(n){var a=this.getExtent();return a[0]=this.toGlobalCoord(a[0]),a[1]=this.toGlobalCoord(a[1]),n&&a[0]>a[1]&&a.reverse(),a},t.prototype.pointToData=function(n,a){return this.coordToData(this.toLocalCoord(n[this.dim==="x"?0:1]),a)},t.prototype.setCategorySortInfo=function(n){if(this.type!=="category")return!1;this.model.option.categorySortInfo=n,this.scale.setSortInfo(n)},t}(s7e),G7e=null;function Y7e(){return G7e}var j7e="expandAxisBreak",Ss=Math.PI,q7e=[[1,2,1,2],[5,3,5,3],[8,3,8,3]],X7e=[[0,1,0,1],[0,3,0,3],[0,3,0,3]],lf=Hn(),SB=Hn(),CB=function(){function e(t){this.recordMap={},this.resolveAxisNameOverlap=t}return e.prototype.ensureRecord=function(t){var n=t.axis.dim,a=t.componentIndex,r=this.recordMap,o=r[n]||(r[n]=[]);return o[a]||(o[a]={ready:{}})},e}();function Z7e(e,t,n,a){var r=n.axis,o=t.ensureRecord(n),i=[],l,s=wS(e.axisName)&&of(e.nameLocation);we(a,function(h){var m=Ks(h);if(!(!m||m.label.ignore)){i.push(m);var g=o.transGroup;s&&(g.transform?pp(Jf,g.transform):hp(Jf),m.transform&&$v(Jf,Jf,m.transform),ln.copy(B0,m.localRect),B0.applyTransform(Jf),l?l.union(B0):ln.copy(l=new ln(0,0,0,0),B0))}});var u=Math.abs(o.dirVec.x)>.1?"x":"y",c=o.transGroup[u];if(i.sort(function(h,m){return Math.abs(h.label[u]-c)-Math.abs(m.label[u]-c)}),s&&l){var d=r.getExtent(),v=Math.min(d[0],d[1]),f=Math.max(d[0],d[1])-v;l.union(new ln(v,0,f,1))}o.stOccupiedRect=l,o.labelInfoList=i}var Jf=zi(),B0=new ln(0,0,0,0),xB=function(e,t,n,a,r,o){if(of(e.nameLocation)){var i=o.stOccupiedRect;i&&kB(m7e({},i,o.transGroup.transform),a,r)}else TB(o.labelInfoList,o.dirVec,a,r)};function kB(e,t,n){var a=new fn;gS(e,t,a,{direction:Math.atan2(n.y,n.x),bidirectional:!1,touchThreshold:.05})&&g7e(t,a)}function TB(e,t,n,a){for(var r=fn.dot(a,t)>=0,o=0,i=e.length;o0?"top":"bottom",o="center"):Mg(r-Ss)?(i=a>0?"bottom":"top",o="center"):(i="middle",r>0&&r0?"right":"left":o=a>0?"left":"right"),{rotation:r,textAlign:o,textVerticalAlign:i}},e.makeAxisEventDataBase=function(t){var n={componentType:t.mainType,componentIndex:t.componentIndex};return n[t.mainType+"Index"]=t.componentIndex,n},e.isLabelSilent=function(t){var n=t.get("tooltip");return t.get("silent")||!(t.get("triggerEvent")||n&&n.show)},e}(),J7e=["axisLine","axisTickLabelEstimate","axisTickLabelDetermine","axisName"],Q7e={axisLine:function(e,t,n,a,r,o,i){var l=a.get(["axisLine","show"]);if(l==="auto"&&(l=!0,e.raw.axisLineAutoShow!=null&&(l=!!e.raw.axisLineAutoShow)),!!l){var s=a.axis.getExtent(),u=o.transform,c=[s[0],0],d=[s[1],0],v=c[0]>d[0];u&&(Po(c,c,u),Po(d,d,u));var f=vt({lineCap:"round"},a.getModel(["axisLine","lineStyle"]).getLineStyle()),h={strokeContainThreshold:e.raw.strokeContainThreshold||5,silent:!0,z2:1,style:f};if(a.get(["axisLine","breakLine"])&&Vg(a.axis.scale))Y7e().buildAxisBreakLine(a,r,o,h);else{var m=new Fs(vt({shape:{x1:c[0],y1:c[1],x2:d[0],y2:d[1]}},h));Ih(m.shape,m.style.lineWidth),m.anid="line",r.add(m)}var g=a.get(["axisLine","symbol"]);if(g!=null){var y=a.get(["axisLine","symbolSize"]);Ut(g)&&(g=[g,g]),(Ut(y)||Gn(y))&&(y=[y,y]);var b=oN(a.get(["axisLine","symbolOffset"])||0,y),_=y[0],w=y[1];we([{rotate:e.rotation+Math.PI/2,offset:b[0],r:0},{rotate:e.rotation-Math.PI/2,offset:b[1],r:Math.sqrt((c[0]-d[0])*(c[0]-d[0])+(c[1]-d[1])*(c[1]-d[1]))}],function(S,k){if(g[k]!=="none"&&g[k]!=null){var T=nf(g[k],-_/2,-w/2,_,w,f.stroke,!0),M=S.r+S.offset,E=v?d:c;T.attr({rotation:S.rotate,x:E[0]+M*Math.cos(e.rotation),y:E[1]-M*Math.sin(e.rotation),silent:!0,z2:11}),r.add(T)}})}}},axisTickLabelEstimate:function(e,t,n,a,r,o,i,l){var s=mM(t,r,l);s&&pM(e,t,n,a,r,o,i,ri.estimate)},axisTickLabelDetermine:function(e,t,n,a,r,o,i,l){var s=mM(t,r,l);s&&pM(e,t,n,a,r,o,i,ri.determine);var u=aAe(e,r,o,a);nAe(e,t.labelLayoutList,u),rAe(e,r,o,a,e.tickDirection)},axisName:function(e,t,n,a,r,o,i,l){var s=n.ensureRecord(a);t.nameEl&&(r.remove(t.nameEl),t.nameEl=s.nameLayout=s.nameLocation=null);var u=e.axisName;if(wS(u)){var c=e.nameLocation,d=e.nameDirection,v=a.getModel("nameTextStyle"),f=a.get("nameGap")||0,h=a.axis.getExtent(),m=a.axis.inverse?-1:1,g=new fn(0,0),y=new fn(0,0);c==="start"?(g.x=h[0]-m*f,y.x=-m):c==="end"?(g.x=h[1]+m*f,y.x=m):(g.x=(h[0]+h[1])/2,g.y=e.labelOffset+d*f,y.y=d);var b=zi();y.transform(o8(b,b,e.rotation));var _=a.get("nameRotate");_!=null&&(_=_*Ss/180);var w,S;of(c)?w=Es.innerTextLayout(e.rotation,_??e.rotation,d):(w=eAe(e.rotation,c,_||0,h),S=e.raw.axisNameAvailableWidth,S!=null&&(S=Math.abs(S/Math.sin(w.rotation)),!isFinite(S)&&(S=null)));var k=v.getFont(),T=a.get("nameTruncate",!0)||{},M=T.ellipsis,E=mg(e.raw.nameTruncateMaxWidth,T.maxWidth,S),A=l.nameMarginLevel||0,I=new Er({x:g.x,y:g.y,rotation:w.rotation,silent:Es.isLabelSilent(a),style:ef(v,{text:u,font:k,overflow:"truncate",width:E,ellipsis:M,fill:v.getTextColor()||a.get(["axisLine","lineStyle","color"]),align:v.get("align")||w.textAlign,verticalAlign:v.get("verticalAlign")||w.textVerticalAlign}),z2:1});if(d2({el:I,componentModel:a,itemName:u}),I.__fullText=u,I.anid="name",a.get("triggerEvent")){var P=Es.makeAxisEventDataBase(a);P.targetType="axisName",P.name=u,In(I).eventData=P}o.add(I),I.updateTransform(),t.nameEl=I;var R=s.nameLayout=Ks({label:I,priority:I.z2,defaultAttr:{ignore:I.ignore},marginDefault:of(c)?q7e[A]:X7e[A]});if(s.nameLocation=c,r.add(I),I.decomposeTransform(),e.shouldNameMoveOverlap&&R){var G=n.ensureRecord(a);n.resolveAxisNameOverlap(e,n,a,R,y,G)}}}};function pM(e,t,n,a,r,o,i,l){EB(t)||oAe(e,t,r,l,a,i);var s=t.labelLayoutList;iAe(e,a,s,o),e.rotation;var u=e.optionHideOverlap;tAe(a,s,u),u&&b7e(ar(s,function(c){return c&&!c.label.ignore})),Z7e(e,n,a,s)}function eAe(e,t,n,a){var r=t$(n-e),o,i,l=a[0]>a[1],s=t==="start"&&!l||t!=="start"&&l;return Mg(r-Ss/2)?(i=s?"bottom":"top",o="center"):Mg(r-Ss*1.5)?(i=s?"top":"bottom",o="center"):(i="middle",rSs/2?o=s?"left":"right":o=s?"right":"left"),{rotation:r,textAlign:o,textVerticalAlign:i}}function tAe(e,t,n){var a=e.axis,r=e.get(["axisLabel","customValues"]);if(mEe(a))return;function o(u,c,d){var v=Ks(t[c]),f=Ks(t[d]),h=a.scale;if(!(!v||!f)){if(u==null){if(!n&&r)return;var m=lf(v.label).labelInfo.tick;if(dS(h)&&m.notNice||pi(h)&&m.offInterval){od(v.label);return}}if(u===!1||v.suggestIgnore){od(v.label);return}if(f.suggestIgnore){od(f.label);return}var g=.1;if(!n){var y=[0,0,0,0];v=oM({marginForce:y},v),f=oM({marginForce:y},f)}gS(v,f,null,{touchThreshold:g})&&od(u?f.label:v.label)}}var i=e.get(["axisLabel","showMinLabel"]),l=e.get(["axisLabel","showMaxLabel"]),s=t.length;o(i,0,1),o(l,s-1,s-2)}function nAe(e,t,n){e.showMinorTicks||we(t,function(a){if(a&&a.label.ignore)for(var r=0;r=0&&_(k,w,S.getStore())})}var f=0;if(v(function(_,w,S){a.set(w.uid,1),(!r||!r.hasKey(w.uid))&&(i=!0),f+=S.count()}),(!r||r.keys().length!==a.keys().length)&&(i=!0),!i&&o!=null){t.liPosMinGap=o;return}_S(ku,f);var h=0;v(function(_,w,S){for(var k=0,T=S.count();k0&&b0?YN:wEe,n.serUids=a}var ku=_S({ctor:k7e},50);function pAe(e){return function(t,n){var a=xf(t,{fromStat:{key:e}});if(zo(a.w2))return[-a.w2/2,a.w2/2]}}function x2(e,t){return e+y3+t}function mAe(e){return vAe(),{liPosMinGap:!pi(e.scale)}}var Id="bar";function gAe(e,t,n,a){AEe(e,{key:t,seriesType:n,coordSysType:a,getMetrics:mAe})}function yAe(e){var t=e.scale.rawExtentInfo.makeRenderInfo().startValue;return t}var AB={left:0,right:0,top:0,bottom:0},n1=["25%","25%"],xl="cartesian2d",bAe=function(e){At(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.mergeDefaultAndTheme=function(n,a){var r=kp(n.outerBounds);e.prototype.mergeDefaultAndTheme.apply(this,arguments),r&&n.outerBounds&&Hs(n.outerBounds,r)},t.prototype.mergeOption=function(n,a){e.prototype.mergeOption.apply(this,arguments),this.option.outerBounds&&n.outerBounds&&Hs(this.option.outerBounds,n.outerBounds)},t.type="grid",t.dependencies=["xAxis","yAxis"],t.layoutMode="box",t.defaultOption={show:!1,z:0,left:"15%",top:65,right:"10%",bottom:80,containLabel:!1,outerBoundsMode:"auto",outerBounds:AB,outerBoundsContain:"all",outerBoundsClampWidth:n1[0],outerBoundsClampHeight:n1[1],backgroundColor:tn.color.transparent,borderWidth:1,borderColor:tn.color.neutral30},t}(Kn),_Ae=d$(),wAe="__ec_stack_";function IB(e){return e.get("stack")||wAe+e.seriesIndex}function SAe(e,t){var n=CAe(e,t);return n.columnMap=xAe(n),n}function CAe(e,t){var n=x2(t,xl),a=[],r=xf(e,{fromStat:{key:n},min:1});return ZN(e,n,function(o){a.push({barWidth:sa(o.get("barWidth"),r.w),barMaxWidth:sa(o.get("barMaxWidth"),r.w),barMinWidth:sa(o.get("barMinWidth")||(LB(o)?.5:1),r.w),barGap:o.get("barGap"),barCategoryGap:o.get("barCategoryGap"),defaultBarGap:o.get("defaultBarGap"),stackId:IB(o)})}),{bandWidthResult:r,seriesInfo:a}}function xAe(e){var t=e.bandWidthResult.w,n=t,a=0,r,o,i=[],l={};we(e.seriesInfo,function(m,g){g||(o=m.defaultBarGap||0);var y=m.stackId;Sr(l,y)||a++;var b=l[y];b||(b=l[y]={width:0,maxWidth:0},i.push(y));var _=m.barWidth;_&&!b.width&&(b.width=_,_=sr(n,_),n-=_);var w=m.barMaxWidth;w&&(b.maxWidth=w);var S=m.barMinWidth;S&&(b.minWidth=S);var k=m.barGap;k!=null&&(o=k);var T=m.barCategoryGap;T!=null&&(r=T)}),r==null&&(r=An(35-i.length*4,15)+"%");var s=sa(r,t),u=sa(o,1),c=(n-s)/(a+(a-1)*u);c=An(c,0),we(i,function(m){var g=l[m],y=g.maxWidth,b=g.minWidth;if(g.width){var _=g.width;y&&(_=sr(_,y)),b&&(_=An(_,b)),g.width=_,n-=_+u*_,a--}else{var _=c;y&&y<_&&(_=sr(y,n)),b&&b>_&&(_=b),_!==c&&(g.width=_,n-=_+u*_,a--)}}),c=(n-s)/(a+(a-1)*u),c=An(c,0);var d=0,v;we(i,function(m){var g=l[m];g.width||(g.width=c),v=g,d+=g.width*(1+u)}),v&&(d-=v.width*u);var f={},h=-d/2;return we(i,function(m){var g=l[m];f[m]=f[m]||{bandWidth:t,offset:h,width:g.width},h+=g.width*(1+u)}),f}function kAe(e){return{seriesType:e,overallReset:function(t){var n=x2(e,xl);TEe(t,n,function(a){var r=SAe(a,e);ZN(a,n,function(o){var i=r.columnMap[IB(o)];o.getData().setLayout({bandWidth:i.bandWidth,offset:i.offset,size:i.width})})})}}}function TAe(e){return{seriesType:e,plan:eS(),reset:function(t){if(uAe(t)){var n=t.getData(),a=t.coordinateSystem,r=a.getBaseAxis(),o=a.getOtherAxis(r),i=n.getDimensionIndex(n.mapDimension(o.dim)),l=n.getDimensionIndex(n.mapDimension(r.dim)),s=t.get("showBackground",!0),u=n.mapDimension(o.dim),c=n.getCalculationInfo("stackResultDimension"),d=af(n,u)&&!!n.getCalculationInfo("stackedOnSeries"),v=o.isHorizontal(),f=o.toGlobalCoord(o.dataToCoord(yAe(o))),h=LB(t),m=t.get("barMinHeight")||0,g=c&&n.getDimensionIndex(c),y=n.getLayout("size"),b=n.getLayout("offset");return{progress:function(_,w){for(var S=_.count,k=h&&gl(S*3),T=h&&s&&gl(S*3),M=h&&gl(S),E=a.master.getRect(),A=v?E.width:E.height,I,P=w.getStore(),R=0;(I=_.next())!=null;){var G=P.get(d?g:i,I),K=P.get(l,I),H=f,z=void 0;d&&(z=+G-P.get(i,I));var B=void 0,W=void 0,N=void 0,V=void 0;if(v){var Y=a.dataToPoint([G,K]);d&&(H=a.dataToPoint([z,K])[0]),B=H,W=Y[1]+b,N=Y[0]-H,V=y,Ca(N)g){_=(k+b)/2;break}S===1&&(w=T-h[0].tickValue)}_==null&&(b?b&&(_=h[h.length-1].coord):_=h[0].coord),l[f]=v.toGlobalCoord(_)}});else{var s=this.getData(),u=s.getLayout("offset"),c=s.getLayout("size"),d=o.getBaseAxis().isHorizontal()?0:1;l[d]+=u+c/2}return l}return[NaN,NaN]},t.prototype.__requireStartValue=function(n){return this.getBaseAxis()!==n},t.type="series.__base_bar__",t.defaultOption={z:2,coordinateSystem:"cartesian2d",legendHoverLink:!0,barMinHeight:0,barMinAngle:0,large:!1,largeThreshold:400,progressive:3e3,progressiveChunkMode:"mod",defaultBarGap:"10%"},t}(ai);ai.registerClass(x3);var AAe=function(e){At(t,e);function t(){var n=e!==null&&e.apply(this,arguments)||this;return n.type=t.type,n}return t.prototype.getInitialData=function(){return uS(null,this,{useEncodeDefaulter:!0,createInvertedIndices:!!this.get("realtimeSort",!0)||null})},t.prototype.getProgressive=function(){return this.get("large")?this.get("progressive"):!1},t.prototype.__preparePipelineContext=function(n,a){var r=f$(this,n,a);return r.progressiveRender&&(r.large=!0),r},t.prototype.brushSelector=function(n,a,r){return r.rect(a.getItemLayout(n))},t.type="series."+Id,t.dependencies=["grid","polar"],t.defaultOption=iR(x3.defaultOption,{clip:!0,roundCap:!1,showBackground:!1,backgroundStyle:{color:"rgba(180, 180, 180, 0.2)",borderColor:null,borderWidth:0,borderType:"solid",borderRadius:0,shadowBlur:0,shadowColor:null,shadowOffsetX:0,shadowOffsetY:0,opacity:1},select:{itemStyle:{borderColor:tn.color.primary,borderWidth:2}},realtimeSort:!1}),t}(x3),IAe=function(){function e(){this.cx=0,this.cy=0,this.r0=0,this.r=0,this.startAngle=0,this.endAngle=Math.PI*2,this.clockwise=!0}return e}(),gM=function(e){At(t,e);function t(n){var a=e.call(this,n)||this;return a.type="sausage",a}return t.prototype.getDefaultShape=function(){return new IAe},t.prototype.buildPath=function(n,a){var r=a.cx,o=a.cy,i=Math.max(a.r0||0,0),l=Math.max(a.r,0),s=(l-i)*.5,u=i+s,c=a.startAngle,d=a.endAngle,v=a.clockwise,f=Math.PI*2,h=v?d-cMath.PI/2&&cl)return!0;l=d}return!1},t.prototype._isOrderDifferentInView=function(n,a){for(var r=a.scale,o=r.getExtent(),i=Math.max(0,o[0]),l=Math.min(o[1],r.getOrdinalMeta().categories.length-1);i<=l;++i)if(n.ordinalNumbers[i]!==r.getRawOrdinalNumber(i))return!0},t.prototype._updateSortWithinSameData=function(n,a,r,o){if(this._isOrderChangedWithinSameData(n,a,r)){var i=this._dataSort(n,r,a);this._isOrderDifferentInView(i,r)&&(this._removeOnRenderedListener(o),o.dispatchAction({type:"changeAxisOrder",componentType:r.dim+"Axis",axisId:r.index,sortInfo:i}))}},t.prototype._dispatchInitSort=function(n,a,r){var o=a.baseAxis,i=this._dataSort(n,o,function(l){return n.get(n.mapDimension(a.otherAxis.dim),l)});r.dispatchAction({type:"changeAxisOrder",componentType:o.dim+"Axis",isInitSort:!0,axisId:o.index,sortInfo:i})},t.prototype.remove=function(n,a){this._clear(this._model),this._removeOnRenderedListener(a)},t.prototype.dispose=function(n,a){this._removeOnRenderedListener(a)},t.prototype._removeOnRenderedListener=function(n){this._onRendered&&(n.getZr().off("rendered",this._onRendered),this._onRendered=null)},t.prototype._clear=function(n){var a=this.group,r=this._data;n&&n.isAnimationEnabled()&&r&&!this._isLargeDraw?(this._removeBackground(),this._backgroundEls=[],r.eachItemGraphicEl(function(o){Fv(o,n,In(o).dataIndex)})):a.removeAll(),this._data=null,this._isFirstFrame=!0},t.prototype._removeBackground=function(){this.group.remove(this._backgroundGroup),this._backgroundGroup=null},t.type=Id,t}(Oo),yM={cartesian2d:function(e,t){var n=t.width<0?-1:1,a=t.height<0?-1:1;n<0&&(t.x+=t.width,t.width=-t.width),a<0&&(t.y+=t.height,t.height=-t.height);var r=e.x+e.width,o=e.y+e.height,i=Pb(t.x,e.x),l=Db(t.x+t.width,r),s=Pb(t.y,e.y),u=Db(t.y+t.height,o),c=lr?l:i,t.y=d&&s>o?u:s,t.width=c?0:l-i,t.height=d?0:u-s,n<0&&(t.x+=t.width,t.width=-t.width),a<0&&(t.y+=t.height,t.height=-t.height),c||d},polar:function(e,t){var n=t.r0<=t.r?1:-1;if(n<0){var a=t.r;t.r=t.r0,t.r0=a}var r=Db(t.r,e.r),o=Pb(t.r0,e.r0);t.r=r,t.r0=o;var i=r-o<0;if(n<0){var a=t.r;t.r=t.r0,t.r0=a}return i}},bM={cartesian2d:function(e,t,n,a,r,o,i,l,s){var u=new ga({shape:vt({},a),z2:1});if(u.__dataIndex=n,u.name="item",o){var c=u.shape,d=r?"height":"width";c[d]=0}return u},polar:function(e,t,n,a,r,o,i,l,s){var u=!r&&s?gM:qs,c=new u({shape:a,z2:1});c.name="item";var d=PB(r);if(c.calculateTextPosition=LAe(d,{isRoundCap:u===gM}),o){var v=c.shape,f=r?"r":"endAngle",h={};v[f]=r?a.r0:a.startAngle,h[f]=a[f],(l?Ar:Do)(c,{shape:h},o)}return c}};function OAe(e,t){var n=e.get("realtimeSort",!0),a=t.getBaseAxis();if(n&&a.type==="category"&&t.type==="cartesian2d")return{baseAxis:a,otherAxis:t.getOtherAxis(a)}}function _M(e,t,n,a,r,o,i,l){var s,u;o?(u={x:a.x,width:a.width},s={y:a.y,height:a.height}):(u={y:a.y,height:a.height},s={x:a.x,width:a.width}),l||(i?Ar:Do)(n,{shape:s},t,r,null);var c=t?e.baseAxis.model:null;(i?Ar:Do)(n,{shape:u},c,r)}function wM(e,t){for(var n=0;n0?1:-1,i=a.height>0?1:-1;return{x:a.x+o*r/2,y:a.y+i*r/2,width:a.width-o*r,height:a.height-i*r}},polar:function(e,t,n){var a=e.getItemLayout(t);return{cx:a.cx,cy:a.cy,r0:a.r0,r:a.r,startAngle:a.startAngle,endAngle:a.endAngle,clockwise:a.clockwise}}};function NAe(e){return e.startAngle!=null&&e.endAngle!=null&&e.startAngle===e.endAngle}function PB(e){return function(t){var n=t?"Arc":"Angle";return function(a){switch(a){case"start":case"insideStart":case"end":case"insideEnd":return a+n;default:return a}}}(e)}function CM(e,t,n,a,r,o,i,l){var s=t.getItemVisual(n,"style");if(l){if(!o.get("roundCap")){var c=e.shape,d=bv(a.getModel("itemStyle"),c,!0);vt(c,d),e.setShape(c)}}else{var u=a.get(["itemStyle","borderRadius"])||0;e.setShape("r",u)}e.useStyle(s);var v=a.getShallow("cursor");v&&e.attr("cursor",v);var f=l?i?r.r>=r.r0?"endArc":"startArc":r.endAngle>=r.startAngle?"endAngle":"startAngle":i?HAe(r,o.coordinateSystem):WAe(r,o.coordinateSystem),h=xp(a);Cp(e,h,{labelFetcher:o,labelDataIndex:n,defaultText:yS(o.getData(),n),inheritColor:s.fill,defaultOpacity:s.opacity,defaultOutsidePosition:f});var m=e.getTextContent();if(l&&m){var g=a.get(["label","position"]);e.textConfig.inside=g==="middle"?!0:null,PAe(e,g==="outside"?f:g,PB(i),a.get(["label","rotate"]))}Yxe(m,h,o.getRawValue(n),function(b){return vB(t,b)});var y=a.getModel(["emphasis"]);Ah(e,y.get("focus"),y.get("blurScope"),y.get("disabled")),$g(e,a),NAe(r)&&(e.style.fill="none",e.style.stroke="none",we(e.states,function(b){b.style&&(b.style.fill=b.style.stroke="none")}))}function BAe(e,t){var n=e.get(["itemStyle","borderColor"]);if(!n||n==="none")return 0;var a=e.get(["itemStyle","borderWidth"])||0,r=isNaN(t.width)?Number.MAX_VALUE:Math.abs(t.width),o=isNaN(t.height)?Number.MAX_VALUE:Math.abs(t.height);return Math.min(a,r,o)}var VAe=function(){function e(){}return e}(),xM=function(e){At(t,e);function t(n){var a=e.call(this,n)||this;return a.type="largeBar",a}return t.prototype.getDefaultShape=function(){return new VAe},t.prototype.buildPath=function(n,a){for(var r=a.points,o=this.baseDimIdx,i=1-this.baseDimIdx,l=[],s=[],u=this.barWidth,c=0;c=0?n:null},30,!1);function zAe(e,t,n){for(var a=e.baseDimIdx,r=1-a,o=e.shape.points,i=e.largeDataIndices,l=[],s=[],u=e.barWidth,c=0,d=o.length/3;c=l[0]&&t<=l[0]+s[0]&&n>=l[1]&&n<=l[1]+s[1])return i[c]}return-1}function DB(e,t,n){if(bB(n,"cartesian2d")){var a=t,r=n.getArea();return{x:e?a.x:r.x,y:e?r.y:a.y,width:e?a.width:r.width,height:e?r.height:a.height}}else{var r=n.getArea(),o=t;return{cx:r.cx,cy:r.cy,r0:e?r.r0:o.r0,r:e?r.r:o.r,startAngle:e?o.startAngle:0,endAngle:e?o.endAngle:Math.PI*2}}}function FAe(e,t,n){var a=e.type==="polar"?qs:ga;return new a({shape:DB(t,n,e),silent:!0,z2:0})}function HAe(e,t){if(e.height===0){var n=t.getOtherAxis(t.getBaseAxis());return n.inverse?"bottom":"top"}return e.height>0?"bottom":"top"}function WAe(e,t){if(e.width===0){var n=t.getOtherAxis(t.getBaseAxis());return n.inverse?"left":"right"}return e.width>=0?"right":"left"}function KAe(e){e.registerChartView(DAe),e.registerSeriesModel(AAe),e.registerLayout(e.PRIORITY.VISUAL.LAYOUT,kAe(Id)),e.registerLayout(e.PRIORITY.VISUAL.PROGRESSIVE_LAYOUT,TAe(Id)),e.registerProcessor(e.PRIORITY.PROCESSOR.STATISTIC,wB(Id)),e.registerAction({type:"changeAxisOrder",event:"changeAxisOrder",update:"update"},function(t,n){var a=t.componentType||"series";n.eachComponent({mainType:a,query:t},function(r){t.sortInfo&&r.axis.setCategorySortInfo(t.sortInfo)})}),EAe(e)}function UAe(e){return{seriesType:e,reset:function(t,n){var a=n.findComponents({mainType:"legend"});if(!(!a||!a.length)){var r=t.getData();r.filterSelf(function(o){for(var i=r.getName(o),l=0;l=0},e.prototype.indexOfName=function(t){var n=this._getDataWithEncodedVisual();return n.indexOfName(t)},e.prototype.getItemVisual=function(t,n){var a=this._getDataWithEncodedVisual();return a.getItemVisual(t,n)},e}(),As="pie",jAe=Hn(),OB=function(e){At(t,e);function t(){var n=e!==null&&e.apply(this,arguments)||this;return n.type=t.type,n}return t.prototype.init=function(n){e.prototype.init.apply(this,arguments),this.legendVisualProvider=new YAe(qn(this.getData,this),qn(this.getRawData,this)),this._defaultLabelLine(n)},t.prototype.mergeOption=function(){e.prototype.mergeOption.apply(this,arguments)},t.prototype.getInitialData=function(){return GAe(this,{coordDimensions:["value"],encodeDefaulter:Sa($ke,this)})},t.prototype.getDataParams=function(n){var a=this.getData(),r=jAe(a),o=r.seats;if(!o){var i=[];a.each(a.mapDimension("value"),function(s){i.push(s)}),o=r.seats=GSe(i,a.hostModel.get("percentPrecision"))}var l=e.prototype.getDataParams.call(this,n);return l.percent=o[n]||0,l.$vars.push("percent"),l},t.prototype._defaultLabelLine=function(n){N_(n,"labelLine",["show"]);var a=n.labelLine,r=n.emphasis.labelLine;a.show=a.show&&n.label.show,r.show=r.show&&n.emphasis.label.show},t.type="series."+As,t.defaultOption={z:2,legendHoverLink:!0,colorBy:"data",center:["50%","50%"],radius:[0,"50%"],clockwise:!0,startAngle:90,endAngle:"auto",padAngle:0,minAngle:0,minShowLabelAngle:0,selectedOffset:10,percentPrecision:2,stillShowZeroSum:!0,coordinateSystemUsage:"box",left:0,top:0,right:0,bottom:0,width:null,height:null,label:{rotate:0,show:!0,overflow:"truncate",position:"outer",alignTo:"none",edgeDistance:"25%",distanceToLabelLine:5},labelLine:{show:!0,length:15,length2:30,smooth:!1,minTurnAngle:90,maxSurfaceAngle:90,lineStyle:{width:1,type:"solid"}},itemStyle:{borderWidth:1,borderJoin:"round"},showEmptyCircle:!0,emptyCircleStyle:{color:"lightgray",opacity:1},labelLayout:{hideOverlap:!0},emphasis:{scale:!0,scaleSize:5},avoidLabelOverlap:!0,animationType:"expansion",animationDuration:1e3,animationTypeUpdate:"transition",animationEasingUpdate:"cubicInOut",animationDurationUpdate:500,animationEasing:"cubicInOut"},t}(ai);wke({fullType:OB.type,getCoord2:function(e){return e.getShallow("center")}});var qAe=Math.PI/180;function MM(e,t,n,a,r,o,i,l,s,u){if(e.length<2)return;function c(m){for(var g=m.rB,y=g*g,b=0;bn?y:g,S=Math.abs(_.label.y-n);if(S>=w.maxY){var k=_.label.x-t-_.len2*r,T=a+_.len,M=Math.abs(k)e.unconstrainedWidth?null:v:null;a.setStyle("width",f)}RB(o,a)}}}function RB(e,t){EM.rect=e,fB(EM,t,ZAe)}var ZAe={minMarginForce:[null,0,null,0],marginDefault:[1,0,1,0]},EM={};function Ob(e){return e.position==="center"}function JAe(e){var t=e.getData(),n=[],a,r,o=!1,i=(e.get("minShowLabelAngle")||0)*qAe,l=t.getLayout("viewRect"),s=t.getLayout("r"),u=l.width,c=l.x,d=l.y,v=l.height;function f(k){k.ignore=!0}function h(k){if(!k.ignore)return!0;for(var T in k.states)if(k.states[T].ignore===!1)return!0;return!1}t.each(function(k){var T=t.getItemGraphicEl(k),M=T.shape,E=T.getTextContent(),A=T.getTextGuideLine(),I=t.getItemModel(k),P=I.getModel("label"),R=P.get("position")||I.get(["emphasis","label","position"]),G=P.get("distanceToLabelLine"),K=P.get("alignTo"),H=sa(P.get("edgeDistance"),u),z=P.get("bleedMargin");z==null&&(z=Math.min(u,v)>200?10:2);var B=I.getModel("labelLine"),W=B.get("length");W=sa(W,u);var N=B.get("length2");if(N=sa(N,u),Math.abs(M.endAngle-M.startAngle)0?"right":"left":Y>0?"left":"right"}var fe=Math.PI,ge=0,Se=P.get("rotate");if(Gn(Se))ge=Se*(fe/180);else if(R==="center")ge=0;else if(Se==="radial"||Se===!0){var De=Y<0?-V+fe:-V;ge=De}else if(Se==="tangential"||Se==="tangential-noflip"&&R!=="outside"&&R!=="outer"){var Te=Math.atan2(Y,X);Te<0&&(Te=fe*2+Te);var Re=X>0;Re&&Se!=="tangential-noflip"&&(Te=fe+Te),ge=Te-fe}if(o=!!ge,E.x=ee,E.y=re,E.rotation=ge,E.setStyle({verticalAlign:"middle"}),ve){E.setStyle({align:le});var Le=E.states.select;Le&&(Le.x+=E.x,Le.y+=E.y)}else{var Ce=new ln(0,0,0,0);RB(Ce,E),n.push({label:E,labelLine:A,position:R,len:W,len2:N,minTurnAngle:B.get("minTurnAngle"),maxSurfaceAngle:B.get("maxSurfaceAngle"),surfaceNormal:new fn(Y,X),linePoints:ie,textAlign:le,labelDistance:G,labelAlignTo:K,edgeDistance:H,bleedMargin:z,rect:Ce,unconstrainedWidth:Ce.width,labelStyleWidth:E.style.width})}T.setTextConfig({inside:ve})}}),!o&&e.get("avoidLabelOverlap")&&XAe(n,a,r,s,u,v,c,d);for(var m=0;mB?(N=G+T*B/2,V=N):(N=G+E,V=W-E),a.setItemLayout(z,{angle:B,startAngle:N,endAngle:V,clockwise:_,cx:i,cy:l,r0:u,r:w?$_(H,k,[u,s]):s}),G=W}),P0){for(var c=i.getItemLayout(0),d=1;isNaN(c&&c.startAngle)&&d=o.r0}},t.type=As,t}(Oo);function aIe(e){return{seriesType:e,reset:function(t,n){var a=t.getData();a.filterSelf(function(r){var o=a.mapDimension("value"),i=a.get(o,r);return!(Gn(i)&&!isNaN(i)&&i<0)})}}}function rIe(e){e.registerChartView(nIe),e.registerSeriesModel(OB),gTe(As,e.registerAction),e.registerLayout(QAe),e.registerProcessor(UAe(As)),e.registerProcessor(aIe(As))}var k3=function(e){At(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.getCoordSysModel=function(){return this.getReferringComponents("grid",ko).models[0]},t.type="cartesian2dAxis",t}(Kn);al(k3,bEe);var BB={show:!0,z:0,inverse:!1,name:"",nameLocation:"end",nameRotate:null,nameTruncate:{maxWidth:null,ellipsis:"...",placeholder:"."},nameTextStyle:{},nameGap:15,silent:!1,triggerEvent:!1,tooltip:{show:!1},axisPointer:{},axisLine:{show:!0,onZero:"auto",onZeroAxisIndex:null,lineStyle:{color:tn.color.axisLine,width:1,type:"solid"},symbol:["none","none"],symbolSize:[10,15],breakLine:!0},axisTick:{show:!0,inside:!1,length:5,lineStyle:{width:1}},axisLabel:{show:!0,inside:!1,rotate:0,showMinLabel:null,showMaxLabel:null,margin:8,fontSize:12,color:tn.color.axisLabel,textMargin:[0,3]},splitLine:{show:!0,showMinLine:!0,showMaxLine:!0,lineStyle:{color:tn.color.axisSplitLine,width:1,type:"solid"}},splitArea:{show:!1,areaStyle:{color:[tn.color.backgroundTint,tn.color.backgroundTransparent]}},breakArea:{show:!0,itemStyle:{color:tn.color.neutral00,borderColor:tn.color.border,borderWidth:1,borderType:[3,3],opacity:.6},zigzagAmplitude:4,zigzagMinSpan:4,zigzagMaxSpan:20,zigzagZ:100,expandOnClick:!0},breakLabelLayout:{moveOverlap:"auto"}},oIe=Bn({boundaryGap:!0,deduplication:null,jitter:0,jitterOverlap:!0,jitterMargin:2,splitLine:{show:!1},axisTick:{alignWithLabel:!1,interval:"auto",show:"auto"},axisLabel:{interval:"auto"}},BB),SS=Bn({boundaryGap:[0,0],axisLine:{show:"auto"},axisTick:{show:"auto"},splitNumber:5,minorTick:{show:!1,splitNumber:5,length:3,lineStyle:{}},minorSplitLine:{show:!1,lineStyle:{color:tn.color.axisMinorSplitLine,width:1}}},BB),iIe=Bn({splitNumber:6,axisLabel:{rich:{primary:{fontWeight:"bold"}}},splitLine:{show:!1}},SS),lIe=zn({logBase:10},SS);const sIe={category:oIe,value:SS,time:iIe,log:lIe};function IM(e,t,n,a){we(WN,function(r,o){var i=Bn(Bn({},sIe[o],!0),a,!0),l=function(s){At(u,s);function u(){var c=s!==null&&s.apply(this,arguments)||this;return c.type=t+"Axis."+o,c}return u.prototype.mergeDefaultAndTheme=function(c,d){var v=Ph(this),f=v?kp(c):{},h=d.getTheme();Bn(c,h.get(o+"Axis")),Bn(c,this.getDefaultOption()),c.type=LM(c),v&&Hs(c,f,v)},u.prototype.optionUpdated=function(){var c=this.option;c.type==="category"&&(this.__ordinalMeta=m3.createByAxisModel(this))},u.prototype.getCategories=function(c){var d=this.option;if(d.type==="category")return c?d.data:this.__ordinalMeta.categories},u.prototype.getOrdinalMeta=function(){return this.__ordinalMeta},u.prototype.updateAxisBreaks=function(c){return{breaks:[]}},u.type=t+"Axis."+o,u.defaultOption=i,u}(n);e.registerComponentModel(l)}),e.registerSubTypeDefaulter(t+"Axis",LM)}function LM(e){return e.type||(e.data?"category":"value")}var uIe=function(){function e(t){this.type="cartesian",this._dimList=[],this._axes={},this.name=t||""}return e.prototype.getAxis=function(t){return this._axes[t]},e.prototype.getAxes=function(){return Xt(this._dimList,function(t){return this._axes[t]},this)},e.prototype.getAxesByScale=function(t){return t=t.toLowerCase(),ar(this.getAxes(),function(n){return n.scale.type===t})},e.prototype.addAxis=function(t){var n=t.dim;this._axes[n]=t,this._dimList.push(n)},e}(),Em=["x","y"];function PM(e){return(e.type==="interval"||e.type==="time")&&!Vg(e)}var cIe=function(e){At(t,e);function t(){var n=e!==null&&e.apply(this,arguments)||this;return n.type=xl,n.dimensions=Em,n}return t.prototype.calcAffineTransform=function(){this._transform=this._invTransform=null;var n=this.getAxis("x").scale,a=this.getAxis("y").scale;if(!(!PM(n)||!PM(a))){var r=Yg(n,null),o=Yg(a,null),i=this.dataToPoint([r[0],o[0]]),l=this.dataToPoint([r[1],o[1]]),s=r[1]-r[0],u=o[1]-o[0];if(!(!s||!u)){var c=(l[0]-i[0])/s,d=(l[1]-i[1])/u,v=i[0]-r[0]*c,f=i[1]-o[0]*d,h=this._transform=[c,0,0,d,v,f];this._invTransform=pp([],h)}}},t.prototype.getBaseAxis=function(){return this.getAxesByScale("ordinal")[0]||this.getAxesByScale("time")[0]||this.getAxis("x")},t.prototype.containPoint=function(n){var a=this.getAxis("x"),r=this.getAxis("y");return a.contain(a.toLocalCoord(n[0]))&&r.contain(r.toLocalCoord(n[1]))},t.prototype.containData=function(n){return this.getAxis("x").containData(n[0])&&this.getAxis("y").containData(n[1])},t.prototype.containZone=function(n,a){var r=this.dataToPoint(n),o=this.dataToPoint(a),i=this.getArea(),l=new ln(r[0],r[1],o[0]-r[0],o[1]-r[1]);return i.intersect(l)},t.prototype.dataToPoint=function(n,a,r){r=r||[];var o=n[0],i=n[1];if(this._transform&&o!=null&&isFinite(o)&&i!=null&&isFinite(i))return Po(r,n,this._transform);var l=this.getAxis("x"),s=this.getAxis("y");return r[0]=l.toGlobalCoord(l.dataToCoord(o,a)),r[1]=s.toGlobalCoord(s.dataToCoord(i,a)),r},t.prototype.clampData=function(n,a){var r=this.getAxis("x").scale,o=this.getAxis("y").scale,i=r.getExtent(),l=o.getExtent(),s=r.parse(n[0]),u=o.parse(n[1]);return a=a||[],a[0]=Math.min(Math.max(Math.min(i[0],i[1]),s),Math.max(i[0],i[1])),a[1]=Math.min(Math.max(Math.min(l[0],l[1]),u),Math.max(l[0],l[1])),a},t.prototype.pointToData=function(n,a,r){if(r=r||[],this._invTransform)return Po(r,n,this._invTransform);var o=this.getAxis("x"),i=this.getAxis("y");return r[0]=o.coordToData(o.toLocalCoord(n[0]),a),r[1]=i.coordToData(i.toLocalCoord(n[1]),a),r},t.prototype.getOtherAxis=function(n){return this.getAxis(n.dim==="x"?"y":"x")},t.prototype.getArea=function(n){n=n||0;var a=this.getAxis("x").getGlobalExtent(),r=this.getAxis("y").getGlobalExtent(),o=Math.min(a[0],a[1])-n,i=Math.min(r[0],r[1])-n,l=Math.max(a[0],a[1])-o+n,s=Math.max(r[0],r[1])-i+n;return new ln(o,i,l,s)},t}(uIe);function dIe(e,t){var n=e.scale,a=e.model,r=aB(n,a,a.ecModel,e),o=rf(n),i=rf(t)?t.intervalStub:t,l=o?n.intervalStub:n,s=n.base,u=i.getTicks(),c=i.getTicks({expandToNicedExtent:!0}),d=u.length-1,v,f,h;if(d===1)v=f=0,h=1;else if(d===2){var m=Ca(u[0].value-u[1].value),g=Ca(u[1].value-u[2].value);v=f=0,m===g?h=2:(h=1,m=T[1])return!0})):w[1]?(E=T[1],G(function(){if(B(),R=En(P-A*h,I),K(),M<=T[0])return!0})):G(function(){R=En(yp(T[0]/A)*A,I),P=En(gc(T[1]/A)*A,I);var Y=Dl((P-R)/A);if(Y<=h){var X=h-Y,ee=void 0,re=r.incl0||o;if(re&&T[0]===0)ee=[0,X];else if(re&&T[1]===0)ee=[X,0];else{var ie=gc(X/2);ee=X%2===0?[ie,ie]:M+E=T[1])return!0}})}UN(n,w,k,[M,E],S,{interval:A,intervalCount:h,intervalPrecision:I,niceExtent:[R,P]})}var DM=[[3,1],[0,2]],fIe=function(){function e(t,n,a){this.type="grid",this._coordsMap={},this._coordsList=[],this._axesMap={},this._axesList=[],this.axisPointerEnabled=!0,this.dimensions=Em,this._initCartesian(t,n,a),this.model=t}return e.prototype.getRect=function(){return this._rect},e.prototype.update=function(t,n){var a=this._axesMap;we(this._axesList,function(i){$Ee(i,LEe);var l=i.scale;pi(l)&&l.setSortInfo(i.model.get("categorySortInfo"))});function r(i){for(var l=la(i),s=[],u=l.length-1;u>=0;u--){var c=i[+l[u]];c.__alignTo?s.push(c):eM(c)}we(s,function(d){hIe(d,d.__alignTo)?eM(d):dIe(d,d.__alignTo.scale)})}r(a.x),r(a.y);var o={};we(a.x,function(i){OM(a,"y",i,o)}),we(a.y,function(i){OM(a,"x",i,o)}),this.resize(this.model,n)},e.prototype.resize=function(t,n,a){var r=Y8(t,n),o=this._rect=wc(t.getBoxLayoutParams(),r.refContainer),i=this._axesMap,l=this._coordsList,s=t.get("containLabel");if(VB(i,o),!a){var u=mIe(o,l,i,s,n),c=void 0;if(s)c=BM(o.clone(),"axisLabel",null,o,i,u,r);else{var d=gIe(t,o,r),v=d.outerBoundsRect,f=d.parsedOuterBoundsContain,h=d.outerBoundsClamp;v&&(c=BM(v,f,h,o,i,u,r))}zB(o,i,ri.determine,null,c,r),we(this._coordsList,function(m){m.calcAffineTransform()})}},e.prototype.getAxis=function(t,n){var a=this._axesMap[t];if(a!=null)return a[n||0]},e.prototype.getAxes=function(){return this._axesList.slice()},e.prototype.getCartesian=function(t,n){if(t!=null&&n!=null){var a="x"+t+"y"+n;return this._coordsMap[a]}Yt(t)&&(n=t.yAxisIndex,t=t.xAxisIndex);for(var r=0,o=this._coordsList;r=0;r--){var o=e[+t[r]];NN(o.scale)&&yEe(o.model,o.type)==null&&(o.model.get("alignTicks")&&o.model.get("interval")==null?a.push(o):n=o)}n||(n=a.pop()),n&&we(a,function(i){i.__alignTo=n})}function hIe(e,t){return Vg(e.scale)||Vg(t.scale)||t.scale.getTicks().length<2}function pIe(e,t){var n=e.getExtent(),a=n[0]+n[1];e.toGlobalCoord=e.dim==="x"?function(r){return r+t}:function(r){return a-r+t},e.toLocalCoord=e.dim==="x"?function(r){return r-t}:function(r){return a-r+t}}function VB(e,t){we(e.x,function(n){return NM(n,t.x,t.width)}),we(e.y,function(n){return NM(n,t.y,t.height)})}function NM(e,t,n){var a=[0,n],r=e.inverse?1:0;e.setExtent(a[r],a[1-r]),pIe(e,t)}function BM(e,t,n,a,r,o,i){zB(a,r,ri.estimate,t,!1,i);var l=[0,0,0,0];u(0),u(1),c(a,0,NaN),c(a,1,NaN);var s=f8e(l,function(v){return v>0})==null;return Ng(a,l,!0,!0,n),VB(r,a),s;function u(v){we(r[vs[v]],function(f){if(Bh(f.model)){var h=o.ensureRecord(f.model),m=h.labelInfoList;if(m)for(var g=0;g0&&!kh(f)&&f>1e-4&&(v/=f),v}}function mIe(e,t,n,a,r){var o=new CB(yIe);return we(n,function(i){return we(i,function(l){if(Bh(l.model)){var s=!a;l.axisBuilder=dAe(e,t,l.model,r,o,s)}})}),o}function zB(e,t,n,a,r,o){var i=n===ri.determine;we(t,function(u){return we(u,function(c){Bh(c.model)&&(fAe(c.axisBuilder,e,c.model),c.axisBuilder.build(i?{axisTickLabelDetermine:!0}:{axisTickLabelEstimate:!0},{noPxChange:r}))})});var l={x:0,y:0};s(0),s(1);function s(u){l[vs[1-u]]=e[Qd[u]]<=o.refContainer[Qd[u]]*.5?0:1-u===1?2:1}we(t,function(u,c){return we(u,function(d){Bh(d.model)&&((a==="all"||i)&&d.axisBuilder.build({axisName:!0},{nameMarginLevel:l[c]}),i&&d.axisBuilder.build({axisLine:!0}))})})}function gIe(e,t,n){var a,r=e.get("outerBoundsMode",!0);r==="same"?a=t.clone():(r==null||r==="auto")&&(a=wc(e.get("outerBounds",!0)||AB,n.refContainer));var o=e.get("outerBoundsContain",!0),i;o==null||o==="auto"||Pn(["all","axisLabel"],o)<0?i="all":i=o;var l=[R_(jt(e.get("outerBoundsClampWidth",!0),n1[0]),t.width),R_(jt(e.get("outerBoundsClampHeight",!0),n1[1]),t.height)];return{outerBoundsRect:a,parsedOuterBoundsContain:i,outerBoundsClamp:l}}var yIe=function(e,t,n,a,r,o){var i=n.axis.dim==="x"?"y":"x";xB(e,t,n,a,r,o),of(e.nameLocation)||we(t.recordMap[i],function(l){l&&l.labelInfoList&&l.dirVec&&TB(l.labelInfoList,l.dirVec,a,r)})};function bIe(e,t){var n={axesInfo:{},seriesInvolved:!1,coordSysAxesInfo:{},coordSysMap:{}};return _Ie(n,e,t),n.seriesInvolved&&SIe(n,e),n}function _Ie(e,t,n){var a=t.getComponent("tooltip"),r=t.getComponent("axisPointer"),o=r.get("link",!0)||[],i=[];we(n.getCoordinateSystems(),function(l){if(!l.axisPointerEnabled)return;var s=Vh(l.model),u=e.coordSysAxesInfo[s]={};e.coordSysMap[s]=l;var c=l.model,d=c.getModel("tooltip",a);if(we(l.getAxes(),Sa(m,!1,null)),l.getTooltipAxes&&a&&d.get("show")){var v=d.get("trigger")==="axis",f=d.get(["axisPointer","type"])==="cross",h=l.getTooltipAxes(d.get(["axisPointer","axis"]));(v||f)&&we(h.baseAxes,Sa(m,f?"cross":!0,v)),f&&we(h.otherAxes,Sa(m,"cross",!1))}function m(g,y,b){var _=b.model.getModel("axisPointer",r),w=_.get("show");if(!(!w||w==="auto"&&!g&&!T3(_))){y==null&&(y=_.get("triggerTooltip")),_=g?wIe(b,d,r,t,g,y):_;var S=_.get("snap"),k=_.get("triggerEmphasis"),T=Vh(b.model),M=y||S||b.type==="category",E=e.axesInfo[T]={key:T,axis:b,coordSys:l,axisPointerModel:_,triggerTooltip:y,triggerEmphasis:k,involveSeries:M,snap:S,useHandle:T3(_),seriesModels:[],linkGroup:null};u[T]=E,e.seriesInvolved=e.seriesInvolved||M;var A=CIe(o,b);if(A!=null){var I=i[A]||(i[A]={axesInfo:{}});I.axesInfo[T]=E,I.mapper=o[A].mapper,E.linkGroup=I}}}})}function wIe(e,t,n,a,r,o){var i=t.getModel("axisPointer"),l=["type","snap","lineStyle","shadowStyle","label","animation","animationDurationUpdate","animationEasingUpdate","z"],s={};we(l,function(v){s[v]=yn(i.get(v))}),s.snap=e.type!=="category"&&!!o,i.get("type")==="cross"&&(s.type="line");var u=s.label||(s.label={});if(u.show==null&&(u.show=!1),r==="cross"){var c=i.get(["label","show"]);if(u.show=c??!0,!o){var d=s.lineStyle=i.get("crossStyle");d&&zn(u,d.textStyle)}}return e.model.getModel("axisPointer",new fa(s,n,a))}function SIe(e,t){t.eachSeries(function(n){var a=n.coordinateSystem,r=n.get(["tooltip","trigger"],!0),o=n.get(["tooltip","show"],!0);!a||!a.model||r==="none"||r===!1||r==="item"||o===!1||n.get(["axisPointer","show"],!0)===!1||we(e.coordSysAxesInfo[Vh(a.model)],function(i){var l=i.axis;a.getAxis(l.dim)===l&&(i.seriesModels.push(n),i.seriesDataCount==null&&(i.seriesDataCount=0),i.seriesDataCount+=n.getData().count())})})}function CIe(e,t){for(var n=t.model,a=t.dim,r=0;r=0||e===t}function xIe(e){var t=CS(e);if(t){var n=t.axisPointerModel,a=t.axis.scale,r=n.option,o=n.get("status"),i=n.get("value");i!=null&&(i=a.parse(i));var l=T3(n);o==null&&(r.status=l?"show":"hide");var s=a.getExtent();(i==null||i>s[1])&&(i=s[1]),il)return!0;if(i){var s=CS(t).seriesDataCount,u=r.getExtent();return Math.abs(u[0]-u[1])/s>l}return!1}return a===!0},e.prototype.makeElOption=function(t,n,a,r,o){},e.prototype.createPointerEl=function(t,n,a,r){var o=n.pointer;if(o){var i=Ou(t).pointerEl=new Hxe[o.type](FM(n.pointer));t.add(i)}},e.prototype.createLabelEl=function(t,n,a,r){if(n.label){var o=Ou(t).labelEl=new Er(FM(n.label));t.add(o),WM(o,r)}},e.prototype.updatePointerEl=function(t,n,a){var r=Ou(t).pointerEl;r&&n.pointer&&(r.setStyle(n.pointer.style),a(r,{shape:n.pointer.shape}))},e.prototype.updateLabelEl=function(t,n,a,r){var o=Ou(t).labelEl;o&&(o.setStyle(n.label.style),a(o,{x:n.label.x,y:n.label.y}),WM(o,r))},e.prototype._renderHandle=function(t){if(!(this._dragging||!this.updateHandleTransform)){var n=this._axisPointerModel,a=this._api.getZr(),r=this._handle,o=n.getModel("handle"),i=n.get("status");if(!o.get("show")||!i||i==="hide"){r&&a.remove(r),this._handle=null;return}var l;this._handle||(l=!0,r=this._handle=L8(o.get("icon"),{cursor:"move",draggable:!0,onmousemove:function(u){OO(u.event)},onmousedown:Rb(this._onHandleDragMove,this,0,0),drift:Rb(this._onHandleDragMove,this),ondragend:Rb(this._onHandleDragEnd,this)}),a.add(r)),KM(r,n,!1),r.setStyle(o.getItemStyle(null,["color","borderColor","borderWidth","opacity","shadowColor","shadowBlur","shadowOffsetX","shadowOffsetY"]));var s=o.get("size");Lt(s)||(s=[s,s]),r.scaleX=s[0]/2,r.scaleY=s[1]/2,XR(this,"_doDispatchAxisPointer",o.get("throttle")||0,"fixRate"),this._moveHandleToValue(t,l)}},e.prototype._moveHandleToValue=function(t,n){HM(this._axisPointerModel,!n&&this._moveAnimation,this._handle,Nb(this.getHandleTransform(t,this._axisModel,this._axisPointerModel)))},e.prototype._onHandleDragMove=function(t,n){var a=this._handle;if(a){this._dragging=!0;var r=this.updateHandleTransform(Nb(a),[t,n],this._axisModel,this._axisPointerModel);this._payloadInfo=r,a.stopAnimation(),a.attr(Nb(r)),Ou(a).lastProp=null,this._doDispatchAxisPointer()}},e.prototype._doDispatchAxisPointer=function(){var t=this._handle;if(t){var n=this._payloadInfo,a=this._axisModel;this._api.dispatchAction({type:"updateAxisPointer",x:n.cursorPoint[0],y:n.cursorPoint[1],tooltipOption:n.tooltipOption,axesInfo:[{axisDim:a.axis.dim,axisIndex:a.componentIndex}]})}},e.prototype._onHandleDragEnd=function(){this._dragging=!1;var t=this._handle;if(t){var n=this._axisPointerModel.get("value");this._moveHandleToValue(n),this._api.dispatchAction({type:"hideTip"})}},e.prototype.clear=function(t){this._lastValue=null,this._lastStatus=null;var n=t.getZr(),a=this._group,r=this._handle;n&&a&&(this._lastGraphicKey=null,a&&n.remove(a),r&&n.remove(r),this._group=null,this._handle=null,this._payloadInfo=null),o3(this,"_doDispatchAxisPointer")},e.prototype.doClear=function(){},e.prototype.buildLabel=function(t,n,a){return a=a||0,{x:t[a],y:t[1-a],width:n[a],height:n[1-a]}},e}();function HM(e,t,n,a){KB(Ou(n).lastProp,a)||(Ou(n).lastProp=a,t?Ar(n,a,e):(n.stopAnimation(),n.attr(a)))}function KB(e,t){if(Yt(e)&&Yt(t)){var n=!0;return we(t,function(a,r){n=n&&KB(e[r],a)}),!!n}else return e===t}function WM(e,t){e[t.get(["label","show"])?"show":"hide"]()}function Nb(e){return{x:e.x||0,y:e.y||0,rotation:e.rotation||0}}function KM(e,t,n){var a=t.get("z"),r=t.get("zlevel");e&&e.traverse(function(o){o.type!=="group"&&(a!=null&&(o.z=a),r!=null&&(o.zlevel=r),o.silent=n)})}function OIe(e){var t=e.get("type"),n=e.getModel(t+"Style"),a;return t==="line"?(a=n.getLineStyle(),a.fill=null):t==="shadow"&&(a=n.getAreaStyle(),a.stroke=null),a}function $Ie(e,t,n,a,r){var o=n.get("value"),i=UB(o,t.axis,t.ecModel,n.get("seriesDataIndices"),{precision:n.get(["label","precision"]),formatter:n.get(["label","formatter"])}),l=n.getModel("label"),s=y2(l.get("padding")||0),u=l.getFont(),c=ZO(i,u),d=r.position,v=c.width+s[1]+s[3],f=c.height+s[0]+s[2],h=r.align;h==="right"&&(d[0]-=v),h==="center"&&(d[0]-=v/2);var m=r.verticalAlign;m==="bottom"&&(d[1]-=f),m==="middle"&&(d[1]-=f/2),RIe(d,v,f,a);var g=l.get("backgroundColor");(!g||g==="auto")&&(g=t.get(["axisLine","lineStyle","color"])),e.label={x:d[0],y:d[1],style:ef(l,{text:i,font:u,fill:l.getTextColor(),padding:s,backgroundColor:g}),z2:10}}function RIe(e,t,n,a){var r=a.getWidth(),o=a.getHeight();e[0]=Math.min(e[0]+t,r)-t,e[1]=Math.min(e[1]+n,o)-n,e[0]=Math.max(e[0],0),e[1]=Math.max(e[1],0)}function UB(e,t,n,a,r){e=t.scale.parse(e);var o=t.scale.getLabel({value:e},{precision:r.precision}),i=r.formatter;if(i){var l={value:qg(t,{value:e}),axisDimension:t.dim,axisIndex:t.index,seriesData:[]};we(a,function(s){var u=n.getSeriesByIndex(s.seriesIndex),c=s.dataIndexInside,d=u&&u.getDataParams(c);d&&l.seriesData.push(d)}),Ut(i)?o=i.replace("{value}",o):dn(i)&&(o=i(l))}return o}function GB(e,t,n){var a=zi();return o8(a,a,n.rotation),b_(a,a,n.position),I8([e.dataToCoord(t),(n.labelOffset||0)+(n.labelDirection||1)*(n.labelMargin||0)],a)}function NIe(e,t,n,a,r,o){var i=Es.innerTextLayout(n.rotation,0,n.labelDirection);n.labelMargin=r.get(["label","margin"]),$Ie(t,a,r,o,{position:GB(a.axis,e,n),align:i.textAlign,verticalAlign:i.textVerticalAlign})}function BIe(e,t,n){return n=n||0,{x1:e[n],y1:e[1-n],x2:t[n],y2:t[1-n]}}function VIe(e,t,n){return n=n||0,{x:e[n],y:e[1-n],width:t[n],height:t[1-n]}}function zIe(e,t,n){return xf(e,{fromStat:{sers:Xt(t,function(a){return n.getSeriesByIndex(a.seriesIndex)})},min:1}).w}function FIe(e,t,n){return[An(sr(t[0],t[1]),e-n/2),sr(e+n/2,An(t[0],t[1]))]}var HIe=function(e){At(t,e);function t(){return e!==null&&e.apply(this,arguments)||this}return t.prototype.makeElOption=function(n,a,r,o,i){var l=r.axis,s=l.grid,u=o.get("type"),c=l.getGlobalExtent(),d=UM(s,l).getOtherAxis(l).getGlobalExtent(),v=l.toGlobalCoord(l.dataToCoord(a,!0));if(u&&u!=="none"){var f=OIe(o),h=WIe[u](l,v,c,d,o.get("seriesDataIndices"),o.ecModel);h.style=f,n.graphicKey=h.type,n.pointer=h}var m=t1(s.getRect(),r);NIe(a,n,m,r,o,i)},t.prototype.getHandleTransform=function(n,a,r){var o=t1(a.axis.grid.getRect(),a,{labelInside:!1});o.labelMargin=r.get(["handle","margin"]);var i=GB(a.axis,n,o);return{x:i[0],y:i[1],rotation:o.rotation+(o.labelDirection<0?Math.PI:0)}},t.prototype.updateHandleTransform=function(n,a,r,o){var i=r.axis,l=i.grid,s=i.getGlobalExtent(!0),u=UM(l,i).getOtherAxis(i).getGlobalExtent(),c=i.dim==="x"?0:1,d=[n.x,n.y];d[c]+=a[c],d[c]=sr(s[1],d[c]),d[c]=An(s[0],d[c]);var v=(u[1]+u[0])/2,f=[v,v];f[c]=d[c];var h=[{verticalAlign:"middle"},{align:"center"}];return{x:d[0],y:d[1],rotation:n.rotation,cursorPoint:f,tooltipOption:h[c]}},t}(DIe);function UM(e,t){var n={};return n[t.dim+"AxisIndex"]=t.index,e.getCartesian(n)}var WIe={line:function(e,t,n,a){var r=BIe([t,a[0]],[t,a[1]],GM(e));return{type:"Line",subPixelOptimize:!0,shape:r}},shadow:function(e,t,n,a,r,o){var i=zIe(e,r,o),l=a[1]-a[0],s=FIe(t,n,i),u=s[0],c=s[1];return{type:"Rect",shape:VIe([u,a[0]],[c-u,l],GM(e))}}};function GM(e){return e.dim==="x"?0:1}var KIe=function(e){At(t,e);function t(){var n=e!==null&&e.apply(this,arguments)||this;return n.type=t.type,n}return t.type="axisPointer",t.defaultOption={show:"auto",z:50,type:"line",snap:!1,triggerTooltip:!0,triggerEmphasis:!0,value:null,status:null,link:[],animation:null,animationDurationUpdate:200,lineStyle:{color:tn.color.border,width:1,type:"dashed"},shadowStyle:{color:tn.color.shadowTint},label:{show:!0,formatter:null,precision:"auto",margin:3,color:tn.color.neutral00,padding:[5,7,5,7],backgroundColor:tn.color.accent60,borderColor:null,borderWidth:0,borderRadius:3},handle:{show:!1,icon:"M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4h1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7v-1.2h6.6z M13.3,22H6.7v-1.2h6.6z M13.3,19.6H6.7v-1.2h6.6z",size:45,margin:50,color:tn.color.accent40,throttle:40}},t}(Kn),yl=Hn(),UIe=we;function YB(e,t,n){if(!hn.node){var a=t.getZr();yl(a).records||(yl(a).records={}),GIe(a,t);var r=yl(a).records[e]||(yl(a).records[e]={});r.handler=n}}function GIe(e,t){if(yl(e).initialized)return;yl(e).initialized=!0,n("click",Sa(Bb,"click")),n("mousemove",Sa(Bb,"mousemove")),n("mousewheel",Sa(Bb,"mousewheel")),n("globalout",jIe);function n(a,r){e.on(a,function(o){var i=qIe(t);UIe(yl(e).records,function(l){l&&r(l,o,i.dispatchAction)}),YIe(i.pendings,t)})}}function YIe(e,t){var n=e.showTip.length,a=e.hideTip.length,r;n?r=e.showTip[n-1]:a&&(r=e.hideTip[a-1]),r&&(r.dispatchAction=null,t.dispatchAction(r))}function jIe(e,t,n){e.handler("leave",null,n)}function Bb(e,t,n,a){t.handler(e,n,a)}function qIe(e){var t={showTip:[],hideTip:[]},n=function(a){var r=t[a.type];r?r.push(a):(a.dispatchAction=n,e.dispatchAction(a))};return{dispatchAction:n,pendings:t}}function E3(e,t){if(!hn.node){var n=t.getZr(),a=(yl(n).records||{})[e];a&&(yl(n).records[e]=null)}}var XIe=function(e){At(t,e);function t(){var n=e!==null&&e.apply(this,arguments)||this;return n.type=t.type,n}return t.prototype.render=function(n,a,r){var o=a.getComponent("tooltip"),i=n.get("triggerOn")||o&&o.get("triggerOn")||"mousemove|click|mousewheel";YB("axisPointer",r,function(l,s,u){i!=="none"&&(l==="leave"||i.indexOf(l)>=0)&&u({type:"updateAxisPointer",currTrigger:l,x:s&&s.offsetX,y:s&&s.offsetY})})},t.prototype.remove=function(n,a){E3("axisPointer",a)},t.prototype.dispose=function(n,a){E3("axisPointer",a)},t.type="axisPointer",t}(el);function jB(e,t){var n=[],a=e.seriesIndex,r;if(a==null||!(r=t.getSeriesByIndex(a)))return{point:[]};var o=r.getData(),i=yc(o,e);if(i==null||i<0||Lt(i))return{point:[]};var l=o.getItemGraphicEl(i),s=r.coordinateSystem;if(r.getTooltipPosition)n=r.getTooltipPosition(i)||[];else if(s&&s.dataToPoint)if(e.isStacked){var u=s.getBaseAxis(),c=s.getOtherAxis(u),d=c.dim,v=u.dim,f=d==="x"||d==="radius"?1:0,h=o.mapDimension(v),m=[];m[f]=o.get(h,i),m[1-f]=o.get(o.getCalculationInfo("stackResultDimension"),i),n=s.dataToPoint(m)||[]}else n=s.dataToPoint(o.getValues(Xt(s.dimensions,function(y){return o.mapDimension(y)}),i))||[];else if(l){var g=l.getBoundingRect().clone();g.applyTransform(l.transform),n=[g.x+g.width/2,g.y+g.height/2]}return{point:n,el:l}}var YM=Hn();function ZIe(e,t,n){var a=e.currTrigger,r=[e.x,e.y],o=e,i=e.dispatchAction||qn(n.dispatchAction,n),l=t.getComponent("axisPointer").coordSysAxesInfo;if(l){Am(r)&&(r=jB({seriesIndex:o.seriesIndex,dataIndex:o.dataIndex},t).point);var s=Am(r),u=o.axesInfo,c=l.axesInfo,d=a==="leave"||Am(r),v={},f={},h={list:[],map:{}},m={showPointer:Sa(QIe,f),showTooltip:Sa(eLe,h)};we(l.coordSysMap,function(y,b){var _=s||y.containPoint(r);we(l.coordSysAxesInfo[b],function(w,S){var k=w.axis,T=rLe(u,w);if(!d&&_&&(!u||T)){var M=T&&T.value;M==null&&!s&&(M=k.pointToData(r)),M!=null&&jM(w,M,m,!1,v)}})});var g={};return we(c,function(y,b){var _=y.linkGroup;_&&!f[b]&&we(_.axesInfo,function(w,S){var k=f[S];if(w!==y&&k){var T=k.value;_.mapper&&(T=y.axis.scale.parse(_.mapper(T,qM(w),qM(y)))),g[y.key]=T}})}),we(g,function(y,b){jM(c[b],y,m,!0,v)}),tLe(f,c,v),nLe(h,r,e,i),aLe(c,i,n),v}}function jM(e,t,n,a,r){var o=e.axis;if(!(o.scale.isBlank()||!o.containData(t))){if(!e.involveSeries){n.showPointer(e,t);return}var i=JIe(t,e),l=i.payloadBatch,s=i.snapToValue;l[0]&&r.seriesIndex==null&&vt(r,l[0]),!a&&e.snap&&o.containData(s)&&s!=null&&(t=s),n.showPointer(e,t,l),n.showTooltip(e,i,s)}}function JIe(e,t){var n=t.axis,a=n.dim,r=e,o=[],i=Number.MAX_VALUE,l=-1;return we(t.seriesModels,function(s,u){var c=s.getData().mapDimensionsAll(a),d,v;if(s.getAxisTooltipData){var f=s.getAxisTooltipData(c,e,n);v=f.dataIndices,d=f.nestestValue}else{if(v=s.indicesOfNearest(a,c[0],e,n.type==="category"?.5:null),!v.length)return;d=s.getData().get(c[0],v[0])}if(zo(d)){var h=e-d,m=Math.abs(h);m<=i&&((m=0&&l<0)&&(i=m,l=h,r=d,o.length=0),we(v,function(g){o.push({seriesIndex:s.seriesIndex,dataIndexInside:g,dataIndex:s.getData().getRawIndex(g)})}))}}),{payloadBatch:o,snapToValue:r}}function QIe(e,t,n,a){e[t.key]={value:n,payloadBatch:a}}function eLe(e,t,n,a){var r=n.payloadBatch,o=t.axis,i=o.model,l=t.axisPointerModel;if(!(!t.triggerTooltip||!r.length)){var s=t.coordSys.model,u=Vh(s),c=e.map[u];c||(c=e.map[u]={coordSysId:s.id,coordSysIndex:s.componentIndex,coordSysType:s.type,coordSysMainType:s.mainType,dataByAxis:[]},e.list.push(c)),c.dataByAxis.push({axisDim:o.dim,axisIndex:i.componentIndex,axisType:i.type,axisId:i.id,value:a,valueLabelOpt:{precision:l.get(["label","precision"]),formatter:l.get(["label","formatter"])},seriesDataIndices:r.slice()})}}function tLe(e,t,n){var a=n.axesInfo=[];we(t,function(r,o){var i=r.axisPointerModel.option,l=e[o];l?(!r.useHandle&&(i.status="show"),i.value=l.value,i.seriesDataIndices=(l.payloadBatch||[]).slice()):!r.useHandle&&(i.status="hide"),i.status==="show"&&a.push({axisDim:r.axis.dim,axisIndex:r.axis.model.componentIndex,value:i.value})})}function nLe(e,t,n,a){if(Am(t)||!e.list.length){a({type:"hideTip"});return}var r=((e.list[0].dataByAxis[0]||{}).seriesDataIndices||[])[0]||{};a({type:"showTip",escapeConnect:!0,x:t[0],y:t[1],tooltipOption:n.tooltipOption,position:n.position,dataIndexInside:r.dataIndexInside,dataIndex:r.dataIndex,seriesIndex:r.seriesIndex,dataByCoordSys:e.list})}function aLe(e,t,n){var a=n.getZr(),r="axisPointerLastHighlights",o=YM(a)[r]||{},i=YM(a)[r]={};we(e,function(c,d){var v=c.axisPointerModel.option;v.status==="show"&&c.triggerEmphasis&&we(v.seriesDataIndices,function(f){i[f.seriesIndex+"|"+f.dataIndex]=f})});var l=[],s=[];function u(c){return{seriesIndex:c.seriesIndex,dataIndex:c.dataIndex}}we(o,function(c,d){!i[d]&&s.push(u(c))}),we(i,function(c,d){!o[d]&&l.push(u(c))}),s.length&&n.dispatchAction({type:"downplay",escapeConnect:!0,notBlur:!0,batch:s}),l.length&&n.dispatchAction({type:"highlight",escapeConnect:!0,notBlur:!0,batch:l})}function rLe(e,t){for(var n=0;n<(e||[]).length;n++){var a=e[n];if(t.axis.dim===a.axisDim&&t.axis.model.componentIndex===a.axisIndex)return a}}function qM(e){var t=e.axis.model,n={},a=n.axisDim=e.axis.dim;return n.axisIndex=n[a+"AxisIndex"]=t.componentIndex,n.axisName=n[a+"AxisName"]=t.name,n.axisId=n[a+"AxisId"]=t.id,n}function Am(e){return!e||e[0]==null||isNaN(e[0])||e[1]==null||isNaN(e[1])}function qB(e){FB.registerAxisPointerClass("CartesianAxisPointer",HIe),e.registerComponentModel(KIe),e.registerComponentView(XIe),e.registerPreprocessor(function(t){if(t){(!t.axisPointer||t.axisPointer.length===0)&&(t.axisPointer={});var n=t.axisPointer.link;n&&!Lt(n)&&(t.axisPointer.link=[n])}}),e.registerProcessor(e.PRIORITY.PROCESSOR.STATISTIC,{overallReset:function(t,n){t.getComponent("axisPointer").coordSysAxesInfo=bIe(t,n)}}),e.registerAction({type:"updateAxisPointer",event:"updateAxisPointer",update:":updateAxisPointer"},ZIe)}function oLe(e){Ws(PIe),Ws(qB)}function iLe(e,t){var n=y2(t.get("padding")),a=t.getItemStyle(["color","opacity"]);a.fill=t.get("backgroundColor");var r=new ga({shape:{x:e.x-n[3],y:e.y-n[0],width:e.width+n[1]+n[3],height:e.height+n[0]+n[2],r:t.get("borderRadius")},style:a,silent:!0,z2:-1});return r}var lLe=function(e){At(t,e);function t(){var n=e!==null&&e.apply(this,arguments)||this;return n.type=t.type,n}return t.type="tooltip",t.dependencies=["axisPointer"],t.defaultOption={z:60,show:!0,showContent:!0,trigger:"item",triggerOn:"mousemove|click|mousewheel",alwaysShowContent:!1,renderMode:"auto",confine:null,showDelay:0,hideDelay:100,transitionDuration:.4,displayTransition:!0,enterable:!1,backgroundColor:tn.color.neutral00,shadowBlur:10,shadowColor:"rgba(0, 0, 0, .2)",shadowOffsetX:1,shadowOffsetY:2,borderRadius:4,borderWidth:1,defaultBorderColor:tn.color.border,padding:null,extraCssText:"",axisPointer:{type:"line",axis:"auto",animation:"auto",animationDurationUpdate:200,animationEasingUpdate:"exponentialOut",crossStyle:{color:tn.color.borderShade,width:1,type:"dashed",textStyle:{}}},textStyle:{color:tn.color.tertiary,fontSize:14}},t}(Kn);function XB(e){var t=e.get("confine");return t!=null?!!t:e.get("renderMode")==="richText"}function ZB(e){if(hn.domSupported){for(var t=document.documentElement.style,n=0,a=e.length;n-1?(l+="top:50%",s+="translateY(-50%) rotate("+(u=o==="left"?-225:-45)+"deg)"):(l+="left:50%",s+="translateX(-50%) rotate("+(u=o==="top"?225:45)+"deg)");var c=u*Math.PI/180,d=i+r,v=d*Math.abs(Math.cos(c))+d*Math.abs(Math.sin(c)),f=Math.round(((v-Math.SQRT2*r)/2+Math.SQRT2*r-(v-d)/2)*100)/100;l+=";"+o+":-"+f+"px";var h=t+" solid "+r+"px;",m=["position:absolute;width:"+i+"px;height:"+i+"px;z-index:-1;",l+";"+s+";","border-bottom:"+h,"border-right:"+h,"background-color:"+a+";"];return'
'}function hLe(e,t,n){var a="cubic-bezier(0.23,1,0.32,1)",r="",o="";return n&&(r=" "+e/2+"s "+a,o="opacity"+r+",visibility"+r),t||(r=" "+e+"s "+a,o+=(o.length?",":"")+(hn.transformSupported?""+xS+r:",left"+r+",top"+r)),cLe+":"+o}function XM(e,t,n){var a=e.toFixed(0)+"px",r=t.toFixed(0)+"px";if(!hn.transformSupported)return n?"top:"+r+";left:"+a+";":[["top",r],["left",a]];var o=hn.transform3dSupported,i="translate"+(o?"3d":"")+"("+a+","+r+(o?",0":"")+")";return n?"top:0;left:0;"+xS+":"+i+";":[["top",0],["left",0],[JB,i]]}function pLe(e){var t=[],n=e.get("fontSize"),a=e.getTextColor();a&&t.push("color:"+a),t.push("font:"+e.getFont());var r=jt(e.get("lineHeight"),Math.round(n*3/2));n&&t.push("line-height:"+r+"px");var o=e.get("textShadowColor"),i=e.get("textShadowBlur")||0,l=e.get("textShadowOffsetX")||0,s=e.get("textShadowOffsetY")||0;return o&&i&&t.push("text-shadow:"+l+"px "+s+"px "+i+"px "+o),we(["decoration","align"],function(u){var c=e.get(u);c&&t.push("text-"+u+":"+c)}),t.join(";")}function mLe(e,t,n,a){var r=[],o=e.get("transitionDuration"),i=e.get("backgroundColor"),l=e.get("shadowBlur"),s=e.get("shadowColor"),u=e.get("shadowOffsetX"),c=e.get("shadowOffsetY"),d=e.getModel("textStyle"),v=jR(e,"html"),f=u+"px "+c+"px "+l+"px "+s;return r.push("box-shadow:"+f),t&&o>0&&r.push(hLe(o,n,a)),i&&r.push("background-color:"+i),we(["width","color","radius"],function(h){var m="border-"+h,g=yR(m),y=e.get(g);y!=null&&r.push(m+":"+y+(h==="color"?"":"px"))}),r.push(pLe(d)),v!=null&&r.push("padding:"+y2(v).join("px ")+"px"),r.join(";")+";"}function ZM(e,t,n,a,r){var o=t&&t.painter;if(n){var i=o&&o.getViewportRoot();i&&E8e(e,i,n,a,r)}else{e[0]=a,e[1]=r;var l=o&&o.getViewportRootOffset();l&&(e[0]+=l.offsetLeft,e[1]+=l.offsetTop)}e[2]=e[0]/t.getWidth(),e[3]=e[1]/t.getHeight()}var gLe=function(){function e(t,n){if(this._show=!1,this._styleCoord=[0,0,0,0],this._enterable=!0,this._alwaysShowContent=!1,this._firstShow=!0,this._longHide=!0,hn.wxa)return null;var a=document.createElement("div");a.domBelongToZr=!0,this.el=a;var r=this._zr=t.getZr(),o=n.appendTo,i=o&&(Ut(o)?document.querySelector(o):xh(o)?o:dn(o)&&o(t.getDom()));ZM(this._styleCoord,r,i,t.getWidth()/2,t.getHeight()/2),(i||t.getDom()).appendChild(a),this._api=t,this._container=i;var l=this;a.onmouseenter=function(){l._enterable&&(clearTimeout(l._hideTimeout),l._show=!0),l._inContent=!0},a.onmousemove=function(s){if(s=s||window.event,!l._enterable){var u=r.handler,c=r.painter.getViewportRoot();go(c,s,!0),u.dispatch("mousemove",s)}},a.onmouseleave=function(){l._inContent=!1,l._enterable&&l._show&&l.hideLater(l._hideDelay)}}return e.prototype.update=function(t){if(!this._container){var n=this._api.getDom(),a=uLe(n,"position"),r=n.style;r.position!=="absolute"&&a!=="absolute"&&(r.position="relative")}var o=t.get("alwaysShowContent");o&&this._moveIfResized(),this._alwaysShowContent=o,this._enableDisplayTransition=t.get("displayTransition")&&t.get("transitionDuration")>0,this.el.className=t.get("className")||""},e.prototype.show=function(t,n){clearTimeout(this._hideTimeout),clearTimeout(this._longHideTimeout);var a=this.el,r=a.style,o=this._styleCoord;a.innerHTML?r.cssText=dLe+mLe(t,!this._firstShow,this._longHide,this._enableDisplayTransition)+XM(o[0],o[1],!0)+("border-color:"+_c(n)+";")+(t.get("extraCssText")||"")+(";pointer-events:"+(this._enterable?"auto":"none")):r.display="none",this._show=!0,this._firstShow=!1,this._longHide=!1},e.prototype.setContent=function(t,n,a,r,o){var i=this.el;if(t==null){i.innerHTML="";return}var l="";if(Ut(o)&&a.get("trigger")==="item"&&!XB(a)&&(l=vLe(a,r,o)),Ut(t))i.innerHTML=t+l;else if(t){i.innerHTML="",Lt(t)||(t=[t]);for(var s=0;s=0?this._tryShow(o,i):r==="leave"&&this._hide(i))},this))},t.prototype._keepShow=function(){var n=this._tooltipModel,a=this._ecModel,r=this._api,o=n.get("triggerOn");if(n.get("trigger")!=="axis"&&(this._lastDataByCoordSys=null,this._cbParamsList=null),this._lastX!=null&&this._lastY!=null&&o!=="none"&&o!=="click"){var i=this;clearTimeout(this._refreshUpdateTimeout),this._refreshUpdateTimeout=setTimeout(function(){!r.isDisposed()&&i.manuallyShowTip(n,a,r,{x:i._lastX,y:i._lastY,dataByCoordSys:i._lastDataByCoordSys})})}},t.prototype.manuallyShowTip=function(n,a,r,o){if(!(o.from===this.uid||hn.node||!r.getDom())){var i=eE(o,r);this._ticket="";var l=o.dataByCoordSys,s=xLe(o,a,r);if(s){var u=s.el.getBoundingRect().clone();u.applyTransform(s.el.transform),this._tryShow({offsetX:u.x+u.width/2,offsetY:u.y+u.height/2,target:s.el,position:o.position,positionDefault:"bottom"},i)}else if(o.tooltip&&o.x!=null&&o.y!=null){var c=bLe;c.x=o.x,c.y=o.y,c.update(),In(c).tooltipConfig={name:null,option:o.tooltip},this._tryShow({offsetX:o.x,offsetY:o.y,target:c},i)}else if(l)this._tryShow({offsetX:o.x,offsetY:o.y,position:o.position,dataByCoordSys:l,tooltipOption:o.tooltipOption},i);else if(o.seriesIndex!=null){if(this._manuallyAxisShowTip(n,a,r,o))return;var d=jB(o,a),v=d.point[0],f=d.point[1];v!=null&&f!=null&&this._tryShow({offsetX:v,offsetY:f,target:d.el,position:o.position,positionDefault:"bottom"},i)}else o.x!=null&&o.y!=null&&(r.dispatchAction({type:"updateAxisPointer",x:o.x,y:o.y}),this._tryShow({offsetX:o.x,offsetY:o.y,position:o.position,target:r.getZr().findHover(o.x,o.y).target},i))}},t.prototype.manuallyHideTip=function(n,a,r,o){var i=this._tooltipContent;this._tooltipModel&&i.hideLater(this._tooltipModel.get("hideDelay")),this._lastX=this._lastY=this._lastDataByCoordSys=null,this._cbParamsList=null,o.from!==this.uid&&this._hide(eE(o,r))},t.prototype._manuallyAxisShowTip=function(n,a,r,o){var i=o.seriesIndex,l=o.dataIndex,s=a.getComponent("axisPointer").coordSysAxesInfo;if(!(i==null||l==null||s==null)){var u=a.getSeriesByIndex(i);if(u){var c=u.getData(),d=Qf([c.getItemModel(l),u,(u.coordinateSystem||{}).model],this._tooltipModel);if(d.get("trigger")==="axis")return r.dispatchAction({type:"updateAxisPointer",seriesIndex:i,dataIndex:l,position:o.position}),!0}}},t.prototype._tryShow=function(n,a){var r=n.target,o=this._tooltipModel;if(o){this._lastX=n.offsetX,this._lastY=n.offsetY;var i=n.dataByCoordSys;if(i&&i.length)this._showAxisTooltip(i,n);else if(r){var l=In(r);if(l.ssrType==="legend")return;this._lastDataByCoordSys=null,this._cbParamsList=null;var s,u;gv(r,function(c){if(c.tooltipDisabled)return s=u=null,!0;s||u||(In(c).dataIndex!=null?s=c:In(c).tooltipConfig!=null&&(u=c))},!0),s?this._showSeriesItemTooltip(n,s,a):u?this._showComponentItemTooltip(n,u,a):this._hide(a)}else this._lastDataByCoordSys=null,this._cbParamsList=null,this._hide(a)}},t.prototype._showOrMove=function(n,a){var r=n.get("showDelay");a=qn(a,this),clearTimeout(this._showTimout),r>0?this._showTimout=setTimeout(a,r):a()},t.prototype._showAxisTooltip=function(n,a){var r=this._ecModel,o=this._tooltipModel,i=[a.offsetX,a.offsetY],l=Qf([a.tooltipOption],o),s=this._renderMode,u=[],c=Oh("section",{blocks:[],noHeader:!0}),d=[],v=new gb;we(n,function(b){we(b.dataByAxis,function(_){var w=r.getComponent(_.axisDim+"Axis",_.axisIndex),S=_.value,k=w.axis,T=k.scale.parse(S);if(!(!w||S==null)){var M=UB(S,k,r,_.seriesDataIndices,_.valueLabelOpt),E=Oh("section",{header:M,noHeader:!Di(M),sortBlocks:!0,blocks:[]});c.blocks.push(E),we(_.seriesDataIndices,function(A){var I=r.getSeriesByIndex(A.seriesIndex),P=A.dataIndexInside,R=I.getDataParams(P);if(!(R.dataIndex<0)){R.axisDim=_.axisDim,R.axisIndex=_.axisIndex,R.axisType=_.axisType,R.axisId=_.axisId,R.axisValue=qg(w.axis,{value:T}),R.axisValueLabel=M,R.marker=v.makeTooltipMarker("item",_c(R.color),s);var G=aT(I.formatTooltip(P,!0,null)),K=G.frag;if(K){var H=Qf([I],o).get("valueFormatter");E.blocks.push(H?vt({valueFormatter:H},K):K)}G.text&&d.push(G.text),u.push(R)}})}})}),c.blocks.reverse(),d.reverse();var f=a.position,h=l.get("order"),m=sT(c,v,s,h,r.get("useUTC"),l.get("textStyle"));m&&d.unshift(m);var g=s==="richText"?` + +`:"
",y=d.join(g);this._showOrMove(l,function(){this._updateContentNotChangedOnAxis(n,u)?this._updatePosition(l,f,i[0],i[1],this._tooltipContent,u):this._showTooltipContent(l,y,u,Math.random()+"",i[0],i[1],f,null,v)})},t.prototype._showSeriesItemTooltip=function(n,a,r){var o=this._ecModel,i=In(a),l=i.seriesIndex,s=o.getSeriesByIndex(l),u=i.dataModel||s,c=i.dataIndex,d=i.dataType,v=u.getData(d),f=this._renderMode,h=n.positionDefault,m=Qf([v.getItemModel(c),u,s&&(s.coordinateSystem||{}).model],this._tooltipModel,h?{position:h}:null),g=m.get("trigger");if(!(g!=null&&g!=="item")){var y=u.getDataParams(c,d),b=new gb;y.marker=b.makeTooltipMarker("item",_c(y.color),f);var _=aT(u.formatTooltip(c,!1,d)),w=m.get("order"),S=m.get("valueFormatter"),k=_.frag,T=k?sT(S?vt({valueFormatter:S},k):k,b,f,w,o.get("useUTC"),m.get("textStyle")):_.text,M="item_"+u.name+"_"+c;this._showOrMove(m,function(){this._showTooltipContent(m,T,y,M,n.offsetX,n.offsetY,n.position,n.target,b)}),r({type:"showTip",dataIndexInside:c,dataIndex:v.getRawIndex(c),seriesIndex:l,from:this.uid})}},t.prototype._showComponentItemTooltip=function(n,a,r){var o=this._renderMode==="html",i=In(a),l=i.tooltipConfig,s=l.option||{},u=s.encodeHTMLContent;if(Ut(s)){var c=s;s={content:c,formatter:c},u=!0}u&&o&&s.content&&(s=yn(s),s.content=Or(s.content));var d=[s],v=this._ecModel.getComponent(i.componentMainType,i.componentIndex);v&&d.push(v),d.push({formatter:s.content});var f=n.positionDefault,h=Qf(d,this._tooltipModel,f?{position:f}:null),m=h.get("content"),g=Math.random()+"",y=new gb;this._showOrMove(h,function(){var b=yn(h.get("formatterParams")||{});this._showTooltipContent(h,m,b,g,n.offsetX,n.offsetY,n.position,a,y)}),r({type:"showTip",from:this.uid})},t.prototype._showTooltipContent=function(n,a,r,o,i,l,s,u,c){if(this._ticket="",!(!n.get("showContent")||!n.get("show"))){var d=this._tooltipContent;d.setEnterable(n.get("enterable"));var v=n.get("formatter");s=s||n.get("position");var f=a,h=this._getNearestPoint([i,l],r,n.get("trigger"),n.get("borderColor"),n.get("defaultBorderColor",!0)),m=h.color;if(v)if(Ut(v)){var g=n.ecModel.get("useUTC"),y=Lt(r)?r[0]:r,b=y&&y.axisType&&y.axisType.indexOf("time")>=0;f=v,b&&(f=g2(y.axisValue,f,g)),f=bR(f,r,!0)}else if(dn(v)){var _=qn(function(w,S){w===this._ticket&&(d.setContent(S,c,n,m,s),this._updatePosition(n,s,i,l,d,r,u))},this);this._ticket=o,f=v(r,o,_)}else f=v;d.setContent(f,c,n,m,s),d.show(n,m),this._updatePosition(n,s,i,l,d,r,u)}},t.prototype._getNearestPoint=function(n,a,r,o,i){if(r==="axis"||Lt(a))return{color:o||i};if(!Lt(a))return{color:o||a.color||a.borderColor}},t.prototype._updatePosition=function(n,a,r,o,i,l,s){var u=this._api.getWidth(),c=this._api.getHeight();a=a||n.get("position");var d=i.getSize(),v=n.get("align"),f=n.get("verticalAlign"),h=s&&s.getBoundingRect().clone();if(s&&h.applyTransform(s.transform),dn(a)&&(a=a([r,o],l,i.el,h,{viewSize:[u,c],contentSize:d.slice()})),Lt(a))r=sa(a[0],u),o=sa(a[1],c);else if(Yt(a)){var m=a;m.width=d[0],m.height=d[1];var g=wc(m,{width:u,height:c});r=g.x,o=g.y,v=null,f=null}else if(Ut(a)&&s){var y=CLe(a,h,d,n.get("borderWidth"));r=y[0],o=y[1]}else{var y=wLe(r,o,i,u,c,v?null:20,f?null:20);r=y[0],o=y[1]}if(v&&(r-=tE(v)?d[0]/2:v==="right"?d[0]:0),f&&(o-=tE(f)?d[1]/2:f==="bottom"?d[1]:0),XB(n)){var y=SLe(r,o,i,u,c);r=y[0],o=y[1]}i.moveTo(r,o)},t.prototype._updateContentNotChangedOnAxis=function(n,a){var r=this._lastDataByCoordSys,o=this._cbParamsList,i=!!r&&r.length===n.length;return i&&we(r,function(l,s){var u=l.dataByAxis||[],c=n[s]||{},d=c.dataByAxis||[];i=i&&u.length===d.length,i&&we(u,function(v,f){var h=d[f]||{},m=v.seriesDataIndices||[],g=h.seriesDataIndices||[];i=i&&v.value===h.value&&v.axisType===h.axisType&&v.axisId===h.axisId&&m.length===g.length,i&&we(m,function(y,b){var _=g[b];i=i&&y.seriesIndex===_.seriesIndex&&y.dataIndex===_.dataIndex}),o&&we(v.seriesDataIndices,function(y){var b=y.seriesIndex,_=a[b],w=o[b];_&&w&&w.data!==_.data&&(i=!1)})})}),this._lastDataByCoordSys=n,this._cbParamsList=a,!!i},t.prototype._hide=function(n){this._lastDataByCoordSys=null,this._cbParamsList=null,n({type:"hideTip",from:this.uid})},t.prototype.dispose=function(n,a){hn.node||!a.getDom()||(o3(this,"_updatePosition"),this._tooltipContent.dispose(),E3("itemTooltip",a),this._tooltipContent=null,this._tooltipModel=null,this._lastDataByCoordSys=null,this._cbParamsList=null)},t.type="tooltip",t}(el);function Qf(e,t,n){var a=t.ecModel,r;n?(r=new fa(n,a,a),r=new fa(t.option,r,a)):r=t;for(var o=e.length-1;o>=0;o--){var i=e[o];i&&(i instanceof fa&&(i=i.get("tooltip",!0)),Ut(i)&&(i={formatter:i}),i&&(r=new fa(i,r,a)))}return r}function eE(e,t){return e.dispatchAction||qn(t.dispatchAction,t)}function wLe(e,t,n,a,r,o,i){var l=n.getSize(),s=l[0],u=l[1];return o!=null&&(e+s+o+2>a?e-=s+o:e+=o),i!=null&&(t+u+i>r?t-=u+i:t+=i),[e,t]}function SLe(e,t,n,a,r){var o=n.getSize(),i=o[0],l=o[1];return e=Math.min(e+i,a)-i,t=Math.min(t+l,r)-l,e=Math.max(e,0),t=Math.max(t,0),[e,t]}function CLe(e,t,n,a){var r=n[0],o=n[1],i=Math.ceil(Math.SQRT2*a)+8,l=0,s=0,u=t.width,c=t.height;switch(e){case"inside":l=t.x+u/2-r/2,s=t.y+c/2-o/2;break;case"top":l=t.x+u/2-r/2,s=t.y-o-i;break;case"bottom":l=t.x+u/2-r/2,s=t.y+c+i;break;case"left":l=t.x-r-i,s=t.y+c/2-o/2;break;case"right":l=t.x+u+i,s=t.y+c/2-o/2}return[l,s]}function tE(e){return e==="center"||e==="middle"}function xLe(e,t,n){var a=f8(e).queryOptionMap,r=a.keys()[0];if(!(!r||r==="series")){var o=_p(t,r,a.get(r),{useDefault:!1,enableAll:!1,enableNone:!1}),i=o.models[0];if(i){var l=n.getViewOfComponentModel(i),s;if(l.group.traverse(function(u){var c=In(u).tooltipConfig;if(c&&c.name===e.name)return s=u,!0}),s)return{componentMainType:r,componentIndex:i.componentIndex,el:s}}}}function kLe(e){Ws(qB),e.registerComponentModel(lLe),e.registerComponentView(_Le),e.registerAction({type:"showTip",event:"showTip",update:"tooltip:manuallyShowTip"},lr),e.registerAction({type:"hideTip",event:"hideTip",update:"tooltip:manuallyHideTip"},lr)}var TLe=function(e,t){if(t==="all")return{type:"all",title:e.getLocaleModel().get(["legend","selector","all"])};if(t==="inverse")return{type:"inverse",title:e.getLocaleModel().get(["legend","selector","inverse"])}},A3=function(e){At(t,e);function t(){var n=e!==null&&e.apply(this,arguments)||this;return n.type=t.type,n.layoutMode={type:"box",ignoreSize:!0},n}return t.prototype.init=function(n,a,r){this.mergeDefaultAndTheme(n,r),n.selected=n.selected||{},this._updateSelector(n)},t.prototype.mergeOption=function(n,a){e.prototype.mergeOption.call(this,n,a),this._updateSelector(n)},t.prototype._updateSelector=function(n){var a=n.selector,r=this.ecModel;a===!0&&(a=n.selector=["all","inverse"]),Lt(a)&&we(a,function(o,i){Ut(o)&&(o={type:o}),a[i]=Bn(o,TLe(r,o.type))})},t.prototype.optionUpdated=function(){this._updateData(this.ecModel);var n=this._data;if(n[0]&&this.get("selectedMode")==="single"){for(var a=!1,r=0;r=0},t.prototype.getOrient=function(){return this.get("orient")==="vertical"?{index:1,name:"vertical"}:{index:0,name:"horizontal"}},t.type="legend.plain",t.dependencies=["series"],t.defaultOption={z:4,show:!0,orient:"horizontal",left:"center",bottom:tn.size.m,align:"auto",backgroundColor:tn.color.transparent,borderColor:tn.color.border,borderRadius:0,borderWidth:0,padding:5,itemGap:8,itemWidth:25,itemHeight:14,symbolRotate:"inherit",symbolKeepAspect:!0,inactiveColor:tn.color.disabled,inactiveBorderColor:tn.color.disabled,inactiveBorderWidth:"auto",itemStyle:{color:"inherit",opacity:"inherit",borderColor:"inherit",borderWidth:"auto",borderCap:"inherit",borderJoin:"inherit",borderDashOffset:"inherit",borderMiterLimit:"inherit"},lineStyle:{width:"auto",color:"inherit",inactiveColor:tn.color.disabled,inactiveWidth:2,opacity:"inherit",type:"inherit",cap:"inherit",join:"inherit",dashOffset:"inherit",miterLimit:"inherit"},textStyle:{color:tn.color.secondary},selectedMode:!0,selector:!1,selectorLabel:{show:!0,borderRadius:10,padding:[3,5,3,5],fontSize:12,fontFamily:"sans-serif",color:tn.color.tertiary,borderWidth:1,borderColor:tn.color.border},emphasis:{selectorLabel:{show:!0,color:tn.color.quaternary}},selectorPosition:"auto",selectorItemGap:7,selectorButtonGap:10,tooltip:{show:!1},triggerEvent:!1},t}(Kn),ed=Sa,I3=we,W0=Ha,eV=function(e){At(t,e);function t(){var n=e!==null&&e.apply(this,arguments)||this;return n.type=t.type,n.newlineDisabled=!1,n}return t.prototype.init=function(){this.group.add(this._contentGroup=new W0),this.group.add(this._selectorGroup=new W0),this._isFirstRender=!0},t.prototype.getContentGroup=function(){return this._contentGroup},t.prototype.getSelectorGroup=function(){return this._selectorGroup},t.prototype.render=function(n,a,r){var o=this._isFirstRender;if(this._isFirstRender=!1,this.resetInner(),!!n.get("show",!0)){var i=n.get("align"),l=n.get("orient");(!i||i==="auto")&&(i=n.get("left")==="right"&&l==="vertical"?"right":"left");var s=n.get("selector",!0),u=n.get("selectorPosition",!0);s&&(!u||u==="auto")&&(u=l==="horizontal"?"end":"start"),this.renderInner(i,n,a,r,s,l,u);var c=Y8(n,r).refContainer,d=n.getBoxLayoutParams(),v=n.get("padding"),f=wc(d,c,v),h=this.layoutInner(n,i,f,o,s,u),m=wc(zn({width:h.width,height:h.height},d),c,v);this.group.x=m.x-h.x,this.group.y=m.y-h.y,this.group.markRedraw(),this.group.add(this._backgroundEl=iLe(h,n))}},t.prototype.resetInner=function(){this.getContentGroup().removeAll(),this._backgroundEl&&this.group.remove(this._backgroundEl),this.getSelectorGroup().removeAll()},t.prototype.renderInner=function(n,a,r,o,i,l,s){var u=this.getContentGroup(),c=an(),d=a.get("selectedMode"),v=a.get("triggerEvent"),f=[];r.eachRawSeries(function(h){!h.get("legendHoverLink")&&f.push(h.id)}),I3(a.getData(),function(h,m){var g=this,y=h.get("name");if(!this.newlineDisabled&&(y===""||y===` +`)){var b=new W0;b.newline=!0,u.add(b);return}var _=r.getSeriesByName(y)[0];if(!c.get(y))if(_){var w=_.getData(),S=w.getVisual("legendLineStyle")||{},k=w.getVisual("legendIcon"),T=w.getVisual("style"),M=this._createItem(_,y,m,h,a,n,S,T,k,d,o);M.on("click",ed(nE,y,null,o,f)).on("mouseover",ed(L3,_.name,null,o,f)).on("mouseout",ed(P3,_.name,null,o,f)),r.ssr&&M.eachChild(function(E){var A=In(E);A.seriesIndex=_.seriesIndex,A.dataIndex=m,A.ssrType="legend"}),v&&M.eachChild(function(E){g.packEventData(E,a,_,m,y)}),c.set(y,!0)}else r.eachRawSeries(function(E){var A=this;if(!c.get(y)&&E.legendVisualProvider){var I=E.legendVisualProvider;if(!I.containName(y))return;var P=I.indexOfName(y),R=I.getItemVisual(P,"style"),G=I.getItemVisual(P,"legendIcon"),K=Fi(R.fill);K&&K[3]===0&&(K[3]=.2,R=vt(vt({},R),{fill:mp(K,"rgba")}));var H=this._createItem(E,y,m,h,a,n,{},R,G,d,o);H.on("click",ed(nE,null,y,o,f)).on("mouseover",ed(L3,null,y,o,f)).on("mouseout",ed(P3,null,y,o,f)),r.ssr&&H.eachChild(function(z){var B=In(z);B.seriesIndex=E.seriesIndex,B.dataIndex=m,B.ssrType="legend"}),v&&H.eachChild(function(z){A.packEventData(z,a,E,m,y)}),c.set(y,!0)}},this)},this),i&&this._createSelector(i,a,o,l,s)},t.prototype.packEventData=function(n,a,r,o,i){var l={componentType:"legend",componentIndex:a.componentIndex,dataIndex:o,value:i,seriesIndex:r.seriesIndex};In(n).eventData=l},t.prototype._createSelector=function(n,a,r,o,i){var l=this.getSelectorGroup();I3(n,function(u){var c=u.type,d=new Er({style:{x:0,y:0,align:"center",verticalAlign:"middle"},onclick:function(){r.dispatchAction({type:c==="all"?"legendAllSelect":"legendInverseSelect",legendId:a.id})}});l.add(d);var v=a.getModel("selectorLabel"),f=a.getModel(["emphasis","selectorLabel"]);Cp(d,{normal:v,emphasis:f},{defaultText:u.title}),G_(d)})},t.prototype._createItem=function(n,a,r,o,i,l,s,u,c,d,v){var f=n.visualDrawType,h=i.get("itemWidth"),m=i.get("itemHeight"),g=i.isSelected(a),y=o.get("symbolRotate"),b=o.get("symbolKeepAspect"),_=o.get("icon");c=_||c||"roundRect";var w=MLe(c,o,s,u,f,g,v),S=new W0,k=o.getModel("textStyle");if(dn(n.getLegendIcon)&&(!_||_==="inherit"))S.add(n.getLegendIcon({itemWidth:h,itemHeight:m,icon:c,iconRotate:y,itemStyle:w.itemStyle,lineStyle:w.lineStyle,symbolKeepAspect:b}));else{var T=_==="inherit"&&n.getData().getVisual("symbol")?y==="inherit"?n.getData().getVisual("symbolRotate"):y:0;S.add(ELe({itemWidth:h,itemHeight:m,icon:c,iconRotate:T,itemStyle:w.itemStyle,symbolKeepAspect:b}))}var M=l==="left"?h+5:-5,E=l,A=i.get("formatter"),I=a;Ut(A)&&A?I=A.replace("{name}",a??""):dn(A)&&(I=A(a));var P=g?k.getTextColor():o.get("inactiveColor");S.add(new Er({style:ef(k,{text:I,x:M,y:m/2,fill:P,align:E,verticalAlign:"middle"},{inheritColor:P})}));var R=new ga({shape:S.getBoundingRect(),style:{fill:"transparent"}}),G=o.getModel("tooltip");return G.get("show")&&d2({el:R,componentModel:i,itemName:a,itemTooltipOption:G.option}),S.add(R),S.eachChild(function(K){K.silent=!0}),R.silent=!d,this.getContentGroup().add(S),G_(S),S.__legendDataIndex=r,S},t.prototype.layoutInner=function(n,a,r,o,i,l){var s=this.getContentGroup(),u=this.getSelectorGroup();Kv(n.get("orient"),s,n.get("itemGap"),r.width,r.height);var c=s.getBoundingRect(),d=[-c.x,-c.y];if(u.markRedraw(),s.markRedraw(),i){Kv("horizontal",u,n.get("selectorItemGap",!0));var v=u.getBoundingRect(),f=[-v.x,-v.y],h=n.get("selectorButtonGap",!0),m=n.getOrient().index,g=m===0?"width":"height",y=m===0?"height":"width",b=m===0?"y":"x";l==="end"?f[m]+=c[g]+h:d[m]+=v[g]+h,f[1-m]+=c[y]/2-v[y]/2,u.x=f[0],u.y=f[1],s.x=d[0],s.y=d[1];var _={x:0,y:0};return _[g]=c[g]+h+v[g],_[y]=Math.max(c[y],v[y]),_[b]=Math.min(0,v[b]+f[1-m]),_}else return s.x=d[0],s.y=d[1],this.group.getBoundingRect()},t.prototype.remove=function(){this.getContentGroup().removeAll(),this._isFirstRender=!0},t.type="legend.plain",t}(el);function MLe(e,t,n,a,r,o,i){function l(g,y){g.lineWidth==="auto"&&(g.lineWidth=y.lineWidth>0?2:0),I3(g,function(b,_){g[_]==="inherit"&&(g[_]=y[_])})}var s=t.getModel("itemStyle"),u=s.getItemStyle(),c=e.lastIndexOf("empty",0)===0?"fill":"stroke",d=s.getShallow("decal");u.decal=!d||d==="inherit"?a.decal:c3(d,i),u.fill==="inherit"&&(u.fill=a[r]),u.stroke==="inherit"&&(u.stroke=a[c]),u.opacity==="inherit"&&(u.opacity=(r==="fill"?a:n).opacity),l(u,a);var v=t.getModel("lineStyle"),f=v.getLineStyle();if(l(f,n),u.fill==="auto"&&(u.fill=a.fill),u.stroke==="auto"&&(u.stroke=a.fill),f.stroke==="auto"&&(f.stroke=a.fill),!o){var h=t.get("inactiveBorderWidth"),m=u[c];u.lineWidth=h==="auto"?a.lineWidth>0&&m?2:0:u.lineWidth,u.fill=t.get("inactiveColor"),u.stroke=t.get("inactiveBorderColor"),f.stroke=v.get("inactiveColor"),f.lineWidth=v.get("inactiveWidth")}return{itemStyle:u,lineStyle:f}}function ELe(e){var t=e.icon||"roundRect",n=nf(t,0,0,e.itemWidth,e.itemHeight,e.itemStyle.fill,e.symbolKeepAspect);return n.setStyle(e.itemStyle),n.rotation=(e.iconRotate||0)*Math.PI/180,n.setOrigin([e.itemWidth/2,e.itemHeight/2]),t.indexOf("empty")>-1&&(n.style.stroke=n.style.fill,n.style.fill=tn.color.neutral00,n.style.lineWidth=2),n}function nE(e,t,n,a){P3(e,t,n,a),n.dispatchAction({type:"legendToggleSelect",name:e??t}),L3(e,t,n,a)}function L3(e,t,n,a){n.usingTHL()||n.dispatchAction({type:"highlight",seriesName:e,name:t,excludeSeriesId:a})}function P3(e,t,n,a){n.usingTHL()||n.dispatchAction({type:"downplay",seriesName:e,name:t,excludeSeriesId:a})}function ev(e,t,n){var a=e==="allSelect"||e==="inverseSelect",r={},o=[];n.eachComponent({mainType:"legend",query:t},function(l){a?l[e]():l[e](t.name),aE(l,r),o.push(l.componentIndex)});var i={};return n.eachComponent("legend",function(l){we(r,function(s,u){l[s?"select":"unSelect"](u)}),aE(l,i)}),a?{selected:i,legendIndex:o}:{name:t.name,selected:i}}function aE(e,t){var n=t||{};return we(e.getData(),function(a){var r=a.get("name");if(!(r===` +`||r==="")){var o=e.isSelected(r);Sr(n,r)?n[r]=n[r]&&o:n[r]=o}}),n}function ALe(e){e.registerAction("legendToggleSelect","legendselectchanged",Sa(ev,"toggleSelected")),e.registerAction("legendAllSelect","legendselectall",Sa(ev,"allSelect")),e.registerAction("legendInverseSelect","legendinverseselect",Sa(ev,"inverseSelect")),e.registerAction("legendSelect","legendselected",Sa(ev,"select")),e.registerAction("legendUnSelect","legendunselected",Sa(ev,"unSelect"))}var ILe=h8(LLe);function LLe(e){var t=e.findComponents({mainType:"legend"});t&&t.length&&e.filterSeries(function(n){for(var a=0;ar[i],g=[-f.x,-f.y];a||(g[o]=c[u]);var y=[0,0],b=[-h.x,-h.y],_=jt(n.get("pageButtonGap",!0),n.get("itemGap",!0));if(m){var w=n.get("pageButtonPosition",!0);w==="end"?b[o]+=r[i]-h[i]:y[o]+=h[i]+_}b[1-o]+=f[l]/2-h[l]/2,c.setPosition(g),d.setPosition(y),v.setPosition(b);var S={x:0,y:0};if(S[i]=m?r[i]:f[i],S[l]=Math.max(f[l],h[l]),S[s]=Math.min(0,h[s]+b[1-o]),d.__rectSize=r[i],m){var k={x:0,y:0};k[i]=Math.max(r[i]-h[i]-_,0),k[l]=S[l],d.setClipPath(new ga({shape:k})),d.__rectSize=k[i]}else v.eachChild(function(M){M.attr({invisible:!0,silent:!0})});var T=this._getPageInfo(n);return T.pageIndex!=null&&Ar(c,{x:T.contentPosition[0],y:T.contentPosition[1]},m?n:null),this._updatePageInfoView(n,T),S},t.prototype._pageGo=function(n,a,r){var o=this._getPageInfo(a)[n];o!=null&&r.dispatchAction({type:"legendScroll",scrollDataIndex:o,legendId:a.id})},t.prototype._updatePageInfoView=function(n,a){var r=this._controllerGroup;we(["pagePrev","pageNext"],function(c){var d=c+"DataIndex",v=a[d]!=null,f=r.childOfName(c);f&&(f.setStyle("fill",v?n.get("pageIconColor",!0):n.get("pageIconInactiveColor",!0)),f.cursor=v?"pointer":"default")});var o=r.childOfName("pageText"),i=n.get("pageFormatter"),l=a.pageIndex,s=l!=null?l+1:0,u=a.pageCount;o&&i&&o.setStyle("text",Ut(i)?i.replace("{current}",s==null?"":s+"").replace("{total}",u==null?"":u+""):i({current:s,total:u}))},t.prototype._getPageInfo=function(n){var a=n.get("scrollDataIndex",!0),r=this.getContentGroup(),o=this._containerGroup.__rectSize,i=n.getOrient().index,l=Vb[i],s=zb[i],u=this._findTargetItemIndex(a),c=r.children(),d=c[u],v=c.length,f=v?1:0,h={contentPosition:[r.x,r.y],pageCount:f,pageIndex:f-1,pagePrevDataIndex:null,pageNextDataIndex:null};if(!d)return h;var m=w(d);h.contentPosition[i]=-m.s;for(var g=u+1,y=m,b=m,_=null;g<=v;++g)_=w(c[g]),(!_&&b.e>y.s+o||_&&!S(_,y.s))&&(b.i>y.i?y=b:y=_,y&&(h.pageNextDataIndex==null&&(h.pageNextDataIndex=y.i),++h.pageCount)),b=_;for(var g=u-1,y=m,b=m,_=null;g>=-1;--g)_=w(c[g]),(!_||!S(b,_.s))&&y.i=T&&k.s<=T+o}},t.prototype._findTargetItemIndex=function(n){if(!this._showController)return 0;var a,r=this.getContentGroup(),o;return r.eachChild(function(i,l){var s=i.__legendDataIndex;o==null&&s!=null&&(o=l),s===n&&(a=l)}),a??o},t.type="legend.scroll",t}(eV);function OLe(e){e.registerAction("legendScroll","legendscroll",function(t,n){var a=t.scrollDataIndex;a!=null&&n.eachComponent({mainType:"legend",subType:"scroll",query:t},function(r){r.setScrollDataIndex(a)})})}function $Le(e){Ws(tV),e.registerComponentModel(PLe),e.registerComponentView(DLe),OLe(e)}function RLe(e){Ws(tV),Ws($Le)}function iE(e,t,n){var a=oo.createCanvas(),r=t.getWidth(),o=t.getHeight(),i=a.style;return i&&(i.position="absolute",i.left="0",i.top="0",i.width=r+"px",i.height=o+"px",a.setAttribute("data-zr-dom-id",e)),a.width=r*n,a.height=o*n,a}function Fb(e){return!e.__cursors.get(Y$)}function lE(e){var t=e.__cursors.get(Y$);return{startIdx:t?t.startIdx:0,endIdx:t?t.endIdx:0}}var nV=function(e){At(t,e);function t(n,a,r){var o=e.call(this)||this;o.motionBlur=!1,o.lastFrameAlpha=.7,o.dpr=1,o.virtual=!1,o.config={},o.zlevel=0,o.zlevel2=bm,o.maxRepaintRectCount=5,o.__dirty=!0,o.__firstTimePaint=!0,o.__prevIdx={startIdx:0,endIdx:0};var i;r=r||xg,typeof n=="string"?i=iE(n,a,r):Yt(n)&&(i=n,n=i.id),o.id=n,o.dom=i;var l=i.style;return l&&(PO(i),i.onselectstart=function(){return!1},l.padding="0",l.margin="0",l.borderWidth="0"),o.painter=a,o.dpr=r,o}return t.prototype.afterBrush=function(){this.__prevIdx=lE(this)},t.prototype.initContext=function(){this.ctx=this.dom.getContext("2d"),this.ctx.dpr=this.dpr},t.prototype.setUnpainted=function(){this.__firstTimePaint=!0},t.prototype.createBackBuffer=function(){var n=this.dpr;this.domBack=iE("back-"+this.id,this.painter,n),this.ctxBack=this.domBack.getContext("2d"),n!==1&&this.ctxBack.scale(n,n)},t.prototype.createRepaintRects=function(n,a,r,o){if(this.__firstTimePaint)return this.__firstTimePaint=!1,null;var i=[],l=this.maxRepaintRectCount,s=!1,u=new ln(0,0,0,0);function c(w){if(!(!w.isFinite()||w.isZero()))if(i.length===0){var S=new ln(0,0,0,0);S.copy(w),i.push(S)}else{for(var k=!1,T=1/0,M=0,E=0;E=l)}}for(var d=lE(this),v=d.startIdx;v=0)&&(i=!0)}),!(!i&&!o.__dirty)){var l=a._opts.useDirtyRect&&!Fb(o)?o.createRepaintRects(t,n,a._width,a._height):null,s=a._i.layerStack[0],u=!0;if(o.__dirty){u=!1,o.__dirty=!1;var c=o.zlevel===s.zl&&o.zlevel2===s.zl2?a._backgroundColor:null;o.clear(!1,c,l)}K0(o,function(d){var v=a._paintPerCursor(o,d,t,l,u);r=r&&v})}},U0),hn.wxa&&Za(this._i,function(o){o&&o.ctx&&o.ctx.draw&&o.ctx.draw()}),r},e.prototype._paintPerCursor=function(t,n,a,r,o){var i=t.ctx;if(r)if(!r.length)n.drawIdx=n.endIdx;else for(var l=this.dpr,s=0;s=n.endIdx},e.prototype._paintPerCursorInRect=function(t,n,a,r,o){for(var i={inHover:!1,allClipped:!1,prevEl:null,viewWidth:this._width,viewHeight:this._height,beforeBrushParam:{contentRetained:o}},l=t.ctx,s=Fb(t),u=s&&oo.getTime(),c=n.drawIdx,d=n.notClearIdx,v=d>=0?Math.min(d,c):c;v15){v++;break}}}}Ed(l,i),n.drawIdx=Math.max(v,c)},e.prototype.getLayer=function(t,n){return this._ensureLayer(t,0,n)},e.prototype._ensureLayer=function(t,n,a){n=n||0;var r=this._singleCanvas;r&&!this._needsManuallyCompositing&&(t=Tu,n=0);var o=Kb(this._i,t)[n];return o||(o=uE("zr_"+t+"."+n,this,t,n),this._layerConfig[t]&&Bn(o,this._layerConfig[t],!0),(a||r&&t!==Tu)&&(o.virtual=!0),this._insertLayer(o,t,n,!1),o.initContext()),o},e.prototype.insertLayer=function(t,n){this._insertLayer(n,t,0,!1)},e.prototype._insertLayer=function(t,n,a,r){var o=this._i,i=o.layers,l=o.layerStack,s=this._domRoot,u=null;if(!(i[n]&&i[n][a])&&BLe(t)){for(var c=l.length,d=0;d0&&(u=Kb(o,l[d-1].zl)[l[d-1].zl2]),l.splice(d,0,{zl:n,zl2:a}),Kb(o,n)[a]=t,!r&&!t.virtual)if(u){var v=u.dom;v.nextSibling?s.insertBefore(t.dom,v.nextSibling):s.appendChild(t.dom)}else s.firstChild?s.insertBefore(t.dom,s.firstChild):s.appendChild(t.dom);t.painter||(t.painter=this)}},e.prototype.eachLayer=function(t,n){return Za(this._i,function(a,r){t.call(n,a,r)})},e.prototype.eachBuiltinLayer=function(t,n){return Za(this._i,function(a,r){t.call(n,a,r)},jv)},e.prototype.eachOtherLayer=function(t,n){return Za(this._i,function(a,r){t.call(n,a,r)},D3)},e.prototype.getLayers=function(){var t={};return Za(this._i,function(n,a,r){t[n.id]=n}),t},e.prototype._updateLayerStatus=function(t,n){var a=this;if(a._singleCanvas)for(var r=1;r=0;_--){var w=b.get(y[_]);if(!w.used)g.__dirty=!0,b.removeKey(y[_]),y.splice(_,1);else{var S=w.endIdxNew;(Fb(g)?S=0;r--){var o=n[r];if(o.zl===t){var i=a[t][o.zl2];if(i.__builtin__)continue;if(n.splice(r,1),a[t][o.zl2]=void 0,!i.virtual){var l=i.dom.parentNode;l&&l.removeChild(i.dom)}}}},e.prototype.resize=function(t,n){if(this._domRoot.style){var a=this._domRoot;a.style.display="none";var r=this._opts,o=this.root;t!=null&&(r.width=t),n!=null&&(r.height=n),t=T0(o,0,r),n=T0(o,1,r),a.style.display="",(this._width!==t||n!==this._height)&&(a.style.width=t+"px",a.style.height=n+"px",Za(this._i,function(i){i.resize(t,n)}),this.refresh({paintAll:!0})),this._width=t,this._height=n}else{if(t==null||n==null)return;this._width=t,this._height=n,this._ensureLayer(Tu).resize(t,n)}return this},e.prototype.clearLayer=function(t){we(this._i.layers[t],function(n){n&&!n.__builtin__&&n.clear()})},e.prototype.dispose=function(){this.root.innerHTML="",this.root=this.storage=this._domRoot=this._i=null},e.prototype.getRenderedCanvas=function(t){if(t=t||{},this._singleCanvas&&!this._compositeManually)return this._i.layers[Tu][0].dom;var n=new nV("image",this,t.pixelRatio||this.dpr);n.initContext(),n.clear(!1,t.backgroundColor||this._backgroundColor);var a=n.ctx;if(t.pixelRatio<=this.dpr){this.refresh();var r=n.dom.width,o=n.dom.height;Za(this._i,function(d){d.__builtin__?a.drawImage(d.dom,0,0,r,o):d.renderToCanvas&&(a.save(),d.renderToCanvas(a),a.restore())})}else{for(var i={inHover:!1,viewWidth:this._width,viewHeight:this._height,beforeBrushParam:{}},l=this.storage.getDisplayList(!0),s=0,u=l.length;s({}));throw new Error(c.error||`HTTP ${s.status}`)}return(await s.json()).data}const yt={dashboard:(e={})=>Mt("/dashboard",e),epayInfo:()=>Mt("/epay-info"),channels:()=>Mt("/channels"),settings:()=>Mt("/settings"),saveSettings:e=>Mt("/settings",{},{method:"POST",body:JSON.stringify(e),headers:{"Content-Type":"application/json",Accept:"application/json"}}),settingsKeys:()=>Mt("/settings/keys"),resetAdmin:e=>Mt("/auth/reset",{},{method:"POST",body:JSON.stringify({password:e}),headers:{"Content-Type":"application/json"}}),toggleChannel:e=>Mt(`/settings/channel/${e}/toggle`,{},{method:"POST"}),toggleChannelMode:(e,t)=>Mt(`/settings/channel/${e}/mode/${t}/toggle`,{},{method:"POST"}),orders:e=>Mt("/orders",e),orderEvents:(e,t=20)=>Mt(`/orders/${e}/events`,{limit:t}),notifyTasks:e=>Mt("/notify-tasks",e),notifyStats:()=>Mt("/notify-stats"),logs:e=>Mt("/logs",e),ledger:e=>Mt("/ledger",e),ledgerVerify:()=>Mt("/ledger/verify",{},{method:"POST"}),ledgerSnapshots:e=>Mt("/ledger/snapshots",e),createSnapshot:()=>Mt("/ledger/snapshot",{},{method:"POST"}),ledgerPublicKey:()=>Mt("/ledger/public-key"),ledgerExport:async()=>{const e=wv(),t=await fetch(_v+"/ledger/export",{headers:{Authorization:"Bearer "+e}});if(!t.ok){const o=await t.json().catch(()=>({}));throw new Error(o.error||"导出失败")}const n=await t.blob(),a=URL.createObjectURL(n),r=document.createElement("a");r.href=a,r.download="ledger_audit_"+new Date().toISOString().slice(0,10)+".csv",document.body.appendChild(r),r.click(),document.body.removeChild(r),URL.revokeObjectURL(a)},tunnel:()=>Mt("/tunnel"),accounts:e=>Mt("/accounts",e),getAccount:e=>Mt(`/accounts/${e}`),accountSecret:e=>Mt(`/accounts/${e}/secret`),createAccount:e=>Mt("/accounts",{},{method:"POST",body:JSON.stringify(e),headers:{"Content-Type":"application/json"}}),updateAccount:(e,t)=>Mt(`/accounts/${e}`,{},{method:"PUT",body:JSON.stringify(t),headers:{"Content-Type":"application/json"}}),deleteAccount:e=>Mt(`/accounts/${e}`,{},{method:"DELETE"}),freezeAccount:e=>Mt(`/accounts/${e}/freeze`,{},{method:"POST"}),unfreezeAccount:e=>Mt(`/accounts/${e}/unfreeze`,{},{method:"POST"}),enableAccount:e=>Mt(`/accounts/${e}/enable`,{},{method:"POST"}),disableAccount:e=>Mt(`/accounts/${e}/disable`,{},{method:"POST"}),accountLogs:(e,t)=>Mt(`/accounts/${e}/logs`,{limit:t}),decodeQRCode:async e=>{const t=new FormData;t.append("file",e);const n=wv(),a=await fetch(_v+"/accounts/decode-qrcode",{method:"POST",headers:{Authorization:"Bearer "+n},body:t});if(a.status===401)throw sessionStorage.removeItem("admin_token"),window.location.reload(),new Error("请重新登录");const r=await a.json();if(!a.ok)throw new Error(r.error||`HTTP ${a.status}`);return r.data.content},upstreams:()=>Mt("/upstreams"),getUpstream:e=>Mt(`/upstreams/${e}`),upstreamSecret:e=>Mt(`/upstreams/${e}/secret`),createUpstream:e=>Mt("/upstreams",{},{method:"POST",body:JSON.stringify(e),headers:{"Content-Type":"application/json"}}),updateUpstream:(e,t)=>Mt(`/upstreams/${e}`,{},{method:"PUT",body:JSON.stringify(t),headers:{"Content-Type":"application/json"}}),deleteUpstream:e=>Mt(`/upstreams/${e}`,{},{method:"DELETE"}),enableUpstream:e=>Mt(`/upstreams/${e}/enable`,{},{method:"POST"}),disableUpstream:e=>Mt(`/upstreams/${e}/disable`,{},{method:"POST"}),upstreamLogs:(e,t)=>Mt(`/upstreams/${e}/logs`,{limit:t}),listExceptions:e=>Mt("/reconciler/exceptions",e),getException:e=>Mt(`/reconciler/exceptions/${e}`),requeryException:e=>Mt(`/reconciler/exceptions/${e}/requery`,{},{method:"POST"}),resolveException:(e,t)=>Mt(`/reconciler/exceptions/${e}/resolve`,{},{method:"POST",body:JSON.stringify(t),headers:{"Content-Type":"application/json"}}),reopenException:e=>Mt(`/reconciler/exceptions/${e}/reopen`,{},{method:"POST"}),addException:e=>Mt("/reconciler/exceptions",{},{method:"POST",body:JSON.stringify(e),headers:{"Content-Type":"application/json"}}),listDeadNotify:()=>Mt("/reconciler/notify-dead"),retryDeadNotify:e=>Mt(`/reconciler/notify-dead/${e}/retry`,{},{method:"POST"}),deleteDeadNotify:e=>Mt(`/reconciler/notify-dead/${e}/delete`,{},{method:"POST"}),dailyReport:e=>Mt("/reconciler/daily-report",e),personalDevices:()=>Mt("/personal/devices"),deletePersonalDevice:e=>Mt(`/personal/devices/${e}`,{},{method:"DELETE"}),freezePersonalDevice:e=>Mt(`/personal/devices/${e}/freeze`,{},{method:"POST"}),unfreezePersonalDevice:e=>Mt(`/personal/devices/${e}/unfreeze`,{},{method:"POST"}),restartPersonalDevice:e=>Mt(`/personal/devices/${e}/restart`,{},{method:"POST"}),personalPushes:e=>Mt("/personal/pushes",e),personalUnmatched:e=>Mt("/personal/unmatched",e),resolveUnmatched:(e,t)=>Mt(`/personal/unmatched/${e}/resolve`,{},{method:"POST",body:JSON.stringify(t),headers:{"Content-Type":"application/json"}}),personalConfigQR:async e=>{const t=wv(),n=await fetch(_v+`/personal/devices/${e}/config-qr`,{headers:{Authorization:"Bearer "+t}});if(n.status===401)throw sessionStorage.removeItem("admin_token"),window.location.reload(),new Error("请重新登录");const a=await n.json().catch(()=>({}));if(!n.ok)throw new Error(a.error||`HTTP ${n.status}`);const r=await n.blob();return URL.createObjectURL(r)},personalScreenshot:async e=>{const t=wv(),n=await fetch(_v+`/personal/devices/${e}/screenshot`,{headers:{Authorization:"Bearer "+t}});if(n.status===401)throw sessionStorage.removeItem("admin_token"),window.location.reload(),new Error("请重新登录");const a=n.headers.get("Content-Type")||"";if(!n.ok||!a.includes("image")){const o=await n.json().catch(()=>({}));throw new Error(o.error||`HTTP ${n.status}`)}const r=await n.blob();return URL.createObjectURL(r)},personalDeviceAction:(e,t)=>Mt(`/personal/devices/${e}/action`,{},{method:"POST",body:JSON.stringify(t),headers:{"Content-Type":"application/json"}}),personalSetAdb:(e,t)=>Mt(`/personal/devices/${e}/adb`,{},{method:"POST",body:JSON.stringify(t),headers:{"Content-Type":"application/json"}}),personalDeviceAccounts:e=>Mt(`/personal/devices/${e}/accounts`),personalAddDeviceAccount:(e,t)=>Mt(`/personal/devices/${e}/accounts`,{},{method:"POST",body:JSON.stringify(t),headers:{"Content-Type":"application/json"}}),personalRemoveDeviceAccount:(e,t)=>Mt(`/personal/devices/${e}/accounts/${t}`,{},{method:"DELETE"}),personalBatchRestart:e=>Mt("/personal/devices/batch/restart",{},{method:"POST",body:JSON.stringify(e),headers:{"Content-Type":"application/json"}}),personalBatchStatus:e=>Mt("/personal/devices/batch/status",{},{method:"POST",body:JSON.stringify(e),headers:{"Content-Type":"application/json"}}),personalUsbDetect:()=>Mt("/personal/usb-detect"),personalWizardStart:e=>Mt("/personal/devices/wizard-start",{},{method:"POST",body:JSON.stringify(e),headers:{"Content-Type":"application/json"}}),personalWizardWifi:e=>Mt(`/personal/devices/${e}/wizard-wifi`,{},{method:"POST"}),personalWizardVerify:e=>Mt(`/personal/devices/${e}/wizard-verify`,{},{method:"POST"}),personalWifiReconnect:e=>Mt(`/personal/devices/${e}/wifi-reconnect`,{},{method:"POST"}),personalQrcodes:()=>Mt("/personal/qrcodes"),personalDeleteQrcode:(e,t,n=0)=>Mt(`/personal/qrcodes/${e}/${t}/${n}`,{},{method:"DELETE"}),personalCaptureWechatQrcode:(e,t=0)=>Mt(`/personal/qrcodes/${e}/wechat/capture?user=${t}`,{},{method:"POST"}),personalCaptureAlipayQrcode:(e,t=999)=>Mt(`/personal/qrcodes/${e}/alipay/capture?user=${t}`,{},{method:"POST"})},KLe={class:"dash"},ULe={class:"kpi-grid"},GLe={class:"kpi-label"},YLe={class:"kpi-value font-mono"},jLe={class:"kpi-delta"},qLe={class:"chart-row"},XLe={class:"pg-card chart-card wide"},ZLe={class:"pg-card-header"},JLe={class:"trend-controls"},QLe={class:"pg-card chart-card"},ePe={class:"chart-row"},tPe={class:"pg-card chart-card wide"},nPe={class:"pg-card chart-card"},aPe={class:"status-row"},rPe={class:"pg-card status-card"},oPe={class:"pg-card-header"},iPe={class:"pg-card-body"},lPe={class:"tunnel-info"},sPe={class:"tunnel-details"},uPe={class:"tunnel-url font-mono"},cPe={key:0,class:"tunnel-cb text-muted"},dPe={class:"pg-card status-card"},fPe={class:"pg-card-body"},vPe={class:"queue-grid"},hPe={class:"queue-item"},pPe={class:"queue-num font-mono text-success"},mPe={class:"queue-item"},gPe={class:"queue-num font-mono text-warning"},yPe={class:"queue-item"},bPe={class:"queue-num font-mono text-danger"},_Pe={class:"queue-item"},wPe={class:"queue-num font-mono"},SPe={class:"rate-label text-muted"},CPe={key:0,class:"pg-card"},xPe={class:"pg-card-header"},kPe={style:{display:"flex","align-items":"center",gap:"8px"}},TPe={class:"pg-card-body"},MPe={class:"ledger-grid"},EPe={class:"ledger-item"},APe={class:"ledger-num font-mono"},IPe={class:"ledger-item"},LPe={class:"ledger-num font-mono text-muted",style:{"font-size":"12px"}},PPe={class:"ledger-item"},DPe={class:"ledger-num text-muted",style:{"font-size":"12px"}},OPe={class:"ledger-item"},$Pe={class:"pg-card"},RPe={class:"pg-card-header"},NPe={class:"font-mono"},BPe={__name:"Dashboard",props:{refreshKey:Number},emits:["tunnel","goto"],setup(e,{emit:t}){Ws([K7e,KAe,rIe,oLe,kLe,RLe,WLe]);const n=t,a=e,r=F({}),o=F(!1),i=F(14),l=F(null),s=F([]),u=F(null),c=F(!1),d=D(()=>r.value.ledger_last_hash||""),v=D(()=>d.value?d.value.slice(0,12)+"…":"—"),f=D(()=>r.value.ledger_last_time?r.value.ledger_last_time.slice(0,19).replace("T"," "):"—");async function h(){c.value=!0;try{const V=await yt.ledgerVerify();u.value=V,V.valid?We.success("账本校验通过,共 "+V.total_entries+" 条"):We.error("账本校验失败,"+(V.errors||[]).length+" 处错误")}catch(V){We.error("校验失败: "+V.message)}finally{c.value=!1}}const m=F(null),g=F(null),y=F(null),b=F(null);let _=[];const w=D(()=>{const V=r.value.trend||[],Y=new Date().toLocaleDateString("en-CA"),X=V.find(re=>re.date===Y)||{total_orders:0,success_orders:0,amount_minor:0},ee=X.total_orders>0?Math.round(X.success_orders/X.total_orders*100):0;return[{label:"今日订单",value:(X.total_orders??0).toLocaleString(),delta:"成功 "+(X.success_orders??0)+" 单"},{label:"今日交易额",value:"¥"+((X.amount_minor??0)/100).toFixed(2),delta:"成功订单金额"},{label:"成功率",value:ee+"%",delta:"今日成功率"},{label:"待支付",value:(r.value.wait_orders??0).toLocaleString(),delta:"累计待支付"},{label:"累计交易额",value:"¥"+((r.value.total_amount_minor??0)/100).toFixed(2),delta:"全部成功订单"}]}),S=D(()=>r.value.tunnel||{}),k=D(()=>S.value.running&&S.value.healthy),T=D(()=>S.value.running?S.value.healthy?"正常运行":"异常":"未连接"),M=D(()=>(r.value.notify_success??0)+(r.value.notify_pending??0)+(r.value.notify_dead??0)),E=D(()=>M.value>0?Math.round((r.value.notify_success??0)/M.value*100):100),A=D(()=>{const V=document.documentElement.classList.contains("dark");return E.value>=99?V?"#22C55E":"#16A34A":E.value>=90?V?"#D97706":"#B45309":V?"#EF4444":"#DC2626"});function I(){const V=document.documentElement.classList.contains("dark");return{accent:"#7170FF",text:V?"#C4C4C8":"#3F3F46",muted:V?"#52525B":"#A1A1AA",border:V?"rgba(255,255,255,0.07)":"rgba(0,0,0,0.06)",bg:V?"#18181B":"#F4F4F5",success:V?"#4ADE80":"#22A069",warning:V?"#FCD34D":"#C27803",danger:V?"#F87171":"#D92D20"}}function P(){const V=I(),Y=r.value.trend||[],X=Y.map(le=>le.date.slice(5)),ee=Y.map(le=>le.total_orders),re=Y.map(le=>le.success_orders),ie=Y.map(le=>(le.amount_minor/100).toFixed(2));if(m.value){const le=yv(m.value)||L0(m.value);le.setOption({tooltip:{trigger:"axis",backgroundColor:V.bg,borderColor:V.border,textStyle:{color:V.text,fontSize:12},axisPointer:{lineStyle:{color:V.border}}},legend:{data:["订单数","成功"],textStyle:{color:V.muted,fontSize:11},top:0},grid:{left:40,right:16,top:32,bottom:10,containLabel:!0},xAxis:{type:"category",data:X,axisLine:{lineStyle:{color:V.border}},axisTick:{show:!1},axisLabel:{color:V.muted,fontSize:11}},yAxis:{type:"value",splitLine:{show:!1},axisLine:{show:!1},axisTick:{show:!1},axisLabel:{color:V.muted,fontSize:11}},series:[{name:"订单数",type:"line",smooth:!0,data:ee,symbol:"circle",symbolSize:5,lineStyle:{width:2,color:V.accent},itemStyle:{color:V.accent}},{name:"成功",type:"line",smooth:!0,data:re,symbol:"circle",symbolSize:4,lineStyle:{width:1.5,color:V.muted,type:"dashed"},itemStyle:{color:V.muted}}]},!0),_.push(le)}if(g.value){const le=yv(g.value)||L0(g.value),ve=s.value.map(J=>({name:J.name,value:J.total_orders})),he=["#7170FF","#9B9BF5","#C5C5FF","#DEDEDFF","#EBEBFF"];le.setOption({tooltip:{trigger:"item",backgroundColor:V.bg,borderColor:V.border,textStyle:{color:V.text,fontSize:12},formatter:"{b}: {c} 单 ({d}%)"},legend:{bottom:4,textStyle:{color:V.muted,fontSize:11}},series:[{type:"pie",radius:["44%","66%"],center:["50%","48%"],avoidLabelOverlap:!0,itemStyle:{borderRadius:4,borderColor:V.bg,borderWidth:2},label:{show:!1},emphasis:{label:{show:!0,fontSize:13,fontWeight:590,color:V.text}},data:ve.map((J,Z)=>({...J,itemStyle:{color:he[Z%he.length]}}))}]},!0),_.push(le)}if(y.value){const le=yv(y.value)||L0(y.value);le.setOption({tooltip:{trigger:"axis",backgroundColor:V.bg,borderColor:V.border,textStyle:{color:V.text,fontSize:12},formatter:ve=>`${ve[0].axisValue}
交易额: ¥${ve[0].value}`},grid:{left:56,right:16,top:12,bottom:10,containLabel:!0},xAxis:{type:"category",data:X,axisLine:{lineStyle:{color:V.border}},axisTick:{show:!1},axisLabel:{color:V.muted,fontSize:11}},yAxis:{type:"value",splitLine:{show:!1},axisLine:{show:!1},axisTick:{show:!1},axisLabel:{color:V.muted,fontSize:11,formatter:ve=>"¥"+ve}},series:[{type:"bar",data:ie,barWidth:"50%",itemStyle:{color:V.accent,borderRadius:[3,3,0,0]}}]},!0),_.push(le)}if(b.value){const le=yv(b.value)||L0(b.value),ve=[{name:"成功",value:r.value.success_orders??0,color:V.accent},{name:"待支付",value:r.value.wait_orders??0,color:V.muted},{name:"已关闭",value:r.value.closed_orders??0,color:V.muted}];le.setOption({tooltip:{trigger:"axis",backgroundColor:V.bg,borderColor:V.border,textStyle:{color:V.text,fontSize:12}},grid:{left:12,right:40,top:8,bottom:10,containLabel:!0},xAxis:{type:"value",splitLine:{show:!1},axisLine:{show:!1},axisTick:{show:!1},axisLabel:{color:V.muted,fontSize:11}},yAxis:{type:"category",data:ve.map(he=>he.name),axisLine:{show:!1},axisTick:{show:!1},axisLabel:{color:V.text,fontSize:12}},series:[{type:"bar",barWidth:"38%",data:ve.map(he=>({value:he.value,itemStyle:{color:he.color,borderRadius:[0,3,3,0]}})),label:{show:!0,position:"right",color:V.muted,fontSize:11}}]},!0),_.push(le)}}async function R(){o.value=!0;try{let V={};l.value&&l.value.length===2?V={start:l.value[0],end:l.value[1]}:V={days:i.value};const[Y,X]=await Promise.all([yt.dashboard(V),yt.channels()]);r.value=Y,s.value=X,n("tunnel",Y.tunnel),setTimeout(P,50)}catch(V){We.error("加载失败: "+V.message)}finally{o.value=!1}}function G(){l.value=null,H()}function K(V){V&&H()}function H(){_.forEach(V=>V.dispose()),_=[],R()}ye(()=>a.refreshKey,R),ye(i,G);function z(){_.forEach(V=>V.resize())}function B(V){return{WAIT_BUYER_PAY:"待支付",TRADE_SUCCESS:"成功",TRADE_CLOSED:"已关闭",CONFIRMING:"确认中",FAILED:"失败"}[V]||V}function W(V){return{WAIT_BUYER_PAY:"warning",TRADE_SUCCESS:"success",TRADE_CLOSED:"info",CONFIRMING:"warning",FAILED:"danger"}[V]||"info"}function N(V){return V?new Date(V).toLocaleString("zh-CN",{hour12:!1}):"—"}return mt(()=>{R(),window.addEventListener("resize",z)}),rn(()=>{window.removeEventListener("resize",z),_.forEach(V=>V.dispose()),_=[]}),(V,Y)=>{var se,fe,ge,Se,De;const X=be("el-radio-button"),ee=be("el-radio-group"),re=be("el-date-picker"),ie=be("Connection"),le=be("el-icon"),ve=be("el-progress"),he=be("el-tag"),J=be("el-button"),Z=be("el-table-column"),j=be("el-table"),oe=Gr("loading");return st((x(),O("div",KLe,[C("div",ULe,[(x(!0),O(Ve,null,dt(w.value,Te=>(x(),O("div",{key:Te.label,class:"kpi-card pg-card"},[C("div",GLe,Q(Te.label),1),C("div",YLe,Q(Te.value),1),C("div",jLe,Q(Te.delta),1)]))),128))]),C("div",qLe,[C("div",XLe,[C("div",ZLe,[Y[6]||(Y[6]=C("span",{class:"chart-title"},"订单趋势",-1)),C("div",JLe,[L(ee,{modelValue:i.value,"onUpdate:modelValue":Y[0]||(Y[0]=Te=>i.value=Te),size:"small",onChange:G},{default:$(()=>[L(X,{value:7},{default:$(()=>[...Y[3]||(Y[3]=[ue("7天",-1)])]),_:1}),L(X,{value:14},{default:$(()=>[...Y[4]||(Y[4]=[ue("14天",-1)])]),_:1}),L(X,{value:30},{default:$(()=>[...Y[5]||(Y[5]=[ue("30天",-1)])]),_:1})]),_:1},8,["modelValue"]),L(re,{modelValue:l.value,"onUpdate:modelValue":Y[1]||(Y[1]=Te=>l.value=Te),type:"daterange","range-separator":"至","start-placeholder":"开始日期","end-placeholder":"结束日期","value-format":"YYYY-MM-DD",size:"small",clearable:!1,"disabled-date":Te=>Te.getTime()>Date.now(),style:{width:"230px"},onChange:K},null,8,["modelValue","disabled-date"])])]),C("div",{ref_key:"trendRef",ref:m,class:"chart-box"},null,512)]),C("div",QLe,[Y[7]||(Y[7]=C("div",{class:"pg-card-header"},[C("span",{class:"chart-title"},"通道占比")],-1)),C("div",{ref_key:"channelPieRef",ref:g,class:"chart-box"},null,512)])]),C("div",ePe,[C("div",tPe,[Y[8]||(Y[8]=C("div",{class:"pg-card-header"},[C("span",{class:"chart-title"},"交易金额")],-1)),C("div",{ref_key:"amountRef",ref:y,class:"chart-box"},null,512)]),C("div",nPe,[Y[9]||(Y[9]=C("div",{class:"pg-card-header"},[C("span",{class:"chart-title"},"订单状态")],-1)),C("div",{ref_key:"statusRef",ref:b,class:"chart-box"},null,512)])]),C("div",aPe,[C("div",rPe,[C("div",oPe,[Y[11]||(Y[11]=C("span",{class:"chart-title"},"公网隧道",-1)),C("div",{class:U(["tunnel-badge",{ok:k.value}])},[Y[10]||(Y[10]=C("span",{class:"dot"},null,-1)),ue(" "+Q(T.value),1)],2)]),C("div",iPe,[C("div",lPe,[L(le,{size:18,class:"tunnel-icon"},{default:$(()=>[L(ie)]),_:1}),C("div",sPe,[C("div",uPe,Q(((se=S.value)==null?void 0:se.url)||"未连接"),1),(fe=S.value)!=null&&fe.callback?(x(),O("div",cPe,Q(S.value.callback),1)):te("",!0)])])])]),C("div",dPe,[Y[16]||(Y[16]=C("div",{class:"pg-card-header"},[C("span",{class:"chart-title"},"回调队列")],-1)),C("div",fPe,[C("div",vPe,[C("div",hPe,[C("div",pPe,Q(r.value.notify_success??0),1),Y[12]||(Y[12]=C("div",{class:"queue-label"},"成功",-1))]),C("div",mPe,[C("div",gPe,Q(r.value.notify_pending??0),1),Y[13]||(Y[13]=C("div",{class:"queue-label"},"待重试",-1))]),C("div",yPe,[C("div",bPe,Q(r.value.notify_dead??0),1),Y[14]||(Y[14]=C("div",{class:"queue-label"},"死信",-1))]),C("div",_Pe,[C("div",wPe,Q(r.value.ledger_count??0),1),Y[15]||(Y[15]=C("div",{class:"queue-label"},"账本条目",-1))])]),M.value>0?(x(),ne(ve,{key:0,percentage:E.value,"stroke-width":4,color:A.value,class:"mt14"},null,8,["percentage","color"])):te("",!0),C("div",SPe,"回调成功率 "+Q(E.value)+"%",1)])])]),d.value?(x(),O("div",CPe,[C("div",xPe,[Y[18]||(Y[18]=C("span",{class:"chart-title"},"账本状态",-1)),C("div",kPe,[L(he,{type:(ge=u.value)!=null&&ge.valid?"success":"warning",size:"small",effect:"light"},{default:$(()=>{var Te;return[ue(Q((Te=u.value)!=null&&Te.valid?"账本完整":"未校验"),1)]}),_:1},8,["type"]),L(J,{size:"small",onClick:h,loading:c.value,plain:""},{default:$(()=>[L(le,null,{default:$(()=>[L(p(Ao))]),_:1}),Y[17]||(Y[17]=ue(" 校验 ",-1))]),_:1},8,["loading"])])]),C("div",TPe,[C("div",MPe,[C("div",EPe,[C("div",APe,Q(r.value.ledger_count??0),1),Y[19]||(Y[19]=C("div",{class:"ledger-label"},"总条目",-1))]),C("div",IPe,[C("div",LPe,Q(v.value),1),Y[20]||(Y[20]=C("div",{class:"ledger-label"},"链尾哈希",-1))]),C("div",PPe,[C("div",DPe,Q(f.value),1),Y[21]||(Y[21]=C("div",{class:"ledger-label"},"最近记录",-1))]),C("div",OPe,[C("div",{class:U(["ledger-num",(Se=u.value)!=null&&Se.snapshot_ok?"text-success":"text-muted"])},Q((De=u.value)!=null&&De.snapshot_ok?"匹配":"—"),3),Y[22]||(Y[22]=C("div",{class:"ledger-label"},"快照锚点",-1))])])])])):te("",!0),C("div",$Pe,[C("div",RPe,[Y[24]||(Y[24]=C("span",{class:"chart-title"},"最近订单",-1)),C("button",{class:"link-btn",onClick:Y[2]||(Y[2]=Te=>V.$emit("goto","orders"))},[Y[23]||(Y[23]=ue(" 查看全部 ",-1)),L(le,{size:13},{default:$(()=>[L(p(za))]),_:1})])]),L(j,{data:r.value.recent_orders||[],size:"small"},{default:$(()=>[L(Z,{prop:"out_trade_no",label:"商户订单号","min-width":"150","show-overflow-tooltip":""}),L(Z,{prop:"subject",label:"商品","min-width":"100","show-overflow-tooltip":""}),L(Z,{label:"金额",width:"100"},{default:$(({row:Te})=>[C("span",NPe,"¥"+Q((Te.amount_minor/100).toFixed(2)),1)]),_:1}),L(Z,{label:"状态",width:"100"},{default:$(({row:Te})=>[L(he,{type:W(Te.status),size:"small",effect:"light"},{default:$(()=>[ue(Q(B(Te.status)),1)]),_:2},1032,["type"])]),_:1}),L(Z,{label:"时间",width:"160"},{default:$(({row:Te})=>[ue(Q(N(Te.created_at)),1)]),_:1})]),_:1},8,["data"])])])),[[oe,o.value]])}}},VPe=fr(BPe,[["__scopeId","data-v-a6ea8b68"]]),zPe={class:"channels-page"},FPe={class:"pg-card epay-info-card"},HPe={class:"pg-card-header"},WPe={class:"pg-card-body"},KPe={class:"cfg-item"},UPe={class:"cfg-value font-mono"},GPe={class:"cfg-item"},YPe={class:"cfg-value font-mono"},jPe={class:"pg-card"},qPe={class:"pg-card-header"},XPe={class:"text-muted",style:{"font-size":"12px"}},ZPe={class:"pg-card-body"},JPe={class:"channel-grid"},QPe={class:"channel-head"},eDe={class:"channel-name-wrap"},tDe={class:"channel-name"},nDe={class:"channel-id font-mono text-muted"},aDe={class:"font-mono",style:{"font-size":"12px"}},rDe={class:"font-mono",style:{"font-size":"12px"}},oDe={class:"mode-switches"},iDe={class:"font-mono text-muted",style:{"font-size":"11px","word-break":"break-all"}},lDe={class:"channel-stats"},sDe={class:"stat"},uDe={class:"stat-num font-mono"},cDe={class:"stat"},dDe={class:"stat-num font-mono"},fDe={class:"stat"},vDe={class:"stat-num font-mono"},hDe={class:"stat"},pDe={class:"stat-num font-mono"},mDe={class:"channel-foot"},gDe={class:"text-muted",style:{"font-size":"11px"}},yDe={key:0,class:"empty-card pg-card"},bDe={class:"pg-card"},_De={class:"pg-card-body"},wDe={class:"step"},SDe={class:"step-num"},CDe={class:"step-title"},xDe={class:"step-desc"},kDe={__name:"Channels",props:{refreshKey:Number},setup(e){const t=e,n=F([]),a=F(!1),r=F({});ye(()=>t.refreshKey,l);function o(f){var h;(h=navigator.clipboard)==null||h.writeText(f),We.success("已复制")}const i=[{title:"进入账户池",desc:"打开「账户池管理」页面,点击「新增账户」"},{title:"填写密钥",desc:"选择通道,粘贴 AppID / 私钥 / 公钥(AES-256-GCM 加密存储)"},{title:"保存生效",desc:"保存后账户自动进入内存账户池参与调度,无需重启"},{title:"开启支付模式",desc:"在通道卡片上切换 page / wap / qrcode 模式(扫码兜底)"}];async function l(){a.value=!0;try{const[f,h]=await Promise.all([yt.channels(),yt.epayInfo()]);n.value=f,r.value=h||{}}catch(f){We.error("加载失败: "+f.message)}finally{a.value=!1}}function s(f){return{alipay:"支付宝",wechat:"微信支付",paypal:"PayPal",crypto:"加密货币"}[f]||f}function u(f){return{alipay:"CircleCheck",wechat:"CircleCheck",paypal:"CircleCheck",crypto:"CircleCheck"}[f]||"CircleCheck"}function c(f){return{page:"电脑网页",wap:"手机WAP",qrcode:"扫码",address:"链上地址"}[f]||f}const d=["page","wap","qrcode"];async function v(f,h){try{const m=await yt.toggleChannelMode(f,h);We.success(`支付模式 ${c(h)} 已${m.enabled?"开启":"关闭"}`),l()}catch(m){We.error("切换失败: "+m.message)}}return mt(l),(f,h)=>{const m=be("el-tag"),g=be("el-button"),y=be("el-col"),b=be("el-row"),_=be("el-alert"),w=be("el-icon"),S=be("el-descriptions-item"),k=be("el-switch"),T=be("el-descriptions"),M=be("el-empty"),E=Gr("loading");return x(),O("div",zPe,[C("div",FPe,[C("div",HPe,[h[2]||(h[2]=C("span",{class:"section-label"},"易支付对接信息",-1)),L(m,{type:"success",size:"small",effect:"light"},{default:$(()=>[...h[1]||(h[1]=[ue("复制给 New API 等支持易支付的项目",-1)])]),_:1})]),C("div",WPe,[L(b,{gutter:12},{default:$(()=>[L(y,{span:8},{default:$(()=>[C("div",KPe,[h[4]||(h[4]=C("div",{class:"cfg-label"},"易支付网关地址(submit)",-1)),C("div",UPe,[C("code",null,Q(r.value.submit_url),1)]),L(g,{size:"small",text:"",type:"primary",onClick:h[0]||(h[0]=A=>o(r.value.submit_url))},{default:$(()=>[...h[3]||(h[3]=[ue("复制",-1)])]),_:1})])]),_:1}),L(y,{span:8},{default:$(()=>[...h[5]||(h[5]=[C("div",{class:"cfg-item"},[C("div",{class:"cfg-label"},"商户ID (pid)"),C("div",{class:"cfg-value text-muted",style:{"font-size":"12px"}},"每个上游独立配置"),C("div",{class:"cfg-value text-muted",style:{"font-size":"12px"}},"→ 见「上游对接」页")],-1)])]),_:1}),L(y,{span:8},{default:$(()=>[...h[6]||(h[6]=[C("div",{class:"cfg-item"},[C("div",{class:"cfg-label"},"商户密钥 (key)"),C("div",{class:"cfg-value text-muted",style:{"font-size":"12px"}},"每个上游独立密钥"),C("div",{class:"cfg-value text-muted",style:{"font-size":"12px"}},"→ 见「上游对接」页")],-1)])]),_:1})]),_:1}),L(b,{gutter:12,style:{"margin-top":"12px"}},{default:$(()=>[L(y,{span:12},{default:$(()=>[...h[7]||(h[7]=[C("div",{class:"cfg-item"},[C("div",{class:"cfg-label"},"notify_url(填商户/上游自己的回调,不是这里)"),C("div",{class:"cfg-value text-muted"},"例:http://127.0.0.1:3000/api/payment/notify(仅同机部署;公网对接填商户自己的公网回调域名)")],-1)])]),_:1}),L(y,{span:12},{default:$(()=>[C("div",GPe,[h[8]||(h[8]=C("div",{class:"cfg-label"},"订单查询接口",-1)),C("div",YPe,[C("code",null,Q(r.value.query_url),1)])])]),_:1})]),_:1}),L(_,{type:"success",closable:!1,class:"mt12",title:"对接地址优先展示公网固定域名(与支付页/兜底回跳一致);本机部署新 API 可用 127.0.0.1:8080(网关固定监听,与 CF 隧道无关)。公网对接请用本页展示的公网地址。"})])]),C("div",jPe,[C("div",qPe,[h[9]||(h[9]=C("span",{class:"section-label"},"支付通道",-1)),C("span",XPe,"已接入 "+Q(n.value.length)+" 个通道",1)]),C("div",ZPe,[L(_,{type:"info",closable:!1,class:"mb16",title:"通道采用可插拔架构:新增支付方式 = 实现 PaymentChannel 接口 + 注册 + 配置。以下为当前已接入的通道。"}),st((x(),O("div",JPe,[(x(!0),O(Ve,null,dt(n.value,A=>(x(),O("div",{key:A.name,class:"channel-card pg-card"},[C("div",QPe,[C("div",{class:U(["channel-logo",A.name])},[L(w,{size:18},{default:$(()=>[(x(),ne(St(u(A.name))))]),_:2},1024)],2),C("div",eDe,[C("div",tDe,Q(s(A.name)),1),C("div",nDe,Q(A.name),1)]),L(m,{type:A.enabled?"success":"danger",size:"small",effect:"light"},{default:$(()=>[ue(Q(A.enabled?"已启用":"已停用"),1)]),_:2},1032,["type"])]),L(T,{column:1,size:"small",class:"mt10"},{default:$(()=>[L(S,{label:"环境"},{default:$(()=>[L(m,{type:A.is_prod?"danger":"warning",size:"small"},{default:$(()=>[ue(Q(A.is_prod?"生产":"沙箱测试"),1)]),_:2},1032,["type"])]),_:2},1024),L(S,{label:"APPID"},{default:$(()=>[C("span",aDe,Q(A.app_id||"—"),1)]),_:2},1024),A.mch_id?(x(),ne(S,{key:0,label:"商户号"},{default:$(()=>[C("span",rDe,Q(A.mch_id),1)]),_:2},1024)):te("",!0),L(S,{label:"支持模式"},{default:$(()=>[C("div",oDe,[(x(),O(Ve,null,dt(d,I=>C("div",{key:I,class:"mode-switch-item"},[L(m,{type:A.modes_enabled&&A.modes_enabled[I]!==!1?"success":"info",size:"small",effect:"plain",style:{"margin-right":"4px"}},{default:$(()=>[ue(Q(c(I)),1)]),_:2},1032,["type"]),A.modes_enabled?(x(),ne(k,{key:0,"model-value":A.modes_enabled[I]!==!1,size:"small",onChange:P=>v(A.name,I)},null,8,["model-value","onChange"])):te("",!0)])),64))])]),_:2},1024),L(S,{label:"回调地址"},{default:$(()=>[C("span",iDe,Q(A.notify_url||"—"),1)]),_:2},1024)]),_:2},1024),A.enabled&&!A.configured?(x(),ne(_,{key:0,type:"warning",closable:!1,class:"mt10",title:"通道已启用但未配置账户",description:"请前往「账户池管理」点击「新增账户」配置密钥,保存后即刻生效,无需重启。"})):te("",!0),C("div",lDe,[C("div",sDe,[C("div",uDe,Q(A.today_orders??0),1),h[10]||(h[10]=C("div",{class:"stat-label"},"今日订单",-1))]),C("div",cDe,[C("div",dDe,"¥"+Q(((A.today_amount??0)/100).toFixed(2)),1),h[11]||(h[11]=C("div",{class:"stat-label"},"今日交易额",-1))]),C("div",fDe,[C("div",vDe,Q(A.total_orders??0),1),h[12]||(h[12]=C("div",{class:"stat-label"},"累计订单",-1))]),C("div",hDe,[C("div",pDe,"¥"+Q(((A.total_amount??0)/100).toFixed(2)),1),h[13]||(h[13]=C("div",{class:"stat-label"},"累计交易额",-1))])]),C("div",mDe,[C("span",gDe,"类型: "+Q(A.kind==="callback"?"回调驱动":"监听驱动"),1),A.enabled?(x(),ne(m,{key:0,type:"success",size:"small"},{default:$(()=>[...h[14]||(h[14]=[ue("已启用",-1)])]),_:1})):(x(),ne(m,{key:1,type:"info",size:"small"},{default:$(()=>[...h[15]||(h[15]=[ue("已禁用",-1)])]),_:1}))])]))),128)),!n.value.length&&!a.value?(x(),O("div",yDe,[L(M,{description:"暂无已接入的通道","image-size":80})])):te("",!0)])),[[E,a.value]])])]),C("div",bDe,[h[16]||(h[16]=C("div",{class:"pg-card-header"},[C("span",{class:"section-label"},"接入新通道")],-1)),C("div",_De,[L(b,{gutter:16},{default:$(()=>[(x(),O(Ve,null,dt(i,(A,I)=>L(y,{span:6,key:I},{default:$(()=>[C("div",wDe,[C("div",SDe,Q(I+1),1),C("div",CDe,Q(A.title),1),C("div",xDe,Q(A.desc),1)])]),_:2},1024)),64))]),_:1})])])])}}},TDe=fr(kDe,[["__scopeId","data-v-77316d40"]]),MDe={class:"orders-page"},EDe={class:"pg-card"},ADe={class:"pg-card-header"},IDe={class:"text-muted",style:{"font-size":"12px"}},LDe={class:"filter-bar"},PDe={key:1,class:"text-muted"},DDe={key:0},ODe={key:1,class:"text-muted"},$De={key:2,class:"text-muted"},RDe={class:"pager mt16"},NDe={class:"detail-head"},BDe={class:"detail-amount"},VDe={class:"font-mono"},zDe={class:"font-mono"},FDe={key:0},HDe={key:1,class:"text-muted"},WDe={key:2,class:"text-muted"},KDe={class:"font-mono"},UDe={key:0,class:"text-muted",style:{"font-size":"11px","margin-left":"6px"}},GDe={class:"font-mono",style:{"font-size":"12px"}},YDe={class:"font-mono",style:{"font-size":"11px"}},jDe={class:"font-mono text-muted",style:{"font-size":"11px"}},qDe={class:"text-muted",style:{"font-size":"12px"}},XDe={key:1,class:"empty-tip"},ZDe={__name:"Orders",props:{refreshKey:Number},setup(e){const t=e,n=F([]),a=F(0),r=F(!1),o=F(1),i=F(null),l=nn({status:"",channel:"",keyword:"",limit:20,offset:0}),s=F(!1),u=F(null),c=F([]),d=F(!1);async function v(){r.value=!0;try{const[T,M]=i.value||[null,null],E=await yt.orders({status:l.status,channel:l.channel,keyword:l.keyword,from:T,to:M,limit:l.limit,offset:l.offset});n.value=E.list,a.value=E.total}catch(T){We.error("加载失败: "+T.message)}finally{r.value=!1}}function f(){o.value=1,l.offset=0,v()}function h(){l.status="",l.channel="",l.keyword="",i.value=null,f()}function m(T){o.value=T,l.offset=(T-1)*l.limit,v()}ye(()=>t.refreshKey,v);async function g(T){u.value=T,s.value=!0,c.value=[],d.value=!0;try{const M=await yt.orderEvents(T.id,20);c.value=M||[]}catch(M){We.warning("流水加载失败: "+M.message),c.value=[]}finally{d.value=!1}}function y(T){return{WAIT_BUYER_PAY:"待支付",TRADE_SUCCESS:"成功",TRADE_CLOSED:"已关闭",CONFIRMING:"确认中",FAILED:"失败"}[T]||T}function b(T){return{WAIT_BUYER_PAY:"warning",TRADE_SUCCESS:"success",TRADE_CLOSED:"info",CONFIRMING:"warning",FAILED:"danger"}[T]||"info"}function _(T){return{created:"订单创建",paid:"支付成功",closed:"订单关闭",notified:"通知商户",failed:"失败"}[T]||T}function w(T){return{created:"info",paid:"success",closed:"info",notified:"primary",failed:"danger"}[T]||"info"}function S(T){return T?new Date(T).toLocaleString("zh-CN",{hour12:!1}):"—"}mt(()=>{const T=window.location.hash;if(T.includes("keyword=")){const M=T.match(/keyword=([^&]*)/);M&&M[1]&&(l.keyword=decodeURIComponent(M[1]))}v()});async function k(T){try{await navigator.clipboard.writeText(T),We.success("已复制")}catch(M){We.error("复制失败: "+M.message)}}return(T,M)=>{const E=be("Search"),A=be("el-icon"),I=be("el-input"),P=be("el-option"),R=be("el-select"),G=be("el-date-picker"),K=be("el-button"),H=be("el-table-column"),z=be("el-tag"),B=be("el-table"),W=be("el-pagination"),N=be("el-descriptions-item"),V=be("el-descriptions"),Y=be("el-timeline-item"),X=be("el-timeline"),ee=be("Document"),re=be("el-drawer"),ie=Gr("loading");return x(),O("div",MDe,[C("div",EDe,[C("div",ADe,[M[6]||(M[6]=C("span",{class:"section-label"},"订单管理",-1)),C("span",IDe,"共 "+Q(a.value)+" 笔",1)]),C("div",LDe,[L(I,{modelValue:l.keyword,"onUpdate:modelValue":M[0]||(M[0]=le=>l.keyword=le),placeholder:"搜索订单号 / 网关单号 / 交易号 / 商品",clearable:"",style:{width:"260px"},onKeyup:kn(f,["enter"]),onClear:f},{prefix:$(()=>[L(A,null,{default:$(()=>[L(E)]),_:1})]),_:1},8,["modelValue"]),L(R,{modelValue:l.status,"onUpdate:modelValue":M[1]||(M[1]=le=>l.status=le),placeholder:"全部状态",clearable:"",style:{width:"130px"},onChange:f},{default:$(()=>[L(P,{label:"待支付",value:"WAIT_BUYER_PAY"}),L(P,{label:"成功",value:"TRADE_SUCCESS"}),L(P,{label:"已关闭",value:"TRADE_CLOSED"}),L(P,{label:"失败",value:"FAILED"})]),_:1},8,["modelValue"]),L(R,{modelValue:l.channel,"onUpdate:modelValue":M[2]||(M[2]=le=>l.channel=le),placeholder:"全部通道",clearable:"",style:{width:"120px"},onChange:f},{default:$(()=>[L(P,{label:"支付宝",value:"alipay"})]),_:1},8,["modelValue"]),L(G,{modelValue:i.value,"onUpdate:modelValue":M[3]||(M[3]=le=>i.value=le),type:"daterange","range-separator":"至","start-placeholder":"开始日期","end-placeholder":"结束日期",style:{width:"260px"},"value-format":"YYYY-MM-DD",onChange:f},null,8,["modelValue"]),L(K,{type:"primary",onClick:f},{default:$(()=>[L(A,null,{default:$(()=>[L(E)]),_:1}),M[7]||(M[7]=ue(" 查询 ",-1))]),_:1}),L(K,{onClick:h},{default:$(()=>[...M[8]||(M[8]=[ue("重置",-1)])]),_:1})]),st((x(),ne(B,{data:n.value,size:"small",onRowClick:g,class:"mt10"},{default:$(()=>[L(H,{prop:"out_trade_no",label:"商户订单号","min-width":"150","show-overflow-tooltip":""}),L(H,{prop:"gateway_no",label:"网关单号","min-width":"150","show-overflow-tooltip":""}),L(H,{label:"上游",width:"130"},{default:$(({row:le})=>[le.upstream_name?(x(),ne(z,{key:0,size:"small",type:"primary",effect:"light"},{default:$(()=>[ue(Q(le.upstream_name),1)]),_:2},1024)):(x(),O("span",PDe,"—"))]),_:1}),L(H,{prop:"channel",label:"通道",width:"80"}),L(H,{label:"账户",width:"110"},{default:$(({row:le})=>[le.account_name?(x(),O("span",DDe,Q(le.account_name),1)):le.account_id?(x(),O("span",ODe,"#"+Q(le.account_id),1)):(x(),O("span",$De,"—"))]),_:1}),L(H,{prop:"subject",label:"商品","min-width":"100","show-overflow-tooltip":""}),L(H,{label:"金额",width:"95"},{default:$(({row:le})=>[C("b",null,"¥"+Q((le.amount_minor/100).toFixed(2)),1)]),_:1}),L(H,{label:"状态",width:"110"},{default:$(({row:le})=>[L(z,{type:b(le.status),size:"small",effect:"light",round:""},{default:$(()=>[ue(Q(y(le.status)),1)]),_:2},1032,["type"])]),_:1}),L(H,{prop:"channel_tx_id",label:"通道交易号","min-width":"150","show-overflow-tooltip":""}),L(H,{label:"支付时间",width:"160"},{default:$(({row:le})=>[ue(Q(S(le.paid_at)),1)]),_:1}),L(H,{label:"创建时间",width:"160"},{default:$(({row:le})=>[ue(Q(S(le.created_at)),1)]),_:1}),L(H,{label:"",width:"70",fixed:"right"},{default:$(({row:le})=>[L(K,{size:"small",text:"",type:"primary",onClick:at(ve=>g(le),["stop"])},{default:$(()=>[...M[9]||(M[9]=[ue("详情",-1)])]),_:1},8,["onClick"])]),_:1})]),_:1},8,["data"])),[[ie,r.value]]),C("div",RDe,[L(W,{background:"",layout:"total, prev, pager, next, jumper",total:a.value,"page-size":l.limit,"current-page":o.value,onCurrentChange:m},null,8,["total","page-size","current-page"])])]),L(re,{modelValue:s.value,"onUpdate:modelValue":M[5]||(M[5]=le=>s.value=le),title:"订单详情",size:"500px",class:"order-drawer"},{default:$(()=>[u.value?(x(),O(Ve,{key:0},[C("div",NDe,[C("div",BDe,"¥"+Q((u.value.amount_minor/100).toFixed(2)),1),L(z,{type:b(u.value.status),size:"small",effect:"dark"},{default:$(()=>[ue(Q(y(u.value.status)),1)]),_:1},8,["type"])]),L(V,{column:1,border:"",size:"small",class:"mt16"},{default:$(()=>[L(N,{label:"商户订单号"},{default:$(()=>[C("span",VDe,Q(u.value.out_trade_no),1)]),_:1}),L(N,{label:"网关单号"},{default:$(()=>[C("span",zDe,Q(u.value.gateway_no),1)]),_:1}),L(N,{label:"通道"},{default:$(()=>[ue(Q(u.value.channel),1)]),_:1}),L(N,{label:"处理账户"},{default:$(()=>[u.value.account_name?(x(),O("span",FDe,Q(u.value.account_name),1)):u.value.account_id?(x(),O("span",HDe,"#"+Q(u.value.account_id),1)):(x(),O("span",WDe,"—"))]),_:1}),L(N,{label:"通道交易号"},{default:$(()=>[C("span",KDe,Q(u.value.channel_tx_id||"—"),1)]),_:1}),L(N,{label:"上游"},{default:$(()=>[C("span",null,Q(u.value.upstream_name||"—"),1),u.value.pid?(x(),O("span",UDe,"(pid: "+Q(u.value.pid)+")",1)):te("",!0)]),_:1}),L(N,{label:"上游请求ID"},{default:$(()=>[C("span",GDe,Q(u.value.attach||"—"),1),u.value.attach?(x(),ne(K,{key:0,size:"small",text:"",type:"primary",onClick:M[4]||(M[4]=le=>k(u.value.attach))},{default:$(()=>[...M[10]||(M[10]=[ue("复制",-1)])]),_:1})):te("",!0)]),_:1}),L(N,{label:"商品"},{default:$(()=>[ue(Q(u.value.subject),1)]),_:1}),L(N,{label:"付款方"},{default:$(()=>[ue(Q(u.value.payer||"—"),1)]),_:1}),L(N,{label:"回调地址"},{default:$(()=>[C("span",YDe,Q(u.value.notify_url||"—"),1)]),_:1}),L(N,{label:"通知状态"},{default:$(()=>[ue(Q(u.value.notified?`已通知 ${u.value.notify_count} 次`:"未通知"),1)]),_:1}),L(N,{label:"创建时间"},{default:$(()=>[ue(Q(S(u.value.created_at)),1)]),_:1}),L(N,{label:"支付时间"},{default:$(()=>[ue(Q(S(u.value.paid_at)),1)]),_:1})]),_:1}),M[13]||(M[13]=C("h4",{class:"section-title"},"资金流水",-1)),st((x(),O("div",null,[c.value.length?(x(),ne(X,{key:0},{default:$(()=>[(x(!0),O(Ve,null,dt(c.value,le=>(x(),ne(Y,{key:le.id,timestamp:S(le.created_at),type:w(le.event_type),placement:"top"},{default:$(()=>[C("b",null,Q(_(le.event_type)),1),C("div",jDe,Q(le.channel_tx_id||""),1),C("div",qDe,"金额 ¥"+Q((le.amount_minor/100).toFixed(2)),1)]),_:2},1032,["timestamp","type"]))),128))]),_:1})):d.value?te("",!0):(x(),O("div",XDe,[L(A,{size:28,color:"var(--text-muted)"},{default:$(()=>[L(ee)]),_:1}),M[11]||(M[11]=C("div",{class:"empty-text"},"暂无资金流水",-1)),M[12]||(M[12]=C("div",{class:"empty-sub"},"该订单可能为历史数据,或支付流程未产生事件记录",-1))]))])),[[ie,d.value]])],64)):te("",!0)]),_:1},8,["modelValue"])])}}},JDe=fr(ZDe,[["__scopeId","data-v-490d8a35"]]),QDe={class:"notify-page"},eOe={class:"stat-grid"},tOe={class:"stat-card pg-card"},nOe={class:"stat-icon success"},aOe={class:"stat-num"},rOe={class:"stat-card pg-card"},oOe={class:"stat-icon warn"},iOe={class:"stat-num"},lOe={class:"stat-card pg-card"},sOe={class:"stat-icon danger"},uOe={class:"stat-num"},cOe={class:"stat-card pg-card"},dOe={class:"stat-icon info"},fOe={class:"stat-num"},vOe={class:"pg-card"},hOe={class:"pg-card-header"},pOe={style:{display:"flex",gap:"8px"}},mOe={key:1,class:"text-muted"},gOe={class:"payload-head"},yOe={class:"font-mono"},bOe={class:"payload"},_Oe={__name:"NotifyTasks",props:{refreshKey:Number},setup(e){const t=e,n=F([]),a=F({}),r=F(""),o=F(!1),i=F(!1),l=F(""),s=F(null);async function u(){o.value=!0;try{const[m,g]=await Promise.all([yt.notifyTasks({status:r.value,limit:50}),yt.notifyStats()]);n.value=m,a.value=g}catch(m){We.error("加载失败: "+m.message)}finally{o.value=!1}}function c(m){s.value=m;try{l.value=JSON.stringify(JSON.parse(m.payload),null,2)}catch{l.value=m.payload}i.value=!0}async function d(){var m;if(!((m=s.value)!=null&&m.id)){We.warning("未选中任务");return}try{await yt.retryDeadNotify(s.value.id),We.success("已按最新回调地址重新入队")}catch(g){We.error("重试失败: "+g.message)}finally{i.value=!1,u()}}ye(()=>t.refreshKey,u);function v(m){return{pending:"待处理",success:"成功",dead:"死信"}[m]||m}function f(m){return{pending:"warning",success:"success",dead:"danger"}[m]||"info"}function h(m){return m?new Date(m).toLocaleString("zh-CN",{hour12:!1}):"—"}return mt(u),(m,g)=>{const y=be("CircleCheck"),b=be("el-icon"),_=be("Clock"),w=be("Warning"),S=be("List"),k=be("el-alert"),T=be("el-option"),M=be("el-select"),E=be("Refresh"),A=be("el-button"),I=be("el-table-column"),P=be("el-tag"),R=be("el-table"),G=be("el-dialog"),K=Gr("loading");return x(),O("div",QDe,[C("div",eOe,[C("div",tOe,[C("div",nOe,[L(b,{size:20},{default:$(()=>[L(y)]),_:1})]),C("div",null,[C("div",aOe,Q(a.value.success??0),1),g[3]||(g[3]=C("div",{class:"stat-label"},"成功投递",-1))])]),C("div",rOe,[C("div",oOe,[L(b,{size:20},{default:$(()=>[L(_)]),_:1})]),C("div",null,[C("div",iOe,Q(a.value.pending??0),1),g[4]||(g[4]=C("div",{class:"stat-label"},"待重试",-1))])]),C("div",lOe,[C("div",sOe,[L(b,{size:20},{default:$(()=>[L(w)]),_:1})]),C("div",null,[C("div",uOe,Q(a.value.dead??0),1),g[5]||(g[5]=C("div",{class:"stat-label"},"死信",-1))])]),C("div",cOe,[C("div",dOe,[L(b,{size:20},{default:$(()=>[L(S)]),_:1})]),C("div",null,[C("div",fOe,Q(a.value.total??0),1),g[6]||(g[6]=C("div",{class:"stat-label"},"任务总数",-1))])])]),(a.value.dead??0)>0?(x(),ne(k,{key:0,type:"error",closable:!1,class:"mt12",title:"检测到死信任务!商户回调多次失败,请检查商户服务器 notify_url 可用性。"})):te("",!0),C("div",vOe,[C("div",hOe,[g[8]||(g[8]=C("span",{class:"section-label"},"回调任务队列",-1)),C("div",pOe,[L(M,{modelValue:r.value,"onUpdate:modelValue":g[0]||(g[0]=H=>r.value=H),placeholder:"全部状态",clearable:"",size:"small",style:{width:"120px"},onChange:u},{default:$(()=>[L(T,{label:"待处理",value:"pending"}),L(T,{label:"成功",value:"success"}),L(T,{label:"死信",value:"dead"})]),_:1},8,["modelValue"]),L(A,{size:"small",type:"primary",onClick:u},{default:$(()=>[L(b,null,{default:$(()=>[L(E)]),_:1}),g[7]||(g[7]=ue(" 刷新 ",-1))]),_:1})])]),st((x(),ne(R,{data:n.value,size:"small","empty-text":"没有找到回调任务,试试清除状态筛选"},{default:$(()=>[L(I,{prop:"id",label:"ID",width:"70"}),L(I,{prop:"out_trade_no",label:"商户订单号","min-width":"150","show-overflow-tooltip":""}),L(I,{prop:"subject",label:"商品","min-width":"90","show-overflow-tooltip":""}),L(I,{label:"金额",width:"90"},{default:$(({row:H})=>[C("b",null,"¥"+Q((H.amount_minor/100).toFixed(2)),1)]),_:1}),L(I,{label:"状态",width:"90"},{default:$(({row:H})=>[L(P,{type:f(H.status),size:"small",effect:"light",round:""},{default:$(()=>[ue(Q(v(H.status)),1)]),_:2},1032,["type"])]),_:1}),L(I,{prop:"retry_count",label:"重试次数",width:"90"},{default:$(({row:H})=>[H.retry_count>0?(x(),ne(P,{key:0,type:"warning",size:"small"},{default:$(()=>[ue(Q(H.retry_count),1)]),_:2},1024)):(x(),O("span",mOe,"0"))]),_:1}),L(I,{label:"下次重试",width:"170"},{default:$(({row:H})=>[ue(Q(h(H.next_retry_at)),1)]),_:1}),L(I,{label:"创建时间",width:"170"},{default:$(({row:H})=>[ue(Q(h(H.created_at)),1)]),_:1}),L(I,{label:"载荷",width:"80"},{default:$(({row:H})=>[L(A,{size:"small",text:"",type:"primary",onClick:z=>c(H)},{default:$(()=>[...g[9]||(g[9]=[ue("查看",-1)])]),_:1},8,["onClick"])]),_:1})]),_:1},8,["data"])),[[K,o.value]])]),L(G,{modelValue:i.value,"onUpdate:modelValue":g[2]||(g[2]=H=>i.value=H),title:"回调载荷",width:"620px"},{footer:$(()=>{var H;return[L(A,{onClick:g[1]||(g[1]=z=>i.value=!1)},{default:$(()=>[...g[10]||(g[10]=[ue("关闭",-1)])]),_:1}),L(A,{type:"primary",disabled:((H=s.value)==null?void 0:H.status)==="success",onClick:d},{default:$(()=>[...g[11]||(g[11]=[ue("手动重试",-1)])]),_:1},8,["disabled"])]}),default:$(()=>{var H,z;return[C("div",gOe,[C("span",yOe,Q((H=s.value)==null?void 0:H.out_trade_no),1),L(P,{type:f((z=s.value)==null?void 0:z.status),size:"small",effect:"light"},{default:$(()=>{var B;return[ue(Q(v((B=s.value)==null?void 0:B.status)),1)]}),_:1},8,["type"])]),C("pre",bOe,Q(l.value),1)]}),_:1},8,["modelValue"])])}}},wOe=fr(_Oe,[["__scopeId","data-v-52866146"]]),SOe={class:"logs-page"},COe={class:"stat-grid"},xOe={class:"stat-card pg-card"},kOe={class:"stat-icon danger"},TOe={class:"stat-num"},MOe={class:"stat-card pg-card"},EOe={class:"stat-icon warn"},AOe={class:"stat-num"},IOe={class:"stat-card pg-card"},LOe={class:"stat-icon info"},POe={class:"stat-num"},DOe={class:"stat-card pg-card"},OOe={class:"stat-icon primary"},$Oe={class:"stat-num"},ROe={class:"pg-card"},NOe={class:"pg-card-header"},BOe={style:{display:"flex",gap:"8px","flex-wrap":"wrap"}},VOe={class:"log-stream"},zOe={class:"log-time font-mono"},FOe={class:"log-scope"},HOe={class:"log-msg"},WOe={key:1,class:"empty-tip"},KOe={class:"pager mt16"},UOe={class:"font-mono"},GOe={class:"fields"},YOe={__name:"Logs",props:{refreshKey:Number},setup(e){const t=e,n=F([]),a=F(0),r=F(!1),o=F(""),i=F(""),l=F(1),s=F(!1),u=F(null),c=F({error:0,warn:0,info:0}),d=["main","epay","order","alipay","notify","ledger","tunnel","admin","logger"];async function v(){r.value=!0;try{const y=await yt.logs({level:o.value,scope:i.value,limit:50,offset:(l.value-1)*50});n.value=y&&y.list||[],a.value=y&&y.total||0}catch(y){ElMessage.error("加载失败: "+y.message),n.value=[],a.value=0}finally{r.value=!1}try{const[y,b,_]=await Promise.allSettled([yt.logs({level:"ERROR",limit:1}).then(w=>w.total),yt.logs({level:"WARN",limit:1}).then(w=>w.total),yt.logs({level:"INFO",limit:1}).then(w=>w.total)]);c.value={error:y.status==="fulfilled"?y.value:0,warn:b.status==="fulfilled"?b.value:0,info:_.status==="fulfilled"?_.value:0}}catch{}}function f(){l.value=1,v()}function h(y){l.value=y,v()}ye(()=>t.refreshKey,v);function m(y){u.value=y,s.value=!0}function g(y){return y?new Date(y).toLocaleString("zh-CN",{hour12:!1}):"—"}return mt(v),(y,b)=>{const _=be("CircleClose"),w=be("el-icon"),S=be("Warning"),k=be("InfoFilled"),T=be("Document"),M=be("el-option"),E=be("el-select"),A=be("Refresh"),I=be("el-button"),P=be("el-pagination"),R=be("el-descriptions-item"),G=be("el-descriptions"),K=be("el-dialog"),H=Gr("loading");return x(),O("div",SOe,[C("div",COe,[C("div",xOe,[C("div",kOe,[L(w,{size:18},{default:$(()=>[L(_)]),_:1})]),C("div",null,[C("div",TOe,Q(c.value.error),1),b[3]||(b[3]=C("div",{class:"stat-label"},"错误",-1))])]),C("div",MOe,[C("div",EOe,[L(w,{size:18},{default:$(()=>[L(S)]),_:1})]),C("div",null,[C("div",AOe,Q(c.value.warn),1),b[4]||(b[4]=C("div",{class:"stat-label"},"警告",-1))])]),C("div",IOe,[C("div",LOe,[L(w,{size:18},{default:$(()=>[L(k)]),_:1})]),C("div",null,[C("div",POe,Q(c.value.info),1),b[5]||(b[5]=C("div",{class:"stat-label"},"信息",-1))])]),C("div",DOe,[C("div",OOe,[L(w,{size:18},{default:$(()=>[L(T)]),_:1})]),C("div",null,[C("div",$Oe,Q(a.value),1),b[6]||(b[6]=C("div",{class:"stat-label"},"日志总数",-1))])])]),C("div",ROe,[C("div",NOe,[b[8]||(b[8]=C("span",{class:"section-label"},"日志流",-1)),C("div",BOe,[L(E,{modelValue:o.value,"onUpdate:modelValue":b[0]||(b[0]=z=>o.value=z),placeholder:"全部级别",clearable:"",size:"small",style:{width:"110px"},onChange:f},{default:$(()=>[L(M,{label:"错误",value:"ERROR"}),L(M,{label:"警告",value:"WARN"}),L(M,{label:"信息",value:"INFO"}),L(M,{label:"调试",value:"DEBUG"})]),_:1},8,["modelValue"]),L(E,{modelValue:i.value,"onUpdate:modelValue":b[1]||(b[1]=z=>i.value=z),placeholder:"全部模块",clearable:"",size:"small",style:{width:"120px"},onChange:f},{default:$(()=>[(x(),O(Ve,null,dt(d,z=>L(M,{key:z,label:z,value:z},null,8,["label","value"])),64))]),_:1},8,["modelValue"]),L(I,{size:"small",type:"primary",onClick:f},{default:$(()=>[L(w,null,{default:$(()=>[L(A)]),_:1}),b[7]||(b[7]=ue(" 刷新 ",-1))]),_:1})])]),st((x(),O("div",VOe,[n.value.length?(x(!0),O(Ve,{key:0},dt(n.value,z=>(x(),O("div",{key:z.id,class:U(["log-line","lv-"+z.level.toLowerCase()])},[C("span",zOe,Q(g(z.created_at)),1),C("span",{class:U(["log-level","lv-"+z.level.toLowerCase()])},Q(z.level),3),C("span",FOe,Q(z.scope),1),C("span",HOe,Q(z.message),1),z.fields||z.trace_id?(x(),ne(I,{key:0,size:"small",text:"",type:"primary",onClick:B=>m(z)},{default:$(()=>[...b[9]||(b[9]=[ue("详情",-1)])]),_:1},8,["onClick"])):te("",!0)],2))),128)):r.value?te("",!0):(x(),O("div",WOe,[L(w,{size:32,color:"var(--text-muted)"},{default:$(()=>[L(T)]),_:1}),b[10]||(b[10]=C("div",{class:"empty-text"},"没有找到日志",-1)),b[11]||(b[11]=C("div",{class:"empty-sub"},"试试清除级别/模块筛选,或等待新日志产生",-1))]))])),[[H,r.value]]),C("div",KOe,[L(P,{background:"",layout:"total, prev, pager, next",total:a.value,"page-size":50,"current-page":l.value,onCurrentChange:h},null,8,["total","current-page"])])]),L(K,{modelValue:s.value,"onUpdate:modelValue":b[2]||(b[2]=z=>s.value=z),title:"日志详情",width:"580px"},{default:$(()=>[u.value?(x(),ne(G,{key:0,column:1,border:"",size:"small"},{default:$(()=>[L(R,{label:"ID"},{default:$(()=>[ue(Q(u.value.id),1)]),_:1}),L(R,{label:"级别"},{default:$(()=>[ue(Q(u.value.level),1)]),_:1}),L(R,{label:"模块"},{default:$(()=>[ue(Q(u.value.scope),1)]),_:1}),L(R,{label:"消息"},{default:$(()=>[ue(Q(u.value.message),1)]),_:1}),L(R,{label:"TraceID"},{default:$(()=>[C("span",UOe,Q(u.value.trace_id||"—"),1)]),_:1}),L(R,{label:"时间"},{default:$(()=>[ue(Q(g(u.value.created_at)),1)]),_:1}),u.value.fields?(x(),ne(R,{key:0,label:"字段"},{default:$(()=>[C("pre",GOe,Q(u.value.fields),1)]),_:1})):te("",!0)]),_:1})):te("",!0)]),_:1},8,["modelValue"])])}}},jOe=fr(YOe,[["__scopeId","data-v-d4a5eb1d"]]),qOe={class:"ledger-page"},XOe={class:"action-bar pg-card"},ZOe={class:"tag-muted"},JOe={class:"pg-card"},QOe={class:"pg-card-header"},e$e={style:{display:"flex","align-items":"center",gap:"10px"}},t$e={class:"pg-card-body"},n$e={class:"chain"},a$e={class:"chain-badge"},r$e={class:"chain-body"},o$e={class:"chain-row"},i$e=["onClick"],l$e={key:1,class:"font-muted"},s$e={class:"chain-row"},u$e={class:"font-mono hash muted"},c$e={class:"chain-row"},d$e={class:"font-mono hash muted"},f$e={class:"chain-row"},v$e={class:"font-muted"},h$e={key:0,class:"chain-link"},p$e={class:"pg-card"},m$e={class:"pg-card-header"},g$e={style:{display:"flex","align-items":"center",gap:"10px"}},y$e={class:"pg-card-body"},b$e={class:"snapshot-grid"},_$e=["onClick"],w$e={class:"snapshot-date"},S$e={class:"snapshot-body"},C$e={class:"sn-row"},x$e={class:"font-mono hash muted"},k$e={class:"sn-row"},T$e={class:"font-mono hash muted"},M$e={class:"sn-row"},E$e={class:"font-muted"},A$e={key:0,class:"verify-result"},I$e={class:"verify-status-text"},L$e={key:0,class:"verify-errors mt16"},P$e={class:"verify-errors-title"},D$e={key:1,class:"verify-loading"},O$e={key:0,class:"snap-detail"},$$e={class:"font-mono",style:{"font-size":"12px","word-break":"break-all"}},R$e={class:"font-mono",style:{"font-size":"12px","word-break":"break-all",color:"var(--text-muted)"}},N$e={class:"mt16",style:{display:"flex",gap:"12px","justify-content":"center"}},B$e={class:"pubkey-container"},V$e={class:"pubkey-block"},z$e={__name:"Ledger",props:{refreshKey:Number},setup(e){const t=e,n=F([]),a=F(0),r=F(!1),o=F(!1),i=F(null),l=F([]),s=F(!1),u=F(!1),c=F(!1),d=F(null),v=F(null),f=F("entries"),h=F(!1),m=F(null),g=F(!1),y=F(null),b=F(!1),_=F(""),w=F(!1),S=F(!1);async function k(){r.value=!0;try{const B=await yt.ledger({limit:30});n.value=B.list,a.value=B.total}catch(B){We.error("加载失败: "+B.message)}finally{r.value=!1}}async function T(){s.value=!0;try{const B=await yt.ledgerSnapshots({limit:30});l.value=B||[]}catch(B){We.error("加载快照失败: "+B.message)}finally{s.value=!1}}async function M(){u.value=!0,c.value=!0,d.value=null;try{const B=await yt.ledgerVerify();d.value=B,v.value=B}catch(B){d.value={valid:!1,total_entries:0,errors:["请求失败: "+B.message],snapshot_ok:!1,checked_at:new Date().toISOString()},We.error("校验失败: "+B.message)}finally{u.value=!1}}function E(B){m.value=B,h.value=!0,y.value=null}async function A(B){g.value=!0;try{const W=await yt.ledger({limit:1});W.list&&W.list.length>0?y.value=W.list[0].entry_hash===B.last_hash:y.value=!1}catch(W){We.error("核对失败: "+W.message),y.value=!1}finally{g.value=!1}}async function I(){b.value=!0;try{const B=await yt.ledgerPublicKey();_.value=B.public_key||"未获取到公钥"}catch(B){_.value="获取公钥失败: "+B.message}}async function P(){w.value=!0;try{await yt.ledgerExport(),We.success("账本已导出")}catch(B){We.error("导出失败: "+B.message)}finally{w.value=!1}}function R(B){window.location.hash="#/orders?keyword="+B,window.location.reload()}function G(B){navigator.clipboard.writeText(B).then(()=>We.success("已复制")).catch(()=>We.error("复制失败"))}async function K(){S.value=!0;try{await yt.createSnapshot(),We.success("快照创建成功"),f.value="snapshots",await T()}catch(B){We.error("创建快照失败: "+B.message)}finally{S.value=!1}}function H(B){i.value=B,o.value=!0}ye(()=>t.refreshKey,()=>{k(),T()});function z(B){return B?new Date(B).toLocaleString("zh-CN",{hour12:!1}):"—"}return mt(()=>{k(),T()}),(B,W)=>{const N=be("el-icon"),V=be("el-button"),Y=be("el-tag"),X=be("el-alert"),ee=be("el-empty"),re=be("el-tab-pane"),ie=be("el-tabs"),le=be("el-descriptions-item"),ve=be("el-descriptions"),he=be("el-dialog"),J=Gr("loading");return x(),O("div",qOe,[C("div",XOe,[L(V,{type:"primary",loading:u.value,onClick:M,plain:""},{default:$(()=>[L(N,null,{default:$(()=>[L(p(Ao))]),_:1}),W[7]||(W[7]=ue(" 校验账本 ",-1))]),_:1},8,["loading"]),L(V,{loading:S.value,onClick:K,plain:""},{default:$(()=>[L(N,null,{default:$(()=>[L(p(kI))]),_:1}),W[8]||(W[8]=ue(" 创建快照 ",-1))]),_:1},8,["loading"]),L(V,{onClick:I,plain:""},{default:$(()=>[L(N,null,{default:$(()=>[L(p(PI))]),_:1}),W[9]||(W[9]=ue(" 账本公钥 ",-1))]),_:1}),L(V,{loading:w.value,onClick:P,plain:""},{default:$(()=>[L(N,null,{default:$(()=>[L(p(AI))]),_:1}),W[10]||(W[10]=ue(" 导出CSV ",-1))]),_:1},8,["loading"]),v.value?(x(),ne(Y,{key:0,type:v.value.valid?"success":"danger",size:"small",effect:"plain",class:"verify-tag"},{default:$(()=>[ue(Q(v.value.valid?"上次校验通过":"上次校验失败")+" ",1),C("span",ZOe,"("+Q(v.value.checked_at?z(v.value.checked_at):"—")+")",1)]),_:1},8,["type"])):te("",!0)]),L(ie,{modelValue:f.value,"onUpdate:modelValue":W[0]||(W[0]=Z=>f.value=Z),class:"mt12"},{default:$(()=>[L(re,{label:"账本条目",name:"entries"},{default:$(()=>[C("div",JOe,[C("div",QOe,[W[12]||(W[12]=C("span",{class:"section-label"},"审计账本 · 哈希链",-1)),C("div",e$e,[L(Y,{type:"success",size:"small",effect:"light"},{default:$(()=>[ue("共 "+Q(a.value)+" 条",1)]),_:1}),L(V,{size:"small",onClick:k},{default:$(()=>[L(N,null,{default:$(()=>[L(p(g4))]),_:1}),W[11]||(W[11]=ue(" 刷新",-1))]),_:1})])]),C("div",t$e,[L(X,{type:"info",closable:!1,class:"mb16",title:"账本为哈希链结构:每条记录包含上一记录哈希 + ECDSA 签名。任何历史修改都会导致链条断裂。"}),st((x(),O("div",n$e,[(x(!0),O(Ve,null,dt(n.value,(Z,j)=>(x(),O(Ve,{key:Z.id},[C("div",{class:U(["chain-node",{head:j===0}])},[C("div",a$e,"#"+Q(Z.id),1),C("div",r$e,[C("div",o$e,[W[13]||(W[13]=C("span",{class:"chain-label"},"事件ID",-1)),Z.event_id?(x(),O("span",{key:0,class:"font-mono event-link",onClick:oe=>R(Z.event_id)},Q(Z.event_id),9,i$e)):(x(),O("span",l$e,"—"))]),C("div",s$e,[W[14]||(W[14]=C("span",{class:"chain-label"},"条目哈希",-1)),C("span",u$e,Q(Z.entry_hash),1)]),C("div",c$e,[W[15]||(W[15]=C("span",{class:"chain-label"},"前置哈希",-1)),C("span",d$e,Q(Z.prev_hash||"—(创世)"),1)]),C("div",f$e,[W[16]||(W[16]=C("span",{class:"chain-label"},"时间",-1)),C("span",v$e,Q(z(Z.created_at)),1)])]),L(V,{size:"small",text:"",type:"primary",onClick:oe=>H(Z)},{default:$(()=>[...W[17]||(W[17]=[ue("完整",-1)])]),_:1},8,["onClick"])],2),j[L(p(DI))]),_:1})])):te("",!0)],64))),128)),!n.value.length&&!r.value?(x(),ne(ee,{key:0,description:"账本为空","image-size":60})):te("",!0)])),[[J,r.value]])])])]),_:1}),L(re,{label:"快照管理",name:"snapshots"},{default:$(()=>[C("div",p$e,[C("div",m$e,[W[19]||(W[19]=C("span",{class:"section-label"},"每日快照",-1)),C("div",g$e,[L(Y,{size:"small",effect:"light"},{default:$(()=>[ue("共 "+Q(l.value.length)+" 条",1)]),_:1}),L(V,{size:"small",onClick:T},{default:$(()=>[L(N,null,{default:$(()=>[L(p(g4))]),_:1}),W[18]||(W[18]=ue(" 刷新",-1))]),_:1})])]),C("div",y$e,[L(X,{type:"info",closable:!1,class:"mb16",title:"快照是对账本链尾哈希的签名记录,用于锚定截至某日的账本状态。"}),st((x(),O("div",b$e,[(x(!0),O(Ve,null,dt(l.value,Z=>(x(),O("div",{key:Z.id,class:"snapshot-card",onClick:j=>E(Z),style:{cursor:"pointer"}},[C("div",w$e,Q(Z.snapshot_date),1),C("div",S$e,[C("div",C$e,[W[20]||(W[20]=C("span",{class:"sn-label"},"链尾哈希",-1)),C("span",x$e,Q(Z.last_hash),1)]),C("div",k$e,[W[21]||(W[21]=C("span",{class:"sn-label"},"签名",-1)),C("span",T$e,Q(Z.signature),1)]),C("div",M$e,[W[22]||(W[22]=C("span",{class:"sn-label"},"时间",-1)),C("span",E$e,Q(z(Z.created_at)),1)])]),L(N,{class:"detail-icon",color:"var(--accent)"},{default:$(()=>[L(p(U6))]),_:1})],8,_$e))),128)),!l.value.length&&!s.value?(x(),ne(ee,{key:0,description:"暂无快照","image-size":60})):te("",!0)])),[[J,s.value]])])])]),_:1})]),_:1},8,["modelValue"]),L(he,{modelValue:c.value,"onUpdate:modelValue":W[1]||(W[1]=Z=>c.value=Z),title:"账本校验结果",width:"600px","close-on-click-modal":!1},{default:$(()=>[d.value?(x(),O("div",A$e,[C("div",{class:U(["verify-status",d.value.valid?"pass":"fail"])},[L(N,{size:48},{default:$(()=>[d.value.valid?(x(),ne(p(Ao),{key:0})):(x(),ne(p($s),{key:1}))]),_:1}),C("div",I$e,Q(d.value.valid?"账本完整":"账本校验失败"),1)],2),L(ve,{column:2,border:"",size:"small",class:"mt16"},{default:$(()=>[L(le,{label:"总条目数"},{default:$(()=>[ue(Q(d.value.total_entries),1)]),_:1}),L(le,{label:"快照锚点"},{default:$(()=>[L(Y,{type:d.value.snapshot_ok?"success":"danger",size:"small"},{default:$(()=>[ue(Q(d.value.snapshot_ok?"匹配":"不匹配"),1)]),_:1},8,["type"])]),_:1}),L(le,{label:"耗时"},{default:$(()=>[ue(Q(d.value.duration||"—"),1)]),_:1}),L(le,{label:"检查时间"},{default:$(()=>[ue(Q(z(d.value.checked_at)),1)]),_:1})]),_:1}),d.value.errors&&d.value.errors.length?(x(),O("div",L$e,[C("div",P$e,"错误详情("+Q(d.value.errors.length)+" 处)",1),(x(!0),O(Ve,null,dt(d.value.errors,(Z,j)=>(x(),O("div",{key:j,class:"verify-error-item"},[L(N,{color:"var(--danger)"},{default:$(()=>[L(p(fc))]),_:1}),C("span",null,Q(Z),1)]))),128))])):te("",!0)])):st((x(),O("div",D$e,[...W[23]||(W[23]=[C("span",null,"正在校验账本...",-1)])])),[[J,u.value]])]),_:1},8,["modelValue"]),L(he,{modelValue:h.value,"onUpdate:modelValue":W[4]||(W[4]=Z=>h.value=Z),title:"快照详情",width:"600px"},{default:$(()=>[m.value?(x(),O("div",O$e,[L(ve,{column:1,border:"",size:"small"},{default:$(()=>[L(le,{label:"快照日期"},{default:$(()=>[ue(Q(m.value.snapshot_date),1)]),_:1}),L(le,{label:"链尾哈希"},{default:$(()=>[C("span",$$e,Q(m.value.last_hash),1)]),_:1}),L(le,{label:"签名"},{default:$(()=>[C("span",R$e,Q(m.value.signature),1)]),_:1}),L(le,{label:"创建时间"},{default:$(()=>[ue(Q(z(m.value.created_at)),1)]),_:1})]),_:1}),C("div",N$e,[L(V,{onClick:W[2]||(W[2]=Z=>A(m.value)),loading:g.value,type:"primary"},{default:$(()=>[L(N,null,{default:$(()=>[L(p(Ao))]),_:1}),W[24]||(W[24]=ue(" 核对锚点 ",-1))]),_:1},8,["loading"]),L(V,{onClick:W[3]||(W[3]=Z=>G(JSON.stringify(m.value,null,2)))},{default:$(()=>[...W[25]||(W[25]=[ue("复制快照信息",-1)])]),_:1})]),y.value!==null?(x(),O("div",{key:0,class:U(["mt16",y.value?"anchor-match":"anchor-mismatch"])},[L(N,{size:20},{default:$(()=>[y.value?(x(),ne(p(Ao),{key:0})):(x(),ne(p($s),{key:1}))]),_:1}),C("span",null,Q(y.value?"锚点匹配:链尾哈希与快照一致":"锚点不匹配:数据可能已被篡改"),1)],2)):te("",!0)])):te("",!0)]),_:1},8,["modelValue"]),L(he,{modelValue:b.value,"onUpdate:modelValue":W[6]||(W[6]=Z=>b.value=Z),title:"账本公钥",width:"700px"},{default:$(()=>[C("div",B$e,[W[27]||(W[27]=C("p",{style:{color:"var(--text-muted)","margin-bottom":"12px","font-size":"13px"}}," 使用此公钥可验证账本条目的 ECDSA P256 签名,确保数据未被篡改。 ",-1)),C("pre",V$e,Q(_.value||"加载中..."),1),L(V,{onClick:W[5]||(W[5]=Z=>G(_.value)),size:"small",class:"mt16"},{default:$(()=>[...W[26]||(W[26]=[ue("复制公钥",-1)])]),_:1})])]),_:1},8,["modelValue"])])}}},F$e=fr(z$e,[["__scopeId","data-v-05660c6d"]]),H$e={class:"accounts-page"},W$e={class:"pg-card"},K$e={class:"pg-card-header"},U$e={class:"header-actions"},G$e={class:"text-muted",style:{"font-size":"12px"}},Y$e={class:"pg-card-body"},j$e={class:"filter-bar mb16"},q$e={class:"account-grid"},X$e={class:"account-head"},Z$e={class:"account-info"},J$e={class:"account-name"},Q$e={class:"account-channel text-muted"},eRe=["title"],tRe={class:"text-muted",style:{"font-size":"12px"}},nRe={class:"font-mono",style:{"font-size":"12px"}},aRe={class:"font-mono",style:{"font-size":"12px"}},rRe={class:"font-mono text-muted",style:{"font-size":"11px"}},oRe={class:"font-mono text-muted",style:{"font-size":"11px"}},iRe={class:"account-actions"},lRe={key:0,class:"empty-card pg-card"},sRe={class:"qr-upload-row"},uRe={class:"secret-box"},cRe={key:0,class:"secret-item"},dRe={class:"secret-value"},fRe={class:"font-mono",style:{"word-break":"break-all"}},vRe={class:"secret-item"},hRe={class:"secret-value"},pRe={class:"font-mono"},mRe={key:0,class:"secret-item"},gRe={class:"secret-value"},yRe={class:"font-mono"},bRe={class:"secret-item"},_Re={class:"secret-value"},wRe={class:"font-mono text-muted"},SRe={class:"secret-item"},CRe={class:"secret-value"},xRe={class:"font-mono text-muted"},kRe={class:"log-item"},TRe={class:"log-detail"},MRe={class:"log-operator text-muted"},ERe={__name:"Accounts",props:{refreshKey:Number},setup(e){const t=e,n=F([]),a=F(!1),r=F(""),o=F(!1),i=F(!1),l=F(!1),s=F(m()),u=F(!1),c=F(!1),d=F([]),v=F(!1),f=F(!1),h=F(null);ye(()=>t.refreshKey,y);function m(){return{id:0,channel:"alipay",name:"",app_id:"",mch_id:"",private_key:"",public_key:"",api_key:"",qr_content:"",private_key_configured:!1,public_key_configured:!1,api_key_configured:!1,qr_content_configured:!1,weight:50,priority:0,auto_ban:!0,dailyLimitYuan:1e3,group:"default",is_prod:!1,remark:""}}const g=D(()=>r.value?n.value.filter(N=>N.channel===r.value):n.value);async function y(){a.value=!0;try{n.value=await yt.accounts({channel:r.value||void 0})}catch(N){We.error("加载失败: "+N.message)}finally{a.value=!1}}function b(N){return{alipay:"支付宝(商家)",wechat:"微信支付(商家)","personal.alipay":"个人免签·支付宝","personal.wechat":"个人免签·微信","personal.qq":"个人免签·QQ"}[N]||N}function _(N){return{normal:"success",cooling:"warning",frozen:"danger",disabled:"info"}[N]||"info"}function w(N){return{normal:"正常",cooling:"冷却中",frozen:"已冻结",disabled:"已禁用"}[N]||N}function S(N){return{create:"success",update:"primary",delete:"danger",freeze:"warning",unfreeze:"success",enable:"success",disable:"info"}[N]||"info"}function k(N){return String(N).startsWith("personal.")}function T(){i.value=!1,s.value=m(),o.value=!0}function M(N){i.value=!0,s.value={id:N.id,channel:N.channel,name:N.name,app_id:N.app_id,mch_id:N.mch_id,private_key:"",public_key:"",api_key:"",private_key_configured:!!N.private_key,public_key_configured:!!N.public_key,api_key_configured:!!N.api_key,qr_content_configured:!!N.qr_content,weight:N.weight,priority:N.priority,auto_ban:N.auto_ban,dailyLimitYuan:N.daily_limit/100,group:N.group,is_prod:N.is_prod,remark:N.remark,qr_content:""},o.value=!0}async function E(N){try{await er.confirm(`将显示账户「${N.name}」的完整明文密钥(含私钥),请确认当前环境安全。`,"查看完整密钥",{confirmButtonText:"确认查看",cancelButtonText:"取消",type:"warning"})}catch{return}v.value=!0,f.value=!0,h.value=null;try{h.value=await yt.accountSecret(N.id)}catch(V){v.value=!1,We.error("获取完整密钥失败: "+V.message)}finally{f.value=!1}}async function A(N){try{await navigator.clipboard.writeText(N),We.success("已复制完整密钥")}catch(V){We.error("复制失败: "+V.message)}}const I=F(!1);async function P(N){I.value=!0;try{const V=await yt.decodeQRCode(N);V?(s.value.qr_content=V,We.success("收款码解析成功")):We.warning("未识别到二维码内容")}catch(V){We.error("解析失败:"+V.message)}finally{I.value=!1}return!1}async function R(){if(!s.value.channel||!s.value.name){We.warning("请填写通道和账户名称");return}if(k(s.value.channel)&&!i.value&&!s.value.qr_content.trim()){We.warning("请填写收款码内容,或上传收款码图片自动解析");return}l.value=!0;try{const N={channel:s.value.channel,name:s.value.name,app_id:s.value.app_id,mch_id:s.value.mch_id,private_key:s.value.private_key,public_key:s.value.public_key,api_key:s.value.api_key,qr_content:s.value.qr_content,weight:s.value.weight,priority:s.value.priority,auto_ban:s.value.auto_ban,daily_limit:Math.round(s.value.dailyLimitYuan*100),group:s.value.group||"default",is_prod:s.value.is_prod,remark:s.value.remark};i.value?(await yt.updateAccount(s.value.id,N),We.success("账户已更新")):(await yt.createAccount(N),We.success("账户创建成功")),o.value=!1,y()}catch(N){We.error("保存失败: "+N.message)}finally{l.value=!1}}async function G(N){try{await yt.freezeAccount(N),We.success("已冻结"),y()}catch(V){We.error(V.message)}}async function K(N){try{await yt.unfreezeAccount(N),We.success("已解冻"),y()}catch(V){We.error(V.message)}}async function H(N){try{await yt.enableAccount(N),We.success("已启用"),y()}catch(V){We.error(V.message)}}async function z(N){try{await yt.disableAccount(N),We.success("已禁用"),y()}catch(V){We.error(V.message)}}async function B(N){try{await er.confirm(`确定删除账户「${N.name}」?此操作不可恢复。`,"确认删除",{confirmButtonText:"删除",cancelButtonText:"取消",type:"warning"}),await yt.deleteAccount(N.id),We.success("已删除"),y()}catch(V){V!=="cancel"&&We.error(V.message)}}async function W(N){u.value=!0,c.value=!0;try{d.value=await yt.accountLogs(N.id,30)}catch(V){We.error("加载日志失败: "+V.message)}finally{c.value=!1}}return mt(y),(N,V)=>{const Y=be("el-icon"),X=be("el-button"),ee=be("el-alert"),re=be("el-option"),ie=be("el-option-group"),le=be("el-select"),ve=be("el-tag"),he=be("el-descriptions-item"),J=be("el-descriptions"),Z=be("el-empty"),j=be("el-step"),oe=be("el-steps"),se=be("el-form-item"),fe=be("el-input"),ge=be("el-col"),Se=be("el-row"),De=be("el-upload"),Te=be("el-input-number"),Re=be("el-switch"),Ce=be("el-form"),Le=be("el-dialog"),me=be("el-timeline-item"),Be=be("el-timeline"),ze=Gr("loading");return x(),O("div",H$e,[C("div",W$e,[C("div",K$e,[V[25]||(V[25]=C("span",{class:"section-label"},"账户池管理",-1)),C("div",U$e,[C("span",G$e,"共 "+Q(n.value.length)+" 个账户",1),L(X,{type:"primary",size:"small",onClick:T},{default:$(()=>[L(Y,null,{default:$(()=>[L(p(hf))]),_:1}),V[24]||(V[24]=ue(" 新增账户 ",-1))]),_:1})])]),C("div",Y$e,[L(ee,{type:"warning",closable:!1,class:"mb16",title:"密钥加密存储:敏感字段(私钥/API密钥)使用 AES-256-GCM 加密后存入独立 accounts.db,仅管理后台可读写。"}),C("div",j$e,[L(le,{modelValue:r.value,"onUpdate:modelValue":V[0]||(V[0]=xe=>r.value=xe),placeholder:"全部通道",clearable:"",size:"small",style:{width:"180px"},onChange:y},{default:$(()=>[L(re,{label:"全部通道",value:""}),L(ie,{label:"商家通道(官方 API)"},{default:$(()=>[L(re,{label:"支付宝(商家)",value:"alipay"}),L(re,{label:"微信支付(商家)",value:"wechat"})]),_:1}),L(ie,{label:"个人免签(收款码监听)"},{default:$(()=>[L(re,{label:"个人·支付宝",value:"personal.alipay"}),L(re,{label:"个人·微信",value:"personal.wechat"})]),_:1})]),_:1},8,["modelValue"]),r.value?(x(),ne(ve,{key:0,size:"small",type:"info",class:"ml8"},{default:$(()=>[ue(Q(b(r.value)),1)]),_:1})):te("",!0)]),st((x(),O("div",q$e,[(x(!0),O(Ve,null,dt(g.value,xe=>(x(),O("div",{key:xe.id,class:"account-card pg-card"},[C("div",X$e,[C("div",{class:U(["account-logo",xe.channel])},[L(Y,{size:18},{default:$(()=>[(x(),ne(St((xe.channel.includes("wechat"),"CircleCheck"))))]),_:2},1024)],2),C("div",Z$e,[C("div",J$e,Q(xe.name),1),C("div",Q$e,Q(b(xe.channel)),1)]),L(ve,{type:_(xe.status),size:"small",effect:"light",round:""},{default:$(()=>[ue(Q(w(xe.status)),1)]),_:2},1032,["type"])]),L(J,{column:2,size:"small",class:"mt10",border:""},{default:$(()=>[k(xe.channel)?(x(),O(Ve,{key:0},[L(he,{label:"收款码内容",span:2},{default:$(()=>[C("span",{class:"font-mono",style:{"font-size":"11px"},title:xe.qr_content},Q(xe.qr_content||"—"),9,eRe)]),_:2},1024),L(he,{label:"绑定设备",span:2},{default:$(()=>[C("span",tRe,Q(xe.remark||"未绑定"),1)]),_:2},1024)],64)):(x(),O(Ve,{key:1},[L(he,{label:"AppID",span:2},{default:$(()=>[C("span",nRe,Q(xe.app_id||"—"),1)]),_:2},1024),xe.mch_id?(x(),ne(he,{key:0,label:"商户号"},{default:$(()=>[C("span",aRe,Q(xe.mch_id),1)]),_:2},1024)):te("",!0),L(he,{label:"应用私钥"},{default:$(()=>[C("span",rRe,Q(xe.private_key||"—"),1)]),_:2},1024),L(he,{label:"公钥/API密钥"},{default:$(()=>[C("span",oRe,Q(xe.public_key||xe.api_key||"—"),1)]),_:2},1024)],64)),L(he,{label:"权重"},{default:$(()=>[L(ve,{size:"small"},{default:$(()=>[ue(Q(xe.weight),1)]),_:2},1024)]),_:2},1024),L(he,{label:"优先级"},{default:$(()=>[L(ve,{size:"small",type:"warning"},{default:$(()=>[ue(Q(xe.priority),1)]),_:2},1024)]),_:2},1024),L(he,{label:"日限额"},{default:$(()=>[C("span",{class:U({"text-danger":xe.daily_limit>0&&(xe.daily_used??0)>=xe.daily_limit})}," ¥"+Q(((xe.daily_used??0)/100).toFixed(2))+" / ¥"+Q(((xe.daily_limit??0)/100).toFixed(2)),3)]),_:2},1024),L(he,{label:"自动禁用"},{default:$(()=>[L(ve,{type:xe.auto_ban?"danger":"info",size:"small"},{default:$(()=>[ue(Q(xe.auto_ban?"开启":"关闭"),1)]),_:2},1032,["type"])]),_:2},1024),L(he,{label:"连续失败"},{default:$(()=>[C("span",{class:U({"text-danger":(xe.fail_count??0)>=3})},Q(xe.fail_count??0)+" 次",3)]),_:2},1024),L(he,{label:"分组"},{default:$(()=>[L(ve,{size:"small",type:"info"},{default:$(()=>[ue(Q(xe.group),1)]),_:2},1024)]),_:2},1024),L(he,{label:"环境"},{default:$(()=>[L(ve,{type:xe.is_prod?"danger":"warning",size:"small"},{default:$(()=>[ue(Q(xe.is_prod?"生产":"沙箱"),1)]),_:2},1032,["type"])]),_:2},1024)]),_:2},1024),C("div",iRe,[L(X,{size:"small",text:"",type:"primary",onClick:rt=>M(xe)},{default:$(()=>[...V[26]||(V[26]=[ue("编辑",-1)])]),_:1},8,["onClick"]),L(X,{size:"small",text:"",type:"primary",onClick:rt=>E(xe)},{default:$(()=>[...V[27]||(V[27]=[ue("查看/复制完整密钥",-1)])]),_:1},8,["onClick"]),xe.status==="normal"?(x(),ne(X,{key:0,size:"small",text:"",type:"warning",onClick:rt=>G(xe.id)},{default:$(()=>[...V[28]||(V[28]=[ue("冻结",-1)])]),_:1},8,["onClick"])):te("",!0),xe.status==="frozen"?(x(),ne(X,{key:1,size:"small",text:"",type:"success",onClick:rt=>K(xe.id)},{default:$(()=>[...V[29]||(V[29]=[ue("解冻",-1)])]),_:1},8,["onClick"])):te("",!0),xe.status==="disabled"?(x(),ne(X,{key:2,size:"small",text:"",type:"success",onClick:rt=>H(xe.id)},{default:$(()=>[...V[30]||(V[30]=[ue("启用",-1)])]),_:1},8,["onClick"])):te("",!0),xe.status==="normal"?(x(),ne(X,{key:3,size:"small",text:"",type:"danger",onClick:rt=>z(xe.id)},{default:$(()=>[...V[31]||(V[31]=[ue("禁用",-1)])]),_:1},8,["onClick"])):te("",!0),L(X,{size:"small",text:"",type:"danger",onClick:rt=>B(xe)},{default:$(()=>[...V[32]||(V[32]=[ue("删除",-1)])]),_:1},8,["onClick"]),L(X,{size:"small",text:"",onClick:rt=>W(xe)},{default:$(()=>[...V[33]||(V[33]=[ue("日志",-1)])]),_:1},8,["onClick"])])]))),128)),!g.value.length&&!a.value?(x(),O("div",lRe,[L(Z,{description:"账户池为空,请按以下步骤配置第一个支付账户","image-size":80}),L(oe,{active:1,"align-center":"",class:"setup-steps"},{default:$(()=>[L(j,{title:"准备密钥",description:"在支付宝开放平台/微信商户平台获取 AppID、私钥、公钥"}),L(j,{title:"点击「新增账户」",description:"选择通道并粘贴密钥,保存后 AES-256-GCM 加密入库"}),L(j,{title:"通道即刻生效",description:"无需重启,账户自动进入账户池参与下单调度"})]),_:1}),L(ee,{type:"info",closable:!1,class:"mt12",title:"已移除 .env 密钥迁移:支付密钥只通过本界面配置,加密存储于独立 accounts.db,不再依赖环境变量。"})])):te("",!0)])),[[ze,a.value]])])]),L(Le,{modelValue:o.value,"onUpdate:modelValue":V[16]||(V[16]=xe=>o.value=xe),title:i.value?"编辑账户":"新增账户",width:"600px","close-on-click-modal":!1},{footer:$(()=>[L(X,{onClick:V[15]||(V[15]=xe=>o.value=!1)},{default:$(()=>[...V[39]||(V[39]=[ue("取消",-1)])]),_:1}),L(X,{type:"primary",loading:l.value,onClick:R},{default:$(()=>[ue(Q(i.value?"保存":"创建"),1)]),_:1},8,["loading"])]),default:$(()=>[L(Ce,{model:s.value,"label-width":"100px",size:"small"},{default:$(()=>[L(se,{label:"通道",required:""},{default:$(()=>[L(le,{modelValue:s.value.channel,"onUpdate:modelValue":V[1]||(V[1]=xe=>s.value.channel=xe),placeholder:"选择通道",style:{width:"100%"}},{default:$(()=>[L(re,{label:"支付宝(商家)",value:"alipay"}),L(re,{label:"微信支付(商家)",value:"wechat"}),L(re,{label:"个人免签·支付宝",value:"personal.alipay"}),L(re,{label:"个人免签·微信",value:"personal.wechat"})]),_:1},8,["modelValue"])]),_:1}),L(se,{label:"账户名称",required:""},{default:$(()=>[L(fe,{modelValue:s.value.name,"onUpdate:modelValue":V[2]||(V[2]=xe=>s.value.name=xe),placeholder:"例:支付宝-主账户 / 个人码-支付宝-测试1"},null,8,["modelValue"])]),_:1}),k(s.value.channel)?te("",!0):(x(),O(Ve,{key:0},[L(Se,{gutter:12},{default:$(()=>[L(ge,{span:12},{default:$(()=>[L(se,{label:"AppID",required:""},{default:$(()=>[L(fe,{modelValue:s.value.app_id,"onUpdate:modelValue":V[3]||(V[3]=xe=>s.value.app_id=xe),placeholder:"支付宝 APPID / 微信 AppID"},null,8,["modelValue"])]),_:1})]),_:1}),L(ge,{span:12},{default:$(()=>[L(se,{label:"商户号"},{default:$(()=>[L(fe,{modelValue:s.value.mch_id,"onUpdate:modelValue":V[4]||(V[4]=xe=>s.value.mch_id=xe),placeholder:"微信商户号"},null,8,["modelValue"])]),_:1})]),_:1})]),_:1}),L(se,{label:"应用私钥",required:""},{default:$(()=>[L(fe,{modelValue:s.value.private_key,"onUpdate:modelValue":V[5]||(V[5]=xe=>s.value.private_key=xe),type:"textarea",rows:3,placeholder:i.value&&s.value.private_key_configured?"已配置(留空保持不变)":"粘贴应用私钥(AES-256-GCM 加密存储)"},null,8,["modelValue","placeholder"]),i.value&&s.value.private_key_configured?(x(),ne(ve,{key:0,size:"small",type:"success"},{default:$(()=>[...V[34]||(V[34]=[ue("已配置",-1)])]),_:1})):te("",!0)]),_:1}),L(se,{label:"公钥/API密钥"},{default:$(()=>[L(fe,{modelValue:s.value.public_key,"onUpdate:modelValue":V[6]||(V[6]=xe=>s.value.public_key=xe),type:"textarea",rows:2,placeholder:i.value&&(s.value.public_key_configured||s.value.api_key_configured)?"已配置(留空保持不变)":"支付宝公钥 或 微信 API 密钥(加密存储)"},null,8,["modelValue","placeholder"]),i.value&&(s.value.public_key_configured||s.value.api_key_configured)?(x(),ne(ve,{key:0,size:"small",type:"success"},{default:$(()=>[...V[35]||(V[35]=[ue("已配置",-1)])]),_:1})):te("",!0)]),_:1})],64)),k(s.value.channel)?(x(),ne(se,{key:1,label:"收款码内容",required:!i.value},{default:$(()=>[L(fe,{modelValue:s.value.qr_content,"onUpdate:modelValue":V[7]||(V[7]=xe=>s.value.qr_content=xe),type:"textarea",rows:2,placeholder:i.value&&s.value.qr_content_configured?"已配置(留空保持不变)":"收款码内容,如 https://qr.alipay.com/xxx"},null,8,["modelValue","placeholder"]),i.value&&s.value.qr_content_configured?(x(),ne(ve,{key:0,size:"small",type:"success"},{default:$(()=>[...V[36]||(V[36]=[ue("已配置",-1)])]),_:1})):te("",!0),C("div",sRe,[L(De,{"show-file-list":!1,"before-upload":P,accept:"image/png,image/jpeg,image/gif",disabled:I.value},{default:$(()=>[L(X,{type:"primary",plain:"",size:"small",loading:I.value},{default:$(()=>[L(Y,null,{default:$(()=>[L(p(NI))]),_:1}),V[37]||(V[37]=ue(" 上传收款码图片自动解析 ",-1))]),_:1},8,["loading"])]),_:1},8,["disabled"]),V[38]||(V[38]=C("span",{class:"text-muted",style:{"font-size":"12px"}},"从支付宝/微信 App「保存收款码」导出图片,上传后自动识别",-1))])]),_:1},8,["required"])):te("",!0),L(Se,{gutter:12},{default:$(()=>[L(ge,{span:8},{default:$(()=>[L(se,{label:"权重"},{default:$(()=>[L(Te,{modelValue:s.value.weight,"onUpdate:modelValue":V[8]||(V[8]=xe=>s.value.weight=xe),min:1,max:999,style:{width:"100%"}},null,8,["modelValue"])]),_:1})]),_:1}),L(ge,{span:8},{default:$(()=>[L(se,{label:"优先级"},{default:$(()=>[L(Te,{modelValue:s.value.priority,"onUpdate:modelValue":V[9]||(V[9]=xe=>s.value.priority=xe),min:0,max:99,style:{width:"100%"}},null,8,["modelValue"])]),_:1})]),_:1}),L(ge,{span:8},{default:$(()=>[L(se,{label:"日限额(元)"},{default:$(()=>[L(Te,{modelValue:s.value.dailyLimitYuan,"onUpdate:modelValue":V[10]||(V[10]=xe=>s.value.dailyLimitYuan=xe),min:0,step:100,style:{width:"100%"}},null,8,["modelValue"])]),_:1})]),_:1})]),_:1}),L(Se,{gutter:12},{default:$(()=>[L(ge,{span:8},{default:$(()=>[L(se,{label:"自动禁用"},{default:$(()=>[L(Re,{modelValue:s.value.auto_ban,"onUpdate:modelValue":V[11]||(V[11]=xe=>s.value.auto_ban=xe)},null,8,["modelValue"])]),_:1})]),_:1}),L(ge,{span:8},{default:$(()=>[L(se,{label:"生产环境"},{default:$(()=>[L(Re,{modelValue:s.value.is_prod,"onUpdate:modelValue":V[12]||(V[12]=xe=>s.value.is_prod=xe)},null,8,["modelValue"])]),_:1})]),_:1}),L(ge,{span:8},{default:$(()=>[L(se,{label:"分组"},{default:$(()=>[L(fe,{modelValue:s.value.group,"onUpdate:modelValue":V[13]||(V[13]=xe=>s.value.group=xe),placeholder:"default"},null,8,["modelValue"])]),_:1})]),_:1})]),_:1}),L(se,{label:"备注"},{default:$(()=>[L(fe,{modelValue:s.value.remark,"onUpdate:modelValue":V[14]||(V[14]=xe=>s.value.remark=xe),type:"textarea",rows:2},null,8,["modelValue"])]),_:1})]),_:1},8,["model"])]),_:1},8,["modelValue","title"]),L(Le,{modelValue:v.value,"onUpdate:modelValue":V[22]||(V[22]=xe=>v.value=xe),title:"完整密钥",width:"640px"},{default:$(()=>[L(ee,{type:"warning",closable:!1,class:"mb16",title:"以下为明文密钥,请勿泄露或截图分享。复制后请及时关闭。"}),st((x(),O("div",uRe,[h.value?(x(),O(Ve,{key:0},[k(h.value.channel)?(x(),O("div",cRe,[V[41]||(V[41]=C("div",{class:"secret-label"},"收款码内容",-1)),C("div",dRe,[C("span",fRe,Q(h.value.qr_content||"—"),1),h.value.qr_content?(x(),ne(X,{key:0,size:"small",text:"",type:"primary",onClick:V[17]||(V[17]=xe=>A(h.value.qr_content))},{default:$(()=>[...V[40]||(V[40]=[ue("复制",-1)])]),_:1})):te("",!0)])])):(x(),O(Ve,{key:1},[C("div",vRe,[V[43]||(V[43]=C("div",{class:"secret-label"},"AppID",-1)),C("div",hRe,[C("span",pRe,Q(h.value.app_id||"—"),1),h.value.app_id?(x(),ne(X,{key:0,size:"small",text:"",type:"primary",onClick:V[18]||(V[18]=xe=>A(h.value.app_id))},{default:$(()=>[...V[42]||(V[42]=[ue("复制",-1)])]),_:1})):te("",!0)])]),h.value.mch_id?(x(),O("div",mRe,[V[45]||(V[45]=C("div",{class:"secret-label"},"商户号",-1)),C("div",gRe,[C("span",yRe,Q(h.value.mch_id),1),L(X,{size:"small",text:"",type:"primary",onClick:V[19]||(V[19]=xe=>A(h.value.mch_id))},{default:$(()=>[...V[44]||(V[44]=[ue("复制",-1)])]),_:1})])])):te("",!0),C("div",bRe,[V[47]||(V[47]=C("div",{class:"secret-label"},"应用私钥",-1)),C("div",_Re,[C("span",wRe,Q(h.value.private_key||"(未配置)"),1),h.value.private_key?(x(),ne(X,{key:0,size:"small",text:"",type:"primary",onClick:V[20]||(V[20]=xe=>A(h.value.private_key))},{default:$(()=>[...V[46]||(V[46]=[ue("复制",-1)])]),_:1})):te("",!0)])]),C("div",SRe,[V[49]||(V[49]=C("div",{class:"secret-label"},"公钥/API密钥",-1)),C("div",CRe,[C("span",xRe,Q(h.value.public_key||h.value.api_key||"(未配置)"),1),h.value.public_key||h.value.api_key?(x(),ne(X,{key:0,size:"small",text:"",type:"primary",onClick:V[21]||(V[21]=xe=>A(h.value.public_key||h.value.api_key))},{default:$(()=>[...V[48]||(V[48]=[ue("复制",-1)])]),_:1})):te("",!0)])])],64))],64)):te("",!0)])),[[ze,f.value]])]),_:1},8,["modelValue"]),L(Le,{modelValue:u.value,"onUpdate:modelValue":V[23]||(V[23]=xe=>u.value=xe),title:"账户操作日志",width:"600px"},{default:$(()=>[st((x(),O("div",null,[d.value.length?(x(),ne(Be,{key:0},{default:$(()=>[(x(!0),O(Ve,null,dt(d.value,xe=>(x(),ne(me,{key:xe.id,timestamp:xe.created_at,placement:"top"},{default:$(()=>[C("div",kRe,[L(ve,{type:S(xe.action),size:"small"},{default:$(()=>[ue(Q(xe.action),1)]),_:2},1032,["type"]),C("span",TRe,Q(xe.detail),1),C("span",MRe,"— "+Q(xe.operator||"系统"),1)])]),_:2},1032,["timestamp"]))),128))]),_:1})):(x(),ne(Z,{key:1,description:"暂无操作日志","image-size":60}))])),[[ze,c.value]])]),_:1},8,["modelValue"])])}}},ARe=fr(ERe,[["__scopeId","data-v-e8a4a312"]]),IRe={class:"upstreams-page"},LRe={class:"pg-card"},PRe={class:"pg-card-header"},DRe={class:"header-actions"},ORe={class:"text-muted",style:{"font-size":"12px"}},$Re={class:"pg-card-body"},RRe={class:"upstream-grid"},NRe={class:"upstream-head"},BRe={class:"upstream-logo"},VRe={class:"upstream-info"},zRe={class:"upstream-name"},FRe={class:"upstream-pid font-mono text-muted"},HRe={class:"font-mono",style:{"font-size":"12px"}},WRe={class:"font-mono text-muted",style:{"font-size":"12px"}},KRe={class:"font-mono text-muted",style:{"font-size":"12px"}},URe={style:{"font-size":"12px"}},GRe={class:"upstream-actions"},YRe={key:0,class:"empty-card pg-card"},jRe={key:0,class:"guide-box"},qRe={class:"guide-steps mb16"},XRe={class:"guide-step"},ZRe={class:"guide-step-body"},JRe={class:"guide-step-desc"},QRe={class:"guide-step"},eNe={class:"guide-step-body"},tNe={class:"guide-step-desc"},nNe={class:"guide-callback font-mono"},aNe={class:"font-mono"},rNe={class:"font-mono",style:{"word-break":"break-all"}},oNe={class:"font-mono",style:{"font-size":"12px"}},iNe={class:"log-item"},lNe={class:"log-detail"},sNe={class:"log-operator text-muted"},uNe={__name:"Upstreams",props:{refreshKey:Number},setup(e){const t=e,n=F([]),a=F(!1),r=F(!1),o=F(!1),i=F(!1),l=F(g()),s=F(!1),u=F(!1),c=F(""),d=F(""),v=F(null),f=F(!1),h=F(!1),m=F([]);ye(()=>t.refreshKey,y);function g(){return{id:0,name:"",pid:"",key:"",base_url:"",notify_domains:"",remark:""}}async function y(){a.value=!0;try{n.value=await yt.upstreams()}catch(I){We.error("加载失败: "+I.message)}finally{a.value=!1}}function b(I){return{create:"success",update:"primary",delete:"danger",enable:"success",disable:"info"}[I]||"info"}function _(){o.value=!1,l.value=g(),r.value=!0}function w(I){o.value=!0,l.value={id:I.id,name:I.name,pid:I.pid,key:"",base_url:I.base_url,notify_domains:I.notify_domains,remark:I.remark},r.value=!0}async function S(){if(!l.value.name||!l.value.pid){We.warning("请填写名称和商户ID (pid)");return}i.value=!0;try{const I={name:l.value.name,pid:l.value.pid,key:l.value.key,base_url:l.value.base_url,notify_domains:l.value.notify_domains,remark:l.value.remark};o.value?(await yt.updateUpstream(l.value.id,I),We.success("上游已更新")):(await yt.createUpstream(I),We.success("上游创建成功")),r.value=!1,y()}catch(I){We.error("保存失败: "+I.message)}finally{i.value=!1}}async function k(I){try{await yt.enableUpstream(I),We.success("已启用"),y()}catch(P){We.error(P.message)}}async function T(I){try{await yt.disableUpstream(I),We.success("已禁用"),y()}catch(P){We.error(P.message)}}async function M(I){try{await er.confirm(`确定删除上游「${I.name}」?`,"确认删除",{confirmButtonText:"删除",cancelButtonText:"取消",type:"warning"}),await yt.deleteUpstream(I.id),We.success("已删除"),y()}catch(P){P!=="cancel"&&We.error(P.message)}}function E(I){er.confirm(`将显示上游「${I.name}」的完整明文密钥,请确认当前环境安全。`,"查看完整密钥",{confirmButtonText:"确认查看",cancelButtonText:"取消",type:"warning"}).then(async()=>{v.value=I,c.value="",d.value="",s.value=!0,u.value=!0;try{const P=await yt.upstreamSecret(I.id);c.value=P.key||"";const R=await yt.epayInfo();d.value=((R==null?void 0:R.submit_url)||"").replace("/submit.php","/alipay/notify")}catch(P){We.error("获取完整密钥失败: "+P.message)}finally{u.value=!1}}).catch(()=>{})}async function A(){if(!v.value)return;const I=c.value||v.value.key,P=`商户ID (pid): ${v.value.pid} +商户密钥: ${I} +网关地址: ${v.value.base_url||""} +下单接口: ${(v.value.base_url||"")+"/submit.php"} +查询接口: ${(v.value.base_url||"")+"/api.php"} +回调端点 notify_url: ${d.value||""}`;try{await navigator.clipboard.writeText(P),We.success("已复制")}catch(R){We.error("复制失败: "+R.message)}}return y(),(I,P)=>{const R=be("el-icon"),G=be("el-button"),K=be("el-alert"),H=be("el-tag"),z=be("el-descriptions-item"),B=be("el-descriptions"),W=be("el-empty"),N=be("el-step"),V=be("el-steps"),Y=be("el-input"),X=be("el-form-item"),ee=be("el-form"),re=be("el-dialog"),ie=be("el-divider"),le=be("el-timeline-item"),ve=be("el-timeline"),he=Gr("loading");return x(),O("div",IRe,[C("div",LRe,[C("div",PRe,[P[11]||(P[11]=C("span",{class:"section-label"},"上游对接方管理(多商户)",-1)),C("div",DRe,[C("span",ORe,"共 "+Q(n.value.length)+" 个上游",1),L(G,{type:"primary",size:"small",onClick:_},{default:$(()=>[L(R,null,{default:$(()=>[L(p(hf))]),_:1}),P[10]||(P[10]=ue(" 新增上游 ",-1))]),_:1})])]),C("div",$Re,[L(K,{type:"info",closable:!1,class:"mb16",title:"每个上游拥有独立的易支付 pid + 密钥:下单验签、回调签名均按 pid 路由,互不伪造。"}),st((x(),O("div",RRe,[(x(!0),O(Ve,null,dt(n.value,J=>(x(),O("div",{key:J.id,class:"upstream-card pg-card"},[C("div",NRe,[C("div",BRe,Q(J.name.charAt(0).toUpperCase()),1),C("div",VRe,[C("div",zRe,Q(J.name),1),C("div",FRe,"pid: "+Q(J.pid),1)]),L(H,{type:J.status==="enabled"?"success":"info",size:"small",effect:"light",round:""},{default:$(()=>[ue(Q(J.status==="enabled"?"启用":"禁用"),1)]),_:2},1032,["type"])]),L(B,{column:1,size:"small",class:"mt10",border:""},{default:$(()=>[L(z,{label:"服务地址"},{default:$(()=>[C("span",HRe,Q(J.base_url||"—"),1)]),_:2},1024),L(z,{label:"商户密钥"},{default:$(()=>[C("span",WRe,Q(J.key||"—"),1)]),_:2},1024),L(z,{label:"回调域名白名单"},{default:$(()=>[C("span",KRe,Q(J.notify_domains||"不限"),1)]),_:2},1024),L(z,{label:"备注"},{default:$(()=>[C("span",URe,Q(J.remark||"—"),1)]),_:2},1024)]),_:2},1024),C("div",GRe,[L(G,{size:"small",text:"",type:"primary",onClick:Z=>w(J)},{default:$(()=>[...P[12]||(P[12]=[ue("编辑",-1)])]),_:1},8,["onClick"]),J.status==="disabled"?(x(),ne(G,{key:0,size:"small",text:"",type:"success",onClick:Z=>k(J.id)},{default:$(()=>[...P[13]||(P[13]=[ue("启用",-1)])]),_:1},8,["onClick"])):te("",!0),J.status==="enabled"?(x(),ne(G,{key:1,size:"small",text:"",type:"warning",onClick:Z=>T(J.id)},{default:$(()=>[...P[14]||(P[14]=[ue("禁用",-1)])]),_:1},8,["onClick"])):te("",!0),L(G,{size:"small",text:"",type:"danger",onClick:Z=>M(J)},{default:$(()=>[...P[15]||(P[15]=[ue("删除",-1)])]),_:1},8,["onClick"]),L(G,{size:"small",text:"",onClick:Z=>E(J)},{default:$(()=>[...P[16]||(P[16]=[ue("对接指引",-1)])]),_:1},8,["onClick"])])]))),128)),!n.value.length&&!a.value?(x(),O("div",YRe,[L(W,{description:"暂无上游配置,系统已自动创建 default","image-size":80}),L(V,{active:1,"align-center":"",class:"setup-steps"},{default:$(()=>[L(N,{title:"新增上游",description:"为每个对接项目创建独立条目(如 new-api)"}),L(N,{title:"分配 pid + 密钥",description:"保存后立即生效,无需重启;密钥加密入库"}),L(N,{title:"告知上游",description:"把对接信息配置到上游项目的易支付设置"})]),_:1})])):te("",!0)])),[[he,a.value]])])]),L(re,{modelValue:r.value,"onUpdate:modelValue":P[7]||(P[7]=J=>r.value=J),title:o.value?"编辑上游":"新增上游",width:"560px","close-on-click-modal":!1},{footer:$(()=>[L(G,{onClick:P[6]||(P[6]=J=>r.value=!1)},{default:$(()=>[...P[18]||(P[18]=[ue("取消",-1)])]),_:1}),L(G,{type:"primary",loading:i.value,onClick:S},{default:$(()=>[ue(Q(o.value?"保存":"创建"),1)]),_:1},8,["loading"])]),default:$(()=>[L(ee,{model:l.value,"label-width":"120px",size:"small"},{default:$(()=>[L(X,{label:"名称",required:""},{default:$(()=>[L(Y,{modelValue:l.value.name,"onUpdate:modelValue":P[0]||(P[0]=J=>l.value.name=J),placeholder:"例:new-api"},null,8,["modelValue"])]),_:1}),L(X,{label:"商户ID (pid)",required:""},{default:$(()=>[L(Y,{modelValue:l.value.pid,"onUpdate:modelValue":P[1]||(P[1]=J=>l.value.pid=J),placeholder:"易支付商户ID,唯一"},null,8,["modelValue"])]),_:1}),L(X,{label:"易支付密钥"},{default:$(()=>[L(Y,{modelValue:l.value.key,"onUpdate:modelValue":P[2]||(P[2]=J=>l.value.key=J),type:"textarea",rows:2,placeholder:o.value?"留空表示不修改":"粘贴易支付密钥(AES-256-GCM 加密存储)","show-password":""},null,8,["modelValue","placeholder"])]),_:1}),L(X,{label:"服务地址"},{default:$(()=>[L(Y,{modelValue:l.value.base_url,"onUpdate:modelValue":P[3]||(P[3]=J=>l.value.base_url=J),placeholder:"https://new-api.example.com"},null,8,["modelValue"])]),_:1}),L(X,{"label-width":"0",style:{"margin-top":"-10px"}},{default:$(()=>[L(K,{type:"warning",closable:!1,"show-icon":"",title:"该地址必须是这个上游商户自己的公网域名!"}),P[17]||(P[17]=C("div",{class:"help-text",style:{"margin-top":"6px"}},[ue(" 填"),C("b",null,"该商户站点"),ue("(如 new-api)用户实际打开的公网地址——支付完成后浏览器会 "),C("b",null,"跳回这里"),ue("(网关在商户传来的 return_url 是内网地址时用它兜底重写)。 例:new-api 部署在 "),C("code",null,"https://new-api.example.com"),ue(",就填它。 不要填网关自己的域名(paygateway 的),也不要填 "),C("code",null,"localhost"),ue("/内网。 ")],-1))]),_:1}),L(X,{label:"回调域名白名单"},{default:$(()=>[L(Y,{modelValue:l.value.notify_domains,"onUpdate:modelValue":P[4]||(P[4]=J=>l.value.notify_domains=J),placeholder:"可选,逗号分隔:api.example.com,example.org"},null,8,["modelValue"])]),_:1}),L(X,{label:"备注"},{default:$(()=>[L(Y,{modelValue:l.value.remark,"onUpdate:modelValue":P[5]||(P[5]=J=>l.value.remark=J),type:"textarea",rows:2,placeholder:"例:New API 充值+订阅"},null,8,["modelValue"])]),_:1})]),_:1},8,["model"])]),_:1},8,["modelValue","title"]),L(re,{modelValue:s.value,"onUpdate:modelValue":P[8]||(P[8]=J=>s.value=J),title:"对接指引",width:"620px"},{default:$(()=>[v.value?st((x(),O("div",jRe,[L(K,{type:"info",closable:!1,class:"mb16","show-icon":"",title:"对接逻辑:pid 和密钥都由「上游项目」配置,paygateway 这里只是登记相同的值用于验签;只有回调端点是上游需要填的。"}),C("div",qRe,[C("div",XRe,[P[24]||(P[24]=C("div",{class:"guide-step-num"},"①",-1)),C("div",ZRe,[P[23]||(P[23]=C("div",{class:"guide-step-title"},"在上游项目(如 New API)配置易支付",-1)),C("div",JRe,[P[21]||(P[21]=C("div",null,[ue("· pid、密钥:"),C("b",null,"由上游项目自己配置/生成"),ue(",把这两个值记下来")],-1)),C("div",null,[P[19]||(P[19]=ue("· 网关地址 / 下单接口:填 ",-1)),C("code",null,Q((v.value.base_url||"http://:8080")+"/submit.php"),1)]),C("div",null,[P[20]||(P[20]=ue("· 查询接口:填 ",-1)),C("code",null,Q((v.value.base_url||"http://:8080")+"/api.php"),1)]),P[22]||(P[22]=C("div",{class:"guide-note"},[ue(" ⚠️ 上述网关地址必须用"),C("b",null,"公网域名"),ue("(如 "),C("code",null,"https://paygateway.de5.net"),ue("), 不能填 "),C("code",null,"localhost"),ue("/"),C("code",null,"127.0.0.1"),ue("/内网 IP——否则支付界面在其他机器上打不开。 ")],-1))])])]),P[28]||(P[28]=C("div",{class:"guide-step"},[C("div",{class:"guide-step-num"},"②"),C("div",{class:"guide-step-body"},[C("div",{class:"guide-step-title"},"把上游的 pid 和密钥登记到这里(本页上游配置)"),C("div",{class:"guide-step-desc"},[ue(" 编辑本上游,把上一步记下的 "),C("b",null,"pid"),ue(" 和 "),C("b",null,"密钥"),ue(" 填进来。两边的值必须"),C("b",null,"完全一致"),ue(",paygateway 才能验签通过。 ")])])],-1)),C("div",QRe,[P[27]||(P[27]=C("div",{class:"guide-step-num"},"③",-1)),C("div",eNe,[P[26]||(P[26]=C("div",{class:"guide-step-title"},"上游项目填回调端点 notify_url",-1)),C("div",tNe,[P[25]||(P[25]=C("div",null,[ue("这是"),C("b",null,"上游需要配置"),ue("的唯一回调项,指向 paygateway 的回调地址:")],-1)),C("div",nNe,Q(d.value||"(启动后自动生成,见「支付通道」页)"),1)])])])]),L(ie,null,{default:$(()=>[...P[29]||(P[29]=[ue("当前登记的 pid / 密钥",-1)])]),_:1}),u.value?te("",!0):(x(),ne(K,{key:0,type:"warning",closable:!1,class:"mb16",title:"密钥为明文展示,复制后请勿泄露。"})),L(B,{column:1,size:"small",border:""},{default:$(()=>[L(z,{label:"商户ID (pid)"},{default:$(()=>[C("span",aNe,Q(v.value.pid),1)]),_:1}),L(z,{label:"商户密钥"},{default:$(()=>[C("span",rNe,Q(c.value||v.value.key),1),L(G,{size:"small",text:"",type:"primary",onClick:A},{default:$(()=>[...P[30]||(P[30]=[ue("复制",-1)])]),_:1})]),_:1}),L(z,{label:"网关地址"},{default:$(()=>[C("span",oNe,Q(v.value.base_url||"(未配置)"),1)]),_:1})]),_:1})])),[[he,u.value]]):te("",!0)]),_:1},8,["modelValue"]),L(re,{modelValue:f.value,"onUpdate:modelValue":P[9]||(P[9]=J=>f.value=J),title:"上游操作日志",width:"600px"},{default:$(()=>[st((x(),O("div",null,[m.value.length?(x(),ne(ve,{key:0},{default:$(()=>[(x(!0),O(Ve,null,dt(m.value,J=>(x(),ne(le,{key:J.id,timestamp:J.created_at,placement:"top"},{default:$(()=>[C("div",iNe,[L(H,{type:b(J.action),size:"small"},{default:$(()=>[ue(Q(J.action),1)]),_:2},1032,["type"]),C("span",lNe,Q(J.detail),1),C("span",sNe,"— "+Q(J.operator||"系统"),1)])]),_:2},1032,["timestamp"]))),128))]),_:1})):(x(),ne(W,{key:1,description:"暂无操作日志","image-size":60}))])),[[he,h.value]])]),_:1},8,["modelValue"])])}}},cNe=fr(uNe,[["__scopeId","data-v-cefda71d"]]),dNe={class:"reconciler-page"},fNe={class:"filter-bar"},vNe={class:"text-muted",style:{"margin-left":"auto","font-size":"12px"}},hNe={key:0,class:"pager"},pNe={class:"daily-report"},mNe={class:"report-head"},gNe={key:0,class:"mt16"},yNe={class:"report-stats"},bNe={class:"stat-box pg-card"},_Ne={class:"stat-val"},wNe={class:"stat-box pg-card"},SNe={class:"stat-val text-success"},CNe={class:"stat-box pg-card"},xNe={class:"stat-val text-warning"},kNe={class:"stat-box pg-card"},TNe={class:"stat-box pg-card"},MNe={key:1,class:"empty-text"},ENe={class:"font-mono"},ANe={class:"font-mono"},INe={class:"evidence-box"},LNe={key:0,class:"text-muted",style:{"font-size":"12px"}},PNe={key:1,class:"text-muted"},Ub=20,DNe={__name:"Reconciler",setup(e){const t=F("exceptions"),n=F(!1),a=F([]),r=F(0),o=F(1),i=F({phase:"pending",reason:""}),l=F(!1),s=F(null),u=F([]),c=F(!1),d=F({action:"settle",paid_amount:0,channel_tx_id:"",payer:"",note:""}),v=F(!1),f=F(null),h=F(!1),m=F({out_trade_no:"",note:""}),g=F(!1),y=F([]),b=F(new Date().toISOString().slice(0,10)),_=F(null),w=F(!1);async function S(){w.value=!0;try{_.value=await yt.dailyReport({date:b.value})}catch(W){We.error("生成报告失败:"+W.message)}finally{w.value=!1}}function k(W){return{query_failed:"danger",amount_mismatch:"warning",state_conflict:"warning",close_uncertain:"danger",notify_dead:"info",manual:""}[W]||""}function T(W){return{query_failed:"查单失败",amount_mismatch:"金额不一致",state_conflict:"状态矛盾",close_uncertain:"关单存疑",notify_dead:"通知dead",manual:"手动加入"}[W]||W}async function M(){n.value=!0;try{const W=await yt.listExceptions({phase:i.value.phase,reason:i.value.reason,page:o.value,page_size:Ub});a.value=W.list||[],r.value=W.total||0}catch(W){We.error("加载失败: "+W.message)}finally{n.value=!1}}async function E(W){try{const N=await yt.getException(W.id);s.value=N.exception,u.value=N.logs||[],l.value=!0}catch(N){We.error("加载详情失败: "+N.message)}}async function A(W){var N;try{await yt.requeryException(W),We.success("查单完成"),((N=s.value)==null?void 0:N.id)===W&&E({id:W})}catch(V){We.error("查单失败: "+V.message)}}function I(W){f.value=W,d.value={action:"settle",paid_amount:W.amount_minor/100,channel_tx_id:W.channel_tx_id||"",payer:"",note:""},c.value=!0}async function P(){v.value=!0;try{const W={action:d.value.action,paid_amount:Math.round(d.value.paid_amount*100),channel_tx_id:d.value.channel_tx_id,payer:d.value.payer,note:d.value.note};await yt.resolveException(f.value.id,W),We.success("处理成功"),c.value=!1,M()}catch(W){We.error("处理失败: "+W.message)}finally{v.value=!1}}async function R(W){try{await yt.reopenException(W),We.success("已重新打开"),l.value=!1,M()}catch(N){We.error("操作失败: "+N.message)}}function G(){m.value={out_trade_no:"",note:""},h.value=!0}async function K(){try{await yt.addException(m.value),We.success("已加入异常池"),h.value=!1,M()}catch(W){We.error("操作失败: "+W.message)}}async function H(){g.value=!0;try{const W=await yt.listDeadNotify();y.value=(W.list||[]).map(N=>{let V="";try{N.payload&&(V=JSON.parse(N.payload).out_trade_no||"")}catch{}return{...N,out_trade_no:V}})}catch(W){We.error("加载失败: "+W.message)}finally{g.value=!1}}async function z(W){try{await yt.retryDeadNotify(W),We.success("已按最新回调地址重新入队"),H()}catch(N){We.error("重发失败: "+N.message)}}async function B(W){try{await er.confirm("彻底删除该死信通知任务?若该订单已支付但未通知到商户,删除后将无法自动补发。","确认删除",{confirmButtonText:"删除",cancelButtonText:"取消",type:"warning"})}catch{return}try{await yt.deleteDeadNotify(W),We.success("已删除"),H()}catch(N){We.error("删除失败: "+N.message)}}return mt(()=>{M(),S()}),(W,N)=>{const V=be("el-option"),Y=be("el-select"),X=be("el-button"),ee=be("el-table-column"),re=be("el-tag"),ie=be("el-table"),le=be("el-pagination"),ve=be("el-tab-pane"),he=be("el-date-picker"),J=be("el-tabs"),Z=be("el-descriptions-item"),j=be("el-descriptions"),oe=be("el-timeline-item"),se=be("el-timeline"),fe=be("el-dialog"),ge=be("el-radio"),Se=be("el-radio-group"),De=be("el-form-item"),Te=be("el-input-number"),Re=be("el-input"),Ce=be("el-form"),Le=Gr("loading");return x(),O("div",dNe,[L(J,{modelValue:t.value,"onUpdate:modelValue":N[4]||(N[4]=me=>t.value=me)},{default:$(()=>[L(ve,{label:"异常订单池",name:"exceptions"},{default:$(()=>[C("div",fNe,[L(Y,{modelValue:i.value.phase,"onUpdate:modelValue":N[0]||(N[0]=me=>i.value.phase=me),placeholder:"阶段",clearable:"",style:{width:"120px"},onChange:M},{default:$(()=>[L(V,{label:"待处理",value:"pending"}),L(V,{label:"处理中",value:"processing"}),L(V,{label:"已处理",value:"resolved"})]),_:1},8,["modelValue"]),L(Y,{modelValue:i.value.reason,"onUpdate:modelValue":N[1]||(N[1]=me=>i.value.reason=me),placeholder:"原因",clearable:"",style:{width:"140px"},onChange:M},{default:$(()=>[L(V,{label:"查单失败",value:"query_failed"}),L(V,{label:"金额不一致",value:"amount_mismatch"}),L(V,{label:"状态矛盾",value:"state_conflict"}),L(V,{label:"关单存疑",value:"close_uncertain"}),L(V,{label:"通知dead",value:"notify_dead"}),L(V,{label:"手动加入",value:"manual"})]),_:1},8,["modelValue"]),L(X,{type:"primary",onClick:G},{default:$(()=>[...N[21]||(N[21]=[ue("手动加入",-1)])]),_:1}),C("span",vNe,"共 "+Q(r.value)+" 条",1)]),st((x(),ne(ie,{data:a.value,size:"small",class:"mt10"},{default:$(()=>[L(ee,{prop:"out_trade_no",label:"订单号","min-width":"160","show-overflow-tooltip":""}),L(ee,{prop:"upstream_name",label:"上游",width:"120"}),L(ee,{prop:"reason",label:"原因",width:"110"},{default:$(({row:me})=>[L(re,{type:k(me.reason),size:"small"},{default:$(()=>[ue(Q(T(me.reason)),1)]),_:2},1032,["type"])]),_:1}),L(ee,{prop:"channel_status",label:"通道状态",width:"110"}),L(ee,{label:"阶段",width:"90"},{default:$(({row:me})=>[L(re,{type:me.phase==="resolved"?"success":me.phase==="processing"?"warning":"danger",size:"small",effect:"light"},{default:$(()=>[ue(Q(me.phase==="pending"?"待处理":me.phase==="processing"?"处理中":"已处理"),1)]),_:2},1032,["type"])]),_:1}),L(ee,{label:"金额",width:"90"},{default:$(({row:me})=>[C("b",null,"¥"+Q((me.amount_minor/100).toFixed(2)),1)]),_:1}),L(ee,{prop:"created_at",label:"创建时间",width:"160"}),L(ee,{label:"",width:"140",fixed:"right"},{default:$(({row:me})=>[L(X,{size:"small",text:"",type:"primary",onClick:at(Be=>E(me),["stop"])},{default:$(()=>[...N[22]||(N[22]=[ue("详情",-1)])]),_:1},8,["onClick"]),me.phase!=="resolved"?(x(),ne(X,{key:0,size:"small",text:"",type:"primary",onClick:at(Be=>I(me),["stop"])},{default:$(()=>[...N[23]||(N[23]=[ue("处理",-1)])]),_:1},8,["onClick"])):te("",!0)]),_:1})]),_:1},8,["data"])),[[Le,n.value]]),r.value>Ub?(x(),O("div",hNe,[L(le,{background:"",layout:"prev,pager,next",total:r.value,"page-size":Ub,"current-page":o.value,"onUpdate:currentPage":N[2]||(N[2]=me=>o.value=me),onCurrentChange:M},null,8,["total","current-page"])])):te("",!0)]),_:1}),L(ve,{label:"Dead 通知",name:"dead"},{default:$(()=>[st((x(),ne(ie,{data:y.value,size:"small",class:"mt10"},{default:$(()=>[L(ee,{prop:"id",label:"任务ID",width:"80"}),L(ee,{prop:"out_trade_no",label:"商户订单号","min-width":"160","show-overflow-tooltip":""}),L(ee,{prop:"retry_count",label:"重试次数",width:"90"}),L(ee,{prop:"created_at",label:"创建时间",width:"160"}),L(ee,{label:"",width:"150"},{default:$(({row:me})=>[L(X,{size:"small",type:"primary",onClick:Be=>z(me.id)},{default:$(()=>[...N[24]||(N[24]=[ue("重发",-1)])]),_:1},8,["onClick"]),L(X,{size:"small",type:"danger",onClick:Be=>B(me.id)},{default:$(()=>[...N[25]||(N[25]=[ue("删除",-1)])]),_:1},8,["onClick"])]),_:1})]),_:1},8,["data"])),[[Le,g.value]])]),_:1}),L(ve,{label:"日终对账",name:"daily"},{default:$(()=>[C("div",pNe,[C("div",mNe,[L(he,{modelValue:b.value,"onUpdate:modelValue":N[3]||(N[3]=me=>b.value=me),type:"date","value-format":"YYYY-MM-DD",size:"small",placeholder:"选择日期"},null,8,["modelValue"]),L(X,{size:"small",type:"primary",loading:w.value,onClick:S},{default:$(()=>[...N[26]||(N[26]=[ue("生成报告",-1)])]),_:1},8,["loading"]),_.value?(x(),ne(re,{key:0,type:_.value.conclusion.startsWith("通过")?"success":"warning",size:"small"},{default:$(()=>[ue(Q(_.value.conclusion),1)]),_:1},8,["type"])):te("",!0)]),_.value?st((x(),O("div",gNe,[C("div",yNe,[C("div",bNe,[C("div",_Ne,Q(_.value.orders_total),1),N[27]||(N[27]=C("div",{class:"stat-label"},"当日订单",-1))]),C("div",wNe,[C("div",SNe,Q(_.value.orders_success),1),N[28]||(N[28]=C("div",{class:"stat-label"},"成功订单",-1))]),C("div",CNe,[C("div",xNe,"¥"+Q((_.value.success_amount/100).toFixed(2)),1),N[29]||(N[29]=C("div",{class:"stat-label"},"成功金额",-1))]),C("div",kNe,[C("div",{class:U(["stat-val",{"text-danger":_.value.notify_dead>0}])},Q(_.value.notify_dead),3),N[30]||(N[30]=C("div",{class:"stat-label"},"回调 Dead",-1))]),C("div",TNe,[C("div",{class:U(["stat-val",{"text-danger":_.value.exceptions_pending>0}])},Q(_.value.exceptions_pending),3),N[31]||(N[31]=C("div",{class:"stat-label"},"异常待处理",-1))])]),L(ie,{data:_.value.by_channel,stripe:"",size:"small",class:"mt16"},{default:$(()=>[L(ee,{prop:"channel",label:"通道","min-width":"160"}),L(ee,{prop:"total_orders",label:"订单",width:"80"}),L(ee,{prop:"success_orders",label:"成功",width:"80"}),L(ee,{label:"成功金额",width:"110"},{default:$(({row:me})=>[ue("¥"+Q((me.amount_minor/100).toFixed(2)),1)]),_:1}),L(ee,{prop:"closed_orders",label:"关闭",width:"80"}),L(ee,{label:"关闭金额",width:"110"},{default:$(({row:me})=>[ue("¥"+Q((me.amount_closed/100).toFixed(2)),1)]),_:1})]),_:1},8,["data"])])),[[Le,w.value]]):(x(),O("div",MNe,"选择日期生成日终对账报告"))])]),_:1})]),_:1},8,["modelValue"]),L(fe,{modelValue:l.value,"onUpdate:modelValue":N[9]||(N[9]=me=>l.value=me),title:"异常单详情",width:"700px"},{footer:$(()=>[L(X,{onClick:N[5]||(N[5]=me=>l.value=!1)},{default:$(()=>[...N[34]||(N[34]=[ue("关闭",-1)])]),_:1}),s.value&&s.value.phase!=="resolved"?(x(),ne(X,{key:0,type:"primary",onClick:N[6]||(N[6]=me=>{l.value=!1,I(s.value)})},{default:$(()=>[...N[35]||(N[35]=[ue("处理",-1)])]),_:1})):te("",!0),s.value&&s.value.phase==="resolved"?(x(),ne(X,{key:1,text:"",onClick:N[7]||(N[7]=me=>R(s.value.id))},{default:$(()=>[...N[36]||(N[36]=[ue("重新打开",-1)])]),_:1})):te("",!0),L(X,{text:"",onClick:N[8]||(N[8]=me=>{var Be;return A((Be=s.value)==null?void 0:Be.id)})},{default:$(()=>[...N[37]||(N[37]=[ue("再次查单",-1)])]),_:1})]),default:$(()=>[s.value?(x(),O(Ve,{key:0},[L(j,{column:2,border:"",size:"small"},{default:$(()=>[L(Z,{label:"订单号",span:2},{default:$(()=>[C("span",ENe,Q(s.value.out_trade_no),1)]),_:1}),L(Z,{label:"上游"},{default:$(()=>[ue(Q(s.value.upstream_name||"—"),1)]),_:1}),L(Z,{label:"通道"},{default:$(()=>[ue(Q(s.value.channel),1)]),_:1}),L(Z,{label:"金额"},{default:$(()=>[ue("¥"+Q((s.value.amount_minor/100).toFixed(2)),1)]),_:1}),L(Z,{label:"通道状态"},{default:$(()=>[ue(Q(s.value.channel_status),1)]),_:1}),L(Z,{label:"交易号"},{default:$(()=>[C("span",ANe,Q(s.value.channel_tx_id||"—"),1)]),_:1}),L(Z,{label:"原因",span:2},{default:$(()=>[ue(Q(T(s.value.reason)),1)]),_:1}),L(Z,{label:"建议",span:2},{default:$(()=>[ue(Q(s.value.suggestion||"—"),1)]),_:1}),L(Z,{label:"处理人"},{default:$(()=>[ue(Q(s.value.handler||"—"),1)]),_:1}),L(Z,{label:"处理动作"},{default:$(()=>[ue(Q(s.value.resolved_action||"—"),1)]),_:1}),L(Z,{label:"备注",span:2},{default:$(()=>[ue(Q(s.value.handler_note||"—"),1)]),_:1})]),_:1}),N[32]||(N[32]=C("h4",{class:"section-title"},"证据时间线",-1)),C("pre",INe,Q(s.value.evidence||"(无)"),1),N[33]||(N[33]=C("h4",{class:"section-title"},"操作日志",-1)),u.value&&u.value.length?(x(),ne(se,{key:0},{default:$(()=>[(x(!0),O(Ve,null,dt(u.value,me=>(x(),ne(oe,{key:me.id,timestamp:me.created_at,placement:"top"},{default:$(()=>[C("p",null,[C("b",null,Q(me.action),1),ue(" — "+Q(me.operator),1)]),me.detail?(x(),O("p",LNe,Q(me.detail),1)):te("",!0)]),_:2},1032,["timestamp"]))),128))]),_:1})):(x(),O("p",PNe,"无操作记录"))],64)):te("",!0)]),_:1},8,["modelValue"]),L(fe,{modelValue:c.value,"onUpdate:modelValue":N[16]||(N[16]=me=>c.value=me),title:"人工处理",width:"500px"},{footer:$(()=>[L(X,{onClick:N[15]||(N[15]=me=>c.value=!1)},{default:$(()=>[...N[42]||(N[42]=[ue("取消",-1)])]),_:1}),L(X,{type:"primary",onClick:P,loading:v.value},{default:$(()=>[...N[43]||(N[43]=[ue("确认处理",-1)])]),_:1},8,["loading"])]),default:$(()=>[L(Ce,{model:d.value,"label-width":"100px"},{default:$(()=>[L(De,{label:"动作"},{default:$(()=>[L(Se,{modelValue:d.value.action,"onUpdate:modelValue":N[10]||(N[10]=me=>d.value.action=me)},{default:$(()=>[L(ge,{value:"settle"},{default:$(()=>[...N[38]||(N[38]=[ue("确认入账",-1)])]),_:1}),L(ge,{value:"close"},{default:$(()=>[...N[39]||(N[39]=[ue("确认未支付,关单",-1)])]),_:1}),L(ge,{value:"noop"},{default:$(()=>[...N[40]||(N[40]=[ue("无需处理",-1)])]),_:1})]),_:1},8,["modelValue"])]),_:1}),d.value.action==="settle"?(x(),ne(De,{key:0,label:"实际金额"},{default:$(()=>[L(Te,{modelValue:d.value.paid_amount,"onUpdate:modelValue":N[11]||(N[11]=me=>d.value.paid_amount=me),min:1,precision:2},null,8,["modelValue"]),N[41]||(N[41]=C("span",{class:"text-muted",style:{"margin-left":"8px"}},"元",-1))]),_:1})):te("",!0),d.value.action==="settle"?(x(),ne(De,{key:1,label:"通道交易号"},{default:$(()=>[L(Re,{modelValue:d.value.channel_tx_id,"onUpdate:modelValue":N[12]||(N[12]=me=>d.value.channel_tx_id=me),placeholder:"从支付宝账单核对后填入"},null,8,["modelValue"])]),_:1})):te("",!0),d.value.action==="settle"?(x(),ne(De,{key:2,label:"付款方"},{default:$(()=>[L(Re,{modelValue:d.value.payer,"onUpdate:modelValue":N[13]||(N[13]=me=>d.value.payer=me),placeholder:"可选"},null,8,["modelValue"])]),_:1})):te("",!0),L(De,{label:"备注"},{default:$(()=>[L(Re,{modelValue:d.value.note,"onUpdate:modelValue":N[14]||(N[14]=me=>d.value.note=me),type:"textarea",rows:2,placeholder:"处理说明"},null,8,["modelValue"])]),_:1})]),_:1},8,["model"])]),_:1},8,["modelValue"]),L(fe,{modelValue:h.value,"onUpdate:modelValue":N[20]||(N[20]=me=>h.value=me),title:"手动加入异常池",width:"400px"},{footer:$(()=>[L(X,{onClick:N[19]||(N[19]=me=>h.value=!1)},{default:$(()=>[...N[44]||(N[44]=[ue("取消",-1)])]),_:1}),L(X,{type:"primary",onClick:K},{default:$(()=>[...N[45]||(N[45]=[ue("加入",-1)])]),_:1})]),default:$(()=>[L(Ce,{model:m.value,"label-width":"100px"},{default:$(()=>[L(De,{label:"订单号"},{default:$(()=>[L(Re,{modelValue:m.value.out_trade_no,"onUpdate:modelValue":N[17]||(N[17]=me=>m.value.out_trade_no=me),placeholder:"输入订单号"},null,8,["modelValue"])]),_:1}),L(De,{label:"说明"},{default:$(()=>[L(Re,{modelValue:m.value.note,"onUpdate:modelValue":N[18]||(N[18]=me=>m.value.note=me),type:"textarea",rows:2},null,8,["modelValue"])]),_:1})]),_:1},8,["model"])]),_:1},8,["modelValue"])])}}},ONe=fr(DNe,[["__scopeId","data-v-e25116cd"]]),oV=F(0);function O3(){oV.value++}const $Ne={class:"device-stream"},RNe={class:"stream-wrap"},NNe={class:"stream-stage"},BNe={key:0,class:"stream-overlay"},VNe={class:"stream-msg"},zNe={__name:"DeviceStream",props:{serials:{type:Array,default:()=>[]},deviceId:{type:Number,default:0}},setup(e){const t=e,n=F([...t.serials]);ye(()=>t.serials,K=>{n.value=[...K]});const a=F(null),r=F(!1),o=F(!1),i=F("连接中…"),l=F("");let s=null,u=null,c=null,d={width:0,height:0},v=null,f=window.devicePixelRatio||1,h=0,m=!1,g=0;function y(){const K=n.value[h]||"",H=location.protocol==="https:"?"wss":"ws",z=sessionStorage.getItem("admin_token")||"";return`${H}://${location.host}/api/personal/devices/stream?serial=${encodeURIComponent(K)}&token=${encodeURIComponent(z)}`}function b(){if(!r.value){if(!n.value.length){w("设备未配置 adb 通道");return}r.value=!0,m=!1,l.value="",i.value=`连接中…(${h+1}/${n.value.length})`;try{s=new WebSocket(y())}catch(K){w("WebSocket 创建失败: "+K.message);return}s.binaryType="arraybuffer",s.onopen=()=>{o.value=!0,i.value="已连接",g=0},s.onmessage=S,s.onclose=()=>{o.value=!1,r.value=!1,M(),!m&&(h=3){w("连接已断开(多次重建失败,请检查设备网络/USB)");return}g++,r.value=!0,i.value=`adb 通道离线,正在重建连接(${g}/3)…`;try{const K=await yt.personalWifiReconnect(t.deviceId),H=K&&K.serial;if(!H)throw new Error("无可用通道");n.value.includes(H)||n.value.unshift(H),h=0,l.value="",setTimeout(b,300)}catch(K){i.value="连接已断开(等待重试)",l.value=K.message||"连接已断开",setTimeout(_,3e3)}}function w(K){r.value=!1,o.value=!1,l.value=K,i.value=K}function S(K){if(typeof K.data=="string"){try{const W=JSON.parse(K.data);W.type==="meta"&&(d=W,E())}catch{}return}const H=new Uint8Array(K.data);if(H.length<1)return;const z=H[0],B=H.slice(1);if(u||k(),!!u)try{const W=new EncodedVideoChunk({type:z===1||z===2?"key":"delta",timestamp:0,data:B});u.decode(W)}catch{}}function k(){if(!u){if(typeof VideoDecoder>"u"){l.value="浏览器不支持 WebCodecs(请使用 Chrome/Edge 94+)",i.value=l.value;return}u=new VideoDecoder({output:K=>{T(K),K.close()},error:()=>{u=null}});try{u.configure({codec:"avc1.42E01E",avc:{format:"annexb"},optimizeForLatency:!0}),i.value="解码就绪"}catch(K){l.value="解码器配置失败: "+K.message,i.value=l.value,u=null}}}function T(K){const H=a.value;if(!H)return;c=c||H.getContext("2d");const z=H.clientWidth,B=H.clientHeight,W=Math.min(z/K.displayWidth,B/K.displayHeight),N=K.displayWidth*W,V=K.displayHeight*W,Y=(z-N)/2,X=(B-V)/2;c.clearRect(0,0,H.width,H.height),c.drawImage(K,Y,X,N,V)}function M(){if(u){try{u.close()}catch{}u=null}c=null}function E(){const K=a.value;!K||!d.width||(K.width=Math.round(K.clientWidth*f),K.height=Math.round(K.clientHeight*f),c=K.getContext("2d"),c.setTransform(f,0,0,f,0,0))}function A(K){s&&s.readyState===WebSocket.OPEN&&s.send(JSON.stringify(K))}function I(K){const H=a.value;if(!H||!d.width||!d.height)return null;const z=H.getBoundingClientRect(),B=Math.min(z.width/d.width,z.height/d.height),W=d.width*B,N=d.height*B,V=(z.width-W)/2,Y=(z.height-N)/2,X=K.clientX-z.left,ee=K.clientY-z.top;return XV+W||eeY+N?null:{x:Math.round((X-V)/B),y:Math.round((ee-Y)/B)}}function P(K){if(!d.width)return;const H=I(K);H&&(v=H,a.value.setPointerCapture(K.pointerId),A({type:"touch",action:0,x:H.x,y:H.y}))}function R(K){if(!v||!d.width)return;const H=I(K);H&&A({type:"touch",action:2,x:H.x,y:H.y})}function G(){v&&(A({type:"touch",action:1,x:v.x,y:v.y}),v=null)}return mt(()=>{b(),window.addEventListener("resize",E)}),rn(()=>{window.removeEventListener("resize",E),m=!0,s&&s.close(),M()}),(K,H)=>{const z=be("el-icon"),B=be("el-button");return x(),O("div",$Ne,[C("div",RNe,[C("div",NNe,[C("canvas",{ref_key:"canvasEl",ref:a,class:"stream-canvas",onPointerdown:P,onPointermove:R,onPointerup:G,onPointercancel:G,onContextmenu:H[0]||(H[0]=at(()=>{},["prevent"]))},null,544),o.value?te("",!0):(x(),O("div",BNe,[r.value?(x(),ne(z,{key:0,class:"is-loading",size:40},{default:$(()=>[L(p(Mr))]),_:1})):(x(),ne(z,{key:1,size:40},{default:$(()=>[L(p($s))]),_:1})),C("div",VNe,Q(i.value),1),!r.value&&l.value?(x(),ne(B,{key:2,size:"small",type:"primary",onClick:b},{default:$(()=>[...H[1]||(H[1]=[ue("重连",-1)])]),_:1})):te("",!0)]))])])])}}},FNe=fr(zNe,[["__scopeId","data-v-c99e0b77"]]),HNe={class:"personal-devices-page"},WNe={class:"pg-card"},KNe={class:"pg-card-header"},UNe={class:"header-actions"},GNe={key:0,class:"text-muted",style:{"font-size":"12px"}},YNe={key:1,class:"text-muted",style:{"font-size":"12px",color:"var(--el-color-warning)"}},jNe={class:"pg-card-body"},qNe={class:"guide-content"},XNe={class:"pg-card"},ZNe={class:"pg-card-body"},JNe={class:"device-grid"},QNe=["onClick"],eBe={key:0,class:"phone-checkbox"},tBe={class:"phone-frame"},nBe={class:"phone-screen"},aBe=["src"],rBe={key:1,class:"phone-placeholder"},oBe={class:"phone-placeholder-name"},iBe=["onClick"],lBe={class:"phone-info"},sBe={class:"phone-name"},uBe={class:"phone-meta"},cBe={key:6,class:"snapshot-tag"},dBe={key:0,class:"empty-state"},fBe={key:0,class:"batch-actions-bar"},vBe={class:"batch-actions-left"},hBe={class:"batch-actions-right"},pBe={key:0,class:"console-wrap"},mBe={class:"console-header"},gBe={class:"console-device-info"},yBe={class:"device-name"},bBe={class:"font-mono text-muted",style:{"font-size":"11px"}},_Be={class:"font-mono text-muted",style:{"font-size":"11px"}},wBe={style:{"margin-top":"4px"}},SBe={class:"console-header-actions"},CBe={key:0,class:"console-stream"},xBe={key:0,style:{"margin-bottom":"12px"}},kBe={class:"wizard-body"},TBe={key:0},MBe={key:0,style:{"text-align":"center",padding:"16px 0"}},EBe={key:1,style:{"text-align":"center",padding:"32px 0"}},ABe={key:2},IBe={class:"usb-device-list"},LBe=["onClick"],PBe={class:"usb-device-info"},DBe={class:"usb-device-serial font-mono"},OBe={class:"usb-device-model text-muted",style:{"font-size":"12px"}},$Be={class:"usb-device-right"},RBe={key:1},NBe={style:{"text-align":"center",padding:"24px 0"}},BBe={key:0,style:{"margin-top":"16px"}},VBe={key:1,style:{"margin-top":"16px"}},zBe={key:2,style:{"margin-top":"16px",color:"var(--text-muted)","font-size":"13px"}},FBe={key:2},HBe={key:3},WBe={key:0},KBe={style:{"text-align":"center",padding:"24px 0"}},UBe={key:1,style:{"text-align":"center",padding:"16px 0"}},GBe={style:{"margin-top":"20px"}},YBe={__name:"PersonalDevices",props:{refreshKey:{type:Number,default:0}},setup(e){const t=e,n=F([]),a=F(!1),r=F([]),o=F(!1),i=F(null),l=D(()=>{const Ee=i.value;if(!Ee)return[];const de=[];return Ee.adb_host&&de.push(Ee.adb_port?`${Ee.adb_host}:${Ee.adb_port}`:Ee.adb_host),Ee.adb_wifi_host&&Ee.adb_wifi_port&&de.push(`${Ee.adb_wifi_host}:${Ee.adb_wifi_port}`),de}),s=F(!1),u=F(new Set),c=D(()=>n.value.length>0&&u.value.size===n.value.length),d=D(()=>u.value.size),v=D(()=>n.value.filter(Ee=>Ee.status==="online").length);function f(){s.value=!s.value,s.value||u.value.clear()}function h(Ee){u.value.has(Ee)?u.value.delete(Ee):u.value.add(Ee),u.value=new Set(u.value)}function m(){c.value?u.value.clear():u.value=new Set(n.value.map(Ee=>Ee.id)),u.value=new Set(u.value)}function g(){u.value.clear(),u.value=new Set(u.value)}function y(Ee){return{online:"success",offline:"info",frozen:"danger"}[Ee]||"info"}function b(Ee){return{online:"在线",offline:"离线",frozen:"已冻结"}[Ee]||Ee}function _(Ee){return Ee?Ee.includes("wechat")?"微信":Ee.includes("alipay")?"支付宝":Ee:""}function w(Ee){const de=Ee&&Ee.control_state;return de?de==="usb"?{type:"success",label:"控制面:USB"}:de==="relay"?{type:"primary",label:"控制面:反代"}:de==="tunnel_only"?{type:"info",label:"控制面:隧道"}:{type:"info",label:"控制面:"+de}:null}async function S(){a.value=!0;try{const Ee=await yt.personalDevices(),de=new Map(n.value.map(Dt=>[Dt.id,Dt])),it=Ee.devices||[];for(const Dt of it){const sn=de.get(Dt.id);sn&&sn._cachedSnapshot&&(Dt._cachedSnapshot=sn._cachedSnapshot)}n.value=it}catch(Ee){We.error("加载设备失败:"+Ee.message)}finally{a.value=!1}}async function k(){try{const Ee=await yt.accounts({});r.value=(Ee||[]).filter(de=>String(de.channel).startsWith("personal."))}catch{}}async function T(Ee){await er.confirm(`确认冻结设备「${Ee.name||Ee.device_id}」?`,"冻结设备",{type:"warning"});try{await yt.freezePersonalDevice(Ee.id),We.success("已冻结"),S()}catch(de){We.error("冻结失败:"+de.message)}}async function M(Ee){try{await yt.unfreezePersonalDevice(Ee.id),We.success("已解冻"),S()}catch(de){We.error("解冻失败:"+de.message)}}const E=F({});async function A(Ee){await er.confirm(`确认重启设备「${Ee.name||Ee.device_id}」?重启可能需要 30-60 秒。`,"重启设备",{type:"warning"}),E.value={...E.value,[Ee.id]:!0};try{const de=await yt.restartPersonalDevice(Ee.id),Dt=(de&&de.stages||[]).map(gn=>`${gn.stage}:${gn.ok===!0?"成功":"失败"}`).join(" / ");!!de&&de.ok===!0?We.success(`重启完成${Dt?"("+Dt+")":""}`):We.error(`重启失败${Dt?"("+Dt+")":""}`),setTimeout(()=>S(),8e3)}catch(de){We.error("重启失败:"+de.message)}finally{E.value={...E.value,[Ee.id]:!1}}}const I=F({});async function P(Ee){await er.confirm(`确认预热设备「${Ee.name||Ee.device_id}」?将执行冷启动导航到支付宝账本页(约 18 秒)。`,"手动预热",{type:"info"}),I.value={...I.value,[Ee.id]:!0};try{const de=await yt.personalDeviceAction(Ee.id,{action:"warmup"});de&&de.data&&de.data.ok?We.success("预热完成:账本页已就绪,任务卡片已锁定"):We.error("预热失败")}catch(de){We.error("预热失败:"+de.message)}finally{I.value={...I.value,[Ee.id]:!1}}}async function R(Ee){await er.confirm(`确认删除设备「${Ee.name||Ee.device_id}」?`,"删除设备",{type:"error"});try{await yt.deletePersonalDevice(Ee.id),We.success("已删除"),S()}catch(de){We.error("删除失败:"+de.message)}}function G(Ee){i.value=Ee,o.value=!0,N(Ee.id)}function K(){i.value=null,H.value=[]}const H=F([]),z=F(!1),B=F(!1),W=F({account_id:null});async function N(Ee){try{const de=await yt.personalDeviceAccounts(Ee);H.value=de&&de.accounts||[];const it=i.value;it&&de&&(it._accountCount=(de.accounts||[]).length)}catch{}}const V=D(()=>{const Ee=new Set(H.value.map(de=>de.account_id));return r.value.filter(de=>Ee.has(de.id)?!1:String(de.channel).startsWith("personal."))});function Y(){W.value={account_id:null},z.value=!0,k()}async function X(){const Ee=i.value;if(!(!Ee||!W.value.account_id)){B.value=!0;try{await yt.personalAddDeviceAccount(Ee.id,{account_id:W.value.account_id}),We.success("账户已绑定(下次心跳后生效)"),z.value=!1,await N(Ee.id),S()}catch(de){We.error("绑定失败:"+de.message)}finally{B.value=!1}}}async function ee(Ee){const de=i.value;if(de){await er.confirm(`确认解绑账户「${Ee.name}」?该账户将不再由本设备服务。`,"解绑账户",{type:"warning"});try{await yt.personalRemoveDeviceAccount(de.id,Ee.account_id),We.success("已解绑"),await N(de.id),S()}catch(it){We.error("解绑失败:"+it.message)}}}const re=F(!1),ie=F(!1),le=F({platform:"alipay",user:0});function ve(){i.value&&(le.value={platform:"alipay",user:0},re.value=!0)}async function he(){const Ee=i.value;if(!(!Ee||!Ee.device_id)){ie.value=!0;try{le.value.platform==="wechat"?await yt.personalCaptureWechatQrcode(Ee.device_id,le.value.user):await yt.personalCaptureAlipayQrcode(Ee.device_id,le.value.user),We.success("收款码捕获成功"),re.value=!1,O3(),S()}catch(de){We.error("捕获失败:"+de.message)}finally{ie.value=!1}}}const J=F(!1),Z=F(!1),j=F({adb_host:"",adb_port:null,adb_wifi_host:"",adb_wifi_port:null});function oe(){const Ee=i.value;Ee&&(j.value={adb_host:Ee.adb_host||"",adb_port:Ee.adb_port||null,adb_wifi_host:Ee.adb_wifi_host||"",adb_wifi_port:Ee.adb_wifi_port||null},J.value=!0)}async function se(){const Ee=i.value;if(Ee){Z.value=!0;try{await yt.personalSetAdb(Ee.id,j.value),We.success("ADB 通道已更新,下次操作生效"),Object.assign(Ee,j.value),J.value=!1,S()}catch(de){We.error("保存失败:"+de.message)}finally{Z.value=!1}}}const fe=F(!1),ge=F(0),Se=F(!1),De=F([]),Te=F(""),Re=F(null),Ce=F(null),Le=F({name:"",account_id:null}),me=F(!1),Be=F(""),ze=F(""),xe=F(!1),rt=F(""),nt=F(!1),Pt=F(""),Wt=F(""),Jt=F("");let Rt=null;function Qt(Ee){const de=Ee.setup_progress;return!de||!de.length?!1:de.includes("usb_connected")}function _e(Ee){const de=Ee.setup_progress;return de?[...new Set(de)].length:0}function Ne(){fe.value=!0,ge.value=0,De.value=[],Te.value="",Re.value=null,Ce.value=null,Le.value={name:"",account_id:null},me.value=!1,Be.value="",ze.value="",xe.value=!1,rt.value="",nt.value=!1,Pt.value="",Wt.value="",Jt.value="",k()}function tt(){Ue(),Ce.value=null}function Me(){fe.value=!1}function Ue(){Rt!==null&&(clearInterval(Rt),Rt=null)}async function Gt(){if(fe.value){Se.value=!0;try{const Ee=await yt.personalUsbDetect(),de=Ee&&Ee.devices||[];if(De.value=de.filter(it=>!it.bound),Te.value){const it=De.value.find(Dt=>Dt.serial===Te.value)||de.find(Dt=>Dt.serial===Te.value);it&&(Re.value=it)}}catch{}finally{Se.value=!1}}}function je(Ee){Te.value=Ee.serial,Re.value=Ee}function ft(){Te.value&&(ge.value=1)}function ht(){ge.value=2}function pt(){Ue(),ge.value-=1,ge.value<=1&&(Gt(),Rt===null&&(Rt=setInterval(Gt,2500)))}function It(){if(!Le.value.name.trim())return We.warning("请填写设备名称");if(!Le.value.account_id)return We.warning("请选择收款账户");me.value=!0;const Ee={serial:Te.value,name:Le.value.name,account_id:Le.value.account_id};yt.personalWizardStart(Ee).then(de=>{Ce.value=de.id||de.device_id,Ue(),Ct(),ge.value=3}).catch(de=>{We.error("绑定设备失败:"+de.message)}).finally(()=>{me.value=!1})}function Ct(){const Ee=Ce.value;Ee&&(Be.value="running",ze.value="",yt.personalWizardWifi(Ee).then(de=>{de&&de.ok?(Be.value="done",ze.value=`${de.wifi_host}:${de.wifi_port}`):(Be.value="skipped",ze.value=de&&de.warning||"配置失败(USB 直连不受影响)")}).catch(()=>{Be.value="skipped",ze.value="配置失败(USB 直连不受影响)"}))}function $t(){Ce.value&&(xe.value=!0,rt.value="",yt.personalWizardVerify(Ce.value).then(Ee=>{Pt.value=Ee.hint||"",Wt.value=Ee.warning||"",Jt.value=Ee.keepalive_hint||"",nt.value=!0}).catch(Ee=>{rt.value="下发配置失败:"+Ee.message}).finally(()=>{xe.value=!1}))}function Sn(){fe.value=!1,S()}const Fe=F(null);function qe(){const Ee=Array.from(u.value);return Ee.length===n.value.length?{all:!0}:{ids:Ee}}function ut(Ee,de){const it=Ee.results||[],Dt=Ee.total||it.length,sn=Ee.missing_ids||[],gn=it.filter(Ia=>Ia.ok).length,Aa=it.filter(Ia=>!Ia.ok).length,jr=it.filter(Ia=>Ia.throttled);let vr=`${de}完成:共 ${Dt} 台 +成功 ${gn} 台`;if(Aa>0){vr+=` +失败 ${Aa} 台:`;const Ia=it.filter(Oa=>!Oa.ok).slice(0,5);vr+=Ia.map(Oa=>`${Oa.device_id}(${Oa.error||"未知错误"})`).join(";"),it.filter(Oa=>!Oa.ok).length>5&&(vr+="...")}return jr.length>0&&(vr+=` +距上次重启不足 5 分钟已跳过 ${jr.length} 台`),sn.length>0&&(vr+=` +不存在或其他原因跳过 ${sn.length} 台(id: ${sn.join(",")})`),vr}async function Ie(){const Ee=d.value,de=c.value;await er.confirm(de?`确认重启全部 ${n.value.length} 台设备?`:`确认重启选中的 ${Ee} 台设备?`,"批量重启",{type:"warning"}),Fe.value="restart";try{const it=qe(),Dt=await yt.personalBatchRestart(it),sn=ut(Dt,"重启");(Dt.results||[]).some(Aa=>!Aa.ok)||(Dt.missing_ids||[]).length>0?We.warning(sn):We.success(sn),S(),g(),s.value=!1}catch(it){We.error("批量重启失败:"+it.message)}finally{Fe.value=null}}async function Ke(){const Ee=d.value,de=c.value;await er.confirm(de?`确认冻结全部 ${n.value.length} 台设备?`:`确认冻结选中的 ${Ee} 台设备?`,"批量冻结",{type:"warning"}),Fe.value="freeze";try{const it=qe(),Dt=await yt.personalBatchStatus({...it,status:"frozen"}),sn=ut(Dt,"冻结");(Dt.results||[]).some(Aa=>!Aa.ok)||(Dt.missing_ids||[]).length>0?We.warning(sn):We.success(sn),S(),g(),s.value=!1}catch(it){We.error("批量冻结失败:"+it.message)}finally{Fe.value=null}}async function Ge(){const Ee=d.value,de=c.value;await er.confirm(de?`确认解冻全部 ${n.value.length} 台设备?`:`确认解冻选中的 ${Ee} 台设备?`,"批量解冻",{type:"warning"}),Fe.value="unfreeze";try{const it=qe(),Dt=await yt.personalBatchStatus({...it,status:"offline"}),sn=ut(Dt,"解冻");(Dt.results||[]).some(Aa=>!Aa.ok)||(Dt.missing_ids||[]).length>0?We.warning(sn):We.success(sn),S(),g(),s.value=!1}catch(it){We.error("批量解冻失败:"+it.message)}finally{Fe.value=null}}return mt(S),ye(()=>t.refreshKey,S),ye(()=>fe.value,Ee=>{Ee?(Gt(),Rt=setInterval(Gt,2500)):Ue()}),Us(Ue),(Ee,de)=>{const it=be("el-button"),Dt=be("el-icon"),sn=be("el-alert"),gn=be("el-tag"),Aa=be("el-tab-pane"),jr=be("el-tabs"),vr=be("el-collapse-item"),Ia=be("el-collapse"),Oa=be("el-checkbox"),Oe=be("el-dialog"),lt=be("el-input"),Bt=be("el-form-item"),_n=be("el-form"),Yn=be("el-option"),Zn=be("el-select"),ja=be("el-radio"),Gl=be("el-radio-group"),co=be("el-step"),Yl=be("el-steps"),Xs=Gr("loading");return x(),O("div",HNe,[C("div",WNe,[C("div",KNe,[de[22]||(de[22]=C("span",{class:"section-label"},"监听端设备",-1)),C("div",UNe,[s.value?(x(),O("span",YNe,"已选 "+Q(d.value)+" 台",1)):(x(),O("span",GNe,"共 "+Q(n.value.length)+" 台 · 在线 "+Q(v.value)+" 台",1)),s.value?(x(),ne(it,{key:3,size:"small",onClick:f},{default:$(()=>[...de[20]||(de[20]=[ue("退出批量",-1)])]),_:1})):(x(),ne(it,{key:2,type:"warning",size:"small",onClick:f},{default:$(()=>[...de[19]||(de[19]=[ue(" 批量操作 ",-1)])]),_:1})),L(it,{type:"success",size:"small",onClick:Ne},{default:$(()=>[L(Dt,null,{default:$(()=>[L(p(hf))]),_:1}),de[21]||(de[21]=ue(" USB 引导接入 ",-1))]),_:1})])]),C("div",jNe,[L(sn,{type:"info",closable:!1,class:"mb16",title:"ADB 真机绑定个人收款账户:设备上线由 adb 连通性判定(USB 直连 / WiFi 备用)。"}),L(Ia,{class:"integration-guide"},{default:$(()=>[L(vr,{name:"guide"},{title:$(()=>[de[24]||(de[24]=C("span",{class:"guide-title"},"集成流程(USB 真机)",-1)),L(gn,{type:"info",size:"small",effect:"plain",round:"",class:"guide-tag"},{default:$(()=>[...de[23]||(de[23]=[ue("点击展开",-1)])]),_:1})]),default:$(()=>[C("div",qNe,[L(jr,null,{default:$(()=>[L(Aa,{label:"真机 USB 直连"},{default:$(()=>[de[25]||(de[25]=C("ol",{class:"step-list"},[C("li",null,[C("b",null,"接入引导"),ue(":点击右上「USB 引导接入」,向导自动检测已连接的 USB 真机(手机插 USB + 开启开发者选项 + USB 调试)。")]),C("li",null,[C("b",null,"授权调试"),ue(":手机会弹出「允许 USB 调试」,点击允许,向导自动进入下一步。")]),C("li",null,[C("b",null,"绑定设备"),ue(":填设备名称、选择收款账户,向导自动创建设备记录。")]),C("li",null,[C("b",null,"ADB 连通即上线"),ue(":USB 直连后设备自动上线,WiFi 备用通道自动配置(断线自动切换保活)。")])],-1)),L(sn,{type:"info",closable:!1,class:"mt8",title:"历史变更(2026-08-17):redroid 容器载体方案已废弃;个人免签监听端统一走 USB 引导接入自动检测,手填「新增设备」表单已移除。"})]),_:1})]),_:1})])]),_:1})]),_:1})])]),C("div",XNe,[st((x(),O("div",ZNe,[C("div",JNe,[(x(!0),O(Ve,null,dt(n.value,pe=>(x(),O("div",{key:pe.id,class:U(["phone-card",{"phone-card-selected":u.value.has(pe.id)}]),onClick:ct=>s.value?h(pe.id):G(pe)},[s.value?(x(),O("div",eBe,[L(Oa,{"model-value":u.value.has(pe.id),onClick:de[0]||(de[0]=at(()=>{},["stop"])),onChange:ct=>h(pe.id)},null,8,["model-value","onChange"])])):te("",!0),C("div",tBe,[de[27]||(de[27]=C("div",{class:"phone-notch"},null,-1)),C("div",nBe,[pe._cachedSnapshot?(x(),O("img",{key:0,src:pe._cachedSnapshot,class:"phone-screenshot"},null,8,aBe)):(x(),O("div",rBe,[de[26]||(de[26]=C("div",{class:"phone-placeholder-icon"},"📱",-1)),C("div",oBe,Q(pe.name||"未命名"),1)]))]),C("div",{class:"phone-home",onClick:at(ct=>G(pe),["stop"])},null,8,iBe)]),C("div",lBe,[C("div",sBe,Q(pe.name||"未命名"),1),C("div",uBe,[L(gn,{type:y(pe.status),size:"small",effect:"light",round:""},{default:$(()=>[ue(Q(b(pe.status)),1)]),_:2},1032,["type"]),L(gn,{type:pe.provider==="alipay"?"primary":"success",size:"small",effect:"plain",round:""},{default:$(()=>[ue(Q(pe.provider==="alipay"?"支付宝":"微信"),1)]),_:2},1032,["type"]),pe._accountCount>1?(x(),ne(gn,{key:0,type:"primary",size:"small",effect:"plain",round:""},{default:$(()=>[ue("+"+Q(pe._accountCount-1)+" 账户",1)]),_:2},1024)):te("",!0),pe.adb_host?(x(),ne(gn,{key:1,type:"info",size:"small",effect:"plain",round:""},{default:$(()=>[...de[28]||(de[28]=[ue("USB",-1)])]),_:1})):pe.adb_wifi_host?(x(),ne(gn,{key:2,type:"info",size:"small",effect:"plain",round:""},{default:$(()=>[...de[29]||(de[29]=[ue("WiFi",-1)])]),_:1})):te("",!0),pe.adb_host&&pe.adb_wifi_host?(x(),ne(gn,{key:3,type:"warning",size:"small",effect:"plain",round:""},{default:$(()=>[...de[30]||(de[30]=[ue("双通道",-1)])]),_:1})):te("",!0),w(pe)?(x(),ne(gn,{key:4,type:w(pe).type,size:"small",effect:"plain",round:""},{default:$(()=>[ue(Q(w(pe).label),1)]),_:2},1032,["type"])):te("",!0),pe.ts_ip?(x(),ne(gn,{key:5,type:"info",size:"small",effect:"plain",round:"",class:"font-mono"},{default:$(()=>[ue(Q(pe.ts_ip),1)]),_:2},1024)):te("",!0),pe._cachedSnapshot?(x(),O("span",cBe,"快照")):te("",!0),Qt(pe)?(x(),ne(gn,{key:7,type:"warning",size:"small",effect:"plain",round:""},{default:$(()=>[ue("配置中 "+Q(_e(pe))+"/5",1)]),_:2},1024)):te("",!0)])]),C("div",{class:"phone-actions",onClick:de[1]||(de[1]=at(()=>{},["stop"]))},[L(it,{size:"small",type:"primary",text:"",onClick:ct=>G(pe)},{default:$(()=>[...de[31]||(de[31]=[ue("控制台",-1)])]),_:1},8,["onClick"]),pe.status!=="frozen"?(x(),ne(it,{key:0,size:"small",type:"danger",text:"",onClick:ct=>T(pe)},{default:$(()=>[...de[32]||(de[32]=[ue("冻结",-1)])]),_:1},8,["onClick"])):(x(),ne(it,{key:1,size:"small",type:"success",text:"",onClick:ct=>M(pe)},{default:$(()=>[...de[33]||(de[33]=[ue("解冻",-1)])]),_:1},8,["onClick"])),L(it,{size:"small",type:"danger",text:"",onClick:ct=>R(pe)},{default:$(()=>[...de[34]||(de[34]=[ue("删除",-1)])]),_:1},8,["onClick"]),L(it,{size:"small",type:"warning",text:"",loading:E.value[pe.id],onClick:ct=>A(pe)},{default:$(()=>[...de[35]||(de[35]=[ue("重启",-1)])]),_:1},8,["loading","onClick"]),L(it,{size:"small",type:"success",text:"",loading:I.value[pe.id],onClick:ct=>P(pe)},{default:$(()=>[...de[36]||(de[36]=[ue("预热",-1)])]),_:1},8,["loading","onClick"])])],10,QNe))),128)),!a.value&&n.value.length===0?(x(),O("div",dBe,[de[38]||(de[38]=C("div",{style:{"font-size":"40px","margin-bottom":"12px"}},"📱",-1)),de[39]||(de[39]=C("div",{class:"text-muted"},"暂无监听端设备",-1)),L(it,{type:"success",plain:"",style:{"margin-top":"16px"},onClick:Ne},{default:$(()=>[...de[37]||(de[37]=[ue("USB 引导接入",-1)])]),_:1})])):te("",!0)]),s.value?(x(),O("div",fBe,[C("div",vBe,[L(Oa,{"model-value":c.value,indeterminate:d.value>0&&!c.value,onChange:m},{default:$(()=>[ue(Q(c.value?"取消全选":"全选"),1)]),_:1},8,["model-value","indeterminate"]),L(it,{size:"small",text:"",onClick:g,disabled:d.value===0},{default:$(()=>[...de[40]||(de[40]=[ue("清空",-1)])]),_:1},8,["disabled"])]),C("div",hBe,[L(it,{type:"warning",size:"small",disabled:d.value===0,loading:Fe.value==="restart",onClick:Ie},{default:$(()=>[...de[41]||(de[41]=[ue(" 批量重启 ",-1)])]),_:1},8,["disabled","loading"]),L(it,{type:"danger",size:"small",disabled:d.value===0,loading:Fe.value==="freeze",onClick:Ke},{default:$(()=>[...de[42]||(de[42]=[ue(" 批量冻结 ",-1)])]),_:1},8,["disabled","loading"]),L(it,{type:"success",size:"small",disabled:d.value===0,loading:Fe.value==="unfreeze",onClick:Ge},{default:$(()=>[...de[43]||(de[43]=[ue(" 批量解冻 ",-1)])]),_:1},8,["disabled","loading"])])])):te("",!0)])),[[Xs,a.value]])]),L(Oe,{modelValue:o.value,"onUpdate:modelValue":de[2]||(de[2]=pe=>o.value=pe),title:"设备控制台",width:"1100px",top:"4vh","destroy-on-close":"",onClosed:K},{default:$(()=>[i.value?(x(),O("div",pBe,[C("div",mBe,[C("div",gBe,[C("div",yBe,Q(i.value.name||"未命名"),1),C("div",bBe,Q(i.value.device_id),1),C("div",_Be,"USB: "+Q(i.value.adb_host||"—")+Q(i.value.adb_port?":"+i.value.adb_port:"")+" · WiFi: "+Q(i.value.adb_wifi_host?i.value.adb_wifi_host+":"+(i.value.adb_wifi_port||"—"):"未配置"),1),C("div",wBe,[L(gn,{type:y(i.value.status),size:"small",effect:"light",round:""},{default:$(()=>[ue(Q(b(i.value.status)),1)]),_:1},8,["type"]),w(i.value)?(x(),ne(gn,{key:0,type:w(i.value).type,size:"small",effect:"plain",round:"",style:{"margin-left":"6px"}},{default:$(()=>[ue(Q(w(i.value).label),1)]),_:1},8,["type"])):te("",!0),i.value.ts_ip?(x(),ne(gn,{key:1,type:"info",size:"small",effect:"plain",round:"",class:"font-mono",style:{"margin-left":"6px"}},{default:$(()=>[ue(Q(i.value.ts_ip),1)]),_:1})):te("",!0),(x(!0),O(Ve,null,dt(H.value,pe=>(x(),ne(gn,{key:pe.account_id,type:pe.is_primary?"primary":"info",size:"small",effect:"plain",round:"",style:{"margin-left":"6px"}},{default:$(()=>[ue(Q(pe.name),1),pe.is_primary?(x(),O(Ve,{key:0},[ue("(主)")],64)):te("",!0)]),_:2},1032,["type"]))),128))])]),C("div",SBe,[L(it,{size:"small",plain:"",onClick:oe},{default:$(()=>[...de[44]||(de[44]=[ue("编辑 ADB",-1)])]),_:1}),L(it,{size:"small",type:"success",plain:"",onClick:Y},{default:$(()=>[...de[45]||(de[45]=[ue("绑定账户",-1)])]),_:1}),L(it,{size:"small",type:"warning",plain:"",onClick:ve},{default:$(()=>[...de[46]||(de[46]=[ue("捕获收款码",-1)])]),_:1})])]),l.value.length?(x(),O("div",CBe,[L(FNe,{serials:l.value,"device-id":i.value.id},null,8,["serials","device-id"])])):(x(),ne(sn,{key:1,type:"warning",closable:!1,title:"该设备未配置 ADB 地址,无法实时操作。请编辑设备填写 adb_host/adb_port。"}))])):te("",!0)]),_:1},8,["modelValue"]),L(Oe,{modelValue:J.value,"onUpdate:modelValue":de[8]||(de[8]=pe=>J.value=pe),title:"编辑 ADB 通道",width:"480px"},{footer:$(()=>[L(it,{onClick:de[7]||(de[7]=pe=>J.value=!1)},{default:$(()=>[...de[50]||(de[50]=[ue("取消",-1)])]),_:1}),L(it,{type:"primary",loading:Z.value,onClick:se},{default:$(()=>[...de[51]||(de[51]=[ue("保存",-1)])]),_:1},8,["loading"])]),default:$(()=>[L(_n,{"label-width":"90px"},{default:$(()=>[L(Bt,{label:"USB 直连"},{default:$(()=>[L(lt,{modelValue:j.value.adb_host,"onUpdate:modelValue":de[3]||(de[3]=pe=>j.value.adb_host=pe),placeholder:"真机 USB serial,如 4f2bdc30"},null,8,["modelValue"]),de[47]||(de[47]=C("div",{class:"text-muted",style:{"font-size":"11px",width:"100%"}},"真机 USB 直连填 adb serial;adb over WiFi(可选)填 host:port",-1))]),_:1}),L(Bt,{label:"TCP 端口"},{default:$(()=>[L(lt,{modelValue:j.value.adb_port,"onUpdate:modelValue":de[4]||(de[4]=pe=>j.value.adb_port=pe),modelModifiers:{number:!0},placeholder:"如 5555(USB 直连留空)"},null,8,["modelValue"])]),_:1}),L(Bt,{label:"WiFi 备用"},{default:$(()=>[L(lt,{modelValue:j.value.adb_wifi_host,"onUpdate:modelValue":de[5]||(de[5]=pe=>j.value.adb_wifi_host=pe),placeholder:"同局域网无线调试 IP(可选)",style:{width:"45%"}},null,8,["modelValue"]),de[48]||(de[48]=C("span",{style:{margin:"0 6px",color:"var(--text-muted)"}},":",-1)),L(lt,{modelValue:j.value.adb_wifi_port,"onUpdate:modelValue":de[6]||(de[6]=pe=>j.value.adb_wifi_port=pe),modelModifiers:{number:!0},placeholder:"5555",style:{width:"45%"}},null,8,["modelValue"]),de[49]||(de[49]=C("div",{class:"text-muted",style:{"font-size":"11px",width:"100%"}},"可选:USB 断线后自动切到 WiFi 通道保活",-1))]),_:1})]),_:1})]),_:1},8,["modelValue"]),L(Oe,{modelValue:z.value,"onUpdate:modelValue":de[11]||(de[11]=pe=>z.value=pe),title:"绑定更多账户(应用双开)",width:"520px"},{footer:$(()=>[L(it,{onClick:de[10]||(de[10]=pe=>z.value=!1)},{default:$(()=>[...de[53]||(de[53]=[ue("取消",-1)])]),_:1}),L(it,{type:"primary",loading:B.value,disabled:!W.value.account_id,onClick:X},{default:$(()=>[...de[54]||(de[54]=[ue(" 绑定 ",-1)])]),_:1},8,["loading","disabled"])]),default:$(()=>[L(sn,{type:"info",closable:!1,style:{"margin-bottom":"12px"},title:"一台设备(双开微信/支付宝)可服务多个收款账户。已绑定账户在设备恢复心跳/接单时会同时生效。"}),H.value.length?(x(),O("div",xBe,[de[52]||(de[52]=C("div",{style:{"font-size":"12px",color:"var(--text-muted)","margin-bottom":"6px"}},"已绑定账户:",-1)),(x(!0),O(Ve,null,dt(H.value,pe=>(x(),ne(gn,{key:pe.account_id,type:pe.is_primary?"primary":"info",size:"small",effect:"plain",round:"",style:{margin:"0 6px 6px 0"},closable:"",onClose:ct=>ee(pe)},{default:$(()=>[ue(Q(pe.name)+Q(pe.is_primary?"(主)":""),1)]),_:2},1032,["type","closable","onClose"]))),128))])):te("",!0),L(_n,{"label-width":"90px"},{default:$(()=>[L(Bt,{label:"添加账户",required:""},{default:$(()=>[L(Zn,{modelValue:W.value.account_id,"onUpdate:modelValue":de[9]||(de[9]=pe=>W.value.account_id=pe),placeholder:"选择同通道的个人收款账户",style:{width:"100%"}},{default:$(()=>[(x(!0),O(Ve,null,dt(V.value,pe=>(x(),ne(Yn,{key:pe.id,label:`#${pe.id} ${pe.name}(${_(pe.channel)})`,value:pe.id},null,8,["label","value"]))),128))]),_:1},8,["modelValue"])]),_:1})]),_:1})]),_:1},8,["modelValue"]),L(Oe,{modelValue:re.value,"onUpdate:modelValue":de[15]||(de[15]=pe=>re.value=pe),title:"捕获收款码",width:"420px"},{footer:$(()=>[L(it,{onClick:de[14]||(de[14]=pe=>re.value=!1)},{default:$(()=>[...de[59]||(de[59]=[ue("取消",-1)])]),_:1}),L(it,{type:"warning",loading:ie.value,onClick:he},{default:$(()=>[...de[60]||(de[60]=[ue(" 开始捕获(约 30~90 秒) ",-1)])]),_:1},8,["loading"])]),default:$(()=>[L(sn,{type:"info",closable:!1,class:"mb12",title:"即使设备尚未捕获过收款码也可直接捕获。服务端 adb 会自动打开微信/支付宝并导航到收款码页,请确保设备 adb 在线。"}),L(_n,{"label-width":"90px"},{default:$(()=>[L(Bt,{label:"平台",required:""},{default:$(()=>[L(Gl,{modelValue:le.value.platform,"onUpdate:modelValue":de[12]||(de[12]=pe=>le.value.platform=pe)},{default:$(()=>[L(ja,{value:"alipay"},{default:$(()=>[...de[55]||(de[55]=[ue("支付宝",-1)])]),_:1}),L(ja,{value:"wechat"},{default:$(()=>[...de[56]||(de[56]=[ue("微信",-1)])]),_:1})]),_:1},8,["modelValue"])]),_:1}),L(Bt,{label:"应用空间",required:""},{default:$(()=>[L(Gl,{modelValue:le.value.user,"onUpdate:modelValue":de[13]||(de[13]=pe=>le.value.user=pe)},{default:$(()=>[L(ja,{value:0},{default:$(()=>[...de[57]||(de[57]=[ue("主空间",-1)])]),_:1}),L(ja,{value:999},{default:$(()=>[...de[58]||(de[58]=[ue("双开",-1)])]),_:1})]),_:1},8,["modelValue"])]),_:1})]),_:1})]),_:1},8,["modelValue"]),L(Oe,{modelValue:fe.value,"onUpdate:modelValue":de[18]||(de[18]=pe=>fe.value=pe),title:"USB 真机引导接入",width:"720px","destroy-on-close":"",onClosed:tt},{footer:$(()=>[L(it,{onClick:Me},{default:$(()=>[...de[75]||(de[75]=[ue("关闭",-1)])]),_:1}),ge.value>0&&ge.value<3?(x(),ne(it,{key:0,onClick:pt},{default:$(()=>[...de[76]||(de[76]=[ue("上一步",-1)])]),_:1})):te("",!0),ge.value===0?(x(),ne(it,{key:1,type:"primary",disabled:!Te.value,onClick:ft},{default:$(()=>[...de[77]||(de[77]=[ue(" 下一步 ",-1)])]),_:1},8,["disabled"])):te("",!0),ge.value===1?(x(),ne(it,{key:2,type:"primary",disabled:!(Re.value&&Re.value.state==="device"),onClick:ht},{default:$(()=>[...de[78]||(de[78]=[ue(" 下一步 ",-1)])]),_:1},8,["disabled"])):te("",!0),ge.value===2?(x(),ne(it,{key:3,type:"primary",loading:me.value,onClick:It},{default:$(()=>[...de[79]||(de[79]=[ue(" 创建并继续 ",-1)])]),_:1},8,["loading"])):te("",!0)]),default:$(()=>[L(Yl,{active:ge.value,simple:"",style:{"margin-bottom":"20px"}},{default:$(()=>[L(co,{title:"连接 USB"}),L(co,{title:"授权调试"}),L(co,{title:"绑定设备"}),L(co,{title:"下发配置"})]),_:1},8,["active"]),C("div",kBe,[ge.value===0?(x(),O("div",TBe,[Se.value?(x(),O("div",MBe,[L(Dt,{class:"is-loading",style:{"font-size":"24px"}},{default:$(()=>[L(p(Mr))]),_:1}),de[61]||(de[61]=C("div",{style:{"margin-top":"8px",color:"var(--text-muted)","font-size":"13px"}},"正在检测 USB 设备...",-1))])):De.value.length===0?(x(),O("div",EBe,[...de[62]||(de[62]=[C("div",{style:{"font-size":"48px","margin-bottom":"12px"}},"🔌",-1),C("div",{style:{color:"var(--text-muted)"}},"未检测到 USB 真机,请用数据线连接手机并开启开发者选项中的 USB 调试",-1)])])):(x(),O("div",ABe,[de[68]||(de[68]=C("div",{style:{"margin-bottom":"12px",color:"var(--text-muted)","font-size":"13px"}},"请选择要接入的设备:",-1)),C("div",IBe,[(x(!0),O(Ve,null,dt(De.value,pe=>(x(),O("div",{key:pe.serial,class:U(["usb-device-item",{"usb-device-item-selected":Te.value===pe.serial}]),onClick:ct=>je(pe)},[C("div",PBe,[C("div",DBe,Q(pe.serial),1),C("div",OBe,[pe.kind==="wifi"?(x(),ne(gn,{key:0,type:"info",size:"small",effect:"plain",style:{"margin-right":"4px"}},{default:$(()=>[...de[63]||(de[63]=[ue("WiFi",-1)])]),_:1})):(x(),ne(gn,{key:1,type:"success",size:"small",effect:"plain",style:{"margin-right":"4px"}},{default:$(()=>[...de[64]||(de[64]=[ue("USB",-1)])]),_:1})),ue(" "+Q(pe.model||"未知型号"),1)])]),C("div",$Be,[pe.state==="device"?(x(),ne(gn,{key:0,type:"success",size:"small",effect:"plain"},{default:$(()=>[...de[65]||(de[65]=[ue("可下一步",-1)])]),_:1})):pe.state==="unauthorized"?(x(),ne(gn,{key:1,type:"warning",size:"small",effect:"plain"},{default:$(()=>[...de[66]||(de[66]=[ue("待授权",-1)])]),_:1})):(x(),ne(gn,{key:2,type:"info",size:"small",effect:"plain"},{default:$(()=>[...de[67]||(de[67]=[ue("连接异常",-1)])]),_:1})),Te.value===pe.serial?(x(),ne(Dt,{key:3,style:{"margin-left":"6px",color:"var(--el-color-success)"}},{default:$(()=>[L(p(Os))]),_:1})):te("",!0)])],10,LBe))),128))]),Te.value&&Re.value&&Re.value.state==="unauthorized"?(x(),ne(sn,{key:0,type:"warning",closable:!1,style:{"margin-top":"12px"},title:"请在手机弹出的对话框中点击「允许 USB 调试」"})):te("",!0)]))])):te("",!0),ge.value===1?(x(),O("div",RBe,[C("div",NBe,[L(Dt,{class:"is-loading",style:{"font-size":"32px"}},{default:$(()=>[L(p(Mr))]),_:1}),de[70]||(de[70]=C("div",{style:{"margin-top":"12px","font-size":"15px","font-weight":"500"}},"等待手机端授权...",-1)),de[71]||(de[71]=C("div",{style:{"margin-top":"8px",color:"var(--text-muted)","font-size":"13px"}}," 请在手机屏幕上点击「允许 USB 调试」 ",-1)),Re.value?(x(),O("div",BBe,[L(gn,{type:"info",size:"small",effect:"plain",class:"font-mono"},{default:$(()=>[ue(Q(Te.value),1)]),_:1}),L(gn,{type:"info",size:"small",effect:"plain",style:{"margin-left":"6px"}},{default:$(()=>[ue(Q(Re.value.model||"未知型号"),1)]),_:1})])):te("",!0),Re.value&&Re.value.state==="device"?(x(),O("div",VBe,[L(Dt,{style:{color:"var(--el-color-success)","font-size":"28px"}},{default:$(()=>[L(p(Ao))]),_:1}),de[69]||(de[69]=C("div",{style:{color:"var(--el-color-success)","margin-top":"6px","font-weight":"500"}},"授权成功,点击下方「下一步」继续",-1))])):(x(),O("div",zBe," 若手机未弹出授权框,请断开 USB 重插,或检查「开发者选项 → USB 调试」是否开启 "))])])):te("",!0),ge.value===2?(x(),O("div",FBe,[L(_n,{"label-width":"90px"},{default:$(()=>[L(Bt,{label:"设备名称",required:""},{default:$(()=>[L(lt,{modelValue:Le.value.name,"onUpdate:modelValue":de[16]||(de[16]=pe=>Le.value.name=pe),placeholder:"如:卧室安卓机-1",maxlength:"30"},null,8,["modelValue"])]),_:1}),L(Bt,{label:"收款账户",required:""},{default:$(()=>[L(Zn,{modelValue:Le.value.account_id,"onUpdate:modelValue":de[17]||(de[17]=pe=>Le.value.account_id=pe),placeholder:"选择个人通道收款账户",style:{width:"100%"}},{default:$(()=>[(x(!0),O(Ve,null,dt(r.value,pe=>(x(),ne(Yn,{key:pe.id,label:`#${pe.id} ${pe.name}`,value:pe.id},null,8,["label","value"]))),128))]),_:1},8,["modelValue"])]),_:1})]),_:1}),Ce.value?(x(),ne(sn,{key:0,type:"info",closable:!1,style:{"margin-top":"8px"},title:"该设备已绑定,将继续未完成的配置流程"})):te("",!0)])):te("",!0),ge.value===3?(x(),O("div",HBe,[nt.value?(x(),O("div",UBe,[L(Dt,{style:{color:"var(--el-color-success)","font-size":"40px"}},{default:$(()=>[L(p(Ao))]),_:1}),de[74]||(de[74]=C("div",{style:{"margin-top":"10px","font-size":"15px","font-weight":"500"}},"配置下发成功!",-1)),Pt.value?(x(),ne(sn,{key:0,type:"info",closable:!1,style:{"margin-top":"12px","text-align":"left"},title:Pt.value},null,8,["title"])):te("",!0),Wt.value?(x(),ne(sn,{key:1,type:"warning",closable:!1,style:{"margin-top":"8px","text-align":"left"},title:Wt.value},null,8,["title"])):te("",!0),Jt.value?(x(),ne(sn,{key:2,type:"warning",closable:!1,"show-icon":"",style:{"margin-top":"8px","text-align":"left"},title:Jt.value},null,8,["title"])):te("",!0),C("div",GBe,[L(it,{type:"primary",onClick:Sn},{default:$(()=>[...de[73]||(de[73]=[ue("完成",-1)])]),_:1})])])):(x(),O("div",WBe,[C("div",KBe,[L(it,{type:"primary",size:"large",loading:xe.value,onClick:$t},{default:$(()=>[...de[72]||(de[72]=[ue(" 下发并启动 ",-1)])]),_:1},8,["loading"]),rt.value?(x(),ne(sn,{key:0,type:"danger",closable:!1,style:{"margin-top":"16px"},title:rt.value},null,8,["title"])):te("",!0)])]))])):te("",!0)])]),_:1},8,["modelValue"])])}}},jBe=fr(YBe,[["__scopeId","data-v-3b83dd8d"]]),qBe={class:"unmatched-page"},XBe={class:"pg-card"},ZBe={class:"pg-card-header"},JBe={class:"header-actions"},QBe={class:"text-muted",style:{"font-size":"12px"}},eVe={class:"pg-card-body"},tVe={class:"pg-card"},nVe={class:"pg-card-body"},aVe={class:"font-mono"},rVe={class:"text-danger",style:{"font-weight":"600"}},oVe={key:0,class:"font-mono"},iVe={key:1,class:"text-muted"},lVe={key:1,class:"text-muted"},sVe={key:1,class:"text-muted"},uVe={class:"text-muted"},cVe={class:"font-mono text-muted",style:{"font-size":"12px"}},dVe=["title"],fVe={key:0,class:"text-muted",style:{"text-align":"center",padding:"40px 0"}},vVe={class:"text-danger",style:{"font-weight":"600"}},hVe={__name:"Unmatched",props:{refreshKey:{type:Number,default:0}},setup(e){const t=e,n=F([]),a=F(!1),r=F("pending"),o=F(!1),i=F(!1),l=F({}),s=F({status:"resolved",note:""});function u(g){return{pending:"warning",resolved:"success",refunded:"info"}[g]||"info"}function c(g){return{pending:"待处理",resolved:"已确认",refunded:"已退款"}[g]||g}function d(g){return{big_amount:"大额",burst:"高频",repeat_payer:"同源重复"}[g]||g}function v(g){return g?String(g).replace("T"," ").slice(0,19):"—"}async function f(){a.value=!0;try{const g=await yt.personalUnmatched({status:r.value,limit:100});n.value=g.unmatched||[]}catch(g){We.error("加载失败:"+g.message)}finally{a.value=!1}}function h(g){l.value=g,s.value={status:"resolved",note:""},o.value=!0}async function m(){i.value=!0;try{await yt.resolveUnmatched(l.value.id,{status:s.value.status,note:s.value.note}),We.success("已处理"),o.value=!1,f()}catch(g){We.error("处理失败:"+g.message)}finally{i.value=!1}}return mt(f),ye(()=>t.refreshKey,f),(g,y)=>{const b=be("el-option"),_=be("el-select"),w=be("el-alert"),S=be("el-table-column"),k=be("el-tag"),T=be("el-button"),M=be("el-table"),E=be("el-descriptions-item"),A=be("el-descriptions"),I=be("el-radio"),P=be("el-radio-group"),R=be("el-form-item"),G=be("el-input"),K=be("el-form"),H=be("el-dialog"),z=Gr("loading");return x(),O("div",qBe,[C("div",XBe,[C("div",ZBe,[y[5]||(y[5]=C("span",{class:"section-label"},"无订单收款",-1)),C("div",JBe,[L(_,{modelValue:r.value,"onUpdate:modelValue":y[0]||(y[0]=B=>r.value=B),size:"small",style:{width:"130px"},onChange:f},{default:$(()=>[L(b,{label:"全部",value:""}),L(b,{label:"待处理",value:"pending"}),L(b,{label:"已确认",value:"resolved"}),L(b,{label:"已退款",value:"refunded"})]),_:1},8,["modelValue"]),C("span",QBe,"共 "+Q(n.value.length)+" 条",1)])]),C("div",eVe,[L(w,{type:"warning",closable:!1,class:"mb16",title:"有人向收款码转账但没有匹配到订单。请人工核对后处理:已确认(资金已收到)或已退款(原路退回)。"}),L(w,{type:"error",closable:!1,class:"mb16",title:"司法提示:系统按行为模式自动标记疑似风险收款(大额/同源高频)。标记金额疑为黑钱/赃款的,请立即核实付款人身份与资金来源,确认异常的请保全本页记录并联系司法部门处理。"})])]),C("div",tVe,[st((x(),O("div",nVe,[L(M,{data:n.value,stripe:"",style:{width:"100%"}},{default:$(()=>[L(S,{label:"ID",width:"70"},{default:$(({row:B})=>[C("span",aVe,"#"+Q(B.id),1)]),_:1}),L(S,{label:"通道",width:"100"},{default:$(({row:B})=>[L(k,{type:B.provider==="alipay"?"primary":"success",size:"small"},{default:$(()=>[ue(Q(B.provider==="alipay"?"支付宝":"微信"),1)]),_:2},1032,["type"])]),_:1}),L(S,{label:"金额",width:"120"},{default:$(({row:B})=>[C("span",rVe,"¥"+Q((B.amount_minor/100).toFixed(2)),1)]),_:1}),L(S,{label:"付款人",width:"120"},{default:$(({row:B})=>[B.payer?(x(),O("span",oVe,Q(B.payer),1)):(x(),O("span",iVe,"—"))]),_:1}),L(S,{label:"风险标记",width:"180"},{default:$(({row:B})=>[B.risk_level>0?(x(),O(Ve,{key:0},[L(k,{type:"danger",size:"small",effect:"dark",style:{"margin-right":"4px"}},{default:$(()=>[ue(Q(B.risk_level===2?"高风险":"建议核实"),1)]),_:2},1024),(x(!0),O(Ve,null,dt(String(B.risk_tags||"").split(",").filter(Boolean),W=>(x(),ne(k,{key:W,type:"danger",size:"small",effect:"plain",style:{"margin-right":"2px"}},{default:$(()=>[ue(Q(d(W)),1)]),_:2},1024))),128))],64)):(x(),O("span",lVe,"—"))]),_:1}),L(S,{label:"备注",width:"110"},{default:$(({row:B})=>[B.note?(x(),ne(k,{key:0,size:"small",type:"warning"},{default:$(()=>[ue(Q(B.note),1)]),_:2},1024)):(x(),O("span",sVe,"无"))]),_:1}),L(S,{label:"状态",width:"100"},{default:$(({row:B})=>[L(k,{type:u(B.status),size:"small",effect:"light",round:""},{default:$(()=>[ue(Q(c(B.status)),1)]),_:2},1032,["type"])]),_:1}),L(S,{label:"处理人",width:"110"},{default:$(({row:B})=>[C("span",uVe,Q(B.handler||"—"),1)]),_:1}),L(S,{label:"时间",width:"170"},{default:$(({row:B})=>[C("span",cVe,Q(v(B.created_at)),1)]),_:1}),L(S,{label:"通知原文","min-width":"200"},{default:$(({row:B})=>[C("div",{class:"raw-cell",title:B.raw},Q(B.raw||"—"),9,dVe)]),_:1}),L(S,{label:"操作",width:"100",fixed:"right"},{default:$(({row:B})=>[B.status==="pending"?(x(),ne(T,{key:0,size:"small",type:"primary",text:"",onClick:W=>h(B)},{default:$(()=>[...y[6]||(y[6]=[ue("处理",-1)])]),_:1},8,["onClick"])):te("",!0)]),_:1})]),_:1},8,["data"]),!a.value&&n.value.length===0?(x(),O("div",fVe," 暂无无订单收款记录 ")):te("",!0)])),[[z,a.value]])]),L(H,{modelValue:o.value,"onUpdate:modelValue":y[4]||(y[4]=B=>o.value=B),title:"处理无订单收款",width:"500px"},{footer:$(()=>[L(T,{onClick:y[3]||(y[3]=B=>o.value=!1)},{default:$(()=>[...y[9]||(y[9]=[ue("取消",-1)])]),_:1}),L(T,{type:"primary",loading:i.value,onClick:m},{default:$(()=>[...y[10]||(y[10]=[ue("提交",-1)])]),_:1},8,["loading"])]),default:$(()=>[L(A,{column:2,size:"small",border:"",class:"mb16"},{default:$(()=>[L(E,{label:"金额"},{default:$(()=>[C("span",vVe,"¥"+Q((l.value.amount_minor/100).toFixed(2)),1)]),_:1}),L(E,{label:"通道"},{default:$(()=>[ue(Q(l.value.provider==="alipay"?"支付宝":"微信"),1)]),_:1}),L(E,{label:"备注码"},{default:$(()=>[ue(Q(l.value.note||"—"),1)]),_:1}),L(E,{label:"ID"},{default:$(()=>[ue("#"+Q(l.value.id),1)]),_:1})]),_:1}),L(K,{"label-width":"90px"},{default:$(()=>[L(R,{label:"处理结果",required:""},{default:$(()=>[L(P,{modelValue:s.value.status,"onUpdate:modelValue":y[1]||(y[1]=B=>s.value.status=B)},{default:$(()=>[L(I,{value:"resolved"},{default:$(()=>[...y[7]||(y[7]=[ue("已确认收款",-1)])]),_:1}),L(I,{value:"refunded"},{default:$(()=>[...y[8]||(y[8]=[ue("已原路退款",-1)])]),_:1})]),_:1},8,["modelValue"])]),_:1}),L(R,{label:"处理备注"},{default:$(()=>[L(G,{modelValue:s.value.note,"onUpdate:modelValue":y[2]||(y[2]=B=>s.value.note=B),type:"textarea",rows:3,placeholder:"填写处理说明:核对依据、退款凭证等(留痕审计)"},null,8,["modelValue"])]),_:1})]),_:1})]),_:1},8,["modelValue"])])}}},pVe=fr(hVe,[["__scopeId","data-v-1df8350a"]]),mVe={class:"settings-page"},gVe={class:"pg-card"},yVe={class:"pg-card-body"},bVe={class:"channel-grid"},_Ve={class:"ch-info"},wVe={class:"ch-name"},SVe={class:"ch-status"},CVe={class:"mt16"},xVe={key:0,class:"saved-hint"},kVe={class:"admin-item"},TVe={class:"admin-info"},MVe={class:"admin-value font-mono"},EVe={class:"key-grid"},AVe={class:"key-label"},IVe={class:"key-value font-mono"},LVe={class:"key-actions"},PVe={__name:"Settings",setup(e){const t=nn({order_close_minutes:30,order_close_interval:60}),n=F([]),a=F(!1),r=F(!1),o=F({}),i=F(""),l=F(sessionStorage.getItem("admin_username")||""),s=F(!1),u=F(""),c=F(!1),d=[{key:"ledger_private_key",label:"审计账本私钥(ECDSA P-256)"},{key:"qr_token_secret",label:"套码 token 签名密钥"}];function v(){u.value="",s.value=!0}async function f(){if(!u.value){We.warning("请输入当前密码");return}c.value=!0;try{await yt.resetAdmin(u.value),We.success("管理员已重置,正在跳转到初始化页..."),sessionStorage.removeItem("admin_token"),sessionStorage.removeItem("admin_username"),setTimeout(()=>{window.location.reload()},1200)}catch(_){We.error("重置失败: "+_.message)}finally{c.value=!1}}async function h(){try{const _=await yt.settings(),w=_.settings||{};w.order_close_minutes&&(t.order_close_minutes=parseInt(w.order_close_minutes)),w.order_close_interval&&(t.order_close_interval=parseInt(w.order_close_interval)),n.value=(_.channels||[]).map(S=>{var k;return{name:S,enabled:((k=_.channel_enabled)==null?void 0:k[S])!==!1}})}catch(_){We.error("加载设置失败: "+_.message)}}async function m(_){if(i.value===_){i.value="";return}try{await er.confirm("将显示完整密钥(明文),请确认当前环境安全。查看行为会记入审计日志。","查看安全密钥",{confirmButtonText:"确认查看",cancelButtonText:"取消",type:"warning"});const w=await yt.settingsKeys();o.value=w||{},i.value=_}catch(w){w!=="cancel"&&We.error("获取密钥失败: "+(w.message||w))}}async function g(_){const w=o.value[_];if(!w){We.warning("密钥不存在");return}try{await navigator.clipboard.writeText(w),We.success("已复制")}catch(S){We.error("复制失败: "+S.message)}}async function y(){a.value=!0,r.value=!1;try{const _=[];_.push({key:"order_close_minutes",value:String(t.order_close_minutes)}),_.push({key:"order_close_interval",value:String(t.order_close_interval)}),await yt.saveSettings({settings:_}),O3(),r.value=!0,setTimeout(()=>{r.value=!1},3e3),We.success("保存成功")}catch(_){We.error("保存失败: "+_.message)}finally{a.value=!1}}async function b(_){try{const w=await yt.toggleChannel(_),S=n.value.find(k=>k.name===_);S&&S.enabled!==w.enabled&&(S.enabled=w.enabled),O3(),We.success(`通道 ${_} 已${w.enabled?"启用":"禁用"}`)}catch(w){const S=n.value.find(k=>k.name===_);S&&(S.enabled=!S.enabled),We.error("切换失败: "+w.message)}}return ye(oV,h),mt(h),(_,w)=>{const S=be("el-alert"),k=be("el-divider"),T=be("el-switch"),M=be("el-input-number"),E=be("el-form-item"),A=be("el-form"),I=be("el-button"),P=be("el-icon"),R=be("el-input"),G=be("el-dialog");return x(),O("div",mVe,[C("div",gVe,[w[15]||(w[15]=C("div",{class:"pg-card-header"},[C("span",{class:"section-label"},"系统设置")],-1)),C("div",yVe,[L(S,{type:"info",closable:!1,"show-icon":"",class:"mb16",title:"设置保存后立即生效,无需重启。"}),L(S,{type:"info",closable:!1,"show-icon":"",class:"mb16",title:"上游对接(易支付 pid/密钥/服务地址)已迁移到「上游对接」页:每个上游独立配置,AES-256-GCM 加密存于 upstreams.db。"}),L(k),w[11]||(w[11]=C("h3",{class:"sec-title"},"支付通道",-1)),C("div",bVe,[(x(!0),O(Ve,null,dt(n.value,K=>(x(),O("div",{key:K.name,class:U(["channel-switch",{disabled:!K.enabled}])},[C("div",_Ve,[C("div",wVe,Q(K.name),1),C("div",SVe,Q(K.enabled?"已启用":"已禁用"),1)]),L(T,{modelValue:K.enabled,"onUpdate:modelValue":H=>K.enabled=H,onChange:H=>b(K.name)},null,8,["modelValue","onUpdate:modelValue","onChange"])],2))),128))]),L(k),w[12]||(w[12]=C("h3",{class:"sec-title"},"系统参数",-1)),L(A,{model:t,"label-width":"180px",size:"small"},{default:$(()=>[L(E,{label:"订单超时关闭(分钟)"},{default:$(()=>[L(M,{modelValue:t.order_close_minutes,"onUpdate:modelValue":w[0]||(w[0]=K=>t.order_close_minutes=K),min:0,max:1440},null,8,["modelValue"]),w[5]||(w[5]=C("div",{class:"form-tip"},"0 = 不自动关闭",-1))]),_:1}),L(E,{label:"扫描间隔(秒)"},{default:$(()=>[L(M,{modelValue:t.order_close_interval,"onUpdate:modelValue":w[1]||(w[1]=K=>t.order_close_interval=K),min:10,max:600},null,8,["modelValue"])]),_:1})]),_:1},8,["model"]),C("div",CVe,[L(I,{type:"primary",size:"default",onClick:y,loading:a.value},{default:$(()=>[...w[6]||(w[6]=[ue("保存全部设置",-1)])]),_:1},8,["loading"]),r.value?(x(),O("span",xVe,[L(P,{size:14},{default:$(()=>[L(p(Ao))]),_:1}),w[7]||(w[7]=ue(" 已保存 ",-1))])):te("",!0)]),L(k),w[13]||(w[13]=C("h3",{class:"sec-title"},"管理员账号",-1)),C("div",kVe,[C("div",TVe,[w[8]||(w[8]=C("div",{class:"admin-label"},"当前登录账号",-1)),C("div",MVe,Q(l.value||"—"),1)]),L(I,{size:"small",type:"danger",plain:"",onClick:v},{default:$(()=>[...w[9]||(w[9]=[ue("重置管理员",-1)])]),_:1})]),L(S,{type:"warning",closable:!1,"show-icon":"",class:"mb12 mt12",title:"重置管理员会清除当前登录凭据并回到首次注册流程,需重新设置新的管理员账号(旧账号立即失效)。此操作需验证当前密码。"}),L(k),w[14]||(w[14]=C("h3",{class:"sec-title"},"安全密钥",-1)),L(S,{type:"info",closable:!1,"show-icon":"",class:"mb12",title:"账本签名私钥与套码 token 密钥已自动生成并存库(首次启动无需手动配置)。查看完整值需确认,操作会记入审计日志。"}),C("div",EVe,[(x(),O(Ve,null,dt(d,K=>C("div",{key:K.key,class:"key-item"},[C("div",AVe,Q(K.label),1),C("div",IVe,Q(i.value===K.key?o.value[K.key]||"(未生成)":"••••••••••••••••"),1),C("div",LVe,[L(I,{size:"small",text:"",type:"primary",onClick:H=>m(K.key)},{default:$(()=>[ue(Q(i.value===K.key?"隐藏":"查看"),1)]),_:2},1032,["onClick"]),L(I,{size:"small",text:"",type:"primary",disabled:i.value!==K.key,onClick:H=>g(K.key)},{default:$(()=>[...w[10]||(w[10]=[ue("复制",-1)])]),_:1},8,["disabled","onClick"])])])),64))])])]),L(G,{modelValue:s.value,"onUpdate:modelValue":w[4]||(w[4]=K=>s.value=K),title:"重置管理员账号",width:"440px","close-on-click-modal":!1},{footer:$(()=>[L(I,{onClick:w[3]||(w[3]=K=>s.value=!1)},{default:$(()=>[...w[16]||(w[16]=[ue("取消",-1)])]),_:1}),L(I,{type:"danger",loading:c.value,onClick:f},{default:$(()=>[...w[17]||(w[17]=[ue("确认重置",-1)])]),_:1},8,["loading"])]),default:$(()=>[L(S,{type:"warning",closable:!1,class:"mb16","show-icon":"",title:"重置后将回到首次注册页,需要重新设置管理员。当前账号立即失效。"}),L(A,{"label-width":"100px",size:"small"},{default:$(()=>[L(E,{label:"当前密码",required:""},{default:$(()=>[L(R,{modelValue:u.value,"onUpdate:modelValue":w[2]||(w[2]=K=>u.value=K),type:"password","show-password":"",placeholder:"验证当前管理员密码",onKeyup:kn(f,["enter"])},null,8,["modelValue"])]),_:1})]),_:1})]),_:1},8,["modelValue"])])}}},DVe=fr(PVe,[["__scopeId","data-v-49bef0e4"]]),OVe={key:1,class:"empty"},$Ve={key:2,class:"qrcode-grid"},RVe={class:"card-header"},NVe={class:"device-name"},BVe={key:0,class:"card-account"},VVe={class:"account-name"},zVe={key:1,class:"card-account"},FVe={class:"card-body"},HVe={key:0,class:"qr-area"},WVe=["src"],KVe={class:"qr-deadline"},UVe={key:1,class:"no-qr"},GVe={class:"card-footer"},YVe={__name:"Qrcodes",props:{refreshKey:Number},setup(e){const t=F(!1),n=F(""),a=F([]),r=F(""),o=F("");let i=null;async function l(){t.value=!0,n.value="";try{const f=await yt.personalQrcodes();a.value=Array.isArray(f)?f:[]}catch(f){n.value=f.message}finally{t.value=!1}}function s(f){return f?"https://chart.googleapis.com/chart?chs=200x200&cht=qr&chl="+encodeURIComponent(f):""}function u(f){if(!f.expire_at)return 0;const h=new Date(f.expire_at).getTime(),m=Date.now();return Math.max(0,Math.floor((h-m)/1e3))}function c(f){const h=u(f);if(h<=0)return"已过期";const m=Math.floor(h/60),g=h%60;return`${m}:${String(g).padStart(2,"0")}`}async function d(f,h,m=0){const g=f+"-"+m;r.value=g;try{h==="wechat"?await yt.personalCaptureWechatQrcode(f,m):await yt.personalCaptureAlipayQrcode(f,m),await l(),n.value=""}catch(y){n.value="adb捕获失败: "+y.message}finally{r.value=""}}async function v(f){const h=f.user_id??0;try{await er.confirm(`确定移除「${f.device_name||f.device_id}」的${f.platform==="alipay"?"支付宝":"微信"}收款码(${h===999?"双开":"主空间"})?`,"移除收款码",{type:"warning",confirmButtonText:"移除",cancelButtonText:"取消"})}catch{return}o.value=f.device_id+"-"+f.platform+"-"+h;try{await yt.personalDeleteQrcode(f.device_id,f.platform,h),await l()}catch(m){n.value="移除失败: "+m.message}finally{o.value=""}}return mt(()=>{l(),i=setInterval(l,15e3)}),Us(()=>{i&&clearInterval(i)}),(f,h)=>{const m=be("el-alert"),g=be("el-empty"),y=be("el-tag"),b=be("el-icon"),_=be("el-button"),w=Gr("loading");return st((x(),O("div",null,[n.value?(x(),ne(m,{key:0,title:n.value,type:"error","show-icon":"",closable:"",class:"mb-3"},null,8,["title"])):te("",!0),!t.value&&a.value.length===0?(x(),O("div",OVe,[L(g,{description:"暂无收款码数据"})])):(x(),O("div",$Ve,[(x(!0),O(Ve,null,dt(a.value,S=>(x(),O("div",{key:S.device_id+"-"+S.platform+"-"+(S.user_id??0),class:"qrcode-card"},[C("div",RVe,[C("span",NVe,Q(S.device_name||S.device_id),1),S.platform==="alipay"?(x(),ne(y,{key:0,type:"primary",size:"small"},{default:$(()=>[...h[0]||(h[0]=[ue("支付宝",-1)])]),_:1})):S.platform==="wechat"?(x(),ne(y,{key:1,type:"success",size:"small"},{default:$(()=>[...h[1]||(h[1]=[ue("微信",-1)])]),_:1})):te("",!0),L(y,{type:S.status==="active"?"success":"info",size:"small"},{default:$(()=>[ue(Q(S.status==="active"?"有效":"已过期"),1)]),_:2},1032,["type"]),(S.user_id??0)===999?(x(),ne(y,{key:2,type:"warning",size:"small",effect:"plain"},{default:$(()=>[...h[2]||(h[2]=[ue("双开",-1)])]),_:1})):(x(),ne(y,{key:3,size:"small",effect:"plain"},{default:$(()=>[...h[3]||(h[3]=[ue("主空间",-1)])]),_:1}))]),S.account?(x(),O("div",BVe,[h[4]||(h[4]=ue(" 收款账户: ",-1)),C("span",VVe,Q(S.account),1)])):(x(),O("div",zVe,[...h[5]||(h[5]=[ue("收款账户: ",-1),C("span",{class:"account-unknown"},"未知",-1)])])),C("div",FVe,[S.qr_content?(x(),O("div",HVe,[C("img",{src:s(S.qr_content),class:"qr-img",alt:"收款码"},null,8,WVe),C("div",KVe,[h[6]||(h[6]=ue(" 过期倒计时: ",-1)),C("span",{class:U({urgent:u(S)<15})},Q(c(S)),3)])])):(x(),O("div",UVe,"暂无收款码"))]),C("div",GVe,[S.platform==="wechat"?(x(),ne(_,{key:0,type:"success",size:"small",loading:r.value===S.device_id+"-"+(S.user_id??0),onClick:k=>d(S.device_id,"wechat",S.user_id??0)},{default:$(()=>[L(b,null,{default:$(()=>[L(p(p4))]),_:1}),h[7]||(h[7]=ue(" adb捕获 ",-1))]),_:1},8,["loading","onClick"])):te("",!0),S.platform==="alipay"?(x(),ne(_,{key:1,type:"warning",size:"small",loading:r.value===S.device_id+"-"+(S.user_id??0),onClick:k=>d(S.device_id,"alipay",S.user_id??0)},{default:$(()=>[L(b,null,{default:$(()=>[L(p(p4))]),_:1}),h[8]||(h[8]=ue(" adb捕获 ",-1))]),_:1},8,["loading","onClick"])):te("",!0),L(_,{type:"danger",size:"small",plain:"",loading:o.value===S.device_id+"-"+S.platform+"-"+(S.user_id??0),onClick:k=>v(S)},{default:$(()=>[L(b,null,{default:$(()=>[L(p(W6))]),_:1}),h[9]||(h[9]=ue(" 移除 ",-1))]),_:1},8,["loading","onClick"])])]))),128))]))])),[[w,t.value]])}}},jVe=fr(YVe,[["__scopeId","data-v-9290643a"]]),qVe={class:"sidebar"},XVe={class:"nav"},ZVe=["onClick"],JVe={class:"sidebar-foot"},QVe={class:"tunnel-text"},eze={class:"main-area"},tze={class:"topbar"},nze={class:"page-title"},aze={class:"topbar-right"},rze={class:"content"},oze={__name:"App",setup(e){const t=[{id:"dashboard",label:"仪表盘",icon:"Odometer"},{id:"channels",label:"支付通道",icon:"Connection"},{id:"orders",label:"订单管理",icon:"List"},{id:"notify",label:"回调队列",icon:"Bell"},{id:"logs",label:"系统日志",icon:"Document"},{id:"accounts",label:"账户池",icon:"Wallet"},{id:"upstreams",label:"上游对接",icon:"Share"},{id:"reconciler",label:"对账中心",icon:"WarningFilled"},{id:"ledger",label:"审计账本",icon:"Lock"},{id:"devices",label:"监听端",icon:"Iphone"},{id:"unmatched",label:"无订单收款",icon:"Coin"},{id:"qrcodes",label:"收款码",icon:"Camera"},{id:"settings",label:"系统设置",icon:"Setting"}],n={dashboard:"仪表盘",channels:"支付通道",orders:"订单管理",notify:"回调队列",logs:"系统日志",accounts:"账户池",upstreams:"上游对接",reconciler:"对账中心",ledger:"审计账本",devices:"监听端",unmatched:"无订单收款",qrcodes:"动态收款码",settings:"系统设置"},a=F(!1),r=F("dashboard"),o=F(!1),i=F(!1),l=F(0);mt(()=>{sessionStorage.getItem("admin_token")&&(a.value=!0);const h=localStorage.getItem("pg-theme");o.value=h==="dark",document.documentElement.classList.toggle("dark",o.value)});const s=D(()=>n[r.value]);function u(f){r.value=f}function c(f){i.value=(f==null?void 0:f.running)&&(f==null?void 0:f.healthy)}function d(){o.value=!o.value,document.documentElement.classList.toggle("dark",o.value),localStorage.setItem("pg-theme",o.value?"dark":"light")}function v(){l.value++}return(f,h)=>{const m=be("el-icon"),g=be("Refresh"),y=be("el-tooltip"),b=be("Sunny"),_=be("Moon");return a.value?(x(),O("div",{key:1,class:U(["app-shell",{dark:o.value}])},[C("aside",qVe,[h[2]||(h[2]=_7('
PayGateway
管理控制台
',1)),C("nav",XVe,[(x(),O(Ve,null,dt(t,w=>C("button",{key:w.id,class:U(["nav-item",{active:r.value===w.id}]),onClick:S=>u(w.id)},[L(m,{size:15},{default:$(()=>[(x(),ne(St(w.icon)))]),_:2},1024),C("span",null,Q(w.label),1),w.badge?(x(),O("span",{key:0,class:U(["nav-badge",w.badgeType])},Q(w.badge),3)):te("",!0)],10,ZVe)),64))]),C("div",JVe,[C("div",{class:U(["tunnel-mini",{ok:i.value}])},[h[1]||(h[1]=C("span",{class:"dot"},null,-1)),C("span",QVe,Q(i.value?"隧道正常":"未连接"),1)],2)])]),C("div",eze,[C("header",tze,[C("div",nze,Q(s.value),1),C("div",aze,[L(y,{content:"刷新数据",placement:"bottom"},{default:$(()=>[C("button",{class:"icon-btn",onClick:v},[L(m,{size:15},{default:$(()=>[L(g)]),_:1})])]),_:1}),L(y,{content:o.value?"浅色模式":"深色模式",placement:"bottom"},{default:$(()=>[C("button",{class:"icon-btn",onClick:d},[L(m,{size:15},{default:$(()=>[o.value?(x(),ne(b,{key:0})):(x(),ne(_,{key:1}))]),_:1})])]),_:1},8,["content"]),h[3]||(h[3]=C("div",{class:"avatar"},"G",-1))])]),C("main",rze,[L(Pa,{name:"page",mode:"out-in"},{default:$(()=>[r.value==="dashboard"?(x(),ne(VPe,{key:0,"refresh-key":l.value,onTunnel:c,onGoto:u},null,8,["refresh-key"])):r.value==="channels"?(x(),ne(TDe,{key:1,"refresh-key":l.value},null,8,["refresh-key"])):r.value==="orders"?(x(),ne(JDe,{key:2,"refresh-key":l.value},null,8,["refresh-key"])):r.value==="notify"?(x(),ne(wOe,{key:3,"refresh-key":l.value},null,8,["refresh-key"])):r.value==="logs"?(x(),ne(jOe,{key:4,"refresh-key":l.value},null,8,["refresh-key"])):r.value==="accounts"?(x(),ne(ARe,{key:5,"refresh-key":l.value},null,8,["refresh-key"])):r.value==="upstreams"?(x(),ne(cNe,{key:6,"refresh-key":l.value},null,8,["refresh-key"])):r.value==="reconciler"?(x(),ne(ONe,{key:7,"refresh-key":l.value},null,8,["refresh-key"])):r.value==="ledger"?(x(),ne(F$e,{key:8,"refresh-key":l.value},null,8,["refresh-key"])):r.value==="devices"?(x(),ne(jBe,{key:9,"refresh-key":l.value},null,8,["refresh-key"])):r.value==="unmatched"?(x(),ne(pVe,{key:10,"refresh-key":l.value},null,8,["refresh-key"])):r.value==="qrcodes"?(x(),ne(jVe,{key:11,"refresh-key":l.value},null,8,["refresh-key"])):r.value==="settings"?(x(),ne(DVe,{key:12,"refresh-key":l.value},null,8,["refresh-key"])):te("",!0)]),_:1})])])],2)):(x(),ne(Xwe,{key:0,onLoginSuccess:h[0]||(h[0]=w=>a.value=!0)}))}}},ize=fr(oze,[["__scopeId","data-v-b117130e"]]),kS=V7(ize);kS.use(Fwe,{locale:Hwe});for(const[e,t]of Object.entries(lie))kS.component(e,t);kS.mount("#app")});export default lze(); diff --git a/internal/admin/static/index.html b/internal/admin/static/index.html new file mode 100644 index 0000000..c7b8362 --- /dev/null +++ b/internal/admin/static/index.html @@ -0,0 +1,14 @@ + + + + + + PayGateway 管理后台 + + + + + +
+ + diff --git a/internal/admin/upstreams.go b/internal/admin/upstreams.go new file mode 100755 index 0000000..4b1a206 --- /dev/null +++ b/internal/admin/upstreams.go @@ -0,0 +1,453 @@ +// Package admin 管理后台:上游对接方管理 API +// +// 安全设计: +// - 本文件是唯一访问 upstreams.db 的组件 +// - 敏感字段(易支付密钥 key)AES-256-GCM 加密后落库 +// - 解密后的上游数据推送到 UpstreamPool(内存),供 epay 验签/回调签名使用 +// - 常规 API 响应不返回明文密钥(仅 "****" + 后4位) +// - 唯一例外:GET /upstreams/:id/secret(需登录,供管理后台「查看/复制完整密钥」),返回解密后的明文 key +package admin + +import ( + "context" + "fmt" + "net/http" + "strconv" + "strings" + "time" + + "github.com/gin-gonic/gin" + + "paygateway/internal/account" + "paygateway/internal/logger" + "paygateway/internal/store" + "paygateway/internal/upstream" +) + +// UpstreamHandler 上游管理处理器 +type UpstreamHandler struct { + upstreamDB *store.UpstreamDB // 唯一访问 upstreams.db 的组件 + pool *upstream.UpstreamPool + initCrypto func(string) error // 初始化加密模块(与账户共用主密钥) + encFn func([]byte) (string, error) + decFn func(string) ([]byte, error) +} + +// NewUpstreamHandler 创建上游管理处理器 +func NewUpstreamHandler(db *store.UpstreamDB, pool *upstream.UpstreamPool, masterKey string) (*UpstreamHandler, error) { + // 复用 account 包的 AES-256-GCM 加密能力(主密钥与账户池一致) + if err := account.InitCrypto(masterKey); err != nil { + return nil, fmt.Errorf("上游加密初始化失败: %w", err) + } + return &UpstreamHandler{ + upstreamDB: db, + pool: pool, + initCrypto: account.InitCrypto, + encFn: account.Encrypt, + decFn: account.Decrypt, + }, nil +} + +// InitFromDB 启动时从 upstreams.db 加载所有上游,解密后初始化 UpstreamPool +func (h *UpstreamHandler) InitFromDB(ctx context.Context) error { + encrypted, err := h.upstreamDB.ListUpstreams(ctx) + if err != nil { + return fmt.Errorf("加载上游列表失败: %w", err) + } + var decryptErrs []string + for _, e := range encrypted { + up, err := h.decryptUpstream(e) + if err != nil { + logger.Error("upstream", "解密上游失败(主密钥不一致或数据损坏)", + "id", e.ID, "name", e.Name, "err", err) + decryptErrs = append(decryptErrs, fmt.Sprintf("id=%d name=%s: %v", e.ID, e.Name, err)) + continue + } + h.pool.Add(up) + } + // P1 守卫:DB 非空但所有/部分记录解密失败 = 主密钥变更或数据损坏,禁止静默启动(否则网关下单链路全断) + if len(decryptErrs) > 0 { + return fmt.Errorf("upstreams.db 解密失败 %d/%d 条(疑似 ACCOUNT_MASTER_KEY 变更),已拒绝启动;请恢复 .env 中的 ACCOUNT_MASTER_KEY 或清空 upstreams.db 后重新对接。失败明细:\n%s", + len(decryptErrs), len(encrypted), strings.Join(decryptErrs, "\n")) + } + logger.Info("upstream", "上游池初始化完成", "count", h.pool.Count()) + return nil +} + +// RegisterRoutes 注册上游管理路由(需在 authMiddleware 组内) +func (h *UpstreamHandler) RegisterRoutes(rg *gin.RouterGroup) { + rg.GET("/upstreams", h.handleListUpstreams) + rg.GET("/upstreams/:id", h.handleGetUpstream) + rg.POST("/upstreams", h.handleCreateUpstream) + rg.PUT("/upstreams/:id", h.handleUpdateUpstream) + rg.DELETE("/upstreams/:id", h.handleDeleteUpstream) + rg.POST("/upstreams/:id/enable", h.handleEnableUpstream) + rg.POST("/upstreams/:id/disable", h.handleDisableUpstream) + rg.GET("/upstreams/:id/logs", h.handleUpstreamLogs) + rg.GET("/upstreams/:id/secret", h.handleUpstreamSecret) +} + +// ========== API Handlers ========== + +// UpstreamResponse 前端展示的上游信息(密钥脱敏) +type UpstreamResponse struct { + ID int64 `json:"id"` + Name string `json:"name"` + Pid string `json:"pid"` + Key string `json:"key"` // 脱敏显示 + BaseURL string `json:"base_url"` + NotifyDomains string `json:"notify_domains"` + Status string `json:"status"` + Remark string `json:"remark,omitempty"` + CreatedAt string `json:"created_at"` + UpdatedAt string `json:"updated_at"` +} + +// upstreamToResponse 将 Upstream 转为前端响应(脱敏密钥) +func upstreamToResponse(up *upstream.Upstream) *UpstreamResponse { + return &UpstreamResponse{ + ID: up.ID, + Name: up.Name, + Pid: up.Pid, + Key: maskKey(up.Key), + BaseURL: up.BaseURL, + NotifyDomains: up.NotifyDomains, + Status: string(up.Status), + Remark: up.Remark, + CreatedAt: up.CreatedAt.Format(time.RFC3339), + UpdatedAt: up.UpdatedAt.Format(time.RFC3339), + } +} + +// encryptUpstream 加密上游的敏感字段,返回 UpstreamEncrypted(用于落库) +func (h *UpstreamHandler) encryptUpstream(up *upstream.Upstream) (*upstream.UpstreamEncrypted, error) { + e := &upstream.UpstreamEncrypted{ + Name: up.Name, + Pid: up.Pid, + BaseURL: up.BaseURL, + NotifyDomains: up.NotifyDomains, + Status: string(up.Status), + Remark: up.Remark, + CreatedAt: up.CreatedAt, + UpdatedAt: up.UpdatedAt, + } + var err error + if up.Key != "" { + e.KeyEnc, err = h.encFn([]byte(up.Key)) + if err != nil { + return nil, fmt.Errorf("加密易支付密钥失败: %w", err) + } + } + return e, nil +} + +// decryptUpstream 解密上游的敏感字段,返回 Upstream(用于内存) +func (h *UpstreamHandler) decryptUpstream(e *upstream.UpstreamEncrypted) (*upstream.Upstream, error) { + up := &upstream.Upstream{ + ID: e.ID, + Name: e.Name, + Pid: e.Pid, + BaseURL: e.BaseURL, + NotifyDomains: e.NotifyDomains, + Status: upstream.UpstreamStatus(e.Status), + Remark: e.Remark, + CreatedAt: e.CreatedAt, + UpdatedAt: e.UpdatedAt, + } + if e.KeyEnc != "" { + data, err := h.decFn(e.KeyEnc) + if err != nil { + return nil, fmt.Errorf("解密易支付密钥失败: %w", err) + } + up.Key = string(data) + } + return up, nil +} + +// ========== List ========== + +func (h *UpstreamHandler) handleListUpstreams(c *gin.Context) { + ups := h.pool.All() + resp := make([]*UpstreamResponse, 0, len(ups)) + for _, up := range ups { + resp = append(resp, upstreamToResponse(up)) + } + c.JSON(http.StatusOK, gin.H{"data": resp}) +} + +// ========== Get ========== + +func (h *UpstreamHandler) handleGetUpstream(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + if id <= 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "无效上游ID"}) + return + } + up := h.pool.ByID(id) + if up == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "上游不存在"}) + return + } + c.JSON(http.StatusOK, gin.H{"data": upstreamToResponse(up)}) +} + +// handleUpstreamSecret 返回上游完整密钥(明文,需登录)。 +// 用途:管理后台「查看/复制完整密钥」与「对接指引」复制真实可用的密钥。 +// 安全提示:仅登录用户可调用;前端展示时应二次确认并提示勿泄露。 +func (h *UpstreamHandler) handleUpstreamSecret(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + if id <= 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "无效上游ID"}) + return + } + up := h.pool.ByID(id) + if up == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "上游不存在"}) + return + } + logger.Info("upstream", "上游密钥被查看(明文)", "id", id, "name", up.Name, "operator", c.GetString("username")) + c.JSON(http.StatusOK, gin.H{"data": gin.H{ + "id": up.ID, + "name": up.Name, + "pid": up.Pid, + "key": up.Key, + }}) +} + +// ========== Create ========== + +type createUpstreamReq struct { + Name string `json:"name" binding:"required"` + Pid string `json:"pid" binding:"required"` + Key string `json:"key"` + BaseURL string `json:"base_url"` + NotifyDomains string `json:"notify_domains"` + Remark string `json:"remark"` +} + +func (h *UpstreamHandler) handleCreateUpstream(c *gin.Context) { + var req createUpstreamReq + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误: " + err.Error()}) + return + } + if req.Key == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "易支付密钥不能为空"}) + return + } + + now := time.Now() + up := &upstream.Upstream{ + Name: req.Name, + Pid: req.Pid, + Key: req.Key, + BaseURL: req.BaseURL, + NotifyDomains: req.NotifyDomains, + Status: upstream.StatusEnabled, + Remark: req.Remark, + CreatedAt: now, + UpdatedAt: now, + } + + // 加密敏感字段 + enc, err := h.encryptUpstream(up) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + + // 写入 upstreams.db(仅 Admin API 可访问) + id, err := h.upstreamDB.CreateUpstream(c.Request.Context(), enc) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "创建上游失败(pid 可能已存在): " + err.Error()}) + return + } + up.ID = id + + // 推送到内存池(其他模块立即可用) + h.pool.Add(up) + + // 审计日志 + _ = h.upstreamDB.InsertUpstreamLog(c.Request.Context(), &upstream.UpstreamLog{ + UpstreamID: id, + Action: "create", + Detail: fmt.Sprintf("创建上游: %s (pid=%s)", req.Name, req.Pid), + Operator: c.GetString("username"), + }) + + logger.Info("upstream", "上游创建成功", "id", id, "name", req.Name, "pid", req.Pid) + c.JSON(http.StatusOK, gin.H{"data": upstreamToResponse(up)}) +} + +// ========== Update ========== + +type updateUpstreamReq struct { + Name string `json:"name" binding:"required"` + Pid string `json:"pid" binding:"required"` + Key string `json:"key"` + BaseURL string `json:"base_url"` + NotifyDomains string `json:"notify_domains"` + Remark string `json:"remark"` +} + +func (h *UpstreamHandler) handleUpdateUpstream(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + if id <= 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "无效上游ID"}) + return + } + + var req updateUpstreamReq + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误: " + err.Error()}) + return + } + + // 保留原有状态与密钥(密钥字段前端不传(空)时保留原值,避免误清空) + existing := h.pool.ByID(id) + if existing == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "上游不存在"}) + return + } + + up := &upstream.Upstream{ + ID: id, + Name: req.Name, + Pid: req.Pid, + Key: existing.Key, + BaseURL: req.BaseURL, + NotifyDomains: req.NotifyDomains, + Status: existing.Status, + Remark: req.Remark, + CreatedAt: existing.CreatedAt, + UpdatedAt: time.Now(), + } + if req.Key != "" { + up.Key = req.Key + } + + // 加密敏感字段 + enc, err := h.encryptUpstream(up) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + enc.ID = id + + // 更新 upstreams.db + if err := h.upstreamDB.UpdateUpstream(c.Request.Context(), enc); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "更新上游失败(pid 可能已存在): " + err.Error()}) + return + } + + // 更新内存池 + h.pool.Update(up) + + // 审计日志 + _ = h.upstreamDB.InsertUpstreamLog(c.Request.Context(), &upstream.UpstreamLog{ + UpstreamID: id, + Action: "update", + Detail: fmt.Sprintf("更新上游: %s (pid=%s)", req.Name, req.Pid), + Operator: c.GetString("username"), + }) + + logger.Info("upstream", "上游更新成功", "id", id) + c.JSON(http.StatusOK, gin.H{"data": upstreamToResponse(up)}) +} + +// ========== Delete ========== + +func (h *UpstreamHandler) handleDeleteUpstream(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + if id <= 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "无效上游ID"}) + return + } + + ctx := c.Request.Context() + + // 删除数据库 + if err := h.upstreamDB.DeleteUpstream(ctx, id); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "删除上游失败: " + err.Error()}) + return + } + + // 移除内存池 + h.pool.Remove(id) + + // 审计日志 + _ = h.upstreamDB.InsertUpstreamLog(ctx, &upstream.UpstreamLog{ + UpstreamID: id, + Action: "delete", + Detail: fmt.Sprintf("删除上游 ID: %d", id), + Operator: c.GetString("username"), + }) + + logger.Info("upstream", "上游已删除", "id", id) + c.JSON(http.StatusOK, gin.H{"data": "ok"}) +} + +// ========== Enable / Disable ========== + +func (h *UpstreamHandler) handleEnableUpstream(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + if id <= 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "无效上游ID"}) + return + } + ctx := c.Request.Context() + + if err := h.upstreamDB.SetUpstreamStatus(ctx, id, string(upstream.StatusEnabled)); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + h.pool.SetStatus(id, upstream.StatusEnabled) + + _ = h.upstreamDB.InsertUpstreamLog(ctx, &upstream.UpstreamLog{ + UpstreamID: id, Action: "enable", Detail: "手动启用", Operator: c.GetString("username"), + }) + logger.Info("upstream", "上游已启用", "id", id) + c.JSON(http.StatusOK, gin.H{"data": "ok"}) +} + +func (h *UpstreamHandler) handleDisableUpstream(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + if id <= 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "无效上游ID"}) + return + } + ctx := c.Request.Context() + + if err := h.upstreamDB.SetUpstreamStatus(ctx, id, string(upstream.StatusDisabled)); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + h.pool.SetStatus(id, upstream.StatusDisabled) + + _ = h.upstreamDB.InsertUpstreamLog(ctx, &upstream.UpstreamLog{ + UpstreamID: id, Action: "disable", Detail: "手动禁用", Operator: c.GetString("username"), + }) + logger.Info("upstream", "上游已禁用", "id", id) + c.JSON(http.StatusOK, gin.H{"data": "ok"}) +} + +// ========== Upstream Logs ========== + +func (h *UpstreamHandler) handleUpstreamLogs(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + if id <= 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "无效上游ID"}) + return + } + limit, _ := strconv.Atoi(c.DefaultQuery("limit", "30")) + if limit <= 0 || limit > 100 { + limit = 30 + } + + logs, err := h.upstreamDB.ListUpstreamLogs(c.Request.Context(), id, limit) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"data": logs}) +} diff --git a/internal/admin/wizard.go b/internal/admin/wizard.go new file mode 100644 index 0000000..62933d3 --- /dev/null +++ b/internal/admin/wizard.go @@ -0,0 +1,377 @@ +// 引导向导(USB 真机集成自动化) +// +// 目标:把「插 USB → 开 USB 调试 → 绑设备」全流程从手动操作变成 web 前端逐步引导。 +// 2026-08-30 重构:监听端 APP 弃用,向导收敛为「绑定设备即完成」; +// 设备上线/账户解冻由 DeviceWatcher 按 adb 可达性驱动(不再依赖心跳/完成度门槛)。 +// +// 三步接口(全部在 authMiddleware 组内): +// - GET /personal/usb-detect 前端轮询 USB 设备插入状态 +// - POST /personal/devices/wizard-start 绑定设备(幂等,断点续传) +// - POST /personal/devices/:id/wizard-wifi 配置 WiFi 备用通道(USB 断线自动切换) +// - POST /personal/devices/:id/wizard-verify 验证 adb 连通 + 返回保活引导(wizard 收尾) +// +// 完成度推进: +// +// wizard-start → setup_progress=["usb_connected","bound"] +package admin + +import ( + "context" + "fmt" + "net/http" + "strconv" + "time" + + "github.com/gin-gonic/gin" + + "paygateway/internal/account" + "paygateway/internal/logger" + "paygateway/internal/personal" +) + +// handleUSBCheck 列出当前已连 USB 真机中「未绑定本平台」的设备。 +// GET /api/admin/personal/usb-detect +// +// 前端 2-3s 轮询,识别新插入的设备(state 从 unauthorized → device 表示手机端已授权)。 +// 响应:{"data":{"devices":[{"serial":"4f2bdc30","state":"device","model":"MI 8","bound":false},...]}} +func (h *PersonalHandler) handleUSBCheck(c *gin.Context) { + if h.adbMgr == nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "adb 管理器未配置,USB 检测不可用"}) + return + } + devs, err := h.adbMgr.ListDevices(c.Request.Context()) + if err != nil { + c.JSON(http.StatusBadGateway, gin.H{"error": "设备检测失败: " + err.Error()}) + return + } + // 已绑定 serial 集合(匹配 adb_host,USB 纯 serial 与 WiFi host:port 皆算) + bound := h.boundSerials(c.Request.Context()) + out := make([]gin.H, 0, len(devs)) + for _, d := range devs { + _, isBound := bound[d.Serial] + out = append(out, gin.H{ + "serial": d.Serial, "state": d.State, "model": d.Model, "kind": d.Kind, "bound": isBound, + }) + } + c.JSON(http.StatusOK, gin.H{"data": gin.H{"devices": out}}) +} + +// boundSerials 返回当前已绑定设备的 adb serial 集合(累加 USB 主通道 + WiFi 备用通道) +func (h *PersonalHandler) boundSerials(ctx context.Context) map[string]int64 { + out := make(map[string]int64) + devs, err := h.pdb.ListDevices(ctx) + if err != nil { + return out + } + for _, d := range devs { + if d.AdbHost != "" { + out[d.AdbHost] = d.ID + } + if d.AdbWifiHost != "" && d.AdbWifiPort > 0 { + out[fmt.Sprintf("%s:%d", d.AdbWifiHost, d.AdbWifiPort)] = d.ID + } + } + return out +} + +func containsColon(s string) bool { + for i := 0; i < len(s); i++ { + if s[i] == ':' { + return true + } + } + return false +} + +// wizardStartReq wizard-start 请求体 +type wizardStartReq struct { + Serial string `json:"serial"` // USB serial(必填) + Name string `json:"name"` // 设备备注名(必填) + AccountID int64 `json:"account_id"` // 收款账户 ID(必填) +} + +// handleWizardStart 引导第 3 步:绑定设备(创建设备记录 + 写 setup 进度)。 +// POST /api/admin/personal/devices/wizard-start +// +// 幂等 + 断点续传: +// - serial 未绑定 → 创建设备,setup_progress=["usb_connected","bound"] +// - serial 已绑定 → 返回已有设备 + 当前进度(前端从断点继续,不重复创建) +// +// 响应:{"data":{"ok":true,"id":7,"device_id":"...","setup_progress":["usb_connected","bound"]}} +func (h *PersonalHandler) handleWizardStart(c *gin.Context) { + var req wizardStartReq + if err := c.ShouldBindJSON(&req); err != nil || req.Serial == "" || req.Name == "" || req.AccountID <= 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误:需要 serial + name + account_id"}) + return + } + // serial 可为 USB 直连设备号(不含 ":")或 WiFi host:port(含 ":",须已 adb connect 过) + if req.Serial == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误:需要 serial"}) + return + } + acct := h.pool.GetByID(req.AccountID) + if acct == nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "账户不存在"}) + return + } + if !isPersonalChannelName(acct.Channel) { + c.JSON(http.StatusBadRequest, gin.H{"error": "账户不是个人通道账户(channel=" + acct.Channel + ")"}) + return + } + if acct.QRContent == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "账户未配置收款码(qr_content),请先上传收款码"}) + return + } + // 幂等:serial 已绑定 → 断点续传 + devs, err := h.pdb.ListDevices(c.Request.Context()) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + return + } + for _, d := range devs { + if d.AdbHost == req.Serial { + steps := personal.ParseSetupProgress(d.SetupProgress) + // 已绑定即视为已接入(上线判定由 DeviceWatcher 按 adb 可达性驱动) + if personal.HasSetupStep(steps, personal.SetupStepBound) { + c.JSON(http.StatusOK, gin.H{"data": gin.H{ + "ok": true, "id": d.ID, "device_id": d.DeviceID, + "setup_progress": steps, "already_complete": true, + }}) + return + } + c.JSON(http.StatusOK, gin.H{"data": gin.H{ + "ok": true, "id": d.ID, "device_id": d.DeviceID, + "setup_progress": steps, "resumed": true, + }}) + return + } + } + // 新建:生成设备密钥 + 加密 + 写入(绑定逻辑内联,密钥仅此处返回一次) + secret, err := personal.NewDeviceSecret() + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "生成设备密钥失败: " + err.Error()}) + return + } + secretEnc, err := account.Encrypt([]byte(secret)) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "加密设备密钥失败: " + err.Error()}) + return + } + provider := providerFromChannelName(acct.Channel) + deviceID := personal.NewDeviceID() + steps := []string{personal.SetupStepUSBConnected, personal.SetupStepBound} + d := &personal.DeviceEncrypted{ + DeviceID: deviceID, Name: req.Name, AccountID: acct.ID, + Provider: provider, SecretEnc: secretEnc, Status: string(personal.DeviceOffline), + AdbHost: req.Serial, + // 完成度门槛:新建即标记 usb_connected + bound,账户后续由心跳/删除 handler 守恒冻结 + SetupProgress: personal.FormatSetupProgress(steps), + } + id, err := h.pdb.CreateDevice(c.Request.Context(), d) + if err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "创建设备失败: " + err.Error()}) + return + } + // 账户冻结守恒:新建半配置设备 → 该账户若无其他在线设备则冻结 + // (设备 adb 可达后由 DeviceWatcher 自动解冻) + if !h.accountHasConnectedDevice(c.Request.Context(), acct.ID) { + if a := h.pool.GetByID(acct.ID); a != nil && a.Status == account.StatusNormal { + h.pool.FreezeByOffline(acct.ID) + logger.Info("admin", "账户已冻结(wizard 新建设备未完成 setup)", "account_id", acct.ID, "name", a.Name) + } + } + logger.Info("admin", "向导设备已绑定", "device_id", deviceID, "name", req.Name, "serial", req.Serial, "account", acct.ID) + c.JSON(http.StatusOK, gin.H{"data": gin.H{ + "ok": true, "id": id, "device_id": deviceID, + "setup_progress": steps, + }}) +} + +// handleWizardWifiBackup 自动配置 WiFi 备用通道(引导向导第 4 步前)。 +// POST /api/admin/personal/devices/:id/wizard-wifi +// +// 全自动,无需用户填写任何 WiFi 信息: +// 1. 设备必须 USB 直连(AdbHost 为纯 serial)——WiFi 备用通道的开启依赖 USB 先授权 +// 2. adb tcpip 5555:开启设备无线 adb(进入 TCP/IP 监听模式) +// 3. 自动获取设备 WiFi 局域网 IP(wlan0/eth0/getprop 多路尝试) +// 4. 组合 :5555 写入 adb_wifi_host/adb_wifi_port(设备 USB 断开后自动切换保活) +// +// 幂等:备用通道已配置且设备已在 TCP 监听 → 直接返回现有配置不重复执行。 +// 失败不阻塞主流程(返回 ok=false + warning),USB 直连本身仍可用。 +func (h *PersonalHandler) handleWizardWifiBackup(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + if id <= 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"}) + return + } + if h.adbMgr == nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "adb 管理器未配置"}) + return + } + dev, err := h.pdb.GetDeviceByID(c.Request.Context(), id) + if err != nil || dev == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "设备不存在"}) + return + } + // 仅 USB 直连设备可自动配置(依赖 USB 通道的 adb 权限) + if dev.AdbHost == "" || containsColon(dev.AdbHost) { + c.JSON(http.StatusBadRequest, gin.H{"error": "仅 USB 直连设备可自动配置 WiFi 备用通道"}) + return + } + ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second) + defer cancel() + + serial := dev.AdbHost + // 幂等:已有备用通道且设备在线(TCP 监听已开)→ 直接返回 + if dev.AdbWifiHost != "" && dev.AdbWifiPort > 0 { + wifiSerial := fmt.Sprintf("%s:%d", dev.AdbWifiHost, dev.AdbWifiPort) + if h.adbMgr.Online(ctx, wifiSerial) { + logger.Info("admin", "WiFi 备用通道已生效,跳过配置", "device", dev.DeviceID, "wifi", wifiSerial) + c.JSON(http.StatusOK, gin.H{"data": gin.H{"ok": true, "wifi_host": dev.AdbWifiHost, "wifi_port": dev.AdbWifiPort}}) + return + } + } + // 1. 开启无线 adb + if err := h.adbMgr.TCPIP(ctx, serial, 5555); err != nil { + logger.Warn("admin", "开启无线 adb 失败", "device", dev.DeviceID, "err", err) + c.JSON(http.StatusOK, gin.H{"data": gin.H{"ok": false, "warning": "开启无线 adb 失败:" + err.Error()}}) + return + } + // adb tcpip 会重启设备端 adbd(切到 TCP 监听模式),USB adb 通道短暂中断。 + // 立即查 IP 会失败(adbd 未就绪),等待 2s 让其恢复后再获取 IP。 + time.Sleep(2 * time.Second) + // 2. 获取 WiFi IP(重试 2 次,覆盖 adbd 重启窗口) + var ip string + for attempt := 0; attempt < 3; attempt++ { + ip = h.adbMgr.WifiIP(ctx, serial) + if ip != "" { + break + } + if attempt < 2 { + time.Sleep(time.Second) + } + } + if ip == "" { + logger.Warn("admin", "获取设备 WiFi IP 失败(设备可能未连 WiFi)", "device", dev.DeviceID) + c.JSON(http.StatusOK, gin.H{"data": gin.H{"ok": false, "warning": "未获取到设备 WiFi IP(请确认手机已连接 WiFi)"}}) + return + } + // 3. 写入备用通道 + wifiPort := 5555 + if err := h.pdb.SetDeviceAdb(c.Request.Context(), id, dev.AdbHost, dev.AdbPort, ip, wifiPort); err != nil { + logger.Warn("admin", "写入 WiFi 备用通道失败", "err", err, "id", id) + c.JSON(http.StatusInternalServerError, gin.H{"error": "写入 WiFi 备用通道失败: " + err.Error()}) + return + } + logger.Info("admin", "WiFi 备用通道已自动配置", "device", dev.DeviceID, "serial", serial, "wifi", fmt.Sprintf("%s:%d", ip, wifiPort)) + c.JSON(http.StatusOK, gin.H{"data": gin.H{"ok": true, "wifi_host": ip, "wifi_port": wifiPort}}) +} + +// handleWizardVerify 引导最后一步:验证 adb 连通性 + 返回保活引导提示。 +// POST /api/admin/personal/devices/:id/wizard-verify +// +// 2026-08-30 重构:监听端 APP 弃用,向导不再下发配置/拉起 App; +// 设备上线由 DeviceWatcher 按 adb 可达性驱动。本步骤只做 adb 连通验证, +// 并返回 MIUI 省电策略"无限制"的保活引导(墓碑冻结无 adb 入口,需 UI 设置)。 +func (h *PersonalHandler) handleWizardVerify(c *gin.Context) { + id, _ := strconv.ParseInt(c.Param("id"), 10, 64) + if id <= 0 { + c.JSON(http.StatusBadRequest, gin.H{"error": "参数错误"}) + return + } + if h.adbMgr == nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "adb 管理器未配置"}) + return + } + dev, err := h.pdb.GetDeviceByID(c.Request.Context(), id) + if err != nil || dev == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "设备不存在"}) + return + } + serial, ok := h.pickSerialForWizard(c.Request.Context(), dev) + if !ok { + c.JSON(http.StatusBadGateway, gin.H{"error": "设备离线:USB 未连接或未授权(请确认手机已开 USB 调试并允许)"}) + return + } + resp := gin.H{"ok": true, "serial": serial} + // 保活引导提示(MIUI 墓碑冻结/杀后台需 UI 层"省电策略→无限制"才能豁免, + // 无 root 无 adb 入口——实测 2026-08-30 红米k60/MIUI14)。 + // 微信/支付宝需无限制,否则后台被系统冻结会掉线。 + resp["keepalive_hint"] = fmt.Sprintf( + "请在手机上完成保活设置(共 %d 个应用):设置→应用设置→应用管理→%s→省电策略→选择「无限制」。"+ + "不设置的话系统可能冻结后台进程导致掉线。", keepaliveHintAppsCount(dev.Provider), keepaliveHintApps(dev.Provider)) + c.JSON(http.StatusOK, gin.H{"data": resp}) +} + +// keepaliveHintApps 保活引导的应用显示名(支付通道 APP,按 provider 区分)。 +func keepaliveHintApps(provider string) string { + return "「" + keepaliveChannelApp(provider) + "」" +} + +// keepaliveHintAppsCount 保活引导的应用数量(当前恒为 1:支付通道 APP)。 +func keepaliveHintAppsCount(provider string) int { + return 1 +} + +// keepaliveChannelApp 支付通道 APP 显示名(与包名映射,用于引导文案)。 +func keepaliveChannelApp(provider string) string { + switch provider { + case "wechat": + return "微信" + case "alipay": + return "支付宝" + default: + return "" + } +} + +// pickSerialForWizard 取设备可用的 adb serial 并校验在线(向导专用)。 +// AdbHost 可为 USB 纯 serial 或 WiFi host:port;没有或不可用时回退 WiFi 备用通道。 +func (h *PersonalHandler) pickSerialForWizard(ctx context.Context, dev *personal.DeviceEncrypted) (string, bool) { + candidates := make([]string, 0, 2) + if dev.AdbHost != "" { + candidates = append(candidates, dev.AdbHost) + } + if dev.AdbWifiHost != "" && dev.AdbWifiPort > 0 { + candidates = append(candidates, fmt.Sprintf("%s:%d", dev.AdbWifiHost, dev.AdbWifiPort)) + } + for _, s := range candidates { + if h.adbMgr.Online(ctx, s) { + return s, true + } + } + return "", false +} + +// handleWifiReconnect 设备断网/断 USB 后重连 WiFi 备用通道(adb connect 重建链路)。 +// 心跳走公网隧道可能已恢复(status=online),但 scrcpy 画面流的 adb 连接会在断网时失效, +// 需要重新 adb connect 才能恢复实时画面。成功返回可用的 serial 供前端重连, +// USB 主通道若在线则优先返回 USB serial,否则尝试重连 WiFi。 +func (h *PersonalHandler) handleWifiReconnect(c *gin.Context) { + id, err := strconv.ParseInt(c.Param("id"), 10, 64) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "设备 id 无效"}) + return + } + ctx := c.Request.Context() + dev, err := h.pdb.GetDeviceByID(ctx, id) + if err != nil || dev == nil { + c.JSON(http.StatusNotFound, gin.H{"error": "设备不存在"}) + return + } + + if dev.AdbHost != "" && h.adbMgr.Online(ctx, dev.AdbHost) { + c.JSON(http.StatusOK, gin.H{"data": gin.H{"serial": dev.AdbHost, "channel": "usb"}}) + return + } + + if dev.AdbWifiHost != "" && dev.AdbWifiPort > 0 { + serial := fmt.Sprintf("%s:%d", dev.AdbWifiHost, dev.AdbWifiPort) + if err := h.adbMgr.Connect(ctx, serial); err == nil && h.adbMgr.Online(ctx, serial) { + c.JSON(http.StatusOK, gin.H{"data": gin.H{"serial": serial, "channel": "wifi"}}) + return + } + } + + c.JSON(http.StatusBadGateway, gin.H{"error": "设备离线:USB 未连接且 WiFi 重连失败,请确认设备在线并已开 USB 调试"}) +} diff --git a/internal/admin/wizard_test.go b/internal/admin/wizard_test.go new file mode 100644 index 0000000..0cbbbc9 --- /dev/null +++ b/internal/admin/wizard_test.go @@ -0,0 +1,39 @@ +package admin + +import ( + "strings" + "testing" +) + +// TestKeepaliveHintApps 保活引导文案的支付通道应用映射。 +// 2026-08-30:监听端 APP 弃用,保活引导只包含支付通道 APP。 +func TestKeepaliveHintApps(t *testing.T) { + cases := []struct { + provider string + wantApp string // 期望文案包含的支付 APP 显示名 + wantChannel string // 期望的通道显示名(空=无) + count int + }{ + {"wechat", "「微信」", "微信", 1}, + {"alipay", "「支付宝」", "支付宝", 1}, + {"unknown", "", "", 1}, + {"", "", "", 1}, + } + for _, tc := range cases { + t.Run(tc.provider, func(t *testing.T) { + apps := keepaliveHintApps(tc.provider) + if tc.wantApp != "" && !strings.Contains(apps, tc.wantApp) { + t.Fatalf("keepaliveHintApps(%q)=%q 应包含 %q", tc.provider, apps, tc.wantApp) + } + if strings.Contains(apps, "监听端") { + t.Fatalf("keepaliveHintApps(%q)=%q 不应再包含监听端 APP", tc.provider, apps) + } + if got := keepaliveHintAppsCount(tc.provider); got != tc.count { + t.Fatalf("keepaliveHintAppsCount(%q)=%d, want %d", tc.provider, got, tc.count) + } + if got := keepaliveChannelApp(tc.provider); got != tc.wantChannel { + t.Fatalf("keepaliveChannelApp(%q)=%q, want %q", tc.provider, got, tc.wantChannel) + } + }) + } +} diff --git a/internal/alert/alert.go b/internal/alert/alert.go new file mode 100755 index 0000000..74119d1 --- /dev/null +++ b/internal/alert/alert.go @@ -0,0 +1,179 @@ +// Package alert 告警系统(RESILIENCE.md P1-3) +// +// 支持 Bark(iOS 推送)与通用 Webhook(POST JSON),异步发送 + 防抖: +// - 发送失败静默重试 2 次(不阻塞业务主链路) +// - 同类型告警 1 分钟窗口内去重(防止刷屏) +// - 配置:ALERT_BARK_URL / ALERT_WEBHOOK_URL(env),留空则该通道禁用 +package alert + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "net/http" + "runtime/debug" + "sync" + "time" + + "paygateway/internal/logger" +) + +// Client 告警客户端 +type Client struct { + barkURL string // Bark 推送 URL(形如 https://api.day.app/) + webhookURL string // 通用 Webhook URL(POST JSON {title, body, level, fields}) + client *http.Client + + mu sync.Mutex + last map[string]time.Time // 防抖:title → 上次发送时间 +} + +// Options 告警配置 +type Options struct { + BarkURL string + WebhookURL string +} + +// New 创建告警客户端(两者均空时返回 nil,调用方跳过) +func New(opts Options) *Client { + if opts.BarkURL == "" && opts.WebhookURL == "" { + return nil + } + return &Client{ + barkURL: opts.BarkURL, + webhookURL: opts.WebhookURL, + client: &http.Client{Timeout: 8 * time.Second}, + last: make(map[string]time.Time), + } +} + +// Enabled 是否有任何告警通道 +func (a *Client) Enabled() bool { return a != nil } + +// dedup 防抖:同 title 窗口内去重 +func (a *Client) dedup(title string) bool { + a.mu.Lock() + defer a.mu.Unlock() + now := time.Now() + if t, ok := a.last[title]; ok && now.Sub(t) < time.Minute { + return false + } + a.last[title] = now + return true +} + +// Send 发送告警(异步,不阻塞) +// level: info / warn / error;title: 告警类型(防抖 key);body: 详情 +func (a *Client) Send(level, title, body string) { + if a == nil { + return + } + if !a.dedup(title) { + return // 1 分钟窗口内已发过同类告警 + } + title = fmt.Sprintf("[%s] %s", level, title) + logger.Warn("alert", "发送告警", "level", level, "title", title, "body", body) + + go func() { + defer func() { + if r := recover(); r != nil { + logger.Error("alert", "告警发送协程 panic(已恢复)", "title", title, + "panic", r, "stack", string(debug.Stack())) + } + }() + // 重试 3 次(1s/3s/9s 退避),全部失败仅记日志 + var lastErr error + for i := 0; i < 3; i++ { + if err := a.sendOnce(level, title, body); err == nil { + return + } else { + lastErr = err + } + time.Sleep(time.Duration(1<= 400 { + return fmt.Errorf("bark HTTP %d", resp.StatusCode) + } + return nil +} + +// sendWebhook 通用 Webhook(POST JSON) +func (a *Client) sendWebhook(level, title, body string) error { + payload, _ := json.Marshal(map[string]string{ + "level": level, + "title": title, + "body": body, + "time": time.Now().Format(time.RFC3339), + }) + req, err := http.NewRequest(http.MethodPost, a.webhookURL, bytes.NewReader(payload)) + if err != nil { + return err + } + req.Header.Set("Content-Type", "application/json") + resp, err := a.client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode >= 400 { + return fmt.Errorf("webhook HTTP %d", resp.StatusCode) + } + return nil +} + +// Close 关闭空闲连接 +func (a *Client) Close() { + if a == nil { + return + } + a.client.CloseIdleConnections() +} + +var _ = context.Background diff --git a/internal/channel/alipay/alipay.go b/internal/channel/alipay/alipay.go new file mode 100755 index 0000000..a50d1c1 --- /dev/null +++ b/internal/channel/alipay/alipay.go @@ -0,0 +1,447 @@ +// Package alipay 支付宝支付通道(基于 gopay 库) +// +// 账户池集成: +// - 持有 AccountPool,下单选账户、回调反查 app_id +// - 每个账户对应一个 gopay.Client,按需创建并缓存 +// - 下单成功/失败后上报结果给 pool(熔断统计) +package alipay + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "sync" + "time" + + "github.com/go-pay/gopay" + "github.com/go-pay/gopay/alipay" + "paygateway/internal/account" + "paygateway/internal/channel" + "paygateway/internal/logger" + "paygateway/internal/model" +) + +// Config 支付宝通道配置(仅回调地址 + 调试开关;账户密钥一律来自账户池) +type Config struct { + NotifyURL string // 异步回调地址 + Debug bool // 调试日志 +} + +// Channel 支付宝通道实现 +type Channel struct { + cfg Config + pool *account.AccountPool // 账户池(nil = 传统单账户模式) + clientCache map[int64]*alipay.Client // 账户ID → gopay client 缓存 + clientMu sync.RWMutex + modesMu sync.RWMutex // 保护 enabledModes(管理后台切换与下单并发) + enabledModes map[string]bool // 支付模式启停状态,默认全开 +} + +// defaultEnabledModes 默认开启所有模式 +func defaultEnabledModes() map[string]bool { + return map[string]bool{ + "page": true, + "wap": true, + "qrcode": true, + } +} + +// New 创建支付宝通道(账户池模式:所有账户数据从 pool 读取) +func New(cfg Config, pool *account.AccountPool) (*Channel, error) { + ch := &Channel{ + cfg: cfg, + pool: pool, + clientCache: make(map[int64]*alipay.Client), + enabledModes: defaultEnabledModes(), + } + return ch, nil +} + +// newClient 创建 gopay 支付宝客户端 +func (c *Channel) newClient(appID, privateKey string, isProd bool, notifyURL string, debug bool) (*alipay.Client, error) { + client, err := alipay.NewClient(appID, privateKey, isProd) + if err != nil { + return nil, fmt.Errorf("支付宝客户端初始化失败: %w", err) + } + client.SetCharset("utf-8"). + SetSignType(alipay.RSA2). + SetNotifyUrl(notifyURL) + client.GetHttpClient().SetTimeout(120 * time.Second) + if debug { + client.DebugSwitch = gopay.DebugOn + } + return client, nil +} + +// getOrCreateClient 获取或创建账户对应的 gopay client +func (c *Channel) getOrCreateClient(acct *account.Account) (*alipay.Client, error) { + // 先查缓存 + c.clientMu.RLock() + if cli, ok := c.clientCache[acct.ID]; ok { + c.clientMu.RUnlock() + return cli, nil + } + c.clientMu.RUnlock() + + // 创建新 client + c.clientMu.Lock() + defer c.clientMu.Unlock() + + // 双重检查 + if cli, ok := c.clientCache[acct.ID]; ok { + return cli, nil + } + + notifyURL := c.cfg.NotifyURL + debug := c.cfg.Debug + client, err := c.newClient(acct.AppID, acct.PrivateKey, acct.IsProd, notifyURL, debug) + if err != nil { + return nil, err + } + c.clientCache[acct.ID] = client + logger.Info("alipay", "账户客户端已创建", "account_id", acct.ID, "name", acct.Name) + return client, nil +} + +// SetNotifyURL 动态更新回调地址(隧道重启换域名时调用) +func (c *Channel) SetNotifyURL(url string) { + c.cfg.NotifyURL = url + // 更新所有缓存的 client + c.clientMu.Lock() + defer c.clientMu.Unlock() + for id, client := range c.clientCache { + client.SetNotifyUrl(url) + _ = id + } + logger.Info("alipay", "回调地址已更新", "notify_url", url) +} + +// RemoveClient 移除账户对应的 gopay client 缓存。 +// 账户更新(app_id / 私钥变更)后必须调用,否则缓存的旧 client 会继续使用旧参数下单。 +func (c *Channel) RemoveClient(accountID int64) { + c.clientMu.Lock() + if _, ok := c.clientCache[accountID]; ok { + delete(c.clientCache, accountID) + logger.Info("alipay", "账户客户端缓存已清除", "account_id", accountID) + } + c.clientMu.Unlock() +} + +// Name 通道标识 +func (c *Channel) Name() string { return "merchant.alipay" } + +// Type 通道大类:商家通道 +func (c *Channel) Type() channel.ChannelType { return channel.TypeMerchant } + +// Provider 支付品牌:支付宝 +func (c *Channel) Provider() channel.Provider { return channel.ProviderAlipay } + +// ConfirmAbility 确认能力:可主动查单 +func (c *Channel) ConfirmAbility() channel.ConfirmAbility { return channel.ConfirmByQuery } + +// NotifyURL 返回当前回调地址(管理后台展示) +func (c *Channel) NotifyURL() string { return c.cfg.NotifyURL } + +// MchID 支付宝无商户号概念 +func (c *Channel) MchID() string { return "" } + +// Kind 通道类型:回调驱动 +func (c *Channel) Kind() channel.ChannelKind { return channel.KindCallback } + +// Modes 所有支持的支付模式 +func (c *Channel) Modes() []channel.PayMode { + return []channel.PayMode{channel.ModePage, channel.ModeWap, channel.ModeQRCode} +} + +// SetModeEnabled 开启/关闭某个支付模式 +func (c *Channel) SetModeEnabled(mode string, enabled bool) { + c.modesMu.Lock() + c.enabledModes[mode] = enabled + c.modesMu.Unlock() + logger.Info("alipay", "支付模式已切换", "mode", mode, "enabled", enabled) +} + +// IsModeEnabled 检查某个支付模式是否开启 +func (c *Channel) IsModeEnabled(mode string) bool { + c.modesMu.RLock() + defer c.modesMu.RUnlock() + return c.enabledModes[mode] +} + +// EnabledModes 返回当前启用的模式列表 +func (c *Channel) EnabledModes() map[string]bool { + c.modesMu.RLock() + defer c.modesMu.RUnlock() + result := make(map[string]bool) + for k, v := range c.enabledModes { + result[k] = v + } + return result +} + +// Listener 回调型无监听器 +func (c *Channel) Listener() channel.EventListener { return nil } + +// selectAccount 从账户池选一个可用账户 +// 返回 nil 表示无可用账户或未启用账户池 +func (c *Channel) selectAccount(group string) *account.Account { + if c.pool == nil { + return nil + } + return c.pool.Select("alipay", group) +} + +// CreatePayment 下单:按 PayMode 创建对应支付宝订单 +func (c *Channel) CreatePayment(ctx context.Context, req *channel.PayRequest) (*channel.PayCredential, error) { + payMode := c.resolveMode(req) + + // 检查支付模式是否已开启(resolveMode 已降级,走到这里说明全部模式都已关闭) + if !c.IsModeEnabled(string(payMode)) { + enabled := make([]string, 0) + for m, on := range c.enabledModes { + if on { + enabled = append(enabled, m) + } + } + return nil, fmt.Errorf("支付模式 %q 未开启(当前已开启: %v)", payMode, enabled) + } + + // 从账户池选账户 + var acct *account.Account + if c.pool != nil { + acct = c.pool.Select("alipay", "default") + if acct == nil { + return nil, fmt.Errorf("支付宝暂无可用账户(所有账户已达日限额或冷却中)") + } + } + + // 获取 gopay client(账户池模式:必选账户) + var client *alipay.Client + if acct == nil { + return nil, fmt.Errorf("支付宝暂无可用账户") + } + client, err := c.getOrCreateClient(acct) + if err != nil { + return nil, fmt.Errorf("获取支付宝客户端失败: %w", err) + } + + bm := make(gopay.BodyMap) + bm.Set("subject", req.Subject). + Set("out_trade_no", req.OutTradeNo). + Set("total_amount", yuan(req.AmountMinor)). + Set("timeout_express", "30m") + + // 支付宝 API 调用超时 120 秒 + apiCtx, cancel := context.WithTimeout(context.Background(), 120*time.Second) + defer cancel() + + var cred *channel.PayCredential + var payErr error + + switch payMode { + case channel.ModePage: + logger.Info("alipay", "模式: 电脑网站支付 (page)") + bm.Set("product_code", "FAST_INSTANT_TRADE_PAY") + payURL, err := client.TradePagePay(apiCtx, bm) + if err != nil { + payErr = fmt.Errorf("电脑网站支付下单失败: %w", err) + } else { + cred = &channel.PayCredential{PayURL: payURL, Mode: channel.ModePage, ExpireAt: time.Now().Add(30 * time.Minute)} + } + + case channel.ModeWap: + logger.Info("alipay", "模式: 手机网站支付 (wap)") + bm.Set("product_code", "QUICK_WAP_WAY") + payURL, err := client.TradeWapPay(apiCtx, bm) + if err != nil { + payErr = fmt.Errorf("手机网站支付下单失败: %w", err) + } else { + cred = &channel.PayCredential{PayURL: payURL, Mode: channel.ModeWap, ExpireAt: time.Now().Add(30 * time.Minute)} + } + + default: // ModeQRCode 扫码支付 + logger.Info("alipay", "模式: 扫码支付 (当面付)") + aliRsp, err := client.TradePrecreate(apiCtx, bm) + if err != nil { + payErr = fmt.Errorf("当面付下单失败: %w", err) + } else { + qrCode := aliRsp.Response.QrCode + cred = &channel.PayCredential{QRCode: qrCode, Mode: channel.ModeQRCode, ExpireAt: time.Now().Add(2 * time.Hour)} + // 生成备用 WAP 链接(仅当 wap 模式开启时;关闭 wap 则不再生成备用跳转) + if c.IsModeEnabled(string(channel.ModeWap)) { + if wapURL, werr := c.wapBackup(apiCtx, client, bm); werr == nil { + cred.PayURL = wapURL + } + } + } + } + + // 上报结果给账户池 + if acct != nil && c.pool != nil { + c.pool.ReportResult(acct.ID, payErr == nil) + if payErr == nil && cred != nil { + c.pool.AddDailyUsed(acct.ID, req.AmountMinor) + } + } + + if payErr != nil { + return nil, payErr + } + // 记录使用的账户ID + if cred != nil && acct != nil { + cred.AccountID = acct.ID + } + return cred, nil +} + +// resolveMode 解析支付模式(用户指定 > 设备识别 > 智能降级到已开启模式) +// 用户显式指定 pay_type 但该模式已关闭时,降级到设备匹配的已开启模式; +// 设备识别出的模式已关闭时,按降级链选择第一个已开启的模式。 +// 全部模式关闭时返回识别出的默认模式(由 CreatePayment 报错提示)。 +func (c *Channel) resolveMode(req *channel.PayRequest) channel.PayMode { + // 用户显式指定 + if req.PayMode != "" { + if c.IsModeEnabled(string(req.PayMode)) { + return channel.PayMode(req.PayMode) + } + logger.Warn("alipay", "用户指定支付模式未开启,按设备降级", "requested", req.PayMode, "device", req.Device) + } + // 设备识别 + 降级链 + return c.fallbackMode(req.Device) +} + +// fallbackMode 按设备选择第一个已开启的模式(降级链) +// pc 偏好:page → qrcode → wap(电脑端扫码兜底) +// mobile 偏好:wap → qrcode → page(手机端扫码兜底) +// 未知设备默认:qrcode → page → wap +func (c *Channel) fallbackMode(device string) channel.PayMode { + var candidates []channel.PayMode + switch device { + case "page", "pc": + candidates = []channel.PayMode{channel.ModePage, channel.ModeQRCode, channel.ModeWap} + case "wap", "mobile": + candidates = []channel.PayMode{channel.ModeWap, channel.ModeQRCode, channel.ModePage} + default: + candidates = []channel.PayMode{channel.ModeQRCode, channel.ModePage, channel.ModeWap} + } + for _, m := range candidates { + if c.IsModeEnabled(string(m)) { + return m + } + } + // 全部关闭:返回设备首选,由 CreatePayment 给出明确错误 + return candidates[0] +} + +// wapBackup 生成备用 WAP 链接 +func (c *Channel) wapBackup(ctx context.Context, client *alipay.Client, bm gopay.BodyMap) (string, error) { + wapBM := make(gopay.BodyMap) + for k, v := range bm { + wapBM[k] = v + } + wapBM.Set("product_code", "QUICK_WAP_WAY") + wapCtx, cancel := context.WithTimeout(ctx, 20*time.Second) + defer cancel() + return client.TradeWapPay(wapCtx, wapBM) +} + +// VerifyNotify 验签支付宝异步回调 +// 账户池模式下:通过 app_id 反查账户,用该账户的公钥验签 +func (c *Channel) VerifyNotify(ctx context.Context, req *http.Request) (*channel.NotifyResult, error) { + notifyReq, err := alipay.ParseNotifyToBodyMap(req) + if err != nil { + return nil, fmt.Errorf("解析支付宝回调失败: %w", err) + } + + appID := notifyReq.GetString("app_id") + + // 通过 app_id 反查账户池中的账户 + if c.pool == nil { + return nil, fmt.Errorf("账户池未初始化,无法验签") + } + acct := c.pool.LookupByAppID(appID) + if acct == nil { + logger.Warn("alipay", "回调 app_id 未匹配到账户", "app_id", appID) + return nil, fmt.Errorf("未找到 app_id=%s 对应的账户", appID) + } + // 用该账户的公钥验签 + ok, err := alipay.VerifySign(acct.PublicKey, notifyReq) + if err != nil || !ok { + return nil, fmt.Errorf("支付宝验签失败(账户: %s): %v", acct.Name, err) + } + logger.Info("alipay", "回调验签通过", "account", acct.Name, "app_id", appID) + + tradeStatus := notifyReq.GetString("trade_status") + status := model.StatusWaitPay + switch tradeStatus { + case "TRADE_SUCCESS", "TRADE_FINISHED": + status = model.StatusSuccess + case "TRADE_CLOSED": + status = model.StatusClosed + } + amountMinor, _ := model.ParseMoneyToMinor(notifyReq.GetString("total_amount")) + + acctID := int64(0) + if acct != nil { + acctID = acct.ID + } + + return &channel.NotifyResult{ + Valid: true, + OutTradeNo: notifyReq.GetString("out_trade_no"), + ChannelTxID: notifyReq.GetString("trade_no"), + Status: status, + AmountMinor: amountMinor, + Payer: notifyReq.GetString("buyer_id"), + AppID: appID, + AccountID: acctID, + RawPayload: rawJSON(notifyReq), + }, nil +} + +// QueryOrder 主动查询(对账用) +func (c *Channel) QueryOrder(ctx context.Context, outTradeNo string) (*channel.OrderStatus, error) { + // 遍历账户池中所有支付宝账户查单 + if c.pool == nil { + return nil, fmt.Errorf("账户池未初始化") + } + accts := c.pool.ListByChannel("alipay") + for _, acct := range accts { + client, err := c.getOrCreateClient(acct) + if err != nil { + continue + } + bm := make(gopay.BodyMap) + bm.Set("out_trade_no", outTradeNo) + rsp, err := client.TradeQuery(ctx, bm) + if err != nil { + continue + } + st := &channel.OrderStatus{ChannelTxID: rsp.Response.TradeNo} + switch rsp.Response.TradeStatus { + case "TRADE_SUCCESS", "TRADE_FINISHED": + st.Status = model.StatusSuccess + case "TRADE_CLOSED": + st.Status = model.StatusClosed + default: + st.Status = model.StatusWaitPay + } + // 解析实际支付金额(元字符串 → 分,字符串十进制解析,避免 float 精度问题) + if amt, err := model.ParseMoneyToMinor(rsp.Response.TotalAmount); err == nil { + st.AmountMinor = amt + } + return st, nil + } + return nil, fmt.Errorf("所有支付宝账户查询均失败") +} + +// yuan 最小单位(分) → 元字符串(支付宝协议) +func yuan(minor int64) string { + return fmt.Sprintf("%d.%02d", minor/100, minor%100) +} + +func rawJSON(v any) string { + b, _ := json.Marshal(v) + return string(b) +} diff --git a/internal/channel/alipay/alipay_test.go b/internal/channel/alipay/alipay_test.go new file mode 100755 index 0000000..2d180ac --- /dev/null +++ b/internal/channel/alipay/alipay_test.go @@ -0,0 +1,161 @@ +package alipay + +import ( + "testing" + + "github.com/go-pay/gopay/alipay" + "paygateway/internal/account" + "paygateway/internal/channel" +) + +// newTestChannel 创建测试通道(默认全开) +func newTestChannel() *Channel { + ch := &Channel{ + pool: &account.AccountPool{}, + clientCache: make(map[int64]*alipay.Client), + enabledModes: defaultEnabledModes(), + } + return ch +} + +func TestResolveMode_AllEnabled(t *testing.T) { + ch := newTestChannel() + + cases := []struct { + name string + payMode string + device string + expected channel.PayMode + }{ + {"显式指定 page", "page", "pc", channel.ModePage}, + {"显式指定 wap", "wap", "mobile", channel.ModeWap}, + {"显式指定 qrcode", "qrcode", "pc", channel.ModeQRCode}, + {"pc 设备识别", "", "pc", channel.ModePage}, + {"page 设备识别", "", "page", channel.ModePage}, + {"mobile 设备识别", "", "mobile", channel.ModeWap}, + {"wap 设备识别", "", "wap", channel.ModeWap}, + {"未知设备默认扫码", "", "unknown", channel.ModeQRCode}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + req := &channel.PayRequest{PayMode: channel.PayMode(tc.payMode), Device: tc.device} + got := ch.resolveMode(req) + if got != tc.expected { + t.Fatalf("resolveMode(%q, %q) = %q, want %q", tc.payMode, tc.device, got, tc.expected) + } + }) + } +} + +func TestResolveMode_OnlyQRCode(t *testing.T) { + ch := newTestChannel() + ch.SetModeEnabled("page", false) + ch.SetModeEnabled("wap", false) + // 只开 qrcode + + cases := []struct { + name string + payMode string + device string + expected channel.PayMode + }{ + {"pc 降级到 qrcode", "", "pc", channel.ModeQRCode}, + {"mobile 降级到 qrcode", "", "mobile", channel.ModeQRCode}, + {"未知设备降级到 qrcode", "", "unknown", channel.ModeQRCode}, + {"显式指定已关闭的 page 降级", "page", "pc", channel.ModeQRCode}, + {"显式指定已关闭的 wap 降级", "wap", "mobile", channel.ModeQRCode}, + {"显式指定 qrcode 保持", "qrcode", "pc", channel.ModeQRCode}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + req := &channel.PayRequest{PayMode: channel.PayMode(tc.payMode), Device: tc.device} + got := ch.resolveMode(req) + if got != tc.expected { + t.Fatalf("resolveMode(%q, %q) = %q, want %q", tc.payMode, tc.device, got, tc.expected) + } + }) + } +} + +func TestResolveMode_OnlyWap(t *testing.T) { + ch := newTestChannel() + ch.SetModeEnabled("page", false) + ch.SetModeEnabled("qrcode", false) + // 只开 wap + + cases := []struct { + name string + payMode string + device string + expected channel.PayMode + }{ + {"pc 降级到 wap", "", "pc", channel.ModeWap}, + {"mobile 保持 wap", "", "mobile", channel.ModeWap}, + {"显式指定已关闭的 page 降级", "page", "pc", channel.ModeWap}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + req := &channel.PayRequest{PayMode: channel.PayMode(tc.payMode), Device: tc.device} + got := ch.resolveMode(req) + if got != tc.expected { + t.Fatalf("resolveMode(%q, %q) = %q, want %q", tc.payMode, tc.device, got, tc.expected) + } + }) + } +} + +func TestResolveMode_OnlyPage(t *testing.T) { + ch := newTestChannel() + ch.SetModeEnabled("wap", false) + ch.SetModeEnabled("qrcode", false) + // 只开 page + + cases := []struct { + name string + payMode string + device string + expected channel.PayMode + }{ + {"pc 保持 page", "", "pc", channel.ModePage}, + {"mobile 降级到 page", "", "mobile", channel.ModePage}, + {"显式指定已关闭的 wap 降级", "wap", "mobile", channel.ModePage}, + } + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + req := &channel.PayRequest{PayMode: channel.PayMode(tc.payMode), Device: tc.device} + got := ch.resolveMode(req) + if got != tc.expected { + t.Fatalf("resolveMode(%q, %q) = %q, want %q", tc.payMode, tc.device, got, tc.expected) + } + }) + } +} + +func TestResolveMode_AllDisabled(t *testing.T) { + ch := newTestChannel() + ch.SetModeEnabled("page", false) + ch.SetModeEnabled("wap", false) + ch.SetModeEnabled("qrcode", false) + + // 全部关闭:fallback 返回设备首选(pc→page),由 CreatePayment 报错 + req := &channel.PayRequest{PayMode: "", Device: "pc"} + got := ch.resolveMode(req) + if got != channel.ModePage { + t.Fatalf("resolveMode(pc, all disabled) = %q, want %q", got, channel.ModePage) + } + // 全部关闭时 CreatePayment 必须报错 + if _, err := ch.CreatePayment(nil, &channel.PayRequest{Device: "pc", Subject: "t", OutTradeNo: "x", AmountMinor: 100, Currency: "CNY"}); err == nil { + t.Fatal("全模式关闭时 CreatePayment 应返回错误") + } +} + +func TestEnabledModesSnapshot(t *testing.T) { + ch := newTestChannel() + ch.SetModeEnabled("wap", false) + snapshot := ch.EnabledModes() + // 修改快照不应影响内部状态 + snapshot["wap"] = true + if ch.IsModeEnabled("wap") { + t.Fatal("修改快照不应影响内部状态") + } +} diff --git a/internal/channel/interface.go b/internal/channel/interface.go new file mode 100755 index 0000000..e64b64c --- /dev/null +++ b/internal/channel/interface.go @@ -0,0 +1,157 @@ +// Package channel 支付通道抽象:统一接口 + 注册表,通道可插拔 +package channel + +import ( + "context" + "net/http" + "time" +) + +// ChannelType 通道大类 +type ChannelType string + +const ( + // TypeMerchant 商家通道:官方 API,gopay 驱动,可主动查单 + TypeMerchant ChannelType = "merchant" + // TypePersonal 个人通道:免签收款码,监听驱动,无法主动查单 + TypePersonal ChannelType = "personal" +) + +// Provider 支付品牌(两类通道共用) +type Provider string + +const ( + ProviderAlipay Provider = "alipay" + ProviderWechat Provider = "wechat" + ProviderQQ Provider = "qq" // 预留 + ProviderUSDT Provider = "usdt" // 预留 +) + +// ChannelKind 通道类型(支付结果获取方式) +type ChannelKind int + +const ( + // KindCallback 回调驱动:支付宝/微信等(用户付款 → 通道异步回调) + KindCallback ChannelKind = iota + // KindListener 监听驱动:链上支付/免签监听(用户转账 → 监听确认) + KindListener +) + +// ConfirmAbility 确认能力:决定 ConfirmScheduler 的兜底策略 +type ConfirmAbility int + +const ( + // ConfirmByQuery 商家通道:可主动调 API 查单(回调丢失→查单补账) + ConfirmByQuery ConfirmAbility = iota + // ConfirmByListen 个人通道:靠监听端推送,无法主动查 + ConfirmByListen + // ConfirmByManual 最终兜底:监听失联/匹配失败→人工异常池 + ConfirmByManual +) + +// PayMode 支付模式 +type PayMode string + +const ( + ModePage PayMode = "page" // 电脑网站支付(跳转收银台) + ModeWap PayMode = "wap" // 手机网站支付 + ModeQRCode PayMode = "qrcode" // 扫码支付(当面付) + ModeAddress PayMode = "address" // 链上收款地址(未来 crypto) +) + +// PayRequest 下单请求(通道无关) +type PayRequest struct { + OutTradeNo string // 网关订单号 + GatewayNo string // 网关单号 + Subject string // 商品名称 + AmountMinor int64 // 金额(最小单位) + Currency string // 币种 + NotifyURL string // 通道异步回调地址 + ReturnURL string // 跳转地址 + PayMode PayMode // 用户选择的支付模式 + Device string // pc / mobile(用于默认模式判断) +} + +// PayCredential 支付凭据(下单返回) +type PayCredential struct { + PayURL string // 跳转链接(page/wap) + QRCode string // 二维码内容(qrcode/address) + Address string // 链上收款地址(未来 crypto) + AccountID int64 // 处理这笔订单的账户ID(账户池用) + Mode PayMode // 实际使用的支付模式(含降级后的结果) + ExpireAt time.Time // 过期时间 + + // 个人免签通道附加数据(由通道返回,order.Service 持久化到订单) + NoteCode string // 备注码(用户转账备注) + QrToken string // 套码 token(/mq/{token} 校验) + QRContent string // 真实收款码内容(仅服务端使用,302 跳转目标) + DeviceID string // 承接的监听端设备(匹配后回填) +} + +// NotifyResult 回调/监听事件解析结果 +type NotifyResult struct { + Valid bool // 验签/校验是否通过 + OutTradeNo string // 商户订单号(通道回调携带) + ChannelTxID string // 通道交易号(去重依据) + Status string // 与 model 状态一致:TRADE_SUCCESS / TRADE_CLOSED / FAILED + AmountMinor int64 // 实付金额(必须与订单比对!) + Payer string // 付款方标识(可选) + DeviceID string // 个人通道:承接的监听端设备ID(审计追溯用) + AppID string // 支付宝 app_id(回调反查账户用) + MchID string // 微信商户号(回调反查账户用) + AccountID int64 // 处理这笔回调的账户ID(留痕/审计) + RawPayload string // 原始数据(审计用) +} + +// OrderStatus 主动查询结果 +type OrderStatus struct { + ChannelTxID string + Status string + AmountMinor int64 +} + +// EventListener 事件监听器(链上支付用;回调型通道返回 nil) +type EventListener interface { + Start(ctx context.Context) error + Events() <-chan ChainEvent +} + +// ChainEvent 链上事件 +type ChainEvent struct { + ChannelTxID string + Address string + AmountMinor int64 + Confirmations int64 + RawPayload string +} + +// ClientCacheInvalidator 可清除账户客户端缓存的可选接口。 +// 商家通道(alipay/wechat)持有 gopay client 缓存;账户 app_id/密钥变更后必须清除, +// 否则缓存的旧 client 会继续用旧参数下单(如旧 app_id 导致 invalid-app-id)。 +type ClientCacheInvalidator interface { + RemoveClient(accountID int64) +} + +// PaymentChannel 所有支付通道必须实现的统一接口 +type PaymentChannel interface { + // Name 通道标识:merchant.alipay / personal.wechat 等 + Name() string + // Type 通道大类:merchant / personal + Type() ChannelType + // Kind 通道类型(回调驱动 / 监听驱动) + Kind() ChannelKind + // Modes 支持的支付模式 + Modes() []PayMode + // ConfirmAbility 确认能力(决定 ConfirmScheduler 的兜底策略) + ConfirmAbility() ConfirmAbility + // Provider 支付品牌:alipay / wechat / qq / usdt + Provider() Provider + // CreatePayment 下单:返回支付凭据 + CreatePayment(ctx context.Context, req *PayRequest) (*PayCredential, error) + // VerifyNotify 验签回调(KindCallback 用):解析 + 验签原始请求 + VerifyNotify(ctx context.Context, req *http.Request) (*NotifyResult, error) + // QueryOrder 主动查单(对账 + 补偿兜底) + QueryOrder(ctx context.Context, outTradeNo string) (*OrderStatus, error) + // Listener 事件监听(KindListener 必须实现;KindCallback 返回 nil) + Listener() EventListener +} diff --git a/internal/channel/personal/personal.go b/internal/channel/personal/personal.go new file mode 100755 index 0000000..604d2ec --- /dev/null +++ b/internal/channel/personal/personal.go @@ -0,0 +1,279 @@ +// Package personal 个人免签通道(收款码 + 备注码 + 监听端确认) +// +// 与商家通道(merchant.*)的本质区别: +// - 无法主动查单(ConfirmByManual):用户扫收款码转账 → 监听端推送 → 服务端匹配入账 +// - 支付凭据 = 真实收款码直发(/qr/real 动态图片接口) +// - 每笔订单分配 4 位备注码,用户转账时填写 → 精确匹配 +// - 账户池复用:每个账户 = 一个收款码(QRContent),轮换/冷却/限额复用 +// +// 实现要点(docs/MIANQIAN.md): +// - CreatePayment:选账户 → 分配备注码 → 生成二维码 token → 直发码 URL +// - 收款确认:/personal/push(监听端推送)→ HMAC 验签 → 备注码/金额匹配 → SettlePayment +// - VerifyNotify / QueryOrder 不支持(个人通道无官方 API),分别返回错误 +package personal + +import ( + "context" + "errors" + "fmt" + "net/http" + "strings" + "time" + + "paygateway/internal/account" + "paygateway/internal/channel" + "paygateway/internal/logger" + "paygateway/internal/personal" + "paygateway/internal/store" +) + +// Config 个人通道配置 +type Config struct { + BaseURL string // 网关公网基础地址(管理后台展示,隧道域名动态更新) + OrderTTL time.Duration // 订单有效期(备注码/二维码 token 过期时间) + NoteTTL time.Duration // 备注码占用有效期(默认 5 分钟) + TokenTTL time.Duration // 二维码 token 有效期(默认 30 分钟) + MaxAmount int64 // 单笔限额(分,0=不限;docs/MIANQIAN.md §10 风控) +} + +// Channel 个人免签通道实现 +type Channel struct { + cfg Config + pool *account.AccountPool + store store.Store // 业务库(订单查询/匹配) + personalDB store.PersonalStore // personal.db(设备/推送流水) + alloc *personal.NoteCodeAllocator + provider channel.Provider // alipay / wechat + baseURL string // 当前公网基础地址(SetBaseURL 动态更新) + adbOnline func(ctx context.Context, serial string) bool // 设备 adb 可达判定(main.go 注入 adbMgr().Online) + + // balancedSelect 均衡分配器(main.go 注入,nil = 退化为旧 selectServableAccount 加权随机): + // 两层分配:先选设备(最闲最健康),再选设备内账户(最闲最健康)。 + // 返回选中账户;nil = 无可用。main.go 实现持有 waitPayPool/ledgermon 等负载数据源。 + balancedSelect func(ctx context.Context) *account.Account +} + +// SetBalancedSelect 注入均衡分配器(docs/PLAN-DYNAMIC-CLOSE-ALLOC.md §5)。 +// nil = 退化为旧逻辑(SelectFrom 加权随机,不感知负载)。 +func (c *Channel) SetBalancedSelect(fn func(ctx context.Context) *account.Account) { + c.balancedSelect = fn +} + +// SetAdbOnline 注入设备 adb 可达判定函数(main.go 注入 adbMgr().Online 包装)。 +// 2026-08-29 重构:设备在线判定改为 adb 可达性优先,不再依赖监听 App 心跳。 +func (c *Channel) SetAdbOnline(fn func(ctx context.Context, serial string) bool) { + c.adbOnline = fn +} + +// New 创建个人免签通道 +// provider: channel.ProviderAlipay / channel.ProviderWechat +func New(cfg Config, provider channel.Provider, pool *account.AccountPool, st store.Store, pdb store.PersonalStore) *Channel { + if cfg.OrderTTL <= 0 { + cfg.OrderTTL = 30 * time.Minute + } + if cfg.NoteTTL <= 0 { + cfg.NoteTTL = personal.NoteCodeTTL + } + if cfg.TokenTTL <= 0 { + cfg.TokenTTL = 30 * time.Minute + } + return &Channel{ + cfg: cfg, + pool: pool, + store: st, + personalDB: pdb, + alloc: personal.NewNoteCodeAllocator(), + provider: provider, + baseURL: cfg.BaseURL, + } +} + +// SetBaseURL 动态更新公网基础地址(隧道重启换域名时调用) +func (c *Channel) SetBaseURL(url string) { + c.baseURL = strings.TrimSuffix(url, "/") + logger.Info("personal", "公网地址已更新", "provider", c.provider, "base_url", c.baseURL) +} + +// BaseURL 当前公网基础地址(管理后台展示) +func (c *Channel) BaseURL() string { return c.baseURL } + +// channelName 通道注册名 +func (c *Channel) channelName() string { return "personal." + string(c.provider) } + +// Name 通道标识 +func (c *Channel) Name() string { return c.channelName() } + +// Type 通道大类:个人通道 +func (c *Channel) Type() channel.ChannelType { return channel.TypePersonal } + +// Provider 支付品牌 +func (c *Channel) Provider() channel.Provider { return c.provider } + +// Kind 通道类型:监听驱动 +func (c *Channel) Kind() channel.ChannelKind { return channel.KindListener } + +// ConfirmAbility 确认能力:无法主动查单,最终兜底 = 人工异常池 +func (c *Channel) ConfirmAbility() channel.ConfirmAbility { return channel.ConfirmByManual } + +// Modes 支持的支付模式:仅扫码(个人收款码) +func (c *Channel) Modes() []channel.PayMode { + return []channel.PayMode{channel.ModeQRCode} +} + +// Listener 监听驱动但通过 HTTP 推送,无独立事件监听器 +func (c *Channel) Listener() channel.EventListener { return nil } + +// ErrNotQueryable 个人通道无法主动查单 +var ErrNotQueryable = errors.New("个人通道无法主动查单(靠监听端推送确认)") + +// formatYuan 分 → 元字符串(如 50000 → "500.00") +func formatYuan(minor int64) string { + return fmt.Sprintf("%d.%02d", minor/100, minor%100) +} + +// CreatePayment 下单(个人免签) +// +// 流程: +// 1. 账户池选一个可用收款账户(含收款码内容 QRContent) +// 2. 分配 4 位备注码(系统生成,5 分钟占用) +// 3. 生成二维码 token(HMAC 签名,防劫持) +// 4. 直发码 URL:/qr/real?token={token}(动态图片接口下发真实收款码) +// +// 返回的 PayCredential.QRCode 就是二维码内容;前端按 src 渲染。 +func (c *Channel) CreatePayment(ctx context.Context, req *channel.PayRequest) (*channel.PayCredential, error) { + if c.baseURL == "" { + return nil, fmt.Errorf("个人通道公网地址未配置(隧道未就绪)") + } + // 单笔限额风控(docs/MIANQIAN.md §10):大额拒绝下单 + 日志告警 + if c.cfg.MaxAmount > 0 && req.AmountMinor > c.cfg.MaxAmount { + logger.Warn("personal", "单笔限额风控:金额超限,拒绝下单", + "provider", c.provider, "out_trade_no", req.OutTradeNo, + "amount", req.AmountMinor, "max", c.cfg.MaxAmount) + return nil, fmt.Errorf("单笔金额超限(最多 ¥%s)", formatYuan(c.cfg.MaxAmount)) + } + // 1. 选账户(仅选有在线监听设备绑定的账户,防路由到无设备账户造成死单) + acct := c.selectServableAccount(ctx) + if acct == nil { + return nil, fmt.Errorf("%s 个人通道暂无可用收款账户(无在线监听设备绑定或已达日限额/冷却中)", c.provider) + } + if acct.QRContent == "" { + return nil, fmt.Errorf("账户「%s」未配置收款码内容(qr_content),无法接单", acct.Name) + } + if reason := account.InvalidQRContent(c.channelName(), acct.QRContent); reason != "" { + logger.Error("personal", "账户收款码内容非法,拒绝路由(脏数据防线)", + "provider", c.provider, "account", acct.Name, "account_id", acct.ID, "reason", reason) + return nil, fmt.Errorf("账户「%s」收款码内容异常(%s),已拒绝路由,请联系管理员修复", acct.Name, reason) + } + // 2. 分配备注码 + noteCode, err := c.alloc.Alloc() + if err != nil { + return nil, fmt.Errorf("备注码分配失败: %w", err) + } + // 3. 生成二维码 token(自包含签名,/qr/real 动态图片接口用) + token, err := personal.GenerateQrToken(req.OutTradeNo, time.Now().Add(c.cfg.TokenTTL)) + if err != nil { + c.alloc.Release(noteCode) + return nil, fmt.Errorf("二维码 token 生成失败: %w", err) + } + qrURL := "/qr/real?token=" + token + // 通知栏监听方案只依赖 包名+金额+时间 匹配,备注码已无提示意义;日志不再提备注码 + logger.Info("personal", "下单成功(直发码)", + "provider", c.provider, "out_trade_no", req.OutTradeNo, + "account", acct.Name, "account_id", acct.ID, + "amount", req.AmountMinor) + + // 4. 保存备注码/套码 token 到订单(order.Service 通过 cred 回调写回,这里无法直接写) + // 通过 PayCredential 附加字段透传给 order.Service 持久化 + return &channel.PayCredential{ + QRCode: qrURL, + Mode: channel.ModeQRCode, + ExpireAt: time.Now().Add(c.cfg.OrderTTL), + AccountID: acct.ID, + // 个人通道附加数据:备注码 + 套码 token + 收款码内容(用于 /mq 302 / /qr/real 图片下发) + NoteCode: noteCode, + QrToken: token, + QRContent: acct.QRContent, + }, nil +} + +// VerifyNotify 个人通道不支持 HTTP 验签回调(收款确认走 /personal/push,HMAC 验签在 handler) +func (c *Channel) VerifyNotify(ctx context.Context, req *http.Request) (*channel.NotifyResult, error) { + return nil, fmt.Errorf("个人通道不支持 HTTP 回调验签,收款确认走 /personal/push") +} + +// QueryOrder 个人通道无法主动查单 +func (c *Channel) QueryOrder(ctx context.Context, outTradeNo string) (*channel.OrderStatus, error) { + return nil, ErrNotQueryable +} + +// SelectAccount 按收款码内容选择可用账户(供 /mq 302 校验用) +func (c *Channel) SelectAccount() *account.Account { + return c.pool.Select(c.channelName(), "default") +} + +// servableAccountIDs 返回有**adb 可达**设备绑定的账户 ID 集合(设备主账户 + 附加账户)。 +// 设备可用判定:adb 可达(USB/WiFi/LAN)。 +// 不再依赖监听 App 心跳/DB status(2026-08-29 重构)。 +// 设备冻结/通道不匹配均不计入;查询失败返回 nil(调用方按无可用账户处理)。 +func (c *Channel) servableAccountIDs(ctx context.Context) map[int64]bool { + devs, err := c.personalDB.ListDevices(ctx) + if err != nil { + logger.Error("personal", "查询监听设备失败", "err", err) + return nil + } + out := make(map[int64]bool) + for _, d := range devs { + // 设备可用判定:adb 可达性。 + // 注:不做 d.Provider 过滤——一台真机同时装有支付宝+微信,设备行只存单一 provider + // 标签,按标签过滤会让另一平台永远无账户可用(老 bug,被僵尸设备数据掩盖过)。 + // 通道归属由 SelectFrom 的 channel 过滤保证。 + if serial := c.serialOf(d); serial == "" || c.adbOnline == nil || !c.adbOnline(ctx, serial) { + continue + } + for _, id := range store.DeviceAccountIDs(ctx, c.personalDB, d.ID, d.AccountID) { + out[id] = true + } + } + return out +} + +// serialOf 解析设备 adb serial(TCP host:port 或 USB 纯 serial) +func (c *Channel) serialOf(d *personal.DeviceEncrypted) string { + if d.AdbHost != "" { + if d.AdbPort > 0 { + return fmt.Sprintf("%s:%d", d.AdbHost, d.AdbPort) + } + return d.AdbHost + } + if d.AdbWifiHost != "" && d.AdbWifiPort > 0 { + return fmt.Sprintf("%s:%d", d.AdbWifiHost, d.AdbWifiPort) + } + return "" +} + +// selectServableAccount 设备感知选号:可用账户 ∩ 在线监听设备绑定账户。 +// 均衡分配器已注入时走两层评分(设备负载×账户负载×健康度);否则退化为加权随机。 +func (c *Channel) selectServableAccount(ctx context.Context) *account.Account { + if c.balancedSelect != nil { + return c.balancedSelect(ctx) + } + servable := c.servableAccountIDs(ctx) + if len(servable) == 0 { + logger.Warn("personal", "无在线监听设备可服务,拒绝路由", "provider", c.provider) + return nil + } + return c.pool.SelectFrom(c.channelName(), "default", servable) +} + +// GetAccountByID 按 ID 获取账户 +func (c *Channel) GetAccountByID(id int64) *account.Account { + return c.pool.GetByID(id) +} + +// ReleaseNoteCode 释放备注码(订单支付成功/关闭后调用) +// 幂等:重复释放不会产生副作用(delete 不存在 key 是 no-op) +func (c *Channel) ReleaseNoteCode(code string) { + if code != "" { + c.alloc.Release(code) + } +} diff --git a/internal/channel/registry.go b/internal/channel/registry.go new file mode 100755 index 0000000..af63f05 --- /dev/null +++ b/internal/channel/registry.go @@ -0,0 +1,139 @@ +package channel + +import ( + "fmt" + "sync" +) + +// registry 通道注册表(线程安全) +var ( + mu sync.RWMutex + registry = make(map[string]PaymentChannel) + enabled = make(map[string]bool) // 通道启用状态,默认启用 +) + +// Register 注册通道(每个通道在 init 或组装阶段调用) +func Register(c PaymentChannel) { + mu.Lock() + defer mu.Unlock() + registry[c.Name()] = c + enabled[c.Name()] = true // 默认启用 +} + +// Get 按名称获取通道(仅返回已启用的通道) +func Get(name string) (PaymentChannel, bool) { + mu.RLock() + defer mu.RUnlock() + if !enabled[name] { + return nil, false + } + c, ok := registry[name] + return c, ok +} + +// Names 返回已启用通道名列表 +func Names() []string { + mu.RLock() + defer mu.RUnlock() + out := make([]string, 0, len(registry)) + for name := range registry { + if enabled[name] { + out = append(out, name) + } + } + return out +} + +// AllNames 返回所有已注册通道名列表(含禁用,管理后台用) +func AllNames() []string { + mu.RLock() + defer mu.RUnlock() + out := make([]string, 0, len(registry)) + for name := range registry { + out = append(out, name) + } + return out +} + +// IsEnabled 检查通道是否启用 +func IsEnabled(name string) bool { + mu.RLock() + defer mu.RUnlock() + return enabled[name] +} + +// SetEnabled 设置通道启用/禁用状态 +func SetEnabled(name string, on bool) { + mu.Lock() + defer mu.Unlock() + enabled[name] = on +} + +// Lookup 按名称查找通道(不检查启用状态,管理后台用) +func Lookup(name string) (PaymentChannel, bool) { + mu.RLock() + defer mu.RUnlock() + c, ok := registry[name] + return c, ok +} + +// EpayTypeToChannel 易支付协议类型 → 通道注册名映射 +// epay 协议 type 参数使用 "alipay"/"wxpay",需要转为通道注册名 +var EpayTypeToChannel = map[string]string{ + "alipay": "merchant.alipay", + "wxpay": "merchant.wechat", + "qqpay": "merchant.qq", + "usdt": "merchant.usdt", + // 个人免签通道(显式选择,商户传 type=personal_alipay / personal_wxpay) + "personal_alipay": "personal.alipay", + "personal_wxpay": "personal.wechat", +} + +// EpayTypeToPersonalChannel 易支付类型 → 个人免签通道名(优先路由到个人通道) +// 当对应个人通道已注册且配置了收款账户时,用 ResolveEpayType 优先选择个人通道 +var EpayTypeToPersonalChannel = map[string]string{ + "alipay": "personal.alipay", + "wxpay": "personal.wechat", + "qqpay": "personal.qq", +} + +// ResolveEpayType 解析易支付 type 参数为实际通道名 +// 路由规则(个人免签优先): +// 1. 若对应个人通道已注册且已启用 → 优先使用个人通道(个人免签 0 费率) +// 2. 否则回退到商家通道(merchant.*) +// 3. 都不存在 → 返回原映射(调用方报"不支持的支付类型") +func ResolveEpayType(epayType string) string { + // 个人通道优先(仅当注册且启用) + if pName, ok := EpayTypeToPersonalChannel[epayType]; ok { + if c, ok := Get(pName); ok && c != nil { + return pName + } + } + // 回退商家通道 + if mName, ok := EpayTypeToChannel[epayType]; ok { + return mName + } + return "" +} + +// ProviderFromName 从通道名提取 Provider(如 "merchant.alipay" → "alipay") +func ProviderFromName(name string) string { + for i := len(name) - 1; i >= 0; i-- { + if name[i] == '.' { + return name[i+1:] + } + } + return name +} + +// ErrUnsupported 不支持的支付类型 +var ErrUnsupported = fmt.Errorf("不支持的支付类型") + +// MustGet 获取通道,不存在则 panic(组装阶段校验配置) +func MustGet(name string) PaymentChannel { + c, ok := Get(name) + if !ok { + panic(fmt.Sprintf("通道未注册: %s", name)) + } + return c +} diff --git a/internal/channel/wechat/wechat.go b/internal/channel/wechat/wechat.go new file mode 100755 index 0000000..20d515b --- /dev/null +++ b/internal/channel/wechat/wechat.go @@ -0,0 +1,353 @@ +// Package wechat 微信支付通道(基于 gopay 库,V2 协议) +// +// 账户池集成: +// - 持有 AccountPool,下单选账户、回调反查 mch_id +// - 每个账户对应一个 gopay.Client,按需创建并缓存 +package wechat + +import ( + "context" + "crypto/md5" + "encoding/hex" + "encoding/xml" + "fmt" + "net/http" + "strconv" + "sync" + "time" + + "github.com/go-pay/gopay" + "github.com/go-pay/gopay/wechat" + "paygateway/internal/account" + "paygateway/internal/channel" + "paygateway/internal/logger" + "paygateway/internal/model" +) + +// Config 微信通道配置(仅回调地址 + 调试开关;账户密钥一律来自账户池) +type Config struct { + NotifyURL string // 异步回调地址 + Debug bool // 调试日志 +} + +// Channel 微信通道实现 +type Channel struct { + cfg Config + pool *account.AccountPool + clientCache map[int64]*wechat.Client + clientMu sync.RWMutex +} + +// New 创建微信通道(账户池模式:所有账户数据从 pool 读取) +func New(cfg Config, pool *account.AccountPool) (*Channel, error) { + ch := &Channel{ + cfg: cfg, + pool: pool, + clientCache: make(map[int64]*wechat.Client), + } + return ch, nil +} + +// getOrCreateClient 获取或创建账户对应的 gopay client +func (c *Channel) getOrCreateClient(acct *account.Account) (*wechat.Client, error) { + c.clientMu.RLock() + if cli, ok := c.clientCache[acct.ID]; ok { + c.clientMu.RUnlock() + return cli, nil + } + c.clientMu.RUnlock() + + c.clientMu.Lock() + defer c.clientMu.Unlock() + + if cli, ok := c.clientCache[acct.ID]; ok { + return cli, nil + } + + if acct.AppID == "" || acct.MchID == "" || acct.ApiKey == "" { + return nil, fmt.Errorf("微信账户配置不完整: AppID/MchID/ApiKey 必填") + } + client := wechat.NewClient(acct.AppID, acct.MchID, acct.ApiKey, acct.IsProd) + if c.cfg.Debug { + client.DebugSwitch = gopay.DebugOn + } + c.clientCache[acct.ID] = client + logger.Info("wechat", "账户客户端已创建", "account_id", acct.ID, "name", acct.Name) + return client, nil +} + +// SetNotifyURL 动态更新回调地址 +func (c *Channel) SetNotifyURL(url string) { + c.cfg.NotifyURL = url + logger.Info("wechat", "回调地址已更新", "notify_url", url) +} + +// RemoveClient 移除账户对应的 gopay client 缓存(账户 app_id/密钥变更后必须调用) +func (c *Channel) RemoveClient(accountID int64) { + c.clientMu.Lock() + if _, ok := c.clientCache[accountID]; ok { + delete(c.clientCache, accountID) + logger.Info("wechat", "账户客户端缓存已清除", "account_id", accountID) + } + c.clientMu.Unlock() +} + +// Name 通道标识 +func (c *Channel) Name() string { return "merchant.wechat" } + +// Type 通道大类:商家通道 +func (c *Channel) Type() channel.ChannelType { return channel.TypeMerchant } + +// Provider 支付品牌:微信 +func (c *Channel) Provider() channel.Provider { return channel.ProviderWechat } + +// ConfirmAbility 确认能力:可主动查单 +func (c *Channel) ConfirmAbility() channel.ConfirmAbility { return channel.ConfirmByQuery } + +// NotifyURL 返回当前回调地址 +func (c *Channel) NotifyURL() string { return c.cfg.NotifyURL } + +// Kind 通道类型:回调驱动 +func (c *Channel) Kind() channel.ChannelKind { return channel.KindCallback } + +// Modes 支持的支付模式 +func (c *Channel) Modes() []channel.PayMode { + return []channel.PayMode{channel.ModeQRCode, channel.ModeWap, channel.ModePage} +} + +// Listener 回调型无监听器 +func (c *Channel) Listener() channel.EventListener { return nil } + +// selectAccount 从账户池选一个可用账户 +func (c *Channel) selectAccount(group string) *account.Account { + if c.pool == nil { + return nil + } + return c.pool.Select("wechat", group) +} + +// CreatePayment 下单 +func (c *Channel) CreatePayment(ctx context.Context, req *channel.PayRequest) (*channel.PayCredential, error) { + // 从账户池选账户 + var acct *account.Account + if c.pool != nil { + acct = c.pool.Select("wechat", "default") + if acct == nil { + return nil, fmt.Errorf("微信暂无可用账户(所有账户已达日限额或冷却中)") + } + } + + // 获取 gopay client(账户池模式:必选账户) + var client *wechat.Client + if acct == nil { + return nil, fmt.Errorf("微信暂无可用账户") + } + client, err := c.getOrCreateClient(acct) + if err != nil { + return nil, fmt.Errorf("获取微信客户端失败: %w", err) + } + + bm := make(gopay.BodyMap) + bm.Set("nonce_str", randomStr(32)). + Set("body", truncate(req.Subject, 30)). + Set("out_trade_no", req.OutTradeNo). + Set("total_fee", req.AmountMinor). + Set("spbill_create_ip", "127.0.0.1"). + Set("notify_url", c.cfg.NotifyURL) + + payMode := c.resolveMode(req) + apiCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + var cred *channel.PayCredential + var payErr error + + switch payMode { + case channel.ModeWap: + logger.Info("wechat", "模式: H5支付 (MWEB)") + bm.Set("trade_type", wechat.TradeType_H5). + Set("scene_info", `{"h5_info":{"type":"Wap","wap_url":"http://localhost:8080","wap_name":"PayGateway"}}`) + wxRsp, err := client.UnifiedOrder(apiCtx, bm) + if err != nil { + payErr = fmt.Errorf("H5下单失败: %w", err) + } else if wxRsp.ReturnCode != "SUCCESS" || wxRsp.ResultCode != "SUCCESS" { + payErr = fmt.Errorf("H5下单失败: return=%s(%s) result=%s(%s)", + wxRsp.ReturnCode, wxRsp.ReturnMsg, wxRsp.ResultCode, wxRsp.ErrCodeDes) + } else { + cred = &channel.PayCredential{PayURL: wxRsp.MwebUrl, Mode: channel.ModeWap, ExpireAt: time.Now().Add(2 * time.Hour)} + } + + default: + logger.Info("wechat", "模式: Native扫码支付") + bm.Set("trade_type", wechat.TradeType_Native) + wxRsp, err := client.UnifiedOrder(apiCtx, bm) + if err != nil { + payErr = fmt.Errorf("Native下单失败: %w", err) + } else if wxRsp.ReturnCode != "SUCCESS" || wxRsp.ResultCode != "SUCCESS" { + payErr = fmt.Errorf("Native下单失败: return=%s(%s) result=%s(%s)", + wxRsp.ReturnCode, wxRsp.ReturnMsg, wxRsp.ResultCode, wxRsp.ErrCodeDes) + } else { + cred = &channel.PayCredential{QRCode: wxRsp.CodeUrl, Mode: channel.ModeQRCode, ExpireAt: time.Now().Add(2 * time.Hour)} + } + } + + // 上报结果给账户池 + if acct != nil && c.pool != nil { + c.pool.ReportResult(acct.ID, payErr == nil) + if payErr == nil && cred != nil { + c.pool.AddDailyUsed(acct.ID, req.AmountMinor) + } + } + + // 记录使用的账户ID + if payErr == nil && cred != nil && acct != nil { + cred.AccountID = acct.ID + } + + if payErr != nil { + return nil, payErr + } + return cred, nil +} + +// resolveMode 解析支付模式 +func (c *Channel) resolveMode(req *channel.PayRequest) channel.PayMode { + switch { + case req.PayMode == channel.ModeWap || req.Device == "wap": + return channel.ModeWap + case req.PayMode == channel.ModePage || req.Device == "page": + return channel.ModeQRCode + case req.Device == "mobile": + return channel.ModeWap + default: + return channel.ModeQRCode + } +} + +// VerifyNotify 验签微信异步回调 +// 通过 mch_id 反查账户池中的账户,用该账户的 API 密钥验签 +func (c *Channel) VerifyNotify(ctx context.Context, req *http.Request) (*channel.NotifyResult, error) { + bm, err := wechat.ParseNotifyToBodyMap(req) + if err != nil { + return nil, fmt.Errorf("解析微信回调失败: %w", err) + } + + mchID := bm.GetString("mch_id") + + if c.pool == nil { + return nil, fmt.Errorf("账户池未初始化,无法验签") + } + acct := c.pool.LookupByMchID(mchID) + if acct == nil { + logger.Warn("wechat", "回调 mch_id 未匹配到账户", "mch_id", mchID) + return nil, fmt.Errorf("未找到 mch_id=%s 对应的账户", mchID) + } + ok, err := wechat.VerifySign(acct.ApiKey, wechat.SignType_MD5, bm) + if err != nil || !ok { + return nil, fmt.Errorf("微信验签失败(账户: %s): %v", acct.Name, err) + } + logger.Info("wechat", "回调验签通过", "account", acct.Name, "mch_id", mchID) + + acctID := int64(0) + if acct != nil { + acctID = acct.ID + } + + if bm.GetString("return_code") != "SUCCESS" || bm.GetString("result_code") != "SUCCESS" { + return &channel.NotifyResult{ + Valid: true, OutTradeNo: bm.GetString("out_trade_no"), + Status: model.StatusFailed, MchID: mchID, AccountID: acctID, RawPayload: rawXML(bm), + }, nil + } + + amountMinor, _ := strconv.ParseInt(bm.GetString("total_fee"), 10, 64) + return &channel.NotifyResult{ + Valid: true, OutTradeNo: bm.GetString("out_trade_no"), + ChannelTxID: bm.GetString("transaction_id"), Status: model.StatusSuccess, + AmountMinor: amountMinor, Payer: bm.GetString("openid"), + MchID: mchID, AccountID: acctID, RawPayload: rawXML(bm), + }, nil +} + +// QueryOrder 主动查询订单 +func (c *Channel) QueryOrder(ctx context.Context, outTradeNo string) (*channel.OrderStatus, error) { + if c.pool == nil { + return nil, fmt.Errorf("账户池未初始化") + } + accts := c.pool.ListByChannel("wechat") + for _, acct := range accts { + client, err := c.getOrCreateClient(acct) + if err != nil { + continue + } + bm := make(gopay.BodyMap) + bm.Set("nonce_str", randomStr(32)).Set("out_trade_no", outTradeNo) + wxRsp, _, err := client.QueryOrder(ctx, bm) + if err != nil { + continue + } + return parseOrderStatus(wxRsp), nil + } + return nil, fmt.Errorf("所有微信账户查询均失败") +} + +func parseOrderStatus(wxRsp *wechat.QueryOrderResponse) *channel.OrderStatus { + st := &channel.OrderStatus{ChannelTxID: wxRsp.TransactionId} + switch wxRsp.TradeState { + case "SUCCESS": + st.Status = model.StatusSuccess + case "CLOSED", "REVOKED": + st.Status = model.StatusClosed + case "PAYERROR": + st.Status = model.StatusFailed + default: + st.Status = model.StatusWaitPay + } + if fee, err := strconv.ParseInt(wxRsp.TotalFee, 10, 64); err == nil { + st.AmountMinor = fee + } + return st +} + +// NotifySuccessResponse 微信成功应答 XML +func NotifySuccessResponse() string { + return `` +} + +// NotifyFailResponse 微信失败应答 XML +func NotifyFailResponse() string { + return `` +} + +func rawXML(bm gopay.BodyMap) string { + type kv struct { + XMLName xml.Name `xml:"xml"` + Fields map[string]string + } + fields := make(map[string]string) + for k, v := range bm { + if s, ok := v.(string); ok { + fields[k] = s + } + } + b, _ := xml.Marshal(kv{Fields: fields}) + return string(b) +} + +func randomStr(n int) string { + const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + h := md5.Sum([]byte(fmt.Sprintf("%d-%d", time.Now().UnixNano(), len(letters)))) + s := hex.EncodeToString(h[:]) + if len(s) > n { + return s[:n] + } + return s +} + +func truncate(s string, n int) string { + r := []rune(s) + if len(r) <= n { + return s + } + return string(r[:n]) +} diff --git a/internal/epay/sign.go b/internal/epay/sign.go new file mode 100755 index 0000000..729ccf4 --- /dev/null +++ b/internal/epay/sign.go @@ -0,0 +1,85 @@ +// Package epay 易支付协议层:对外接口(submit/api/签名),保持向后兼容 +package epay + +import ( + "crypto/md5" + "crypto/subtle" + "encoding/hex" + "fmt" + "sort" + "strings" +) + +// ParamsFilter 过滤参数,生成签名时需删除 "sign" 和 "sign_type" 参数 +func ParamsFilter(params map[string]string) map[string]string { + filtered := make(map[string]string) + for k, v := range params { + // pay_type 是网关扩展参数(选择支付模式),不参与签名 + if k == "sign" || k == "sign_type" || k == "pay_type" || v == "" { + continue + } + filtered[k] = v + } + return filtered +} + +// ParamsSort 对参数排序 +func ParamsSort(params map[string]string) []string { + keys := make([]string, 0, len(params)) + for k := range params { + keys = append(keys, k) + } + sort.Strings(keys) + return keys +} + +// CreateUrlString 生成待签名字符串 "a=1&b=2&c=3" +func CreateUrlString(params map[string]string) string { + keys := ParamsSort(params) + parts := make([]string, 0, len(keys)) + for _, k := range keys { + parts = append(parts, k+"="+params[k]) + } + return strings.Join(parts, "&") +} + +// MD5String 生成加盐 MD5 字符串 +func MD5String(urlString, key string) string { + digest := md5.Sum([]byte(urlString + key)) + return hex.EncodeToString(digest[:]) +} + +// GenerateSign 生成签名 +func GenerateSign(params map[string]string, key string) string { + filtered := ParamsFilter(params) + return MD5String(CreateUrlString(filtered), key) +} + +// VerifySign 验证签名(常数时间比较,防时序侧信道逐字节猜签名) +func VerifySign(params map[string]string, key string) bool { + sign := params["sign"] + expected := GenerateSign(params, key) + return subtle.ConstantTimeCompare([]byte(sign), []byte(expected)) == 1 +} + +// BuildSignedParams 生成带签名的参数 +func BuildSignedParams(params map[string]string, key string) map[string]string { + params["sign"] = GenerateSign(params, key) + params["sign_type"] = "MD5" + return params +} + +// BuildQueryString 将参数拼接成 URL query string +func BuildQueryString(params map[string]string) string { + keys := ParamsSort(params) + parts := make([]string, 0, len(keys)) + for _, k := range keys { + parts = append(parts, k+"="+params[k]) + } + return strings.Join(parts, "&") +} + +// DebugPrint 打印参数用于调试 +func DebugPrint(tag string, params map[string]string) { + fmt.Printf("[%s] %s\n", tag, BuildQueryString(params)) +} diff --git a/internal/ledger/ledger.go b/internal/ledger/ledger.go new file mode 100755 index 0000000..c30ae34 --- /dev/null +++ b/internal/ledger/ledger.go @@ -0,0 +1,224 @@ +// Package ledger 资金审计账本:哈希链 + 数字签名,保证每笔交易不可篡改 +package ledger + +import ( + "context" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "crypto/x509" + "encoding/hex" + "encoding/pem" + "fmt" + "math/big" + "sync" + "time" + + "paygateway/internal/store" +) + +// Ledger 审计账本 +type Ledger struct { + mu sync.Mutex + store store.LedgerStore + signKey *ecdsa.PrivateKey +} + +// New 创建账本;privKeyPEM 为空时自动生成新密钥对(打印公钥供保存) +func New(st store.LedgerStore, privKeyPEM string) (*Ledger, error) { + var key *ecdsa.PrivateKey + var err error + if privKeyPEM == "" { + key, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, fmt.Errorf("生成审计密钥失败: %w", err) + } + // 输出私钥 PEM 供保存(首次运行) + der, err := x509.MarshalECPrivateKey(key) + if err == nil { + pemBytes := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: der}) + fmt.Printf("[ledger] 首次运行,生成审计密钥,请保存到环境变量 LEDGER_PRIVATE_KEY:\n%s\n", string(pemBytes)) + } + } else { + key, err = parsePrivateKey(privKeyPEM) + if err != nil { + return nil, fmt.Errorf("解析审计私钥失败: %w", err) + } + } + return &Ledger{store: st, signKey: key}, nil +} + +func parsePrivateKey(pemStr string) (*ecdsa.PrivateKey, error) { + block, _ := pem.Decode([]byte(pemStr)) + if block == nil { + return nil, fmt.Errorf("无效的 PEM 私钥") + } + key, err := x509.ParseECPrivateKey(block.Bytes) + if err != nil { + return nil, err + } + return key, nil +} + +// PublicKeyPEM 返回公钥 PEM(用于校验签名) +func (l *Ledger) PublicKeyPEM() string { + der, _ := x509.MarshalPKIXPublicKey(&l.signKey.PublicKey) + return string(pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: der})) +} + +// HasEvent 按 eventID(订单ID)查询账本是否已有记录(崩溃恢复去重用) +func (l *Ledger) HasEvent(ctx context.Context, eventID int64) (bool, error) { + l.mu.Lock() + defer l.mu.Unlock() + return l.store.HasLedgerEvent(ctx, eventID) +} + +// Append 追加一条不可篡改记录:哈希链 + 签名 +// payload 为事件全部内容(订单号+金额+状态+通道交易号+时间+原始数据),必须包含所有不可变字段 +func (l *Ledger) Append(ctx context.Context, eventID int64, payload string) error { + l.mu.Lock() + defer l.mu.Unlock() + + // 取链尾哈希 + last, err := l.store.GetLastLedgerEntry(ctx) + if err != nil { + return fmt.Errorf("读取链尾失败: %w", err) + } + prevHash := "" + if last != nil { + prevHash = last.EntryHash + } + + payloadHash := sha256Hex(payload) + entryHash := sha256Hex(prevHash + payloadHash) + + // 签名 entry_hash + sig, err := l.sign(entryHash) + if err != nil { + return fmt.Errorf("签名失败: %w", err) + } + + // 写入账本(只增不改,数据库层有触发器保护) + if err := l.store.AppendLedger(ctx, prevHash, payloadHash, entryHash, sig, eventID); err != nil { + return fmt.Errorf("写入账本失败: %w", err) + } + return nil +} + +// Verify 全链完整性校验:从 genesis 重算哈希链 + 校验签名 +func (l *Ledger) Verify(ctx context.Context) (*VerifyResult, error) { + res := &VerifyResult{StartTime: time.Now()} + + count, err := l.store.CountLedger(ctx) + if err != nil { + return nil, err + } + res.TotalEntries = count + + var prevHash string + // 快照锚点校验:锚点必须存在于链上(快照是历史时刻的锚点,允许落后于链尾) + sn, _ := l.store.GetLatestSnapshot(ctx) + anchorFound := false + for id := int64(1); id <= count; id++ { + entry, err := l.store.GetLedgerEntry(ctx, id) + if err != nil { + return nil, err + } + if entry == nil { + res.Errors = append(res.Errors, fmt.Sprintf("账本条目缺失: id=%d", id)) + break + } + // 校验链 + expected := sha256Hex(prevHash + entry.PayloadHash) + if entry.EntryHash != expected { + res.Errors = append(res.Errors, fmt.Sprintf("哈希链断裂: id=%d 期望=%s 实际=%s", entry.ID, expected, entry.EntryHash)) + } + // 校验签名 + if !l.verify(entry.EntryHash, entry.Signature) { + res.Errors = append(res.Errors, fmt.Sprintf("签名校验失败: id=%d", entry.ID)) + } + if sn != nil && entry.EntryHash == sn.LastHash { + anchorFound = true + } + prevHash = entry.EntryHash + } + + // 快照锚点存在性:在链上即代表快照覆盖的链段完整(历史锚点,非必须等于链尾) + if sn != nil { + if anchorFound { + res.SnapshotOK = true + } else { + res.Errors = append(res.Errors, fmt.Sprintf("快照锚点不在链上: 快照=%s", sn.LastHash)) + } + } + res.Valid = len(res.Errors) == 0 + res.EndTime = time.Now() + return res, nil +} + +// CreateSnapshot 生成每日快照(链尾哈希 + 签名) +func (l *Ledger) CreateSnapshot(ctx context.Context, date string) error { + l.mu.Lock() + defer l.mu.Unlock() + last, err := l.store.GetLastLedgerEntry(ctx) + if err != nil { + return err + } + if last == nil { + return fmt.Errorf("账本为空,无法生成快照") + } + sig, err := l.sign(last.EntryHash) + if err != nil { + return err + } + return l.store.CreateSnapshot(ctx, date, last.EntryHash, sig) +} + +// VerifyResult 校验结果 +type VerifyResult struct { + Valid bool + TotalEntries int64 + Errors []string + SnapshotOK bool + StartTime time.Time + EndTime time.Time +} + +func (r *VerifyResult) Summary() string { + if r.Valid { + return fmt.Sprintf("账本完整 ✅ 共 %d 条,快照锚点匹配", r.TotalEntries) + } + return fmt.Sprintf("账本校验失败 ❌ 共 %d 条,%d 处错误", r.TotalEntries, len(r.Errors)) +} + +// ---------- 工具 ---------- + +func sha256Hex(s string) string { + h := sha256.Sum256([]byte(s)) + return hex.EncodeToString(h[:]) +} + +func (l *Ledger) sign(data string) (string, error) { + h := sha256.Sum256([]byte(data)) + r, s, err := ecdsa.Sign(rand.Reader, l.signKey, h[:]) + if err != nil { + return "", err + } + // 编码 r||s(各32字节) + out := make([]byte, 64) + r.FillBytes(out[:32]) + s.FillBytes(out[32:]) + return hex.EncodeToString(out), nil +} + +func (l *Ledger) verify(data, sigHex string) bool { + sig, err := hex.DecodeString(sigHex) + if err != nil || len(sig) != 64 { + return false + } + h := sha256.Sum256([]byte(data)) + r := new(big.Int).SetBytes(sig[:32]) + s := new(big.Int).SetBytes(sig[32:]) + return ecdsa.Verify(&l.signKey.PublicKey, h[:], r, s) +} diff --git a/internal/ledger/ledger_test.go b/internal/ledger/ledger_test.go new file mode 100755 index 0000000..ee5af2a --- /dev/null +++ b/internal/ledger/ledger_test.go @@ -0,0 +1,369 @@ +package ledger + +import ( + "context" + "strings" + "sync" + "testing" + "time" + + "paygateway/internal/store" +) + +// mockLedgerStore 内存版 LedgerStore,用于单元测试 +type mockLedgerStore struct { + mu sync.Mutex + entries []*store.LedgerEntry + snapshots []*store.Snapshot + nextID int64 +} + +func (m *mockLedgerStore) AppendLedger(_ context.Context, prevHash, payloadHash, entryHash, signature string, eventID int64) error { + m.mu.Lock() + defer m.mu.Unlock() + m.nextID++ + m.entries = append(m.entries, &store.LedgerEntry{ + ID: m.nextID, + PrevHash: prevHash, + PayloadHash: payloadHash, + EntryHash: entryHash, + Signature: signature, + EventID: eventID, + CreatedAt: time.Now(), + }) + return nil +} + +func (m *mockLedgerStore) GetLastLedgerEntry(_ context.Context) (*store.LedgerEntry, error) { + m.mu.Lock() + defer m.mu.Unlock() + if len(m.entries) == 0 { + return nil, nil + } + return m.entries[len(m.entries)-1], nil +} + +func (m *mockLedgerStore) CountLedger(_ context.Context) (int64, error) { + m.mu.Lock() + defer m.mu.Unlock() + return int64(len(m.entries)), nil +} + +func (m *mockLedgerStore) GetLedgerEntry(_ context.Context, id int64) (*store.LedgerEntry, error) { + m.mu.Lock() + defer m.mu.Unlock() + for _, e := range m.entries { + if e.ID == id { + return e, nil + } + } + return nil, nil +} + +func (m *mockLedgerStore) HasLedgerEvent(_ context.Context, eventID int64) (bool, error) { + m.mu.Lock() + defer m.mu.Unlock() + for _, e := range m.entries { + if e.EventID == eventID { + return true, nil + } + } + return false, nil +} + +func (m *mockLedgerStore) ListLedger(_ context.Context, limit int) ([]*store.LedgerEntry, error) { + m.mu.Lock() + defer m.mu.Unlock() + if limit <= 0 || limit > len(m.entries) { + limit = len(m.entries) + } + out := make([]*store.LedgerEntry, 0, limit) + for i := len(m.entries) - 1; i >= 0 && len(out) < limit; i-- { + out = append(out, m.entries[i]) + } + return out, nil +} + +func (m *mockLedgerStore) CreateSnapshot(_ context.Context, date, lastHash, signature string) error { + m.mu.Lock() + defer m.mu.Unlock() + m.snapshots = append(m.snapshots, &store.Snapshot{ + ID: int64(len(m.snapshots) + 1), + SnapshotDate: date, + LastHash: lastHash, + Signature: signature, + CreatedAt: time.Now(), + }) + return nil +} + +func (m *mockLedgerStore) GetLatestSnapshot(_ context.Context) (*store.Snapshot, error) { + m.mu.Lock() + defer m.mu.Unlock() + if len(m.snapshots) == 0 { + return nil, nil + } + return m.snapshots[len(m.snapshots)-1], nil +} + +func (m *mockLedgerStore) ListSnapshots(_ context.Context, limit int) ([]*store.Snapshot, error) { + m.mu.Lock() + defer m.mu.Unlock() + if limit <= 0 || limit > len(m.snapshots) { + limit = len(m.snapshots) + } + out := make([]*store.Snapshot, 0, limit) + for i := len(m.snapshots) - 1; i >= 0 && len(out) < limit; i-- { + out = append(out, m.snapshots[i]) + } + return out, nil +} + +func (m *mockLedgerStore) Close() error { return nil } + +// newTestLedger 创建带新密钥的测试账本 +func newTestLedger(t *testing.T) (*Ledger, *mockLedgerStore) { + t.Helper() + m := &mockLedgerStore{} + // 传空私钥 → 自动生成新密钥对 + lg, err := New(m, "") + if err != nil { + t.Fatalf("创建账本失败: %v", err) + } + return lg, m +} + +// TestAppendAndVerify 测试:追加记录 + 全链校验通过 +func TestAppendAndVerify(t *testing.T) { + lg, m := newTestLedger(t) + ctx := context.Background() + + // 追加 3 条记录 + for i := int64(1); i <= 3; i++ { + payload := "order=TEST001&amount=100&status=PAID&event=" + itoa(i) + if err := lg.Append(ctx, i, payload); err != nil { + t.Fatalf("追加第 %d 条失败: %v", i, err) + } + } + + // 数量正确 + count, _ := m.CountLedger(ctx) + if count != 3 { + t.Fatalf("账本条目数 = %d, 期望 3", count) + } + + // 全链校验通过 + res, err := lg.Verify(ctx) + if err != nil { + t.Fatalf("校验失败: %v", err) + } + if !res.Valid { + t.Fatalf("账本应有效,实际错误: %v", res.Errors) + } + if res.TotalEntries != 3 { + t.Fatalf("校验条目数 = %d, 期望 3", res.TotalEntries) + } +} + +// TestHashChainLinked 测试:哈希链正确连接(每条记录包含上一条哈希) +func TestHashChainLinked(t *testing.T) { + lg, m := newTestLedger(t) + ctx := context.Background() + + _ = lg.Append(ctx, 1, "payload-1") + _ = lg.Append(ctx, 2, "payload-2") + _ = lg.Append(ctx, 3, "payload-3") + + m.mu.Lock() + defer m.mu.Unlock() + + // 检查哈希链 + if m.entries[0].PrevHash != "" { + t.Errorf("第一条 prev_hash 应为空(创世),实际 %q", m.entries[0].PrevHash) + } + if m.entries[1].PrevHash != m.entries[0].EntryHash { + t.Errorf("第2条 prev_hash 应等于第1条 entry_hash") + } + if m.entries[2].PrevHash != m.entries[1].EntryHash { + t.Errorf("第3条 prev_hash 应等于第2条 entry_hash") + } +} + +// TestTamperDetected 测试:篡改任意条目 → 校验失败 +func TestTamperDetected(t *testing.T) { + lg, m := newTestLedger(t) + ctx := context.Background() + + _ = lg.Append(ctx, 1, "payload-1") + _ = lg.Append(ctx, 2, "payload-2") + _ = lg.Append(ctx, 3, "payload-3") + + // 篡改第 2 条的 entry_hash(模拟攻击者修改数据库) + m.mu.Lock() + m.entries[1].EntryHash = "fakehash123" + m.mu.Unlock() + + res, err := lg.Verify(ctx) + if err != nil { + t.Fatalf("校验出错: %v", err) + } + if res.Valid { + t.Fatal("篡改后账本不应有效") + } + found := false + for _, e := range res.Errors { + if strings.Contains(e, "哈希链断裂") { + found = true + } + } + if !found { + t.Errorf("应报告哈希链断裂,实际错误: %v", res.Errors) + } +} + +// TestTamperSignatureDetected 测试:修改 payload_hash → 签名校验失败 +func TestTamperSignatureDetected(t *testing.T) { + lg, m := newTestLedger(t) + ctx := context.Background() + + _ = lg.Append(ctx, 1, "payload-1") + _ = lg.Append(ctx, 2, "payload-2") + + // 篡改 payload_hash(修改内容但保留原签名) + m.mu.Lock() + m.entries[0].PayloadHash = "changed" + m.mu.Unlock() + + res, err := lg.Verify(ctx) + if err != nil { + t.Fatalf("校验出错: %v", err) + } + if res.Valid { + t.Fatal("篡改后账本不应有效") + } +} + +// TestSnapshotCreateAndVerify 测试:快照创建 + 锚点校验 +func TestSnapshotCreateAndVerify(t *testing.T) { + lg, m := newTestLedger(t) + ctx := context.Background() + + _ = lg.Append(ctx, 1, "payload-1") + _ = lg.Append(ctx, 2, "payload-2") + + // 创建快照 + if err := lg.CreateSnapshot(ctx, "2026-08-08"); err != nil { + t.Fatalf("创建快照失败: %v", err) + } + + sn, _ := m.GetLatestSnapshot(ctx) + if sn == nil { + t.Fatal("快照未创建") + } + + // 快照链尾哈希应等于最后一条的 entry_hash + last, _ := m.GetLastLedgerEntry(ctx) + if sn.LastHash != last.EntryHash { + t.Errorf("快照链尾 %q 不等于账本链尾 %q", sn.LastHash, last.EntryHash) + } + + // 校验应通过且快照锚点匹配 + res, err := lg.Verify(ctx) + if err != nil { + t.Fatalf("校验失败: %v", err) + } + if !res.Valid || !res.SnapshotOK { + t.Errorf("校验应有效且快照锚点匹配,valid=%v snapshot_ok=%v", res.Valid, res.SnapshotOK) + } +} + +// TestSnapshotAnchorMismatch 测试:快照锚点不在链上 → 校验失败(真正的数据异常) +func TestSnapshotAnchorMismatch(t *testing.T) { + lg, m := newTestLedger(t) + ctx := context.Background() + + _ = lg.Append(ctx, 1, "payload-1") + _ = lg.Append(ctx, 2, "payload-2") + if err := lg.CreateSnapshot(ctx, "2026-08-08"); err != nil { + t.Fatalf("创建快照失败: %v", err) + } + + // 快照后正常追加新记录:锚点是历史锚点(仍在链上),校验应通过 + _ = lg.Append(ctx, 3, "payload-3") + + res, err := lg.Verify(ctx) + if err != nil { + t.Fatalf("校验出错: %v", err) + } + if !res.Valid || !res.SnapshotOK { + t.Errorf("快照锚点在链上(历史锚点)应有效,valid=%v snapshot_ok=%v", res.Valid, res.SnapshotOK) + } + + // 篡改快照锚点为链上不存在的哈希(模拟数据库被整体替换 / 快照被篡改) + m.mu.Lock() + m.snapshots[len(m.snapshots)-1].LastHash = "deadbeef-not-on-chain" + m.mu.Unlock() + + res, err = lg.Verify(ctx) + if err != nil { + t.Fatalf("校验出错: %v", err) + } + if res.Valid { + t.Fatal("快照锚点不在链上时账本不应标记为有效") + } + if res.SnapshotOK { + t.Error("快照锚点不在链上时 SnapshotOK 应为 false") + } +} + +// TestPublicKeyPEM 测试:公钥导出为合法 PEM +func TestPublicKeyPEM(t *testing.T) { + lg, _ := newTestLedger(t) + pem := lg.PublicKeyPEM() + if !strings.Contains(pem, "-----BEGIN PUBLIC KEY-----") { + t.Errorf("公钥应包含 PEM 头,实际: %q", pem) + } + if !strings.Contains(pem, "-----END PUBLIC KEY-----") { + t.Errorf("公钥应包含 PEM 尾,实际: %q", pem) + } +} + +// TestSignatureUnique 测试:相同内容不同事件 → 不同签名(防重放) +func TestSignatureUnique(t *testing.T) { + lg, m := newTestLedger(t) + ctx := context.Background() + + // 相同 payload 但不同 eventID + _ = lg.Append(ctx, 1, "same-payload") + _ = lg.Append(ctx, 2, "same-payload") + + m.mu.Lock() + defer m.mu.Unlock() + if m.entries[0].EntryHash == m.entries[1].EntryHash { + t.Error("不同事件不应有相同 entry_hash(防重放)") + } + if m.entries[0].Signature == m.entries[1].Signature { + t.Error("不同事件不应有相同签名(防重放)") + } +} +func itoa(n int64) string { + if n == 0 { + return "0" + } + neg := n < 0 + if neg { + n = -n + } + var b [20]byte + i := len(b) + for n > 0 { + i-- + b[i] = byte('0' + n%10) + n /= 10 + } + if neg { + i-- + b[i] = '-' + } + return string(b[i:]) +} diff --git a/internal/ledgermon/detector.go b/internal/ledgermon/detector.go new file mode 100644 index 0000000..c15421f --- /dev/null +++ b/internal/ledgermon/detector.go @@ -0,0 +1,178 @@ +package ledgermon + +import ( + "context" + "sync" + + "paygateway/internal/admin/capture" + "paygateway/internal/logger" +) + +// SeenStore 增量指纹持久化(由 store 层实现,SQLite ledger_checksums 表) +// 接口刻意最小:仅指纹集合读写,避免与 PersonalStore 全接口耦合。 +type SeenStore interface { + // LoadSeen 加载某 (设备, 账户) 已见指纹集合(服务端重启后恢复基线,防重启漏检) + LoadSeen(ctx context.Context, deviceID string, accountID int64) ([]string, error) + // SaveSeen 持久化增量后的完整指纹集合(覆盖写;集合小(可见行 ≤ 数十),全量写足够) + SaveSeen(ctx context.Context, deviceID string, accountID int64, fingerprints []string) error +} + +// IncrementalDetector 基线 + 增量检测:账本页行去重(指纹 = 时间|金额,见 capture.AlipayLedgerRow) +// +// 语义: +// - 首次读取(无基线)→ 全量建立 seen 集合,不推送(防把历史收款当新收款) +// - 后续读取 → 只推送不在 seen 集合的新行(时间|金额指纹,秒级时间戳区分同金额多笔) +// - 指纹持久化:服务端重启后从 store 恢复,重启期间的新收款不丢 +// +// 注意:baselined 与 seen 大小解耦——账本页空态(无可见行)也是有效基线, +// 否则首笔真实收款会被当作"历史"吞掉(漏收款)。 +// +// 并发安全:单账户循环内调用,但持久化读写用锁兜底(防未来多 goroutine 场景)。 +type IncrementalDetector struct { + mu sync.Mutex + deviceID string + accountID int64 + baselined bool // 是否已建立基线(与 seen 大小无关,空态页也是基线) + seen map[string]bool + store SeenStore // nil = 仅内存(不持久化) +} + +// NewDetector 创建增量检测器。store 可 nil(仅内存基线)。 +func NewDetector(deviceID string, accountID int64, store SeenStore) *IncrementalDetector { + return &IncrementalDetector{ + deviceID: deviceID, + accountID: accountID, + seen: make(map[string]bool), + store: store, + } +} + +// Diff 输入账本页当前可见行,返回新收款行(不在 seen 集合),并更新基线。 +// 基线为空(首次/重启后无持久化)→ 全部标记 seen,返回空(不推送历史)。 +// rows 可为空(空态页):仍标记基线已建立,后续新行正常推送。 +func (d *IncrementalDetector) Diff(ctx context.Context, rows []capture.AlipayLedgerRow) []capture.AlipayLedgerRow { + d.mu.Lock() + defer d.mu.Unlock() + d.restoreBaselineLocked(ctx) + + // 无基线(首次)→ 全量建基线,不推送(空态页也建基线,防首笔收款被吞) + if !d.baselined { + for _, r := range rows { + d.seen[r.Fingerprint()] = true + } + d.baselined = true + d.persistLocked(ctx) + logger.Info("ledgermon", "账本页基线已建立", + "device_id", d.deviceID, "account_id", d.accountID, "rows", len(rows)) + return nil + } + + var fresh []capture.AlipayLedgerRow + for _, r := range rows { + fp := r.Fingerprint() + if !d.seen[fp] { + d.seen[fp] = true + fresh = append(fresh, r) + } + } + if len(fresh) > 0 { + d.persistLocked(ctx) + } + return fresh +} + +// Fresh 返回不在 seen 集合的新收款行,但**不提交** seen(资金安全:先推送成功再提交, +// 推送失败下一轮仍会再次检测到该行,不会永久丢单)。首次(无基线)→ 建基线返回空。 +// rows 可为空(空态页):首次仍标记基线已建立。 +func (d *IncrementalDetector) Fresh(ctx context.Context, rows []capture.AlipayLedgerRow) []capture.AlipayLedgerRow { + d.mu.Lock() + defer d.mu.Unlock() + d.restoreBaselineLocked(ctx) + + if !d.baselined { + for _, r := range rows { + d.seen[r.Fingerprint()] = true + } + d.baselined = true + d.persistLocked(ctx) + logger.Info("ledgermon", "账本页基线已建立(Fresh)", + "device_id", d.deviceID, "account_id", d.accountID, "rows", len(rows)) + return nil + } + + var fresh []capture.AlipayLedgerRow + for _, r := range rows { + if !d.seen[r.Fingerprint()] { + fresh = append(fresh, r) + } + } + return fresh +} + +// Commit 将 fresh 行指纹提交到 seen 并持久化(Dispatch 成功后调用)。 +// 未提交的 fresh 行在下一轮 Fresh 中仍会被返回(重试推送,不丢单)。 +func (d *IncrementalDetector) Commit(ctx context.Context, fresh []capture.AlipayLedgerRow) { + if len(fresh) == 0 { + return + } + d.mu.Lock() + defer d.mu.Unlock() + changed := false + for _, r := range fresh { + if !d.seen[r.Fingerprint()] { + d.seen[r.Fingerprint()] = true + changed = true + } + } + if changed { + d.persistLocked(ctx) + } +} + +// restoreBaselineLocked 从 store 恢复基线(仅首次/重启后;调用方持锁)。 +func (d *IncrementalDetector) restoreBaselineLocked(ctx context.Context) { + if d.baselined || d.store == nil { + return + } + if stored, err := d.store.LoadSeen(ctx, d.deviceID, d.accountID); err == nil { + for _, fp := range stored { + d.seen[fp] = true + } + if len(stored) > 0 { + d.baselined = true + } + logger.Info("ledgermon", "增量基线已从持久化恢复", + "device_id", d.deviceID, "account_id", d.accountID, "fingerprints", len(stored)) + } else { + logger.Warn("ledgermon", "增量基线加载失败(将全量建基线)", "err", err, "device_id", d.deviceID) + } +} + +// persistLocked 持久化 seen 集合(调用方持锁)。集合膨胀保护:只保留最近 N 个指纹 +// (账本页可见行有限,但跨天累计会增长;保留最近 1000 个足够覆盖重试窗口)。 +const maxSeenFingerprints = 1000 + +func (d *IncrementalDetector) persistLocked(ctx context.Context) { + if d.store == nil { + return + } + // map 顺序不稳定 → 排序后保留尾部(时间戳指纹字典序≈时间序) + fps := make([]string, 0, len(d.seen)) + for fp := range d.seen { + fps = append(fps, fp) + } + // 简单稳定化:只存最近 1000 条(不排序也可,去重集合无顺序要求;截断即可) + if len(fps) > maxSeenFingerprints { + fps = fps[:maxSeenFingerprints] + } + if err := d.store.SaveSeen(ctx, d.deviceID, d.accountID, fps); err != nil { + logger.Warn("ledgermon", "增量基线持久化失败", "err", err, "device_id", d.deviceID, "account_id", d.accountID) + } +} + +// Size 返回已见指纹数(监控/调试) +func (d *IncrementalDetector) Size() int { + d.mu.Lock() + defer d.mu.Unlock() + return len(d.seen) +} diff --git a/internal/ledgermon/detector_test.go b/internal/ledgermon/detector_test.go new file mode 100644 index 0000000..dd76d67 --- /dev/null +++ b/internal/ledgermon/detector_test.go @@ -0,0 +1,129 @@ +package ledgermon + +import ( + "context" + "testing" + + "paygateway/internal/admin/capture" +) + +// memSeenStore 内存版 SeenStore(测试用,模拟 SQLite 持久化) +type memSeenStore struct { + m map[string][]string // deviceID|accountID -> fingerprints +} + +func newMemSeenStore() *memSeenStore { return &memSeenStore{m: map[string][]string{}} } + +func (s *memSeenStore) LoadSeen(_ context.Context, deviceID string, accountID int64) ([]string, error) { + return s.m[deviceID], nil +} +func (s *memSeenStore) SaveSeen(_ context.Context, deviceID string, accountID int64, fps []string) error { + s.m[deviceID] = append([]string(nil), fps...) + return nil +} + +func row(time, amount string) capture.AlipayLedgerRow { + return capture.AlipayLedgerRow{ + Payer: "**怡", + Time: time, + Amount: amount, + AmountMinor: 100, + } +} + +// TestDetector_FirstBaseline 首次读取(无基线)→ 全量建基线,不推送(防把历史收款当新收款) +func TestDetector_FirstBaseline(t *testing.T) { + d := NewDetector("dev1", 1, nil) + rows := []capture.AlipayLedgerRow{row("10:00:01", "+1.00"), row("10:00:02", "+2.00")} + fresh := d.Diff(context.Background(), rows) + if len(fresh) != 0 { + t.Fatalf("首次应建基线不推送,实际推送 %d 行", len(fresh)) + } + if d.Size() != 2 { + t.Fatalf("基线应含 2 个指纹,实际 %d", d.Size()) + } +} + +// TestDetector_Incremental 后续新增行 → 只推送新行 +func TestDetector_Incremental(t *testing.T) { + d := NewDetector("dev1", 1, nil) + // 基线 + d.Diff(context.Background(), []capture.AlipayLedgerRow{row("10:00:01", "+1.00")}) + // 新增一行 + fresh := d.Diff(context.Background(), []capture.AlipayLedgerRow{ + row("10:00:01", "+1.00"), // 已见 + row("10:00:02", "+2.00"), // 新行 + }) + if len(fresh) != 1 || fresh[0].Time != "10:00:02" { + t.Fatalf("应只推送新行 10:00:02,实际 %+v", fresh) + } +} + +// TestDetector_SameAmountMulti 同金额多笔:靠秒级时间戳区分,两笔都推送 +func TestDetector_SameAmountMulti(t *testing.T) { + d := NewDetector("dev1", 1, nil) + d.Diff(context.Background(), []capture.AlipayLedgerRow{row("10:00:01", "+1.00")}) + fresh := d.Diff(context.Background(), []capture.AlipayLedgerRow{ + row("10:00:01", "+1.00"), + row("10:00:02", "+1.00"), // 同金额不同秒 + }) + if len(fresh) != 1 || fresh[0].Time != "10:00:02" { + t.Fatalf("同金额不同秒应作为新行推送,实际 %+v", fresh) + } +} + +// TestDetector_NoChange 页面无变化(重复读取)→ 不推送 +func TestDetector_NoChange(t *testing.T) { + d := NewDetector("dev1", 1, nil) + rows := []capture.AlipayLedgerRow{row("10:00:01", "+1.00")} + d.Diff(context.Background(), rows) + if fresh := d.Diff(context.Background(), rows); len(fresh) != 0 { + t.Fatalf("重复行不应推送,实际 %d 行", len(fresh)) + } +} + +// TestDetector_RestartRestore 服务端重启:从 store 恢复基线,重启期间的新收款不丢 +func TestDetector_RestartRestore(t *testing.T) { + store := newMemSeenStore() + // 第一次运行:建基线(10:00:01 已见)+ 新收款 10:00:02 + d1 := NewDetector("dev1", 1, store) + d1.Diff(context.Background(), []capture.AlipayLedgerRow{row("10:00:01", "+1.00")}) + d1.Diff(context.Background(), []capture.AlipayLedgerRow{ + row("10:00:01", "+1.00"), row("10:00:02", "+2.00"), + }) + // 模拟重启:新 detector 从 store 恢复 + d2 := NewDetector("dev1", 1, store) + // 重启期间新收款 10:00:03 已出现在账本页 + fresh := d2.Diff(context.Background(), []capture.AlipayLedgerRow{ + row("10:00:01", "+1.00"), // 历史已见 → 不推 + row("10:00:02", "+2.00"), // 重启前已处理 → 不推 + row("10:00:03", "+3.00"), // 重启期间新收款 → 推 + }) + if len(fresh) != 1 || fresh[0].Time != "10:00:03" { + t.Fatalf("重启后应只推送新收款 10:00:03,实际 %+v", fresh) + } +} + +// TestDetector_RestartWithoutStore 重启但无持久化 → 重新全量建基线(不推送历史) +func TestDetector_RestartWithoutStore(t *testing.T) { + d1 := NewDetector("dev1", 1, nil) + d1.Diff(context.Background(), []capture.AlipayLedgerRow{row("10:00:01", "+1.00")}) + // 重启(无 store):全部行视为历史基线 + d2 := NewDetector("dev1", 1, nil) + fresh := d2.Diff(context.Background(), []capture.AlipayLedgerRow{ + row("10:00:01", "+1.00"), row("10:00:02", "+2.00"), + }) + if len(fresh) != 0 { + t.Fatalf("无持久化重启应全量建基线不推送,实际 %d 行", len(fresh)) + } +} + +// TestDetector_PersistAfterBaseline 基线建立后应持久化(store 有数据) +func TestDetector_PersistAfterBaseline(t *testing.T) { + store := newMemSeenStore() + d := NewDetector("dev1", 1, store) + d.Diff(context.Background(), []capture.AlipayLedgerRow{row("10:00:01", "+1.00")}) + if stored := store.m["dev1"]; len(stored) != 1 { + t.Fatalf("基线应持久化 1 个指纹,实际 %d", len(stored)) + } +} diff --git a/internal/ledgermon/device_actor.go b/internal/ledgermon/device_actor.go new file mode 100644 index 0000000..5fc90dc --- /dev/null +++ b/internal/ledgermon/device_actor.go @@ -0,0 +1,577 @@ +// Package ledgermon 服务端账本页 adb 监听(DeviceActor 模型,2026-08-28 重构) +// +// DeviceActor 模型:每台设备恰好一个 DeviceActor goroutine,串行消费命令队列。 +// 所有 adb 操作(导航/读取/锁定/手动预热)只通过 actor 的命令执行。 +// 无 lease、无仲裁、无抢占,竞态从结构上消失(docs/DESIGN-DEVICE-ACTOR.md)。 +package ledgermon + +import ( + "context" + "fmt" + "sync" + "sync/atomic" + "time" + + "paygateway/internal/adbmgr" + "paygateway/internal/admin/capture" + "paygateway/internal/logger" +) + +// ========== 命令接口 ========== + +// Command 设备操作命令(actor 队列元素,串行执行) +type Command interface { + Execute(ctx context.Context, d *DeviceCtx) error +} + +// ========== 命令实现 ========== + +// WarmupCmd 预热:导航到指定账户账本页 + 建立基线(设备上线时入队) +type WarmupCmd struct { + AccountID int64 + Provider string // alipay / wechat + UserID int // 0=主空间,999=双开 + Done chan error // 完成信号(锁定协调器/状态查询等待用) +} + +func (c *WarmupCmd) Execute(ctx context.Context, d *DeviceCtx) error { + acct := d.Account(c.Provider, c.UserID) + if acct == nil { + return fmt.Errorf("device_actor: 账户上下文不存在 provider=%s user_id=%d", c.Provider, c.UserID) + } + // 预热优先走 Warmup(task lock 热启动优先 → 账本页栈不存在时冷启动坐标导航建页) + if w, ok := acct.Reader.(Warmer); ok { + if err := w.Warmup(ctx); err != nil { + logger.Warn("device_actor", "预热导航失败", + "err", err, "device_id", d.DeviceID, "account_id", c.AccountID) + return err + } + } else if err := acct.Reader.EnsureForeground(ctx); err != nil { + logger.Warn("device_actor", "预热导航失败(无 Warmer)", + "err", err, "device_id", d.DeviceID, "account_id", c.AccountID) + return err + } + // 读取基线(冷启动导航完成时账本页即最新数据,优先用 ReadFresh 免下拉刷新) + var rows []capture.AlipayLedgerRow + var err error + if fr, ok := acct.Reader.(FreshReader); ok { + rows, err = fr.ReadFresh(ctx) + } else { + rows, err = acct.Reader.Read(ctx) + } + if err != nil { + logger.Warn("device_actor", "预热基线读取失败", + "err", err, "device_id", d.DeviceID, "account_id", c.AccountID) + return err + } + // 空行也走 Diff:空态页是有效基线,保证首笔真实收款不被当历史吞掉 + acct.Detector.Diff(ctx, rows) + logger.Info("device_actor", "预热完成:账本页已就绪,基线已建立", + "device_id", d.DeviceID, "account_id", c.AccountID, "user_id", c.UserID, "rows", len(rows)) + return nil +} + +// PollCmd 轮询:热启动恢复账本页 + 读取 + 增量检测 + 推送(调度器入队)。 +// Done 可选(nil=投递后不等待,兼容旧调用方);非 nil 时调度器等待执行完成(pull 模式)。 +type PollCmd struct { + AccountID int64 + Provider string // alipay / wechat + UserID int // 0=主空间,999=双开 + Done chan error +} + +func (c *PollCmd) Execute(ctx context.Context, d *DeviceCtx) error { + acct := d.Account(c.Provider, c.UserID) + if acct == nil { + return nil // 账户已删除/离线,静默跳过 + } + // 热启动恢复前台(绝不冷启动,失败本轮跳过) + if err := acct.Reader.EnsureForeground(ctx); err != nil { + logger.Warn("device_actor", "轮询恢复前台失败,本轮跳过", + "err", err, "device_id", d.DeviceID, "account_id", c.AccountID) + // 热启动连续失败 → 任务栈大概率已丢失,升级冷启动预热自愈(防永久漏检)。 + if acct.bumpPollFail() >= pollFailWarmupThreshold { + logger.Warn("device_actor", "热启动连续失败,重投预热命令自愈(冷启动重建账本页)", + "device_id", d.DeviceID, "account_id", c.AccountID, "failures", acct.pollFailures()) + d.Enqueue(&WarmupCmd{AccountID: c.AccountID, Provider: c.Provider, UserID: c.UserID}) + } + d.RecordPoll(false) + return nil + } + acct.resetPollFail() + rows, err := acct.Reader.Read(ctx) + if err != nil { + logger.Warn("device_actor", "轮询读取失败", + "err", err, "device_id", d.DeviceID, "account_id", c.AccountID) + d.RecordPoll(false) + return nil + } + d.RecordPoll(true) + // 资金安全:先检测新行(Fresh 不提交 seen),推送成功后才 Commit—— + // Dispatch 失败时该行下轮仍会被检测到并重试,绝不永久丢单。 + fresh := acct.Detector.Fresh(ctx, rows) + if len(fresh) == 0 { + return nil + } + if d.Dispatch == nil { + acct.Detector.Commit(ctx, fresh) + return nil + } + if err := d.Dispatch(ctx, d.DeviceID, acct.Provider, acct.UserID, fresh); err != nil { + logger.Error("device_actor", "收款事件推送失败(行保留待重试,不提交指纹)", + "err", err, "device_id", d.DeviceID, "account_id", c.AccountID, "rows", len(fresh)) + return nil + } + acct.Detector.Commit(ctx, fresh) + return nil +} + +// ManualWarmupCmd 手动预热:完整冷启动导航 + 锁定任务(admin 触发,同步等结果) +type ManualWarmupCmd struct { + // Providers 指定要预热的通道:{"alipay":true,"wechat":true},空 = 全部通道 + Providers map[string]bool + Done chan error +} + +func (c *ManualWarmupCmd) Execute(ctx context.Context, d *DeviceCtx) error { + // 按固定顺序遍历账户(key 排序保证确定性) + for _, key := range d.SortedAccountKeys() { + acct := d.Accounts()[key] + if c.Providers != nil && !c.Providers[acct.Provider] { + continue + } + switch acct.Provider { + case "alipay": + if err := capture.CaptureAlipayLedger(ctx, d.Adb, d.Serial, acct.UserID); err != nil { + return fmt.Errorf("支付宝预热失败 (user_id=%d): %w", acct.UserID, err) + } + case "wechat": + if err := capture.CaptureWechatLedger(ctx, d.Adb, d.Serial, acct.UserID); err != nil { + return fmt.Errorf("微信预热失败 (user_id=%d): %w", acct.UserID, err) + } + } + } + // 全部账户导航完成后锁定支付任务卡片(支付宝+微信,每应用锁主进程一张)。 + // 锁定失败不致命:热启动恢复降级为冷启动导航(EnsureForeground 兜底)。 + if _, err := capture.LockPaymentTasks(ctx, d.Adb, d.Serial, "支付宝", "微信"); err != nil { + logger.Warn("device_actor", "手动预热后锁定任务卡片失败", + "err", err, "device_id", d.DeviceID) + } + return nil +} + +// LockCmd 锁定任务卡片(防一键清理;全部账户预热完成后自动入队) +type LockCmd struct { + Done chan error +} + +func (c *LockCmd) Execute(ctx context.Context, d *DeviceCtx) error { + // 先应用命令级保活(电池白名单/后台运行豁免/禁用缓存冻结),再锁任务卡片(防一键清理)。 + // 命令级保活幂等,重复执行无害;失败不阻断锁任务(锁任务是热启动恢复的关键)。 + for _, pkg := range []string{adbmgr.WechatPkg, adbmgr.AlipayPkg} { + if _, err := d.Adb.KeepAliveApply(ctx, d.Serial, pkg); err != nil { + logger.Warn("device_actor", "保活命令应用失败(不阻断锁任务)", + "err", err, "device_id", d.DeviceID, "pkg", pkg) + } + } + if err := d.Adb.KeepAliveDeviceFreezer(ctx, d.Serial); err != nil { + logger.Warn("device_actor", "禁用缓存冻结失败(不阻断锁任务)", + "err", err, "device_id", d.DeviceID) + } + locked, err := capture.LockPaymentTasks(ctx, d.Adb, d.Serial, "支付宝", "微信") + if err != nil { + logger.Warn("device_actor", "设备任务锁定失败(热启动恢复将降级为冷启动)", + "err", err, "device_id", d.DeviceID) + return err + } + logger.Info("device_actor", "设备支付任务已全部锁定(防一键清理)", + "device_id", d.DeviceID, "locked", locked) + return nil +} + +// ========== 设备执行上下文 ========== + +// AccountCtx 账户级执行上下文(每个 (device, provider, userID) 一个) +type AccountCtx struct { + AccountID int64 + Provider string + UserID int + Reader LedgerReader + Detector *IncrementalDetector + + // pollFail 热启动连续失败次数(actor 单 goroutine 内读写,无并发) + pollFail int +} + +// pollFailWarmupThreshold 热启动连续失败升级阈值:达到后重投 WarmupCmd 冷启动自愈。 +// 3 次 ≈ 3 个轮询周期(1s 间隔),任务栈丢失时热启动必然失败,及时冷启动恢复。 +const pollFailWarmupThreshold = 3 + +// warmupRetryDelay 设备上线预热失败后的重试退避间隔。预热建基线是必要动作, +// 偶发失败(uiautomator dump 冷启动被 kill)后短暂退避重试,避免立即重试撞同一次故障。 +const warmupRetryDelay = 5 * time.Second + +func (a *AccountCtx) bumpPollFail() int { a.pollFail++; return a.pollFail } +func (a *AccountCtx) pollFailures() int { return a.pollFail } +func (a *AccountCtx) resetPollFail() { a.pollFail = 0 } + +// DeviceCtx 设备级执行上下文。Adb/Serial 供 ManualWarmupCmd/LockCmd 直连 capture。 +type DeviceCtx struct { + DeviceID string + Serial string + Adb *adbmgr.Manager + Dispatch DispatchFunc + // actor 设备执行者(命令自愈时向队列投递新命令用,避免重入锁) + actor *DeviceActor + // accounts 账户上下文快照(atomic.Pointer:reconcile 写、actor 读无锁,杜绝 map 并发读写崩溃) + accounts atomic.Pointer[map[string]*AccountCtx] +} + +// Enqueue 向本设备命令队列投递命令(自愈等 actor 内部路径使用) +func (d *DeviceCtx) Enqueue(cmd Command) { d.actor.Enqueue(cmd) } + +// RecordPoll 记录一次 poll 成败(设备健康度滑动窗口,均衡分配器数据源) +func (d *DeviceCtx) RecordPoll(ok bool) { + if d.actor != nil { + d.actor.RecordPoll(ok) + } +} + +// Accounts 返回账户上下文快照(不可变 map,安全并发读) +func (d *DeviceCtx) Accounts() map[string]*AccountCtx { + return *d.accounts.Load() +} + +// Account 按 provider+userID 查找账户上下文 +func (d *DeviceCtx) Account(provider string, userID int) *AccountCtx { + return (*d.accounts.Load())[monitorKey(d.DeviceID, provider, userID)] +} + +// SortedAccountKeys 返回按 key 排序的账户列表(保证执行顺序确定性) +func (d *DeviceCtx) SortedAccountKeys() []string { + accts := d.Accounts() + keys := make([]string, 0, len(accts)) + for k := range accts { + keys = append(keys, k) + } + for i := 0; i < len(keys); i++ { + for j := i + 1; j < len(keys); j++ { + if keys[i] > keys[j] { + keys[i], keys[j] = keys[j], keys[i] + } + } + } + return keys +} + +// ========== DeviceActor ========== + +// maxQueue 命令队列上限:WarmupCmd/PollCmd 队满(>maxQueue)时丢弃 PollCmd +// (跳过一轮,资金安全优先),保留 WarmupCmd/ManualWarmupCmd。 +const maxQueue = 16 + +// DeviceActor 每设备单执行者:串行消费命令队列,唯一 adb 访问者。 +type DeviceActor struct { + DeviceID string + DeviceCtx *DeviceCtx + + mu sync.Mutex + queue []Command + pendingPoll map[int64]bool // 已在队列中的 PollCmd(按 accountID 去重) + cond *sync.Cond + + current atomic.Value // string:当前执行命令名(可观测),空=空闲 + last atomic.Value // lastResult:最近一次命令执行结果(可观测) + stop chan struct{} + done chan struct{} + + // health 设备 poll 健康度滑动窗口(均衡分配器数据源,docs/PLAN-DYNAMIC-CLOSE-ALLOC.md §6) + health *healthWindow +} + +// healthWindow 设备 poll 成功率滑动窗口(最近 size 次,无历史中性 0.9)。 +// 并发安全(RecordPoll 由 actor goroutine + 分配器读并发调用)。 +type healthWindow struct { + mu sync.Mutex + ok []bool + next int + filled bool + size int +} + +func newHealthWindow(size int) *healthWindow { + if size <= 0 { + size = 20 + } + return &healthWindow{ok: make([]bool, size), size: size} +} + +func (h *healthWindow) record(ok bool) { + h.mu.Lock() + defer h.mu.Unlock() + h.ok[h.next] = ok + h.next = (h.next + 1) % h.size + if h.next == 0 { + h.filled = true + } +} + +func (h *healthWindow) health() float64 { + h.mu.Lock() + defer h.mu.Unlock() + if !h.filled { + n := h.next + if n == 0 { + return 0.9 // 无历史:中性,不因无数据饿死新设备 + } + sum := 0 + for i := 0; i < n; i++ { + if h.ok[i] { + sum++ + } + } + return float64(sum) / float64(n) + } + sum := 0 + for _, v := range h.ok { + if v { + sum++ + } + } + return float64(sum) / float64(h.size) +} + +// cmdName 返回命令的简短描述(可观测) +func cmdName(cmd Command) string { + switch cmd.(type) { + case *WarmupCmd: + return "warmup" + case *PollCmd: + return "poll" + case *ManualWarmupCmd: + return "manual_warmup" + case *LockCmd: + return "lock" + default: + return "unknown" + } +} + +// NewDeviceActor 创建设备执行者并启动后台 goroutine。 +func NewDeviceActor(deviceID, serial string, adb *adbmgr.Manager, dispatch DispatchFunc) *DeviceActor { + accounts := make(map[string]*AccountCtx) + a := &DeviceActor{ + DeviceID: deviceID, + DeviceCtx: &DeviceCtx{ + DeviceID: deviceID, + Serial: serial, + Adb: adb, + Dispatch: dispatch, + }, + pendingPoll: make(map[int64]bool), + stop: make(chan struct{}), + done: make(chan struct{}), + health: newHealthWindow(0), + } + a.DeviceCtx.actor = a + a.DeviceCtx.accounts.Store(&accounts) + a.cond = sync.NewCond(&a.mu) + a.current.Store("") + a.last.Store(lastResult{}) + go a.loop() + return a +} + +// AddAccount 添加账户上下文(reconcile 时调用;幂等:已存在时覆盖)。 +// 写路径:锁内复制 map 快照后原子替换——读方(actor 命令执行)无锁 Load,无数据竞态。 +func (a *DeviceActor) AddAccount(acct *AccountCtx) { + a.mu.Lock() + defer a.mu.Unlock() + old := a.DeviceCtx.Accounts() + next := make(map[string]*AccountCtx, len(old)+1) + for k, v := range old { + next[k] = v + } + next[monitorKey(a.DeviceID, acct.Provider, acct.UserID)] = acct + a.DeviceCtx.accounts.Store(&next) +} + +// RemoveAccount 移除账户上下文(设备离线时调用) +func (a *DeviceActor) RemoveAccount(key string) { + a.mu.Lock() + defer a.mu.Unlock() + old := a.DeviceCtx.Accounts() + if _, ok := old[key]; !ok { + return + } + next := make(map[string]*AccountCtx, len(old)) + for k, v := range old { + if k != key { + next[k] = v + } + } + a.DeviceCtx.accounts.Store(&next) +} + +// Enqueue 投递命令到队列(非阻塞)。 +// - PollCmd:同账户去重(已有在队则合并);队满(>maxQueue)时丢弃(宁可跳过一轮)。 +// - ManualWarmupCmd:插队到队首(手动操作优先,但不打断执行中的命令——只影响排队顺序)。 +// - WarmupCmd/LockCmd:正常入队(数量有限,不丢弃)。 +func (a *DeviceActor) Enqueue(cmd Command) { + a.mu.Lock() + defer a.mu.Unlock() + + switch c := cmd.(type) { + case *PollCmd: + if a.pendingPoll[c.AccountID] { + return // 同账户已在队中,合并(防下单风暴堆积) + } + if len(a.queue) >= maxQueue { + logger.Warn("device_actor", "命令队列已满,丢弃 PollCmd", + "device_id", a.DeviceID, "account_id", c.AccountID) + return + } + a.pendingPoll[c.AccountID] = true + a.queue = append(a.queue, cmd) + case *ManualWarmupCmd: + // 插队到队首:手动操作优先,但执行中的命令不被打断(原子性保证) + a.queue = append([]Command{cmd}, a.queue...) + default: + a.queue = append(a.queue, cmd) + } + a.cond.Signal() +} + +// CurrentCmd 返回当前执行命令名(可观测) +func (a *DeviceActor) CurrentCmd() string { + s, _ := a.current.Load().(string) + return s +} + +// QueueLen 返回当前队列长度(可观测) +func (a *DeviceActor) QueueLen() int { + a.mu.Lock() + defer a.mu.Unlock() + return len(a.queue) +} + +// Stop 停止执行者(等待当前命令执行完)。队列中残留命令不再执行, +// 其 Done 等待者(Monitor.warmupOnce/WarmupDevice)由 loop 退出时统一唤醒,避免永久阻塞。 +func (a *DeviceActor) Stop() { + close(a.stop) + a.cond.Broadcast() + <-a.done +} + +// loop 执行循环:串行取命令 → 执行 → 唤醒等待者 +func (a *DeviceActor) loop() { + defer close(a.done) + ctx := context.Background() + for { + cmd := a.pop() + if cmd == nil { + // 已停止:唤醒队列中残留命令的 Done 等待者(不再执行,直接报错返回) + a.abortQueued() + return + } + name := cmdName(cmd) + a.current.Store(name) + err := cmd.Execute(ctx, a.DeviceCtx) + a.setLastResult(name, err) + // 先记录结果再唤醒等待者:Status 能看到最后命令及其错误 + signalCmdDone(cmd, err) + a.current.Store("") + } +} + +// abortQueued 停止时对队列中所有残留命令的 Done 等待者回发错误(防永久阻塞)。 +func (a *DeviceActor) abortQueued() { + a.mu.Lock() + queue := a.queue + a.queue = nil + a.mu.Unlock() + for _, cmd := range queue { + signalCmdDone(cmd, fmt.Errorf("device_actor: 设备执行者已停止,命令未执行")) + } +} + +// lastResult 最近一次执行结果(可观测:Status 展示,供 admin 诊断连续失败) +type lastResult struct { + cmd string + err error + at time.Time +} + +func (a *DeviceActor) setLastResult(cmd string, err error) { + a.last.Store(lastResult{cmd: cmd, err: err, at: time.Now()}) +} + +// LastResult 返回最近一次命令执行结果(Status 展示) +func (a *DeviceActor) LastResult() (string, error) { + lr := a.last.Load().(lastResult) + return lr.cmd, lr.err +} + +// RecordPoll 记录一次 poll 成败(均衡分配器健康度数据源) +func (a *DeviceActor) RecordPoll(ok bool) { + if a.health != nil { + a.health.record(ok) + } +} + +// PollHealth 设备 poll 成功率(0~1;无历史返回 0.9 中性) +func (a *DeviceActor) PollHealth() float64 { + if a.health == nil { + return 0.9 + } + return a.health.health() +} + +// pop 阻塞等待直到有命令可执行或收到停止信号。 +// 每轮先查 stop 再取命令——停止后不再执行队列中残留命令(Stop 只等当前命令,不排空队列)。 +func (a *DeviceActor) pop() Command { + a.mu.Lock() + defer a.mu.Unlock() + for { + select { + case <-a.stop: + return nil + default: + } + if len(a.queue) > 0 { + cmd := a.queue[0] + a.queue = a.queue[1:] + if pc, ok := cmd.(*PollCmd); ok { + delete(a.pendingPoll, pc.AccountID) + } + return cmd + } + a.cond.Wait() + } +} + +// signalCmdDone 通知命令完成(幂等:无 Done 通道的命令直接返回) +func signalCmdDone(cmd Command, err error) { + var done chan error + switch c := cmd.(type) { + case *WarmupCmd: + done = c.Done + case *PollCmd: + done = c.Done + case *ManualWarmupCmd: + done = c.Done + case *LockCmd: + done = c.Done + } + if done != nil { + done <- err // 缓冲 1,actor 不回读,不会阻塞 + } +} + +// SeenRows 返回某账户已见指纹数(增量基线大小,Status 展示) +func (a *DeviceActor) SeenRows(provider string, userID int) int { + if acct := a.DeviceCtx.Account(provider, userID); acct != nil && acct.Detector != nil { + return acct.Detector.Size() + } + return 0 +} diff --git a/internal/ledgermon/monitor.go b/internal/ledgermon/monitor.go new file mode 100644 index 0000000..8d22e09 --- /dev/null +++ b/internal/ledgermon/monitor.go @@ -0,0 +1,652 @@ +package ledgermon + +import ( + "context" + "fmt" + "runtime/debug" + "sync" + "sync/atomic" + "time" + + "paygateway/internal/adbmgr" + "paygateway/internal/admin/capture" + "paygateway/internal/logger" + "paygateway/internal/pool" +) + +// DispatchFunc 收款事件推送回调(由 main.go 注入:复用匹配+入账链路,见 docs §6.1) +// 行无备注,匹配走金额+时间窗口;source=ledger。space 为账本页所在用户空间(0=主,999=双开), +// 透传给匹配链路区分双开账户收款。 +type DispatchFunc func(ctx context.Context, deviceID string, provider string, space int, rows []capture.AlipayLedgerRow) error + +// LedgerMonitor 每账户命令生产者(DeviceActor 模型,docs/DESIGN-DEVICE-ACTOR.md §4.2) +// +// 从"拥有 goroutine 的执行者"降级为**纯命令生产者**:保留账户级状态机 +// (订单驱动暂停/激活/终结判定),所有 adb 操作通过 DeviceActor 命令队列执行, +// 由 actor 串行化(每设备单执行者),天然互斥、无抢占。 +type LedgerMonitor struct { + accountID int64 + deviceID string + provider string + serial string + userId int // 0=主空间,999=双开分身 + // actor 设备执行者(投递命令用,reconcile 创建 monitor 时注入) + actor *DeviceActor + // hasPending 该账户是否仍有待支付订单(main.go 注入:ListWaitPayByAccount 非空) + hasPending func(ctx context.Context, accountID int64) bool + pollEvery time.Duration // 有待支付订单时的轮询间隔(默认 1s) + wake chan struct{} // 下单激活信号(非阻塞发送) + stop chan struct{} + done chan struct{} + // monitorActive 当前是否处于轮询态(admin 状态展示) + monitorActive atomic.Bool + // warmupDone 预热完成信号(close 后 Manager 可判断设备所有账户已就绪) + warmupDone chan struct{} + // schedulerMode 由 DeviceScheduler 接管轮询(公平调度);本 monitor 只做预热。 + // 订单驱动轮询语义移交给调度器,monitor 完成预热后等待 stop(不再投 PollCmd)。 + schedulerMode bool +} + +// NewMonitor 创建账户命令生产者。pollEvery<=0 用默认 1s。 +func NewMonitor(accountID int64, deviceID, provider, serial string, userId int, + actor *DeviceActor, hasPending func(ctx context.Context, accountID int64) bool, + pollEvery time.Duration) *LedgerMonitor { + if pollEvery <= 0 { + pollEvery = 1 * time.Second + } + return &LedgerMonitor{ + accountID: accountID, deviceID: deviceID, provider: provider, serial: serial, userId: userId, + actor: actor, hasPending: hasPending, pollEvery: pollEvery, + wake: make(chan struct{}, 1), stop: make(chan struct{}), done: make(chan struct{}), + warmupDone: make(chan struct{}), + } +} + +// Start 启动命令生产者(后台 goroutine,初始暂停态) +func (m *LedgerMonitor) Start() { go m.loop() } + +// Stop 停止命令生产者(等待退出) +func (m *LedgerMonitor) Stop() { + close(m.stop) + <-m.done +} + +// SetSchedulerMode 启用公平调度模式:monitor 只做预热,轮询由 DeviceScheduler 接管。 +// 必须在 Start() 之前调用。 +func (m *LedgerMonitor) SetSchedulerMode() { m.schedulerMode = true } + +// Activate 下单钩子:唤醒命令生产者(非阻塞;已在轮询则幂等)。 +// 唤醒后向 actor 周期投递 PollCmd,轮询持续到该账户待支付订单终结。 +// 公平调度模式下唤醒被忽略(轮询由 DeviceScheduler 统一决策)。 +func (m *LedgerMonitor) Activate() { + if m.schedulerMode { + return + } + select { + case m.wake <- struct{}{}: + default: // 已在唤醒队列/轮询中 + } +} + +// IsActive 是否处于轮询态(admin 状态展示) +func (m *LedgerMonitor) IsActive() bool { return m.monitorActive.Load() } + +// loop 订单驱动命令生产循环:预热 → 等待唤醒 → 有待支付订单才周期投递 PollCmd → +// 订单终结暂停。命令只入队不执行——执行在 DeviceActor 内串行完成(天然互斥)。 +func (m *LedgerMonitor) loop() { + defer close(m.done) + ctx := context.Background() + // 预热:投递 WarmupCmd 等待完成(设备上线即导航建基线,不依赖订单)。 + // actor 串行执行,多账户预热天然排队,无互相抢占。 + m.warmupOnce(ctx) + // 公平调度模式:轮询由 DeviceScheduler 统一决策(读内存池快照), + // monitor 预热完成后仅响应 stop(不投 PollCmd,避免双生产者)。 + if m.schedulerMode { + <-m.stop + return + } + for { + // 暂停态:等待下单唤醒 + select { + case <-m.stop: + return + case <-m.wake: + } + // 唤醒后确认有待支付订单;无(竞态:下单后立即终结)→ 直接回暂停 + if !m.hasPendingOrder(ctx) { + continue + } + // 轮询态:每次 poll 后检查订单,终结立即暂停(不空转) + m.monitorActive.Store(true) + for { + select { + case <-m.stop: + return + case <-time.After(m.pollEvery): + } + m.pollOnce(ctx) + if !m.hasPendingOrder(ctx) { + m.monitorActive.Store(false) + logger.Info("ledgermon", "账户待支付订单已终结,监听暂停", + "account_id", m.accountID, "device_id", m.deviceID) + break + } + } + } +} + +// warmupOnce 投递预热命令并等待完成。 +// 预热失败重试(带退避):设备上线预热建基线是必要动作,单次偶发失败(如 +// uiautomator dump 冷启动被 kill —— 真机实测 account3 一次 `signal: killed` +// 后整会话无基线,导致后续订单查询全失败)不应永久搁浅。此处是设备上线 +// 无实时回调的安全位置,冷启动重试不会干扰订单支付确认。 +// 预热命令在 actor 队列里与轮询/手动预热串行,天然无竞态。 +func (m *LedgerMonitor) warmupOnce(ctx context.Context) { + const maxRetries = 3 + var lastErr error + for attempt := 0; attempt <= maxRetries; attempt++ { + done := make(chan error, 1) + m.actor.Enqueue(&WarmupCmd{ + AccountID: m.accountID, + Provider: m.provider, + UserID: m.userId, + Done: done, + }) + lastErr = <-done // 等 actor 执行完 + if lastErr == nil { + break + } + if attempt < maxRetries { + logger.Warn("ledgermon", "预热失败,即将重试", + "account_id", m.accountID, "provider", m.provider, + "attempt", attempt+1, "err", lastErr) + select { + case <-ctx.Done(): + return + case <-time.After(warmupRetryDelay): + } + } + } + if lastErr != nil { + logger.Warn("ledgermon", "预热多次重试后仍失败", + "account_id", m.accountID, "provider", m.provider, "err", lastErr) + } + m.signalWarmupDone() +} + +// pollOnce 投递单轮轮询命令(不等待执行——actor 串行队列保证不重叠)。 +func (m *LedgerMonitor) pollOnce(ctx context.Context) { + m.actor.Enqueue(&PollCmd{AccountID: m.accountID, Provider: m.provider, UserID: m.userId}) +} + +// hasPendingOrder 该账户是否仍有待支付订单(未注入时视为有,保持轮询) +func (m *LedgerMonitor) hasPendingOrder(ctx context.Context) bool { + if m.hasPending == nil { + return true + } + return m.hasPending(ctx, m.accountID) +} + +// signalWarmupDone 标记预热完成(幂等:close 一次后再次调用无副作用)。 +func (m *LedgerMonitor) signalWarmupDone() { + select { + case <-m.warmupDone: + default: + close(m.warmupDone) + } +} + +// WarmupDone 返回预热完成信号(Manager 设备级编排用:全部账户就绪后统一锁定任务) +func (m *LedgerMonitor) WarmupDone() <-chan struct{} { return m.warmupDone } + +// ========== 编排管理器 ========== + +// MonitoredDevice 一台待监听设备(由 main.go 解析注入:设备 + 可服务账户 + adb serial) +type MonitoredDevice struct { + DeviceID string + Serial string + Provider string + AccountID int64 + UserID int // 0=主空间,999=双开 +} + +// DeviceResolver 设备解析回调(main.go 注入:从 personal.db 枚举在线设备 + 账户 + serial) +type DeviceResolver func(ctx context.Context) ([]MonitoredDevice, error) + +// ReaderFactory 按设备构造读取器(main.go 注入:封装 adb 管理器 + 通道分派) +type ReaderFactory func(d MonitoredDevice) LedgerReader + +// Manager 监听编排(DeviceActor 模型):按设备解析账户 → 创建 DeviceActor + +// 命令生产者(公平调度模式:DeviceScheduler;旧模式:LedgerMonitor)→ 状态查询。 +// 每设备一个 actor,串行执行全部 adb 操作。 +type Manager struct { + mu sync.Mutex + actors map[string]*DeviceActor // key: deviceID + monitors map[string]*LedgerMonitor // key: monitorKey(deviceID, provider, userID) + scheds map[string]*DeviceScheduler // key: deviceID(公平调度模式;pool 非 nil 时启用) + resolver DeviceResolver + factory ReaderFactory + dispatch DispatchFunc + adb *adbmgr.Manager + hasPending func(ctx context.Context, accountID int64) bool + pollEvery time.Duration + store SeenStore + // pool WAIT_PAY 内存订单池(非 nil → 公平调度模式:DeviceScheduler 接管轮询) + pool *pool.WaitPayPool + // lockArmed 每设备锁定协调是否已启动(map[deviceID]struct{},防重复启动) + lockArmed map[string]struct{} + // lockDone 每设备锁定是否已完成(map[deviceID]struct{},防重复执行) + lockDone map[string]struct{} + // warmupInFlight 每设备手动预热是否进行中(map[deviceID]bool,防重复点击堆积) + warmupInFlight map[string]bool +} + +// monitorKey 账户命令生产者唯一键:(deviceID, provider, userID)。 +// 同一设备的账本页实例 = 每 (平台, 用户空间) 一个(支付宝主空间/双开、微信主空间/双开各不同), +// 必须含 provider 维度——否则 alipay@user0 与 wechat@user0 会碰撞覆盖。 +func monitorKey(deviceID, provider string, userID int) string { + return fmt.Sprintf("%s|%s|%d", deviceID, provider, userID) +} + +// NewManager 创建编排器。store 可 nil(增量仅内存)。 +// hasPending 判断账户是否仍有待支付订单(main.go 注入;nil = 视为恒有,仅测试用)。 +// adb 用于 ManualWarmupCmd/LockCmd 直连 capture(可 nil:不启用设备级手动预热/锁定)。 +// waitPayPool 非 nil → 公平调度模式:DeviceScheduler 按内存池快照统一决策轮询(推荐); +// nil → 旧模式:LedgerMonitor 订单驱动轮询(测试/无池环境兼容)。 +func NewManager(adb *adbmgr.Manager, resolver DeviceResolver, factory ReaderFactory, + dispatch DispatchFunc, hasPending func(ctx context.Context, accountID int64) bool, + pollEvery time.Duration, store SeenStore, waitPayPool *pool.WaitPayPool) *Manager { + if pollEvery <= 0 { + pollEvery = 1 * time.Second + } + return &Manager{ + adb: adb, resolver: resolver, factory: factory, dispatch: dispatch, + hasPending: hasPending, pollEvery: pollEvery, store: store, + pool: waitPayPool, + actors: make(map[string]*DeviceActor), + monitors: make(map[string]*LedgerMonitor), + scheds: make(map[string]*DeviceScheduler), + lockArmed: make(map[string]struct{}), + lockDone: make(map[string]struct{}), + warmupInFlight: make(map[string]bool), + } +} + +// Start 启动编排:周期 reconcile(设备增删/下线自动收敛)。 +func (m *Manager) Start(ctx context.Context) { + go m.reconcileLoop(ctx) +} + +// reconcileLoop 周期扫描设备(30s):新增设备创建 actor/生产者,消失设备停止 +func (m *Manager) reconcileLoop(ctx context.Context) { + ticker := time.NewTicker(30 * time.Second) + defer ticker.Stop() + m.reconcileOnce(ctx) + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + m.reconcileOnce(ctx) + } + } +} + +// reconcileOnce 按设备收敛:创建设备 DeviceActor(幂等),为每个账户创建命令生产者, +// 并向 actor 投递 WarmupCmd。设备消失 → 停止 actor 与全部生产者。 +func (m *Manager) reconcileOnce(ctx context.Context) { + if m.resolver == nil { + return + } + devs, err := m.resolver(ctx) + if err != nil { + logger.Warn("ledgermon", "设备解析失败", "err", err) + return + } + seen := make(map[string]bool) + m.mu.Lock() + defer m.mu.Unlock() + + // 按设备分组(消除 resolver 返回顺序依赖:任一账户有 reader 即建 actor,其余逐账户补齐) + byDevice := make(map[string][]MonitoredDevice) + for _, d := range devs { + byDevice[d.DeviceID] = append(byDevice[d.DeviceID], d) + } + for deviceID, list := range byDevice { + for _, d := range list { + seen[monitorKey(d.DeviceID, d.Provider, d.UserID)] = true + } + // 设备级 actor:每设备恰好一个(幂等创建)。任一账户有读取通道即建, + // 不再依赖"首个被枚举账户"的 provider 是否实现。 + actor := m.actors[deviceID] + if actor == nil { + var reader LedgerReader + for _, d := range list { + if m.factory == nil { + break + } + if reader = m.factory(d); reader != nil { + actor = NewDeviceActor(d.DeviceID, d.Serial, m.adb, m.dispatch) + actor.AddAccount(&AccountCtx{ + AccountID: d.AccountID, + Provider: d.Provider, + UserID: d.UserID, + Reader: reader, + Detector: NewDetector(d.DeviceID, d.AccountID, m.store), + }) + m.actors[d.DeviceID] = actor + logger.Info("ledgermon", "设备 DeviceActor 已创建(串行执行者)", + "device_id", d.DeviceID, "serial", d.Serial) + break + } + } + if actor == nil { + logger.Info("ledgermon", "设备全部读取通道未实现,跳过监听", + "device_id", deviceID, "accounts", len(list)) + continue + } + } + // 账户级命令生产者:每 (账户, 通道, 空间) 一个(幂等创建) + for _, d := range list { + key := monitorKey(d.DeviceID, d.Provider, d.UserID) + if _, ok := m.monitors[key]; ok { + continue + } + // 为 actor 补齐该账户的读取上下文 + if acct := actor.DeviceCtx.Account(d.Provider, d.UserID); acct == nil { + reader := m.factory(d) + if reader == nil { + continue + } + actor.AddAccount(&AccountCtx{ + AccountID: d.AccountID, + Provider: d.Provider, + UserID: d.UserID, + Reader: reader, + Detector: NewDetector(d.DeviceID, d.AccountID, m.store), + }) + } + mon := NewMonitor(d.AccountID, d.DeviceID, d.Provider, d.Serial, d.UserID, + actor, m.hasPending, m.pollEvery) + if m.pool != nil { + // 公平调度模式:monitor 只做预热,轮询由 DeviceScheduler 统一决策 + mon.SetSchedulerMode() + } + m.monitors[key] = mon + mon.Start() + logger.Info("ledgermon", "账户命令生产者已启动(初始暂停,等待下单唤醒)", + "device_id", d.DeviceID, "account_id", d.AccountID, "user_id", d.UserID, "provider", d.Provider) + m.armDeviceLock(d.DeviceID) + } + // 公平调度模式:每设备一个 DeviceScheduler(幂等创建),账户快照同步给调度器 + if m.pool != nil { + m.ensureScheduler(deviceID, actor) + } + } + // 移除消失设备(先停账户生产者,再停设备 actor) + for key, mon := range m.monitors { + if !seen[key] { + mon.Stop() + delete(m.monitors, key) + logger.Info("ledgermon", "账户命令生产者已停止(账户/设备消失)", "device_id", mon.deviceID) + } + } + // 公平调度模式:设备消失 → 停止该设备 scheduler + for deviceID, sched := range m.scheds { + if _, ok := byDevice[deviceID]; !ok { + sched.Stop() + delete(m.scheds, deviceID) + logger.Info("ledgermon", "设备 DeviceScheduler 已停止(设备消失)", "device_id", deviceID) + } + } + for deviceID, actor := range m.actors { + hasAccount := false + for key := range m.monitors { + if mon := m.monitors[key]; mon.deviceID == deviceID { + hasAccount = true + break + } + } + if !hasAccount { + actor.Stop() + delete(m.actors, deviceID) + delete(m.lockArmed, deviceID) + delete(m.lockDone, deviceID) + logger.Info("ledgermon", "设备 DeviceActor 已停止(设备消失)", "device_id", deviceID) + } + } +} + +// ensureScheduler 公平调度模式:每设备幂等创建 DeviceScheduler,并同步账户快照。 +// 账户快照从 actor 账户上下文提取(accountID → provider/userID),供调度器按池匹配。 +// 调用方持 m.mu 锁。 +func (m *Manager) ensureScheduler(deviceID string, actor *DeviceActor) { + if m.pool == nil { + return + } + sched := m.scheds[deviceID] + if sched == nil { + sched = NewDeviceScheduler(deviceID, actor, m.pool, DefaultSchedulerConfig()) + m.scheds[deviceID] = sched + sched.Start() + logger.Info("ledgermon", "设备 DeviceScheduler 已创建(公平时间占比调度)", + "device_id", deviceID) + } + sched.SetAccounts(schedulerAcctsOf(actor)) +} + +// schedulerAcctsOf 从 actor 账户快照提取调度器视图(accountID → provider/userID) +func schedulerAcctsOf(actor *DeviceActor) []schedulerAcct { + accts := actor.DeviceCtx.Accounts() + out := make([]schedulerAcct, 0, len(accts)) + for _, ac := range accts { + out = append(out, schedulerAcct{accountID: ac.AccountID, provider: ac.Provider, userID: ac.UserID}) + } + return out +} + +// armDeviceLock 为设备启动锁定协调(幂等:同设备只启动一次)。 +// 等设备全部账户 WarmupCmd 完成后,向 actor 投递 LockCmd(锁定最近任务,防一键清理)。 +// 锁定在 actor 队列内串行执行——天然不会与账户导航/轮询抢前台。 +func (m *Manager) armDeviceLock(deviceID string) { + if _, ok := m.lockDone[deviceID]; ok { + return // 本设备已锁定过,跳过 + } + if _, ok := m.lockArmed[deviceID]; ok { + return // 协调已启动,等待即可 + } + if m.adb == nil { + return // 无 adb(测试环境),锁定无意义 + } + m.lockArmed[deviceID] = struct{}{} + go func() { + defer func() { + if r := recover(); r != nil { + logger.Error("ledgermon", "设备加锁协程 panic(已恢复)", "device_id", deviceID, + "panic", r, "stack", string(debug.Stack())) + } + }() + m.waitAllWarmup(deviceID) + m.mu.Lock() + actor := m.actors[deviceID] + m.mu.Unlock() + if actor != nil { + actor.Enqueue(&LockCmd{}) + } + m.mu.Lock() + m.lockDone[deviceID] = struct{}{} + delete(m.lockArmed, deviceID) + m.mu.Unlock() + }() +} + +// waitAllWarmup 阻塞等待设备全部账户预热完成(任一账户预热失败 → 本轮不等待超时)。 +func (m *Manager) waitAllWarmup(deviceID string) { + deadline := time.Now().Add(3 * time.Minute) // 冷启动导航上限保护 + for time.Now().Before(deadline) { + m.mu.Lock() + var chans []<-chan struct{} + for _, mon := range m.monitors { + if mon.deviceID == deviceID { + chans = append(chans, mon.WarmupDone()) + } + } + m.mu.Unlock() + if len(chans) == 0 { + time.Sleep(2 * time.Second) + continue + } + allDone := true + for _, ch := range chans { + select { + case <-ch: + default: + allDone = false + } + } + if allDone { + return + } + time.Sleep(2 * time.Second) + } +} + +// ActivateAccount 下单钩子(main.go 调用):路由账户后唤醒对应命令生产者。 +// 公平调度模式:唤醒所属设备 scheduler(池空休眠时立即响应新订单); +// 旧模式:唤醒 LedgerMonitor。 +func (m *Manager) ActivateAccount(accountID int64) { + m.mu.Lock() + defer m.mu.Unlock() + for _, mon := range m.monitors { + if mon.accountID == accountID { + if sched := m.scheds[mon.deviceID]; sched != nil { + sched.Wake() + logger.Info("ledgermon", "下单触发设备调度器唤醒", + "account_id", accountID, "device_id", mon.deviceID) + return + } + mon.Activate() + logger.Info("ledgermon", "下单触发账户监听激活", + "account_id", accountID, "device_id", mon.deviceID) + return + } + } + logger.Debug("ledgermon", "账户无监听循环,忽略下单激活", "account_id", accountID) +} + +// WarmupDevice 手动预热(admin 触发):向设备 actor 投递 ManualWarmupCmd 并等待完成。 +// ManualWarmupCmd 是队列中的一个命令——与自动预热/轮询天然互斥,无特殊分支。 +// providers 指定通道({"alipay":true,"wechat":true}),空 = 全部。 +// 每设备单飞:已有手动预热在排队/执行时直接返回错误,防重复点击堆积(每个最长 240s)。 +func (m *Manager) WarmupDevice(ctx context.Context, deviceID string, providers map[string]bool) error { + m.mu.Lock() + actor := m.actors[deviceID] + if actor == nil { + m.mu.Unlock() + return fmt.Errorf("设备无 DeviceActor(设备离线或未配置监听)") + } + if m.warmupInFlight[deviceID] { + m.mu.Unlock() + return fmt.Errorf("设备手动预热进行中,请勿重复触发") + } + m.warmupInFlight[deviceID] = true + m.mu.Unlock() + + done := make(chan error, 1) + actor.Enqueue(&ManualWarmupCmd{Providers: providers, Done: done}) + var err error + select { + case err = <-done: + case <-ctx.Done(): + err = ctx.Err() + } + m.mu.Lock() + delete(m.warmupInFlight, deviceID) + m.mu.Unlock() + return err +} + +// StopAll 停止全部设备 actor 与账户生产者/调度器(服务端退出) +func (m *Manager) StopAll() { + m.mu.Lock() + defer m.mu.Unlock() + for _, mon := range m.monitors { + mon.Stop() + } + m.monitors = make(map[string]*LedgerMonitor) + for deviceID, sched := range m.scheds { + sched.Stop() + delete(m.scheds, deviceID) + } + for deviceID, actor := range m.actors { + actor.Stop() + delete(m.actors, deviceID) + } +} + +// DevicePollHealth 返回各设备 poll 成功率(deviceID → 0~1;无 actor 返回 0.9 中性)。 +// 均衡分配器健康度数据源(docs/PLAN-DYNAMIC-CLOSE-ALLOC.md §5.2)。 +func (m *Manager) DevicePollHealth() map[string]float64 { + m.mu.Lock() + defer m.mu.Unlock() + out := make(map[string]float64, len(m.actors)) + for devID, actor := range m.actors { + out[devID] = actor.PollHealth() + } + return out +} + +// MonitorState 单账户监听状态(admin 只读接口) +type MonitorState struct { + DeviceID string `json:"device_id"` + AccountID int64 `json:"account_id"` + Provider string `json:"provider"` + UserID int `json:"user_id"` + Serial string `json:"serial"` + Active bool `json:"active"` // 是否处于订单驱动轮询态(有待支付订单) + SeenRows int `json:"seen_rows"` // 已见指纹数(增量基线大小) + CurrentCmd string `json:"current_cmd"` // 设备 actor 当前执行命令(""=空闲) + QueueLen int `json:"queue_len"` // 设备 actor 等待队列长度 + LastCmd string `json:"last_cmd"` // 设备 actor 最近执行命令 + LastErr string `json:"last_err"` // 最近命令错误(""=成功,供诊断连续失败) +} + +// Status 返回全部账户监听状态(admin 只读接口) +func (m *Manager) Status() []MonitorState { + m.mu.Lock() + defer m.mu.Unlock() + out := make([]MonitorState, 0, len(m.monitors)) + for _, mon := range m.monitors { + actor := m.actors[mon.deviceID] + cur, qlen, lastCmd, lastErr := "", 0, "", "" + if actor != nil { + cur = actor.CurrentCmd() + qlen = actor.QueueLen() + if lc, lerr := actor.LastResult(); lerr != nil { + lastCmd, lastErr = lc, lerr.Error() + } + } + out = append(out, MonitorState{ + DeviceID: mon.deviceID, + AccountID: mon.accountID, + Provider: mon.provider, + UserID: mon.userId, + Serial: mon.serial, + Active: mon.IsActive(), + SeenRows: actorSeenRows(actor, mon.provider, mon.userId), + CurrentCmd: cur, + QueueLen: qlen, + LastCmd: lastCmd, + LastErr: lastErr, + }) + } + return out +} + +// actorSeenRows 从设备 actor 取账户已见指纹数(actor 为 nil 返回 0) +func actorSeenRows(actor *DeviceActor, provider string, userID int) int { + if actor == nil { + return 0 + } + return actor.SeenRows(provider, userID) +} diff --git a/internal/ledgermon/monitor_test.go b/internal/ledgermon/monitor_test.go new file mode 100644 index 0000000..45d5697 --- /dev/null +++ b/internal/ledgermon/monitor_test.go @@ -0,0 +1,278 @@ +package ledgermon + +import ( + "context" + "fmt" + "sync" + "sync/atomic" + "testing" + "time" + + "paygateway/internal/admin/capture" +) + +// fakeReader 测试用读取器:记录被调用的次数 +type fakeReader struct { + calls atomic.Int64 +} + +func (f *fakeReader) EnsureForeground(ctx context.Context) error { return nil } +func (f *fakeReader) Read(ctx context.Context) ([]capture.AlipayLedgerRow, error) { + f.calls.Add(1) + return nil, nil // 无新行 +} + +// newTestActor 创建设备 actor 并注册测试账户(adb nil:测试只用 PollCmd/WarmupCmd) +func newTestActor(t *testing.T, reader LedgerReader) *DeviceActor { + t.Helper() + actor := NewDeviceActor("dev1", "s1", nil, nil) + actor.AddAccount(&AccountCtx{ + AccountID: 1, + Provider: "alipay", + UserID: 0, + Reader: reader, + Detector: NewDetector("dev1", 1, nil), + }) + t.Cleanup(actor.Stop) + return actor +} + +// TestMonitor_OrderDriven 订单驱动:无待支付订单 → 不投递 PollCmd(读取零次); +// 激活(有订单)→ 轮询;订单终结 → 自动暂停。 +func TestMonitor_OrderDriven(t *testing.T) { + pending := atomic.Bool{} + reader := &fakeReader{} + actor := newTestActor(t, reader) + mon := NewMonitor(1, "dev1", "alipay", "s1", 0, actor, + func(ctx context.Context, accountID int64) bool { return pending.Load() }, + 20*time.Millisecond) // 快间隔加速测试 + + mon.Start() + defer mon.Stop() + + // 无订单:预热 WarmupCmd 会读取一次建基线,之后不应再轮询(保持 1 次) + time.Sleep(80 * time.Millisecond) + if got := reader.calls.Load(); got != 1 { + t.Fatalf("无待支付订单时应仅预热读取 1 次(建基线),实际读取 %d 次", got) + } + if mon.IsActive() { + t.Fatal("无订单时应处于暂停态") + } + + // 下单激活:开始轮询(在预热 1 次基础上递增) + pending.Store(true) + mon.Activate() + deadline := time.Now().Add(2 * time.Second) + for reader.calls.Load() == 1 && time.Now().Before(deadline) { + time.Sleep(10 * time.Millisecond) + } + if got := reader.calls.Load(); got <= 1 { + t.Fatal("激活后应开始轮询(读取超过预热基线)") + } + if !mon.IsActive() { + t.Fatal("有订单时应处于轮询态") + } + + // 订单终结:应自动暂停(允许至多一轮已在途 poll 完成,之后不再增长) + pending.Store(false) + before := reader.calls.Load() + time.Sleep(150 * time.Millisecond) // 多个间隔窗口 + after := reader.calls.Load() + if after > before+1 { + t.Fatalf("订单终结后应暂停轮询(允许 ≤1 轮在途),之前 %d 次,之后 %d 次", before, after) + } + if mon.IsActive() { + t.Fatal("订单终结后应回到暂停态") + } +} + +// TestMonitor_ActivateIdempotent 重复激活幂等:已在轮询时再次激活不产生额外循环 +func TestMonitor_ActivateIdempotent(t *testing.T) { + pending := atomic.Bool{} + pending.Store(true) + reader := &fakeReader{} + actor := newTestActor(t, reader) + mon := NewMonitor(1, "dev1", "alipay", "s1", 0, actor, + func(ctx context.Context, accountID int64) bool { return pending.Load() }, + 10*time.Millisecond) + + mon.Start() + defer mon.Stop() + mon.Activate() + mon.Activate() // 重复激活 + time.Sleep(60 * time.Millisecond) + if !mon.IsActive() { + t.Fatal("激活后应处于轮询态") + } +} + +// TestMonitor_WakeThenPauseThenWake 暂停后可再次被唤醒(新订单) +func TestMonitor_WakeThenPauseThenWake(t *testing.T) { + pending := atomic.Bool{} + reader := &fakeReader{} + actor := newTestActor(t, reader) + mon := NewMonitor(1, "dev1", "alipay", "s1", 0, actor, + func(ctx context.Context, accountID int64) bool { return pending.Load() }, + 20*time.Millisecond) + + mon.Start() + defer mon.Stop() + + // 第一轮:激活 → 轮询 → 暂停 + pending.Store(true) + mon.Activate() + time.Sleep(60 * time.Millisecond) + pending.Store(false) + time.Sleep(60 * time.Millisecond) // 等自动暂停 + + // 第二轮:再次激活(新订单) + before := reader.calls.Load() + pending.Store(true) + mon.Activate() + deadline := time.Now().Add(2 * time.Second) + for reader.calls.Load() <= before && time.Now().Before(deadline) { + time.Sleep(10 * time.Millisecond) + } + if reader.calls.Load() <= before { + t.Fatal("暂停后应可再次被唤醒轮询") + } +} + +// TestDeviceActor_PollDedup PollCmd 同账户去重:并发投递多条同账户 PollCmd 不堆积 +// (去重只防排队堆积:正在执行的 poll 被 pop 后同账户可再入队,但队列不会因风暴膨胀)。 +func TestDeviceActor_PollDedup(t *testing.T) { + reader := &fakeReader{} + actor := newTestActor(t, reader) + for i := 0; i < 10; i++ { + actor.Enqueue(&PollCmd{AccountID: 1, Provider: "alipay", UserID: 0}) + } + // 等待全部执行完成 + deadline := time.Now().Add(2 * time.Second) + for { + qlen := actor.QueueLen() + cur := actor.CurrentCmd() + if qlen == 0 && cur == "" { + break + } + if time.Now().After(deadline) { + t.Fatalf("命令未在期限内执行完: queue=%d current=%q", qlen, cur) + } + time.Sleep(5 * time.Millisecond) + } + if got := reader.calls.Load(); got > 10 { + t.Fatalf("去重后执行次数不应超过投递次数,实际 %d 次", got) + } + if actor.QueueLen() != 0 { + t.Fatal("全部执行完后队列应为空") + } +} + +// TestDeviceActor_SerialExecution 串行执行:WarmupCmd 完成后 LockCmd 才执行 +// (命令之间无并发,天然原子)。 +func TestDeviceActor_SerialExecution(t *testing.T) { + reader := &fakeReader{} + actor := newTestActor(t, reader) + warmupDone := make(chan error, 1) + actor.Enqueue(&WarmupCmd{AccountID: 1, Provider: "alipay", UserID: 0, Done: warmupDone}) + if err := <-warmupDone; err != nil { + t.Fatalf("WarmupCmd 执行失败: %v", err) + } + if got := reader.calls.Load(); got != 1 { + t.Fatalf("WarmupCmd 应执行 1 次读取(建基线),实际 %d 次", got) + } + if actor.CurrentCmd() != "" { + t.Fatal("命令完成后 actor 应回到空闲态") + } +} + +// TestDeviceActor_DispatchFailureNoLoss 资金安全:Dispatch 失败时行不被提交 seen, +// 下轮重新检测并重试——绝不永久丢单。 +func TestDeviceActor_DispatchFailureNoLoss(t *testing.T) { + // 自定义行构造器:Fingerprint 为时间|金额 + row := func(ts, amt string) capture.AlipayLedgerRow { + return capture.AlipayLedgerRow{Time: ts, Amount: amt} + } + reader := &seqReader{rows: [][]capture.AlipayLedgerRow{ + {row("10:00:01", "1.00")}, // 首次:建基线 + {row("10:00:02", "2.00")}, // 第二次:新行,Dispatch 失败 + {row("10:00:02", "2.00")}, // 第三次:应再次检测到并成功推送 + }} + dispatchCalls := atomic.Int64{} + dispatchErr := atomic.Bool{} + dispatchErr.Store(true) // 先失败 + actor := NewDeviceActor("dev3", "s3", nil, func(ctx context.Context, deviceID string, provider string, space int, rows []capture.AlipayLedgerRow) error { + dispatchCalls.Add(1) + if dispatchErr.Load() { + return fmt.Errorf("模拟推送失败") + } + return nil + }) + acct := &AccountCtx{ + AccountID: 1, Provider: "alipay", UserID: 0, + Reader: reader, + Detector: NewDetector("dev3", 1, nil), + } + actor.AddAccount(acct) + defer actor.Stop() + + // 第一次 poll:建基线,无推送 + actor.Enqueue(&PollCmd{AccountID: 1, Provider: "alipay", UserID: 0}) + waitDone(t, actor, 1) + if dispatchCalls.Load() != 0 { + t.Fatalf("首次建基线不应推送,实际推送 %d 次", dispatchCalls.Load()) + } + // 第二次 poll:新行,Dispatch 失败 → 不提交 + actor.Enqueue(&PollCmd{AccountID: 1, Provider: "alipay", UserID: 0}) + waitDone(t, actor, 2) + if dispatchCalls.Load() != 1 { + t.Fatalf("第二次应尝试推送 1 次,实际 %d 次", dispatchCalls.Load()) + } + // 第三次 poll:应重新检测到同一行并推送成功(不丢单) + dispatchErr.Store(false) + actor.Enqueue(&PollCmd{AccountID: 1, Provider: "alipay", UserID: 0}) + waitDone(t, actor, 3) + if dispatchCalls.Load() != 2 { + t.Fatalf("Dispatch 失败后下轮应重试推送(共 2 次),实际 %d 次", dispatchCalls.Load()) + } + // 第四次 poll:已提交,不再推送 + reader.append(nil) + actor.Enqueue(&PollCmd{AccountID: 1, Provider: "alipay", UserID: 0}) + waitDone(t, actor, 4) + if dispatchCalls.Load() != 2 { + t.Fatalf("提交后不应重复推送,实际推送 %d 次", dispatchCalls.Load()) + } +} + +// seqReader 按调用次序返回预置行序列(append 与 Read 可能并发,mutex 保护) +type seqReader struct { + mu sync.Mutex + rows [][]capture.AlipayLedgerRow + idx int +} + +func (r *seqReader) EnsureForeground(ctx context.Context) error { return nil } +func (r *seqReader) Read(ctx context.Context) ([]capture.AlipayLedgerRow, error) { + r.mu.Lock() + defer r.mu.Unlock() + if r.idx >= len(r.rows) { + return nil, nil + } + rows := r.rows[r.idx] + r.idx++ + return rows, nil +} +func (r *seqReader) append(rows []capture.AlipayLedgerRow) { + r.mu.Lock() + defer r.mu.Unlock() + r.rows = append(r.rows, rows) +} + +// waitDone 等待 actor 处理完第 n 次入队 +func waitDone(t *testing.T, actor *DeviceActor, n int) { + t.Helper() + deadline := time.Now().Add(2 * time.Second) + for actor.QueueLen() > 0 && time.Now().Before(deadline) { + time.Sleep(5 * time.Millisecond) + } + time.Sleep(20 * time.Millisecond) // 等当前命令执行完成 +} diff --git a/internal/ledgermon/reader_alipay.go b/internal/ledgermon/reader_alipay.go new file mode 100644 index 0000000..0200b11 --- /dev/null +++ b/internal/ledgermon/reader_alipay.go @@ -0,0 +1,233 @@ +package ledgermon + +import ( + "context" + "fmt" + "hash/fnv" + "strings" + "time" + + "paygateway/internal/adbmgr" + "paygateway/internal/admin/capture" +) + +// 支付宝常量(与 capture 包一致;独立声明避免导出 capture 内部常量) +const ( + alipayPkg = "com.eg.android.AlipayGphone" + alipayLaunchAct = ".AlipayLogin" + alipayLedgerAct = "XRiverActivity$App" // 稳定的完整收款记录页(2026-08-27 真机实测) + alipayLedgerActStr = "XRiverActivity$App" +) + +// LedgerReader 账本页读取器接口(按通道分派) +type LedgerReader interface { + // EnsureForeground 确保账本页在前台(免点击恢复,失败返回错误 → 本轮跳过)。 + // 下单实时回调路径:只走热启动(task lock),绝不冷启动。 + EnsureForeground(ctx context.Context) error + // Read 读取当前前台账本页可见行。页面无变化返回 (nil, nil)(变化检测跳过解析)。 + Read(ctx context.Context) ([]capture.AlipayLedgerRow, error) +} + +// Warmer 可选接口:预热专用导航(task lock 热启动优先 → 失败走冷启动坐标导航建账本页)。 +// 预热阶段(warmupOnce)调用——账本页任务栈不存在时必须用冷启动建立; +// 实时回调(pollOnce)只用 EnsureForeground(纯热启动,绝不冷启动)。 +type Warmer interface { + Warmup(ctx context.Context) error +} + +// FreshReader 可选接口:实现方支持免下拉刷新读取。冷启动导航刚完成时账本页 +// 就是最新数据,无需下拉刷新(省 1.5s 且避免刷新动画扰动)。 +type FreshReader interface { + ReadFresh(ctx context.Context) ([]capture.AlipayLedgerRow, error) +} + +// TaskLocker 可选接口:实现方支持锁定最近任务(防一键清理移除任务栈, +// 保证热启动能恢复账本页,避免重复冷启动导航)。无此能力时预热跳过锁定。 +type TaskLocker interface { + LockTasks(ctx context.Context) (int, error) +} + +// AlipayReader 支付宝账本页 dump 文本通道读取器。 +// +// 链路:uiautomator dump(系统级通道,不受 FLAG_SECURE 限制,实测可读出账本行) +// → 复用 capture.ReadAlipayLedger 解析(付款方|HH:MM:SS|+金额)。 +// 变化检测:dump XML 文本哈希对比上一帧(页面无变化 → 不解析直接返回空)。 +// +// 关键:账本页是 WebView 容器(NebulaX),数据不自动刷新——新收款不会自动出现在 +// 页面里,必须下拉刷新(pull-to-refresh)触发数据重载后才能 dump 到新行。 +type AlipayReader struct { + adb *adbmgr.Manager + serial string + userId int // 0=主空间,999=双开分身 + lastDumpHash uint64 +} + +// 下拉刷新手势参数(实测账本页 WebView 需下拉才能重载数据): +// 从列表区中部 (540,600) 向下滑到 (540,1700),触发 pull-to-refresh。 +const ( + refreshSwipeX = 540 + refreshSwipeY0 = 600 + refreshSwipeY1 = 1700 + refreshSwipeMs = 400 + refreshLoadWait = 1500 * time.Millisecond // 刷新动画 + 数据重载等待 +) + +// NewAlipayReader 创建支付宝读取器。userId>0 时以该 user 空间启动(双开)。 +func NewAlipayReader(adb *adbmgr.Manager, serial string, userId int) *AlipayReader { + return &AlipayReader{adb: adb, serial: serial, userId: userId} +} + +// refresh 下拉刷新账本页(WebView 数据重载)。失败不致命:下一轮重试。 +func (r *AlipayReader) refresh(ctx context.Context) error { + err := r.adb.SwipeRef(ctx, r.serial, refreshSwipeX, refreshSwipeY0, refreshSwipeX, refreshSwipeY1, refreshSwipeMs) + if err != nil { + return err + } + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(refreshLoadWait): + } + return nil +} + +// EnsureForeground 确保支付宝完整收款记录页(XRiverTransActivity)在前台。 +// +// 下单实时回调路径(2026-08-28 重构):只走热启动,绝不冷启动。 +// 1. 前台检测(dumpsys 三要素:包名+Activity+用户空间,~100ms):已在账本页 → 零开销返回 +// 2. 热启动(~260ms):am task lock 直接唤醒 lite01 账本页任务栈到前台 +// ——am start .AlipayLogin 只恢复主任务栈(栈顶首页/总览),稳定账本页 +// XRiverActivity$App 在 lite01 亲和栈,am start 永远到不了它。task lock 按 +// taskId 精确唤醒账本页栈,与微信 taskLock(wechatTaskID) 同机制。 +// 3. 进程被杀/任务栈丢失/唤醒失败 → 返回错误本轮跳过(不读错页面)。 +// 冷启动导航(CaptureAlipayLedger)只在预热阶段执行(设备上线 warmupOnce), +// 绝不出现在下单实时回调——17.7s 冷启动会错过付款窗口。 +func (r *AlipayReader) EnsureForeground(ctx context.Context) error { + // ① 已在账本页(三要素确认,防双开/异应用误判) + if r.isLedgerForeground(ctx) { + return nil + } + // ② 进程被杀 → 任务栈已丢,task lock 无栈可唤醒,本轮跳过(不冷启动) + if alive, _ := r.adb.ProcessAlive(ctx, r.serial, alipayPkg); !alive { + return fmt.Errorf("支付宝进程不存在,热启动无法恢复(需重新预热)") + } + // ③ 热启动:am task lock 唤醒账本页任务栈(微信同款,~260ms) + taskID, terr := r.adb.FindLedgerTaskID(ctx, r.serial, alipayPkg, alipayLedgerActStr, r.userId) + if terr != nil { + return fmt.Errorf("账本页任务栈未找到,热启动无法恢复(需重新预热): %w", terr) + } + if lerr := r.adb.TaskLock(ctx, r.serial, taskID); lerr != nil { + return fmt.Errorf("task lock 唤醒账本页任务栈失败: %w", lerr) + } + // 唤醒成功后 PINNED 锁定前台,确认到账本页立即释放(否则拦截后续下拉刷新) + defer func() { _ = r.adb.UnpinTask(ctx, r.serial) }() + if !r.waitLedgerForeground(ctx, 2*time.Second) { + return fmt.Errorf("task lock 唤醒后未确认账本页前台") + } + return nil +} + +// Warmup 预热专用导航(Warmer 接口):task lock 热启动优先,账本页任务栈不存在 +// (进程被杀/首次启动/被清理)时走冷启动坐标导航建账本页。仅预热阶段调用。 +func (r *AlipayReader) Warmup(ctx context.Context) error { + // 已在账本页 → 零开销 + if r.isLedgerForeground(ctx) { + return nil + } + // task lock 热启动优先 + if taskID, terr := r.adb.FindLedgerTaskID(ctx, r.serial, alipayPkg, alipayLedgerActStr, r.userId); terr == nil { + if lerr := r.adb.TaskLock(ctx, r.serial, taskID); lerr == nil { + if r.waitLedgerForeground(ctx, 2*time.Second) { + _ = r.adb.UnpinTask(ctx, r.serial) + return nil + } + _ = r.adb.UnpinTask(ctx, r.serial) + } + } + // 冷启动坐标导航建账本页(仅预热阶段,绝不用于实时回调) + return capture.CaptureAlipayLedger(ctx, r.adb, r.serial, r.userId) +} + +// isLedgerOverview 前台是否为收款总览界面(XRiverActivity,非稳定页)。 +// 热启动恢复到主任务栈栈顶时是总览界面(稳定页在 lite01 栈),需据此补点进入稳定页。 +// 注意:ForegroundAppDetail 返回的活动名带 dumpsys ActivityRecord 的尾部 '}'(如 +// "...XRiverActivity}"),不能精确 == 匹配,必须子串匹配;同时排除稳定页 "$App" 后缀。 +func (r *AlipayReader) isLedgerOverview(ctx context.Context) bool { + pkg, act, uid, err := r.adb.ForegroundAppDetail(ctx, r.serial) + return err == nil && pkg == alipayPkg && + strings.Contains(act, "XRiverActivity") && + !strings.Contains(act, alipayLedgerActStr) && uid == r.userId +} + +// isLedgerForeground 前台是否已是本账户账本页(包名 + Activity + 用户空间三要素全匹配)。 +// 双开(user999)与主空间(user0)的账本页 Activity 同名,必须按 userID 区分, +// 否则 user0 的监听循环会把 user999 的账本页误判为已就绪 → 下拉刷新读到的全是错误账户数据。 +func (r *AlipayReader) isLedgerForeground(ctx context.Context) bool { + pkg, act, uid, err := r.adb.ForegroundAppDetail(ctx, r.serial) + return err == nil && pkg == alipayPkg && + strings.Contains(act, alipayLedgerActStr) && uid == r.userId +} + +// waitLedgerForeground 轮询等待账本页出现在前台(最长 timeout) +func (r *AlipayReader) waitLedgerForeground(ctx context.Context, timeout time.Duration) bool { + deadline := time.Now().Add(timeout) + for time.Now().Before(deadline) { + if r.isLedgerForeground(ctx) { + return true + } + select { + case <-ctx.Done(): + return false + case <-time.After(800 * time.Millisecond): + } + } + return false +} + +// Read dump 当前账本页并解析可见行(带下拉刷新:WebView 数据不自动更新, +// 新收款必须触发重载后才能被 dump 读到)。页面无变化 → 返回 (nil, nil)。 +func (r *AlipayReader) Read(ctx context.Context) ([]capture.AlipayLedgerRow, error) { + if err := r.refresh(ctx); err != nil { + return nil, err + } + return r.readDump(ctx) +} + +// ReadFresh 免下拉刷新读取(FreshReader 接口实现)。冷启动导航刚完成时账本页 +// 就是最新数据,下拉刷新是多余开销(省 1.5s 且避免刷新动画扰动页面判定)。 +func (r *AlipayReader) ReadFresh(ctx context.Context) ([]capture.AlipayLedgerRow, error) { + return r.readDump(ctx) +} + +// readDump dump 当前账本页并解析可见行,带变化检测。 +func (r *AlipayReader) readDump(ctx context.Context) ([]capture.AlipayLedgerRow, error) { + xml, err := r.adb.DumpUI(ctx, r.serial) + if err != nil { + return nil, err + } + h := fnv.New64a() + h.Write([]byte(xml)) + sum := h.Sum64() + if sum == r.lastDumpHash { + return nil, nil // 页面无变化 + } + r.lastDumpHash = sum + rows, rerr := capture.ReadAlipayLedger(xml) + if rerr != nil { + return nil, rerr + } + // 资金安全:退化空 dump(无障碍未填充→空树)误判为'0 行'→建空基线→真实历史行 + // 后续被当 fresh 错误入账。退化空 dump 返回错误,跳过本轮不建基线(同微信 readDump)。 + if len(rows) == 0 && capture.DegenerateDump(xml) { + return nil, fmt.Errorf("账本页 dump 为空/未渲染(可能无障碍被回收或页面未就绪),拒绝建立空基线") + } + return rows, nil +} + +// LockTasks 锁定最近任务中支付应用主任务卡片(TaskLocker 接口实现)。 +// 锁定后一键清理不会移除账本页任务栈,热启动可恢复账本页(避免重复冷启动导航)。 +// 每应用只锁一张主任务栈(锁多个同应用进程会触发 MIUI 保护性杀进程)。 +// 锁定完成后退回最近任务界面,不调用账户级导航(避免误触发冷启动跳转)。 +func (r *AlipayReader) LockTasks(ctx context.Context) (int, error) { + return capture.LockPaymentTasks(ctx, r.adb, r.serial, "支付宝", "微信") +} diff --git a/internal/ledgermon/reader_wechat.go b/internal/ledgermon/reader_wechat.go new file mode 100644 index 0000000..754777e --- /dev/null +++ b/internal/ledgermon/reader_wechat.go @@ -0,0 +1,264 @@ +package ledgermon + +import ( + "context" + "fmt" + "hash/fnv" + "strings" + "time" + + "paygateway/internal/adbmgr" + "paygateway/internal/admin/capture" + "paygateway/internal/logger" +) + +// 微信常量(与 capture 包独立声明) +const ( + wechatPkg = "com.tencent.mm" + wechatLedgerAct = "AppBrandUI00" // 收款小账本小程序容器(PLAN-ADB-LEDGER.md §2.2 实测) +) + +// WechatReader 微信收款记录页(AppBrandUI00 小程序容器)读取器。 +// +// 与支付宝同逻辑(2026-08-28 实现): +// - 热启动:am task lock 唤醒账本页任务栈(微信 am start 直拉小程序被校验拒绝, +// task lock 是唯一放行路径,task id 动态需 dumpsys 解析——PLAN-ADB-LEDGER.md §2.3) +// - 冷启动:capture.CaptureWechatLedger 坐标导航(微信首页→我→服务→收付款→ +// 二维码收款→收款小账本→收款记录) +// - 任务卡片锁定:复用 capture.LockPaymentTasks("微信") +// - 读取:OCR 通道(Phase 2 接入);当前 EnsureForeground 保前台 + Read 占位空读 +type WechatReader struct { + adb *adbmgr.Manager + serial string + userId int // 0=主空间,999=双开分身 + lastDumpHash uint64 +} + +// NewWechatReader 创建微信读取器。userId>0 时以该 user 空间启动(双开)。 +func NewWechatReader(adb *adbmgr.Manager, serial string, userId int) *WechatReader { + return &WechatReader{adb: adb, serial: serial, userId: userId} +} + +// EnsureForeground 确保微信收款记录页(AppBrandUI00)在前台。 +// 下单实时回调路径:只走热启动(task lock),绝不冷启动(与支付宝 EnsureForeground 同策略)。 +// 冷启动导航(CaptureWechatLedger)只在预热阶段执行。 +func (r *WechatReader) EnsureForeground(ctx context.Context) error { + // ① 已在账本页(三要素确认) + if r.isLedgerForeground(ctx) { + return nil + } + // ② 进程被杀 → 任务栈已丢,task lock 无栈可唤醒,本轮跳过(不冷启动) + if alive, _ := r.adb.ProcessAlive(ctx, r.serial, wechatPkg); !alive { + return fmt.Errorf("微信进程不存在,热启动无法恢复(需重新预热)") + } + // ③ 热启动:am task lock 唤醒账本页任务栈(task id 动态解析) + taskID, terr := r.adb.FindLedgerTaskID(ctx, r.serial, wechatPkg, wechatLedgerAct, r.userId) + if terr != nil { + return fmt.Errorf("微信账本页任务栈未找到,热启动无法恢复(需重新预热): %w", terr) + } + if lerr := r.adb.TaskLock(ctx, r.serial, taskID); lerr != nil { + return fmt.Errorf("task lock 唤醒微信账本页任务栈失败: %w", lerr) + } + // 唤醒成功后 PINNED 锁定前台,确认到账本页立即释放(否则拦截后续读取) + defer func() { _ = r.adb.UnpinTask(ctx, r.serial) }() + if !r.waitLedgerForeground(ctx, 2*time.Second) { + return fmt.Errorf("task lock 唤醒后未确认微信账本页前台") + } + return nil +} + +// Warmup 预热专用导航(Warmer 接口):task lock 热启动优先,微信账本页任务栈 +// (AppBrandUI00)不存在(进程被杀/首次启动/被清理)时走冷启动坐标导航建账本页。 +// 与支付宝 Warmup 同逻辑。仅预热阶段调用。 +func (r *WechatReader) Warmup(ctx context.Context) error { + // 已在账本页 → 零开销 + if r.isLedgerForeground(ctx) { + return nil + } + // task lock 热启动优先 + if taskID, terr := r.adb.FindLedgerTaskID(ctx, r.serial, wechatPkg, wechatLedgerAct, r.userId); terr == nil { + if lerr := r.adb.TaskLock(ctx, r.serial, taskID); lerr == nil { + if r.waitLedgerForeground(ctx, 2*time.Second) { + _ = r.adb.UnpinTask(ctx, r.serial) + return nil + } + _ = r.adb.UnpinTask(ctx, r.serial) + } + } + // 冷启动坐标导航建账本页(仅预热阶段,绝不用于实时回调) + return capture.CaptureWechatLedger(ctx, r.adb, r.serial, r.userId) +} + +// Read 读取当前前台微信账本页可见行。 +// 链路:下拉刷新重载数据 → ensureAccessibility 确保绑定 → uiautomator dump → 解析。 +// 页面无变化返回 (nil, nil)。 +func (r *WechatReader) Read(ctx context.Context) ([]capture.AlipayLedgerRow, error) { + if err := r.refresh(ctx); err != nil { + return nil, err + } + return r.readDump(ctx) +} + +// ReadFresh 免刷新读取(冷启动导航刚完成即最新数据)。 +// 与 Read 同路径(readDump 内 ensureAccessibility 紧贴 dump 前瞬态重绑)。 +func (r *WechatReader) ReadFresh(ctx context.Context) ([]capture.AlipayLedgerRow, error) { + return r.readDump(ctx) +} + +// refresh 下拉刷新微信账本页重载数据(小程序 WebView 收款记录需刷新才见最新行)。 +// 刷新只操作页面,不触碰无障碍——绑定由 readDump 内 ensureAccessibility 在 dump +// 前紧贴重建(2026-09-13 真机实证:刷新→等3s→激活→立即 dump 成功读出账单行)。 +func (r *WechatReader) refresh(ctx context.Context) error { + if err := r.adb.SwipeRef(ctx, r.serial, refreshSwipeX, refreshSwipeY0, refreshSwipeX, refreshSwipeY1, refreshSwipeMs); err != nil { + return err + } + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(refreshLoadWait): + } + return nil +} + +// readDump 确保无障碍绑定并 dump 当前微信账本页,解析可见行,带变化检测。 +// +// 关键时序(红米 K60 真机实证):MIUI MiuiBooster 即使豁免也会在数秒内回收 +// 无障碍服务绑定(Bound 非空→空)。因此绑定必须【距 DumpUI 最近】—— +// ensureAccessibility 紧贴 DumpUI 调用;下拉刷新等页面操作排在它之前, +// 刷新结束到 dump 之间不夹任何长动作,dump 即落在绑定窗口内 +// (实证:刷新→等3s→激活→立即 dump 成功读出账单行;激活→滚动→惰待 dump 则空)。 +func (r *WechatReader) readDump(ctx context.Context) ([]capture.AlipayLedgerRow, error) { + // 紧贴 dump 前瞬态重绑 + 确认绑定(绑定被回收则重新激活,穷尽绑定窗口) + if err := r.ensureAccessibility(ctx); err != nil { + return nil, err + } + xml, err := r.adb.DumpUI(ctx, r.serial) + if err != nil { + return nil, err + } + h := fnv.New64a() + h.Write([]byte(xml)) + sum := h.Sum64() + if sum == r.lastDumpHash { + return nil, nil // 页面无变化 + } + r.lastDumpHash = sum + rows, rerr := capture.ReadWechatLedger(xml) + if rerr != nil { + return nil, rerr + } + // 资金安全:退化空 dump(无障碍未填充→空树)会被误判为'0 行'→建立空基线, + // 后续真实历史行变 fresh 错误入账。退化空 dump 返回错误,跳过本轮不建基线。 + if len(rows) == 0 && capture.DegenerateDump(xml) { + return nil, fmt.Errorf("账本页 dump 为空/未渲染(可能无障碍被回收或页面未就绪),拒绝建立空基线") + } + return rows, nil +} + +// ensureAccessibility 确保系统无障碍已开启(微信 XWeb 小程序 dump 的前置条件)。 +// +// 三项必要步骤(2026-09-11 真机平板实测根因,红米 K60 亦适用): +// 1. MIUI 省电豁免:MiuiBooster 省电守护会周期性回收 com.miui.accessibility(VoiceAccess) +// 服务进程,随后 BatterySaverPolicy 把 accessibility_enabled 清回 0(实测 ~45-50s 落回)。 +// 必须在启用前先给无障碍包授予后台运行豁免(appops RUN_IN_BACKGROUND allow + 白名单), +// 否则即便 enabled=1 也会被周期性清零。豁免幂等。 +// 2. 写有效服务:enabled_accessibility_services 必须写入**真实安装**的组件串。若沿用 +// 已被污染的旧值(系统常清成 null),再次写回会导致 AccessibilityManager 无法绑定 → +// 连带清空 enabled。此处固定用设备实测可绑定的 MIUI VoiceAccess。 +// 3. settings put secure accessibility_enabled 1 开启。 +func (r *WechatReader) ensureAccessibility(ctx context.Context) error { + if err := r.miuiBoostExempt(ctx); err != nil { + logger.Warn("ledgermon", "MIUI 省电豁免失败(无障碍仍可能被周期性清零)", + "device", r.serial, "err", err) + } + // 已真正绑定则直接复用(幂等)。不能只看 accessibility_enabled==1 提前返回—— + // 系统可能 enabled=1 但服务未绑定(污染值/MiuiBooster 清服务),此时 dump 仍退化。 + if r.accessibilityBound(ctx) { + return nil + } + // 固定写设备实测可绑定的 MIUI VoiceAccess(不用旧值,防污染值连带清空)。 + // 必须用【全路径】组件串:简写 ".voiceaccess.VoiceAccessAccessibilityService" + // 实测 AccessibilityManager 无法绑定(Bound/Enabled services 空),服务形同虚设。 + svc := "com.miui.accessibility/com.miui.accessibility.voiceaccess.VoiceAccessAccessibilityService" + if _, err := r.adb.Shell(ctx, 10*time.Second, r.serial, "settings put secure enabled_accessibility_services "+svc); err != nil { + logger.Warn("ledgermon", "设置无障碍服务失败", "device", r.serial, "err", err) + } + if _, err := r.adb.Shell(ctx, 10*time.Second, r.serial, "settings put secure accessibility_enabled 1"); err != nil { + return fmt.Errorf("开启无障碍失败: %w", err) + } + // 等系统激活并绑定无障碍服务(XWeb 节点树构建需要时间;MiuiBooster 窗口内)。 + // 轮询确认已真正绑定(Bound services 非空)——enabled=1 但未绑定时 dump 仍退化空。 + deadline := time.Now().Add(6 * time.Second) + for time.Now().Before(deadline) { + if r.accessibilityBound(ctx) { + logger.Info("ledgermon", "已开启系统无障碍(微信 dump 解锁)", "device", r.serial) + return nil + } + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(400 * time.Millisecond): + } + } + return fmt.Errorf("系统无障碍服务未能绑定(enabled=1 但 Bound services 空),微信 dump 将退化") +} + +// accessibilityBound 查询无障碍服务是否真正绑定(dumpsys accessibility Bound services 非空)。 +// enabled=1 只表示开关打开,必须服务进程挂上(Bound)才暴露 XWeb 节点树。 +func (r *WechatReader) accessibilityBound(ctx context.Context) bool { + out, err := r.adb.Shell(ctx, 6*time.Second, r.serial, "dumpsys accessibility") + if err != nil { + return false + } + for _, line := range strings.Split(out, "\n") { + line = strings.TrimSpace(line) + // Bound services:{Service[label=语音控制,...]} —— 有实际服务实例即视为已绑定 + if strings.HasPrefix(line, "Bound services:") && strings.Contains(line, "Service[") { + return true + } + } + return false +} + +// miuiBoostExempt MIUI 省电/性能守护豁免:让无障碍服务包不被周期性回收。 +// 实测(红米 K60/平板):不打这个豁免,com.miui.accessibility 约 45-50s 被 +// MiuiBooster 回收 → accessibility_enabled 被清 0 → 微信 dump 回到空节点树。 +// 幂等:重复执行无害。豁免成功后 enabled 实测稳定保持 120s+。 +func (r *WechatReader) miuiBoostExempt(ctx context.Context) error { + const pkg = "com.miui.accessibility" + // appops 后台运行豁免(MiuiBooster 依赖 RUN_IN_BACKGROUND 判定回收) + if _, err := r.adb.Shell(ctx, 10*time.Second, r.serial, "cmd appops set "+pkg+" RUN_IN_BACKGROUND allow"); err != nil { + return err + } + // Doze 白名单(可选,防深度空闲回收) + _, _ = r.adb.Shell(ctx, 10*time.Second, r.serial, "dumpsys deviceidle whitelist +"+pkg) + return nil +} + +// LockTasks 锁定微信任务卡片(防一键清理移除任务栈,保证热启动可恢复)。 +func (r *WechatReader) LockTasks(ctx context.Context) (int, error) { + return capture.LockPaymentTasks(ctx, r.adb, r.serial, "微信") +} + +// isLedgerForeground 前台是否为微信收款记录页(AppBrandUI00 小程序容器 + 用户空间)。 +func (r *WechatReader) isLedgerForeground(ctx context.Context) bool { + pkg, act, uid, err := r.adb.ForegroundAppDetail(ctx, r.serial) + return err == nil && pkg == wechatPkg && + strings.Contains(act, wechatLedgerAct) && uid == r.userId +} + +// waitLedgerForeground 轮询等待账本页出现在前台(最长 timeout) +func (r *WechatReader) waitLedgerForeground(ctx context.Context, timeout time.Duration) bool { + deadline := time.Now().Add(timeout) + for time.Now().Before(deadline) { + if r.isLedgerForeground(ctx) { + return true + } + select { + case <-ctx.Done(): + return false + case <-time.After(800 * time.Millisecond): + } + } + return false +} diff --git a/internal/ledgermon/scheduler.go b/internal/ledgermon/scheduler.go new file mode 100644 index 0000000..61ae4eb --- /dev/null +++ b/internal/ledgermon/scheduler.go @@ -0,0 +1,423 @@ +// DeviceScheduler 设备级公平时间占比弹性调度器(docs/DESIGN-FAIR-SCHEDULER.md) +// +// 职责:每设备恰好一个调度 goroutine,是唯一 PollCmd 生产者;DeviceActor 保持 +// 被动串行执行者(命令队列不变)。调度输入为 WaitPayPool 内存订单池快照(零锁)。 +// +// 核心语义(2026-08-29 与用户确认): +// - 记账单位 = 一次完整 poll(原子不可打断,绝不按墙钟固定时间切片) +// - 顺序维度(前台处理权):等待最久的账户先做(age 降序) +// - 配额维度(时间使用权):与订单数量挂钩(q ∝ count,floor=1,cap=60%) +// - 硬紧急:minTTL < UrgentTTL 的账户无条件置顶(防关单丢单) +// - 窗口动态:窗口 = 一组配额 poll 操作的集合,墙钟长度随单次 poll 实测时长浮动 +// - 反馈:每 poll 后查池,账户池空立即提前终止该 visit;窗口结束立即重算 +package ledgermon + +import ( + "context" + "math" + "math/rand" + "sort" + "sync" + "sync/atomic" + "time" + + "paygateway/internal/logger" + "paygateway/internal/pool" +) + +// SchedulerConfig 调度器参数(默认值见 DefaultSchedulerConfig;可注入覆盖) +type SchedulerConfig struct { + // BaseQuota Q_total 基数:窗口总配额 = 活跃账户数 × BaseQuota(默认 3) + BaseQuota int + // CapPct 单账户占整轮窗口配额上限比例(默认 0.6) + CapPct float64 + // DwellMax 单账户连续 poll 上限(强制看一眼其它账户,默认 3) + DwellMax int + // UrgentTTL 硬紧急阈值:minTTL < UrgentTTL 的账户无条件置顶(默认 8s ≈ 2×单次 poll) + UrgentTTL time.Duration + // IdleSleep 池空休眠间隔(下单唤醒前的最小探测周期,默认 500ms) + IdleSleep time.Duration + // PollMinGap / PollMaxGap 相邻两次账单页 poll 之间的随机间隔(默认 1s~3s,时间驱动)。 + // 只对「有待支付订单的账户」生效(plan 已限定 Count>0 账户,无订单账户不刷); + // 随机化打破固定节奏,避免支付宝/微信账单页按恒定频率被检测为抓取轰炸。 + // PollMaxGap < PollMinGap 或 PollMaxGap<=0 时退化为无间隔(测试用,极低延迟)。 + PollMinGap time.Duration + PollMaxGap time.Duration + // AccountPoller 按 (provider,userID) 执行单次 poll 的钩子(注入 PollCmd 到 actor) + AccountPoller func(ctx context.Context, provider string, userID int) error +} + +// DefaultSchedulerConfig 默认参数(依据 DESIGN-FAIR-SCHEDULER.md §5.3 参数表) +func DefaultSchedulerConfig() SchedulerConfig { + return SchedulerConfig{ + BaseQuota: 3, + CapPct: 0.6, + DwellMax: 3, + UrgentTTL: 8 * time.Second, + IdleSleep: 500 * time.Millisecond, + PollMinGap: 1 * time.Second, + PollMaxGap: 3 * time.Second, + AccountPoller: nil, // 由 NewDeviceScheduler 注入 actor 封装 + } +} + +// schedulerAcct 调度器视图中的账户(provider/userID 从 actor 账户快照解析) +type schedulerAcct struct { + accountID int64 + provider string + userID int +} + +// DeviceScheduler 设备级调度器:窗口决策循环(决策只在 poll 边界发生) +type DeviceScheduler struct { + deviceID string + pool *pool.WaitPayPool + cfg SchedulerConfig + // accounts 本设备账户快照(reconcile 写入,调度循环读取;atomic 防竞态) + accounts atomic.Value // []schedulerAcct + // activeIDs 当前活跃账户(池快照中 count>0 的账户,按窗口重算) + // 调度循环内部使用,无需锁(单 goroutine) + + wake chan struct{} // 下单唤醒(非阻塞;池空休眠时立即响应) + stop chan struct{} + done chan struct{} + + // 可观测(admin Status) + current atomic.Value // 当前窗口描述(windowDesc) + + // roundStat 窗口耗时滑动统计(设备一轮完整轮询实际耗时,供 poll 成本校准/超限判定) + roundStat *roundStat +} + +// roundStat 窗口耗时滑动平均(最近 N 轮)。 +type roundStat struct { + mu sync.Mutex + vals []time.Duration + next int + full bool + size int +} + +func newRoundStat(size int) *roundStat { + if size <= 0 { + size = 10 + } + return &roundStat{vals: make([]time.Duration, size), size: size} +} + +func (r *roundStat) record(d time.Duration) { + r.mu.Lock() + defer r.mu.Unlock() + r.vals[r.next] = d + r.next = (r.next + 1) % r.size + if r.next == 0 { + r.full = true + } +} + +// avg 返回滑动平均耗时(无数据返回 0)。 +func (r *roundStat) avg() time.Duration { + r.mu.Lock() + defer r.mu.Unlock() + if !r.full { + n := r.next + if n == 0 { + return 0 + } + var sum time.Duration + for i := 0; i < n; i++ { + sum += r.vals[i] + } + return sum / time.Duration(n) + } + var sum time.Duration + for _, v := range r.vals { + sum += v + } + return sum / time.Duration(r.size) +} + +// windowDesc 当前窗口可观测状态 +type windowDesc struct { + Active int // 本窗口活跃账户数 + Total int // 本窗口总配额 + Seq []int64 // 本窗口执行顺序(accountID) + Quota map[int64]int + At time.Time +} + +// NewDeviceScheduler 创建设备调度器(不启动 goroutine;调用 Start()) +// poll 由 actor 封装:投递带 Done 的 PollCmd 并等待执行完成(pull 模式) +func NewDeviceScheduler(deviceID string, actor *DeviceActor, p *pool.WaitPayPool, cfg SchedulerConfig) *DeviceScheduler { + if cfg.BaseQuota <= 0 { + cfg.BaseQuota = 3 + } + if cfg.CapPct <= 0 || cfg.CapPct >= 1 { + cfg.CapPct = 0.6 + } + if cfg.DwellMax <= 0 { + cfg.DwellMax = 3 + } + if cfg.UrgentTTL <= 0 { + cfg.UrgentTTL = 8 * time.Second + } + if cfg.IdleSleep <= 0 { + cfg.IdleSleep = 500 * time.Millisecond + } + if cfg.PollMaxGap <= 0 || cfg.PollMaxGap < cfg.PollMinGap { + // 无有效间隔(测试置 0)→ 退化为不等待(极低延迟,与旧 execute 逐字节一致) + cfg.PollMinGap, cfg.PollMaxGap = 0, 0 + } else if cfg.PollMinGap <= 0 { + cfg.PollMinGap = 1 * time.Second + } + s := &DeviceScheduler{ + deviceID: deviceID, + pool: p, + cfg: cfg, + wake: make(chan struct{}, 1), + stop: make(chan struct{}), + done: make(chan struct{}), + roundStat: newRoundStat(10), + } + s.accounts.Store([]schedulerAcct{}) + s.current.Store(windowDesc{}) + // 注入默认 poller:经 actor 串行执行(Done 等待 = pull) + if cfg.AccountPoller == nil { + s.cfg.AccountPoller = func(ctx context.Context, provider string, userID int) error { + acct := actor.DeviceCtx.Account(provider, userID) + if acct == nil { + return nil // 账户已删除/离线,静默跳过 + } + done := make(chan error, 1) + actor.Enqueue(&PollCmd{AccountID: acct.AccountID, Provider: provider, UserID: userID, Done: done}) + select { + case err := <-done: + return err + case <-ctx.Done(): + return ctx.Err() + } + } + } + return s +} + +// SetAccounts 更新本设备账户快照(reconcile 调用) +func (s *DeviceScheduler) SetAccounts(accts []schedulerAcct) { + s.accounts.Store(accts) +} + +// Wake 下单唤醒(非阻塞;池空休眠时立即响应) +func (s *DeviceScheduler) Wake() { + select { + case s.wake <- struct{}{}: + default: + } +} + +// Start 启动调度 goroutine +func (s *DeviceScheduler) Start() { + go s.loop() +} + +// Stop 停止调度器(等待退出) +func (s *DeviceScheduler) Stop() { + close(s.stop) + <-s.done +} + +// loop 窗口决策循环:S1读池→S2配额→S3排序→S4执行→S5重算(池空休眠) +func (s *DeviceScheduler) loop() { + defer close(s.done) + ctx := context.Background() + for { + select { + case <-s.stop: + return + default: + } + // 设备维度:只读本设备子池快照(多设备天然隔离,互不干扰) + stat := s.pool.DeviceSnapshot(s.deviceID) + order, quotas := s.plan(stat) + if len(order) == 0 { + // 无活跃账户:休眠等待(下单唤醒或周期探测) + select { + case <-s.stop: + return + case <-s.wake: + case <-time.After(s.cfg.IdleSleep): + } + continue + } + s.publishWindow(order, quotas) + s.execute(ctx, order, quotas) + // 每笔 poll 已在 execute 内按 PollMinGap~PollMaxGap 随机间隔(默认1~3s)执行, + // 此处只需保证 stop/wake 响应,不再额外休眠固定时长(否则会稀释 1~3s 的节奏)。 + select { + case <-s.stop: + return + case <-s.wake: + case <-time.After(50 * time.Millisecond): + } + } +} + +// plan 纯调度决策:输入设备子池快照 → (执行顺序, 每账户配额) +// 配额:q_a = max(1, min(Q_total × count_a/Σcount, Q_total×CapPct)) +// 顺序:硬紧急(minTTL 0 && st.DupAmount { + active = append(active, st) + } + } + if len(active) == 0 { + return nil, nil + } + // S2 配额 + qTotal := len(active) * s.cfg.BaseQuota + totalOrders := 0 + for _, a := range active { + totalOrders += a.Count + } + capQuota := int(math.Floor(float64(qTotal) * s.cfg.CapPct)) + if capQuota < 1 { + capQuota = 1 + } + quotas := make(map[int64]int, len(active)) + for _, a := range active { + q := 1 // floor:至少 1 次 + if totalOrders > 0 { + q = int(math.Round(float64(qTotal) * float64(a.Count) / float64(totalOrders))) + } + if q < 1 { + q = 1 + } + if q > capQuota { + q = capQuota + } + quotas[a.AccountID] = q + } + // S3 排序 + sort.Slice(active, func(i, j int) bool { + ui, uj := active[i].MinTTL < s.cfg.UrgentTTL, active[j].MinTTL < s.cfg.UrgentTTL + switch { + case ui && !uj: + return true // 硬紧急置顶 + case !ui && uj: + return false + case ui && uj: + return active[i].MinTTL < active[j].MinTTL // 紧急内按 ttl 升序 + default: + if active[i].OldestAge != active[j].OldestAge { + return active[i].OldestAge > active[j].OldestAge // 等最久的先做 + } + return active[i].AccountID < active[j].AccountID // 确定性 + } + }) + order := make([]int64, 0, len(active)) + for _, a := range active { + order = append(order, a.AccountID) + } + return order, quotas +} + +// execute 按顺序执行:每账户连续 poll ≤ min(配额, DwellMax);每 poll 后查池, +// 账户池空 → 提前终止该 visit(防空转);命令执行失败不致命(下窗口重试)。 +func (s *DeviceScheduler) execute(ctx context.Context, order []int64, quotas map[int64]int) { + start := time.Now() + defer func() { s.roundStat.record(time.Since(start)) }() + accts := s.accounts.Load().([]schedulerAcct) + byID := make(map[int64]*schedulerAcct, len(accts)) + for i := range accts { + a := &accts[i] + byID[a.accountID] = a + } + for _, acctID := range order { + acct := byID[acctID] + if acct == nil { + continue + } + quota := quotas[acctID] + served := 0 + for served < quota { + if served >= s.cfg.DwellMax { + break // 连续占用上限:强制看一眼其它账户 + } + select { + case <-s.stop: + return + default: + } + // 时间驱动 + 随机间隔:每次账单页 poll 前均匀随机等待 [PollMinGap, PollMaxGap](默认1~3s), + // 打破固定节奏,避免微信/支付宝账单页被检测为抓取轰炸(风控)。仅同金额订单账户进入本循环。 + if g := s.pollGap(); g > 0 { + select { + case <-s.stop: + return + case <-time.After(g): + } + } + if err := s.cfg.AccountPoller(ctx, acct.provider, acct.userID); err != nil { + logger.Warn("ledgermon", "调度 poll 执行失败(下窗口重试)", + "device_id", s.deviceID, "account_id", acctID, "err", err) + } + served++ + // 反馈:同金额订单歧义解除(该账户不再有 ≥2 笔同金额待支付)→ 提前终止本 visit + if !s.pool.HasDupAmount(acctID) { + break + } + } + } +} + +// pollGap 返回相邻两次账单页 poll 之间的随机等待时长:均匀分布于 [PollMinGap, PollMaxGap]。 +// 随机化打破固定节奏(防微信/支付宝账单页被检测为抓取轰炸风控)。PollMaxGap<=0(测试退化)返回 0。 +func (s *DeviceScheduler) pollGap() time.Duration { + if s.cfg.PollMaxGap <= 0 { + return 0 + } + d := int64(s.cfg.PollMaxGap - s.cfg.PollMinGap) + if d <= 0 { + return s.cfg.PollMinGap + } + return s.cfg.PollMinGap + time.Duration(rand.Int63n(d)) +} + +// publishWindow 发布当前窗口可观测状态 +func (s *DeviceScheduler) publishWindow(order []int64, quotas map[int64]int) { + seq := make([]int64, len(order)) + copy(seq, order) + q := make(map[int64]int, len(quotas)) + for k, v := range quotas { + q[k] = v + } + s.current.Store(windowDesc{Active: len(order), Total: s.windowTotal(quotas), Seq: seq, Quota: q, At: time.Now()}) +} + +// windowTotal 窗口总配额 +func (s *DeviceScheduler) windowTotal(quotas map[int64]int) int { + t := 0 + for _, v := range quotas { + t += v + } + return t +} + +// CurrentWindow 当前窗口描述(admin 可观测) +func (s *DeviceScheduler) CurrentWindow() windowDesc { + d, _ := s.current.Load().(windowDesc) + return d +} + +// AvgRound 窗口平均耗时(最近 N 轮,poll 成本校准用;无数据返回 0) +func (s *DeviceScheduler) AvgRound() time.Duration { + return s.roundStat.avg() +} diff --git a/internal/ledgermon/scheduler_test.go b/internal/ledgermon/scheduler_test.go new file mode 100644 index 0000000..df36cdd --- /dev/null +++ b/internal/ledgermon/scheduler_test.go @@ -0,0 +1,233 @@ +package ledgermon + +import ( + "context" + "sync/atomic" + "testing" + "time" + + "paygateway/internal/model" + "paygateway/internal/pool" +) + +// newTestScheduler 创建测试调度器(无 actor,用假 poller 计数)。 +// 测试置 0 poll 间隔(PollMaxGap<=0 退化不等待),避免随机 1~3s 等待拖慢单测。 +func newTestScheduler(t *testing.T, p *pool.WaitPayPool, cfg SchedulerConfig) (*DeviceScheduler, *atomic.Int64) { + t.Helper() + var calls atomic.Int64 + if cfg.BaseQuota <= 0 { + cfg = DefaultSchedulerConfig() + } + cfg.PollMinGap, cfg.PollMaxGap = 0, 0 + // 池注入设备映射:全部账户归 "devT"(调度器读本设备快照) + p.SetDeviceOf(func(accountID int64) (string, bool) { return "devT", true }) + s := NewDeviceScheduler("devT", nil, p, cfg) + s.cfg.AccountPoller = func(ctx context.Context, provider string, userID int) error { + calls.Add(1) + return nil + } + s.SetAccounts([]schedulerAcct{ + {accountID: 1, provider: "alipay", userID: 0}, + {accountID: 2, provider: "wechat", userID: 0}, + {accountID: 3, provider: "wechat", userID: 999}, + }) + return s, &calls +} + +// TestPlan_QuotaProportional 配额与订单数挂钩:订单多的账户分更多配额(floor=1,cap 约束) +func TestPlan_QuotaProportional(t *testing.T) { + p := pool.NewWaitPayPool(30) + // 账户1: 10单同金额,账户2: 5单同金额,账户3: 1单 → 仅前两者含同金额订单 + for i := 0; i < 10; i++ { + p.Add(orderWith(1, "o1_"+itos(i), 100)) + } + for i := 0; i < 5; i++ { + p.Add(orderWith(2, "o2_"+itos(i), 200)) + } + p.Add(orderWith(3, "o3_0", 300)) + + s, _ := newTestScheduler(t, p, DefaultSchedulerConfig()) + order, quotas := s.plan(p.DeviceSnapshot("devT")) + + // 账户3 仅 1 单(无同金额)→ 不再入列(仅同金额订单账户才轮询账单页) + if len(order) != 2 { + t.Fatalf("仅同金额账户应入列,实际 %d(账户3 无同金额应排除)", len(order)) + } + // 账户1 订单最多 → 配额应 ≥ 账户2 配额 + if quotas[1] < quotas[2] { + t.Fatalf("订单多的账户配额应不少于订单少的,acct1=%d acct2=%d", quotas[1], quotas[2]) + } + // cap 约束:任何账户配额 ≤ Q_total × 60% + qTotal := 2 * s.cfg.BaseQuota + capQ := int(float64(qTotal) * s.cfg.CapPct) + for _, q := range quotas { + if q > capQ { + t.Fatalf("配额 %d 超过 cap %d", q, capQ) + } + } +} + +// TestPlan_OrderByOldestAge 顺序维度:等待最久的账户先做(age 降序) +func TestPlan_OrderByOldestAge(t *testing.T) { + p := pool.NewWaitPayPool(30) + now := time.Now() + // 账户2 的订单最久(提前 10min)且同为金额100;账户1 新近且同为金额100 + p.Add(&model.Order{OutTradeNo: "old", AccountID: 2, AmountMinor: 100, CreatedAt: now.Add(-10 * time.Minute)}) + p.Add(&model.Order{OutTradeNo: "old2", AccountID: 2, AmountMinor: 100, CreatedAt: now.Add(-9 * time.Minute)}) + p.Add(&model.Order{OutTradeNo: "new", AccountID: 1, AmountMinor: 100, CreatedAt: now}) + p.Add(&model.Order{OutTradeNo: "new2", AccountID: 1, AmountMinor: 100, CreatedAt: now}) + + s, _ := newTestScheduler(t, p, DefaultSchedulerConfig()) + order, _ := s.plan(p.DeviceSnapshot("devT")) + if len(order) != 2 { + t.Fatalf("应 2 个活跃账户(各自有同金额订单),实际 %d", len(order)) + } + if order[0] != 2 { + t.Fatalf("等待最久的账户2 应先执行,实际顺序 %v", order) + } +} + +// TestPlan_UrgentPreemptsAll 硬紧急:minTTL < UrgentTTL 的账户无条件置顶(即使订单少) +func TestPlan_UrgentPreemptsAll(t *testing.T) { + p := pool.NewWaitPayPool(30) + now := time.Now() + // 账户1: 8 单同金额(含歧义);账户2: 2 单同金额但即将关单 + for i := 0; i < 8; i++ { + p.Add(&model.Order{OutTradeNo: "bulk_" + itos(i), AccountID: 1, AmountMinor: 100, CreatedAt: now.Add(-30 * time.Second)}) + } + // 需要强制账户2 MinTTL 很小:CloseAt = CreatedAt + 30min 不够紧急。改用 1min 窗口池。 + p2 := pool.NewWaitPayPool(1) + for i := 0; i < 8; i++ { + p2.Add(&model.Order{OutTradeNo: "bulk_" + itos(i), AccountID: 1, AmountMinor: 100, CreatedAt: now.Add(-20 * time.Second)}) + } + p2.Add(&model.Order{OutTradeNo: "urgent", AccountID: 2, AmountMinor: 500, CreatedAt: now.Add(-55 * time.Second)}) + p2.Add(&model.Order{OutTradeNo: "urgent2", AccountID: 2, AmountMinor: 500, CreatedAt: now.Add(-54 * time.Second)}) + + s, _ := newTestScheduler(t, p2, DefaultSchedulerConfig()) + order, _ := s.plan(p2.DeviceSnapshot("devT")) + if len(order) != 2 { + t.Fatalf("应 2 个活跃账户,实际 %d", len(order)) + } + if order[0] != 2 { + t.Fatalf("硬紧急账户2 应无条件置顶(8 单账户让位),实际顺序 %v", order) + } +} + +// TestExecute_DwellMaxCap 连续 poll 上限:单账户配额大时连续执行不超过 DwellMax +func TestExecute_DwellMaxCap(t *testing.T) { + p := pool.NewWaitPayPool(30) + // 仅账户1 大量订单 → 窗口内配额可能 > DwellMax,但连续执行被 DwellMax 限制 + for i := 0; i < 20; i++ { + p.Add(orderWith(1, "o1_"+itos(i), 100)) + } + s, calls := newTestScheduler(t, p, DefaultSchedulerConfig()) + order, quotas := s.plan(p.DeviceSnapshot("devT")) + s.execute(tctx(), order, quotas) + if got := calls.Load(); got > int64(s.cfg.DwellMax) { + t.Fatalf("单账户连续 poll 超过 DwellMax=%d,实际 %d", s.cfg.DwellMax, got) + } +} + +// TestExecute_EarlyTerminateDupResolved 提前终止:visit 中途同金额歧义解除 → 立即停止该 visit +func TestExecute_EarlyTerminateDupResolved(t *testing.T) { + p := pool.NewWaitPayPool(30) + p.Add(orderWith(1, "o1_0", 100)) + p.Add(orderWith(1, "o1_1", 100)) + p.Add(orderWith(2, "o2_0", 500)) + p.Add(orderWith(2, "o2_1", 500)) + + s, _ := newTestScheduler(t, p, DefaultSchedulerConfig()) + // 自定义 poller:统计账户1(alipay/user0)的 poll 次数;第一次 poll 后清空账户1 池(模拟同金额订单入账), + // 验证 visit 提前终止——账户1 的 poll 次数应远小于其配额。 + var acct1Calls atomic.Int64 + s.cfg.AccountPoller = func(ctx context.Context, provider string, userID int) error { + if provider == "alipay" && userID == 0 { + n := acct1Calls.Add(1) + if n == 1 { + p.Remove("o1_0") + p.Remove("o1_1") + } + } + return nil + } + order, quotas := s.plan(p.DeviceSnapshot("devT")) + s.execute(tctx(), order, quotas) + if got := acct1Calls.Load(); got >= int64(quotas[1]) && quotas[1] > 1 { + t.Fatalf("同金额歧义解除后应提前终止 visit,配额=%d 实际 poll=%d", quotas[1], got) + } +} + +// TestPlan_NoDupNoActive 账户仅有金额互异的订单 → 不触发账单页轮询(通知栏按金额唯一匹配即可) +func TestPlan_NoDupNoActive(t *testing.T) { + p := pool.NewWaitPayPool(30) + p.Add(orderWith(1, "o1_0", 100)) + p.Add(orderWith(1, "o1_1", 200)) + p.Add(orderWith(2, "o2_0", 300)) + + s, _ := newTestScheduler(t, p, DefaultSchedulerConfig()) + order, quotas := s.plan(p.DeviceSnapshot("devT")) + if len(order) != 0 || quotas != nil { + t.Fatalf("无同金额订单应不触发账单页轮询:order=%v quotas=%v", order, quotas) + } +} + +// TestPlan_DupTriggersActive 同一账户出现 ≥2 笔同金额订单 → 触发账单页轮询 +func TestPlan_DupTriggersActive(t *testing.T) { + p := pool.NewWaitPayPool(30) + // 账户1: 两笔同为100 → 触发;账户2: 一笔100一笔200 → 不触发 + p.Add(orderWith(1, "a1", 100)) + p.Add(orderWith(1, "a2", 100)) + p.Add(orderWith(2, "b1", 100)) + p.Add(orderWith(2, "b2", 200)) + + s, _ := newTestScheduler(t, p, DefaultSchedulerConfig()) + order, _ := s.plan(p.DeviceSnapshot("devT")) + if len(order) != 1 || order[0] != 1 { + t.Fatalf("仅同金额账户1 应触发,实际 order=%v", order) + } +} + +// TestPlan_NoActiveNoQuota 池空 → 无活跃账户 → 无窗口 +func TestPlan_NoActiveNoQuota(t *testing.T) { + p := pool.NewWaitPayPool(30) + s, _ := newTestScheduler(t, p, DefaultSchedulerConfig()) + order, quotas := s.plan(p.DeviceSnapshot("devT")) + if len(order) != 0 || quotas != nil { + t.Fatalf("池空应无活跃账户:order=%v quotas=%v", order, quotas) + } +} + +// ---- 测试辅助 ---- + +func pollingScheduler(gapMin, gapMax time.Duration) *DeviceScheduler { + p := pool.NewWaitPayPool(30) + s := NewDeviceScheduler("devT", nil, p, SchedulerConfig{}) + s.cfg.PollMinGap, s.cfg.PollMaxGap = gapMin, gapMax + return s +} + +func TestPollGap_Range(t *testing.T) { + s := pollingScheduler(time.Second, 3*time.Second) + for i := 0; i < 500; i++ { + if g := s.pollGap(); g < time.Second || g > 3*time.Second { + t.Fatalf("pollGap 越界: %v (应∈[1s,3s])", g) + } + } +} + +func TestPollGap_Disabled(t *testing.T) { + s := pollingScheduler(0, 0) + if g := s.pollGap(); g != 0 { + t.Fatalf("PollMaxGap<=0 应返回 0 不等待,实际 %v", g) + } +} + +func orderWith(accountID int64, outTradeNo string, amountMinor int64) *model.Order { + return &model.Order{OutTradeNo: outTradeNo, AccountID: accountID, AmountMinor: amountMinor, CreatedAt: time.Now()} +} + +func itos(n int) string { + return string(rune('0' + n)) +} + +func tctx() context.Context { return context.Background() } diff --git a/internal/logger/logger.go b/internal/logger/logger.go new file mode 100755 index 0000000..a4db8f9 --- /dev/null +++ b/internal/logger/logger.go @@ -0,0 +1,300 @@ +// Package logger 日志系统:slog + 文件轮转 + 异步写库 + trace_id +package logger + +import ( + "context" + "encoding/json" + "fmt" + "io" + "log/slog" + "os" + "strings" + "sync" + "time" + + "gopkg.in/natefinch/lumberjack.v2" +) + +// Options 日志配置 +type Options struct { + Level string // debug / info / warn / error + LogFile string // 日志文件路径(空则不写文件);若含 "{date}" 占位符则按天分文件,如 "logs/paygateway-{date}.log" + MaxSizeMB int // 单个文件最大 MB + MaxBackups int // 保留文件数 + MaxAgeDays int // 保留天数 + EnableDB bool // 是否异步写库(管理界面查询) + DBWriter DBWriter +} + +const datePlaceholder = "{date}" + +// 按天分文件状态:当前日期 + 对应文件,跨天时切换 +var ( + dayMu sync.Mutex + curDay string + dayLogger *lumberjack.Logger +) + +// DBWriter 日志落库接口(由 store 包实现,避免循环依赖) +type DBWriter interface { + InsertLog(ctx context.Context, level, scope, message, traceID, fields string) error +} + +// LogEntry 结构化日志条目(异步队列用) +type LogEntry struct { + Level string + Scope string + Message string + TraceID string + Fields string + Time time.Time +} + +var ( + mu sync.Mutex + logger *slog.Logger + dbWriter DBWriter + logCh chan LogEntry + closeCh chan struct{} + once sync.Once + closeOnce sync.Once // Close 只允许执行一次(防双 close panic) + enabled bool +) + +// Init 初始化全局日志 +func Init(opts Options) { + once.Do(func() { + enabled = true + level := parseLevel(opts.Level) + + var writers []io.Writer + writers = append(writers, os.Stdout) + + if opts.LogFile != "" { + writers = append(writers, newFileWriter(opts)) + } + + multi := io.MultiWriter(writers...) + logger = slog.New(slog.NewJSONHandler(multi, &slog.HandlerOptions{ + Level: level, + })) + + dbWriter = opts.DBWriter + if opts.EnableDB && dbWriter != nil { + logCh = make(chan LogEntry, 2048) + closeCh = make(chan struct{}) + go dbWriterLoop() + } + }) +} + +// SetDBWriter 设置日志落库 writer(Init 之后调用;EnableDB 时生效) +func SetDBWriter(w DBWriter) { + if !enabled { + return + } + dbWriter = w + if logCh == nil && w != nil { + logCh = make(chan LogEntry, 2048) + closeCh = make(chan struct{}) + go dbWriterLoop() + } +} + +// Close 关闭日志(优雅退出时调用,flush 队列) +// 只关闭 closeCh 通知 writer 排空队列后退出;不关闭 logCh —— +// 避免后续任何 logger 调用向已关闭 channel 发送导致 panic(向关闭 channel 发送会 panic,非阻塞 select 也拦不住) +func Close() { + if !enabled || logCh == nil { + return + } + closeOnce.Do(func() { + close(closeCh) + }) +} + +// newFileWriter 构造文件写入器。LogFile 含 "{date}" 时按天分文件,否则单文件+大小轮转。 +func newFileWriter(opts Options) io.Writer { + if !strings.Contains(opts.LogFile, datePlaceholder) { + return &lumberjack.Logger{ + Filename: opts.LogFile, + MaxSize: opts.MaxSizeMB, + MaxBackups: opts.MaxBackups, + MaxAge: opts.MaxAgeDays, + Compress: true, + } + } + return &dayFileWriter{opts: opts} +} + +// dayFileWriter 按天切换文件的 io.Writer。每次 Write 检查当前日期,跨天时重建 lumberjack 指向新文件。 +type dayFileWriter struct { + opts Options +} + +func (w *dayFileWriter) Write(p []byte) (int, error) { + day := time.Now().Format("2006-01-02") + dayMu.Lock() + defer dayMu.Unlock() + if dayLogger == nil || curDay != day { + path := strings.ReplaceAll(w.opts.LogFile, datePlaceholder, day) + dayLogger = &lumberjack.Logger{ + Filename: path, + MaxSize: w.opts.MaxSizeMB, + MaxBackups: w.opts.MaxBackups, + MaxAge: w.opts.MaxAgeDays, + Compress: true, + } + curDay = day + } + return dayLogger.Write(p) +} + +// parseLevel 解析日志级别 +func parseLevel(s string) slog.Level { + switch s { + case "debug": + return slog.LevelDebug + case "warn": + return slog.LevelWarn + case "error": + return slog.LevelError + default: + return slog.LevelInfo + } +} + +// WithTrace 返回带 trace_id 的 logger(gin 中间件使用) +func WithTrace(traceID string) *slog.Logger { + if !enabled || logger == nil { + return slog.Default() + } + return logger.With("trace_id", traceID) +} + +// Debug / Info / Warn / Error 全局快捷方法 +func Debug(scope, msg string, args ...any) { + if !enabled || logger == nil { + return + } + entry := logEntry("debug", scope, msg, "", args) + logger.Debug(msg, append([]any{"scope", scope}, args...)...) + pushDB(entry) +} + +func Info(scope, msg string, args ...any) { + if !enabled || logger == nil { + return + } + logger.Info(msg, append([]any{"scope", scope}, args...)...) + pushDB(logEntry("info", scope, msg, "", args)) +} + +func Warn(scope, msg string, args ...any) { + if !enabled || logger == nil { + return + } + logger.Warn(msg, append([]any{"scope", scope}, args...)...) + pushDB(logEntry("warn", scope, msg, "", args)) +} + +func Error(scope, msg string, args ...any) { + if !enabled || logger == nil { + return + } + logger.Error(msg, append([]any{"scope", scope}, args...)...) + pushDB(logEntry("error", scope, msg, "", args)) +} + +// logEntry 构造结构化条目(trace_id 从 slog 参数中提取) +func logEntry(level, scope, msg, traceID string, args []any) LogEntry { + fields := make(map[string]any) + for i := 0; i+1 < len(args); i += 2 { + if k, ok := args[i].(string); ok { + fields[k] = args[i+1] + } + } + if tid, ok := fields["trace_id"]; ok { + traceID = fmt.Sprint(tid) + delete(fields, "trace_id") + } + fieldsJSON := "" + if len(fields) > 0 { + b, _ := jsonMarshal(fields) + fieldsJSON = string(b) + } + return LogEntry{ + Level: level, + Scope: scope, + Message: msg, + TraceID: traceID, + Fields: fieldsJSON, + Time: time.Now(), + } +} + +func jsonMarshal(v any) ([]byte, error) { + return json.Marshal(v) +} + +// dbWriterLoop 异步批量写库:攒 50 条或 1s flush +func dbWriterLoop() { + batch := make([]LogEntry, 0, 50) + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + + flush := func() { + if len(batch) == 0 || dbWriter == nil { + return + } + for _, e := range batch { + if err := dbWriter.InsertLog(context.Background(), e.Level, e.Scope, e.Message, e.TraceID, e.Fields); err != nil { + // 写库失败不影响主流程,降级打印 + fmt.Fprintf(os.Stderr, "[logger] db write failed: %v\n", err) + } + } + batch = batch[:0] + } + + for { + select { + case e, ok := <-logCh: + if !ok { + flush() + return + } + batch = append(batch, e) + if len(batch) >= 50 { + flush() + } + case <-ticker.C: + flush() + case <-closeCh: + // 收到关闭信号:排空剩余日志后退出(logCh 永不关闭,避免发送方 panic) + flush() + for { + select { + case e := <-logCh: + batch = append(batch, e) + if len(batch) >= 50 { + flush() + } + default: + flush() + return + } + } + } + } +} + +func pushDB(e LogEntry) { + if logCh == nil { + return + } + select { + case logCh <- e: + default: + // 队列满,丢弃(保主流程) + } +} diff --git a/internal/model/exception.go b/internal/model/exception.go new file mode 100755 index 0000000..62866a4 --- /dev/null +++ b/internal/model/exception.go @@ -0,0 +1,63 @@ +package model + +// ExceptionOrder 人工异常处理池订单(自动化兜底失败后的最终兜底) +// 只增不改:resolved 后可 reopen,不可删除;全程审计(exception_logs) +type ExceptionOrder struct { + ID int64 `json:"id"` + OutTradeNo string `json:"out_trade_no"` + Pid string `json:"pid"` + UpstreamName string `json:"upstream_name"` + Channel string `json:"channel"` + AmountMinor int64 `json:"amount_minor"` + OrderStatus string `json:"order_status"` + ChannelStatus string `json:"channel_status"` // TRADE_SUCCESS / TRADE_CLOSED / QUERY_FAILED / UNKNOWN + ChannelTxID string `json:"channel_tx_id"` + PaidAmount int64 `json:"paid_amount"` // 人工确认的实际支付金额(默认=AmountMinor) + Payer string `json:"payer"` + Reason string `json:"reason"` + Phase string `json:"phase"` // pending / processing / resolved + ResolvedAction string `json:"resolved_action"` + Evidence string `json:"evidence"` // 证据时间线 JSON(自动收集,只读) + Suggestion string `json:"suggestion"` + Handler string `json:"handler"` + HandlerNote string `json:"handler_note"` + ResolvedAt string `json:"resolved_at"` + UpdatedAt string `json:"updated_at"` + CreatedAt string `json:"created_at"` +} + +// ExceptionLog 异常池操作审计 +type ExceptionLog struct { + ID int64 `json:"id"` + ExceptionID int64 `json:"exception_id"` + Action string `json:"action"` + Operator string `json:"operator"` + Detail string `json:"detail"` + CreatedAt string `json:"created_at"` +} + +// 异常原因(自动进池触发点) +const ( + ExReasonQueryFailed = "query_failed" // 查单连续失败,无法确认 + ExReasonAmountMismatch = "amount_mismatch" // 查单金额 ≠ 订单金额 + ExReasonStateConflict = "state_conflict" // 回调与查单状态矛盾 + ExReasonCloseUncertain = "close_uncertain" // 关单时查单失败,存疑 + ExReasonNotifyDead = "notify_dead" // 通知 dead 且重发失败 + ExReasonManual = "manual" // 管理后台手动加入 + ExReasonListenOffline = "listen_offline" // 监听端掉线,无法确认是否已支付(个人通道) + ExReasonListenTimeout = "listen_timeout" // 监听端在线但超时无推送,存疑(个人通道) +) + +// 异常订单阶段 +const ( + ExPhasePending = "pending" + ExPhaseProcessing = "processing" + ExPhaseResolved = "resolved" +) + +// 人工处理动作(resolved_action) +const ( + ExActionSettle = "settle" // 确认入账 + ExActionClose = "close" // 确认未支付,关单 + ExActionNoop = "noop" // 无需处理 +) diff --git a/internal/model/order.go b/internal/model/order.go new file mode 100755 index 0000000..7a7c244 --- /dev/null +++ b/internal/model/order.go @@ -0,0 +1,134 @@ +// Package model 领域模型:订单、状态常量、金额类型 +package model + +import ( + "fmt" + "strconv" + "strings" + "time" +) + +// 订单状态(状态机) +const ( + StatusCreated = "CREATED" // 已创建 + StatusWaitPay = "WAIT_BUYER_PAY" // 等待支付(与易支付协议保持一致) + StatusConfirming = "CONFIRMING" // 链上确认中(未来 crypto 用) + StatusSuccess = "TRADE_SUCCESS" // 支付成功(与易支付协议保持一致) + StatusClosed = "TRADE_CLOSED" // 已关闭/超时(与易支付协议保持一致) + StatusFailed = "FAILED" // 支付失败(链上重组等) +) + +// 订单状态允许的流转(防止状态回跳/非法跳转) +var allowedTransitions = map[string][]string{ + StatusCreated: {StatusWaitPay, StatusClosed}, + StatusWaitPay: {StatusSuccess, StatusClosed, StatusConfirming}, + StatusConfirming: {StatusSuccess, StatusFailed, StatusClosed}, + StatusSuccess: {}, // 终态,不可再变 + StatusClosed: {}, // 终态 + StatusFailed: {}, // 终态 +} + +// CanTransition 判断状态流转是否合法 +func CanTransition(from, to string) bool { + if from == to { + return true // 幂等 + } + for _, next := range allowedTransitions[from] { + if next == to { + return true + } + } + return false +} + +// Order 订单领域模型 +type Order struct { + ID int64 `json:"id"` + OutTradeNo string `json:"out_trade_no"` // 接入方订单号(幂等依据) + GatewayNo string `json:"gateway_no"` // 网关订单号 + Channel string `json:"channel"` // alipay / wechat / paypal / usdt + ChannelTxID string `json:"channel_tx_id"` // 通道交易号(回调去重) + AccountID int64 `json:"account_id"` // 处理这笔订单的账户ID(账户池) + Pid string `json:"pid"` // 上游易支付商户ID(多商户路由,空=default) + UpstreamName string `json:"upstream_name,omitempty"` // 上游名称(来源标识) + Attach string `json:"attach,omitempty"` // 上游请求ID(易支付 attach 字段,原样透传,用于对账) + AccountName string `json:"account_name,omitempty"` // 账户名称(展示用,查询时附加) + AmountMinor int64 `json:"amount_minor"` // 金额(最小单位:分) + Currency string `json:"currency"` // CNY / USD / USDT + Subject string `json:"subject"` // 商品名称 + Status string `json:"status"` // 状态机 + NotifyURL string `json:"notify_url"` // 回调地址(接入方) + ReturnURL string `json:"return_url"` // 跳转地址 + QrCode string `json:"qr_code"` // 二维码内容 + PayURL string `json:"pay_url"` // 支付链接/凭据 + Payer string `json:"payer"` // 付款方标识(可选) + NoteCode string `json:"note_code,omitempty"` // 个人免签:备注码 + QrToken string `json:"qr_token,omitempty"` // 个人免签:套码 token + DeviceID string `json:"device_id,omitempty"` // 个人免签:承接的监听端设备 + BuyerKey string `json:"buyer_key,omitempty"` // 下单环境唯一标识(IP+UA+设备指纹 hash,买家级串行/防刷单用) + FPKey string `json:"fp_key,omitempty"` // 浏览器指纹标识(canvas/时区/屏幕 hash,支付页 JS 上报,抗换 IP) + Notified bool `json:"notified"` // 是否已通知接入方 + CreatedAt time.Time `json:"created_at"` + SessionExpiresAt time.Time `json:"session_expires_at,omitempty"` + PaidAt *time.Time `json:"paid_at,omitempty"` + NotifiedAt *time.Time `json:"notified_at,omitempty"` + NotifyCount int `json:"notify_count"` // 通知次数(outbox 重试用) + + // 人工异常处理标记(人工池处理后永久留痕) + ExceptionID int64 `json:"exception_id"` + HandledBy string `json:"handled_by"` + HandledAction string `json:"handled_action"` // manual_settle / manual_close / noop + HandledAt string `json:"handled_at"` + HandledNote string `json:"handled_note"` +} + +// 兼容易支付协议:金额为字符串"元",需要与最小单位互转 + +// MoneyYuan 返回金额(元,字符串,易支付协议格式) +func (o *Order) MoneyYuan() string { + if o.AmountMinor <= 0 { + return "0.00" + } + return fmt.Sprintf("%d.%02d", o.AmountMinor/100, o.AmountMinor%100) +} + +// ParseMoneyToMinor 将金额字符串(元)解析为最小单位(分) +// 支持 "0.01"、"10"、"10.5"、"10.50" 等格式;非法返回 error +func ParseMoneyToMinor(money string) (int64, error) { + money = strings.TrimSpace(money) + if money == "" { + return 0, fmt.Errorf("金额为空") + } + // 拆分整数和小数部分 + parts := strings.Split(money, ".") + intPart := parts[0] + fracPart := "" + if len(parts) > 1 { + fracPart = parts[1] + if len(parts) > 2 { + return 0, fmt.Errorf("金额格式非法: %s", money) + } + } + // 整数部分 + whole, err := strconv.ParseInt(intPart, 10, 64) + if err != nil || whole < 0 { + return 0, fmt.Errorf("金额整数部分非法: %s", money) + } + // 小数部分:最多 2 位,补零到 2 位 + if len(fracPart) > 2 { + return 0, fmt.Errorf("金额最多两位小数: %s", money) + } + frac := fracPart + strings.Repeat("0", 2-len(fracPart)) + fracInt := int64(0) + if frac != "" { + fracInt, err = strconv.ParseInt(frac, 10, 64) + if err != nil { + return 0, fmt.Errorf("金额小数部分非法: %s", money) + } + } + minor := whole*100 + fracInt + if minor <= 0 { + return 0, fmt.Errorf("金额必须大于 0: %s", money) + } + return minor, nil +} diff --git a/internal/notify/urlguard.go b/internal/notify/urlguard.go new file mode 100644 index 0000000..838f340 --- /dev/null +++ b/internal/notify/urlguard.go @@ -0,0 +1,139 @@ +// Package notify 回调 URL 安全防护: +// SSRF 面在商户可控的 notify_url —— 下单时校验格式,发送时用安全拨号器 +// 在 TCP 连接层面复核解析出的 IP(防 DNS rebinding 绕过域名黑名单)。 +package notify + +import ( + "context" + "fmt" + "net" + "net/http" + "net/url" + "strings" + "time" +) + +// ValidateNotifyURL 校验回调地址。 +// scheme 必须为 http/https;allowPrivate=false 时拒绝解析到私网/环回/链路本地/未指定地址的目标。 +func ValidateNotifyURL(raw string, allowPrivate bool) error { + if raw == "" { + return nil // 空地址合法(worker 跳过发送) + } + u, err := url.Parse(raw) + if err != nil { + return fmt.Errorf("notify_url 无法解析: %w", err) + } + if u.Scheme != "http" && u.Scheme != "https" { + return fmt.Errorf("notify_url 协议必须是 http/https: %q", u.Scheme) + } + host := u.Hostname() + if host == "" { + return fmt.Errorf("notify_url 缺少主机名") + } + if allowPrivate { + return nil + } + ips, err := net.LookupIP(host) + if err != nil { + return fmt.Errorf("notify_url 域名解析失败: %w", err) + } + for _, ip := range ips { + if isPrivateIP(ip) { + return fmt.Errorf("notify_url 指向内网地址 %s(已拒绝)", ip) + } + } + return nil +} + +// IsPrivateURL 判断 URL 主机指向是否为内网/环回/链路本地等非公网地址 +// (return_url 兜底重写用:return_url 是浏览器跳转,不涉及 SSRF 拨号)。 +// DNS 解析失败按"非私有"处理:语法级检查已可靠拦截 localhost/环回/私网 IP 字面量, +// 纯域名解析失败(网关断网/无 resolver)不应把正常公网域名饿死出候选。 +func IsPrivateURL(raw string) bool { + u, err := url.Parse(raw) + if err != nil || u.Host == "" { + return true + } + if u.Scheme != "http" && u.Scheme != "https" { + return true + } + if ip := net.ParseIP(u.Hostname()); ip != nil { + return isPrivateIP(ip) + } + host := strings.ToLower(u.Hostname()) + if host == "localhost" || strings.HasSuffix(host, ".local") { + return true + } + ips, err := net.LookupIP(u.Hostname()) + if err != nil { + return false // 解析不可达:语法检查已过,放行(见函数注释) + } + for _, ip := range ips { + if isPrivateIP(ip) { + return true + } + } + return false +} + +func isPrivateIP(ip net.IP) bool { + privateRanges := []struct { + network *net.IPNet + }{ + {cidr("127.0.0.0/8")}, // IPv4 环回 + {cidr("10.0.0.0/8")}, // 私网 + {cidr("172.16.0.0/12")}, // 私网 + {cidr("192.168.0.0/16")}, // 私网 + {cidr("169.254.0.0/16")}, // 链路本地(云元数据 169.254.169.254 在此段) + {cidr("0.0.0.0/8")}, // 未指定 + {cidr("100.64.0.0/10")}, // 运营商级 NAT(Tailscale 等) + {cidr("::1/128")}, // IPv6 环回 + {cidr("fc00::/7")}, // IPv6 ULA + {cidr("fe80::/10")}, // IPv6 链路本地 + } + for _, r := range privateRanges { + if r.network.Contains(ip) { + return true + } + } + return false +} + +func cidr(s string) *net.IPNet { + _, n, err := net.ParseCIDR(s) + if err != nil { + panic(fmt.Sprintf("内置 CIDR 非法 %s: %v", s, err)) + } + return n +} + +// NewNotifyHTTPClient 构造带 SSRF 复核的 HTTP 客户端: +// Control 复核发生在实际 connect 的地址上,域名解析结果与连接地址不可替换 +// (DNS rebinding 无效)。allowPrivate=true 时行为与默认客户端一致。 +func NewNotifyHTTPClient(timeout time.Duration, allowPrivate bool) *http.Client { + dialer := &net.Dialer{Timeout: timeout} + transport := &http.Transport{} + if !allowPrivate { + transport.DialContext = func(ctx context.Context, network, addr string) (net.Conn, error) { + host, _, err := net.SplitHostPort(addr) + if err == nil { + var ips []net.IP + if ip := net.ParseIP(host); ip != nil { + ips = []net.IP{ip} + } else if ips, err = net.LookupIP(host); err != nil { + return nil, fmt.Errorf("notify 目标解析失败: %w", err) + } + for _, ip := range ips { + if isPrivateIP(ip) { + return nil, fmt.Errorf("notify 目标指向内网地址 %s(已拒绝)", ip) + } + } + } + return dialer.DialContext(ctx, network, addr) + } + } + return &http.Client{ + Timeout: timeout, + Transport: transport, + } +} diff --git a/internal/notify/urlguard_test.go b/internal/notify/urlguard_test.go new file mode 100644 index 0000000..b12a6ed --- /dev/null +++ b/internal/notify/urlguard_test.go @@ -0,0 +1,62 @@ +package notify + +import ( + "net" + "testing" +) + +func TestValidateNotifyURL(t *testing.T) { + tests := []struct { + name string + raw string + allowPrivate bool + wantErr bool + }{ + {"空地址合法", "", true, false}, + {"空地址私网封锁下仍合法", "", false, false}, + {"公网 http(IP字面量)", "http://8.8.4.4/notify", false, false}, + {"公网 https(IP字面量)", "https://[2606:4700:4700::1111]/notify.php", false, false}, + {"ftp 协议拒绝", "ftp://example.com/x", true, true}, + {"javascript 协议拒绝", "javascript:alert(1)", true, true}, + {"缺主机名", "http:///path", true, true}, + {"环回封禁", "http://127.0.0.1:3000/api/notify", false, true}, + {"环回放行", "http://127.0.0.1:3000/api/notify", true, false}, + {"localhost 域名封禁", "http://localhost:8080/notify", false, true}, + {"内网 IP 封禁", "http://192.168.1.10:8080/notify", false, true}, + {"链路本地封禁(云元数据)", "http://169.254.169.254/latest/meta-data", false, true}, + {"Tailscale CGNAT 段封禁", "http://100.64.0.1/notify", false, true}, + {"内网 IP 放行", "http://192.168.1.10:8080/notify", true, false}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateNotifyURL(tt.raw, tt.allowPrivate) + if (err != nil) != tt.wantErr { + t.Fatalf("ValidateNotifyURL(%q, %v) err=%v, wantErr=%v", tt.raw, tt.allowPrivate, err, tt.wantErr) + } + }) + } +} + +func TestIsPrivateIP(t *testing.T) { + cases := map[string]bool{ + "127.0.0.1": true, + "10.1.2.3": true, + "172.16.0.1": true, + "172.31.255.255": true, + "172.32.0.1": false, + "192.168.0.1": true, + "169.254.169.254": true, + "100.64.0.1": true, + "8.8.8.8": false, + "1.1.1.1": false, + "::1": true, + "fe80::1": true, + "fd00::1": true, + "2606:4700:4700::": false, + } + for ip, want := range cases { + if got := isPrivateIP(net.ParseIP(ip)); got != want { + t.Errorf("isPrivateIP(%s) = %v, want %v", ip, got, want) + } + } +} diff --git a/internal/notify/worker.go b/internal/notify/worker.go new file mode 100755 index 0000000..1b81fe9 --- /dev/null +++ b/internal/notify/worker.go @@ -0,0 +1,181 @@ +// Package notify 可靠回调投递(Outbox):worker 扫描 pending 任务 → POST 接入方 → 重试 +package notify + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" + + "paygateway/internal/alert" + "paygateway/internal/logger" + "paygateway/internal/store" +) + +// Worker 回调投递 worker +type Worker struct { + store store.Store + client *http.Client + interval time.Duration + // RetryDelays 重试间隔(指数退避) + RetryDelays []time.Duration + // MaxRetries 最大重试次数(超过标记 dead) + MaxRetries int + alert *alert.Client // 告警(RESILIENCE P1-3,nil=禁用) +} + +// SetAlert 注入告警客户端(可选) +func (w *Worker) SetAlert(a *alert.Client) { w.alert = a } + +// NewWorker 创建回调 worker。allowPrivateNotify=false 时启用 SSRF 复核拨号器。 +func NewWorker(st store.Store, allowPrivateNotify bool) *Worker { + return &Worker{ + store: st, + client: NewNotifyHTTPClient(15*time.Second, allowPrivateNotify), + interval: 3 * time.Second, + RetryDelays: []time.Duration{ + 1 * time.Minute, 5 * time.Minute, 15 * time.Minute, + 1 * time.Hour, 6 * time.Hour, 24 * time.Hour, + }, + MaxRetries: 6, // 总窗口 ≈ 31h,覆盖上游长时间不可用 + } +} + +// Start 启动循环(阻塞,应放 goroutine) +func (w *Worker) Start(ctx context.Context) { + logger.Info("notify", "回调 worker 启动", "interval", w.interval.String()) + ticker := time.NewTicker(w.interval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + logger.Info("notify", "回调 worker 停止") + return + case <-ticker.C: + w.processBatch(ctx) + } + } +} + +// processBatch 处理一批到期任务 +func (w *Worker) processBatch(ctx context.Context) { + tasks, err := w.store.GetPendingNotifyTasks(ctx, 20) + if err != nil { + logger.Error("notify", "获取回调任务失败", "err", err) + return + } + for _, task := range tasks { + w.process(ctx, task) + } +} + +// process 处理单个任务 +func (w *Worker) process(ctx context.Context, task *store.NotifyTask) { + // 解析 payload(易支付协议参数,已含签名) + var params map[string]string + if err := json.Unmarshal([]byte(task.Payload), ¶ms); err != nil { + // payload 损坏:重试也无效,标记 dead + logger.Error("notify", "回调任务 payload 损坏,标记 dead", "task_id", task.ID, "err", err) + w.store.UpdateNotifyTask(ctx, task.ID, "dead", task.RetryCount, time.Now()) + return + } + + notifyURL := params["notify_url"] + if notifyURL == "" { + // 没有回调地址(payload 里应该有),标记成功跳过 + w.store.UpdateNotifyTask(ctx, task.ID, "success", task.RetryCount, time.Now()) + return + } + // 注意:notify_url 保留在 body 中并参与签名。 + // New API(go-epay)验签逻辑 = 用收到的全部参数重算签名比对, + // 若删除 notify_url 会导致签名不一致 → 验签失败。易支付标准签名本就包含 notify_url。 + + body := buildForm(params) + start := time.Now() + resp, err := w.client.Post(notifyURL, "application/x-www-form-urlencoded", bytes.NewReader(body)) + latency := time.Since(start).Milliseconds() + + if err != nil { + w.retryOrDead(ctx, task, "请求失败: "+err.Error()) + return + } + defer resp.Body.Close() + respBody, _ := io.ReadAll(resp.Body) + + logger.Info("notify", "回调接入方", + "task_id", task.ID, + "out_trade_no", params["out_trade_no"], + "pid", params["pid"], + "url", notifyURL, + "status", resp.StatusCode, + "latency_ms", latency) + + // 易支付协议:商户处理成功必须响应正文 "success"。 + // 仅凭 HTTP 200 判定会把 LB/网关的错误页、鉴权墙当成功,导致上游永远收不到有效通知。 + if resp.StatusCode == 200 && strings.EqualFold(strings.TrimSpace(string(respBody)), "success") { + logger.Info("notify", "回调成功 ✅", "task_id", task.ID) + w.store.UpdateNotifyTask(ctx, task.ID, "success", task.RetryCount, time.Now()) + // 同步更新订单通知状态(管理后台展示) + if otn := params["out_trade_no"]; otn != "" { + if err := w.store.MarkNotified(ctx, otn, task.RetryCount+1); err != nil { + logger.Warn("notify", "更新订单通知状态失败", "err", err, "out_trade_no", otn) + } + } + } else if resp.StatusCode == 200 { + logger.Warn("notify", "回调 HTTP 200 但响应非 success(视为失败重试)", + "task_id", task.ID, "body", string(respBody)) + w.retryOrDead(ctx, task, "200 但响应体非 success") + } else { + logger.Warn("notify", "回调失败", "task_id", task.ID, "status", resp.StatusCode, "body", string(respBody)) + w.retryOrDead(ctx, task, resp.Status) + } +} + +// retryOrDead 重试或标记 dead +func (w *Worker) retryOrDead(ctx context.Context, task *store.NotifyTask, reason string) { + retryCount := task.RetryCount + 1 + if retryCount > w.MaxRetries { + logger.Error("notify", "回调任务超过最大重试,标记 dead", "task_id", task.ID, "reason", reason) + w.store.UpdateNotifyTask(ctx, task.ID, "dead", retryCount, time.Now()) + // 告警:通知 dead(RESILIENCE P1-3) + if w.alert != nil { + w.alert.Send("error", "回调任务dead", + fmt.Sprintf("订单 %s 回调超过最大重试标记 dead(%d 次)\n原因: %s", taskPayloadTradeNo(task), retryCount, reason)) + } + return + } + delay := time.Minute + if retryCount-1 < len(w.RetryDelays) { + delay = w.RetryDelays[retryCount-1] + } + next := time.Now().Add(delay) + logger.Warn("notify", "回调任务将重试", "task_id", task.ID, "retry", retryCount, "next", next.Format(time.RFC3339)) + w.store.UpdateNotifyTask(ctx, task.ID, "pending", retryCount, next) +} + +// taskPayloadTradeNo 从任务 payload 提取订单号(告警信息用,失败返回空串) +func taskPayloadTradeNo(task *store.NotifyTask) string { + var params map[string]string + if err := json.Unmarshal([]byte(task.Payload), ¶ms); err != nil { + return "" + } + return params["out_trade_no"] +} + +// buildForm 构造表单 body(值做 URL 编码,兼容中文/特殊字符) +func buildForm(params map[string]string) []byte { + var buf bytes.Buffer + for k, v := range params { + buf.WriteString(url.QueryEscape(k) + "=" + url.QueryEscape(v) + "&") + } + s := buf.String() + if len(s) > 0 { + s = s[:len(s)-1] + } + return []byte(s) +} diff --git a/internal/notifywatcher/hash.go b/internal/notifywatcher/hash.go new file mode 100644 index 0000000..85cc891 --- /dev/null +++ b/internal/notifywatcher/hash.go @@ -0,0 +1,12 @@ +package notifywatcher + +import ( + "crypto/sha256" + "encoding/hex" +) + +// sha256Hex 计算字符串的 SHA-256 十六进制(指纹去重键)。 +func sha256Hex(s string) string { + h := sha256.Sum256([]byte(s)) + return hex.EncodeToString(h[:]) +} diff --git a/internal/notifywatcher/notifywatcher.go b/internal/notifywatcher/notifywatcher.go new file mode 100644 index 0000000..f5e7d2d --- /dev/null +++ b/internal/notifywatcher/notifywatcher.go @@ -0,0 +1,221 @@ +// Package notifywatcher 通知栏快速通道监听(独立于 ledgermon 的账本慢通道)。 +// +// 背景(2026-08-30 真机实测 + 用户需求确认): +// - 微信/支付宝收款到账会弹系统通知(dumpsys notification --noredact 可读全文); +// - 通知栏比账本页快 ~15s,是收款感知的快速通道; +// - 通知栏数据不清理会一直存在 → 匹配窗口必须以通知 when(收款发生时刻)为锚, +// 不能用 time.Now()(否则旧通知会错误匹配新订单); +// - 账户区分靠 uid:微信/支付宝主空间 uid 无 999 前缀,双开(XSpace) uid 有 999 前缀 +// (实测:com.tencent.mm uid=10277 主号 / uid=99910277 双开;支付宝同理 10250/99910250)。 +// +// 本包只负责:设备枚举 → 按设备轮询 dumpsys notification --noredact → 解析 → +// 指纹去重 → 回调 main.go 注入的匹配入账函数(复用 matchPersonalOrder 链路)。 +// 不直接触碰 UI/账本页,与 ledgermon 的 DeviceActor 串行队列零竞争。 +package notifywatcher + +import ( + "context" + "sync" + "time" + + "paygateway/internal/logger" +) + +// 默认轮询参数(Config 未设置时兜底)。 +const ( + defaultPollEvery = 500 * time.Millisecond // 有 WAIT_PAY 订单时的高频轮询 + defaultIdleEvery = 5 * time.Second // 池空时的低频探测 + defaultShellTimeout = 3 * time.Second // 单次 dumpsys 超时 +) + +// Device 设备级监听单元(由 DeviceResolver 枚举,main.go 注入)。 +type Device struct { + DeviceID string // personal.devices.device_id + Serial string // adb serial(host:port 或 USB 序列号) +} + +// DeviceResolver 在线设备枚举(main.go 注入:ListDevices + adb.Online 过滤)。 +type DeviceResolver func(ctx context.Context) ([]Device, error) + +// HasPending 是否仍有待支付订单(main.go 注入:waitPayPool.Snapshot().Total > 0)。 +// 池 reconcile 间隙可能短暂为空,调用方用低频探测兜底。 +type HasPending func(ctx context.Context) bool + +// Handler 收款通知回调(main.go 注入:复用 matchPersonalOrder 入账链路)。 +// 返回 error 表示处理失败(记录日志,不影响轮询继续)。 +type Handler func(ctx context.Context, ev Event) error + +// Sheller adb 命令执行抽象(生产 = *adbmgr.Manager;测试 = fake 桩)。 +// 只暴露 dumpsys 所需的 Shell 方法,与 ledgermon 的 UI 操作零交集。 +type Sheller interface { + Shell(ctx context.Context, timeout time.Duration, serial, command string) (string, error) +} + +// SeenStore 去重存储(main.go 注入:personalDB.PushExists)。 +// 指纹含设备ID+通知key+金额+when,同一通知跨轮幂等。 +type SeenStore interface { + PushExists(ctx context.Context, deviceID, rawHash string) (bool, error) +} + +// Config 轮询参数(零值使用默认)。 +type Config struct { + PollEvery time.Duration // 有 WAIT_PAY 订单轮询间隔(默认 500ms) + IdleEvery time.Duration // 池空探测间隔(默认 5s) + ShellTimeout time.Duration // 单次 dumpsys 超时(默认 3s) +} + +// Manager 通知栏快速通道监听器。 +// +// 与 ledgermon 的关系(架构决策,用户确认): +// - 完全独立:自己的 goroutine + 自己的 adbMgr() 实例(Manager.mu 独立 → 天然并行, +// 实测 dumpsys notification ∥ uiautomator dump ∥ input 互不阻塞); +// - 订单驱动:waitPayPool 有订单 → 高频轮询;池空 → 低频探测,不空转; +// - 匹配回调复用 main.go 的 matchPersonalOrder(空间账户过滤 + 金额时间窗 + 冻结守卫), +// 与账本链路共享同一套资金安全逻辑,绝不另起炉灶。 +type Manager struct { + adb Sheller + resolve DeviceResolver + hasPendFn HasPending // 订单驱动判定(nil 视为恒有空闲) + handler Handler + seen SeenStore + cfg Config + wake chan struct{} // 事件唤醒信号(新订单进池触发,容量1信号合并) + + mu sync.Mutex + lastPoll map[string]time.Time // deviceID → 上次轮询时刻(日志节流) +} + +// New 创建通知栏监听器。adb 为 nil 时禁用(设备枚举阶段直接返回)。 +func New(adb Sheller, resolve DeviceResolver, hasPend HasPending, + handler Handler, seen SeenStore, cfg Config) *Manager { + if cfg.PollEvery <= 0 { + cfg.PollEvery = defaultPollEvery + } + if cfg.IdleEvery <= 0 { + cfg.IdleEvery = defaultIdleEvery + } + if cfg.ShellTimeout <= 0 { + cfg.ShellTimeout = defaultShellTimeout + } + return &Manager{ + adb: adb, resolve: resolve, hasPendFn: hasPend, handler: handler, + seen: seen, cfg: cfg, lastPoll: make(map[string]time.Time), + wake: make(chan struct{}, 1), + } +} + +// Wake 事件唤醒:新订单进入 WAIT_PAY 池时调用(main.go 经 SetWaitPayHooks.Add 链入)。 +// 池空休眠态立即醒来轮询一轮;轮询态发送不阻塞(信号合并)。 +func (m *Manager) Wake() { + select { + case m.wake <- struct{}{}: + default: // 已有待消费信号,合并 + } +} + +// Start 启动监听循环(阻塞,调用方 go 启动)。 +// +// 事件驱动(2026-08-30 用户确认):池空 → 完全停止轮询(零空转),阻塞等待 +// Wake 事件或低频兜底定时器;新订单进池(Wake)→ 立即轮询,无间隔延迟。 +// idle 兜底:应对池快照与 DB 短暂不一致(reconcile 间隙),防漏唤醒长期休眠。 +func (m *Manager) Start(ctx context.Context) { + if m.adb == nil || m.resolve == nil || m.handler == nil { + logger.Warn("notifywatcher", "依赖缺失,通知栏监听未启动", + "adb", m.adb != nil, "resolve", m.resolve != nil, "handler", m.handler != nil) + return + } + logger.Info("notifywatcher", "通知栏快速通道监听已启动(事件驱动)", + "poll", m.cfg.PollEvery.String(), "idle", m.cfg.IdleEvery.String()) + + for { + // 订单驱动:池有订单才轮询;池空 → 阻塞等事件/兜底定时器 + if m.hasPendFn != nil && m.hasPendFn(ctx) { + m.pollOnce(ctx) + select { + case <-ctx.Done(): + logger.Info("notifywatcher", "通知栏监听已停止") + return + case <-m.wake: + // 唤醒信号:直接继续下一轮(高频路径) + case <-time.After(m.cfg.PollEvery): + } + continue + } + // 池空休眠:等新订单事件(即时唤醒)或低频兜底探测(防 reconcile 间隙漏唤醒) + select { + case <-ctx.Done(): + logger.Info("notifywatcher", "通知栏监听已停止") + return + case <-m.wake: + case <-time.After(m.cfg.IdleEvery): + } + } +} + +// pollOnce 单轮:枚举设备 → 逐台 dump 通知栏 → 解析 → 去重 → 回调。 +func (m *Manager) pollOnce(ctx context.Context) { + devs, err := m.resolve(ctx) + if err != nil { + logger.Warn("notifywatcher", "设备枚举失败(下轮重试)", "err", err) + return + } + for _, d := range devs { + if err := m.pollDevice(ctx, d); err != nil { + // 单设备失败不中断其他设备 + logger.Warn("notifywatcher", "设备通知轮询失败", + "err", err, "device_id", d.DeviceID, "serial", d.Serial) + } + } +} + +// pollDevice 单设备:dump → 解析 → 逐条去重回调。 +func (m *Manager) pollDevice(ctx context.Context, d Device) error { + if d.Serial == "" { + return nil + } + out, err := m.adb.Shell(ctx, m.cfg.ShellTimeout, d.Serial, "dumpsys notification --noredact") + if err != nil { + return err + } + evs := Parse(out, d.DeviceID) + if len(evs) == 0 { + return nil + } + for i := range evs { + ev := evs[i] + // 指纹去重:同设备同通知只处理一次(通知栏不清理会一直在) + fp := ev.Fingerprint() + if m.seen != nil { + dup, derr := m.seen.PushExists(ctx, d.DeviceID, fp) + if derr != nil { + logger.Error("notifywatcher", "去重查询失败(按未处理继续)", + "err", derr, "device_id", d.DeviceID) + } else if dup { + continue + } + } + // 节流日志:同设备每秒最多一条(防止池空时高频刷屏) + m.throttledLog(d.DeviceID, ev) + if err := m.handler(ctx, ev); err != nil { + logger.Error("notifywatcher", "收款通知处理失败", + "err", err, "device_id", d.DeviceID, + "provider", ev.Provider, "space", ev.Space, "amount", ev.AmountMinor) + continue + } + } + return nil +} + +// throttledLog 通知捕获日志(每设备 1s 节流)。 +func (m *Manager) throttledLog(deviceID string, ev Event) { + m.mu.Lock() + defer m.mu.Unlock() + now := time.Now() + if last, ok := m.lastPoll[deviceID]; ok && now.Sub(last) < time.Second { + return + } + m.lastPoll[deviceID] = now + logger.Info("notifywatcher", "捕获收款通知", + "device_id", deviceID, "provider", ev.Provider, + "space", ev.Space, "amount_minor", ev.AmountMinor, "when", ev.When) +} diff --git a/internal/notifywatcher/notifywatcher_test.go b/internal/notifywatcher/notifywatcher_test.go new file mode 100644 index 0000000..bdbb7ae --- /dev/null +++ b/internal/notifywatcher/notifywatcher_test.go @@ -0,0 +1,195 @@ +package notifywatcher + +import ( + "context" + "sync" + "sync/atomic" + "testing" + "time" +) + +// fakeADB 注入 adb Shell 输出的桩(实现 Sheller 接口)。 +type fakeADB struct { + mu sync.Mutex + calls int + out string +} + +func (f *fakeADB) Shell(ctx context.Context, timeout time.Duration, serial, command string) (string, error) { + f.mu.Lock() + defer f.mu.Unlock() + f.calls++ + return f.out, nil +} + +// fakeSeen 去重存储桩(实现 SeenStore 接口)。 +type fakeSeen struct { + dupe bool +} + +func (f *fakeSeen) PushExists(ctx context.Context, deviceID, rawHash string) (bool, error) { + return f.dupe, nil +} + +func TestNewDefaults(t *testing.T) { + m := New(nil, nil, nil, nil, nil, Config{}) + if m.cfg.PollEvery != defaultPollEvery { + t.Errorf("PollEvery = %v, 期望默认 %v", m.cfg.PollEvery, defaultPollEvery) + } + if m.cfg.IdleEvery != defaultIdleEvery { + t.Errorf("IdleEvery = %v, 期望默认 %v", m.cfg.IdleEvery, defaultIdleEvery) + } + if m.cfg.ShellTimeout != defaultShellTimeout { + t.Errorf("ShellTimeout = %v, 期望默认 %v", m.cfg.ShellTimeout, defaultShellTimeout) + } +} + +func TestPollOnce_CallsDumpAndHandler(t *testing.T) { + adb := &fakeADB{out: realWechatMain} + var mu sync.Mutex + handled := 0 + m := New(adb, func(ctx context.Context) ([]Device, error) { + return []Device{{DeviceID: "dev-01", Serial: "1.2.3.4:5555"}}, nil + }, nil, func(ctx context.Context, ev Event) error { + mu.Lock() + defer mu.Unlock() + handled++ + if ev.Provider != "wechat" || ev.AmountMinor != 10 { + t.Errorf("回调参数异常: %+v", ev) + } + return nil + }, &fakeSeen{}, Config{}) + + m.pollOnce(context.Background()) + mu.Lock() + defer mu.Unlock() + if handled != 1 { + t.Errorf("期望回调 1 次,实际 %d", handled) + } +} + +func TestPollOnce_Dedup(t *testing.T) { + adb := &fakeADB{out: realWechatMain} + handled := 0 + m := New(adb, func(ctx context.Context) ([]Device, error) { + return []Device{{DeviceID: "dev-01", Serial: "s1"}}, nil + }, nil, func(ctx context.Context, ev Event) error { + handled++ + return nil + }, &fakeSeen{dupe: true}, Config{}) + + m.pollOnce(context.Background()) + if handled != 0 { + t.Errorf("已去重通知不应回调,实际 %d 次", handled) + } +} + +func TestPollOnce_MultipleEvents(t *testing.T) { + adb := &fakeADB{out: realWechatMain + "\n" + realAlipay} + handled := make(map[string]int) + var mu sync.Mutex + m := New(adb, func(ctx context.Context) ([]Device, error) { + return []Device{{DeviceID: "dev-01", Serial: "s1"}}, nil + }, nil, func(ctx context.Context, ev Event) error { + mu.Lock() + defer mu.Unlock() + handled[ev.Provider]++ + return nil + }, &fakeSeen{}, Config{}) + + m.pollOnce(context.Background()) + mu.Lock() + defer mu.Unlock() + if handled["wechat"] != 1 || handled["alipay"] != 1 { + t.Errorf("期望微信/支付宝各 1 次,实际 %+v", handled) + } +} + +func TestPollOnce_HandlerError(t *testing.T) { + adb := &fakeADB{out: realWechatMain} + calls := 0 + m := New(adb, func(ctx context.Context) ([]Device, error) { + return []Device{{DeviceID: "dev-01", Serial: "s1"}}, nil + }, nil, func(ctx context.Context, ev Event) error { + calls++ + return context.DeadlineExceeded + }, &fakeSeen{}, Config{}) + + m.pollOnce(context.Background()) + if calls != 1 { + t.Errorf("回调应被调用 1 次,实际 %d", calls) + } +} + +func TestStart_OrderDrivenInterval(t *testing.T) { + // 有设备枚举测量轮询频率(adb.calls 反映实际 dump 次数) + adb := &fakeADB{out: ""} + var orderDriven atomic.Bool + orderDriven.Store(true) + hasPend := func(ctx context.Context) bool { return orderDriven.Load() } + + m := New(adb, func(ctx context.Context) ([]Device, error) { + return []Device{{DeviceID: "dev-01", Serial: "s1"}}, nil + }, hasPend, func(ctx context.Context, ev Event) error { return nil }, + nil, Config{PollEvery: 30 * time.Millisecond, IdleEvery: 200 * time.Millisecond}) + + ctx, cancel := context.WithCancel(context.Background()) + go m.Start(ctx) + + time.Sleep(400 * time.Millisecond) + adb.mu.Lock() + activeCalls := adb.calls + adb.mu.Unlock() + + // 转池空 → 完全停止轮询(事件驱动休眠,仅 idle 兜底 200ms 一次) + orderDriven.Store(false) + time.Sleep(400 * time.Millisecond) + adb.mu.Lock() + afterCalls := adb.calls + adb.mu.Unlock() + + // 事件唤醒:真实时序 = 订单先入池(hasPend 变 true)再 Wake → 休眠态立即醒来轮询 + orderDriven.Store(true) // 订单入池 + m.Wake() // 通知栏监听被唤醒 + time.Sleep(150 * time.Millisecond) + adb.mu.Lock() + wakeAfter := adb.calls + adb.mu.Unlock() + + cancel() + time.Sleep(50 * time.Millisecond) + + if activeCalls < 5 { + t.Errorf("有订单阶段 dump 过少: %d(期望高频 ≥5)", activeCalls) + } + // 池空 400ms:idle 兜底 200ms 一次 → 最多 2-3 次(事件驱动下基本停止) + if afterCalls-activeCalls > 4 { + t.Errorf("池空阶段 dump 过多: %d(期望休眠低频)", afterCalls-activeCalls) + } + // 订单入池 + Wake 后:150ms 内应恢复高频轮询(事件驱动即时响应) + if wakeAfter-activeCalls < 1 { + t.Errorf("Wake 后未恢复轮询: before=%d after=%d", activeCalls, wakeAfter) + } +} + +func TestStart_Cancel(t *testing.T) { + adb := &fakeADB{out: ""} + m := New(adb, func(ctx context.Context) ([]Device, error) { + return []Device{{DeviceID: "dev-01", Serial: "s1"}}, nil + }, nil, func(ctx context.Context, ev Event) error { return nil }, + nil, Config{PollEvery: 10 * time.Millisecond, IdleEvery: 10 * time.Millisecond}) + + ctx, cancel := context.WithCancel(context.Background()) + done := make(chan struct{}) + go func() { + m.Start(ctx) + close(done) + }() + time.Sleep(50 * time.Millisecond) + cancel() + select { + case <-done: + case <-time.After(2 * time.Second): + t.Error("Start 未在 cancel 后退出") + } +} diff --git a/internal/notifywatcher/parse.go b/internal/notifywatcher/parse.go new file mode 100644 index 0000000..e616ba7 --- /dev/null +++ b/internal/notifywatcher/parse.go @@ -0,0 +1,226 @@ +// Package notifywatcher 通知栏快速通道监听(独立于 ledgermon 的账本慢通道)。 +// +// 背景(2026-08-30 真机实测 + 用户需求确认): +// - 微信/支付宝收款到账会弹系统通知(dumpsys notification --noredact 可读全文); +// - 通知栏比账本页快 ~15s,是收款感知的快速通道; +// - 通知栏数据不清理会一直存在 → 匹配窗口必须以通知 when(收款发生时刻)为锚, +// 不能用 time.Now()(否则旧通知会错误匹配新订单); +// - 账户区分靠 uid:微信/支付宝主空间 uid 无 999 前缀,双开(XSpace) uid 有 999 前缀 +// (实测:com.tencent.mm uid=10277 主号 / uid=99910277 双开;支付宝同理 10250/99910250)。 +// +// 本包只负责:设备枚举 → 按设备轮询 dumpsys notification --noredact → 解析 → +// 指纹去重 → 回调 main.go 注入的匹配入账函数(复用 matchPersonalOrder 链路)。 +// 不直接触碰 UI/账本页,与 ledgermon 的 DeviceActor 串行队列零竞争。 +package notifywatcher + +import ( + "fmt" + "regexp" + "strconv" + "strings" +) + +// 收款通知金额提取正则(微信/支付宝实测格式): +// - 微信: "微信支付收款0.10元(朋友到店)" → 文本含 "收款0.10元" +// - 支付宝: "你已成功收款0.10元(老顾客消费)" → 标题含 "收款0.10元" +// +// 兼容 "收款 1 元"、"到账 12.5元" 等变体(贪心取收款/到账后第一个金额)。 +var amountRe = regexp.MustCompile(`(?:收款|到账)\s*([0-9]+(?:\.[0-9]{1,2})?)元`) + +// 目标包名:微信 / 支付宝(个人免签通道对应的收款 APP)。 +const ( + pkgWechat = "com.tencent.mm" + pkgAlipay = "com.eg.android.AlipayGphone" +) + +// Event 单条收款通知的解析结果(设备级:属于哪台设备、哪个空间)。 +type Event struct { + DeviceID string // 设备ID(personal.devices.device_id,枚举时携带) + Provider string // "wechat" / "alipay" + Space int // 0=主空间,999=双开(XSpace) + AmountMinor int64 // 金额(分) + When int64 // 通知创建时间(when= 毫秒时间戳,收款发生时刻) + Key string // 系统通知 key(0|pkg|id|tag|uid,同通知轮询去重基准之一) + Title string // 通知标题(审计) + Text string // 通知正文(审计) + Raw string // 指纹原文(审计;含设备ID保证跨设备隔离) +} + +// Fingerprint 通知指纹:同设备+同通知(key)+同金额+同时刻 只处理一次。 +// when 来自通知系统时间戳,同一条通知每次 dump 不变 → 幂等去重可靠。 +func (e *Event) Fingerprint() string { + raw := fmt.Sprintf("notify:%s|%s|%d|%s|%d|%d", e.DeviceID, e.Provider, e.Space, e.Key, e.AmountMinor, e.When) + return sha256Hex(raw) +} + +// Parse 解析 dumpsys notification --noredact 输出,返回该设备的所有收款通知。 +// deviceID 由调用方传入(同一份输出只会属于一台设备)。 +// 规则: +// 1. 只关心 pkg ∈ {微信, 支付宝} 的 NotificationRecord; +// 2. 从 user=UserHandle{0}/{999} 判定空间; +// 3. 从标题/正文提取金额(收款/到账 XX元),提取不到金额的跳过(非收款通知); +// 4. 同一条通知被 Android 聚合/折叠时可能产生多条记录,按 (key,when) 幂等去重。 +func Parse(out string, deviceID string) []Event { + var events []Event + seen := make(map[string]struct{}) + + // 按 NotificationRecord( 分块解析:块内收集 pkg/user/key/title/text/when + for _, block := range splitRecords(out) { + pkg := fieldOf(block, "pkg=") + if pkg != pkgWechat && pkg != pkgAlipay { + continue + } + provider := "wechat" + if pkg == pkgAlipay { + provider = "alipay" + } + // 空间:user=UserHandle{999} → 双开;user=UserHandle{0} → 主空间 + space := 0 + if m := regexp.MustCompile(`user=UserHandle\{(\d+)\}`).FindStringSubmatch(block); len(m) == 2 { + if v, err := strconv.Atoi(m[1]); err == nil { + space = v + } + } + // 通知 key:NotificationRecord(0x...: pkg=... key=0|pkg|id|tag|uid) + key := fieldOf(block, "key=") + // when 毫秒时间戳 + var when int64 + if m := regexp.MustCompile(`when=(\d+)`).FindStringSubmatch(block); len(m) == 2 { + when, _ = strconv.ParseInt(m[1], 10, 64) + } + title, text := textsOf(block) + // 金额:优先标题(支付宝在标题,微信标题也可能含),再正文 + amount := extractAmount(title) + if amount <= 0 { + amount = extractAmount(text) + } + if amount <= 0 { + continue // 非收款通知(无金额)或金额格式未知 → 不处理 + } + if when <= 0 || key == "" { + continue // 缺时间戳/缺key 的畸形记录不处理 + } + // 幂等:同一 (key,when) 只保留一条(聚合折叠场景) + uid := key + "|" + strconv.FormatInt(when, 10) + if _, ok := seen[uid]; ok { + continue + } + seen[uid] = struct{}{} + + events = append(events, Event{ + DeviceID: deviceID, + Provider: provider, + Space: space, + AmountMinor: amount, + When: when, + Key: key, + Title: title, + Text: text, + Raw: fmt.Sprintf("notify:%s|%s|%d|%s", deviceID, provider, space, key), + }) + } + return events +} + +// splitRecords 按 NotificationRecord( 拆分通知块(每块含完整通知字段)。 +func splitRecords(out string) []string { + var blocks []string + cur := strings.Builder{} + for _, line := range strings.Split(out, "\n") { + if strings.Contains(line, "NotificationRecord(") { + if cur.Len() > 0 { + blocks = append(blocks, cur.String()) + } + cur.Reset() + } + cur.WriteString(line) + cur.WriteString("\n") + } + if cur.Len() > 0 { + blocks = append(blocks, cur.String()) + } + return blocks +} + +// fieldOf 提取形如 "name=value" 的字段值(value 到下一个空格/冒号为止)。 +func fieldOf(block, prefix string) string { + i := strings.Index(block, prefix) + if i < 0 { + return "" + } + rest := block[i+len(prefix):] + end := strings.IndexAny(rest, " \n") + if end < 0 { + return rest + } + return rest[:end] +} + +// textsOf 提取通知标题与正文(android.title / android.text 的 String(...) 内容)。 +func textsOf(block string) (title, text string) { + title = quotedStringOf(block, "android.title=String (") + text = quotedStringOf(block, "android.text=String (") + // 标题缺失时用 tickerText 兜底(tickerText=微信收款助手: 微信支付收款0.10元) + if title == "" { + if t := quotedStringOf(block, "tickerText="); t != "" { + if i := strings.Index(t, ": "); i >= 0 { + title = t[:i] + if text == "" { + text = t[i+2:] + } + } else { + title = t + } + } + } + return title, text +} + +// quotedStringOf 提取 "prefix<内容>)" 形式的值(内容到最近右括号)。 +func quotedStringOf(block, prefix string) string { + i := strings.Index(block, prefix) + if i < 0 { + return "" + } + rest := block[i+len(prefix):] + j := strings.Index(rest, ")") + if j < 0 { + return rest + } + return rest[:j] +} + +// extractAmount 从通知文本提取金额(分)。"收款0.10元" → 10。 +func extractAmount(s string) int64 { + m := amountRe.FindStringSubmatch(s) + if len(m) != 2 { + return 0 + } + return amountToMinor(m[1]) +} + +// amountToMinor 金额字符串转分("0.10" → 10,"1" → 100,"1.5" → 150)。 +func amountToMinor(s string) int64 { + var major, minor int64 + if _, err := fmt.Sscanf(s, "%d.%d", &major, &minor); err != nil { + // 无小数:整数元 + if v, err := strconv.ParseInt(s, 10, 64); err == nil { + return v * 100 + } + return 0 + } + if i := strings.IndexByte(s, '.'); i >= 0 { + frac := s[i+1:] + switch len(frac) { + case 1: + minor *= 10 + case 2: + // 保持原样 + default: + minor = 0 + } + } else { + minor = 0 + } + return major*100 + minor +} diff --git a/internal/notifywatcher/parse_test.go b/internal/notifywatcher/parse_test.go new file mode 100644 index 0000000..4b76b36 --- /dev/null +++ b/internal/notifywatcher/parse_test.go @@ -0,0 +1,235 @@ +package notifywatcher + +import ( + "strings" + "testing" +) + +// 真机实测数据(2026-08-30,红米 k60 MIUI14 Android 13)。 +// 数据来源:dumpsys notification --noredact 实测输出(转账 0.10 元触发微信/支付宝收款通知)。 + +// realWechatMain 微信主号收款通知(uid=10277,文本含"(朋友到店)"场景标签)。 +const realWechatMain = ` NotificationRecord(0x0eb536c3: pkg=com.tencent.mm user=UserHandle{0} id=-124356843 tag=null importance=4 key=0|com.tencent.mm|-124356843|null|10277: Notification(channel=message_channel_new_id shortcut=null contentView=null vibrate=null sound=null tick defaults=0x0 flags=0x11 color=0x00000000 category=msg vis=PRIVATE)) + uid=10277 userId=0 + opPkg=com.tencent.mm + icon=Icon(typ=RESOURCE pkg=com.tencent.mm id=0x7f080bc8) + flags=0x11 + originalFlags=0x11 + pri=1 + key=0|com.tencent.mm|-124356843|null|10277 + seen=true + groupKey=0|com.tencent.mm|-124356843|null|10277 + notification= + fullscreenIntent=null + contentIntent=PendingIntent{7a9eb40: PendingIntentRecord{e3b9b0e com.tencent.mm startActivity (allowlist: 8cc672e:+30s0ms/0/NOTIFICATION_SERVICE/NotificationManagerService)}} + deleteIntent=null + number=700 + groupAlertBehavior=0 + when=1788083827846 + tickerText=微信收款助手: 微信支付收款0.10元(朋友到店) + contentView=null + bigContentView=null + headsUpContentView=null + color=0x00000000 + timeout=unknown + extras={ + android.title=String (微信收款助手) + android.reduced.images=Boolean (true) + android.subText=null + android.showChronometer=Boolean (false) + android.text=String (微信支付收款0.10元(朋友到店)) + android.progress=Integer (0) + android.progressMax=Integer (0) + android.appInfo=ApplicationInfo (ApplicationInfo{c86da79 com.tencent.mm}) + android.showWhen=Boolean (true) + android.largeIcon=Icon (Icon(typ=BITMAP size=130x130)) + android.infoText=null + android.progressIndeterminate=Boolean (false) + android.remoteInputHistory=null + }` + +// realWechatDual 微信双开(XSpace)收款通知(uid=99910277,聚合 [2条])。 +const realWechatDual = ` NotificationRecord(0x0e9213be: pkg=com.tencent.mm user=UserHandle{999} id=-124356843 tag=null importance=4 key=999|com.tencent.mm|-124356843|null|99910277: Notification(channel=message_channel_new_id shortcut=null contentView=null vibrate=null sound=null tick defaults=0x0 flags=0x11 color=0x00000000 category=msg vis=PRIVATE)) + uid=99910277 userId=999 + icon=Icon(typ=RESOURCE pkg=com.tencent.mm id=0x7f080bc8) + pri=1 + key=999|com.tencent.mm|-124356843|null|99910277 + when=1788083804845 + tickerText=微信收款助手: 微信支付收款0.10元 + extras={ + android.title=String (微信收款助手) + android.subText=null + android.text=String ([2条]微信收款助手: 微信支付收款0.10元) + android.showWhen=Boolean (true) + android.infoText=null + }` + +// realAlipay 支付宝收款通知(uid=10250,标题含"(老顾客消费)"场景标签)。 +const realAlipay = ` NotificationRecord(0x03b84a6f: pkg=com.eg.android.AlipayGphone user=UserHandle{0} id=1223552217 tag=null importance=4 key=0|com.eg.android.AlipayGphone|1223552217|null|10250: Notification(channel=alipay_default shortcut=null contentView=null vibrate=null sound=null tick defaults=0x4 flags=0x11 color=0x00000000 groupKey=com.eg.android.AlipayGphone.POPULAR_EVENT_GROUP vis=PRIVATE)) + uid=10250 userId=0 + when=1788083627490 + tickerText=已转入余额 发起店铺充值活动>> + extras={ + android.title=String (你已成功收款0.10元(老顾客消费)) + android.text=String (已转入余额 发起店铺充值活动>>) + android.showWhen=Boolean (true) + }` + +// realAlipayDual 支付宝双开(XSpace)收款通知(uid=99910250)。 +const realAlipayDual = ` NotificationRecord(0x0d7d01ac: pkg=com.eg.android.AlipayGphone user=UserHandle{999} id=1027977183 tag=null importance=4 key=999|com.eg.android.AlipayGphone|1027977183|null|99910250: Notification(channel=alipay_default shortcut=null contentView=null vibrate=null sound=null tick defaults=0x4 flags=0x11 color=0x00000000 groupKey=com.eg.android.AlipayGphone.POPULAR_EVENT_GROUP vis=PRIVATE)) + uid=99910250 userId=999 + when=1788083627491 + extras={ + android.title=String (你已成功收款0.10元) + android.text=String (已转入余额) + }` + +// nonPaymentAlipay 支付宝非收款通知(活动/营销,无"收款"文本,应被跳过)。 +const nonPaymentAlipay = ` NotificationRecord(0x03c8c250: pkg=com.eg.android.AlipayGphone user=UserHandle{0} id=2049664768 tag=null importance=4 key=0|com.eg.android.AlipayGphone|2049664768|null|10250: Notification(channel=alipay_default shortcut=null contentView=null vibrate=null sound=null defaults=0x0 flags=0x200 color=0x00000000 groupKey=com.eg.android.AlipayGphone.POPULAR_EVENT_GROUP vis=PRIVATE)) + uid=10250 userId=0 + when=1788083628051 + extras={ + android.title=String (活动通知) + android.text=String (限时优惠) + }` + +// unrelated 其他 APP 通知(MIUI 系统/视频),应被忽略。 +const unrelated = ` NotificationRecord(0x0015f4bf: pkg=com.android.updater user=UserHandle{0} id=0 tag=null importance=5 key=0|com.android.updater|0|null|6102: Notification(channel=updater_channel_id shortcut=null contentView=null vibrate=null sound=null tick defaults=0x4 flags=0x22 color=0x00000000 vis=PRIVATE)) + uid=6102 userId=0 + when=1788080945841 + extras={ + android.title=String (发现系统有新版本) + android.text=String (1.0.16.0.UMNCNXM) + }` + +func TestParse_WechatMain(t *testing.T) { + evs := Parse(realWechatMain, "dev-01") + if len(evs) != 1 { + t.Fatalf("期望 1 条事件,实际 %d", len(evs)) + } + e := evs[0] + if e.Provider != "wechat" { + t.Errorf("provider = %q, 期望 wechat", e.Provider) + } + if e.Space != 0 { + t.Errorf("space = %d, 期望 0(主空间)", e.Space) + } + if e.AmountMinor != 10 { + t.Errorf("amount = %d, 期望 10(0.10元)", e.AmountMinor) + } + if e.When != 1788083827846 { + t.Errorf("when = %d, 期望 1788083827846", e.When) + } + if !strings.Contains(e.Key, "com.tencent.mm") { + t.Errorf("key 异常: %q", e.Key) + } + // 指纹稳定:同一事件两次生成必须相同 + if e.Fingerprint() != evs[0].Fingerprint() { + t.Error("指纹不稳定") + } +} + +func TestParse_WechatDual(t *testing.T) { + evs := Parse(realWechatDual, "dev-01") + if len(evs) != 1 { + t.Fatalf("期望 1 条事件,实际 %d", len(evs)) + } + e := evs[0] + if e.Provider != "wechat" { + t.Errorf("provider = %q, 期望 wechat", e.Provider) + } + if e.Space != 999 { + t.Errorf("space = %d, 期望 999(双开)", e.Space) + } + if e.AmountMinor != 10 { + t.Errorf("amount = %d, 期望 10", e.AmountMinor) + } +} + +func TestParse_Alipay(t *testing.T) { + evs := Parse(realAlipay, "dev-01") + if len(evs) != 1 { + t.Fatalf("期望 1 条事件,实际 %d", len(evs)) + } + e := evs[0] + if e.Provider != "alipay" { + t.Errorf("provider = %q, 期望 alipay", e.Provider) + } + if e.Space != 0 { + t.Errorf("space = %d, 期望 0", e.Space) + } + if e.AmountMinor != 10 { + t.Errorf("amount = %d, 期望 10", e.AmountMinor) + } +} + +func TestParse_AlipayDual(t *testing.T) { + evs := Parse(realAlipayDual, "dev-01") + if len(evs) != 1 { + t.Fatalf("期望 1 条事件,实际 %d", len(evs)) + } + e := evs[0] + if e.Space != 999 { + t.Errorf("space = %d, 期望 999", e.Space) + } + if e.AmountMinor != 10 { + t.Errorf("amount = %d, 期望 10", e.AmountMinor) + } +} + +func TestParse_SkipNonPayment(t *testing.T) { + // 非收款通知(活动/营销/其他APP)→ 0 条 + evs := Parse(nonPaymentAlipay+unrelated, "dev-01") + if len(evs) != 0 { + t.Errorf("期望 0 条事件(非收款通知应跳过),实际 %d", len(evs)) + } +} + +func TestParse_MixedFullDump(t *testing.T) { + // 完整通知栏混合输出:微信主+双开、支付宝主+双开、非收款、其他APP + out := strings.Join([]string{realWechatMain, realWechatDual, realAlipay, realAlipayDual, + nonPaymentAlipay, unrelated}, "\n") + evs := Parse(out, "dev-01") + if len(evs) != 4 { + t.Fatalf("期望 4 条收款事件,实际 %d", len(evs)) + } + // 设备ID必须写入(跨设备隔离) + for _, e := range evs { + if e.DeviceID != "dev-01" { + t.Errorf("device_id 未写入: %q", e.DeviceID) + } + } +} + +func TestParse_DedupAggregated(t *testing.T) { + // 同一条通知在 dump 中重复出现(模拟轮询间隔内通知未变)→ 幂等 + out := realWechatMain + "\n" + realWechatMain + evs := Parse(out, "dev-01") + if len(evs) != 1 { + t.Errorf("重复记录应去重为 1 条,实际 %d", len(evs)) + } +} + +func TestExtractAmount(t *testing.T) { + cases := []struct { + text string + want int64 + }{ + {"微信支付收款0.10元(朋友到店)", 10}, + {"你已成功收款0.10元(老顾客消费)", 10}, + {"[2条]微信收款助手: 微信支付收款0.10元", 10}, + {"收款1元", 100}, + {"收款 12.5元", 1250}, + {"到账 100.00元", 10000}, + {"收款0.10", 0}, // 无"元"单位 → 不匹配(避免误提取) + {"已转入余额", 0}, // 无金额 + {"限时优惠", 0}, // 非收款 + {"充值10元话费", 0}, // 有"元"但非收款/到账语境 → 不匹配 + } + for _, c := range cases { + got := extractAmount(c.text) + if got != c.want { + t.Errorf("extractAmount(%q) = %d, 期望 %d", c.text, got, c.want) + } + } +} diff --git a/internal/order/service.go b/internal/order/service.go new file mode 100755 index 0000000..34a041e --- /dev/null +++ b/internal/order/service.go @@ -0,0 +1,1310 @@ +// Package order 订单编排层:状态机 / 幂等 / 金额校验 / 审计账本 +package order + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "strings" + "time" + + "paygateway/internal/account" + "paygateway/internal/alert" + "paygateway/internal/channel" + "paygateway/internal/epay" + "paygateway/internal/ledger" + "paygateway/internal/logger" + "paygateway/internal/model" + "paygateway/internal/store" + "paygateway/internal/upstream" +) + +// Service 订单服务 +type Service struct { + store store.Store + ledger *ledger.Ledger + accountPool *account.AccountPool // 账户池(日志/展示账户名称) + upstreamPool *upstream.UpstreamPool // 上游池(多商户:按 pid 路由验签/签名) + alert *alert.Client // 告警(RESILIENCE P1-3,nil=禁用) + + // 可选:个人通道备注码释放回调(main.go 注入,订单终态时释放备注码,避免 TTL 惰性回收) + noteCodeReleaser func(channelName, noteCode string) + + // WaitPayHooks WAIT_PAY 内存订单池钩子(main.go 注入,可选): + // 个人免签通道订单进入/离开 WAIT_PAY 状态时通知池(调度器数据源),保证池与 DB 同步。 + // 回调在状态流转成功后调用(幂等),不参与资金判定——池只是索引,DB 是真相。 + waitPay *WaitPayHooks + + // 个人通道监听端检查(main.go 注入,与 reconciler.Scheduler.SetPersonalCheck 共用同一函数): + // 个人/监听通道关单前调用,监听端离线 → 进异常池人工核实,绝不误关已付款订单。 + personalCheck PersonalCheckFunc + + // 关单窗口(ORDER_CLOSE_MINUTES,0=未开启自动关单):复用订单时判断是否已超窗 + closeWindow time.Duration +} + +// WaitPayHooks WAIT_PAY 池同步钩子(注入后非 nil 才生效) +type WaitPayHooks struct { + // Add 订单进入 WAIT_PAY 且已绑定账户(下单/复活后调用,幂等覆盖) + Add func(o *model.Order) + // Upsert 订单凭据刷新/换通道(NoteCode/AccountID 可能变化,覆盖更新) + Upsert func(o *model.Order) + // Remove 订单离开 WAIT_PAY(入账/关单后调用,幂等) + Remove func(outTradeNo string) + // CloseAtOf 查询订单的动态关单时间(pool 注入,可选):返回 (closeAt, found)。 + // 动态关单窗口下,扫描器用该时间判断是否真正到期——未到期则跳过(窗口由设备负载决定)。 + CloseAtOf func(outTradeNo string) (time.Time, bool) +} + +// PersonalCheckFunc 个人通道监听端检查(ConfirmByManual 通道关单前调用)。 +// 返回 (offline, reason):offline=true 表示监听端离线/异常,订单进异常池人工核实(用户可能已付款但未监听到)。 +type PersonalCheckFunc func(ctx context.Context, o *model.Order) (offline bool, reason string) + +// SetPersonalCheck 注入个人通道监听端检查(与 reconciler.Scheduler 共用,防止两条关单路径安全检查分叉)。 +// nil 安全(不注入 = 不检查直接关,仅测试/无设备环境)。 +func (s *Service) SetPersonalCheck(fn PersonalCheckFunc) { s.personalCheck = fn } + +// SetWaitPayHooks 注入 WAIT_PAY 池钩子(main.go 调用;nil 安全) +func (s *Service) SetWaitPayHooks(h *WaitPayHooks) { s.waitPay = h } + +// SetAlert 注入告警客户端(可选) +func (s *Service) SetAlert(a *alert.Client) { s.alert = a } + +// SetCloseWindow 注入关单窗口(分钟),复用订单超窗判定用(0=不启用超窗刷新) +func (s *Service) SetCloseWindow(minutes int) { + if minutes > 0 { + s.closeWindow = time.Duration(minutes) * time.Minute + } +} + +// isExpiredWindow 订单是否已超出关单窗口(WAIT_PAY 但 CreatedAt 超窗,关单清扫器尚未执行) +func (s *Service) isExpiredWindow(o *model.Order) bool { + return s.closeWindow > 0 && !o.CreatedAt.IsZero() && time.Since(o.CreatedAt) >= s.closeWindow +} + +// SetNoteCodeReleaser 注入个人通道备注码释放回调(可选) +// 回调在订单终态(SUCCESS/CLOSED)时调用,避免备注码仅靠 TTL 惰性回收 +func (s *Service) SetNoteCodeReleaser(fn func(channelName, noteCode string)) { + s.noteCodeReleaser = fn +} + +// releaseNoteCode 订单终态时释放备注码(仅 personal.* 通道生效) +func (s *Service) releaseNoteCode(o *model.Order) { + if s.noteCodeReleaser != nil && o.NoteCode != "" && strings.HasPrefix(o.Channel, "personal.") { + s.noteCodeReleaser(o.Channel, o.NoteCode) + } +} + +// New 创建订单服务 +func New(st store.Store, lg *ledger.Ledger, pool *account.AccountPool, upPool *upstream.UpstreamPool) *Service { + return &Service{store: st, ledger: lg, accountPool: pool, upstreamPool: upPool} +} + +// signKey 按订单 pid 返回签名密钥(多商户路由,严格模式) +// pid 未登记或已禁用 → 返回空(回调签名无效,防止未登记上游的订单入账,防御) +func (s *Service) signKey(pid string) string { + if s.upstreamPool == nil || pid == "" { + return "" + } + if u := s.upstreamPool.ByPid(pid); u != nil && u.Status == upstream.StatusEnabled && u.Key != "" { + return u.Key + } + return "" +} + +// upstreamName 返回订单归属的上游名称(展示用) +func (s *Service) upstreamName(pid string) string { + if s.upstreamPool == nil || pid == "" { + return "" + } + if u := s.upstreamPool.ByPid(pid); u != nil { + return u.Name + } + return "" +} + +// accountName 返回账户池中账户名称(用于日志/展示;查不到返回空) +func (s *Service) accountName(id int64) string { + if s.accountPool == nil || id <= 0 { + return "" + } + if acct := s.accountPool.GetByID(id); acct != nil { + return acct.Name + } + return "" +} + +// queryOrder 历史遍历查单;新调用应传订单所属通道名以避免跨通道错账。 +func (s *Service) queryOrder(ctx context.Context, outTradeNo string) (*channel.OrderStatus, error) { + return s.queryOrderFor(ctx, outTradeNo, "") +} + +var ErrNotQueryable = errors.New("订单所属通道无法主动查单") + +func (s *Service) queryOrderFor(ctx context.Context, outTradeNo, channelName string) (*channel.OrderStatus, error) { + if channelName != "" { + ch, ok := channel.Lookup(channelName) + if !ok { + return nil, fmt.Errorf("通道未注册: %s", channelName) + } + if ch.ConfirmAbility() != channel.ConfirmByQuery { + return nil, ErrNotQueryable + } + if !channel.IsEnabled(channelName) { + return nil, fmt.Errorf("通道已禁用: %s", channelName) + } + return ch.QueryOrder(ctx, outTradeNo) + } + var lastErr error + for _, name := range channel.Names() { + ch, ok := channel.Get(name) + if !ok || ch.ConfirmAbility() != channel.ConfirmByQuery { + continue + } + st, err := ch.QueryOrder(ctx, outTradeNo) + if err != nil { + lastErr = err + continue + } + return st, nil + } + if lastErr == nil { + lastErr = fmt.Errorf("无可查询通道") + } + return nil, lastErr +} + +// CreateOrderResult 下单结果 +type CreateOrderResult struct { + Order *model.Order + Cred *channel.PayCredential + Reused bool // 是否复用了已有订单(幂等) + Existing *model.Order +} + +// CreateOrder 下单(幂等:同一订单号重复请求复用原订单,不重复调通道) +func (s *Service) CreateOrder(ctx context.Context, req *CreateRequest) (*CreateOrderResult, error) { + // 金额解析(字符串元 → 最小单位分):前置解析,复用路径需以本次下单金额为最新标准 + amountMinor, err := model.ParseMoneyToMinor(req.Money) + if err != nil { + return nil, fmt.Errorf("金额非法: %w", err) + } + + // 1. 幂等检查 + if existing, err := s.store.GetOrder(ctx, req.OutTradeNo); err != nil { + return nil, err + } else if existing != nil { + logger.Warn("order", "订单已存在,复用", "out_trade_no", req.OutTradeNo, "status", existing.Status) + return s.reuseOrCreateError(ctx, existing, req.Type, amountMinor) + } + + // 1.5 买家级串行:同一买家环境已有待支付订单 → 复用原订单(防海量刷单/错配) + // 双维度检查:buyer_key(IP+UA+device+buyer_id) 或 fp_key(浏览器指纹 cookie) 命中均拦截。 + // 换 IP 攻击:buyer_key 变但 fp_key(cookie) 不变 → 仍被拦截。 + if req.BuyerKey != "" && strings.HasPrefix(req.Type, "personal.") { + var pending []*model.Order + var err error + if req.FPKey != "" { + pending, err = s.store.ListWaitPayByBuyerOrFP(ctx, req.BuyerKey, req.FPKey) + } else { + pending, err = s.store.ListWaitPayByBuyer(ctx, req.BuyerKey) + } + if err != nil { + logger.Error("order", "买家待支付订单查询失败", "err", err, "buyer_key", req.BuyerKey) + } else if len(pending) > 0 { + o := pending[0] + bk := req.BuyerKey + if len(bk) > 12 { + bk = bk[:12] + } + if o.OutTradeNo != req.OutTradeNo { + // 不同订单号:上游订单号与金额为不可变主键,无法复用 OLD。 + // 关闭 OLD(CAS/幂等,释放备注码+移出池)。并发时仅 CAS 胜出者创建 NEW, + // 落败者复用获胜新单或返回安全冲突错误,绝不产生第二笔活跃 WAIT_PAY。 + if !o.SessionExpiresAt.IsZero() && !time.Now().Before(o.SessionExpiresAt) { + return nil, fmt.Errorf("订单 %s 的支付会话已过期,请重新发起充值", o.OutTradeNo) + } + req.SessionExpiresAt = s.sessionExpiryOf(o) + logger.Warn("order", "买家已有待支付订单,替换旧单创建新单", "buyer_key", bk, "pending", o.OutTradeNo, "new", req.OutTradeNo) + reuse, create, err := s.replacePendingOrder(ctx, o, req.OutTradeNo, amountMinor) + if err != nil { + return nil, err + } + if !create { + if reuse != nil { + // 并发替换者已创建获胜新单:复用之,不再重复创建。 + logger.Warn("order", "买家替换并发,复用获胜新单", "buyer_key", bk, "new", req.OutTradeNo, "winner", reuse.OutTradeNo) + return &CreateOrderResult{Order: reuse, Reused: true, Existing: reuse}, nil + } + // 并发结算胜出(用户已付 OLD):禁止再创建同买家新单,避免重复收款。 + return nil, fmt.Errorf("订单 %s 已支付,请勿重复下单", o.OutTradeNo) + } + } else { + // 同订单号:幂等复用(金额不可变,见 reuseOrCreateError 的金额校验) + if o.PayURL != "" || o.QrCode != "" { + return s.reuseOrCreateError(ctx, o, req.Type, amountMinor) + } + return nil, fmt.Errorf("您有未支付订单 %s,请先完成支付", o.OutTradeNo) + } + } + } + + // 2. 获取通道 + ch, ok := channel.Get(req.Type) + if !ok { + return nil, channel.ErrUnsupported + } + + // 4. 创建订单(数据库唯一约束兜底幂等) + // 记录上游标识:pid 来自下单参数(多商户路由),用于订单归属 + 回调签名 + now := time.Now() + sessionExpiresAt := req.SessionExpiresAt + if sessionExpiresAt.IsZero() && s.closeWindow > 0 { + sessionExpiresAt = now.Add(s.closeWindow) + } + o := &model.Order{ + OutTradeNo: req.OutTradeNo, + GatewayNo: fmt.Sprintf("GW%d", now.UnixNano()), + Channel: req.Type, + Pid: req.RawParams["pid"], + Attach: req.RawParams["attach"], + AmountMinor: amountMinor, + Currency: "CNY", + Subject: req.Name, + Status: model.StatusWaitPay, + NotifyURL: req.NotifyURL, + ReturnURL: req.ReturnURL, + BuyerKey: req.BuyerKey, + FPKey: req.FPKey, + CreatedAt: now, + SessionExpiresAt: sessionExpiresAt, + } + o.UpstreamName = s.upstreamName(o.Pid) + if err := s.store.CreateOrder(ctx, o); err != nil { + // 并发同号:另一请求刚插入同一 out_trade_no(唯一约束冲突)→ 转复用路径, + // 不让商户收到裸 DB 错误 + if existing, gerr := s.store.GetOrder(ctx, req.OutTradeNo); gerr == nil && existing != nil { + return s.reuseOrCreateError(ctx, existing, req.Type, amountMinor) + } + return nil, fmt.Errorf("创建订单失败: %w", err) + } + s.recordEvent(ctx, o, "created", "", amountMinor, req.rawJSON()) + + // 5. 调通道下单 + payReq := &channel.PayRequest{ + OutTradeNo: o.OutTradeNo, + GatewayNo: o.GatewayNo, + Subject: o.Subject, + AmountMinor: o.AmountMinor, + Currency: o.Currency, + NotifyURL: "", // 通道 notify 由网关统一配置 + ReturnURL: o.ReturnURL, + PayMode: channel.PayMode(req.PayMode), + Device: req.Device, + } + cred, err := ch.CreatePayment(ctx, payReq) + if err != nil { + logger.Error("order", "通道下单失败", "err", err, "out_trade_no", o.OutTradeNo) + // 清理无凭据订单:留着会让同号重试撞"已处理"死墙(订单号被永久烧掉)。 + // 删除后商户可用原号重试;created 事件随行删除,失败事实在日志与告警中留痕。 + if _, derr := s.store.DeleteOrder(ctx, o.OutTradeNo); derr != nil { + logger.Error("order", "清理失败订单未遂(该单号将无法重用,需人工处理)", + "err", derr, "out_trade_no", o.OutTradeNo) + } + return nil, err + } + + // 6. 保存支付凭据(条件更新,WAIT_BUYER_PAY -> WAIT_BUYER_PAY 幂等) + // 使用后台上下文,避免 HTTP 客户端断开导致数据库操作被取消 + dbCtx, dbCancel := context.WithTimeout(context.Background(), 30*time.Second) + defer dbCancel() + if cred.PayURL != "" { + o.PayURL = cred.PayURL + } + if cred.QRCode != "" { + o.QrCode = cred.QRCode + } + if cred.AccountID > 0 { + o.AccountID = cred.AccountID + } + if _, err := s.store.UpdateOrderStatus(dbCtx, o.OutTradeNo, model.StatusWaitPay, model.StatusWaitPay, func(upd *model.Order) { + if cred.PayURL != "" { + upd.PayURL = cred.PayURL + } + if cred.QRCode != "" { + upd.QrCode = cred.QRCode + } + if cred.AccountID > 0 { + upd.AccountID = cred.AccountID + } + // 个人免签通道:持久化备注码 + 套码 token(匹配/跳转用) + if cred.NoteCode != "" { + upd.NoteCode = cred.NoteCode + } + if cred.QrToken != "" { + upd.QrToken = cred.QrToken + } + if cred.DeviceID != "" { + upd.DeviceID = cred.DeviceID + } + }); err != nil { + return nil, err + } + // 读回最新(含凭据) + latest, _ := s.store.GetOrder(dbCtx, o.OutTradeNo) + if latest != nil { + o = latest + } + + logger.Info("order", "下单成功 ✅", + "out_trade_no", o.OutTradeNo, + "pid", o.Pid, + "upstream", o.UpstreamName, + "channel", req.Type, + "amount", amountMinor, + "mode", cred.Mode, + "account_id", cred.AccountID, + "account", s.accountName(cred.AccountID)) + + // WAIT_PAY 池同步:个人免签订单已绑定账户 + 凭据,进入调度数据源 + s.notifyWaitPayAdd(o) + + return &CreateOrderResult{Order: o, Cred: cred}, nil +} + +// reuseOrCreateError 已存在订单的统一处置: +// - 待支付订单(WAIT_PAY)且有凭据: +// - 请求通道 ≠ 订单通道(均为个人免签)→ 自动迁移订单到新通道账户(保留原过期时间) +// - 否则 → 复用原订单(保留原过期时间) +// - 已关闭订单(TRADE_CLOSED)同号重提 → 复活重新计时(重新走一个支付生命周期) +// - 其他终态(TRADE_SUCCESS 等)→ 报"已处理" +// +// 僵尸订单防护语义(只针对 WAIT_PAY): +// - WAIT_PAY 订单占资源(买家串行锁/备注码/账户绑定/倒计时), +// 复用/切换均不重置 CreatedAt,到期必关 → 无法无限续命占资源 +// - TRADE_CLOSED 已关闭不占资源,同号复活只是重新支付,无危害 +func (s *Service) reuseOrCreateError(ctx context.Context, existing *model.Order, reqType string, amountMinor int64) (*CreateOrderResult, error) { + hasCred := existing.PayURL != "" || existing.QrCode != "" + if !hasCred { + return nil, fmt.Errorf("订单 %s 已处理(状态: %s)", existing.OutTradeNo, existing.Status) + } + // 金额不可变:订单号+金额均为上游不可变主键。同号重提若金额不同 → 拒单, + // 绝不覆盖已存在订单金额,也绝不以新金额刷新凭据(否则回调按订单金额校验会永不入账/错账)。 + // 不同订单号的替换路径见 replacePendingOrder(CreateOrder 买家守卫调用)。 + if amountMinor > 0 && amountMinor != existing.AmountMinor { + return nil, fmt.Errorf("订单 %s 金额不可变:原金额=%d 新金额=%d(订单号已存在,请使用新订单号提交)", + existing.OutTradeNo, existing.AmountMinor, amountMinor) + } + // 已关闭订单同号重提 → 复活(上游同号重试是易支付协议标准行为) + if existing.Status == model.StatusClosed { + revived, err := s.ReviveOrder(ctx, existing, reqType) + if err != nil { + logger.Error("order", "已关闭订单复活失败", "err", err, "out_trade_no", existing.OutTradeNo, "to", reqType) + return nil, fmt.Errorf("订单 %s 已过期,重新下单失败: %w", existing.OutTradeNo, err) + } + return &CreateOrderResult{Order: revived, Reused: true, Existing: revived}, nil + } + if existing.Status != model.StatusWaitPay { + return nil, fmt.Errorf("订单 %s 已处理(状态: %s)", existing.OutTradeNo, existing.Status) + } + // 自动切换支付方式:上游换了支付模式的入口进来(同号不同 type),订单整体迁移到新通道 + if reqType != "" && reqType != existing.Channel { + if strings.HasPrefix(existing.Channel, "personal.") && strings.HasPrefix(reqType, "personal.") { + switched, err := s.SwitchChannel(ctx, existing, reqType) + if err != nil { + logger.Warn("order", "自动切换支付方式失败,退回复用原订单", "err", err, "out_trade_no", existing.OutTradeNo, "from", existing.Channel, "to", reqType) + } else { + return &CreateOrderResult{Order: switched, Reused: true, Existing: switched}, nil + } + } + } + // 复用前超窗检查:WAIT_PAY 但已超过关单窗口(关单清扫器还没跑)→ 刷新凭据并重置倒计时, + // 避免复用出的支付页立即过期跳回过期页(不违反僵尸防护:只对"已超窗"订单重新计时)。 + if s.isExpiredWindow(existing) { + refreshed, err := s.RefreshPayment(ctx, existing) + if err != nil { + logger.Warn("order", "复用超窗订单刷新失败,按原单返回", "err", err, "out_trade_no", existing.OutTradeNo, "amount", existing.AmountMinor) + } else { + return &CreateOrderResult{Order: refreshed, Reused: true, Existing: refreshed}, nil + } + } + return &CreateOrderResult{Order: existing, Reused: true, Existing: existing}, nil +} + +// replacePendingOrder 买家已有 WAIT_PAY 待支付订单(OLD),本次 request 用了不同 out_trade_no(NEW)。 +// OLD 的订单号+金额是上游不可变主键,无法复用 → 关闭 OLD 释放资源,再由调用方为 NEW 走正常下单。 +// +// 并发安全:多个同买家请求可能同时试图替换同一个 OLD。用 UpdateOrderStatus 的 CAS 语义仲裁—— +// 仅 CAS 胜出者(replaceOrder 原子关单+写 replaced 事件)创建 NEW;CAS 落败者不得再创建自己的 NEW, +// 否则同一买家会出现多笔 WAIT_PAY。落败者依据 OLD 的终态与 replaced 事件分派: +// +// - OLD=SUCCESS(并发结算胜出)→ create=false,禁止创建新单(防重复收款)。 +// - OLD=CLOSED 且带 replaced 事件(被并发替换者关闭)→ create=false,若替换者的 NEW 已存在则复用, +// 否则返回可安全重试的冲突错误;绝不重复创建 NEW。 +// - OLD=CLOSED 且无 replaced 事件(仅被清扫器/超时关闭)→ create=true,正常为新单走创建流程。 +// +// 返回 (reuse, create, err):create=false 时若 reuse!=nil 则调用方应复用该订单,否则调用方不得创建 NEW。 +func (s *Service) replacePendingOrder(ctx context.Context, old *model.Order, newOutTradeNo string, newAmountMinor int64) (*model.Order, bool, error) { + payload := fmt.Sprintf(`{"old_out_trade_no":%q,"old_amount":%d,"new_out_trade_no":%q,"new_amount":%d,"reason":"buyer_replaced","time":%q}`, + old.OutTradeNo, old.AmountMinor, newOutTradeNo, newAmountMinor, time.Now().Format(time.RFC3339)) + closed, err := s.store.ReplaceOrder(ctx, old.OutTradeNo, old.ID, old.AmountMinor, payload) + if err != nil { + return nil, false, err + } + if closed { + // 赢得替换:释放备注码 + 移出 WAIT_PAY 池(各一次),随后调用方为 NEW 走正常下单。 + s.releaseNoteCode(old) + s.notifyWaitPayRemove(old) + logger.Warn("order", "买家待支付订单已被新订单替换关闭", "old", old.OutTradeNo, "new", newOutTradeNo, "new_amount", newAmountMinor) + return nil, true, nil + } + + latest, gerr := s.store.GetOrder(ctx, old.OutTradeNo) + if gerr == nil && latest != nil && latest.Status == model.StatusSuccess { + logger.Error("order", "买家待支付并发结算胜出,取消新单创建(防重复收款)", "old", old.OutTradeNo, "new", newOutTradeNo) + return nil, false, nil + } + + // 非结算:OLD 带 replaced 事件 ⇒ 被并发替换者关闭,不得再创建自己的 NEW。 + if s.hasReplacedRecently(ctx, old) { + return s.reuseWinnerNew(ctx, old, newOutTradeNo) + } + + // OLD 仅被清扫器/超时关闭(无 replaced 事件)→ 安全为本次请求创建全新 NEW。 + return nil, true, nil +} + +// hasReplacedRecently 判断 OLD 是否刚被「并发替换者」关闭:存在近窗口内的 replaced 事件。 +// 近窗口过滤掉「被替换后同号复活、又被清扫器关闭」的罕见历史场景,避免误判为并发替换。 +func (s *Service) hasReplacedRecently(ctx context.Context, old *model.Order) bool { + evs, err := s.store.ListEvents(ctx, old.ID, 20) + if err != nil { + return false + } + cutoff := time.Now().Add(-30 * time.Second) + for _, e := range evs { + if e.EventType == "replaced" && e.CreatedAt.After(cutoff) { + return true + } + } + return false +} + +// reuseWinnerNew 并发替换者已关闭 OLD:返回其创建的 NEW 供复用,若尚不可见则返回可安全重试的冲突错误。 +func (s *Service) reuseWinnerNew(ctx context.Context, old *model.Order, newOutTradeNo string) (*model.Order, bool, error) { + pending, err := s.store.ListWaitPayByBuyerOrFP(ctx, old.BuyerKey, old.FPKey) + if err != nil { + return nil, false, err + } + for _, p := range pending { + if p.OutTradeNo != old.OutTradeNo { + logger.Warn("order", "买家替换并发,复用获胜新单而非重复创建", "old", old.OutTradeNo, "new", newOutTradeNo, "winner", p.OutTradeNo) + return p, false, nil + } + } + return nil, false, fmt.Errorf("订单 %s 正在被并发替换为新单,请稍后重试", old.OutTradeNo) +} + +// CreateRequest 下单请求(易支付协议参数) +type CreateRequest struct { + OutTradeNo string + Type string // alipay / wxpay / paypal / usdt + Name string + Money string // 元(字符串) + NotifyURL string + ReturnURL string + PayMode string // page / wap / qrcode(pay_type) + Device string // pc / mobile + BuyerKey string // 下单环境唯一标识(IP+UA+设备指纹 hash,买家级串行/防刷单) + FPKey string // 浏览器指纹标识(cookie pg_fp,换 IP 场景串行兜底) + SessionExpiresAt time.Time // 内部替换继承的支付会话硬截止时间 + RawParams map[string]string +} + +func (s *Service) sessionExpiryOf(o *model.Order) time.Time { + if o == nil { + return time.Time{} + } + return o.SessionExpiresAt +} + +// RefreshPayment 刷新支付凭据(二维码刷新):保持原订单号和上游关联不变,重新调通道生成新二维码/备注码。 +// +// 易支付协议中 out_trade_no 是上游的业务主键,刷新二维码不允许改单号, +// 否则回调带上新单号(如 xxx-R2)时上游找不到订单 → 回调入账失败。 +func (s *Service) RefreshPayment(ctx context.Context, o *model.Order) (*model.Order, error) { + if o == nil || o.OutTradeNo == "" { + return nil, fmt.Errorf("订单为空") + } + if o.Status != model.StatusWaitPay { + return nil, fmt.Errorf("订单当前状态(%s)不可刷新", o.Status) + } + ch, ok := channel.Get(o.Channel) + if !ok { + return nil, channel.ErrUnsupported + } + // 释放旧备注码(避免 5 分钟 TTL 惰性回收期间码被占用) + if strings.HasPrefix(o.Channel, "personal.") { + s.releaseNoteCode(o) + } + // 重新调通道生成支付凭据(用原订单号) + payReq := &channel.PayRequest{ + OutTradeNo: o.OutTradeNo, + GatewayNo: o.GatewayNo, + Subject: o.Subject, + AmountMinor: o.AmountMinor, + Currency: o.Currency, + NotifyURL: "", + ReturnURL: o.ReturnURL, + PayMode: channel.ModeQRCode, + Device: "pc", + } + cred, err := ch.CreatePayment(ctx, payReq) + if err != nil { + logger.Error("order", "刷新支付凭据失败", "err", err, "out_trade_no", o.OutTradeNo) + return nil, err + } + // 条件更新订单凭据(WAIT_BUYER_PAY -> WAIT_BUYER_PAY,幂等) + dbCtx, dbCancel := context.WithTimeout(context.Background(), 30*time.Second) + defer dbCancel() + if _, err := s.store.UpdateOrderStatus(dbCtx, o.OutTradeNo, model.StatusWaitPay, model.StatusWaitPay, func(upd *model.Order) { + // 刷新二维码 = 用户重新支付:重置倒计时给完整支付窗口。 + // 不违反僵尸防护:订单始终 WAIT_PAY 未复活已关闭订单; + // 且刷新是用户主动点击(每点一次重新计时),与"同号重提自动续命"语义不同。 + upd.CreatedAt = time.Now() + if cred.PayURL != "" { + upd.PayURL = cred.PayURL + } + if cred.QRCode != "" { + upd.QrCode = cred.QRCode + } + if cred.AccountID > 0 { + upd.AccountID = cred.AccountID + } + if cred.NoteCode != "" { + upd.NoteCode = cred.NoteCode + } + if cred.QrToken != "" { + upd.QrToken = cred.QrToken + } + if cred.DeviceID != "" { + upd.DeviceID = cred.DeviceID + } + }); err != nil { + return nil, err + } + // 读回最新订单(含新凭据) + latest, _ := s.store.GetOrder(dbCtx, o.OutTradeNo) + if latest != nil { + o = latest + } + logger.Info("order", "支付凭据已刷新 ✅", + "out_trade_no", o.OutTradeNo, "channel", o.Channel, + "pid", o.Pid, "amount", o.AmountMinor) + // WAIT_PAY 池同步:凭据/备注码已更新,覆盖池条目 + s.notifyWaitPayUpsert(o) + return o, nil +} + +// SwitchChannel 切换支付方式(个人免签通道间迁移订单) +// +// 用户下单后想换支付方式(如支付宝→微信):保持订单号和金额不变, +// 重新调目标通道选账户生成凭据,订单整体迁移到目标通道的可用账户。 +// 仅允许未过期(WAIT_PAY)订单切换;保留原 CreatedAt(过期时间复用, +// 防换支付方式无限刷新倒计时/复活 → 僵尸订单刷单漏洞)。 +// TRADE_CLOSED 订单是终态,任何重提都报"已处理"(不允许复活)。 +// +// 幂等性:目标通道与当前一致时直接返回原订单(不重复调通道)。 +func (s *Service) SwitchChannel(ctx context.Context, o *model.Order, newChannel string) (*model.Order, error) { + if o == nil || o.OutTradeNo == "" { + return nil, fmt.Errorf("订单为空") + } + if o.Channel == newChannel { + return o, nil + } + if !strings.HasPrefix(o.Channel, "personal.") || !strings.HasPrefix(newChannel, "personal.") { + return nil, fmt.Errorf("仅支持个人免签通道间切换支付方式") + } + if o.Status != model.StatusWaitPay { + return nil, fmt.Errorf("订单当前状态(%s)不可切换支付方式(仅待支付订单可切换,过期订单不可复活)", o.Status) + } + ch, ok := channel.Get(newChannel) + if !ok { + return nil, channel.ErrUnsupported + } + // 释放旧通道备注码(避免 TTL 惰性回收期间码被占用) + if strings.HasPrefix(o.Channel, "personal.") { + s.releaseNoteCode(o) + } + // 重新调目标通道生成支付凭据(用原订单号;SelectFrom 按 channel 过滤自动选对平台账户) + payReq := &channel.PayRequest{ + OutTradeNo: o.OutTradeNo, + GatewayNo: o.GatewayNo, + Subject: o.Subject, + AmountMinor: o.AmountMinor, + Currency: o.Currency, + NotifyURL: "", + ReturnURL: o.ReturnURL, + PayMode: channel.ModeQRCode, + Device: "pc", + } + cred, err := ch.CreatePayment(ctx, payReq) + if err != nil { + logger.Error("order", "切换支付方式失败(目标通道下单失败)", "err", err, "out_trade_no", o.OutTradeNo, "to", newChannel) + return nil, err + } + // WAIT_PAY→WAIT_PAY 换凭据;不重置 CreatedAt(过期时间复用,防僵尸订单刷单) + dbCtx, dbCancel := context.WithTimeout(context.Background(), 30*time.Second) + defer dbCancel() + ok2, err := s.store.UpdateOrderStatus(dbCtx, o.OutTradeNo, model.StatusWaitPay, model.StatusWaitPay, func(upd *model.Order) { + upd.Channel = newChannel + if cred.PayURL != "" { + upd.PayURL = cred.PayURL + } + if cred.QRCode != "" { + upd.QrCode = cred.QRCode + } + if cred.AccountID > 0 { + upd.AccountID = cred.AccountID + } + if cred.NoteCode != "" { + upd.NoteCode = cred.NoteCode + } + if cred.QrToken != "" { + upd.QrToken = cred.QrToken + } + if cred.DeviceID != "" { + upd.DeviceID = cred.DeviceID + } + }) + if err != nil { + return nil, err + } + if !ok2 { + return nil, fmt.Errorf("订单状态已变化,请刷新后重试") + } + // 读回最新订单(含新凭据) + latest, _ := s.store.GetOrder(dbCtx, o.OutTradeNo) + if latest != nil { + o = latest + } + logger.Info("order", "支付方式已切换 ✅", + "out_trade_no", o.OutTradeNo, "from", o.Channel, "to", newChannel, + "account_id", cred.AccountID, "status", o.Status, "amount", o.AmountMinor) + // WAIT_PAY 池同步:通道/账户/备注码已更新,覆盖池条目 + s.notifyWaitPayUpsert(o) + return o, nil +} + +// ReviveOrder 已关闭订单同号重提复活:TRADE_CLOSED → WAIT_PAY,重新走一个支付生命周期。 +// +// 上游固定订单号重试(易支付协议标准行为)时调用;已关闭订单不占资源 +// (买家串行锁/备注码/倒计时均已释放),复活 = 重新支付,无僵尸订单风险。 +// reqType 非空且与订单通道不同时顺带切换支付方式(SelectFrom 按 channel 选目标平台账户)。 +func (s *Service) ReviveOrder(ctx context.Context, o *model.Order, reqType string) (*model.Order, error) { + if o == nil || o.OutTradeNo == "" { + return nil, fmt.Errorf("订单为空") + } + if o.Status != model.StatusClosed { + return nil, fmt.Errorf("订单状态(%s)不可复活(仅 TRADE_CLOSED)", o.Status) + } + newChannel := o.Channel + if reqType != "" { + newChannel = reqType + } + if !strings.HasPrefix(newChannel, "personal.") { + return nil, fmt.Errorf("仅支持个人免签通道复活") + } + ch, ok := channel.Get(newChannel) + if !ok { + return nil, channel.ErrUnsupported + } + // 释放旧通道备注码(复活后重新分配,避免 TTL 惰性回收期间码被占用) + if strings.HasPrefix(o.Channel, "personal.") { + s.releaseNoteCode(o) + } + // 重新调通道生成支付凭据(用原订单号;选账户按目标通道过滤) + payReq := &channel.PayRequest{ + OutTradeNo: o.OutTradeNo, + GatewayNo: o.GatewayNo, + Subject: o.Subject, + AmountMinor: o.AmountMinor, + Currency: o.Currency, + NotifyURL: "", + ReturnURL: o.ReturnURL, + PayMode: channel.ModeQRCode, + Device: "pc", + } + cred, err := ch.CreatePayment(ctx, payReq) + if err != nil { + logger.Error("order", "复活订单失败(目标通道下单失败)", "err", err, "out_trade_no", o.OutTradeNo, "to", newChannel) + return nil, err + } + // TRADE_CLOSED → WAIT_PAY,重置 CreatedAt 重新计时,清除 paid_at + dbCtx, dbCancel := context.WithTimeout(context.Background(), 30*time.Second) + defer dbCancel() + ok2, err := s.store.UpdateOrderStatus(dbCtx, o.OutTradeNo, model.StatusClosed, model.StatusWaitPay, func(upd *model.Order) { + upd.Channel = newChannel + upd.CreatedAt = time.Now() + upd.PaidAt = nil // from!=to 分支会 COALESCE 填 now,必须清掉 + if cred.PayURL != "" { + upd.PayURL = cred.PayURL + } + if cred.QRCode != "" { + upd.QrCode = cred.QRCode + } + if cred.AccountID > 0 { + upd.AccountID = cred.AccountID + } + if cred.NoteCode != "" { + upd.NoteCode = cred.NoteCode + } + if cred.QrToken != "" { + upd.QrToken = cred.QrToken + } + if cred.DeviceID != "" { + upd.DeviceID = cred.DeviceID + } + }) + if err != nil { + return nil, err + } + if !ok2 { + return nil, fmt.Errorf("订单状态已变化,请刷新后重试") + } + latest, _ := s.store.GetOrder(dbCtx, o.OutTradeNo) + if latest != nil { + o = latest + } + logger.Info("order", "已关闭订单已复活 ✅", + "out_trade_no", o.OutTradeNo, "from", o.Channel, "to", newChannel, + "account_id", cred.AccountID, "status", o.Status, "amount", o.AmountMinor) + // WAIT_PAY 池同步:CLOSED → WAIT_PAY 重新进入池 + s.notifyWaitPayAdd(o) + return o, nil +} + +func (r *CreateRequest) rawJSON() string { + b, _ := json.Marshal(r.RawParams) + return string(b) +} + +// ---------- 回调处理 ---------- + +// SettlePayment 支付成功结算(核心资金操作,唯一入口) +// nr: 通道验签后的通知结果(含 out_trade_no / 实付金额 / 通道交易号) +func (s *Service) SettlePayment(ctx context.Context, nr *channel.NotifyResult) (bool, error) { + outTradeNo := nr.OutTradeNo + // 1. 查订单 + o, err := s.store.GetOrder(ctx, outTradeNo) + if err != nil { + return false, err + } + if o == nil { + logger.Error("order", "订单不存在", "out_trade_no", outTradeNo) + return false, nil + } + + // 2. 金额校验(防改价攻击) + // 严格模式:实付金额缺失(0)同样拒绝——所有通道的通知/查单路径都能提供金额, + // 放行未知金额 = 允许未经验证的资金入账。拒绝后通道会重试,持续失败转人工池。 + if nr.AmountMinor != o.AmountMinor { + logger.Error("order", "金额校验失败(重大安全事件)", "out_trade_no", outTradeNo, "amount", o.AmountMinor, "paid", nr.AmountMinor) + s.recordEvent(ctx, o, "amount_mismatch", nr.ChannelTxID, nr.AmountMinor, nr.RawPayload) + return false, fmt.Errorf("金额不匹配: 订单=%d 实付=%d", o.AmountMinor, nr.AmountMinor) + } + + // 3. 账户验证:回调的账户与订单记录的账户一致 + if nr.AccountID > 0 && o.AccountID > 0 && nr.AccountID != o.AccountID { + logger.Error("order", "账户不匹配(重大安全事件)", + "out_trade_no", outTradeNo, + "order_account_id", o.AccountID, + "callback_account_id", nr.AccountID) + return false, fmt.Errorf("账户不匹配: 订单=%d 回调=%d", o.AccountID, nr.AccountID) + } + + // 4a. 幂等:已支付直接返回成功(同一通道交易号重复回调) + if o.Status == model.StatusSuccess { + // 崩溃恢复:若事件/账本在上次结算中丢失(条件更新成功后进程崩溃),此处补写 + s.ensureRecorded(ctx, o, nr) + // 备注码释放(幂等:重复释放无害) + s.releaseNoteCode(o) + // WAIT_PAY 池同步:确保已移出池(幂等;崩溃恢复场景兜底) + s.notifyWaitPayRemove(o) + logger.Info("order", "订单已支付,幂等返回", "out_trade_no", outTradeNo) + return true, nil + } + + // 4b. H9 迟到支付回调:订单已被超时关单(CLOSED),上游却在关单后送来支付成功通知。 + // Closed 为资金安全终态,禁止重开(state graph: StatusClosed={}); + // 但用户已付款真实存在,丢失证据=无法追讨/退款。落证据事件(不计入账本,避免凭空入账),告警待人工核查与上游对账。 + if o.Status == model.StatusClosed { + lateEvent := fmt.Sprintf(`{"out_trade_no":%q,"amount":%d,"status":"LATE_PAID_AFTER_CLOSED","channel_tx_id":%q,"payer":%q,"device_id":%q,"account_id":%d,"time":%q}`, + outTradeNo, o.AmountMinor, nr.ChannelTxID, nr.Payer, nr.DeviceID, nr.AccountID, time.Now().Format(time.RFC3339)) + if err := s.store.InsertEvent(ctx, o.ID, "late_paid_after_closed", nr.ChannelTxID, o.AmountMinor, lateEvent); err != nil { + logger.Error("order", "LATE_PAID_AFTER_CLOSED 事件落库失败", "err", err, "out_trade_no", outTradeNo) + return false, err + } + logger.Error("order", "H9 迟到支付:订单已关单但上游通知支付成功", + "out_trade_no", outTradeNo, "channel_tx_id", nr.ChannelTxID, + "amount", o.AmountMinor, "closed_event_id", o.ID, "notify_payload", nr.RawPayload) + if s.alert != nil { + s.alert.Send("error", "迟到支付(已关单):需人工对账", + fmt.Sprintf("订单 %s 已被网关关单,但上游 %s 通知支付成功 (channel_tx_id=%s, amount=%d)\n请核对上游商户后台并主动退款或补开,避免用户资金流失投诉", + outTradeNo, nr.ChannelTxID, nr.ChannelTxID, o.AmountMinor)) + } + // 返回 true 让通道收到 success 停止重试;资金不自动入账,等待人工对账 + return true, nil + } + + // 5. 条件更新状态 WAIT_BUYER_PAY -> TRADE_SUCCESS(互斥锁:防并发双写账本/事件) + ok, err := s.store.UpdateOrderStatus(ctx, outTradeNo, model.StatusWaitPay, model.StatusSuccess, func(upd *model.Order) { + upd.ChannelTxID = nr.ChannelTxID + upd.Payer = nr.Payer + if nr.DeviceID != "" { + upd.DeviceID = nr.DeviceID + } + }) + if err != nil { + return false, err + } + if !ok { + logger.Warn("order", "状态更新被拒绝(条件不满足)", "out_trade_no", outTradeNo, "from", o.Status) + return false, nil + } + + // 6. 资金流水 + 审计账本 + // 写入顺序说明:先事件后账本。任一失败都返回错误让通道重试回调; + // 重试时进入上面的幂等分支,由 ensureRecorded 补写缺失部分(账本按 eventID 去重,不会重复记账) + eventPayload := fmt.Sprintf(`{"out_trade_no":%q,"amount":%d,"status":"TRADE_SUCCESS","channel_tx_id":%q,"payer":%q,"device_id":%q,"account_id":%d,"time":%q}`, + outTradeNo, o.AmountMinor, nr.ChannelTxID, nr.Payer, nr.DeviceID, nr.AccountID, time.Now().Format(time.RFC3339)) + if err := s.store.InsertEvent(ctx, o.ID, "paid", nr.ChannelTxID, o.AmountMinor, nr.RawPayload); err != nil { + logger.Error("order", "资金流水写入失败(返回错误,等待通道重试)", "err", err, "out_trade_no", outTradeNo) + return false, err + } + if err := s.ledger.Append(ctx, o.ID, eventPayload); err != nil { + logger.Error("order", "账本写入失败(返回错误,等待通道重试,幂等分支将补写)", "err", err, "out_trade_no", outTradeNo) + if s.alert != nil { + s.alert.Send("error", "账本写入失败", + fmt.Sprintf("订单 %s 入账但账本写入失败(重大)\n错误: %v\n幂等分支将补写,请关注", outTradeNo, err)) + } + return false, err + } + logger.Info("order", "订单支付成功 ✅", + "out_trade_no", outTradeNo, + "pid", o.Pid, + "upstream", o.UpstreamName, + "amount", o.AmountMinor, + "tx_id", nr.ChannelTxID) + + // 7. 创建回调任务(outbox) + s.EnqueueNotify(ctx, o) + // 备注码释放(订单终态 SUCCESS,备注码不再需要) + s.releaseNoteCode(o) + // WAIT_PAY 池同步:订单已入账,移出池 + s.notifyWaitPayRemove(o) + return true, nil +} + +// ensureRecorded 崩溃恢复补写:条件更新成功后进程若崩溃,事件/账本可能缺失。 +// 在幂等分支调用:检查并按需补写,保证"资金事件必有账本记录"(账本按 eventID 去重,绝不重复记账)。 +func (s *Service) ensureRecorded(ctx context.Context, o *model.Order, nr *channel.NotifyResult) { + // 账本缺失 → 补写(eventID=订单ID 去重) + has, err := s.ledger.HasEvent(ctx, o.ID) + if err != nil { + logger.Error("order", "检查账本记录失败(崩溃恢复)", "err", err, "out_trade_no", o.OutTradeNo) + return + } + if !has { + eventPayload := fmt.Sprintf(`{"out_trade_no":%q,"amount":%d,"status":"TRADE_SUCCESS","channel_tx_id":%q,"payer":%q,"device_id":%q,"account_id":%d,"time":%q,"recovered":true}`, + o.OutTradeNo, o.AmountMinor, nr.ChannelTxID, nr.Payer, nr.DeviceID, nr.AccountID, time.Now().Format(time.RFC3339)) + if err := s.ledger.Append(ctx, o.ID, eventPayload); err != nil { + logger.Error("order", "崩溃恢复:账本补写失败(严重,需人工核查)", "err", err, "out_trade_no", o.OutTradeNo) + } else { + logger.Warn("order", "崩溃恢复:账本已补写", "out_trade_no", o.OutTradeNo, "event_id", o.ID) + } + } + // 事件缺失 → 补写(幂等:同一订单+交易号只记一条 paid 事件) + if nr.ChannelTxID != "" { + evs, err := s.store.ListEvents(ctx, o.ID, 50) + if err == nil { + found := false + for _, e := range evs { + if e.EventType == "paid" && e.ChannelTxID == nr.ChannelTxID { + found = true + break + } + } + if !found { + if err := s.store.InsertEvent(ctx, o.ID, "paid", nr.ChannelTxID, o.AmountMinor, nr.RawPayload); err != nil { + logger.Error("order", "崩溃恢复:事件补写失败", "err", err, "out_trade_no", o.OutTradeNo) + } else { + logger.Warn("order", "崩溃恢复:资金事件已补写", "out_trade_no", o.OutTradeNo) + } + } + } + } +} + +// CloseExpiredOrders 关闭超过 before 仍未支付的订单(定时任务调用) +// 关单前必查单(与 ConfirmScheduler 同策略):已支付 → 补账;查单失败 → 跳过留待调度器/人工池;确认未支付 → 才关闭 +// 返回本次关闭的订单数 +func (s *Service) CloseExpiredOrders(ctx context.Context, before time.Time) (int, error) { + expired, err := s.store.ListExpiredOrders(ctx, before) + if err != nil { + return 0, err + } + closed := 0 + // 扫描级设备在线缓存:同设备多个到期订单只做一次 adb 探测(Online 有连接开销)。 + // 仅缓存已绑定设备(DeviceID≠"")的结果;未绑定设备查任意可用设备,不缓存。 + type devProbe struct { + offline bool + reason string + } + probeCache := map[string]devProbe{} + for _, o := range expired { + // ConfirmAbility 分叉:不可主动查单的通道(个人/监听)走安全关单 + if ch, ok := channel.Lookup(o.Channel); ok && ch.ConfirmAbility() != channel.ConfirmByQuery { + // 动态关单窗口:池中记录的 CloseAt 未到 → 本次扫描跳过(窗口由设备负载决定,见 PLAN-DYNAMIC-CLOSE-ALLOC) + if s.waitPay != nil && s.waitPay.CloseAtOf != nil { + if ct, ok := s.waitPay.CloseAtOf(o.OutTradeNo); ok && time.Now().Before(ct) { + continue + } + } + // 关单前安全检查(与 confirmPersonal 同语义,防止两条关单路径安全检查分叉): + // 监听端离线 → 进异常池人工核实(用户可能已付款但未监听到),绝不直接关单。 + if s.personalCheck != nil { + offline, reason := false, "" + if o.DeviceID != "" { + if pr, ok := probeCache[o.DeviceID]; ok { + offline, reason = pr.offline, pr.reason + } else { + offline, reason = s.personalCheck(ctx, o) + probeCache[o.DeviceID] = devProbe{offline: offline, reason: reason} + } + } else { + offline, reason = s.personalCheck(ctx, o) + } + if offline { + s.EnqueueException(ctx, o, model.ExReasonListenOffline, "LISTEN_OFFLINE", "", + fmt.Sprintf(`{"action":"close_scan_offline","time":%q}`, time.Now().Format(time.RFC3339)), + "监听端掉线,无法确认用户是否已付款,需人工核实: "+reason) + continue + } + } + // 监听端在线但此前已进过 LISTEN_TIMEOUT 异常池(复查过仍无推送)→ 不进异常池重复核查, + // 说明监听端僵死风险已存在:不直接关单,交由人工核实(与 confirmPersonal 的复查语义一致)。 + if ex, _ := s.store.GetExceptionByOutTradeNo(ctx, o.OutTradeNo); ex != nil && ex.Reason == model.ExReasonListenTimeout { + s.EnqueueException(ctx, o, model.ExReasonListenTimeout, "LISTEN_TIMEOUT", "", + fmt.Sprintf(`{"action":"close_scan_stalled","time":%q}`, time.Now().Format(time.RFC3339)), + "监听端在线但长时间未收到收款推送(扫描器复查命中历史 LISTEN_TIMEOUT),需人工确认实际收款情况") + continue + } + if s.closeExpiredOrder(ctx, o, nil, "") { + closed++ + } + continue + } + // 商家通道关单前必查单;只查订单所属通道避免跨通道命中同名订单 + // 到期判定:ORDER_CLOSE_MINUTES(商家通道固定关单时间,不受个人免签动态窗口影响)。 + // 扫描器候选集可能被动态窗口放宽(max(商家,动态MAX)+裕量),此处二次过滤真正到期订单。 + if !s.isExpiredWindow(o) { + continue + } + st, qerr := s.queryOrderFor(ctx, o.OutTradeNo, o.Channel) + if qerr == nil && st != nil && st.Status == model.StatusSuccess { + if _, serr := s.SettleByQuery(ctx, o.OutTradeNo, st.ChannelTxID, st.AmountMinor, + fmt.Sprintf(`{"action":"close_scan_found_paid","time":%q}`, time.Now().Format(time.RFC3339))); serr != nil { + logger.Error("order", "扫描关单前查单发现已支付但补账失败(转人工核查)", + "err", serr, "out_trade_no", o.OutTradeNo) + } + continue + } + if qerr != nil && !errors.Is(qerr, ErrNotQueryable) { + // 查单失败绝不关单(资金安全)。但不能每轮扫描都重查+重 WARN 同一批卡单: + // 首次失败入人工池(close_uncertain),已存在则静默跳过,交人工池处理。 + if ex, gerr := s.store.GetExceptionByOutTradeNo(ctx, o.OutTradeNo); gerr == nil && ex != nil { + continue + } + s.EnqueueException(ctx, o, model.ExReasonCloseUncertain, "QUERY_FAILED", "", + fmt.Sprintf(`{"action":"close_scan_query_failed","time":%q,"err":%q}`, + time.Now().Format(time.RFC3339), qerr.Error()), + "关单扫描查单失败,无法确认是否已支付,需人工核对后处理") + continue + } + if s.closeExpiredOrder(ctx, o, st, "") { + closed++ + } + } + if closed > 0 { + logger.Info("order", "自动关闭扫描完成", "closed", closed) + } + return closed, nil +} + +// closeExpiredOrder 执行单笔超时关单:st=nil 表示无法/未查单(直接关),st != nil 表示已查过确认未支付。 +func (s *Service) closeExpiredOrder(ctx context.Context, o *model.Order, st *channel.OrderStatus, evidenceJSON string) bool { + ok, err := s.store.UpdateOrderStatus(ctx, o.OutTradeNo, model.StatusWaitPay, model.StatusClosed, nil) + if err != nil { + logger.Error("order", "关闭订单失败", "err", err, "out_trade_no", o.OutTradeNo) + return false + } + if !ok { + return false + } + rawPayload := fmt.Sprintf(`{"reason":"timeout","queried":%t%s}`, st != nil, evidenceJSON) + if err := s.store.InsertEvent(ctx, o.ID, "closed", "", o.AmountMinor, rawPayload); err != nil { + logger.Warn("order", "写关闭事件失败", "err", err, "out_trade_no", o.OutTradeNo) + } + s.releaseNoteCode(o) + // WAIT_PAY 池同步:订单已关单,移出池 + s.notifyWaitPayRemove(o) + logger.Info("order", "超时未支付订单已关闭", "out_trade_no", o.OutTradeNo, "id", o.ID) + return true +} + +// CloseOrder 单笔幂等关单(条件更新 WAIT→CLOSED,写关闭事件审计) +// 供 ConfirmScheduler / 人工池 / 查单确认"未支付"后调用 +func (s *Service) CloseOrder(ctx context.Context, outTradeNo string, reason string) (bool, error) { + o, err := s.store.GetOrder(ctx, outTradeNo) + if err != nil { + return false, err + } + if o == nil { + return false, nil + } + if o.Status == model.StatusClosed { + return true, nil // 幂等 + } + ok, err := s.store.UpdateOrderStatus(ctx, outTradeNo, model.StatusWaitPay, model.StatusClosed, nil) + if err != nil { + return false, err + } + if !ok { + return false, nil // 已被并发处理(如刚支付成功) + } + if err := s.store.InsertEvent(ctx, o.ID, "closed", "", o.AmountMinor, + fmt.Sprintf(`{"reason":%q,"time":%q}`, reason, time.Now().Format(time.RFC3339))); err != nil { + logger.Warn("order", "写关闭事件失败", "err", err, "out_trade_no", outTradeNo) + } + // 备注码释放(订单终态 CLOSED) + s.releaseNoteCode(o) + // WAIT_PAY 池同步:订单已关单,移出池 + s.notifyWaitPayRemove(o) + logger.Info("order", "订单已关闭", "out_trade_no", outTradeNo, "reason", reason) + return true, nil +} + +// SettleByQuery 查单补账:查单确认已支付后,走 SettlePayment 幂等入账 +// 金额不一致 → 进异常池(amount_mismatch),绝不自动入账 +func (s *Service) SettleByQuery(ctx context.Context, outTradeNo string, channelTxID string, paidAmount int64, evidence string) (bool, error) { + o, err := s.store.GetOrder(ctx, outTradeNo) + if err != nil { + return false, err + } + if o == nil { + return false, nil + } + // 金额校验:查单金额与订单金额不一致 → 进异常池,不自动入账 + if paidAmount != 0 && paidAmount != o.AmountMinor { + logger.Error("order", "查单金额与订单不一致(重大)", "out_trade_no", outTradeNo, + "order_amount", o.AmountMinor, "query_amount", paidAmount) + s.EnqueueException(ctx, o, model.ExReasonAmountMismatch, model.StatusSuccess, channelTxID, + evidence, fmt.Sprintf("查单金额 %d 与订单金额 %d 不一致", paidAmount, o.AmountMinor)) + return false, fmt.Errorf("查单金额不匹配: 订单=%d 查单=%d", o.AmountMinor, paidAmount) + } + // 幂等入账(走 SettlePayment:状态机 + 账本 + 通知) + return s.SettlePayment(ctx, &channel.NotifyResult{ + OutTradeNo: outTradeNo, + ChannelTxID: channelTxID, + AmountMinor: paidAmount, + RawPayload: evidence, + }) +} + +// SettleManual 人工确认入账(管理员授权操作,允许金额覆盖实际支付额) +// 与 SettlePayment 同路径:状态机 + 账本 hash 链 + 创建通知任务,全程审计 +func (s *Service) SettleManual(ctx context.Context, outTradeNo, channelTxID string, paidAmount int64, payer, handler, note string) (bool, error) { + o, err := s.store.GetOrder(ctx, outTradeNo) + if err != nil { + return false, err + } + if o == nil { + return false, nil + } + if o.Status == model.StatusSuccess { + // 崩溃恢复:若账本在上次人工入账中丢失(条件更新成功后进程崩溃),此处补写 + s.ensureRecorded(ctx, o, &channel.NotifyResult{ChannelTxID: channelTxID}) + // 备注码释放(幂等:重复释放无害) + s.releaseNoteCode(o) + logger.Info("order", "订单已支付,人工入账幂等返回", "out_trade_no", outTradeNo) + return true, nil + } + // 终态保护:已关单订单禁止人工入账重开(与 SettlePayment H9 守卫一致) + if o.Status == model.StatusClosed { + logger.Error("order", "人工入账拒绝:订单已关单", "out_trade_no", outTradeNo, "amount", paidAmount, "handler", handler) + if s.alert != nil { + s.alert.Send("error", "人工入账被拒(已关单)", + fmt.Sprintf("管理员 %s 试图人工入账已关单订单 %s (amount=%d)\n关单终态不可重开,请走 H9 迟到支付证据流程或上游对账退款", + handler, outTradeNo, paidAmount)) + } + return false, fmt.Errorf("订单 %s 已关单,禁止人工入账", outTradeNo) + } + // 互斥锁:条件更新(防并发双写账本/事件) + ok, err := s.store.UpdateOrderStatus(ctx, outTradeNo, model.StatusWaitPay, model.StatusSuccess, func(upd *model.Order) { + if channelTxID != "" { + upd.ChannelTxID = channelTxID + } + if payer != "" { + upd.Payer = payer + } + }) + if err != nil { + return false, err + } + if !ok { + return false, nil // 状态非法(如已关闭) + } + amount := paidAmount + if amount <= 0 { + amount = o.AmountMinor + } + eventPayload := fmt.Sprintf(`{"out_trade_no":%q,"amount":%d,"status":"TRADE_SUCCESS","channel_tx_id":%q,"source":"manual","handler":%q,"note":%q,"time":%q}`, + outTradeNo, amount, channelTxID, handler, note, time.Now().Format(time.RFC3339)) + // 先事件后账本;任一失败返回错误,重试时由幂等分支 ensureRecorded 补写 + if err := s.store.InsertEvent(ctx, o.ID, "paid_manual", channelTxID, amount, eventPayload); err != nil { + logger.Error("order", "人工入账流水写入失败(等待重试)", "err", err, "out_trade_no", outTradeNo) + return false, err + } + if err := s.ledger.Append(ctx, o.ID, eventPayload); err != nil { + logger.Error("order", "人工入账账本写入失败(等待重试,幂等分支将补写)", "err", err, "out_trade_no", outTradeNo) + if s.alert != nil { + s.alert.Send("error", "人工入账账本写入失败", + fmt.Sprintf("订单 %s 人工入账但账本写入失败(重大)\n错误: %v", outTradeNo, err)) + } + return false, err + } + logger.Warn("order", "人工确认入账(管理员操作)", "out_trade_no", outTradeNo, + "amount", amount, "handler", handler, "pid", o.Pid, "upstream", o.UpstreamName) + // 创建通知任务(易支付协议:通知金额用订单金额) + s.EnqueueNotify(ctx, o) + // 备注码释放(订单终态 SUCCESS) + s.releaseNoteCode(o) + // WAIT_PAY 池同步:订单已入账,移出池 + s.notifyWaitPayRemove(o) + return true, nil +} + +// EnqueueException 订单进人工异常池(幂等:同单已存在则仅补审计) +func (s *Service) EnqueueException(ctx context.Context, o *model.Order, reason, channelStatus, channelTxID, evidence, suggestion string) { + e := &model.ExceptionOrder{ + OutTradeNo: o.OutTradeNo, + Pid: o.Pid, + UpstreamName: o.UpstreamName, + Channel: o.Channel, + AmountMinor: o.AmountMinor, + OrderStatus: o.Status, + ChannelStatus: channelStatus, + ChannelTxID: channelTxID, + Reason: reason, + Evidence: evidence, + Suggestion: suggestion, + } + if err := s.store.CreateException(ctx, e); err != nil { + logger.Error("order", "创建异常单失败", "err", err, "out_trade_no", o.OutTradeNo) + return + } + logger.Warn("order", "订单进入人工异常池", "out_trade_no", o.OutTradeNo, + "reason", reason, "channel_status", channelStatus) + // 告警:进池事件(RESILIENCE P1-3) + if s.alert != nil { + s.alert.Send("error", "异常池进单", + fmt.Sprintf("订单 %s 进入人工异常池\n原因: %s\n通道状态: %s\n建议: %s", + o.OutTradeNo, reason, channelStatus, suggestion)) + } +} + +// BuildNotifyPayload 构造易支付协议回调参数并签名,返回 JSON payload。 +// 供 EnqueueNotify(下单入队)与死信手动重发(用订单最新 notify_url)复用,保证字段/签名单一来源。 +func (s *Service) BuildNotifyPayload(o *model.Order) string { + params := map[string]string{ + "trade_no": o.GatewayNo, + "out_trade_no": o.OutTradeNo, + "type": o.Channel, + "name": o.Subject, + "money": o.MoneyYuan(), + "trade_status": "TRADE_SUCCESS", + // notify_url 必须进 payload:worker 用它作为发送地址, + // 且参与签名(易支付协议标准),New API 验签时用收到的全部参数重算,保证一致 + "notify_url": o.NotifyURL, + } + // attach(上游请求ID)原样带回,参与签名,供上游对账 + if o.Attach != "" { + params["attach"] = o.Attach + } + if o.ChannelTxID != "" { + params["trade_no"] = o.ChannelTxID // 优先用通道交易号(易支付协议语义) + } + epay.BuildSignedParams(params, s.signKey(o.Pid)) + payload, _ := json.Marshal(params) + return string(payload) +} + +// EnqueueNotify 创建回调接入方任务(outbox) +func (s *Service) EnqueueNotify(ctx context.Context, o *model.Order) { + payload := s.BuildNotifyPayload(o) + taskID, err := s.store.CreateNotifyTask(ctx, o.ID, payload) + if err != nil { + logger.Error("order", "创建回调任务失败", "err", err, "out_trade_no", o.OutTradeNo) + return + } + logger.Info("order", "回调任务已入队", "task_id", taskID, "out_trade_no", o.OutTradeNo) +} + +// recordEvent 记录资金流水 +func (s *Service) recordEvent(ctx context.Context, o *model.Order, eventType, txID string, amount int64, raw string) { + if err := s.store.InsertEvent(ctx, o.ID, eventType, txID, amount, raw); err != nil { + logger.Error("order", "写入资金流水失败", "err", err, "out_trade_no", o.OutTradeNo, "event", eventType) + } +} + +// QueryOrder 查单(易支付协议 /api.php) +func (s *Service) QueryOrder(ctx context.Context, outTradeNo string) (*model.Order, error) { + return s.store.GetOrder(ctx, outTradeNo) +} + +// ---------- WAIT_PAY 池同步钩子(个人免签通道,资金判定不依赖钩子) ---------- + +// notifyWaitPayAdd 订单进入 WAIT_PAY 且已绑定账户 → 通知池(仅 personal.* 通道) +func (s *Service) notifyWaitPayAdd(o *model.Order) { + if s.waitPay == nil || s.waitPay.Add == nil || o == nil || o.AccountID <= 0 { + return + } + if !strings.HasPrefix(o.Channel, "personal.") { + return + } + s.waitPay.Add(o) +} + +// notifyWaitPayUpsert 订单凭据刷新/换通道(WAIT_PAY 内 NoteCode/AccountID 变化)→ 覆盖更新池 +func (s *Service) notifyWaitPayUpsert(o *model.Order) { + if s.waitPay == nil || s.waitPay.Upsert == nil || o == nil || o.AccountID <= 0 { + return + } + if !strings.HasPrefix(o.Channel, "personal.") { + return + } + s.waitPay.Upsert(o) +} + +// notifyWaitPayRemove 订单离开 WAIT_PAY(入账/关单终态)→ 通知池移除 +func (s *Service) notifyWaitPayRemove(o *model.Order) { + if s.waitPay == nil || s.waitPay.Remove == nil || o == nil { + return + } + if !strings.HasPrefix(o.Channel, "personal.") { + return + } + s.waitPay.Remove(o.OutTradeNo) +} diff --git a/internal/order/service_test.go b/internal/order/service_test.go new file mode 100755 index 0000000..4638625 --- /dev/null +++ b/internal/order/service_test.go @@ -0,0 +1,1497 @@ +package order + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "path/filepath" + "testing" + "time" + + "paygateway/internal/account" + "paygateway/internal/channel" + "paygateway/internal/ledger" + "paygateway/internal/model" + "paygateway/internal/store" + "paygateway/internal/upstream" +) + +// newTestService 创建真实 SQLite + 真实账本的测试服务 +func newTestService(t *testing.T) (*Service, *store.SQLiteStore, *store.LedgerDB, string) { + t.Helper() + dir := t.TempDir() + st, err := store.NewSQLite(filepath.Join(dir, "test.db")) + if err != nil { + t.Fatalf("创建 SQLite 失败: %v", err) + } + lgdb, err := store.NewLedgerDB(filepath.Join(dir, "ledger.db")) + if err != nil { + t.Fatalf("创建账本 DB 失败: %v", err) + } + lg, err := ledger.New(lgdb, "") // 自动生成测试密钥 + if err != nil { + t.Fatalf("创建账本失败: %v", err) + } + svc := New(st, lg, account.NewPool(), upstream.NewPool()) + return svc, st, lgdb, dir +} + +// createTestOrder 直接落库一笔待支付订单(绕过通道注册,专注测资金路径) +func createTestOrder(t *testing.T, st *store.SQLiteStore, outTradeNo string, amount int64) *model.Order { + t.Helper() + now := time.Now() + o := &model.Order{ + OutTradeNo: outTradeNo, + GatewayNo: "GW" + outTradeNo, + Channel: "alipay", + Pid: "2001", + AmountMinor: amount, + Currency: "CNY", + Subject: "测试", + Status: model.StatusWaitPay, + NotifyURL: "http://example.com/notify", + CreatedAt: now, + } + if err := st.CreateOrder(context.Background(), o); err != nil { + t.Fatalf("创建订单失败: %v", err) + } + return o +} + +// TestSettlePayment_Normal 正常结算:状态→SUCCESS,事件+账本都有记录 +func TestSettlePayment_Normal(t *testing.T) { + svc, st, _, _ := newTestService(t) + ctx := context.Background() + o := createTestOrder(t, st, "NORMAL001", 100) // 1.00 元 + + ok, err := svc.SettlePayment(ctx, &channel.NotifyResult{ + OutTradeNo: o.OutTradeNo, + ChannelTxID: "ALI_TX_001", + AmountMinor: 100, + }) + if err != nil || !ok { + t.Fatalf("SettlePayment = %v, %v; 期望 true, nil", ok, err) + } + + // 状态 + got, _ := st.GetOrder(ctx, o.OutTradeNo) + if got.Status != model.StatusSuccess { + t.Fatalf("订单状态 = %s, 期望 %s", got.Status, model.StatusSuccess) + } + // 账本有记录 + has, err := svc.ledger.HasEvent(ctx, got.ID) + if err != nil || !has { + t.Fatalf("账本应有记录, has=%v err=%v", has, err) + } + // 事件有记录 + evs, _ := st.ListEvents(ctx, got.ID, 10) + if len(evs) != 1 || evs[0].EventType != "paid" { + t.Fatalf("事件记录 = %+v, 期望 1 条 paid", evs) + } +} + +// TestSettlePayment_Idempotent 幂等:重复回调不重复记账 +func TestSettlePayment_Idempotent(t *testing.T) { + svc, st, lgdb, _ := newTestService(t) + ctx := context.Background() + o := createTestOrder(t, st, "IDEM001", 100) + + // 第一次结算 + if ok, err := svc.SettlePayment(ctx, &channel.NotifyResult{OutTradeNo: o.OutTradeNo, ChannelTxID: "ALI_TX_2", AmountMinor: 100}); err != nil || !ok { + t.Fatalf("第一次结算失败: %v %v", ok, err) + } + count1, _ := lgdb.CountLedger(ctx) + + // 重复回调(同交易号) + if ok, err := svc.SettlePayment(ctx, &channel.NotifyResult{OutTradeNo: o.OutTradeNo, ChannelTxID: "ALI_TX_2", AmountMinor: 100}); err != nil || !ok { + t.Fatalf("幂等重试失败: %v %v", ok, err) + } + count2, _ := lgdb.CountLedger(ctx) + if count2 != count1 { + t.Fatalf("幂等重试不应重复记账: before=%d after=%d", count1, count2) + } + // 事件也应只有 1 条 paid + got, _ := st.GetOrder(ctx, o.OutTradeNo) + evs, _ := st.ListEvents(ctx, got.ID, 10) + paid := 0 + for _, e := range evs { + if e.EventType == "paid" { + paid++ + } + } + if paid != 1 { + t.Fatalf("paid 事件条数 = %d, 期望 1", paid) + } +} + +// TestSettlePayment_CrashRecovery 崩溃恢复:状态已 SUCCESS 但账本缺失(模拟条件更新后崩溃)→ 幂等分支补写 +func TestSettlePayment_CrashRecovery(t *testing.T) { + svc, st, lgdb, _ := newTestService(t) + ctx := context.Background() + o := createTestOrder(t, st, "CRASH001", 100) + + // 模拟崩溃窗口:仅条件更新状态为 SUCCESS(等价于进程在事件/账本写入前崩溃) + ok, err := st.UpdateOrderStatus(ctx, o.OutTradeNo, model.StatusWaitPay, model.StatusSuccess, nil) + if err != nil || !ok { + t.Fatalf("模拟崩溃状态更新失败: %v %v", ok, err) + } + // 确认账本确实没有记录 + got, _ := st.GetOrder(ctx, o.OutTradeNo) + has, _ := svc.ledger.HasEvent(ctx, got.ID) + if has { + t.Fatal("前置条件错误:账本不应有记录") + } + + // 回调重试 → 幂等分支 → ensureRecorded 补写账本 + if ok, err := svc.SettlePayment(ctx, &channel.NotifyResult{OutTradeNo: o.OutTradeNo, ChannelTxID: "ALI_TX_3", AmountMinor: 100}); err != nil || !ok { + t.Fatalf("崩溃恢复结算失败: %v %v", ok, err) + } + has, err = svc.ledger.HasEvent(ctx, got.ID) + if err != nil || !has { + t.Fatalf("崩溃恢复后账本应有记录, has=%v err=%v", has, err) + } + // 不应重复记账(再触发一次幂等返回,账本条数不变) + count1, _ := lgdb.CountLedger(ctx) + svc.SettlePayment(ctx, &channel.NotifyResult{OutTradeNo: o.OutTradeNo, ChannelTxID: "ALI_TX_3", AmountMinor: 100}) + count2, _ := lgdb.CountLedger(ctx) + if count2 != count1 { + t.Fatalf("崩溃恢复后再结算不应重复记账: before=%d after=%d", count1, count2) + } +} + +// TestSettlePayment_AmountMismatch 金额不匹配:拒绝入账(防改价) +func TestSettlePayment_AmountMismatch(t *testing.T) { + svc, st, _, _ := newTestService(t) + ctx := context.Background() + o := createTestOrder(t, st, "AMT001", 100) + + ok, err := svc.SettlePayment(ctx, &channel.NotifyResult{ + OutTradeNo: o.OutTradeNo, + ChannelTxID: "ALI_TX_4", + AmountMinor: 50, // 实付 0.50 ≠ 订单 1.00 + }) + if err == nil || ok { + t.Fatalf("金额不匹配应返回错误并拒绝, ok=%v err=%v", ok, err) + } + got, _ := st.GetOrder(ctx, o.OutTradeNo) + if got.Status == model.StatusSuccess { + t.Fatal("金额不匹配时订单不应变为 SUCCESS") + } + // 应记录 amount_mismatch 事件(审计) + evs, _ := st.ListEvents(ctx, got.ID, 10) + found := false + for _, e := range evs { + if e.EventType == "amount_mismatch" { + found = true + } + } + if !found { + t.Fatal("应记录 amount_mismatch 审计事件") + } +} + +// TestSettleManual_Recovery 人工入账 + 幂等不重复记账 +func TestSettleManual_Recovery(t *testing.T) { + svc, st, lgdb, _ := newTestService(t) + ctx := context.Background() + o := createTestOrder(t, st, "MANUAL001", 100) + + ok, err := svc.SettleManual(ctx, o.OutTradeNo, "MAN_TX_1", 100, "payer1", "admin", "测试") + if err != nil || !ok { + t.Fatalf("人工入账失败: %v %v", ok, err) + } + count1, _ := lgdb.CountLedger(ctx) + + // 幂等重试不重复记账 + if ok, err := svc.SettleManual(ctx, o.OutTradeNo, "MAN_TX_1", 100, "payer1", "admin", "重试"); err != nil || !ok { + t.Fatalf("人工入账幂等重试失败: %v %v", ok, err) + } + count2, _ := lgdb.CountLedger(ctx) + if count2 != count1 { + t.Fatalf("人工入账幂等重试不应重复记账: before=%d after=%d", count1, count2) + } +} + +// TestSettlePayment_DeviceID 个人免签:SettlePayment 带 device_id → 订单 + 账本正确记录 +func TestSettlePayment_DeviceID(t *testing.T) { + svc, st, _, _ := newTestService(t) + ctx := context.Background() + o := createTestOrder(t, st, "DEVICE001", 100) + deviceID := "test-device-12345" + + // 下单时绑定收款账户(CreateOrder 路径语义:cred.AccountID → 订单 AccountID) + if ok, err := st.UpdateOrderStatus(ctx, o.OutTradeNo, model.StatusWaitPay, model.StatusWaitPay, func(upd *model.Order) { + upd.AccountID = 2 + }); err != nil || !ok { + t.Fatalf("设置订单账户失败: %v %v", ok, err) + } + + ok, err := svc.SettlePayment(ctx, &channel.NotifyResult{ + OutTradeNo: o.OutTradeNo, + ChannelTxID: "PSN-DEVICE-001", + AmountMinor: 100, + Payer: "personal:" + deviceID, + DeviceID: deviceID, + AccountID: 2, + }) + if err != nil || !ok { + t.Fatalf("SettlePayment = %v, %v; 期望 true, nil", ok, err) + } + + got, _ := st.GetOrder(ctx, o.OutTradeNo) + if got.DeviceID != deviceID { + t.Fatalf("订单 device_id = %q, 期望 %q", got.DeviceID, deviceID) + } + if got.Payer != "personal:"+deviceID { + t.Fatalf("订单 payer = %q, 期望 personal:%s", got.Payer, deviceID) + } + if got.AccountID != 2 { + t.Fatalf("订单 account_id = %d, 期望 2", got.AccountID) + } + + has, err := svc.ledger.HasEvent(ctx, got.ID) + if err != nil || !has { + t.Fatalf("账本应有记录, has=%v err=%v", has, err) + } + res, err := svc.ledger.Verify(ctx) + if err != nil || res == nil || !res.Valid { + t.Fatalf("账本验证应通过, res=%v err=%v", res, err) + } +} + +// TestCloseExpiredOrders_PersonalOffline_NoClose 漏单防护:个人通道设备离线时绝不关单 +// (CloseExpiredOrders 走安全检查 → 离线 → 进异常池,订单保持 WAIT_PAY) +func TestCloseExpiredOrders_PersonalOffline_NoClose(t *testing.T) { + registerStubPersonal(t) + svc, st, _, _ := newTestService(t) + ctx := context.Background() + + // 个人通道订单(旧于关单窗口) + now := time.Now() + o := &model.Order{ + OutTradeNo: "P_OFFLINE", + GatewayNo: "GW_P_OFFLINE", + Channel: "personal.alipay", + Pid: "2001", + AmountMinor: 1000, + Currency: "CNY", + Subject: "测试", + Status: model.StatusWaitPay, + NotifyURL: "http://example.com/notify", + CreatedAt: now.Add(-40 * time.Minute), + DeviceID: "dev-offline", + } + if err := st.CreateOrder(ctx, o); err != nil { + t.Fatalf("创建订单失败: %v", err) + } + + // 注入 personalCheck:模拟设备离线(返回 offline=true) + svc.SetPersonalCheck(func(ctx context.Context, o *model.Order) (bool, string) { + return true, "设备 adb 不可达(测试模拟)" + }) + + closed, err := svc.CloseExpiredOrders(ctx, now.Add(-30*time.Minute)) + if err != nil { + t.Fatalf("CloseExpiredOrders err: %v", err) + } + if closed != 0 { + t.Fatalf("设备离线时不应关单:closed=%d", closed) + } + + // 订单必须保持 WAIT_PAY(未被误关) + got, err := st.GetOrder(ctx, o.OutTradeNo) + if err != nil || got == nil { + t.Fatalf("GetOrder: %v", err) + } + if got.Status != model.StatusWaitPay { + t.Fatalf("设备离线时订单应保持 WAIT_PAY,实际 %s(漏单!)", got.Status) + } + + // 必须已进人工异常池(人工核实路径) + exc, err := st.GetExceptionByOutTradeNo(ctx, o.OutTradeNo) + if err != nil || exc == nil { + t.Fatalf("离线订单应进人工异常池, exc=%v err=%v", exc, err) + } + if exc.Reason != model.ExReasonListenOffline { + t.Fatalf("异常池原因应为 listen_offline: %s", exc.Reason) + } +} + +// TestCloseExpiredOrders_PersonalOnline_Close 个人通道设备在线且已到关单窗口 → 正常关单 +func TestCloseExpiredOrders_PersonalOnline_Close(t *testing.T) { + registerStubPersonal(t) + svc, st, _, _ := newTestService(t) + ctx := context.Background() + + now := time.Now() + o := &model.Order{ + OutTradeNo: "P_ONLINE", + GatewayNo: "GW_P_ONLINE", + Channel: "personal.alipay", + Pid: "2001", + AmountMinor: 1000, + Currency: "CNY", + Subject: "测试", + Status: model.StatusWaitPay, + NotifyURL: "http://example.com/notify", + CreatedAt: now.Add(-40 * time.Minute), + DeviceID: "dev-online", + } + if err := st.CreateOrder(ctx, o); err != nil { + t.Fatalf("创建订单失败: %v", err) + } + + // 设备在线(返回 offline=false) + svc.SetPersonalCheck(func(ctx context.Context, o *model.Order) (bool, string) { + return false, "" + }) + + closed, err := svc.CloseExpiredOrders(ctx, now.Add(-30*time.Minute)) + if err != nil { + t.Fatalf("CloseExpiredOrders err: %v", err) + } + if closed != 1 { + t.Fatalf("设备在线应关单 1 笔:closed=%d", closed) + } + got, _ := st.GetOrder(ctx, o.OutTradeNo) + if got.Status != model.StatusClosed { + t.Fatalf("设备在线应已关单:%s", got.Status) + } +} + +// TestCloseExpiredOrders_DynamicWindow_NotDue 动态关单窗口:CloseAtOf 未到期 → 跳过不关 +func TestCloseExpiredOrders_DynamicWindow_NotDue(t *testing.T) { + registerStubPersonal(t) + svc, st, _, _ := newTestService(t) + ctx := context.Background() + + now := time.Now() + o := &model.Order{ + OutTradeNo: "P_DYN", + GatewayNo: "GW_P_DYN", + Channel: "personal.alipay", + Pid: "2001", + AmountMinor: 1000, + Currency: "CNY", + Subject: "测试", + Status: model.StatusWaitPay, + NotifyURL: "http://example.com/notify", + CreatedAt: now.Add(-40 * time.Minute), // 旧订单 + } + if err := st.CreateOrder(ctx, o); err != nil { + t.Fatalf("创建订单失败: %v", err) + } + + // 注入 CloseAtOf:模拟动态窗口未到期(CloseAt 在未来)→ 跳过 + svc.SetWaitPayHooks(&WaitPayHooks{ + CloseAtOf: func(outTradeNo string) (time.Time, bool) { + if outTradeNo == "P_DYN" { + return now.Add(10 * time.Minute), true // 未到期 + } + return time.Time{}, false + }, + }) + // 设备在线(即使在线也不该关,因为窗口未到) + svc.SetPersonalCheck(func(ctx context.Context, o *model.Order) (bool, string) { + return false, "" + }) + + closed, err := svc.CloseExpiredOrders(ctx, now.Add(-30*time.Minute)) + if err != nil { + t.Fatalf("CloseExpiredOrders err: %v", err) + } + if closed != 0 { + t.Fatalf("动态窗口未到期不应关单:closed=%d", closed) + } + got, _ := st.GetOrder(ctx, o.OutTradeNo) + if got.Status != model.StatusWaitPay { + t.Fatalf("未到期订单应保持 WAIT_PAY:%s", got.Status) + } +} + +// stubPersonalChannel 个人通道桩(测试用):注册表 Lookup 返回 ConfirmByManual, +// 让 CloseExpiredOrders 走个人安全检查分支(不实际下单/查单)。 +type stubPersonalChannel struct{} + +func (stubPersonalChannel) Name() string { return "personal.alipay" } +func (stubPersonalChannel) Type() channel.ChannelType { return channel.TypePersonal } +func (stubPersonalChannel) Kind() channel.ChannelKind { return channel.KindListener } +func (stubPersonalChannel) Modes() []channel.PayMode { return []channel.PayMode{channel.ModeQRCode} } +func (stubPersonalChannel) ConfirmAbility() channel.ConfirmAbility { return channel.ConfirmByManual } +func (stubPersonalChannel) Provider() channel.Provider { return channel.ProviderAlipay } +func (stubPersonalChannel) CreatePayment(context.Context, *channel.PayRequest) (*channel.PayCredential, error) { + return nil, nil +} +func (stubPersonalChannel) VerifyNotify(context.Context, *http.Request) (*channel.NotifyResult, error) { + return nil, nil +} +func (stubPersonalChannel) QueryOrder(context.Context, string) (*channel.OrderStatus, error) { + return nil, ErrNotQueryable +} +func (stubPersonalChannel) Listener() channel.EventListener { return nil } + +// registerStubPersonal 注册个人通道桩(幂等:已注册跳过) +func registerStubPersonal(t *testing.T) { + t.Helper() + if _, ok := channel.Lookup("personal.alipay"); ok { + return + } + channel.Register(stubPersonalChannel{}) +} + +// TestCloseExpiredOrders_Merchant_RespectsFixedWindow 商家通道固定关单窗口: +// 候选集被动态窗口放宽(before=now-11min)时,未到 ORDER_CLOSE_MINUTES(30min) 的商家订单不关单。 +func TestCloseExpiredOrders_Merchant_RespectsFixedWindow(t *testing.T) { + svc, st, _, _ := newTestService(t) + ctx := context.Background() + now := time.Now() + + // 商家通道订单,20 分钟前创建(早于动态候选集 11min,但未到商家固定 30min) + o := &model.Order{ + OutTradeNo: "M_FIXED", + GatewayNo: "GW_M_FIXED", + Channel: "alipay", // 商家通道(ConfirmByQuery) + Pid: "2001", + AmountMinor: 1000, + Currency: "CNY", + Subject: "测试", + Status: model.StatusWaitPay, + NotifyURL: "http://example.com/notify", + CreatedAt: now.Add(-20 * time.Minute), + } + if err := st.CreateOrder(ctx, o); err != nil { + t.Fatalf("创建订单失败: %v", err) + } + // 商家固定关单窗口 30min + svc.SetCloseWindow(30) + + // 候选集放宽到 11min(模拟动态窗口模式下扫描器传的 before) + closed, err := svc.CloseExpiredOrders(ctx, now.Add(-11*time.Minute)) + if err != nil { + t.Fatalf("CloseExpiredOrders err: %v", err) + } + if closed != 0 { + t.Fatalf("商家订单未到 30min 固定窗口不应关单:closed=%d", closed) + } + got, _ := st.GetOrder(ctx, o.OutTradeNo) + if got.Status != model.StatusWaitPay { + t.Fatalf("商家订单应保持 WAIT_PAY:%s", got.Status) + } +} + +// TestCloseExpiredOrders_Merchant_OverFixedWindow 商家通道已到固定窗口 → 正常关单 +func TestCloseExpiredOrders_Merchant_OverFixedWindow(t *testing.T) { + registerStubMerchant(t) + svc, st, _, _ := newTestService(t) + ctx := context.Background() + now := time.Now() + + o := &model.Order{ + OutTradeNo: "M_OVER", + GatewayNo: "GW_M_OVER", + Channel: "alipay", + Pid: "2001", + AmountMinor: 1000, + Currency: "CNY", + Subject: "测试", + Status: model.StatusWaitPay, + NotifyURL: "http://example.com/notify", + CreatedAt: now.Add(-40 * time.Minute), // 已超 30min + } + if err := st.CreateOrder(ctx, o); err != nil { + t.Fatalf("创建订单失败: %v", err) + } + svc.SetCloseWindow(30) + + // 商家通道无可用查单通道 → queryOrderFor 返回 ErrNotQueryable → 视为未支付关单 + closed, err := svc.CloseExpiredOrders(ctx, now.Add(-30*time.Minute)) + if err != nil { + t.Fatalf("CloseExpiredOrders err: %v", err) + } + if closed != 1 { + t.Fatalf("商家订单已到固定窗口应关单:closed=%d", closed) + } + got, _ := st.GetOrder(ctx, o.OutTradeNo) + if got.Status != model.StatusClosed { + t.Fatalf("商家订单应已关闭:%s", got.Status) + } +} + +// stubMerchantChannel 商家通道桩(测试用):ConfirmByQuery,QueryOrder 返回未支付。 +type stubMerchantChannel struct{} + +func (stubMerchantChannel) Name() string { return "alipay" } +func (stubMerchantChannel) Type() channel.ChannelType { return channel.TypeMerchant } +func (stubMerchantChannel) Kind() channel.ChannelKind { return channel.KindCallback } +func (stubMerchantChannel) Modes() []channel.PayMode { + return []channel.PayMode{channel.ModePage, channel.ModeQRCode} +} +func (stubMerchantChannel) ConfirmAbility() channel.ConfirmAbility { return channel.ConfirmByQuery } +func (stubMerchantChannel) Provider() channel.Provider { return channel.ProviderAlipay } +func (stubMerchantChannel) CreatePayment(context.Context, *channel.PayRequest) (*channel.PayCredential, error) { + return nil, nil +} +func (stubMerchantChannel) VerifyNotify(context.Context, *http.Request) (*channel.NotifyResult, error) { + return nil, nil +} +func (stubMerchantChannel) QueryOrder(context.Context, string) (*channel.OrderStatus, error) { + // 模拟官方通道查单:订单仍处于"未支付"状态 + return &channel.OrderStatus{Status: model.StatusWaitPay}, nil +} +func (stubMerchantChannel) Listener() channel.EventListener { return nil } + +// registerStubMerchant 注册商家通道桩(幂等:已注册跳过) +func registerStubMerchant(t *testing.T) { + t.Helper() + if _, ok := channel.Lookup("alipay"); ok { + return + } + channel.Register(stubMerchantChannel{}) +} + +// TestCloseExpiredOrders_PersonalOnline_StalledListener 监听端在线但此前已进过 LISTEN_TIMEOUT 异常池 +// (复查过仍无推送,监听端僵死风险)→ 扫描器不直接关单,交由人工核实(P2-1 修复)。 +func TestCloseExpiredOrders_PersonalOnline_StalledListener(t *testing.T) { + registerStubPersonal(t) + svc, st, _, _ := newTestService(t) + ctx := context.Background() + + now := time.Now() + o := &model.Order{ + OutTradeNo: "P_STALLED", + GatewayNo: "GW_P_STALLED", + Channel: "personal.alipay", + Pid: "2001", + AmountMinor: 1000, + Currency: "CNY", + Subject: "测试", + Status: model.StatusWaitPay, + NotifyURL: "http://example.com/notify", + CreatedAt: now.Add(-40 * time.Minute), + DeviceID: "dev-online", + } + if err := st.CreateOrder(ctx, o); err != nil { + t.Fatalf("创建订单失败: %v", err) + } + + // 设备在线(返回 offline=false) + svc.SetPersonalCheck(func(ctx context.Context, o *model.Order) (bool, string) { + return false, "" + }) + + // 先模拟 reconcile 复查路径已将该单进过 LISTEN_TIMEOUT 异常池 + exc := &model.ExceptionOrder{ + OutTradeNo: o.OutTradeNo, + Pid: o.Pid, + Channel: o.Channel, + AmountMinor: o.AmountMinor, + OrderStatus: o.Status, + Reason: model.ExReasonListenTimeout, + Suggestion: "监听端在线但长时间未收到收款推送,需人工确认实际收款情况", + } + if err := st.CreateException(ctx, exc); err != nil { + t.Fatalf("预置异常记录失败: %v", err) + } + + closed, err := svc.CloseExpiredOrders(ctx, now.Add(-30*time.Minute)) + if err != nil { + t.Fatalf("CloseExpiredOrders err: %v", err) + } + if closed != 0 { + t.Fatalf("监听端僵死风险订单不应关单:closed=%d", closed) + } + got, _ := st.GetOrder(ctx, o.OutTradeNo) + if got.Status != model.StatusClosed { + t.Logf("订单保持 WAIT_PAY(转人工核实路径)") + } else { + t.Fatalf("监听端僵死风险订单被误关(漏单!):%s", got.Status) + } +} + +// stubQueryFailChannel 商家通道桩:关单前查单必失败(模拟沙箱/凭据异常)。 +type stubQueryFailChannel struct{ name string } + +func (c stubQueryFailChannel) Name() string { return c.name } +func (c stubQueryFailChannel) Type() channel.ChannelType { return channel.TypeMerchant } +func (c stubQueryFailChannel) Kind() channel.ChannelKind { return channel.KindCallback } +func (c stubQueryFailChannel) Modes() []channel.PayMode { return []channel.PayMode{channel.ModeQRCode} } +func (c stubQueryFailChannel) ConfirmAbility() channel.ConfirmAbility { return channel.ConfirmByQuery } +func (c stubQueryFailChannel) Provider() channel.Provider { return channel.ProviderAlipay } +func (c stubQueryFailChannel) CreatePayment(context.Context, *channel.PayRequest) (*channel.PayCredential, error) { + return nil, nil +} +func (c stubQueryFailChannel) VerifyNotify(context.Context, *http.Request) (*channel.NotifyResult, error) { + return nil, nil +} +func (c stubQueryFailChannel) QueryOrder(context.Context, string) (*channel.OrderStatus, error) { + return nil, fmt.Errorf("所有支付宝账户查询均失败") +} +func (c stubQueryFailChannel) Listener() channel.EventListener { return nil } + +// TestCloseExpiredOrders_QueryFail_EntersPoolOnce 查单失败风暴抑制: +// 首次扫描失败 → 入人工池(close_uncertain)一次;后续扫描静默跳过,不再重复入池。 +func TestCloseExpiredOrders_QueryFail_EntersPoolOnce(t *testing.T) { + chName := "merchant.queryfail" + if _, ok := channel.Lookup(chName); !ok { + channel.Register(stubQueryFailChannel{name: chName}) + } + svc, st, _, _ := newTestService(t) + svc.SetCloseWindow(30) + ctx := context.Background() + now := time.Now() + + o := &model.Order{ + OutTradeNo: "M_QFAIL", GatewayNo: "GW_M_QFAIL", Channel: chName, + Pid: "2001", AmountMinor: 1000, Currency: "CNY", Subject: "测试", + Status: model.StatusWaitPay, CreatedAt: now.Add(-40 * time.Minute), + } + if err := st.CreateOrder(ctx, o); err != nil { + t.Fatalf("下单失败: %v", err) + } + + // 第一次扫描:查单失败 → 入池一次,订单不关 + closed, err := svc.CloseExpiredOrders(ctx, now) + if err != nil { + t.Fatalf("扫描 err: %v", err) + } + if closed != 0 { + t.Fatalf("查单失败不应关单, closed=%d", closed) + } + got, _ := st.GetOrder(ctx, "M_QFAIL") + if got.Status != model.StatusWaitPay { + t.Fatalf("查单失败订单保持 WAIT_PAY:%s", got.Status) + } + exc, err := st.GetExceptionByOutTradeNo(ctx, "M_QFAIL") + if err != nil || exc == nil { + t.Fatalf("首次查单失败应入人工池: err=%v exc=%v", err, exc) + } + if exc.Reason != model.ExReasonCloseUncertain { + t.Fatalf("异常原因应 close_uncertain,实际 %s", exc.Reason) + } + + // 第二次扫描:已有记录 → 静默跳过,不再重复入池(风暴抑制断言) + before, err := st.CountExceptions(ctx, "") + if err != nil { + t.Fatalf("计数失败: %v", err) + } + _, err = svc.CloseExpiredOrders(ctx, now) + if err != nil { + t.Fatalf("第二次扫描 err: %v", err) + } + after, err := st.CountExceptions(ctx, "") + if err != nil { + t.Fatalf("计数失败: %v", err) + } + if after != before { + t.Fatalf("第二次扫描不应再入池:before=%d after=%d", before, after) + } +} + +// stubPaymentChannel 返回有效凭据的通道桩,用于测试复用更新金额触发凭据刷新(RefreshPayment/SwitchChannel/ReviveOrder)。 +type stubPaymentChannel struct{} + +func (stubPaymentChannel) Name() string { return "personal.test" } +func (stubPaymentChannel) Type() channel.ChannelType { return channel.TypePersonal } +func (stubPaymentChannel) Kind() channel.ChannelKind { return channel.KindListener } +func (stubPaymentChannel) Modes() []channel.PayMode { return []channel.PayMode{channel.ModeQRCode} } +func (stubPaymentChannel) ConfirmAbility() channel.ConfirmAbility { return channel.ConfirmByManual } +func (stubPaymentChannel) Provider() channel.Provider { return channel.ProviderAlipay } +func (stubPaymentChannel) CreatePayment(context.Context, *channel.PayRequest) (*channel.PayCredential, error) { + return &channel.PayCredential{QRCode: "psn://test", AccountID: 1}, nil +} +func (stubPaymentChannel) VerifyNotify(context.Context, *http.Request) (*channel.NotifyResult, error) { + return nil, nil +} +func (stubPaymentChannel) QueryOrder(context.Context, string) (*channel.OrderStatus, error) { + return nil, ErrNotQueryable +} +func (stubPaymentChannel) Listener() channel.EventListener { return nil } + +// registerStubPayment 注册返回有效凭据的通道桩(幂等:已注册跳过) +func registerStubPayment(t *testing.T) { + t.Helper() + if _, ok := channel.Lookup("personal.test"); ok { + return + } + channel.Register(stubPaymentChannel{}) +} + +// createWaitPayOrderWithCred 创建带凭据的 WAIT_PAY 订单,并通过 GetOrder 返回带 ID 的最新对象(模拟真实调用链)。 +func createWaitPayOrderWithCred(t *testing.T, st *store.SQLiteStore, outTradeNo, ch string, amount int64) *model.Order { + t.Helper() + o := &model.Order{ + OutTradeNo: outTradeNo, + GatewayNo: "GW" + outTradeNo, + Channel: ch, + Pid: "2001", + AmountMinor: amount, + Currency: "CNY", + Subject: "测试", + Status: model.StatusWaitPay, + NotifyURL: "http://example.com/notify", + QrCode: "old://qr", + CreatedAt: time.Now(), + } + if err := st.CreateOrder(context.Background(), o); err != nil { + t.Fatalf("创建订单失败: %v", err) + } + latest, err := st.GetOrder(context.Background(), outTradeNo) + if err != nil { + t.Fatalf("读回订单失败: %v", err) + } + return latest +} + +// TestReuseOrder_AmountChanged_Refresh 同订单号复用传不同金额 → 金额不可变,拒单。 +// 回归:旧行为覆盖已存在订单金额(UpdateOrderAmount)+ 以新金额刷新凭据;该行为会导致 +// 回调按订单金额严校验时实付/订单不一致而永不入账,且订单号+金额是上游不可变主键, +// 同号改金额应由上游换新单号(买家级替换,见 CreateOrder 守卫)而非覆盖复用。 +func TestReuseOrder_AmountChanged_Refresh(t *testing.T) { + registerStubPayment(t) + svc, st, _, _ := newTestService(t) + o := createWaitPayOrderWithCred(t, st, "REUSE_AMT", "personal.test", 100) + + _, err := svc.reuseOrCreateError(context.Background(), o, "personal.test", 200) + if err == nil { + t.Fatal("同号不同金额应拒单(金额不可变)") + } + + got, _ := st.GetOrder(context.Background(), o.OutTradeNo) + if got.AmountMinor != 100 { + t.Fatalf("金额不可变:DB 订单金额 = %d, 期望保持 100(未被覆盖)", got.AmountMinor) + } + if got.QrCode != "old://qr" { + t.Fatalf("金额不可变:不应刷新凭据,%q", got.QrCode) + } + if got.Status != model.StatusWaitPay { + t.Fatalf("金额不可变:订单不应被改状态,status=%s", got.Status) + } + evs, _ := st.ListEvents(context.Background(), got.ID, 10) + for _, e := range evs { + if e.EventType == "amount_updated" { + t.Fatal("不应再记录 amount_updated 事件(金额不可变)") + } + } +} + +// TestReuseOrder_AmountSame_NoRefresh 复用订单传入相同金额 → 纯复用,不刷新凭据、不改金额。 +func TestReuseOrder_AmountSame_NoRefresh(t *testing.T) { + registerStubPayment(t) + svc, st, _, _ := newTestService(t) + o := createWaitPayOrderWithCred(t, st, "REUSE_SAME", "personal.test", 100) + + res, err := svc.reuseOrCreateError(context.Background(), o, "personal.test", 100) + if err != nil { + t.Fatalf("reuseOrCreateError err: %v", err) + } + if !res.Reused { + t.Fatal("应标记为复用") + } + if res.Order.AmountMinor != 100 { + t.Fatalf("同金额复用订单金额 = %d, 期望 100", res.Order.AmountMinor) + } + got, _ := st.GetOrder(context.Background(), o.OutTradeNo) + if got.QrCode != "old://qr" { + t.Fatalf("同金额复用不应刷新凭据:%q", got.QrCode) + } +} + +// TestReuseOrder_AmountChanged_ClosedRevive 已关单订单同号复用传新金额 → 金额不可变,拒单(复活前置金额校验)。 +func TestReuseOrder_AmountChanged_ClosedRevive(t *testing.T) { + registerStubPayment(t) + svc, st, _, _ := newTestService(t) + _ = createWaitPayOrderWithCred(t, st, "REUSE_CLOSED", "personal.test", 100) + if ok, err := svc.CloseOrder(context.Background(), "REUSE_CLOSED", "timeout"); err != nil || !ok { + t.Fatalf("关单失败: ok=%v err=%v", ok, err) + } + closed, _ := st.GetOrder(context.Background(), "REUSE_CLOSED") + + _, err := svc.reuseOrCreateError(context.Background(), closed, "personal.test", 300) + if err == nil { + t.Fatal("同号不同金额应拒单(即使已关单,金额仍不可变)") + } + got, _ := st.GetOrder(context.Background(), "REUSE_CLOSED") + if got.AmountMinor != 100 { + t.Fatalf("金额不可变:DB 金额 = %d, 期望保持 100", got.AmountMinor) + } + if got.Status != model.StatusClosed { + t.Fatalf("金额不可变:拒单后订单不应被复活,status=%s", got.Status) + } +} + +// TestReuseOrder_AmountChanged_SwitchChannel 复用传新金额且换支付方式 → 金额不可变,拒单(通道切换前金额校验)。 +func TestReuseOrder_AmountChanged_SwitchChannel(t *testing.T) { + registerStubPersonal(t) // 原通道 personal.alipay(返回 nil 凭据,仅作 from 侧) + registerStubPayment(t) // 目标通道 personal.test(返回有效凭据) + svc, st, _, _ := newTestService(t) + o := createWaitPayOrderWithCred(t, st, "REUSE_SWITCH", "personal.alipay", 100) + + _, err := svc.reuseOrCreateError(context.Background(), o, "personal.test", 400) + if err == nil { + t.Fatal("同号不同金额应拒单(即使要切换通道,金额仍不可变)") + } + got, _ := st.GetOrder(context.Background(), o.OutTradeNo) + if got.Channel != "personal.alipay" { + t.Fatalf("金额不可变:拒单后通道不应被切换,channel=%s", got.Channel) + } + if got.AmountMinor != 100 { + t.Fatalf("金额不可变:DB 金额 = %d, 期望保持 100", got.AmountMinor) + } +} + +// TestCreateOrder_BuyerPendingNewTrade_ReplacesOld 买家级串行:同一 buyer_key 已有 WAIT_BUYER_PAY +// 待支付订单(OLD),又以新 out_trade_no 提交新订单(NEW) 时,应废弃旧单、返回新单。 +// Given: OLD 为 WAIT_BUYER_PAY @10.00 元、buyer_key=buyer-replace、通道 personal.test 已带凭据; +// +// NEW 为同 buyer_key、不同 out_trade_no、金额 1.00 元。 +// +// When: Service.CreateOrder(NEW) +// Then: 返回订单用 NEW(out_trade_no=NEW)、金额 100 分;OLD 已被关闭;NEW 存在于 DB。 +// 回归:当前 CreateOrder 买家守卫(service.go:205-227)错误地复用 OLD 并覆盖其金额,返回旧单。 +func TestCreateOrder_BuyerPendingNewTrade_ReplacesOld(t *testing.T) { + registerStubPayment(t) // personal.test 返回有效凭据 + svc, st, _, _ := newTestService(t) + ctx := context.Background() + + // Given: OLD_ORDER WAIT_BUYER_PAY @10.00元,同 buyer_key,已带凭据 + old := createWaitPayOrderWithCred(t, st, "OLD_REPLACE", "personal.test", 1000) + if ok, err := st.UpdateOrderStatus(ctx, old.OutTradeNo, model.StatusWaitPay, model.StatusWaitPay, func(upd *model.Order) { + upd.BuyerKey = "buyer-replace" + }); err != nil || !ok { + t.Fatalf("设置 OLD buyer_key 失败: ok=%v err=%v", ok, err) + } + old, _ = st.GetOrder(ctx, old.OutTradeNo) + if old.BuyerKey != "buyer-replace" { + t.Fatalf("前置条件:OLD buyer_key 未生效: %q", old.BuyerKey) + } + + // When: 以新单号提交 NEW_ORDER @1.00元 + res, err := svc.CreateOrder(ctx, &CreateRequest{ + OutTradeNo: "NEW_REPLACE", + Type: "personal.test", + Name: "新单", + Money: "1.00", + NotifyURL: "http://example.com/notify", + BuyerKey: "buyer-replace", + }) + if err != nil { + t.Fatalf("CreateOrder err: %v", err) + } + + // Then: 返回订单为新单,金额 100 分 + if res == nil || res.Order == nil { + t.Fatal("CreateOrder 返回空结果") + } + if res.Order.OutTradeNo != "NEW_REPLACE" { + t.Fatalf("返回订单 = %s, 期望新单 NEW_REPLACE(当前错误复用了旧单)", res.Order.OutTradeNo) + } + if res.Order.AmountMinor != 100 { + t.Fatalf("返回订单金额 = %d, 期望 100", res.Order.AmountMinor) + } + + // Then: OLD 已被关闭 + gotOld, _ := st.GetOrder(ctx, "OLD_REPLACE") + if gotOld == nil { + t.Fatal("OLD_REPLACE 应存在") + } + if gotOld.Status != model.StatusClosed { + t.Fatalf("OLD 状态 = %s, 期望 CLOSED(旧单应被关闭,而非继续复用)", gotOld.Status) + } + + // Then: NEW 存在于 DB + gotNew, err := st.GetOrder(ctx, "NEW_REPLACE") + if err != nil || gotNew == nil { + t.Fatalf("NEW_REPLACE 应存在于 DB, err=%v", err) + } +} + +// TestCreateOrder_BuyerReplacement_InheritsSessionExpiry 换金额不能重置支付会话总截止时间。 +func TestCreateOrder_BuyerReplacement_InheritsSessionExpiry(t *testing.T) { + registerStubPayment(t) + svc, st, _, _ := newTestService(t) + ctx := context.Background() + svc.SetCloseWindow(30) + expires := time.Now().Add(7 * time.Minute).Round(0) + old := createWaitPayOrderWithCred(t, st, "OLD_SESSION", "personal.test", 1000) + if ok, err := st.UpdateOrderStatus(ctx, old.OutTradeNo, model.StatusWaitPay, model.StatusWaitPay, func(upd *model.Order) { + upd.BuyerKey = "buyer-session" + upd.SessionExpiresAt = expires + }); err != nil || !ok { + t.Fatalf("设置会话截止时间失败: ok=%v err=%v", ok, err) + } + + res, err := svc.CreateOrder(ctx, &CreateRequest{ + OutTradeNo: "NEW_SESSION", + Type: "personal.test", + Name: "新单", + Money: "1.00", + NotifyURL: "http://example.com/notify", + BuyerKey: "buyer-session", + }) + if err != nil { + t.Fatalf("替换下单失败: %v", err) + } + got, _ := st.GetOrder(ctx, res.Order.OutTradeNo) + if !got.SessionExpiresAt.Equal(expires) { + t.Fatalf("新订单会话截止时间 = %v, 期望继承 %v", got.SessionExpiresAt, expires) + } + if !got.CreatedAt.Before(got.SessionExpiresAt) { + t.Fatalf("新订单创建时间必须早于会话截止时间: created=%v expires=%v", got.CreatedAt, got.SessionExpiresAt) + } +} + +// TestCreateOrder_BuyerReplacement_ExpiredSessionRejects 换金额不能复活已过期支付会话。 +func TestCreateOrder_BuyerReplacement_ExpiredSessionRejects(t *testing.T) { + registerStubPayment(t) + svc, st, _, _ := newTestService(t) + ctx := context.Background() + old := createWaitPayOrderWithCred(t, st, "OLD_EXPIRED_SESSION", "personal.test", 1000) + expires := time.Now().Add(-time.Minute) + if ok, err := st.UpdateOrderStatus(ctx, old.OutTradeNo, model.StatusWaitPay, model.StatusWaitPay, func(upd *model.Order) { + upd.BuyerKey = "buyer-expired-session" + upd.SessionExpiresAt = expires + }); err != nil || !ok { + t.Fatalf("设置过期会话失败: ok=%v err=%v", ok, err) + } + + _, err := svc.CreateOrder(ctx, &CreateRequest{ + OutTradeNo: "NEW_EXPIRED_SESSION", + Type: "personal.test", + Name: "新单", + Money: "1.00", + NotifyURL: "http://example.com/notify", + BuyerKey: "buyer-expired-session", + }) + if err == nil { + t.Fatal("过期支付会话不应继续替换创建新 WAIT_PAY") + } + if got, _ := st.GetOrder(ctx, "NEW_EXPIRED_SESSION"); got != nil { + t.Fatal("过期会话拒绝后不应创建新订单") + } +} + +// TestBuildNotifyPayload_UsesLatestNotifyURL 死信重发核心:BuildNotifyPayload 反映订单最新 NotifyURL。 +// 回归:死信重发此前固化任务创建时的旧地址 → 一直回调不了;此测试锁住「改对地址后重建 payload 用最新值」。 +func TestBuildNotifyPayload_UsesLatestNotifyURL(t *testing.T) { + svc, _, _, _ := newTestService(t) + o := &model.Order{ + GatewayNo: "GW123", + OutTradeNo: "OTN123", + Channel: "alipay", + Subject: "测试", + AmountMinor: 100, + NotifyURL: "http://old.example/cb", + } + var p map[string]string + parse := func(payload string) map[string]string { + m := map[string]string{} + if err := json.Unmarshal([]byte(payload), &m); err != nil { + t.Fatalf("payload 解析失败: %v", err) + } + return m + } + + p = parse(svc.BuildNotifyPayload(o)) + if p["notify_url"] != "http://old.example/cb" { + t.Fatalf("notify_url = %q, 期望旧地址", p["notify_url"]) + } + if p["sign"] == "" { + t.Fatal("payload 应含 sign") + } + if p["money"] != "1.00" { + t.Fatalf("money = %q, 期望 1.00", p["money"]) + } + + // 订单回调地址改对后,重建 payload 应反映最新地址 + o.NotifyURL = "http://new.example/cb" + p = parse(svc.BuildNotifyPayload(o)) + if p["notify_url"] != "http://new.example/cb" { + t.Fatalf("notify_url = %q, 期望最新地址 http://new.example/cb", p["notify_url"]) + } +} + +// TestCreateOrder_SameOrderNumber_DifferentAmount_Rejected 同订单号但金额不同 → 金额不可变,拒单。 +// Given: 已存在订单 SAME_NO @10.00元。 +// When: 以同订单号 + 1.00 元重提。 +// Then: 返回错误;DB 金额保持 1000;订单仍 WAIT_PAY;无 amount_updated 事件、无凭据刷新。 +func TestCreateOrder_SameOrderNumber_DifferentAmount_Rejected(t *testing.T) { + registerStubPayment(t) + svc, st, _, _ := newTestService(t) + ctx := context.Background() + createWaitPayOrderWithCred(t, st, "SAME_AMT", "personal.test", 1000) + + _, err := svc.CreateOrder(ctx, &CreateRequest{ + OutTradeNo: "SAME_AMT", + Type: "personal.test", + Name: "重提", + Money: "1.00", + NotifyURL: "http://example.com/notify", + BuyerKey: "buyer-same", + }) + if err == nil { + t.Fatal("同号不同金额应拒单(金额不可变)") + } + + got, _ := st.GetOrder(ctx, "SAME_AMT") + if got.AmountMinor != 1000 { + t.Fatalf("金额不可变:DB 金额 = %d, 期望保持 1000", got.AmountMinor) + } + if got.QrCode != "old://qr" { + t.Fatalf("金额不可变:不应刷新凭据,%q", got.QrCode) + } + if got.Status != model.StatusWaitPay { + t.Fatalf("金额不可变:订单应保持 WAIT_PAY,status=%s", got.Status) + } + evs, _ := st.ListEvents(ctx, got.ID, 10) + for _, e := range evs { + if e.EventType == "amount_updated" || e.EventType == "replaced" { + t.Fatalf("同号不同金额不应产生 %s 事件", e.EventType) + } + } +} + +// TestCreateOrder_SameOrderNumber_SameAmount_Reuse 同订单号且金额相同 → 幂等复用,不新建。 +// Given: 已存在订单 SAME_OK @10.00元(带凭据)。 +// When: 以同订单号 + 同金额 10.00 元重提。 +// Then: 返回 Reused=true,返回原订单(out_trade_no 相同、金额不变、凭据不变)。 +func TestCreateOrder_SameOrderNumber_SameAmount_Reuse(t *testing.T) { + registerStubPayment(t) + svc, st, _, _ := newTestService(t) + ctx := context.Background() + old := createWaitPayOrderWithCred(t, st, "SAME_OK", "personal.test", 1000) + + res, err := svc.CreateOrder(ctx, &CreateRequest{ + OutTradeNo: "SAME_OK", + Type: "personal.test", + Name: "重提", + Money: "10.00", + NotifyURL: "http://example.com/notify", + BuyerKey: "buyer-same-ok", + }) + if err != nil { + t.Fatalf("CreateOrder err: %v", err) + } + if res == nil || !res.Reused { + t.Fatalf("同号同金额应复用 Reused=true, res=%+v", res) + } + if res.Order.OutTradeNo != "SAME_OK" || res.Order.AmountMinor != 1000 { + t.Fatalf("复用应返回原单 SAME_OK@1000,got %s@%d", res.Order.OutTradeNo, res.Order.AmountMinor) + } + got, _ := st.GetOrder(ctx, "SAME_OK") + if got.AmountMinor != 1000 || got.QrCode != "old://qr" { + t.Fatalf("同号同金额复用不应改金额/凭据:amount=%d qr=%q", got.AmountMinor, got.QrCode) + } + if got.ID != old.ID { + t.Fatalf("同号同金额应复用同一订单记录,id=%d vs old=%d", got.ID, old.ID) + } +} + +// TestCreateOrder_BuyerReplacement_EventAndCleanup 买家替换旧单 → replaced 事件恰一条、备注码释放一次、池移除一次。 +// Given: OLD_REPL2 WAIT_PAY @10.00元、buyer_key、带 NoteCode。 +// When: CreateOrder(NEW_REPL2 @1.00元)。 +// Then: OLD 被关闭;写且仅写一条 replaced 事件;备注码释放一次;池移除一次;NEW 以新金额/新单号持久化。 +func TestCreateOrder_BuyerReplacement_EventAndCleanup(t *testing.T) { + registerStubPayment(t) + svc, st, _, _ := newTestService(t) + ctx := context.Background() + + released := 0 + removed := 0 + svc.SetNoteCodeReleaser(func(ch, code string) { released++ }) + svc.SetWaitPayHooks(&WaitPayHooks{ + Remove: func(outTradeNo string) { removed++ }, + }) + + old := createWaitPayOrderWithCred(t, st, "OLD_REPL2", "personal.test", 1000) + if ok, err := st.UpdateOrderStatus(ctx, old.OutTradeNo, model.StatusWaitPay, model.StatusWaitPay, func(upd *model.Order) { + upd.BuyerKey = "buyer-repl2" + upd.NoteCode = "NC-REPL2" + upd.AccountID = 1 + }); err != nil || !ok { + t.Fatalf("设置 OLD buyer_key/note_code 失败: %v %v", ok, err) + } + + res, err := svc.CreateOrder(ctx, &CreateRequest{ + OutTradeNo: "NEW_REPL2", + Type: "personal.test", + Name: "新单", + Money: "1.00", + NotifyURL: "http://example.com/notify", + BuyerKey: "buyer-repl2", + }) + if err != nil { + t.Fatalf("CreateOrder err: %v", err) + } + if res.Order.OutTradeNo != "NEW_REPL2" || res.Order.AmountMinor != 100 { + t.Fatalf("返回应为新单 NEW_REPL2@100,got %s@%d", res.Order.OutTradeNo, res.Order.AmountMinor) + } + + gotOld, _ := st.GetOrder(ctx, "OLD_REPL2") + if gotOld.Status != model.StatusClosed { + t.Fatalf("OLD 应 CLOSED,got %s", gotOld.Status) + } + evs, _ := st.ListEvents(ctx, gotOld.ID, 10) + replaced := 0 + for _, e := range evs { + if e.EventType == "replaced" { + replaced++ + if !json.Valid([]byte(e.RawPayload)) { + t.Fatalf("replaced 事件 payload 非法 JSON: %s", e.RawPayload) + } + } + } + if replaced != 1 { + t.Fatalf("replaced 事件条数 = %d, 期望恰 1", replaced) + } + if released != 1 { + t.Fatalf("备注码应释放恰 1 次,got %d", released) + } + if removed != 1 { + t.Fatalf("WAIT_PAY 池应移除恰 1 次,got %d", removed) + } + gotNew, _ := st.GetOrder(ctx, "NEW_REPL2") + if gotNew == nil || gotNew.AmountMinor != 100 { + t.Fatalf("NEW 应以新金额持久化:%+v", gotNew) + } +} + +// TestCreateOrder_BuyerReplacement_CallbackIdentity 替换后新单回调 payload 指向新单号/新金额。 +// When: 买家替换后创建 NEW。 +// Then: NEW 的 BuildNotifyPayload 的 out_trade_no/money 反映 NEW,而非 OLD。 +func TestCreateOrder_BuyerReplacement_CallbackIdentity(t *testing.T) { + registerStubPayment(t) + svc, st, _, _ := newTestService(t) + ctx := context.Background() + + old := createWaitPayOrderWithCred(t, st, "OLD_CB", "personal.test", 1000) + if ok, err := st.UpdateOrderStatus(ctx, old.OutTradeNo, model.StatusWaitPay, model.StatusWaitPay, func(upd *model.Order) { + upd.BuyerKey = "buyer-cb" + }); err != nil || !ok { + t.Fatalf("设置 OLD buyer_key 失败: %v %v", ok, err) + } + + res, err := svc.CreateOrder(ctx, &CreateRequest{ + OutTradeNo: "NEW_CB", + Type: "personal.test", + Name: "新单", + Money: "0.05", + NotifyURL: "http://example.com/notify", + BuyerKey: "buyer-cb", + }) + if err != nil { + t.Fatalf("CreateOrder err: %v", err) + } + + var params map[string]string + if err := json.Unmarshal([]byte(svc.BuildNotifyPayload(res.Order)), ¶ms); err != nil { + t.Fatalf("payload 解析失败: %v", err) + } + if params["out_trade_no"] != "NEW_CB" { + t.Fatalf("回调 out_trade_no = %q, 期望 NEW_CB(不能引用 OLD)", params["out_trade_no"]) + } + if params["money"] != "0.05" { + t.Fatalf("回调 money = %q, 期望 0.05(新单金额)", params["money"]) + } +} + +// TestReplacePendingOrder_AlreadyClosed_NoEvent OLD 已被并发替换关闭 → 替换幂等:不重复写 replaced 事件,仍可继续创建 NEW。 +func TestReplacePendingOrder_AlreadyClosed_NoEvent(t *testing.T) { + registerStubPayment(t) + svc, st, _, _ := newTestService(t) + ctx := context.Background() + old := createWaitPayOrderWithCred(t, st, "OLD_CLOSED", "personal.test", 1000) + + if ok, err := svc.CloseOrder(ctx, old.OutTradeNo, "timeout"); err != nil || !ok { + t.Fatalf("预置 OLD 为 CLOSED 失败: %v %v", ok, err) + } + + _, create, err := svc.replacePendingOrder(ctx, old, "NEW_CLOSED", 100) + if err != nil { + t.Fatalf("replacePendingOrder err: %v", err) + } + if !create { + t.Fatal("OLD 已被关闭(非结算)时仍应继续创建 NEW") + } + evs, _ := st.ListEvents(ctx, old.ID, 10) + for _, e := range evs { + if e.EventType == "replaced" { + t.Fatal("OLD 已被并发替换关闭时不应再写 replaced 事件(幂等)") + } + } +} + +// TestReplacePendingOrder_ConcurrentSettlement_NoProceed OLD 已被并发结算(SUCCESS) → 替换 CAS 失败,返回 proceed=false,禁止创建新单。 +func TestReplacePendingOrder_ConcurrentSettlement_NoProceed(t *testing.T) { + registerStubPayment(t) + svc, st, _, _ := newTestService(t) + ctx := context.Background() + old := createWaitPayOrderWithCred(t, st, "OLD_SETTLED", "personal.test", 1000) + + if ok, err := svc.SettlePayment(ctx, &channel.NotifyResult{OutTradeNo: old.OutTradeNo, ChannelTxID: "TX-SETTLED", AmountMinor: 1000}); err != nil || !ok { + t.Fatalf("预置 OLD 结算失败: %v %v", ok, err) + } + + _, create, err := svc.replacePendingOrder(ctx, old, "NEW_SETTLED", 100) + if err != nil { + t.Fatalf("replacePendingOrder err: %v", err) + } + if create { + t.Fatal("OLD 已被并发结算胜出时不得创建新单(防重复收款)") + } + // CAS 失败 → 订单保持 SUCCESS,不写 replaced,不移除池/释放码 + got, _ := st.GetOrder(ctx, old.OutTradeNo) + if got.Status != model.StatusSuccess { + t.Fatalf("OLD 应保持 SUCCESS,got %s", got.Status) + } + evs, _ := st.ListEvents(ctx, old.ID, 10) + for _, e := range evs { + if e.EventType == "replaced" { + t.Fatal("结算胜出后不应写 replaced 事件") + } + } +} + +// TestCreateOrder_BuyerReplacement_AfterSettleAllowsNew OLD 已被结算(SUCCESS)后,买家可正常下新单。 +// Given: OLD 已结算入账(不再是 WAIT_PAY,不占用买家串行锁)。 +// When: CreateOrder 以同 buyer_key + 新单号提交。 +// Then: 成功创建 NEW(已支付旧单不阻塞新单;替换仅在 OLD 仍 WAIT_PAY 的竞态窗口内通过 CAS 拒绝,见单测)。 +func TestCreateOrder_BuyerReplacement_AfterSettleAllowsNew(t *testing.T) { + registerStubPayment(t) + svc, st, _, _ := newTestService(t) + ctx := context.Background() + + old := createWaitPayOrderWithCred(t, st, "OLD_SETTLED3", "personal.test", 1000) + if ok, err := st.UpdateOrderStatus(ctx, old.OutTradeNo, model.StatusWaitPay, model.StatusWaitPay, func(upd *model.Order) { + upd.BuyerKey = "buyer-settled3" + }); err != nil || !ok { + t.Fatalf("设置 buyer_key 失败: %v %v", ok, err) + } + if ok, err := svc.SettlePayment(ctx, &channel.NotifyResult{OutTradeNo: old.OutTradeNo, ChannelTxID: "TX-S3", AmountMinor: 1000}); err != nil || !ok { + t.Fatalf("预置结算失败: %v %v", ok, err) + } + + res, err := svc.CreateOrder(ctx, &CreateRequest{ + OutTradeNo: "NEW_SETTLED3", + Type: "personal.test", + Name: "新单", + Money: "1.00", + NotifyURL: "http://example.com/notify", + BuyerKey: "buyer-settled3", + }) + if err != nil { + t.Fatalf("CreateOrder err(已支付旧单不阻塞新单): %v", err) + } + if res.Order.OutTradeNo != "NEW_SETTLED3" { + t.Fatalf("应创建新单 NEW_SETTLED3,got %s", res.Order.OutTradeNo) + } + got, _ := st.GetOrder(ctx, "NEW_SETTLED3") + if got == nil { + t.Fatal("NEW_SETTLED3 应存在于 DB") + } +} + +// TestReplacePendingOrder_Concurrent_Goroutine 真实 SQLite 并发:替代替换 与 结算 并行, +// 断言不变量:绝不双写 replaced、绝不双入账;OLD 终态必为 SUCCESS 或 CLOSED 之一。 +func TestReplacePendingOrder_Concurrent_Goroutine(t *testing.T) { + registerStubPayment(t) + svc, st, _, _ := newTestService(t) + ctx := context.Background() + old := createWaitPayOrderWithCred(t, st, "OLD_RACE", "personal.test", 1000) + + start := make(chan struct{}) + done := make(chan struct{}, 2) + + go func() { + <-start + svc.SettlePayment(ctx, &channel.NotifyResult{OutTradeNo: old.OutTradeNo, ChannelTxID: "TX-RACE", AmountMinor: 1000}) + done <- struct{}{} + }() + go func() { + <-start + svc.replacePendingOrder(ctx, old, "NEW_RACE", 100) + done <- struct{}{} + }() + close(start) + <-done + <-done + + got, _ := st.GetOrder(ctx, old.OutTradeNo) + if got.Status != model.StatusSuccess && got.Status != model.StatusClosed { + t.Fatalf("OLD 终态应为 SUCCESS 或 CLOSED,got %s", got.Status) + } + evs, _ := st.ListEvents(ctx, old.ID, 50) + replaced := 0 + paid := 0 + for _, e := range evs { + switch e.EventType { + case "replaced": + replaced++ + case "paid": + paid++ + } + } + if replaced > 1 { + t.Fatalf("replaced 事件最多写 1 条,got %d", replaced) + } + if paid > 1 { + t.Fatalf("paid 事件最多写 1 条(不重复入账),got %d", paid) + } + if got.Status == model.StatusSuccess && replaced != 0 { + t.Fatalf("结算胜出时不应写 replaced,got %d", replaced) + } +} + +// replaceLoserCreates 模拟并发替换者已原子关单 OLD(写 replaced 事件),然后把当前调用当作 CAS 落败者。 +// OLD 代码下该场景 CAS 落败后会继续放行创建 NEW(proceed=true),酿成同买家多笔 WAIT_PAY; +// 修复后落败者必须 create=false(复用获胜 NEW 或不创建)。 +func TestReplacePendingOrder_LoserNeverCreates(t *testing.T) { + registerStubPayment(t) + svc, st, _, _ := newTestService(t) + ctx := context.Background() + + old := createWaitPayOrderWithCred(t, st, "OLD_LOSER", "personal.test", 1000) + if ok, err := st.UpdateOrderStatus(ctx, old.OutTradeNo, model.StatusWaitPay, model.StatusWaitPay, func(u *model.Order) { + u.BuyerKey = "buyer-loser" + }); err != nil || !ok { + t.Fatalf("设置 buyer_key 失败: %v %v", ok, err) + } + old, _ = st.GetOrder(ctx, old.OutTradeNo) + + // When: 并发替换者已先原子关闭 OLD 并写入 replaced 事件(模拟 A 在 CAS 中胜出)。 + if ok, err := svc.store.ReplaceOrder(ctx, old.OutTradeNo, old.ID, old.AmountMinor, `{"new_out_trade_no":"WINNER_LOSER"}`); err != nil || !ok { + t.Fatalf("模拟并发替换者关单失败: %v %v", ok, err) + } + + // When: 当前调用作为 CAS 落败者尝试替换同一个 OLD。 + _, create, err := svc.replacePendingOrder(ctx, old, "NEW_LOSER", 200) + if err == nil { + t.Fatal("OLD 已被并发替换者关闭且获胜 NEW 未就绪时,落败者应返回可重试冲突错误") + } + if create { + t.Fatal("CAS 落败者(OLD 已被并发替换者关闭)绝不能再创建自己的 NEW——违反单活跃 WAIT_PAY 不变量") + } +} + +// TestReplacePendingOrder_LoserReusesWinner OLD 已被并发替换者关闭且获胜 NEW 已可见时,落败者复用获胜 NEW。 +func TestReplacePendingOrder_LoserReusesWinner(t *testing.T) { + registerStubPayment(t) + svc, st, _, _ := newTestService(t) + ctx := context.Background() + + old := createWaitPayOrderWithCred(t, st, "OLD_REUSE", "personal.test", 1000) + if ok, err := st.UpdateOrderStatus(ctx, old.OutTradeNo, model.StatusWaitPay, model.StatusWaitPay, func(u *model.Order) { + u.BuyerKey = "buyer-reuse" + }); err != nil || !ok { + t.Fatalf("设置 buyer_key 失败: %v %v", ok, err) + } + old, _ = st.GetOrder(ctx, old.OutTradeNo) + + // 并发替换者已关 OLD 并已创建获胜 NEW(同买家环境 WAIT_PAY)。 + if ok, err := svc.store.ReplaceOrder(ctx, old.OutTradeNo, old.ID, old.AmountMinor, `{"new_out_trade_no":"WINNER_REUSE"}`); err != nil || !ok { + t.Fatalf("模拟并发替换者关单失败: %v %v", ok, err) + } + winner := createWaitPayOrderWithCred(t, st, "WINNER_REUSE", "personal.test", 300) + if ok, err := st.UpdateOrderStatus(ctx, winner.OutTradeNo, model.StatusWaitPay, model.StatusWaitPay, func(u *model.Order) { + u.BuyerKey = "buyer-reuse" + }); err != nil || !ok { + t.Fatalf("设置获胜 NEW buyer_key 失败: %v %v", ok, err) + } + + reuse, create, err := svc.replacePendingOrder(ctx, old, "NEW_REUSE", 200) + if err != nil { + t.Fatalf("落败者应复用获胜 NEW 而非报错,got err: %v", err) + } + if create { + t.Fatal("落败者不得创建自己的 NEW") + } + if reuse == nil || reuse.OutTradeNo != "WINNER_REUSE" { + t.Fatalf("落败者应复用获胜 NEW WINNER_REUSE,got %+v", reuse) + } +} + +// TestReplacePendingOrder_ConcurrentLoser_Race 真实 SQLite 并发:两个请求同时替换同一 OLD。 +// 断言:至多一方 create=true(绝不出现两方都放行创建 → 同买家双 WAIT_PAY)。 +func TestReplacePendingOrder_ConcurrentLoser_Race(t *testing.T) { + registerStubPayment(t) + svc, st, _, _ := newTestService(t) + ctx := context.Background() + + old := createWaitPayOrderWithCred(t, st, "OLD_RACE2", "personal.test", 1000) + if ok, err := st.UpdateOrderStatus(ctx, old.OutTradeNo, model.StatusWaitPay, model.StatusWaitPay, func(u *model.Order) { + u.BuyerKey = "buyer-race2" + }); err != nil || !ok { + t.Fatalf("设置 buyer_key 失败: %v %v", ok, err) + } + old, _ = st.GetOrder(ctx, old.OutTradeNo) + + start := make(chan struct{}) + done := make(chan bool, 2) + + for i := 0; i < 2; i++ { + go func() { + <-start + _, create, _ := svc.replacePendingOrder(ctx, old, "NEW_RACE2", int64(100*(i+1))) + done <- create + }() + } + close(start) + creates := 0 + for i := 0; i < 2; i++ { + if <-done { + creates++ + } + } + if creates != 1 { + t.Fatalf("并发替换同一 OLD 时恰一方可创建 NEW,got %d(>1 产生同买家多笔 WAIT_PAY)", creates) + } +} + +// TestCreateOrder_BuyerSequentialReplacement_NoGhost 连续多次改价(不同订单号): +// 每次替换关闭旧单、只保留一笔活跃 WAIT_PAY;历史订单为 CLOSED(允许存在),绝无活跃幽灵 WAIT_PAY。 +func TestCreateOrder_BuyerSequentialReplacement_NoGhost(t *testing.T) { + registerStubPayment(t) + svc, st, _, _ := newTestService(t) + ctx := context.Background() + + old := createWaitPayOrderWithCred(t, st, "OLD_SEQ", "personal.test", 1000) + if ok, err := st.UpdateOrderStatus(ctx, old.OutTradeNo, model.StatusWaitPay, model.StatusWaitPay, func(u *model.Order) { + u.BuyerKey = "buyer-seq" + }); err != nil || !ok { + t.Fatalf("设置 buyer_key 失败: %v %v", ok, err) + } + + order := func(no, money string) { + res, err := svc.CreateOrder(ctx, &CreateRequest{ + OutTradeNo: no, Type: "personal.test", Name: "新单", Money: money, + NotifyURL: "http://example.com/notify", BuyerKey: "buyer-seq", + }) + if err != nil { + t.Fatalf("CreateOrder(%s) err: %v", no, err) + } + if res != nil && res.Order != nil && res.Order.OutTradeNo != no { + t.Fatalf("顺序改价应生成新单 %s,got %s", no, res.Order.OutTradeNo) + } + } + order("NEW_SEQ_1", "1.00") + order("NEW_SEQ_2", "2.00") + order("NEW_SEQ_3", "3.00") + + // Then: 恰一笔活跃 WAIT_PAY(最新 NEW_SEQ_3),其余均为 CLOSED。 + pend, err := st.ListWaitPayByBuyer(ctx, "buyer-seq") + if err != nil { + t.Fatalf("ListWaitPayByBuyer err: %v", err) + } + if len(pend) != 1 { + t.Fatalf("连续改价后应仅剩 1 笔活跃 WAIT_PAY,got %d(存在活跃幽灵)", len(pend)) + } + if pend[0].OutTradeNo != "NEW_SEQ_3" { + t.Fatalf("活跃 WAIT_PAY 应为最新 NEW_SEQ_3,got %s", pend[0].OutTradeNo) + } + for _, no := range []string{"OLD_SEQ", "NEW_SEQ_1", "NEW_SEQ_2"} { + got, _ := st.GetOrder(ctx, no) + if got == nil || got.Status != model.StatusClosed { + t.Fatalf("历史订单 %s 应已 CLOSED(非活跃幽灵),got %v", no, got) + } + } +} diff --git a/internal/personal/model.go b/internal/personal/model.go new file mode 100755 index 0000000..5932596 --- /dev/null +++ b/internal/personal/model.go @@ -0,0 +1,191 @@ +// Package personal 个人免签通道核心:备注码分配、套码防劫持、匹配引擎 +// +// 设计文档:docs/MIANQIAN.md +// 核心思路:个人收款码 + 系统生成备注码 + 账本读取(adb)→ 服务端精确匹配入账。 +// 所有资金写入只走 order.Service 幂等入口(SettlePayment),本包不做任何直接状态修改。 +package personal + +import ( + "encoding/json" + "time" +) + +// DeviceStatus 设备状态(由 DeviceWatcher 按 adb 可达性维护) +type DeviceStatus string + +const ( + DeviceOnline DeviceStatus = "online" // 在线(adb 可达) + DeviceOffline DeviceStatus = "offline" // 离线(adb 不可达) + DeviceFrozen DeviceStatus = "frozen" // 手动冻结(不可接新单) +) + +// ControlState 控制面状态(服务端 adb 通路的可达性) +// - usb : USB 直连或 adb over WiFi 正常(control_state=usb) +// - tunnel_only : 仅报文面可达,adb 不通 +// +// 2026-08-29 重构:监听 App 弃用,relay(CF wss 反代隧道)状态已移除。 +const ( + ControlStateUSB = "usb" + ControlStateTunnelOnly = "tunnel_only" +) + +// Device 真机设备(personal.db personal_devices 表) +// 每个设备绑定一个收款账户(account_id),承载账本读取 +type Device struct { + ID int64 `json:"id"` + DeviceID string `json:"device_id"` // 客户端 UUID(首次启动生成) + Name string `json:"name"` // 备注名(如"卧室安卓机") + AccountID int64 `json:"account_id"` + Provider string `json:"provider"` // alipay / wechat + Secret string `json:"-"` // 通信密钥(仅内存,加密存储) + Status DeviceStatus `json:"status"` + LastPush time.Time `json:"last_push"` + IP string `json:"ip"` + // adb 连接信息(web 可视化操作设备用) + // AdbHost 为 USB 直连设备 serial(如 "4f2bdc30",无端口)或 host(如 "127.0.0.1") + AdbHost string `json:"adb_host,omitempty"` // USB serial 或 host(首选通道) + AdbPort int `json:"adb_port,omitempty"` // 如 5555;0 = USB 直连(无端口) + // WiFi 备用通道(USB 断线时自动切换;与 AdbHost 同网段) + AdbWifiHost string `json:"adb_wifi_host,omitempty"` // 如 192.168.1.119 + AdbWifiPort int `json:"adb_wifi_port,omitempty"` // 如 5555;0 = 未配置 + // 引导向导完成度(JSON 数组字符串,与 DeviceEncrypted.SetupProgress 同语义;"" 视为 "[]") + SetupProgress string `json:"setup_progress,omitempty"` + // TsIP 设备组网 IP(Tailscale 100.x.x.x,未上线为空)。P2P 控制面恢复 adb 时使用。 + TsIP string `json:"ts_ip,omitempty"` + // AdbListen adbd TCP 监听是否就绪(adb 探测,0/1) + AdbListen bool `json:"adb_listen,omitempty"` + // ControlState 控制面状态:usb / tunnel_only + ControlState string `json:"control_state,omitempty"` + CreatedAt time.Time `json:"created_at"` +} + +// DeviceEncrypted personal.db 存储版本(secret 加密) +type DeviceEncrypted struct { + ID int64 `json:"id"` + DeviceID string `json:"device_id"` + Name string `json:"name"` + AccountID int64 `json:"account_id"` + Provider string `json:"provider"` + SecretEnc string `json:"secret_enc"` + Status string `json:"status"` + LastPush string `json:"last_push"` + IP string `json:"ip"` + AdbHost string `json:"adb_host,omitempty"` + AdbPort int `json:"adb_port,omitempty"` + AdbWifiHost string `json:"adb_wifi_host,omitempty"` + AdbWifiPort int `json:"adb_wifi_port,omitempty"` + // SetupProgress 引导向导完成度(JSON 数组字符串,元素取自 SetupStep* 常量)。 + // 空数组或 "[]" 表示新建未配置;含全部 4 步表示接入完成可参与接单。 + SetupProgress string `json:"setup_progress,omitempty"` + // TsIP 设备组网 IP(Tailscale 100.x.x.x,未上线为空)。P2P 控制面恢复 adb 时使用。 + TsIP string `json:"ts_ip,omitempty"` + // AdbListen adbd TCP 监听是否就绪(adb 探测,0/1) + AdbListen int `json:"adb_listen,omitempty"` + // ControlState 控制面状态:usb / tunnel_only + ControlState string `json:"control_state,omitempty"` + CreatedAt string `json:"created_at"` +} + +// 引导向导完成的阶段(SetupProgress 数组元素值)。改这些常量需同步改 wizard 接口与前端。 +// 2026-08-30 重构:监听端 APP 弃用,向导收敛为 2 步(usb_connected → bound), +// 设备上线由 DeviceWatcher 按 adb 可达性驱动,不再有 apk_installed/config_pushed/heartbeat_connected。 +const ( + SetupStepUSBConnected = "usb_connected" // 第 1 步:USB 真机连上,adb devices state=device + SetupStepBound = "bound" // 第 2 步:已绑定收款账户(personal_devices 记录建好) +) + +// Push 收款事件(personal.db personal_pushes 表,审计用) +type Push struct { + ID int64 `json:"id"` + DeviceID string `json:"device_id"` + Provider string `json:"provider"` + AmountMinor int64 `json:"amount_minor"` + Note string `json:"note"` // 备注码(可空) + RawHash string `json:"raw_hash"` + MatchedOrder string `json:"matched_order"` // 匹配到的订单号(可空) + MatchResult string `json:"match_result"` // matched / no_order / ambiguous / amount_fallback + RiskText string `json:"risk_text,omitempty"` // 风控文案(封号监控上报) + Space int `json:"space"` // 通知来源空间:0=主空间,999=双开(XSpace) + Raw string `json:"raw"` // 通知原文 + CreatedAt time.Time `json:"created_at"` +} + +// UnmatchedPayment 无订单收款(personal.db unmatched_payments 表,人工处理) +type UnmatchedPayment struct { + ID int64 `json:"id"` + Provider string `json:"provider"` + AmountMinor int64 `json:"amount_minor"` + Note string `json:"note"` + Raw string `json:"raw"` + Status string `json:"status"` // pending / resolved / refunded + Handler string `json:"handler"` + // 风控留痕(2026-09-14 需求:无订单收款不做熔断——直发码无法阻止对方转账; + // 记录身份→行为标记→告警提示,保留司法可核对记录) + Payer string `json:"payer"` // 付款人(账本行脱敏名;通知栏无则空) + DeviceID string `json:"device_id"` // 收款设备(锁定收款码归属) + RiskTags string `json:"risk_tags"` // 逗号分隔:big_amount/burst/repeat_payer + RiskLevel int `json:"risk_level"` // 0 正常 / 1 疑似核实 / 2 高风险 + CreatedAt time.Time `json:"created_at"` +} + +// DeviceQrcode 设备动态收款码(服务端 adb 捕获支付宝/微信收款码页) +type DeviceQrcode struct { + ID int64 `json:"id"` + DeviceID string `json:"device_id"` + Platform string `json:"platform"` // alipay / wechat + UserID int `json:"user_id"` // Android 用户空间:0=主空间,999=双开(XSpace) + QRContent string `json:"qr_content"` // 解码后的二维码内容(URL 或串) + Account string `json:"account"` // 收款账户标识(昵称/微信号) + ExpireIn int64 `json:"expire_in"` // 码有效期(秒) + ExpireAt string `json:"expire_at"` // 过期时间(RFC3339) + Status string `json:"status"` // active / expired + PendingFetch int64 `json:"pending_fetch"` // 服务端待下发 fetch_qrcode 指令标志(1=待触发) + CreatedAt string `json:"created_at"` +} + +// UserSpaceLabel 返回用户空间的显示标签 +func (q *DeviceQrcode) UserSpaceLabel() string { + if q.UserID == 999 { + return "双开" // 小米 XSpace 分身 + } + return "主空间" +} + +// ParseSetupProgress 把 SetupProgress 字段(DB 存的 JSON 数组字符串)解析为有序切片。// 空串或非法 JSON 视为空切片(不报错,半配置态守恒语义靠心跳 handler 不解冻账户兜底)。 +func ParseSetupProgress(s string) []string { + if s == "" || s == "[]" { + return nil + } + var out []string + if err := json.Unmarshal([]byte(s), &out); err != nil { + return nil + } + return out +} + +// FormatSetupProgress 把字符串切片格式化为 JSON 数组字符串入库。 +func FormatSetupProgress(steps []string) string { + if len(steps) == 0 { + return "[]" + } + b, _ := json.Marshal(steps) + return string(b) +} + +// HasSetupStep 返回 steps 是否包含 step。 +func HasSetupStep(steps []string, step string) bool { + for _, s := range steps { + if s == step { + return true + } + } + return false +} + +// AppendSetupStep 幂等追加 step 到 steps(已含则原样返回切片)。 +func AppendSetupStep(steps []string, step string) []string { + if HasSetupStep(steps, step) { + return steps + } + return append(steps, step) +} diff --git a/internal/personal/notecode.go b/internal/personal/notecode.go new file mode 100755 index 0000000..4f4d866 --- /dev/null +++ b/internal/personal/notecode.go @@ -0,0 +1,108 @@ +package personal + +import ( + "crypto/rand" + "math/big" + "sync" + "time" +) + +// 备注码分配器(NoteCodeAllocator) +// +// 设计(docs/MIANQIAN.md 6.x): +// - 4 位字符集:a-z 小写 + 0-9,排除易混淆 o/0/l/1 → 32 字符 +// - 组合数:32^4 = 1,048,576,5 分钟窗口内实际并发订单远小于此,几乎不会碰撞 +// - 位图 + 过期队列:高并发分配 O(1),到期自动释放 +// - 系统生成,用户输入 → 精确匹配 → 资金安全 + +// noteChars 备注码字符集(排除易混淆 o/0/l/1) +const noteChars = "abcdefghijkmnpqrstuvwxyz23456789" + +// NoteCodeLen 备注码长度 +const NoteCodeLen = 4 + +// NoteCodeTTL 备注码有效期(订单 5 分钟内未支付则备注码可复用) +const NoteCodeTTL = 5 * time.Minute + +// NoteCodeAllocator 备注码分配器(线程安全) +type NoteCodeAllocator struct { + mu sync.Mutex + used map[string]time.Time // code → 过期时间 +} + +// NewNoteCodeAllocator 创建分配器 +func NewNoteCodeAllocator() *NoteCodeAllocator { + return &NoteCodeAllocator{ + used: make(map[string]time.Time), + } +} + +// Alloc 分配一个未占用且未过期的备注码 +func (a *NoteCodeAllocator) Alloc() (string, error) { + a.mu.Lock() + defer a.mu.Unlock() + + // 先清理已过期条目(避免 map 无限增长) + now := time.Now() + for code, expire := range a.used { + if !expire.After(now) { + delete(a.used, code) + } + } + + // 随机生成直到未占用(最多尝试 N 次;全部占满时等量扩容逻辑不适用——32^4 足够大) + for i := 0; i < 100; i++ { + code, err := randomCode() + if err != nil { + return "", err + } + if _, taken := a.used[code]; !taken { + a.used[code] = now.Add(NoteCodeTTL) + return code, nil + } + } + // 极端情况:100 次随机碰撞(说明窗口内并发订单接近 100 万),退化为线性扫描 + // 扫描全部 32^4 组合找第一个空位;若真全满则清空最旧一批 + for _, c1 := range noteChars { + for _, c2 := range noteChars { + for _, c3 := range noteChars { + for _, c4 := range noteChars { + code := string([]rune{c1, c2, c3, c4}) + if _, taken := a.used[code]; !taken { + a.used[code] = now.Add(NoteCodeTTL) + return code, nil + } + } + } + } + } + // 全部占用:直接清空(实际不可能发生,兜底防死循环) + a.used = make(map[string]time.Time) + code, err := randomCode() + if err != nil { + return "", err + } + a.used[code] = now.Add(NoteCodeTTL) + return code, nil +} + +// Release 释放备注码(订单支付成功/关闭后调用,可选优化:过期自动释放已足够) +func (a *NoteCodeAllocator) Release(code string) { + a.mu.Lock() + defer a.mu.Unlock() + delete(a.used, code) +} + +// randomCode 生成随机 4 位备注码 +func randomCode() (string, error) { + chars := []rune(noteChars) + out := make([]rune, NoteCodeLen) + for i := range out { + n, err := rand.Int(rand.Reader, big.NewInt(int64(len(chars)))) + if err != nil { + return "", err + } + out[i] = chars[n.Int64()] + } + return string(out), nil +} diff --git a/internal/personal/notecode_test.go b/internal/personal/notecode_test.go new file mode 100755 index 0000000..007aa45 --- /dev/null +++ b/internal/personal/notecode_test.go @@ -0,0 +1,82 @@ +package personal + +import ( + "strings" + "testing" + "time" +) + +func TestAllocFormat(t *testing.T) { + a := NewNoteCodeAllocator() + for i := 0; i < 200; i++ { + code, err := a.Alloc() + if err != nil { + t.Fatalf("Alloc: %v", err) + } + if len(code) != NoteCodeLen { + t.Fatalf("code length: got %q want %d", code, NoteCodeLen) + } + for _, ch := range code { + if !strings.ContainsRune(noteChars, ch) { + t.Fatalf("code %q contains invalid char %q", code, ch) + } + } + } +} + +func TestAllocUniqueInWindow(t *testing.T) { + a := NewNoteCodeAllocator() + seen := make(map[string]bool) + for i := 0; i < 5000; i++ { + code, err := a.Alloc() + if err != nil { + t.Fatalf("Alloc: %v", err) + } + if seen[code] { + t.Fatalf("duplicate code in window: %q", code) + } + seen[code] = true + } +} + +func TestAllocNoConfusableChars(t *testing.T) { + a := NewNoteCodeAllocator() + for i := 0; i < 1000; i++ { + code, _ := a.Alloc() + for _, ch := range code { + if ch == 'o' || ch == '0' || ch == 'l' || ch == '1' { + t.Fatalf("confusable char %q in code %q", ch, code) + } + } + } +} + +func TestAllocExpiryReuse(t *testing.T) { + a := NewNoteCodeAllocator() + code, _ := a.Alloc() + // 手动过期(模拟 5 分钟后) + a.mu.Lock() + a.used[code] = time.Now().Add(-time.Second) + a.mu.Unlock() + // 再分配应能拿到同一 code(或至少不报错) + code2, err := a.Alloc() + if err != nil { + t.Fatalf("Alloc after expiry: %v", err) + } + _ = code2 +} + +func TestAllocRelease(t *testing.T) { + a := NewNoteCodeAllocator() + code, _ := a.Alloc() + a.Release(code) + // 释放后可立即复用 + code2, _ := a.Alloc() + if code2 == code { + return // OK + } + // 不强制相同(随机),但必须成功 + if code2 == "" { + t.Fatalf("released code should be allocatable") + } +} diff --git a/internal/personal/ratelimit.go b/internal/personal/ratelimit.go new file mode 100755 index 0000000..f1ff8ce --- /dev/null +++ b/internal/personal/ratelimit.go @@ -0,0 +1,93 @@ +// Package personal 频次风控:滑动窗口限流器(个人免签推送/心跳用) +// +// 设计(docs/MIANQIAN.md §10 风控): +// - 频次风控:每分钟收款笔数上限(可配置),超限拒绝 + 告警 +// - 滑动窗口实现:按 key(device_id)维护 60s 内的时间戳列表,O(1) 摊销 +// - 内存占用受控:窗口外旧记录惰性清理,key 总数上限保护 +package personal + +import ( + "sync" + "time" +) + +// RateLimiter 滑动窗口限流器(线程安全) +type RateLimiter struct { + mu sync.Mutex + limit int // 窗口内最大次数(<=0 表示不限) + window time.Duration // 窗口时长(默认 60s) + maxKeys int // key 数上限(防内存无限增长,默认 10000) + hits map[string][]int64 // key → 窗口内时间戳(Unix 秒) +} + +// NewRateLimiter 创建限流器 +// limit: 窗口内最大次数;window: 窗口时长;maxKeys: key 数上限(0 用默认 10000) +func NewRateLimiter(limit int, window time.Duration, maxKeys int) *RateLimiter { + if window <= 0 { + window = time.Minute + } + if maxKeys <= 0 { + maxKeys = 10000 + } + return &RateLimiter{ + limit: limit, + window: window, + maxKeys: maxKeys, + hits: make(map[string][]int64), + } +} + +// Allow 判断 key 是否允许本次请求(允许则记录) +func (r *RateLimiter) Allow(key string) bool { + if r.limit <= 0 { + return true // 未配置限额 + } + r.mu.Lock() + defer r.mu.Unlock() + + now := time.Now().Unix() + cutoff := now - int64(r.window.Seconds()) + + // 惰性清理:移除窗口外记录;空列表直接删 key + hits := r.hits[key] + kept := hits[:0] + for _, t := range hits { + if t > cutoff { + kept = append(kept, t) + } + } + if len(kept) == 0 { + delete(r.hits, key) + kept = nil + } + if len(kept) >= r.limit { + r.hits[key] = kept + return false + } + // key 数上限保护(仅新增 key 时触发) + if len(kept) == 0 && len(r.hits) >= r.maxKeys { + // 清掉最久未活动的 key:简单策略,遍历删除首个空窗口 key + for k, v := range r.hits { + if len(v) > 0 && v[len(v)-1] <= cutoff { + delete(r.hits, k) + break + } + } + } + r.hits[key] = append(kept, now) + return true +} + +// Count 返回 key 当前窗口内次数(诊断用) +func (r *RateLimiter) Count(key string) int { + r.mu.Lock() + defer r.mu.Unlock() + cutoff := time.Now().Unix() - int64(r.window.Seconds()) + n := 0 + for _, t := range r.hits[key] { + if t > cutoff { + n++ + } + } + return n +} diff --git a/internal/personal/ratelimit_test.go b/internal/personal/ratelimit_test.go new file mode 100755 index 0000000..9720677 --- /dev/null +++ b/internal/personal/ratelimit_test.go @@ -0,0 +1,65 @@ +package personal + +import ( + "testing" + "time" +) + +// TestRateLimiterAllow 基础限流:超限拒绝 +func TestRateLimiterAllow(t *testing.T) { + rl := NewRateLimiter(3, time.Minute, 0) + if !rl.Allow("dev-1") { + t.Fatal("第1次应允许") + } + if !rl.Allow("dev-1") { + t.Fatal("第2次应允许") + } + if !rl.Allow("dev-1") { + t.Fatal("第3次应允许") + } + if rl.Allow("dev-1") { + t.Fatal("第4次应拒绝(窗口内已满)") + } + if rl.Count("dev-1") != 3 { + t.Fatalf("窗口内次数应为 3,实际 %d", rl.Count("dev-1")) + } +} + +// TestRateLimiterWindow 窗口滑动:旧记录过期后恢复 +func TestRateLimiterWindow(t *testing.T) { + rl := NewRateLimiter(2, time.Second, 0) // 1 秒窗口 + if !rl.Allow("dev-2") || !rl.Allow("dev-2") { + t.Fatal("前两次应允许") + } + if rl.Allow("dev-2") { + t.Fatal("第3次应拒绝") + } + time.Sleep(1100 * time.Millisecond) // 窗口过期 + if !rl.Allow("dev-2") { + t.Fatal("窗口过期后应恢复允许") + } +} + +// TestRateLimiterPerKey 不同 key 相互独立 +func TestRateLimiterPerKey(t *testing.T) { + rl := NewRateLimiter(1, time.Minute, 0) + if !rl.Allow("dev-a") { + t.Fatal("dev-a 第1次应允许") + } + if rl.Allow("dev-a") { + t.Fatal("dev-a 第2次应拒绝") + } + if !rl.Allow("dev-b") { + t.Fatal("dev-b 应独立允许") + } +} + +// TestRateLimiterNoLimit 未配置限额时全部放行 +func TestRateLimiterNoLimit(t *testing.T) { + rl := NewRateLimiter(0, time.Minute, 0) + for i := 0; i < 100; i++ { + if !rl.Allow("dev-x") { + t.Fatal("无限额时不应拒绝") + } + } +} diff --git a/internal/personal/sign.go b/internal/personal/sign.go new file mode 100755 index 0000000..a1269e7 --- /dev/null +++ b/internal/personal/sign.go @@ -0,0 +1,79 @@ +package personal + +import ( + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "encoding/hex" + "fmt" + "strings" + "time" +) + +// 监听端通信签名(docs/MIANQIAN.md 8.1) +// +// HTTPS 强制(生产) +// sign = HMAC-SHA256(secret, method|path|timestamp|field1|field2|...) +// timestamp 防重放:±5 分钟窗口 +// device_id:客户端唯一标识(首次启动生成 UUID) +// +// 字段化签名设计(关键): +// - 签名基于"字段值按固定顺序拼接",而非对整个 JSON body 字节签名 +// - 原因:JSON 序列化字段顺序不可控(Go map 随机序),若对 body 字节签名, +// 客户端和服务端 JSON 字段顺序不一致会导致验签失败(且无法把 sign 放进 body 同时签名) +// - 客户端:构造业务字段 → 计算签名 → 把 sign 加入 JSON 发送 +// - 服务端:解析 JSON 取出字段 → 用相同顺序计算期望签名 → 常量时间比较 + +// SignWindow 签名时间戳窗口(±5 分钟) +const SignWindow = 5 * time.Minute + +// SignMsg 计算监听端请求签名 +// fields 按固定顺序传入(客户端/服务端必须一致): +// - 心跳: device_id +// - 推送: device_id, provider, amount, note, raw +func SignMsg(secret, method, path string, timestamp int64, fields ...string) string { + mac := hmac.New(sha256.New, []byte(secret)) + fmt.Fprintf(mac, "%s|%s|%d|", method, path, timestamp) + mac.Write([]byte(strings.Join(fields, "|"))) + return hex.EncodeToString(mac.Sum(nil)) +} + +// VerifySign 校验监听端请求签名 +// 校验:签名匹配 + 时间戳在 ±SignWindow 窗口内(防重放) +func VerifySign(secret, method, path string, timestamp int64, sign string, fields ...string) error { + if sign == "" { + return fmt.Errorf("缺少签名") + } + now := time.Now().Unix() + diff := now - timestamp + if diff < 0 { + diff = -diff + } + if diff > int64(SignWindow.Seconds()) { + return fmt.Errorf("时间戳超出窗口(防重放): diff=%ds", diff) + } + expect := SignMsg(secret, method, path, timestamp, fields...) + if !hmac.Equal([]byte(expect), []byte(sign)) { + return fmt.Errorf("签名不匹配") + } + return nil +} + +// NewDeviceSecret 生成设备通信密钥(32 字节 hex) +func NewDeviceSecret() (string, error) { + buf := make([]byte, 32) + if _, err := rand.Read(buf); err != nil { + return "", fmt.Errorf("生成设备密钥失败: %w", err) + } + return hex.EncodeToString(buf), nil +} + +// NewDeviceID 生成设备唯一标识(UUID v4 简化版,32 位 hex) +func NewDeviceID() string { + buf := make([]byte, 16) + if _, err := rand.Read(buf); err != nil { + // 兜底:rand 失败时用时间戳(几乎不可能) + return fmt.Sprintf("dev-%d", time.Now().UnixNano()) + } + return hex.EncodeToString(buf) +} diff --git a/internal/personal/sign_test.go b/internal/personal/sign_test.go new file mode 100755 index 0000000..3417951 --- /dev/null +++ b/internal/personal/sign_test.go @@ -0,0 +1,113 @@ +package personal + +import ( + "testing" + "time" +) + +func TestVerifySignValid(t *testing.T) { + secret := "test-secret-key" + method := "POST" + path := "/personal/push" + ts := time.Now().Unix() + + sign := SignMsg(secret, method, path, ts, "d1", "alipay", "10.00", "ab3k", "支付宝到账10.00元", "0") + if err := VerifySign(secret, method, path, ts, sign, "d1", "alipay", "10.00", "ab3k", "支付宝到账10.00元", "0"); err != nil { + t.Fatalf("valid sign should pass: %v", err) + } +} + +func TestVerifySignWrongSecret(t *testing.T) { + ts := time.Now().Unix() + sign := SignMsg("secret-a", "POST", "/personal/push", ts, "d1") + if err := VerifySign("secret-b", "POST", "/personal/push", ts, sign, "d1"); err == nil { + t.Fatalf("wrong secret should fail") + } +} + +func TestVerifySignTamperedField(t *testing.T) { + secret := "s" + ts := time.Now().Unix() + sign := SignMsg(secret, "POST", "/personal/push", ts, "d1", "10.00") + // 篡改金额字段(签名不变)→ 验签失败 + if err := VerifySign(secret, "POST", "/personal/push", ts, sign, "d1", "100.00"); err == nil { + t.Fatalf("tampered field should fail") + } +} + +func TestVerifySignReplayWindow(t *testing.T) { + secret := "s" + // 时间戳超出窗口(10 分钟前) + oldTS := time.Now().Add(-10 * time.Minute).Unix() + sign := SignMsg(secret, "POST", "/personal/push", oldTS, "d1") + if err := VerifySign(secret, "POST", "/personal/push", oldTS, sign, "d1"); err == nil { + t.Fatalf("out-of-window timestamp should fail (replay protection)") + } + // 窗口内(1 分钟前)应通过 + recentTS := time.Now().Add(-1 * time.Minute).Unix() + sign2 := SignMsg(secret, "POST", "/personal/push", recentTS, "d1") + if err := VerifySign(secret, "POST", "/personal/push", recentTS, sign2, "d1"); err != nil { + t.Fatalf("in-window timestamp should pass: %v", err) + } +} + +func TestVerifySignEmpty(t *testing.T) { + if err := VerifySign("s", "POST", "/personal/push", time.Now().Unix(), "", "d1"); err == nil { + t.Fatalf("empty sign should fail") + } +} + +// TestVerifySignRiskText 封号监控链路(docs/MIANQIAN.md §10): +// 监听端检测到风控文案 → 上报 risk_text(amount/note 为空串), +// risk_text 必须参与签名(客户端 SignUtil.signPush 字段序含 risk_text), +// 且空串也参与拼接 —— 模拟 APP 真实上报的字段序,验证服务端验签通过 +func TestVerifySignRiskText(t *testing.T) { + secret := "test-secret-key" + method := "POST" + path := "/personal/push" + ts := time.Now().Unix() + deviceID := "dev-1234" + provider := "alipay" + + // 模拟 APP 风控上报:amount="" note="",raw 与 risk_text 均带原文 + // 注意:Kotlin SignUtil.signPush 字段序 = [device_id, provider, amount, note, raw, risk_text, space] + raw := "您的账户存在风控提示,请及时处理" + amount, note := "", "" + riskText := "您的账户存在风控提示,请及时处理" + sign := SignMsg(secret, method, path, ts, deviceID, provider, amount, note, raw, riskText, "0") + if err := VerifySign(secret, method, path, ts, sign, deviceID, provider, amount, note, raw, riskText, "0"); err != nil { + t.Fatalf("risk-text sign should pass (fields order must match client): %v", err) + } + // 篡改 risk_text → 验签失败 + if err := VerifySign(secret, method, path, ts, sign, deviceID, provider, amount, note, raw, "伪造的风控文案", "0"); err == nil { + t.Fatalf("tampered risk_text should fail") + } +} + +// TestVerifySignRiskTextEmpty 普通收款上报(risk_text 为空串也必须参与拼接) +func TestVerifySignRiskTextEmpty(t *testing.T) { + secret := "s" + ts := time.Now().Unix() + // 客户端普通推送:risk_text 默认为 "",但 SignUtil 仍拼入字段序 + sign := SignMsg(secret, "POST", "/personal/push", ts, "d1", "alipay", "10.00", "ab3k", "支付宝到账10.00元", "", "0") + if err := VerifySign(secret, "POST", "/personal/push", ts, sign, "d1", "alipay", "10.00", "ab3k", "支付宝到账10.00元", "", "0"); err != nil { + t.Fatalf("empty risk_text must still be signed (client sends it): %v", err) + } +} + +func TestNewDeviceSecret(t *testing.T) { + s1, err := NewDeviceSecret() + if err != nil { + t.Fatalf("NewDeviceSecret: %v", err) + } + s2, err := NewDeviceSecret() + if err != nil { + t.Fatalf("NewDeviceSecret: %v", err) + } + if len(s1) != 64 { // 32 字节 hex + t.Fatalf("secret length: got %d want 64", len(s1)) + } + if s1 == s2 { + t.Fatalf("secrets should differ") + } +} diff --git a/internal/personal/token.go b/internal/personal/token.go new file mode 100755 index 0000000..848de0d --- /dev/null +++ b/internal/personal/token.go @@ -0,0 +1,127 @@ +package personal + +import ( + "crypto/hmac" + "crypto/rand" + "crypto/sha256" + "encoding/base64" + "encoding/hex" + "fmt" + "strconv" + "strings" + "time" +) + +// 二维码防劫持 token(docs/MIANQIAN.md 5.3) +// +// 支付页面不内嵌真实收款码,只引用网关签发的动态图片接口: +// +// 图片地址 = /qr/real?token={token} +// 浏览器请求 → 网关校验(token 签名 + 订单存在 + WAIT_PAY + 未过期) +// → 下发真实收款码 PNG(真实码内容不进支付页 HTML) +// +// token 格式(自包含,无需数据库索引): +// +// {随机16字节 base64url}.{orderID:expireUnix}.{HMAC-SHA256(orderID|expireUnix|随机, secret) 前 22 字节 base64url} +// +// 服务端校验:解码 → 重算 HMAC → 常量时间比较 → 检查过期 → 返回 orderID +// 攻击者无法伪造(无 secret),篡改任一字段 HMAC 失配即拒绝。 + +// QrTokenSecret 套码 token 签名密钥(服务端持有,内存) +var qrTokenSecret []byte + +// InitQrToken 初始化套码 token 签名密钥(hex 32 字节) +// 未调用 InitQrToken 时自动生成并持久化由 main 负责;这里校验长度 +func InitQrToken(secret string) error { + key, err := hex.DecodeString(secret) + if err != nil { + return fmt.Errorf("套码 token 密钥格式错误: %w", err) + } + if len(key) != 32 { + return fmt.Errorf("套码 token 密钥长度错误: 需要 32 字节,实际 %d", len(key)) + } + qrTokenSecret = key + return nil +} + +// QrTokenSecretSet 是否已初始化 +func QrTokenSecretSet() bool { return len(qrTokenSecret) == 32 } + +// GenerateQrToken 生成套码 token +// key: 订单号(out_trade_no)或订单 ID 字符串 +func GenerateQrToken(key string, expireAt time.Time) (string, error) { + if !QrTokenSecretSet() { + return "", fmt.Errorf("套码 token 密钥未初始化") + } + // 随机 16 字节 + rnd := make([]byte, 16) + if _, err := rand.Read(rnd); err != nil { + return "", fmt.Errorf("生成 token 随机数失败: %w", err) + } + // payload: key|expireUnix + payload := fmt.Sprintf("%s|%d", key, expireAt.Unix()) + // HMAC over payload|random + mac := hmac.New(sha256.New, qrTokenSecret) + mac.Write([]byte(payload)) + mac.Write([]byte("|")) + mac.Write(rnd) + sig := base64.RawURLEncoding.EncodeToString(mac.Sum(nil)[:22]) + + token := base64.RawURLEncoding.EncodeToString(rnd) + "." + + base64.RawURLEncoding.EncodeToString([]byte(payload)) + "." + sig + return token, nil +} + +// ParseQrToken 校验并解析 token,返回 (key, expireAt) +// 校验失败返回 error(篡改/伪造/过期) +func ParseQrToken(token string) (string, time.Time, error) { + if !QrTokenSecretSet() { + return "", time.Time{}, fmt.Errorf("套码 token 密钥未初始化") + } + parts := strings.Split(token, ".") + if len(parts) != 3 { + return "", time.Time{}, fmt.Errorf("token 格式错误") + } + rnd, err := base64.RawURLEncoding.DecodeString(parts[0]) + if err != nil || len(rnd) != 16 { + return "", time.Time{}, fmt.Errorf("token 随机数非法") + } + payloadBytes, err := base64.RawURLEncoding.DecodeString(parts[1]) + if err != nil { + return "", time.Time{}, fmt.Errorf("token payload 非法") + } + payload := string(payloadBytes) + sig, err := base64.RawURLEncoding.DecodeString(parts[2]) + if err != nil || len(sig) == 0 { + return "", time.Time{}, fmt.Errorf("token 签名非法") + } + + // 重算 HMAC 并常量时间比较 + mac := hmac.New(sha256.New, qrTokenSecret) + mac.Write([]byte(payload)) + mac.Write([]byte("|")) + mac.Write(rnd) + expect := mac.Sum(nil)[:22] + if !hmac.Equal(expect, sig) { + return "", time.Time{}, fmt.Errorf("token 签名不匹配(篡改或伪造)") + } + + // 解析 payload: key|expireUnix + kv := strings.Split(payload, "|") + if len(kv) != 2 { + return "", time.Time{}, fmt.Errorf("token payload 格式错误") + } + key := kv[0] + if key == "" { + return "", time.Time{}, fmt.Errorf("token key 非法") + } + expireUnix, err := strconv.ParseInt(kv[1], 10, 64) + if err != nil { + return "", time.Time{}, fmt.Errorf("token expire 非法") + } + expireAt := time.Unix(expireUnix, 0) + if !expireAt.After(time.Now()) { + return "", time.Time{}, fmt.Errorf("token 已过期") + } + return key, expireAt, nil +} diff --git a/internal/personal/token_test.go b/internal/personal/token_test.go new file mode 100755 index 0000000..2d9c830 --- /dev/null +++ b/internal/personal/token_test.go @@ -0,0 +1,96 @@ +package personal + +import ( + "encoding/base64" + "strings" + "testing" + "time" +) + +// base64URL 辅助:普通 base64 编码 +func base64URL(s string) string { + return base64.RawURLEncoding.EncodeToString([]byte(s)) +} + +func TestGenerateQrTokenRoundTrip(t *testing.T) { + if err := InitQrToken(strings.Repeat("ab", 32)); err != nil { + t.Fatalf("InitQrToken: %v", err) + } + token, err := GenerateQrToken("ORDER-123", time.Now().Add(30*time.Minute)) + if err != nil { + t.Fatalf("GenerateQrToken: %v", err) + } + key, expire, err := ParseQrToken(token) + if err != nil { + t.Fatalf("ParseQrToken: %v", err) + } + if key != "ORDER-123" { + t.Fatalf("key mismatch: got %q want ORDER-123", key) + } + if !expire.After(time.Now()) { + t.Fatalf("expire should be in future") + } +} + +func TestGenerateQrTokenTampered(t *testing.T) { + if err := InitQrToken(strings.Repeat("ab", 32)); err != nil { + t.Fatalf("InitQrToken: %v", err) + } + token, err := GenerateQrToken("ORDER-123", time.Now().Add(30*time.Minute)) + if err != nil { + t.Fatalf("GenerateQrToken: %v", err) + } + // 篡改 payload(改成另一个订单号) + parts := strings.Split(token, ".") + if len(parts) != 3 { + t.Fatalf("token format wrong") + } + // 篡改 payload 部分为另一个订单号(保持时间戳格式,签名必然失配) + evilPayload := base64URL("ORDER-EVIL|9999999999") + evil := parts[0] + "." + evilPayload + "." + parts[2] + if _, _, err := ParseQrToken(evil); err == nil { + t.Fatalf("tampered token should fail") + } +} + +func TestParseQrTokenExpired(t *testing.T) { + if err := InitQrToken(strings.Repeat("ab", 32)); err != nil { + t.Fatalf("InitQrToken: %v", err) + } + token, err := GenerateQrToken("ORDER-1", time.Now().Add(-time.Minute)) // 已过期 + if err != nil { + t.Fatalf("GenerateQrToken: %v", err) + } + if _, _, err := ParseQrToken(token); err == nil { + t.Fatalf("expired token should fail") + } +} + +func TestParseQrTokenGarbage(t *testing.T) { + if err := InitQrToken(strings.Repeat("ab", 32)); err != nil { + t.Fatalf("InitQrToken: %v", err) + } + if _, _, err := ParseQrToken("garbage"); err == nil { + t.Fatalf("garbage token should fail") + } + if _, _, err := ParseQrToken(""); err == nil { + t.Fatalf("empty token should fail") + } +} + +func TestGenerateQrTokenUnique(t *testing.T) { + if err := InitQrToken(strings.Repeat("ab", 32)); err != nil { + t.Fatalf("InitQrToken: %v", err) + } + seen := make(map[string]bool) + for i := 0; i < 100; i++ { + token, err := GenerateQrToken("ORDER-1", time.Now().Add(time.Minute)) + if err != nil { + t.Fatalf("GenerateQrToken: %v", err) + } + if seen[token] { + t.Fatalf("duplicate token") + } + seen[token] = true + } +} diff --git a/internal/pool/waitpay_pool.go b/internal/pool/waitpay_pool.go new file mode 100644 index 0000000..c0b0e68 --- /dev/null +++ b/internal/pool/waitpay_pool.go @@ -0,0 +1,800 @@ +// Package pool 提供 WAIT_PAY 订单的并发安全内存池。 +// +// 设计要点: +// - 设备维度子池:devices[deviceID] 一个设备子池,多设备天然隔离、互不干扰, +// 每台设备只处理自己账户的订单(docs/PLAN-DYNAMIC-CLOSE-ALLOC.md §3); +// - 单一 sync.RWMutex 守护全部索引表(写入低频),聚合快照通过 atomic.Pointer 持有,读路径零锁; +// - 动态关单窗口:CloseAt 由注入的 winCalc(设备负载驱动)计算,单调只增(防误关); +// - 本包不依赖 store/order 等业务包,避免反向依赖循环;仅依赖 model(领域类型)和 logger(结构化告警)。 +package pool + +import ( + "context" + "sort" + "sync" + "sync/atomic" + "time" + + "paygateway/internal/logger" + "paygateway/internal/model" +) + +// 默认关单窗口:closeWindowMinutes<=0 且未注入 winCalc 时使用 +const defaultCloseWindow = 30 * time.Minute + +// Entry 池中订单条目(不可变值对象,Add/Upsert 时由调用方持有原对象;本包不会修改)。 +type Entry struct { + OutTradeNo string + AccountID int64 + DeviceID string // 所属设备(账户→设备映射解析;未知归 "") + Channel string // personal.alipay / personal.wechat + AmountMinor int64 // 分 + NoteCode string // 备注码(可空) + CreatedAt time.Time + CloseAt time.Time // 预计关单时间 = CreatedAt + 动态窗口(单调只增) + SessionExpiresAt time.Time // 支付会话硬截止时间,替换订单不得延长 +} + +// AccountStat 单账户聚合统计(快照元素,值类型不可变)。 +type AccountStat struct { + AccountID int64 + Count int + // DupAmount 同设备同账户是否存在 ≥2 笔相同金额的 WAIT_PAY 订单; + // 决定调度器是否需轮询账单页(金额互异的订单由通知栏按金额唯一匹配入账)。 + DupAmount bool + OldestAge time.Duration // 最久订单已等待时长(>0) + MinTTL time.Duration // 距最早关单剩余时间(可为负=已超时) +} + +// Snapshot 全池聚合快照(原子指针持有,不可变;notifywatcher 等全局判断用)。 +type Snapshot struct { + Accounts []AccountStat + Total int + UpdatedAt time.Time +} + +// DeviceStat 单设备聚合快照(设备调度器/均衡分配器零锁读取)。 +type DeviceStat struct { + DeviceID string + Total int // 该设备 WAIT_PAY 总数(设备负载) + Accounts []AccountStat // 该设备内按账户聚合 + ActiveAccts int // 活跃账户数(Count>0) + EstRound time.Duration // 估算一轮耗时(ActiveAccts × 配额 × pollCost,供动态窗口/超限判定) + CloseWindow time.Duration // 当前生效动态关单窗口(最新 Add 时计算) + UpdatedAt time.Time +} + +// devicePool 单设备子池:三张索引表(与旧全局结构同构,按设备隔离)。 +type devicePool struct { + buckets map[int64]map[string]*Entry + byNote map[int64]map[string]string + byAmount map[int64]map[int64]map[string]struct{} + stat atomic.Pointer[DeviceStat] +} + +// DeviceOfFunc 账户 → 设备 解析(main.go 注入:personal.db 设备绑定)。 +// ok=false 时订单归入 "" 兜底桶(无设备绑定/解析失败,Reconcile 修复)。 +type DeviceOfFunc func(accountID int64) (deviceID string, ok bool) + +// WindowCalcFunc 动态关单窗口计算器(main.go 注入;nil = 固定 fallback 窗口)。 +// 入参:该设备当前活跃账户数与待支付订单总数(Add 时点快照)。 +// 返回:订单关单窗口(实现方负责 clamp 到 [MIN, MAX])。 +type WindowCalcFunc func(activeAccounts, total int) time.Duration + +// WaitPayPool 并发安全的 WAIT_PAY 订单池。 +// +// 内部数据结构: +// - devices[deviceID].buckets[accountID][outTradeNo] = *Entry +// - devices[deviceID].byNote[accountID][noteCode] = outTradeNo(noteCode 为空时不入索引) +// - devices[deviceID].byAmount[accountID][amountMinor][outTradeNo] = struct{}{} +// - acctDev[accountID] = deviceID(账户→设备反查,匹配/Count/移除零遍历) +// +// 所有表必须在写锁内同步变更;快照由 rebuildStatsLocked 在写锁内一次性生成。 +type WaitPayPool struct { + deviceOf DeviceOfFunc + winCalc WindowCalcFunc + fallback time.Duration // winCalc 为 nil 时的固定关窗(closeWindowMinutes) + + mu sync.RWMutex + devices map[string]*devicePool + acctDev map[int64]string // accountID → deviceID + + snap atomic.Pointer[Snapshot] +} + +// NewWaitPayPool 创建池。closeWindowMinutes<=0 且未注入 winCalc 时 CloseAt 用 CreatedAt+30min 兜底。 +func NewWaitPayPool(closeWindowMinutes int) *WaitPayPool { + w := defaultCloseWindow + if closeWindowMinutes > 0 { + w = time.Duration(closeWindowMinutes) * time.Minute + } + p := &WaitPayPool{ + fallback: w, + devices: make(map[string]*devicePool), + acctDev: make(map[int64]string), + } + p.devices[""] = newDevicePool() + p.publishGlobalLocked() + return p +} + +// newDevicePool 创建空设备子池并初始化空快照。 +func newDevicePool() *devicePool { + dp := &devicePool{ + buckets: make(map[int64]map[string]*Entry), + byNote: make(map[int64]map[string]string), + byAmount: make(map[int64]map[int64]map[string]struct{}), + } + dp.stat.Store(&DeviceStat{}) + return dp +} + +// SetDeviceOf 注入账户→设备解析(main.go 在设备增删时调用;可重复注入)。 +func (p *WaitPayPool) SetDeviceOf(fn DeviceOfFunc) { + p.mu.Lock() + defer p.mu.Unlock() + p.deviceOf = fn + // 注入后全量重分桶:条目按新映射重新解析设备归属(DeviceID 先清空再走新 deviceOf) + if fn != nil { + var all []*Entry + for _, dp := range p.devices { + for _, m := range dp.buckets { + for _, e := range m { + e.DeviceID = "" + all = append(all, e) + } + } + } + p.devices = make(map[string]*devicePool) + p.acctDev = make(map[int64]string) + p.devices[""] = newDevicePool() + for _, e := range all { + p.indexLocked(e) + } + } + p.publishGlobalLocked() +} + +// SetWindowCalc 注入动态关单窗口计算器(main.go 调用;nil = 固定 fallback)。 +// 注入后对池内现有条目重新计算 CloseAt(仅延长不缩短,保持单调)。 +func (p *WaitPayPool) SetWindowCalc(fn WindowCalcFunc) { + p.mu.Lock() + defer p.mu.Unlock() + p.winCalc = fn + for _, dp := range p.devices { + for _, m := range dp.buckets { + for _, e := range m { + devID := p.deviceOfEntry(e) + nw := p.windowFor(devID) + if nw > 0 && e.CloseAt.Before(e.CreatedAt.Add(nw)) { + e.CloseAt = e.CreatedAt.Add(nw) + } + } + } + } + p.publishGlobalLocked() +} + +// deviceOfEntry 返回条目所属设备(优先 entry.DeviceID;兜底 deviceOf 解析)。 +func (p *WaitPayPool) deviceOfEntry(e *Entry) string { + if e.DeviceID != "" { + return e.DeviceID + } + if p.deviceOf != nil { + if d, ok := p.deviceOf(e.AccountID); ok && d != "" { + return d + } + } + return "" +} + +// windowFor 计算某设备当前动态关单窗口:winCalc(活跃账户数, 待支付订单数)。 +// 活跃账户数 = 该设备子池中 Count>0 的账户数(不含待添加条目本身)。 +func (p *WaitPayPool) windowFor(deviceID string) time.Duration { + if p.winCalc == nil { + return p.fallback + } + dp := p.devices[deviceID] + if dp == nil { + return p.fallback + } + active, total := 0, 0 + for _, m := range dp.buckets { + if len(m) > 0 { + active++ + total += len(m) + } + } + w := p.winCalc(active, total) + if w <= 0 { + return p.fallback + } + return w +} + +// orderToEntry 从 model.Order 构造池内 Entry。 +// CloseAt = CreatedAt + 动态窗口(Add 时点设备负载计算);单调只增(Upsert 覆盖时 CloseAt 不倒退)。 +func (p *WaitPayPool) orderToEntry(o *model.Order) *Entry { + devID := "" + if p.deviceOf != nil { + if d, ok := p.deviceOf(o.AccountID); ok { + devID = d + } + } + closeAt := o.CreatedAt.Add(p.windowFor(devID)) + if !o.SessionExpiresAt.IsZero() && o.SessionExpiresAt.Before(closeAt) { + closeAt = o.SessionExpiresAt + } + return &Entry{ + OutTradeNo: o.OutTradeNo, + AccountID: o.AccountID, + DeviceID: devID, + Channel: o.Channel, + AmountMinor: o.AmountMinor, + NoteCode: o.NoteCode, + CreatedAt: o.CreatedAt, + CloseAt: closeAt, + SessionExpiresAt: o.SessionExpiresAt, + } +} + +// Add 加入池。幂等:同 outTradeNo 已存在则覆盖更新(NoteCode/Amount/Channel 等变更会被刷新)。 +// CloseAt 单调只增:覆盖时若新窗口计算的 CloseAt 早于旧值,保持旧值(防负载下降提前关单误关)。 +func (p *WaitPayPool) Add(o *model.Order) { + if o == nil || o.OutTradeNo == "" { + return + } + + p.mu.Lock() + defer p.mu.Unlock() + + entry := p.orderToEntry(o) + + // 若已存在:先从旧索引中移除,再用新 entry 写入 + if old, ok := p.lookupLocked(o.OutTradeNo); ok { + if entry.CloseAt.Before(old.CloseAt) { + entry.CloseAt = old.CloseAt // 单调只增 + } + p.unindexLocked(old) + // 显式删除旧桶条目(unindexLocked 只清 byNote/byAmount,buckets 由调用方删)。 + // 设备映射变更时(旧 DeviceID ≠ 新 DeviceID)必须删旧桶,否则同订单残留双设备子池, + // 导致旧设备负载虚高、均衡分配失真(P2-3)。 + if od, ok := p.devices[old.DeviceID]; ok { + if m, ok := od.buckets[old.AccountID]; ok { + delete(m, old.OutTradeNo) + if len(m) == 0 { + delete(od.buckets, old.AccountID) + delete(p.acctDev, old.AccountID) + } + } + } + } + p.indexLocked(entry) + p.publishGlobalLocked() +} + +// Upsert 覆盖语义别名。Add 本身已支持幂等覆盖(refresh credential / channel 时 NoteCode 可能变化)。 +func (p *WaitPayPool) Upsert(o *model.Order) { + p.Add(o) +} + +// CloseAtOf 查询订单的动态关单时间(order.Service 注入 WaitPayHooks.CloseAtOf 用)。 +// 返回 (closeAt, found)。池中不存在 → found=false(已入账/已关单/非个人通道)。 +func (p *WaitPayPool) CloseAtOf(outTradeNo string) (time.Time, bool) { + p.mu.RLock() + defer p.mu.RUnlock() + if e, ok := p.lookupLocked(outTradeNo); ok { + return e.CloseAt, true + } + return time.Time{}, false +} + +// Remove 移出池。幂等:不存在无副作用。 +// +// 内部会扫描所有设备/账户桶以定位 outTradeNo(业务上 outTradeNo 全局唯一,正常情况仅匹配一处; +// 扫描全表可防御性清理被异常双写的同名条目,并保证索引不留孤儿)。 +func (p *WaitPayPool) Remove(outTradeNo string) { + if outTradeNo == "" { + return + } + + p.mu.Lock() + defer p.mu.Unlock() + + removed := false + for _, dp := range p.devices { + for accID, m := range dp.buckets { + if e, ok := m[outTradeNo]; ok { + p.unindexLocked(e) + delete(m, outTradeNo) + if len(m) == 0 { + delete(dp.buckets, accID) + delete(p.acctDev, accID) + } + removed = true + } + } + } + if !removed { + return + } + p.publishGlobalLocked() +} + +// Snapshot 返回当前聚合快照。零锁:通过 atomic.Pointer 读取已发布快照。 +// 返回的 *Snapshot 与内部 Accounts 切片不可变,调用方可安全并发读取。 +func (p *WaitPayPool) Snapshot() *Snapshot { + return p.snap.Load() +} + +// DeviceSnapshot 返回单设备聚合快照(零锁)。设备无子池/无订单 → 空统计(Total=0)。 +// 设备调度器与均衡分配器的数据源(PLAN-DYNAMIC-CLOSE-ALLOC §3.2)。 +func (p *WaitPayPool) DeviceSnapshot(deviceID string) *DeviceStat { + p.mu.RLock() + dp := p.devices[deviceID] + p.mu.RUnlock() + if dp == nil { + return &DeviceStat{DeviceID: deviceID} + } + st := dp.stat.Load() + if st == nil { + return &DeviceStat{DeviceID: deviceID} + } + return st +} + +// MatchByNote 备注码精确命中(在账户所属设备子池内匹配)。 +// 按 noteCode 找订单,要求 CreatedAt >= since。同码多单时返回 CreatedAt 最新者;未命中返回 nil。 +// +// 注意:本方法只做池内匹配,调用方必须另行做 DB 二次校验与账户/通道/空间校验。 +func (p *WaitPayPool) MatchByNote(accountID int64, note string, since time.Time) *Entry { + if note == "" { + return nil + } + + p.mu.RLock() + defer p.mu.RUnlock() + + devID, ok := p.acctDev[accountID] + if !ok { + return nil + } + dp := p.devices[devID] + if dp == nil { + return nil + } + noteMap, ok := dp.byNote[accountID] + if !ok { + return nil + } + outNo, ok := noteMap[note] + if !ok { + return nil + } + entryMap, ok := dp.buckets[accountID] + if !ok { + return nil + } + e, ok := entryMap[outNo] + if !ok { + return nil // 索引与桶不一致(理论上不应发生;防御性返回 nil) + } + if e.CreatedAt.Before(since) { + return nil + } + return e +} + +// MatchByAmount 金额兜底候选(在账户所属设备子池内匹配)。 +// 按 amountMinor 找订单(CreatedAt >= since),返回全部候选。 +// 多候选由调用方判歧义转人工;未命中返回 nil。 +func (p *WaitPayPool) MatchByAmount(accountID int64, amountMinor int64, since time.Time) []*Entry { + p.mu.RLock() + defer p.mu.RUnlock() + + devID, ok := p.acctDev[accountID] + if !ok { + return nil + } + dp := p.devices[devID] + if dp == nil { + return nil + } + amtMap, ok := dp.byAmount[accountID] + if !ok { + return nil + } + set, ok := amtMap[amountMinor] + if !ok || len(set) == 0 { + return nil + } + entryMap, ok := dp.buckets[accountID] + if !ok { + return nil + } + + out := make([]*Entry, 0, len(set)) + for outNo := range set { + e, ok := entryMap[outNo] + if !ok { + continue + } + if e.CreatedAt.Before(since) { + continue + } + out = append(out, e) + } + if len(out) == 0 { + return nil + } + // 稳定排序:先按 CreatedAt 升序,再按 OutTradeNo 字典序,保证测试可重复与歧义判定确定性 + sort.Slice(out, func(i, j int) bool { + if !out[i].CreatedAt.Equal(out[j].CreatedAt) { + return out[i].CreatedAt.Before(out[j].CreatedAt) + } + return out[i].OutTradeNo < out[j].OutTradeNo + }) + return out +} + +// Count 某账户待支付订单数。无该账户返回 0。 +func (p *WaitPayPool) Count(accountID int64) int { + p.mu.RLock() + defer p.mu.RUnlock() + devID, ok := p.acctDev[accountID] + if !ok { + return 0 + } + if dp := p.devices[devID]; dp != nil { + return len(dp.buckets[accountID]) + } + return 0 +} + +// HasDupAmount 某账户(同设备同账户)是否仍存在 ≥2 笔相同金额的待支付订单。 +// 调度器 execute 反馈判定用(同金额歧义解除 → 提前终止账单页轮询 visit)。 +func (p *WaitPayPool) HasDupAmount(accountID int64) bool { + p.mu.RLock() + defer p.mu.RUnlock() + devID, ok := p.acctDev[accountID] + if !ok { + return false + } + dp := p.devices[devID] + if dp == nil { + return false + } + for _, set := range dp.byAmount[accountID] { + if len(set) >= 2 { + return true + } + } + return false +} + +// Rebuild 全量重建:清空后批量 Add。启动时调用;调用方负责从 DB 加载 WAIT_PAY 订单。 +func (p *WaitPayPool) Rebuild(ctx context.Context, orders []*model.Order) { + p.mu.Lock() + defer p.mu.Unlock() + + p.devices = make(map[string]*devicePool) + p.acctDev = make(map[int64]string) + p.devices[""] = newDevicePool() + + for _, o := range orders { + if o == nil || o.OutTradeNo == "" { + continue + } + if ctx != nil { + select { + case <-ctx.Done(): + logger.Warn("pool", "Rebuild 中途取消", + "reason", ctx.Err(), + "loaded", len(p.acctDev)) + p.publishGlobalLocked() + return + default: + } + } + p.indexLocked(p.orderToEntry(o)) + } + p.publishGlobalLocked() +} + +// Reconcile 对账:入参为当前 DB 中全部 WAIT_PAY 订单(调用方查询)。 +// 与池内容 diff:DB 有池无(Add 遗漏)→ 补入;池有 DB 无(已终态未移除)→ 移除。 +// +// 返回 (added, removed, err)。当前实现无 I/O,err 恒为 nil;保留签名便于未来加 DB 校验。 +func (p *WaitPayPool) Reconcile(ctx context.Context, dbOrders []*model.Order) (added, removed int, err error) { + dbSet := make(map[string]struct{}, len(dbOrders)) + for _, o := range dbOrders { + if o != nil && o.OutTradeNo != "" { + dbSet[o.OutTradeNo] = struct{}{} + } + } + + p.mu.Lock() + defer p.mu.Unlock() + + // 池内 outTradeNo 全集 + poolSet := make(map[string]struct{}) + for _, dp := range p.devices { + for _, m := range dp.buckets { + for k := range m { + poolSet[k] = struct{}{} + } + } + } + + // DB 有池无 → Add + for _, o := range dbOrders { + if o == nil || o.OutTradeNo == "" { + continue + } + if _, exists := poolSet[o.OutTradeNo]; !exists { + p.indexLocked(p.orderToEntry(o)) + added++ + } + } + + // 池有 DB 无 → Remove + for outNo := range poolSet { + if _, exists := dbSet[outNo]; !exists { + for _, dp := range p.devices { + for accID, m := range dp.buckets { + if e, ok := m[outNo]; ok { + p.unindexLocked(e) + delete(m, outNo) + if len(m) == 0 { + delete(dp.buckets, accID) + delete(p.acctDev, accID) + } + removed++ + break + } + } + } + } + } + + if added > 0 || removed > 0 { + logger.Info("pool", "Reconcile 完成", + "added", added, "removed", removed, "total", len(dbOrders)) + } + + p.publishGlobalLocked() + return added, removed, nil +} + +// ReconcileLoop 周期对账(后台 goroutine):每 interval 从 loader 拉取 DB 全部 +// WAIT_PAY 订单并与池 diff(DB 有池无→补入;池有 DB 无→移除),防漂移。 +// loader 由调用方注入(main.go 包 store 查询),本包不依赖 store 避免循环依赖。 +// 退出条件:ctx 取消。 +func (p *WaitPayPool) ReconcileLoop(ctx context.Context, interval time.Duration, loader func() ([]*model.Order, error)) { + if interval <= 0 { + interval = 30 * time.Second + } + ticker := time.NewTicker(interval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + } + if loader == nil { + continue + } + orders, err := loader() + if err != nil { + logger.Warn("pool", "对账加载 DB 待支付订单失败(本周期跳过)", "err", err) + continue + } + if added, removed, rerr := p.Reconcile(ctx, orders); rerr != nil { + logger.Warn("pool", "对账执行失败(本周期跳过)", "err", rerr) + } else if added > 0 || removed > 0 { + logger.Info("pool", "对账同步完成", "added", added, "removed", removed) + } + } +} + +// --- 内部索引维护(必须持写锁) --- + +// lookupLocked 全池按 outTradeNo 定位条目(防御性扫描全部设备/账户桶)。 +func (p *WaitPayPool) lookupLocked(outTradeNo string) (*Entry, bool) { + for _, dp := range p.devices { + for _, m := range dp.buckets { + if e, ok := m[outTradeNo]; ok { + return e, true + } + } + } + return nil, false +} + +// subpoolLocked 获取账户所属设备子池(不存在则创建,更新 acctDev)。 +func (p *WaitPayPool) subpoolLocked(deviceID string) *devicePool { + dp, ok := p.devices[deviceID] + if !ok { + dp = newDevicePool() + p.devices[deviceID] = dp + } + return dp +} + +// indexLocked 将 entry 写入设备子池三张索引表 + 账户→设备反查。 +func (p *WaitPayPool) indexLocked(e *Entry) { + devID := e.DeviceID + if devID == "" { + devID = p.deviceOfEntry(e) + e.DeviceID = devID + } + dp := p.subpoolLocked(devID) + + bm, ok := dp.buckets[e.AccountID] + if !ok { + bm = make(map[string]*Entry) + dp.buckets[e.AccountID] = bm + } + bm[e.OutTradeNo] = e + p.acctDev[e.AccountID] = devID + + if e.NoteCode != "" { + nm, ok := dp.byNote[e.AccountID] + if !ok { + nm = make(map[string]string) + dp.byNote[e.AccountID] = nm + } + nm[e.NoteCode] = e.OutTradeNo + } + + am, ok := dp.byAmount[e.AccountID] + if !ok { + am = make(map[int64]map[string]struct{}) + dp.byAmount[e.AccountID] = am + } + set, ok := am[e.AmountMinor] + if !ok { + set = make(map[string]struct{}) + am[e.AmountMinor] = set + } + set[e.OutTradeNo] = struct{}{} +} + +// unindexLocked 从设备子池三张索引表移除 entry 的所有引用。 +// 注意:buckets 自身的 map[string]*Entry 由调用方负责 delete 与清理空 accountID 桶。 +func (p *WaitPayPool) unindexLocked(e *Entry) { + dp := p.devices[e.DeviceID] + if dp == nil { + return + } + if nm, ok := dp.byNote[e.AccountID]; ok { + if cur, ok := nm[e.NoteCode]; ok && cur == e.OutTradeNo { + delete(nm, e.NoteCode) + if len(nm) == 0 { + delete(dp.byNote, e.AccountID) + } + } + } + if am, ok := dp.byAmount[e.AccountID]; ok { + if set, ok := am[e.AmountMinor]; ok { + delete(set, e.OutTradeNo) + if len(set) == 0 { + delete(am, e.AmountMinor) + if len(am) == 0 { + delete(dp.byAmount, e.AccountID) + } + } + } + } +} + +// publishGlobalLocked 在写锁内重建并发布:全局快照 + 各设备子池 DeviceStat。 +// 调用者必须持有写锁(Lock)。 +func (p *WaitPayPool) publishGlobalLocked() { + now := time.Now() + all := make([]AccountStat, 0, len(p.acctDev)) + grand := 0 + + devIDs := make([]string, 0, len(p.devices)) + for id := range p.devices { + devIDs = append(devIDs, id) + } + sort.Strings(devIDs) + + for _, devID := range devIDs { + dp := p.devices[devID] + stats, total := accountStatsOf(dp, now) + if len(stats) == 0 && total == 0 { + dp.stat.Store(&DeviceStat{DeviceID: devID, UpdatedAt: now}) + continue + } + // 动态窗口(当前设备负载下新增订单的窗口;用于分配器/调度器观测) + win := p.fallback + if p.winCalc != nil { + active, t := 0, 0 + for _, st := range stats { + active++ + t += st.Count + } + if w := p.winCalc(active, t); w > 0 { + win = w + } + } + dp.stat.Store(&DeviceStat{ + DeviceID: devID, + Total: total, + Accounts: stats, + ActiveAccts: len(stats), + CloseWindow: win, + UpdatedAt: now, + }) + all = append(all, stats...) + grand += total + } + + p.snap.Store(&Snapshot{ + Accounts: all, + Total: grand, + UpdatedAt: now, + }) +} + +// accountStatsOf 计算单个设备子池的账户聚合统计(按 accountID 升序,快照稳定)。 +func accountStatsOf(dp *devicePool, now time.Time) ([]AccountStat, int) { + total := 0 + stats := make([]AccountStat, 0, len(dp.buckets)) + accIDs := make([]int64, 0, len(dp.buckets)) + for id := range dp.buckets { + accIDs = append(accIDs, id) + } + sort.Slice(accIDs, func(i, j int) bool { return accIDs[i] < accIDs[j] }) + for _, accID := range accIDs { + m := dp.buckets[accID] + if len(m) == 0 { + continue + } + var ( + oldestCreated time.Time + minClose time.Time + first = true + ) + for _, e := range m { + if first { + oldestCreated = e.CreatedAt + minClose = e.CloseAt + first = false + } else { + if e.CreatedAt.Before(oldestCreated) { + oldestCreated = e.CreatedAt + } + if e.CloseAt.Before(minClose) { + minClose = e.CloseAt + } + } + } + dupAmount := false + if am := dp.byAmount[accID]; len(am) > 0 { + for _, set := range am { + if len(set) >= 2 { // 同金额 ≥2 笔 → 通知栏匹配歧义,需账本页拆单 + dupAmount = true + break + } + } + } + age := now.Sub(oldestCreated) + if age < 0 { + age = 0 + } + ttl := minClose.Sub(now) // 可为负(已超时) + stats = append(stats, AccountStat{ + AccountID: accID, + Count: len(m), + DupAmount: dupAmount, + OldestAge: age, + MinTTL: ttl, + }) + total += len(m) + } + return stats, total +} diff --git a/internal/pool/waitpay_pool_test.go b/internal/pool/waitpay_pool_test.go new file mode 100644 index 0000000..121ad9c --- /dev/null +++ b/internal/pool/waitpay_pool_test.go @@ -0,0 +1,671 @@ +// Package pool 单元测试。 +// +// 设计目标: +// - 每个测试一个独立子测试(t.Run),失败信息含子测试名; +// - 并发测试必须可重复(确定性输出)且 -race 干净; +// - 时间相关测试使用固定基准时间,避免 sleep/flake。 +package pool + +import ( + "context" + "strconv" + "sync" + "sync/atomic" + "testing" + "time" + + "paygateway/internal/model" +) + +// baseTime 测试基准时间(统一基准便于断言 CloseAt/OldestAge) +var baseTime = time.Date(2026, 8, 29, 10, 0, 0, 0, time.UTC) + +// newOrder 构造一个测试订单。Channel 用 "personal.alipay"。 +func newOrder(outTradeNo string, accountID, amountMinor int64, noteCode string, createdAt time.Time) *model.Order { + return &model.Order{ + OutTradeNo: outTradeNo, + AccountID: accountID, + Channel: "personal.alipay", + AmountMinor: amountMinor, + NoteCode: noteCode, + CreatedAt: createdAt, + } +} + +// findAccountStat 工具:按 accountID 取快照元素 +func findAccountStat(snap *Snapshot, accountID int64) *AccountStat { + for i := range snap.Accounts { + if snap.Accounts[i].AccountID == accountID { + return &snap.Accounts[i] + } + } + return nil +} + +// ---------- 1. Add → Snapshot ---------- +func TestWaitPayPool_Add_ShowsInSnapshot(t *testing.T) { + p := NewWaitPayPool(15) + o := newOrder("O1", 100, 1000, "N1", baseTime) + p.Add(o) + + snap := p.Snapshot() + if snap.Total != 1 { + t.Fatalf("Total: want 1, got %d", snap.Total) + } + st := findAccountStat(snap, 100) + if st == nil { + t.Fatalf("账户 100 缺统计") + } + if st.Count != 1 { + t.Fatalf("Count: want 1, got %d", st.Count) + } + // CloseAt 应为 CreatedAt + 15min + if !snap.UpdatedAt.Equal(snap.UpdatedAt) { + t.Fatalf("UpdatedAt 应被设置") + } +} + +// ---------- 2. Add 同 outTradeNo 两次 → 仍 count=1 ---------- +func TestWaitPayPool_Add_IdempotentOverwrite(t *testing.T) { + p := NewWaitPayPool(15) + p.Add(newOrder("O1", 100, 1000, "N1", baseTime)) + p.Add(newOrder("O1", 100, 2000, "N2", baseTime.Add(time.Minute))) // 覆盖 + + snap := p.Snapshot() + if snap.Total != 1 { + t.Fatalf("Total: want 1, got %d", snap.Total) + } + if p.Count(100) != 1 { + t.Fatalf("Count(100): want 1, got %d", p.Count(100)) + } + // 最新 amountMinor 应为 2000 + entry := p.MatchByAmount(100, 2000, baseTime) + if entry == nil || len(entry) != 1 || entry[0].AmountMinor != 2000 { + t.Fatalf("覆盖后 amountMinor=2000 应可命中:got %v", entry) + } + // 旧 amountMinor=1000 已脱离索引 + if got := p.MatchByAmount(100, 1000, baseTime); got != nil { + t.Fatalf("旧 amountMinor=1000 应无候选:got %v", got) + } +} + +// ---------- 3. Remove 不存在 → 不 panic,count 不变 ---------- +func TestWaitPayPool_Remove_NonExistent(t *testing.T) { + p := NewWaitPayPool(15) + p.Add(newOrder("O1", 100, 1000, "N1", baseTime)) + p.Remove("DOES_NOT_EXIST") + if p.Count(100) != 1 { + t.Fatalf("Count 应仍为 1:got %d", p.Count(100)) + } + if p.Snapshot().Total != 1 { + t.Fatalf("Total 应仍为 1") + } +} + +// ---------- 4. Remove 已存在 → count=0 ---------- +func TestWaitPayPool_Remove_Existing(t *testing.T) { + p := NewWaitPayPool(15) + p.Add(newOrder("O1", 100, 1000, "N1", baseTime)) + p.Remove("O1") + if p.Count(100) != 0 { + t.Fatalf("Count 应为 0:got %d", p.Count(100)) + } + if p.Snapshot().Total != 0 { + t.Fatalf("Total 应为 0") + } + // 索引也清理 + if got := p.MatchByNote(100, "N1", baseTime); got != nil { + t.Fatalf("MatchByNote 应 nil:got %v", got) + } + if got := p.MatchByAmount(100, 1000, baseTime); got != nil { + t.Fatalf("MatchByAmount 应 nil:got %v", got) + } +} + +// ---------- 5. Upsert 更新 NoteCode → 新码命中、旧码 miss ---------- +func TestWaitPayPool_Upsert_NoteCodeChange(t *testing.T) { + p := NewWaitPayPool(15) + p.Add(newOrder("O1", 100, 1000, "N1", baseTime)) + p.Upsert(newOrder("O1", 100, 1000, "N2", baseTime)) + + if got := p.MatchByNote(100, "N2", baseTime); got == nil || got.OutTradeNo != "O1" { + t.Fatalf("MatchByNote 新码应命中 O1:got %v", got) + } + if got := p.MatchByNote(100, "N1", baseTime); got != nil { + t.Fatalf("MatchByNote 旧码应 miss:got %v", got) + } +} + +// ---------- 6. MatchByNote 尊重 since ---------- +func TestWaitPayPool_MatchByNote_SinceFilter(t *testing.T) { + p := NewWaitPayPool(15) + old := baseTime.Add(-2 * time.Hour) + p.Add(newOrder("O1", 100, 1000, "N1", old)) + + // since = baseTime → 旧单 CreatedAt < since → nil + if got := p.MatchByNote(100, "N1", baseTime); got != nil { + t.Fatalf("since 应过滤掉旧单:got %v", got) + } + // since = old → 旧单 CreatedAt >= since → 命中 + if got := p.MatchByNote(100, "N1", old); got == nil || got.OutTradeNo != "O1" { + t.Fatalf("since=old 应命中:got %v", got) + } +} + +// ---------- 7. MatchByNote 备注码复用:返回 CreatedAt 最新者 ---------- +func TestWaitPayPool_MatchByNote_ReuseReturnsLatest(t *testing.T) { + p := NewWaitPayPool(15) + // 同一 noteCode TTL 复用:先一笔旧单被 Remove(关单),再补一笔新单 + p.Add(newOrder("O_OLD", 100, 1000, "N1", baseTime.Add(-3*time.Minute))) + p.Add(newOrder("O_NEW", 100, 1000, "N1", baseTime)) + + got := p.MatchByNote(100, "N1", baseTime.Add(-5*time.Minute)) + if got == nil { + t.Fatalf("MatchByNote 应命中") + } + if got.OutTradeNo != "O_NEW" { + t.Fatalf("应返回最新 CreatedAt 的 O_NEW:got %s", got.OutTradeNo) + } +} + +// ---------- 8. MatchByAmount 多候选返回;无候选 nil ---------- +func TestWaitPayPool_MatchByAmount(t *testing.T) { + p := NewWaitPayPool(15) + p.Add(newOrder("O1", 100, 1000, "N1", baseTime)) + p.Add(newOrder("O2", 100, 1000, "N2", baseTime.Add(time.Second))) + p.Add(newOrder("O3", 100, 2000, "N3", baseTime)) + + // 同金额 1000 → 两个候选 + got := p.MatchByAmount(100, 1000, baseTime) + if got == nil { + t.Fatalf("MatchByAmount 应有候选") + } + if len(got) != 2 { + t.Fatalf("候选数:want 2, got %d", len(got)) + } + // 排序稳定性:先 CreatedAt 升序 + if !got[0].CreatedAt.Before(got[1].CreatedAt) { + t.Fatalf("候选排序应按 CreatedAt 升序") + } + + // 2000 → 一个候选 + got2 := p.MatchByAmount(100, 2000, baseTime) + if got2 == nil || len(got2) != 1 || got2[0].OutTradeNo != "O3" { + t.Fatalf("2000 候选异常:got %v", got2) + } + + // 不存在的金额 → nil + if got3 := p.MatchByAmount(100, 9999, baseTime); got3 != nil { + t.Fatalf("无候选应 nil:got %v", got3) + } + + // 不存在的账户 → nil + if got4 := p.MatchByAmount(999, 1000, baseTime); got4 != nil { + t.Fatalf("无账户应 nil:got %v", got4) + } +} + +// ---------- 9. 并发 Add/Remove/Snapshot storm → -race 干净,final 一致 ---------- +func TestWaitPayPool_ConcurrentStorm(t *testing.T) { + p := NewWaitPayPool(15) + // 注入 winCalc 以验证并发 Add 不会触发 map 迭代竞态(P0 修复: orderToEntry 在锁内)。 + p.SetWindowCalc(func(active, total int) time.Duration { + _ = active + _ = total + return 5 * time.Minute + }) + // 初始化一个设备绑定,使 windowFor 走活跃账户计数路径 + p.SetDeviceOf(func(accountID int64) (string, bool) { + return "dev1", true + }) + + const ( + workers = 16 + opsPer = 200 + accBase = 1000 + outPrefix = "O" + ) + + var wg sync.WaitGroup + var added, removed int64 + + for w := 0; w < workers; w++ { + wg.Add(1) + go func(workerID int) { + defer wg.Done() + for i := 0; i < opsPer; i++ { + accID := accBase + int64(workerID) + outNo := outPrefix + strconv.Itoa(workerID) + "_" + strconv.Itoa(i) + note := "N" + strconv.Itoa(i) + switch (workerID + i) % 3 { + case 0: + // Add + p.Add(newOrder(outNo, accID, int64(100+i), note, baseTime.Add(time.Duration(i)*time.Millisecond))) + atomic.AddInt64(&added, 1) + case 1: + // Remove 自己刚 Add 的(可能不存在 → 幂等) + p.Remove(outNo) + atomic.AddInt64(&removed, 1) + case 2: + // Snapshot 读 + snap := p.Snapshot() + if snap == nil { + t.Errorf("Snapshot nil") + return + } + _ = snap.Total + } + } + }(w) + } + + wg.Wait() + + // 终态一致性: + // - 快照 Total == Count(每个账户) 之和 == 池内实际条目数(通过公共 API 统计) + var bucketCount int + for _, st := range p.Snapshot().Accounts { + bucketCount += p.Count(st.AccountID) + } + + snap := p.Snapshot() + if snap.Total != bucketCount { + t.Fatalf("快照 Total(%d) 与 buckets 实际(%d) 不一致", snap.Total, bucketCount) + } + for _, st := range snap.Accounts { + if p.Count(st.AccountID) != st.Count { + t.Fatalf("账户 %d 快照 Count(%d) 与 Count()(实际 %d) 不一致", st.AccountID, st.Count, p.Count(st.AccountID)) + } + } + t.Logf("storm done: added=%d removed=%d final=%d", added, removed, snap.Total) +} + +// ---------- 10. Rebuild 清空后批量加入 ---------- +func TestWaitPayPool_Rebuild_ClearsThenAdds(t *testing.T) { + p := NewWaitPayPool(15) + p.Add(newOrder("OLD1", 100, 1000, "N1", baseTime)) + p.Add(newOrder("OLD2", 200, 2000, "N2", baseTime)) + + orders := []*model.Order{ + newOrder("NEW1", 100, 1000, "N1", baseTime), + newOrder("NEW2", 300, 3000, "N3", baseTime), + newOrder("NEW3", 300, 4000, "N4", baseTime), + } + p.Rebuild(context.Background(), orders) + + if p.Count(200) != 0 { + t.Fatalf("旧账户 200 应清空:got %d", p.Count(200)) + } + if p.Count(100) != 1 { + t.Fatalf("账户 100 应仅剩 NEW1:got %d", p.Count(100)) + } + if p.Count(300) != 2 { + t.Fatalf("账户 300 应有 2 单:got %d", p.Count(300)) + } + if p.Snapshot().Total != 3 { + t.Fatalf("Total 应 3") + } +} + +// ---------- 11. Reconcile:DB-only / pool-only / shared 三类 ---------- +func TestWaitPayPool_Reconcile_Diff(t *testing.T) { + p := NewWaitPayPool(15) + + // 池内:SHARED + POOL_ONLY + p.Add(newOrder("SHARED", 100, 1000, "N1", baseTime)) + p.Add(newOrder("POOL_ONLY", 100, 2000, "N2", baseTime)) + + // DB:SHARED + DB_ONLY + dbOrders := []*model.Order{ + newOrder("SHARED", 100, 1000, "N1", baseTime), + newOrder("DB_ONLY", 200, 3000, "N3", baseTime), + } + + added, removed, err := p.Reconcile(context.Background(), dbOrders) + if err != nil { + t.Fatalf("err: %v", err) + } + if added != 1 || removed != 1 { + t.Fatalf("added/removed: want 1/1, got %d/%d", added, removed) + } + + // SHARED 仍存在(POOL_ONLY 已被移除 → 账户 100 仅剩 SHARED) + if p.Count(100) != 1 { + t.Fatalf("账户 100 应有 1 单(POOL_ONLY 已移除):got %d", p.Count(100)) + } + if got := p.MatchByAmount(100, 1000, baseTime); got == nil || len(got) != 1 || got[0].OutTradeNo != "SHARED" { + t.Fatalf("SHARED 应命中:got %v", got) + } + // POOL_ONLY 已移除 + if got := p.MatchByAmount(100, 2000, baseTime); got != nil { + t.Fatalf("POOL_ONLY 应被移除:got %v", got) + } + // DB_ONLY 已补入 + if got := p.MatchByAmount(200, 3000, baseTime); got == nil || len(got) != 1 || got[0].OutTradeNo != "DB_ONLY" { + t.Fatalf("DB_ONLY 应补入:got %v", got) + } + if p.Snapshot().Total != 2 { + t.Fatalf("Total 应 2(SHARED+DB_ONLY)") + } +} + +// ---------- 12. closeWindowMinutes=0 → CloseAt 默认 CreatedAt+30min ---------- +func TestWaitPayPool_CloseAt_DefaultWhenZero(t *testing.T) { + p := NewWaitPayPool(0) // 显式 0 → 走 30min 兜底 + p.Add(newOrder("O1", 100, 1000, "N1", baseTime)) + + e := p.MatchByNote(100, "N1", baseTime) + if e == nil { + t.Fatalf("MatchByNote 应命中") + } + wantClose := baseTime.Add(30 * time.Minute) + if !e.CloseAt.Equal(wantClose) { + t.Fatalf("CloseAt: want %v, got %v", wantClose, e.CloseAt) + } +} + +// ---------- closeWindowMinutes=-1(负数)→ 同样走默认 30min ---------- +func TestWaitPayPool_CloseAt_DefaultWhenNegative(t *testing.T) { + p := NewWaitPayPool(-5) + p.Add(newOrder("O1", 100, 1000, "N1", baseTime)) + + e := p.MatchByNote(100, "N1", baseTime) + if e == nil { + t.Fatalf("MatchByNote 应命中") + } + if !e.CloseAt.Equal(baseTime.Add(30 * time.Minute)) { + t.Fatalf("CloseAt 默认 30min:got %v", e.CloseAt) + } +} + +// ---------- Upsert 改变 Amount → byAmount 索引正确迁移 ---------- +func TestWaitPayPool_Upsert_AmountChange(t *testing.T) { + p := NewWaitPayPool(15) + p.Add(newOrder("O1", 100, 1000, "N1", baseTime)) + p.Upsert(newOrder("O1", 100, 2000, "N1", baseTime)) + + if got := p.MatchByAmount(100, 1000, baseTime); got != nil { + t.Fatalf("旧金额 1000 应无候选:got %v", got) + } + got := p.MatchByAmount(100, 2000, baseTime) + if got == nil || len(got) != 1 || got[0].OutTradeNo != "O1" { + t.Fatalf("新金额 2000 应命中 O1:got %v", got) + } +} + +// ---------- Remove 后 Reconcile 不应再次"添加"已终态订单 ---------- +func TestWaitPayPool_Reconcile_NoGhostResurrection(t *testing.T) { + p := NewWaitPayPool(15) + p.Add(newOrder("O1", 100, 1000, "N1", baseTime)) + p.Remove("O1") + + // DB 已不包含 O1 → Reconcile 不应补回 + added, removed, err := p.Reconcile(context.Background(), nil) + if err != nil || added != 0 || removed != 0 { + t.Fatalf("空 DB:want 0/0/nil, got %d/%d/%v", added, removed, err) + } + if p.Count(100) != 0 { + t.Fatalf("Count 应 0:got %d", p.Count(100)) + } +} + +// ---------- Count 无账户 → 0;不 panic ---------- +func TestWaitPayPool_Count_UnknownAccount(t *testing.T) { + p := NewWaitPayPool(15) + if p.Count(999) != 0 { + t.Fatalf("未知账户应 0") + } +} + +// ---------- Snapshot.Accounts 按 accountID 升序(稳定) ---------- +func TestWaitPayPool_Snapshot_AccountsOrdered(t *testing.T) { + p := NewWaitPayPool(15) + p.Add(newOrder("O1", 300, 100, "", baseTime)) + p.Add(newOrder("O2", 100, 100, "", baseTime)) + p.Add(newOrder("O3", 200, 100, "", baseTime)) + + snap := p.Snapshot() + want := []int64{100, 200, 300} + for i, st := range snap.Accounts { + if st.AccountID != want[i] { + t.Fatalf("Accounts[%d]: want %d, got %d", i, want[i], st.AccountID) + } + } +} + +// ---------- 同一 outTradeNo 不同账户:Remove 全局命中 ---------- +func TestWaitPayPool_Remove_GloballyUnique(t *testing.T) { + // 说明:业务上 outTradeNo 全局唯一(接入方订单号),但池内部按 accountID 桶索引; + // 若误把同一 outTradeNo 加到两个账户(人为错误),Remove 应清理所有桶中的同名条目。 + p := NewWaitPayPool(15) + p.Add(newOrder("O1", 100, 1000, "", baseTime)) + p.Add(newOrder("O1", 200, 1000, "", baseTime)) // 同 outTradeNo,不同账户(业务不应发生;测试索引清理) + + p.Remove("O1") + if p.Count(100) != 0 || p.Count(200) != 0 { + t.Fatalf("Remove 应清理所有桶中同名:got 100=%d 200=%d", p.Count(100), p.Count(200)) + } +} + +// ---------- MinTTL 负值(已超时)---------- +func TestWaitPayPool_Snapshot_MinTTL_Overdue(t *testing.T) { + p := NewWaitPayPool(0) // 30min closeWindow + // 单子 40 分钟前创建 → 一定已超时 + old := time.Now().Add(-40 * time.Minute) + p.Add(newOrder("O1", 100, 1000, "", old)) + + snap := p.Snapshot() + st := findAccountStat(snap, 100) + if st == nil { + t.Fatalf("缺账户统计") + } + if st.MinTTL >= 0 { + t.Fatalf("MinTTL 应 <0(已超时):got %v", st.MinTTL) + } +} + +// ---------- 13. 设备维度子池:DeviceSnapshot 按设备隔离 ---------- +func TestWaitPayPool_DeviceSnapshot_Isolation(t *testing.T) { + p := NewWaitPayPool(15) + devOf := func(accountID int64) (string, bool) { + switch accountID { + case 100, 101: + return "devA", true + case 200: + return "devB", true + default: + return "", false + } + } + p.SetDeviceOf(devOf) + p.Add(newOrder("A1", 100, 1000, "N1", baseTime)) + p.Add(newOrder("A2", 101, 2000, "N2", baseTime)) + p.Add(newOrder("B1", 200, 3000, "N3", baseTime)) + + da := p.DeviceSnapshot("devA") + if da == nil || da.Total != 2 || da.ActiveAccts != 2 { + t.Fatalf("devA: want Total=2 ActiveAccts=2, got %+v", da) + } + db := p.DeviceSnapshot("devB") + if db == nil || db.Total != 1 || db.ActiveAccts != 1 { + t.Fatalf("devB: want Total=1 ActiveAccts=1, got %+v", db) + } + // 全局快照不受设备拆分影响 + if p.Snapshot().Total != 3 { + t.Fatalf("全局 Total: want 3, got %d", p.Snapshot().Total) + } + // 匹配/计数按账户命中(跨设备不混淆) + if p.Count(100) != 1 || p.Count(200) != 1 { + t.Fatalf("Count 异常: 100=%d 200=%d", p.Count(100), p.Count(200)) + } + if got := p.MatchByNote(100, "N1", baseTime); got == nil || got.OutTradeNo != "A1" { + t.Fatalf("MatchByNote devA 应命中 A1: %v", got) + } + if got := p.MatchByNote(200, "N3", baseTime); got == nil || got.OutTradeNo != "B1" { + t.Fatalf("MatchByNote devB 应命中 B1: %v", got) + } +} + +// ---------- 14. 动态关单窗口:winCalc 注入后 CloseAt 随负载变化且 clamp ---------- +func TestWaitPayPool_DynamicCloseAt_WindowCalc(t *testing.T) { + p := NewWaitPayPool(0) // fallback 30min,但 winCalc 优先 + // winCalc: 负载越高窗口越长,clamp [5min, 10min] + p.SetWindowCalc(func(active, total int) time.Duration { + w := 5*time.Minute + time.Duration(total)*time.Minute + if w > 10*time.Minute { + w = 10 * time.Minute + } + return w + }) + // 第 1 单:total=0 → 5min + p.Add(newOrder("O1", 100, 1000, "N1", baseTime)) + e1 := p.MatchByNote(100, "N1", baseTime) + if e1 == nil { + t.Fatalf("MatchByNote 应命中") + } + want1 := baseTime.Add(5 * time.Minute) + if !e1.CloseAt.Equal(want1) { + t.Fatalf("第 1 单 CloseAt: want %v, got %v", want1, e1.CloseAt) + } + // 第 2 单:total=1 → 6min + p.Add(newOrder("O2", 100, 2000, "N2", baseTime)) + e2 := p.MatchByNote(100, "N2", baseTime) + if e2 == nil { + t.Fatalf("第 2 单应命中") + } + want2 := baseTime.Add(6 * time.Minute) + if !e2.CloseAt.Equal(want2) { + t.Fatalf("第 2 单 CloseAt: want %v, got %v", want2, e2.CloseAt) + } + // 高负载下窗口应受上限 clamp(模拟 8+ 单 → 10min) + p.Remove("O1") + p.Remove("O2") + for i := 0; i < 12; i++ { + p.Add(newOrder("HI"+string(rune('A'+i)), int64(300+i), 1000, "", baseTime)) + } + hi := p.MatchByAmount(311, 1000, baseTime) // 最后一单 + if hi == nil || len(hi) != 1 { + t.Fatalf("高负载单应命中: %v", hi) + } + // 该设备累计 12 单 → winCalc → 10min 上限 + if !hi[0].CloseAt.Equal(baseTime.Add(10 * time.Minute)) { + t.Fatalf("高负载 CloseAt 应 clamp 到 10min: got %v", hi[0].CloseAt) + } +} + +// ---------- 15. CloseAt 单调只增:Upsert 覆盖时不倒退 ---------- +func TestWaitPayPool_CloseAt_Monotonic(t *testing.T) { + p := NewWaitPayPool(15) + // 初始窗口 15min + p.Add(newOrder("O1", 100, 1000, "N1", baseTime)) + first := p.MatchByNote(100, "N1", baseTime) + if first == nil { + t.Fatalf("应命中") + } + // 切换到固定 5min 窗口后 Upsert:CloseAt 不得早于旧值 + p.SetWindowCalc(func(active, total int) time.Duration { return 5 * time.Minute }) + p.Upsert(newOrder("O1", 100, 1000, "N2", baseTime.Add(time.Minute))) + got := p.MatchByNote(100, "N2", baseTime) + if got == nil { + t.Fatalf("Upsert 后应命中") + } + if got.CloseAt.Before(first.CloseAt) { + t.Fatalf("CloseAt 单调只增被违反: first=%v got=%v", first.CloseAt, got.CloseAt) + } + // 新窗口 5min 在 CreatedAt+1min 后 → CreatedAt+6min < first(15min) → 保持 15min + want := baseTime.Add(15 * time.Minute) + if !got.CloseAt.Equal(want) { + t.Fatalf("CloseAt 应保持旧值 15min: want %v, got %v", want, got.CloseAt) + } +} + +// ---------- 16. CloseAtOf 查询(扫描器动态关单判断)---------- +func TestWaitPayPool_CloseAtOf(t *testing.T) { + p := NewWaitPayPool(15) + p.Add(newOrder("O1", 100, 1000, "N1", baseTime)) + ct, ok := p.CloseAtOf("O1") + if !ok { + t.Fatalf("CloseAtOf 应命中") + } + if !ct.Equal(baseTime.Add(15 * time.Minute)) { + t.Fatalf("CloseAtOf: want 15min, got %v", ct) + } + if _, ok := p.CloseAtOf("NO_SUCH"); ok { + t.Fatalf("CloseAtOf 未知订单应 not found") + } + p.Remove("O1") + if _, ok := p.CloseAtOf("O1"); ok { + t.Fatalf("CloseAtOf 移除后应 not found") + } +} + +// ---------- 17. SetDeviceOf 重分桶:绑定变化后条目迁移到新设备 ---------- +func TestWaitPayPool_SetDeviceOf_Repartition(t *testing.T) { + p := NewWaitPayPool(15) + p.Add(newOrder("O1", 100, 1000, "N1", baseTime)) // 无 deviceOf,归 "" + if st := p.DeviceSnapshot(""); st == nil || st.Total != 1 { + t.Fatalf("注入前应归兜底桶: %+v", st) + } + // 注入映射:100 → devX + p.SetDeviceOf(func(accountID int64) (string, bool) { + if accountID == 100 { + return "devX", true + } + return "", false + }) + if st := p.DeviceSnapshot("devX"); st == nil || st.Total != 1 { + t.Fatalf("注入后应迁移到 devX: %+v", st) + } + if st := p.DeviceSnapshot(""); st == nil || st.Total != 0 { + t.Fatalf("兜底桶应清空: %+v", st) + } + // 匹配链路仍按账户命中 + if got := p.MatchByNote(100, "N1", baseTime); got == nil || got.OutTradeNo != "O1" { + t.Fatalf("迁移后匹配应命中: %v", got) + } +} + +// ---------- 18. DupAmount:同账户同金额 ≥2 笔 → true;金额互异 → false ---------- +func TestWaitPayPool_DupAmount(t *testing.T) { + p := NewWaitPayPool(15) + + // 账户100: 两笔不同金额 → 无同金额 + p.Add(newOrder("A1", 100, 1000, "", baseTime)) + p.Add(newOrder("A2", 100, 2000, "", baseTime.Add(time.Minute))) + // 账户200: 两笔同为 3000 → 有同金额 + p.Add(newOrder("B1", 200, 3000, "", baseTime)) + p.Add(newOrder("B2", 200, 3000, "", baseTime.Add(time.Minute))) + + snap := p.Snapshot() + if st := findAccountStat(snap, 100); st == nil || st.DupAmount { + t.Fatalf("账户100 金额互异应 DupAmount=false, got %+v", st) + } + if st := findAccountStat(snap, 200); st == nil || !st.DupAmount { + t.Fatalf("账户200 同金额 2 笔应 DupAmount=true, got %+v", st) + } + + if p.HasDupAmount(100) { + t.Fatalf("HasDupAmount(100) 应 false") + } + if !p.HasDupAmount(200) { + t.Fatalf("HasDupAmount(200) 应 true") + } + if p.HasDupAmount(999) { + t.Fatalf("HasDupAmount 未知账户应 false") + } +} + +// ---------- 19. HasDupAmount:同一账户同金额消除(移除一笔/改金额)→ 转 false ---------- +func TestWaitPayPool_HasDupAmount_Resolved(t *testing.T) { + p := NewWaitPayPool(15) + p.Add(newOrder("B1", 200, 3000, "", baseTime)) + p.Add(newOrder("B2", 200, 3000, "", baseTime.Add(time.Minute))) + if !p.HasDupAmount(200) { + t.Fatalf("初始应 true") + } + // 移除一笔 → 只剩一笔 3000 → 歧义解除 + p.Remove("B2") + if p.HasDupAmount(200) { + t.Fatalf("移除一笔后同金额消除应 false") + } +} diff --git a/internal/ratelimit/ratelimit.go b/internal/ratelimit/ratelimit.go new file mode 100644 index 0000000..771c007 --- /dev/null +++ b/internal/ratelimit/ratelimit.go @@ -0,0 +1,108 @@ +// Package ratelimit 提供基于 IP 的滑动窗口速率限制中间件。 +// +// 用途:保护公网支付接口(/qr/real、/qr/refresh、/submit.php 等), +// 防止单 IP 刷接口 / 暴力枚举 token。多实例部署需改用 Redis 后端,本实现仅覆盖单进程。 +package ratelimit + +import ( + "net/http" + "strconv" + "sync" + "time" + + "github.com/gin-gonic/gin" +) + +// limiter 公网 IP 速率限制器实例。 +type limiter struct { + mu sync.Mutex + buckets map[string]*bucket + max int // 窗口内最大请求数 + window time.Duration // 滑动窗口长度 +} + +type bucket struct { + mu sync.Mutex + stamp []time.Time // 窗口内请求时间戳 +} + +func newLimiter(max int, window time.Duration) *limiter { + return &limiter{buckets: make(map[string]*bucket), max: max, window: window} +} + +// allow 判定是否放行;不放行则返回剩余重试等待秒。 +func (l *limiter) allow(key string, now time.Time) (bool, int) { + l.mu.Lock() + b, ok := l.buckets[key] + if !ok { + b = &bucket{} + l.buckets[key] = b + } + l.mu.Unlock() + + b.mu.Lock() + defer b.mu.Unlock() + cutoff := now.Add(-l.window) + keep := b.stamp[:0] + for _, t := range b.stamp { + if t.After(cutoff) { + keep = append(keep, t) + } + } + if len(keep) >= l.max { + b.stamp = keep + // 计算最早一条何时滑出窗口,作为 retry-after + retry := int(keep[0].Add(l.window).Sub(now).Seconds()) + if retry < 1 { + retry = 1 + } + return false, retry + } + b.stamp = append(keep, now) + return true, 0 +} + +// prune 清理无活动桶,避免内存泄漏。 +func (l *limiter) prune(now time.Time) { + cutoff := now.Add(-l.window) + l.mu.Lock() + defer l.mu.Unlock() + for key, b := range l.buckets { + b.mu.Lock() + stale := len(b.stamp) == 0 || (len(b.stamp) > 0 && b.stamp[len(b.stamp)-1].Before(cutoff)) + b.mu.Unlock() + if stale { + delete(l.buckets, key) + } + } +} + +// IPRateLimit 返回一个 gin 中间件:按 c.ClientIP() 限流,每 window 最多 max req。 +// 触发上限时返回 429 + Retry-After 头。 +// 通过 max=0 可禁用(直接 next),便于配置可关闭。 +func IPRateLimit(max int, window time.Duration) gin.HandlerFunc { + if max <= 0 || window <= 0 { + return func(c *gin.Context) { c.Next() } + } + l := newLimiter(max, window) + var pruneCounter int + return func(c *gin.Context) { + ip := c.ClientIP() + now := time.Now() + // 每 64 请求清理一次过期桶,平摊成本 + pruneCounter++ + if pruneCounter%64 == 0 { + l.prune(now) + } + ok, retry := l.allow(ip, now) + if !ok { + c.Header("Retry-After", strconv.Itoa(retry)) + c.AbortWithStatusJSON(http.StatusTooManyRequests, gin.H{ + "error": "请求过于频繁,请稍后再试", + "retry_after_sec": retry, + }) + return + } + c.Next() + } +} diff --git a/internal/reconciler/dailyreport.go b/internal/reconciler/dailyreport.go new file mode 100755 index 0000000..0a9ac75 --- /dev/null +++ b/internal/reconciler/dailyreport.go @@ -0,0 +1,166 @@ +// Package reconciler 日终对账报告(RESILIENCE.md P2-1) +// +// 对账维度(网关 vs 通道 vs 上游): +// - 网关订单:按通道拆分(merchant.* / personal.*),统计订单数/成功数/金额 +// - 资金核对:成功订单金额 vs 订单金额(差异 = 异常池 / 金额不匹配单) +// - 回调可靠性:notify_tasks 各状态数量(pending / success / dead) +// - 异常池:待处理数量(自动化兜底失败的最终兜底) +// - 结论:每通道差异明细,供人工核对 +// +// 每日 00:00 由 main 调用(与账本快照/日限额重置同周期),输出结构化日志; +// 管理后台「对账中心」可实时查询(GET /api/reconciler/daily-report?date=YYYY-MM-DD)。 +package reconciler + +import ( + "context" + "fmt" + "strings" + "time" + + "paygateway/internal/channel" + "paygateway/internal/logger" + "paygateway/internal/model" + "paygateway/internal/store" +) + +// ChannelReport 单通道对账 +type ChannelReport struct { + Channel string `json:"channel"` + TotalOrders int64 `json:"total_orders"` // 当日订单 + SuccessOrders int64 `json:"success_orders"` // 当日成功 + ClosedOrders int64 `json:"closed_orders"` // 当日关闭 + AmountMinor int64 `json:"amount_minor"` // 当日成功金额(分) + AmountClosed int64 `json:"amount_closed"` // 当日关闭金额(分) +} + +// DailyReport 日终对账报告 +type DailyReport struct { + Date string `json:"date"` // 2026-08-10 + + OrdersTotal int64 `json:"orders_total"` + OrdersSuccess int64 `json:"orders_success"` + OrdersClosed int64 `json:"orders_closed"` + SuccessAmount int64 `json:"success_amount"` // 成功金额(分) + ClosedAmount int64 `json:"closed_amount"` // 关闭金额(分) + + NotifyPending int64 `json:"notify_pending"` // 待投递回调 + NotifySuccess int64 `json:"notify_success"` + NotifyDead int64 `json:"notify_dead"` // 死信(需人工重发) + + ExceptionsPending int64 `json:"exceptions_pending"` // 异常池待处理 + + ByChannel []*ChannelReport `json:"by_channel"` // 通道拆分 + + Conclusion string `json:"conclusion"` // 对账结论 +} + +// BuildDailyReport 生成指定日期的对账报告(date 格式 2006-01-02,空=今天) +func BuildDailyReport(ctx context.Context, st store.Store, date string) (*DailyReport, error) { + if date == "" { + date = time.Now().Format("2006-01-02") + } + r := &DailyReport{Date: date, ByChannel: []*ChannelReport{}} + + // 1. 网关侧总量 + total, err := st.CountOrders(ctx, "") + if err != nil { + return nil, err + } + success, err := st.CountOrders(ctx, model.StatusSuccess) + if err != nil { + return nil, err + } + closed, err := st.CountOrders(ctx, model.StatusClosed) + if err != nil { + return nil, err + } + r.OrdersTotal, r.OrdersSuccess, r.OrdersClosed = total, success, closed + + if successAmount, err := st.SumAmount(ctx, model.StatusSuccess); err == nil { + r.SuccessAmount = successAmount + } + if closedAmount, err := st.SumAmount(ctx, model.StatusClosed); err == nil { + r.ClosedAmount = closedAmount + } + + // 2. 按通道拆分(当日口径:订单/成功/关闭 + 金额) + for _, name := range channel.AllNames() { + total, _, err := st.DailyStatsByChannel(ctx, name, "", date) + if err != nil { + continue + } + succ, succAmount, err := st.DailyStatsByChannel(ctx, name, model.StatusSuccess, date) + if err != nil { + continue + } + closed, closedAmount, err := st.DailyStatsByChannel(ctx, name, model.StatusClosed, date) + if err != nil { + continue + } + r.ByChannel = append(r.ByChannel, &ChannelReport{ + Channel: name, TotalOrders: total, SuccessOrders: succ, + ClosedOrders: closed, AmountMinor: succAmount, AmountClosed: closedAmount, + }) + } + + // 3. 回调可靠性 + if n, err := st.CountNotifyTasks(ctx, "pending"); err == nil { + r.NotifyPending = n + } + if n, err := st.CountNotifyTasks(ctx, "success"); err == nil { + r.NotifySuccess = n + } + if n, err := st.CountNotifyTasks(ctx, "dead"); err == nil { + r.NotifyDead = n + } + + // 4. 异常池 + if n, err := st.CountExceptions(ctx, model.ExPhasePending); err == nil { + r.ExceptionsPending = n + } + + // 5. 结论 + r.Conclusion = buildConclusion(r) + return r, nil +} + +// buildConclusion 对账结论:差异分析 +func buildConclusion(r *DailyReport) string { + var issues []string + // 金额对账:成功金额 vs 订单金额差异来自异常池(金额不匹配单不计入成功) + // 注意:当日成功金额 CountOrdersByChannelToday 是全天累计;全局成功金额是历史累计, + // 因此这里不做全局金额等式校验,改为提示性结论: + if r.NotifyDead > 0 { + issues = append(issues, fmt.Sprintf("有 %d 笔回调 dead(需人工重发)", r.NotifyDead)) + } + if r.ExceptionsPending > 0 { + issues = append(issues, fmt.Sprintf("异常池有 %d 笔待处理(资金安全关键,请及时人工核实)", r.ExceptionsPending)) + } + for _, c := range r.ByChannel { + if c.SuccessOrders > 0 && c.ClosedOrders > 0 { + // 提示关闭占比过高的通道 + if float64(c.ClosedOrders)/float64(c.SuccessOrders+c.ClosedOrders) > 0.3 { + issues = append(issues, fmt.Sprintf("通道 %s 关闭率偏高(成功 %d / 关闭 %d),注意观察", c.Channel, c.SuccessOrders, c.ClosedOrders)) + } + } + } + if len(issues) == 0 { + return "✅ 对账正常:无 dead 回调、无待处理异常" + } + return "⚠️ 需要关注:" + strings.Join(issues, ";") +} + +// Log 输出结构化日志(每日 00:00 调用) +func (r *DailyReport) Log() { + logger.Info("reconciler", "=== 日终对账报告 "+r.Date+" ===") + logger.Info("reconciler", "订单", "total", r.OrdersTotal, "success", r.OrdersSuccess, + "closed", r.OrdersClosed, "success_amount", r.SuccessAmount) + for _, c := range r.ByChannel { + logger.Info("reconciler", "通道", "name", c.Channel, "total", c.TotalOrders, + "success", c.SuccessOrders, "closed", c.ClosedOrders, + "amount", c.AmountMinor, "closed_amount", c.AmountClosed) + } + logger.Info("reconciler", "回调", "pending", r.NotifyPending, "success", r.NotifySuccess, "dead", r.NotifyDead) + logger.Info("reconciler", "异常池待处理", "count", r.ExceptionsPending) + logger.Info("reconciler", "结论", "text", r.Conclusion) +} diff --git a/internal/reconciler/devicewatcher.go b/internal/reconciler/devicewatcher.go new file mode 100755 index 0000000..8b8f12a --- /dev/null +++ b/internal/reconciler/devicewatcher.go @@ -0,0 +1,363 @@ +// Package reconciler 设备离线检测任务(个人免签通道) +// +// 设计(docs/MIANQIAN.md §9 掉线兜底与异常处理): +// - 设备离线判定以 adb 可达性为准(监听 App 已弃用,2026-08-29 重构) +// - 设备 OFFLINE → 其绑定的收款账户冻结(不可接新单) +// - 该账户下已下单未支付的订单 → 进人工异常池(listen_offline), +// 人工从收款记录核对实际收款情况(用户可能已付款但未监听到) +// +// 与 ConfirmScheduler 的 passive 检查互补: +// - ConfirmScheduler.confirmPersonal 在关单时被动检查监听端是否离线 +// - 本任务主动扫描:设备一掉线立即冻结账户 + 挂起订单,不等关单时间 +package reconciler + +import ( + "context" + "encoding/json" + "fmt" + "strconv" + "time" + + "paygateway/internal/account" + "paygateway/internal/adbmgr" + "paygateway/internal/logger" + "paygateway/internal/model" + "paygateway/internal/order" + "paygateway/internal/personal" + "paygateway/internal/store" +) + +// AdbProber 检测 adb 是否可达的接口(adbmgr.Manager 已实现此语义;抽象为接口便于 mock 与可选依赖) +type AdbProber interface { + Online(ctx context.Context, serial string) bool + Connect(ctx context.Context, serial string) error +} + +// DeviceWatcherConfig 设备离线检测参数 +type DeviceWatcherConfig struct { + HeartbeatTimeout time.Duration // 心跳超时(默认 120s) + ScanInterval time.Duration // 扫描间隔(默认 30s) + // MissThreshold 连续扫描未收到心跳的次数阈值,达到才判离线(默认 2)。 + // 配合 HeartbeatTimeout 形成「超时 + 连续确认」双保险:单次扫描抖动不误判。 + MissThreshold int + // ControlPlaneInterval 控制面状态机扫描间隔(默认 30s)。 + // 独立于 ScanInterval:心跳超时与 adb 重连关注点不同,避免耦合。 + ControlPlaneInterval time.Duration +} + +// DefaultDeviceWatcherConfig 默认参数 +func DefaultDeviceWatcherConfig() *DeviceWatcherConfig { + return &DeviceWatcherConfig{ + HeartbeatTimeout: 120 * time.Second, + ScanInterval: 30 * time.Second, + MissThreshold: 2, + ControlPlaneInterval: 30 * time.Second, + } +} + +// Recoverer 设备离线恢复器(由 admin 包实现并注入),解耦 reconcile → admin 反向依赖。 +// 实现侧负责限频,避免短时间对同一设备反复重启。 +type Recoverer interface { + AttemptRestart(ctx context.Context, id int64) error +} + +// DeviceWatcher 监听端设备离线检测器 +type DeviceWatcher struct { + pdb store.PersonalStore + pool *account.AccountPool + st store.Store + svc *order.Service + cfg *DeviceWatcherConfig + recoverer Recoverer // nil = 关闭自动重启 + adb AdbProber // nil = 关闭控制面状态机 + + // missCount 记录每设备连续超时扫描次数(按设备 ID),达到 MissThreshold 才判离线 + missCount map[int64]int + // controlPlaneLast 记录每设备上次控制面扫描时间(按 device_id),限频 + controlPlaneLast map[string]time.Time +} + +// NewDeviceWatcher 创建设备离线检测器 +// recoverer 可为 nil(关闭自动重启能力,行为退化为原始的「只冻结+挂单」语义)。 +// adb 可为 nil(关闭控制面状态机,行为退化为「仅心跳冻结+挂单」语义)。 +func NewDeviceWatcher(pdb store.PersonalStore, pool *account.AccountPool, st store.Store, svc *order.Service, cfg *DeviceWatcherConfig, recoverer Recoverer, adb AdbProber) *DeviceWatcher { + if cfg == nil { + cfg = DefaultDeviceWatcherConfig() + } + if cfg.HeartbeatTimeout <= 0 { + cfg.HeartbeatTimeout = 120 * time.Second + } + if cfg.ScanInterval <= 0 { + cfg.ScanInterval = 30 * time.Second + } + if cfg.MissThreshold <= 0 { + cfg.MissThreshold = 2 + } + if cfg.ControlPlaneInterval <= 0 { + cfg.ControlPlaneInterval = 30 * time.Second + } + return &DeviceWatcher{ + pdb: pdb, pool: pool, st: st, svc: svc, cfg: cfg, recoverer: recoverer, + adb: adb, + missCount: make(map[int64]int), + controlPlaneLast: make(map[string]time.Time), + } +} + +// AsAdbProber 把 *adbmgr.Manager 适配为 AdbProber 接口(避免在 reconciler 包直接 import adbmgr 类型)。 +func AsAdbProber(m *adbmgr.Manager) AdbProber { + if m == nil { + return nil + } + return adbProberAdapter{m} +} + +type adbProberAdapter struct{ m *adbmgr.Manager } + +func (a adbProberAdapter) Online(ctx context.Context, serial string) bool { + return a.m.Online(ctx, serial) +} +func (a adbProberAdapter) Connect(ctx context.Context, serial string) error { + return a.m.Connect(ctx, serial) +} + +// Start 启动(阻塞,放 goroutine) +func (w *DeviceWatcher) Start(ctx context.Context) { + logger.Info("reconciler", "监听端离线检测已启动", + "heartbeat_timeout", w.cfg.HeartbeatTimeout.String(), + "scan_interval", w.cfg.ScanInterval.String()) + ticker := time.NewTicker(w.cfg.ScanInterval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + logger.Info("reconciler", "监听端离线检测停止") + return + case <-ticker.C: + w.scan(ctx) + // 控制面状态机:adb prober 配置时才跑 + if w.adb != nil { + w.scanControlPlane(ctx) + } + } + } +} + +// scan 单次扫描:以 adb 可达性为准维护设备在线状态(2026-08-29 重构:脱离监听 App 依赖): +// - adb 可达(USB/WiFi/LAN,含自动 adb connect 重连)→ 视为在线; +// 若此前离线(offline)则恢复 online + 解冻离线联动冻结的账户 +// - adb 不可达 → 回退心跳超时判定,超时判离线并冻结账户 +func (w *DeviceWatcher) scan(ctx context.Context) { + devs, err := w.pdb.ListDevices(ctx) + if err != nil { + logger.Error("reconciler", "设备列表查询失败", "err", err) + return + } + for _, d := range devs { + // 手动冻结/风控冻结的设备:不自动处理(仅人工解冻) + if d.Status == string(personal.DeviceFrozen) { + delete(w.missCount, d.ID) + continue + } + // adb 可达优先:设备 serial 可连(自动 adb connect)→ 视为在线 + if w.adb != nil { + if serial := adbSerialOf(d); serial != "" && w.adb.Online(ctx, serial) { + if d.Status != string(personal.DeviceOnline) { + w.recoverOnline(ctx, d) // 离线→在线:置 online + 解冻账户 + } + delete(w.missCount, d.ID) + continue + } + } + // adb 不可达(且 status=online):连续 miss 达阈值判离线 + 冻结联动。 + // 2026-08-30 重构:监听端 APP 弃用,LastHeartbeat 不再更新——此分支语义从 + // "心跳超时"收敛为"adb 不可达确认"(纯 adb 控制下 adb 是唯一存活信号), + // 保留 missCount 防抖避免瞬时 adb 抖动误判离线。 + if d.Status != string(personal.DeviceOnline) { + delete(w.missCount, d.ID) + continue + } + w.countMiss(d.ID) + if w.missCount[d.ID] >= w.cfg.MissThreshold { + w.handleOffline(ctx, d, "adb 不可达(连续 "+strconv.Itoa(w.cfg.MissThreshold)+" 次扫描确认)") + delete(w.missCount, d.ID) + } + } +} + +// recoverOnline 设备 adb 恢复可达 → 置 online + 解冻离线联动冻结的绑定账户。 +// 仅解冻 FrozenReason=offline 的账户;风控/手动冻结的账户保持 frozen(资金安全)。 +func (w *DeviceWatcher) recoverOnline(ctx context.Context, d *personal.DeviceEncrypted) { + logger.Info("reconciler", "设备 adb 恢复,置 online + 解冻离线联动账户", "device_id", d.DeviceID, "name", d.Name) + if err := w.pdb.SetDeviceStatus(ctx, d.ID, string(personal.DeviceOnline)); err != nil { + logger.Error("reconciler", "设备置在线失败", "err", err, "device_id", d.DeviceID) + } + for _, acctID := range store.DeviceAccountIDs(ctx, w.pdb, d.ID, d.AccountID) { + if acct := w.pool.GetByID(acctID); acct != nil && acct.Status == account.StatusFrozen { + w.pool.UnfreezeByOffline(acctID) + if updated := w.pool.GetByID(acctID); updated != nil && updated.Status == account.StatusNormal { + logger.Info("reconciler", "账户已自动解冻(设备 adb 恢复)", "account_id", acctID, "name", acct.Name) + } + } + } +} + +// adbSerialOf 解析设备 adb serial(TCP host:port 或 USB 纯 serial;与 cmd/gateway deviceADBSerial 同源)。 +// 不可达/未配置返回空串(回退心跳判定)。 +func adbSerialOf(d *personal.DeviceEncrypted) string { + if d.AdbHost != "" { + if d.AdbPort > 0 { + return fmt.Sprintf("%s:%d", d.AdbHost, d.AdbPort) + } + return d.AdbHost + } + if d.AdbWifiHost != "" && d.AdbWifiPort > 0 { + return fmt.Sprintf("%s:%d", d.AdbWifiHost, d.AdbWifiPort) + } + return "" +} + +// countMiss 累加设备连续超时计数(防止 map 误写) +func (w *DeviceWatcher) countMiss(id int64) { + w.missCount[id]++ +} + +// handleOffline 设备离线联动:置 OFFLINE → 冻结账户 → WAIT_PAY 订单进异常池 +func (w *DeviceWatcher) handleOffline(ctx context.Context, d *personal.DeviceEncrypted, reason string) { + logger.Warn("reconciler", "监听端离线", "device_id", d.DeviceID, "name", d.Name, + "account_id", d.AccountID, "provider", d.Provider, "reason", reason) + + // 1. 设备置 OFFLINE + if err := w.pdb.SetDeviceStatus(ctx, d.ID, string(personal.DeviceOffline)); err != nil { + logger.Error("reconciler", "设备置离线失败", "err", err, "device_id", d.DeviceID) + } + + // 1.5 触发后台自动重启尝试(不阻塞主流程;限频由 recoverer 实现侧负责) + if w.recoverer != nil { + go func(dev *personal.DeviceEncrypted) { + rctx, cancel := context.WithTimeout(context.Background(), 90*time.Second) + defer cancel() + if err := w.recoverer.AttemptRestart(rctx, dev.ID); err != nil { + logger.Warn("reconciler", "设备自动重启尝试失败", "device_id", dev.DeviceID, "err", err) + } + }(d) + } + + // 2. 冻结全部绑定账户(主账户 + 附加账户,不可接新单;设备 adb 恢复后自动解冻) + for _, acctID := range store.DeviceAccountIDs(ctx, w.pdb, d.ID, d.AccountID) { + acct := w.pool.GetByID(acctID) + if acct != nil && acct.Status == account.StatusNormal { + w.pool.FreezeByOffline(acctID) + logger.Warn("reconciler", "账户已冻结(监听端离线联动)", + "account_id", acctID, "name", acct.Name) + } + // 3. 该账户下 WAIT_PAY 订单进人工异常池(用户可能已付款但未监听到) + w.enqueueWaitPayOrders(ctx, d, acctID) + } +} + +// enqueueWaitPayOrders 将指定账户下待支付订单挂起进异常池(幂等) +func (w *DeviceWatcher) enqueueWaitPayOrders(ctx context.Context, d *personal.DeviceEncrypted, accountID int64) { + orders, err := w.st.ListWaitPayByAccount(ctx, accountID) + if err != nil { + logger.Error("reconciler", "查询待支付订单失败", "err", err, "account_id", accountID) + return + } + for _, o := range orders { + // 已进池的跳过(幂等) + if existing, _ := w.st.GetExceptionByOutTradeNo(ctx, o.OutTradeNo); existing != nil { + continue + } + ev := w.evidence(d, o) + w.svc.EnqueueException(ctx, o, model.ExReasonListenOffline, "LISTEN_OFFLINE", "", + ev, "监听端离线("+d.DeviceID+"),无法确认用户是否已付款,需人工核实") + } +} + +// evidence 生成离线证据时间线片段 +func (w *DeviceWatcher) evidence(d *personal.DeviceEncrypted, o *model.Order) string { + m := map[string]any{ + "action": "listen_offline", + "time": time.Now().Format(time.RFC3339), + "device_id": d.DeviceID, + "device_name": d.Name, + "provider": d.Provider, + "order": o.OutTradeNo, + "amount": o.AmountMinor, + "channel": o.Channel, + } + b, _ := json.Marshal(m) + return string(b) +} + +// scanControlPlane 控制面状态机扫描(docs/P2P-ADB-RECOVERY.md §6.3): +// USB / 局域网 adb 可达 → 'usb';都不可用 → 'tunnel_only'(adbd 监听依赖系统无线调试)。 +// +// 触发时机:每 ControlPlaneInterval 跑一次(与心跳超时解耦;Phase 1 跟随 ScanInterval 共跑)。 +func (w *DeviceWatcher) scanControlPlane(ctx context.Context) { + devs, err := w.pdb.ListDevices(ctx) + if err != nil { + logger.Error("reconciler", "控制面扫描设备列表失败", "err", err) + return + } + now := time.Now() + for _, d := range devs { + // 单设备限频:避免每轮都连同一条 adb 通道 + if last, ok := w.controlPlaneLast[d.DeviceID]; ok && now.Sub(last) < w.cfg.ControlPlaneInterval { + continue + } + // 手动冻结的设备不处理控制面(evaluateControlPlane 内部仍会按 adb 可达性评估状态) + if d.Status == string(personal.DeviceFrozen) { + continue + } + w.controlPlaneLast[d.DeviceID] = now + w.evaluateControlPlane(ctx, d) + } +} + +// evaluateControlPlane 单设备控制面状态计算(docs/P2P-ADB-RECOVERY.md §6.3): +// 1. USB / 局域网 adb serial 在线 → 'usb'(最优先,本机通道延迟最低) +// 2. 不可达 → 'tunnel_only'(adbd 监听依赖系统无线调试) +// +// 监听 App 已弃用(2026-08-29):relay 反向隧道分支移除,adb 可达性是唯一判定依据。 +func (w *DeviceWatcher) evaluateControlPlane(ctx context.Context, d *personal.DeviceEncrypted) { + // 1) USB / 局域网 adb 可达 → usb + if w.adb != nil { + if usbSerial := w.usbSerial(d); usbSerial != "" && w.adb.Online(ctx, usbSerial) { + w.writeControlState(ctx, d, personal.ControlStateUSB) + return + } + } + // 2) 都不可用 → tunnel_only(adbd 监听依赖 Android 11+ 系统无线调试,App 侧无需也不应再下发复活指令) + w.writeControlState(ctx, d, personal.ControlStateTunnelOnly) +} + +// usbSerial 取设备首选 USB/WiFi 通道 serial(与 admin.personalHandler.deviceSerial 同语义) +func (w *DeviceWatcher) usbSerial(d *personal.DeviceEncrypted) string { + if d.AdbHost != "" { + if d.AdbPort > 0 { + return d.AdbHost + ":" + strconv.Itoa(d.AdbPort) + } + return d.AdbHost + } + if d.AdbWifiHost != "" && d.AdbWifiPort > 0 { + return d.AdbWifiHost + ":" + strconv.Itoa(d.AdbWifiPort) + } + return "" +} + +// writeControlState 持久化控制面状态(状态未变不写库,避免噪声) +func (w *DeviceWatcher) writeControlState(ctx context.Context, d *personal.DeviceEncrypted, state string) { + if d.ControlState == state { + return // 状态未变 → 不写库,避免噪声 + } + adbListen := d.AdbListen != 0 + // ts_ip 字段保留 DB 列(Phase 1 迁移产物,监听端暂时兼容可能携带该字段), + // 但心跳路径不再写它 —— 用设备最新 DB 值兜底即可。 + if err := w.pdb.SetDeviceControlPlane(ctx, d.DeviceID, d.TsIP, adbListen, state); err != nil { + logger.Warn("reconciler", "控制面状态写库失败", "err", err, "device_id", d.DeviceID, "state", state) + return + } + d.ControlState = state + logger.Info("reconciler", "控制面状态切换", "device_id", d.DeviceID, "name", d.Name, "state", state) +} diff --git a/internal/reconciler/devicewatcher_test.go b/internal/reconciler/devicewatcher_test.go new file mode 100755 index 0000000..cb5ae4e --- /dev/null +++ b/internal/reconciler/devicewatcher_test.go @@ -0,0 +1,265 @@ +package reconciler + +import ( + "context" + "path/filepath" + "testing" + "time" + + "paygateway/internal/account" + "paygateway/internal/ledger" + "paygateway/internal/model" + "paygateway/internal/order" + "paygateway/internal/personal" + "paygateway/internal/store" + "paygateway/internal/upstream" +) + +// newTestEnv 构建测试环境:临时业务库 + personal.db + 账户池 + 订单服务 +func newTestEnv(t *testing.T) (st store.Store, pdb *store.PersonalDB, pool *account.AccountPool, svc *order.Service, dir string) { + t.Helper() + dir = t.TempDir() + + st, err := store.NewSQLite(filepath.Join(dir, "test.db")) + if err != nil { + t.Fatalf("创建业务库失败: %v", err) + } + t.Cleanup(func() { st.Close() }) + + ldb, err := store.NewLedgerDB(filepath.Join(dir, "ledger.db")) + if err != nil { + t.Fatalf("创建账本库失败: %v", err) + } + t.Cleanup(func() { ldb.Close() }) + + pdb, err = store.NewPersonalDB(filepath.Join(dir, "personal.db")) + if err != nil { + t.Fatalf("创建个人库失败: %v", err) + } + t.Cleanup(func() { pdb.Close() }) + + pool = account.NewPool() + lg, err := ledger.New(ldb, "") + if err != nil { + t.Fatalf("创建账本失败: %v", err) + } + svc = order.New(st, lg, pool, upstream.NewPool()) + + // 账户池加一个个人免签账户 + acct := &account.Account{ + ID: 1, Channel: "personal.alipay", Name: "测试账户", Status: account.StatusNormal, + QRContent: "https://qr.alipay.com/test123", DailyLimit: 100000, + } + pool.Add(acct) + return st, pdb, pool, svc, dir +} + +// TestDeviceWatcherOffline 核心场景:adb 不可达 → 设备 OFFLINE → 账户冻结 → 订单进异常池 +func TestDeviceWatcherOffline(t *testing.T) { + ctx := context.Background() + st, pdb, pool, svc, _ := newTestEnv(t) + + // 1. 创建设备(绑定账户1,status=online,但无 adb serial → adb 不可达) + devID, err := pdb.CreateDevice(ctx, &personal.DeviceEncrypted{ + DeviceID: "dev-offline-1", Name: "离线测试机", AccountID: 1, + Provider: "alipay", SecretEnc: "xx", Status: string(personal.DeviceOnline), + }) + if err != nil { + t.Fatalf("创建设备失败: %v", err) + } + + // 2. 创建 WAIT_PAY 订单(account_id=1) + o := &model.Order{ + OutTradeNo: "T-ORDER-001", GatewayNo: "G-001", Channel: "personal.alipay", + AccountID: 1, AmountMinor: 100, Status: model.StatusWaitPay, + CreatedAt: time.Now(), + } + if err := st.CreateOrder(ctx, o); err != nil { + t.Fatalf("创建订单失败: %v", err) + } + + // 3. 运行离线扫描(adb 全程不可达:连续两次扫描确认判离线) + w := NewDeviceWatcher(pdb, pool, st, svc, &DeviceWatcherConfig{ + ScanInterval: time.Second, + }, nil, mockAdbProber{online: map[string]bool{}}) + w.scan(ctx) + + // 3.5 单次不可达不应立即判离线(双保险防抖动) + dev0, _ := pdb.GetDevice(ctx, "dev-offline-1") + if dev0 == nil || dev0.Status != string(personal.DeviceOnline) { + t.Fatalf("单次不可达不应置 OFFLINE(需连续确认),实际 status=%v", dev0.Status) + } + if acct0 := pool.GetByID(1); acct0.Status != account.StatusNormal { + t.Fatalf("单次不可达不应冻结账户,实际 status=%v", acct0.Status) + } + + // 3.6 连续第二次扫描 → 判离线 + w.scan(ctx) + + // 4. 断言:设备 OFFLINE + dev, _ := pdb.GetDevice(ctx, "dev-offline-1") + if dev == nil || dev.Status != string(personal.DeviceOffline) { + t.Fatalf("设备应置 OFFLINE,实际 status=%v", dev.Status) + } + + // 5. 断言:账户冻结 + acct := pool.GetByID(1) + if acct.Status != account.StatusFrozen { + t.Fatalf("账户应冻结,实际 status=%v", acct.Status) + } + + // 6. 断言:订单进异常池(reason=listen_offline) + exc, err := st.GetExceptionByOutTradeNo(ctx, "T-ORDER-001") + if err != nil || exc == nil { + t.Fatalf("订单应进异常池,err=%v exc=%v", err, exc) + } + if exc.Reason != model.ExReasonListenOffline { + t.Fatalf("异常原因应为 listen_offline,实际 %s", exc.Reason) + } + + // 7. 幂等:再次扫描不重复进池 + w.scan(ctx) + exc2, _ := st.GetExceptionByOutTradeNo(ctx, "T-ORDER-001") + if exc2.ID != exc.ID { + t.Fatal("重复扫描不应创建重复异常单") + } + + _ = devID +} + +// TestDeviceWatcherOnline adb 可达 → 不触发任何联动 +func TestDeviceWatcherOnline(t *testing.T) { + ctx := context.Background() + st, pdb, pool, svc, _ := newTestEnv(t) + + // 设备配了 WiFi adb 且可达 → 在线 + if _, err := pdb.CreateDevice(ctx, &personal.DeviceEncrypted{ + DeviceID: "dev-online-1", Name: "在线测试机", AccountID: 1, + Provider: "alipay", SecretEnc: "xx", Status: string(personal.DeviceOnline), + AdbWifiHost: "192.168.1.50", AdbWifiPort: 5555, + }); err != nil { + t.Fatalf("创建设备失败: %v", err) + } + o := &model.Order{ + OutTradeNo: "T-ORDER-002", GatewayNo: "G-002", Channel: "personal.alipay", + AccountID: 1, AmountMinor: 200, Status: model.StatusWaitPay, + CreatedAt: time.Now(), + } + if err := st.CreateOrder(ctx, o); err != nil { + t.Fatalf("创建订单失败: %v", err) + } + + w := NewDeviceWatcher(pdb, pool, st, svc, nil, nil, mockAdbProber{ + online: map[string]bool{"192.168.1.50:5555": true}, + }) + w.scan(ctx) + + // 设备仍在线、账户仍 normal、订单未进池 + dev, _ := pdb.GetDevice(ctx, "dev-online-1") + if dev.Status != string(personal.DeviceOnline) { + t.Fatalf("设备应保持在线,实际 %s", dev.Status) + } + if acct := pool.GetByID(1); acct.Status != account.StatusNormal { + t.Fatalf("账户应保持 normal,实际 %s", acct.Status) + } + if exc, _ := st.GetExceptionByOutTradeNo(ctx, "T-ORDER-002"); exc != nil { + t.Fatal("在线设备订单不应进异常池") + } +} + +// TestDeviceWatcherFrozen 手动冻结设备不被自动置离线 +func TestDeviceWatcherFrozen(t *testing.T) { + ctx := context.Background() + st, pdb, pool, svc, _ := newTestEnv(t) + + // 冻结设备(即使 adb 不可达也不重复处理) + if _, err := pdb.CreateDevice(ctx, &personal.DeviceEncrypted{ + DeviceID: "dev-frozen-1", Name: "冻结机", AccountID: 1, + Provider: "alipay", SecretEnc: "xx", Status: string(personal.DeviceFrozen), + }); err != nil { + t.Fatalf("创建设备失败: %v", err) + } + + w := NewDeviceWatcher(pdb, pool, st, svc, nil, nil, mockAdbProber{online: map[string]bool{}}) + w.scan(ctx) + + dev, _ := pdb.GetDevice(ctx, "dev-frozen-1") + if dev.Status != string(personal.DeviceFrozen) { + t.Fatalf("冻结设备不应被改变,实际 %s", dev.Status) + } + if acct := pool.GetByID(1); acct.Status != account.StatusNormal { + t.Fatal("冻结设备不应触发账户冻结(账户冻结由设备离线联动触发,冻结设备不在线)") + } +} + +// mockAdbProber 测试用 AdbProber:serial → 可达性 +type mockAdbProber struct { + online map[string]bool +} + +func (m mockAdbProber) Online(_ context.Context, serial string) bool { return m.online[serial] } +func (mockAdbProber) Connect(context.Context, string) error { return nil } + +// TestDeviceWatcherAdbRecover 设备 offline + adb 恢复 → 自动置 online + 解冻离线联动账户 +func TestDeviceWatcherAdbRecover(t *testing.T) { + ctx := context.Background() + st, pdb, pool, svc, _ := newTestEnv(t) + + // 设备 offline(此前离线联动冻结了账户),配了 WiFi adb + if _, err := pdb.CreateDevice(ctx, &personal.DeviceEncrypted{ + DeviceID: "dev-recover-1", Name: "恢复测试机", AccountID: 1, + Provider: "alipay", SecretEnc: "xx", Status: string(personal.DeviceOffline), + AdbWifiHost: "192.168.1.50", AdbWifiPort: 5555, + }); err != nil { + t.Fatalf("创建设备失败: %v", err) + } + // 账户处于离线联动冻结态(模拟 handleOffline 后的状态) + pool.FreezeByOffline(1) + + w := NewDeviceWatcher(pdb, pool, st, svc, nil, nil, mockAdbProber{ + online: map[string]bool{"192.168.1.50:5555": true}, + }) + w.scan(ctx) + + dev, _ := pdb.GetDevice(ctx, "dev-recover-1") + if dev.Status != string(personal.DeviceOnline) { + t.Fatalf("adb 恢复后设备应置 online,实际 %s", dev.Status) + } + if acct := pool.GetByID(1); acct.Status != account.StatusNormal { + t.Fatalf("离线联动冻结的账户应在设备恢复后自动解冻,实际 %s", acct.Status) + } +} + +// TestDeviceWatcherManualFrozenNotAutoUnfreeze 手动冻结的账户绝不被设备 adb 恢复自动解冻(资金安全底线) +func TestDeviceWatcherManualFrozenNotAutoUnfreeze(t *testing.T) { + ctx := context.Background() + st, pdb, pool, svc, _ := newTestEnv(t) + + // 设备 offline + adb 可达(会触发 recoverOnline) + if _, err := pdb.CreateDevice(ctx, &personal.DeviceEncrypted{ + DeviceID: "dev-manual-1", Name: "手动冻结测试机", AccountID: 1, + Provider: "alipay", SecretEnc: "xx", Status: string(personal.DeviceOffline), + AdbHost: "4f2bdc30", + }); err != nil { + t.Fatalf("创建设备失败: %v", err) + } + // 账户是「手动冻结」(不是离线联动)——模拟管理员 handleFreezeAccount 后的状态 + pool.FreezeManual(1) + + w := NewDeviceWatcher(pdb, pool, st, svc, nil, nil, mockAdbProber{ + online: map[string]bool{"4f2bdc30": true}, + }) + w.scan(ctx) + + // 设备可恢复 online,但手动冻结的账户必须保持 frozen + dev, _ := pdb.GetDevice(ctx, "dev-manual-1") + if dev.Status != string(personal.DeviceOnline) { + t.Fatalf("设备应恢复 online,实际 %s", dev.Status) + } + if acct := pool.GetByID(1); acct.Status != account.StatusFrozen { + t.Fatalf("手动冻结的账户绝不能被自动解冻,实际 %s", acct.Status) + } + if pool.IsOfflineFrozen(1) { + t.Fatal("手动冻结账户不应标记为 offlineFrozen") + } +} diff --git a/internal/reconciler/scheduler.go b/internal/reconciler/scheduler.go new file mode 100755 index 0000000..0db7c50 --- /dev/null +++ b/internal/reconciler/scheduler.go @@ -0,0 +1,441 @@ +// Package reconciler 容灾对账中心: +// ConfirmScheduler(订单确认调度器,主动查单兜底)+ NotifyReaper(dead 通知管理) +// +// 设计要点: +// - 事件驱动 + timer heap(min-heap),不轮询全表;只有 TTL 到期的订单才被处理 +// - 回调是快路径(立即入账),查单是慢路径(超时未回调 → 主动查单补账) +// - 关单前必查单:查单失败宁可推迟关单,绝不误关可能已支付的订单 +// - 自动兜底失败(查单连续失败/金额不一致/状态矛盾)→ 进人工异常池,等人工审查 +package reconciler + +import ( + "container/heap" + "context" + "encoding/json" + "fmt" + "strings" + "sync" + "time" + + "paygateway/internal/channel" + "paygateway/internal/logger" + "paygateway/internal/model" + "paygateway/internal/order" + "paygateway/internal/store" +) + +// Config 确认调度器参数(可配置) +type Config struct { + ConfirmWait time.Duration // 下单后正常回调等待窗口(默认 60s) + QueryIntervalMin time.Duration // 查单最小间隔(默认 30s) + QueryIntervalMax time.Duration // 查单最大间隔(默认 5m) + QueryBackoff time.Duration // 查单失败退避基础(默认 1m) + CloseAfter time.Duration // 关单时间(默认 30m) + QueryFailAlert int // 查单连续失败进异常池阈值(默认 5) + TickInterval time.Duration // 调度 tick(默认 1s) + BatchSize int // 每 tick 最多处理订单数(默认 50) + Concurrency int // 查单并发上限(默认 10) +} + +// DefaultConfig 默认参数 +func DefaultConfig() *Config { + return &Config{ + ConfirmWait: 60 * time.Second, + QueryIntervalMin: 30 * time.Second, + QueryIntervalMax: 5 * time.Minute, + QueryBackoff: time.Minute, + CloseAfter: 30 * time.Minute, + QueryFailAlert: 5, + TickInterval: time.Second, + BatchSize: 50, + Concurrency: 10, + } +} + +// QueryFunc 通道查单(main 注入:遍历通道注册表) +type QueryFunc func(ctx context.Context, outTradeNo string) (*channel.OrderStatus, error) + +// PersonalCheckFunc 个人通道订单专用检查(main 注入,nil = 不检查) +// 返回 (offline, reason):offline=true 表示监听端离线/异常,订单进异常池人工核实 +// 用于个人通道关单前的安全确认:监听端掉线时不能关单(用户可能已付款但未监听到) +type PersonalCheckFunc func(ctx context.Context, o *model.Order) (offline bool, reason string) + +// entry 堆元素 +type entry struct { + outTradeNo string + nextCheckAt time.Time + index int +} + +type confirmHeap []*entry + +func (h confirmHeap) Len() int { return len(h) } +func (h confirmHeap) Less(i, j int) bool { return h[i].nextCheckAt.Before(h[j].nextCheckAt) } +func (h confirmHeap) Swap(i, j int) { + h[i], h[j] = h[j], h[i] + h[i].index = i + h[j].index = j +} +func (h *confirmHeap) Push(x any) { + e := x.(*entry) + e.index = len(*h) + *h = append(*h, e) +} +func (h *confirmHeap) Pop() any { + old := *h + n := len(old) + e := old[n-1] + old[n-1] = nil + e.index = -1 + *h = old[:n-1] + return e +} + +// Scheduler 订单确认调度器 +type Scheduler struct { + store store.Store + svc *order.Service + query QueryFunc + cfg *Config + + personalCheck PersonalCheckFunc // 个人通道监听端检查(nil = 不检查,关单前直接关) + + mu sync.Mutex + heap confirmHeap + idx map[string]*entry + checkCount map[string]int // 每单确认次数(退避/进池判定) +} + +// NewScheduler 创建确认调度器 +func NewScheduler(st store.Store, svc *order.Service, q QueryFunc, cfg *Config) *Scheduler { + if cfg == nil { + cfg = DefaultConfig() + } + return &Scheduler{ + store: st, + svc: svc, + query: q, + cfg: cfg, + heap: confirmHeap{}, + idx: make(map[string]*entry), + checkCount: make(map[string]int), + } +} + +// Query 通道查单(管理后台手动查单/人工复核用) +func (s *Scheduler) Query(ctx context.Context, outTradeNo string) (*channel.OrderStatus, error) { + if s.query == nil { + return nil, fmt.Errorf("查单函数未注入") + } + return s.query(ctx, outTradeNo) +} + +// SetPersonalCheck 注入个人通道监听端检查(ConfirmByListen/ConfirmByManual 通道关单前调用) +func (s *Scheduler) SetPersonalCheck(fn PersonalCheckFunc) { + s.personalCheck = fn +} + +// abilityFor 按订单通道名获取确认能力(未知通道默认按可查单处理) +func abilityFor(chName string) channel.ConfirmAbility { + ch, ok := channel.Lookup(chName) + if !ok { + return channel.ConfirmByQuery + } + return ch.ConfirmAbility() +} + +// OnOrderCreated 订单创建事件:进入待确认(回调窗口后检查) +func (s *Scheduler) OnOrderCreated(o *model.Order) { + s.push(o.OutTradeNo, time.Now().Add(s.cfg.ConfirmWait)) +} + +// OnSettled 订单已入账:移出待确认 +func (s *Scheduler) OnSettled(outTradeNo string) { s.remove(outTradeNo) } + +// OnClosed 订单已关闭:移出待确认 +func (s *Scheduler) OnClosed(outTradeNo string) { s.remove(outTradeNo) } + +// Start 启动(阻塞,放 goroutine):恢复堆 + 周期 tick +func (s *Scheduler) Start(ctx context.Context) { + s.restore(ctx) + logger.Info("reconciler", "确认调度器启动", "interval", s.cfg.TickInterval.String(), + "confirm_wait", s.cfg.ConfirmWait.String(), "close_after", s.cfg.CloseAfter.String()) + ticker := time.NewTicker(s.cfg.TickInterval) + defer ticker.Stop() + for { + select { + case <-ctx.Done(): + logger.Info("reconciler", "确认调度器停止") + return + case <-ticker.C: + s.tick(ctx) + } + } +} + +// restore 启动恢复:加载所有未完成订单重建堆(动作幂等,重复执行无副作用) +func (s *Scheduler) restore(ctx context.Context) { + orders, err := s.store.ListExpiredOrders(ctx, time.Now()) + if err != nil { + logger.Error("reconciler", "恢复待确认订单失败", "err", err) + return + } + now := time.Now() + for _, o := range orders { + next := o.CreatedAt.Add(s.cfg.ConfirmWait) + if next.Before(now) { + next = now // 已过窗口,立即检查 + } + s.push(o.OutTradeNo, next) + } + logger.Info("reconciler", "确认调度器恢复完成", "pending", len(orders)) +} + +// tick 处理一批到期订单 +func (s *Scheduler) tick(ctx context.Context) { + due := s.popDue(time.Now(), s.cfg.BatchSize) + if len(due) == 0 { + return + } + sem := make(chan struct{}, s.cfg.Concurrency) + var wg sync.WaitGroup + for _, no := range due { + wg.Add(1) + sem <- struct{}{} + go func(outTradeNo string) { + defer wg.Done() + defer func() { <-sem }() + s.confirm(ctx, outTradeNo) + }(no) + } + wg.Wait() +} + +// confirm 单个订单的确认流程(查单补账 / 关单 / 进异常池) +func (s *Scheduler) confirm(ctx context.Context, outTradeNo string) { + o, err := s.store.GetOrder(ctx, outTradeNo) + if err != nil || o == nil { + s.remove(outTradeNo) + return + } + // 终态直接出堆(回调可能恰好先到) + if o.Status == model.StatusSuccess || o.Status == model.StatusClosed { + s.remove(outTradeNo) + return + } + if o.Status != model.StatusWaitPay { + s.remove(outTradeNo) + return + } + + n := s.incCheck(outTradeNo) + + // 按通道确认能力分叉:个人通道(无法主动查单)走监听确认路径 + if abilityFor(o.Channel) != channel.ConfirmByQuery { + s.confirmPersonal(ctx, o, n) + return + } + + // 已到关单时间:关单前最后查一次(绝不误关已支付订单) + if time.Since(o.CreatedAt) >= s.cfg.CloseAfter { + s.confirmBeforeClose(ctx, o, n) + return + } + + // 未到关单时间:主动查单 + st, qerr := s.query(ctx, outTradeNo) + if qerr != nil || st == nil { + if n >= s.cfg.QueryFailAlert { + s.svc.EnqueueException(ctx, o, model.ExReasonQueryFailed, "QUERY_FAILED", "", + s.evidence("query_failed", qerr), "查单连续失败,需人工核对实际支付情况") + s.remove(outTradeNo) + return + } + s.repush(outTradeNo, s.backoff(n)) + return + } + + switch st.Status { + case model.StatusSuccess: + s.settle(ctx, o, st) + case model.StatusClosed: + if ok, _ := s.svc.CloseOrder(ctx, outTradeNo, "query_closed"); ok { + s.remove(outTradeNo) + } else { + s.repush(outTradeNo, 30*time.Second) + } + default: // 未支付:自适应间隔复查 + s.repush(outTradeNo, s.queryInterval(n)) + } +} + +// confirmBeforeClose 关单前查单(查单失败 → 进异常池,绝不关单) +func (s *Scheduler) confirmBeforeClose(ctx context.Context, o *model.Order, n int) { + st, qerr := s.query(ctx, o.OutTradeNo) + if qerr != nil || st == nil { + s.svc.EnqueueException(ctx, o, model.ExReasonCloseUncertain, "QUERY_FAILED", "", + s.evidence("close_query_failed", qerr), "关单前查单失败,需人工确认是否已支付") + s.remove(o.OutTradeNo) + return + } + switch st.Status { + case model.StatusSuccess: + s.settle(ctx, o, st) + case model.StatusClosed: + if ok, _ := s.svc.CloseOrder(ctx, o.OutTradeNo, "query_before_close"); ok { + s.remove(o.OutTradeNo) + } + default: + if ok, _ := s.svc.CloseOrder(ctx, o.OutTradeNo, "query_before_close_not_paid"); ok { + s.remove(o.OutTradeNo) + } + } +} + +// confirmPersonal 个人通道订单确认(无法主动查单,靠监听端推送入账) +// +// 分叉逻辑: +// - 未到关单时间:不查单(个人码无查询 API),自适应间隔复查,等监听端推送 +// - 已到关单时间: +// - 若注入 personalCheck 且监听端离线 → 不能关单(用户可能已付款但未监听到)→ 进异常池人工核实 +// - 监听端在线但无推送 → 用户大概率未付款 → 关单 +// - 复查次数超阈值仍无结果 → 进异常池(人工确认实际收款情况) +func (s *Scheduler) confirmPersonal(ctx context.Context, o *model.Order, n int) { + // 未到关单时间:等监听端推送(不查单),自适应间隔复查 + if time.Since(o.CreatedAt) < s.cfg.CloseAfter { + s.repush(o.OutTradeNo, s.queryInterval(n)) + return + } + + // 已到关单时间 + // 监听端离线检查:离线时订单进异常池(避免误关已付款订单),绝不直接关单 + if s.personalCheck != nil { + offline, reason := s.personalCheck(ctx, o) + if offline { + s.svc.EnqueueException(ctx, o, model.ExReasonListenOffline, "LISTEN_OFFLINE", "", + s.evidence("listen_offline", nil), "监听端掉线,无法确认用户是否已付款,需人工核实: "+reason) + s.remove(o.OutTradeNo) + return + } + } + + // 复查次数过多(监听端在线但长时间无推送):存疑,进异常池人工确认 + if n >= s.cfg.QueryFailAlert { + s.svc.EnqueueException(ctx, o, model.ExReasonListenTimeout, "LISTEN_TIMEOUT", "", + s.evidence("listen_timeout", nil), "监听端在线但长时间未收到收款推送,需人工确认实际收款情况") + s.remove(o.OutTradeNo) + return + } + + // 监听端在线 + 无推送 + 未超复查阈值:关单(用户大概率未付款) + if ok, _ := s.svc.CloseOrder(ctx, o.OutTradeNo, "personal_timeout_close"); ok { + s.remove(o.OutTradeNo) + } else { + s.repush(o.OutTradeNo, 30*time.Second) + } +} + +// settle 查单补账(金额不一致自动进异常池) +func (s *Scheduler) settle(ctx context.Context, o *model.Order, st *channel.OrderStatus) { + ok, err := s.svc.SettleByQuery(ctx, o.OutTradeNo, st.ChannelTxID, st.AmountMinor, + s.evidence("query_success", nil)) + if err != nil { + // 金额不一致已自动进池;其他错误重试 + if !strings.Contains(err.Error(), "金额") { + s.repush(o.OutTradeNo, s.cfg.QueryBackoff) + } else { + s.remove(o.OutTradeNo) + } + return + } + if ok { + logger.Info("reconciler", "查单补账成功", "out_trade_no", o.OutTradeNo, "tx_id", st.ChannelTxID) + s.remove(o.OutTradeNo) + } else { + s.repush(o.OutTradeNo, 30*time.Second) + } +} + +// ========== heap 操作 ========== + +func (s *Scheduler) push(outTradeNo string, next time.Time) { + s.mu.Lock() + defer s.mu.Unlock() + if e, ok := s.idx[outTradeNo]; ok { + e.nextCheckAt = next + heap.Fix(&s.heap, e.index) + return + } + e := &entry{outTradeNo: outTradeNo, nextCheckAt: next} + s.idx[outTradeNo] = e + heap.Push(&s.heap, e) +} + +func (s *Scheduler) remove(outTradeNo string) { + s.mu.Lock() + defer s.mu.Unlock() + if e, ok := s.idx[outTradeNo]; ok { + heap.Remove(&s.heap, e.index) + delete(s.idx, outTradeNo) + } + delete(s.checkCount, outTradeNo) +} + +func (s *Scheduler) repush(outTradeNo string, delay time.Duration) { + s.push(outTradeNo, time.Now().Add(delay)) +} + +// popDue 取出到期且数量受限的订单 +func (s *Scheduler) popDue(now time.Time, max int) []string { + s.mu.Lock() + defer s.mu.Unlock() + var out []string + for len(s.heap) > 0 && len(out) < max { + top := s.heap[0] + if top.nextCheckAt.After(now) { + break + } + heap.Pop(&s.heap) + delete(s.idx, top.outTradeNo) + out = append(out, top.outTradeNo) + } + return out +} + +func (s *Scheduler) incCheck(outTradeNo string) int { + s.mu.Lock() + defer s.mu.Unlock() + s.checkCount[outTradeNo]++ + return s.checkCount[outTradeNo] +} + +// backoff 查单失败退避:1m/5m/15m/1h → 上限 1h +func (s *Scheduler) backoff(n int) time.Duration { + steps := []time.Duration{time.Minute, 5 * time.Minute, 15 * time.Minute, time.Hour} + if n-1 < len(steps) { + return steps[n-1] + } + return time.Hour +} + +// queryInterval 未支付复查间隔:30s → 1m → 2m → 5m(自适应拉长) +func (s *Scheduler) queryInterval(n int) time.Duration { + iv := s.cfg.QueryIntervalMin + for i := 1; i < n && iv < s.cfg.QueryIntervalMax; i++ { + iv *= 2 + } + if iv > s.cfg.QueryIntervalMax { + iv = s.cfg.QueryIntervalMax + } + return iv +} + +// evidence 生成证据时间线片段(JSON,自动收集只读) +func (s *Scheduler) evidence(action string, err error) string { + m := map[string]any{"action": action, "time": time.Now().Format(time.RFC3339)} + if err != nil { + m["error"] = err.Error() + } + b, _ := json.Marshal(m) + return string(b) +} diff --git a/internal/scrcpy/bridge.go b/internal/scrcpy/bridge.go new file mode 100644 index 0000000..cffd599 --- /dev/null +++ b/internal/scrcpy/bridge.go @@ -0,0 +1,361 @@ +// WebSocket 桥接:把 scrcpy 实时流转发给浏览器,接收浏览器控制指令 +// +// 消息协议: +// +// 服务端 → 客户端(二进制):H.264 Annex-B 帧数据(SPS/PPS/IDR/P,逐帧转发) +// 服务端 → 客户端(文本 JSON):连接建立后先发一条 meta +// 客户端 → 服务端(文本 JSON): +// {"type":"touch","action":0|1|2,"x":N,"y":N} +// {"type":"key","action":0|1,"keycode":N} +// {"type":"text","text":"..."} +// {"type":"reset"} +// +// 多查看者:每个设备一个 scrcpy 会话 + 广播 hub,视频循环单读多播。 +// 会话生命周期独立于单个 WS 连接(用 context.Background()), +// 避免某个浏览器断开时误杀共享会话。 +package scrcpy + +import ( + "context" + "encoding/json" + "net/http" + "runtime/debug" + "sync" + "time" + + "github.com/gorilla/websocket" + + "paygateway/internal/logger" +) + +// upgrader WebSocket 升级器 +var upgrader = websocket.Upgrader{ + ReadBufferSize: 4096, + WriteBufferSize: 128 * 1024, + CheckOrigin: func(r *http.Request) bool { return true }, +} + +// StreamServer 实时流服务(会话池 + WebSocket 入口) +type StreamServer struct { + mu sync.Mutex + sessions map[string]*deviceSession // key: adb serial + nextPort int +} + +// deviceSession 单个设备的会话:scrcpy 连接 + 视频广播 +type deviceSession struct { + owner *StreamServer + serial string + conn *Connection + // 会话创建时间(首帧延迟 = videoLoop 启动到第一帧) + startAt time.Time + // 视频广播 + subMu sync.Mutex + subs map[chan []byte]struct{} + stopSub chan struct{} + stopOnce sync.Once + // 最近一个 config 帧(SPS/PPS,新订阅者连接时先发它才能解码 IDR) + lastConfig []byte + // 分辨率(videoLoop 解析 session 头后更新) + width uint16 + height uint16 +} + +// NewStreamServer 创建流服务 +func NewStreamServer() *StreamServer { + return &StreamServer{ + sessions: make(map[string]*deviceSession), + nextPort: 27200, + } +} + +func (s *StreamServer) allocPort() int { + s.nextPort++ + return s.nextPort +} + +// getOrCreate 获取或创建设备会话(后台 ctx,独立于 WS 连接) +func (s *StreamServer) getOrCreate(serial string) (*deviceSession, error) { + s.mu.Lock() + defer s.mu.Unlock() + if ds, ok := s.sessions[serial]; ok { + return ds, nil + } + sess, err := StartSession(context.Background(), serial, s.allocPort()) + if err != nil { + return nil, err + } + ds := &deviceSession{ + owner: s, + serial: serial, + conn: sess.Connection(), + subs: make(map[chan []byte]struct{}), + stopSub: make(chan struct{}), + startAt: time.Now(), + } + s.sessions[serial] = ds + go ds.videoLoop() + return ds, nil +} + +// removeSession 在 videoLoop 退出时被同一会话主动调用来剔除自己 +func (s *StreamServer) removeSession(serial string) { + s.mu.Lock() + defer s.mu.Unlock() + delete(s.sessions, serial) +} + +func (s *StreamServer) HasSession(serial string) bool { + s.mu.Lock() + defer s.mu.Unlock() + _, ok := s.sessions[serial] + return ok +} + +// videoLoop 单读 scrcpy 视频流,广播给所有订阅者 +// 设备断开/读错误时 break:主动从 sessions map 删除自己 + 通知订阅者 + 关会话 +// 不删除会导致 getOrCreate 拿到死会话(USB 拔了再插复现) +func (ds *deviceSession) videoLoop() { + defer ds.handleDead() + firstFrameAt := time.Time{} + for { + frame, err := ds.conn.ReadVideo() + if err != nil { + break + } + if firstFrameAt.IsZero() { + firstFrameAt = time.Now() + logger.Info("scrcpy", "首帧到达(会话启动耗时)", "serial", ds.serial, "delay_ms", time.Since(ds.startAt).Milliseconds()) + } + // 缓存 config 帧(SPS/PPS),供新订阅者连接时补发 + if frame.Config { + ds.subMu.Lock() + ds.lastConfig = frame.Data + ds.subMu.Unlock() + } + // 分辨率:ReadVideo 已解析 session 头并更新 conn.Width/Height + ds.subMu.Lock() + if ds.conn.Width > 0 { + ds.width = ds.conn.Width + ds.height = ds.conn.Height + } + // 封装:1字节类型前缀 + 帧数据 + // 0x01=config(SPS/PPS) 0x02=IDR关键帧 0x03=P帧 + // 前端据此标记 EncodedVideoChunk 的 key/delta,是 WebCodecs 解码的前提 + var tag byte + switch { + case frame.Config: + tag = 0x01 + case frame.KeyFrame: + tag = 0x02 + default: + tag = 0x03 + } + // 关键帧拼接 SPS/PPS:scrcpy 编码器关闭了重复参数集, + // IDR 帧不含参数集,WebCodecs annexb 模式要求关键帧自带, + // 否则浏览器无法解码(黑屏)。拼接后 = SPS/PPS + IDR 的合法 Annex-B 流。 + data := frame.Data + if tag == 0x02 && ds.lastConfig != nil { + merged := make([]byte, 0, len(ds.lastConfig)+len(data)) + merged = append(merged, ds.lastConfig...) + merged = append(merged, data...) + data = merged + } + payload := make([]byte, 1+len(data)) + payload[0] = tag + copy(payload[1:], data) + for ch := range ds.subs { + select { + case ch <- payload: + default: // 慢消费者丢弃(保持实时性) + } + } + ds.subMu.Unlock() + } +} + +// handleDead videoLoop 退出时清理 +func (ds *deviceSession) handleDead() { + ds.owner.removeSession(ds.serial) + if ds.conn != nil { + ds.conn.Close() + } + ds.closeAll() +} + +// subscribe 订阅视频流,返回接收 channel 和取消函数 +func (ds *deviceSession) subscribe() (chan []byte, func()) { + ch := make(chan []byte, 16) + ds.subMu.Lock() + ds.subs[ch] = struct{}{} + ds.subMu.Unlock() + cancel := func() { + ds.subMu.Lock() + delete(ds.subs, ch) + ds.subMu.Unlock() + } + return ch, cancel +} + +func (ds *deviceSession) closeAll() { + ds.stopOnce.Do(func() { + close(ds.stopSub) + }) +} + +// CloseSession 外部主动关会话(设备将被删除/冻结时调用) +func (s *StreamServer) CloseSession(serial string) { + s.mu.Lock() + ds, ok := s.sessions[serial] + if ok { + delete(s.sessions, serial) + } + s.mu.Unlock() + if !ok { + return + } + if ds.conn != nil { + ds.conn.Close() + } + ds.closeAll() +} + +// HandleStream 处理 WebSocket 连接(video 广播订阅 + control 注入) +// GET /stream?serial=127.0.0.1:5556 +func (s *StreamServer) HandleStream(w http.ResponseWriter, r *http.Request) { + serial := r.URL.Query().Get("serial") + if serial == "" { + http.Error(w, "缺少 serial 参数", http.StatusBadRequest) + return + } + + ds, err := s.getOrCreate(serial) + if err != nil { + http.Error(w, "启动设备会话失败: "+err.Error(), http.StatusBadGateway) + return + } + + ws, err := upgrader.Upgrade(w, r, nil) + if err != nil { + return + } + defer ws.Close() + + // 先订阅:帧开始累积,等待 meta 就绪期间不丢流 + frames, cancelSub := ds.subscribe() + defer cancelSub() + + // 等 meta 就绪(新会话需解析 session 头;已有会话秒就绪) + // 等待期间先发 meta 再补 config,然后正常推流 + deadline := time.Now().Add(3 * time.Second) + sentMeta := false + for time.Now().Before(deadline) { + ds.subMu.Lock() + ready := ds.width > 0 + cfg := ds.lastConfig + w := ds.width + h := ds.height + ds.subMu.Unlock() + if ready && !sentMeta { + sentMeta = true + meta, _ := json.Marshal(metaMsg{ + Type: "meta", + Width: int(w), + Height: int(h), + Device: ds.conn.Device, + }) + if err := ws.WriteMessage(websocket.TextMessage, meta); err != nil { + return + } + // 补发缓存的 config 帧(SPS/PPS,带类型前缀),否则浏览器无法解码后续 IDR + if cfg != nil { + payload := make([]byte, 1+len(cfg)) + payload[0] = 0x01 // config + copy(payload[1:], cfg) + if err := ws.WriteMessage(websocket.BinaryMessage, payload); err != nil { + return + } + } + // 请求关键帧:通知 scrcpy-server 立即输出 IDR + _ = ds.conn.ResetVideo() + // 唤醒屏幕:设备可能已熄屏(MediaCodec 无新帧)。BACK_OR_SCREEN_ON=4 唤醒 + 关锁屏, + // 保证熄屏后再次打开控制台也能立即恢复画面(stay_awake 只能保会话期间的唤醒)。 + _ = ds.conn.WakeScreen() + } + if ready { + break + } + // 避免空转:等待期间也从帧 channel 排空,保持连接活跃 + select { + case <-ds.stopSub: + return + case <-time.After(50 * time.Millisecond): + } + } + + // 视频转发协程(帧已在等待期间开始累积,此处直接转发) + wsDone := make(chan struct{}) + go func() { + defer close(wsDone) + defer func() { + if r := recover(); r != nil { + logger.Error("scrcpy", "视频转发协程 panic(已恢复)", "serial", ds.serial, + "panic", r, "stack", string(debug.Stack())) + } + }() + for { + select { + case <-ds.stopSub: + return + case data, ok := <-frames: + if !ok { + return + } + if err := ws.WriteMessage(websocket.BinaryMessage, data); err != nil { + return + } + } + } + }() + + // 控制接收:读 WS JSON → 注入设备 + for { + _, data, err := ws.ReadMessage() + if err != nil { + break + } + var msg controlMsg + if err := json.Unmarshal(data, &msg); err != nil { + continue + } + switch msg.Type { + case "touch": + _ = ds.conn.InjectTouch(byte(msg.Action), int32(msg.X), int32(msg.Y)) + case "key": + _ = ds.conn.InjectKeycode(byte(msg.Action), int32(msg.Keycode)) + case "text": + _ = ds.conn.InjectText(msg.Text) + case "reset": + _ = ds.conn.ResetVideo() + } + } + <-wsDone +} + +// metaMsg 连接建立后发给浏览器的设备信息 +type metaMsg struct { + Type string `json:"type"` + Width int `json:"width"` + Height int `json:"height"` + Device string `json:"device"` +} + +// controlMsg 浏览器发来的控制指令 +type controlMsg struct { + Type string `json:"type"` + Action int `json:"action"` + X int `json:"x"` + Y int `json:"y"` + Keycode int `json:"keycode"` + Text string `json:"text"` +} diff --git a/internal/scrcpy/scrcpy-server.jar b/internal/scrcpy/scrcpy-server.jar new file mode 100644 index 0000000000000000000000000000000000000000..5bc5a06a15eac612894ed9d0b9ff9f173baaba39 GIT binary patch literal 733706 zcmZ6ycQjjn{6F4>+Iw#jM9qj1BWmx_&=ysSNKmy~o3{3rm@$i>TGXmidyBo=(yA5_ zvr1@*s($nSe9rg$#y#i$k(_hSecso7yYW zzJBfjL7wh`W-?L-7P2PhCNd@rX0p;#6VuJg3rYmj2{X{xw7I{$sX5++aTH`RJrQGa zZ-;k>*YENr>;F$F)JY$tpHNB&p`4Qc?^2$)2LyWh`pCNZ`UC~|ddqtHAbsV6+(UvW zcMtNtVJbT+KP7{gA2XM|^O6X+ma3PLGnsq~_d8*@ObQ@nu?McDkdDmO=-JG`wH(>D zSDv4DITq`!-PerCekz>|9|xx>9UVP|dnQ#8K}0;#M1mMUoJ-6lCUMooQxAJ`5sC4+ zL2BUGfr0LUatQYjnNL(Lwi_o3 z-}BgTgek}GD;n7%3l($A?R1Sqh zFOukg&Ly^=>lNtvuL8a15+*gn79Sr!Q1Ulg)1{GU$buNH>*`2@v%l(R48?(4P|!~y zJ0Xr9y}pj?SoV)PHCg^HmkLMyq~(I#SU=T7e~t>-F?ZzA>u&aU0sgx9wbb6`7ju8} ze&Dl|i*7>mcy|XMcXtmL_eaMJdMcmcIQ+Y{{-bx3{*rQ#9&T^0Ml(;H%}eq5 zb5F4{{x72>4?skp)RY3?5ddMzO)IQaP@##2v30Mj__`;?s!Xi&O`{-O4-;Wrv=*lc z@=6B%!h0Dhv3zLbe1kp@z=FFXaP8~J zix}mm{TA?4%*vo2B4=~3ZG8G{Vp?ApNZnlMNER=HvN0CGWx}ODx~KYfWgDNYf{_=^w&XO7jCw@k#*-IsbbgT zqdMFBcS*6QtsiGC&3P79!lE!{4#9q0>gqR&9j!{H&Z*kG0JZJAa zMy1C-0~}jdzh zhuuVDHT*IfZS8FfFcGwC(pcQ-y>@4qoQL0)MS(RweF99o%mt3&> zUQ7C`OBR#)?TeI|6_4Wnv_T>zD7C}y&#YPfaoErpd-IX|W8$>K+fJu9ix1vFzTvdJ zpJ)ZPz8tCxct836)V)LbhxI#+51*aUev$8#dPda3KMcdVc6_`1dK|P_Xz!8FUIdF4FpMYiDlmA(2TUt#xP7MscQ+&ZsOP+$hsfEf*a1*x59ohtFt(^Ut zmFkm!zBQJLxS#5qJQPiey7#YbJ#{%pZ-0e-{ZH7ZtDj)Dukqz&`XrB2hyes)Zt}{* za0c#P}w=q2zA) z>cZ<;>lRx`r;R$FbPw?}c$Q@)J>KD`pocC5nZSES>*nz{B{&1QK!N z2|2FeVU)drJ!{r9Tj;Tv0ZBclN-m&VvW8!Pgqe&KTa47C*A9uZQdYtHM$m zKRffjv8T{r07v}&6&~SWFwiGlvHD46cBc6eWZ8?G(Z9Id#beXMV|gh5enpw_iO1>Q zriauACBA}R+#{D~XFRMS$HD@Pw-fXSUX`t>OmrLCjlc9T)|$E)=70EH%@RC}h_*_L zFP`5EG=w0?Kt8U?{>dI$ zKDkG#!7L!l5wDyI@$vv0=pN(fyM-i%n(=nGC+8&Dcw)5G6BEZb0DU;h0Qgw^Y>op8ZBV+Xp-umdq>_Wh;7 zO|gBzrueVUNXCH^6k`R?2nSbV{5^zPrZNDXAl5mKS|Dod%;&2FmD5~3O{0==k>ze1 zccW>JuO)o9r@IwmzWFL+OsXYwAp1jmG3sWkT69_luYoI6E;Fk zk7!G63gaQwN?knT48&$kn!tDUB};ckU+au#GV9m2tx$|{HHMN%68A~STI$>RSadB> z^ANRB;Bbr(qA?-A3H#3OpoE@ul>7ocviaX@rVC5Gf?Oq{{WB!x@=GOB5z3oMJsweB zvss}61i(M^4yNr85(nz*h%mqHHJ5pZ!A-1(BpL0W%C8w_pLWFNUSk^hc{YP~!ktq? ztuJ!U2_0R6ySDMtl08fz)2s-9Go1g`&zI>mjACcRXd%tLnFJpOzIV2q((R&wC zsT9LI?ftPKX24N=$)d5G#h1jJwl&Sb)m#$kD6%{^E;Sl%Th3IsHN}E#NKD&X)eIgz zaQ=9};2u&cy->}0pJ#$0w=vDlQS#T1T2VutmG3q?^Up6*-Dx{QjF$ybrJEol_np}| zRvCm~2XvAVy!@dwv^aSI$CT!2_)vWYtCQ+V_1bP4xsTo}xiC*vOhti}@+Tv58?fuk zVqxFfHCcA*opYr(;?0{*bW{??Dp)APL|E3nHZKO6tRsZj2cMuT=(k8LTV27kHhzgR> zX?GvLJj6D;t{0dEs9jm3tM^_36YBl_y6V5+Na|1Q;>=bynPQz|{I=^ic4jh>Emh*; zr!fOXYhs7I?B{okuaiU2KkWvo>I3s6g_Pthc&30(O(|5{ zuom2qt=|ohnQ|gEU2VHz)?Ee@u=63W3sUydYr6X%%a{LLzsT4an_J}dLA%_$PI|)$3@;*4vkZ1AOugbfL>gXbK6@Y z1wECHobg2pL(ly^$M+g3h%esLa4yH3N{%S3lBnap+=oFMLP|mnKCP514n4PLx*@> z`(Kmn;XiW0zMiO*-8b8yHz{QPsegq5C%Mr*>nly&(W;_B(wk?@Z2j$|k0)gzk+mh- zCf9xMwp5v}*)BnA%b&cKh&B1Z)_=a^N^b9pLbEt5G)&1)_GK27@H^v~J-v8KKM=_3 zq6==aSg#aGacE!jx2^FaT<<&DGJbyiK`~0uVjF)=B`^o49>(x^Yh1TA0P_b?|4;^2 zCY!a`Z?d0NS#o52`>YvrWc%`saMLq0j=wO`1*AxH09JsJt^ZGp3(FV$LCiTgaT1^$ zl%79zi;cgA(J%nR&8LwPK5}kV{ ziodrVY%cjBBMgYbFHuT$Rc`+Rh$Zfce7;k%Jpe0tRPAH0@AxP7fPr&+_}U%6r}s1u zvyN^m$2Y~l&fXJYLhQXvn8$#MBpyossisN@sc#l&Eb15xMhC|$WEB@lxZRz;tlp4T zi1>;0A)>p(bx#w^1lDFtKM4M`PP<&Yeq~k&<|a~zpSP)N%I)kbV$BUqn(G=WuG9as z<(5idL3ewl?oSY}D!qDNC@rPEtv{n}5#79=QCLL%11v1$o04SxdnX*%_f zd1ced9)LEgex+z?Q+$U3DOZhGnLK(feT-6mC#C%L2#wz(O1Go7&r`RGxQ& zvS0etPuGk{wfZNGY=+)}r%(kIy`jnSezqwdW72KCyrk1=FvUGWe$W`iccF#w4f&eS6%ha}1++qIY7n=a6Z{b=&T~A2->-mFFaY-JY z3Xjme&o@`p8dN38j_vc6Awsy5>gP11h972cxWTIBWVel-4VAF=6Pv*w8`ENnh!Pp~ zzw1qCU(LiPd9RDceLC&xmv<~Xb?C~x+SxYc=vYY#L!mA99tazq=It(vZ%{`jPY}v4 z$sef;Tci0R#+e~yz#lvVw|x#547K8QIVu6Z@SLh+S8#MgLbORUzKF06O+J7r*XW5y z;glkI28|_}vWts|;CCk7_FWk4yVH_G+vPRE!n8r(4=C(}LBOLUAD!D#tD;M-yR#4% z$Yy9%LevmE{cy{&^j4CSx5$!QV+Cz9)L>73GaZ@g%4pK@fo_r~=+%;2K8ga6i-8&lVDT7XaIgRXK}|0vTjULpS1px|MNo(jqOxs`{$*_l$dqD=Y9 zt*$tl&Or0~N0NslX%Nli( zjMCnIbM|BE3&XmAXMGK{yIk@Q-v7RqV%m>ou@OeC)C+GBvc;cD&St1dzwBY)a$QM5bAvmY@4R~s`3&lPL8;psu_11IJLUog&;A+E;M==Wh} zo{$N{W8MJ=ISll%Ye+_E{f1fBuNA~HM!nOx+k2Z5 zjICae)&4d>vgvc`jv>Xu47aRcvc=T@V6QEg(z0t;13aN zwrj}HwXemJmtS~)98|~}VPhMhIetJN3_?rEdjn8#SDsOAL^?43WP9Q#ZW8$;_4~EB zCdv5LTG9LIgVrUq0wM)Uinb`u?ZEa7p*@L_^`zB@5=~ic%iWuj@y+oGnpu*H5d$0; zt+FX8Ge!r6hqX^nCMatmk9Wzx|(aSiK7Du7ss|4&6jHB*!cRBD)6<)G9+# zPROxgLElkl;8E*M9CC@vy?P%DJa^OF~5cy5y*qqsq*%{R3r{lhSJ$|yf^Rz zy;qDdPrsKO`eJDC0FvWjNtvs8tL6xLd%AID11ST0>A6DDRChTuyCJS8O&J5{M$2X- zQ&wqf^bSNanl*eQ7|wk=`g~B1)Gc$j1;w!`WoIt$LTh9q?V}mi98bd`i{|l~Plyx{ zoxHJT1v5QTcds$I8m5$^EU>A9b>`Gqsn9Hi=g5=e&mcIEtf!UH`?-{7sR%J8`@x^J zuHqePqaZo|tUovz5E3D-EpJ`)2{a;UTfwD0lRR~dVwktu8hO&Hf>9EvQ?%bx5tB#_Nw+AzeH$!qr5jt1ZXG{GjT$DSYBYNO&Vk$|X zGNh2swnBw*+nAXeTu^g94F1?u{I_ULn%UT97%Hkk4owRt=kq~cXm#-ndQ^ckHfm9- zl5M)lpR~A5zSGiKXM?k=>E50`6W$)XIjm4TRO?Pd1D#av=MPg~3Mt47O*?t1|6e`z z$L;_davFJ$=xfQgl5G=kz+ey>GX{B~tUX`4YW@IrV`=zX(JJ+->@)6;_phCQN4oaeLQv->uHR= z?TutMYVXyWvsHkFhCIV`TDf;u(h6=5cpym{{EH)s^_Vri^~|j5kT`JmhD|pi++jXz zb>e~OYjO&^qa@kLt8`CO&mZ>RvWRy|MS^>pvX%D;mn7SIWiip-ngO@+PhRR=jVc#r zx9?SF3W(p;6!|JiGIVkxD6dp$=8$uVK`t!p*zwZ62dXq`eZYFmu1%whs2Mp%*&D&% zu{a)O84$05d$U!XC?$Nv`=JJ}x= zGd{%HWu|b0%q%{GlR1k-9=UgATniups6vwFI?aGy@Pw+Ca;>ktV`^23c^kDz`f#;D zUJAewZZ9z0YC7VWzLh=KIbdYFd1GfbTP3JcTaR}nD9Vj@cd=iHDX`$iviRfMN37z^ z)9_euc7>Sptqf!kPFOs@-|+%LPnm1Cg)rR(n=E9|6n}QL^tZXx`qT#k^Vsp9wJ420 z&_VyHfF%B9p#9@r$*Wkrr4!u&+Z&R)JIKo5l}*ZfNICGwwxu(*;xo?)H_m(HLM6xb zzJJ7X$NQNH+#xx^FZC^U<_qeNQ36W#qYh90TW~~meD^_w=&((vKJSzl z(PEx4hTP^Dy`nXV8ZY%|0`5{A$dIbLiuZp0DEP^te6TS&=Oifkke6|*_c}$FP&KLD z_*=r(X=Kq|rl`d;74XErxP$QTvyg|Cc2&dEGywuzaN_H_`p|ex#7#km`HZ334VF*F zhiMM0o=#-~&T~)q_tqPIe~BlPM8D0X_nEu0M*Cy#A-dVbOsA+jQ*(Uw{`GiPS z)6=(mt7@vD#_!KosHBWdURh^U1y#{pZ8tjm*;RO`78)84wk>1TRw@B{ql8D=2CmLQ zGTBQAymTb7q0p@0nw$5dUU2^9#YcQ>%rIMCpA|mu269^+iGG`ZhBkK>eR!I359j;R z7d@)fY3x5nV&5HI9OVlwAuUvDb@5I*{?4p#{8rIvPUSUQG-t8*wx&q26FXfP-mO$% zARyiycQJGlgrd9GrrT1vdD(duTU@j*XVrp{gc0ex*Z6nlW3ffRvx-f+q0nN}g>qUg zo_;%IV@b*~@X^Db_K>8e2&0G8B#pww5g@!}Hp@hWIqqezBxSoTn9aFdE7|fZV#8*K z#QrybR3^7E{k{N^tLDoRa~v7dn)-n5h#aFOunuM(U2rOf$7Y;jD2=Bk{SuxNeokOG z-~f{I$8kF^RzSM^@y(Q;ZqK8q)8x^6#ZtFr@o34-=fe!Ha`)Per>52hnHi88?-C!+ zYSif5h;n0>Y`5C4KNFhd8FR4oE0uh|Bw5Eh^^?Nxu^q32NhFRp)&&`-sqfE?HPq+Q zRu~>gRpj?xi9&H#w~x4KYlL%D$dt}Km1udvND+&`PuM@{MM(9HM3@9d;k1gkEq$rM zsk7z?Sjv*YR?Qu}g^RQ`mMh3b#O5?5YgzKX{9_$OSQTp|51xaoDJe@6@YuI;-P36^ zl20)W5jj}=u4Xv++*saFsWeSsmAdB74wUl#|K<-F3T>nAYg)McCmqcc_f5D$zo<=t zE;jf;XBy&!RbIM7F5z1_?;(}+k*9H?GVV&%CW<#7!Jn3AuU_?nXAoPe<@M}(VL}+x zi*F8)7s5976An*Esw2?xM{~?`G$Bdg2vf#_*%b5y0$A97^HtwR18yXRmgx~n10WBs zX&4K5l}@-x28V>@FU}`z@2yMoCTMVX8ZtV}l!9u%JIndSeOZ=Vx(*c=^hbyqT9y#( zS8y^vj}#w!7)K9-DGJd8UJ772G5Mz+X^N=NVasH`&6nx{ge#2>rN7N4emq9P#uo>u zgfN~hzYe9HZ8EA_D(Sm;`Z=Q91T~-*pA~nKG1(jJUe+00LFMw|*fGtE2TI}4Dd0Y7 z)Xra=pB(b+ZfL@^=S`Y3aLxySFzT;9-ZUziq`pyOsq}d+hBXx_Hjf>0y+A09gc|Q0 zdOh9iH1-@LLBGuutT4D;eoZcn5!m3^Y#V%hu`Yy{Sd`oz)z?hYv~n?p=Qu#Bpmp1p z^3;k&isr5o^mfD2ADh#@?rmPLZu=j(3orRud^<^;UNL}ljSMnZG(F_ns2Sy6D3`PF zFbRJ@U-&Hbf#!Vvr0&H7!Fx*sE|!5w|3XfClr2aXH0PS z=7t?)KsG;hKgUy^DkV~YAXVKyY9T}#71$L`uSJNfPFp96aa3~%A1cdk5A#suX!d1- z%8AKEVX#*Tjs*ys=5E1huiKBR{|<;(xEsk@!BRifW_FiTReBSIx;0q2hPNs@dLZhW<5u5%c zxJ&ZZ+3!xD6N6kYtz#$rNisoqc!n*12V{yD5uD}xW5RI`IA&I$p&aQJrR#aY@IRP% zq-^17V05PRT+tlC*KF6vr_xj$DL~}dZ0~2>KgcxV8mFM>6g$Uvs$@9k6UZ!7`Vji| zrIC%pJl2T;!I5>E6L&tv*LuMH2ik8U=U1$u3HZSF*isWZYNV5Fo&1jcG2Hza! zcTkzU9`67LvN`sx_lB>ff6^D6<11lw@j1ul{a5hP-wX)88;b)5aCsXgO#{*7L-n7B zpxZSj_xa|ZQ!EF`7~Ua3l)KRDr2(pXzpRefZ$t6NgJ`Vxe?q@?TonTXSZ&%HKjSAhdn&v!fz!=>LjkhlBme~+n zx*NV(E=b?5+o~KecEaGo`p+*R_U&*PT^-MeLw%-8ao7vdzxe}qrZT;{_=s1#D`+b0 zdoAVAQ8A0R;$J~<^y~J$66pb@TZTUuI}LA8D;8T6Nq&$}B;JNqEB?)&lKve~f+!ry zxRPB)_tk#Pv&mbLx^(t=l+1=qO>467G(1&0!r*HBe?~+^;kSeq7skz`FLKqLR(J0N zlpJeB7>XMjesDpgpc}L@8wTHN#_;mp2OEa z24r+59D_eA7C%~Es~p(LFPT`s=5LgW=V=YdlgvXM)G6RcPk<+ zeYp;Wp_X=6_0-I}vjXtCLZsiIb)}>p<0lSZt#j~>q9?q+CLi3%x~R>!PI*SzXaoN4 z7C)VmAu!079Hmdf<E)yTX4&0GrZ{KO}`?FAX zmECUao1?@p<)0fIS4PL3D z{mL`K_QWqEHF%Jszw}*?+5~`pe&z>~pWjJ!*UB69xB7d?`-5jn^Y?+U$;n1)FgaP} z*p;K1obm+`_CULHtUBlcg7X zK256eUK2wTOvjDcv?hA`U=0q7$}BDCHTWC=H4HIEzA#0C@g8k5QJWO%`TQrX0w&oRNKT4A@j< zMK^R>q%uL2%0Mb;m(HcqdP{~H9#%+eH|Nu9Uf9834zuJQ2pcE?@8# z@@hU8E#uC7H_;HItFuxiQMWZO7yfSJMR>{H4qzw-N;+( z9N&bvtBphTNiy}TvR?Bg&&=JR9clxSV)j|OFsD^LulZz{IYRNC+8Cpdg)!X{Xt2<> zO6UjAsN-+HVx0ct(YzJ}M^7W>hXC*q<}EyV+S5!M*;x2c-?hF>=GWGMKx<$u4aX>w zxpU-hi;Hweo1Wv7KLiU3+(f0KBX z>88-qd4c08odetu0xG_d(xb1^Z3|h^DJCCYcAhWNF-I|mx9PlJHW6huI4zK%$dP7C zlhC!Jmz-WRC5kc|m&qkF!5&i{D*k!TLw~7#R>+OPg~B7q1+29j44y~4rRo&ETa7=? zjyzDlj~!;(NB{LiB~E(|K%QWTyruK8+XA9yQGer^&c~FKVTBw~2r3Yr+QZ`q{Ii=y zC<%XbIn|FsM*WiD3+yOWG6(!!t>ivcIP%J7=(fSz;Rm^Xu^e$9M1Rhed{|x^%ERc& zN;Kp_3>J8Q|7Y<#D_jQ+A$t7P_~=6lZo;RBGSrbRixHnx^-fOT!is$a7Oy z(s(Ao>6l;2s3ak85d+?n`9yzjB9~RC&yV9ZE1!b}{$U(Dy_#OD>Ia#+{81mF(DASu zljsXT=MSYFaQ5UfU|M5`qQY)8HVs$shli5x|IPleEb%AYt1u2HgOX1r3O9AV>BbKA zXp)~5tg8Buf~E+ULCTrO$q5U>5^% zq|hQUvq{_m?5lmBR&vA!@6G5tesLoYYc!98|5>P4>eTM&5C{DNKD%_sHEKBsSu6vcB?IR$N!}Y0 zjZ-_%)ET}9PodwYoLKRJi|Ureg6~GgmgWyQ>8egMznV{L7a-2=hw*)z%UU`giYy^7 zwU<%3E%id@P7VZ1XNyD6^>#|m2;*b{US2LBL8nm)(7(=i=5={pkxQ%|3HB}t@i$io@g zlwQrD@!kZrcP2dEzP2xvv+M^R!&oZ8?Fb2@qJIAQD{pB#(o*iSX-xuV9})$1Um6p--Y z)h_H9vOe=ss*}~DFa>O}qvVKPBaT`dT*BxmGOo6}dbMCC1z$_xap??ZgW+IbvE$k} z?04^5Ny69(CZ9~T*&@h;OZ2CO99e^f;0Qe{Vsl0Zp-9GO;RWNm-GCy6wk1ay$ws+y zq(BEPnk*k{r}2z>w~|I%a0deJH3oNye$Nr}kt3A8M{}3bVlX_`bcnHvo6uGXes9CB~^`>F>X^V?f;$osI8^M;AiNCX4&hDi_f z=y>&#{pMN8$J%vm)9HEVMa%w(b)}r~&dy%PTFE2b$40&%F<$8N+4M&^huVa>n5%=b zhkNeVJ4Txpj|680oNqYknbK*v*yzui{94@t^x^(kF5C@P&^OY z&AZdWL#^l_-`&-(|4R zNz|kyq>7o{eq0p1rywCr61>kd4*s3hFb~DC_cUfb#C@&ou}Ob&K5W8vfzkL3j?F0& zAMjB&cXv=q9il{Bp(i2Vs-(N}2V8vEzwQ=4f7Rl4MI)ql{m(H=Hx8tgk2kExZ2Lw& zyCx(lPf_E(L>M!A%|BH+Ca5x@!+Q!`)jCEaZvL1}n8ay&2&f;?cwYF+ZH)&^W&X-+ zwul^Vd^V1NdQaO{YGo}AGx`LU`VAh%8sYVsNJpB!@Z$LYcyj(^Kf8zO0%3{r)SPLc<^=i(7u* zn+bQukv9F`>?NYvFQ{cb^_&e4=<`e>;Xa}1CpA{u#D+qCK*`)Y{Lpx(2?fM~TD(&{ zQSPvZOC$brfiYij8g->M1zbTW0Seb?_a-vLO~15?auxKP-T?)+kE4@j3uUx|TjL56q#G$qgHFE#6W5D2#6Go8>cO2< zv@_6HKma=LT+@ox^nlOzRytjT>1{9T!uJpyZ=Hvc(fiq~CY28m z2oy=Qn7PJ4{PTR0&oNHe4LfRkr#X+mwzFPtsn4a}zcf{KfldBEy_6(d$20b(c1_`K z+jNTRMy&KgCGS_Bao68|NyqO7Gr$q}cMI#1>a%G=$1WlRJmYM48cQjPB5dhj;1oH# zmBEF<#(s*@ac7)<@k*AIvIO$Y7g(ku`RJGWE?XXEG6bI!%XxqB_5!#Xa;_E;RIvN% zs+|Um4vIy|fb=oEXb= zV%51?v$kjLLL?u_oR9nnYRm-c$<(e@q&8*}8|}8NTFo033RTx!UD$;UTvydB${RDq zU|eBnSo6=VOdyxjgx5-}T|SnFUK!)+LR0D1=%;>9CVP~XzLby}*US;A$;C7SdpCVF zs5{C}zgv|}}_m>$}_v=QT?>tv(P!~S^{ZZ@pj2ygVu;H-2j`|_~y;11RD4j2trABX71pxs z@{c@a1YYsblVK@wZWFJ(OK-cb!irxH_q%;qzIT6FJ0othJ24KX?={#DAw_x$aDB=y zeB)YXTW8lc)$+z|_2_dW%U=nu)|o$F?4yBBjjm%C3EDbqz4C{{gG-j4{K%>*+0wMXl^#3sKPh?v1+3Ej8r^K0=xt9HaE7~Fd z-EdvKLh~;*&iBqxe`s>;E%AqLF1yIaMX~978wYk1;YKpqTCDUR$OXuja>3#)OJQoo zoc8~5k0aqFqh^YHvjxsjlVwogkzCG8GqWh9R8M2_BZ1W`cq{%q0L5E2C2_s=G?k|q zY?J2$yi-R{2`JZ9Rv3{ZWemsz)nmdMf94fXs6uUuad*QR#cf|ccP_oTp*sL)#;W>n06b8=h z+S_CqQJaMN9Aq-&xryFT3^=5?ZCM#)%;$wl)=5t*=6G=KwhW3A`yIJ86_DcxFd}@p*vYPiVTD`7*RK-Cvb%F&{x=vIh8Oi>Dqh@ zOg+Xo#a)RQ^2;~n0FrtrJGgGF?=_(8lc;@Z;oE^pN3%o_emq24i5vJ}tbJ?Xl z696KK=AK$Vl@Dfb)>~95R7#(ArHb#dr-9@d_&Hw2Rhja7OnlIab^cPuim)=)`&;AJ ztD1`H1iq_rw+-nDS>(y;o2>bhjwp_niZC4e=A{+;dxf|CrjWTCCrz7y zqhsp<#FxYvvovJ@)?XgyhExeFE#B;Y=}@8hZHD3nmT7eHj2TNPWEjY}0e2S%pHPB} z)op{3b3-Wl3|1?j5&*@t&b})}F~7?B-o7+*bC^f(AG`CcW5>L?n$Q2`H_?m8rq{bH zrkJr^H!;5M^6VP({{KCEQe;_{zfumZwt?V{eNLy!7 zx8Sa7{g+Er#9}Ah6mKl`KKbpR9q33gf8x#lC;PvI`;6+WgWpZgA zCX!(@8D*)iSLU%JV2V`UC9uwr*N(qR(X5_+tGf{;CUI!2V;BH`>Y80i_tsO_OnoNn zf&oQg@_n9iemRB98?4izMmNoSjMxHf&{M~5ieI2(kx^#@ZKIa$Mmk4rxP4FX^QsY__3j6a_<4fpPdS&G#?$BL}#a?XY#SMshlimDSgSzSb(JL z(x`=~sdb6;!V5~N{{?K*qA23@I<{Yit0DI7-UCX?6mPJ9kpC$HVC(HBPS?X`tc$@U zGaxDyhCq4-6i3?-Qo;FS%b590A<-q(g{~Pp;+oqS&*<&OO%q}qt9Az5meN5zhYsDn zNaw%tf}_j6pGx;{ja-d+w8R1Pm$p&*{(qb|M3}avjBissN4(7Hbyf19{9&!?s~NOn z?J8jU6xMxio%AHtO|C<2@{tp}=(D$G!1sy?M-d{obluA2#O;SXR}R9(e(6wAntFGE z4MjBD?k$dTN4>~rk*tM- zxbjQD8sqzCN+ln>*A+I~#skIN;e{NPDq6gw*K!+@BA3s8zMq&XgH*D|@Q!#tx9>wf z@yj+_-c&BVo;6e;!i68T@T53X!xZl}hR2F!d~re<#wP#zd^Xi(0wwHgjGa8%>eppD zOx120XDr4JDGISjB%I&ML^2>@yNhFr^@qvU(Bjugrn+;Olr`Hj(H~or*O5&HvXn&p zrx1gej@=ojDHDWh8AgwB5s7BXmvrBmrbCXtb+u1wH6~e^f-{TzZL2O*vU-&MEL~7w zgDZpFn-RJP0stk4Ug}>}SISH}A3#0{e9o^aY3mf3w}`7`x$UVFOc^P@C;svLonmm< zChZy;+iClP>(Tb)^{U4F`A!kjs6T2sFHMv_D<(EhxbhZK$mhzD+vT_B5~lO9-R5qv ze+|iL0&6nNp#!LXu7!Ko0g9e@6VR*K%-;-1&AwWM22Ir)YIM@!QN`y`CD!QKr(wba{fe8?UDr{SGI zuUmhMdyGxJ0<4SPL&~(YcqYxc8nWLC5FnpId+A~9W%(%fm>y$GrZJm1^_Caf71%Mh zR=;?S2nlchb#W@nAl#s49}H0O&py&5aITu=-(|~6qcqu!6o|Mf0WLQ1MhMH3bz?Cl zI${CV$<#pzj``Dsx}xvu@=11Mk%OYZc902Y!CZVv>H{vbrD2`=gE;HT9~(FzJy}Zk zF2(H^xzmvJw`fE0O``R%I^eP=UdP{SCNcfk9in7M8RWpCdzu^K=rQ#K_EHWCkFxME zKv63dD^o1)=}@!S0AeNpA)%d9W26xV&c9{;fKO)v#aOsF%=R0S?L+)xcm0@`Y7ET`8&kg)t+CEZhxyKB7Qz=QG?eVdTWlHJl{T(!vUtuYZ|V+p+`(3^8Nal*D7s>L%c!_|Z~qAVEnD~ISoao`f@ zCS~_8e8TX6u9+6e_(iQ>dH>+K!UsaZDNanB3^KAgQ^0O`Z$Ox7U)Dz@n(>itJu?h) zp&&P2g2@jp>=lB7XT~H$s=4QBk(4-+`C!3Ex?kIakUNcO?3!yzJs-ea@XWYQTioS3 zT33q0I}izDt8De9okM3NFI3aVVEe&72eGQLb?Y#;%!0k_Dp)b^6bG>xU9n7HMDLFA zM`y)=AukxO=J&&&1Y{R>iUw$ft43!%)69M$mbNv)gKT>KXm4F$`t)Z0SyZ96iGk2( z#i#z)SSO@g{Zjcm0ghLp614UpOACkjyhE7>%Hi#!lHCtqI%kz!$*yMA!VXIR4veun zUl|pEXR03~coW(O>BW>)J|S=djN60%DMjO&tMs=Dd9JqC=-zv;m|P=+;M9N)Qtcn+ z9$0*}iU_btlRI|3rsKVFX)~lhJj$aAW^DGv6kb@*fV#}DHYC_h3JgT~u4NTa0+T9r z+mjy1=I3<+n@qxqpZmLT)hutf`-~|V!nuD?a9wsIuC@hbd1IPz!`^}4jGyevhNAU; zZ8e9XBoufka$3}sf8M8vPnKQQ6nDuTLciD#5@MNI={9AeNN^Xau51Y*)69Ys8lE6s z+0Wp7Y^{qncZhLzdYG~-PcPz5L)OvqIwx9vyJk>QEAHh@$|pr7oO>I5(@6{VFk=Ii zWScxDipNXIUxaqERiuOhxi@O8jN;h_4SydMncq)~@oGWIP$rpF#`RB=p-%~fvj0Yj z1=)y=5!k#ee|^h`8NdX>>v`_XW>&S}#I?4k6^6k{yw{pBvU>!cywG0${@(`{MpsO& z6C@~ci7f1h;go;MwEoX+p(dNB0Sh?BtGq@m6f*DcbA%r4BILIL#FRrprof$<E3Y2^=^Ker!XqFAT9odkOH z*-kW|f@N}hkg@}$<#=y`gF|!%qrzxtjrCl-<}f7;Zq(z8<0ktg`NltAY>Z7*=xm<7 zZzq`PhlgJ@#|?b&6z2|9P$uZI0v?zOD^_ zLsZ3N=9Di~vJd3r|99E(Z$#*Z(OH*JBEDBEutQwG12Oy&s~xk%G!8-)Am^pQjrU5~ z+t(U1H@sab!6ba(3Y&|0?Ni|-OUJp)yfH|nvKB8MklS2B>Ahw0LVkVw>o&=K+XK&% z+xYB$(JI#)S|!WBq#MzgY#BlYgptw#exAh&9lNtsV9i-5s3I%y{kmlP08JFnn_Oda zomX=uP7FvH%3i`)sMZTDIY4``?}_6i<#RmEX(;g$s#-VU&9)JKjJa(z#D(v*+9=}+ zt0(3Vof815mfTevg!+&Q60<%ifkrJUS%D%E%BOVvlx^r%^(Y0gEJUTP6DVOlVYmDt zxJq+E^YSK8+bWGSGZR9nzM|}Dem7nzi_?2kKs987A9nN?vcBmHwn#( znpf@wKFxC6q(X1DC$s@1Zwz@D^Gkp;CX3des8b$|GnQ@_W}l33+CL{b*{7b+56lb z4E|pvm@?KK0FxEF2FqLim~6-zwhmeq7B`^HcxsT%>hH+x_Zz4a$ZYj$qM(<_>*N*O z0oy(QR5WHCSgecc;5M7oF%s?oWrEveF(}26b>#QJ<6`1NI+0OphY$2GT{Lt6UrM>h z79~Y10;kKov|?Be5atyn*4VIZwf2Sf@|}e;Gmyo7RJ8}RcaHlT*g7F9+m5du)L#}Y zzv_j-++_N!$0~u&qi*;WJ%_}{7k)tWoA=jPSB*nKiNmS_pQd8RQJK zfMj>?_jkubl8{d4>?FpV50ai#Jtmv=Nu7{YlA}kR-xo46AUS1%pQ#YizkDE}VGMBP zP_{O99sDc5#I2hz!*|LY&!#dHJys|wLgag#!t-TS6(xQ3^MF1Nxtzz=Gw;MNN{giF z7SkmhodrFdX@1Zz=9U^}coUEv>Q&$%RGs$p)wbH5%uM9oB+d`f_;|1h?N(I~;4~;2 zPqF224pHf)*@Wyil}ITR4#@wik6*LcpjkdJv@Uy3|Ky__+1zBR?dV_8)j&WoDpxiT zT~W@RN(J`_1V^Xew9e15aXoR-WUFX+i@(5ZlWwJ|E|>|03s>MP6b@PBeJN&wY_aks zv>M;DJ;7pE!T%t?*@KC6fZ01A~G-b;8peC{ZB=o&j*j z0lu}MPx9O+YfllhFQcld?rQ+K`r{yoZ!%0fj!NKDl(O^8gz8V1^I#N=%GNka#Y>FH zED_&*grL}fql)DlKV-*aRs14EW840hY`v-~r=URwUQ>nCZQ%g6_9>)b@QjXgDnmQP z;hDR5zh@UQIu>$F;X0)B@)dtkxyL5^MTlHk_NflFes(HjlP!PsZ@6ReNnDRX7YX}| zkSqQyS8jW<{6v2<&#ai&Ii;Wj%27tZWGd9LIU{tc5U8#fA4uQ;RMECL`N7}Qoqw_w za>%hL^T%L$Y|>M-4tIyKj7wZoV|~<80px-k`|HAd3BgOViHc=xI8R*Eab5`}^?Phd zr(eSBD8lZu3kCfh#HZR+3X(Td70XHi5t)YffKYsZ$JV16wEDEhZf2PSqu5WE7dQj_ z$&tVNS-eoiF8O0Di9^}~och^L{uO*{CNS(YaM4WcH{?a40v?`09=5+7`-ei{82Xa= z4!|lDAutt2>zBzu4S}wDfgvdnHOp-)#Mau2FK?agOq!7kta{pmoj%OXhQx5{Rf3@4 zf#+SMXWyJN6Pkd7PsBxyPnitH8R(Z%)WM2;9Qv4JNDI)V4J_%qLW1fYCPIU}1x$#H(8Bj-H`vNR0$;kj&Zy zSg!vTxs7=k7Aq10hbCM8wtyu7?sN{$#OYw2wCvU*ZP3ko2Hf2bh2J>=4Hpxj>DLMF z$B|z&7y)>LOJ+tP77h&&K-{cX;~;Y_KKA7BG{ZTJ@ie?yOY&4NQCYYW9|J&#zrH}Z zq}Q=lX@_ue*b!=+PKynY_a!_%{avG)wh^8I5c*PCGSdOkG~dGOj!m*m=(Y+oy@I~Q z^a_tP`q0{*fl7j1mC8*Z+Qz6p(-N}6=$&wEK|@-UvR*1J;;L$l&7Za7u|iIVd(nF* z@5MQplv!iT*Ao1lh%dZ|SXEyzqlnS4ly;GFNYROO^yxV`@457fb#KSyo~rg!v&ljR zO#m~BkG`ZmF7~NgRH2^C%zq{f+=xPD{=MHLj#j6+%S%H-!^3g{Za@3# zrec<{;-fi~0FzTn@u&lb`ELY=mtVSb2kQ;nks`o6MOXZB;`TEc1M!|DAQW%+Ei?g~ zQ#e3ii`ySqekc848H}WNM<*jW>{5=_0#|Y*hLr) zsKyMP$VPp?f|EkS=)+#v^zf0Fl6q1Oi9chA~Ubvg_DO@?D)1q zhMXe^Xef!(3CJs+@$B>kzn&ceO57|T+lLqtO#cC0-qnvfVqF7;-Vzyuhs_IW0043d ze-P?J$73vPaDn@Ni)sD z6_6|qqLgq^t1R*QvmRn2zho1&hZyP;kSgviah*puQvC1P4=CZj+Q)G)H4F^+WbVcS zv%KJuCe}BBYQo#(LHWY^gIl~y}s#=k; zHcF#}z(eX*cm}Ue4L=Z$Z4VK%Hb${{tTF05%4XrG$Ll7Gk9BzekK;$}AZ3B<4G%}0 z(7$FApUlCq+oM9$&qRV5$)~~}q%bH)0B$D_;OOvLLI$KjWD-SPT4DOxF{Oq;)0}GL zp{Q>PR|m6x;E~cRU)=)0%YgfNr(f0SOY9l-I(5(`Lwp!n@;Wfv0*gZ_;O#`-5`nF> z902g8#NjN5s6h zQtRs%G#HkGJhzAWS%si19-G7z03z<sT1l;Cy%KRv=3==*w4dJSKe+|Zes z1=wSX3X&600d5=Mj2-doeX6f>qZOz=kzu9j#9}T!&a*^Z^`%%vdNMmEYr0eeJp^!~y2Y=6@fZNS6a4C%fDagTA1I|p%c?3k`5HtuOqWVnZVwUzvhscp z8$OlRAt5U=T}@dwY@iT&b*h_I;cw5LZ94ii4IBp`DUQgeEiJl`Q^RtG#ht-N148*5 z`x}fGm!mo?(lP$)733l=%0L@1i;CL`2SLmdM-{-axiwo4FM_oSCVz3(tI7fCG>cQXk}3Qz6;z)@W3dWCpjFER zuQ;NaS%BzjJbpHnMoZwxm^?eca(JAl>Uahs&5T3lR6c#TGi>tO6z)XmTio8k=r7@{ zY?qxfO8#DW%vC`QoBu7xs53^W1RSf@7dqHsV;B-1-SMOYV@p-{huOMlKi5t&bM^jw zsyVF2Zh2Eu`rT|?%BLMEoh-XGb- zQdTjQcK0xHJXhLZ$H#hoG)h!acbiJt0o+re5`9eaV!nCIf&ywlahaK%HC>Jc_@e0E z2qZfKX1PJ2s$3$TFp*^u9Uf8^aGQ8H4A{tKf(MLCMSqN_$$3f&WT8E5`3A)YwLr(= z2G00d+1b9+@0pkiMu0*s^ST4s?(~aXO-%{=R5w)_P$n3d40sXxz0t5(1Jylp@uG&& z^Qx&TX#)qXZ3ISb9s2#^DkXds)<5SK8% z&+AFjt8QYZwTI#);A0BRSn65;8O`+0Im|*-+FeG@O$7UBO0K5o-9)J zYKrmAUael9HB+5_x zJzJPqw*Emdz*SoeTPPp!a^kDB!MZ!xG%1ec-I8>R0po}i234Q`Ytw#ZLhu`ng_S2q zxIcduD{7KvW@)aM1MIq%f!XAnB~3fr>RA}czhKE;1tK#kWxZT_%ta076@L`&s54Kf z#E9h*aaa3ok?gQ4cz3c8z)oar;t0=(Z3YC4O$6X;m7e_oDlQumbBSTgC6u#{%oCg%sQ<2?}P#|HZ02+v?tcSDxOeAv=3vg+}js&VE>%%1kgNZvQU{yiGxQ z%r!iuiMu=1EL}Kj5Cy#JrRMmq1m+OI^#fmNkw|BXM#mD`qV5LZh1 zv2NCG6H7tv{wDVY`bAm&FK#Lhx$w6?Vt}STR(c};xXEQQtc;Zl&7tt**^e~2zxr#5 z?=P)L!5uR3!VXM)ELF;$W)r#5DUN`G2hMSbDs&;MA;B|%E~VHLK9~3&-bET-I#(vl zLp9D0Fq&RzzS+*Ph3lF+q`rFDd4HS%BT5e*fZz=lR;Z3}WoIuWs)lr)?EWYe&_ohf z1`ioCY6}GAe0bnV*#mIuKzvt7TS7)DdbI<-Zn7b>-qw&;002Oq?G*rlZ~9n)r5oD8 zG%38sMyoa~XUnM$p$;MB|yZem3-(aM@!^sve-;NY+@`BwKwwN&{9^0s)&8yUAV{NCoPFz6nAL z>rCBod-&D$_P^ikU@p}lAcACX^BfK0T=j*GNBq#CCF1x|2ZSY;`BI&ZA{!VJ$KbJO3E zpUkwE_1rglFl?hTay`qz;RE&9_MotlU%`AB;|=MuHH_s+8@m$E+o^bh*^@iA}u{(cP*lfk{(uXu0iV23v*H3)~xSRnar0~-Qh$r<1UV4a5PV;8ACV3_G+0F}ZW&XG}fFHRQAAU)D zl6N#y#gdhoYL`>Vz(mSemS8ZeEFByv#YZxVB zlsNT3&j+rT&zr(p;0akFSrwhbAJU9~>t&1naVAYR9XA6`5H9Iep>FR}0)604`}jIALrm43W41k}(8W7Mlay8Dj9 zxKBiI8OQAK0caLz;lC2d00yqHJf9r~5NOUttc*=W!gpv{Is3u3x-7sK0gc#)M3?Xj zoX;}I%;+1#G674fUJszx*JrVA-Ai}+PU%?#V9{zS1uW%57Df$;!1o{Up*5%bt^ke) z7~?usVD!fcaN3TC88svWGa3L=R|m$_)s~+U$ll?VefSxT_i%1VHE8%}qrNZ^>bRb!EL?%B<*MvwHj!o2RPor; zRV7q%B2(MH0^f2+Iq20y_R^mczT0iYKiGl=3@d;%6rj@z@R-%7?fV+ zKwRc;b*!+|1&=*nI!(I>TVaA;j#6}y9{as`!7fks=1VM-G)aOgGb4&-8HJ&j08dw3 zV~g0NC`EB~lf*114%5LHGk(Jucq30KP7-t;l4d2qm{-03_NB_`0lAD8U`b8;<3P_; zIHdWsAuDx%ogQd7YW@F+p4Gpj_X1m>poG9)oQZOnU0LW&eTu#Ge?-q!6Oc?4_Itc8 z0M@<$6brL4Ly3EnF7Hc@Beye4waM7CUkzzz9yUxxzek^bZAd-Xw_$7lJw|)pkbX=sGh^2ahg%)Csa17dXpLnv+;M=)UvBr16zfe5Lo+GS9oaP`;0Do2hNXfpKdOs-GKhIQ zT`C&zQA1{TD(a1JseqP?y3pIH*Lh8)BAzZ9Qgc($Y5t``mfPxL-=<=U|CEY_Z)?b2 z2}MN{*gXOQr22R5XxcDl7XZ>Ifp=7+4o@3#?RZh9J@QXah%`(Fe2u~1>v zO7u&nmWLCt>%MCT)&pT%`zKz!S3uh*NeHs`kfIIdSnHGPZn1?e*4FQN=#wA1u=Ri_ z1ZH1+oa1~m;%9Z7W&H2O5(+xb)Ct?fx5mBv`Hcx+`8C9Q$)Z&iH>Dzw&Jhff-KWn5 zF5ea0$Z+)x?^vmZ)#L!*)?48+06Gt=0`ZCiO!0qr742zO z`VIMe`Z#t?k|g~UWX}8l^(uPWT%};Ss|UpvE+a{Gl{a|%1M;+~?8#?W1@pNd#f1K4 zWbbz5jsK^3@hecm`QHe=&P=frwx=8s9}tR8)U*>cC;-}bLNSG-cA|2lw!EkU#KYI7 zLyUK}xEiMai}2x_&kh6ze#y4ncOnX%9gHCK$xxu)e54{V0M_=&GJtFnI7UQTUEZBsPDB9D&o{_4ao|>ZLdl*i#J#v+`%EX*%H<6b|@1@n@R? zNk4>}z95dAaY&o~R1eeJ$r%~gZ<{EpM=f8(oMH}Njd)VVh&Hk5MMISFdN|NTA zvIbdUt`z@QuEMuiC(Uo`eOU{B_k3afX-iw5<>5(GJg{s39W<7@kdaW(X<;2E?28KN z-}L^FBysdYaaAP(NfE(<-A$G)o1jErpQvDQx6X$ofnqWp~NIo?C3xGY_Na$Hqe;H^N4 zBlTKgk0Pr;VU?!6q)-7sZjr&LHwDwt|A}BpN&WtBQXI_ozfW;~kgfmAZM)c|;?heD zn0VQM@F_ZUt%XL=#Bkv!vA!c_hC4i1G!6Ypbhi&NGG13CLpYeaFIEMez zJC?OMnApG*+swTFCu#fd!3Tuk*#9O~|6jk4gb!Aav?zN?(~cGC88Z_`gTfF*iVU2x zq^Z!$LhFR_x*KJJ2sYM8;^Y^0*-?r&7v2W<*WO;L((b`cQ6UWAFToM29Tpw*%uC#a zgo#1P6mMYh{mJskDP%vxiQ~y+#^u*wQ`H-j4r%5H7Aq3cNYXlj#J;qIr(Zak@ANuY znbWweo|CdY^~Ot_N)(HS%_`q4qfn`r-CGo7b%co{;iyrwWmsxgqIo!3&=u<;%iYoP zxqRIEv9{`sH7#u{?0#d_R#kfFEL|R{hpc2cVLoZh2+q3h3PmmMsk2 zhwd%O_K%L_k^all_uW@F%Ob#Aj=b5F80qJeN1h?`8BWf<2LdmPF8_#~YsA>KSB?-G~=6Idp`Epdb=-omQZu>ruaA)J1XVA@ryd#$!Rz z(2^;`d3K&wzO?jizsf;}ZJd6bkyZ|(1@;{(R)Oe%uwY8Pt#!0B0!Fnd0_Ux#!%r%&dZvmE@hA-Wn3FUUsdrqS*^BQP4c z@%t(`Fs$pClIoZ9<7kUx%_Ofp1?^DatCASf2XO*7JmAlM`Z@`}PQhfVL+NIj|Hq0%qF;}3NPj3K>*5BYk`V|Up z4(q|s(t`&e=;s@jc66T?n8O-RBUZaP#PF97t~4&6&dj6-KCTTo;0Vk1y-*1sHAMO& z36M1^;ayBa#XMmjdkJ0I zonX&=O-BoSfx^?Wi}a&FnKUR&NY)P5(RXiS+iLhAT=N-YnnaeX*3-Q3Gna#hLD z5d{;1X~7YNeCBe6-dlF393>r2Iq363^Ue7(2+}YQ4YpzmD{@y@owsbqU0$6_g)ei4 zrSI{rUN79WN6k4uNR>fqo^n)zMZ%(wE;8}MEPZ3yT188m$k?42zyc?R$erFuuzfKy zKU)V2%mQvc_7{u9TdrPsaUQ-NnSi9Lc!h-@j+P<~vR3t@0ZXDCGGvm2G0_g$GVzpq z@#o=rH(XV>sNwFYR+o~x5%LBK1PZJ^1d0i+J@z$rnpG+Gb$n`7V!;aH%FTaN=X>3L z{A`1Bwb`piKdc5i$nH%Vn&$iNc*Bdp01YPGzOw87jauyLo=C6n>&;&1>i+1iyT18T zsjDkan2pG6eIy1|RqrL8EHjhpyb7)*=#BL8)D|%K$oFLv;TMvK@U|?%zaMXIJEpUg zGJq&2#hfku^nxTKg2l$;>cE~{H<7iEIkcbmCvKaVb`Q0z)+%)^p5xxDwS{Cty@?D) zKSBl~IRs|g$tv@%1y?%yHi~%96TLtj7w&IJdb1aQ6Z0C4ZxrMlVJ4-c^3TJQTE8r8 znOhug{Ulx8TMUyw85z0lFw+BGfAW=e_Ts$obHXSeD+xm-To_0cB@*RM2_)~Mr;UEB zS;lmK19m$C-~9;3q9`G}!X0N7$8sKYXrETrWKxaLr)3G0o`*czkMx9_wA_D8IYSFL z)^tYdkLNbk*X9l44+2RjJ*S>fhuwY*VpvRnH^^a6GcO0sCtSv2Dh7$-qtrnVWit&xcyB))wLqWSxy3$~ih>bdWW zIb!TBu3f>g{Aaj`9W(3&PWVK}D>p0m+{S4-X(X<9kRvGd^gv+)$GJL^?vzdyeRDI=Xg|y0F`(yS~mk4Ze8oO!wd_Jvv z7<&5v%nJjRp5s@&<10PyD1bvh-+Ve||M5z#-Lg$@MCi?&A3=YIMR50}F#Z;vnEUgz zDZ##o++*8RztjPV_Y1a(&0E!-@>GLkX^Y(UwaQkJ6JbGI#_rv~^C1MI;kbvjC-;iy z&q@3eQ!&P;>Vue?bx4{cNzSgd$hUs=97(lQA=Fo;eo(wU68^*|9-27j{>`WMYC%W9 z`8};0ROcLR__f@@TK(K{novg{4V`WnGa3l!jn4sl`V^mH4qpgt*sTg~?DV=@n5~*^ z2!0;PHhVMGZaN@Aas|#kXF}He5`O~6@xQ70)x^H_4fulyobzXM4>s^Tv(L=x*y7wX zW=M<9H+m3#E26259A9RZsqGhEi`N-E@6z@(Q8^cL>FDI>P}{p^MbBhM%FMQ8hfB>a zmqvsn?_NJe@4s(aksJ$(Pcy$1)`eg{gGvo|Wg}yr=lu?c|IQDVI$ZOqE)(DAi4B&l8XN5fYu8*L}o9h5oTt#Re92d?V=?%<<0;l7#nd8c&;gggn^z^F9CSMdU{00-OPj%q)+J`80Dl<%yEQsMy zJuFLbAn=pVTiEO;X_qP*RA*e{G$Ad+0(;oEWA;^|98}(1!~=>)ksmF}z&!>cgu#+% z1k1hPY9za{3-ooh$TBF$n+{H4Q4Q`f7q<3^*~Ir^rpq^`8^Z@r#$HqtwVICnGtitr zHtt+fE7SDYnXCN-W8lQS(YJa)>~3Uz;d9XjVecSUko9KxWd5uK9)#g0@$}dc1giZ} zLg4H|CCZh*2X@ZLdmwExTNC1P6Q$zXwPLYBuzE7$Ar-}zv3TbF=KScvpqcjZw4-Pp zHymGDW6eR&s*k1K&CI~C$uDvg3epU$dehCiIG%n0Y5v%`OrsnbcLFp~x_JD`KEu>yb)vkKzf^==KNfzW&P<(@*9lV*+^5z>H-Dhi! z2SlLexnS;g6{^9ULO6S5T!T5(!M#w7-QE)HCN^S<>Ll8v-ikpjFT%+Z@Dr00D@oU= zXmjpK-`4S#pz#*KZCmrK2TE<$2 zB}EIB1^(qh59TEm5YEt&Jo;-EQL&aUM1CPgJe3l`TGEvt7m@m)xWvcAlX2PnXF z8`0n6_cJobr|=NJ8~4`Cj$jOB-=y`6)4Oo7c`+@t87C*7`{G<3>4u!!0FBqgcXsAb zUQ9!XYE_yCndw^6Q%V9c#7J96g1FBhX6gYY4I3Fc59-^C{o1|`e})&(IdQh{iRK%a zTWSY%SnO@@e_prN4m4gKsp`r%2)L5j5fE0%MnOnht9@6_RH*QIu|txh{f zX+Hnz2B|ztt%IxhQ6;x79db7~#>@rn+!glf9; z1$FA&SBKgbsv8KlYt_z8AD%Ix8U1BTjqo6zv&H7=pc#ES-~}eH)qcn)t4M4ew$KZG z=-0fkX9)%xGGbZbbON=|54>cqVcG<@;1Ij*I+5sluMkBlM3&5_6z5@hZc+ZcyW)z` zsuDc7)}uZds5)H9pw2lTDy4&%549mzIi5H^R|a2sR|p!MGs9z#E8(;2*&3iVXv3h& zact$j1Yv7Jn0kV9#=Qjq&~n$UwJbik6Zr6;y-OO#Uj4paF|9hNwI1qDy!@wMR`I;g zA>AlA@On{k@rXLeVs#`cd?L5x8qfNTVN&)*0m6L(Z+YCbmj})iNAyjU0<=D7ddaoH z(QDoQ4L=fa%rETDsb89lvBq`#ojtj;l5ljKS^45yaZF;Yy-Ej+@1_eliJQ;ai;MJM zGrBDBotv_Zfm)#Bz)aZE@AgG>LhmtjuEiIjCnqYPH5AXQf&9vSD(i7A#O3Kx4yhvTrIgba2bx>Fr$X={Fb?8)xe9^w(4t zv?rW&SX@Ua-d!gawMjchKzkegyr7bbjxJ1x$2I9OTMN`dY@xf2pu6{2#X`ffx2AML zF~Qs3q*0-4oT= zHAa+)utmCv&`@k%UUF2+0PuwAen6s&4(Ic^K%>!hxHEJ47y}fjOvuUhsEjvDe}eAF zlVY~^EarB|oq5qY*`2TVU&3la?9m1RIOt&$wxuBj>q_Y-7fsL4gM2DqV&O*H-w$(Saj_Fryt+m^)%ZORUyE9m-yNfn`sL-r= z;$lc_A%G^DW)%Cs zzS*?=!fJ0m{P%Z6#J;p|VvqBkfzW2wJ`en=wip|97+@0;43Q480WYr9ryU=jJ1h$yA| zzTn0#C!)C*KexqjHn6zt;3lVWlu zLMU&9tnrbcHSnTsk4Ye+q$4(LA*6a|Z?J=At$S{R zYhcSV61V$(hemMcDQNZCj7l%xH;~!h{DbQsT(#ZBjWhAz7on(wn7t6tXg$Y^+`KXrp9Xy*PUA4vyU~IL zkatJ#Yr(u|Ty|B@ozz1U_B~-gBoQ<-=&n6&h`NRTPMdA5PEpr*iyAhXh;5t~b$1A= zaYY3LS~|=@ZU*a68_*Z$a`<5oT>K6izs2iXEm*f&2xqxx@j=MbSq+Z5(}1ocqpb9H z-p9N}du^}YEvvZApu@|eCu!*iu2u~Xf~fIdaju!(PUV=xWM8%gyz5+c7#Nm!(|Jqh z>k`p(YbSPBkI{8_)iO745ZBUnPG0MG%X0H3+4^8F*Z5%XrOCm3q~uAB+j3?3&n^g$ z;a8TQ5r?13E}MaOHi^8-MM=B>#Sd5JhUphyfOZg}bQVXxjy-;7 z;$6W@jkMl|27QChIXi71vAyPMw(=Vyto;c; zINrDKRlE22R5^qeM+@+SK!yuG@cj!R|UabJ)(7DOGQVDV!AP++XGUQG7SmK~}& zJdmqhv{1)W5IQh_GqIdrPpQ$4col1kcsz@LJPRjQLq%Y`=rGCaQ^LC|M|UB(~NLSjpmuP5<88UAZ-*@ZkcRr*;kyrdc&PR+pU`>UZ{-WcN8Y zU%gLrZ%X@O{G*@_?^a0YC3$7i9L~b6QYbIBcIb~^SP(Vo8>MzDtn{!}6IkqxEL!+(lTN@b2P^7XqYF~%!j-&7BE=OJedZj_V!q9(sS8f-TOaZ_^y zWyBfCOS}}Xf}CX0v{W4O&F*0XAJJi`M11R0v^E1TL-9$LH=-@)>kW(b9q@n=!I_Tr zz4o}9AidEmb;PyRX*f8lRS6)Cw^oc&alJx^)5_iEREjGF(61{*5L?;dopVI&LBX>F z)^u0bOe=?c=CurwKD#5umUGXvuB%Szo4Zxd!quoYLJ9{WJ-5@r&;6IIN~B+IeM{Z# zmJj(vd( zZ_#N&1dn9*9FGi?J7Vo>nQK{>fCCSp2|Lom-VR43w5EnhPQMDp1G{~`pi(>sj>~nS zr2RG+dfhVr#Kzf?T=k)~*bDD|%NS#fv$coTI^pDc{>OE|B;`W)z}cIg&aTBe1_iUK z0~qH+G@}p6?DMVQ;ujDtM|XzW3g54!?V_`Co((Z`4mi!jgl^f`z_mNrGJGdGW-R4e zyg+qzjc*ea`M~1y%*}TL|Bh%4(co)>hIaQ$o1n*Z`?Wuh2yUBS@z?&G;Xo|f)KyZo zA1~(uNzJf!(Pn08B?%3(zK|AbwT*GU*iTJF=iG+lw7SOJF5+GGU|+XCIcE5z@wk6k zF=Z>>(#%2?>?-B;=fps4ZaF;)Co^1pwo&X#6pTPcok*vr<)Jdm!?ra%MDu!gTx&7BF?sI$ zL8Fu_6svvD#%cfMtH3S%iZqdk{eur33uk`ELjfR9P`M}Ty#g#g5iC?5F5RuU!GQ`F zi?Is~?r#hg#em3z1&0rd-~B@&z1N0=t|TF1 zw@j$b0P5MU!7~&GOeSF}p+$Fgm*bAN;fU$_0jHnH7;ZB;iWp7Ds$D7QtY7$?-IM7A zgv5aTV4cesLl-O#7b~C36}iZk3$|}nkKM`NF(s(z6{0?lrsjN29nn0;fPMJr(MBFA zJ&QkNrRW%l5Gn$hDcYysRQj25q6|43L+@srp67mM0(ekFc|)$=4U0n^bErsen^f+s z%rZW(4FX{0hMO$XqU@fWxow&I*9k#-vv1k&A{tsUI&6?Oc`7HUVJl!aaW-CizMECybh%@`r-JoWjm{drd8%%%=;*}~`W(!W`x$~X&ge|q z7ct!AshbPfkt(syf4z9d^~L;o)Wsfpq;{gtdzl3q^m7n=TU|VIws0>4-|L)Mmiouo zy(lWE)2&W6>cW+p|5yBYrRm-9Ja`>NG46aK#Kz6|J`H^fNZ~+AL`B+co9GIk^b=dCP*3;K=P#kRce{tGf)p~d0&E9Z#CeWFPym#-i zR`xgs@sL;MB3G1T#w(+c{tL*q!KX)1?K`D)Xj}j0b;NdEb(D%nJ*tQHtwN89cTtdC z1`VX*oV6ilQSt_35oZ;}fQ}-nx@CIn2P&1=`Y-`?k69E+O$Sge-|Y5*SHxw)rsS9Q zUwDCABrE$oOKkDt>Eh5bIX@hZQ=iA+Yg#FJB zh`IFxy{Z-+me8OEZ^lsDww9papYy2UNbyfW99VcQn8k+dBWmZY2=%`S3hJGB0IOCCEZq-f!*;~N7icx==HT< zxYq*4E;JYJvp!>}<&tm;1LqCWlbup&!iBJa>U>PbaH>EiSGVJ|}t)Aci>JNcyt3S8!V7{yF9A_8w_rstuo0g*( zjl@MW;NgsV2H#A1=Xa-&ZDTq)TtkPOk9}DB{bzJ~Ozc|=ej0vNKJ{h_-1@X)h=k}v z>!hq=@P`Iv+y&C+Zq(gSTvh22D(B1nH=W(GgZthXvIa}cjsuNUk*TxJLcciMPtIow zBJVKwYgM8b&)APKk@848WEE^`(fhG*MaVhdCPI$YfC{*JOz~yXCziEh8ix;Sg`yS% zuKz(ND`80H0u zdj7XJ5H--jw3)6IWH5V}hF*RZwb&?N6SU-Mf>8U8HCkSr3}l^F#jW8``@7ai+1-Yp zpvnhc(`d4Vj-Rz`-#<$Rja7i9q&Dl65DXDwPU)3S;rRES#f@hnMT_LlL8EnKJ%=Xu zYNK>}-3jPsU5(M@be9rzS=?sy(?#dZ8HTZHO{(t3rIH2PqBHaxI4U)b#gCdfwEFyA z$c5{|5kA|%ty!t_Lf0~_tHD|X?Y7oJym)GBogn-kXO(t-zXL~m-YH!!GUYnJNp1nb zHq#$2@%^wcc8}xEA&E$PrdgoXZ07mOsAq9JS>;Z%yU#B-Z@MawL7a-Kb&L?*>A6J% zQRGnGfi~y7FmR==1xtf@PcTy@wCp8m|72WnFyGsqb*IlAkhry|tev;~vl0isfBune z7-v5%aivN!LNpt(DSm(tz7M54{^Pj>(zvFu_;kLwn=Ww9kXlc~LD*_n)R~S^yRaF) z(3YrHy>Ww+i(~OQz@s8iTy;ZM^<2RLXU4@!Wf}+ZF#3dBmu^FAmn?l-COR|S&u)Fh zFWRT`%33|}3m4n(pZFcgH$@2-WPYPgNCo%@4$vk4>`5Qa;Gw0aJ)^7dyGhU~` zso*Su1sxfVs8bmau)G?pKBelxWy4F~I9BkSO`1UOay ztG(Y$9>%k*PRjS%!}B>cg`g<@4;=jq`|Jzn@D`J3@NsgK5H*hM5CN%#QT{@49ITGU=dN8PVwK zVcM~?yCSQW?kZ|_i%LgIO*Em=y$%oxtCibb+1Q(S-D`;2xzI3(do>-uTm5qDSS35$ zznAXf`&eCQAOb&n5hZVP(UDQV_pi^yzh(F^YbUk>HM_1B2q_T4)e}g1 z#_~Ls8Ce2sl|7_-hAbzgrzDiV*p2#{0Ys7~7SwT-%+Mb5Bw{oyYjPGy{W()bds`vDX#uag1m7BYgauw#5-(Z2J4;=O!Z z|D_AoZZ2GuGUsfjO#ixo_Z0)z^=(H43@A5fxaK#eNuoCmzb2jKs>&yM^IS4^$TSxd z?W7+-rC}lS35mAEeFyyAKHEDOn~f)W2fm4Jg*&L_KUH-MoPleqJ0k#kqtlK9hQLhlua-*{A8THS~o-yR2G)OFS0{0ohx=h=!s}WSAp2U2^ z?P$wnZdL`LrW#3hQsp#$61Ve%PhhCB0`^;%R%x^SQ1-LLNSleJQcYJ*>POz#8y&WJSEF z!Hpjn{GM2op6K*rUgdjgoXp^iNQ}k6y{}51lN346T6>R52mMI=V4I?dJfYM0m}j)# zh8n-Bj%{Qf?fCC=dqiI4hQ}~7?`7uXrtQj{YP9GrOxx{W9d1^UL*m+Q&WHWitT1}d zVGrN?{p%Mc(7Q4hh`CX==xQ+_fpisJ%e*_NCI_(;Md|Uy)lFu$i=(_#-LCe}OM}Bz zWw&Dx9S)+KJcYsavbw>2N!;dO-NzO)(M`H)21s!y++Jl_HItH@6Xh9A4exxJ-#1(G zo($<)!GC{+?Cn1j;}rQ}$86>1vI`$|$Jvftg&q4!mKQmudfoi|5@`@{kmBcr&-_Zh zVJ|#=Ht0;CRlOXzY{zwa^|{U?ZhV}MP0G@qcH0G((8~66G$YAnE~JqVKAXZg15Ct7 zsY<=ku^3>yEihN<@72;+OP8Tz3S4$M&XJl-0$V;s@EYRsG9aS?(Aw7WQH>W&0uZRi zXgqY37AMguB%htgxv_HfYhB>YV^9*_7=p50x7D02FBe%6?d9g7u{UFk~_HCp-OtEbn}pxU5Apfi+n z__K-1v0D-LABV#0XUr`X%U;@9%w5Ne;rYPl*WAIA=PDC+weIt;q0+Jg_&%kcfZv0& z54N;2ty`=$1Yv7)x#LoKE0D(TDP*O4?7tg?KqS=STILPS`;nUEC%q4TkLDQWk~}V0 z%qCt_=s`{{8O(Fqu3zGb&O_Kg%bO$WzwqI8=zk_OcgyRpEE~Jx_Gcf` zqXxr8X7j)Iz}j*LU;HOhRKA+KKDRtyLO4ZME@GV`UkeOM56GC_)HV|`N@B|gat-H~ zhtg$(C+`i4YD7CmlLwgZXOjL9JO^v%*hlb<)CR++ zPT^L|`80Ic{0-Y;1;!)n>vj+enub1d-`gbD2WHCdtop~7;ecPt>kIfU)m0x2soN#ommKVDtWM+d(>`9->%4|L=1LLc%b}`qeN=@HW7^l^F zY0uFb(ayN#LcI~~#ON2vr6`Ra>mHfGs)CFQs9_UZSTu!9V)<19<|hleGfEznIFZ3O z@$DW0OnYGhWKmIJX_~ZU%O9iz6GC_}?$yRj983+5ZAQT4xrcV|J&MkW-r*+G8!XpE zMbeyI)Vp-4I~qNZ@g<5!=!YGQi^OiPmUF9HYE=BdZ~g1ylxOY-u1E;?)4jjQ!Y@{O z*?-NU&nqp-jm9iQ&IU^{e_P$7h(7@aU6nHTEV}<5_pK$B3iKiItvpeQ@-ebZLaPF4 zC3=7T@7}`+#s7u#$KWHiEbP+;zQinh6Llzricdv_R4<$QYhn@!w zYxJ0_b`w0NEG3+!!?-dDU<+8q|3DjU-{Ks;-nmNiKmD*%bwL1m-@~XmY3_M8W8*=A zzIMN07vwbam&>N2*Q%lp@BPe5Dxi!XQn3Gr4ESy`>At_C`p9eGHpZiO%GtM8&|Kp97;zX zKW`726|J0LU~#b5z63ot3j)l8L?zj~RX>@1M=~yuulTzWy3PEIuk#eu*L*K8nLC?n zCbHIV&4P3$lLHonLfLw08EaR<-?@?G1w5z@{V6Ip6rJAtIcA&um>aumSWxT zyBqSd+aBr~+a6w>Ir!j&-3z-obJ}oa*o}VzE5iP+D&!kmygLA)YP&hrs2!)H;VH3f z??7pv%dq2)O_IfT#kZ%MF{F@`7ylApgMs$~p=s3Op~WcO@s!Zuxy~XXLAT|Uesbu^ zmzf&JQ-eLYT8;q2pWn9UlKM39#A_14)HQI7|F6PV$6AAq!`27sMQ-zi!hyxiCR|xC z)i)2Zeo&tipRgvv-#{e$Iv5Q%D4otnTm6bNpHBpuvyRPG0q?@P+d#>kBJcwMWco=JqcZWC-C z7o-F#Dlk0C9Je3~YG=TCn}R9kOnfenPE#gX@ADJXginCAaZ$vH>Si~B=wVgzveqAp z0sJ`r2?N1~?Q5%ygLdCdFn7)@ZvR9W#D04X?yWj(?AHYy3?#!k<}vZ2zhSzeX#PH$ zM-8SrbxGeGl4OKW6qsuk)f=x_9Z-I!>-mwRiRg6@PFA=N1;2rs0k?;h94A$PJDx~EsW&kY@E+?jj|Je+)9=h1}xZUIc5XQM2oxd~lsapu# zd3*cY5??YoUwPd=iB~w-rlVonZnqUKKYc9w8iBjD$nOECzAW1}u^)iUurol?^q@g@ z-&)KXAX2ONJs6>!2_n-TcrJho%cJj)kA$s2OZ{#cwA*e8wX;;hPOe@T8M=5aRHLAI?nb`dvGQC9dg-i4_YCy^V17PRfN?kLTDAfu zY(enFd+?zY@$UlDUtDr23LirU@18|DMhapD`5kjEHb?&VqC)+!`bZRP?%6$>%q(Tt4T!!w(_e31 zt$FWvs$WwID1d%!dipoa-1)u_Xn1OtQlx}lEgT=z<3DYwU)<4pu%?rVCx~r{*|(h6 zR;W*FNnMN7K2;yrL5FdBf_PSm34ZVEIrdg;duujcR(|S-zpO-|hN?Hd(?}+YRVIgz{xWr9T4ACoPZ!>p97Cb zmHuWOXW!9b;o9`hI1Uz0>O!Lt#m)aiGXF#AckASMF28Y)LaWOl~ z<{>8YP7&JrCqD9y5a1KjVhhj?x<;iBDfWD-3#AIh;7Z<82a^N+-(hY99$1(ZD0g%O zl{T7|cMv$;DS{gjO0fg?M#vZ1Rh)lAth`SlUcpD3q$PD}HwcClY)PGcC^fM-UbV0}E&93+Rl&YzjM~;*HGFl4US1la7 z%xfX_vh1_lB7-7+p{m-}(v^*t616{E@_%)^o)7$KVbaw!1Uj=aZ$wvduf@8(AQ>|4 zHrQ?Tk6AeHe;ZU{p0r0HAkuNqIE8mFZr(t*we~y^WlXhR7jpDPS^p7+lmCM-LyB(M zqP+)Nw8~em2i^a25=54GjYbQ@ET7YfkZRMH>;h?laNm%1}p_PGAE{5?$fnlCQEZUC}2ozax< zAN|5LJy}R`bidt@=hW|9m^3Btp*Z)8j~!D$bv~^xsY6AJ8uxm&-YtUFf_Wr~@)aPJadq$<1_w zsYZq07}uu#?)93Q_MC8V9D#cWw)448TxdC7c&|c&-8zS81{tCz_I(aKaGCwnX zLqqjTnXgu7c;H1v#?}(%nBqj#E2n1HUoEmQ@;fWcFh9_Pr6jc~CDI11StByTu@l@{ z>OVBlsity+khi2UDAWA&*H=f(wxpyiuAfi4X0NFu;KiUoi|emup=i-YW3ASkEi_iZ zIIsS%0@^F2Tj3rmThyFzT2mJ)5M??Jjg^HR@a`WN;E(k|FLRYahbW1U=ZDd%lx`cTTO%haF=s!64W@A4c`Y=%8?j^r*0`w#aViF^c zP>kkM?Z#u8Kdz2v-pPJVHPVv4E+<`Rf6{)I^QXjy#vi>jWYJ>h+b`QLx=*_*MQ}I% zskxr?pH~{^}hBuB`q?eK((a*B8~>slO~! z!Qjj+VQ2WtU(pRY|cU)T8(1;#5y$vIK-< zPvNHxw&Z9?b)9d-t>9YOw?}$Kd%yg8A_sp4golSBScygUjoH3OAp7#cp||N26L3fzX%r1(?6udjXyI(MM3XqilEl#^B`(ZsCoNSV6HP z0($5n`dc$Q-uMJjw{&_4hf9kRNle*t4{i1pFbwy&rPSs5FBb8sr++R+bqGf`Lq??% zyGvVrJD1M%#kvV0qkJoDGsL5onRe)RW|<)qN1c*CNs`ZMA=0rovCnGO;9Qf?HX_Cr zS`zh&_&3(AhHFHdu92|?oa;6iEUfF8U7z{A#GiXQf*>2Swse_DDg=hX6SDXNO zB`^*p;M!cn*qyy$oiSC5OM!b?J3nvc8ppC@T}v55sDAlT!{v3?(Q7mQHYrY==YiNZ z<=;@5i7h%V*$66H$Tt-Y0qz%ZA+2ME&&tNiAEb|AUVrQv4T=S`=k_Mpjyx0%$z_Ny zifR_q1=w@ZKvBqzh=q0f(*~|q{fco-Pf)Y@js;X)rzr5QQ7crshV}IiIO9Z1l=nOA z;0ZXUrryp4&MFv7jU|b;IExf|5=5%`ztL{{?Y}AQ<(TX_hvKbCz}*TwNMr4G!Zn6i zf{mL+_l71s_N_ym9pnKG`AlV7TpxOztNoLX&j04$8K^2Ih`Ye|roK&B?ftcSTHSaY zruK)}5x;WhCL{`I-0|)9DXEW~0B^Z<&u10u(GIp!Ne|WpLoO18s~IxAabD0!n^tKo2_3?2DDLym{-HuoJ#scEVbY`IXT)LFLsD3Ki*JChswH@oGXQR1?HbjA9kMZ~<_>tDi(ZuWMGrL=f1UJm z_^h&z{5fInj@tIWYh`-T^e-HvaUN0WSZ58-~+TQ zN^717*9~At`!rwL535R!C0sfXs6M5QW`$~qsLN|=56#7&XyPWloh6CbqR!Wia*7|e z2!%E}7P*J|ntSs;n+|epc4|daZ_Hs+hB7<4&$^w-CRJXQY=K#_Yko0*b(UE8*#96q z43iz2oq*ncYW0G5&YU3ok|MinaW0UO)+2z6g;ISi2k`iMWATsmhFd1_W+yy0R3HoM zX*&&};%TFbF5 z3+TSVBOGG2Pp!B6e|#XZKK;NlB{T=4T&(drtHeU1e#%fQ zC}Y=V2p6<{Y*AF|BT5H=W=MW32FDhb0v>l;E{XIBaZs-8aL9-B=(_)gi#UGgc+8gP zU3lw%oJQQ`Vg&=qm7FMno>b3`5dL!#+D+F@-Y1h!6rP(ZI;tfTt#eXccE)eAMZ864 z9mwNO-RfNYhgF(I)=Qw`M(jjBJ#ne(cpS=St*rDX9BXW$` zqBCIp%N ze$uM`LN$55)Em}{#1)r}CB^2fn<-$-v=p}{nM_F}Dwe4w%!;^(X`D~E69CHjB#NJ}KXdA3V|dIWVjAGYhpJ?QQu2zTW!a)dnMI1(#@M2c7RHQuRqOth(Q+ z%-z)7&%s^Lgy4*YD~@GMFBa0CWB42v z3bUCfRZXq^S6rplYH0Vweha*!w4xizUS2V|h@`34tZ3B`eph7?s#c*kRGk`TtD$+8 z@qu)#M}>qAQJtQXwRVx!pv>nU@cJ{wH%rDLLE(BTQjLa{M)& z5o#4{$um91)ZWk}k0{O|u3imUT+xG8EFuMqGt?4(2J|pDDu9MGc9{SF z^UpUIbT4{agkUN2gHp>wN-SUGV@FrwU&?(;SZ>c9J@`kKrm5I<(|_ZyLeWza&qF_W z$L~Za^Zs)hO$a+7+(T&yDQG#7emf#$!N+f+Z;OKPOZ8Bca+k7ZYdW^9=DbU77}<+9!sn3_L&Wnn zkKN8@<%GQr-1H2MEf5+OT#E~6OA#4SWxd#cq2@SX=<`HOD%%|yyI%Uj@(K0_@%94v zcy=6y*65hLd21Pe3(<-de*`-QJ2uUidfP1L6JG6UMw7hY5QKC3&+ZNP+J zLV$rN?FCWSg(JAj3+Gd;AV`rOp%Ar&v02~BbMSClv${*@TjEiy?#J!snVhPo_k_ur zzMb3h(;E#xK+)jky_x2HkUshmTAU>Oja8X?0C}LZp5y)H{(9ewulMX4POXc8Z*z4| zM4@u0S^cd3&#rI6Od6tMf5LRJysnwZ24M|dO!9g0ZzVTE z*pd5TL#DDL&l_Y;g|uA9n@p)$^vP$1Jt}Zd4sP<783|~Rm*rP)5)KyzOb)c0627FU zwW4=T3g+*QNH!*`O}I~(Ko=!%C{eakR<4{8QlGC%4sRWZm85JZ)ZL+Mk3}#BRq%__ zow&aI5|W1Qe7K#z|Lsn}kSr$KpM;xjh&SPlqztFjk)EGt-#f=$w8yeIx8vqD2QBJZ>isn~RZhav+-X%yrwdX>*woV$)a9j4b%F1t-=kbDN5g^3KQ zjO&!l8;5{>Y8O5^iLtLqObc`{Cv$#K93{<~A3CG2>Wz;`(u5Zr5@$<&|j9}ule(F)jOKB*t0 zl~$Q2e~Rz9#vRS>6Jj;?DNTo7kzRkBqlH+w#=P&A);4PD&eO+tUym6bJ}bn^+kepd zSYq8SL1I)04uBS)%FLq(W426Ax`-F z;RA}v9pc@0BcFtr`w{?@$}Vt=-sAH6LNT9aq;2~nV!03KWhoTNOLGX1X#d?iSikrB z4|<4yVJEC=D{AN}>PlS|aIBv#6p@F+vRtmvHm-mukjv{xeHm)urFGc*6f+5tA1tdD z)TV!^rHFU7+MM)!s8(HlRwUv17-FKXv}g2(=%_W`8)N-xNx{d$K>+xqpL?`F_;}?z z++@jU5yfC7VnUZd8;#Jq<0{0|z|-Et#LzK|)-8yQKamu$5oBBXPb711GCInyYe@_d z-W4RliRzd$w`XtUVK!Kd9?FD?HpEk$xtEE;m^ZLK6KPs?OCOJuL~tKYbzhI9JoS_I zoXmnFGN@H!T#AS87H;Tv?NUS*RvOc|VLsSAe^L5zYt+R%T3m@KB`c}PR+MiC@yq@n z)=RG_0n4K?7M-vY8EWJrAWaLqSQ?85UgSMc9}%eLeb|5MA$?Zbov8JQ06X7G{WS~H z5IU7SA9-6jaP3DhA&jdI2wZEeZ7amR?I&rfX}SVeV`1s|=mU`sj^JX?KdylX234GF zkOu0&s70nMjLioiu-aCw0Dt?2A+?^zyqL5(fEByH1Jd9x_guq?(}*pqqZ{MP${tBL zipYAhRVla}&eM?S<}oFAl+7GFasPo5>XyeVWm2`8dc--OKuJ9IxHf>3=18Vd;O@QKBul6j%;bOxwuv8Z}pIh6h2 z2briA(%hg^MR)LpmH$`CtsJB`^S=Ezj8S<}Hsh693iMTI!v58UrFWPUn*xpy_6fMp zZQjj9!0n9-{UQT$d_kE1ZfA$+-QE^KLq&8s*&B3g%8wtkHX$*$XLEj@2aTtv;r6~7 zJrdB_KClBGibE5HUvfJxxr)OIUkAOJ4O@_a_B9h$_gDAz_y9#^RN>xq%f5;*(CL7< zQ~cXa1fe`1+R?35%%T82S}M2t zqS^Hlt#glX>7>BJwXT}y)a9LJs1{{?#`vCBpLF`y-9Bm^eZu}4Tx`0!Pmk$LF^ze{ zK1xIxa2QoQDcZbv>zaWnW%^;QOg>&(j4j|;FGS$IYo*8Q_r?Jq0Fu>(;!;wq-p-NI zlTV#Ms0-m#n@FtG{jv%d-lYw%Vuj9pMwm&aTxv%i9-$LaT;udx@?QudcPA(^ik`3~C@ z|AfcDAo-!CguM&wk|(Zjphb}zpS$lI&O4tX=F9o8{jOy*ZU4`SdIP)xzOCm_Kkei- z)^hy5ke7r+vcZ^8o1!FC_AQ5VZ2wqcJA^}F;E~xdc1aVZj_<-0(L6e7 zoK<#)cvufo>TU1@C!{{M08IX^Q`=hlJxk-vRTJ%Fvqgo0G_wY2k=f8yP@0SVLxe4- z+qZ`(ZNsZ#L0tl(eqzviXoDFcb?>fIVu;v*&0@;`*DMyc;eAqXfF~zKt9xss4U#_U z#y^Dr?0Qk!f=O`K@!IS2_K<_OjU0jeJq;{tSXGYDg(DytJ|gS4|AB;c{#^Y@e|smd<-M-=)aK5w6SEeX=X#?~_9J z;H5)f7fMDGp4nC)QSxjb*S+R;#gBaIX+bhz$@SI_PUp30f1X(@B4r?#1P=vAhm_As zA?Bwa(OH}R-Ktuw+rnQR=@^WGh)TRpk-a2q=Xk|qmf_eH>l8!FpTGHBfmK7q*+QoUeX04Mn2T39Zw_P)(oGBSRZs2);vWFv zJvIDU?z=G+(Jw~!+5q-->J8>AZZ^&%Awkoi@XnM@jma2bNaYXm$ah!OHzRg>3mSK~ z?}!54fl~+C0Uel6oq)#aVGC@@f2q5 z0jNmDd``n)H9sE9JA*Z_9eTd7^#KJq-LDQQd9Sg4`Msbf@H*)f#CcAz2dqRkDI?vg7uywfDidoOmi)vxyrMX z+FfO-M@A6#dT^5tVCa&AaGeUl&$L-tvmL-ke?Cix`0w^!vgnkqO?Z1{%CMluyU@W+ zt8!yn&(D+<`q9lZ5eu}n=R@F2IY+gW&g?aX?Xf$&@8ey3aHeCj)yj<7&l zJhfKJwmUQjD_@F8qQ9gDm;?6onVEN5+OtwK{_OvI~GZ|(@C_?n7xdoan<|?*QVCKCU5a)}OU0YdyLGNTV|4Z=D4%_&tfr&}T8SJzbWAf`UF{iT z;*waH|ub@37!MwpMALra#pomHvs%d&2*C2mD?tfBOhO; ziXZR3*#!`G{HSTYJ8W{oeo^1eK8w(mon-N@Q*Ky~xWRb{EN}WOux2bxcJZ;P>TM;J*=6S{ zQ|Pm=uIAmhKqU#W0nR#cuGcwXX?=@oeZ(93Bu-}1S&{rU5o6~Wz*NM{_7&2{x|Q!! zFQ3aPDsi)~D874%`PWqpA|K`RwLV@EyPxe-VA2qVo+wD4?l6RcH zU!>$mtAxBfFe{_Sj9yeAKcOW3IEae#heGqP-)anOpcYy+bF%7Y_+M?H@(giS=bLWW z3*_&3_EAE))FS_melS*3yI(@@a*zI6WPksE2TF|nW<2$c-<9oSp!M~>Gi54^ck{Nz zXq|Q~8iP0}AgXk%;fHfBU{|=a4A);$Cj9I#By-KLAUZi6!T;tm#TcoLI>j;2k9(sD z$G}?njyK)ryCT(c%@!opchcL0g`Bpr_xYud(#8xE4E2MI7M_Q^bZ;EPzv)I<-Q0Kq zU0Ln>Q+UlwUSLEyaE!HZ85;DB_T^1C?~Y!xwS+IJ^=eD*%cY_=**BapBra{nXUp1C zoL`aw|X; zJ_SBvz`TKWV=79J--XZ#|=&UmY}G#)mollrY~(6qQdenEgRE|a(2k5x`ocvOVCRQQg_g* zWt9<+6_yANQx44sRjX9Hj`tHiYs{3NAB54TNT39&5^cu<%WoNOJV#-*TEt%Irm<-7u>@A z%b?sD)ME)#X9Ys(RZ96RfLj@3vClEz+F@I9dNP>YLF$U86IaSOmj1W`&$Yl?{K+Up z8AW@tNPe>e`&`*G=JZ5e@D*N&R8KjlyFof@FW9;^aNCErG*QPjB}P6PSbTC2cnM^% zt2WDhu%*gJN*$0h-sk+c63G7ZoE2~PxLLOpqe#ku{5cHlA(`kC+Ce~mbtlNY_vQVE zG^j7Rz`Zqy<2+iQeEWrvB^u~hbQ{;%|}E*lma zLSa+8{}t1UoTyM+@N`&bA;Zk9sNfDm@;SI*k1b!CIWDc0u9dz{cI|*T?%p9xJ8iuS z@0bCggH;T{5Cw%<^vuK+?l{*jh!5CpHr46_og}Lg*DOlzeGH1az1U}N-Xp;U-|9K0 z{iRPlMPHxf__ojtU4E1)c?A4{*AD0GrCP>(9U@PtChcMPUFS3KG2CLz{A~1@W zKk?LDj5h~)+~cQLPUg9fjm<`%EJYPwcRTwLaOx*<+ z`7I?*f8Z4ryU9i3SiJA-zn8+f3b%PfLt%_Tvd5B8%x1AfXxHp=U^U)6rs*v{hS&3t zTOS+sMx-g0u)*2FWnDF+v*7UuBg4PbZ%N1VJ}?BkV!3g%_?De-yWD?UH7`@lC}tI0 zNeP&BxA`6J2G8MK&GB8>hUw^Du4}#c?3Z!5SOOKg^vs0|y~#uIvJl5;-#?ONP~_aTP|y`+@5-*?=t=>aFY)RlZZ$ym-iDBA zchJ|hBOt-oD`#6r!MX&INTJFV&xEpxyqeTcda)1BfuE^r&zL7nkM}FcOM8pwu5pCJPHBit1~d z-=(IsZDQ$T>CnjMHJS_=p6&Z-cvr}DYmYaF>5F&fxUn!hS7@f;m-+d?I)DjIE{-iL z;Lh(0=ORvm5VYG1KDoOtH4EOD6AcZUzE{;<6)`S!-5~=8n?$xLBH2w^J$aFJ{nk!~%Y$v)uvzW~axQ@|+lY zBr7XsX~>VLe&HQZU#6EynfeDBn)T$D;_wBzH{ri$cZ2gB)RAA_m-cOL(h?8s+_psT zXW4l+I}O|t45YS_~9@4 zDTtvG4^=#iW(uifle6oNbxUMWN1j5*-N+~ARyO@XjjiG+OWez3YJV#&`pOVY;VdwI zO=NcWtwLWi(WRN{vSi88cws;4%DfGmV5^qsy;3mGh%8)^q<40Q-^T@^<~6Fwt;SI{ zDfi&*3fZ_r0+Cm@OaH-Xx#mh`gS+5oQ&i6(9!Dq+ISXenmWrd>9adBR(NfzfE0)l` zHhr3_EtJNcFmXPsJ^OP!rYE{`m^&hL6=%Wi5xUB=*cG}8l?NIBa3i|>9<*V1x($Yv z``fc{VN-zkNbuRC`j2#8X8Tf}GhhGdv{VrnEj@xiAvf{@|9)q1{1h-zq*L{EjLJCJ zyj5DSz;bC~x%M*Cte{+a$I%BGHOh9L5PInfen%4mPgrdVfD9&Z2H=u+d(^Q4jPxb9 z-0X`=a&O#@r>vE|aj)>fkWjy9iqZ>_V4d!Lmi18dU!tf zyupk*iiW;tG`1QP34sy6T6>sU+;{AqKe$P9N@KY{a$bj0LNpI!`hq{72AJOJeEW0z zB080uZFc*cyjPG!Y%1Z+=XL?1U&k~Pl1sfrQ+m-aocEx#{kI3aU_ea19B#sRoUoU} z=;Rqfg5&4@iK_jgS-<_akhdakx{1J6L-RmnRo=F~yOSK@07SM&#j?_I4xE1IgTk!< z+vzvl?`Ko@fzO>-CfNE;^jYXl3i>FpDlC@;X`9@f{NXBsEbMe$%$v-lEl%Do5uY4d zG$meCwSjy+>A_}y*Li&)gDu%Ul%d@iRTT4A0nv-MSqTQgD@3~Y4BtI0N1Qb+vXev zgN4)W*Uf8%+qE%Syj&pj_raY@Y5<6ILAGoZ;qvWz*{I6Oy#E?Sm$}w`B4qD>ib#q+ z&o_P!cwO*_cS~sdxlwSZtN>&7`t~9p~#h}&j)M^!AiL2v<NSLov- zw@36_VqU(z=f=6DS<(=3%^#=JjPmQng4#TaW(`m31zrij={BQa=bO_birb?ZFC1&O z!cUR3>i}GmJ87`MxjRN>~wY!2z=E1Sty$2N$w~?0h3fWwlc|%_02-}6AwH6uz5P1jG zD~O55u&|nl^v>qAnSPh4@8i=R<}{;eW71X>e7fBU3wM#G9anv`qKoxgyoEmrWO9hM zfj2=g3k1l#rZ?jI9jB){wRXIOdK>aiW0QZRq6*r|xvt2BBf06BL^#k4{XB$eQB!DC zfEsrtM#p0BmWVj?ygv|sjEaBRtqvn7;WpuDkC@ROp|zy*OESc?^)hD50jS`OHAu`1 zElPaKIJ=?Ob}{>xm2-Xlu{sM?cZ1fm9&N|Vb+j;?als`~wM)(bx1RiniVBg@Ab1yV zBINvrh$ss1vEGaAJ1*EQzG!-eLi=^f(7A#s5_Ok&dCo5_JnA=eLy}e4_hTt87r(Xh z967_u^cjK;UI+@iflaR-e(B%YihZ-rCd!YsIjXYK98WJ+Y*V!DFz*6^osjm`yOAv@ zMc->LlN?1i3W1wJwtS0;!PbzhJTWcnn99Ae=W3CQ2?O`F02b6U22rXIr>3$arnlh4C%>*_4~yj;jUtI5s2F)|fEXwn1pgHDe_12RdcF%x%}=T&=; zDmCk`w+6nAxqd1UaX?+Ys#`sgcg#j! zVU*ueJvRNd3|j@bDoX+7Z zr<|hX%GxxReehCZRRuoy;8g@m)diV1OibmC)22_fV$Y6bq`GwjI;(>pv?kS11v$5g z4Paydq$}M6@vosSIuMzxZI$DYpfVD2K*g+l|U3&;KC;MXPx!asTNKBg8-gdBHg zdX)vk2A9l@wOzxMmT&DT8%%p=U#Qb#Vk8lfy;7Y*xd|2M^0QT8qZy{r{vBcwuG+4R z*<3;SPwy1^xFmZ_iqfjbAql6^buFJe0R~J_+=HFZAK^e+`%U65uTtxk*wW|c&lpc0 zKk?SXKy=wzisPJKZOwVl3GP7{JOxcOKCMIOk`}}O-~kjI6#>SlQa@@AZN&YFO3^&( zEdNF~Y#w&K)z%I>;1=2x0KtZRI996));m*)`XJhw%>1))xx+YoPU*5+D*VWI=yUKj93&_nI6jk6|TP9C2rA zvkX#ocLgBBHzcO7JtH{3!G;@$ZjQFC+f8!^rzDBJQ9*0zhgd?(kUFk0`t(<=TgV$+ zbVKx~{Dn7}G<6l8g&vyMRj-kly?GD2nf0B<(OBVVkYA!Dt06)0OEvF;FL}bsQBzF; zw#1dqF7w{oe!|er1TXM+-kdfzhv)I+S-><_;b;6)r|Vg4D(&~vUIpPlnr&@Vv3nxf zA8ZgbDI!9w;(Gogy;b?f)Q##K?BO_@E7#Q2e%Oj#xF3Ba+9>t&v#4GpNTKJU!}z21 z@>w6`Jr8E)A&y>nD0LHzKStOXCwDTT9!Nrf!nM>VZ+Nfg^thSTr~6G86a&;e@Kk*e z^wC|OTn8^~WEiP#gF)3>page1Y9?^oA=4uV2u~w}%+Vc5)TO?`d3GaiaxslaT0O)N zqMl$pS&FfR#uOAbGX+h_v8pCaL1GeH}E;ai3Yc}MVQVJKnRzppGQHS;wUXb zo-qjx2Q@h9%o|%S>U2wWmZ_Jq9k-t8w^fq|qtIoHP?-P%wUWb~4LN2od1vR+4osaR zR6>y!1qs>ZPrvQIVMq0Q5dBH(^Qn=r=U_hMwOhMo`74Yx z_I;LrtNVb0MXm?k6j2Ct-eY5*EyVAPQGbi~bxNz_&o*u6kEpJ{MvO3@6k;N%97u&* z)~Nir$<*xBpQgquSlPIWzVZ&yH!k8E}@E_4OmQ@)ZLj5|+Iqq=Op zIn9Yfr;v`bL8kSx3S1~rR$&!NeHyICtKfdlrOyw*(7Y;er%L@iTX5e|$!yjGeQo!1@|tIGM(IGOeDj4<3gpmw(i` zSPZQiZFO?$W`<8txMn*U333}nI+!W+Yfj^V5yMt!oxw6FH!snY;q1yX=akkP7iZJ! z&B8d!Kd7QG@I>C`bJR@S9UJ78lmNe~EtBTT0HQ3lGngi>WuG~_DG^D&fmS`Ldwp8~ z2e{q%3B!?rPJRiz1ad1H0}olPzE*}-DRqgb*Pe2%P5)xBV$hpHJ!zwD%2?6vD`*N-jL#8% z@?LPLuyy?o-D}4X8}B}^6YCT8Z1Hhc78pzyj=(CNNG-lL*<7PnV$(B810q(-O&@KOf6ko3+%-5>1qKSC76)KLs6M6o!j*$ z`h={cVY*<*aq|zh9zWVQ-@TuR8L+HihaST62V2xjqw2J6|JZI8Uk_Pw z^JCw^9)b8ep5VKj)kK)&Cr)O^hU4mvyYYy{3dUyLY3_qW0V$Z(_b3l^$|E341vELh zQ(xc#U>c5b-_;S^$8{&X=F?LPwZGtLUhP>fjGo-dTCVG4q`8<}YC!P@{oIuu{lm&C zD}+}Z4F_ZZ*Q3r(ojC8f3zz$XbnhGO{F<9s)?wvOA<-^`)8p0WfnMSQDW$w4n>NkR z3$zu3l2?Jr-VGnvFbcxP+ty|a91b`yl0S1NBQ_ms+xn&TS;4~Y3=va6DSWZjSixS0 z)*qE>+VGs1BWBp}9Wdl$cT3-TJF|kLn%8K3kMK~MFpQ`iE{gS{6I5xiI;j(p5Tb>v zO%*mdcqMWm4hu^Cfk+kPkSksj+YkyS%r|j;65yW%&Sn#BY2OlUYUd4 zhMP5B{%96rK(n$fDlrXu5ORv9!Dwq8>EylE0YTB$@3x9{)K@Il<;poZndwdw{5qFd z^)Pd}5#=<$bx5vHd?Zf=U&Zj@{yZu@l5ATZH?DZ%fmh7tqX1;^onn(3of@HF zhurmN7X546<^ivLIOc`EgBENEd?c@%$;fx%x^bcqq(kcFDw{roIY0es7cN#7OW?(H z;bQA;nDM^MkRbbkw9Uok3E%WSZde^xr^{D{jg6z5NpwwwGwdu0;Rs}-7hbA_i5h0J zDI6avqkTbYNoHEduap@QXKC22BxG**GdPRFiKvvG8iiK~O9Fbb3+4hM zpZOY7tM;R1!Tj3|5fa(Ya>{Z&+h3KHN{&aJfKBbCa>M?#_$ozJz zi4NCu9reS9xt6NX$)45^qrc^!s)4=U{bKe9Ri52nHjgQ~0-C>4ruVn%x55j84|)Vt^t_-gRD@%&J-9z6G1Z z?rgst39ZYimn-If+$2toPP)A~428T7I6k6PwQ_U8GSD}RC4@J)wN=@3TIoSl5)-7jK6Xui)bG`z|OiauOCME*2fx~P>V44_aCZ$+}J4_1dd9Y8)um1*Hf41t; zj=@{S@qk7*SKHH3FP_%isoXtK7^4d*7bmAi|?RM5yGGgcUBLQK(@G zlG$K|sh$Pw?+jtf*rE6rt_-Fg0`ufNq34T-EFx|#-OaN&NNlV(N0OGC| zQBNYRZf&pVb8n|t%OoRh^J;G{JY%CN>p>hXZ_h$)VTnf?C5G(!0I%;abY>2`;>FsQv|6^JkDi2GmZFZ&2lI%X_;3m zJ%O#27+%M#<1{4p8n_4>onH%bHJ$82i%6*m4TkqF)S zSg>@?Jwx+r>n1NH(4J_M15VT=d?$%jC}CoyX!?`!H=>9l+ifC!-Pd;Bik2T?pX>4) z6v4_(zvj?X8>9qh_2B@ab{cS(0&ykE2XYb$y1!C$h$eUzk?Bb3)rFq4t{0|JZzXTa z@Rz(ue@Tfr82!b*x=?dE2SdJ`%5zgU@If_5oMei{wvNqG)m!&`93HE(?Qn%xD?ifD9(-0%9No%z zYs_~bRtg)*AK3@Co4hx*^nmz|;CHi4jG_jBKjb1}zP^V^gE0*@VJPf{70ID(Mo7~p z<6vvh>4nA`fS-?u^In>v_oSf!8Qnd{d+D8NyC646`D9o-9XN=R{kl;xxUXr`F*peP z|FeAl@CnEdf)g0#|Qz$F`x4%)@UZw6o@YkcFmA4zq8^h@FF+8q_I}v-n2p@CrxlIz4hVJbnEaEtOi6&+#M};p<+eV z#bF`}EXaN)Uf~?hiyp?F1|p}6WbXu9S8hMt%XBcqzL}xMKbuGYxpOQT8@m4R?)2}x zgcNi$@7PL%*(@)Qjb20IKttN@S~$=bmJ$amHqs4q+Cd(fM-=vh65&?rG|R`hZ_c3% zbSKZ4$-8AceoODAp%@Q9aQ`f8luU5SVld^q=sV7wBzVL~Rlyc|K|-E4`6r^ScHvCT z-AobHLtP*gg(!dsOw>bKi40P*zFpY%dxzbfuYi%P7>HJe%vIkWSD|0#m>P{-zNAY! z>$G>csawWLYN`wAvLPY_tOnT_xH+6)B&vGB1Gd}p z3#%}*jSSb@DsozP(+3u+cIe;U6K+g6KUjmUD6_C*dQ_X1O}Cx(!HH0~+oG3S7)7cU zVIyUWdtctdiEb4>1Kx`{G03JULo<*zRig@vFM5&=_G6vjRB<)1US{u9si_s9HNQ>f zJ`4-xoU@apWLm{jSeanQd6gliki+L1lDxPdOz+oAgOSN3dHCV!H`lyn`*2=s64J}qT+bm2>o7tE5u-~po{R<& zuk1*njP$W(H6j*7x0+qmMDmuBY|}%Gt%b*$?ri1g;h>q{q+_z7$|?C-%14x-yB?A* z*}g>26x9fgkm|Q&SHFR22Pb0%X(9$$>-=Z15iN!tfdy(N;k^1dLB^d4`FDMMXdl-iSF&Q4c97ZYnmj7Y$U3E>Ze?M2XySG@U29uf!9X- zjX8rqY|YPRee*T+UI$A zhfgiOn2{=v?kL}hIsr2bwIz@E*t&9r6SI<8@1;?2Z`ou{TLF6XVPzmLY=!UXES4Ws z4vBMJ5hWXt&meECZ~qxQ5gL36`!JbMR4u@idDpZZUx3G52}zl`#E3~|TmmJslj6ht zcNB~(wnKVY9LI5{1LI_Vkai-YKfK5*e>e{}-q{g73-=)c^`w*Q} z1Z3X?B;!oUfy0MWkZm;X8`91((u&d&?--7$v{aqGQ*XtMMf!C;XrfJ0uz57*_9kY+ z9_zW3b(MBh?--cv#nQ@Y^C}>~cr3%#3NA-xWlf)gb>;|j>ou{epUFAO^z3hPi%fRt zx~%)TxabbrghT~X;Yt9Lt%+tR#NolV)v_nKFCWz#EE2!DK!)N5@Eb{}Fs?ULgnAM` zfS^$fncc9aQ2(G)78L;{*e#nR&B74#bKQD6yy0X@dKAa3uB$;C3%)&Zk}H=?0`sbl zK0~!6^SnizgF?&~SUW3BaN6W8_Yxe(3u_inCvczZ_aWKyOCYW~S7PxK$6zg<_qxwm z`zj}HEB%v2UbKRFu7P1F=^W{&%XcqK5#XjD=f|TUD?s-|M{xcOHOd{SJc^#vUC4ii zactUdxMQQriW8iF%fRv1adF|^w(FLv6y9O3kWmJbQ}BTb;XUtVdU^BSr#|bJCmiSs z3MC&>2&J82nR@G)?!{$$%yiCB-+uuuqKYYCAI!Qf*d%E6{9=EyROlvc>Q(6u&w?hN zR();!t?9|kKXm5K1+A3#;s+fReSdGARb(|W{6CckBz86h2E<<;doc6iu1{Qzs6r6g zvHEehLSCuen~5+5b1zTjyqhM`NmnzD!hQqiArhURY8;ch^_HsDwOt}W>`3-HsBCxN zrAoPiGlpQ!a1^uC)8hq(p~86^rV?_^o!cQDI-d961cSet-jy8#XP(Ro&PT`De8j6b z)$Z8E*BF{T6wIr;^(I5X%+6S!U}$%L;NFPcz_XH)yn)F-Oh){wU-=BQeNCQh|CH&x zRHvi3uG_4*dascS9O}IjblN3F8dqA5PV0}JIH7m|}CT$A|7tf02?-B%;&5CEN?fw#fxb#$>%44_tX@`$ro)YpvT6gZBAh?NHub)`*5$7s;YtRn=UJ-W z#m_kct35ggFSSA6a>Lh=xw@3~?bXmh*itmZobZ5>i^;cc*lsPNE)Ay*-l_mHc+uwR zrMEC%NRS<-YEOExXYoJ`0y$3Az)|LC_1%!FKO zQ!p#+P$1VHT1uat{y|2?8fkF>cG>tVQuVQKaYJ|4ZTs;b^?2^fnzK*!RA}3O@-+M) zKYa&+oyobu&LdL%<8_u(l*WPQA7KUJ0%SZj#;F&=zW|edkh?7sJK<3UZtw&FqyRcNBa@tDb`v8af z6_eKIc(1UzxcXyIYiNTFpS0Qa;-7JL>)lW&go*TnUT^_vR= zOHZhGONv4DX*K6I%rPq1FX6f9-`$P1Cwwx-eWWLRqybKx+%MQcmHQKA4VecQsoz&7 zf0j!26SC!1>~CVaCySy0mybY<_4Hf%&3%+P*UWu`Ff6Z^iUi%p9u?zd^ze+2?Mo%K z*F|ei!5aL>$WhIgPOXn;)NahLo>7U<$<<3tS&!t+jru6^PlC^HTRA^pKj~p?Yxx9Y z`=mR!A+=uXIhGH!;?`YD!>zi5Oxl?)MwtQL6U-W~FPMJaPQ0A)(zo;oq0;Jvl~Vbt_Il`vhs3Wu4|2Mg_k|QkY2%g!Z|RrupJiO~ zg_;;Q{LerSJcOGVY02ANA77pu>O4xt<+!-9O0tT=G{HCeN8ZZb9Is%B%VhNWCT!^S zKMiNr_5}G1gp@Z=fX9`Y`?`2^Z+;(ia^kl2@~uXlIz?+#TuG}|zDvm2spRLP&j#8d zhW{qATdhuAL`xoeKCxo7)5Nq$u7dKw^GzJ3D3ng{SX}tS?_rl`6tah<# zih_*rEs5a1mZ{0B9QJf9ma27^E45YcALb@|+f!NARI(_g*j}TzZHJa z8Kjrj^Jw!v*Yo`7x2og*T6MBNn6y29)3A$nGrd7G~A;=$Yu2~|ZDy>^ZKQ z!ZPywEiSs^_st;n6@T@W;V}Q7v#LCROxKYWDpZ&&%k-``e3IoNymX3U97=b_pX9vh z@jc~<=4HII48&MQ1$?BRcn^DYBWvp+F#mf$pkU8Mz=mI0!2ZTZ$i^sw?oXIa*nIV+ zxb3~13;I8qAyZC%(?Tgs-<3)Oj;&zD_C~e==d&Sb32Gz4tO(2+A(#wtzoTfQDB5|U zhp9hnmZ>RAHOpYoBEDuBUvpHVa2T*%j@{K%gSnv(3aGROH|me_?C#O8ET=pb!rg;= z+4-Kw1ixYZ?)goh{2n_CsiE5X+TJDTy5Mzd`fAjH*e+P%>Y+Zu)59t3g`|7s{p{EC zgoG4o>*2H0e{7G~ZJ_HXucr$i-N|(O{)cTIX+^a}m->4j+HSA_I7KNmrPmdxH>?&? zd%rE+OyR{Gnu1$y%RF&1nSVVpFQSSL%QDlZ3TFxpt+&d!*5wjNJxHm+%_ZeonR7p5De8dP0QE`tfO0t+^G z{6tv$w1ZTE9RWYk4}vgy9mPKo>K$YBkt?gWp1%(C|L_bi<5Yi}AyYw42%8N0;ks^w zJw7)*gi!17_yw7>$DY1P(hq=mCb-Rm5woLzfWC~FFH2z(`3YL`^ zXJ}=xr|86(|0UUoy(4MPPL|wqb!DD*VI^AxmQuw|nBmgG4o#T0XS-LIm3dSb@UPmk zcQ)-Lk#Wf8$gcd^AJWlhNcV`+F0N37rz=#bP*F$LiF0pQ&OCwwPkv4hjxJZ{msXZn zH@2f1NGQM$)uO)z|M_hlb)H{p2bHlghMiOi>+RgrzJ71#Q)!AUe!xw3FWScnEYvhG?zHIIQuZtOpI%+T@vo>~ z`BTg($E+IUekI_?jn@y_Ws}M;*AP-Sr58MJ*Vkl?DygxeP+=r+8+I_x!leZ*B zGu7;=jty~-8sfY)k-^v3m{ksYXfhsT(U=iY&w{uS(JKulu7_0zXgbZsgEXDfHGmH< z0h&jgw=UVsuUaiB^V}H$Jn5mhmi@4ovJs*sS+as-rtL+4D=y0ZxQOJUYjGJ(t~w& zc#8e~Cm6FQET@LsT*__tfYc-11e4-ylHV)0B0flcB0X5LvwO_2%bw0B`R(O6zX}55 zirkS2JJ=-NMOHk2_F@t8m1o)nmswn9#=VUh`9)VSHut5_*&SMMXgXb)a#O|QaGCik zH_(YTez@8X)#;jw*0gu3Cl8{3v{TMk8{#PEw>!^Q+pzgy!3SmmBth}lP5XYrW%xJU z_*bE!vnMTd>Zuo8u$2L9uzh7rdw`AlLX{m) z*C>LWN0e}c^GxW?s>}@3P5x_!dQ$(dI?Sm5;n}#!hp9qz4ve#ow$i+6cZpuAf6bQ{Y@_UBnN_ z{zL6JmytWTi2ryQt~FmmD zkMqNjVoqKR{azaTD*l?2r`9;@R#lN1@}{obyIRyn@co&uB5>3{!(l%{-sEp$RTH6>N9l8i0=aOb0C=E|9T(%4K9%Qc?G4` z%8pMvt@`TA=`Z=`PFaE{Gc~VZAaavn85Plp*^sT$?c*ex+A2Yg1bc;pa~VJq9lEaH zhrq2jI4k3Cw6EJfQN{Xif4n>*grYTq}!Vu{y2w4?_ThR#TtiNZQ}wOS)OxZ|cKH z`b{4Fy^Fql z-|zm&-3eHs?uV1uFw<`mn zn`FIcH-1N2+~XZUmNve(@A+aKr{~W}nXlI;cy~5INgRAKd+-sj-1bZ_sC#Uu#L8WZ zAE+92HyF1f6y0oX~2g@hN^s0PdC9-Ir zICE`sst++)EI)&8SX&<8F)yuU)XhKcr20O&{9V113w^vRv)4_D3`{Z4%C~b=p$=?h z|K9cB3frPaFx#sG)@~=mQ>nBkPQ3J;6kS34x{PR7P^8Bz6KmcL<|nROm`yv6awV&f zuUAkGZ09kqRTVO21?9x9tsvQU>82tKM)XDOzKl0$L(^R83k&@vzG;v%-N+^ft4}M= zCG}^X`BvrTHj?Y!bvoXc0wnU}h8D(p{WJ;2#M>-#f1j_UobMFkORCGlPxxETPgr*W z&NqfYKF`M%^6?0~C`^_`{()S0i|--9`1a~`WT?N^9r4f=AC z&@YpCF={{;M~|FIj-u<4lgI@)`XDKQ*zj!=@SR}q9e)!&G;i2oaAik8nk+D9^NnWn z4QllV^0N=<=jMat>^DC5e*g-98a~M26`qGY@HUwEG7dfFxWpq`vx?3mkIkb$?L5d3 zE&|G7ZfQeu42F`iALfVjB#Qhsv6UZ47ACbt+QRqExl1!U>teEnUQm9L!Tj+?@#$1g zD6{C;bb73^3Vo$$D%l0$Sf=T4+~CuAK<)?sgq1Ge>$>QUZHSLM3%&H90Qq^?^ay!V zuN!n3-!Dw&uhmE|m<5|qsliIjm%Ej!E1We}tV}@fRwzV8_`WPF7g}E%y-XImdbb#5 z;cNSZ)A<+A!&5DbZWS#Z-wI?n)%M}{DHJ*xvsA<=+f`UNKSD5&9+mP zuajt2cZ5ONRX>>G;&nvZ6_HUpTG%aXHQkWF~F$b2ur^RgI!px*`9oma=S>4$OqmpQxRGGqN zyJi%KJPu8yf2hc>6~n#i9p--5E2w$5ttD=ROx*kt3F$qB|*r-!Wn0b+>I_4AmU6m{HogP}0KKQpH5@hqRxbO)Y4HOWMbW zK=+%rmk+cTHm=!E#sYG>f()AiWGqNFTSACe1QH4^4Q3kLO@H+NKJAU~ri`767T1vF zuxbaz>4+d&PzAF;htn%y8QbkGfC)ttb!_9Q#Goy;U*qQ|Wt-Or)!%sV7A^$~jLg=< zMPT{E=FFMoibItbiqk%%PntFEM}JFS6Ow)jCf*6&6|_Z&ytMJ%pb{*uEkp|HwR+BzGtVjO;#)Wn{ObT)R-#KwV=22>r9v$ zQ^6Rn+VBc)JgHN`hffL5ps6BWQA#wE==wP)pZR}fj>Gsa_C-JEp#SCZkqWXUKS(vnNV z3F2_gj}z$EH5#qO(D{Ecf90Yo+~;@%R%_f3?~baUWDNWYGHSY~++(ZxL6lOrN>-cQ zEO)p@DYCk`G1hQ}CoKvSfLe%nh1YjzsjR-?5$$qt9=YXIt>WhgPI$@FEb5-}1E|Fj zSZ;a&KS0Vuood_4x74=z$4|B%XGW>5iaAaywAX-Rps3>{1GV;$$A3jcsI3Y+PRg~{ zfMTG?<0S6Ic8)^~yX81DLJby$v~B#oekOKQirUQfK5GyMx@nPX1i?SK=9~DKnx|JTBexY9~G0uU;ut~?6 zIJMPy{@=#cfa0J?X%ZLM&VlyiQut8$O=$G67w%roUM7hEuR@?tF7&nOxxX$hJ!~Ik z;RslcWfC7{MB`^YC&T#FZJdLMVRMc%*=nmy$4M%#h7b?M%8?MDb`GZ1=$z>#Xkt{- zos1{MvtkNqz|K}R{Mu^ocmF2J+P&q)bQCcrfn_-(%%Vf9}*Ui zycrrO)eveosu8IsW+c=e;k*hO`n%b>e;51vlEoFyh0hQHM$w;$V}t0>?FYYsI%)eq*ff8h)WKC)&%V*42TS+bINCHwqnq7mfy=QWr68d; zvtJf&x9zJSn&6HovJFH-^N7plD+bE#A(k=h(&J1Aep~W*GNP?UI2tM^PWsP?>cgOR znJq=x)@xhtg%-+~S8BmBT14^5IMgh_h3TxeiVvlOmjsr!TaPX63zpI%o|}xJ&hV%C z5Q#NrQ!jP-4d5XV4&W_;h*~l8e;1iLJ3lI``^a$lmBq|E__6Vjn=$N|<4j4lRrSY{ z&aE~0NGM8_WZ-yg#G%Nxn9JR|G_nXtvhQUqx%Q*I^|yXs)UZ`RpK45DM^xrO{5G>V zML7^sAV;Gg?K`!tv#8a<+lS>f7&T}aVQ;XePdCQFDYwx1dK4qCKYuO0>E%wJYka%y zezq}cbM$(k`uy0PZJ92*&>~C`U!OzB)}y|iz8X-KFnv3qDiMKNZvb!a>7wsi6jsDX z}Fp@YMNlE%Tc-UxcsXgbapPku3MsH5l(n3!6P<5hC zL3(1zy_Ie4)vD_QCnDvDnv*Z<*Q9oK3C~bnu#{VR^pG79QqAFNlF-gk*sR6YVMH`& z#z5~y#2PGEd-k561}4bJvYCE(2L{` zy21DI+Fl)1m&AhY9adhjX$pFg5o=mIGQ8#%_}+LT7QS1|tViUWAlAN0ISKIdwc|uE}bo{wkhX`Roj$s1gQ~mky2;uWBl!JuuDGK zS^UTY%|i(-lG$WN15c27DQ{OIPoqU2*W``06m5b01Spd_m8 zTxWiP1nC`IeLCt4RJyNOzy)iAV@>JwQHP<3>NtA6Uk0I=wBsp5hEaf68{>?W)@Mfl zfWe}}4A*Oo<9E@T|II?j#q#z44n69P?7_TI0o+V6h9%i|C@nss7m}Tqy7nUQcl#LT z%=KVVLW|@nUu{jJ6zcY^#vUcw(*`j^A}}NT;6*^~U5{`Jz8e>r?XB0>ej)f`L9WgT zxi%clDC0#EbYEZlr4Ub%iZBDMgutrotd-Bsx=Ukm|HYp1G$h)8G)Q=!yL~Zb)^TfZ z9L@+R<2<{^C^Kpx4fr?6Gp@1HKWsyKl=12yRH^Skn$i#XLudQ`ApQ;BYXZ)$_BEe- zAD>yKlrl?lp&QKwKU-}Dcn7*1XKQ$1jK15$&%1JEIS@<)v?8!y|7u8Y9sGp-t752# zQ2PPkfg^W@)tJY{^reFZm>gy~RW4@hk>-MT@+l4W8>ZQe)!5Uy^%1;pP6S76orTgK%?i z@xC_JmdnpQMG6K{V%|qg+s3w}4dR_XdL%nb&5zv-sJfT+->ky?m{EYgSypDU^Z&=< zGEj9kcCU)o4o|+S+*0Aj8PZsTazkxfmoQQ3YH@^FIrt$DdrR*f+Lhq_a_v91_NUBd zyU=mO9=T|Bq;lU*8Srny9F;Cwsp#LN1~2kLyjH)qKysjue11m%PVBiBtuCz6r&j^| zn=pqsuS|d(Ue)d8Y>EA92&A}iNPZ(2YDk0Vy*!}!bbpeX0M^97?*G@Sa8vqkRk8x1 z3lssQ#oE5*C>ZggbQt)tAhICvuzy>68DKg#d+lEEZrv?4Vr!(>8PgcxznP~Gy9lmA znoW7{J=UEAoxRotML^y$Hp!X_0`%IL2BJgjJ*P9fh4!+c<*9oMb zCH4Fz{KRi5$hOP*Wj(p5y7rrFD}-+-_wbsrKNhUo)0`!pvz#&lQlS5fAdPI;jc%uu zDowwto+);rU!Je)bPr{XO4dJ?G>Fs0ns2AoiebO;t(egG4!G9ylJJdQ!jFaU_B4qL zr0#}YLBvepA>2$7ykGxI-QIH5tk)6_Zql#9pu0_*FW5#>i4QYm1K$UJ(5N|*epc=Q zp#8t9@0izxs^+3nL7~=rKv`$UA!K*bV5d>%QkaqAl1uPWIh+DNpLR*06i$BRfr5PI zCw@Ks@L%=xqp{1_U>s`fgdpjxZKMsqi5uYoV z!u}5LVY5@f;}Nso4Z?=R^9^#SRwz(bVfJ!HoH|szuU+k9){>=kgc$UaX-hs-7W2aK zYDnZj1n_T=*Xtawj?(W7-UQOeNIm}$?D|Ns0+Ukp&Gt(fFZ6?b6{Zs(#T`i31R3=0 zYys&cQcrxm`rm|AE?z2EvxP!aTPCHW90NL}r8JSzagTQ_)VM{*c@H*rM#8?K zLE5&8|58h+aV69|CDf+He=%H%GS2CC-d}_~Ov1hfYQ*qPwExA@AT3*Hhn14wF)RW_m~W1U=^awbkc#0DU^dFK7M%?K~Ym#SQ$z|BK?7|A<;S*ZzMH z(fw4P_&zYz6bO4)Ya ze^C{oWs6T*T80W51DqN5)~R-=lqZhwWUe-&`M!}j@*CeZ5uT-50#tzEd&P;lqmpdm z#b8lv8(}T9;%MdmiCMo<;Rr_!Lfn5ovy>S`_gD|ElM~`-D;}eVK_Fk&GC?EZb7@k|VsT`rYQ(ZMJ)ENL9aq z%`;T7JNADR#0qf6X4HTIbX>ud-CFQJ0$FVQA4Ckf$`0m}=|72MkRQ@h`)uUcn@w4p z4CW}Y0Vht@uL4rb+5+NL`hwh{(~i;f|BRYxh(Hmq=)FRcKrYnUwg5aol_zxCH=3Ts z&tP(nspxyjAbNA!#vApfc5il^k^Jz%re|0iC|_!8X6VjcPgQ~Q%KS8GaJG-$F8uK z{|xc}ZlLRelBvK8jXMjjl=Ia$*vp?n{C^nKyP)hU@T%j^5-a7=d^IwKDN1S>InNoh z4ej>k`^R3hI>Ca3ws>zrM7$skydxE#-6)|r!hR~&gXkMk(iw-1|I6K^<7Z8r=4O|B&h zcnsA}EWA$ zkhuFxyHm|>NRtIrrUVD$e}n#7j>LcZlAVm9#|?+jI1F&FTvI2!3PZj?b}Bz+AH^R& z$_TBeEmufa;T_oj30f;0>j0q-X!{roVlP}Fj&D~FN<2!M+f6(wlK%oI(5iUSbQkpZ zNb^53c8^yk_^agD9eg%&_1~<;z_dgh9pjzB3@0@Jq{$((K{;{Am}$HdVqs1g8iZ*u>`eG9gl;b#9#0gj z=|#4+t9atT{IYxrA$jiXV-h*s6J4we^TGW6;BIR}ax>6oeHMy@qGalWF6M>#pxE`s zn=Je6VDvrA$a#WWGDV|{abo_`3WoutqHzI8{XRAYIt_`Za|1J&BcukbEcyS8;_~iH z!Qe-SMg-TUYGGAQu*H|ZCED*_jLkcWyd^4fOhiQF-E{g#n8@`UMfszmizatPMeYTP zCZ9ODA-n(M!Pi363}4K812QB_m)W=C>+BDEx_L4S0*WM$EN)>#Nxa|VcuGJndgkv! z@akz)eFs@}y*Bzk+17L}e_xOqtOS3H*7$eiWnrQ=I}F~(J(*R`&-3qMF-4cRzbcAn z>>ou^@>jIom)dg7Ti#Xx`J%`{kZ1NVi^RWai=PKK3?9cj`46#roWeWC^0dEetK>6-=-Z}ARu|G}QgV#qmV+nEe>u9xnP ziHN!=nb-W>Qk45iT5?XOM%MEMwmEi9tL6Y#2nNhpmJ^bwADDewt*y80Z}?mN(5zE6 zFnC!t0yVXKzV)Vddg+ggqUV~Pyx%?GuTXudXiq%?X0 zv3KGd*uiG>n19%mgYc--=F6~|N5H*8;n_1Tz+DIdg%-t&2m3)dm? zHHkn2sl~6cm#X{^904!Jou0avErEqlKmU+G>{HO*7oLzL6E;R!)m}b0VLt3BAyZna zek!@AOg`c*q3~!`G)cO?2LjBPB)j0b0$w^l4OX+;?S?WN^9`aCo|8sBgCVQ;vX%H- z1`XDW?EHPbaOgDB!Mq|8LbZr;U^X6uJ>T|(_)!+JM{@8g2f5{nvYS?cV zU;Xr|eC)UUpWz70OstnY$S4u&d$zkK3f1(X4mk0OAb4x>I*ahU$Zm)AVVn(nlf%Q4 z6cbnF*?KRs2zsvk6`r@>@#oP^nIPkLtfb^~dAXfihaP(YhnqaU;1pAcei?c_(>{8) z$$_P~i*oUjD>7-Pa(eO|+YyHHnuCf@$NZb!{a=GLVp{Gl@XtRgcYN=RyO@Rh66j%a zO77Y-yjr#hiWev4$EwAU+Vv1Z>%eKj^sw!DOGbgs9b1{}Rel@4pPIO)?djt^IX46E zY@#KGk>qjkXF=^J{C^yHH+4w9a!T~V{QW1_GKq(f*2mKX*lR(0zTOS|)E!N<1jqX% zX7}82eHP+^&mUiM^0^&NJ5@{M9Z3f$D%&OW)s3`#?VDbPxz`Uwm+MU>c917g+V3U! zq_D=Nh`~MA&u-IlXyx1a7ZoXOMqeZpifJF2#Ei?B1?Eh~C0QE#3V4=NlV6&Z*{tJ# zf|c10MeLhZZ%_KIj<*q0wrfHQ_){-PoaP+sylaE|vV1r=pEUc0w!3QwTugUHRP9LcGaGplg-x3ZsHxI%~ zHikSFGnmFZ$N-&t_mly44sAwg_?@ymE*>GD01=!U*~Rkv10nN^RoL9{`>Geu1$yaK zuk06xHQTv6I6sU8DqXJ3u`XsdSE zHp9y|ws#p_8o|$J)+MMKM&e?nRHe1v6d1to#q&xlHDr;{KA#c1t_)(4XTz_`6 zMb&8c$t5_SK+L}GvYnkDW;sGZaN4%5b#bJhBtS}*owomlSUv$wKa;DlxJ0|&2FrwU z{7g0zHCrwP)|hvG@+mRaVr#;TtYjSy>aScnr$n`U=~wq048RvJ-@qSIOCoRAmoVMw5pIIn9Ez9kA{LH0i_reU$#|JYlQwo9o!gW#qmj#49$cCKvNzVk2kQ)ieW3UJWV zE-+F6O(1-PhL%C;4BMLf2?*Gziu^XkuWjEh(~Ffa16dWYL_h|n>eCrkH4`rpz(_xq zTR{f~%S}dt+V<(0|5bSvED@qVg4QAn>>%R9{n$)9bTj&Dq+rc5!E;U9;ey_(A3ymp zxC=jjZEC`W@vXRfq4j{NaZJQiaXW7JOF1_JY@yDq(cqg z2PNayYX8AgAefFC__tJoZd2ijxV3oPJ}MPw@LFyJFtZzE6+^^-D!|SV#%kUFx2Qc~ zeK=qsc#jpWpNBnHs(Cx{=Qyr*P6}|%Ob@}7*i(flkd#Q>0WHwx*kBmewUc;tk^0m_ zcP=yi09(SO3LzoCltJk1^qt|s{gsj;s!%*6MNfAl>L{txnQImO_jp>ohylB81#6BW ztjIs~i#4Nu&WQP1?PYKAo6OBi?uCO}PBbxe)1=s~BL9e)l5qh(x902`#FmiE(p9V{ zSzQ}bN*oiD8Vn!btqIn?4-Ks*{~+zz(@`XQv@U>T$;1E1y?J{&VceeX_sf0{W#2>- z*R?|FftfKtCY2r63to$drNHcp_j;q6aNSp9&;+T4CdP+J^m@6Cko;T zu+#1f*x{w>LUL=r*T$=xiWkGhU5a%20(N-^hyQ!T&NeR*fBt}SJ}usGE#7dzm#Mpv zX#tJ*57e>4(j{M3)%sj@d%fu{8a`ul!GWA?azQ1JHpKBlcfTXZ>l~F`o89qNzR91> zKip<@Fsm=G|8i^3dnklk;{NI}cEjc$58>#tX|aGdkmyc=+}qZH_M#Rj-~yYjGlC`lm|^ zTuwYllO?8B<;Y^(8Y7n+StE{pp~&N2`U)M2GGEpTNHA_y&K#Qfj#^Fe6tDiic0*x-*1GXX)8}@b_|;AYsIe93bA)6e}o25 zwc0HM)gcM7{qTKSE_OhS3B|N6Em6?gYDfM?(ihnSWkrSvMiPScQTsvaZLy^7psh1z zaPaW0ozsp&0`9PYfBy!kL71QtFvOAJ@v!-*=qZsMqKTVGNOdC*J{=y~X z070C)sF(B>2^8y`zpHafsm}!b6RWXdxZ@`iRTJrpX9Yz*O=nO!aX}m;fYT1!_4R<> zGqkH-&2VsGG|Op#W^xL6yp6}%S9q!lcw{^Ca*2n15JzeLx&TH7do(|5_aEh%a$@wo zVd@KVWZww&YxxS~25pjFw)b4TPELczT$fc>%GT7{v+^6+R|jsTNn#)-mL3>wPpd#N zQ|ESOxj(N0_TET*8o!4oD5{pLT@kI%WwYM?}nYa&mPj^+UA*K+KT z@@L0L79VSGMV;NKG%5Gn`kZrQE4 zg1)7zcy=wK@Kk(OEhn|zVRQPs#noKz@%GI0`8mWhSz+!nao;ar?^kHIon)?CiyCgl z_1jDH51o-ENh?SjYv-EmxOiHb#TxO9P(C{O*0OJsKbi}XpV11#j0_onZ^Fzqui}4) z928wVL_O}b$o5hCF@AkLPh#gfs`D9N1yR1GBT$`MfC#00{`MGB%w+=K_(trH4!I<;Q*1Df4NT{M;4gDrwWp6w&(L zo#^8U6f!1l!HET%K3^Bfs%|Y2D5I<}*ylb$vJWeLFF0o6I_R^2Sx28|4 zFB)4@H+iJp8M4u&l4Aq)$EMOxTZ4_ASpA(#(IhGJ!d1x8-`=n%S8TrP3Xi`c&Q#P$ zS5?JF8?#)%<*AG3%}KHO+Ig8<@(QF2>3WY1k6K(rfWf@j>>E{r(bh^w0*W{u?K1Qv z#sycNTCNKAPg1y}tx-qnyV>NaU`Kg^K~|35CiiW`ahIrz3*N6QxR$l*h~9-ZHi)CS z5P&x!Se_U6NMhV1+L)=-dSRfgGz+k~ROa#_#a3?5P>v=4U*JuHOXLW~k~HsJezmh` z7Bj5wXB)zu?w27yjy4&0L2OrhfG2EcPA>X*pP+?U4rpbyJ8UkrK%}pu>R{|G#L@;Qki1H+WCfpsEVsQbDD~W)SIH{=2O$p4Z-@eO_Op zMZJ}$-Mf>^=x(?a6L2{u{E-3MoYt}A!kJ6+AFl+6dIdQ)9n@AC|J`%1X7I#j@%wx! z=|5jJ)>V*8HC%6QxCV@T#wzYmUZkIPL)74(n`NJ=Z^L-WXO4!SB2raS&|a3hUB&tb zqLgz3J1RK(m3&GzR3Sd%wVuxCha!wQGVr5^#;-=v4IA#kn;O3=L^t?T=r1!Juk3Of zK)($s2~m%gbbT9=5u(OQz5h@xBbm~O1J2bRVO-fb=*7pp=hPEw`$M?zK@-vcLwc0zFV&DC+}T$ z#lRBu0-``kIGVZ2n{>0!G7PYnY#P#>^!-awE)FnHQO-qOeSq&D!A6@`!e8kd9cXdn zrN8!vKRq$IBbJ0b+63fd+#@erruhZlt-@0hqLw1YN3gNx5YzV|7RFg27Eijm)vRp* zy?So*NM9Kv9Z#ltgkys@rB`zJXSd50Nt<2UY)Mkb$O!#ZKbE($s0L(h!`^cR)>G&3 z$_A6r;PA@XPPSA1?s8tEYG|_Tg98~Z2Jfq2MXqT!XX`=MsJN0&{~W=zz@Gc2>NRRT znSScKrwqH7tUH8#)FbK z>(w1xCwS`D;;r}es#KKL!;kV;W^SsTl$3lvU3eC9;4XqJo)}~uae3swL*~n3fp-}ih%i9I)e2>Gf zkNd+h#LV$kls9J(m{t@1BK3K>enxh|I$ma+t2wVxvSZ9vF^fJe(!<<52~xl|!r=kJD~5)IVHjIRTn z@vvhw_&26UJ;1sZP~_v~mwCJBON*Py{NqN)u(>)dd!JdU8Zth@%Fu|gQE&mRENL^~ z;nt9X{OBeOpTPSP2F~negz|TMrLoh#cEl|#f2qiVu`IC7R*fxP+##=~`?q%5XR94} zB1dueav3HP_PjrH4Is0Rgb}Wvu0aPMy3+$6xYJD~m`9eCB$=m|m23wTsG#_;@02jF zD0*5ht|bf?aUnAoEg?Sb*A#VCVNhSYX8vhpx%*(|MB9iKtqS?!5=LkYwv&H6zl_g}N-ie+W-9rfMZW7UD^}G3q9Y9abOD4k(a8tD6l36s4fG zZN4-koG@W#mL;3b<`2?nZog-J>jLRAmCr)P1X@T*e;L|cvpkzoGoq1>A&r%{w5cTu zX^i>YudA(DvtMeiliw>8F076?nYGC`fIA}UZ0&Vsvuhmihph-A18MQNkz_5MQA_T62EjnJ#1&@UuE`tZ_c2}Wc(eo_$!Holzqhn zLelHPdv{>{s_h(bfmO>zegjf`HeuuBZ3IX5PUJ9oPh`nW7~!IHphC+;xuF^i>+JEF z_(8I4NUYAgTw>Jmaf+}y%HuD@Y$un`k!{C<574yzW|O*!tr`z`KIK%Q;U&qu+&66i z5$l_FH;lYCn~rD9@ROO|C*}{4Wu_%&x6Xa{4LHS0-1Vvs;MYu~U2~jvay|Q>PDstO<@95`X^1 zt)=a8eT7{$A4+(~o)>j?XF<|^k&>E&#ws_LUVgZ|$&kc^ZGhy6Pi~ZoS{*GDFS?0; zGVM<`oaV?;JB>-rVuq#~w7JfRPGeN`YHPsy8X&v|yj=q>?E%_* zK}UcFp~+`=nvTF1WaMZ=)f$bb#c`62W`fW!_Wvyo|u8% zZDM5zD#q+box40pXnd*xg&U6Un84YuZHG%7{ZFN*6L367=F8~ls%DgT^dwR@M8Kt1 zld}=fOs*TT$mt?n8g~eJ?F3RkI%_zdh1c_to7y_*a>~k+oYcrQKhga{%+s-_+t+af z`qXit+ud<2q`p>8Y4BS*o`YtV0I==gcoE_|A|ob1NJ=)k@?n9fVe(liZ`84yTMP0B|5 zHRTY_@w2p(ENzid_&AoO=t{^&BNOt_X8of(6==-9GOGj)j_zO+^Uz8X4ryS&PElC@ zGKLj-MQPA6F-(Jb1YOph%PJPjWknX~GHC6f3CtonkI85D4XNXD)K)Bn!~1jJ>*k0D zx^(lz%ie@~v=gUvbEAs0%-5;UCw6HsHn|3Scr27zn`_QCn~%+Q`=9i#etZY`=INj0 zJDm2bdWP`aE2Nmou>+D~^7p}#_M3OEF*nifbgjP8u+WsefUPr1#G2$^ql)73QaZ(u z#qqD$O|Ji{&OR0S+FYM3vm}H(l!9_eL7h%PJxD=aOF^CdUtlJLfd4>&o#|69^r>RR zv&X5pS2-yk3yiM2b z1LF1(d@QHnP+oK>2Rc*;9eNBMdggz@adfBvI+Pn7Dvl22`!C3>?q9r^(E-Vx>FOTF z>!Z$)tNi36=O`-qSlX+urTl^J)#*ex5MmgL53S2%m^K%ndZEiaPPBtA{8>c$CI&pv zyo0T?VpOo=b1Qpi0GC9T!GmNn+@qGNRF_8=oSkBtc$Wj$Y^UJ)vWzM1L?W6H^$`qq z!_Yt*NXse+&uzv5p8|?~VjnUxLb-L_bkmv&O&F*=s(|9(36P-@Df&~vT|P%?i3}@p zQ$WYaR`m<|fy9$Ywy4Cod${yvj02cEi{{(+8`1+!z|H>efnOwSzHaSYtOB`oq{HY_5t zzOk!oc79P8&*kSBl1@<$sZ`7E!fXzXP<({As2JV*n=lSBcf2dq2lv?(qK>RGj)L#{m0yfLWj$>;Be?2&`GySdPi zGGn0qp}RxGjM(CfN9!JbpI01@P}wC>SwppFc2C^WEz2|$#yhH7tcKqC{GF9W_btl# z(=|lzLwBfQ`3ODDIF~9`C{NTnzEhu2`(j_cZ%_(2vjR_|PiDX(K7UUq%zh@beYyq` zXwchCo@v3G%qWt}vfxp|T0PwbK2Em7%}v!=bZ>CorgtSDLSTMca^Z1btv(Z%LD0-; za>Ov7I9!&mCMVG>E(~o*h7fsk;nvvZIv`t1Fpg z?<~Bzh>lEISVg+F9Mm?>GpKlOAocnzpel0Mc86hNsZpne8gwH`X%AAA?=zQcKR-QA zO_m?xHe=F6Gpah{nE<4?RpJ%XBB8rfJ!PfvAtHmeZt z-EW{_+qh5ws+SvA#XDgKy+K$R?|T^S@`)3NhYD?tl;{th!di!-B$@+a4Yv z&5fM-c>Kr{jgI=5f`!Wp%aMPQHWI4Cg>2vik(!UiU##H-Vp*MfZ)U-b@#h6EDo(r6 zc)fILOSg!GqTFAewqgp1k&VOg3Q~4#OY$667HZT;>}L){8pF|W|CB{M8g--fo&4YZ zXB<|B5Z2aaiSFY)DttAG+xZ)g+=#?&fENJAdY$hU=<)j$FAC*cn%g+Rc;ZdXfhO0B zy41${pOW55wOOB@d)AEhS@csZV2_`{#T!A6_9b8A{_@+#mXj>14UD|5N;w+-vhW0a z#?*x-=M9eAtf(&>NNncuXcUc`EM@A?*rXMEKBz6`5Y)3SK+6_gq~(5vM!ydJv8Xxn zmxjd2vfX=gSfS^09Vh-|NAIjZx7}fnnf^)WANl(u_Svr>m5sJ@B)ioAA8pp4c<`krX$4)cm^RujFKn>!d#3x`{BT4o=)E|( z)?-ZJ9fEWv9cM#)F#!0KMvAO28nm}$Zuyi}rlT8lo2OfvJ@gaLpbpX0>gIMVwKe7~ z^HscqXNAe3XldxFW!P;xG65KCIKS(e068ZKe>GI`;WxsskrPjvT`D?`7q!)!YU=8h zx0SLS@p+osOT%-Rp;>GBQ@a2k6-c4OZnAzDK*S2-FcnCmt4RW=41h2M@QjB3ijTN^ zdiGY{c!vK+cPVM)tV+XN+oiXn0_JjY1#SVvBybI~i;2%5b zxpF~yd{?ImMmUe4{MM>bk{S+mmYP0B*4gjI9jWl*%Kuuv9W|}*Ehy5yQD)S=+)Z4a9XG@{U%+gFZ=RuAyl0DZcuaX z?kw(AX58P}ZMB)Z$zw~bptuIYTQ@(b&JT`ki4VQosu?als=2Um30%Kw)tuX1IJe33 zqqItJ^Ov0i&)lagY6~*crk<4I=SGH<+~7weI&&L&rk}4Nk*4y|0IDflezly&srhNy zw;kD#Ex*|$4-l_G)%E}C{U@_o0CIZC`0QOmwDf0VQtO)HN0 z%dB7#HSvLOJ#5-QpX1wCj9c)Zj9Uc_GSNro9y8`mqDC-#58~Pi9ON^IgfinmX4fNW zf~x|s_|(j=sgb8A${eA)hSVKFk1LrO>(D-4SVYuD#LUkNVkj)Lmt1XREPSsG7n=>W z?&-Ro)HHQgb(SVqVYbukiVL?b&muV+cJ*H4Cyn;C?mesx&^q|kuvPM!VPWg*2eZh* zEjZ>bN06;<7Wf`Ih&nWGK}pP z8v^~8U0iJ5}C>xXogmLx2rIF|dzdbXoeAK`?36(|h|L8vP z6lFC8DD1Jvbu@~ROuZ0^A5pipWv)-i8|Lg3e6-uLsY*fL{uNoAi!8bDU?5m>AUG?+ zIp?Hwp?pHBRoQI1g>XoM{OI`o+WPrN8f11cZw+Czt}v-6g?e2WbJsxQp*cZr4Jmg= zt^czUY4Xf3zmq@B^drlQ#T;$tJK~a*B!|kGOicf~fCk2qjsZu_n}0Iznl{~QTwPF; zA8k~~;R>itmo%BB8D@iNhJ6{hwgOK}n8Afiqs2Qy?iz(#hCF9OzlzlP6okoI&}ZcR zULCN4_Pt$${{#77g{O^==d)MsHvUkuSxdjv1QXawFDzre;P)XF-w{UH+))#uW`&!r`+`Q1y-UGe{u&j@ z>Cb%pg81uJQ|S4rHvKh}zHBJpRDb^zDX@(<@Qq>9ym+omeAw;Y*9EdW#iX_?dOa)0 zS2_-37zZN@hk0U#DSxzjhOZ+xo<7jR_GJX?5HG8_0I4H{NTpavad+bYLk*{?qg3#{ zAvwr9*Y(1s@n)SQ8IPG^)Ncns+e??>OqyQe2u$&^6m7c+6IZIi?%$ytA02xJsvXR~ z&wNrfm|rjMXL-L<{GsOK^p=mFPlCN5EjFZ9anmQWMCV+gS%=?&TC58N=**7sdb-#e z3-MwjxDKDO#=;ptnV*CRcYdU#7jx4fOsyx9cQJ-eeh;I(cce(5z!p)y)9}xSJFI@c zQtEngU1K8T^5Y(AMp4jgyKVX+`>&e6<{!2}wuJ1i z%i)(iWWa755kUNKsTv|RrDhL#3G@<80hFV+u!K@lF#p~ zasr1JEun*T454OFFBv@p6`fCajpR}FInOQ;;1>6s9_}B$8=TH@F$O<5`)-Rc6R}0*fFYT_RW2B6%0u~ad*!0L zfi(VMx6q~AC-UmFtC`Eexa)I4=WtV=Rkjt?%-VtV2Vsojy(Vbkw}=3PF8&{?v7O%C zBF0blwU<_o_ad3k&bYvoUwzH=en*ha-?A0B*)8TTodr?*doh2E82xji6@DN2J(Pc_ zH*MW$C4iyRvH0}uUEf$-YI>G5%Jh?4Q>6d7@JApg{y#0P%$+T*ZHM-zlECA$8t>)1 z5Q3XpmC&UC%}T|YMUOHEkNdTa&%}^}jk4H+5iC=yVuewS{T*)AD!Ida{9E$9a2H&F z5#pe7^6B6>CU~>+;Pvzu;(qDm&IF@>?miVml zzEb9Q)mvFZB?hvzl<79&8j`q?;A)WdOD#ENM-rcbYxA+c4?CzSy$35gX)AubP4TfDz#JLC6i4Kz*Cy}(2|GdD zJ55IHJ)_;tM&(s1wlA8O_BjqfI;-Rlj`T<`j*iefQ>fv>L}J678<&3Ag`N`Yx3zg! zS-I?uuh@dqPTLwJd(1edR2^%m3lgwhyj-KUC6mbW7m=|_aQ}`TeKUAQz+LxYTk&^~ z-3-jxz0tExw4~ra-DeFfn`=+Xc+5}L$%HRdJinLK|6qM(>BZI_^QJ`%O-m5FX{XKp z&a1klSi2&^;`dTG)Z((L0DE@BqFrwUwzu-^`L8Cy{#

TH^Yy=$&!ce%Og-_d!Ze}~%0S2SwcU+~2*`RV`GX_|_&P_p`6gDz@J`s{*w|3RtUxjH*U#)WFT$zYx$~*1ODm28 zp?geqi+=mQlVD5sFkM{(8pu^wcK`xOAu?Sp-eRT8D#EG9*5ML>%n;#IaFo$f#gzJ) zVoH%5qW7+3@>gDL&V|{cY{mIuk0GpZjWT>mhX?s<=*7&H-8)~Ae9IOgaMveiY^L}` z^-P!f}6MdFpSmT|ScYECNFVpA{t zzRlHfLqt?x{KvhTr<*7LtH8G2{#80#4XqYjxU6VBC7nh|Fg()dwS5#m!xj>^;KZYT z=70f=RV2Sr61?glIDr(Lfc*;|!TagSvVi1BS3;mt@gHEfBbKgs9$_+sJ-yo&%TRJg z06!fE$fGk*;rUu88f^8kCMmyDQT+=9jQ<=48c)-Wz8&d9B` zy<%hG$MJeQZ|QV7W#;&rUgy>^Cwq>s?ffr-Wo}rZNw0oRM4ZZ(iuwy!znm5Phn ze=)|bK1R}DPd{GZ9JEhNRsQ7Un=)2kjHznZcKE_u#st@KMOr!JYq-gODkD1fQPH)u0jn0gY=T-bv&~3 zuZWhFVTCPxjD6Tp>f1+BNZgbTc3yNawhE4yZ}=#c*7eu;egh+_`Ax|Q1`#Vlt>Cz(BvG_jQl`XS@I;BNz`cVe+1AxBhWPmx%Q&%uKVq^Zj+xV*It) z*Et6LRoPx`?}){sweQ4h8q^zY-}!pJIhnvALXZ_Wk4apBSXtW$zI+~J7-0R9(J$U7 zZ|G?`l`0V>FOjDs=AT<#Dq>(n0~s2%nemn*31@uIrZR8tOOf>ku`|#k^JbEe<4cX+ zCWaq`X2s7*&G*hU)RYQ&8s4sIQa&~H3O;B|w{ET^D0stprt2HF*gPwOEDBQKS~myG zs+PAe+|flfq>7=lhwM$S&h6M|?XkXJhnS-m)qB<%PE#Z!&gOR|Jhls0SypF!F(qc5 zOD6?uqpwqtOH$>)`S4E7OGJq;t`NeFaqAEcP-Xk>BiHW_MRr_W-IT9*5Zi^fUcFkKq$uT*d>Buupd{&-|KaTdJuO>G;JE?iq8r zU>`(oK)RAbn-<^u*;v+bH6JL8U$n(ej(t+KJ(t#a{W8k7bUoOgJ>R#i0+lJhp2(b> zqE~;$=$x$e%0^QBp0eBC)0*9w>!14J+gwhr^V>K?hRuym{nRC2-t_QtwXZ4R3}~%U z{600QKhsigIyHDOcLU)j@=Rn9QL^bRO=7${V;d z8fiTLmp*fxh4_ zprpnXl=S>py4b|O2)>YRD}xJqAfo_`%eFMWCV;eXA_w}i;TAAH=`@dg+T%WBx~>8% z%6q7wP6F*qvICZ`{U!(9l3ese+@%cMvBsU|XCs%_V=_*zGg16_TCjmQ?D0Dn3TG7lS)Cgtc|;g9QrKfnNg>+X43(N#^aSVTT58DN z6v|&KqC~7~B5kp~p7mV!|7VZ8rG#fS67-#@Yt4SJ_S<~b7L?-Us55o;>2HE^0rJ|! z;@!#u@U3B>d6cTnk44=r@K6pY8tV&J<;Twc?;Sfqx8-&T{%N+it%^f^!8b3~a9#_r z&q&0*y!+p(qirWR?^>m8FhO6CC!-T5| zQF+44MyEAmxn7W*kIry(v)g`^J50OPkC^%7AM({Xc)K0caNI)9JMKN$Id*Yu|P*`-IZ z3g_)kuLv${ z-XdpR;P)l;!s_syQcz;}wbMkmGrOR&&>d~zxbSNyiEcT*9;_e7bu$C!qLdzO;W90j zCY5f8)7x0!-R1cHYS5V*a-`AQYBU?w%jVB#$7S|yz39*LK)XX4$1!O)!6__E?CNeI z`2dZYLEUM`n>IYUgU}VlkPl z9%E8cGM`^Z$hbhw`shWm5qEFtT>K!O{X;4H%6Rq_*u@Wy7oGpe{3E;X8i4N_=w?rV zvNMrxcr`YR>CLu>8PJm>g)ajNUQ(~Jh;Oa`a92R(#eY5xdB~3xCPB(w5ViNWrtbhM zBeZj9lF9(!l|xY(Ap9$7B6#4`2=zD(7PB{LK7PH)@7#vUDJ;%Y2<52`HtVH^pGzbB z)6#33^oJI`+JQeQgqidwf%=WL$KuyzLp6Jpd5faA@5Xva7R31r^fdAo#cg}XdPr%r zFKKWf^}`fC9OIGP6@3}D-UHPUy=v&7N ztbZVXxMPU>@eg0eefg&hRl8$GKD|5L_9gxGQS`y@#PHZF?pmY zZTP&KQmer|XILy)tGvPM!va;s>+A1vKR0mef=24X30RmFWnyZ-$@>#qA>0~xY zH~%z9`S5`G(p)ao`D0Ft?4uDIQF*G;iCjotLxs0=@$i#wHP>Mth`p25%&Cxg9VfFH zPVy5>yZ_so>eG`$a)+0_Lo^P8*$#r(x(BU4xZc&Qv66(AG2cyp$+bpWx`^3+qG&)< zGoOph#%$?SRXHkD2YcCFqF#Tox?o%-Sh+{D8<(%bf#1_X=FDd~9jm#ZZL&(c@Fl-- zp7Rf#st$^B530XWF1m@4Ki42@{9(hUP2nZdFi^*sy{9cwt1a^9+Ey;*vAC68S8I=>zqu4;VArELD?rtR}rmus%f zvX#$2KAv6ZBv9~7aMCB94<@uE#R0k*rk@b2|4Av%O0rYf`D$;o^z9MU%l+yERMK{m z`HEl?o!24J|4wUc|8rZ}o(~*Y{X5ooTh2<@z1W_*lOrg%lp}j0*~b5~eZh*6J0f~= zN%74%`UZ_fNf)dLpOK;^|3mrGglRy^o>!*B_d1e-5<|Lt9-5rJ=hE)O-{bPi$Eu(d z6}P{TfJ)tla{!5Z^*dP(1*cecMlM@{x*hZ?xWRkL~B0_Y!^ptLMZI_= zQP+M{{LkcFtx?^T79P0Z5O`ffLIJj`#ByTUWg6`J6@bVka58Ry1A#4nPOA=FqZBhf zPD6EiX>tmrx%7xl!!B}(1MPk-V=6#n@fqQ8PUsW$jibrpF4)S?9Bng|(1s~hg zd>JBEzY3S#)N-lzTIP~P3W`tcLY!4M@>37oZ8wDl?iw>1?jyW^DT$Cq4Hyl0PfChrKYFPCA5HP!gpSahiz2-cF(G49@=CppKN9AgFO$VpABttPNWf>Cwe|`! zbD0jZZG%DZ%Uhs=8?U!{oR2c}X0w>icrWsjw=7q4y3^>aVFO~`u-k#NPCYy-5~~O2LVNrW-te9B})Zal5AL ziK4KR(?%7~h4ZBzi+*L9bJFjb+T3}>Yf>zvZVYG zDf|nY;=82l@f&+-Bghgajigx58?t35!Si$~aW_9#bVaPACvrztdeNUuK zQ~dH^|D8_RP0i$xLVc}q8)CvnfT>pNpCDO4bOanvN3N@y@0QfbQj7Guu}za|%4rZ0 zV+}0ndN}h=hXz2D#cGdeqz8`UzL}4FsFTGM7;0d@#1p|Rs-%GPKxCZqk~wF%u2@`< zJOoJYb|pX*T>z5^)ryYNiXCWPJ<1&)DonEmu!LwktdxJex9kwiNL)!ZG{{ z{FkyI4eJ8f;o-@XhIj?3wW0o?OR(Q~ z*D{>}sI0(F5gG58)6&M_;X_J1G>mgMb}6hY?ZD13ss^X1jUwKMLqrMGsgQRB>fGq& zxFL;r9|_rW>@GU&-iHQ7t>d@R zB?{vwwRu*LKH}Cm^nm6k3FHp}7Y{bep=+nhI&Fst0_2W3Xh$50sk#v)LTam)@n zi`ufrJ$DT`LYWt{e@(-2A8c}OgtIe341vRCsIB*?e_psPhWzIy;y&xhw8w-j+|=4~ zDQry+URE4IB<~W6=^az;62{%3^`?LHyF)Vx_Er3$nSf3e52a4`j~?ce&~9}6^6)GbiEui!*K6}kLrY{lF8{Y5UZC|6$Rm$s^LB?Kf zQ7_}5*5QP0d!$TewUgI=Y6nQ`l&J@2hGTTm5-UH-c&v@TKmq4Jk34%ltn*|QPN|;r z-_!AdvFIw_DRIZS+Dl}GWX6aKqG5*B=ZfAbS46zl(sPtw<~3N~C3V)s1lm2&47gaphV7AN zg>hgj@!3R|IxMTc`S7aGsH?BgJS|k-tl&iBU%S?0iZPW=T(z8R)}WKmTkTU`%r9|U zXHLf-sS=s$e8I``$re!!nxviPd>*;aRxwG0$Vi06 z3)1I9IE%wml}DXQHZe=GLlI~{BWrSZo*dd2Hx@NCM?;297r0!5FJ z|Fwc66+N}H@zyBuWe8yI(Z-mfV)@1?>HdYJJ1}+(oie0}&?xDT&Zv@maA*#**Y0&kLJ4IVvldeG*wmFADyl8@WgE zg|p(T{i0~pAc}BN<{P9x?aToEU&tpTq{1;er{XfR>FrOr>O*Gj#1rE#pYpwmF;Nesp{`o78||Jq z;?$?8mG@5*E10F*VU}~A%4LPB59d78%DO|8-%ay%w23g64q_Rc<3XXN*gIqWTjQ^f zla;?a7d>EE7;JgHW{ew^@$Nkrm@0jFdXOv2dNXb0DeevI{`|{=^)y$HR`e`&>zHa5Qtee6 zzi;3vS4z(v2a24-pOtf`Wp-T7P~g$b>)-AD)#!z^$s54ARo#8Tf__|`v*L@!aK`oT zaobv<>{~ElmWRdELSk;f{Z)r>Q$eEPTa)iu6D!BNG+*U}zrVtq%R!~z_0nFap3d4z zmrlRc&hxtJB>T7U*W3@cvP0ek!kdm#HSSUuQhJ_)ntF3+aJkM`ximkq@;`nimeEJg zFQj9yW4avzHR*ifaonW}Cq`LrS{J>!*}r|t*s2ZwfUqn71H1c^A?K#3KoRS>+eVmi zABh?~BmQ#xdh4rPa@!A-VpBF(th>`$>!YfAQI93Gc}=6AJJ!wP%dv4ukyYW#S4+k= zEW;gs&3k-Nkg|k;poijboC71|DEe81Z{_}1N&{q50u)_FqLlSnPhXZGM=?5k6{Gv9 z_>R^09GYFy+d#PS)rVWQ@=r#hO!_lxV&y0mIae^c*{93@SUu{=p(P!;*9QJnYJq}R zMdVyd-zI)2t1Eha4MY9$pj;dDc!Zt{=P7%>2YH(D8}OS5da!yt)G-t5o}mJQC7gGR zh#yD-KYp#+((c&OoL9fglXQ08y8G3^Yh_F%J#a0a1*g9FH-Ruyjycdva=fH2>s>wT z`LT7SN$bxajxy*{`5zv=pNuPfmO2{-iTszO?oqqce&(RUSq+BjNt)r?-N@h^ImAO& zgZBLT)aKKN<7N}9sI3xbmooodrO;6!BQ;fnW^l`@AJ;(#U15HgiK(WqciCd9nfN|q zC}!;F77-7nuz6R|N73pE=}e{cgWSP@2jZCA!GkkiPl#cI#eb|*LN5(y3D7e4bYEH% zdDFq0hG@rc+9NcpugtS`Fg84^)3YuoR+g^UuDB|?P z=avjfKUosmfmKzt?0j_8p38o*nP^E{@6NeEl%fKzD|v2Ek8WXomLFxm?{>?ru%jJ6 zE}b2l4=>2ha6CO?bW3CSso@CFc-1z0KKo@z*}KuPv~O-#i8tzE4dXKvzF)=v54yfP zF3I-$zp}Ej|&$%zou`NShsD4dHSQ$7p4Ns{r=Y2_g?^~g< zYxa{H2LFB(gzLA74-UeC@*erY58kw>9VmH%QSJ#uo)T$ktyq}Xp6f3CfOxJs;$1eqZX+LFP8?C$Dh;~JNY#b?J%0OWMW}H z5|Z4rkTrYDcb;D=`~}-TJ<`LelP;Z-h7?WM z*?bzDk*z^@7T1jb*J|wJBQ$B>BYRU2AJK?3X^`&Dleov|sY|AX*_0x;yM^z~tHDFJ z2;PK#wON6^nQ2jm8clkpSRC=}oh@lGA0ARPW#m-g5i(e2yy&FHF7h9$%6Pad=X0cf8zRaDonyu0*+`B1CD+LMdo&!c@>&hw;gfpNL6<-f8rJ%y*Q|1JO{a{kxS zPEzVDddBq5uKVAf zRJ+V@o|WP>X{wXfyt-DLWH-Lty;c!GER1_HbX`)Bfc0YmQ%}tfRx%O3vvB#gYig{E zRk@1H5}zm2DLChuzUg+?Z9dd@QtY-#Low(_O3=*GyfU z2zj=r`6?9B!xom66=xE`Qf-vf3@#AjC#SoG*J5y2Hrqd-W(ZYrtUE z^i+>kHNCo8lYZIDzY}@OIjw%S+Vzu9RIv8j(k=by7Xs`Hk;~DEJ*@f@b4bf>)}6Tx zy;b{}e$P-YM<{pNt12GapBR2HzGO6tJG5(*qDKGPFBzUAt&%$5o};+96RTS5{NwoJ z2U%4W2GTp%EWky_PEZUIY^UP?Uh=Ik95 z+8vU|9i32G7(Y6pIQ`@osa}&SS7nZp{}FoWd#eC`*#5RuD!sd! zsRg^b+o)+5VaNB}d^fM`l>2Nw3$3xf@-MalHv-Iijt^W7YSi|sI)5VQvfo$R=D>{= zH99BKm9!7~U3cOm>&fAh2j)yfKrA>OtV;%wh3F}dO#PtwUc7kQ9PqN_)J%AitgucN z1n5p|>n52E((TVmXK3+>5D8_iY7l94{ayTV;0^Gend`y(_@|gQv|LX}I_f;%li62J zrV&ryE=d#qU)mioqWWNam0?{ML+t+YumEoDsPQ z1x-^PO*T=$+tA!t`8rXR_9U+QHdAgHNHYl z2B<@SgR*>e?D!}3DG%l{x>1Sm5wH0Fo%YzlvT`G8b{omcpQrD&Cl+)YCG$U@quid$ zXbm_U7XUTVXVb5qHI1I_`G`|l*N?_L`U$Okzv{K9g#I;fXQ7Gl#3J>-lc&{|7Cs06 zH}8$dRwLi#ozf1Vd|K~iL|L#ev>S##A9=G|dna8vcfgq)Zlb#M<22sG+c!t)l&Phu z>x?!YKR94us81QcseIZ>uh*9JIV(3S4`U|2Q!riRd1Mj4dW-8`R*h&k9Je71zV^f~ zxi}O|L0_Pc4j7&4S%2+|A9pLat*5-5Nlb%sR7WqwKk+Xaud?)+TzzSw5s~P;#Ej5X zBMjI3u0p@b$~bUVpjkP=PeV6^eC$61J!oLI;7NA;9BrIz9EUO<7;u`p(pvpApyWZs z{(++R^lK@?J#2gY%pm>4VK8|k-rXm2c)^)cIV_&@$rU;*EgWQz{gDhgtB=_hOnKaZ z_tS}tmc}9 zhK+o6#iBUp&(+qobL^1TvV=9?2Zt`Bacgztuqls2$b6ww?~&G^7i)*^gUnUPHghJO z7p!e9lb;u&y$!iVqmN9zqor@8pUTgoFy?)kgJZ4`` zq@Efp=05+sHdQl|Hk$Hrtg$S%#^f@PlJRkVs3ddf0{heHA>kk2)m*$t&0e9-3#SivaBW~6AZ!4VrEz4$i0`5}t*E?>B^3-5O~V{ZtO)HCC( zq2khB)o;DjwixPC)^pTbb>s(3!j_##ZuQg%WMiYHxd%B7Mv3|CkT% z!3$~*XN+taee{A3#j7tSr%$ccK(=a?KwbAln1}Y5hm5QkziKl#^GF{U3im!qPwb|# z88_{)0H2fGQT=LiRtK6Rn{)i8VQ!6ZVfC6eY zBEPk#G+^mm|P-epxqX&-+W)3=MJNwuj%Qv#ivnwmmZ>oUVxh$Cu zwk3nR&=X~%I=Af6t5|y4N}|v8C5prSh=fr`EF^8AWzPO|ecHl&H*Scyc#);NNEoE# zmtDa*L|FcD#@l#LK|EmB;yxSRsrhl&8xy-$H#jkyK7857Fjz(Cqe7SJ=)0o>vFK|4 z`x*f0!^oDd9+bHD#%GLTM0NFK*{M6XDXH)`YnqfUNB>r_hc0)X^#>Bq-F4?|{nOc! zvsJR$Sz2S)M@!B_9A--&RBQDZ6iy^Rvi{ROm*e7dyL{i!|2tYK9kr}3a_l{=Pv%uV z`?F`JE#>rzf_t@V>Sq0)wv1c9qqitx|K&5krjnK|6 zUoM|(L(fypR>Ujjfp4PU3yecz`$It*W3fc2-+ffax|(Z4x-@*rotRVpd~r zfJ-9Oy4q@f<;D*CBq^2}JY8tZ|E5m5XMKBSWeY1M{bJ9x@#=Z4l|P+$V!>_lfU=E5 zaG~U!BK$rYelE49vg#jG4^)0|eSqV_NNWI|9Fu$+t+}j<_i4g7L<~EQ*p?HPxWhR{ zw;WWlTCQgGxlf#nJ-;W6T`yOxs0_&H-&R=5S+Vf3HLZ}>WZRf|13ph(u1}0e?MJ*m zkDnbpH{*|o*pUoYZEE^L2>n(0vWZ|x{-6B=Ckf}fxRL!GgExrs$=2EakM~z8mptrV z{xrUH5s#>WEq_`fU0XO^=Hk$&+*G55)?4=W2H;5t5|dWfK}Eqpww?FW0)v+krXi^w z+5Pba^7)xoC8qfugHTXWGR&_u8w_=|>~W}>V5`oJcYTo8&B#PCG5pjXNrk%YSTFbA zVo!N^_=i8q&L%SkkRhA%Rd>`|=e5eQExz=n!7IVF9*)Js(}OX|I%lun0?w`t2jplq zk3H!ddou=Gu!YNeo2!#tiSxZo*ldLObfW_x6B|+KA8V zC4*M>!I_A-fcn<&Ff(+m9X7}M^~hbY`Q5e)!+LK*JV}#Zo$;A44eaD{{@85D`}hC> z&)lL4SyO;=@W%gGS`V^F6+C~k+f~KT$;-b)<>veq%14Tk5L=!!f~~MJ4K`ta49jqH_$MRydX=4L9~7I$Hij2D4=ZP zY}fbEhJdmt4kCR!@%io??lXj6X$4q>cu;Tn4VK~9dot)sn)MPpc<;!7)n^Gh)#@%( zq5DcQVME(&_|r|rRVo&H(Rs;FSjne{aLNkwy#y9E|2Ee8Zo>u2ITPUnO8Mw=&blA# zAO-VBiFIQ?MJeT&V4Fd-@hjsu>DMx^XIf`o?zt0Df-w8=M*}tr^fN%_Gb@j(scNLt zV(kiNraHe@HCd0jW$=?;`D7Ge%>z`&?29C7Eq-(6>Lr5E&dT?K(0x_!@wde$uihz5 z`uEyMDp{CEN5sng*zD@q!tsK)E4TlZK0{f5c{`84!t%yVNGIG3Z81;Mp#SN3=@c$i zN3$myBUaD0#mMwyBvgK>Hkv)@)oYJ=5(un{o$*j^BkPnU`uGO*f!yu1`hXzwg0>_M z`&#Zqs3%u(Woq_2GL;-&7c2O$<)uHjJc+w4O@J(*>rs&9*!Y$8toO@%(Kzlr#Yi{i zPx)MJ&msF>|0&31k1($`TpDad$6+o>foTHMdSKdkfl0niMY*+t>%iUhbjB`B9}HX# z=SGdTg;w>?7yxgL{o;P?pCSh1*=E>SnGak4qgR(wYlsv$Klv&7oy6G@?@&mR3ZlYW z2TIHSI4!C`-}RI0>i1rA8UB-z-?fq|dl7g+7oTu;aY6Lk9%!P}Mfgz!wn z2NCR3cs=3P!uNE#rW#Z{apC9PdDThh^2>khzZ#Q%=<H|B`dib_`A`HLd8MfpKM^2zjZ=T|T@amz8T z!qvp2^hNHgAM~0#V}#+DeA8yQLxfz&0rrs!*s1|;ms-E`(!Y3jZ%+K)N%xyJ>C_Z% z=yFTg!sPW}g0llB9e0bXqI*i56wwf7=B39@XS}G)p%l@ZR}3V-JG%bVRUQS z-<4b|?e8j5P+wd>r$>*qFewmbA$xjPzp4cb{u$6nE-99H!9+H+5F6HP^tA-I|0sIJ4mc;dcUj5UXQ#Bdm+GK$EcpuF?elPKR?Ev8b!RLSls9-5cMN`o_}{|Ao4W4C@e81_l3NE?z$y9j zal_4X$Cz2$^e4ps^?x7Rn*`2T4O%r|O(IT-+v|&p)I))%CaL_KBLB8_f9Iu;ffY*R z!gUJ>_s*$Zx1fU}mjz9{@O$sFKJ@v{H@km#oIyu+xZOB&1!AC3yuAg z_hLaz6TX*ho^KiU@56$=4rU}X#d{wG_J9M7;gkzKh+EFy#ooCC=OAD`DSOcB=fc1E zzB%&EuJ3!N917I2xQ~;FjyrjrRV5b_msOaW)n8|D$2#Hfhe{%D$f z&rQ3rrH>b5)4isOg3p8;3IT=eFvu`YG>$dyIaC;|XiwnE_eq@N?brg{SuZs+HA^*f zHE8CQOpE`4)#u(%y*a&~!CqUt*t`mgoc1-1H1lMz8)oQw@E(IMP;T%!wk!L?`sJXU zi>?bux4pMbh(N>L5ieH4w*}lVQNJRt0AI_rR4W-<_-C;wIJ06RbdACty{iQ;Y@eDvDZIW{6!&voH3_a+{C;G%^`HO|JlkfR#YQ#Qyh z^|LU#rNMi1D)ZmTY|b@X-4bCz=Af|U)u>Jj2);j~Vld)%cSymnyG6s<_e;%N=3=}} zPtVod_APZ-ve6sC?-6L}9lKff($m%ZXoYHp3ES8k;SF4r=0n0BknEb!(Cv$A(<9C@ zdA$C~ww|u3hdsAdP(jKQkKyjrOEss#@?PG?As5*4WFPI2aA&q8?>`}K56d~I);4vF zoAg*3En4IrwERw&T`D_u=ms{Y{5jvpr_XqBc+he2Al?0|@f+Z{XR`3)p7>$?>%uoV z5L@yAwX|VDg-z*gI?&=f_LLq003objE}YUxdW^f(gub$!yZyw3MiK)AZ(`Lk)M?Ia zzG^@|D`Y#l`2+}%%BXW`V)<|H#VRCAtV?W6?BX(hj=3kb7P9VbC&!$4r0R`Yd1*0j zi)GY}*jL34Yc3jp{PE$SK##wlsdckUeD6lA`ZQa*J1g%^y6RvE*#>5$rf+sza9{wZ zC-vx-UL3%&B#rZ*F_cbakYo!1DmVA-7RhDZ2l}+%AX}VfElW_}^%w{|YiU|9HlB=G zAX>1Arm}z2H7uJC3Ar0M7iAq~+8{_{SxK)*el&7NNTF>#1n?NEem3@Xg#}lYZ^6dM-X$VZ$lkzR+Q|C&73Q_w zo=&-DVuSl={n7f8|0mQk(HNKa?xtRXwV>gVDCEbefDj- zRPIp_*FN)_blr1kL~bJtu=y;Z{P}z5jM@x6tp4W=t+;H-LZ3jH{lOXVkFq^nV{=&M zKWDL=VB3TDpB#lAh90KhoO)lU74E?Fr%krkCWdN|(na2_iJ!_pzrVRsavKic9ERR~ zRyVwS_*})}(yl?X66i+Q--A*7*_GUN)e8SDVf9gKW~L>jMw{v++ZX~l*4IZ^{Cxhy zL4v8~;Kr8`Qx8wA$nbM${x3pV@ThIKpl0!Uc- z@cD}8cI=Q?8lg?ZP9v$aM%*P4-IN!6RXyynIm8c9TpE1+!pP9fqt{2swQ6nbYETSv zV)?iA?XgkQZ_(hawi%yrQ4ro`D5$EfM{coteC839UAMAbVA{xPxbYc!4*hDu?k2W_ z)tFj`<0V%+k>+2C&hx#pbS>WNJQTcskMH;BZe9!@)JN&Ccj7EfeX`cihx)AiyHZ%* zfXwW7Uv)`d{N9^xR5*W#e7}DAZ)|6z=3dMlq#Y`k)bh(?P2$qC%c~=R z+e9|lU?|?&ZtO5}Yoeds&z#|BxiJbHGrLBE~Ky2W4n=@as4I&Oj>ypvmA zvh@yOWkqRT9ffm-9)CmF74)hf>Z~yIzm;y`{C<3<(JiB@KK5Ion=POua}asuxPOU9 zJ+@*bpobOq5>Wi3^&=<36 z55qN_U|zCZ>9p`m@6DaX{^2^eTAlLyuPt6|D4VnO4hq51&(OIqrAI7kZCq5wlPV3L zXs#?ym7MRZPZjNDHq+YLWwvmUj-=rdaLdIriSu%J zaru)#?9{cYqC04cV!$n3fH>;JI>AOA9yVo%k2p;knfMsmlDA~dqQ92a={^}2H3{?j zXKoi;TCm06%Ie_HXAGn^f)ngd^Kg9IghNtkXcz0hZ>!f&nJ%amukT~0h29RGl;pr! zK~D+QI=E;>z=(h}=Ilcia6zn04{+bJ0Oz0rxtWTo9pctv_fJ*3yh&p@g}C0Nvq5= z_UR9F?8ch!mY#3ywi_G0o4p>3 za%lc7B_noSDA&_6xOb7*%zgCKa_R1fd&yP4`sKSo*!XoHT|5f-F}#dktC$0@g#@4} zmw|xb{+tme&8zX2KfmyevVPfpX?jV9Sd|-U%_@)-^u1IOb4l#EZ$p-HEtvekU0^ zx2f=r!@6^L{Nd%Pw_N#itGEMP=xdfnt9@_y?cVTp{Q-zHRYv_)mbkhgiNXLWom?1* zeJC&riX>=uiYJ$g$LOJ~%*UDz<*EHHx2scs5mSGarv7^KzlKTug~6VE1L1|Q0p|=_ zx0C*WMp7L6JPKl;dQtA+EentZth<@NgE1wt1Mn*!dI=%AA*Dq=A1cQ$*Q%Afk5wf; zG4ZNOb>=+AxQZ-Nvxt6H+SV=AW9hGG-IpAl)4hMU&C|i6)^<#9#l);uzucr* z1ZY_jIV*1&AiAv?lZNXDt4r-HMruC?_t)H6xI|HqCF$LNNO}Jk)lb2tKCqyYqI{R6 zxoU|`L_;X_^&crJ&iTb+xPx1%k-uF{LQQ*xqt~~-OH+i0;Cb)-;O)uu$#WUcu!ZGb zyr?^4r9Q=Pih@3H#!pxS>6h!DM`%}QR4B8J7uf-0WQ>6S-$L0ujLp3hG!!tWIED&YRDVY};;y-U>a99%owz`QgSafTXwxlaPNx&W6~? zmz0=z3&S(7j?}{J1odlX%6$-!*4}HlfXQL|;OwO*c$RBf(wwa<{x`Zae_mOqEGxCg zVJKw&AuH`*RVrW9^f1Wrk8wEQAjKzew9eI(beO{wHBn^g1X~3m{krM&9gCXy0-}lY z%c<2`J1=CA+;4Hrti)1?^^)qYNbzFQ++oUcX-=~l*^KbBm*H*FTqIV7txy==(j&AZ zuzKS2Fgj!C<^K8s4HuO2?CSvv0iCGFNQnKs!VUS`7X{TSUy zfZ8hBuEOKO7{AJ~@A>NZ%vC-08gI0UbA}j}A$I!P_}=<(wa-gu)f%6~QhCK)Pl~!` zLKdoY#KU+>&6Jy4?4GM^mU~D&5^WT^+jSA?xX|ntZ~%}n1sd_tz8a9_e{AeH>at5; zMX6k-ER0suACl&LQ#)#ywHjzeKO7Mm@mBB_@wwk^aN1(&*s4i}%5}Nn+>J|p4Yl15 ze|U;3@qdnvyttDS0sPe<+#pkE6?;zft_wufeyAB?%G~gitJ$Csn^}%2x;<{dB<68E za_W;l7x9muW}WIR;wIX_>aqJ)@G^3L?F7ZXxXu0yWUMyT_-ZC4_aY&;K7yjWGU+){ z7@Vl*SO6xQmw|5h46z_%MPzy(42_9@1nT>fm*p2+qdYJo8+NmD2b@PP-qggCL<#xn zRT4o`=@DE-j^u|N9}{j5NiZf`U`iq&2YVvS%O-Q{1t`t6F8&w2@!Y>nn5`!y{B0{4 zZ;kM-c5^;+Xy=}yM^oX0X8z%l3YBG`gYjUf^+Gd|Wf6kh4pAv$bp-|Y{8I06gax$@ zeP9(-b9cj&>bS3G+rk{Q)a7cc+$)~E9;k``Tg=h;IBe+#eq&WPA(7bSDSbXUfB`oi$*vWn0ovIgCX^@d^aW@usVX4S2lZsF#Y zlc#zZ=+EmmA?kZvFQF?F=K^t>DYI=6Y$Vvd9%rkMD$0mmTvJ`L3icXWITZUO3z)Z} z{9_?lZ{UPQNNpPvwQs3pTPiuoy-!CWpM1K9QP;d)P-;wpf+Tsi=&4uk$RFVjF=}2% zoR@0{otJ>lu0_AgOYfvl*A6-b*J#wl{$W-07nmizVVaIxt=mwpci*0OvK|FK{bMwEt@LH)wU9$=ua9K~{6Nwbwf~$p?&nt8s+ohj5Y2(( zVOX2_X2Yq{&v#sd+t zYOeOID%2+nkX3hENG?wT(ymd>+S?pE0n9)~USn#5)UgOF+7756MHJ_W_lX=vnt(e; zBK|?`U(OpF3q2hMT9$qkpO??t?0;qeRnvpj6OU}6Y?tcOD7({Rj!JUI(%bsL_e~Na@ zs>Hd+(sxL%LJ2=bnwF^DDFX2&@JLhcnTZNp)}hw@>)4#HZu@dX*J;r4PG#8UtV>`* zI;Z0}w)GK23E6t?7x@GZaeIZ*@`v=(MbuhLQ}_igppm~~tYAT@8!^}b)eya{p+M}>4FMaF8_3fbpID-vzS=Q3Y~N=hJ9tmUb|f3WmgDGVG!3#%Tcvn(u$)7w*3c@}mU zVJW%!1v}y`)pcIPJt&ZBGE)n^aPW3`>GPObSq3hHy8Zm3A%;&oTz{RnW0>y5NQ%N7 zOGag`Nln2MrG+d+n10EOm$U{ierH4EL8GN!)DBOQk?#tyksUClpx0_eT`GD>qHS5h zM)wePmRVG(3)3jBP^`S2s0tFa9GEfWQ9R2?`(JJdb6_AFj7FC|%zZL>IUM-C8R%1o z28;>M{R_5h4idvr_9TtklJ;4Oy-A<=I ziyvp2>O@s;^oT-~1%&P`22ja$huqI>qWI6~E#vH{h^HgvY^)_?$1fw~wKlkC0X&^8 zvQ%H~UopB4G#qONpWiRsD|=L|ZY?cobZ>}xOFv(jq6^xHI>*;>k#2)-#aT(-lwkZE zNx3xoH_>5)Le>sp1i@R9B`e$+%-GD>R8AH?XYs7^#QrX#l~34vvINU=E~uuU29HQY zloTKvxS&&FI$mskeTZ!nroIeFq*t^^{|Vd3BhiGf3+E}tQ~@)64mke1sF`DVSohnS zoP(FzRU$Swv^sH0eKKB zt!;I7NC4^;a^Pz3r7!3^_->Yb6$Td23*BACk9#ptd5Oo@ABn@JeG7` z;w!h4%GN%YbL&*x4_!e~9A+TXR(?`51Jnk7{179Xrd&AfL{r{1&z^Q-;tHpA6H`@t z#Z`$SkIeJVHW^VKx#(9N*y%Mxr7Q(Zg2+J~;Wf>th8THEs%llATVCc)JQL=a!@8MWX&FOJnNjdY#w+Xy`ZK!fBnz4U_^dFHk!`M)=vlqTQloxl>C4|4* z5c6({qD>3&69E~Ue~~V!d(G$_0D4j3QQXUXwl8R}I8`3gR(T*77&iH~5VcdFyMc&R z6-U?JH-N`*CXaC`4bfP(FGq|Qudb`MdBQ2J!YK2h%E;g+SZoHw%4=?$1n~ zHTt%89vK^kxIe%|V=oEs z6vNNrWcHNQ;qE7)GzdPpuK_(~VjoDH!|LKjnnia#ru}?TCpmy;N>jsbC?%&blNf>g znL6~pLy}#GiRTpP_f#Rf|8(zQ{8X!vsQF3AMpRM^XK~%q7f4a- zF4~PF_GI91$W~CY$a!3JYegZ;dTKyPs&If*KrL!VjtU&Yw(=e^mktRaOIe)~QkHn{ zGD-g&q=EDieXfg?EK>deXEtcH3xACvfUR_6M!Rb|_XJ>0c%bed)IR<$kb1Yn(>1%z zn5Uxa>axRo*S0h`5Oc#JvYnz0x^nSY2iDYU5)O|&-Jp@k4NaqDbMK>%LIWHj_g7`b zi#ICHlQt`mI{LBKsCtgTca6&Z(jG+?L|Jr}FZEq9xwaY~E#EHxPM+0cE_y~)?m)Ql z=aDS1!K05o^h(iTdQRMzvWHRq7RKh6hNt{ zFWN*~#vz6QE%euz1J9U|NeeY*Tw%=6LND?Fkz5R%bsj0ahxt4~H=0YN_&H%8p!a2M zC8mhfV>lffW&8r{A}!=^k!bV9vYsyaiq(=w)Vi>cDim>wWEZ*tao183X(qfL#nr(6 zJVM;exXlc95sObjR7C#FBi3wr>yogn=kYz$y-s^_juFg*2NYh%dkswu31hdU=ZM*K)PCPFI%E=uzv0mpFnXN;P`yJU9ywkpYN6&@9X&f%YT5kK>YGLi$v37}O& zkX;~oRBPTC=6Q*viPZo?NXl14 z8ng4!5Hm12ioTU%E_rPx!l&@&O&Ea+aB4%G%z+1ZNy)~HZ8CNxbQ&huG9WaTHiJl0 z&|dO*KP&0frd1zF9wD5=_@{{FmSf)OetWEft!ihonWx06n#(v8 zc9M6Pxk)~27x9BgTVBQt-ie%J*apBlJ|vyw=LEN%w=dOQqq!7Lk0c;Uo&!!&?(D)Q zp^yfkjJhueiLX)S1}HgxuQxA-R$Dl|kr{f1b!?Z&fsfj^5}F`#m51$I+Y=xbkJ0ZH zh|f@j7O)z}ym2IoWJ&xhjSvgTS2;i=)*MD_=xQXqrH4|pyTsR{wbHS_8{8Yl6R?X7 zi88eUKBg8>Llc0(R2Ay64(!f?F^o<*!jR|7G*c4p{M98$1!P&md|j|*G?5}Pq3%be zG+b$9ewIp41O`yAt8;)fbG5L|-@81sc^;Ka^ZSw?k-`qrVZdA>3kv21GiPLjpfRI` zeMb4-gXiD)i|TL5RSg<%alhB@5oP*tgi%d{GsDhOGMK0ai)Cm#>F#U-PDJGub5u(v(t+;Mt*j2d*5X4_-?7^)vbGrB z$_fSX;%FOV`0uxZUyJ_p!=7A8-35Lxx@oXPv0#=BEkhLANl(%#3{t%4gP+Jk92$)~ zDs6Q`*+)vRNlIFXAJ_nN3*vlfLHPS;9=!vg9jK$c#9ug+$Eft?7Wf;9)dnEd$L-8! z>C}Ck9L5<8l!qt~_lV?Uc;6ZZnuGbuelw8G&t@aZ$E7bpY0x>YF=Ljd>?nbzNQ_`D z>=WVDept@# z5uOFp(pDESE5W=6K{gW;L;y?anIlFpmTGOgw7h~Kvrj(F2GUqinl3;FuhtBQpV(Ac zA41tuW760694ssBEL`_Bx!6c_eaSdo+ALESQ<=~J4ioN)rC--4|o%pPFB1Nc5{YGqw zmRd-UoPm6oIxGRhY+ZXMR|{EoVS2oMf$%Vg`1A^Zd{h;MZu5C z2%C%$hEV-fJz=;N?xr@nt&_B&Hqq$I4_%g4h5FcT5sdkKwR367j;5C(ZndPv`LdN^ zTZtdD=WG-uS`DlYLLdXTUP(E1EWWZLo)P$1--6X@sKd4#`)yRJftV{5h*B8)7&bI3 zPhEcJEh6J4qpH2?;v{6$_98gLc|>_^J{pKe2gyq}KBm~Gw_RZDV4S3BpOI*_=T-{h z-cViih$0$$GMR1pYt9H3JIvAUz)S5saq<$4aIL^|=EQW!63!?$loI#Zn2ugCg%?NK3z@g&Nb5cxFwOOW*XyRPI5U znKc-C%$`B)K^WAX2q$}o0hq9qHO;$YdHdEV7MM&qz$D%1d7{i*BsSzFYM97^04Hem zYGFFO3>x|n&!2&&q1D2+@SK>chr&b}n3;3@IP0}2Ba5G?$cGZCGB9it-%3%Yl{(A} zHJL^PGo_=g^BZ0henQ2q2bnH^MO%jXhM5LHPZxM;i(o%sDo%8V`IS~N%L-s@q!q}h z-g_Crr(Bm9geRdS3g9f!lZ<-CCPp|d+830o!aT%Kg(p(niljS7Ns;j5q(m7GxMV>$ z)BCTn8rQLT4lwFi#({w zZ70dLSoEG1{RZ8BiA@*p;-iZsevP-pQ8s@3nM%oSDW5@!Lw$OPem9T(PdPI0ZTz=i z8v4XZYHqu}p9Gi0*+?-5w>IH1O}UEhlDF{{<eg9H64D6QiCWTu`-CoS!Km>r zF)u3099G~-Y-b~Q12_{^8jV)rons!77w(V&0`AEdfaz}|#kb_3FY$jp5hoXE3-$4y zu#5aF}YSA_7wGV;7ggRO*ifdcV4 z>b;-nm2?whlGddr6Z%}5miFC6ZrlV!j&Ks^FC1%bAVR!<3alYzhzjWfbs(ZL?t_q@+^%bC~$9dZ#Rz*0D-NcI`Wd$j~Dc z_p;C^bJQCaP(~5v0jtUET_QJTeG0Gti{u4*6Yl{Ntq4*BmAJ@(ZZsMBots z5qAlV-6=vdj|w2aOKIL&6cd@#LkC|M87OLxw2>z;MZ2jM!o@o$82PUX_p-|#Vb*goD? zCPJ+hv5D74dsrz+;Js^vzQvl$0AfC?@|1j?Kd^^X)gs-(LA$gTv6|dbW)-5O9U%>7Bp#j=HF%AuKLYAy; zCvTx!>5Qn~mwr1zJjxiQ9Z?_WU;8Z&zrM=3?MHgdM~V0K5@_nsAwev9C+|eyKx^C6 zyvV9mLiNZDO3J*>JeXC8L&QqY{~}$u#JVN(tT6BDKIbh8bWmzx>sIN)4~qcFh48cz z$xK66!~DA#x*=>s%^GD@JF$wwWbAfKL$!{poj!6O)rk5= zY)ciywMV5>mKmr^d&2aYV=0^-R4Y@W&omxlz-YVG=h=S$guBMG_{U;F-wqO=*JYJR z@o3xKQX!44J)@Dx(pM903zw7e42f!WTP0-Wb!+!lM3`0^?wLm;Q6Wq{EQmRs)hWyy zrH0H(Wb7o|FBYxHV}H?=m%c+iH7C%2YbcA1i14L7{Hx6E%M?n7Jl=ciXD*4Q%l{<) zglSbF>AkBnWnDo>e`jwkWMutyNO-N>lxJNEa3$k12jNm=a&k8dcLnYsJNc zab6)lgsP&XKK~C~iC1z4G8HxNxIBJ=0a8FLHH^ptkn*r?S*^@*6UNb4w7k6f2H+Lp z8yV`|r*yQAx3lw}XFH- z2*8vm)?LEf`u(KG?oin7Fe~Op#kzf{4g52zEjsitX(&n~ zI#dH1LiM7KBvR%Y^c(Hk5mgM-2RJ5KNTyaL0GPL7(qcJMEZqYYi8@QBpQytCH7M9< zu?5qlJ>F6F(8iEy(h=`0GkM2op$S;`=@wZmaiAUhg<<=ca=3?nQaaSs)#%8hl@7!hooZ!Tj&Tlr{19f@5-+dB$WN^Cyi*P+npvTGRiqDSghjol88MCF_DH!@5bus_&zGet>P zgU3%y+C!-}4%#j$-Xwa+j8PMrqn3;~PvK9c=7(eT9d`*oY&1XaTNkvWF>WF~yrBvEB}{)!LuB>6I;LcN52ikxcd zOuN2X7??Le9chvtke65Q}iZ$()^2 zol}FQHh-n}s)4s7C^Vv4OM0P_bLSuSM94#zp}X{~;%TQP`m)1tpulRsV4SRYhz!C? z+OQti+cVC983>0=|Qla2AaaVEWQnK4_7wi%d>tQLWJz4N<|b-j=lL^$Gt ztcZ&ncQ+u3B>WEi2C-nJI*gJQpli>tl1}h2#HB|2eI|JgZr2b>t%%Pt$7IrR-C1oP zka^P02yvDnqdu%n?=em)tf~ZB+C`p$b2bv)VTO45jdVu$_Bkh#;66;`=n;|YfOQHY z8K#0WGbH>Xa%UibOe8^!3jq6d04U~C}#>OI+$qL{Ay$%+}(iZ)g zwFC`Z!T}Npr1S#zxbFqLv%h2y0}6ZrTk9;1LPU~sxMxZQ(Uu;h_oE~eG>n2MM#g|x zX!dqXM`}8p%-vIyQK(yO>IVd{!;5i(5Nz@XZy10f@{&nRzc~Ueh?+-1Bx$(8dhKepL{=>jm)9*bF6rH`Fe3}PvR(~If2MsbpWhCsBTX? z>W=~mOaDUuaR-uNW>LI6(rX^tO2;D%V|+U+M0&e<EK<3XW zQDmXHrmi|mdI>w|HzI=EehLpopKj<$9+zMI2jOGEK%8lxqTLJ#ot^4Vq*>?O{;{!( zI!}ihv?HBhG~~4Mtq=GHf(n{X;Kk9Ob5R7kvv)EsO#J0}Ow0DI> z_rG+vcRH3no7CtkdZA=s4^D60h`BG%4!T>_6cDSKBt^WIuEpl zfE=moc{`d7tvWzie+J__pY7ZxOUobNPc8u92RNGH3o#@kYtc<|?+!ZPI*+6cNYRRK zEKbZ7NVMb$Cp-h{!wJ)wB;5w$su{rC*y0V)B)q5T>N<$P8jauuI@cg6+Ck)Oe|rG_ ztL{j3<1-MSj2txiZx$n_df0oG!_cAVyBlskwreI=L3)s}U&MpYBs%oL#%Yy-Y>|+fr zNY5wj$hCLi)5z?$-^}rK;ys*BDfveMXR-mbUK^$YKD77UMLOWOBSUyfaESkIUJe7| zx@fpX?v$hYlY%+w4$6(3foNPtLGdM|7w25J1)m`EA|H|OAt#*6eV)YlN-}Fi;0pD_ z+_b^pl%|Bo6HLfa9^RFVyzKMZfGT5CuKYnlTa1%eM>M9A9PBmbAH<*-Pc$p1DZZB9;fFAu!3=77|MLWu8whw+5U#c z3C*bLP(YP5H408oV2zaK4JpXpP92~`4G$RAdJ-@=a^+3@hP=1!A&mU21gkyP?KV$J z40lL0`FQWv0x;xSkpYkIItJ$_i&fv4sKyN+Lbtuq|awNx`jQ- z1)JI+w~A<1qFa-rBNVV#F!l9kM(JnN(7e{dWX3eS0IGCwvET!YG<=#mk}8XKwaO3q zIRd+&m8-sDB7F2RvD#U&4s?Bn5pyjuf_XvYErS2ZyBtoYb~Hy)QF z=X6%OyIdmf7F~hF5Uhu>vjh3U{z8iK z!aF-PxCw%|H24{ zTR@iBfyhLTV1+=I|5DLc8r|}@8WzT?yO@c`oDGqFy7+Ho;dKyed>vqd$Nxt}w~-jY z47lk3n0&nz#^Y*a0<0=b;Xj5q`y3fr%}WC80Y*gd<#Z;7>(N&EjIZ2b_t4QB4OTOZ zd-%KU!tmv;%JUGSAi#pY`hm?!=N~l7Mu^Wf2|1R(1%@-ZZbQu-P^M{Ts?0{n#o zigF$}dW~XoHC(61gmV!}-@)`t{$aWeh>63D{RuM7*byA8pLsJjvkqHE}H^!7ft!r^IG#y+=nM#e+D)M+|vH}oQ zV<(fAGJmF#Cs|w0KI@kIwGPNEu&w;rY({FxPY^YJaR64<2a&^FqsCv^-%c~B=pzbY z?os2%IV6+0H!{6X3`1{YSvNAdLs>!(n(|EoP_d}X!BQ1;AXA4 zGzzM%(cMWFkHs)~#&||_0@tXU@;+#Y429iP3xQtsa<1vXT?ZIddmcC?IzkHTfhpyh z={G}Xn|Nu=c4~CZPVVRxt=F|jneamwIyGp?4@Zp}Pk~My*j^k%Di%i!cR+ceac7<2 zuHr6h4#yKYki#N*(I;~4RyRqtFwIS@q#$Ug4jx6)+Grxv6$CCEKXnlmkrn4uddV2t z^B^DSwg?a3IF}&9=$tvMQxl060>qhd!f)d7foQ#foOx_A1_HO1Os3fw_L(*5a_(Tw z*nU<^!Xm+=mUHCJ73n5wVm_ShDdLE#l^(W zxbNufzMKu|`60h6ZZs%lG+Y|yw-9qB}^rl>)h#2=K1Youg?JUOxDX@7hY z%%H{!roinnr~$w8k|L0^Fqu@&PIRBJA!3HKBQ14(YurMk{A>{m6t^|G&O8 z3ydU#8Z&_hQ%Syp8`+Eo5<=?YQFtREBADD$4VFO=MyoGjdce$ftRo0!DLlaoD%_7IndtEr$WTq$`}IBx=LP_$2w_<2Tetxvtv?#) z=|p@P34I7jAh#cN7XRfZs*N)Kr)Iq%JD%cCQ(JS%e}LBjYS7p%A@X%BZN^}z{y|D| zvvh`B=Dq|M@Sg-=dInFIOpf%D2M&{&BE^9tziBUzd&0E?&oo)1f9Yp^nl?FKZBvU>OuNBWH2Jas`laWyPm(3ux27{UG?R2^Bn+V95z* zRJ*GdO;9X=uI{4dH{grsrLr0w+vz07_2mt`CQ3S;?0O{-i_o^{h-xolX#{xaWE&N* zCr=vggkEU{V*kWSCJoo`CGub{0ceCfsrrB*swUoCGT|D%^-Hl~%P`}J^p;VOR6qvN zgBL~DG@=NA#nV#qj0cS(yiX=`OK9Z{S%pf3h*b6wD3Y4=sP)+%hyrAVGW0mAN82U= zR+~_61M2qb?NnC#C!Kx~=0Q_iq_TZP=aJC)o5I=t!43Hzt0#k6<*SIy*@`&UH2jZM zKt}wt?$q>Ia*DfLhEzm_I1r|fIi6J^$XJBcZ~APN1+P}`$BwneEHJcEfThD?wD55X zz8-{!_Omvpmt@Cs2T3TkZXV!~dQ7w8m$`N74c4Vjl0gS-oce&ds~CTE_ zgy6d4ncz+K&7re{jgHKFYMqg#FGAg@i(FUjxI*J%MO4VN(b3U07cvS0yeMJYt+3$J4ha-45N%9{w zI(gRT%fXQ_e4=lA0I%r?%xZ-t1F}z-Cn1+U{G#EHk!F@?xK;I+H1OYVTH2 zXjJ5XrwH`@7bMIsmp4moHxt2?g>s_h|ZS$mCGZ(i3xSEQ`B~1=M3Vi z$WDJbE$3uws+%z^m(OXMu;!ojvDer|3Lf;`8T71+5wy%;xj4V$2UN2R?VF|j^uO%78-`lA8^6gqPvUq^H|0`Rx4c$Yy zZT!XTb03k`L4Wm9g08|Fg7welxKn8*H}wI@Q;$dj{R{gW6@ATwDt2AhM;Ya@stX5a zZo+kCOsnmRThHrU@f;sc>MKT-Bj=x(`@&<%mGzUgsY?H|q6_ z7MuVBO4ND@+l?HZ@{hfnuPq9HOZ{OZDKGV8-IEJhy)_HKlCT`BxsG1VN$&!}Y?WRY z8$SMZLCTb}I=E=+N|IrsWXfc$t%9lE(>C+%-cIrUgJ9lbXAYSB=WtE;^`2W?UPf1_ zoB;mxBrZ&-l-05}J>-;@ww&+#{cctkD}i55?nYDU;%hp!-&V`#H+MO7%|;C^`8j(@ zabXq@D$jBgZkZSn_8vNAltwZgc)ZX{2}M^ov}ogqb72x4p|&51D>$i7E~#2V3wShC@Uk=RX8}}ct#>;dWKw^dCnSF zZZypYugKpa8FfMv?Xr)(;vE?*IK80(qC}_N@jj&%1l(b{?3ni>Aa-vFfwy9 zy&3bmH|Or} zkuUdo7v~%vI(&V=JN#DiXXgP}L{Wumsx#}Bitwu&La!cEd_HHPxXSg0QL^q~`z;D0 zT(P#LL1GY$gFts18dQ&rA|9Ml)_e3CFNE=VwNXYJhT=u=i z(pCrfkISnZ0tYVQ)3L*!@flLN;k!i+y>AqQ`8Gy>$gKj~}9`K?4&s&{2@+3=M2 z6$R)8$*7tM73DO5CXM!E{hZ~VAY#5FZ7rnm^q2s3zZHGQ&WmR5l3YOty!lPZ8P6{{ z2CsTTe_31n6WVYeU;6$&yrZI;4HJ-L)byg~+uKvS6+&e~`8?ObZ^K&$fnOIYbafav zZmo_ky<6wZW(q*l>Tq#YKsQGd#TbHrsND8=^^O_zVKFd$_^{lFH6W|$zHVh0#}?Nj z<~-~~_og$OWu^Q#8E+&T+%!9;i9gmxCpASl=|NFzQVo)T2|IB;p=KJ0*Ixq^1ZDd}{rpi$sqVH$8R zW`9r{>ENW6PN=a(1K;l&B=z40gSlR3f%v4=G`o2mG9b5(X9Zgj-!-RqWTYhy}Ujjf{{+`#3U-8Zay@$&T?#^+s4&WO{MIp#jX4~Ls!wOOnj zeisoQk<4)@XWsP=YJ5X|hAzuguH@wS&u^k8is6mIq8qO%Ynz;MWk*SQvfMkxv0&ZM zfWrjmmw>c83UW&n07>2qa?q63U8VakO0FYRV@R~dim zRL83g-U1NhlwO+#Kg`~8b zhcL_ca!>is5fVc8H10iNGSlejW*EJgFf`F)4NT(hb#gtl_zbC$A5qt09nlT~S4mXQY-aSlJ3eK+Ql`fnoW z#UBHUs+=?xk?e6%5GMnU>`xmfs_GJba5*K(l!zXih_DF1bVWZC{4jcgw{bDW!-!9C zZSiezJA*npVK)isMt+pM(u7$)0qQOW%cbI|z%R&u|$fVOVT z=}GW5P38yDC$3B*f7lv-A3QG~SEuEIQGT z)&u0?Z(3#iq|MZjGR~1rH_Rl)<2Lau?yG`N_|1j}|59zGxImWJf@t}**!9$G^Ts=m zk@^P@4r4g)e3>3|PBiic_P|!Y$gZCYdV7W8^;Zpk8oSGQ^^TmH^QpEF7ff&OZioDW zU8?OF;V1;!f|cm92cdN-L<# zoTpX#3ShgFl?t^OijJm4S5O1xuK1mx3@OlqJYbJ2?U~dFYLsd?`omSMH>3THj`M%0 z-YczcHMYfhURAws%DT=uz?P_R`F3ZleeBWwcIq_Z9Q5wm&FtM&XQ1?9ItbM_i)SRK1?rFEw>^=8NH8+0)xT0k9paRuB)4V1b9oQMVh-omyz6|bS( z!qChVXb^c%iB43*4qnB&@DQ@D5X2cFo>w4+)Ij|XX9npOn$^^`CB@TE&n7$%Q`?{{ zdMOohI`0G&X;YRym*Qv!XcQg%e%=1AS9!GH`tqNZr-lBGd5uZ?Hvk<|TNuCy;Rc*~ zrtaxI6RfL#;_y*sa}@rUY5 z>ucHL6MbZNtCY^bPj*ch)e_B2c*)o<+10-uemgE~lp(H$DW~(Ct>**1ctLEVF4&Uy zu(zJqSZ7HIERmq-g$~aObBQ zFdbVCB!c%aQZuXx5FiF7F$JVD-VBjg^9CdNpYuwgQI-N=y7ua>`$vpJ{kVkk?(?t& z)i#X;)$0Mp#}Tq?_8eKM0=E=zBI5<5WXqO@nG|m$hi@rDo;Fh6%IP^LCfLm1?itmz zm25wMwA5#DOOZQ30Ig0RP|DEto4<+*DVfmX<#bDtA>a>|hg^h%erWhc&$HDQssJdr z_VA50@nde?C9CL3JR+4Q>!h!nF>M-Nc&jHOQi&D`)msXWUKmnMDDOC5OfaYVeagz@ zwuVO}NC2jxJUX^q$W&h6sZXs-$&G zZoE}=$WnpBF-W<^9;4gN#*dTAV?UO?ziVoDS3R%A zYvqJ<#@y&8j&a#K{xxvz+hoJS;?D%n$@rGL0jV|=o|gRXd2JNmUQ;TtFIbfF= zglV511;HMl9u-5V&ENn>AC0-!JEW^3@zhT*@mKn1jH(Q~9jXo{H(Ph7G! z1MavCjdD>t*OjJC#y|Ak)@3W!ppT$WoIr3teeW#U?5BO+{iLrZs?Q$)p=p*rZZgq4 zZ_+p)jEc`$qTE@zBE$hm_82RbS=x3JYECR_v9qH(IXJ~~r6K~b-fu^`zRY*I*i8Jq zhSze1-!2)$NnbdMQJdc{`Qs^8Maeg1`@Z}efi(H|x#qWDAkNZ_gX%N@Gb6FIyLv{m zPxMa@#axA`w;K2|LaYRyN6gB2GKPyYL>}EYRRQfo;Pt*6c1@B6xO4#~s(X#)#eZKlk4$gzo;F zss=L~dnkvJ4AlsL6YYl|lk@pcA`LA5Tz9)b17IpuM zOfN zyMXV?X{3PXndaSPf#)=$G+e4r0=1!|tabEWq{7n5yAx8XOvR zwU;y+)$1oBV4kjR25?p9HU-%II_WXkUHTQDhBDY4?k}s6iLQy9QQ|%gnc>^|S?$OJ z>)s9kOZB^{Yv_QpQ9-;K__bPAjTTgGrg{4TnmyU%gSoCa=fMwn4B`P+Eeg?wT}nbc z!`en6ny?>J5N6=5^%GifeZQLxxNp@w9$}ITgRY%i*8rfjTDZN)zgE5YM_5Yi|d?f1%uhx67UMN=LGuot=AZVs2_7gg9;RJ zkH2N`!eq@G!hW`PQ=TT+=^*`$oCil)mk6^&t>dcjyLXyd6Nhg+o1Y;UY#Fna6U$MR zdTWZc59B?zDkQ}Li4Y`RZ5p*{YT;p(*pF$g($z8nOqyMlg%i(`toPZ_Po<~zit1Rt ztJrWJ!l#FMq&WXC0k_>F%(CbgzkM(khaG2IFu(9-q1>7(>kwU5U;R!8&@A4Tfx_?L zBr2UDPT4aurSFi!A@t^16%T&?#ce`Y7bS;Dp)OXeqU(%2gezAq3r|gzrehRL`vPSQ zM^r8}bpjq(WIbOleC9IQ_SmH|tJh}4AUC$wr=j9`@4bz=!;63ko@!T}$1ykAtsjmi z{kY*SX0jRI9Hw$VZ8nA)sD4FycdC&6?W_{9Wi*1q>shfH_6w@HoErF~Lq4tL&-yJ5 z26sRGflC8hu|J)9?JDk3Ung0D|7uRt{7!h-CTbxasHi!aUlS7hPFs<2@lDvAbXo%o zV>|B^zjM?uCHgxLSm%nyax{SFEdqnmyM}5BLpw)#HSrMUP}Wt{K%Vx;`z-#p?a(f%a#{Ono6f!L5w^7wML~54t=DEJRi#-(7Vd`7x3unOGesg!~s-?q4fz^w%vlo|bUW^~Le~bmf?~-zt5MNGrnWn#i zUF>##_v0d8H_Sz!Q=^8}uzgorQJve@QC@0T_UO7P!i7g*$U%aZkO}{#+;y=_$-Ut! z%DwWHeVIANZNN?ugGCYVM%zjohJrpbh@>qsJ^rcG)sC+&*|RhO;i)0Hs^E~q}F zYMgds=UaiJJ|fX}lWomK_o$`tjl+I`?ys7yI;3Tdv1dog?ogwupJ|f!yW0xps-L<0 zGb6nVCn_#Z*;iDj|Coek=M@#*i#NGtOc?9pYJIEhFMror%}*g+yH|AP&I=aD#*W9p zLfkL4{*9-Nr$5vD%La`PP<;RTpF-Nq;MKWI-5ScYo2l{>Md3G{8}2OxnS6#d zl%4w%`7JI|=!I~Pfuh;5(CyD>ka?;Vw@Cjd# z^uzCpk&+doT#<6ksUP082P^w21k{O|WD5-^KF*SToTHu+@MoT%(UR5T3t`}Q#5ENq_E!5ekLil$N z6(}+@C=UQ)?LjN8Pj|LAN5gk2H%FOwE4N2&cDXZr#JM49cSmYI->;Y=b>Hob^kwoG zc+O>(U=W7a>-o+3H4!I`iEX=3e7d8ST)RlU-=g(<+&KJnYmgrpcJk_tGsD79KD~dh zxrLrzdT&r5d4WUPlNUN)g@gjFe)?$8ZqBzbVSslYY@NSOhqs1twh6?B{T}*zO7>-q zn{|Zxub2(EVkzW{OIII_Xk7>Si)`}I9W2oGm_GqNeUeK&5(DHj7a#Vx-#M#%royTm z{Nv2a5}j>ZVq@vqVt_e4&mE%ibg>f7e-7<9qTRJIDMvbqZT$#&WR0hpYPTy4lPFo1 zFuuA$USQ4Rg(j*v?WHz@E?KY-E9ig)3reD6^z-83Y!o%K?_Tjx`p6I^lJ z&jSY5=Rr+?=c1Z+GRx_UH-!tmGf!Ap^@6MxKT5qBVN)oi&n=MW=p-91sLzDPWbIvU zD{u(X>Cpvorv02}@K#8sXYgjFuBYu9d+iX+P?1uH<<8yhaPN6X`(k40-X#Bg_J;J3 zz%HeJyD6n=?(`8yOuKDj+{Z5g9Tl{O%JRt;oY6>@y^fkDm|K=-Z0E!69e*3GYqCn4 zM-E6|_WM2<^K{&oLQ`9hwWB%OJ`Rp=hQHh&CovU-Evz@tZQc7^D7vW$A*k zvh$yZKzKH4Ym>9elxf9ZjO6Jf&7|BWdk*r(BcIcoXi8x?2{(5ek?-J+!(w$COAa=rWr)tC{>84{N z8mZGrJ17$8#rnpUUBJgXHN%WCIX zLz|u>{96P4g!`qR*m7W7c?DN@PQU2olcps6y5_D~u1b>DS3ovIgS`*IhG1k&rr_bB zZynLyZ2(B!ON?Q5;R}`IVn}}PgvGG#UliUy7v5S)+RNHTtWSt;hQ0p;#6)7qhf_BL zGz;DxQ`J)I#kwb~sAM~6D%TUSY;3WgrgUCTMQ~A7%_qF!f%~f zZZ+fh>rOv#myLCealH6~ErRdyn^HcRN0HiJ={%avCjYT~^^lybQ?QRt{lA6=1s?K7v8lv$P8zxnkyjmH&kbJRl!))c@Q2d(AzVr+0_`i-{RTSPz zsi&0ac-kDB+6`jeP1izy_S99I$$>QUzG@Y||MlR^^i*P^Uo}>);la?QYB}FTzK73; zi>68gD2VZxm3s!M7)?+HKYxs>f0Umn!Jf=u<`<-rvI_IpOP!iP-FvSeJDt&HBjN!N zVqB@x`Pk}K#}+rr@%5aIGK@iho--W)9e6lG@4oM(l%JxlnA}yz`ty2!c7gw-EaNg$ zY+wETp1VympHa4_w%o>(t558L`r2~*xfV1>KKC!^WZs+AcBJ{3>U=L*LwHpDb|B$% zP2Z#Q+gyy9e~XQbanHn|<*4lsYJ)_+9j7<12Ho5^>g;c6 z@VlDVu}CP^04FHWXT5ypH@5+7(fWNZ$ z5ga&ak!pi?tBo-IG%7JujQ5A(?gdXDy%1Bc<_rMy-EdEq2xT!Z?@ zg7AV5fzShJA^vsxIfL}kf*G@GVyjzgtB6%G)-}rY^mI7b@WAQ4fzZ(VIaPYj@8LYt zv(85>xrKCH|0HsqPtDWU*{6f<_n-1N&;Tl{@xbwB{f&lsFT=Zr%k|TO3mGj%*0E?N zC*Mn<-&Yr)D4%SaTK}~|uQob&7desFQFqIpDg=$mf^4i~w~81!nkV>`1x1|E6g7M&q>VQ?VtI(hD9!4N;~z70a6{4 z#Mj}sg0gYV(B#^$Bq-%c_XES_;0;5{y_qjQ7;`tZayU1~Qt~rg z&lb&bh=}MBgf>5%ZjyTG@wPB0f33ofPgPSftU6Ha-}f9%Sp8r>aO>WOH#-W?pZthD4dtE~Xk!72vy2LCSbZZm_-0p~t9RXr{Z#GC!2opCJo1 zP;BN1fDKNt-)f0peZluSu-IUtD6uLgii`KzpctzlNTD`C_L&SyUWexH!R7{XDL%3m zv0gRE)0|~oX;!!#6XB}rD?$FcOQ?lf!Y)6db!+swG2WwXU)SyOJ)y!%eAYzBq*ANM z-~=i7kww5x>PC{ndr^sKfgFPZ8eg`B2JtTVc&fZeCqthKHZmX!NgUny@txq=6H3&p zm}v4Up>y-+tJ&&7#gqgd{;}g7P4&A@k0;k!?E5RVk{~NR>Gtt!>ZfPjDwp&Nr*|NM z*p1QNFRs$Dw#TlbbBlq@0g7Q@&9U>9El0n0>Yq$gToRA|@G~xsNzI8>oQhMquJ~T5 z8G8?|=Ydb&OZgaVpz{5R5$mtbv`5DB&nxU*H@`o{2e}!|6^6hXrj>zyEB9thPqz5c zsV=X!gvg?jP=KzeAG|t0N>7Zx$S`{-qIUF?Z{ClY?tpOC18M)W$FF*<0y1VwGyYx% zti7FM_%pJtf12u~lkCbk@s(gu{)_6FyT!0h(ZFx$Ug~s$VaO+0WurXM;$8ACbTijTU@O-t`rnnD-7VD^z+t^B4MSM$+!Qyr0#Gf)_ydf+InOm%)FA4?CNiIoD}b%QvN^Tp`Gd}>5-|OiuS)nt+Ut%RYo`Kwb~55--h`A2XuBw}bI`|geE!lD zRmSz@{s!z!`#DpLa<~pwwZFkx`?jTyD9aG~#p7>5dBwF`2%pXgiIeR*RNq>*njqr( zYscY>2Wdt0_58%!K-hAJC598LxGzNfFjt#aStR|yF&#DE;)GY#`?nG#vRS(w`CagX zzc@^t^_Qm^&S%Ke9}e^UVbP0~S~^&ea&mdz{ai<)_I(=W+xrhz)(-&18gXI!BHSvH zemsJ&cHhr4W-0%uC^mQMtPNBQbK9(%bjiJG9Wri9U}8F_D|%!toC0~bF`q{rzvrhM z>OTj`jn5YGX zkkk?MCTZMPKE~>1tz3j385nUEzdhQpi$ygsT3A@@$Dd%+^qn?v8RrhVszCB1s)$!w)Pqr4~f@6n0^vOP@+^((9tUeX@ zyHo4F@;|8`@TKP!5IU)6nr(g;t?OP6_Ny|crSEe^KGu&E4qsB(EPlRnICf*WV4d!} zxzgQ0OSOco+O8)f?g%Ykz?U$}z^1EnRC)uE`{CYS#>JmBD*d)8kMoVV_K!_|oV$u* zJ>^dy$E4UBK0AfTlHviyRVkTA+P&6u+MU$Jw~|@*4eQ)K)r?51f*MCY6vF=fw*B7y z2;q_d5JR6k0?Jp#IyN&L zzV5NN-C8K;qjlPLx}+4LbuyW&x?UsF`I~pEzI2xQw@3qI3372L>tX05SCH72Wo(ey zCPbBYvlHPEH;rCP$9Pz>Gloh@!xEpMpt%o#>e)|CuP9 z1nkSIdQs9LwlYU`+;}S>ISf+lgQLs?Nq5`%r{!= z@uCrZUeOzIH&lmj9HzV~lNVJyvH(FTN=o1V`!OwAj(oT)#!qKnmTJQEoIB@!h;(=0 znd}nx;bgl`hTT(*SMgC;gyWN{V^Vz1Ox5J@^6&D}c)J!3_!1CQh$zBTTKP0A!CgOK zW-5%Ahw|^%5b8J08mb+>d5k5S9X=q+7f(Mp%um03<-qOMzbSPTD*Q90_IGfiTYwZ{ z>E|8`!bu##cPe~sMqMR`x*M3fO4?YrYk9K^zr{uy>_*tJmfE}!{CKzaMdy<4&Si_h z!w+n=8AJ0<8)YH)v-Oqe` zOp01l>l72WJoNx(Ljcj?(NOz=DJ9C>V-cA{(Zj~#@kT90Eqs9!Vb3O8RCxcZQPN#(*|_*srMt&<6xphH7wdSLllkb? zKo--|vYTT)L9FnkT+0rp-hHWiUHs{u*cadX`WBejuQnxuB8$AlKW|6DADU}FSPEadh&NvAWiklvPcTr#9*1qWUCVZ2?>i_z6?#Ez{Gmcv z{k3TZn<_*Y_f~6-FT25`G6u3GPyEmHwu86krB_e*$J{{-bA+bO&yK*>>!V8jf?lF8 z@AfVOE)D`ipb5re^ecdihx?49guKhosyQuqtpYH4qrhvy^?+X+U>!52jQG9FeIvhk z_u2-nf7#?MzgBA+LA*Kh3U^BNVi~g+{PSXScF}=;pgM#vGROdT$pDEjJUU0KQUo@9 ze`)#7yLagFn`MpB^4h-Q0btJWVm&Yfv>Q<)%y!iSaLyM46IwVH-j~pwd{kAt_nPLT z4@=U46*kuU`YTeH@Qmi;N^AM~!n?8*1D2#ntt_ygzfwJTv_1D!8>{ zU9SB7qsBYqdDgS^nzk)e%8%c!g2_|DR`|PTS|dM%(3aFe-aeCaN`uyBJ33fLLliwV zgsuyb`;uFj0BFBvAA+j=o6)!E6U(!U_n~g~vNH>7sJzemxzZ42N8L>O2*{ z47n!P$v+UV;$YlxduZAA7j0@ZR3n9C#XpM}q8=}AKiXTzhlQ&Gob0&9 zmLo*}Oz1(Jo0e!p7D<~sd$MVJnbe5^`&u!R?E}>`?*p;jYKh~iV7IF)pu@lapfLwH zCSSZiKsqOAHmZ$n8^~l2TQNlB6`M|iiszqpE`uRWwpE{LKk7GJWE^(gk%m{nk0^-G zm5JXNj?cS)9v^Jr1c>6Vc%BagU2hu8fG^2i*q2W{QBbslWD-Mt;|MICUeX~d*hkQ+ z1Ij!F(RwSej#heKhk9SDysvfM*J0k*TJLMX`zm^0OPsHw`3t9{1#^*yUFLnQ@V=IN zUrU`Y>a;at454#u`%{_m>A{E2zs~kOJL9tx$g{WPrgM~}AGD3*9_PGMzA&^OT>~ZC zed|`p^&46DYkhUUc1YdldgM3t<+S=N6naip)KrD4Iiucsr@vSzGqrV=!#j+Ha?P@y zDE!^2%~-a~KU~}LRG%lVki6CEblYczC-a@U!sV@p@MdgrX;{Jf*=VJZeN@k~t=mzj zc+Ya$mCN_*QED7TJYQ?V_y#oM{G(ZU>71cwS1ii>dLZt3y4QID3XX?yt&tgB@ zY4>UPHKx5IX2qJ;%c@u}NWu<<8^$OR9qwA0SRCw{+I7~bt zkSS`cLhNghzuTR<@#TxT`RY|7tXD<1R>q`VM;YdUp<$XROdpS97z4vpV;D71u4jjq zze0woWRiy)!rx0PSDU&_tTJf)oV>L%;cFdai-y&;)~E7N|kLasyk%UtD@c?9Mx zMadq{i{s+zbw|?HWj+Nx^5#D*%I_we`^PgMu8G~(} zv@3_M`HW$?l#Md;r9Z3dlEU!mVZ)~|d>X@V;#<#mFRLFW-`!?C%C492pF0=aFl>1n zu)LS?!S^c3{pYc4c>R!Z%&#A^Lq4bgCNA8)}pP-B}1kUs39GQ0ZA2^Hi@T!B? z@+R4vp;hocdK*r9AtzjTwf-eaZxrqf3`Zw7n@Q)|w6GfE%G`%`VsySLZ|);sfD@M@ln$Wzn9~Gqwrr8Mgr)qIBz~E{yiN3R|x+SUWM>pC!M{W zbQXwu%A0#i|8ve4=}G@}Zai4({U|@W6CO)DkKj0@b~Zy zl#@N=KA4$vax&k^$y_HV=Q$;J!(Zc2s^1CME&XM$=A#gvd$^O0;oo9Qpql_PIEWTh zF8r@_-upQ9zD&5&FwN0M8t}~#EZ-{qt765oU|uECjWfCl<7DS13}M5;kvmE2fbPDz z9_Ty)zzO)Q2YDv(FzT~F>%Gz*5Xs$S3y9|76q@cr64!xZb<&z9jo%@suTv5vn=^g| zd!FCG$xcwnk%zC$OP&0p`4l%u7rw&cWGf|aeoi*Vjf`Gz&e7)>zzrj}U=s3x@vIvv+$RYmdlEi1+=%E2n|gl=B@(fI zX!%13l5EsyT7hO1stmFAW_}|?%kuV1{1V{dB=(8F;Me(Kb<~^T@K4sL_Au6VZH8On z>e!YoRN5w!$2!cBU=1{)ZLyjYTIgeN^z4a~7G0I)R$lEXTZGe}LLaxp2jOF_4 zo!9~`4+VXp57R2oDLb-U7%5xUfjNJZpIa7DS{^Z=1LsHcjCPhOZ+XbTxn(5u7Tum> zbTHj@X$!)2kciYk>u>%HE#LeJ!yEuDA7p{;ZH0R@tg*7m%u42>SZCPr5-4z6F)M7v ztr*o`Reb$U3GAR%Ccd%3(>G7x$UoV_$(hBd&NOlVPMI>6QGW>|Pmn%Xx5cc4n}$Dy zM7Ru7p}0l77^fGHJx-e`b{XjoK_^z;!O0a@ym%8L_mk)`0ZE7R_d=tE_6bU9)T1r`AZTT(s@^R+XUD7;4qLA_~;5DIeIfXtd#o&a=$GN7H>68@c18&%^A{9CVqu7Q#oYp?gcHZ1N$FArNs>e zGF9ycw$GZlG4~MUUj}ljLnNm%AYmkN47!r9<;3wU)zYl#mSQYmMdDj7A9QVM)UT9t zFUkRT>^jhm5;%fqD-=Z~OHYQg-o4FAItNZNZ7ZXDBHF@q8%CvAKNT=0VeJ?q38A>O z0Mf2jzycLZ3nQRaErYoYINqw5Pql_oKBqI<<7rYg^htMx-NqNu`smoH7~2DUCXRGr zM=G6+>2FVC42+=NDY&KJyOwiJ{NNP)LlABrUf%=R_Fniusr-WZdbDG-|rDEli#j zsfX576?8YXFzcI^l!?;c$4Vi43pOaynj>2mC(+0O8LWq?v=#>~&e?+)oxB1$Vs0a> zRUq#{srL@ZHBUu4eTcGOU(<9(Y0@}NW1hUnX)~{b(i+Epm^Y$lw~cxDnyh@pvA%+` zI9ZA!8>2!ZM!e>f>%f(Ap5NVB3R}%(2^ticyHcn)3;FUu`3XC7fW<4_dnG;>r=wHr zk^gW@Y2#U#t%i8PK9}zzY-rX)S|c6u+k{@(B5>t#tGa&iD!oh9SyGLmbpEoma*wk8 zOd$_xo}J>1LKZQ_-rb+$O6g9O{7Sj^QiS@j+`Adi%0)kvy0im`ztEZAV0-_nXigC4 zd-|qLVrlk0E11n;27S|7(e)F$b``EBboDa|L#}3Y-HI{1!pXHGqg9rAt55HnKAF{5 z%+vd(;V5C=#|&Y8;Buru3yw%u1zGuOK=I@u-@zTakI1=B> z_l3S)rebZ^D9B;7`7?K79UAse?Mag;NM|gYsSRo*1<`GOY5htJcO;*MW9^sfBGSsx zUaFOTOJ}7nMEW(8{FI$2xYOM16qKWCMPH!%D_kDdfh~n#$n$v;h^LU$@GYZLFeXY} zL$w#fqMa4Yb8xJ|p7K?>x8{1)N~vepu8eT_Cvp_ig1I(w6rqpYf>KgKsb(1^p;R%; zCjQb4bL+Qb9SZd}oqCqiuFy?|w+lNlIa~6XEXX^8sr}rF>C_Xi7VBnh<{0dUhhdJk zMZRuXy;cjP`8sq@q`MlF-ug+Ss-i0kftS7(Y*F(8?rM%N7XHH{2 z-ClF5|RmLE{_-OZH$x{#T2qP`Au`` zpIeG%Io(G2b&MtXZWWYQ3o?4l(Y17SE~S1Y&HEEx9+0P6P^$?I83y04l=~2A|>qGfB$WJ!4&$ebL}=Jbx?2S?mf6yq0Du((E54B zy9aj~lUhHydhFo}wfqN#Wg^3gZzjg~vS`EiMDq6EbRhv&v)DUl%^9$*D*I_<%vS=P&wKO z^=2ScQva4a;oXpSxjN1lgT)5y--j%r9rFZaMiots@`JqG&Bu%XMLr7VSzM!C+;3Z^ zyWw8@*>E7jrFB{}`EDfn+#a!qDomV0SB>rIR#7g$^5&8Nym^cRx3?g5lgfyw>#xet zMeH17cS7MjxLO$p{W?KH04UXXf(lw-0@ zoom_~%Y7`)HrjC>YE?9w&0wWk%#k>oL5z79`9dGZiyLI`W`7XK0Z4T-#Z0)SG><(iv0ERU09zoE3h9)_D|$K*)D;7vQG*we76An?_L>Z z2lmglUF7bQ?Hbr8`?@grPWaND?oy@@Cr7t;`R9Lh+ij=3f|vTtcLl%yBe&|`l_&r0 z(4Zf>u}}U-*b5o3Uk*v#$Q4ZLMoPb10&ySyzvcy}bUyz_{jdJR0slvD)qgF>JuWhJ z8P>`FQ#ePuGDQ2#4}x2Y1j->r^J&s7SLjY}Q%SHVOIT&5z|!ebfmIXi*%DTrEwFUX zQDC(Md$EMo_7Yf}wdw+^C)i(0SbeU*;;3T?tdU^vl(5D^fu$3^0vk%Ok4xClB7wzO zc35D;3HEsj8(t!?VTeWqHj-f9l(3PN0vjRAii9moutfASrc(_QVo4@ z%y-F0MK7#lPxA^N_8%pzb-2JkAb}T>86)zG%*7R zoA6ahn?)hE+2xO!TfoR`Y<~OFfkt{=Zmp>NQ`rR*nuI~ zc_nOBk0?o%58GG5R<9D+Y9IFS61HZZz}EP%N0hK5ju6-pKJ2%?TSKs-thYZ1n zN-ds0F$5d-VgEJ+oAqILAA)W2VV4cT{=$dt8iL))hg~rQoAY7k46P#{wlD;He4W4M z%YB#)0gRt#JnU=PPXzk?)4tyDM4;b4AHewg{a*qYf4_e{fbsYH_W~Gy@BTD^@%Qd8 z0vLbq{x*Q|_wFA87=P~$F+UCb{Wl!I`1|gtLGAL1KuwGu)GmYA1|N3UA=t4#?4d)j ztv>8-L$GZ=?9w6F`GL~-`|th%jKA-$W`3&q+tiu>#^0va24eGb@^t}>pO2nh=g0W@ z!#M$rpEo?>YrD?`+U}oyb^T0$JH8yi_*>}v0gS&LejUL0IqG)-jK8&Im>=WsBjW=Y zf7?AYfbsW{B>{}T?cP`C$M}2c9|IVF@A+4qpKkv8`96U0*UzIqT0a}0^)o&iJR8Wt zp8^;^4PFUg{I&2#0OO~@2LX(~em)Ce{3ZP|fbrA%p8<@&=Kd4F_-VZ*Km$K@4r6|d zpQ7V@9Gm+3vw?j2nH}!-ill@L^XE!n)_D z=x;;O-8XvDIrG!a%6;>$lZ%7(6J^NLi>@yA{GgtxXOnMUV19U3(VYkO1^l1u!FwHj zJPE&`=*|Q86Z1e<-|mv4T(2O#_FVG;%vsIjZK+BSZn-kY$M9QlPV^_*WX=iUU zV=VJGt1_;-=Y_5;%JU&PQ--sha5RU~#acMPbwz3CyO4d2&(P$Kwb*0K%Cu>{h0dk5 z+O)@5Fy9cnIL&4iq}eS{=zNY(({im8|J@Xx{gR;RI~_Vsq8?UaK? zRosXMRCd1HfQJYyU-I!Y@W}sBc=Q)Ei)}%14LlGsVotO`;@8Vi%?s#v} zJr5xFcxbiab)pVyXI_|pSYJ^cgq=J*fMI??^jT0UtA*oI3*`EPl%Ay+%%Qg0+e((j zUl8-kS!#%pW{)Cud1huoLQW8S=*PG8&S=801o^Y5sov&oWj#G#FkeNE5%<=Ko-N8X z3%M7W26_JhKp)nff35M)Md$pCUCJ|-ZOddB0lCy*%Du>?9;V^o9teqjtS#;unbb$$ zT=eUP&SU$rUZt0nd+X<;7P;%^^O%2KU3J=UFUr(@_&W}KK91t9%{Fp>=(O#;eWB7M z*7{Xwr?r3cIYqAIKQF8SH7IjgWq+B6iCT zWE3wG>wBc}P@#}!sN54=z~n0Y6H0c_yol^5eqRjg10Trx_yVyGC;N@% zoXgDF3t4xnpXEFM&dnL5U9olheG6GH?{{>H=?^PVto_Qp&w6MzLm1yOMs#t{zJ`RA zI_$6L_W*pS7Q6r>kdv;GR=?nva<|n!!64oI1X=a5PZu}IyhS=$SobO=+{OG-}?%i7^fm6p{Y#bQj5--o43!z zFKdx}Q;J#f+bMo+@Ap)(%dp0fRx;f-Tt6V&aLUHEsBgV2QKu}a)L9jjk||=e-5AAM zkb72Zy8)@ok~hyXXtgoH6qC-rhajo`!IZiM6+CaAxog4|oYIV)7iU$8`#zWO}tYgon>D0QR}1g$e_Ag%OaEwqm&>t8d3aJrSJ|OpTsT`C)S6`sq zfq>AP9WYM|!B%fHp`DgbX;B z;}`xR8cLm7?EE(zcP?2BE0`alze4sazLqR<88YvUM$AZuCSWR?w8Nk=B-V%rgGs#ff zwQAR|)^1b?znb5#fqpfk)5vA24Xs_+CKtY;-54o+gRn7lyEYCo7W!rAw=!>DL>f3+ zOd*^8&16HAXIk)SaxhaR-xaxNBujlo^PfWYGg>p8auqK@+A?9sx6DVJ@-f=Q18La^ ztVmhz2GV`Eq}xDWq%kSKRLn=3--+_|w|u%rnY zf8o_}F;Msa@Zf8h|E|@44LCYiUnSXxO)WU~S1{L-sR{0_3|2*b#VXz6T?!+MTw_Qx zFyZX#mv}A7Ynr3y_Y=mO0as4$v(~b{!~pB7T+Og`aniQjB={`W4eztoFw$KmnBu{T zLR&QrmtiYx=bb2Fuk!7am#uR5-`BEI4av4&a;^5cq}~^V>1xOXWCXWXqHn$|JFOth9f**D5K87okm)_Yh1( z-c0Gs@l>?URg#~(8rNceV8uC|4k3#>6^hNs(iP1Mg}v%kQtyCtTrEJceKkALIdh`7 z>oEy8OAw5$I<8xKTrai%gH}nq3ywCXxEo7o=}JvM-j1n}y_#lWgMl!4`x`YYZsH9& zb+ioQSVQBAPTJO^#TlGdks6Ml*iX{XuGaubTCFHU_A6sh$|{Vf`!;3>NrtT4chM<( z{8qryg^qb6hBeyt9Yw6II@t!=Q&_0z@M3P)=^lc1_QdOC3& zIoq0RU?z4;;ok<0Zn{VR_U_`VXz#79#`*GH(}Xtfr&RV?6ceUIG2P(BL_XJI4v2m( zUs=COjz46lROn)3`CFN=DvNb%RZ#u9vj2(bcaosBu;Pa8{#fx&u=Pme42`dhtP6|1 zZIqIjpm~*$l9-s`Cs78}M18ahYJIV#Xpc~Aae|Oe8%mu4k|)kyC2bJp?M7%8I$9qH zdxG6r(geQC6#AUp!Z!T`m&C@fLtid9l)Fxajt(C62xtR-?$uB1%}cOC4yRqmY>DUf5dv=4G4!h3{F;I23n z;kl5M>c61de_Y#mjHoJC@gg)^=R{Gl=7tpIf@)2IqFiWDCIy9>Xm=g+B4$rAUX9|- ziTB@7S|nS+?`-LAqqcX~B4IZeTVPr%QLpV_T3ea}nP*+QCPa%rtdhFJrF^@!T{&!R zj|kNE+Oa`V&Yzomy6pa-?6CelT~ zG_sKS`|vg;+;vIP6Y;N0GRM!KR>?JmBJ3briaA;H<6jzrpAx{oIs`vebJsnoysvwB zm!D43cdE&(=mU-%t!*pj zZaJZ8I8=@<-6|nZdnBPXh0-F&M)?$`)g3b7{~TPoVy-Dx>j8wQ;gCr*waTSG&85UwpeR$<6y1A_VY>N`&LF4 z`{-t$v@*f5c>hVQU;8Tw@)WjT59|``r;Q4QNW!)8p!?%m*A0r~F^l(DIKyo}6Ri<#cM)_JXvjq8op)AmSf4aqq+^8z}$FS zO<6|zrj?!z;+Dy@HbF?CN&cBAqYZK;WatgdR0a(iH4#Fb;(gn6PVr{J;H+fnr{oXE z`()7lX|3xfT2Pyww%44wJ-hDQ?as+|SuNKM=GrHr%r*k+)omBK)bYr__80u$UDKl# zq+DtPw#}~;*9N2SkD+nVGHjY!TW{BH`Qb(?1d;HP`} zIn$Z{hXm%L)$ZDzv^jAK>*E_)uW|QVM_LYKiY-=U~WR;+S2dfpd03+$WECvRl=!NIMmB&2NOf`vfv# zwN4RSq2A+s3Y6|Y(J(}J;+L$Y8C#ll4#v@>aL1x+gR?T*$jil97J2?C^Fy8tV0f|sfP1GztEV$ff3$a5T6aoA8=dqtX>DyO z{K_|{eQdSd1C?Ws^;NHVp0nO7dGfP}zgCQ=Qu^$kb{=yiP~?o`;vFzeu7&b3amh8i zr=KU*kZnlvhl6XH&bpt4oUx7Ua_}Ifs{?nqD0JToyGO*3aH-6Q%Gg_{UBm|LtZ4gb zejj2dDAES)ctzS}*b(~|rP3f|gef$)Q#zE!L3LX@C`AS9^=|IetH^OC3l0>FU zmKXa$#w1r>tD&@lK_Rj9><%6rH)I^;&RRNy4;(%gjA?|kw%iymtu1#BGYZMrT3TbG z?nOIOlB#THtvJF;d+c((xWSng&9_jJoVbNa0AwK2^*H~dcw(@FiN z;Ud;se;zwX{S|9h$DRmsWp|@NJa_rLW-v=E#sr4!`sE0&Rv*?>V$a%H0QHOK!&;_t)=|ddlr5&gZy(&hLoP7b4>$dHVe@f#ZAtV4N85$>*uNHg z^X@)rVoi`YUB>3|Uo-dIl>#Hz$oD(GI@I&2f0h9gHuPj&jTBs!Xw$Gp-a+Q9pMb@4tF92c!PMg0 znrlJ0?cay$#oXhM*p7C_{FEux+V9Hl?P)^kY_hb!K{NCBXjj}_%bu`CuJ>3-u`7K0 z@*||%+g+wOXVAQdQO|1%?z6vd{UffF&WmK%Y;AA*iojma4r}BYK%x-@>OyLU*0jqn^Sa-e~fw`bT?hf zx~E^tT2hqPO)R(bCLG1z3r!PexsXR!##R6Lxz`%+{>QD1=a;g$H=f9|1!9e|a}(>H zcB$A)pqjAK_32f*rE~#TEVGNz{?_|67sYyU`&G{x_w1vz$W1cFCc5X;ASw7>hnsb$ zE@iG{oE;P7NyE=u-SKzdL49Nsj>4DvbP4`laCHVQ4d@IUxW?5n*yO>7JJWRKuxFaC zL>la+xc0GvV`h)CwR@P{G!9-9Oye5no@L^&bd8j&M|0vc*D-0o`dV=!ltUdC_r`E} zZ%o+fw=xeg(T^3n^>&T4b1+)>+F<{6j5Hs54!V{#Aq$>NcPT>Og6mkFo?CDY_Om^4 z{K@<#hcd~*Lf}Ln|YmkA{P4%^_rNL z`4OD7nAn?;Y1eq|ZsQvN8Mz0LH}0X7&d6PlW8oF`?iso3eP`sZALfkQuhzKsUavHsI}(JA_z`po+92Ub1G86f>}5sRxnGPaT$7t>?1xK;g1ZtLzdF9 z?0Psr?`JnM*FyLWOj-yZ?!MUHVEVT@_PYILuW{Clg#BuH2N=PxMflS|>+e%h(s)e|IhIK- zMZTHOex_%HRQ0YdZ(TbT66$2(kH)T~_Q80oy1A0S%8W_E^AiKmYAcngBuI}7iCGny zvLqbS67Yw!$!*v?qE@I!&gI!VX6aOE);ELi#9TFoisn=*F}{Bxa+$G;=7IP{S{93` zenXjX(cIJVGgp^)?mdQ7T&NU66(XG^>{;Q)C*31d*G&yhg^C3)KmFwe432ud8Rn;7 z&}f{X_5ZlThk%J+vHwf!XEsRNxGt}M+#3UK4vYa$d+?i>YnPhZ>GNx(T|UzD2|2n{ zR;VIW8eL0wE_il3j<*!DAJf*5h1q&YO&AlQd94KrWB&hR?oGg?D6YWqs_JvDot~cM zYL1-)mO%w)mt9y?!g7e`Dm&X?a52Iv7>sUq0AV?lg++~tCW@NG+o*{LYP@fQ@k$W9 z??mxF1TI$Cechir*mFJX#ru+P)@_4-M z^tS;2MmkTssmx!mic3&%Zm!VHQtzKpJgIX_zwiGQz`muTO=P*+-*4vMpz!s``M^(Iv%~Zv!xSOhY757mUui_r6 z;#J&BMZAjgIk4YyH3#m5IdF@d0}Fy!6qx56DQhSj4^81f{A-3fj^lIWG2%i`q@tcS z(CcdM)-X21{gkICJXR0sLxS`NM0(m|Vn>ot`f8#piedLu~+GGqPihsZ>#_z1JM~^V*SFkYcL0V{gkdChp!Tm$D zF#91ozMyemO=`Y=7T6y@Nc-a{3eH0nyaS$3;J#F7evtDGLf-Sx|HMOlApK^D-S<>a z?7oHcg8|QQP(<+&56QG7J(=$z<$Q#~Yey}w^iBI9;I)US@@GTHLb=w;`)y?Z{WiG9 z*ajTl!_;qknDboXd_4wfk5GU1BRs7jBb1i+V~GcQSf1MmdE*HHdnJcmZ}>p*)f;;N z?yQH>2|WX_GGr_9r#{SezD10SN8tVsHp zH!PGf10?K6Vc!0M&ei@%+x0E9D)5nW`f}xupa$03HmG+ib+xX-SdvewxrX(f`9I-@ zCjcivU2WSWe%K25VQYXNl#jpjmJ7ZMeRP^6Y;H77t&rMAHv-sOn~SM)HS~y4NN=SZ zDK&4MiYayI2_wIO7@E*$SF-oZmJ2=w%a@l_WeRC%i(~P$@kv-AEJH%h)D`p zY^4)f4QYzTN#T09-)fPK(I~m~KDsqGXk#QoZrz%T6jQw>)DpoQc#nRQX^7rLFUlE( z%b^!HiLU@!Fj`ElHHY!*x!O=DgU@))5b-S!L=pfM%3KMfSlv*s&as*dpeSo|j7~t@ zLhCuz?5u;42TsZ8bk_2_PA7!=&Q^^j{H9vm!*iGlzf&R&SN6nXppIiEL!V4Uy?O0y z+oCrgclNtSS($)Sm?WzUE{2N-69@ZN-pqBe9LuCAFMyGGoG};smlZ}jNw|3x{ zxbQ`}nSzqF&c zNnhUTz{;TUApPy-f}Xj_LAx3Cwm%u&*1*0COP(ujuW+r1HL})pNVtO5ToKU!+bZ_x z+C^%Q-d46pZ~dS4=)Wu%a-5Q!4_Bw!pQlbx`}5X5wg8pVyMP7G09C$y@hR#?{8yf&ZY~*uw_$v1$+y+yfeok?_}ieiYO?Swf<8;h z>#BzBPgC?x_9#Dl5Gl`4H&LAR2Qp%ZN_md@uRTl4(~?!Bx#-*ylybl49$w7fO{(JJ zx%^ys=n9AYh@iaSYv$3fV5MD)YlO^AJpue=i`0!Mn6-2kLEQ+rBS&MKoohr-rk|&H zFZ@+KNefWUqxUryQ}+>3&kHimYAB^WCDRzBJt)(jmT7ib+A}ik^u|)!b9k0Ro`#t; zwyG<2qbBSWh^F(oK}gSL`8TD{z4#0AsehPWelPwMPf@o*t|bTO#vQ?EDZg^KZYM&X zV+oF6-A`}?uhW|G7cS5;1)N~sm(oZF(8KpO=o5)lN3GZ9g zz4K5ozg#z)X)~*}^;moJW>UvyFh(Y^w2TgQTb0lmDLTJH$s^Zmu2&x5rS%7X5$yvz zPL*CqD}2|%e5`LL8n;j65w5i>tBTGhO`J&+;b}SbFlmN-x8lxPot**gV(WOjeos=> zGjyF!(nkxuD0UHV*GR~Hy|lPP{U}dsh>E+S^8g`1dP#2zAz>Q{dW{a|?}8PA?=O=3 z36XL--1`Ih6OZ@lvFn+*D>^rD9axnvPCe6#59W4VN{WBn3UPk3C#SSL@~e9ElX!O{ z{?b~&R)GC*Y(so5l=N>qR|xykqO%oszolQRPjZ6qE;?Ze~u%QEfTVxas9%Ky1v-HSg#`9IUn{O`E#g<7Y* z&@QCmI-R?@J!g3wy3&pLD^R>~sMx@_k;&47*m*c_vxcT=QdazWq>y`2wUFiM$fOak4 z8?RYW*?(_A>))dyW-pHDO>&(KBQF3=-a+31tTPm5W;?6_$HNo$G+7L(c*0I0{YcWm z-r*yEjBO)HYyQ-?=o04^7|p`e59vr)HJtun#r$39Ka}G@zxfR)_a@+< zYtSzre|jCBu9i=*+6j)1N7~`31E`Yr=GWmm6s~Vlzxf0B!ts1&ybE`Aoc13RGDyki z-HR)Poi_5uI_C*4z^jhdPmhsAvusBL)-(Z1_zmw-l)L4^NtgrK83ohN!@E>H;733I z7A@3$NSEYq(j47yfY#=r6?Is)j-Z`+vH3=#r`4Nc>Ux5{=@LZjS?@qw>M8fS>s(8? zhv}!`tC&aX(ZXZ@=a!slbOsWN$r>Uq|uZgnQt?bMwDW$9p(^Hob_vx9f)vW=V?v! zI)Wnuydkv#PZVeUhGYled-$$r@coa7|16A1^Csff;W_<6lAYRS7W4N4W)*!m1HOFj zQy#$l%Su+} z?;Nh)i07_8&p!;F1#N^ffRT9-e7n8ZR2NjYwE{<`Gl_uW&NrG)+KuHaUe#SN(0c~pKZ5m2!I*ecxngnTM(7Ni6 z=nW>*+iYl^2d#_R;Z1Qf+GfJNuA6O9Xni;w-IUP7ZPlN^J=2XOBW*6E53t#$aCiXb zgK>y%8lbZ_7uu&M8PAh|cH1`Hx}nY9K{pN3?Hf!pc|$Zxw?2yBv}uS*GgZ*yWSF-Y zaAj4=PGe!WIB!ZfW6Xd?7AG?`olSyqwdzoMuoZ10gunmK6IAFA;J5yuThMRI=tWur z=2S1-U4VD2HtY#En+=!cCq;x?Q|z<^^EVCjWcrkLgCF~BBv%ZS{oI133bXYA^&f4ne7>Z9~aN( zg_XYnJP(=<^h#Wge{Z*pf1Wze{Fp>y#ndvq0yg^@UNxNGJ&pMNI4|`|`HV-l3-Y)e==>+Rzr(`4WnWC8E z*y0_)3u0YE!aJs5T|*eGG}*61>>Znfx?q+Mi9r3(G+c=|qOcM-tMS8`-x2={O~e(C zcX_w+`Ee22n!ucp6ambMCsH_`7=@^}py-3%8Ba{Z5pn9H+=dJHaO{U`yH)(n7m4aE zg$wkj^{-XGsr)yU=a={b@W&J^qra^GQ{`9q=FT#FgZ1NAs{FG2W>rSfVfRTG<=>{h z2fi);vZm9hkuXquAN%5=03X8KW5mGktapUZlxfIY!t+8W%2!x{V9An>%a(3IrzVNR zr3sy>=QJwg9%EZju0M?^8U5xFLPp;<^&^V&(_tPRCuQ_)(>~#8DNQ}^kk1~Q6cu{G zZPPyPljqaQ=c%6s_3o-`rur1+Y1MtktWTdx9KM+R1$_tb2mpjU*5aIC^$D1p2W+9@d<}D=ejo11MQ1xdk*X%>bEUFG4==H6EX(J~&I-9HAtI)osN?pa z48GM?R<_fUytK7a=-`#~V>+FmajWZeE1uksfPKk;J>kbhXhESS@sCh)0KEP<8?o_{ z)Lr(m{GCCB4IkgN-jZIJjO^^$=91JyHbzRTF=m?yL$xW{r%lQ?#h{hK_g1R;2-<{a z>AH|}Bkt})=d)I0dDRlY+X+LL6tjk4T{JbFisej=={ z^Ld)ulSctujOSe>x0ym(ajV(PWhAd)3>r)WZu2u6DZ$UB{Uplgzu>yRsDah#+-L@p z9@r6-jZ|kK?^?(E3|I3s;^}WQaaR!~T}QdbRG?V{A3iIE51qd4l5GOo`xBvY{C^aB zY3@wiWt?POdr05A-*%PS0X)I~?{+A8`Lval`lz4NlCO~iS4th})aR&ULh{nO8}0YL zpygVr zN@<6hKgU9kIB1FGkWy^iB`bwIr{wnq=fpbhQwnVsim6Yv=_L2H@;W7bQ_;yZD&OI0 z{7otJtEH3Nm*qCFZ=IlYf3}zD{%o(%{W)c&INNn+WyvunM&4DLKYtB0f4&MdfBqI| z{(LDlf68@xesQeQ?fC-M*R4{w=a>rpuJtQL9IyMO$A3*nflX7ws%M z!!6v!hn4AMW0~KsRB}aaA?_JZuJ^c6WYvcb) zUn`lj@{5SEBWf7X9;t2cW#I}MZSVkJK%l?EUG-Zl%l?7XpcnMi5dA-uReSUsE5(_4 z2c&hT_C+sJdX3&@suRHURHX7EWCltfG+~3o>9?=qvhN6aPtu5;&heVCmp>l)NWlAl zLZ32ZwVYkOTy0w< zY&Y2_az7LnkB0dN-fEjuQEyia)8bu#Z_592X>&YcO_Di`- zXX`Ns1D>;r$B0Y^x$w7{_b0bAfTtdkJ6rDLR>C3y?i#R4^rJ@}O%=(fRCsc5btMx_ z@cCkrA&_q_yhU9IPPLK4l430sNu43=Os8}DKixbDdfy|L@K!Q1hU`nz`@5!VH6Ag6 z&a`!_Gc^tP^=PQ*4Aw)^zt9+%#U8m#mcifkFgIg-Zie{YDrkMg3tsqYp6?34tJS)o zFtLo_=ENGqZ2-0wQ9=!`u(OL!P1R~%JlyDCI zRyf!82ggP@HqeP^O&V2A{+AWLRh_JhzZvLa%$_3t3ZW6iHt+DFQTP zDt(a~ZM-i-X2wEM3Ehd(7@jpi&%8;JRo%SiHz7TO=`l=yhP)I7-sWvDbO9fWjqvtR zf^k@yLi<47lOq$M#TW5+w-5nioPgmRg88bjcdOu90W*$ue;$lbIXo|RGq3F#{`O7z zwY(M2zGPW+40-w+k6f%~kn}g9bpLdiP>?Pn>` zXy!@Ao?^Z7dM93q@F~Y_0%0v2MU=ef@TBq;Dr4C?QRE7Ms-8j8S@gi1o086AME9v= zhQV~tyj;bTgYTL}JoD<{IcBt?w?4z;*4qa6b;jjeCg#OhbVBkuKah5bIso4o@7LoS zsLv4k0ked4$s-=Uqz^xXeWEnB7Q?qZ5`4>{$&zKzQ?S+_l_fKDe*)BN>Zd@jrQgDD zxt7c{s`Mq!_pp^_G4+s*PIu*l-F{Tsx^{`isvQH(O5bn zp1jEIee$_7U#D|At{0haegsxjbU10)dNh$RA~$bJhG&w-O{2)m;mFKP1imFI@atCI z%bQ?r3H-W=_d*2Lr#m!z?^4r?UddXb5r+8l9!SMJmqTu$dvQ7XannOVdNM3l8K!qq zz+I81hy^XZPqw`1{7|MQAoaIF>O4q|hVbcZS^g#4h8g>Voj68g$lRTBY+{L62{)e+ z;O5PZMe?ZL7dI>U&RMGjKiWhSD5s_olv4v2>Cw532^iJCkHp`K}G9!C1q&&-ngDm+^R0=oRdr+@GqO zkRDZGFC3ses&XC8Nfh-h1#aG-TcjbU-AHopdt?Ql&xYFdxZG9IgujONjpyyV3*dbT z^DkyQQZq$JS&@h3`k~O%aqh2O>BtCZ_+IFW;`@A=28)oKmS{%h;~IPP`c*Y^m(>z90?y`7aCWlJM{i`HD-dv-nr=zC&c+)Yeq%V6$ z9$h8$&;q|v&YNO4&Hr7`>rrl#Gl2Ungz;(qho1L^7soI0Y=Fiz;WH7Raz;!?f1uO3 z9Kt3dp6m$8hcYsh%OHeK2qVv}5;oQS!F4cFcsUeW0fiwH^p0pt-eEuh!{z#$Y8=7~ z`|^R}n_pd}d@1&U4d3U~mmuT$aQ~3S_aX049l%cTb0%H##_!4Xm`+W|BVBtSVcPSw zhx_Jxo;<*#uGKpRuBM&2&*R$eWU;%`o2RUuG1jhWtX{2tE={7U^d!%btMsKJ^dD;H*bUSCu(`^G! zw++Dlj7~D}s?mfo+DMYpS@)o_arkCc-*1u;j8}{EMHetdGKBg%7&96*S#;j_7)iSH zB<_UlQP`A4IsU`Gk8r+9`Jy;i+P4)_CL2I|HjjlOlgjMd6mF?nt>P(+gR=X;m}bZa zz_X9m^DvsFbGz-qx!pHq944OI<@wrCvaRaX!q3}eEk-8m5nkK+@#|-7nf`5c?lch> zjgKmAx}E0KeqgBm;1|-zKGu!jhBcS_ATIeEGMSM7i#J18m&-@zQuAAM;4C(e*{nYu zIE&3r+pr4e>))mW7lQKPe#?iTd>G0fqdVUcgEhkX^H&gIolb}TZLwRU{gy*L`vtn- znZ-C=vry^A##jx;=t>=>r%0IlhVQ@bxirRhE$=&3vi$e{L-}@E#Y(ZLvA={Zew>%D z(V&j}(Rxbz_B!HaW0qd6d^S0N2@uaQ6bz82g{A%ZlbrLreLug&7I$UVBL3jH6cn)gTkow?&XQ_b94Xfbt|o|f+p#&=L- z0&1U0RO*4eEt6?&MXnA8>rFd>_{WMoc+w+J{B(bzNJHuw(~#=u%3E~0(V0IGb@+Ha z2T3W=AlT^G zd%nmcIMQ#*qR7g4-SY)(p+$Q*E%o@JI@*q)wLb58XoGm@}bt(H=DC=GUClx}L;S7J>23k2 z%xQiy$ufW~yHXQL3g+DLRIBjme8Or)Uk{ajKzt_&J+qjmD(o?qO8nVQtKnOkNcow^ z%GJWRjKJ*4Dn*s<4{rf$<%yNn z5UVc3J||uI@95KrKYcjsY@R_{>x<5jn8&Cw_8fWxwLTGgk}y72m04+_oGrTe~}sp%3ZctYEdU6Y`yrFv<-f zB5i#%+2_AptT`7c{7&bd_n^+E3SnJpG2 zpjB5rO)zoxUOuJ;k`HJIb>f3E^sdx*b*F5s@ZCd_mg{nkKrHFhG48v&gp*P>P4V6k z=k84Im%G9o&0(sTS6iXpf*ezW)8bL!T~WS#py?=`4br4f2y%!~$2dOg6I&d4#t);8 zIulnDv>I8=C5W;nm>Zbi;QrU`ec!#ZaWv}tFd+@ykQz%dAZ>^4C z?WSIor$}m z;~);U#1rB&l$Il1VF5?*DDmvUP;!xWIvH)b)+E~|>A{n2oy@c-OIYb-WBmv+Q>T1= zGy@juFojfiA;(BUs>>x=&PlpW!shrnXe%ysMH1Go-#d~eNaZ=C@Pn0Qea|OF`kwu! z&!zd@`QvqQt}6ch6Z#+J%1T$PmhT*k$v~`C(>YG7wBJ(y z(B^BGv?;sZ_e@bZZzsTN;cdH0M>~eU(=Mkzgnr@>r=E@21MWw`{aNyU7~G#D?`z@y zOnE;9?tjYh=3Hkq-2aT<&vVA0)~C#qf&261eG2Y3$ozxhexuC42i%{_vF=(m`^R2b9?{ilwCqTd_G}Fkv5zv^k;G5q)ANK|+^3%5?}tN) zSK!TX)|L7|!<3=S&yHe#dKh5M@9Rk(Cusz0($awpMFUP`g!E?V;fn6#uM~Z4`%tDU zb*x^`%X{dZRD*d={yYTdIqF)(v>c0R86vbVkV`{;dOUtD%e245=1LnN7?m}`7Q-Xy z;W&qYu1RNsDx3hd%bgNCxCFURFHkA5@`y?#3-sxP%C>!3-<1K1>+!$=rq6sM#4 z**zK3cOq(Amf>t<8uxlBsX9CdMVvaC@AsLBfsNoA<>M4_w(HDu_G0B{`4iVDnFD_h zW1eO0&YsLAB4CNvnjsCKEwQem*_LghCZ`ZpY=eW;dxP==(7b#Dz zitxAS!u#=gTz|iP*4_-^U`#)UpQ%CHW{riN*n~Cq7upW@XoI>3Mr*4Q7INqh54Esx z4pS~&K^;NL&o*rF?F-d>IB|{i_276+NXe$~ryBY6qFmcbSMQ`X%75J#Kr>7O9(i_p z417CkL>$H?vcP%M>Bx`4`Ykrkq|==$zLq2jz`0{}psG{P zl9Fu}&c!p<2whaac`U>IHbb5y{?xJ1_g82rH-3S1uz?Ki&?sx4nqz)*n)%cA28a)7 zv=BA?W~~2XXzzG^DiQS;omo^w=lTVga?hR$V+=bvK$$ia@Xu%HS682I6q{Qqt;ae% zz&*!M^w5u#_ldxJDm;O{ioQdn-8#+8P%@H>*9dvD_`Z(-ejWPD1B;)N-wK{?doHzYN67> zs)zM(o7BNN)+|38DftYW$dq3t{MO&C5p-Gx+J%0VGPRy|bG)l{sgC}N8W~SdcEy?@paJ{^y!uK`o?~|G0nNiz)yBsUg-aBBQpUiysI}D@Ibvmmc7wWvAhwd0t zJ}xcZVj(M6{39cYwa>9wLiu2RcTLF$^LN;{`(WOkjBH9DIh~LJPJuc4V?FgPvcu+n z%QeF~ULTahcOS`h(v>Bqr+KolTUE5;y>K^PLt1e*@a7$` zey-!^XzlLz;Qk`GM;gxO^q)vC!2S9BzJuIB>oJa%3p16?b6YuOD00o>l%XhnINaaP z@8`J-;QkJIe+=ATBJYo6C41?i(8iz3{;dZp3|rEviD;K|n0P*s`*E6^{VtoGIuh2) zevq%gCTV$~(S8TtG!x#pXXgWE{ylu<-=MA@7{jhqD^G8O8fI%q;mv_Eu={CG;A}RM z^Z^ZdVT~fB0@OZ98%UDc56c;|P`X6U^h)49ibpl~K?=o=unPvFaZ z$s(xxa4u2lbYfJ%h#4u)@7Cg)VRM&!mr2j}OnSoS@MkZDuUK|0!)Ral1KiArLRHt8Rjoc4EkEf+$)D}+hY3)nr1{7a=*FyFR zVZ65{0j6kQdNj*fIDh#3>N;As^k~`A&eU^yO4UTr|4oP4LKyYC<}$eIMQ5AFIk~PQ zn9`G^r1NCqAHWBk(CJL#^3XO(-_7Aqi{L52aT$)lEJ-u`3@{4UbmW+=O6uGoP8$}) zAX)`=FU9$p$5o3Fa(|=?KNG?O(Nx3bGm0{d8TxgZ9%f60XIAb*9AGC*R(9GyF)B~{ z`3aiRHJHkuTj5z~!~|z+I>rgmxo;1wS6B5KSTEPQ;nszoF(E4L3{#p_9@iL4`wg&) zQoe>Klo{Qpo-rQi>KUfbjjgHPS zI!NhSdF-7!zjqGqSR8x$8|E-&%~&|~yUhCeGAo4ISRtIHza-dRu89Nf4UurD?EIxi z)S@&3F^-eawNmdcsCYLbD&7@(0ME#@TbT5R0?+6469-E0^o1Uup*QJLtcAil*k|(# zjI*iV1K5v#4P$kRz7FtHlXf;=S60X)mvi3qY?290r5GbJL`H|ix$s4tpLmL0N=tOy z!zZhA#`a470iH`d6C+z8-w{OkFUQq=IB-wLqJ|EvMS~(gBSu-s5@QtmAkcIJoJ&hM zm#Ro3{xqZlPUbXA6Zgn|Dc5E5r{TVQACE-R2EBBMR?qiC?x)%aa&d@U>x~6kZq2PE zHJ}uAK+rd>@H;zL3;u5DyW{$$?~a4fx>VA4q90$8d#JEa89^GHB)=c;#9%Z(@H*%) z9`n#qzO`Y$2mJ4FJ@M2GL+KtX>g=tPh3-Q;UB-}FRJ7s;CkHWL+3x&@DZ1^GlLeo$ zE8W%w)Yekyp$jFo)mcWXoe{D%?RWf;@8;Z}=P_L@ql;OjGo%it6aSbWunzq;I(|Cr9@_1+ z9iEHMO5EeL_G6BRSw|`Ej6nI#KY_VAT3_wDhiisEAL=oHx}7CFr~C7d=lgk<7UnNz zXAs=aHS{pU_pbK&OZX10@zd31d`}nOb`n$3&&lJ0*@8d#;s3GwCL{S=vQXJ*8IeUsY6ZKPFYX4512w#DAjYVgF7=!loiOf$e zXP6@P@AmZafL}!!%D7X+d1jFuZlL8^8%B(w#pZ(H)E85;jAnQix5)hU zC-L1&+ZYYyG{5I0X8pnn$$7>+?VUQrAR&HIa{*qB!fBmsnHe@X)dTQ7GBhsEd~@Fi z{0clx4N%?hkHcuNd466qiO2rz&_-(*fius&h#io}D4gQ`7@%&@*O$(199=dOBezZG zTKiPvatn>iEa11U#e!LrnquX-grb4BAh#Drqqw+A*#G2470HEy*6YC@Dx3T3@IEK} z()OMR_m^n`8(#)r2GmIFuk@wO9vLRT$M0FnFrK04We@h7PJv;ov7h{POZYDCgrXtL z(HFPp68;$iHtv&sOJH68x}^;Fc^XnX)35k9qyW|xWqTCvSfzam?L>wmjS1Hpm}}?5 zbtK9$VBadxciB&I1N)Q)`_~UnQTtc1&;C^mp~1&a;Q4bYbNw=(iW?q=fF# z`y7vbZ-l2^J>1iO&O9ToH`$Mfm}};P{G9F>eooh0sS&E-3_y=_C!lm2o{m8Nz(6f+ zi^p<*)||s5Fr%!lQmeEVsOXP4UQ$}79y>WKo@IIYR{5U`;($rNd>Qs=MdUEvIvJr1 zF_nw4;-$s!Rnp>D*m($zebffGLKA}oK4Kmn3cn$YQ&XOE+$Hr-JpO4eZOq({2) zx{ASBX0f8aqQawPXPE+LuNq#`&h1fn*;Z$qvN!Cab0QJ3FT^*aEznY8SJ>dpD#Ply zHWPNt&as#+;9MrPfXiFkCG~#GTiT^+QD2VHDqt?Pz}$M8U_5)6MeJGjb9x>7A-j%! zPM>8vDDK0;g7Y^px2_~u=0KRA=O8ss&mD_a?vCbZp)}gg=XFohYLohYL_JN+RO@G0 zWUY7>)c9ba#?j8HDdUb*`MSp>^>9#XkGeth^3y1{xzDFXsX?+-Ht*`?wRtlAM|-)| zBHh)-YKAr$dc)K5W6?H0Z#0<)a2qWXV*xu?+rr<#WUgVZaqaX+pnp0v5gGm%ZaG%W zzpc&EGbZ+OJD(o|dB4s9o=#}?&mq!JbOq0c?^lK8d*JzI@}4(2&&PcQKae0L{e<5` zzy1Ju+UC;$$Ga{@(=0UivYS$K;kVJT$v~qX_RvqrLK0`Qn>R35i+?xhk3w#}C3Tyu96k&7d&d-uIvzE)57eiStxrE+D z9E^hE9(d7g5+SIE{zz#=QYVCLhdO4GQf#!+nfFgvhI|F-D|LKY2h?s4YfqjDeS8e)1!FLoY*fP) z-WvwvQN4|M$DYRKcJ#tnE+d1`D?&c49Z2@U2+;WzWDsl8p4o|~mZg-@w(u>I;XBoY zU@z6zs(34BNome#OT~P{|KYX5FN#P0IJ8J!!x2h!Irr(h#9B*I>z^p>Xb)m?RCIBaUYU`2N||j`2oV1SN{eI z|F?P?Fx2M8u6){ZwD#0lOhva+`|7B*LQl+3;kU)$`V)rHuo^>ncD9ad4m$h{9Hk`= z*$eV-LAvyHoEgIeh4sd%teCo87jf^u$9=69-ufvky!|8Qzq6hdn{U(W{V1FL*6GaC zr{&!Nu8F7i)_fSlIvFiXlO&cR`>cwfTbvmcu)xuS?6rM8{tVVkt$4;m7 z)i{ykr~~*)v^ci^1~6gay&tp2rGv!oZ_kNg{$8GcDbGK>&+bo`W?(WF-aCVJHUCjx zc1-)*fI;2@yzn0MM*bRosqVjp-=2lM7~hDeJF9f`tp2KPQH(S^hJ+|a-n-)fc>g|N zA7>MutMj|7b=>v5(3g#f4{Wrm1l+wb?1c^;gP5U|(mDGFB)zE9>BRmC`5z({yFtGY z+PfP0^$&4$PKI5;|L`YV2M~SWHHBeiShs!MRzxN2SZ8xa+j@il;hC%}e}_IITweR6@UuI4JDlsVW^0nQb*DSXe_yoZ`|=BK z)REp?gVd_i4Zlqxqyp+_2>N9;%rVy+LrxnnvXrnp6s`82>n|MW;LL}3-(tykHo>y;z^+u}=)C1|(izb(F2Crx3a&lel=i|I0V z&?M{AZ#tdjIA*V7iR-y0)M$OQ9wYR2|4mV62iJ7;-xPJSZ>baY4rG0Ai25kEbHMe{ z0I9bSLA%_|w}2%19mgFR@_V>WBXz{2g5Pxb8FDkHoAKP@0JzqZ1ITKw`9aAU{Qbe? zO!@v-Tt0RH`L%q1D}Ucg?w}%0Scq)aPJWI(1fW<~z_)pNZj5LAzR{z)Xt7cn1hkU>>QTSAcysCqh~+V=;;TKa2u=5rmp2Y zhej4_=dp|qIqdfuz0kvro^i+scV@LUN#@YWV*PwpB=<{AfU|k56qA3~6>FtWf362G zSNl0@*MG^{jbE^mZ!i^i+;=Ww!oRbS?ccao#bW@xUUxAo)L+PPyE82jpv#L#XP|`@Sq{|;mocdCm%3`?&^8w#HxK{E1mojd@MG}BB9@7Y^w0$kb+PO5) z3&l5oSX(IrzJ!(hz&*ZJ$a#ftN}WvNzMgw#6jM)WK58U2qQ&%|_*|5%izzizFQE$fAFJUEri-mzU z*~dJX6b6RjIuouT(z|GH+hqIMQ{c%GPx^j#6T!T_i*AH>BK?V(=rMBJ@luP}gm+Ps zNljvabYm6MSJ+RY2MFeCqV=dT-p(#%LKmEu)0FP9&;@U2*i#wSMmfEqKg4;|>6{x% z0^j%t8GGF4R^=j1$OB68$nlgMxqWpu{q9;}?+LpBKLZzWskaE)D=k{a<)*1ianI)` zIWGg9{0jWxNoKvYx4^nSSu6Bqnph}^Ifr?W-+qQ`JCAa~>Hd(S^E5wY?b}jl9nj!m;%6^m@+4Mk&x&~_@Uq#X@cQ>dfMAnUCn>ydQQJ$JU!vD)oD+AGp5on2dww9#Cn&p^7w88dn)6*T>)<+ zQffA$N9ZrRuu9)Us*=uL{~P9J-3Cr!Gnad5H1Txdfb5E;W=qI2TbO0Ege|RwSb8p| z-e(V}eCwZaHPE^TS?5^M(4>&YamSS=+VOj|KHr%#Opn|2&#;b!c{A0z5a`pIP&G+} zVj(YlE01E7JU)W70tCZpzP@~XS< zD9gB{86nw4OHJ2mu}s+c_RF|KT5L1q=2{}A?d}Vy5%&=O+s=P~(4xWb>K5IMBqKJ# zv}7biTB3LjlNQ5_V9%t@$~Z;wNG#$-MbF>Ixitcm8tw%LaoP>r?nB!b;(G475?1eg z==(8ceZMEa=Qe^%uIACd7(WY0r;|XZW@nsWx<(7pjbUU6aR-RI6RA52@7%@~sv8}Y zYVQ7c=gt=I=Mr}zzcZGb?(!BxH$EN54aJAQ*&cXRmoKgQbS_~RHZ=#hR4o3Daz9S&FVP5i7OolTrz4r9=It%Xso z1OD73-g#`tD1@3qtrjSWr4yW1#{N6LNBDd2$Yr(uIlpF;XfxF1y6$-3^3K0nMNBi? znb}29zjY#o(bGF@s=L8ZX2(NG6H1zz-eE#XQ)9yz zlw=y^r8H>S*}rD<&if7X;i_mo?iY)0XSuRISP}HWHUF^>5Qgf5a9ybnu3=a@A!CaZ zvKH!W!#d|*3w0jWXAbRAnnN9>Ih5334ZwG~dJEBXK5MG^oenc;REw?|Zi9G+Htf%z zeSGd9FfPa9ox2QwlRC~F$Vcarq#ok;Ns4nvqaK+(9$U?@f1qvHz62@R!~M8N;BKjx z>CAI}1H0DMOq{n0TZ8i@?(TBl)eWvEel>I7#FfnLDzkVt1>XQ8;|_%JFmWAXJdzk- zL$UJa2U>)K_}UxHWt~{}Wj#W6*X6vgqu*SAD%7pwY8gy}6=gDm*N8FR4bK!c&XF~` z633zZCnno_ZIAG;-Q~QEvFeC_Gt|jsdo}m3%3{|YP;_1~xStug;t7EJzKT4Z1kO8q z0kk6A5)QGJXvlE)g1e}@AOAhHMT6gY{Qd;~y9R%gQ5XpwuP_Sy8AidxE39A(udssc zP?UcUo^wT24?Q5iXBO|D)_~TywMXb|qK~;SL2EbPxeiae;a=E1igT*Fd(`>rN-E}@ zk?BmWqYmGTtB9g?Gt>ZF@d@8Cl&tH49^ud1PeIKNLW=@DgLD_p47A7wdL@G1Rge-z z$&*C{z3h^5+W6R$Drw{E%Jd^1?NRy>*!l>;voK}7z7^h`$jg0&I|rFhN021LbJNS} zUEghVP4@sd=MHH|4<&_P-3j<*kor!*^Q*ZfVs;vyk214GW1*G^vs`>N|JZjO@8K6M z=D*+f{`c=fO?e#8fA$DHnxgYHQkt;7=E6LF0^WFRPK^sY+EXO0bv6H0w}wQ;Dzp=} zM=x#cOg)LK37%raGBflYXw_b|u3@dW&RcGd&;=ETZK#rZSjg){_AGD@%aTS#$zR{e zuzwIoH4@Yh5PDrBP`Uv$!EE?jMoRInGV3b+;oJp0?LEL-+|Ancds!7xaV*itEw@9@ z-^Kj8yP02qFMRKZ++H(AbUrV}t&_3XoWF6L*8jZl7|etG( zM+4~!i$r5wdj}{ZpvWxvvw@Ziw-{QaC8kB;ImEwB!*4qX>f0A~?h)v9lMJKZhv(nz zmFIYCL2iFtvYH|1)Y6Tq3H4wN@be8R1icm~XpOO-}fG5&&7WUUovcDF?cvQ(g zJB8C5JyhwkeB8CGUnP9LeWCKJu$dK`6Gm#f@Cmj%-N?^#& z(w!kh*`OGPIA;^yEtahl_;wiVpfIOg=oif;Kz(XV?@VbX*VRuob1gVdAuzdj>!>G% z>)V1}9#5=|^h$ageZ+c!zPHRejGrVWLMX2na@7s%L@cE)CxUhtMDeY1WKFzseg<0t@D(ra+F>5BIK**f9d33+D6Ko7S5w1-&< z(kli+67)697UV8X%@8AsHty4b`gz&F6Z5!HP8rAL*rw!q$PnR|(FUj&0?bqRcR?AX zjh_|}tB{gUN=lw-x}f24$3hLeDg!-gCaNR`hPm|kUMTHI3;z@r${;;$8&9j_b|2F$ zl(u>LRdtv)m3!ZxhF;fgPd{U?K4~FOKWkK2n%o8b$dE<<#wo7z=?| z(Ts5?$yh?~f3!y5U2c!m4D?h&tf~wVJo4)zLMmy8zG>%FtU->K`znLJTFzc&--nB! zjVZZN;xdmMI?|(Ld8bx|&gOdN$Ay%KTNman`nEYf6yoi2 zf#(dHs5h%p4&%Nl+&WAd!aaE$#=Vxf(=cTW*R(ney=Uldp-+nCcF2?vrl7_n9g?AU za{D1}HxzPhdIk104bFwI?;c$zWV0R_SH=e)F?v@yR)(Tv*E0H-VGWdXco6SPjcY_a zu}J848maLfDdrEi+N@~RK$uJJcz2Zl^21EZ z??Udu*-bzZv4}Y=dL(IyM)Zy-)8J|hivqrlMmS9pibO;HpJBzo{(3rzMzhz!%B%{T zE%C745)B((biWK-HT<@_!?~H|5VHGV{b6%h1nQ53^^OSC9|?1cB@~8w{}j|~@fBYM z`$b&STP(5WuhnuR{AWddC=<>ef%REH`9OWl(y<=Rf))MD!z|lYD$n~_%fwLes!^wRJ9Kri> zF5`~K@gGH09C1p%lJt(Be>k8CJ^FNQi6-&u9%e#jfcT-!Opsk?79j)5=p93z1AHTK zvd6v|*XbN!ag7V$HH6JU73$+My&}5$l9Xno0sbouGC!BUrvq=E>3D*=T(+*{PvP|f zhTr@k&_^T1Ue>xh*_)f?zvG28Y87#(3;O9t*^)HsL56jyvtQgntJez|*Kot1^$FAX3r?+Gq-|;!t6SRd5?Hb$?(rigv*fqAGEo|7Y<95tWopCB{3N^wv z+=9}vQ0ju?;dyNE-09R=A?~9)3v=rn*o~evhwv3J6yOeqeJn!TmTqMOjk_Wj8`P+? zHDfhx18@duMd9N#;O8&^o8q%Ji16~1a6tS z?LWw)bGwyCt9fZA@}VpApy-Tbs&OqwHuNX)$!p8I0C}uE6#Z%GdL86xY;IBmrwFV~MsNM@Dl8 zo%_f(?ko2V{KtBQ{z(UU9{LA3KJtdtdw8B4CY2|rqgS@ZEyt8HAR~V)xX;gp!<~Z zpg+tB%6FIs`oN zmzsLTnVp}16}}|_*Eh>3^jxM4t3eLTrOulv`A)B*ga0zpU+@}?Dtan@GoXV-S<0fc z9p2^!RlEag1~3(1C!mpajbk&)&xc=Q^E$ROAFhIzkp33vb{=Qkl&}vBV1GQ)Z+)G^ zcBx-@GdS5sNzw^QQc30cGixur&T>vgJ}o$#p7lDL*ZKx<9*S*PH7VNS_?QOISZy~;lkP`p20O}aY>zIN& zEScR3{Q@mVPh&2XQCp>ay+zZxbULL2e<<}TJVy&hIWMLYTKH4x9kREw474sSW&s1}==E~jq0u=Q5P7Ersn zoh$y!O1LBWJ|{e5yw@vY9}Bn#)VtGm2X~lWL)_7L#^yG`uU*~_jMK*3fv4hOu4`hU zu8GS}`nd0vP$n$eb0HZW7VX(vwkKcK4_}tyYmXkdOFsj=%pGa#w9-eNUtOXDu%XQUAN<3z4gQBaxYV_#jG9EJtU*&VUO2wFPHwb&lE@v*DpmBX7zXMte z`$5tGUa55eQRjDD>&Fr_J75N@#uAcUv|5W>yU!)Q0CU}WK-@wJ;;mAG=zblSZuoH@ z#x{>C`yNH3q&FzNw<9Hu@CMpSQK_A#5gdEU}kqGI#M4=EGIU1%wV3o+%8Jx*((AZ$F~wF+!$p5HU#oS)an* zQYT~vtvE(*G6^fcp?N?cD?oh|l%oT625_GV_Rw}Hn@jee>*o6MTnHr*%#Fi02z^UG zzXM==wu?NA2GiStIa#|Zje?Eyz@kTRJ0pK%$C>)=c% zNzg~{k=6S02dHV#zTh)9cj4zO=T_$b@g9PX5Aw|aoMGLH7aJtoFg$1j;4`^K{rP`| zHZ+O;g<4$~R_RE-N@ta%LD{KlWQ9bPtWiVfHug!2ePt4a0{7ob!Invq=OqcJbrpA<(b@@AYB!%0bpQSIrUbqQYH zBXz@or%o*gaxm*Gt@N`@gdC!p?A%Gn?ewRKI8{2Ok8BXU(>_{AzMr7!o`O70JWo~H zh7~6A*+4_t+7M#v9w|NBJlT>}1I0sKJ#AKIWp z8w_ZJ32m^T4K}nP1Z@aI8zRt#D6}C4ZK#4a#Gwriv>^d)aG?!JXhSu$VF0vY;GU#= z;GPopo@4;GD~g;d&L#xxJp$MR0XD$b0loq7VK&+pz_$T@2;heSegxo00e%eNR{{Ju zz;^(C0^qv~67CondEDDZ1Kj!mZZ3d30N?^_9bg*(+XUD!J8c_a zhX8gMU`GIU6kx{yb``*m18fIiCjhn!u#*70dNk>-&PkY~fm1|YaT>tP2QX^`nAHFi z;OYR^0JtW=wE(USa6?m0^k5F9bg#%%LG^!z_I~W2w;T)Rs>*00agrPRROFx zz;bFxw=+`0sDagt>-nlvg?@^FF(QEB01SWw9KCG-924MJ0LKP6A%GJGI1zvo1voK) zQw4D1!%25ME9-Z-4Fe(foT_xhs?u0@oLF^M*81G~)Y@)6Zrx{XvM#jFu)3||tU1>H zR&{q(rk2T=sz}`<-}~gL!lqNzQ6&IjK%T$qcyl*3#L}_`JS{@`!&|3{Ge%WDCdxn4 zZ|P_%fFA=Y5$VLQP8G6fRX!@pKiE&{NR2EV1x$#z&^}Gr6socjQTCQyN`@N(xag|| zasQyxguFnN4U4i@^eGt{FH44Dr$Y91)M-LqqsoOuxs4U2?D_y+Na~N)pC)8gs+=v# ztuHHO)yYyex3VW>@@WoV6RM0Q$}9;=nE3#X$#q2tIp8$4Plz%GS>o?qJD?t;Eh(Ma zS$*>8WJ&Y5+G?Ih#dZ8{pFA2_k{?@}WSB?oNk{j|!(dMuQ|t1`PP@h%5^|-!dtA9c zyHehBLn<$(FFZ}jrmhQ#GY@J1zbc4lrYYMpY}ew`R2)3jE+MsVclXwm{xOmD3jY=P zpgcbRFc@RC*JcPt>$!nzmUZRt4q+UV2SX%?VI}@fI!&Ds`C5_WGr~{Wwy==J<+XA@ ze=AQ@ZLik^zane_PCiZ80<_b5t(`S!?b>*)U9Zzhaeu`(o+{_BpK8$j^mwh9x|e$h z!lycTx<@`0Q}>1P(&7oropqX8uiM5IQ$K)lZBI68?QWB5F;guc&kSgNZw6z+5gvgV z+xv^tL=0=}4b%qo$ThMaD^&x%-c%UX;@z$V`NKE1pjrS1~zTwx^$GhK~ zw}?sp_>qrccwJpxU0q#WU0q#`_*=llj$O$1`>C2fI5n^mS4#?k`XCD=D2;v@qm*ke zWwCgv zOQe(J#?*r`siIh#jtlghx=$`-Z!-uu=6_~m_{>6az9{4yvX5rK3LeN@aOG?;*RzN732v@MNIu4 z`MfBS*?fX`LT3>h`GXFPT3`MKwV3N4EVQ3*$~N7wTh{?1`i$iiD3Ohv0wuCJrvUsB z@3G8RCP@RZ$SKedi(E1NvGY9^fqN3M^c&PW6qa9LzG6NXMhI~oQjN`L6CW>RXO$&{ zt|IA*lj3q?@_C{+E=L0}`*uctJTAWVqaDn}4Zvoqf!aCQpS1jvXg44uu3Gd)uSmt?k|V=5%b()3Zs?`G7tbq3u56yY`?W-p;0Z8pQYHLtG9S z{2(3vVG1ws8*iy^wBR46!*8;{_iV_d8{UtJvn%-bnY{lcWG#y)3W*KZSo7DIny}At z_6B(`xl#Qt2Dz7pnt0GK-F`2LX`L}m%x;dWJqJVVxi+vTeG8yC)Ke~#dIiJ2R%i_b~&jn4) z)@ASo1U{F-_pTsyE1>QW+-s1vuHfgCCU!PlEcKKdv`=Cx^%@v*KCMaEOM`nbiLFx! z)g`rfw`P{TBeO(ar9s&J`(TgnL$F)UENBJ3D?A3ik^;D(GOy97pI2k_`h=<|5GgT=eDh5gr07_6v;$(k{Y zQUrG55F-9@HJ+%DJE4krA+Zm75*<+@FOVCxZn+i2nk9JU68Wb@Gn`0jbS1y*N!iC) zM%l$O8KE&sc7@8mfD8c*&3wF#mVw?l;Y;IKB|DiAG<&as%y#;@z$3A-gM9m^CgE2c z%~1atGv^Nz`3{&q>p>`eCk7`vy=l%Qdpodob6b-_S?=~#Ka+jc&(pqIK;=T%S7+$< z)kt6aYJ|zYTHs+{4Y$}=yPJf4)sNUsPj&$NDwYLy)0vokyQ~Q8EB5US{_Tkq!MM9?MoVe-Kd=%0Zs{D&JkS~b^D3XMJDLvIT00`bqHQ$!l{0xNG6iwur?z9SUIy za^S_KLo}ye3{8^T;vzh<6X2>qMVPgdB)=GC@E!9t)WnxX4e|F6FXTtl8(^utm=K*M`i$t32jA)H&0)t;wEqz0RYp zOI+rZn(-*H=OU=r^|W0iXN@gDVKQalu5qrUE!%yraJkdP2?j57kGc2HN{0|v2nWEqzf8cbb*-T*Xnb;!7<0hnE%XW%rA49yMpHV zzPy~yX|C-pXm-x|le2^$7XB<=#C#n8Td@(z`1-_yi3ij_lC9PQxTRuwNxm%LR%U>U zacgP=@6Wusk12P`3cB&qCoX+_P+|0^=&73@*Y+w4E?Yi3UqO2N+cM&1kasO&e&3g7 z!Rnio!!%2*8cu7=mC0l@;O{1sEhh;$>|E9G^Rv`2i+!MW=pwe?T|D=Vz5uQ0`~bwS~c9KWPQtFZtYelUMLr z2)MszfD`lO=pyEK^PVsH5rI=#?2j{>VBQvf%8dCa2v(dI# zOfHYcP*pUH+C`5Z-FvfYs$2L2>K2K*w{MrD=+00S(YjfrZWgIa6k~Z7ib5WGXWhgx z)nz+{vc*g%Zby;WopL1Bj(jdBv#_5+DMsaTECTOgw~LUPjjG;ej~_s9?_=PT81n&( znY_)L_*mneGK$<7dLl%|Tp?pV>G7^Wp#eKM0Bn@=VdP?_OCP)RP83pZ9Q4GX$F)M4 zKx&UGdoNW(0_WHOIEBoeyVzSluQaU>7iNGHZBJgDX?<8}Ss$**A=lRIk@xuP4sWRxP&2 zQl+)7N(IW6RNZ#k;Dk~8!dgQp7j10S+c?uf+Z{S>7dU9!rPmwgsCTbk?_5W{`}KOG z9Q7X3>s_eV6J`7LvN4Xbr}VP1j`p6{+Z&;`C(2&d%g%6=y{VV=ag@Qnjg?jDWx~ht zdr>brNm1o&x~CCk@9AZi=w+f0AL)HK4=f2w*=Ks$`Hr%0^s*91*+2BMQoYPMS4L)u z^D6NN=Gq}yj%-4b{6@i1^p-3~A4xnYEhkd%2KN?{qXZTKtbo(=ax7-Pyf@$-3_b?S z+0A@f9$v!EZp0Xv%d(d+9}la`C zEaPn|_6P0|#?_uFdTum!q#Tza(`~+793CmB29Cn1aiefryR>UL&w-myzMh3s5e8B}ZX+-yX8=!W+cw6!C4M@lzFNVX*nxSI}fFrZYr@c&5~ zvhQbEzHijuJ+Z=zH(><4*z@&U!h9p_#HK@to!F!jqcFU>0A6kO#yNaDi+?MWy799W z<3uG)BPrM)z|VXwA(WCvVdiZizh#0Uxh)HBvPSKMr=A#=z->DF7rEG1Dv$a)YYpXtETjNY^)mYk^ zxJ2y3nNJyfRaVMWJ?`1c&z+X@hACVpLCblF@n>|NlP}X;Y0X1H_%g%97ia_c@<6^f zU*=?N%gGm~9=IpVe6I7eC4T2Ri%t{gI>KhTWr^L-QG^&gMl-Z!rzLnSn(*H*Tf*ZJ z-0*ADPD}C}n=midEpf&!=(v-OrzJve=RtL*^PoD%d60O+W1%uhs?*Pd#$x*$9s=Ja zG{$rWw9w-WXtd=FXe*(+l+NcmJ;r7)J=>|n>>G=*Vc*8$G}^<@d&bz#dq!K%d+u0b zJ?{~6uuJEYh(dvQFq;2QeIbiQVv3lA-_I0w&I?(R9w*3T;?heq#W6gg+ZDnO2KzHj z8!uN8OC+U^!^QG=IaM-_t_T{9c?;3J>~Q)o*0y_>h}@~;2yc5u410@><u;e&;|#3VZ7X^J)NamEj9XL=IL_99T{r%RcwpGpcCOtTEt<0`U_!Mp%&Pm<2VljIw4 zx+a-jEmyB;THB|Y{v*83Wpba`QG zDbZ4>Qe#;=;2sH3TL>N+sq7m=HY9YL&X1=~6SleNVJEROlTVT$5BNz^SDH(T`2^fz zzP|SGQGjZ)+W3lPeA(G8{P|#^nEw-8=Rf>!*HV_lDwUjIng18yPAFAAfzFGH1V&}~ zsq*tNi$xa_G=Y@Ip{I}NB23@nlXWkV+@i6Pi7wtF zH$TyZ9{!_?Jibq~e7|q1>Was>FJ;B^;!@RuN6h6{m$JK;Hh)w3g-(AH=`&lw`M=92 z`s{xc8m-L!fUSEd9FnTS;D5RhlixD^EnkEWg+d|fm%;>Fjhd}iF#AH>!4q?=1YIIG z*5qVsgA0&&llIM}<~M045X=(@#t8&dp5DL1OU3>}+}CJOZ_i@ywBJFkJIF{ybC4lP zj;o_EoM0r{gVqxFs%T8gQtQ2s5UF379in*Jz~4mYw#}xwR4RDpSR`YDkE{l9ZB$66 zSjp@Q6@)|#*I$>a7C#${+;Yx=|NqVXn=?kVgGY>LM;h}fWa_@jEDo%pW=t$G81x-G zZEMiR#^6<#isaWv;p9!>nkPP)oej>bX)nW>9(;RTo0odL^J6s&Lir)FwgMdJHexHy zeMovpPHV$?<^iV1{Bb=>aS7l2-8`>dA+Zpf$+rXYf>Jm;RCpgmTVx%cdq6%AO4rmY z6)Co_5$Dwab1H{{Sd#KkOzs!TK?foSV&y0j>qn$emD`aOT8)&4B645S`lB1wNIc$* zBl4qbpDf)L#<6|p2>8B;zU3v)>$-O)S*k4UoaWNbopJ;nSXEABzaK=(MD}eo+IbAx zY115zgdSagS126X77E4o4ZAZ$YbYVqSAn)d-Jp&b=X0;fWP|t0HrwFcC$qgooi-Jx z5|`mbL8WOGHgw>)S?r!*U6R0+6S#P#mWvJ&DZJ&)?h;)`*V43Foo(!yn4EmRRQP(X(yVLeWmvq6B-T7;hs`3oIbH;} z0HG~$=KPJWllFCEI;k`{T8T@Q|p-%>TsWY_-FDL8&tZZ1RMiXKWN|IFeNm`u` z)L|o*68tmg}}#ok`%fbcSLK6y@_s{8d=S=}OW?`|IV%`q&CI2OU@>}4Wn27?Qs;!wR5 zsVPPWh-JArN?&g&sG@#@8S2?Ix6llAS2V=EQ!~`Nqp(tFq%T#(&L~E#Pf!o&!hAno z_CCZ1vD@9w=ob#Dz1>AK z`CFVcZqK&+ci<14moodXq7$PyAvbDs zvca45yj0*A%Mvt=l;4oe_AP9*Ag;yi!?_&iWz?PKKZbvSoyb%!V>UAAN3(s=U+55% ztckKg%NPyIO{Idq;ap}GNXu2Xx@Wo6wa@*t)hT2i53|gDF>X{>CNDS5eWq97Wv_|~ z>zo&VpuL`^RZ!5H;$STE_C?%C?(EMl=5_;xH~c8z(w(pt}K z`?~vc0Y}>8@gy>|Eo^0huVopt2fWAsv}}8v)`PAzoaUfmALp+bpH2(;H}D(k*=ag( z3^Y9(kfxVgO_NoJQ(d8s_LX%ZzrS0?d_G?N*sY4M#+*8|jM+=(I%YU=zWAqQ%s1rM zkKGATk}v7;X|8-Kx9}72f1ZK%78lyTb)mh*h4v!>Xm4?$U0$9^F3KHp5nJxxUm+Lj z@*r|iZjn{!%L$^M2Tg0J{dB)Y??Sy!%h{Ya*R#V4ew8j~X9Iq;t&t?z;Fwh|F>lAb zu5?>&j-xZv$79iWIg|HB-KG%rds*s#t=Mf2_`__9%ZW|VC!5`clc~Pz%XwCU{uyGr znf2j~p6oZQt#PTbX4k1&q4}QOz>MSn!Ko^zL+IAw%e{1K1<|e9A+k*CuxAB?rDrb7 zd91$;cHbEOi$Dv!6Fl+uaNwQffp@P9-l?s?JAFBi)waQIBj&Mz8S%_d>`&MPuOh=w zH)}at)9koZ(|o#qIlE&MWInAdNUQsDBt4$l-2O^B+AA%xdoyoeltFDne(mRzQ|WnH zWj36rU2Hj@oMOp0a)C4@!}(-p-^fl6-$)v#5{&_TBLuFz(c&9P;~~^HB4#M_jidwk zMn2`fkzcgJH`4fneIw&MeIwI}ZS@fy_cKhsk?;O1eIwINz7d1&2JL$v-^dKIhxmt& zZ=^nuZv?O$`O$nM)46Yi^@**8^#Ob%K-Ir|d?RVo{wPgw7v>xv-w0_}jpW#Et^XF^ z$PmZcZuesV{j!68e?JD)30vw|{TP?{_%Wm$$yU$s#f($CZ=|2@8@Z{~z7fD5%klP&G(t8NAKyrtlz;B)8<}yOd?RPrd?SmN3*U&< zj--27QKt3CHS`OkVcl{@-(roI%Ele5Egm?b>~>ynfy7sClRsgnOFg^3s8gqtxxdH9 zpEBK~FPJWBtv^NhF2Lr<6f{1=)Sr8oyZMJ$+5LK7oxX+s%fvkl!>=ItH<`=D z1Gc@7@Hqg_Eb9vSTVnh8T>JX*@N#1xZ}idF?+aV-(dCR6)-x}sCqdBBahb3ayxV?C zm#v<;_OU?)gT{9}X>|8F1ZnR* zy*;OI;iEw9S@j;BE9w)0e1V@EJhQAjqWoJf%Qu*2r`Z>gW9y%r|KSM7;pTIYSFpWF z5I;k7h2WFZw~)Vrweg@!`=7he>E>%_$8os%7dm>->E%;!j=R&3P@?yz(jjNvR(OrO z(_fI}bU*CUr#`&w5tp(Pgufu5Zvo`Q%b#%i3Z{cv<3nhT9~WfWOwSPBuZJxCVwlcY zp_=BwpLP30>@~-%a68LSYu~_ak5EC#xlT_f>-0Sie?tT@e}kJ};cR_uo&JLhR`}~x z;TaI{m$U*N+P-21+jF?Xo4pAJO^q4a=wWMPj&B=#UE8>^m2E6o!Tg2p@K!H_*~bud z)KT@ZU*a+L!rLI`-zwgAYyAsQ*L;6+BbQUNN5Sb&XrTUtUDTg&g^xd>UYh=c{Rtg& z-2DmH;Z)*Uhd)8seTEO<(j1xQQ%U3qxpw*-2(I#LEk1|qa4q;8bUrhm!*vdyLuP-% zwSoKzBL2|sPY9#ZniTjG23q|ICsTjIEMhlZ1pSlGH2D**0t??oFSO2|fUV^;Lo_Q- zpspF~gWdr>sQVL=vzR}D?5>M3qZ>o~XL5RuEM?Jr1^T^EeeZZZPA404G%WlLtX%ge zB+tuJ<;?yBf<>E=!z??6(`EN3gi&$0UW$zI@+ZIxcHmD4oBRo3>Q9)({Rym32r}mo zx;kUspFk{5H-7?9b#;!nKVgn(e=vvOUVNPV38Y>1#{VLJg56&L^j*_Re*x6F-p5~X zsgJ*4j_xn`argE>~tYgv~&C&Bvq` z$(UUXu#G;t(-)C01$EXPYyr}c%r#jC?+3TD?qHGzdsc^ei_a%f`pTqP{ z!9_VC(8D$Tb1PJMThy@W-d-Vc1v%pkQY!09n8O}kA#8|?m<>Vjse{E@#U$)6f^I0Q zu=*oD7WU&rFMkAUQNVw}_iXbfo*)CX8?1RQ=yIX;$+7R%;1A>HPaJ-d?^f_!PabrM z5e6GF>@vR8*5&(07~6lXaQeywouQSgiO%zM8%)He$5x8?<1DhC56VrL?x0$8M9h*K zlf^j@yPidQ0pI#A7y0B-I35mN9GX8{dF52ZZ9FzU`o5;-!&61fzqpexd{mFy=BxMv zeG|E(;H!v3YDaABy)x9Ppc+x|HGid-Y*kQ>sv(1}{!N0e0Yq+?ybC;SSNhA1z$V~3 ztn`)}J8YsAH11m{Xe2u}8;xbHq_IZ;8U>#(*7+uVi@kX-+(YR`UI#YyZsHjPvE;EBX1dqmNd2!QZo13jX%c`73A>{-Gya zXdCa*$Gt9noWT3I*9FhTzJ1*5+Q-X0`{;y+K3?T1Z#^9HcD2rr(;ai-`V8{c!zOP> zn!M#rz=}PN@JBsq*;lhN;g5RSg?~5eys*U+5boQ@8{ijlT?d)?%yxjlw_+vtAGr3> zidEEEr}M?$Pb<8eUUU0~ZT1=HZL?eoKh^;oKY-_3Rhuo+gW{_zc0Q-4IK9PmfX)N_Y8{SoI$*LeCP?C+0UCpW5VbKw1v8%%kJ?pW#c zR{$-|E7@9<))HANjX7}IIQQx>r4AT@|5q!`@na&#m@}qzj|J`wPq@cc`sE#Cbd46d zQCxE4brpMUOHMsj_VmhN`O2PO=@NhDl56i3G5=k1?XA%1aOOLEbEU=iEa^UMm%L~1 ztn|uz#?Ll%8ZLCu@PR0E$(JV9EPK8*f%g-A>{-q~xAUGab(paZ7(oN%U!*WxHr!zT zF7R-B559R*^4A1Rn-Ip*JMYP3p{Fz|{&3KidndSO(gV`$Z@SJ~cFi{;(37;G0 z`OaYSb(qdBFs$$GR$0zHO!?ys|EGZM=zu-L0UO*;+2Y}+T!2%F`3^s&Ggj%_92Tqe zfWD7*`Y8#n^7$4&C7dhaM9&7Sb9?ki0 zjC;D#6!&D%-JnUi-f>U2kiCuUI6F4tp3V;x_XJo%xj$Ci(~aCu$@;|Np3V;t_XJca zdf%LW%9~7n%9{x84!Ot2Pf6NU^K<`u{ggmomV-WjKPA-3&9&=mogeBHA3tR*_uu2E z1R9HS{r!~Z=zhwZTkWR=`~|t*e#)Q8joMc}e#)Cj`QLNQF-XEsIg+mF`y75?!%w+G z;acYy{FJx&`YCTYPH|6TZE;T}tDNU?qO8oNOwiYTmAmb%$F8puwv16`pZn*kHY3?(<(wZ11(H{63LALDnn>j9CoMSF802|l+9Mc@r)OVG4OjFG&OMYD0 zEw*XsDoY+(*&O2}bl?hphr`@=zFvs@w(0|gaN{V6z-y@I5M5=po5j5;5ho$`Q+2CE z9K4%9&>G8h?kW!(*c6K-=)RES4MOWBRz67P8&m=Y{9c6_$IpoW(Q_nu((HtVIHtG@ ztXqVUsFOHGx~8{+u8&$lmtEJ=`K3PZIO`Jn!5pvBoA0gt`eKej67^;~Y+N4qVs{+YOxkCmP2kbw+a4?RzX4%^mXpL=L=IT@7^%HJxb3k^2UdCEc8`sErWLqfS|#GB96n0V zm?*^pZ^T2r!Pg@vE>*XEj63Gmdr^LIrRF>4Znl>Bj=5X8amRe6t4#&*StkUV_h#>d zVQ(7u%!BpG{>^UB!aQ@oXP)UU*$vd!x|{l1Z}f@%x=6Y?L+sa&_2#DN>%>c$OO1s#PjGi zS@LQAZIyct%!o;jR4&g)Z+c+jeDt286dzWUljcu4F@$RFljYF!nyTlcLR=0D39Ze9 zy9?2Y^6fSE5#ebkAB{ZTdEt=fYs~idm#f_G767*IR=LLmK-m#eHUP0VXj0%Ck(h4; z@JR5U8Vbq%>6iHD@C~7CsRs^8eKBPFgI&nuq(koxOcE=Tb}*h_w)WNKF7P0@-q=)`TgZT^eV>@ z)R#zRT79NR3}waA$;nDnEGGi*0d_?Nk+|ASAm*)qblhE}{x*!l`}&YN;l5Zw`_5^H zCA2qLg4w&_SYQbO7Bxg>3&Fzj3}TNKpll}fXJC{QDTqY)_*D}+`*1m%OsH>1`T&xq zN+6X9l#P=pjQi6s5HG-GRqhcA%Y7kB8R7cj!SL>1KO$vI$HOW%8D)pn@C#w%UobLz z!$z4QTfe;48HW@!rg)IYn2L2Y#^qxefg8xjqMXe9esUYS^PJq2748>~2tPidt6iAI z$}%d;Nw4~nUOhU?_(sM_3ZqBUPfFXc6xmlPO5pD%lpPUx`-W-mH#9H2cPx}8c)zna zO?8B({yI%jLQ_7aY4)ZpMDxrk`xZjI*(h64m2z33lssno23hA6`O*1z$ynYd%h7#Z zco{vD-9#)Azz6f7uS|2pMa>wUYsez9hSbtEWQl1FIeLwC4Ke=_>!8_gWp6kvHof7n z*z-;ArOG5}rT&J)a{W#36~1qJFEhR8u+-x{hozSH9L`@Y-t=B97fE~Qo8BXG;q8W% zn0>o17v6JV-`jMdwl^M@THbg7U%B~>2Pbcqn0T|q!JBg^ zfAUc&T1L3DR_D$dojbR2?o5)l;CKc5wE@Qmv0s~UypH|49mglJU+Zvu2K%)M7ZNFA zzi%bKWBm74@;i(F-bQ|B^WW>q?;QSnBl(@ne{Uzh^N{x#tu~F(GLJD@Wf`MMWQ^{m zW3+_u{*zo7qtTT2Yccy)PREFSTf@J-(ho)#%Tt4tyd|l7K-jNTS7hgB>uTOM@F%Vz36A2x~C3Lk-LRVqK_Ut&@{S6Y4 zKQpD?fz#S$xrSezt;tYs_7mTMjk>xHY2{gjI2*c}jD0uqcO+Si>LpD&AjRb>DF;Q! z3}L53u#2doxrpIx2zmu)Lx_IOqh~{q>l)5s2GVa|9)&1T^EuC4-iA1T!l)S6OVFbG z`4gP|KpX5_iM2tq;A#_c!Pi)gNE`H|$=KV^PcxghLA`|9v*weXf7iJV>=oEK%LS%$ z7B+^<_n8!Ya!?*)#2{Jj&e^O!ZO)r{vc@a_cYf~GV(^Np;6 zv4!l&#Zs+QBtLdiu}uEP$>L&ME0riK|A zJXue$G^m%8a=;fa=&4dG92xnr?^?~?c&JN4d0g(PMo@jlT{xAzljyv7qC(AucinQC zY?;g8)VooAmvqLQ`yF^_J+o)p^uE>Xdt9qm?+S2kBP3`yx92BFcM|&QC8(=#AIC$a ztdfrHl?irEE8ejVlR2=0p2x!9WVb+ODP&-WIk_at?v*TwTF-3@^mAK&&qNYurPb)u zqxrIk+8b>!beO#hxlamW-$Eoq@E-G{NUjV@yR=^F~8N=6eJ zFR^zBwsjNl5HPwJPA6TpN)44wf%A8(4V=AyAWp%&kcF zgR&&mSM0zI+L~MzV{X+6LjGP^Dduo94yq5u{Qrv6RWI$(-_!z}ou-KOYaU%Y1|Vyn z0OqBcVttwCILH4x-xG^6@kY0x>&R;MeoKRTGu5tDWcQQOc4B~+S-joR2CXSKj>H#t zE{OYep*75ZG$FndH)_|C-RdOtb2=h4(j|FyZc+w(<>-goDq>8Rrp1`<;$zDE0Xb_} z+zn7rm)M0Hk~4FYQcAmr{OfMqQqFrqbL1=B_Z(lt&O>OOa#E*eR&Gt(60}}Um6LiE zq+aKiy2hGCxoYQP^rB?W^MB$R@utYm(}W*vc`o?D?!onicVi1D2;OAp-M#AD?9%rt zmcH8VM2bF@tYP^?dzP}-n6>1KlDPW__#+9h&x-K@sUbNhm)+j`Qd&d?r>s&w`FHMbUFMS51Z7R$M{I758~$_f4PuZ%NeZ&~r@pV;%gI$6Ymtd&7N zMJz~&kN=r#MEQD|j&cPXW#A$3e{EXa3t0!g>f}winpBKllFau`>U13IWPng*2I2H~ zuc7|#bT0V2e~!)mYb5pr>N&aQwIiscbAF%+I^VM7mF^QyoTB6tenmv!UM)h<4lsZ zarqjtmfRP3EfF%%utw1FKcsJaOtFJJzI&;uTzxjzI8S2uXL22$(x7~AF5BDvozhq$ zZ6@=knP|0Q>3RB26r=YVo!iyq zBxX-!P_g_f*^fQbJ*`daT7hP0Z$#rf{;^WL`Gcy->@5oan!?l(m{$pmD2um+`-$Rg zV++^+joOp2?h!3(+|3mF|26_s0Ww7Thv_)9e)r!--~AU$Y!oGnC*TwA2@rg^kF+@$ z(RYz**}F)uO5$B47zeU%k&45w5=@6+kAo}9S-Y=OF2UV;ZR`Pw(l^#YAF|!BCtvgR zoe$90pgo-1hQ6!x`BC=HwvdP9Qek^N>+l`u{)IUr#(FP3Bb%Xq?~w2JCR<_5?;Xk} zea~#T$8{S{$nE|$BA$28Dcr8mUZAHNux~HW?*Tl?=aDhK5hS=T-X8p+xfimz4`JoC zDt%uH`jmy3WYiPL?%5HUdJ)joy(1X>2t{a4??|XPR4bK4Hj+8AJ$Dx>jJ8MdSUen8 zz@s49=J+#QPJ0rmERyDA!E2}A@`wzf?|-|7weQsOjoS0MajNY@uG${TE2!2FS+u_4 zGk z+Anud`(?Mqe%W|wYyCpOd}-isVsyQt&lAz+bF)O7Cq~Wdk5T7$7XO<#-=}5TTXfEt z-vc)8WfWQP!FQ-LUF{UL@3%x*gL>Gp7c=Ue6g7{Nwca0dz03aKSvFnm=!5fJy`JdX zXq{IbJb3ky4}XhkoiNuWUlAGWgv>r3;hzdoeTb=l>U;1%Ni?nj^0b7Ap4PsN4o2+G zyO<~cSi|SKnWK!S`30{Q&m4btj!$pV7JXzm~aW z_*})_;Q08l`|5V#gT%&|kY~S_5nX${{Pq8I_1D9nc5B7{I3-S zbw30C6s=`^XS5kMK>KB4&itQc&YNk4I_6j$c;k@vQ+p7PPdq^Qoz>o)zd+Cv^p3^* zlTBl%j_+dJ^I~&!ZId`3xTn$@qw^p>zwm)T_p^ljowPR7oQmd|W3Ecq3LRURr&5a( z-d)4=TQ^d6*d$X~++S3c7X@Ea^6K1GRJO8A@ix2^^^B33uNCCojk03-u}1Y&@>P_9 zKRk=RXxXTpNWRp~-7B!tZ>LbGx|{dnmNoFNGV-0*5pbb++m_UxRON`#sBZCIyc)%D zuQi=fYQY}dm)P*>y36v0tSYNa3~iaXT9IRkVY?%}F4MnvTcyMz>9gT?YVjUiw}7;C zHI!{0Sk?h>B!(KLy-K4fv2J%STHdBCoBkdLzoC?VEw3j%*j>@6l>x1>#$>y^O<83_ zR>fot*eYcWwGf=WFCi3+A$Yr$^-K)ybveO1aQBJ)%PEPWyDwQyOhEeY^3+^{o3w(u zm35+D3gJs4)N4cxC8XUaCZzOOTAG&#?e3LOfL=6^!brQzSsBn#mX#kC`t!WC!hbMN z-`6kZ8h~k07$=#Ilg!5n(;SpAPH=w1#!2SiPN7iHii|-Y>vsp%vU?5`r1sfAF9gpI!kR@%kO{ay%b^T-t0My4sc zm5fHU_h@`z9*qvR(V#TQ)qW%Jex?x+{Cf1?!Y?05ZUw)5q&EP+d?hv`zr=d<=~}jL zu9jjbB@tbxZq8$|&v2??x?eC`oFDAh?FkxhJ5%_jSY5Dhgfv4>K+gyt##f}RI>1rG zxE|8=7o57*4cv<>rM9S28i-PZNPkIf*wdX%`>LS2TGETL+=u>YFR43)N|aP4;wUwk zz+)_NuMe)mVG3XNA%*@dE>%kHT(FWp*TN{ZjHS@8Q0R~2-{gnIT$R@`KXsi-)+Q<; zlAgzq#+Z`w^L_gi|FTj#k)A2_mPQClkwGc%*_eD(p;lA4vQPU+z38vhK#;vQXs9I7 z8g1zgSf_dnrTR~_&c}S@Xq&ysF4@@<=gIRGYM8b28H;eRj-3g?Y-4t7jKbC>_Tr($EN3TSMbh+KaU3(-$FLkx3y9ZH6U|4@7iN=8 z={=R;*=Xu>l=pd+6qyUBFMt93BRx-qonAR9&xsYps=@kGd+>iT>#uGe;m>Mlr#*pB z-OT0$$4Ru_p22mu5UEv&-m3lyqL$a#D3O;iwRAR*DQiprz8Xprt9I86B_llw#Ufxm z9wY;rSi5^LwaX-G(~+;Elo$dHMexV!iIrIP6HaZcWj)YwF8Ku(Q}5MT=pdG)gW;Hz z9#1T%^0GGY??jmWi(6F#$(Tk)VaQ_;W7gL&C>f=o81F@F_lWw%`*DFOj{3}XY+nZB z5Taw-iE76XO4Sf5$($k5AjL7@1zOt>n0jMQ)A>?g12~;yE!O_NQ^cEmo%1BqSi=Yy zzAi>j{W|8?Hs~qk^nfOb3@nz|m`WCX0{UR6EFqv%Lam`~$~rIF%1yMf^XX~p*c}Sz z`~u6fCCM#L46|*hgzzY`fhsWBK(p3~yFjN-Z3sSFa)R(zq8VZjRGk zM|%uk5_9Wj5G}5yQ{+pMw-6=mwr@|`Er<%=#kzIO_A5cn)OQif1K-6%I8J+cIeG*e zex}WQ?P*lsAbaLXq-+AwT6wx3Xget{<>eSvhr(!5=n-5N9uSJ4(jhaI3B3RsqQqES z%Wl;i*J6%0sp0yV-?<}rJ0f##3`PN?afY^%`dcT&Nw2kfVh!M`Nhb8gp~Wn| zZ+(#9*=~}f2;Z&FmBa}2j4E`;yOHpun*NT{-!Ky*NI=C4*v=<{fbT7HVbtI|FPB^5 zd*X3iSyn}~Onb_AfaAXrj7#cjqQ%Pc&`=yA7FdXA8JTFADAh7UDu|W|%SZWM^W@YF z(yz|D+YAlMcvM&p9SjjTGO8toP%3*fR*0Ht8x-*2^ zaK@exdJJFd$?sHRkFFzNt{x=TQGMxt>cbH7^B%$3nd8J15}R;heU0KJrqzkec!_@{ z{0T|*z4r4sKF}W@uQOsA46hfgri^;`{VS~7jmd6g)sE91)N6oK~4JDtb3^kjYpcg}OWORAP~=qOZ5M0YBbGPGMNqZSp@g&CF|M!UXU5W>y(0EAEc)8P(r4^I;V&>iqo%c#u%}nr>z%nFYLo0dG+M) zJY?j6EhpvY+1Db73u?0(g)l^1YtXA5 ze5d5X^~?v_knEWUG5f+dI(faG(=KYn4=|l{#d;AR_HL5pf@x6iqUV|os-&j;Y2f zLggLf64a4UscNivU{8wprFWayT|ZG*BEKF|YcO3c_$i2QjS z!=;g%Lw1-+oaN9+5kHCN{68<{9H=9nY z$ckciB>NA^XQ2?r@oX!tB3rivH@1+k?aPm_V5pygXxi4}h(%{QW-ldf9ednTPnvS< zr&lnjQXeK(Eb?{!Uv}+Oi_V{UGdO$I|4_f?B~#}9blZ<^)-JptE}=t>+NBTLTXxET zSVnc2bIxLW4)?>2(O=qrL!txZk)_uD!OzWBzw%>4`!n3-Zg4;LGr9TXN{8@xma0us z`=>yK9PUiH1~?$igwzIz^?LsGo4+?4rM2Ta znz5gHbSJ_Yhu4a@v>5OL#>7wOX@MwT5W(I&Xl(m-QqZt)imz z;%&Cv#lNoS&p7oJTQmAS3>Q7A9x<$OxV&-JU>M-=swN%L!nXoMgv@g}yM;7ncI1hZ zwWPh8C)o4C)#^8mtkVGR;Z}d`h~9nm=1Y-SQ^(3{lgPS`8~kvahnIvzs*8{HZG|G9 z>MNwfzZK1^6b_zgJWk>4Jr_PCC%Av`6K(7HxsYo7f%4M`oBTII8V#x9h7pE@VoPL6 zGb+;nzR}Hx?hGq>Rt>TG2LKAOh;H6J7xNL9NkY#gwj&IoK@-#M9w=;+v?whtrXz_O zr&-@WdH~|eQtm)yT?N^y@rn(~C|jRie|oM!@iV$5j^?{ag`TM|t2Z>NC9bO=+L;Qv z4e`r>WLC(hY00UK|Gg9=;k^*HE`WZJlA%1jh?L}{OH*gY8!J2kfN^agP1DUp6f?+J`t!%eaq~y_!N@GlaJ!{&7^j zJ-m?Jli&iW(wUzpTdD#s0&EMR!J!$M`ujJHq0kgp+2am9`_7x_zYs#TK6t-JP}G@Z z_YCHzN6XcVD&D;S7@+_cM51qJtvBbmU!Wz)0X{C_2Og`8S=3p}To>OPB#`_F@sAzk z2+WXndJ;b@ntZUBkdncFe9^M80YCaMA`T8D69>7C#22du0u*j!jVs6F-7j4I=LFP) z^4Guk83Ji4+ryK|6C=)~2Yn1k_ZJH)>T8Wh>)T5&<78viO?(PfT1q*uWwZ*BB;x`H z;@Z)JG#;wb2RB1%<8Z)*VRfhK_^PB#r)%4B$Bhu_*!sSBZ-#ZqzWF(c;frhy{Zs`n z$+-L;1{!vjJ#lfl~z?yk+bII&&odB0fn@ia(-=9 zyZd%WT>Dd2WO&ibXsmB(Z>rRAj6z~>iXw1@!Y6iF=GIX1bw6*{2edWbaC-`4w@q1e z|0ESz)e_N(tH#hEh1`t$s~!)n<+JMR`kNozt)QiYK=r~er?wcvz3ttA+bYh2)Yo?!#7tWo!gh5Xs9rh= z@0#|+2N%DXGh3!!202STp<&*OJ2oL1?3mrGRa)ur32X;kF}Sd}j*QC|$Wr7Y5X=#d zBtFlB#Y612dsxenB zTj6n=e$u8&m&(^I1@OJ{9GQJI`2nd_v9P`W{gGE`I&Zk4DyoFvB$qjF$C&~iTHC*WEt=m%vaF^u%7vx;P&lpFQ!8=X8^HcDjyD_EF> zMS2qI8?V_Q)wySCMB~=npDU~<1cN*8+PYII?jDTZ!N?>-f>}(rnU@AmMs&|W=@FWe zzDmL&*%_Y?OOR+KS9E!Od|C?8X%Lew{Vbbc4rE)1}(QBn2yWsud#d^{e+=( zM`pXT#Zh5V=tq80j$hwHiY;{7X{+o84l23dNMa*~X+K->c@3xz7R(Kf z*ru*n6bxxuJqKLlEE!n+6Z0J&c?wKS7a`#vLBvh!`=PR!M1G)VBI3V`=p>YUDF&U_=Eg z5XLT)YG|>ik~*?-@=R#plWUTh%}Y1)jzxevT7!*#LB_w{UaJt8TRQf zVVg|xDeL%&DIWlW2)`GWdz58@)AL$ZXc(JNV4%gGIacl#yAY^(U_{%{sz;`cJwtFh zX=o&cPcYYk0?T7$snqj?-l1+tb>PCyFGtCu4m(au8hX@?r9Ckx4F^hM%kl4d48Drz z3k^oc{Zh43D0&T^&Y8Aq&Uj)rDU_cYIy90`k@DWjDShN(6~-lWFKt=S7Xr*`wX|BW za!&bs`7=czHC=GpWLgO7boDDG=(E+B%g>beAr=Lh zy6Hk}$E9#E!$5@QZM*K5+(-@FJOZ5Zww*pBy7wo)Rg}Efl*jlG-8l!Kq{igOi`y>J zO1q;1R3p;P4W`-k*Ce9$noVB8n>#opqLarnB~1Ggl!-DN`Vijq(vR!v@_5yfGenbL z(kH7h$%GlC-CEj^q!Jvswn|ur%O39ZNR%bTp!xWYuQrb#%FK3?Gackuv&Y z&UnI~;QDz_A1QZ7lI71D*pm6x;_{pr14cPmhnnsHY+qw(wEpXVZ_aS5Y-L6CMBBQ+NMHbktw@Wel|Q-y#YzCSXGR z{qdx>`oja6%w~p}4V7}T|bQ!AdF8vkKEt*l=JPWaQ-j}8cJlUCoD%Qy+ zp@~p1F77MYFV|N$iVGk)P}a$U&VjJ%)U*YZba(wYP-16C|BFOQ!SEpQ zb8cnnU5gCTapMhXnpYqBUcr}45}P~j<_N3j2=?Am#E+ud7e!?S0t2cv%pNmm>dL>2 zwHti+Q)eF9yZ_PG2bIgz())VonxBk)BI(X3dhojDV6_qNA9?@2@1aGPD^F|ep1H}k z5RK(q)XxM_VJ^Yy9HA$f8pM6d{r{jt7tERK5*Fu4e1WnK+rxN#ufR2DmT+(!KOXMs zt^}y;)C`B2%N?qJ-0ELiE_Md7t@QR)YZ>%b$8crD%=?P2C0TD9 zaDLc!ls-BduC51E2~XsYgX@;5FrBi^gWp@ntx;DuG!bcr8_&6IQ`4f6zvKTCGFvcQ z=;#>3w{9`DtL0%N+xsO+-FB8ythZpe9I?B&Od*j1or1P6+^$?;XKVS^5{@8Tm&J@$ z201Pt)&-4Ll!CMXM@cs2zQwEcLZ5$Gm>b`J{r!a?`HSK~%hCw+v65wPM@W$^OM38z zC{qCQNsbWP-VNfr{CaoW-F2&ZCVHVgWDQ65qHW^?yil^W%lm3+E~~A27|>ST&F6S? zGlp)O9rgBVDulz_iGqT!uF?bRXP3Q2SNgrf>m&lLZByYdn?{Gdy(BNwW8BWtd+Q0# z($i0lq>gGn?tB~Gk&;^N_D;-2jdg%Tf7}eWRrFJMxSzj9C4PuvXwRcj3LLGYxLTug zUnGmloH&-l0_O@l@+E?>BRm$n#g4!|p?^j=GLx>n@R zX1ch3#JuOHp8QsFggoQb3y)+b8PmCgS8|ANi#gpZJ0$MS6hh-PR)rKo^E7+L%zIWu z(UGJL;8Y>L;6aPzz5S2&H0*JkrSI&@zBN5J%Qdh#Yx&xv{fWuEaV9IB{=EWuKoNr- z$D-pb<--eUCx1xe$7=d=Pe)nL5yw9=3Y|-|&Lil#IV)Xftus6Km-f>wsS%?7L?_VH z{uOmZY?)_EAd;v~0?n{==aM?`aSH1+I0=x&S-M?d=;sgG5Lc53cfIQuK+KeQ8uV+m z$Y`aKmrfD9c$b=FVIu@hUiO$LkY94}A8~9t!fh7~BTKK5i6@g-x|k)9zg@gcMje!q zeyvHl3vpcXl#dfFuD6~dIKjdp)&^Wgul9*zbB*Zn57wXbk7#)tZSa@BR1>H*YPqzi z8N70sF!y+Vm%JI*j*ln^QZ`nWQjnzKV8^3SdLWm`BJx5Em6o3ajO-Kt5Uk zHFvDltX6`wkT=|o5SpI`xYGM&0&3hE6gd|&xAy<_EyutNXu(Mt?lPRV*qV$XtfxQUNoX= zS(m~H$p8)WwF|>(9;Cmrrdwccs}R%FrA}I9Gcnq(g6f~1AbO?7aZ%Cyuu-~&`9M>8 z-F+F@2Y3MKntH(5nu6(N^2zc-t3~|CoFM}*e8kP?9E(VQWxZ@tWoRX@sr`^tOwlx`XdBjM+5A z2u>7eZR^b&sZQNUZeT!+XaKNrmo-z+dQ7vRb?ZXg@iUJ<>-?d_q=5duF zo2qVmlJ*J2{gDcQosN5?w(X|2HabSF3v-LBfA7${>3o<1qX%Smfy^P z(`V%P74PQ*58LGr=dFt{;p+Ed@9(RHX28>hX!yH(ccB?fdMKhE;k&r&=w;EdJ zskiLxI~1#l;my2t_WuuX%)D(!{$|ca!?A+4pGhg*dj}i>&;P@)X5NBN{{w&lhwVrI zBLyA;ul~cBIvtx3ysN-p?BURIr;?%Jj3;$eQbBzZ6!N7GZUh;?M2RG!np~^Y$en>n!J#{U>U>ta2TdqGDN!sFRw$o@Jv`s%;Ka8w~BW-WZ zOEPSE6h}#f7o)rvm^beO@G4o2pK!@%Xr)uJH!1nr`}~RoPP3pU%5e&5cktr>c!618+RFZQPPb2L#6a{LsAGo`E--==9JF zRE0;K_z`3VWd5C>+s&bh6up==P@{IqBd_|z$~h$a^w@kVD6*DPhk}W6JC-ylT7p*4r zgjA0URrAOfaDdWoJmqSQZu$jgs$;uLZ3W?9H_-&JJBZ?>N>R5>L$*~KUp$e2E4us? zs=@Sa@?)Qj;p5m`JHT5&xGtY%<)bhS>J+8F$sb`jl3mVUezMR%L+z(_)3dihway<` z0DiEIG~wHHXB@YOw!XDsL5f9%$&jMvu~|!AUX`!>%J-iwfA}iiGA@%5 zVJ2}TwZh{SM-=lvOA61KOL+2kn3G3$rIMw-s12x*g@YbgpC!x&s%`I;nvTQ9%E;AwiI6eCgz@BZ@jM0 z_1@&a-?aoj-uDJ_{i;)|J$Nxk*MbP_ekK^${QF(vXZ?v1TB_*zpU$U2H}420#m22O z71oP6Fa^9n*E&AXx_szzQ}&DSR9A$*`!MvB6$(S0z?Cu%VzBXv&WIgr%ROO`6yFl2Gi|!2(vu0yZa#M`jm2oQmzuKO5=aje;s3D zK_{w5X!ViKFJ24BZ!6f;vSz;Vh;;oJ$Zk#@%Cq!FGMA{3*+(uBn}Kdn%qhr(?t?bB zQd^Kj+Q|z_MBwhG`GPh0!glw**TvQ*@a{c7gX760_B&g1p-y^$-piyfxSvlcADJA;;Ss0JI^oYjKC zOE7CpS?xl5_s9P+uB42AbX zdjz(F?y2`MkWW}p=_oqv=GD?P+GqvPgJxb;K_%&mo!dm*HEI)+n=re5Q4 zN83d#9(|jHy;2u9{pFRsE^cb4v;c#zixO47RrXoP>GRjv^IEsLs%lrhURsa%BecBE zUUVP0L$tUKydw@okG#D~zHg0PT0fByyO{SLmB$L6LCc=w9Xq!ORU6riPLIO6wv*yl z?C2lL=rv5A@Dq^c;N^3v{-vhF*NZQj7hV+*KBw^d#Lajtz`KX!6iI&A$gg)j+QRQ1=x zFR<;^OtGxj3MI?PvgHj}F>g9TfFu?Wxqr*saO;|W;6lnG3y{^kGSPo&8|6({*{>`P zJ7a$WJ1g|QUEDUO6%Zry2kdn-sQcgbrpv?51WIfAzt^%uTo<;TkI9@M5E2In=%At> z(=a`rQP7gUA`Sg*6mv9~Xt}B32 zd&@ys8qkU8my@GA*O?YMUp~}eNRLDZ#sAo&45~B~*_2dp@x16s2(u&MqbY#HlJQwu zhBFIT@yEA9@l^gp18jeW%0^>mSz?EB+cs7Zw)CclJG4ggveHJ+)XiA z(|^@M^rZB=dg*lWNI7&}C(I|KIjCZ0A5JugKaFX{-30utDOkV9-AsFpYJK%|Og_Zr zg!E0JF26FY>cqejzwyZOkFs({+U_^a<5$nPqpcz&#!2F9x>Y^?Gr}qh)2dz!WK`)S zT4;O8O`AIJ{JEK^7;b`Cd4=YZ17BoBjC!?6MvjtL? zpcv`?07u)@AW!izF2z2|CY4{lrM1E{$rEH9=pS-?R01cQ3|w^$Q2y@1-ghM@Fe5^@ z>jiK9R;HgCGq7)lDl_&2utTb!I==M)A?eJxEut8dQ()pBSi3%T`UN};VNv1?mygi! z;xTg1%_*qTeEWC7^VbJQ%mY)z@q zr0CZvuO0D;E4K$rb|Io%Ml3a@Vv{4{FG>19HpBeJAQV-z-xNiQWF1 zq|7_+?x=}DW;^DF)}8f-)RlYdArHNl)D%z4BDUnGt-PU(_BfDNPBiz=gsMRBK}L%o{L7(Wc)@^n~|fCgv6um^5aLU516X-JU4J zlG|&|=zKoF>&mvt%KJERUw&?;rPX*A{Aivfz}AsF3eMfU?sz%*))Qz%#+L(f;@w*vFR59e8_PbZC7aGd#-vKd=fA@7?t=bUIgV}8`ScdTO8YNu~>exjCq zbsp$Rt=U6cf)OSP50+w)OHY2+{l&4t;;c26tO({D`?1QSvON0#Q96VL$JeFl^0v!< zNJu+25@mze8Um;*6?%TqiRoBM`7T=7C$o51>u#J?i^vDIU^!~M(CCKpbi9ChwMupL z(drz6chRHmQoTxRjH}t=r#l?cT#6RglUwid@cyV{Kt5LA5kFLAKYcG^g*tk855u`d z-TznVt(>}P)l9GDj^w`nY@QJ3>8LVC@o&9d%f!#LriIU74y=8<5^fOBHDwRR1--*( zzr%_1S-Wgapau+MGz7ex#CvmJ@e3upwJG({sy5%L^mC#Ux!y3&+)Q4v9Ni?Q0Mm$W zZ*)paDiFLJ%$#X)_}mTj3WTv~PYnXtt2hz@vxd}+BO8s}AeRBdt@mRESCDS`a2M(LxM#k#XJ`?M~Tp^g7#M4C!$@NFpyV^+I@p`r^HnyX25I9L0WTDaBC8O*4DYe?vLoQvmQb)1aw?mnw9(VTGt?sMu)^Ll zt_ZK(tHi(Wy30t&dmD#<5`8+jVY%xm0p-3+kC(ZuC|c#qtsM{mkK9#|As2C6t*fq9 zTtKu`-dy&hH_2zmtaT3?0(`o0KQeDxI^% ziRvTeG$CHOt+Yi1`&bGd7Vt$YCkrV6+k+rNM39bT^GcY{ zu`^lg9SUfQeXF6I<_tzr@1=^%-CVKQuMnnXR``%oho(f>u@Y)6kmAD!*_^sPD7QgK z-EC1?W=N!FNJET%$vdL%f(CL|u?$@WBfS+RqeX6l9|B`TnA8nh zmc~FDUxYxK8qkPlFEO0>RRUIgk!GCtS$24jRA0Y(_vqIK&VXOtWs$>i`wx8QQk{u^ zj-|?tI~v8MrP`mLM@GImYjd=h=}?IY#>YQ;U+ef1BN2o^jgG+otXqlI+}Cj2jJ6;H zoy)|W*IG=sM6<$Y%>k}VpOf;DXRb`d+~4yfw;E!2VwaP{w?#KHcvQATb5vK9W;mU? zcb@e){$A$>)(d=;blR3yUQyZ&405G6XLhAmCUIrzR-BU?D^WMMxBGT`eeY!X=W)Gh zkYT{DzRO}6t=J@fW!j5g36M^c|C&4Qo}QmmilRyjErI8hubId5~Pk7 zjnnlbu7&U(RLfQoQ{Z+{yEn)M;K?4Fn?ZH(9&jl^5a(O>VVxb?9_0e4ZQk)(S?eKx+b`Mu%);D$94*0o%^Z9kGS0_2%2@QV30ZG_~~ zP?QO(f_xZSO*SqTFousz6U-An6gd7{%q9}cpnVm~(tBl3;;=KOUq~r>C7bG>kz!a; z8rPw;<=q@uQCZn~(rDdflIzNBPv8La|HV_5bj?6M7QyUlVqnvIM{zZvOcNXvg{WIo zO`I@bpG!UN_BwQ=&J{xfL>g-jGV?1f11fmEOlS~uJ714mQ9hu=$e8O2PfFDfFIDlv z0HU(GUjCQm%v1L-j;7n4+%NbA%0KemAAR^P>d@`DusI$6Aw|?<+>@q&+&1lbLnhKR zzInt_r@YEvWboi1>rOpGFn<{uWI}zjCIuR3nh-6|C*}|J*C_TMV zN_@Y|^rWE+b5}30bKf`fun}9|?~UcL3>+2Hpt^D2M?QDocN>YkS7Jk$Qkg3xgWl1N zGMwy=LwbH=w1-x#i;d*h(!J&mhDT+rR%P;|K}Q3RJCVwx5H4s>C0W3)g{uK)!+MV(0*yZgM7h9?LsAo#qZANrT#m%dnCGE z??R=9g(EE!OP)Fx3rK$WEeJL8E+Za^D&Fu;rNsm+NuG53v>fwjJEGf3H`*0`$fZ7c z4!~&R+yUda#@D`4_Jc!-z;gYUozzcP)Ubq`{k9$?0( z?(Wx(-Jt7_J22aiLvAV`->v<6G~g{Zj2sU3!gxn|A;IQ@Mozmp;Kp3af&EtUqg9L% zXNH^%(+l6MLTmG3W@~dimVsmG(<~y3aXcZ3bfQlIvy_ntSIH0uWu5SDj-lv89wD6^ z)zCL7R^J8t-~CSE)7-Pj6s%bf;D_9YFU9J;s|H$2J&LMiu>K|+z%zCbtAACzL!bZ0 z$T`uvl@6NYLdRc25oKYSD+na#JmNN>;^ zA>T~7%2IXc=Hq^W_u+=YfF*PhBRril&Q!XlmnLt11er;8NQ-A!_l=c5$O zA204aqk|94a);Y04@Mz+GVr)LchQ;*xrB+gOmlA|YWR3g%2|)8*Bayuo&ZTHg0FBE zNRgCJPi0tMQLH-8{Qm5jbnisuqmif_rKA?vg@}BQu$dh=h>@nWgZC1!d~NMN4Hg9w z2e9{R#|qj?Fp9YSd1UnUk$;5t=A%cy9@Hy%W{Jpy2 z-)QY$3*Hc~?t$>)TH`l@zWK#+itx-&>8l*s)jx404QQV_r&>3sSc55t`gC9JnZ1Oe zPw32l4ob-Wyk!FhOuWFzNyzAr>|mV9&c?cyh%Cpq>7>TC1^0XnxgvBEq5gW~Rq&TB zG@t$}90r>+cE@Do{3lQ=2&T;yL@^P<`-Ft$mkiQyB;DUC*?l~ExDk3z16Wwyy6%=`ooE2HAXFiHGobbXY)k%@|px zWDJX^8shIX*G#tkt{&z0N)CrK(!Vkt+b<((B&#Go4mGQ2t00v>d904x-0d6lyq&*? z^~Uhko|V(?pI5&7U4}~e0bD!EJ8jBLxSnj4z&EOI5a4gBm+67tc;u$ij)?*;WGgTI zz7V%I^*Z`)wy3KFA*+eSnmou#{RY_AUU!}mJ`5b~Drt;h{&4hAI5FedgsxxhYx9Fr z+?f1!dYhM?yS^_c+b(QPKz30+m-*%A(iELTUD4e+31Lqn1{36)4B&s8vTOQUux|@R@OfoQaNq;I)pG%vii)SMNYPtF zcHjNDQCQYL_uzlkBds~Q880hLTd^T9(krCC5c=Espp38l2D)!zPNOvBOp0ul}u$uGFVzY;_~lPMJuVQnODP(#%oU#6c*MS@B8+As1{L7JN{S!AoYLdgXWz?6}AZK8V8Ls8W(7 z*T|jttG*ymy+;$hl6h&ucrF)w0KqNj#t^+K6%@GaT@!u4C9hgYad>Z1c{zyXYMKFLj z#LeS&;LK9Z&G5nQz>apT>3-=uN{Jy=iKf<_31}W#p%ODniVEVo$|P|#(OqDtyDAEk zu`=basz7#rF2_5F7q}{>l>cdr?~h^UdNf8KTG>m5GO8fI?ZaVm_aF`pWUvdJfnrk_ zi&c~V$Od>*+u5ID_;J5St`d4b&f*s#yyBE<8(d6k8~DpFQ>VT&a6LdBr1}(v0m9$y5P{)+4YDPLyH4#iU~IAPIFxyr=e~?n81Oy&+1X*~d-CJdgSDe&N`$ZO;2Np^LyZ%Tk=jxKN7yZL;F4vYm_!XW} zOtZGrg#&@`U}|L=&EMSYKio~@l8K@O@A}L(%z<3n3lT_#7ETAP{4)}3Sr~n0@Y&l( z)VHzSf?P_bg0Y%D;{pCxGVAq0qi%7JW`|*cMfBFHky`v;=-=?0!NN=Fm&%=k7NndM zc{u}9V$;JY5waq(PTefz!d{_3b+4A5b+jdbg!=i_?LY!?;W7wfWnOh&%hR#=+Rfdg118UxCHD zC!YN1i&E7YNXL7`f1YtlQ8CKY8?{iKc%QKX3iD;?waL1UHv8SJtX(Rjr|_{0vxr#c z(slm*D)t7jGl}M5%*9m|&1rBX8-Kae``|4J7j5^=Bt!e%lFA3rGA%_XicZ@w)&eEzN z*T2ecTXn$GT<3;nISUxid|Y4Wk8Dd9RW3AfAJN-&bhr6B>U!|cMU+Sm?U=fKsO=`I zOj;+g*SrMaa5un0lH}HDd%q%W+&v^6VpDYn7XfC)6m^3AuD+WM$=V?pin`Iiqm=;( znWF;hrtUSwqC`wM7Q0p3X2MBtbW?M+DkRVqL*skN@CpfihSc1*tlre$M<<^x07RX! zuK8G*BE>C^v2+gZ9!*Ptqc4L?0N#bY{ohp*l7Wt~^g0rbu~PV{+n(QR>$GPNm_KCJ z)-6x(`55>e+xOaoygQcWnN8e1%Fe4L^v`ZrcozW6q(#^v2BH=Kr9NW^UbPZ!WHk~# zDMf3TRjgJ78XEA{6V`2sxZqS1dKuW8)b}$k{Fv`J(GVrdSJ~$LRovS`3oBXuVSP+6 zSHetGD*gfFuTAcv`v8U=kIk;iCV}t@X)i)qsrk*glW6d;jLGRj3@~RHg(3*#$ z#7w*nyCt;_R7?c5@=XLxI|Z{rq64@Qb-V|bLu3l^rd0STQ&`SX@KoS z4YsM}S;9v9+7j}qAC7TO&;ECwEfDhJ={R9_dsU-WJ9!agY^+|@tx!!#xJfcDsv79f~ld8oe~)i8Sw@&Rv}`^6~0NJ_EWZUY*s!ouSqr zxpG9sq``?D+Zot^YkC74uH)GNx^aIjPph~TxGKh)Ivob*3?-+|_Q^Jp!RR)nrJ2nJL z8ZKEs3oRtCS68N7E+pHMn4*FBsQtu9#BDLKeb3c!2ll!qbHpl)`qC=5{#Sa^D(C)J zy3+QaWxMQvopQz10>9Powz}-ttK|k*haXbjWDfJhF8&gKC=xL#v-}tm=}q_MxSA1? z%IKYcOVDWkdLMm34K=QyIEn6>-a%|?-LJ9V2%8aYUb}!C6%NJAKe4<5K zZJi|rGyx><0bzeoJh6D3uC#Vzx#Hm;ws>X?m0f34uq8m*Rh2A^9plc_>k9j$u#`F` z5pyoX`58QbF|=B2{a^Prn=1l8o?Pwu0R0jD_HNZ;>B7GL@fkQP72)G}{p*zJPgf{Q zHO8W9`VGEG6^NBnyXz55Jvh=T;#|_=POjOP=*|%t6Iz!V6#@-&8EvO>Kg#sXap>8T zp}lMK5efpO`2>3w0X>w5?sMIO(JIe=T5C5cfQa4uNLwSe?LMwj5To@KFk_?@&XUCcseZxtMB^|345 zVo_vy#t>ozTOlcdJ6+*++cL)w{5R=ht$zpE$B2ei4*Ze(A%9C3-N2*yP5;3z+)(!u z{*PHd5tX0a&U@-UZ4CBf^n+;ga0U-UBcF|+XD&enrBHQ`17VQFx8Z};Ts?EYp z4+a~)#7#-==hllEKKVYrkEcXlsrHtninu9Lp)&C0_n00v&s&q!aTEK_1xf$NW6-aK zffT!X$~pqFI-Tg!=7@!8<=qMqR)=Wb-JZI*&be|<+blDEIJH_`L@n1kCO^aWI7lcu8zG^_;oB0U|$Vsz^M@tW*qiI5SU;>(naop`x?ULXBbhg?IEN+hWH^0Gkq?e(cIF0v|{diX=Z;X6WR^jAQt9^O>#HmJXB*Ia;|jv}eDZ*-mx?7ya?!D06KYy&Qt zp+9$St?&{Y5BwhXtu_+M+SO;qcJk0jq-2F#gLE<*@NhP|mxJ>u?{lyfFEHv^e>=bd zf5TrsH)&TVoxq87%j?if7X3;?;Co?VJ={JaU2xqgJ^{G_E(?Z`Suxj+~L( z;iXkPwU!>~iJ(I#wH!H2bF!)EanySi-!-B}lhSFiYx_;vfnEDhxO^NEpZmInn}J}@ zdmi-^F*mgK;VHaY_0%_$Q>7iXLe%J5x*iy(6yW`}WzO+v^9#WYo+E_Qz7-xSymjsT z<3~H?`BAMrQvSb{6FmQ)J=sTDn$Vo|dTmU%XXQi3@3U5XrqE?3#w*l@AX1)3d#O*o z#B*<>(j7O-8+jZUW4eg5L+*(-mh;Y(>)oR=4o67W6_zP*lNkm`DOvT_vpevAFk|<= zlf-V*A~6mAU;EycHf&FJg6qwGwdgOBP-?2|<# zzgtU`aAyF$?N>NT%SvL;Y5fdx+$=J4?3CFMp24>WJ#P&ki%%BA24MKVnqc`0yK5VM zi`}(d@yy<9xGT+OS!j@OYRg;0qK9e@$`wtA*BPIO9*6v=74J*3kFe_Se7fCuLD{|t z#57fVGMY9w;TvLaV{QDua{uION%olkk_UM{Y8NBDcgz$`jI2rU+&r;Gpgd3+UXzlI z{!1Kfld*p-;I{FLQ7s2zox2~~En^J@0>Da=zy`IGjJgGXLW62twz(ZD))_E9o;KcSD1cAUTIh&$WWyBwf3noNP?|}bwaxjSTQAkxPKM}Z z_(EG}?wl`*km*du@fT39rEs-I?JC2Iy{%A&mCaq9De3%zT+Il2LB7U$O{lKVeMB>i zf88UP(bhRVABNDpX)NA9s8{=&!C&Boi%}i^3AH7sS`tbEAo*`X7g%5H6*|V zc)08ce0GTX4!AfTQHRbTKM+jTPwx#7i}v?jgNRLJG-U3g`p#bq!QL^^w_bKZGvLUQiCsFL4x24u5ubJDdlO1+v-UOCc4u9 z@BWbxGLl#KAjW*o>J+pZYHhe)v`)SFUMY6QT&i{tun)3?3U1sKw0^L{yJj@w+k3m>A#1DvWq+9CZXIBu z6A38+!CQgfNzQ&MmyUDF3LZs5(pqFy19lwC&VK4~VG)mjzRm3aBaD<#(g1y#=z%Gg zTkO?*XFqYlOQ{Xl1MqTW6r|;&psXM^$FP;r7P~VPOIHZ^KQw)HTvX2&D4>9VfYKeJ zlF|Z8Hz*|_T}p?rl1s0MfHX*VmvlEPA{|RNEWPy7EG!FezrXk1=W};w=FFUP=bS(8 zy>oKw@G|f;-u!L>IZ#2%oaPxG+mRI>J0iZpR&B~mfdNFvcGw1B2vmIZx1N0~{VOIu zg9ZA+CF-SI{a@HjHiCzodYH#G`CYipu}5;*^Wo)9o+;Vk-FeIJSChUah_zN$>xvjX zt9dJX7S8F%mnvMUYG`#Bc4NDKC1}wM^85~R@G0#*w2^j!9z8+tk$T@v0%o6Xj5vyn zH3B214sK94E`j}m9jl@W6+i2aPekR1{uskRr}rxca}wx0q?tp#4R;dcZ??1(WT9*< z73VjP5$wn1?{gNXB9c9NxaF;oY|^W1Sjbzv!IkFZq3B%%{+<)zWzB4lKsaY$+Do$X zB)Y;1$lE8{*^gPU4_3}dS&tt1JEVMVAdot90ZcN@tn|mc4Ze&fM{5TJ#P@eS4|)@g z$q(H=@cAabhN0+F-S3I!<&%!-!mC%S7f@$$M-~1oD!l^4p!Xzaa_-b`M`4wYV9n{m z1d84H`?8;98?XBrWz{;?3lq!Y!l8 zLr#;q^1Fjq2jwZXWM>VZeZ)<-<@h}EJyE+P_Z=%&CCaDwTxGLdk3Ph_v?-`EUaZ|n zUc8X_B7(mb?|o*`ZecrDW=x+G9( zi_kqaqdR!Cr_Veg8nLGt=21X_Fw*g+XY?Mex7U)ovxxB!!uEj6wv}PEy-QRIE#MwU z{hK}52JFA3&zvwMan3fJ}g)qBbsVB4OtMT0oz zAa+2JCkjhh^^Y+p`hblxkEHM1?z*7*dSM(yIKO(^s2R+O3PY&b?@0ASleB2Dmtf}aa^Uyzvi9yGP zI^i~`mp{nON-Mf&@QvP(q`%?v*&dDE>F*N86!>lWy+uhany#I&BO{UNm0qv4B%Br) z?Ld-BGG`(`l`D3AyJ5QZXQ9+l|1dJ;u$KMKp7$y@?X)QEn=t8FVe7v_Bgcs!O@wRq z0WJr%cLXw0t2=r971c~($f1sDR(Ja3)*e0EHW|sS@Vi4&6$SAk2F6+u<}kry0{DU^ z;}!f3r**{3$YKD7GnAn*bEQtgD&BRJ>xFn8&+=Xex}D8y)lqwAjhQ-MZRlYy459kA&|k@RtB{deB*8{ zD21a!#S^O}tm3j~W?3Z@u^0Lw_1{SG<0*ffYIP!o&t&zPWa>mT>XxBXru-|6 zgbCh}-#!@+d?EvqY<=I$!yp`%Jf3=iLAb#n9Jv2Ue9n)tcn@7XnNoYE+euN(s$OjO zy?3l=0XFn=KX{#-?A~JO~T6N3fX;c1y+sELN9-;)$ zEIfNGK_vNj-0)0FX3@}6E423)!e^pyQbsu09~vf-{L!B0CL7UPHC3ddtv!gu;w8^5TyhY>=Y88n zn=j>1UoYHP!slOia&?C}>jthSgfzleyxSMf&vOo?`~o|+ZhZ_m)TKR)g1f_EtCg|80<()&;311YKa3eM`_&xA>ovD?$E+M?Cs8|;0x0nUj#X+j_>!!9Ac({Kc0 zCP+}WPQGS=<%$g;H`4Cw)kILLgnm)9EY|4Z<@kj{>CeJ7rN#1}{@e3w%7Z`Hyw1tF zCH}_sE~OVP`B|PgjdWYotarA?K!q29K>5HPsT-f`?oZiG$E&bE{g*x~0f}?W0CI%} z8M2e}Yr+&mc<{xAL&YRDjPr}W>Ih|k7d{soqssecwNJodZ zcmy2#9&(?+g@?MM=5hzU5gM9gpJBrLPwkhS;@n?|3oKN(l?vhjRKPy~7Egb_Xu!=A-KtcZqkmF9O8VO8*qzK= zoEEQ@lTSmjGCj3bf^64M56>qnDAgtYzJBojD^6xFG^`$(5jLucX8(m(uVd`-a<_yL z54;}}^|*cmrSh3{_5=6KH?FwxT@#yr1ttd)q4cC&+o+4_tLZ|r;9AW-u2(E&tG_w9 z(i384g8HDe!o1hks`bh=cCp0l)9`=5?@lHHvJIXM8~tDsFv96&65te8z~`8?$b5fk zeqqok|Ef85BBEAXH_=wlAU0$;L6EF-vpCK7L3#^RT66U*kSp=7pY5Z+5I0_J^IStp zpj>VE7ItH``ch>A5(KSx_yM6U|yd~Xw$_ZI!cNWu9 z4NIkN{pMS9_CjVm3sp!A$Hb*UBkw+bI3_oL&CTb=x3@ABd6#I)8zXX5T`6uHR>F=cppzM%7(S|!8jIDtfkH-+&JK|6wpnY5V`Q=Jda938VR_5(}2g|M3j&$B_@#Y7gMZantoz@CcXe;-fMz|^+gF|5lnmCv}YZ5FcRPv#&-P<^BWA(8pcpF@vLb??6~2& zwQ4=SmzWnR@w#lzz8VgwoBnLjOwg*eC@ctaic&@{vxC02YH{E=`prolrJ$_Kdahg^ zc^xN+ge(uTv}zsFm`hv_GO8bwiw`ee`Aki%f{2$s&O{_pI4^Q)4ev-h^kBFsbj$H* z*^%Z9H7$sn3y*`-AQ3zHsX7LaRf>HLa|j#X1wqQURb!R#e*V=Xe^Ba&>`{u}ztJ}+ z4BBEs>N3nlC0x{PiXdV$Lrf6gDV90_cCHHGukR zE3I%txlu5#)q7H6DOrCRB!k9>4hO7jar#R%JpTCoJZa&ydVPd0L}cvoD_^Gz(TSM} zcw%#b*+ey8c%?GaSbjwQIb}oSo4-cnSh@Zn>L>)EMb`r-4GiBOQ{_mPql@(RjPWCX z7wOGeB`*yusmni>g9a8Ba3g|Q${cWGOV|fW;)cCA@7O)G%k<$|W!|XyIg$~MA3V5g zn}5Dj9`vQjQDK>u8v9&gS;>sP9H%(ts8ccVEQkCTI5HS)hxMRoac>Z|1>0YPG5&!p zC4p-PABycVYb}4sq@XQDwqb=zD=y2*&mPvFQ{Qh%>{t+w_!S^tS$@3VX59vQ3}^|t z`whlI{PrKZn9iY(PoOPlCGaArob`aB(B7Q$k&M;NM`oS^a?l53cy!n zfza!g&U);ltV?HvsKS2xXdWU4D*)88zvNrKM>_#?KFHYOQvk3%edXibdf$F_HQlS1 zAnQ0_^gc`3Lp%z(ETep5TgnGyF9fL0gUQk_mUFAOYgL3t>Vu`W0&v{yj;HK0rQPL& zhigJRv+#p3#U{dmVS)&f`PDJs=n?{p7NJ~M$ik^I@&$i-$)+Cd&AxxEC=eZGW$02synes z&Xx2@NXBp|b!oy!k_Mq)4Jwi;ey~mZK-&W!4-((8_xnEb9A;k^E+swX`hADpZ)guZ zw4gDbL!1BLnWyWkl6bg_j~Q|pMRKMT&Is!8JCK>=zqI1su$<)`sI~v;2M5qn*yHne z14Ce|4{?ZU8HI$M?j;Y0EC41`2wt7%l!hhbW~Tj}{&nkBp=L6{8e6+FTB5Bvz>9Cp=ApecC+Ckmv3j z?Rl#Q@{^uun=7N7d8fa{AG}v)%|TLM27_FbzaLk@k#|{v9%?PpedDOVvj-`F-M8F% zb3pk)MAPiqnJ&zG%SU>4Y>%GrxiU6)kazC$q=#UaN1r63<5Bli9*Cvl)}q!ojioZBqS`$D)z-Qn zD<5ezRN~l2T1MEHbl2upmP_nImnnAd(BZU{fQQqj!_VFRBCaM{3w<)KN*OjqG*Q~^ z1xbGyH1dYo6&;VBE|>G$zj^MU+7msiR77_rCudC8>!$pKhq`^>7qRvOS;{Ruv=m&X zh6b?iTO8{=Og_8hb1wty{k%2czoh(juXb<6f)c(zk)>ddIPMUGRG!^jspJ4jZ~aY< z&W~7gfU!t_lg)GIqRpko51=g*+f?Kc9NO=CXXJPH@~#r6Bfka)tnC8NU7}tbpd6}T zDJC$;<}KRg09956Lo{~fY}}&d4p8A$Far~q)=t;d{JAG)_mWGSk**^EUyUj$>DDO4 zn5i>*(;?WQ?!8aSSu-*bJX3U6DZLH*%K5;Zb4G7A1ij{b0HvG}1q=b*IU#>a?o@cU zsaq3EKrAu=t2ZLVooO+)KNe15eORR*MgHWF#Bj0%={>QB6?cRvK71wKvi+XZ-c`FRXU)5;t^ZZF3R7Q72FfspASp1ZoCn_-x>C^ zj`gUT%e0}&C)yz*T95cUq^Xub?_(E552m^n{#;1`R2Z8-GlG!2 zEj_yZ7rYZA)JS(_4`9vX!cDOHtC_25cQ&0|ntMMv&enx+;NEwQ?cL-moXb|xUwGU5 zPWu&!Od3-rZG>&3Qr!1`{_MCb^BUi|lFlTwB4Dv+eTWyFf$*JZSdz}&*f2dL=ly9$ zl4u|#aRU^%ihPr}F_$eG*sOM%ktG^Iu0=U96YD4)&i zObST-c^~2}XP^+KdBYjF&~duN(~HRLI7NMkfRSSEWW^aMb0{El%e$9Njr2?3WlT-I z;pGefbKKCzEa(m#C=i~fD}~5g{-p44Z-4UTN5*?$i8%T^&rVj+O`}?hRJ@`HbLa<1y^WeGkrpDG{Z{Gaz#KMu9YIGN5lS4*vLa zjvN~`H}--VR-80B{KI<^m3y2&`aZ--)z~}E$I+Bn$C4FN z&{%m(huRtsLPPOKH6D1DtuJ3D>l3Lpe=Cx}Qfp?XY9sAb{_}U0>vK(mF3;D8cQyQh zkE4`M05zh=2W)tvZJ04nWwbxi`ub&MgLTV3U zw`DUUD>Ngq$QAy+;WO%#gJeiHpQ0`G#?UXgFCxFlLI&`zBH0z1lrm=JpY9og=mZu+G$UMqg?E})L&aL zcV%JwgF<(Pt&c9(myWtqnr`O!#LqXxmKe^Z5>A}uZ@#L&OmHHyB27x_o>N=hRr|bc zQ+IkhpjLnBd&(Hc22;oS?+UhX>m#{2W&C8b#JIVpV(G6g-HyCsK^_#_N$6Dmyo=O*wM;#x_Jf9AQ`(MKYt4!56(lA>xt66-1)Y3)$K*xC1FhM3YjQ$ z=SU>~4cm0hOnMYUg&#A9#gG*wwzKuEU?!+Q0!yWtJ;su}UlJSNFVf(TK+1;wq}6`k zli=WaLG2#}()-_g8a!zjVAPkVY%zPLhA@Ru z(x$AQ4cWWc@fD!q+Yen!$omry^IHXxTp8;}s2+MigF;3Dqd3lDUQaz^{?5Wlatd;J zAjXIcI>rI_Rn&;#6&=Vz(6YR}k38PT9PK$v%&0AB z!hg&<#T|V_I7v((6V=SA+=tJW7jKs)qdd`kSwC-`**_8J z6Jp){3iJtS6m`}jARJ3IQXup=)+W!V!IEi(OwxX7^}6m{nlw7p%W3I8hWbO_;I_7~v9>@PVJC>6T}hI?q9a-6EVfH& zcdYB{H?ShY5QTQ-^`jc89~(SJLY<=+jGS>p7Oa$2||z4Kfi|H3x>i zbUjvd>+81K$U#>p61Ns3ia^QZku(+M^!`WHVld`2xc1*mGlLt6|66K6Fb^SkMSnZo?)76Ro4IFS9RBGt%>8oH`-p0( zmQ(8}m3Sg1Ez^U}S{r%bu~U9i&t57sKb+mNJCi+6F6rs0^#RiNHty;^)8bKQ_H$g@ zt*GzjFI&nIJ1Y2_)UglS0g$*-zbuuN8#2%8>(X}h{9|G!PcWPIwH=cuBBd1N9@|#_ za({JK``R_U4RTV7ic(y$AoMJ-B?&Vy-wtrT%3yhZ_nN?W@3o%o8c1@0F7o*JS8eD9 zWigHc5yjOl`CAXQM@gRyjE6wFt4KF}2NqaW$LThYo>tea#9Xy_K>80pADg6;=rwAq zpPm2Cr5?2ySH~epg+0jelfEVx_`R_>bE9(3%_jHR$%x-FIZYSJ`NZq5Rv{{i8)$KI z%q~M82#p20T(B*K{xz@Y0zF7?l5(3lmU8TbT22lk zHBPSIQ*N|jtR&aU!po;CZuGFE~UZAo!eP{-|x_i-GA_vl;%?GmAb-i_q>A z*Y4)!e<=k23@*hX7T5iAJZo%HVQb%$Y5pm=&Ib@0TaK)F`H!9YLT^9LHfq_VNhf*k z={9EsaM90~q{}xNWfY8xn=Xy`ru{m1nRoUITd-x!b`HH`w2y&yj6p)^KEshhH2z4N z@j({(ldNs={_K#GA_w6}m(fPN!O@`AU&{6OTV>Mj&g^okr=vfR#j2Z5u4@M#YxF~F zLU7aIEyUXC%~)%Ol9V~c``Aj7%#|)*VYqql*6o_B??`7cQ>2E!4CMt){Rw5z#mv(# zj7v=4yQruHCsX*U)3_Mzd#g9ZP9!n^P8j?J==h$&I8XloyoDI~KE@I$G=d5gVwV{X z?SbyRBC5yF?EL)3dSkA-O2Wj1y2ON@jztk^LIDdw=kaq{H=-S`B1B@N0NPD8V`Mrj zZ$wyHhD=_ELyli@SOD+A61+}5XSnSG00k^g!0+jPzgc2%URYmB-Z6EK^M5_obdgyu zx^%$3@Bj=5fFX~(;L$)a$Vzs{DKWemg*ZU<0jbebU8hUE=OYHQK+#>T&V%Oi+y5Xl zTN;q{JFiRdVsl`7!TFd>Cd}pi>P^`B(kODO>vAbT6mfu#y@#BTLI>*>eg9F@cIeO?iKhIBvRLqcjosHJvy1u|BpG*#NqMVvbj zaK~Lp1MD%i{eSAli(jvPwRG-$CsR_gEALN?yG#0b<>&kEiYnJwT7hrDfKv5}Ijv5V zuuf)Gt*#ko=1j$R@Lng#T4pY>b1*Th&+Ser&a4?pn|z4JCt-X9SU6q98gk*UqyK)W z@mtfP=R6UO#u*IE=Jcx{KQ`-}K8}`cyel)WLvY3&)~heD6V-vI%Ioa>F-Z^Me+L4) zIQ=47{A32IrK{`M&1`_7%j#*MaKRFym+tMH?iu`JVGpgM{XVmxOlEDC1-p#8X2PR@ z=^Ve3>SpYjGgrUCz%N18IaWi`ML(ccSwVMrU+)$-)m;7cgw3(XBL7Ti6Uv6T$^APP zYB+Y_^-Os;D>YSg>^9gY^R>*c-FoHRbbpoA=o-hcS+O)jz-r=OT}taotL`tTmg)Yz zcy3ja$*gwD&MRd;(x^Gx5_8p}Pu2eV4q92}Lvx)1iYo~}+RhcGrHo43g%wwHf3$%@ zO8tI+xL$qcsYSjoO_6^L3TbnapGL=gXl7GfK@fQ6Tvqx~RG=d4VBW73&1!)i&-1J1 zMMXkNxv!$fo_Y3mO;G*^xc?vc>c@ZJ<7b`@uw3_;E9!eD}{v~7#LnU$N=v6 z?lOc+bA%0k1x_+q$m=&u6BIebzO}6YqKkqq*>7hps0<;!&G%=2Zu?QQ5UHB`@UBb5 zlZuBG$j?^V)Y~$0SEkSH$tx~)sIyL?I7F=f!NX#`^k|*{{_F!2{DGccifmeC!TsL_ zZNUWMG$&bk#J$&g<0J?bG#LNn${`PuZZ5v{>SH*u6n-HjXWGf>7e_J9cq*Y=Q)}JB zI<;f2vC553xeTjaX3}$sez(VP!!DXcm3J}Qk*+Z|iV(r~nBq}6+A-VA-+pr>*_b~u zs-m!V=&tj?8=AC?a|=n{Hej`GeIHjuIXub8p;j(8MERa7UFh6JE}4UdKDh4Rym!gP z;l%DWQtN$z?Hzey(H9v8T$?^iMGY$MJQ{eMq6tac#$i0#CdnMh+rA%| z*y>DUZR9SvtrV#)gnn)7e4s2iI{W(5tJT;(MR}Q*{Ds`nq&;Q>$5vWlaW+-uJhbnO z1aVWgi8+SDzw;DGS+|BJ6g?5PGU~AAz#g^LG{i+fNiuzq%wsICHWxKJxTty-rSR@H z<(o_&yAxfmHn%F(S<-FF>+dF>xpVu-QJt(Vqxbo4UzcNPC5+1!1nmt&-{qRJ3#$JW zWNAwhkHkl^LUSi^k5~gn&6xdwC2;y_S z?Ef9fu9m)0v`A-?4{fayw0>zbK%pL5pZjzQm)23zx+OF|k7j5*%I4&63~XzLcEn=4 z`~gZZ@c?hVdZ@5|fKpXFK#3+mRg|}y^s{~{pI{C>0VQmRfUxYyyZz`ph=RKf!SyPx zDUseWalC}T&(0Oes1gp88#nv<=4|AMF+CDD+aOKS;lc1+`5uYVp{h7 zS^=Kvn2`*Xhi@e(`)K$yu@BSOi3il`{YE+0OE(e)q7KqVpNVA~Th#oE9ZRlTCz`Hq zKo|jROt$Nh{3-DmKppAjcUI(=&x&J#0Es_?t!4J4R@0Upu>&5UStG;{b-+F+g(W;Wq!1g{Bhly^aw>7KgV*rQv0 z(TO@|g$av_7U^;@y-1^>RB0O1*y;H7qut0-C>Iu=q;T)jZ!j;+N1W@ErBtu<`xW&7#^X^%B(VJy7#vCh_a_)Ds=sBAjED~dwIow*aE zSAJ)y9ODxfJn5?)LFES>$e7J3vabb&Wj4HcT^Wt@CV2Rp>P{@#v2>hhl_sa@AtN); zSVDf>tpe}q8w!Ja&cYd(9Cwy81Ef6SEta|H!_z$a{-`$t=N6nk^L~Pw z#vaG99eW+s(t~XoS<>}Vp2Q3Sy?3j*$s_RqEf+&#OR`S0sdl>#~o)n^yuw}nBoPNFJ@8TskD;68q_z+p~8Aa zfOu^w#`D)X2HI5?7yWOhMy`nLz(~XC?mgg{Ut$e9PiBt!87L+2M1T+G1Gw^QaI)_{ z(|+(Q@qj?u`q9}7_4X7YrVvQk8oEpA)%?~i8t7LGs~`A&i~h}k3W%IRNED&a=TgVl zU%+t(*T9{5#6(pi<**7=vaJXeU=WMKRGgmWAZ!hUlwe5c{YN4or`fmqe>4JaOj<8d z`sFZ?+BHf6w(kJ3Lj{8&C3fqZt8P2TS=QSF@1U3aJ{ra!odqqEdma0}Bd<^|bjLPV zH&6lpHU@1;G;`uzu6JB+z6d9(7>-~cZE0?!$4vYo@NFAEgGMJrL{OK&WV@w0B0`|E ze2~T4_Vx;8sY~ttBM`cg*T%txC0JOt&Wp~zHQnjj9%0s48fdq<-Ar~6xltEHp$V|j zfpQog+CjfE5QsqT<{Ks-DBWpGv0eFAU;T*x!+?ntMo*EGr>s3NdJXN++r8=1#_+g< z(&!ZXkHsAnBVg=0de$F!MHGH>FZ2Her3AcgfSoVw+T5bCf$$QTU20%X{NXj*>T%Zp zI>f+hF9=li6153CcEEJ5QT3~1S`g@u-RpW*sH@K*K1yhjw|CJ+d68-f9L9n2e5Rrx zw+Lg5FIWMc_!=Rb&a;x&yka+}7?zL6uI|+nZ&sUc>bGi+oZa0W;81Ur%jHt170l+< zQF{vP*crvkZ3A=mZ7;bp(_idbTG>1uy9IAwy?qF5FG109S?K#()Walh7Urze0yIQE zSt#_Mu7LQj=x;O;mF_%sxt>+)S7rti67rukA)X<85Mpzlw_}FBj(&I7+xEQ2CI3E?`nfz_ zM{{ij$ZVPAb>tP6ym*ZNHQNpKc1pxk|8A~#R}rj#S7rIj$XWMGerUY^z>a9wD=jfY zs12{!@c=o=Y(gSY45iQC_4~zY$rw+czum_TZr!Jr=C0OmeWxP-Toq$o6Kf|VRXIEK(*&y1Y1(w!5{## z^DS^VpW|8u8!Yo(WeO8Pjlc7b)MX;lOWz12<rO%9lvTWSxOivc>WH3IVg_LB=L@St} zI*g2$E@k}!m;3rQTC=^2-HqL+N)sSc+yB7r!}wtIdCMlC|DhNs8ca&jwFy?qzU?&e zsLOh@*a>?OLZDZvLsTH_hEW=>A4WgPwO(S3yW^?~I%RsfZwvP$XeVrpY`lKV6&qGY z)Gf+8MAM9izi9}EiMCJK74~Hgp#j~na_<;3f}XJj3#RwQR3l}&YtwsdQ*jJ7XWJAk zQ_Tv4(-8nqqRh4c>)*rLuPrp01Sed_IR>l^C0mUgxZ=y+y`?zU=Re*|sHF~BCR1ie zFH?_yp<8Sv3J4IZ8P4%cFJjkIH@*iZhj2V?(uB z{s7#IP9wlT;&U+@f9tkF6+xe`gmHUx146Gj$xUZi*%e(=0kP>6kQ$p7S90(w8@s}< zlEZ&@1QO;h!=F2>L(DTTY572x_8s0|32FF9f9#t$xAaQ?I z-K*ZH9lw4}O6l>uvF6{CWH09XqaQNj;21i}KKh6~=7^S?aND;1d=XOTiGhJwv|wlk zxUcg~A3P1*w}rTfY&i$|ra)1Q*Hmo>5LD$r4Gr59c>|IN_bm2;sxSq6@aZ z(BDkTkOF2zJp^#iV)eUrPEdcOd#M(L%@s9A`LD5CR%YPi4m173g_CmQ_*VBU;MZ@!Qy9x1BW7uiBjH2WU9C^HD>4sV|*Qn3Dfk^4ga znMQPO&KH!k#+RrfHaccw?%kiga-JWzB6i~>S?HuIvQ_#n3z8urM=H_AKO|rYz_*T8 zZI=bQe&eyeF$3U=tdxQnK>*7Ta(9hclpzj>X}vpaS>BFcB^-L*&X;^AhebKI&-1H(@EnN)L+ z9S^zy->MA$v!n0VUcWCNN#Fb1*}QUyKsm@R*u@{tX12n3%?Ga!$Vp_Z4bKR>ZI=&iguMUf(APhag zZ{kh<;??JYe_&}(vdmZqw1p>lX7oVW7Z{Yk&e};Yw6wCHztb8=LfIb(-`XyIZotv z{R>#&7FJa&FkB@L+P0)N6349jS9E`cv9f!XK_nE>!)E(nru=&w-b)CH_5)$c?L){V zWOPOa5E!<>070o;aSQNWBJif}ARtmIcS7pU9C`>5AkcrzQ8cij`U;#ATQVoxR!kE0 z=ME1(4ovt4KHUWcPe2Gpw}`=YHkAX~vylL!n~u4_HpX~1h?-cSzvdU2z(&AX`*etf z@^_0Kl>fcYUp~qKgj)>rV9`;(-TN3fvzP& z0t(NMa5JIU_l4gsjNVmx4e;|D#f~q}RaNs()AoL|Em2-zq?7fGLI=EV5v|d0r~h=yP47zG>QTWx9W+IYahZ83Y9%3rYvyPwTW~` z7^fJ%m%WR1v{fE`^4EjpuK9&LzmlwLA+OF;`#udK^7Hl(&iDLENs5X4d%+Bs(?2hN z)HB}hhFdE%Tv|UI&w1rsJkfoPrK|iWG5+txC~C}9zB_eDPUxR?wgiFUK*sN9kA~Za zD`6LVhwmVRco!KJ3>^s#Hl_i9s69PX<7r7nM{bdp<<|TU;Qbj8`xlXb5ph5@0)C~X57_Xe+f``Ivf`*?LZMucH-u-t5|sFB)CYM z>-Q(WF;PLvI7XkH@x;rEJfTUYD9H+mo}%LrGEa+OPpDfN$|}6A`cE6oz09vtaV6#R z3ZiAYQ#=1SvgUY@*OOXxI)G$0>sSZWikbYhf{7!#*UvvDsyPlBeaVG4N`q6TGPI`}o z%OdDXS{2d^5M$rYcaEOpUTAMbJ?!yi-7WE1L&$0yw9D@GIPvSRGP3rg%5sN%NrB`Q zpeMSq0Tz+Shm;$-%q*DXK@BPh$b0!<657jp2vi3 zVU1rKL3XzgT)P-et*U4)_2d5U-Jc3@u0+bV-s+cP-;L#CKU88VKH8lPV3eh!Dc_Y1 zH$aq5v#JE|e!?rAM5W$cd7a%LBi6M1RPu8$@x#gDU&UPBe1|+43%>k~_q%)q&DF%w zXPMJa@oE7)GTxm#$T2y({8Rhl(hUhz@*tcx;c~;~h>4Eelg**){51U`xTCb~kz>Jb z^;ge88U4qZ>la9g{ly&O>wb zY@W2B_j%Ac?*KqqtY20yYQOC^cy;E|`<>YwJon)ASDVX7dsRPbKRu?0oCQYa!jO$? z{DN|=`h(R&2wN>FL&(J_R9E^L%zzu8Jq;L`KS5-|{+9c)WSx#o7xfwflq{?8rzHx*h$=hpo6Hxavgnw%#Uy_W#fTDU-{AE9^>JnUa3!z7@sa$h5}^=A{IRvw zKnawx`CFG-K$jYV{NuB8yd{&k4rU49q~@nN%wYLIK9QICdYr4C+Oa$6`&I9fw5-VY zme((31zo)}&4{(W)#WrZ`*PA%5{$k5zEjglH0g9XA68_?PI=K}TxpD(s$R>4s(M*E zXX$|`dWq7b)>CGUn2+Troc<*?d9os2$;S~Z?Put>ucCMKN&il`5M?hJ(dX|IRl!xb zQcyfX8t?}Zv^Esbht;AfZym?B50BOQ*9o_9<-R_l`^*$!cy5(gCiU%-)u=X+Qrv-* z@nQm#*l8mIt-qV_tAW-cPWXuWm4lEpqr)ONzEH!KCWJ7akepYoTQf{pLqfqr^vg8->RBTBR6~O=w)P zSzT}1LB^!=t+STib#zZ!_HEp9|BBLn1rk#%CKk^)7#TNy|NV@MXmB`yVd(VtQ%q|* zbSnPxCEv9xSHF|Gs`>GzevE;FX2(BQp8j%Yq8z`zoH?w|UwqHzd(TqibtS^B^fa>( zAzRzhUTB{Q^adbq?$c)o(hGNm?-zZY^$~u`L zgOV@8AGsH&#!|!T%GrJoUF@&Cl!>{)L@KRb$+>$Ie~EKS!~6! zc%#hNGv;2qJbPQy7@;BHG)3=L83$WA=rz{m=4wlbjEX*wIafnaprm|n`xpCeP6aZd z7X+%OZph}Y6BCoiN8kRx#c71#{gk;-yP`H~Lvk-U)`71LB~SVgpc}>^^nyba7APjlW~}vgTtxz}iPR;5 z&*=WzWgV?X*l@QI9dd0-5%AVGf?dppYH#$G z17tvo;{b`_>&7=75TeFMr=aJ@NfFKy+_~@btdNx$VC419GjK{Ow2aCR=jWU1^=ne$ z5AV@t->buh?gFu`kKOkC{=pg>9w%SMF?615SU*@3ArO;13!ZURpV4N{vOVmAe=>VR zH(b?)b2m6gW@PU$rl&(iKc5N z6+zKrLN^8E&ulMP222hj8N!jDiXUy!e)p!!pHjIjkhsUP1{Z(iYe;}{$dfvqQO)1+i+i25>ZMe>9>6_4p*gI2u;dmpgSd*lvC1IxK=eny1 z7tDgTZ3BTC<*-(J4}M0-vdO`K7r*encI%_+%;f1(%FD>LFUlQULN#Lr#Xz`h@Vo6YkhbxI5f|M}NL6PF_N5djNd%qdJcuXYzj^&(Yvft)E zEZfhl;^Q;X!$96?-l!b4kjl?1LXG^g8Vo!Qgs1KRlOMOOJHX3p_q>66owxH)CFjoV|8V@8 zw7%$Uz%9)~PsN5s(WZq@Pu-PRA~;UnSuGeh;SrKyXj40Rl(UpE;ziMO;j^Iv>$j}; zN^OBF%}LFWx5F;V2h_EYrH|3=2Lg$UuypilPtV^DYeSELwEy733UYk(3$AfjG<3>` zh=M${^L*2ac%DX_bC|Qjqebtv=I7!fUr_YXVGbmyj72W!7tfs52OM6Z{GGS9{ z?v-m6`gZ*M|A&X_EPegkwCvB_W9HAzv-+B|Bbuax+;sM7^05G5a`f@I)aQ&AD!^X8 zdZeUT%xX!6xz?z5wTm=26+BJnY`C)5+W(i&J%cJHLWk;sn3v5yMS{ewis@%Q`o!^R zWl>>-xkh>g{gCTdC211Z;Hx=~8Pg;gz95gck_hPYUB|DaUvrX~0OjlXXu2y1?k?Vj zn^?ot?*w{cD`2HL;cM=65_sUInBG*ZuJ9u?WB-#+ZI2OpWZrNg?}-EDH_s5Km`mCl zIDic#`bRWJv+4cw+MBe4=;aCTr;~hxpT?}KWzP^FnL9vR-%~swlXM7>BOJq+_*WgCAZQ-}vEw=V=)1iOCORAA zb;b*6;$M&cWGbMIx1wN+xbG4mTfb5l3_ zw^-RSEk zhFc(vlxPY(%;1rAYc{l-(h=auF*P1DpaMx^4xQs$zCIWa6)cuRYm#56T~mh1wV_;5 zgiFotPnH#Gnqc;ZO*%Db&EI*b2OIAP8Kw&b6d3o$`zlOFQ{U4*U~kAZog^mMi*-`r zQs&FO%FPKg*9q`!TLzW{Cfb7G+%S{gFeCngf0I50{C7K`76FA$KM6_qt}t zoV^}!>R`?-S0Kwttl?dJtUGIrkmU{Ok2~#-=Me(p%CX<9rhf)MFfiZieZNb&9fAz# z-79l|*%HBoiQtIHyE)`ZH5KwIRwx$^wr z$?t-6u#Be!U@4pO!GlHILTQ!EI3*ddQWEty@$;T8BbH=|Tn3(LVIudS+ty}mFjwT& z0@ab!>myD4hm57pVGi{)Q)G_S=|l6o_NDHJ!}|Y$`0vU}E=>oR^|3a`TgmKs9_3Eo z&W;5*{e|rsws=eUqMuzR0S(dvUUfO7eg~3GoVRj+O4F%Q2Z-@0`C9tzuY7ipy_=nu zkp{JUXIqpc%a?(JwLD3%s1+A=?F=UBPk;Q<+21Voo&$m^q%{B~x{VRo zN9$HWLtBqp7Xha(nqTCSXT6>!6Z{?)Lmr~lt>citVPXS7zW)WTSrp?{wxzfJ#FCYs+LLeNwwuPg3n z2DJa1cGNH9&`JPcMtmENGfA1J@H66Dwf5BN6zWaX(lD--_Z{j-z1j4H>m0q5$WI z%*lY9x=Qhlq$`ew3o6zO;`Q(Cd@K->lf+Qy9M`E4xU=6oX67Ks)rfF%)+~?p*G!HE z$irNCTU!;rpd0|_qxGhxJ7CO@wTDd`xOayO)(fOA|aH(_*;1kg~@q>n`cofnz#&31ex75CEY4m3?v@1v|brAQE{ue|N zIy%AC@d@q)_yiZnM8toO{sz~Oz9?crL1(z|@7l4j?fuaPsgklPNT}|s8k#$GOyig?GXHN@RRyiGNd=XSV{3!1 z(=)on`M(nU7WRU#_Cr|!o>r(`g8p-A7qMLsj&=~os~9aq&*#qQqIuQU4<~xY(xryQ zY9RmGk#Q@sgT~3Bj0GujLiZV2zbLIAlv{Mt>|k~d7d`7H3UM%Qcl z?Gb8Io4RAy@+-UO+Dh6RozsuUF=%Zi1eaM0{zTW1dFFV;y*8dQY1-5+U(p@ZzN56; z<3BU0(!O}}Wz?3SeUa*q(72(C#%Hnpv6$(P6)G(6+9#%EZH_b{!W7MCjnhK-pq9*ot8f-$ukELEvzluG4%Xz$)V zdUonRkumUp=5UIO@r)z$oKW!_b1}^ccP>2i^%*jBmSX=Rsk}9ykgq4DlXG>7nu`J zQ$BRqVo!C6wqz>)w1OgfE0m`d)IKDpuvE3j)eDaGj?~$Yt7Fb1a6BXx+jf1`nj0S< zLFdMeuuuQ23|ZQzC}gfR^9gi`es+5#&555#PG8|lp+RnSN-2&u(y%RZiV}rZEXiY) z1l9kbb0DF+sY{Gy?1$H&Xqk>*tS9rKLdGHdNyXflwGp5QQgdBI*&y&6ooIbRb0zFq z@w?D(IM0p>s<*eN%KD`7D&=O6pH;^0_g}tL zx3di1)0Hsu04H4$NIX>1hd13qAfxQC!CoCZ=g=Io-u znIiR5ShH9yRq1F>0((4{SmSw4>e^Tm|Lya=$$0fArHrnJ?r`R`qpK-e=Se0+!i(-J zDb8>0;C<*U&N=ZP9vTLbVXjSw?;t)JBoX3G>Z`acnKSrX79?sF&6kHy+`V^B{0}jd z8K!zWl9A?-p8#o2h5x3`_?uD>drLWZ8Aq(5=<+4i5tK8b=H9Z4V<>X9JwBi;g6(b; zeT@7ZXvH69-!$VQWe@);+#Scz&Hgzx8)9R0v>O}+_ktw;8PD1dglhNgzQ}8jfF~VW3h5&?%?0M!WD`<4b#R0x6QyuXwRPAY zpu<0u@jAcMuRL(-{3lX6|D{9D>ja&%^^m1&>w7<8tWsL-@n_5UI}JDcVxc;BVp$kk z=TYE=1L^S203A}D({=lIJIH>4?%Ll+UX1v4@aIws;{!YBp2G44z;Q;rz;yjty+d68 z=ENVz6wwYciE9w6i`>DQ($BtwMQN;a=#bK=Lv4aAHinK}Zv(7%fCukBcz#eou@3@D z=lLC?{V4Kx(z5JF2Xry&0of?`ENMpDyCngMwg|Tvi)ul*M2ge zv1|WEUwUIWdVlvVTg#Q*=3Dlgfo$22b~tffE8zUpm)>5E-ozXH%KjIZrGDXm?lAY` zsaMM4mhCEjd)4RLYt}b<*QR?1vDe4yCAgQNyES9<82wrM}pgW+2ntWms#7paZ$3EI>*g#I6MEt zUx;&6d%UuYAFE|Mon@+oG6%Yp86uRabSYCUl(}=ev&>MTOqC0r;bs|{i|!CurhzGA zSial{w&{qM65ntEcm*>OhIz{cn19E28xwfv5ivdE^d1a-w;JX2LN^&l0(8Gp2hmh? zB)vCNuYR=G_OXrI={zeTb~qY)2ba+^LW+N4uj3!@i+`#w{t*^_YLlO|lfPHV#*dI( z3TH%-hpt??rxu-b#Vt$QwzCJg*xA2SW~ELwYi_O}HLJT47T+@h`OeCT?@f^J_g(nX z{lX7-@^6VFzV8pA7Ap7__1$y4uusqA1?Q205#8L2pm`oz^dF;lG|1fa{`LGj5Q;@r z<@}OQEA2IjG6iSbdYu_FSKT14x0T>SSgW|mtd(ju*W33M_FQxJPA6a5?wCTVPf^N+ zf^*(^j+=Kb-s#+b&o}qq)YdASNqr<;T6_GXGSNqJ?@Th66aUC*N|$D@b?6(K8>h#2 zXcg*73cX9#p4wTKV$MLFbWTxrXtp1x?JkO8r-oWj#BjXjliTU=$z8FN*8RvjlTE0$ zd~>buyExkP{Pnt>ar69jmv)-^kwu^O^&EHv zF7*H8Lw{-@{bopiyM=!$L!ZWGDGOItf*aX@*Bt&z z4f4c{Q+Mwru}9Ti^{N5)e_#J34z8(uJr%cjZQ05D+`M`1FYsE5GrdqwA5%^{L{7F` zyK{fpXI{%g>+qYLdEJh6cwGoyk&klabw>!fi2t|rBR*%o4(#{@=gGzh z>tw^uGEDjjz;QKnEbMQM1#+*Hj=809%=J;*8?KDRNA&?Xb19QS1C2d|>41;;_m^}R zB$dn=iyMN3FxmyqyW>Lc4yuppeWH)KzWS&wpg!jM=mYN=J`(3ar#?`xbejj=WPK$? zaWXp_rc2pR%Pjk83S-|;tiKWHtJvbiHmpkG095OAPn2LkQ-X~EDC58$x`usP z%8Zr!ApIlrWaF0y+O{adX`HfyBH3!&>JNY9@XK3q5ftm%kHlEURO)q87wDzvb<9bp zs0+Kd+L7calDi-Nin5#`EmiMGer}&VJ?q2-d89c^uQ)K-`g6lE*ae5%NhO163&QmO z)O`tjRmIi#+-w#Ocojd2u znKNh3oH+}^d-G#&9X=<#KSjOpg6t)MP*wpH`!4SvALq{@r*4ur2);HrU)SKxtqz`| z_Q-;A%4sI|b$idgm-*;pcO5?GU61tEWW7BZ73Z|Y@+Jdrd!KjsAow|MuKAqgqQpk$ zUFBxq_^hntB?EZr&-^yB(g+@Fzksm|3e+e97- zEPRm{nvmZNs~jMY-!;+YkpiuRUJh$KRhue0C(896I*aila-6FF3CiyVJpG&wNJEo!AaLYM7;$6&;A8s_ zUcB0!XVvHvvKykQAi5!K07z@&3eJ_Qz4F>m?LlPUU^uCZw7uzboaeSa=K05;^E|f& z%=0e~$UL|C=K0b6KF@7|^Nef+DN91A_J?_YZY?GtF>SoTBV)Sd&NgImIdzTjKjd*Tgwa+zs{&|ia(^a^ROy1Y7N;%Y@k&R92XmbPeG+D^@!y}y z^R4zWkL^s$ikt7TuiqPJF%7s)IrmctGwZ`p7 z6KE461++|$9SF5d#Q&#j3fZfy@At0Ho;sPG% z06oiWda}KLFvB^sDXwF_y{#S|uygH)0@wBcm*jQW)S0BjT=${s7-iCUrO>AZ&bvJ_ z#Emm@?m-1?zGb$}myN^F#W+H89MQFYdpy<*KwfBNTv%A1ydd%-;8=uw;T1b+eay&w zZ0{%4YxmE7f@>tT)?4o;e=iA5#Qwr}gV}ctzIA|iBRyvAz+kR#Aqksbl_nff&mdF1 zs3ssJ9o(eh-Zy%!+n>`}V@K_C#JSV?&VZhJJ8OCq@rafAu!Vcaikdq@>OoBeTrcC+ z78fItPx9fqp2+&CKOp;!kk`jn+436aY1&%1EytEu{R89`VgXs?ij-V=XVKorPhERp z_Hmq_*=xOisa|=8{EXw$Tzbq|YY(g*fPUO_*Zybtp(8$*t##M;DTGf8Ig}g-T%fKy z*vLX;cqlAo_N@%BBRu>vS!E+}T8G?ry0ptGikKRXdmQAcelpJt)xa*h4)|%c*Ip-Z z`Po``O_t(t39^drJw6zV@|(48{mzL)pOAwhT@|EF0BJq0Re<$`M@AKb)x!79y@BJl zHzOY|`P|n$c%5|G$nMe zv?IRrkOH`j@!(Rw&L1P}#RYffzVM-v{;lis>DfVNBK!W;)=tW7doSqm8q-oXf#!4p z)11D_>tHoVT@&>Bqt?0ab&D3b6OXY0GaX!}H_)>)iWi-{;5LKF^P>kXIGXkSpsb z!y|MC#{<@H@R<_NWsBCi^B_5MdsXpjduUA)XE0H5Z%UQB;+{BT!tZ#e@`P`n0;@Qj z_Y%U>O!879F~&j&2DexXn!EQ*Jm)!fGM+Ib&f|$uUs~*e8B%n$^(CNd#1R285Va<5 zGjHZuIRs@P2Ve_b(<|u5XxotaK+JU9ajyOQI=9`znPbOGJK)n&#$ga&_oiHDD@vkT zOG%g{^-*QuWyF5h;r^nfBt#-?miZIPf1}`I#D0smlo%wjmHlM$FT(?4wPE%q8ns#& zWfBqYb2M4|a%4!PDN=P|BsI|BybO_j;0N_@278PH=MsXRMx_Kjjmijm8pQ~D8dVUu zmFYHjKuvCT7Cu6Y+#I<#@@V8)?aj!l$PVysV(X%$w&XkwZfHu_&+!sMP*>Z;;Ewmm z77>r?T>;*ccwJvsH>mrmdA%S*JU2l#(X7mJu*IPrfUI}hHJy0)btq*8Ac^{<{y?(F znm;^EENwQ!YaNe@r6lta^pAQ+WWBpKm8SX+viHW6JZT}@cz-&{Y!vqo!q!%?-Y*yJ zI9{g(Bd_)$ykT6HfmUq|#`La=8wjia0fE)xf`$BW@OrllcJqUV$jBcEJIIo4x^4!9M}dH$LtM6w{kQMO{ssL+ z|46$j2b^}ecK!2q%6hl#Djs&SNZ1PQ`_4H#ZNJeJZgb=+cg)#&>)rmq#r9v}>tFW0 zKCb-vFIw-f)tp}*IrH1`o;Ex8yGz%5eLL#N3tE*H<2erbnaO-G32VOFx<6{FD14Cv z=>-k?a>n6{ng#D4o*MUBG(!iM{gvp6tc=5Jy2*-A{X8a63L1PmK%5k%cAzf zA@{7vyUJRDjsiCa2M-kWnl1UYou^XxGBmUQmus*WI(4@uT8I7&y14e^6Jv%bZEe8{oCBufUap-BL?Fr~<^b*oQuLkJwGeA#pU0T3?d%fE( z>An}hyS>A_XC!_W65dO-{48YqSot-9S{=sm9M4n^nfd_#22T!OM{USxmHYz#Wnv>GdW#rs4&^DlRv>CNlicYaRI#GQAW^$Vg4VSg4iv3heY8e7eAotmUKm$487m>B^2;v&u`wImzS_>6 z(~qk=UPa}+ajX!b`!M|0x_M#325*jETl46f7g9y@0>WqW|1Qo8;&9C8goDs8-8dk< zPuS1&Mp#I1g!9!6ew(WkPL)=wv=7*|%senFd@v>dt#{AwIUD>oS0`-|UbPRNm(Ix| z=`8SIG=ASZ&li6#^Ng}4c%HA>uk(z#r0jV{S#$dazkLAns6sAKABUUM%eTdk=eP9(K)oe>Lj>2QuVB6=#4I=6ai$^DyWz)vbCdkB^*3N<%Fn zN)m|#(}Gs_Mr7{SE2E7B=KN{RD|esX;I>1%bIP{vM}Nt5uU|UWVeKW>xoAn|wqTE-xnzlV9hGe?7T@|l@a^^C+kc}w=aaMVe7)>Zz1VzbZS>dL~jSQlN^2sV27w_Wxx>VOPqPr;@sFqq_{&!~;w`i=+*WC=tT+x=S} z8YBKKo_TZP@5DPj(Ty$Q)f<4Y3snS)QKV13D`5Bfq41*xQ%}P9_8^vpB~UA ztJZ+fl}_F0k6rkavXk%s*oD(Jy8ZreUBp$c1aZ<##G6ea9fa*0YInd-J?6N9Pzxs{ z?K$Uebl=}Q{T8d(dH{)wIg{o26xIbhFT$x$oKZz(1iwvE?f2Za)2M1Fu>GEEjuGiQ z`JIja*sqZ?$`)~;4je+-6(PRHXe1x*=*^Tb~9O&|g5kgX6WfB8%QYU@e{!{2aL`0MQNJNu~TP}{j z=CD~|T+VA7-RDS6;Q{q){%fR;`h=>Q)Mb8qB=6Cj!Xb2-^Wk?Lvt~kuvbou(qnhru z&!KEwzR`Pj6;`pqZmXD> zM1n$A5fqxIC#0|6?B^cauTYl_Z{GwXqZJ^`p!6!{kZ~*N-7IhlPnT9h-V#Uj|E|fL=#!TOH-L zx*SP{gsm>ecSY#6E+lMqRMuQNiVP1SZHKTi&hyx2PZG9SWpzdSX(NTKD}r+kw8vJ% z$ZGT0VGS~tR0FT5XJvdS&|nFV4+Rv(nZ#WxOPVIUzu9)q7TDi#C{@C>!|7JXZ3SRsS-N zsfAJ5UKN=V$euzth8!ygZ`pil%e2GfJKG0v_9k!4jWee#z?U%x9!uS1houhtI%ktV zN4z*6*g4{{E^7BCk>gv{MNNphs8H<`?tIJ!aW{lDQNM%ju?}ZK)ZygqbJJA&80#I{ zU+r11Q6?|ns`(|cE?v&ORgPgl!41ffq&6yUKBA0gZ)KBs8=L4>$OU6$^hDLd2AkUw z`Z#?AY0`(oD%E?e#Uqbp>#ylHN?OVWk-Cs*cN9s4n{`2%Ew?8%LXOnz7HHYQXo>6P< zv94fy0&3H?9z77f!Pw6UTc;K`sUfJ+mXmUKBFTJ!y0y?u7=>li6o}WIe0{od&Dj zOq+<>EaBObfL41p`F->e52RIOKVk%Q8LRs-LRlT#?Da?Rd#^e7es{f2wMVGCPE~18 zmzDh(S3l0=VkDhe=kx=w-R!=DGt}sVe9rd`0~mX??#2M&AG+Cn)^Wmjo-4o`0HjWp z#^jCl>`1U^)MqAV=7{X_SLADIx8S*&Lc?HBdiShlI;V>x=4#+*>KCv0HkJyYD z)?Z3lhSl3bUl4fxHb9=(zGjG_e}!7E6&m8bzY&EqK&u|}RA2^7s&$da^>OgFS`nua zxxStckSBilHii6{4B1Cac*Gcy7`^TEZC$z9UlVhZtdWY|Qs5N>x_|3Hi#Y4HtlNuk z8|AlnX4tmbn?pgIKiDY9OZ--&+gilW4=eb6!%J*rzxAC$R!DyVnUkOOIWyp#HPLs@ zYUJmvflM~Pq%0CBySuH0TviJ>Rs7V=;Oxao?136QiT%6=_h>KvhU~Y;ysrk&W=PlA zmg4EUdw`%TuK(&S?zod+zm`HiZ1x>@aBPC|^I0YR+1{_CQ}piaf>Nr*DcT3qTl{su z1&4#}>))*X%7IYfevNMXiDb6;&ynvl4e#H)rtp-I(_d!PmVL_;|6`sj?T@ZJ@r_&d zSDtv)yUY_W`agV&dtY_-FX}~QVkXZA;hl^A0dOv0JKfh~0uFweS>?=2f7F)zdFgXS z<)!~|U|xEp{RuvL*?C>>D=M$+Ju0v3Nshd({(4R?yXr4?Zt?rQiris+`Muh8ol&=( zwZ$EKW~jb`a|hVIPx-x#DdcQgRiR*<$zTli!1!_&@7_gi!ZyYM%h<3XLJ{;1kSkSO z8+|GYIYHq6Djo6*WL7)tCU;1J`&<6mkqRJ}5y2#fL z^R((l;kPa_-^85xRG2IUucJyGdiC+1S$V)|Uep?J;Nhd)CZwHN4DB{K-{#rQnd9M1h0EfS`Vo8(OxewvbyJwqRw$CPO4X6LdzVe`2^YXpa>&&-@i6{wY z|3bc=Yl-oo2KbJv<{$jYoFi~dl1#1Bm*$2oG9RO=FZ*>@{o5)k{6!|9zX*kVjO~1{ z-yh?IKOc1q+RkQ@sg}H`;rR@{y>*L-U2UaN(i#erNF(GPgnK~5cfc@rJ>1D{GW$%Yf_V#kwyVfClf#2M+ zw=db$*ZaQqG<+-9q@ynS4cF(BXCn%%qkEdX@{GszT#K@5sgzZ4mVc{ASydd*sBa4F z!SHm}>A0q|zBPTr^|+=FQ){|d__y}pnqP0k1N$83lM^RgfNSuAX8guIAMqmQxHwt- zt8XEHuQfm>ICTk|CiO|Lo?*Wm<$4Ey?(D%nHyYv$Zoj+d;^24gK1ZuQ{cGeSwnl~~ zVU4VJtP%KjZ}he=UccyTJx$(g{otoX=m++iTOrrn3by99|No;O1g_a*9c$L+Geh#3 zOFt(2Z?Cl1sn{#=`PDuCeag?rA2U8~=X|{JGw?Bv>wb0qIfc_g=IM5=*Ng0FnP zjGhR!gd(KQSU?%wGGM!1Ks%Z$!wZkLluPp7qsFJ<5qlu%P>;P@M5@BGh8U(!fjx=(ZhyUi5re#8pDNAr-TT*%K9zt zd;f^SCCY0Uaf=M6OT@lEF`FNC+c^T61YiS{{XL-umczyl_7K3JWO(bbH-9VaEy zcQwgyne4&k`4LF1VzC;&*I0##ykX}azAZL4y$QI9dyr9E-8SkVUWx|r(vZF6CGaYW z%maK{G{s48$HRaJ&}iaTe?C4N4?ng17!BWRj0Am#Ya%OF#^&%h<6&6)fY&K``Ka%; zzb|~02>~vSmCyJnD*5P}Ma)&uy5E<%5_mXPJ)0}PocwAM|M=Xwms@-< zhq?++o1(5lM|!gXCsmUqwWImj*dzcM;z3MOrH$a{dlTy|kT~}qGh~0z{|ahoEo@dg zw9Uo}!_c8RT25y@WY0~Sy3jK)2R8f>w#W^7clMm5WyJX#!2G+6Z=z~n>twynWWCtB z2%Lva{_Q3`m)>g3H4h#pcI_9nZficTmjPOrQ!f@dBA%AFNYJxJW+t{86%PIA&~r?# zU+2v2hU7-2Q^NaezlZwf#p--QrTzo9i2-sv9rfD`$ZFTwu+6aJEOQOp4Y`N#5u3Wx z26W|C#PN?F9Jg~Ei`4_adI#!w-<3Mv44)o2yFeSw(Pry`Tb(VYeMsJ(fd~JTR)!nAc`gs`J;9O^J@$_T9rTDs7 z`{I3Dy?w0kt&M))_Ta+gl()?l7vlQ4U7go(EWhx=5N82b4T4{96*;w`jV{};-AQZJ zDsgurt*n=vwS#AH%gP*b`@>cO_+NdYcZ?7E*47_;d84G;CeqwW9$N8W^R-nx!AtRyK<)+24dXN#xlBDr6 zk7&r^Zf0PXdX&j92QO{)*Xnpy=1Q_mz@(hiY77(e8lV@G5056XqR@6G}7pUtr2}brurmCclNj%yU%>*K&M^+`2)v;=AtFCO>8fM$k|G*A0h2c zS{b=K-jB3@>3R11rC(aCT^+B~s(;Yk|4j#t+ zjj}Z*X(J}`U>b2{--G$=xCW#r^fe(Z`l{8^Mwj_a${qXkzpG5ndr2}ejh-v_B{k{I z1*f#1C%tv0-rwr}2JVpWO%i#7ukmk){040?Yr9R!pJ}7p{C!I3#kNmLNsr^b^*<@o z^Zx#;#h#xe^F|Ap;72GV!_P{^?A;Xw>u!?#yP?PW7O#wEMRt1n*I{9V!=C$?p)H%_dMyv|#5& zVm$*99R_@G{%GZs!}P95iOFNimom&AW;|O<{=s!HDGRn?9v9bFCOc3Cd}WU#w9W6= z1JI%+rfQ2gmTvR=J_&4X93fV`#PO|v4LzQ8-fx@R{Pn+<%9_>KW}1{Tym#WDKy5f~D4`-$*}hf{z@0kPT?_O4Pk3 zn*+x9Mik@E#U9ZXK!00Sh}NxI7-faZ&j#Nfwar`4#P=GSo{M_;Qv$kP=xMx-NytkzHkQkxvYJcW7UreU?vy6~w z%OxFAE_H1~KaJ(G{0bA32?<&Dh~7ZuSK z@x5F5m7smW#&?~P8#cawbK(0?RLIpHYf(~U2K<|5=PUf~w!PmA{(D>Tdx1{GUpV{0 zoWng`Qd`1pu_*7+C+oUx{#pli{GZ#l^1955y(8+51yugPLZ%1i9s9Ufu2E%==GJWu z{G<_=1>5{F7k*sw<1xgI2f}0c4DsQ!(uYr1_Sb;V+iv*4x9urk z?%LO72JszHx4*ol{N-zjz{8(2SKuM+#jiviaRVzv4qd?G=>v{OOCNZMxp{d%^UEvy zfnSzy^XIw8$PK^=&Y6ZQizvzp#1kT>{qeQ|wVG_4kyk#ldFA76-nwDIu_R)SM0P}P zd_}b{zJkw}dp2>0))kE%2+Br>SZR2g|pNujyZM8A&sV~V1`5Ad{dR#{u z)9wBm?)!tr{S=KOiCSGq5Z!reC8+$NGj^qFyE{H^U+AnVB=bE^NiJL&Na+uqKJl6D ze%)f{NZd^x5xWUH4{LmtVZ=d?`OBnR2O7N{_Z52&mtXTe-2TFb-MrVZn*(9D?R7s* zu6}T@23tYYwO5NZiM)5@cGThe-mf1{S}wjlcDpxc6L6Q9w=$B5H<(b>70w}EZY4?! z-|z!rgKcFRg?nuXI-IoKUuzICwKXt^g8y|=m*-}Pp?g?i{VR?&_IJv5zs`yl4*Sb4 z>o-x0c+c`*-|o)?JueWKU-)o=lcFvo&UhvhCL!@;kGP#t^jH_>8SBHYv7Wo#uNymO z+48^eB3;gDOe72S8UtM7M;{;tXO-Maxa$F&YT?ri&EKwFf7 zz?ayT`%i55=Osd!CuJHznFf2YrrU6~6~yUb7f!niwta}V#acmhhrc%Q`Hosp`Eyo5 zR@rrtjG8p z=&yf4e9t~?U-3m7h_%DVkKD=uZ7_#JhP9dEXydLY_&SgBstpMZ%wOv;bEu5@BJ#Ng z_tTM3#`-LkH#7gL4ZW(xLn=me71~$*_B^ykh|I`Cc_u{A2SM)O7)O17Z5q-R>^zE*Xig;bS!|i)pEM7rXX!jjA8=srL zAUd`6L8m*vKsvQK=!Ej@o_&;O-rA=7N9M}NWY#a@cmIC`ewX?1Ls{_fKFTv+ZPxuF zb7f?M9#``AcmEOiUGBpV=FEcDQp;+I6gKebM$FzS zU$CC|TRYtO<@O%0&r)05#trfL=NCq&!gbeIeW7%+aYJ8%^*h{kEA!RQ%g4D1ALq)` zH`Z5NZP)7Cd4OQ4T8j=?p-&D3ELf4Ux}O79)hF&cFMA8i420#Zbtq)Z!Jqi=-tyIx z3Dh7JZ|IIYV^7Vd)$aNjhaEtes&0`7=HZ|CYl%ByN~^M~hEP7(W*}^L^%R6V<$#T~ zMcaTCTUKM;7-5&~WH2vw=_)WfmF?b9o#1t$lQ4+R*tp;t2I97lx6oVlY66*~ygqRv zlawP1xGpG5RSs0gSBCSeMonZp#ayVh^i{1+6K$z%%X56Qw)NCqIiZ!V?8O|ZD#s3^ z!C(*FMUEh)Z}`UDqQ!`7w4BnS{x8}hFZ{%v_mHZ7l^*DdH%us+VMZuI3Ri&A>NBe# z@0>hmee$1cCqx|YU^pJP4>-0kY3j$%Aeq|Ff*r^^woit{J{cmzm96jBpX{4G6Tg47 z=}5LD=P=%aujX#mK<9guA!mzCXnEJ)RfVe&Vf9>-x&n z2jacI*G`yIrT;*6lMJgY$F^sB!wFcb$hZZ@YNR8{UcR{6zRx z%qG7wI?}t0uI#+>td_HOv)?{77S_Xku<5zSJ^C7qnR-CpR&|>{D2GUd-G<25CgKpu zPP^}1dbfc!TD#gWD9=`R4*t|w)Yv&>m*Kpxi|)jDi5WHdVsy%VZTvAh`fctuK`}a0 z&hqr*h|$3|tFH34@yFoIxy1u3AO`1WH)4#(mutK+I0@f){4qEKc47?9jGDqRID1?% zICwYp<7+%Hc-+4GUi!gny?88Fc;GnFJH2D|$Kt#^$4fJRERIc&Q);}iIPgxy;`AE9 z-%NLX1Ml9N=Y{Ey#kuzkZySFsjtwKHV4!Zk^uxYmae9rJReOvI)!&U53%?&5=BrVs z92mM2@7<%r@HQ8z{|=+RGfr~kPK=WzR95P6@z~AhJKsO+h&Pk@H|L1D3^_}qpgHE! z(C&q(u#qeoXnNXT*AvwKIbVB(KgxyQ^FG>6zrgu^iR(T5fw&MuzIW2SVW+tJo|(|7 zpmUGe#a97ne?8W710nJVYM21G&P)($)3y%rI2Qpp6fZa|=PTcR~^Tqm2upQ*u8nN?2j5F@;M_(=JLqgOUKQl%0ll@&>`Ene; z6MX>AA8>GwteNf`0p}twd#ulRs z>!C-;{PJl1WCl#++;QTPBPA|Axl7J9^r!J`#`D5m){oWRjpv&m??fA}$S(yIQ{Wpo zuQV}w$e&4%bp+%o3Ma|-I*9HIt2Z4dK`pa{(ZqC;j$A2inov%1xe_{=Qf(Ry8_NuX z*GJ84XfoKFlh_+g)2C_jjjn5%Ej`wxfwOoXBgB95S;)&Fqucs^4dzflnmx2r)R^zE zO6j|d7z<6PQwVi@n8s&xSTWklxu%2y=CDo?b9M%cK!rG3!G+f-knB?CZAKN-qzX3@uxWk{U!$e$S~z~*02ueYpnls z+5h9hGZc3;hYulb;SA|GF-C9FNEmt#@!oSCB{X4-H7hvR^t4}D(a@edkv-Gl=WHS) zo|&B|3GAS^MYB3jv}sr~%BJBgk_=DcSwdzZ?67z)DH(>!vOpQ&VxY_{MhnqT%GFPy z_V)T(z0)5%Jd*KI!yvbx{Dz(Wc(I?=IOD~BQsa#mgE(Gqyx31`obh5msqw}R-+n%> zmyt%ddDtK#s|~1_%Ih8Ptx>W?Q zpX28Gb6ev0-kXE`U{`Zk;fT8!un%Kgq1pc8sa@W>)N{>Gq=!wFioLLH`yvkw)SE#6 za9(f16^k)smopZFzgIaq;m^S9F^0*0hxGf-b?#i0K(V_j-8QL_yTqCnF$@pLKJ4DG zuYn)=*MYkx#`i14I#`j%H}XMlgF?AHn!%m!pq0C>eaS%KKXI+}SHrrTQ)L}Qs~s|D zj^Iz-c=)5Fs?@DzWFpVBX>E-rxNUJg-kp4GS1Ez2<50N->ZL!*>!k}Gib}kX+vSes z3iGdEp2MyPF3u^A{D%om)HVzAOm?D1M_A-pyI$XD;$a zxa!>BzRT~UZr8cDF@J~YmiaJu*SvT0&piS2c7IJekIi~vO>o|}r<83Op8rJU!Fjnx z?7bAkU_cIzj&wkH6)sNPa<3c?oTL*hi$I}PV#Pr0GiKfJ`#DGM! zML&r&%sqlM%pOA;IxNyKF;2!bMA&!z*>_MphK3E;pH7qSJ94V6EwG& zwFrle9&=S`Ig@FpY5dJ%_GZZdS{r#$%RSFpl-Mn*HPP!+8a}y5eDhcK%`@y9%D#D) zwFqGg?Xb+3#ZMhCnY2n5)jtm-r3{NW?Reg#i*6aG-$bxx>T4wTOgT-2B2C(BiE`(H2S8Mw3Ua*vhwoY%qF;py`9bFp>ShU+VP@E$2{?2Z{&sA(({a&jA}C(CNmjU zJ?1@iCG5+Y&kv-X4AV}Aapz0bv~$Tpv@3hzVJDWx%Et_PxOW6GwHZ%W(iR=gTO(Po znG(1|?s(RuqDFmWsJ1locqGT=qu3*8E8RwBJX685BuZRp#}bCi`5%+WGqvJKQr8(8^Hd0a9S*JqJY!$jKYga9h;+W>xP;)Np49wjhwB$33Q1t8n%?5 z#OV?tvGOkK)|$8ZX`_Tuqos6xFKFf^k}^$CM3^kOt>zDm4w-jnyue0T8YYniwWTj> znXcp7=JwoKBIaly} z={W7O7BFlw56^h1g0_^;A=;eC9e__5W`0SWj=iLT%y@}mQO>Y9x3-*PQO>Y9vo<2I zxP-vd$dcNK#A307V$VwG&j{#$j{tOb)*Own-z1smpG z;3OHG8j3P5#vs6W-eGO)xLg)_2qQ#t8u#6p9a&?5RES4lyoqpRo_3jbi*~>Es2F7} znZoFK9bguTN1#v8>HodU9XlA|RfX?jduX{4nX6r)-Nt%&e17~`-P&Oh+p{p7$s!#uvbc8Pk9H<3tm zIf3fa^J8IBMw+j#C-C!p?$bM}tic3tLnc!r5pElrAG^x&rVZXiqrI6e%~$`sSA8d1 z(Vq4zV)JwG?^O-(J#YE<$_V-fMm`n3fj1Ev5;$C62DXsq+*krXPhxt*8}VN9hThA5 z?->rceZVt?SFhgyEvCyB__ht+hC)pGH|PG*i+#wJdQbL;Ws~^7(>3UWw`8&o>q`#! zRMb(tiG;!epJ+Mg`OR}$2>hI5eO2qdy@WiLzTJTLJ;Un}r%*`Ctb*it}zy_Pv@0wvR2-NN-bBi2c5@ zra^0J7eBe})@|yW_dVaNQs1odd^1^n^KZ{LAF6LY^n7!I`sM@AHy^2QK9b+yJ@YA_ zqU~MSSv9iEFnVDOA`TMU&C1hmm8acVdD?yGY1f^n-AA$=`p8}Msk?@pT?ennyu3_! zh52jYRVn}cTK#Oj%e{G7}kKXMGhtImIh|6?Q(Wizh&U~ay z;wci4{W|F&{Q<`jsPX^4k}^ z=eqSSAEiHiiZxPRb<{|Cm7Owo)_7~AJpC!2E3rmOm$gf_>bt+)sz2S)7Id%ktdGl0 z9{pyY6QBDO@m*Pt=R|n^(x*b->ad2=TzWXuh{{tMzu&R%yzq`{>8HXj*kKN(ZN&fn ztxxd|YbbT*4*x%i7d(eMH(W#T_FeYwFpig=GDt_3VNvhIBE_FU<;c$Zp|mqQjOMcN zK9+*{6>Ted+m41|bYU9q{^G8xfi|o)pNbroHmo*AdwvU;-S@u5UC;JUG1q|Y-zB_T zhSScfp)|3%vuYUYuOcPxHHT2Uex|-#*p>udfI|lxtH{bdl?`Og@4hb4rTvJIo<;j0yT^9eI}F{$mGS_6A#`DVcYD87Lhv{1FJV;jykyX4%WllC z;_TDleHu#kbksAf8W>h-=&4HdR9#6E?6f4#3eeYt-O|SuxbExi=OgnxKZH7IbhX!T2?8T%GOL)>VP7>cKGfkR9PHf_ zxE2Am9pDx_vnH_x`H0(AcZ*zGNQ)6%!j2<-_SBnM2R(&(WduTQlWR0*9!lYkqkG1v zlr^bT&qO#^%s8I^2Is48b2yB=Y6M+0xRd=`spA^`*>2&};ay|GTY+K2*O%~7z;Ax( z;4S~^gfC&0FU)pZhop3mPwgX;F9b#kbJi04rzGaqar`%!DK6>R{~|^~=)iwceb^ku z;nxfJ?HbPGO}a)WGr$WcOk)a&@G}+jzGSz!59>D5DMR`k*O6gHq#kYp7;k3}9t2r^ z>d0s&C)cOCtp`FTuV?G#wH(P%XPt(ZcMHEYKR+!@^kjP~uuer-u9MeyW6YJ%=R^EU zO428GOc+6P6EmE{I@%dMwJ!5s?vvirer!}fDHci+Z7|&FhDbalS|p5~lg_N@Fgn^9 z|5}p7Sd!sIZ5HIUH>d<~>|v;hn=R()S-dr!s8k zKZJHxkDxRA9ZF~R2kAN`73TZ@NWTBeT!VW=-lY6Hlw1R_%kl3-U7tY`2PgkC=xo=q zCS}5$``ah8pK}R-d|HHE1qb30c+bDA+kQJSQ``epse-l1Krs} zQ@UpBUC&FOLml*) z#(oBRENT$cK;8QUPd#c~X~uZAXi7PC@>BMpPClCHg1!9PLA+rvvDyVDUum^SRR=!_nm97nudS`Q8JIi!!MvHm| zJ8SpcnH^PxuKh#DxPZP{rE9bPvt51Y+Cr}UT=GUw`*)ZO7rJ1SLty86DJQ^pHo|kJ zG=zD@2GTUEn%u%J(Y}UUK(kB~~SDI=uB0Oq@?YKrpPZ$fc++T2QrP^frdia9pd? zrn8aXnzl#Y&Zy|jAMoNHvFAGPdXL~a&y%|F5ljlztuu+P$ zL%LV2f$rS7N?ss6z|YK(^gz20*Ejrgpw39>*D<`8G0k8b$b`&Fj?w!!MB9a!oo80m&Ci|ONISmV zOm8lUM0>K|PTdh{i-JyZMk+))PHv`)uIgm}Rz~f!H`JHow6)Gj+4a0+Ohm8nNwe3F zIBBs_GT~~Uv2wk5j&QFwXbt@~$a58*BWCvE9(@Ma?SX0}v`i0a9h2BRO+T}O49y+I zM$#^53zCB2e-!&I6zc6{|Hn1dZRhmjIl&K`W6IcJ8WTjAw->(Yl5;mB+eG*FEQLIp zBaN=J)cFDYh9@W-_r<+f;|9-9aB^y9Cnxre0sQAYH8prnP5iVp254fRmV_N2WX=`6 zGH!xV6Z!-nyQkrd#m-6DwELWNO|N}Un&UVpp`Lublubg`Cd{vxO%&Ew8yg8!fQI}A zv&k4Lej@68bxW@uOVDvyv+I1-Na0+S2uH5gZVNhBE$Fq+RdYP&s(U1Vx#f5r8OTml zW7Ubuu3vGVluLDFfWhRK)yz2iY|=pM$U*$~BiZkX_*b+hdNvzH1)oD68%sn@aiI@% zw;z=nY>IdjA#Ij?5BH!q%E4RtLSCzBrq;;&5PR}3Bp<9``x?{`uhJmHGeE3Rd-`vC zrM}Mg5VN5|=_lQt6?Dd<<|5joA!bS`H$InOGuN|^xt0B=A~X?9*|q|?~eX8WI2oF#07 zGZ^m_I%mbtwfA*hN@5r%oUd^H-{}=P;W*mm@5;_EH;qyQ-t|}) zr6A&B=3tnWI^#6R%N!Q@)^P7_=*IIHlblw!)yYO8d`m=)=J#5^ zI=|^bK8FlgFqR(kIG(FZ)Je1V+!9{z6nvfknDY7yR`7LO=q&o(l<+?m>%NM$2mj+P z^J+K&vH!14QJy>d&+;@lLA`?$VV8AN3cOWrPEl;j@ca|UqszPo+G%j2gItRd74^;Z zcWkZqm`|mIzxO#jvX_5jz}_o7-R=WwXumhR%ePteF5|_F- z#zJ>n3sM*}B=Z2XHfQrTOJ#<^{P=a`#ZR_%QQnZ0$9|Ifg{5Q6F!w1MB2O1g9x}}b zYUff%A5Y2Kq;rOoz@GfQ2r5*#vYpzx*+e!*urFBIR~Z-7ky=C4#xOXhx2Ht?($j~V zDihQzJR4V+c?Z1X`kTT1!<}aXnV*35eGs49+d1|@e0B%C(cRqSw11qy`Veya))d4L zj-yD&j&!TEM_}6-tZm%W)=4k>3o*y=%}n-1Klu&FnQ^qsd^Sb+Pd(-n0#bb|SerG7 zvl96R#Cn{=z8yrwnX=p55hjXaSo^q5)jqz%@!YP2Y@hqiVZT+m`BB{eH*=WVX%*UINP^9btn-M)c69}qakBWPknC+o!?<#;v^Kb3n8;(V8!-<)-X zW31x?Z9MqRJ?x8A;5e&fKYx+jbT~bd@y2+@p&-LM+3%T^70Ooh0PE)v&dYc6Q(GIi z<*+?AdxYYcBWOnh8-w+~DT{R0w^4Y4cy?yTD~nNu&tgOfMusnvwCmIkSNjRMv*%45ulrLVp&tPHvHyP|R<0VYrQQko^iT@4$A|ehzYr@~$)^XQos=rSWIucF z*>OIX6p*2a-%G4b3&-y_9M*3oev3T#4OZ_2evfke9^>CElK8o0-$DnLkNB`e9N#X0 zBkBSFlzTYj%IUJUr_f&m`}lywrEeDchCt*#=x1QLw%e^%?Jg_WZX;_KbG6Hv9ZXt3 zm$F>`CfilJZ@Jn%FWce%x?AGU-xJo{@-JU67gxh85fF<0XKD}1)aY0H)^mWHyW!zEjWYDjy$17Z@n24qWzD-Qlp zEiU8W0mkB#Q}3#m@1V{z%$nw?@jO;5Zf@Ju7^gD|zmW3g?3v_zA23(PxV~SjzQ^~+ z2he=FqWS7_?;Oo2*nUm9caFZ|!p}cP7s$Ls;{Cr|bA&Lz5il1k`7PXZ*Ib;X=3=^U zF3v457xRjoi;LA*a*UQQ`sU($&i4Uxai#0~b-uayegJKAindFAb8&OQ_OJQo;w~3{ z{<-+ES}*W^bKrXUxmqtd*Lry{V2)nlbCh$<(Qnio<?KvIJuc)8?iNO%QqE8G#7(zSkwwCf< z#`7GomF#?bB89y{Ba?}y^io++qB#KEBimDB@*Kp-@*)mMP(Y(gevv#kVV#H zL&K*O=B<&@tkv+gOoS`Lc3zAR7%a#)n>+DX(Cx$B*7B6dc5xidbxfv58ahc-w;E+k zuLx^X+5F~CI-X7?TDTH2k@Sv-d1XB=(@AeDi837D;@>Z7&%CQynSbJ0aDyiFKjcF= zS3R2&+(;!i3R;fmw9GXKZq#`_EzAN6qngMgzo1RGwHo}5_#GVb-7W^lPQoc|7DYM#4^Y5aMK#I=gSK40PrzCp`VR|5t;$5Ds|1^=Oh=4$r}BxO3iAn!DgwIxQN~au>loN7?_A=~N@i zR|@+sy6~9Ji%y{3<|_7E$3@4{v6oZQIrliq=8S#0ENt{n4u!%nQ~xcao4?ai8HKyI zv56RwSdw?xlyzGlvY#_QT4vb16A`>~zn0D1i#$4#je8WACCgKDoV>|6Be9v^#~N*V zCCTNE1^cFh8OH9^!b{9+;DB2Ub~{*TE1u`*WtC5h37X7S}O{ z%&9usGoFQW36p9d)j_Jk`DDCNO1?7&qBiV%6!p~IYOeAldG z+!!HAL)^(jmV+DE8-5$UE~#Cpz7$rKq$cj(P1ilnRgw>L=1H`b-(w9ix~&REwVCV* zWGaXg@vl8jo2TAUc1xU#8+F|Kffo|ySJ<4O9>DK@EKzlEHfg6x?Ri&SaDV@As{Rq* z+ZDb?Nqp-VzFSj(?@?4l#bazdeshodW{Uh~F#E>fW1JH3jT?t$3J0`7!#kLN7=she zgSynsNs9IV-zn3PbK_z4u9m&K&-HGRdS|hBzn1UBJ=Ejuow$dp;rCA8?npUX&9PW&{EuZhi_MOj1m*AbxF@bkJ5osS^rlWo5*;JTx z%$mw?c@x?=CJET?cp8)J)X>M#5|W$4_Bky?6I-UTCk+sN2`glMZq~81WA;=EwsY+cA~V^~IZKQapMkY`CVQ5%j%V!- zhfHHkQV1_1wm#TX2FDBAp*O61Ic$kW*BjK|Fs@wpXa1WB)Y97+{V;lPUW{| zFLA!aZ_4C1&YY>R8`$+^Addb0l(Vj7?&89JlfS$J@6@KrJGE)ld6yAqFdH~Ouj2O0 zsZ{EGY+lEjFv=yV!DMjz^zh8=WJ!z5QtjN&?r7T4$Yy?GHCaY=hS>+KC5)8q*Ap|F zMV>Kz5at=PYYEmen2;k{%+1A&vSL<31b;goY_3cetl{?hyP@iJ`eig=g6Vn`FpJcOD!bY;3`_?ZWX>vVW(-VgF(`B@@x3yGgOpwuFS!ro+ zM%Si5PP>lVm>zcfWIA?Ih_IEzU>r$9rL7cyOKclYBV=lrvGh+9M&{t$?My$5P|yz} zpv!eK{czF!vgas7FhBO+jZI}FcPm?oOmEBG3L2;p$-KL8_IUkIy}1SKdAO7~^Iacd z^mPZA9OTS{J;}iH{K?dLk57omAyP+%a(P)Z2;}DsWh;hnjxofUU)D(A`yFeWJ$Mk@ zAlr8l(^xx^(|2^8+fIKn?W{V5I_rU+%-Y_<+rB;mv#mcE3bPql!3MZOCwr=lQKVJ> z7MvaV^Ks+^Cb?R)F-)f)GvOefQwy?_H0cl{_^S}yr_Z&vMC*n8+A6bbg=@o4Ynf4X z;ZYjT2B%*!2%_;V9*uAEdluz?M__e?$?$NG85`*D`*BC#;epuq%#%+B3qADDGXy+h z+8F{g-hVx21W^6kt*;!^cWYI{&xp(V-^a-6tsT}WbguPKi1CbdGR3ObbFGKflka;f zK9s784|A>GgsMnpv+d(NzfYd3bMe%76i3 zM4htkzYVlAdGCC_qWB14U(ec|8pubjOrNrO=U~P!_wnlD3B5@-?O7H7Q^*Tm&v%;4 z#vb$MP*XZQ^W?7-nAs|JHiP-nil1FiJCw0MGQ@s~^Ge~ql+7w7x$IXcW?qL`#V4=J zS@qWQ7~rnwA?l9kFw;X+%@X*nn!p`ZzC05Cc^^5*86W!#L);7G%vlspGhp8w2Pct4 zrJiuy3;f#f+bw4)yXA*IJ7@YjjA5Giphs1OI(NeEc@>KNoE8 zw}FG7q>Z0#cSWCtzZydK&t>8LKT^aWXOwXJ#_dukGIbAeJ=)P{`RwS}=6OTt#G=hJ z;G=3DK1jn8hADLU+2rdKb3}@D`?4YQ-a0aZuiHcTy8Rl3oRXlic9~bE#O>o~{^sir zeH!8;#F|etE$H{$rZjpG-opxd4!_>fhUwf^K%1pTzB_1c5vtdrK z&V&9{%Z#|kSDNi58D333yJtw+5H&|-4K&u)lQ%5;Bx~c$0lLx<=O@cLPq*W{?U?;8 z>()VD-U7W$@X9xsj^)425q5r@^X2mpr`ha$(H*l4H*KNrg?evZ z67$}#Y(LZa{-T&I2lC*w*^AS?KAbR5vB2pU6^K*ueLmppGrH5+=Mq<+y6jWX{-=W) zSRXxBI)*h-RIYX8LyPrc&5vbSJCoRRbM`H!oSWHdZgB2<6~CPAST}Z_kuK|(K7P6Q z8pQcD2Jb50y6M{HcyHcX=H+`z=?^}fwqNb&)B2T<_I>yHCtsf_*EpZ^ z(H{B~w0~UE{;?R+Ud1R~>%=Qs?(73-QK#nSbl==;S%o=AE-^5^>H4xX_dbCfY@o9Z zr@~gSB1H8Tb{a_ND=4KcG)&}fVDz*fHAqBygr(2)7>ajQ=Ne+~1&ax^*Jbsh_I?l@ z(3~5kPNa9^8&& z-;55xt+d?6t(x$BJcbv?DSLq<*zv%w*@-xUeJadwG**N{v?T;Ma*0lRaz{%Xqdc0I z?F%0I84|~fAiEaFan^ukkw|aze84Rn0o;yaT0~F#;pOR^#4W6Fi%8tUVeLDoRT?(^ z<`$qImspH`$9U-XT`KNYz4W`G%%1N;U10AG(e;ggtv|Il8+NBhUU9{A3>bNS$36bm`v&yNiyWd`iY zlO+7+y%_()`!N2}a_9J?Y_Y6e?1(7N_tk zm7=y}2PqAY4>K{{9_eVP3PC61@aY+2&mpB|P6k1W(-TJ)XT`gyUo- z*+g~okU{*rV@X*ETFhaif)?Lq|JOzY4d#*3==dnp0#A(0YZxN4xvsQ(wrTLCf_!kg zj|P)NetB@FhX%)a_~479L0eHYm|l<%zU8CAsR4X2%d-|{6_f{+<=(XrEf;HHfnzN^ z7Q?l`e+pa+(Q>gC9*gabJiwl98k|{>4^H#Zpeuk6&hpUU{DSh}^Q1wLJn+-tI|cdR zn~Db5{aW<|)%mWhcHoxvI(Hs8{VGE^ zo;wPTho3AQaPplk@{b4M{J0<-uE9It1Nw=kzhpHAT%c_myXOa*S8nzU9C7z{x+)HAT%chr{L<;oz8`3>fnS&zPSnIA$j= z)D*=F-Z3LCF9zT;(SysY1#xlmOHEPy;>87J-?EUnGft3J_AH`-_i{w4LcIz5L6E%ZY0+?|zrW zMVw|e83T&#GGV;WJ`dZOLG2Dx?Ygm@71VBwYS)A9vO(=yRlB*^uHD%VeYU<77IFMZ z@?E9pJS%IvkJHOJ&w}Sb6W7G?mZ8U-0eSa%J;N$bCVzXq9394c_G{IAUB~4Q+bcZl zW*Tc-?`RvmZkjZ)YO29+maQ7S(5&g)XLJ?hFGz;IRo-p=E1Huz48+*ac+iw%zkR=t z{mW|X_o%Ty@l15a@`Ow_#kD^D1d{}(s#=@SX z`_vrex2YYtIr5*E^3Bl`YK|7}hdF91YK~r1bM!w4WRBYUm?K>8|5WS!f8~0gO^ylI zlGkXDb>1M&rIUQX+75NKO_DEX<=Ikb6b$D|C4aiCS4QYOQUGO8oyOL)-o@8*n&-X@ zOM0wheYf+z|ElEZ$k8ZAZ{>2dm5h}1ppqU`(qjDjMgG?S%3T)rKbpd1b2iuKBFW)A&E~&;*%y)+8R#0hT0rU=eCa; z*4&;4ulE>UA%<62;5CN~4`J@u>Im}95+!p-jz*b#YlXqRMu)oO?KmyP`qlXFRSusD z#0_XvP29L>X{cdSXD}?Nz(UC1u%ypmhQ|>ckLQBs3ucDlkx_W?-?ds!7x3+ryP=vV zg$tw0P|tip+$yJuXRee@GlD$)mfEupt90*KSR1w_B4RACZCLGD5ze<^sb?jOCVd31 zGMAkBGM6)A#|rU?Nv5M4Q6k$g36&|S3s2XQk#Ckmj@S8Yz42LHCbGuz9XDJVhV__? zh`nvTYfW->oa1E>P244Qx%QRS8HFi)>Ui7@$^Vs=Kj1u zwoNnLu|?MJ7BMJJyj7js2<8OB@8BzU{#*Mi+aIh);Wi(?%YL&0d>!r9sIy$m+FHEr zlWP6ES?ktWKwmS7TrH@tNo|GeW*g%M`f*nc}Y&qhRyYLa|ZV9nX>UsP)p;3@6iaGTESfmfH!(h}7O`o0y zc&;so=Or;Go}y}`yq(E|=Upy5=f|1>7xqQ&GvaAP3*i}Y;YnhGo(~nE=bbJ*Nvzqv zt^7=Q9#jy|%5o>3(Q<+3W3l{rE^y%)EpN7OQ9cu%qZOXN`-1rMvZC-jsvw@92Y(ia z=gfk5ejfZ;9G*P|@%%javu`}PUcm4a^#|V%z*Fe4{n@_7^jKqHQ5^T;1P<>bQ|Zb# z7KCw8%nPG$Zt=sovmgxJ2cz$q_QUu|K^UdwUKoAPk{`x{3dX+=*nIr9AdLM(tKS!d zv47_LF9l(Iw)sH2>=MsD?FlU|dx9;$Luwu0&2zVUYz=wuKg1WH{q$Jh4QG9} z20J4y-iNI4;OX?I1o<6u$?uRyeusT{-lc31g9_ZA^nqtc;29#r)jja$g7l2Us7roR zkNo!2^G+8%^WCi!Pfx_Nq@)m@mF3hWzo|!l`|(`h!ZY8!%iiNzTjJhlK7anayeR%` z^5FUT^Jf5_0sJ|&AU!`1{wz#S#Pb^k@%%javoM}`KD@9b9&k@cV&40@OV2d}?g{YKA-M4#W#P6LFv?c#U;diIU7wTUD zZEp3nSsBpgE>D{^0c{q@HaHj0|EsTi?Y*B1v=MJo@PDXm#J{KzG4p5>{KVhJ`c;M7 zFNc)++uZMI6A5VZplpLS*NRfqffxAh&eK_#=@dCeH~0;P?z2J%+?fl`{s7{qk*8`r zFq}Mhy_DSVtLl#a%PA1n*+s*e!eRX>W)$Paf7B5a4wGz9K z#12ms;^|PX<&G)K`u$}#y3Y>E7o)V0zYN+Yhr4W(ce;ErNNku~HNqF;p?&g~`DbY% zf0+Y4H+}od;M{CgbF=U>%uQQSa}zErb}TT;cSS zvGE$}v|1`ZnE<>B_{p45R>)80PLH3AkoUHq42e}aa`q@of%22Fap}uX=4)kz{ABL* z_{oSFbNk7-&rk{%~b zqKb5-X3N4KsI~sCsZ-0J9Wj`C$l;`@Gl&A02m6{#verp-#zIZRdR^+rD zqzc#$1b=W?4XA|+{a54-5wPy|z;fI9oc4oM0sBE=SU9eG3y#ZaLr4{{AruGep@Ojf zA9G&A-W!qM%KgKFQX#*C7cJ%`KRkr{BBTptcsvH0wYxvid6B;b#MRv&5ij* zpjWDZIZT?2wWUeqtn^BN*PZ>;YgO{&y)OCj6(oAL1o%;Vy;CRh{Ri92lWkegC4=#v+Xdo1KMlltZkr6odmbU^Gk&KGl>};%r zXGP?DGXn1swiA-Ucu%o|b7C|rjr4x=1W%0SDal~GXZ5sB_0T#iN%5Y4i ze6-JAAJTr8c<@P!j7wYiqWGSU#MfBw#>aO;9>2DO?=la*MEwNcO8rIgy$<;1aeUWT z9Dh8IM=TZF@?+4!^+_ZyG49;Bw(Qt*a2fScxQqa-1>XGP-Ma`LTOe0xOV+j8=)T6|xR#JA<-TebN5?Rjy_R+n!#2@)spwDYh>wx{reyW4Z?oJnxnlW<-eiBrUTyK!!vGYLVQ zzxK_e|IYaI$EF9z=hHr%|DEw!0jFzxz7^S?TWfq)ZcoCwDH7+_8lRPM(*4838|e~(3h(49ywd^iykFs+mJ085 z!aEy%?-@!lyuB8R!7b4ybv?x!YW+%xfhDuzBAL9^3G(}{Tz z{%6fa)0f)G)4lLH|ibnma|(tgUy`Ow>2IBYFcg6F?F5fIS+? zpM0J#4oRH9^!ltL@^JJ_o7gfV8JC`!w^DJ3$G(v~c&vxVXC34r=RgRL8p-3UJ|1zK zM}pD$OSkWR1js79Z`kHRatnAAw@n(H#{%GCIQ3W>Sr66c-;eNNh17$__u+OQpC2;5 zFO_jmX&Lvl0>-;N!g-ukJeG_pX7N27-!Vz^7#~fQd2=7B9-3#Px7AspfTpe`HBM7= zXr3BL^AaCTmQ>wRx&qDfBWYgdqnW5z-4fMDGcNn=`#^I%r+GohoT0o=!+G2oLw}LT z{p8GW=&Rv#ll(UA;yWn+1>K)S(!DH%ZlYdu&qyC#L(;v~M>o9x-E=);fYTkZ0HmCt z8za2;MdH0A1TT;6bEJ|FZ$kFj<3M*8PItXMCq$g7e}^V4ecM#9l;Uo}tx<$$G2NDX zfGU_SgD+^|+kAWL>7B;=vcR_-#JE(I&;w{tA#TX=9ldHs4jfHtA#vb9C zT_vt+ET%&-<_OnbRpP3~!aNjXjc^@gkN;b@vV5Ce)%ZWMN_;~v->Sp6wn}_MFW;)e zcUhJAhF-o^hwlb^UflLykZ(bZgzKIvacvp-RvFjttHiZsw;Li2Wgk?3hh!Wu@rq+ z3>!IOp1qK5zjf6B9k_W7gGsglRz61 zT;Wa~F(OpSdjj!}IlSjo$=k^QVX?3b5aGNjk~y6Bu2u4OZZWV}*ewR(yt_krQ-AMc z^FD1z`@5@({$AwcJyiYORYiXvR3-1t?eDHC`g>WGyf?SMy9ViRo}(FfcW~b4g!Ff^ zGtBzf9Bq9BS<|AStXZV=TmK$8k!0Lo$YzUtkE@ce+j`p^ZN06E@2OStbz7#Jqb<{s zd})1I=i}>LUj$#jCAm4;l8oz%i*F0b@4uuBXKZ~pMR$mw8#1;8U$19lM9{MVV{0OF zILUGJH}Y}tvx7c9n$1VI`FOnuBZ6K8i}ITYKI4gx?(n&xNYHxYbjJl>b0{6be%J^#j3HUAtb3uEyO_Z;}YDtWr+-k7T9US*z7SIN^o@5WR$ z?}9vO&b{p8S23>vr)$2wo+2O4)m6^7VHM|_OJ=|8o@;;i@fdur4XZfU zkjHr9;Tr#+R>{MeXTvJavr0Tzx;h@t92-_~j`?}G@4v)zbN%LbE!^K?>}2rSWf9LV ziFkHt;8_jSNj$r#&i|~@es)sQ|7^JZtS0bmjQuQCPtWN5R4z^H$}zkR6*f~%F}msH zCMyl2>cLivXR$a(-ZGk3j!-h}g-oCIueic`txfS9%czg5y_Rm|Sg$$Uz_Uhqbs=aH z*J8E<1@i!;S&QoMom7EuZGDjMxkJdetpZ<>vBS%E-4OEauE2L?2;cLDknbFu@3DjS z?-@hLch?Gh!}|BkA>_MH1-@bZd)5&0T~vW@SpS|q1blVL_aNXqo%21;_DKtkU$cGZ z=?_TbS6#ORq2v>J^@PT+@O(oxm@!Miv4GMsrE-7jEB(wws}Ovr^Q`?ozTVthmvVnC z@m&EjzD~i_DJS_3tH}54W^q#0!xz3&eOE=+_YzNixmVxL_7Yv+vnte=_?{oh_c9M( z?)g`6r5v2^nHBg_zLqP}bZ+2yTL+wWE2Iroiduk{h_MYtZU z5*N2sx^N}x9b6((*}>)9DISiCKd*p`;x2y=xVky6RkmJD76S)Ymff#N-5RyrwB`4J zaGTBiFq@6-Avpz5a15{d+va&6hVZr#7*@)D6Bt%Nui4(A3fYNlD`hAKYKK2 z3vhZqrty6b-TtOa`*Bx1+`6QD5Gf0bFZwZU+zd==(u;{DgP7J2f@ysOrdo;VUp`E3 z%X)CkCOnu{B#R&epO3pR3E5^KrfwqylazffOy3B$liE~$JJr^E+bK~WY$q;1BHBsF zm+*F?F!D!SSF=3?m}W83@~$gt>cBP1y%4T8M~K{zuOWR98ZRaztDX{oAlmG=%@%tjYyLg1VoVDTL_^? zYUn+L9w3BJB%$;2`_H^H&vUc)+&=g0mD%U+=CivcVpX>~s9IjOn>pekH&wjSu)UXg zsK`Tw<{pu;b}LNJO8%^P4%BY=-~&TgMCiZfSAblAGvF&D78~gWc&GsHS^RYL)^bXv zwSp}(ke@p$kiN+{NDIB!zPUX@4Cg{L%nY(*$Mbarsg!Gj1vk%OnO{3g%*2kLw{r*_X(AU?f|1|`hg83N{5!} z4UcxQ`l??w9m}t83n*6UaQNu?yZ)CsCw1RP=7LO0u%p_0bVYro(9eHCSV#~jBRg3C z)SL10m%>PM&>drTKOAAH<+KAa(0He%El`_jE$;v?8mrovsi#L-O1tr~p45Mxd`q`_ z$|`NPj?p_^rV4f8-3MLQd?T;((BG<7Jv)Be_X%w!2b?8IQUW<;`gf6f@-(m{v|;s+ zA}iu?LQZ!Ujpc|(mY<4ew4wv;tE&I@c~e>}u{&J?wkgD(%bz9pPuP~$7*_*Dr+{td zv;A0m+mc0+&n<63KQ?cwPq)JWJ}Q4 zzrkUlPDb=SIL&d?ofaDMH3f3-Ncn+Wo>0L!x*BuE`BLRIerI*>cytH99wpSDewTPl#)2B}Rl)B7#3VIRfKRMP#h7m}Vj zMl?*^hhKdjXSQ&Q{?XalKNO9c?mq~9Sg9<`3|tN!J3RVMfAcKmloyu74*Tn?d@RN$ zqS$i)IDWr^!>@vJOB62E)z?bo1R^du%qfy`f_huXdT@#CpKfM;(MN8arkuKQT~CIc z(nwx2DOl53UBXp9mfP!5<4^2Y68pgrit>bY0+Sn$R7AI-la zt;7@mWtH4;|3ly|4lbKd5apbQ?%`L*x0aaZ);I0bGv?O^ip7!FvX5sYb#B7OVlk1b zH(_#h(58;-x001kdxzJb<(E|>_8Ht>lX)-}^$w_-_pkkkO(#0!aqnCb8ZCwdd%T{q zd4d4$+n2Oj*q0n`X1{tz&etLM3rZt&oJ|FCT_1Os%cciq>qCQ1m+s1CQ-J6?*GG8u z$PlFFk3drFs$6n8O-yWFY*dPDTD!$3J-qu{#4hDNM-W0byt$01T&p^m|C0R42vWMl z%8xk0_z(aqA?kODbor^IH=bS3%UdA}kOZ7Xd^tGa7NX+mhg-$8B_%R{l504}$;x=RPsy0cz~Z5_|nW1MyYwVueT z@{Eb?-hO|}VdJE=$3Lq$WEos#hwY`ZM42<7oV{)FkAI~M+n_P;a8p_e2IyAphzHE~ zPzI4t8^~>MV8hwnrL7UQNvJp{+y@a3`R> zquUL(=x*Lg-sx^}zu(ucYWGBj%a`=oOM^mMBeM2seuhaIYf$+U+Nh;Po-Mtj5(P7& z%SFC}l8m^umueKg^|LZ=?^%;Hg^VZ2bQqM*eke3teLc1~vwdXk^+Y+8v+FzAI5U~C z*K;{&S5d|rf&q0_{kb$$$-^g}%Gku*Jx))bj6Nx)UTy4ZXWVYXA`7Y6TN6z%JqTop z_c?|)ww~hxya1L=^V*Fk%Cp)QbaQ5?+Z9aD8w>QzBUZuX&rMD8CO4J_4mW>97k_-c zDPM|Q4zueB9p*d-9b6V_WO17DyW&IDCq9VA`cf@n+(0cOiu{B-eH=!fREYRLdKHC= z3LP_pj?o zQc#8n!PyF5^`)2!&+3asB}P+Az=OAp)0E4Ju~CipcJdx^LfzUzBgQKRPt;u=OXqLF zA5Lc(6z$T*RPDS+uX2{B`ZS787JbE5E>SFJ8|+mpE;rXD4uL8>JKS5R%Z|bd4i^+e zUE4jkT=&iTkb1j%>*?P9>T7euY(c;s zwx0gfZ$ZCa{P!t#RG>v$z?ozZoUhM=D!TafF0;3ODk^rtY5pgcbN2fKIoAoKLYP+V zq7&4eJ#yJ3^5Q@6X4G1WpZHy>yg%M~r8S3N?Y}ok5>NqYFGn%4VUXhtPtG6_{Hj3FEHK#Qan((W>V+HZC%A7mK zOd8$h(nAE3e<>V?L`>}S_Q@ChZvB;Pmv=6DbMemB>xVh=@c9rt+JSqt?vVRQZyk_D zu@!A8brWiC8}sd*PRSm$ODBwBT~e*5|Hp$5{%R-Uq3lr8yRM%LkS9_!=VN+AiP9xIVHo zKJ#o>4;RGw4&h1i!YQzca(wTf-S^OqlqaYFm#<4N{Ixs&YO6Mkds~CeI(CV`j33L* zN#)u)i|Q)`4~EAMQ|kkfYG)^4KXkxlgN9t%tlrA1DXV;|TRD7^HNGTfhegNr39|XW zXgK4^enz&WihzWR?Rdh$&ii(wwVbUU^vGLAi$A%0X?NKXS~~*`?FlTV0OQ*`A!XC7 zlKX8K&4YZUI$MI?^7TVFT5VcZrPN&8iX-Mwkz*_QvKpBGoS(-G>^F+1Ou*J8@mV6{%KBy+Tb@ZHOxweECXuV0fj2uUQyYT-v_BpsH3ark7=3kc#7 z6Snpxs%6y65KVw4)|^0{{%P%HjcOf0Va3C}@<#D~h#|=L@^%9ACgSh7J>jes8u2yM zxeyl$Y;lrXc#+DzEFMwRzggpYU$=epr}q)FHk(#>z;R@2wWIO0v3#I4Qb8qLB6O@{ z0lr@jXg0APN#5rBPN7Mpeh=;gOQX=76FlS(HN!P$K-QLmt>}5o57I_zuFjT>`7~0k zFavMP{x0pNYIh4JpuIBgAzO3zqdJbI_WfqM%zx*a7yFLNFwc@{-L(E;lwYRF4jj5P z?clO@2!`DdR#+`s;dh29ptTr@|&!dV}dkcCLgZm?VHKQ5(gw(J;gUo&J@k@5M1{3 z1JeGKR{NWG4*XME^UsfC7wtO3sj$LZeLpwis{w9ztDv(`&t@v5qF#&#Y>hziZPt zG3<-ua;5OmcTqDmlnxp5Q7 z?&8=Y$XRhp5hM?Mn=RWRYTniQ>t&mCi^fjEu(6&W`EFPqm7D<2FAW^EH5nr)04$2iX2)$FthubK4@3KCq}IrCH1hITFg8x6a5$fABEY zNtkrtIk-nIMfUBxCAa5TKE~?(t?5s_S*+E9DLxCJ{>bn6{OOC$CK}snJn7#%f1>m zmt}prwaFh~0F4D5?k0AHZ~~`i7_S6$xeq~%>(fYAcj(UygNkSI5`UP+ze~JJ=h zOOa(t;;S+amtC?W8)o9$#66CQ^k)&kfHmVs{674OjlBOpZ&pZ(@D+%Kd)WnFoc@p= zz{e;vhBJ_7!iq(#kXrWmLpLFgScXFJpwVB9#D|zB2K1s>U}&4Q#5aQGG1WU-nK}OE1HkOkNft4oL{iNbAbCmS$ zL<#mQ%=>Sw(zsLTu7QQEtD`5sSniu}FcP8r@H~p76AII#E?8 zAE#LT0hj>`kqfs*+z@el^3&{UdB(X=gilp4ZJq_uxeUX6IDgNI}{UNY+ zohgc(Q3t^sN_3}(EF~RGi(3y?>6cOH3Srg@yz~XkV>eE9dHH_V-~Af)hdYW|jhPckkHbq`EGT zk!$k$F5(N!a|q0nz5DuBqeSLONe;3}iC@t+z-6vfk52yxW|I9K4B8R4jI&QEVo?Idp=*H_I$i< zrG#73*@9@?KH7oSff~2}nf?!p>nsnu+0@;liVVU^D* zb@?wenPX3;2LGNQUoGW;6k@7tOI>b%;O+0bpvcf0GuO^2sp9@Ai@f64@i3{K(&IlG z58W*paOtCr1&@P!kVpH3MxyzGjzz7V<@R#%pK&?FT7a@ zh7FGb>(z_~1f5?76K_>7457V}8+k@A!xzCY!oV43yA(R|`g5w)RfJd`>6MnMxrLOQ zgR)y}lu;aev3+8-INnq&iME1E8?WKPvS}Usvk+RxtTvQUYq<7<$b#&XTjg+b99G%(v|Q*u%*Rb4_8_KmG=_Bw|sHk5N@{i|{#4i~3MD0j0W;hm)OI;J05JZw-U} zZ1$zIDG>8+rhmpE8G7@Udv(VAbsq{yjw8cR_{pyFxt^k_lVsz)Y_E2~+XhDWE4 z*Cvm;R`^R+#d^0je5@){&cPOTF+6@||eGIO$VSAl&s?F>E{gXE$yqYTn*uHYju z%Rs%pqUz!Ei=lx|v7X@loF{UAr230l?j3Bi?o58)NUCUkyURDDn@=<15@>8RO>R0+?te#m<~~d7s$@&eiEh0K zMbZ}=Ld0#_SqCZC9YEh5ihSb$fhE>Gp;}|Z24-QzyO#Ye-L^DwSf;nJ(d!}f>f02e z*>PG_{l&3W=EQ?&JxY-4p8aPJ4}1QmcJ-x)et;(MRH|iT$E1= zEpaGXUX(mwvnd~$U@eMls7+C4@pu*GGy>f69sGS#BJ|3wGIoaNr-QE1|6)Eo8M4=SZ;hP}R zdd~2}wn!OF*7Z#N+2}SZ?^oPwS*Zx6;@| z2-g{&hJRQx66x(2R0ExK9!BQna^1{IDBKuJ?On%o88qa^G_Dix-YHm zn;FQhi|L~$b&Q>$lEsyaI!f#g#Sv{o`z6e=rTLc%g9i~I{Fp|eM*)gw;c3?qg~2x( zGj8{MFL*Dq``Sw+E|YGfk87OrMhm=LQlDqAP2hm7gql`|^9~EW!50@Clf5qAR{zvM zD@l>ov;i;__+JT7N>5*N**!3SV%ELX#M5!q&%7?pE*4|5ZWOfjtDaq6yoMb)Prk1%b%rkr zHJQe$pYd1Lu*>&0vs=2WV%1e$zSC${u5{FK*HkY(89Sk>@5a}r@7HPYHaIcYe7+bb zYIQ2A`vqZf3nmn0LnHnD=@&pp^J^O>n`Q}1AXk6X@yj6yd(!;B++6TzM4D6Hco2#4 z0;p|cN~M-PkK$sSp>cD^=!2qb_)DN~PFQGtfYH=7P7GzPJ-pJF0c0<+BRbNKmTibl>)%M>sAPuxi3!OR^bHlY5DCG1NG|$>eO%~6oh3vE-~87zFqJG{ zuEZhXE%S1_gITG$$!ZLVP$#fI^VdoDf5huHo%@R!^vBXi(8W#!%J*U~{NZf!(e-!1 zfr%K4kNX{NVL|F!FW2RpwO6j{sJx!NX-2EO&D=UqV;O)^C~xHdomV~rIXQd#))KHm}e`V=}M~NF(f->I;D6$knT2I1; zJYsD#m7u*$rM?XKM~MHWBXjgs0r4GO1XD=sU`6Z}DT?C_NN64}N) z^d?#LblSr@1d(muo_NDLz7O}+xu0Pdjy?Kd53w*Qunb~*%1zMOfdt6jc-2^flRJIe zsfH+0OayOFJaD9W@*iA+prbdK4@%YmP!jQZkIYr3yEPz>1~pUBch!7a^)GXowdkd7 zWx^p4RV?v-_DSNdCLgo77ItE<(G)9`H|Jiq#_Dr(;dC(ifC|R77or6g(hmtuE)#kb z3f{?)l_<5|!HQr*qDFKEjJM#k?km9WAZt$RI$nt&Zm?V93^F%xH*Dsxm_~Mc*U+Y7ij_Ri!JH-slT_*DuzX_REHT;!E z(Z;PAUvZYCi1kY-dslwnpr`_(*Q6WhW65V8ySus6tdtCM&N_B&RAW=DJ|xt>>pr#}s=nDU-C=hha?Gf~s6uXlk`s2{XogzE6FyAEShlf8CJ~?o+UrP&W_f zBnExlA z8;nXtjc3q1(GG5nw}Z#%!{Dd~u!_>c=%}z{5(U1ybCDVJBCi}UCayYbc9mZiSZKy~ zTZ*2uHRi%=JMD@)OaL!*^B4EdS=dVvK8F8PY82r2s@LzHv@TX5-hMjIy>lWXQV9Hf zB10DRf3$9X?8btgq%rZXpD2uKaH58i!6{G%2H~xsEdO{tAnBns;TV==`}AbF`CTKO zvuH=?ED{lN?U03CGd+MRs<$_btU-ZT!uC9kuFm0}03W!8V|w zlS9@r@{_TLo}?y!?FR10c(rV;`yU|_0B*}F)>)m$UaqiQcSY5v{FjSVyEq)kgMRZZN6Bq$X$-(l&_01`^!MDQhr3!?70Rk+9%C{ ziibB8=8etzy(IMYJ9%1e<%ubk`3oAZyjv@_Piv1=6$_x=l0D`^j$O>KPg44cgeNre z(F)J@c!@*eh4-&A7(dmL3M;U62A$_9B?9ii)#VxbZc1psfE>uoE1l97OHRyDo(1~S z@WUHc;#&57C!pC9%<203O~7np`705DFJg$Mdtj;~GtrzGJdqiY*m;DaV4AogLcoO@ z&xI1_!b^O-qNEC(s0#oKi@OEhnUgmEoJrT)&z&KT z%q(^D|CJR}_sT$3;k}xiH8vRR1i3d@mFr;lU$}|jH)!@=|9i2$o~*H0f)e}h(`{A8 z=9X7=TqesY^udzG7|fac8>L-?HNvA%3jhva=SX=FI>9E^dp!0sc>jT1KG~|we*=DO z2Vo0aZ*eR%N25f6kiGjjIr<><#^0vB7~uY@2V(;?p)t<>+U#dkh%PMPaV8e+-TKsi zUB*{?MJ!@@&M?Nk62<#X=)!T(7u_64if!B8rGkHL+ZnD;fNyP!`@i{7#T*hUUUY&1wXC_t=%>O_+{k(p28n>1Cu~PXW|L@<83WG zn!YO@ntzF=W_{&eMCIoiy+vmWgGa+jQ3EwSzOVaF%$v=yOXrd0>Bpy&C8x`?uXPdV zQZHf;MP9uU@$MxN1qq%lY}gMcqqwuc+lzgx)zGd38I`q#tD1tzbCYi2tfpWG?}kyS z_l|)#hvUbD4RYUgH%XMi-LTAsFrAbz9kK)Yu>U$r-hX3OPbO4RU@VT#<_=de-Ljcs z9h-9XpTm|%BtSF7Gtq|YP|W8X+|GK>EU^^oxZl`}KG1K3GuF#H{IZA(pZ_|x!(OhB z>+7^(EV9qT2tsN1@OsN1_-+u?pZX{d|JoN`H1edC;yv1`AJd;I6mdJ~Vl-Y#RM@1i z%pc_XpZpSL=ec#R*B_L8&Y5ygX$2y3AI16@#i|enH0o`(yn_nQiSufrvaYdIL}Qs6 z<{LMb@VThFD79HRLu_!B&-^R=iv`6i?NOp7fi=O}=J$hHWi(F7H{evi2PZ}ovu~*2 zku5+(y=9|N?+{>>O%~cF>F{_# z_l`oAoTqp&!X#%-%wx^*i9wZWl53G8S}^C8Z!nm3e0%8@=C`+)TEXgzCvU0MVq?ZBML)?5B#-(-Ap~vJ>2dIVxwD=&vb%tiLE6DVklP>L{KHIs2ok z@q2!h{ylGMLc16G|0)f|UHH-D?g!AQRCL9BDe&hK&(QATPLI@IO$9ri z&X2A5eU6-vVu+b{=rujJTQ}d)iO)V1a8R`4wrkVZolu8o7UU(MO+kK%%|usp$+ge8 z?O%bh#V4kuo0RsJFLmD%%t+Dvk^qt`+iu_O#h7APB|R9Ozh`(|A9n!z>8LIy6;ESl9|S&sx|LZBUbj4y540^!1)!3 z2g?iC3W$w+Qofqm*LLLy3aWQ9ceb0_$;QN=Bvd8z^2?*2r zYf^K-Tp7XvNWFqwYy!jks~bHAxmas<8`I~!7oH~cNC(if*33NTUA zCj-P1U@{T0o=esyHcK}==q7d~`GxyZH9jM-e85Fi$E%Fmeto|s(-u3^ujK;fepN9d zrF#aJck|Y9&uqqjUjBH*r3c=Q6i(Tj49x#A>EmBd*+|k7_@*k~{c~PPxzG*ukyR!J z>Jg5kRw(8=0820%Kb;{ebN507K9*=NX+4`JL8S_Oygb)b9H3ULjeHid^It?E*Ej1( z#EHyW=EMQ?%amAuLr?(AqtlU3w1vrypDOnIW%p4AVqUx*5TDdPb~&?et_uGPo6WWs z1HqWracqpJKWcVI$R7i_EsMNINKYs@?yGs8fF5FoR0HaEreIHhs0|5AMv=3;d%~ z9$swyIw`YOEZ}f)^khf;Dlq2K1J~~ljsR$0H@W$>T7lnHUDN&pW zQKps{4Q;hn1^rFWUca{li}uHXJP@cJQ4Zxo7g^(G@*(j=8bsMpgKtD$hctAQJ~g4u zZ(deM2t7JmzrZ9>UOjalYBi8`%%d0>uHYYUQD}7Y)=N^5dkq1-Vn;#6dE)~-2C_x9&HH1jAGjW%*ls_^VxMW!w zsZ?U_Gy8Z$>GF2qx?L9te>iS2P~X!a?wk+mS+-$>bWUc*E{1D!$NxbH{Yx`n=D5_j z_dCUS+|xlmljiI>m1nKH;#Z!~hiM~6c-R|UJ$N+kk~-*~X_48eb%nW6^$amI-7vIq z4JEec!#46u)ZLmtKlR^Vt5%Y2i5?UlFEadc71CpBgqc1c&h+D1JrGjE0)I$UAkObK zi`f2SOH`@U)0C1J4BMVFbNX9r(j-W7=0gj+`kf2;8mShJQL7(M=8M*L67xze%ZOXy#4OuJX<(J4-tTW*o1E~6BNpOQPqkJji~JLzSEY?Yea-l+TwLKLX+==SeT%`?gaDrxp`B$zcJ?C436$UX)t;5J zQhc{1yI>2GGHLZr5sVn9w|b=h&U)vM#oK{8JBd1Cju{|=vlYVYwBW%BMsmVyqxxzF z<6qrtPZuOkNn=bDXRvYh8opb-7JAP}pm5VSVPk_6FAV_3vpy49=H#BF$0XzkPOx5` z0R_4vHMp=eh-uXzcNgvb5wnhCl2=r)BD$Rvg^%6)1d13@w!Sx<%F)yZ0a1YdW z4Q8gwaN1vxo-e)>xVJM)46~Q`q@BL~CGb6BlhU@w)P>f_cb~ zQoe-Yzot1KGYFoD{d~O?!Eoi}6S>bN7`dMvanrVXNu#i)`Oo3wC%oV(?#HAv_PWXf z(kF}mv$xnc2(%%Fyd&Vcu8|ygV~1l=C@QQWL;JOP82i(~QdJR!q{q%8!lOD9=Oaz+ zB-ll#Bw~TPLq&$D*H|^I6Ia!ye`yehw-(#taP2+ljd8qb$VBqyRSYX3zi(xY4J})8P2WOKgT5`dguDeEm3Ak>>dM)W#{F)tQ-fpGmnf!hcd%hTd=sS^CO<-6WgK3#DhF0 zzXd^O+PX*n;?wnV&U9~xx#%+TNRJDgx0E>0hKGR=uwforMi7}CkhGqqDvNtn7hhx` z;fYfux9@`aG1OY8>KwB^9+YvtkWsCR`u1?^{PS7{4Dy6GB5OmaDaM?cBDH4Jgb(II zV!u<%RAnNgLzy98zLNTp_9{e>bv!2pzhTSUb0HSC{ug4Kn9djTbGL%wR8dVQ|jZehu!n=*`y`0s%-3ew5|H^%H?8lhxW%)DWNmKYG zus9LkrMH?4Hw7l1vO1atPxWs+RHC4or0EijxUmi}4G&^2j5X(tRiP}vrx#@oO}cJmJKX5uUPgGlAKh!~aPP!1 zjPRDiXn&uFr|ZLr>U zV{jk`DnDs-{d(`>+=OA8akI0j_GOg;h3k7_4>H9uQ3#^mx$JF33( zN`8I0r|F#7gV>*}*KbN#MCa(amFFpStfv6spTdPnFIIZA9!)LRtTT0KjuE(a4cjKo znI+RT$6nWP{JZnJo$`XFsfCTe-PZ1<){kxawV`n0;T&xv(E?+zvYYpacQ70M9T8$q zO?Vp74diX1I)>3m-r6RW845B_mwikk#|H1{f2vFn(yMClcyGKYSyuilX^nHlc?`1@h=j;THSr!YH^{lA z1eUO0?CBZ@?ddwvLIZieXQAm;DVK?78bf5KiR;*bQ!Z}QVK^yNg(}L>s>kY`IKZhT zFww0-)jX|8bhBHcO>VNJcu%^Joh~}hrZyxAtjC_prpet9LrFT;-p95#R>wG$)SEzuM-o*%onRwwpK<8smUeFuW4ZxI4 z8gou=gVh&cH87Y^-)jz?e;40au3DVYM=5j?6;8>msH;qzVq8WtKR1} zvlNSVCR;L0W}x%Rz&|Lvh(oso^2}=6C!6k}UsvW$Qovhdd;xDT_qLvKhd>VLp1=SR zKT>FSb||6XowFr5?dpxO#XPhJEewLUPko6~zZfwLu4P|Lzo~q0UgoB$YQ|K;|N`V<;nnU_;325y_9}n`H zStFXxEO^UV3S@9N>A0g}Ku8nJDkC6SE;=N6#tobAg`UybC=Hedle0jMxzeSgaZo-IY)HpLL7R>4Asqg#-7`6}LbsZ-LK&tIOPC zTcA|h8}N3~Rd0IRBHbC@)DzOj+JxkI<5dzL5;6vC*I8y|mc2A_!HMf^g^D(h#x#{V zsX?ua^5feOt-ECSqKR@oiP2?gL+)nlPYH^!@D_9?VG>`o)mn6 zlJ@y-;*|KkVCBJ%l(90zVP59v(z{FB{SnJ7GN7CsLKN4AIscaJuLW&GQKZfwklkmKWKUQ zyz;RcrTXy46G4zOHs)G{KTS4aHJot$Z(#F@Ul=pH-tcsW;_-Io#9u{Q)1gJ+AP)iG(uhO!>bNipMEeV4z%&>rOq=XS;-{$Iuk<; z(LYu#xp8UTp5W*IB6z7kfDw5UZhhX7{S$wz+|>62vh9At=j!mrUv9i3mu`HR^{QK$ zK~%jssbc2of3Znn5V$(z2oNAg10-Mn(Loc0MSt&Wan0?lj9b!-lX_o5GQ55ycP}d- zjIG`*{|CI~()35`dQ_pv2euKXt>k!eiR<)bx4fKuisNc?xRLzh{GGn>V8c_y`+dcn zq3nMiEtzgrmTH&DN0hr2=;@v`B0nyLagyPpQ`xa*1oE)eOhfB$Q>61_B-SE37Eiu9Lz(w@jWEaAfQm`rda$xuVmLRe= z=f>gj=2e7-tE%Q)wNiJljbwgmKAdY98DfSN9)D>{!elx4eAyuE5iiztSkH$)ie$pV zX#YzI=rRW!p` zh$MV}xV;S_dNTXb=Ix$mxamWM)rQyrBVKjLs%{Z9_U zAmICI;GKXS5MqCAdKHmVV#4d|E1;Sqc=iPgX?zjq*Tkdg_cU1j`-b6w(48Wyz9ngT zi59>06|+_RRk08qX8!Imn`Q0^&H8%}+BTQ!U}|f{gDOf3#^n1uk-8~9E@tjMiO3dN zHXATPQ^xJJR{2-OZo`#sxwGP4*|9kdOsDLa4?QnoTU!v8Qt99SDz{uo21RBa0Ifr`#oZ!b^l}zmT$)7uIvhIXeF^rp_QyMJmt@!{WbGQM|6*_Bg(MF z5qiIWG~ht(Fv{vU+AJj+t#t~}cDZvVuI-ZI=l&d>n$lL^{h}#ej3MaWKvAQ#Bt0Kt zmvNIWE0`1$+fe<KXudcTUPcfOBk25o1^%fAz3FM2m`$yem=wStn>xiP za?7+i@$Ia`7<8N%BOdCXa|g=}9Vm}fnn#3W^V;!x0n_^??QA!tIrhUrdodK|1IDU) z|2beH#$ro)B_do82T2OvV*mK3eaLfn-KbR>Wl_ze6ET|sllQ%Jn3lIu_YGHox1;~S zmR|nrs0)UH*A>{6);}5@T~Lz@>9oeR%<+@h{I~+` zzq1hseKC{xG>cy3wV;o78Ka=hc9Fl2Dq_E^Wm#i6^h6ADDWbA|MK`K@s&{fiNv4qOdt7pI* z+?i`TOnTAr?$B=4g0a4vlGi}%)t@v`>z&7A>=v?2ti-k6h5Wn5bzQsoW>++DaG+yEUM0->q@GnAXqCQOtF}4EE>wN76d)+nBiqy(z?WuiEN0Ba-ON$NVfu1?f4%eQ_!IdUt1~V)Vuopm-)5<3;Wc7B+d}Mw z?|Mqc?%Llp@-aXk7wGXGHG_$asbg1&weg;Xf-Byj2{g337Mfbn2m61zTlZj$e8YFU ze8YEF^bMaMU!pu=`-UI4eZ!AM`i38M`G(JryM4e9wWg#G_--wuEKQ*g_$BV!eL@$% zeZtQth~FNUzs;>jAF%lCF}Jnx`Je#GC;cx%)Qkh{Sal&KrZCzij{|Mh@ zQ2u+hYQlLppMVDY0!DPtbX&~+y;|T-^=t` zb@3dxy3axTiDISqS1=z!R@q-EYPg5%Qj$E@$T@#I8)lPXE@Qvb8~8XG!R27qmWNHg zm>^B+X0IJ4;y0GSn;74Pf6-=#QRmYw_o)$=o;#V&+)^ZD6a25wdOI)f(!!l(2d?>Q zhF#9J20TNsoUauk)9De`BWQ($PlPXZDB*h`?>+<9FqzJs{3J&{C0dodT;s6Tm0*o% zU`Lc5%!~7hqTNSKrgnb-+Wj5g?!O=6c7LiuyIO?;q1*%aUtk1oj;;fjXPrb1;yzkNOFZ%u|+4oOX z?0b^Mxyd3nstH=N&-z%lJ^I_8hqe79du>0${HyJEqP%7kCX;?9)_KfpCf4~lv-VD? zXr{rNdh-(1?z0Wn7;ZHs+Zso@lr+ z)^FVlZzN-xc+Dhi)1@Fye^J^8qdfU7pF*3!)yZ`a_hhk$Zvsr-dOZkZ=3TCzeHg=W z{}6nC&;Cwre2Lx9Z{#V59e$ranf&+Z334yevrB>QG!t@f)?^WrjCY!)8RpC|Cxxle{Qz+i4CKcA9<7Kj@9F-yb@Pet*{f9iQwtIVI0h{RirWZ~X}dN25I!cbzQu zOZZ%6`&xg{y8-h8>lytV(D)9>A+%XNLZOaSfo6oQ;Z>&*?+RIzwKLk7C4TAzsPwQE0>;zarjMs@?}x!89kjkpzT>b z{UJZa2BYMFwm;|;JHez3LktHf-rx89r}&-t((j-AlgdAmb6 zs4tY8ByBwt6ST`bUjoGlt&ro~UB`?n_*l$R$140J6&tI-Sch+YrKK@d5izUy{&{DZ zzI@4+IabVDWh_6NB;FCZqy^8D2I1K(c{X#N&G7yq9-gO9_UCA}%x1~6ne!CyxOtvC zS@^Ap=Mv=x!{M1xf5}&q9Ppb0?_LG`vTB!NS>vP!YaGv|J&yNkU+K`8<;wqj>14X& z&WZE*rEO^y^zf_#&{KdO_fU_+y1}<5RnSvZjMWfWe^52nPN01&$GRP0%}T6Ujx{T> z9_GP%T_jdP53vG07^MPhmSfFItcTfHagO|Kve?rMj@5#7^f~S)%qWLQn@*o~cuZyP z_~<((rviDZ9DIU?xXD(~$Z;B`$M3zM#h<4E-*n26{owA&q2+pdV_;Mj1+=`&UHqP}-{{V9?c^{wNRf6UIw@6`XdTA%e9x7$7?&(4dux7SG) z%5&&A7Wzxcf~kredI{O4fA@_qo~u-5^eY^_SG?D{GIkWm@ev^KX9D-M*4uMSY=yQ; zyQ__!6*rnPR!(J~O{O!2Ri0p2d*A2hrxEzkjHAUFdkWvH3NInB) ziqLcL#9Ox#X9H$`4A*G-i?n`ML-)_onbah&AJ?;5gbjNM`?tPCiD?=2)JgJIWp)il z#HbnVz5@Qz&U#CKFOdF4d$%JunH*a`Yb&5;$4@pw+rNb;)?p{7ju-oU+K+Y}B4a$* zHU)o-$Hy|}VhM|Dz1H0^)8!YG{-t^dC1eAhqb!-J?P&+Dzv33_(qPY^V#L{P{FgMhO}0~%g^Kn{D5c9%+VmurYGqQSiT<8(3a#F`@NfN?fBkIUAJdPXVa$n!?N609TF)wy=&=;~9Z46dtpOo`Uj zy9#u#uw$vmSfaGqQAV4cwa@RhG(QQ`sAglo*UEEV(#s29;&*zW-9|hq&v{ud@B1?U zbsy06x??>1Cv)nEXOc$pI=%$D@|iKQhqQ|1J8{mA&t~FQn>2Ip3FaR{XJua22h95t z%+6EmcqB}l{#l&Ti?Hb_=U(=l$#JicQ*qvZa*9=>54ZwKwz34dqSZNp|f zIu&|!UZECv%t*Gt*qNx5uK--7`x7@MlXb>TS!I#3r0z2{lQi40hL1Ai=B&Dt(v;Yq zrE7#IBOOm0N7pouf)QKSXPpwe5oK)MLZ#2#AzsH?>YTQFZn-#T7v6Tx86^B?F^ZFX zppniXy(G^dWt6${+&ym<{#z49Z1_&h2ixf73T-5Qr!^S=IrlK?tgO7G(|TN1fn#a5 zJ^#pFLj9Iu7qpSR^GyOZwubmvtvj;QGS|?*>mj zigwKAA3eWcXvY=&NvHe)_XxA~y;(IaVya71>QC~dlPr7>!?mfsH zq&Z7E=8J3ToZf}9j(yhG3c}A`2N-!?K%jXB{N49QujuG2?`C_l@{j%u`_bLt?_cyX zdspu_za`i2Cg|P0V7!W(+)CVLJP+jE3o=^+o+yg_ zCO^m0MHt!fj8SS>5&tx=^Yf-9{eU08q9ZPh?^|%?t1^x-$2v8W;}zc*T=A;@ys9XR ze*CIF9~QwB)ay0At3Tk!ujzy=<-)Q|VyPi4@)SCq3&3_9vyS}47s?T`IQ@Nv6(X9;?KSc{HqF;{>>ok1R`$=aQ0_-a z^Srm9f4X6RTt4sZkY3rhP-9~c>lMHO6l6JUf%Ef#wyVCDYU-RE{E{yKJ7Buo2_BH>kr(4pAbSx7WeJ#!wTbjUzcW(+@+%{4rDmf0$ ze^ZPDsGS%G)b1DC?e6sD+pfOkXHTRQ#JdLj*$U1t2P1f4e2MZIwBeBjg~u~u?#Cs{ zr_L{V{AC&Yo8G|Jm>QrYa%9Drs=Tb4@c+?_*>S7i2P|_@K9t@88o7kk(Q%IPsc&|7=fpxzYFtzeE0^eK8`7BZX#_Ndmb_DF{RJ+;4(m8S7;13{M_blMK zjHmIufwqI%XO6DL^KW_fEIcdQ&xprOz+*PUySCod`^-b?ao2VatXCEwUTnRCeOSN1k&fA{36dcYo2w3g5c@^?>c)wvs`&5*sXH-`LPW*a^J zzlBGU@9TM}_m>N(-#n_IuBP(w_1h`JKQ}vpR_PbDa{orXJn)`gUjBhzKJp{IeAI`! za@q>x8wDO~e;WE{#G1aLAkRKno8kw;+C9y;cB2O;!CLqT61<+8La`+icqf*1oSB-$9NT(@O6?AN}qrAMp42yBGW4{o~;865osH-C(=Er>8>O z`-2LeKjgk~im-K1eCmUh(PykOt~72j9u%X* zoAX8Zq(83UlYYtNlYS}Slg4qMwhHV5E;Gp|{i*axhvm=v#N~&+z~hH*nkvpEXv#4X!Tc$7f32MvS zTgHU8jIrw%ahX3`5xFKYzhpwL*`omO*Ws_xhx?JG%0JZfjxY|?w03m-TWvfRenhu| zapJGW4fwY0L9H3aZ2SBm%WkOEv8a_Ju2UCp3TelmgW~Ica!ktVVvZ6S6P*}wd&cK$X`_a zmgX&ws~N`R{0*bF)eJkoLwH+LD(SV;0K<_ky0x4N=y$cKA=Gjzwfsg1q>|x#YC#2e z-fzcwN%%IC8CPX>v8&WsZEbfgW_RGRKDEVJ=}h73z2i%3|Do<(8&lGUpymp@vG|YM zbaUZqLFco}0XPCQy#w4;1&~Tn5Z*6SaKhO0fa+I`}Lyk>$0CyltD< zfOQJ2b;WlKp!kG>yP3Qqp5++sYs}ipcP`QzbsgOcZvI2hs)sB6*5w6@d!j4f&HWFU zElKYCiLqUwJ%8b9&pH?bMZj<&>^HL@lhM~xgjM+5SRJcVN9rhHGV$Luwi&9vow1yq zQYf_V6;JC*XWEzzWqNxqj^*~RP0UQh*iEP2xVHF7Ch=)LT{Am|ZPk?+ko~=y6=(FC z-@&y#mj1NkD9~W0<&7t`rdq}7v!)lWh2Gk>us_QxlNIAA?h|V5UsPY%=c-h7WMA~MZ>yS}cau3ka@ zH}cpJ+^6FTfU)E_rZH}5HvC-h;^H zwmn|sw*R~$Xyg+jB2~~<`B@FB&+%WVKAljXNx<_GUZ3gQBY^eU z4(cPlM_QKGC##^R$6KGZk@W%EilB}45wx*BKwHhq`fyK@Drk%PkSslSYS0%WTF2|# zaqi)D$_cI{YlSW0J5$AZgKB&>c};NAp!T94+WGx!q?i9-s`nn+YWmTQ<*KT&KpSeJ zHi$*Ep%)9rMpniW-Un_Q%g?3?I~$A@Md((5MUIs=d#tp>SlJfF3edA#H_HS)UCrvy zr*outPzU)|JteM~^n}Q-Gf;_+bt|!7m3K zfL{~v!@OY%=Le&-Nf-Q@bfXPRAU~5i_A-^-Jyo2+v+Jrjb#>2+jMiY+y1E$;2wcA4 zl459CSbH!Z-#A$R8+Zhd$ikOoX)e8*7{>0mFd@q{F3YA%8MYH6Kh=&rR!z(|l|4F@ z;$wn+P5v;wYDof^;aig?FsmA#NXhd^XMveA4dQ(Wp#1>K68YBnHw$*IjOTzt%sbea zw`V=fiJ5qMsyAi=+q~aeYsXB8ajdeJrv~$W4K5o6-YMKS-*2Wl=E_({F=1kYV4q&w<=*V4UI^c+XZGty^bnYdbDbRoFK#7ZGgQi2ujko4`j^U614U zzW45XGm}a3<^=?YK;C3#l3`0GlZAvu0|8kSfiPe(Xd#M%OU!_PTipeB#l6BF&VFP7trZ{r$c-7vO#+^wGXd-;1#e*Sr%?X|9;y$JbkY`(9P{Pq$zM`0&G z-+&$Q)xc9`lBY*gw2C}Kb8-$sh_rcnPd5Lo4qu(3s}GJ+cU2O;2vK%g`>j&>D(BdHVWOU;@_vl^@EfoM7=Pzh4>BofP`Ct=Ge3Xybo^q~^ zlsr=^i(dn`AExAgEp^3u1InKo;Adx&r$woj+n-O$wR@JX;&{V$S9%?8*zG%JI zH_r%|=`hpX8CrF%&E)gMQC=sX~er`2@YJ0HZht7 zJ+}EhK;SPm@rAY~LUO!SW!q^4AA73a3;G;y8=}HCml$t9hB#vi@bWhm&)8xNalOXT z7SRX7at}GyYO*-S`M3)bVRPkhDF0ed`TlJAfo%CDd_Rl3JXpcno9%M65^g>Od#-Sa z(5iU|LbkHo_y{{IS_1qyi~~1gUuvlvLnS~(jt8C`);A61Z8AsJJbx+q^EA|av6^Hp`P@1bNrgk(|X*WS@RTLx-+P;1#kZ)G}I5-Vhu~; zt?~OgZEn>Q5N|bN0%7amH^5Mbq^+~Zz6Np+KOI~M1(0U7CdWD4) z`Z!2Q4@qpQ_NQq$AnbA9Mr!?K*|qHSx~8A7ApFt37zaHr;o*UI^NcM?cYaH@A}`K zJKMQ>s5?4BzISJx(D&N`yeG=^9BqeT7_Ej$y7hk>Rr)L=(|$tKPNBpaPp(ie^8rZn zny2l8@b(oyjVy(?a;}kt=R)gQ5KlWoj@AZryjp)>SF)GyDy_%XnE#Fn>B}5F80;<$ zvefIsKdbdv#=bZTJJnz{c?;Iu$D*IYoau=MA*9DXlK zxP)R4VlAv@@9SaTca_daC*W9ECgWuu1GshTLDrajeJ7N-Fymej-G`tbyBeU8XRwz! z{w6P>iAy>dfA{fsljolxZDl%13{oK|AIjLLLxC4q)AU!b)NaxWW2 z!{p;(D9tSsH7pC3brZhkvxXcOcj z><6RDn8jAwE5>qbS~gk+xy5I2+qZl2);G8h3vBy!Oyl?Ee7yZMezT7ewCbMtn?n=t zD*tCV{y(f!HX3s4Sk8Ou`6x@_zXq4JOvAHJZs!DLP#sW8xsI9)gj1ysgc@5qCcxU^ zaLAM>w=N=L5Y%sI;#6%4&Tym0z0nkTeOBS9IX-fX#DgMPrqC!{nV(q%cJV? zeVvf04NwnV)&uZq=k>y0gP#c<%6&AWoE1ku;;VBTSRGMp;bvEaPT{%8P+pq~SsNp- zHmWWq^}AP>^JHBVJ#gv;y)C2%z)ivKCewZ*&;q2;0Zt1{G92Px>M6&}(gMpfgO2C# zmXsB?`cTfe4!^lC(?qD+2Xn(;H5}*0@+)h7;#%n=kLe@TKEeMiCsjV+2i8@YiR{k1m+Ol|s9gmiRpw}lJ8{&#H2o!-VA5_}>!H+Jcbf5K>Km*2VDrJ*)$*TZ(jta9$!U`NkjfU!5FD zgs%HqPRAKJfe2acsr(yn^qovrljF!~{O+ybmD=j19xF3wE9|Ai^uQ+UFNbig4B2C- zsgX(kM4o-Cb!%F^n&W|_JSR_{#V`)`2(H~1GcnIhB5p5-fP)+*?&XrBhvP}3&QBUE zvNF61bt9J|^>H}Ar*NJ718_aL0N2I!%6@zyu3_zrsL%?}jh3($l`8&=! zJnzQYCiFw5M8$dh>-^WcfonSjzShE;&~_R{tQ!Z=b_!(NbE4P7ZzzzX8!P&lYoEBe zxrS>?#3Qv(>3Zvn?cJ+%8TSL8rWJjqYGS@RurGEzw#nDl7w(OV(sO#5JGn=Hz~bJ+ z$&mJ7Pg>L^{wyhGaK=6}D&{TBFcUQkxxE?khP*#v&GqQKaV@$;kjswbt&6S(I~L&j z7g&3=V}xhgl%HwO<6~#3vRA|quen@9w>ai9_Kk+)tG_g=9oF~J{-Q4VwC?PvIv)^p z`$9<9RIqmx>2yAFg#CgJl>Bi2&6ro14hf2WOav8dJ0UwAE@yHiWfzj8v$Ma#+M~kv z$6wpKh3~l%UnRHtX?@{YX^(w26LQ{E&zR^#X zk#DpLS`R#>n@HJ%xxC7cs}iRVpP%X+KU}vIe(0;%iG;O^U}o+w8=|(jyRK|tU0bhws^r#DF`f-Ro;y7H-1DV< ztUa5QeJtI=)5c$G%08BEO@=i~P^g@JEYvbUyI3~S_ zauO`ZZ~VnxEt9=lUbJq9__|##*X=TXE9nfpCkOMPxoH@uJCK^KbRYCh<8~3IU@j(p z1J`L{Zn%$Yg_*`JJG_VV7UzOawllJ`C7mGNGz;Ok%0zzaY*-hAX4o1M#5~r(t`K$f zLfog&Dw3LJk!7ZsMTfu_L(6456R$PL>PWKzuyO(WTwz#NRkjGv1BUL8+~&Jg#v*Dp zSFyclcNx&gazP`%C)RsaCeX;>NyI62Ea2(=z^9h14GbGzuJcuI;pBdjCkr-K!|jw!xRW&FG$@m^RTqC$bz)j`T% zq6P_YooKbF3T38}Gd-Iriw-FcFC|hJ7roQ&T$aET3>S6-$wiPf%$Y)-Ermof4z{Qca!gdPd(m3D@*(g zfI2Wr)>|%cqPTuw+PCY)-S2LqG4v`DCJ#h2_PMoaS*e?TZ*;>l)6;&ZWp|@g{TERs zRo}+*!&R-<{v?V#+5^0tYb?C6-eccqDgD86+D#1Bty6~m;}|{dJ}Wt>$Nf245kyOo z?$032OrpbBzZTvSzf^nX&+CP4MqUs148WUX11VF)aeCd4qG>Z$0QCNGaHJoCX;z#FxLNPb&bc8`6#rS#@D^O`Zz z>;5zPAaP277Q*^+Bg0df3d1GNzbecCX`DZVbO#Jp6%cD|fNKEw2->b|(+o1gAA%j~ zKcapu5J=Q_vz^9vi_i;N=K#l>I;4c8ru)euL}%Q3u1-HP zKvx2R?-LpgW1k^UEpVrtKR~(t(X^XV*4zGsY2N`gAoztnYTqICzXD+@_=_-%2AhwP zag(8~>`$Y9z-SO)1nC03_J{BtAS214J4*v2{Ll@2Opk~EJ8*4<-%WhJQSvYmXO5IS zLB;)aLaLI=zQntFul;v^PVyfE$5WKfF;~!?MwHAoS3s>Z_QjUCtzOk4c+C|st~2&V z*`G(%x{#KzoQJp-P8K5hh-8b6i9x!sw>d5dR+j?);&Ub+fqO@K8HhB;Jm1dJw}<3<*5 zY?CQTafdDKn#>8|8O;KhF+3khyB4(h*HK}M=SZR33$ai$b#a6-Owr2e0^Y7r`p=0= zq%AJ{Us12^WrWR4iu-Ln_UBPedbQ!@(yoZflvP-~ZQX1C)km`SU-+Hsq`0@!MJ-m? ze}oW^JscEo5|o{xFu>xipJ_cx&MpJjC`6<>%tPvWfa=48H3r;iEy$>Q$J z!sM>+&K#5+Sa1JZ8NXQf&d>VRz5BqUd-p-Vx_6hF-L!-$-Mb*`U-vG+a=Ley=IP!Y zmQ=cTALzi#4ny6$@422`2@_%aR-*2m2n(_d`A+KI1z5iBou7Gi?~Y0q+Ix|DO1cbp z?=$Uw?3<$gD~^X%Nwp^xZSNz_GT0vipJ!#P)7O|YqcqQ_SxyJ4lnEWEP;6H^P^CT z$0rBAJKm?}jOUK*>B-`|<5I3qNASDjB8~S>?%BI2vrp|7PnjF~C{t?Nz&OP+MH;V+ z;L2pNp38XPNdK7Ca}jdaYm)i<5iP43pcC)y^?Z#5mrE8!-{fm7aK8pb^hZc9)oXQe zvo;0&gnfeN7-O=1Gn%hcs-1333K{jT^jtD_IWMmo##(RkF%#QL*;2SGseEtdY$;3t z+V>|WY?V&T+D!OGagg^tO{*j4vF=xPhQr~n)Zoj zP5Y!@AMKaHn&xEJG@B_KXBABOXb-XeeYBT**15~_*17*Al`r;BU`<;b#&xbLhHIL` zMA%raV=lsM8Fmo&cO}9qWLRA(`eGMhAy^NG$He+qUa%gP<*kRlhWzzV@fT$c#Xbw$ z8pY^zU9@l=C%BfOL>c+2p7y#J?LeL8l!5%R&}oKwsoiRY^=;CJPqB505bbFn9TUD- zRN2EC3Vnv=%I<0Jh$;Uxmb5_@Z78NgDt?&!H7aJ(vF4D3FLJe?p?m4D6vvSq3tJkbO?)Vdu4XMVa=9Y1- zZKLb|&*U@q$BLTl{jIX?G>my|KA{~^5njpE{cZfaQxi5X(r%O?rAXN&^1Xtgefflr zI~Kebp6km48$z-#BTVSVh;eyvgVL!IqmdEZM>9Fgj5QmGWny1Aw0X@i;40o?Xs@e6 z-$`0he!O=T<~%wRP1(gOHqI(xIU)Oph|g7>}n zW@)*3-;4L9#~YPoJM+`GOvAq5usP;n|8~=_y%jKg7u-H4Fqm!k`}GT^&M^j?8m#w; zLl6sIiUF8R<+&OY*d)^UHy`OeV4s*>6EK}Yi31j}Vd7LCXU`0@rztFEd}<4e85efo z`MI#1@hDkK|4=^!ZE?D3vq{QPwsM$=aU;L|*qyboq3m*E?r$1C^9mnONy9S78xF6{ z)d8Qu@%LY4I0n$4k5sJu7p*u~dFvHwc@W zZNBh;`3d?39FBRc+0;+2KFNm!QtF&v%sV~HeHbK?#2KiqiL@gvORWje-u5vuZ7|X> zP3oYaMi#ee3Hfw*^6_W$2@v;UNxA%ja``3Y^1Y6f>&eofKO7X4>q*ib1j~9E-2TNl5$=3deCoes4#~T;16J|Sf^Sy(yeQK<6ve7{N;YaH4uTm31g6jH|A*TcE6!H;pRn4fC@0< zHK3Fzy*wU9fK%cGo3EKnwwD@oMj6oPk9U&o<%S0IyAMlPPyjWUhdQ0wo8LMYCDLpo!(_`tajcJ2yX)X6vQ{Zqk!QtR&Q!?|@Q{s&3 zHDc(mLvJ{fUxm$ZE82B|+;uJ$MGka3HRxtLY&>a?hjAxnY5+pV3Ttwgj^2c2f?p%YKqMu&h;s_H*p?L0cQxaKF)AW zpyfLE2&bPq-0P_dtM9xS;?nnuKZGk(COVY}X=5z&_L3HCKlBH^tTc-GD3VD2k%k4kL zJIr{Gp7T*Mk2cu#?=YExr|=G*!uyc>iwx>X@14izySXaEXgmZuCpjK_b zK9z}mYK-xl!|wC)eweb2^~aPt`&0W0q?wUrA__5m)*o*{*3c z@ui}g!e**yotIL(;$_?N>J{#Tmwhu8l4o>M|SUw{aP9jXPEf@pOlb$3pv7KTFDk`!`LF-WnD&;;a%@G zqy?pd&)QawGtkS0zNYKOBE5>0$^9LZ?a0rF(%24Ve4onoOB+LECU3)WCF9Mpq-Eh~ zaxpcn@j#hc=5cRcbb;iR->+2Gs4|G>SIwJY+OWFB!?wOB-0adlYjFdK2=ZK;HwKH!cEb zuYF+5gs&~<;OcrctggN7KZ3nuue}7qk=Ih^2@_;_o$hGX85zU2aNiSR1n%7^=5e~c zfpbf9C}BGY)6p_c2zvhDn89r`_(^(71KX}MdI6aO+{EU=v0l>~h5lP- z`xw?ac{Fnzyswg;Jc_|8MsO9|>@EUWul-}lwVbBglgyz#?FYq-2yk+rr;fiHq@~; z+5^A!%=xU9Tk3OYTM*^H*pk5e>P^2h3AW*FPnQQ(*p`XFGia{`gZit%ph34TxU8PF z869L&a570>*1#fDvGpG#0pNBQTmdlX|K@2`yyYOh=?qEL3?>ym;3L)rNczeKhPz3N z2|l&g?TRgeeb3@6>sjJ0{{I_oW+`c$#`jnOsCkK4qaPx*fh~)B7c7e1rU#789f@Bx zZe;}NW4X3vE38O?Q4sf|O4OLexYb0B^iS$?qjC!gm+Zx7$QJ(n2z)ol@5t>Ka)TPT z0UZC=^Kq->-#v|juT8rnS&uzECNyiHzud75^-hExLhl!@WoQFyf#gd*-&sy_cTv{J zu^uKbj#O*Wr}7?RuYDc2A~%+GRg8p@6s?|Pb~#B_p%Eh*h4+dECc;KBul=&q8in7j z1vd0IVqhd#bLwU1&E+Bs!3+Y(84z<6mys`#p| z>*Fdyq;v8ZhPJ<#mXV6ic9@-$ads|(w?!Ren6POpaNX1>{F8UKH!-K-73h>LhvUdY zE+k&#Lj^Hgi4<={L+A`Wc@&%4d%lOD{GGS-=7t z;|e=?&8*XIVkkw!R;93Rj{}T0Go|;2bZ|$b(5p=+pAReUP#)| zzgI=)^d^R-IjQE_eMqYX{CxYNv9uYmHm^Y#?nYssY?*qCHHM_8i*^?N!C-$ky#@An z>FKR3vi3F0GB)VM+#1F4Hx6RFs$2TzL$HqNmVo&xz#G>dyo(nC>08j4-b((M|fPJaw+4ar&waUqJKF#Xjf3Ei?7sk-``e={6_Fz+??tnMV7kIaj zr)i@7+qx;%sXVeD8Dv{OGRSW{GRSZG^|N%j*-a;8WsnoIGRR46AU{jvJ+jBk^JI@s zlhT9xMi0~PN>TQh;qt=?OoY*4D0>uP6J^-Gl_+}@VUt*f93$n7;|t`BZFzFWU$4)% zdF7Kj4MPEk14zCsUECcq0rxqq$K4p~B|$6Fn{oH@S-cjfm=TI!w*1Pt-^G}`SLE1H zdWz|28S*SYnVzjv4dYTzTyd{~k#4q5PYliGY*kq&oB3avQ!PB3bh8P3 zA5?tj@>{*vUK>LWe=n9B6SYnGzR}elVoXy=%8@2ZIg)6nh9*HrGWMTi!gAF~F?Ao* zl==s)P0DuFKcni#_v5bG(HTa@{&ft!38zzgv)=YQW9^Z2Lp!u&c^M_gB$=;+p|=V% zsk~M6U-l_QWlwHW>%Y)_DlFUSr&Lpxy1hB)BZEo(#92-1PWQL6K3z?FBZZEkPB@N& z1r3uSr%f^81wCUw12aFUW!m2cE;QW)rP%dl2En&#ahd7w1egRlvmMbXcwKrygpNwH_1ElSzW1i((9fU zv!$&Fey6BO*tyJkZt8TWvF$Km7r}4Zox+p_>dr_9Yqm!M|2GxZN0(&CmodV>GWM6k zH>3RrKEJE@N* zgQYuXvh@5}ES;Xtdec|Ho)|}Dz7MIN7R-Ca%b>3T!OQHEY_5AFQCa< zmM+5RwfEaa=}eX`Xfl@Gf7#a*mHkS#Y^4kAEqnGZ$~I-$f-(}Lcr%; zVSC{7ID9?}@w*0C4_~ve(^5_Ftp7&NAIxFEr64aNcoZr)x)r38Mcq@(6=W`ORW}Y3 zT-1Dce>x^8@y#-9o`=7PEVm1I(cGWlIn({{oVtmwSPS z&%7d!&$OGBF1K!Qo)Y-XTR8u?kclv%%PqnVfEwQHyFgPYY_yeMyt z&V_g7h5z<&b)Ng9e&Qdok*DgIjWj$wq~&T{nTiYTnAe~Gms z*TfEHxIU)2_vaxzd>n-5!h|d(@m{qBYoza};kY*9&R=??=680w=|Mb~q3V`?i(E;q zjQs@m5#aaR7~&bGM{}#GL#1ukgBsR5m_k5Uf7dBuGqwX5u zw_l1uEiG8xE!1eY2C7mAqI+Y~JM zx(~fH_l?WjR!)yNzq}szFn|B$eco>!dfa3E{nz1j%kHPVjQiICU~gt&ie3(t?OaLj zp?!yMrQW-xsy#BvKUsyoMsxM@`&4@rx8uvc{?hf>zZ{?)kM#FfuO9a|o_ghvpDJ=a z?W?Sey`$fRr5de=U6$EF&>d);4Iw)Bf($hOMDfD2Ia zzWj7ILAu}abcKD^O-AT_^<_74`w6e=Cp@C}U6)&E-{H6RIflAn@8k8wm#g0$P`^)j z{fgW2i+=lOrTe92_cOjS$=9r$54OU7eYD?xU1|SofbsCy0CVbh1B{jT1{f>f4lq{k z>}SsXVSu`QHvo=L^xIxN$%h7*$DdmL&Erh+<9=~j>3(MAY+4Gw_b!#L{U4T)#rKu& zs{zuzpG{W`dvbt0|J`r9taQH~U>rT1tzR+h@d5hbkpcSQg8^XwGXU)G1GLLW*}4~x zqhItnj#ef$4-GDytI>XFL98EIa8Ez9puctF-2uj%FS~9O)^lY$@%7io8TYw9*EMmE zXNa%=Ir!iJ^OO4erw8v0FmC=fz}R@+ldh0fd}wJz-0doCd!+Sc1^F3#{O+`D&ps;` z{|;K#XFi*#m+!n)=996Xvc!3CVgBD+Uf+NCC+q|6$Evhr^qyp8T*;MvSrxlk%h>M+f+W#Z9Zb_O zSsB68s355g=5h*Z>1~h~gS@suUJUZO5Arf0ulpe{3-WpZ^75&?Mw07@c&iSIvs%2@ zh$SwE9Ak0DJu@F4>m=0-^0*H}YvX7SC4Ikm$Gt`R0er6!J1Vy!jvD-q4^OF=t?zLw z$`%vracgl^5WORugEj?&ZNa|~JXK~S7_^??`2A`*Ka+;MtseVs{N)zLGWNtco=eum zgAfuq2jvG>!CRH@6f^$|We>2k@;Uxi-*QNijH5lXHs3;ck3(wQFO4PE1696b^e*V1 zE@;&*XyGnsp{`#Y3N^SE`zp}&&@$F_|6y#=gFj}A?me9KB-f7Z^#y&6Lk(!J6Nq)3 zRcoDY{RrxG61+WXEw;{*_yr?>K#b0Zx2hoEm-mJE{T}Mx8a#1lkNcBYHxGZ5_PC+4 z$06t5of%@q2|qD`Rt>@!j$xj)!RUBw@{o7^*Q8(lh%_+#`? z-n_xH`7XMgIg#GvrPX+fKn9q5kpbpjm7IG%IrplwbI*};Z#B%lH#L+O*(6PmV3+`Y zK!LxnoPS!Uek^mQI?YavV>v@x^_xiQj5A5;RJHfTbpRLoS= z|H_pT)YB%EV?sG5lw(3UekjKe`30by0F)Deasv2$wD>K7a!R0_Ae0kS<%}eMrih`! z{TPXRkHmc-97mFvCoN)hjKru%Vsx~`2vP?ibqS;{$)!G*h_`CLpdEhTiXm2DHK!eD zRRw9sMpDhOU(M+STH{Rz#XFA1bm?kNJ4#8g6lurn5ayI3?Re;QOoKEbz^#(rjlZR> zrGh2|IZc4vN+WOJeE0{vRh7b=%lpDP_c{qcs-rkXK%2g8=7F3dlyFN+Ab{V45vMI97Y5B$}x-{`!4Lc5@B~4Z%&tR zD#$3_oG#&15XMKT=xZpi4Eq{>%UjDuUzZU%LdqjwU=8PRd#TWxbMWg6`Gu{yKRrCYKZIDeD?O9*(qLw%)P+1YfUMds#E+|%32wVf*zm$}u^%57Pm;uv+_)&q9~Wn4$3^+>O2 zy_dh2^7s7q!S^!$F51bNwJ6|iDIF~>P8~_!`QDk;V>iHPNqi%$3>2?3K3Hc~!#Xp9 zuQNyVyUwi6TW1tc_(HS1|8A$^>TF8bO`~Lcv#@QCBhKKq@6dzuFNEB{X{AH>m%*(> zhO!VDrYkSUt!OVt!_h0Cn7bJl{wIhE}N1itQJmkb&q(_#?i$ujPh=UltIRHl^L}&46X&Ep<0Oyp z=5s<`96#5PAml^H=M>&wn^6aY_Z#EABPa#olmaEx5eM5N!sRb8zw*9te%ZWdCUHu^ z_<8(jPAPOA&IpcF!zXhyA~U=*DE|v{Yv=PR#b@h6oUQK2n&~7lGB-cbQZmj@oWr$- zGdQ~N6w|SkjA}MD`5c9`z_p7xI+t53^hr#*4VW8-_`k1y851X-eHgYU`ZX5ztL8B5 zQ*Qmu-54u8SN!r@WlL!-Q|a-0*(E}jj(na&k;_Bx60@Kd=Z*E2g>f5uR9qjAJED)c zN|$e|u6!SkjSJtV7Q83o!nRbeJu;r}Q`gs`9KyTLh_8&6#&Y19n zxGpLDAS(M0n9qn7Ve@x~$!M!7{6_3xOha44Xt6rXL_YIKmbH(^2=!lDcbkP;RXU&wL*o_0l4Y^1%`5 z#{6qIXC?AKg!_rz#2SPz4z!BIgzXbiukruOdR0Nanv3f7kgV4>UayJVGE#oMX6$Q#7OXP1euMMAtE$Ti~PUo^-N>U>(<|b@giB=N!LT1(FY&|P`JqNW2 zpE$erKP7S({-+`_EMbbL1oT{u{h-s3uGaSDE%`Qn@_j?qOUv%)NhvZJ^Bd$DH&eJB z&P~kKb9UVtW4mAHhh&{+_fh9#{);*{7uES8S?6tebv|J?>)c#a=Z9pSxA8hNdHd$H zmI7aByYvs1aUX~Ytxn;!Wds%W$I-eGl@^Q~t}!8e@Pufgj~Q%Zq_Xuy*)OUiNuqSB zG;%>pzJCL+&8nPbFT_Kv#ejz+Ja`?!ty<;$ctx|mu^RGxV@0@r*k&I-=~`BVK1lQDn_6#DND-W7NVk6Fz(UE8v69& z7RU_5bc(V-W=QK#K#WHa;nQnu7Vk0KCz-m7cymjL*J+1gXxuNN4t;6}+$NK-apM?# zn*whF%MRiUicUS%bYNBZAI=BPCI-v2Z|Bx^!di7pZj7rk?v-oM?w66VPa4a8mHmLS zgk0?57G-lJ*S;cuBiqub{}+Xpb*Ri&n+;19=G(!7de{*8s=JmV-#_$0LyPl8yoEoF z-Opd#zkIGm+`Y)Qk(O(t_G}wX&bAS@mYBgEB+`WArGsJmn8lQI4>irpo!N zoG<@PIaoWKwYyWE($)I7rP%M3D5Gy`RIs}B+1D!XZ{*nB-MrPP+MP1gyf((|rj0)L zb57~P^z5W+1+`X`j~l?Q9;6eigGd)4efcR8IxWgK$sXhKeGFh7kCryo`y6@6cVZnL zf8oQ&_2rjqLR|SH!!i@&^6d&@8Nx5tuIJJI@eXO*RID4xal7kRQdr5$ts7ywSDk20 z^l656@w_XETtn;?W5w?+KFYO+g@rt{43Bg@g%_yN zU&;J6Mp^b#YfM<%ORn?C1tO;2RY$HjfV23$#V4%T3Y@j9RY|}(>$xtH=R;4dM>dr9Ekkt_90#7+ccAT(m zE1B66E#ke8Avld=`89>@uH|As@7dYe^W=t90)vES))(rB|ND(yLBq>0{4e>D6a4^tbc9zScS)cZI%? z&*Ih{3AuQj+Gh)!x2Lc!?NrvKpT@e3(^(fggLRo_qNghup{QevGE#m=@6@wc`m}W{ zefoNqKI3ebKJy%wKI>d&T~TG;Nv&I|tgUpd??NoiAMy)Z41JY%7L>OR%3BZRoekxk z1Ld6y%~y%7PL{(Y4ljvSwo4#Imj)C+z5} zvd)9D&WExtfU+)xvM%ENApHH{-(Cde=Bvd?8E_=?O()c?O@Oht3he`F`@+`PsqWS% z@esWAurCnzZhd0*+iBVtCi4n%F>pV3c=GAi4}dtgi8L_vQJ)vTaqL1KbOqVK%6R*t zopfP`j&a)#e)u<&@HE0Rf#h@tHjWc}ccn9~q`r7Vq^JE9 zpash2s#q^?m+h4EZ6{X`>ESL0Txs!Lt3gJu{gXKAe_q1+_iv|g+A<(cXB6S|;D3eF zV8H1#j?<+B;H1tz9vLU@BIR)UkmJ;oT$hzG2IaW_Lw0SQAM9Z2)H5|Z9$oH_jg{>*aC6 zMu@`Om&Myp(wU#ZeEK=er)yw7T@CZex?-)l0rBPYNZD4vKb(VIhyRRqE&n;|T5%2Q zI^t?psR5qG(5S;^))!4&5c{ypxg-piHD4EA0_A;#|?; zb4rW@5g%u~#s7&v{9bEapFJP-@qZqe@46z`$Kyg0c4Zc3O50Q4Dwz4REKJzBg4Lu| z*eJ1vR{4ZP%*S;QhDbdIymN*=Q}}6{@31k*nVgmzrS7HiA;6FA1-q)N;~lGmzLo8D zHO_i*Z#84z8W-M*N{L0SZflJ-1aZ)$9321ZcpE&Y)sx&FW6xGOKOs-#tTE5_dAOU^ zjf{O;ULCORu~x;;eiK!C^N&frd2A2#6q6xAOs6-d7`G6efjzoU&Vf`xdQ;YV9Mrna zLYrAUE#O>b&84{-sPp0WR&fT?4YU@iFH+K}0CMaU9ZDQGMQ!3F8!6_b$(*X`-deA{ zXMmDg;C}xY6QQ}1RNniw3Y!7lK$R7hR(V6N?(XEOY!5AH6*NYi8JfV4T>|U;zD6d! zS6u0Jsklp9rH?if;+h!uQXW@WDzZqmQD*eInRpY)q$kPLioRxAh1@6O&WsDchT^te z#(jnB-s2qx!beXa9^rhH@GHkXFHmZPwGCx>XQ#ofr9r?3=f&x*`TmWq?7d*5BIS~b zw3Y-(bxAp3wkm!H$#hMMufGj#ZdNcKanQPDuXUPr3EjBX+B+_+ahA#aFM<3&!~Chp ze@}Q5`G?_63Jmc`y#U(A z_Z(-Fwjkc}@W=h!SLUgtuejrJT_21)6pR_*F_qumRvkvW_HVRUVzpUI_&p21yqOW; zdQSWvhsA9POlZ|BWC`!a#zP6wka*uy_DcoeoJip9Nk|KMM@e*uz-I{Oy#+q2xNU*K z683Iv73L)0kLR~k0vHF<>%b{QK0+YWbN97c zw+(BPb)WUTm9#EhW37h%a|WRWaQdjFTNkf2XG2IRkRf)wuiU?@%p{+axd%8Gt&>Ng z&lmBv`a9U&IS$_UI-TF%d>LDg%ipmD;GN;^b=jgU45K+ZMBGln+u#}dcPP^{6R;Rs zEv;sPRGHO4KOW_}VHx|InD9+m3Y6!Z*j&^}h7i23yeU2f@0o>guZH}73ry^Uuhd`b z7Z%vB0Bq6MK_+=Kj1$;R#1s$f%Jl>Jv7u0xnMCl8I*F4RWL6*R$+cO!X-nA4t?FJd z+B`@9ywx;hSeIBEt$VG!E0d z!ZXwU^LU|-aSnEkhfWFE#0OA^RlE*sb-8~u_UH3<8obTq{MkOF+YqxA?XNn6qV1>f zGlyq$XAUr~+db0JeMrE_?rjIOFkp~2qmkh4sf;}(ZUSAr*>K!m_o9RWy}cm;^pSkP zEf4g#7bnoV+$8`(-}5J%J?^E6CO*4QHoa$J($0X6XMMuXKnLm8Cotg`PqfLuTl3F^ za96&6*2<`;w?S66_ry|z`u1HtC+_w0WweFJp~QP$ld<=a)cX~@mqjbII+BX)Ln^e7 zSt`;-25HCK18ZkwG`wBI--ZM1VSw52`w0Bv{je~t4EDG$m^QaFlq89x;{GR;po*;!yfoPbF$K8Z>hqdIFS2%*jTQ)1^4KP2CO8=@EP@E%z#VadBI*4~V|xvV#AKdiQXHy?>LIrVd?<3BTczzu>{{TFlf;ui?#lAR{4{=FXDf)9)_>B6} zF2cuOfb;!*;5?`R=U@K-oCiys)mipqZN;+oM+ZF1Mt!tj#f0bSoE+khay%F1sVi9xQPw^hu@q^367NR+j6_J@_erT48g(i%&ey=6l;5 z50hO-kZNd7&MpJ+?dVw?Js3kviIrOmdHi?%t&cG@O`bmBIVSG&|K6tT)2Tfqa`aa2 z4my-Of_sh?-kmGd%?2s$ccwXJSRWB|jJ4(k4V3R6Weo1uLE&=Idv?KC#d=Y$^=D-{ zjRe}Rqz3oQjQ*#^$?vCa`8pRmPpk7i*r2R!idMZ0VpIg32#lG|UpDAY5O#vRO>`KC zB%~``I?pbgf4P28Hc=1z%9ic&+j_-8{?1j}9jm29xA!V@UX@Y4`)6)xX0r6M+?QA7 z@m4Zx6A#PzS6i_j*W*%;ORLT4ajE$KZc}=6Qz9aMHQA~6+84$MmvUiUE{f&OOE3)` zuhNLRP}GXZ-TQ}<#@48k6m7A_EBYX;E#UmQu(6o7h+Kq>PUI5F!}py&^wh-I50itN za`jVk)UxqXmaglXWAHt)=A|d%{Goy1=tt|BurH8%iP^elB=qPM*f&HGzZAz$>>F^8 z8>V94pk(QS4u;2z^H1-d;VZdka0uE5M{X!v3Y`t35=t?k6pzGPl#()#{}831ofbuF zqvP|(FU^+GAFUPnA*~H-BEN{IZ!Z4YE~4F!xy5~R8uZQIl%7;H*EcGk(X4n4=r12L zF7lbvS3cu+Kc8}$&w@VkNsTX->8p0ixMecsA=#8n?nTx%y+uAG)x01#8rJ zJZo~qd5z=YIb6Q$8BHQn5EA44d77F3h3$x(m}*W{as5{luS5jwiywdqmJrpLVMq6Rk z@=6}Cu4mTce8Th^%%yC9WJ`#W98~BxWbN>#g$+?^zJ3pu z_PlJ_Gd$x``H20=cp($@_=wfAK4M`vH#si6t*O2}yP$oF`?0t*Q=Va5T+lu!@jpYf z&pgayFxM@IJlbE2rP7}E=|pH;wGWigp?TX!_1(|*gKjS%c)l_w^&h=(@4rkLW~4LvP|^&2(i zb4j;QEA0mUJ%xXFDl78_o}~09C!4S%roxvV(z6#drLT-Q{RwcYC}@*UBy~_Y3E8J3 z<)6j*U|YO6zS!2R$|vK_!Zm<%x*>{o(8Qd1t&jdF+{@%^!J%aHRjHvCOBIgwcl#LY z*ETC0{wl}%fko*)95CIRGTo}8be|2F?k$<__*CJz`DT1EZ*@j}AxEd~9ucy0ys{yh zn;UAhI_+UPvNYqaf%&9vBcV>%bh_(1Ngv8PNv15voDkx^QN81{UV4zga_$(e$~mPT zcNnO93+2qma+=lMndG|q7z;9#t7Yu7>KVcDoR^NFw$gN(aXaE^Qf(x)Ui(eIL23Tpb5LDr;Z%KC$jg})fV$R!zS$h{(XJDxGAmH`|1h# zy3x4r&bZU#IXe#Gq;|}NVtr1v58l=8cRJtw3Qx>b?Rtsa^%BC@L_C{}X7|0-T<=fO z$;JuA^YSrwAUcWd{e@f`&rKH2lLY226J9}|`_isc9w#g!dnOgm?S@U`x$z;~~H#+6rpbD$B1IR0-mHU~=_TeCQhJ(>1zCBg5(#02SuNVgDCnBQ(P$2rhQ+o@2TRSor|yW_?nE*{RBC{VeZG{ ztM%^EKF05qh}!Q=jpG`JX(oHpTr(kG_ZZW>A=j8zql8Ecng&|XG`JK*+SfGDg7oC5 z_bzVEhWi~-UMA)z>Ns+pC%4*6krJ%}X8%n9c~mM!ALo^}Yp6rw_2-T4*hQ~pUfbyio3mQ9aoWC?2|cSEJ-T^<(0K`yl4^B^ zG|cFAwHnk3pF+Ls#~l-tUDz_Ioh?sh`MO}%c-IB9CL;X-30X;bc3zh8@$LOnb4JM% z@0}q0T(v^XHm>`!laDR+i}^q2r_xu1vQrey(&t}ID7=@2D}16-tnihjn~K%F$KJ?k zjujDKlz;WOdzdcwHmKU@kqL#o6^t%Z@K(R}@K!4R6Me+rDC2MDmcaAlANj2<{d%eR zRdl(r(!RMyq`BLZM#)c~hBAKRNu!f0U8KR)v&Y^OcBTPXIkwcb5!B2+vbKVRAlx>^ z3CM9uZmo^c)>howK9oFnf zUiiM1dIwF+2y5>)=Y{WGsjs1liDB)TF?r#QmHL4+kqm2h=jT&fsqaG*v9LBhKYUoF zK9wehLjEK3!pkf5185=`*4$WLxL&ExrmFrp$o%BbCw{g0lPf9lEv+L9FRdddJ&_`p z%^OD&N4=&mu6~Z4bmm#+;W(FY?!bexQarE0GhjT!sgUDZ_1~w^7Z3H@f7ln|Ry1m2 z-IMK0>wPHAa#ii20{cSP!8w5A`^-o9@&?FdAOT5C0L2W*x(y&kx_P zn`%7#5BcHk-Sj^^pWo+)H+9qRc=%KK;fZegJr935KfJP=>OB8v^TX|KnC2M%Tz^mNc;Z{qfm6R;Y+3KKP*+Q?dA zABy2}tdYL!XkecIh9y#E4pK?KXd{p`Gi&^yDiBi>4(cToSVaiXy4-mN{ttX_JW zb*Z(<+D4yPW3}=%T;0={I8peMmv<+$O{`1b$hwRhSr@yB?a)l>R5YfbTnBZr7i%}L zMf5uM7ByjSa6QZU{X*Z5$g_32x3F~NW)=xG+j!%x957aGkM;f0C{bQIc{A&5xJ7y! zx`j=LAzTjF^Ds(xE`r}``U1?tmzeM-@urV*ufK%r7*hJMRzh-n;K#;@@^d$-dhLG< zL2D25@U&ZmZ#Pc@5^VWXxTym%_@w^rAyiMYH8~3{>@VrgpdD{=dbN;t>5uTrM zJa5gz6ZtNI=N*9O?fv5U#A=1-?SSVU63;u>%q*UFNIdWOL3qxnEpE@RIG(rX;aRs# z;dvL}d1t?P&d=g`C*XOP#PcpT4T`oY;(3?E^R6F+=cnftx98vYL_F`z!}Fqy!t-vx z^B4W%dFXoz&tCwZcS}6)W>d56dAG##?jMBb?1{zgxgd;q{vr?0OS5=x0X#SNiznL9 z5qNF}Jhw5_P%<>aJ;L2+A8a`b+_uI)`4-yE1v`Ro8_7Qgh#GTCJKA#tN)yU$uSqO2b^0+VL#XX>pc02&$PUmso%Zoex z>|z{#1aW8axF6=ly)s#x?;?mho5%e;FYdSR7w3Br#66hD{UR^!;y&;_7~(GFaZ7aX z8T`^d#=Nl5b5EYnLHrZ=)T^N@2^4W6wN$O$d}UYgTVD1 zRfC*SG<1{MlRUacqj-XNlj-gM!zPORS-HJ*q`F+o+4((6!mB9c;T}ZVynl-4?kh~S8TxJ)ZT4Hh@dVjsbD+&mlx;RAug#9%MVpO2&z5Zl zdAP?Fwb@UX+j(s^2iokno;KThifS{&XN{-Lj)yjz!`n>J&742)hx7b=pO>+V!ttqI zW6QQY6x#A+-j)x0+7i=w+fwnrCr=dT+CBD#xL<;uRBBAlZ-j_**kyJ>oW`O!tF9=F z(^M4a!!?C*nzM0KADmH$k2`9NOY&tm=M=>mosFZ~@1lvyr_JMezHUaXSL@O5^1@HQ zM1}u88;+1mi`r+*m_j>h6=MqH)D^`UU(`OyqBvJcnw_?vfc=IKwSsxvF8KDwyH>6P zJ@$GIM_=D~Gpz}FerOHJ^NYMOyN7wqvxj-K-#twB47aG>^+ou8yt1%P4cRzqyw-gy z=1hj1QX_1mJ(9=E;2ahH#vft5?iu`y)&~#_yKAD5*Eoj5(C?46#LBEO&*Xb3b^rXH ziTU@>A7@?KW3Z3<9qgli&la;sSzq+#Flec(_#F9z*T>Yi{~f*A-7rO$-d?6?`3pb? zHcGlQ7wFO^Ntfp4(WL`+L6?4aiK0u8hkHX2U3$2Hmd^#c^n!;j=_f0?g!o+Np-VRc zU7E}35+lL};1d&tAN4SKY?PE%kj=bj!}JgQq{W8$_!8>8Lp=_F9eyeNE`fFQ8r)}*8my8A&Uzjn*lPMLyjzfYy`*{4mqL#as@y}bI6JU$T9HhKqder${|Y& zAaww#=8z=?kO)8`9P;A=$RL2Y9CC00!~#eaha6u3`Id$Q%^b3QR6gV%0GZ4os|wP* z36KDX{J9{_3jmqMA%7`=JOPlsIpn1R$b$eeIpoCx$ejS0z#)GsfLupIR+yYrfY)UZ zzn#bbYeD?=0GYudI|?8t0AwEyIj$hLqX9CEL!K{4vxJ7UFnOjRbOFR4%H#i`05XGy z40R@+3!OyqBztW^sVT^1PoB$D1@YrF1XMvtQF5VS8e(B0E z&-;ynyuTvhz;q4~5|n+xZ0B=GlVK(+IZB>jj}}AeX5;mnRD# zD*)nf$g!T1b&^;d4wUk6Ar;Do&&9F%&ZxYweZqnHJnmch;p4-BnLPZD`Qdfpz#ty} zYJPZiIKX)L)A`|hgaZ{k{JH$_0Jid)ycTl;5HGL?#3tHk^1BIDXQ900kin0vd+1>8 z+`7xtH(v9*^X|Iw)${H`S=LAIuYd5oQ}2HGVBa?{E1Ak0<#Yb|^^rGyK5J~^%R*Tf z%DE0c@FV?+%+-dHccBdCh3XSal^kZlr;l)1%zFnsR$2G_Q^P+xdqwpbbMB3zT;}In z9@*>C<5%pxtYxq1Pb~25_v#79cOLcGn5A30-Z`cb*2$6Z4Cl8j2x0Ji0}Jmz;CTn0 zzrgbpJimtL9(Znn=W2K^fag?rR>N}yJU@mf4bMz?Cc;w>PYj+t;VFZM!t*8YCLhA{ z20Smo^8`E(!gD7)*THicJnP{(0iL7bSpv^|c=n+vX-9@CKpv|NUN#cmE8*e14m|$? z&pyj}^Ami4H68qxeg(<*gN$|vk0=#y(ho9iqYOf=(IG=fCJklB5${K8Wgi`T^}sEft1TnMlb-UxzUKmKY?w8DsyR?U^SPpg^0l|hW@ zmXOFBR-E6Sna1&Ds5vz6-UhcJchjlI_VIg0t=4drk4MvWFAHIq&q|)p6s6B~F)u=CkEttG+z!d(jU2d&|a}NkF#+;1hx8UezR|)Z|KN~g3LqZ=-8 zq!J)+aLAGZNI5`uaLA7fAR0hk=a7R7ApfG_lGixo_yWiWG~`$OYc6yL#J7iI{8a_< zpX2fWToC_pi2s?5@&8f)c>o}+5+N@YK<)s@A*Bd;u>f){KtA6SA%7}>Z1C`|xzKeS zr@t1&JrQ!5SAn_gD1h_;0wki9%SYc90W z!?Wf>6THNy3gQm;@T<8{$HTAYLM;!!nhX7oV7_k@)}yzp{ICw z)LiJXSYjEL_+$a(aDeQ~A;)@3h1KgoEK~5O*)R`(nho0rX5BDM|5kqZ_;AUkd=&pN zKfEqnlHujOnjc;rE?L6o!_)cUdxT34;_;u$5BKn=sy0qZs3he7{~B)^fi-UkJWwgJ z6Bg6Y;dvjP*WmdxJimwMA$abB=SFz0gy(#CPJ`!Icvir3Fg)|&nE}rvc#`nM;X!}e zc$Q|s^9}HjAH(wwJTJrZG&~Q(vlX73;rSUn7sGP~JZs=tLD51eA@ksw`u`$ts?IMr z9#(k9pVKiF;kVQXnUdg1w9CXDRM$PiXeC34Wmgd&!!tN;u|*T_+zyI+Xsyd_q@XrR zxwH(Z&Vj?1igVyz`<@zBzs31<81oZuH)>rcW9S!P_JlLarRrk>o{ku?LUsX zdy-nF{Ia})B6m-A!?i6i!t{zWlfps)6E?zR!gBJ@|XZjtmukgBDS=)|YXup-B?Zd)*pe1F52q@|!P&OTD=L zD7{s$RQ99v?S@7tpnW?s4RXPoaT$AoDJbdCk|?)Fa;7gr=#<1;Xp>V|O}Q&fxrFQ` zmZQ<#dn-K-u}9D8alFj_xALg}zl1SKXAXz9I+$Qv)mMwNnU(g1K7(tNGtzDRg@ufj z_PPB2DE6Acb(|F>N>-T;mwRC^8vOP$p%EI^zp8F>;v|;>rLC`0P2Wx_eH)&ZNEsPK z|Fd)FDa{V>T~@Vm2++CPO}xjbtn1+^L6RiObGZZv!d!Xo(*)cxjU*nil#Cnda)zfa zjNe5td5su9p>Z^n5l5CyVmOjbEo5Td7zr{ggK-<00&YlnyOTxHcWp39s-WGUob2coB5iM*$TSP@$FllKH+CtOy?V3j4hNmS3 zZK3hDi1M~zqAeJyWwO4!E!s(9P%f8p(zt{nmU@U=V@RT${E84$+C|(7 z?Q#?$yd8u!8$Y*Sr}JBbtIUvwt>C$l1}$9}1Lq=OWJa5Tr#`O&659=fz70oo+h#X~^t?DnP+C6J`HT7Dj%sz%2XMb0OyOxIgCf0mUDJ$H!Hjl*|4rTczmq2Q@<=ywz5IJX&wM5H z@-t~`;%lLopXt%dV{E3b^zt8T(#8tfu)YlF<)7Z)tCyciTa@<<{?-WdDL+r@y$)Fh6MjBmZG~Z@`&l%L+Z|%s<2Qrr|vGAJD@hNo z7~h#);>rvAOI(3AtZF-nQahobtP)Mo;E)-(xsmnt=6@|qt2l(KX2pXz%tTv!~J$B(w}_@r}c}K zfrBu%mPyhvEY1C<{FJ`aHrj9^jS3~p6dwUV{Xohl#G2V(xBW}8)AhV;>A z9y_B?1sxTI8SGo8uv4|w8fy0={a?S>pU)$<(-&5WQw2x~U}=m$$={K~vfu8{eU~sU zuD2}E%y3gUHTeC-{D1c6_E^zG_Budg8}6DRw!$zE4H5PxNVK7=dO(*`Z@*#YF$6Q+ zz~jY6o4T(LG=0e3N)8FJHu^Sj|KsSKp0)uzLD&mVF4I22{%PR8p3%0QYYTXb9CwR( z>~$7G219!ox1b2x-<1}DQ^|F=n-7p;yCWNTeD)&fXV=?h+)tjn{az%GwLhtW$MYzH z{{H>;8E2L6?x%ohpa?k#`bn^U^8SPUFz&6FyFav{X0&(%w-YO3w$uOV{)%ATrxm#S zHAQ^X#SJ``Wf8#f-|M#s@MBt`yZ;E2y%2I%1CN_h1n~U#`!51Knr6D;hyrzoa9jm& zG%z>{|1ZK(1o$>B;)bV)$#Mv}yMf2XC<6HY@556BcsVWVhO0N+1PIrk8@L}O!udZ3 zR}tXvv?4creE=ea?;U{e00v+5e*t_&falYCyW#8$w;96uJ;2$-;4J!I1ZNTKhiQG> z@a_iG3BqfRiI6mdxA*@-c#B})OzZ20yB~0F2zLd*eK3Q&&;Me$i(r3E+szGsf1q9v z{_z0+Aq@V${|~@l1p9DWKleDKPGzW+ODiV<*FPeJzCn_I`ROD-e=dBo#&CP~R&%#9 zj2cj58R*wt79$QFf9|&?ql-#OTEwS4VNV&Kwu54P+M#(OJ}tBYRKCvBMRZ#9bfXPG z7fWB{qsK^JCK4*xfjz*j|klvfoQWP9&9Yw`%dXF6s!z$vnR_B6!C^f_HSv>D}mxwyu-D{L^e- zewmkR^OmE_{CRyZnjfEt>p!MzIa~Ng>Hm@fp8gA*)GIjt{)O2gi72Eh*ZfeCLw2Y#JkFE!f^Hm#pPrTh=1SKF+6; zH-sehanS}wGi^2ZqB>yCP+cV>bVtK3@0zD&Sq00EV7qNFOcUF)!oyXz`=TtX@^&%0 z-B6WiBV3yMkZAV}frt2($_Rs!5tN;VN*yg^bq$iI;$KSg41mo*893%kxZah05e^rg z7k;>OQT7veUn=yaiN3T#Ru^9!#J~7@=U`RUP_rQWVse==+~*B-9vEF-wvQ*ZX`gwj zjYD|PG7sP#g@o-eD1mhh`{gCV_H;E-v{4A}SA`sIz?^8LP?0*Ne?Y$n5+dVy;k>ZF z1C*t@lxRiO34p4t<|d{~!Sk$$XdZgT*7k+J4-jS6B2D;i?!p6@dT`4 z_NP(8-vz>7`2QH8Z~r|0OT>E>kzOOZ+&7!nm`v91wP!V1WLV3#XLaJ@sAKv0HGuH3 z@we?xE>pz2h9O}W5gyCmE^jq!*vrB(CHi$#8(tJ86Hw{}W8pqrFVP9?wG+V1n%6Z= ziGPVB&Lk#I!d(pVovok4VZH%r^xou z){B>u@gtq~Qm*zs)?DSR2I_7v+2HpqzbHFC7~Z zXM0M?m?to-&Ne@?eEuc!E;!s(zH@jlI!eYc%iY7l9p2XIW2H?N@V-J1BlFhS>wh}H zKd_g({nG({$se3Emg_V44p?2~>;T-ofA;O(jJcL!qr=xo`=_M&z2W9NFpf@bf#SS^3^@;vqaK?58s10`x`c(xxn18A%Ugd{xGakPFKkJs1BE z<6t?utk3$(ISk4Vek#izFJlGJ*%vc9`v(`Dy+`G)(b@7m)7D5jIt@4Tl`cA3yaP5i zI~^|R?4tQnC;Btc#a9bD8`?@-Bk1h3ht9rote4JC1D*Y|OaGmtOFA3dyTV0hUsdMl zzkT{@fPhET^QIZ%{dsqq?xF={8ns{JcG9x-5lo9@={J(4-$<5zBU$>5WbH$)HR8E9 zrb^FvHamvI$=&1m>P%O7?&xee9DnfK-%Hne>MFc0kDw#>>Zr5$nb>MR&SV5pz?Kte zyS2KCTae1P(XSPB>0IR(BQz(}9y%j*k#fUAdY>(C;y2~uos6AfeO6|Q_#8O*58G?G=dtX#GZ~2sv%0{A7Xs^8@ne?0%>`ZH!qz|= zpSERdqcwmF4zK3Lg|f4=%JN-=&jdcU zn(v7@>SMGg+N&)4oe5rU?HV!9iS1?l?G*3%%Gdb7F4p*iF4o7@w|Qb0Yy57Y4Wvhd zZJf~U8q59owY?t+b??W|%bdGM!ocg7#oahE-99hkaW{_qXyZtE+|B=Mja+_YS!e5Y z1YZo?!QocV*70{|UMjZz{*h8v&g(fI6An#@GGgvCp-{K&5#Dir|Aif8J@a39>LeK_ z-|=721>T1n|oO*)N6 z?lPn5NYaNywRGB`6Lejx(?f91d|x)6$H!PosQ0{bu&E z0LH$aQ;olJNU}|*uLJ2FRYsD%2u5Uz>gki9eZx@ejNWAUz%V(Vs#@m2-9mMfHynxm z9Yk`qOlr4KpUE4Bp*?YpD&$@tD%Ajoj%^NCD7nkbH0>7ZHyPhfmo!AIk?YGOovu^d zn=Oh#oqGghrJ4-hskPkZJDo6SvRb1&vY$q$F*?!x&rSE@zlkCyShJ#0cWgw5KFN*u zj6R{Bz3@w?chfJO+)Yrw%e(0pn-BAz^?ID|;XPpwukC{7Ac1qnp*argLT91{@*H}> z-S=5t&L6TAL0m67yog z-G5Ht?(b-mcYk5rfA>3Ua(EYO676D5Tz#A8{>RrOcyAAi@BF>Jdwgf^?cL-2-j?@o zS);svV`8kOaQ~hkxPJ#XM#TNQN8Eq^N_*3cIj4(xwgPzxd!Ai=n-_FD&k{Fl8zpXr zJ-AtJZAw9v+1O(>+FmLV;tf=HOtJQJZ{PY z5t%WP1)mb0IP(bCj7I+M-YhGJqCX4jp;d-g1C5A~5~U+jO62rAtC7oN+RW1j;d{V` zK>u!e&TN@)oiPY@s&a_J^QDwyP9wJoW_oBP8m+Z}Zt4fOa+q6*gq{q&hI(j?L@T!E z6{xkh!mr37=jTx84#N2PAvX40nlZnp{ylKykpPlFZNJ&$j-o#$);rM5gL>Zv>RsAN zz4am%;z4FU)cYz>uTK`=7@Ui>r||w#Rxg^dzNV(H-tAfZBQRas1|-b_=b-cdoTwN~qIl)p77@pORP z8`cv0E|mQspTOzoWA!%c&Ow%tv#~WjS>&laFG?b+tcUWa&g!)_MgsNZy|Pf|k+b!- z#200j7P09>LOAWn?~wnQG`t&du9gaVAKy>BQ!iESggf(!DuemGU5Wcey5JLb>KbP(l5u& zWW;{c?C8DaeBY_JHM|nH*!*)^;Uar2Zq?gnJzR1W zrMI|NoQHEsAL_<6nUDTfPX5wBe*0Q?{;xf_inw#5yZyxR9(n`o&%4-Ne?g%B?VkE= zc6~#P`JP6uGl?~Df7T)z(FZD*1I0C5?L{VNx{7gpHRidhsEo_{HljH4`5Yn7#rBI8 zq=_Wg{z-v+Y3sxD z7YFih^sLdzfqc{{IQgdp@;7_nZx7@@>B(Pc=VN?%#K&hExy}OfvKOe3!NIpg#=bDf7GT?C}{_s0BZ=CRZ0-EyFO0=}#Z;cgG zZkO;|ATj|E{{B2ICBN0%%;Vyy>FYz$7x;2|3@M^qCzy{d>VH-XpHd57RSREK3!QuW z$t-p>xL(n5&KPiR+1;HuHI8pi$uj*tWzHycm+4&p(Ho*HJ=SiL>KeM|r(RM|s_m-kAQ^(-vib zOaAn8qtsDid#mG)j`FPdKr+*g=`VFeoJQLN%2tK_vG4a5u(Nz?aM&OBzG58b8@X(- zlx$Wo8ihq#puAiE@NP!F8;R}2p@ai(0@^qc{-P~kyxD3v0XUXoQlg(g@xNF@J(&Sx zvEfriTFt;(>;QZv?h;Ra~UYS^M++-^)O(YL^8%SC!Kr1yjN_{*@aNDF|`r9?zl zAn9rjnL^@USq)D^ojy?KX~ij<&)OE{6)bB)*#gE5al7~-g~Bc#C{S*1riI*g@FPPF zOsDS0%NtR5&hT)O@5@^LWxM=#U-<_D<*~+OSgYT&@+U&M95^FR z1nldLz7|l*GDkrwxA3&g3H)~ie^24RWB7Y2|6Ps058%JY;O}Yt_e}gfo&P=pf6w5* zGx)nnM;rDcB~0*ZrufQXU!%oWF8it%UwQ0nKk=2%zV;Viz1UZS_$pvuW5ib>`x-00 zO!n0%z9Q^vocM~eukqrmhkJmo6N~iS@tl|b+)KxC8AqMkpWxdAH7sUGk&yPZ zR7$&}4r)hu)eznq#!GUZV&yn)(}d%~HcSyWhQk@L=K$Yot) zrrL9Lvv0003e44Ap1Hc&H&<8LbG4=0xsq^QAAsu~0oODeu3LR@-5P-F9uHi%`rx|9 zhU@7b!}VYQu0II4rrU7c=7a0e09=3Yz;&AsuBU9cw)PmV7XonIE8v=8!*#n4uGa%_ z-Rpttb{|~t+HgJBW4JyJz;(ZXtI3Az4j)`!1mL>g1J@lsxW2dHda=iF{StudJ^|NE z8?HNjaMAI8xbE}7b*B%m!toNW7kUg=-|>F99uROPZMZi0;My|)*8?87Hu&Hwv*Fs_ zEnGqUfmil6WIVScX0gAO8;8VmGyG2ly5z!6;yRO7}fyltb=snFnD^Ikzd1xx-cn8YuRr`(N}M?C!^iV?WQ)nr;au#9m|*@RKy?%qYEi9FlRX_QYy)PQ1F2h&g0g0`U%)=Hy|^p-@MukBn`pnl zZ6fya*hF9)L(U^w6yISVWVBja;-*LEf%>%ws~e*uVC?vUd&WGN;eE!mSbV(}$aBBL z^^@J--qdX~OBg;NUf#lD7Csusn1vSQ`Z69PWjx?SUxpJUL%0=1F}YJixGW;kyzZQ)1^(lRgxbxSpA!M|n?Nk^b(O|cTyz1kwF zWkQQ{jX*>8UcO1h1x=fSXo*^GWUSZYZe6h=$~gAQEy^Gl-_m`|*XMIc&<2mpcL}o6no%JUtVumk)bqQJ=C>la!Jw} zLX)*aXl=sTBQk!OjN^%AqLF(|l(Q5nQI8$aSGLdW5YIkBy)TYU!t2#XzA5(5^)7v6 zGB$)t9i)ioIG)pz#pFR5&(V4{9%Hhf@o-;Y^b~||K9u`j^p%Iaj}kUe_iGT0`x;k6 z7IH!vGoRTcj8OAr$$Z4MeE1t5p4>2mZii`Sp0)WSE8|u8HUihhgrMKI;+0q~R+m2j z?J6)Qzb?P8-BK}mjzt56mC5I^X7gG5CU2ibbW&N$n+(Qwb0d>!pllhrqL3NiTjqt- z617<=CPVTjZt z;v*aZ>mW-?eV}MdXFjIi#&hPXWo$>IUS^!G>NUNqfD5ka4R>U9ZSM#v)$webI21?G zn?bEJSoupL?)#FLTveXEq&p7UMKG=wf;`dHx*nqdvYw>6g zYzFY(8PfKXnEm{DMfRJ?Ri3-iu}>WANk?56PaoRJoq0REGhbkLW}Nbvsa#(DyYbxa z26*IBX@hv2#c#VPL2X@3bKkKy*>`NPyu4!v!yQ`%ckEEw+-FE9cWkyaNrV1jyomLt zoCK#s&K@YsL(c)5AM7MQTbmN^&QEYVZYdW=n*RKuiZ}~1=JKJOUU$n|UtVo8*@081 zm^?9%k2-s8j5Dfwd}{wVUdHd0ZA9VpSus?~1Y}>2jpwn|+)T+W75Y*~aOE zWgArB@KZ1^l?;a;*4xY-Om3T?&}u`*I)$RVWj?8f?;ZPRtc&qmov+rw{ym@8u-~=v zw?1DVM(=>hp05t4TN~z+eW=?`*{?5=>G*F~^cA|$Z@)C2+n;3`j;3Z!9;1<@Zeg3u zv*xprxbtS5AkGKt*LGfxlMShnWn8Ojty3a&BRs@e0dWj zKD7n#DPrek0(tl@HP1USkhhy%@1#K99(LZsK;A$*Z&4tx+|FAZ$QwGL^F1-#&O0ZN zH_FbtIIuQjWM0F`fjv0Mu6IhH&FOaDQWqX`zmfC=E?3685V5OXEfV|DAna|kJZ!O% z`Ydy)Tg=J5f09@_)+xXCq-?P4C~ zx;`oJWv#WgM%*>(*`Z&it_LXOB3+%DU`aihpf19QKUtIZZS>V$rI{BF_`MIwA6SL%- zuKbg- zFy~`jFrAVG=83NSrM@-A{ArLsfS?`B5U5iR=V%|Ye1H5s0(VS-(=PNc&cLk}w$Vr3 zyv%_k?doTVs~;J^XZA!c&t7ZOi$|z%l3Hi$g}U6^bL2c&r+H!tF}ru1z?(w%xEj_iF-kzrr*3Ywfw0`D^U_Gd%fnPS(5TOS=3Id4C_+upAkH!PON1e^?sFlMC^-?XR6zjtaFwRILDbpmMr%dAh3%Di< z>jQb>b(Gs(lqyh*@LHTpmvDzBNw|*-+_$~5zh;wLy{NX=sUn==e@`6su?qV|p>;6x+hrcn7 ziHJqw@fCYm`c7H<#r*hq+COC8SUXRyPg$c}pN5!!?FQRxH!kL^9k;=#6>HF2K?l8!5_RM5RX5fEg-#@j&)F z(e8Jo-LKSd;rnCUrtERO&0JWH+TB0v5!**?$B!n9SyxPc5c;T6@~GaHIKJG{cuB{f z4fO*4vRk0+0$16fE!$(zZ+kh)L`J!ELVjNl8GmXTj0N@0#Tp;~Fp?fPn$~IfvW=t5 zy0n<BhzK+VxM44CoZeOcD@i%tqK&3fE+cmU zvlCT$*%-40bMS5UB!0&_ZKaiZ8nNRSp}x1TJ=WY}`OZZ>xDxr}ZqAT7NyWeKD>czWc=(%0_Ha_;;7w}9>G zj&%}OkGJ^VuuEHi4_idazOg;@ZIbPA-OOiUVxQlK0goiiGDx5JY~LMWir&vig$Mz}A_+j5+!!GeFVcRxuDR=uyt;QW;c{IQ&XR44qf7tdNK@GU zt}I8}-=|>bp9?iw%>6ua%=rPoIln&mBF~sGW-hlmZU7jbDEIqdO24_Zh51jF``7vX zdG7kpmiyOa(b$NSZ#^DZSMTeCy*G_ZgThDpw<3EgfrHTBA3-0~IdoUdC7hG`r zbtBI(-N?n|s2kbeV@G|^XnC(JFPCrceHum$M}w!_H6NRz+^=HtUO{O8RBj0!1V2-b zoy2X1W+`Ln2!*0ACHyMjdJOgb-BFJbbHx!yKiaO>^4fA~QzHK%94o9Mb$9ZZwW4OD zwMy+z8XC7U8IPpbp815w7zMxVOoo$;BU8~6^;tOeFD-ZD zCboC(>R@}%+U-5-YVR^%d(T08H?a0z(j)f$cFV7&56@rpR`bbHzkY20AEmx)TqpXj zt!z!NV{6*zS*vZU&A^)Cj%taHqwe@}nMoY4=8{P?``!Bj?7-XD-8+GH9`9G;`NByN z5fAIJa;}SgB7o~lCh_yYIfL!e442R~>uh|O;5lb*3asl3fqA<<(Ef{odAlbtZ!g*NCUNVb zNfF<@S z*fO$)ER9}_SYvBBr^`##l%P|G7C1Inj_o#bZ#6JR&2=W?9K1nP>mg-H>3gbLa&N3q zk#eY)VT^;=7{i0)80QU_6gc+tc(KQ}OC6FYY$#d3#p;>4-dL3pRmz9noFr{2U(?@H z)RMNum*tki&pJcQ@q2=1#WE|ai&Tp|K5*qQENSYzB-i^Pj(jqSzo#JABV?~f7`#vgKSRN=| zyZplqvW6_baU#}up*+O2hNYowE3;hAOBh#XH36U*J&SYy-6ZaNJd|ikJ)>STRymC^ zdz*6LEij_ftL6BOtB!U}GRkR`l&KEC`7^YGdggsuJYey*Ci}~6cU~<=ix}kZ9XmWJ zi;%F~GLXYk?Skbf0n1T6fW?+2Pv&bLHSxq!g^f|69a|FjY0hL0?+7D8wi~*N(KVv# zc3o$&vSUSff}1rwCI!ZR*mHmr`$5V8dry{jWXIdq8nJ4uS!1F`8tN&4CpUcLO^GQzlP3Qz)mmRJSWG55@!)LI)wVHij3g2hSZ@{S#$q$u=`C8y_T#Ln| zEX=IApcRGHS^>3IvRW(H_xbR>M102M+g@!O*EhBl+tnE7ZRx_E?mIEU*7?tPv zadRA~o6K!ZieRn<763oEJNq34jL2BGlsh^k?mu5I+C-kRHT_JvTFv!B`GiNTZ-o4@ zcvd&OR({z-w~^d1YM4xBqg2MUrI*3Z?5E3@UlUZ-N7G`K^AP0l?6m*s`cupDP{GN+G=NI!d= zr^BhfeCl6JX-~=MXfd`TVzF|H>yE}Vuw}A*>lROaMa6So#u%Cdd9^rh>-F+9;HROF zhIzxAqZXzoo{JcYcgH^ZPaVUXe(WeDON@JsS_AD%wjniOV2>&I8#hH+<9)+WE>a9~ zSD)8a(y?Lhk;bx4NQq zqvc-LKMQ|%zOFCX>slbzbped;NVcwTlt1*=l_Wp6FATeySprNar{-_YkM^!MfMzyo z$;$kqUKV-c`IQPk*%8dT{9bIN`6M?VNBS4|yW*M6Rq?^KV?qqd97u_Ok7X2hna$Vb zlU^bpMzPu2m>(~~(>#adteh-qUCCb{ z1les+5e+Hvn?YYoyu9j(dU!25} z?!I(#7wyC88*7ZH67L^dZTjJc`9`UD#=JX^OUEUx^;>B7xK3%U`zkrDH5qL<+3)CA z8LKuArK_Vbriyu2C8xJoE~B@w5B!};d~DNQyi`wD291rTySWF@G8B?eZFYV`};R>ivxFi)p#a{jNEZ=fwS!B|k3fMhQX) zCx-pr3#0iz_0GG>sB)Eg@5S@^$t!89N1)BcozhhAR&tu^K=}PuB~n3sVdrHUhUA$w zxuh>}>Z9=NGvCBKO_VXPml%}Y$bO-9PiE%d%lFkeg~iME#R&S)PUAdWg_zxjsB1i& z!e5Qx`#L=9W|HqUL*( z`FpX+#0>gApyT~!vbu>jSI?ly>Y4Dj3I0xpzcbZ#)hAXPWJ9bOesg~uzl`HmcCLQT&ebXG9F^}-eKwiPnU?e5lqm8= zD&>NOp&jM&?Kf=mYbZaMNWQXJnL(RXfLQ~0b%50XIO$Y+U+li>`^MZivrZ|16QoFO zAj4x}HW!323hw&?!gXj*nlk>Aqf3+ffBKJ-_piv|yl`cX&dzF^ab7Z4Ez{ zyJHk#?69u!>z}qP)=hKFG7s|jofJ(Gv0bf$X)<;otu;+DD|QgALm8rS2>goYmy(y1 zHuIe@#+OaTn&}3mId(8T*W=eBeYuimvxptbd4vH|cwD?T^ZVUY(!6&wZQkoZiZ3onj+A$V$_Ls?+crJ$h!*WbG`EM~`FUfwFhB zB}q2Sq6dvh(P%WP)x$a-)PVN=8TQs9o)d*?q2^8-CDP>l;JfoUKL?p9>YlfIG?||E zq@xcUpU+vI^+Ne%A9ha2@$z@5aD?a;bWki!rGL;Y;U6S*X#Jqg26leHSYRfK)sI!Y zAKYFis8uRfxHk#RQ#HQU@RR*|MzVSqMZU(Zc?#BkLhZcvl1ThrDPu!_ybv3UQNd$f zqTe>3d&FVXEfp~Qe^bY~KWJ>4CL3qbwuVrJSyMt@QX^T;t~c1(^&>mGlD0nr&Shc> z_ig3A1a*2bqYP^F0&-Y1MKg)#N^5{?Ik)t%Xqq1#!M~LO4(%b= z>--aKpNk5=PVw`$?)GY4h5IhR_dolwdFfxl?}7}*o+hKAOsiRJry-p*+7pjj(5VZ# z9J)lA05{af22wh$R-{74vypqKVG2K+t;BZNL*>x+o}N9lUB?t6tPT+x%q`I{y+2CQ zlvMG|J-9C0;J$IZ9mI6m z*CZHMjMd|iq3jSiYZt6Qk1FQ^mGF}xaX1ZB&9DOJKF8O4-0?7fI!WsG#QfWJoib0^ zW%_qo=BqY|^SuSmPgSz#@77KJ?w)+xJ}blNA$L4+Hd~5+Z_Am>rVHo)q_5&!(jw&2 z{J-f_IB!!*-eP+#zXJE#Y+4E!6OBaGwnSltsU8Ehcuaf5@#GY4e-SnDCgXlT?tjuY zgc9yYdhirZXQUE`(xHIk=qHd$96}TDo8f%#LOrS%>XOI#QRDP`i8cpt{tBw06fzO3 zP?cPUhw@#*L#J@RTWikSfb)C9xXUXHiSu(x$lE%maGdW=QcZ`_68g5DntLd-svn`} zL)sxUH5VYE@J#`dzJ)YO3n`D6j{nvR`T=XFl83OG^}2)KlsNCUp!_&NRDCpY_OYa& zG2URrH>MJu|JIRuPghi^)qH&Nj34`nDb9{^Cij+m2Inzj{j#?y_D!n#5cVB&`D7#X zpRAJjwRcwhbMniw;@@j_j8SaA+>S9S^S92E`G@G z2DAu30tDs=&3}&KzuNg6x3G3Hb{MPaJv*Iuf^ZIoD2h7T70*b{fYjt64c5_?g3~6W?zA z6zOZnt%Fq3ZtllM)1GLkP*m<|*KXF^X)z|pcUH);0e*EYmj_}CD)WJ^$G-~JLW|#b znEuMqN1|Pkzwhz9AIoA^tRq?9<1kIZG?W;R_Vg{~O9Mpwb-M*uEyVkJPmCuCLn0GPE%W$?gK7_qd4Rbrqvo}7}F@^69)OSs-;9mQfKDEN@nU9j@DHYtpW=ch8 zkt-a$Kw6sH#s0Cvkq`5{_m1K8y#tS=F{V}o{qR|@E@W1fD5 zC&mTJ2mJT+)C%{zAI0`PJ;jyo#!30s$5m6L?P01`%J+V^m)CFh2-;FKhdMTv=gK*; z#st>t279eO5^FWfvsNGJnBuHeN~{&8r?RY7Qp{ws2Wy4?#(b^D2G$B=e(<%Lioyk-46=lJrj6{FP^|@I5pCTBST|<@d>Yc-cTa)5hn zIlw1+TSH5QTMlsfuIlwVqe$#&YS#;>NH>$@3rM;`xvD z@chr)JpU(x=Rd-Om!IgEB6UQG(|`8t&Pnv+(@*4{0dm zJCY4hYRo*r^M~R4D2jSuVGaK*>tuaa{(&oj3eM}?;!amIgkHW9p^eZ7FMb8XyEbZF&_V$tUUh5jPE#@@f}Bcc>LD^NA@BPkKbSL z_`OJTbe_%QH%E^G4*%#LaQJeq=6@{t`_=$||KLBmN>*NF z#}w}GBS*|_jB3nYb_&V~q|Ha?QUci@-sw4_JPi(Tp2czB!x&ch$?;o-ltdH7G+UOJZTrC$RN|2M(IAJ2IB<6S)bk1ig*rJ{2l zo=g7;S+aLT?AOn9eT9CV{=~x2=@rDYcWMJ~WBB*ZX`cHFZHoE*b%K3n;=kHfo}OY} zlAIT3Ia9eWV5;dv;Kkc%HRIyp4ojXu>r}YI)I*Z!f91ZzQpuC(Ms;@bM8=(lSUWjW zxlF0eTwZLD*|Sfgvkz{ksI|h|p<2%+)wwiWuN!1^F5YJ&bJ=~yTE{oJ#Xs}B0keGh40q^FoFfNgnp){<}MJUK>0))P0ei+-zb~N zz;}jL2V@grF;9=T^SC3awhTpUfN1Zifu&Pz3uvmnjn>mJ;nrDjx9Nojx9sA-bH$tv zfHmW9-K?p|amyIaTR_ob;RZP9wj}y$CFCktpwz`}^oEe3ZaHaV(^Z-Vr84ve14=Y) z)K(_gyG(-LX#x5U?NCy4IBdoG$7(f{OwCz9F(y;JQ9?vqrUVJ8N-^R4K!qA@l&A5R z!}RP;R&!JCuQiu z(AgE}v-7PQv2k?dB@#y$P}7CG6&*6Y&6B?8)QCvW(Cqf*e`kAl8QZ(9LQfZBwBo?2 z+&2&RmBzHI)oPg3sk1zi@;mY3z@7M;ZhZyLtC05P%%1TC(+Ym|1Qy4aRqpVF25da74`y@WQar%*@tynbp>hWuz28S-Y240%*m8S=$bBSMBOT5#1u zJR0d)Bi{cNQ@J0#W7k-x;!Dg^p;lv7SvjxwyDsW&%2H3pD7;C`M=5z*Z%bQD=J1{# zvE})9*!l11ZHd9a6M6FwSYrx#o)cRyZy2AsQt}bhnZfFOphxUJ@3q_90e#-UHxR( zWs%11**mkt#5VsT?m$;;KI@H&8j`QpsX3mt^sfE$!FuL4l&5x6@3k!T-t3~Dg!Anz zaK6<8I6unL-}WBBx$m`Rw{ULC0_SHvfOAup{=Vq}oZ0IA2=)5-_7c)>AG7!cXi3uf zK2#2nHlMO?iSmJ-v7QnA$a3KWviC8CYaguzS^AK6cZJ#eNIYAC*1b+2eLI0=s}Ghv z_sU-GIbXRwI%)5DQBKlWd-=wuk~$5?*5c-BJTJ*R^H&L$I*75}-Qn~zeHX?vtK0Exvg^qC zJtP3z>7{trs##&<lfH zui*Lfb`Q?UWd{V$pLcq2F0TsKv)=7Nf9D(!>Cw3Cc+uUI-{hNz-}C^kZ0G5 zzd3-{D-wdA?R3xmuzL`;_j)jA8?vm?`(3UPuXq1W!N}|UaTn@5(QTbgfjZCZLY#!EGkiD9{cq4ennlb| zUQ$ud!95}QKa`6nh@zowzNwjtKW`}H)r-84kg(MOj0u*f8KX%rSN)-yV$^H!%i!_d zP(x8nI3A#$;b)qp#RkWNB>S$>B_7lep_j0|SllrM5SAfCQH2jdAa5|_37-Q+;%I|_ zb1>0#gmVZ{8JvaMU`^E#&d%`l7azX<(j~s`>K>Z(U)@8z_a^A$7=};MyU|L#r{nvd z?)$bxdk=8&sR~Ywa*aCoIBYj3h~1E2c5fP!;qVfOS@;sRNDtH;7bv~2%B|*trJWI1 z6!e_8=KE`ImOXrgYY!j#Z|q^&$1&aXaoB(0oM`LfoOsD~PGI?k(;|Vl6686NvEF2U z@130pxnf*+9?000C=WSp8kYt3Cv!F@;5ZtsM%g#~+kAKf#;L@Z?q}7WQ=2j0HPCJ? zqDA39DXUvGjq`Z$-(x6`98C)0_b+2%-;&WJhpdjA6TzC?gE(rxU4V7Zp2q3HBC@<= zA2T2SlHp32_-ja5FA#n8h4R%5)Z!z&ezA2y!Jw@j09RkGb1d-{6p>K<|6 zkhI9%(}J?K4+jfLsAaL(o_MZCRHx2^S?VnD)u|VCdYi=db3U&LF)8-20xp_a5g)=y3bi-I`0SjMjHvIXE-zo z5$A0f&ReXn+dWJD%q<^#=UHVIGPb4(*1y$U>DEq*curWi*YrrYb3oi1jUMA`RDi!5)~R4p|3x2oM5u} zUpgi@H}?7n*7e?rSa;NP8FPm$-^c8Q^#C967WLO~aCnU9udj_s>`kxb#C2=0Z}h?{ z<1bB|&hmtYHj#exmASofpiS!PoEy~ynUpxl7fFz1g{AAqA)sbNiDWu*5tE2 zVu(J{(>Xovw81S>&<0yzw}QIgjP-bhDcWc4Dxx2|eY_{)>XLD+m$CV5t@(X%$aKDs zqtaWI>GkHni1C;3fJDzOJer=azD>L9Q3)Fdk{o!;x$rnk(cy@ciSt$#3r_|Kx7+ zmv@uDx|{rSyUD+}oBS&~&F6SZ;Gas1Fb+>4#y^o3*@@wejI2%K}wVc>ao4aZEPu=8i z-Nk-i?WW!B-Q<6`i~WAqNxL#O$2Zft&Bk2PN>^n18XX<_87GF8qitR&5^`dA{Mf^C z$_!Vz3t72LwsPTa%H;v3VYxzAIbKS?Fk5?ld)V(DGrAr}j8Dc+|n z;vn3~@KQUGRtVK8y=2rXIM#T(nKPcPC6E>Ckq@L;V(AF1kuD6)HnD3Vw zGFQibR#xPZJOlfKzai`=53K<6R8pUZd*;+<^uJ)Au-Eb;d1%AJ{eNp_NID4b5Xx@2 zxyC|h@q!A2{joD-j7cb!VWq0uk{4Ap z^^MqNCU;RLB10eB^#9S#J2InMF0yBhlt7dA*RTwtUG#+6U5~Qmr zVYaMDDfE(gf!gD;)~FY9r(g|yV}re>N;vnSGwvfziVV(uq=|f2RldSOh?Ik#JcG*@ ztA!lYnXBq<6X!DmDJ#P{z=el@CS8^^+i<>=^bM2~}_Np2GJvnXA7U{1Q?)NaA_dvd8=~SE_ zH_YI6ai!!3w5gqWVI`grj;%TR(RFQWer~1Phvc>y5#d9!!sB;$7o_h=(9ZC5nv5+M zzK$zwU&j>zU&k}(20NBqbL@2L*a~i(A^m)QTgm-=Zq1Xnc=DaUO#=V8jehg+<=iKd z=CEjHX%^ju$3@~_BURxenOH%wS4whe^FC+LS^EHtl=)1in}?k){NbDvjLkvAm6c|V z^F9C}GS)aofsGc+D6rD5^O+ga*YXG9Yk7I4+phBm9a9`%%TsBI_Jf|RK2`Wyo<@_^ zrwL!nWi(m4tdsakXh+TCD?zC-r`qwAk`1R(9Jf2Zk~_^GUn#Lp`dFUo@v*%AA2R(k zPx`0l%k*U)9KiS#-1kzBSK5f4Dts?bvwbg@`Ft-2Tx60aY%>94ftldGm*p;d&5ZdW zi%aMC8^*cv=1FhLWnK7U`df2+GCOZ=NhJO{+F#?oI`T*!U!auyQ%}x3l_rz0YN=&3 zs<)-Dszj^Ar&&N;8QMg#YDfw7K`P9{67d@%RFU z$8sIl*>7FnRO$CY88WXsACzk=-R}-z{5Wp&ya1@ePxH9V^DUL8Phb6Go3uB-i|=i~ zNhdzj-FBKkK9k&AAI^||n*S1hn)iA3*1vR2ar`vzs^or}n9ky93O~)eE4k+-kEfYE z?h?Kw9u=_4(md{xjMC}#*JSpF;vAGcM8D!bk^GDMMDkdkh28o@V%#}Hv?cxdWgfnZ zY`Espy$?UPV?W(j>Gc`q{4LvCHhd>!QPZ;xSZ{qbQ zxr@(kC;Qx~ByP!i|M@#SJzs{<9tp>F4YB=&vjc zKT_#_kEZ*(_<>ID;&t{pjC1SUpHEeCsb?%-`b|imqn~DQ|J_v6*))|r%jMg-&i3tG z7x3*ohi$#3% z%MX#f+}X3wp|fTK9J$i>^K5obhT(r9*L*AtJz42~w}+7I_j1T}FLOV(pM@XWW0h{d zoS$_}aePKs(-Qi#o|?PbrYFq2`8C^|N?Q3W~ z%_TB!ExW7rLPLI!65nCs#DA(~>-t!w+s6&tlD?xdI_;K(jH$a)*`_EZsT0=F8w^bq zk#CJHb{yN4i4CH}T2^993RCdRmcFHUH>1ucHD|5Qw{*4eEiDrsq8V#8!!+ERs2Os6 zOxI9vp7a=9+ljBKza_`#wDXouCGl-2w?G_l?&n|h)C!pEGhpS;TuY;&h2cL{65AJ5 z`pHNdD8=4TcG4vcl(N=%(j^U)vetIuU-}a}V;*E@%qow6X;D+p{7YBUMf82)U%H0Q zu3Os!|I*+ddDwGiICuVwyKv_}Q|b0QweNiGB$Q<4a|`X4E2Tf2d>`4l^WWs>5#o2F zyz}3((;CfOZM4gaDdf8I`MxCv-?#62_AN0m#qmFVvy%ItV)~nvUjNg#D!Di5Tb0@U zPi1Tk=k9;AQWrJguj=eouMaA&J>I*+S)F$)y*{XKR&pOx_E-3z%5!v!<=v^+Q;5md z*k9(6iY3y32V3k~E3FNl*xJsW`&!`450=^1gEDUi%IIC(-EUTEOxrb^#d{9k-EU>d z@A~duPu=&`z@~`s@8v6q@xHltY}A#e5IH{^EhnaUXLOdNmmE5aUOMYdJ1gr@glk@VyW_#wb6!$2QehM*->< z`?^;Rj!)J}`e2kkGM{?NdwpcWLXOkigq)&N*I+MX+yG;cFv_@-1Q_VHxA1+K)IQAIpDR5$SXA+wziSe9{3d{xq3aUMWIp1rJx=;R>o}0 zLpd?-=q4g%l}#+2zfy{JHAOm?Yq?)39v9RNZ2XK9vwO(Sr)X}d92cFv$M%ffw=5_Ns^lZUEf$g74|;-#fymI*kZgZ?xMM5Ddq9B?~Tfv>)s;y z9t>J3oL9-M6r8$OPLg&EOR2kr%>Sf`+XP|0`^JBzmjCv*EbSk@XbRdtETv)}DPqsU zJYHHU?H|so^xHpt+q84`57_?2qWw!e?K}1l&oSrDb4GyH(vLt&o4DSDs+=ik252wV$msy7pT_(eUw*xv z&-vC7Grjk?POJuTkHao!cel>(@v-baMQ^(-xn z>$H}<$Ah)lJ+3)=YK`50jWz|=$nHPQbB~9_yIWId3K~khf5d1g&)l)Ed3?WS*I6Tn zH&4)mN#f|PF}+dHP&H)Nbl8u%?)y!Z|Gp0Uz?oh5vH9QVL*7+~&g@)={b8k7hdp;@ zS2V?^JEg;xwCJ&-e|~;m40ry$3C}w#E*>J&MOn#5gap047|Kp?l{Lu4DrM&$Q0Llc zElTcf5WIyl3S+$*;w28F-heM!$Rt;O`unqd+~+vEb4W4exHE^PJ3NN8xM-3TsUc-w zoyF@2>c~H>biWyL#!PM>Ygwl&bugk`fy|>>DYNRV}4M@bs0L7m&8k?yhKVyY+sZgr5vK9%KgSY{(EedjJY- ztg0GDk%mdJdh3vkbzIp%5&@ollGta-J^4Pv))1$??<2Qa1L#0zw`9j*xY+c?1;ReK z+yweiZex8!NkrxL+y7JZ=I)xy4$rq^le=Z|14utOg$AS63h2^)2Iohde>m1?Vj#JK z&Q&g=QFR(rpQ~I*qf!3rVj3;QU;cOJb6B4j08>D$zW|-`I~I4U1X^&%i~Ze9t{W?b z{)?3>OpFkFN)<+ky@>L(bYV0q(=HTgckU4_m1!5#3|U;o-{!mkZjS|Ua@9y0AMv#&4_qBXs1cL}&|_{7%GqD<7>8w?EbRn|K5}D z9p4q6@!evNPp0#7kIv+}fl^Xtpq}D)Rj8-9luFo4z|hNR0g*D|C!qYkMCvD7hFw8h zhF{L?>qh93{~ICR8LVXy&qnA5Db~N)J>I+2KyKiB7Wd3xbnUbb5|o#rCT_$FH)_@oTy~$FFpq<5zjk@t5s$yaLYW ztJpdI06U+rrabK|6VGv;c9lq@#dwbMv}@oTzfYXwSNhKJ-+0dPO>0cgIUbyw-*z%L zm9Dv|49pGg|7gQNfOxKQgFQFb+jDcRn47uEbu?PTXDK*GzjMve)t))pCUB<|;3!s( zD#kh5%*K8LqXjcOq?U0cCAP|yf|7T)2@Sg`=gk*-}&b4x1M>6o$Z}B zX`lAqOl}8TqMQZP_MhxMC25^cY?>=IRB&8Cn{%0sd;HwKq{?VBp8$A{A-|^u%ua%e ze7vIusI^A1UNbz}CmJc%s)vW>ETrShocojMif3}0a4WF}c#bNPwr-*&+8W?DriZ+* z!0ZWmH_~=8m@%5 zPI}ADG!`4dbaDvG2xSiv(n_@b;4T=MAL>(LjE7$b<%jx~(D883{+@>Vm*h;)i*jp7 zG%u=0L(%Y}+>rS_il^GYihVE^wwP)Y*$d5@-79 z5n%(t+l0TAze3L1u-^x|-(^EMLBwfIo3{YYURmY04eT>KBG%yz&pKeBBG`>@_m%!sW6ZK zHfvBg2B*;$SfvBmN!o7S2n&r82K4y9DNGnf>}^DgGS>T5C~a)FRzjWlXC=y31#9A% zIQH;}xl3upp06=U`6m0&;a=YV7TOa!_o@J$dvy=!+*@~w&XurENczv-R#Ty>(A)4b z2PEbBJrPceckS~#csAF-+58SWn{Uf{HcwQF_1_GN8hAEeG01&3Oa9>{fukAo?>7G0 zdMkiq3hp;+BJ8(D60%CjM6_PRodJAt0&d6K;Ix>aXRI}YaSU#o^YxgburZh>^-6w9 zl<{yLE*s1=1J>H=Sb#%Ey_Si%q85{FW%#PLN-*t4-&}q7SNT5KhgFX44nLm`7jwN` zRTYd@i~1B5?#Wsp&)z+TW2MhO{Yv{^4gdWi-nqGl;!$h!cr z5B};Q7-PX8)bGcQjG51VwVRi-R@%*tVLnF&Dbb;>j+DSF&(0L5RL0z| zAKyjgMjdb%t>!JbDq5>~D_b4lMDXnT204*i8AhTWWt^sJHANN3i4>KQXlczrC|OfG z5UAErsm3`Cjg)B_^H^B@s1{W-4Ifn{V)>etz?GG~m+BgfcR@r;G)|R_< zJ+PZtbH{p>+#nr9zl8~)QEpxLbfWU#$eVDCN<5ay4?VS@Pf)#s*rU;C4V*L4 zfi;QUNVK*#Q2^)Ma07SlUdTZuc2qSeoI)wpId1C=`*=R!tFN@FOM1qU47lZK!8CcF zzLS*imV9NOjq=?5*U{a{T|TRF$Im|g5>wDFXA9cpAA)u{8`d%*5Sb@TbVIqAfmOparafhOMl(I`@r z(}=2YJwN{Y7TPIfWj9ca0yF@y3)emx85miL{2x(Nd5_{hd{2S%mv?w%2>(n<8Nx6o zL-?G@5N@Z4Sq!7#_P>M0!siy!)x+Ge@>`S*v_-v>I=1@1B)L8$ZQTW9XtfT=G1(bE z1ecaJ%{amFno9Ca{U-^c`fRvoueT{VWeH2H}>)%V;5gq&;M|v2ZHyJ{Zfrw7|xvc#7ZqZKeWkr_KBA5jbyq4oS&36(68(U!&SA zb@^x=I(tM)@=*8NIw;K9FU9RsI9wSscQD_bjHkKG*5H%0T;Xz#Cjnt^`x%{ zVtfVIfdWgsPDn>H{EdHF;G)$Wn$YVHA~}j_0_JAr0qQ>YQgR;1gVb%G)tr(sBt`&D zKn9z_i@;wmO&;>dt`C!Au^yxw5~JV-C?z9}P^?6m0(&Ps*wo@VrP@d%9ear4cM-c| zrWl;B6!BbAoUcqKpp6E!8Ym?zjZigjTNwCniH(;VJ$|UmZJc|=esdOc&P?%l zII{OyYvw_pkHD$Z62t#*j%}n70foeBr;qdOJ{}h5AiDgoACAH9rO!0Sx0b{G^?5>Pq&!NwoSdiQ8{qzQ%t@at-s7^Y z!CX!{#mJsN(32nJHTMHvvt%&xnvYV)&S3Q-FC8TD@-O0?Pg;-LcsYpS<>L%5F`7Ws zbsk@TiS)C6+=KV)kCL*d$Ec(WFz5M{cyBo2ZeL^HbtvykJO(#@OU+}nx%zP$@%Z-1 zwr{>fw!OuJ?{C`p{)CP1wLW~`!tniZ&z<=}ipx3Ei7hUCA0)ozo%saddv&Myp02_C z8g@(6#@^~^!Eu<)iN`tav%fvX{V#1Cf6RmX|FHMOu>a>BpGx{iGw)@>}4f}|Vq_qf4Gx8oY~IFIFn7`+cO=SaU^?`VPkckC`)s@P1a7V#@rL=cBekO zk3#N!f@ke>G;KXgx#YmPGu)+I`s}p4Kc4l-mh0^Ft)6{WFUHlPJa6x_M&CZ$x)b<5 zE-mr>Sr5LyH%a3AbDpz)N?M-vV*#)C4dC@>OU3y>Uhv?xlm1dC=`VWleMVZ+;|{*} z5}N&Dva!O8`7gq4@DgDD3-(=#t|iLPrri?IyC*^cBx(>dg5eilif6@tmhe z*lY2!=k5BV()|6~VEc6h>6Vviv2r`JfdO9gtLE4%fiso&&?fe1i}I>i)98E6$}668 za*@Qt#49vi{VI*VUu)&T+BXg6uYc`{vNRGe$e1%(O1k=GhJgy(MaVm~BwnUMr?2Aa z_zK+_T6md_Cog;O^}&Pvx}cPOw{ElVR{S0^ zkjKd?B|Fq)^&9kcRZ*I2-k_rtgJj5fU}e)a+t|s+sNlr0#XSC;#DN>r+_qBg=|g;b zdfQIy={xK-dBd|N*V=0`0oEiHSd%~4bN{BtPVk|$)D`%7fj8~;v4yXf^0*Rd^DWnz z54dP#Bhr(RXE12u& zyt&>UqJKaA#eULX3h~Kp9-mBle6pfJ^2vDK&Eku&LS6zsxn1~`;jq2|Z5)W}p!HQa z@1XQ$r|o2eYm){CFje1R#fd%H-#forgPO|Gq+H9kzU=LDft`QKtC4c8Yx2txyR8%2 z%(lKFN_9S zy3kvh9o&CQmkoV4blR`NjKb)Mi`E9J_)8j9SoPMKX^n^!t+4@ply|FVYp8qUZ6!N6 zPsh1anMTkcyhN7`bC=$;*8M%B654MN^Jq_Qus}LFw!umk65@Pzm0n3s3EAWMeofZY?2f+< z$uny^IDgMsuLC97lRK4HLwnPc7g=J@dPVG|+ydq1l#pC9jECm6Bs)9pF=n#9d<*)KQT36N()7_7(a1aGrkn_41DV zHK5}2YJ8moANMhkA7SHqEfPFKA{<}o;Jh?N%Bna&NRuN#sy_}|p>S$CLCd*yX;s0y z!xL*9;-8iIoZonkwA_#0CYj+KQPms0VjY!EasPhQZ$h(nX zeIn?KuhI7+#p}Empex^AG0Mhwq*1A!p!K*9G5Y2LeCzjvmPN0NAmj*|R=~Y>lYOL{-XU zIcsrRN)@A`a0)%^eWslsMerW+afO~!Nl(za0LCtWB4=Hb#`e3;{e*q1?E4>)&a*#^ zbe{QX1lN2UjYk`Y;aXR^Ge7H&!g`P5n;7Y@f$Q;S8QIA@BH1Y)M6y%gkMz#?Ih@G5 z44jD*v_#0Rw1nYfN2E*rAkwA14`bNR~~Lmft!vF z?t3-6;9p_fbblVPtL=(rbqHygQc92yW}<28HAm3Z?>)MT z3c6Y;>8jVDA-Y-_rMTuRfTCg~yD~!Y<{jzAB$;H|^y$kaqfMTkJxQZE>!uo$#7(-6 zW-3j-gPSB~wwMzjU!`a(0CJVy<2*g)P)N^N`wvx_#R*F{nr2b5(tXfer^k(qN~h5^ zl@VC&09_ZrfOLd+?p>=W`!?9ONjp!{Y3GSi+T*URX^X_o#-R`G1iCVTuFPU|H3#TQ zquB#)@}Cu-g1RJHK8b3@;A=-}ai|{Rw+B&=&4b@gykU|G>983d_t8ks;(6<~vZP zk@zx)PS6Ub*BS*lLEAd~;%)1U0@&`LA}15O19B6!$GR@2vR9}X6K!v_6;~Zz8{Cu$VsTw+xUd>l(z1_^C!(=>WO&KEP#f>*N}zorPS2Q39A_J}7wqZ+s$S*MC+o-|7Zi zVLht6M>(gDc(S`6JL%DC`I@PdSz<0Qiv@z?Wj#CZtqIC5lNU>)**2OKDEbJi8C z!?owi`-<5xx?g+LEA!WXWQjG{H&V*C+wk`_8zb#($rPV|}*BSZBI&taI2{ zpYCU@;d8@}Yl3feQJzMbDQm@O&iSy$VYX^780s$005R&n1@L+1CpDI*Gvs+|rV8Dl zGbCaK^of}F>UnaG{BzGmdAw}sn}uaA%PX^2kun$Ol}Q&Vb9r8wYhNoYb4gyA(M8(1 zLX@Fm%r5}0S_5Sa_}a;+I>I<=z^0Bc4YQFjgk2qxws!a`Ex~lr5q5ibgd$3)n6{4> z3D}9$!;U>s*Wpx7>qTv~Mg7}O-*$Q8n^qn#NRfln@*Vu8p4=hff9rn^|D*xoZyON) z?|Se{JGjpP{&9J0g~QLiL*|?X6!-R&a$^%({aQroEQMxCX;uakp+RLgs7W@4^OYWF zwy3$SlGn_l{4ZmZ4G4uwg_f77nvKZc2 z3r# zzE)ec3q3-io}>hL<%-%}=}AgxxNa${8wY9qJvHYntKD5*8>hweCeVhpq;^+2kizjU z|3c$MnA@#VfO)3UV;vsh*7V2kV(gttX&%fdujj0uTJACH1!LE6=g)Z=%=yw?&UqOv z^YoUsH|OPH_}$}b9c|!t%4sp3(^q#n-}3zVvIn}}uIpzk#6+or|dk8~<2{eiLk{m7QF!S^E)o_Y_S!$D@w z6u`585qLPSD+bT;0M9-Qo@7yY4mWlM&!o`&>{kHK)GstYJpj*G22aHv&d*_?`Ed*2 zIpzz^&j|oeErVy!7n~p2U&jo2k35*-F6OUUPIp~i{*chxkhabXX7IbfE@cRu?+1t0 zhZf*VS!XECRt=*$x4G8RVp?#0$hMFN1ns7WLW{#_nYVW?n<4Z>PAp<4TF|oHSv-0i zvx1M1IaQFVa=)1TjB~;ad2fBG-fNAAZ8tl4IL%H;(X^I>Q_8qn^a*}uO?}{YG&r`^ z-q?l%Bq>^q-*I_-1n|2_yS+z@2*~5737(L1y2_-?E$_I_oWak>S$A)mt>25L)e~VK zZK*{cYwJ<-AAnlI{7nz$y#eOE27tK|V4kwuFjod({*HiorE`v%5A$V^SH)m6VKlH{_4P&~oh6G-8WgA8ojA<>5Y3eSCX=rV z7dr3?sRJKHxxTv#dgxrdAJZXn-8M+5F6L*uN7FpXR&UlvO37BwF4>3TZN+LmPU(_L zgSXYSYz*xJh&-L;;(cl7BGJAP*BdMTywlfx7v4vlY-A@x-&01@?9_c|?~Frh@jfC$ z`@4@A1^5_EyR?1i?%hYU4S<)U1zt`OcsaH^5m-<~RH{sTlrF0sH}cQ|bVJwSd2)82;+}#a|8JuMY56yPNpy7yy6M z1^!MI_?uC%u3iEB^|E!!TUgM+>Zv39#lSvUzpbntIEiB>4C z-0j}VD%Aa-YGmv8r`d+_w70dr7Ipv4{p$Yr2b_$j;X9@N@G*M;e9RK~I8ETA%fm;B zSWlk-KF&vcY&bBCkBg=Ve2fNs3(FPYhg|IJ=GiQy{d>sn&XvBTy2OO;+(8G~5{h`OMD7qruP$rL`zv3RU8 z1-Pp7>(xSgU!hwgx-wA@t}?FMXDw1(ZHKRn>v=81SUN8|fOcNYTW=MuCloqGS)6&g zI|i7H6}!hd4RARLaQPL$AnnC!z8{*tVDq1gAv)OF!BRR5}U401KGe?N}hgA+nA8sz=U^qKOWOWtGlSiLEgMH}m2{xo{5 zTuR!0C~EjcNqOk56W3r%z;0E}&+M7rXup;O_1sds z&0=k-f`7tEaIoaNB8u&IBj^+1%*Myvttv_`Z|}^JgMcSxUdJ^yjQrzti=3H$%CWq7 zE4?$hud7}6H7KvID_O)`+0)-;PjBS+0^{QH#YX6BN}w;@yEicMZ{UHB$4WlU=-iLGd?kyz?BCQ6@^0#d zg_c^W@^UJRyY%&l)ldR-WRbMa?P1WzfsEJmeSoPc1XCd-X%CX(5F`S&0!4a$JKQtz z=Hs6KllkZya6TUX66d3Dfcaqe#51|w^FZ@~RO$C*4>;Dxoy=Pn@$K24o5^jo3&$32 zwX7)o(Av9}2kW*k30A2ae>IObf_H3%H(X=A8!q`qj$`I3=6B1#bsJRV){U2A_Z7}J z*xhyJjdFs%!VyC%8STZ-*pWvYY7t1%9wfyfNO%ww?fLC+&;ERT{(mwb>js>Ul38Eg ze5~tdK4jlRX7RV`g?-zB*2{{v{!1(KZqUd0UbCc3G*DZmqAiR?JCS@Ox&3?P9*(MK zh4+%+`|M&Zri#qIFKaa`Z|~%Ny&?O0#qVn@^fhXiF#Gt$cX;AF=qzS&K$_7XE*nke z4SPp`2n%AEec*BeRp|0P4&t@{9#&`+u5u-8r z@5r!6?hTOl$h|NDj;*aAZDw9BC$hgI^5#zKr*D4bs_ zvOXo8Hj(kAch6SCkJi1}k~K50mlNb#s83iui}UMkq)f&4Qz=qgQc0#M8I_)~O)1mi zh((&=go55oSn7q_v@)|8WG_)yeqP5t4JK)N9eq>k}v? z`>*D?XR`k;A~HS_vmKqq{YeG+0mqZi55y`Z_zq62QpN$eRCa`1#elsEna{9SA+3}g z2X`11<#uXi-b|3R#`hVG`SO>r+zha^>?SPiEUYE8{|-F<9nzh%q|cdre$`*k;T|oo z$8b;YVNZWgc>VmWfDaeW_FiPq`UuXhoZH|05Ai%{7kGZ|e~jlzMezK}mxAX>1$chv zzmDfgMe!`pd7sVN{c|20p`OtHyJz2=GbX|<)a5ykcJAt&mk-IFoqheD{c~@!^Z#l- z`ud#@=S!ZCz9RD>_vQUMcmHg|piuD*>VNaRm$MFtNIP}DMp`;|b+*ZeWY5lfe$W1V zO#45Zk9GadNBfsNAM1+Dhtwy}>y&Rve7n@+j9zDWt;Kvq7k^W8Np1gcem<$S`pq-y z{Mg?9{SM1>KdqHM77F`2cYyvbs2%8yk;MC~&v*>NwAP_;513C4mp*TIMjVnH=<|BJNzWZxk{2!}l0P&#`0x z2_f(7DkTIt#&36q^%Mp7Dunw0<=#OON?Q57k|MtyjaTj#eQ9&d?1UbkkS zYUn$&BcaNB+lTXTqoD|N4?azd_ zXHYLK^Rb{cLDgI*D0#{s^LUx=&ABgE*(AtU3gjiNQseWku{$5UZ>3J=jb`@B1-14m zgHt)iO_bx>o=1DT-W&THo6k$$`jU8weEai3=l>sXmn1yu>;ySzH{rp!FJA^MX@j3# z3^Lk?h;>#mo5ywy;(@yIf}!Q z23RJ1VOWZtR=+qrhLA7nW{3BVAPl?g+3T^)@6qdN2ln)?Tm#tG#M$9^$^-S)43g6q z>nE%SkaT$H?EgRB#7TH&{!iCMaafA2ixvOVbunqcb+Pt;zAh#etc&$u(z=*bWL<3d z64u3}e%6IN%WRy@eNl0*iTHMp&;6#^A{IF8{Y>Uye(3mnc+CI7?83XGpqvuk{e2y> zzeASzXI5xiQ(&jufl|O~kT@5Vqi+tBIHhjo-q~TDqMj$qgm3bz<^E6XgfA5gA?T$r zaOWe(#>;9b-wbl&M5zM#hG2w}l))j%hU^=DIh)6H+Z|Z)wJUoLkN?*HceAUgSFV%W{@w%3Oh2+-7uPZ ziab3W+r)ED{<%lWhwtR^tX<28mAjM=TL3FeMtlup+oX&qhRdkh1Ivt`&F=61O=1Ki zRNy%}4*6#5(wf44Un zHDZ4RUlyww85+m9blXhRX$G!iQ+AJauria5q)jxrmY$Cs4bE2h(h_;i@UE~=lc;xl z74vSdO1#^vxOaP17Kpw_K|Ve}p%>Ec(YxrQ${*?L^dER*2GkX;jGlmzsVMri!gmF7 zpBV%FPR!%G{QY%4CFM)!>0O2%Mw-jOQqMHrT%_e2xmjQON(j zlL4{oQ!0OFT16bC8{m3}y#j|5_M7~i#@j!jOUB~!&z8LXVrS{Nw-$E-b-WZvg` z5w2sJ)>U}#>c3kLo@)Bt4fqX9o;UxubU(Q|Mmp*rL?H1zgJ9)fo_xn!mX5U`FuLN;+<$rBXQBx*X zc>rfOb%kv}cDMbqg7!t*?{c3(*!GXR!h7Yw{3sS^k+t+e@GjveU5Nr87d&oJzK@IF zh&LX*-UD4cKIK3<9ts$-hVt8sd^xH`!5Dvz$n`yMp${#E_z^v=J zzhd`!(@`?7`e&lQbQS8bJ{h}veU|0-sfzaBO?YeRZ(W7wAlZJ3+$`48u6nWyd-6_u ztjD*cpD^t6ldeKrCx3sDy%+8;Wd-{Sww^>f*CO&w%$x+N+hyDS$OhD&%3kEDmiP7+ zx3l46iO(rKoB91o>L~1GXnrrqXBj=NeBxiR3~32q#z`?5?fS&MCFjE1ifD(Bo!+A0ti%`7oCdr zvmE-LyvzRm87hQi_gkwK9qmAJ+7|*yYzZ5Pp+S9 z^574ioOaJMWoj2^@(9~G9gx(8f67l61s%(wmH&w2J>ARg8z;pdGdb! zzB&1RCnaSeW_yiZx4str7rw3f_qK%aw{m_R?)wULL=Vo9F(hQ3ZRJ{TuBlR`uQUFp z{joXuZ|Z!h3TjNhMbz+o4$dj`3%SDRwT9K=xX$qWLdtPHTVz~ZbBg0?hH-tqpY+Qp z>%5T13l%a!9ZFi5e!{UPa{rggL}R5>ChQI{ejkh6Ct70O_Q~@p)DZuxdFO(sEasRDnJ&khUAP{F&|G-);L1jd4ro;m$k zf^W|$yeE|>$~5_wkycubw0elq>vYvL5cH>+*1F+WN$pf(S74h}pJN8jXHS6O>2D7M{?^Gt3hj(vuup)Y!$?ljYVgtcPsu3)wMg?0GcLOPRuBJLR`^VUatz&hWAI?D>`jG3E{`~K?Wth0ny zTv^v0ux=2>!n*qxft7nS#ky;tZfh;~>FUFJz}!N+td&r5e61XnkF#k-V3X}vIb743 zCaa+K#M=BhIch-IPJ%kqLa-q{XXi?LAa4yhR|<-dWxD3((|n5ju~N3X3|8(Sw3>1@ z2yQ9sbq`o?In*1%>RlM{?TYnQ%njQ-sopq%hdb5e+K8y4p>Aw|U-?tXp8Pw1G=B&fZxKDZ1)Ld2IgYz46^WRSO zH>ToHNEps4g0HJZZ=U_r+`>I=A^P#owlzd;h zvi_9=)<1>SKdMN5$!~7xx4y=Fpdc@9uPyZDE9>7nVExlr{h3AUb9sDo!F>7kbM8ir z-Qvj@SpMDt%U|P_m-u+F$ol^};A2j$@8etI_m@T1zb{`*(t4f89P|ONjjSHie6mG7$|NA7o+cacy(IR1-0-_(9Tz8}+X0H2vxz?=RsH+-jrGSCR- zQ(JhGR`O+`a!(do&m$23*JPo;6ycM;EOa*XHplBt#=(45;C!2*tIY<+!Q7_nI*W*@ zp&V*8aQTAd8-~tmYPsw!UU`heh2{S>w?MunxwhwrS51|6s0J%% z%q!%nQX2~Lt*C*k{MZnF%jXr+AeX>=8kFm4(7eL+G}T*Ahk^XI03;q-i=NpRdlCR& zA6S1xUVX)@AC@EzGqAqZkS}A6oL9)d7DJEaOvbu3 z_4D@Zz4u0Dv%e+u-h2Of|E>4lUh8jhUlzRm1LuWxU%~ddPvuP9Vs?!A=fS~6`dO{^ zSo7n&A6YZ=I`&uS6ZNBzpY%iL73!Mh-afFd8SO|jACwVa=X%V%@SV2iwN>1PZ}4tt z_;s|ae%<%&y5wx>m-d9(V9ewh^SDptUTb_E#@;-dW~+{&IqQHryw7T~;w0-F#k_;% zM;{Et{hTM`Kpstlv0oR?<8n-YZI`n&eCOMVd90se(OzXg$IX-R2#=yc-?=BulQCV7 zp)&sCfpy{dk4I5|%xmU}SnnQ0tpcLKd7~{OK2$+iGp~H1*q`%cJjtVJF!u3Ta^6TA zEianh?#(CuJ8vHMXVvbs(e|o#db{QO(%CRi#!Ku2uGUnSA1_h1nI*^4;92$AIv&fh z*P2p?x~*}#w;7+IptVvjXZ4;~CAnNb3ygsiH%kNJ6zQ3{`3fgS9vUC!`Sf101 zt|m$2cX(?l8;W1_lX+bB#&onx2TI)83E#ZEfny~9+*>!(X)uoGBVsR=F+7i|L!BPB znzN3s7+8w{B*`a;dhZZ_CHW1&2-WrU%p+H zc%MasaqK^vC*#-$+t=7{9>;zb#W?n}XlH$AL3}LU_MxB47_#j$TzOkyWGt2$|yGmEm=^-kv( zpxfc|WsK%oG#Cf7YQCiBP8y5{JbJ#Q+p~af+Y9J+zxk4GyJ+xDm!Cd|24gfg%$IU` zevEoQM)MrnmF%LvU-_e#O26{+ym<@Kz{L5IHs(_4ZzEwm!cHq4_!*B^)?Ec24}t1<6R`!MLxpym{>o&FfrmUKe@un#-Hl?%kSKo`1~ca{kVR`TJ_Y{2g|= zj5EI^G=F~j;t+4Xpe7+=b@?%|rUFl1OtTG3C~8i9IY7?&;v)2)Kj({K&X)wn_0{?P zy%h@j{qSz0fnTfunSV9&$nk7qC-df!=bhu#AkI#l-`|^^;+{(S@%Zsp(AW2=(B>Et zl=la=4sE_4$2Kd}ojVD+yQ&D>uap!2On2sd9;0_xXF5A@rYm|Hg40)j@gWM& z7HjH)=`v16kM*jz7V`5}T7A*{cz1q1yWe^}*5Co!JTuTH|A%ookWNRCyOiR6{ngBW zmn&nW^f<4X`FDRZZ>4i~)anQRob_)S^vRC>-!1dp$7t}4FcI~zOr?Oau+CG|kKG=I zBj@~%N;q`(zd&AVg(>wdg?U50yux|>X1};uX`L}Z-ns$u)<^rB=bZB|(<*{D9&_#V zX#aEht*D$AtpKk{SeGS?R~F_C^70DdJSzZaeoW=1z`r-u?JB17(g2^rxTbNe>nwx1 z-(q!B%Fo8~=uO39n=UKxS&VHk-^b2!Xye;?ZS)r-b@>48tblgT-_3Sb4A9PT(9U_g z+0Jn^7#j!MId6XcJzdVasE*$xE*yugVamZ}`7sSQ6sdP{LA^r@>aBS#KYmrtx}>1q zVFmRrou7aIkt5%!<2vGXqW->VUSHbP$+{EH~U$ zs4N&&G zMasT+USYczLfNZ|l*Krm`DMQYWiKyM_S*Se@0+uRQ7EUZfH@pJqSv{Hz0mOYvl~6x zfX7wWVeW@h?o(v%eY{`l{i>zPIzK2`B&I(E<>PEV-()X(+pY6yqpB0zE>|&fjjX-@ z$(A@5oF6(f%Ji)DZ5rgmgz-F4C=h3(l8EK?B{&dDOP~*QwM_^RIKM-N3)TPVFiD>kMkQ^RLsX-OayNQF|r- zI-Vv~lko2o;X5k6PloT9`0j)667hXDe8EAF zQkx+C+rwn~*LnJ}DRqBHS5h+l44%F)rQQtb4}0ZL=jpkWdJUvcE0^i3Sb7r9{wZ}M z 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

dw)x*C|GUKhcHy+IIzH_Z zu*iU{1ju_$UIGGng^_?F14KpUq}G}C z@Kc4z0INofRzT1S&yz^ub}C)`CN?hi6@hNS6;5~E0wUm$HDX!evq zzFdsOZf9FNBV5P%qk0fazQ<f5HajJU8RKea!mU@G5b6Uf(>oH_I+!2TUE|v(yHUMY# zK3mxhvDIW{2cP~qw6bec&trjUN$!Rt5c8M#PAYTq3n>^ch;x-NL#;oVp+Dv`)c%th z`eQyroj;kOKQ1%$XVZFy26WZ-J!xX^J#|+Pipm8NU4Db_KD47IsRyxq*MQ;o5=W&A zq)v@rZa#rOTu_VRFACvbl~+s7n_6`> zF8F%+M`8_6S0s06qcs$Zv)L04t$2Z!$zZuF3ZyPG=tg-umrs)Ua67c z%&Z-o^P`kZkw!sH^TT$dAu2F!U-IG|n_)T8zA_!9(5d8#`$4)|ETi1Lo5K9-EUqs_ z6ZmW<`9T<97B&unJ_Hc4gM5g}15&Uuav*w>yIsYRF3L`qWp^?502oqO5KadH%7{%; z@^}j}Sw}2_JM=7-WX5oc_v?zd|MkRL>8YENQH{sep6hWLx(U2KdS zf@f9QMZiD6Ha%@HIuH*);+LLoift~mNTH3IH8I{lCK*KA`as3zdmKq`PH3MMahWJfCfAh4 zb{HR`q1AXj=6<-J=a0`Oh1l(6aq5#r_AT4u5vARGC&r?!)EF(2c zQf_=yQ=tRmc?z$^$o$yNMr9yn;M9PTfr&JAKY%QlNKRa3=@S&P#7I+L5Xc-W}iKIF@e;Zr}#_PFOseHm-{==BgOc z*x@Hbv&gpuoQFDk1zr)w^%$NDhwSeIri3dTVM`}XO{thC%Otx!~>{3MjQVg zTDtcK@W$w|#dsh8+Z(M?u}R+$=zc+58FfsJ#qn+Nj=nB>Qx>h}JFN=Ymq)0g)qN2c z{tESH3_&*x8opi*rX>vdK@s`3j_-*@`!To}V&tk)%jV6iC%77yWl_$-V<1DLjdi0o zkSL>A-4p3$d*QF^mF74ds?}C9P4*ljJIn(-<#V>MC9~4&AD#SsDo8hE5NH{HKHC7J zPYNeY4gmoURa11{gccEcD6Uf!zJk0KDx#h$dLq<}5EvR-S7pl$nGIRxEjwBX!gzZa z3vvKQY%`7UB_(>ML#=a@M|puP_>o0{T$irtU4d*G$dF0O(<(L&9n|3lyu|UFt>8W$ z1JRyu^lSppK$--1Kl+&3ZNtH}HD#-qR`mrf#Ry~SZzUEn{Ws$YhIrG;kE+V2ua1 zWeoBnnT@{})8E}ly~lD=TXtFq+WdF^s3Y)YnVSr{QeaeZ8d|nCi|8xRVMv!DZ5YHu z^)W0?PJSlf(;&pbTV$Cj6<>8AbtNMNSuMHme<^+X&(`-u}=GH5sas+ z;bkVE5wqtEXW|N0?I0lAxdasFQcLcH7Y=xSLVS3%$3YIOtF?}(76A7LSKNa1; z6NtPH-xep(JM4?N(pc8DJ6JliTcfvRmG2&tJ&SWyDw{1du1YlyrUp&JOg{c#YM);C ztw8R%`k*SQ%aFRBS6DZn*Wz4DO#)sfOS~T!D0{{cx0{@x`B=kWR z;%S9zxrwi|{B&T)RY7n5E zVQ#b`j=F)(XtM4rimQB6Fxi@;`P(0<`DV>KbJV;uXU*HFg=-t_>2fWImy2?5dX;kO z#2TLC$`c9>#hzd$-BF11;$sDav5fdwN0?`4{<_nm*(0Zfm-{6_IJQPEfl$U{Q!7Lw zu1@!0_qSL*Vb&o2J>*7(xY6H`6auo2JA868p(D4r%(+>^cw1EUBop3Y0y#E!sVWS& zgf3bS4X3G6SY`({2T?%;Cs;8N}&JW3;#T=_m(ZbwIwT9%{>SA zm?EoWcZw?OvAEBD_XAX~hP>_3dmw{q)QmILCGl4Sw;L$a z<}z)*+?9A9GU!>m%mKqS9n}rrTN>4@u9aevn)MhWueC8Djoqo7h!8=^XsJ zz(-2eo*4}+>?Ydz?<{;bqoyTp>}xr68|%d=)-+ zBwjI%K>Bz!+z_d|cpLYX;UGy&SrxMicjd|$JTxiZ7>zvKd&zH(qK(14!43%#Twqoz z;>#)%WaxYxrbl5d3^9q7YJiOf%eGb73vGE&CCdZ!8v~hym?-jkbSXMWA>Po2Z{o@* zYfV)TWgYRCQaS94dNC>u>!Nopugbk}AJuawi{=zt9(rD}N6C7g$_QO5<}jIR>vUq; zi})^XBDL`~amz5nde+5Frhzw^C4}Z3Xp=<_Susg>QRpzMj@BG?6z8g=!i>%})+Y;UFG8t_r34~SJE?qCtKg>RQ_)<|tb(7K8Va;sXl8%hC<>MA2ZQIlK zOFu&~?Of}S=s>*FW3k^LzMi&l$f{svxv1qKMIM z@3DTfU@m%2eh!naoQUptO+37ojbM7+k=zC&yw~&+(p*I^`D)AYE&e0bnijQg$bi_U zIZ}}T=;#jXV>Mil6DhzXmWOa-;h&`81EE!3vqgmkz2IeFQMN;OZ2sb6Lk@(n%aUpF zZdaZ&H&VMt%K;`Sv;rcW$mrA**d=Sm5hVvKY3U~}BU5rC^B`LaS!cPI5eKvrcBggR z?D7Uu9Lq~2F;v&2o)Kn&%xB;*UsHxOJ(Mj{B=Tao8RbS>qHml-B-}Z!D+xEB$3@J6 zC_AbHF`$|G-zq(N_=;)5hqJL}h!ph@gA*g$Cn!_4zU>{iq312nmI`7q6mT)%8p{|K zSMc`ES}Cn^(FKY&ms~_iWF}Jmc!83%o~6hh8ElwFU>xnyY*Udpn*ABnX5CdNBbqZG zbxm;y=jKpH4tsbm&fofEYG3Q;qqbW=Um0rs4uw8Fy*+bN*2PLori5pPUJW&Db5rbA zExjh(-3n~r1o!;h?VOJi+L=u0Z|4p#?$cXweg@^otGgbx$m@@<)T|Z_I?>SXk1WvD z>M@T0l^R8YxHv!Yb(CeyN6e3UnIx*uIrLQ4HB}nK6rdki*EzPLKD>oIq%tg71aZaj z$WA8d!|Q2w9XqGhkua32XBkRAal%k~iQSnSb6TYs`Yac1O*^<|vt#Myt>UB#$`!_- zhK^NBSza9dNq$;oKd~I1v;8UM@5;u{Rtgfsa*Pj^97B0qYS2*3FnW-%B`P4EJS757?Y6|18EHHk;G_WMHpN1pbm_7T!CRKKPp!C{uAGol zUtQdUh~@IAjIdP!TXSI^wK>{f{(14zKaN>Dgj@EdG4C5VC1@)dPdKS%MWb%Z)+WJH z`{>CAyf7T=nrs7-clO~GO&UHsI3p3!k}ZX0baaPBZ<}JfA=$|8I%0_(rE7u@rD&?d zdIHgB#HwKu-V=A%HD%ddwAFFzqR=@u-LYz0KVJ8A&K3w&!s_w<9KE$eV!>By8?ul{ zXhSRHV&Xf2-d1KuI~G=aRmVHwzR%2e=u~uoa=`No|BHADy>7|>j4N_h|5?<3YK;&- zVerqq{xhflEW_x8j#DtkhLnNlCkT4gzumrinad0eCgyy_6FJSvfaVREF^z71tk z#<6Y`5eIyi5BM@;m+f#{QFK#m5@v1l_LZB{6aDJX=yQKYpZjO&^Xg;m^V-Mj^Hp+G zi5GX6k2W0U!GOzdo?dp-ExY9iP_`HTLe~6F zGgksKs*Ap+LeyBDcrrh#Vd6*KZ`gS*Mi3 zcEu>iu;YNE+$<})XeM1=(XGs+Gbe4}p`%;%l99(anX0d-R%cMHDXR6D1n@U9uz+1H zTa}mOy1vy~|1O$FZmc`C-Wr&9DEN{Ue@Wb5r;tzz?SQ~5R8R8qs5_vs-l376ZgOmi zF9n+H>Um~UguL9F7d0o4}=~v6qT0@%kR<#5l z)R8CByR9aLJfk#+emdevk;)aAZQ@qsV{qIR)3DG1dNCjh4x5#-pSD{IznXva@J+R4@{0tW3X8VgNVaGM<#luF#3GOtiEBk#3Vt?OgHHW+=+G|Epi{pcI&_~h z=+tkAPA2G)?ZvgKelPJ&pD6OIOI+e~zo7wbin6!{4W8<^#k0zpsBP+)zJo+zn*a53c+}bpR648 z3~z5c(K^{sBNH&5W;0%G{2dIV1|BrQ5Q11p=y?IX5QAP6&=F6jhF%ia(Xlbb79Y{_ z%iQvF_}>6p&Lyvil2<=c$!p>k`$S?%GIk4Q)^Hs&EB=o9rp>3f3VZ^$cFu?A1{9W& z7)@_6mE4ys3Ze_%3~^1dF=s@(bO4DgEjvm}(lg?X#-zwu%Pzn$JTMgw1*KCjMjJkI z3{Ei7Y06q2B3!o(70=Kxeb`XpC1$9n$0~iCPdzOf99kog!p~`7O`|bgF&#}7%*$$;Vp3ZC8MULk;`=uvek>bwc%3yX)_I!KOwtw2 zURAPIuf9GX?aU+?s|;h&=cRlz}OyF!yRc4hytbCNrVAyO%w4!h$11kO%`nN;@pdFC3D(0Ir*z`-ax~JznXzB zUVXx*{ioj!batQ}I_>#ZJw$1LQotSMH6`z7aA#g3RrDBaK=Bk~NwY7$ej8j9Wlc+JgHg>$H>pZExVpF!HdGr*d`WDmRv-s)VKHcQ4GQkmEOrkReGkcS znUfSq8wRnVYJ5fE7Ztu^m6zFWD7rQUnK#S3cnC`w$EOs2RV?;+{fi0@Vdvxcfx<() z`FQ;rzb%$}4;OiQNKf89ZXdbz?T14HFK;g?g+B<-F+R)p`aNzR;m?bknJzzI{6OI^ ziaT04yu^4|RrD*%YpR8}wKTsn;P1;&mf=}`WWe875Z=Q(TFKj&YvLBB@$tSKhSwQi z@%H6Lc+)5!@5`<5cC>;%1u3roP9!h%i^1LSDaI~x?RV&6j@YB{X~r&d+wWl83Hwa= zEMo`E8C}pA`&@WWU?&FO`v%`B@|_rb?;CudN4^t-?|p;s3&?k3@V#&FeNph;M8`3! zzYhPG!&jKf>bKL5SHokbvik3|=e6*4rn1JtY1bR!o8sQ5+f@!frIMw*#WZh+?=TIu zO4BSVns>wZ(9P>x;uL--CQ1+NpN2@PsvH8m5g7^|Q5I&b(+PcHwSz;Ee&jJKJCwdg z8nw!=WJ+05;70`8BaezvC^lg!Yph_dm?MxdPHJ>fs$oG09H2#8r4jmgPihdM<3pcd zo?osNz4c5eFHKAFf^62QsE9WKO!2}IFS`6Oldq5JOVm-65klz-3AGkU3IZ7CinO-y zulpv)muW7rg55=#XN;i>dIOv|_c%M8B!k+RK+d?gM*sN$dG0+q}2Mf(2Z6K3n?QYTT_{K z6n!@5T|)beHJhl-BrOyTdJr|s_<^t4n-QgM+tR3QY{E_wV%Li}?#2emHpsx+zI73% zRG_p6#m=E~(M%RI;>kCoxEMNL*{G2GK=B4iM-iu!Vzd7h;Y7c63R63|imUSLMvrAnJe*V^5aa z7w@BCL|G#aaIi%i1neBc&P$kcTwPfB-_r0o*!GgzGRS*3uPyq^ zu)#A-A!W*s3mPOHKMfZuJTdK1GiVK9gIH^7f^a`JK@e0jH(C-0K-1QkY%lyf88tJm zlG0Lv$ba?3sT2sOyPu?D8n8Xg7Ks^m8|>Gw^Ns(A88h-V&3tEyiPqcj{eUEh&3C*|pl7xBZ3R z6$i{`Yzr6XW}%D*Rvlx3d{O&OuRGH#-8h$dn#p7!AahQ*z(TMzcsYPiP=nQd0a;9m z3*xRAkt2pZEnS>{4MyY{0eyC|gQ-qAxR+A&2dwdR;Uxj@i8ZgI4r9a$g$J)&h*_Z| z%W!TtERrQb(i1bSs}Lk6K}!%c$nR~N8@IANi2Q2?75p`Y&Jw-8ey}q{L5uWeRbh4<{v%$ zynG(mcYVvKSgyPc{l0(v_Qjd8ik*P!%ctOeA_LgxP5a`&vM&~oN{l|4x689uvu!%? zNpsW+`i4ohV`UVQsjEiU)nA$D&?0o|$&AIa(uS!Oal26stvqN(%ks9zhE45JhNbBp zg_A~4U4?^8xa7xNnb9&Pj-q>PDqP{#IYzifk#R#DCA3de7~8jva95QPZsm-9<>stM z-~*f7=ZzVC-Z)F22amPSn;);wS1Dmsytu=BwCyktcDU^B>1B7_vU|=8XMf>;)nD{h zTmmYNqJ)Qj+-RX@?*w&S8+nY8#jUBm&e%HU%vc>iiXa=^2UXUrLYrYeT66T`nNa}; zpg1CnjFNtvzj|7I(sucb^{$Hxy+dl%E8gyVr;te2g7k{mLrxj0(5*`p1Dgc#w?#U% z$vAc5BrSG7DR0P(T3T>ScFs4%w`UjZx(RBRx&BbTyk$$#rok<2H7QnPTos?Fv16}9 zZ>zO@S_8&IaZQNlLJ^B+v^sDg*t@mpbDqkr;dPsH79Q5V2D%TD6K&Fe*@)$m_=aaq zurq5Z6j_YLSw6E+Y+NVkn6wj-GI5ty7pGUOn8`ccec`W&tLc}V_i(aduV3h4i|pqk z9Y*3U{pjI`cFFktr<=8!s zb7Uy{V-mHHWvX<2T`ZFS3cxGIp`vqYm`gEKlDDYucjM6dwiyIjBmk58I9|Hx>55LL zqxv8cw(B91k2XjwmQb>c@f6NO)?KgX1<=(D`jER;PihgjxN%y2NcRN3&djzomEx$4 z)bLkT{iREu_xg%tA_^E9=afv}_0nGzx4hY7?&ouQIX}NOvF#^0Nv-gh#wox_0OD(> z-WPP_4jJ(pjHkZk&$S<0<&^DfrD(@WCng>?!!gQ}Df0@cmQp zxO&j1;WroIFpzHp>qGz*ec5}nPzm)K`f4yE6oNls)W{n!YIL-W!FkPiQQE=iJ=g^% z{9F&m*T6#%1LI{;HnSv7x+a};UMR`wLlAd)JtOf(*b-dYel8s^7Z*Aw7nxyM%Gj1C z`6=1>E>C5oj2g$H%1gVn=@QHL1B#L|+D6G;9WRf&td;{o+b@GS5AKh<$N*G-_`c7~ z#|qe-^mm38+-vs3wP=uZT@`~&ZxnN)IpK))hp4 zR6W5RfP(YGUl^31OKhN4SC$C4M1DpIuz1uZ%3Pu}qXgJM>Jls*hG>(CC3eMfZHa|y zSi>TG7#=8i-WacQ;duow8Dn)Jyr|$cW1L3erRa7zruylTdYK=omq%CF((9__WOI3R z&6Lc5y5dmPk@wXR7E^QQ!)R~$JWscxQ?EWt)mxl9X%ggM=#+zdOo&}UdDCw%Ewwqui`p- zNu$O(8V*KMF$%ZBg3_M$rmJwW8YtGC8XDF$wrVs`r1;zrh=CtbQwexQo3_~J zXIq1Iuu&#JH4XHvAh)c$weC&(h}e%el?aBS$^$G}7I#ur^?7t8I^Zg)WQ>4PGwdVx zi}qM)<@CtktTwttc(}IF#KGH~%NhXptSj7~t2_j|;WZZ3Kf%ddo)EPhY8W%WE?RX> zne4R`{D!#Q-;5}n+b!I!TCA799m&9OV9Ia@|GJA^2kcY)SBTazFw##)WN8+nWeh-` zWk^%(punLhIFO=s3GLACG2MjwBx~rS7l4UbH4{M(r#qN&Mibb>X4C{{DNoUaK*~pw>`aBBEgW1`;c8*%;OYuTv6oF8J}e2@6ff8=hgFVo zgxJdtQnQ4JkPcGwT*38VA%Q7l`{62Dm3W!1BhxiM(+y<0=4U!!V--KsO=P;|XS$6{ zxBN_Zkm;76=`J$eN?^K&O!xgvhsbo_&-4J9?)#aZL#F!)OwS|J3x1{-k?93L(-AVg z;AeUXnO;a>I!30KU8eMwzJg3IyG#jr6`5XknG*6EGQAvQ8t^*~m>x#jmr?T+?=!|# zV=TqHi*fZB$J_Y1942slQg95>v&!iEUGde1xP2SIBm9(M*L)^ETH+zao3#fyLF9ee zf+<}ZzD>Rr&#Rr*gM1N@FU8tk<`)l))+-6vR}p)hfPD@7{klCEZ(zS6A=#%C?5*%^ z0mHkkf{7cofPIFWO0ds{4+QLUu=6R{=dr%;v!?M2Sl<_|>H8Aa_x;wieHrWf0lR_k zV}(B`fIp;QKOBBVz7`p3dm241GYN-$;o$r zDKjmEKZPCn?k}apIfYxd7Ni9vNXv!!lkcR`C%=$_js4eie`?|1j2}A1_@U!kdz|Ng zD}3lkPv_E^4xO)ChtAhO=AmO89^x!^kfACFp@nVSlEUdjC-j^-6$fX08C_&(A~nNh zWC*yW7{e%B5xAb0VILXx{0vu-Vb9BO4H@>l4A+t2hL_<08E*I)ZX&}CFT*WlxZ!2E zjSP3Z40n;?j-TNkGTiYp+((8xUWP+tc;IDt4jCT!8J+n7BIZ(JzTDZPcY7RNF0i%0b%1DZy?93UXC|0gX=AO zyl&$j_M{EGgP$xs9iEDh@KN}*KtAI~K8wib61Mq!*z!pN_C8{#3E1ZaHsq5@b?HTH zJ)B4{E7&XHtJ4rq8ol+uhIFr6N5mW9n*#PJ0eefq-VWapuy?J=cn|CQw9WW4Sm9@_ zv44OSe$E>6&trvf=6pfHz8HRq&zCPN*bjv7iz9-LlD0ZXmgpc+N5oh8i1^x>kBA>z z_+@cKe8oN@vg(M4yGy%gM2(M>-1q2=FwOxmMYoX^wcZ}z8DT8q4l*>6n&IO+Bdixo z*+FWCkME2y7!Hu(hL_>vJ0lE+yU1|I%kc4?5eCC^$ne0+@bR4y#*T0a8IBx=kL!%^ zodf!msKhw_i+n~rG4qUgV&)m~#H=$yp9e1^4BjfQD%f%O+8NIXgUNeX;is)R_zYI~ zFVY$Dcj9M6P_*(YTJMPS2|}yvM>->Z!a5^PQnTzJOABc|GwO_O#CVd}YsJsAhdfvOJp0IVC4uKE@?7!q zTtlAgex4i1bKTE#fIQa|cy1!kbuZ5?2)$T%Oz>&Wqnm*b5P9N2H#!*UC^#M?ITN&JXGz;!o1kWYn2EYtEJpF!la zvUSh#el8D0K=Qo`8MYI7U7zU=OTm{2bQzd29N<59|Aa-Iy<8g-(|7FALZYDA@bq z4+_{13D^%S*jK_I5$Da13fNcqy!l%Ab#dPOn1FmkoHrDL?#KDO`6hPcEa%NnF8ucT zqlaI!VmgJKS28SHUW9gB02z722vd?}m2A;*M^v(}h%6UneL*HFhh`K%`7<-5kiGm9 zqdtkBOLic?6VOQo3d)5}C~Ap>a@iB|MJ%H)qUKhzn3XJEWiPL?msi=ti((O9BWpgbJ)q(${Bo%UvNVwvCLJ^iFT(tq=t!Txp zN7sKsj!Y|NZ4S1huuj`fD4j!(0tHaKBn}p!LJf*Hl*ZQ-KBMq`10O1UR^eBpb;e7B zzO^xh>DZW1z(2?`V$)^d}i9-94Fn}>6>IZhLAbDU;7Y8m6N zu|)%3v*1bNxiiOj?j*Kt!$WhBIOaQvW4`C1iI1nG*_)%ydvmmTZ;m$a&9P4Q64%L& zn!98g-R9IBZBEV6=F}W*PR+5lQ;Bn-*X^t}$7$kij?-*=)^KX(8qOrHhYb%+^0;N@ z7`MzE%zAZ z>XG$l66$y18wv0&58Q13Rzm$-3GH{`TNb>C_6`_gm;c6)sQfpKXD&jUH5G>UqS5z@ zlAhri1CC`a`lWD$ca{z4jf|#t$pU92cx-{UB>1ug-jU!d7Fb1UyDGrf)MZZ9{e%Tp zl^j}NRmpV$zM<}Xiu$GnR@Aoy_%?#K%w9fefmKWHSYTE6T??%0eoBli7|vBaUlG7- zp07yKyI+xnyI&E&L(f;_@=Yc1O(pPk;qF%?>v6v#w|*Dyenk@QenoEkw-V~#N@%|e zx4)uY5E)FKA;a`%*f9N>(X&JGUlImPp1~=+7d242u)$S>m|%lpTU;%`wiuS;CIc+R zunadBMGOqvgLT5NJ%;VTl4977XyRm66YpD1oES~K&rO^dO}x)djD?+Mm~7&GZsNpf z;(c!7#AxDuY~p2*kR&xR5dDAJH(o5m((JoHg>QdlzSQuN=M*Nyv-*Vtgq&KW~vWEbwyFHTtCRaD8D6^+;pHrRC(; zbFVD?R;a`vGJIi+#prD_BBf)kj&h6wJKL97=AL6=NI}cu?a-7gI{|%e3Y_lSN-j}G zXDiI2BwqAG2hO3uf;8p+vS`m?&|RX<**U_-IhEZOB;JdYw4iREiMn$p>h2k+Z{d9@ zWa@~iR(LV?_+O+R=d65SA@cH-PNLqU%wl6FkC^y(lmFdf%DXX2D>ufNVt*vpfMG)R z7z9?FLs2Q2&zFbN=TA?w4NsotbhJa!*qAzDc#-b>oOBoFqT9l2%xP3jRH9yDsz?py znCUKm)WKXwsW;riukydwnB)m&vW^CwfN?1$MNiyhXsxKm$u8jJ$ZzeEH92jJXL!jo zX*PV;r0KeEI(37V_ok_U1FFvWme7&Lm)I7t|!*@V8DL8et`~ zVQx^xFei&fZir;qdIs{x_bQ@2TP+!*`Tqnq1TOs)CDADhY!dAaY9;l&?R;w*3B*3 zSTML+!!sPQ@bSVe68y_ETi>i0GL{e=2` zh}ve*;1&$Vzm#T8?(ONxucTmqo!p^#7k9?r5WD7AF|q|rV>vi#1i{l7+2R?5798la zUg&dP=shoVg4gXkqoMp=WWwR z;r`-m?k`P0V{^YIxR1TuXY+Y^HlJ6fEp)`#mFpAX;Ed5CXABGed9i3)X!umH;9m&8 zD87GGGqd9P$HQ+EN?R7764S;6V#XHtUu$4F3Qs-+SY=!gA4pc* zTTi~7LgjZc6e}*0=2o=9XY6pl5D_I~C4bI`+2a;jryhRygS3Guh9yoTz2u~eZXMV0 z9{K3u;d2bUA*zY+FfJV4WqmsYr#==38rU6_1on*>_D#fo3bAj+ux}&w9mKvH!@h^u zPYY}v4GS`=E#EKNQb)rA4f_Fvy>G#O5Me)L!G0KFU$J06g0LU8U|&Vp*DToA!>nOg zV0(d|{A;O&zn4=dp^7+X9%$KOa-jM}3N90_@Y}lNv4fNLaUFb9%lri5UpNC_F)}=g z=G{oWaCE$4(&yx9SPc=IBTIbV+(&7@mIpD9L0Xq!ajl6YvG~5UXNjK zAoe<9Z^p2<5PK7`w`15R5qlf4cVgJPh`oc@r()P6#6E@Cr(@V>5c@P@pN(OkL+rDN zy%)ny5PL5?6rbU%3%{PVK0@>R%V@!u3OPI|TEX&l97!W)zG)yNGfCh_tP@u;1NacH zjyby7jr5hPY+Z`?Zu|npb(<4SVPrvu6^@oQtif(Nj4L0l8Mv}cfjlaWs!UOv)>Sfl zN?m{Q;o`K4*DD2^!m`KMcoKpyRf_DjD(k29zU3>^pJMZXk9yI;|H41S9%m%mTRVj=9`r%yN@)UPj`ez{ybuMHtqPAr)znCJOTCnr{6a za}HG@vB8N2d~M;s7N^LM#XR`qv2oQRE!k8~gj^X`f8C0iXPqZ`ib#Tgm!ofLdo_4t z;wy6eD(3Ke9`9@^yt8G+oh^rVw!9abZgd*0Q6N5qqJt@L*FkzoVY4Z$W@VC(PX2o8 zd(Zx9l;Ye*cz7lrP@;}sb~A5c{B^=OA9+YjjOJ8{$Kgr{2ZUg@O1lKan9E+AfMvJ1jy?Zp)RZ zk1xy)OGsV(6HyuEq#f4HM$32%c6ddB99E9*4e`a@T=<#7qlZ5+SNxuOTrsj(oSDF| zXovI3I$?Zr{AHFY(MoF0Kt8G0kDeAsL7=j96y%}N;H;C4s@P;WCx{rsM-Ll1m0@aB zJCRNaGRq3=boLnnw>}zHh(fc%M|eBdMw!~Bjk`X4Sr=8kBMh-~f$O7k zEbR+4%=wmR(RCH;r>Wq##l8PYyC&cJ?=lC9>vM#EJsnZnumHu@^w+ceR}%%w z{c%y$Ls#(o7`JE2Gw{4M18JUt9Td`Pyw(3P1>7lN)Sj1koS!EMk8>AaB~bnquK!go zaREidB^MLp@EUC(;@CDmDkEf1BRNT3$voTJ&Sz&o&y6q8M!;3gei23C$}xpU>RYe# z=fZnp(|Tj!Zwwwi{3*TvKq?NEH)lsjE0sG}&$t7x8Mf_4jyri_sBx`1cB{%6H;|U` zcP@Gt&+3VtP+$O8D>%LvW2ptQ<8_F58cUE7(1H|O9A>1|IVB3wr|BX2iSoD>mUKcR zRiGjh`XnVv$VTtU2^F>4#P|)SJdN8Dja&CLPG$%dD+3zH!msFap9F`{8G@i5eFNk` zlO+#_K^SDhC7l@wLdeFMqCR@EbjI$Tkh%Rgzh}&yo`b}o9lfbio6bGPtAbPH;Y5`f z^Y@k5m`hl4_y%Z!+`&_IpKSo=XUjA9BXBo;3^O}u8PY8(qo(Md+ z91GYiKIm1>2vL$;WW~0v)i}uBEDuATbXOh+IP8YpCHy_|>G$&SKHa_6@`nFom@l5iL_Ab*Hmr9B&;JGK2i_k8i|=oEU&@@k_YxjDZFKUjKo-XNSgc?lP7 zoJ~L2dUd|Yv~~*J$^7Xg3IKSuRk(Qh?fdn1Iu>18C#+ovTHct5ZAvFb``s_J4lRIU zm-sP%xS2>d8ep(xJj`f+e2$w64Sh$(ycOAm55-ZP{V*qAs+zhhJM?{QlF2}e`+l>2 zT_0ZWjTBjd>FMyz*y!=5$aZ!xEk!$0>$%xMww%EMm^jCTEw%KUbuH%a;6Q0Zu^N@c z&_c}0`R}PNzBCmUN1Fa`AJ%H+4-QRggn4XCRY28;9| z{CmO4eP|1kuO6Z|=zF`F`@~lH#MHzCDLyTim#@3=f~(oj-IiNc*uqL#smgLyhF>+q zWh9Z!9@N`@mBX~G@A3}6M;iGoUZbmJq7kx=b2LVTt2yUt=MJNT>fIQeE_+RDT#*c# z2}}O{iC?)RRoWC)n9!S)T&BQ}FIS*YGE1E1`=IG*8>nZKo#Hlr^ zN~9;MQLz}W5OwSoZ^!l~>K*Vt9E8!{KKxzuQmRmvr+$Q2rtb@frOV2`a*%9G)%W?5 zOvTa_Mo-&4bM$Xj?DxE^nX%3rO9`7*j@Nkkd)fDhja#MIeI-;SXd#?A!Gyyq)@78< z1pF?It{dSCJXF%inFMxeAC^~6{D+TutzGgWm7qVernW+M1uQq0uwZmss{YiR>yQ8` zTf4o5YO^@<`Q1`v8S&X9oFQQg-3ZPBP6K$RouKB!FSX!yS-I(>3f29CI(8Aj=M4!i z*qeXL+%HCyG;xrBF50G?R-^pIL;Rrvl0-?ok7A``|5nr)gxI%Lwj)`B`e9(18=Qsq zqSDpN`Pic`$t|q9LU>!bn;jgbS3J7htRmUfHAsKW|1-xkN!Unx#ATlyLOegcyef^;$M)|If zmpMpWlS@9{fzMNPkU3sm>2P+XwJ6%0!ay z^k24>QLUb*XshZXEfBe30G6W4mcY&u*%+U2X0#e1 z@YK&CUae^b51U^dhV(&3ULxq}1-lv~(c>eHiC!vCN)ul=Wy5cR`ojRQrcsh`M{bWM zF?AzwbVgaJHR~e^ErQ7fzm>sKCFE(q%>uc6*D*SzN)UN+Cn;U5!U+=WtJFKK@s89z z`$tAZlXKNns2YXO@VNQN_j*yNkN`*OMaG(o!50#u9YS+W}8_d z8~wQs;f>xWqP1(GL|tq7%3c^3-B_^({tpuRjCb59Y2R@Vu@-p-oCrs zJ2bBK*GkJ=1hxZFC)H5FvA!ZZ3#y?aW`Fmc1zyd#JWRk9QCd%;DA^iJ#s@v*;~!D< zA0;--AOCDIcTGzB@pPeUC!*1rN9N@Xh!oPSIHj1w;19ZM2_wMKm(VihBa7QW%EM1 zPMFl~F%$}j6i7(es=$8lG;-m5V-z`S)BUE(Q~JDCUJ>m(oJ$;SHmQZkh2%u|fu36` zURx;C5~&i|=@3SLrv3t??+S64imZExHAca)L63_xL4(7~ufBEt5Flp5UZpaOO@ zV+pyPG>_{9AZCOOMYjy#{0K+l%P1_PrQ2p=zX2_-!Stm_I5Ajkc2yLviRpF zPGFBU2_ zGpf%9RO@1q%)B(V<1xX^^mlRp`U{fh1p!l`l8zBVN6Q;%*969^6gJ7}of*P?2(yJ( zsh9KBE?}tPz2a@{2UEkl!XN>`g}zPABVwg42iZ3EGJ}j=aziFc)+zvaLx&k{mwCWc4gzW#u0I3; z7@+I9z1fzT6%@39;0p|~nAEP8%X|!hkKw*iiN(V^UjN4m-xFX| zggr6!Lug@&Z%wjmhBa618qZUljPGLFL{CQ(yWr2O?KLlbJ4rW=avi|K0?gLGdiiy? zYmkOlaE6e(%VuiifEC$<_l(o+#OVi1j+AsjXHTZEZ$P$pfCLZ^FY@lsFXev8zL z`COK+pbxNz4kTl9I@U!fS#SJCaq{nqX0e80+rIhaJ}8zYEbf#sY`abx1^)iV!)3By zE<-O@5v|*Ik()}>=J#=YBDeSh$!E1!+v>G7r*Iw9)gUViV!-$Pmnnn8p=NFom)|#2 zTc7>Zx&rPU_lX{?!;5QhW{kCV+C#B=0`C!Re)1l&-{{70ktQ936i5)y|DaE5ZqBPB|wa3-RhsVe%-C`lw4R$bMQihFI z-!g$fH5ut}yu1Ly5k104DN)ZPA}mSGq=00lFZ-5({+l4a3leXVO`o8@osHI#)|S?m z9=|y*B)qhw)?Ye#oYtfMG+KgHJ(Ku@HNENC*!~@64*ol&D+ZG4-Eik7#T*A5wRR5)ZqE63a(jq|laMvidmPY?4xLBPlNuyi~#ib0y#eVi?a2}3xD%VPl2OR5=&;)c#vZa55D$!AVoFVA<)3z2#Ligs z zf4G`R+`WRF7jsRdoSK_)!W>?TY_{@Me=`ozs6_0~-gDAW`eWuG2?_M-*4u0e&kgK< zot!lq{Jbc_>&VHnaKHZn)tarLh>=L0|=st&`Fv@&Q@j8MPpfmsW+C%C6n96%@Gz&i%&048d z3SwQdXcD@Lv zCc*44Dj#Iwb1p>fi%sD6L1J689~^E)(jrwZ&()$1jna$3}Z zNhmui5;qz8UNOFNBM4C#nWYod7TT<6Y3wK&&WMTJ##L(cOfqYP_6m}qIrkLY$5X#3 zYqa;q5Z&6@`fgIP-^wU3MV*Uth+FI+iLC5=u`uT(PUl(QTRQ(SB47}&a5Kl?|Gg-L zxxy@6VwP~j!|NK&=|o(uvObjeID|*n@!5gOK;(0>aBxfoU3K*OV-}pS+eW?^IaIV5 zZ;qBZjX>-MlXb(8m8^LhSoqiMI8Jjs z`%M_7b9l0#i#R zDQZ>Mka6?Sx_Mq?P7r&83^YdOMRw|{cnzd+O2;Vj)koS@B(yNvt`mi4|FD8#QiYedDoXVMXF%Do8#-3XW(qS8HAkz7$W+iE@ zkGSBZio&9(TO9;W2<|VO@htVjt=c&!QC|xmUYWyRbr}-NGWnt~;bsx^ZSZ|PXZusX zaG7%IlqGc7d^bygc_`!DCa^HEi_w5O@U~3*Ic+DjQkuA14U43tO}7ogjxeCz*WcwL zmWq|`lGD|>;^$7v4{yS+vAYN^)M9;HMF_vxz<-p*sZN@w#s$Z^CU;F)6&iyGL!y~2 zE_0OB+3r@TXLk>CF;X}aDOf`a1~xf@@!U6H5>zKtUo=ujIYWc3^tCf+9*sJEGphR7 z^?Xw&jL`fhNgE?eAnrN=$v5)N-px|84mL1L&^ua8?NPj@Syl#+Of-?XO)LEW*e>9f;iREFV>sWGZy#Xv;sa=WWz7+k zKsd0I=jK_S5shPnIU8OmfuzZ)de;;k->7*Ru5A^r&LtDGu`D5d1;=C`_weVA2grRq zw&fB*xi=cOm>gjA>!71Pa~}+AITGRQW=$v})RMXh_k-N9pB~wUF)~94IaG0aQrh3$ z+M6(1B!_*texz0x!6MHsNU$ncA=D>pFwRs}dYgQ-o65M!nT{M#Bzzc}BP`1$Jb0)_ zor5oPlo!=C^rH7HY`EWpNszyX`)i<@?geTR{)YdziCzp{-%eG!Z;Y4>>q59NHxcq4 z$J3etHI+%55j$eDI^AFIHo17x==8{Z8>8zOq*dG6Tb_s?xSb$XAKThVF8*bu7jp%0 z*tGfSrJ6}37>q!7;640{^aT=_yXB$dWEQChl-dEnKI!0L{W@H3y)_Z?5f63ZJh1jeLcuwUH$%cMTx*xU6qfGq3DMe<;4FYn&PKqvkVvHQWmgq@NYfh%#C80Q>K`a^mV{CE=q>uC4I@v&r<1z1r0cB4Dj zd-vtdKsBEyhbZd33PTbuw9%4uHp<{T^Ry+SFX0>C-+L#+4R^7YQaW@Jto>`7%ao1~ zSnq^52w+C%m+s7IUMyUV3QCCC3*p#z{P6iYZAG|I0dz7S$>odpZz8z3Em)8?i^lA- zh%=R-e!!4JbuzvIBjdH$trrLM_e{S+gQpQzMXcr@rJf8dg+bhQnDHOs{V%g{{Szo5 zPbN9R#L{o57&`&BNFNE)62$=<6x)m71R2oxi9P!q5I#eUP;U1OXHI(8`$FudfA!vA z5}RIC5}2zR_}SqMFE#z^6aMw0!F7z)9|~IFQVYtRc+EAp{&||TPKj6s!S}5lE9Wby zKYtz?u`a-TV8^C)ujIBB)j4tUqxY;~OY+S%^Kj1P@9hudfZV-(*se4DK`$~uPjLCs=y9tcezhdO`E|?JGCmN zElAb>SWxG|iqPxGO!ne!lw&wy?Y`z$yojMewj^vc^}gwWw-bU=A11q~tgNurkOXBS z#{Xg?zsQ2oFjJIR6O(tFOfG*!yY5NJEj}X01^`VYV&8{daY!%zV6|Zvprlcd&My6w zJ&f;3r@ie4ww3bmo2?QAF!+|FGM&ULH-Zk`WBp1v1`p$>zn1Xu&+mW^9Ta>^nwgH` zr(3&t_#Y^4r4NGAK!>4X0Qdevo{ps7JRQ70L5C@yk0kH}eCa>mdwk@hX5d@S3+rOg zp6d}aMALP}p}6=?Zr=*u@B{k7ewO8{&r)3v6zrhMxaE8-rTnZ{vh(A#v{wnn!QY4; zh{PL;{)Y$3#~w4_%`*Ww7v8{MPT-*)4)TxDwlPy_L_MiQ0>j!V_oZ|dX>I_^zf?pHa5$y69(hHg2 z?nJbWoF3~J_Z+~0I3smc>Bf*I{HsS29s;H*nfG#PA36AAKv!|4cFyzmT3<10+zuH@9yyhfSHBqA# zf!R0@D)0pbcEmlar0&=Cw8n<1uX3SLVe-1~?|;>_g|^`XefwMw%Gk)jgQY!6f|t7_ zO;8Y7=$m=4suU2f5A@d!?$YIgW}(*s*4o0Zj~N*kbYO|DQ+n!D23x&hTKF{H^|<8J z?5}Y@zoJ=y85IXcW!MRxT);ueLpN{}3GuHba}O+Xl8DN$NKolTQh!CMTGM2bP$2^! z>Fz)wR##B{+9q>nlmohy2u6i4@Dg(2A zK8}=7HI$)`YG4YU6Z15D?@&&5FUP<(ln zj!6ofOX+3?qTiayACiewZ6_cgH4c5g0=nN)4K`QNLakNQur*4%HDAoB$ul(|-}Y z^??4G5C2XZx{1+GqIBs;{>a=<@<|mgMnj?fv*8RLdcbq}G1~14%7eqQ< zj#x{9qyaW4)+b&&2)eZ*;qoU;U(uv>1M6>-Lfae;?IKbUZJPcb&QDJ)F+ zsxLzUw)ussw^*1rz_)Y7+u%|eXrWPg@re5n7*{#g2?^UGQ@REm0_Tj-{5`WQ2N&#W zyFT^BX2BR@3`Whn;IeWXA3`BW#@ME`JvsFZDZO!a$?+yy1-*Q7E4{Qb#z(1YBd`@( zSHRq}zWnL|dR4&OtyO6NbMh*UQczCI43+vz>Cr?{00?WV!r&P1meGQ>W!~-3XV}!X zte_YNH>@b$SW?8T6R02-kZQ(v(G?fSJQsE{do#^&sFkc-Cpm8-(TjGpAkX>c(E?R{ zR>t^#tFWWOVs%x)6D87}HdFwje7{x44)R5MJu+)?U4FZwpChR!^bC4s_&>D&Kjgk0 z*_{>0dCz(t02K13%czwM>W3jVe~{L6#>n z(gGQy>3%CpjTiuu)$eB0zguxajK*V+#|rvL26rVgfujU{G=sa8u;Y*fRfzFO0nIQL zgNl0jAy{p!46re{x-K;^7Jn7>B%m3bnoWOZ(beRi&7^@7qv-k#@-`%>>hNBfJoQ(`mBL4KK<~pJ}->8gP~of zm?bu2fn<9y6nJ2w22;jPgLE6!8aO80nR9I|w#p?5>^M8G8h!kQ(+cc3d#@T8*2=XR z#x6SLX(`Fo5wk^M;iZ;DirN7GUv?xW4fj{=;#7c9nTD zY4XeNb`|`=ZA4$5+h-4Ly<_VSpkKG?lgl&iNGX5;ZJ5VUCCt)QE5enEM%D}I zv|MqH?=bV*J3tXJs{iP(o1vgleKC`6!~nnM{yh4)w{=keq5H)JEY^I<E+J!2lKLr5C#nEiZ`2X-?;Emhv@!oZukwKCd>=zTb~2jK198PsV09UoNd3dsLPM zA-nW)FDkeTV!EJ#+$cBWVPM@pvfLR45t!#0&F4Zj`$G}9=Rx{_*%+={nc#P%bYa_8 z{O1zUkFt0gPyDx&ANl3VM3)H&zv_ce?9&-&r5KlZ`aBHx5XaFcJfQycXtjr;54q-j z`K7@v`2w=@za3mmKZq+7Mp`(4IxhZ22zlpI*n_63cv&aF#I&bRoF>9tF`mG?^oFQZT8>p>mFQTr<}YS!!gUYUYggM^-X&) zznO5`>i-eALH@iMm<5EJ_TStEk-rp{);^+6h*W6~yYv4CA^!v5{>^M+ZWP(8l3)b< za`U7N_YE$&=?pu`%78^wu+W6RWzE!q`C1O$i>KPX<5XHeB<)M9^>~j)T7UtE)uz;h zFrt4rq4Jue0z*;~>R@B<63QM)ZCLd{Pj^HEP7oiP$^Y=*NQRi`LV!h)DC; zG@bv;dd74V#EjV%ANoEW^^Jc>;$DOg{g|K$)J!9*icmJ3G$V<0t;^^K^^QVU9T8j( z27SCUuJR4j>`zU*A3DxC8ls>5slI8=*iA!B>Ak{|eEd8-fOeeOl_(K&QmZq-ReMzs zbCRml!xej-5OXrB)4}C=t)YC#In`0DIa#P#FjTw0s8XqFFJ9oOl=*}%5!Ila((!+_ zT`xQ#*V_z!5@{92X-sS*8#$#oaDDM6 zaGMl%Wo}D+a7kE^nclm<;@*o281DZ|inCh)${@U%38p?R04@GM()$0%J>k_1G5Bm0 z;lt8L!0oSqkFi=tFN2JK>VcMJdjUAg=DMqc5#W{ZNCiHN9$jPjuc!I1XZep85Zwzw zMj2jxNosiL*YyjcKMi1A1Qj6ok;MIeI9LFckgkq^wu#2*>Q-MuGhnV0Q(jrzt)}U_{wH3dx|_mUw0`#B$bbFx&T!|9m!rPtI3Cb*HsRq0#YXtqo~)+NnP_T zPEn_%7wBUG5@#x4SRYaV;`Q`S7Yta)@ZJ<}=aO&*yWeV}*_&V$9BvrZN(;n>UX2y? zqf<0Uwq<}k^Ah#rQ#8o5J%K#KD(sej4!7rPGEwoceA!>RN(QUR=U*mWgxp9or!n~N z!i{$0O=B6mAh`k#)`6oYO3!gzd~(bQ-}_@uil2%{-r=v8WNxGi;$NEMf&M`IU156- z6cPAly%u@Fj`v^-&0OL2hVVnBUO;*eV4{kG^<2&Ki>?$g0a~p&H{&|=8Dayypf#y( zx`XZ?omC>!e?E+24aX}D_ghzxe$l-wheML*?}Q}(9$?;d(USKbD|=WD$0X1D|GN~R zhgJt%2$vCx4_D zoA`I#b27c5KM1OM>R!^+LYIX)6rwSUMrQB9i0KAOjj=UX*DC$*) zVzYdjra82Ls^u(|w|+O4Wj58tHHs$MM-O{r`ZJnR_SrqTl;{B zvqY0f{2+WOFrx$n(Z-mhMVBAubO++r7lD> zseF~=S}MEz$u!NgN5`k)YX_HZ`Ynp|kv%kEhJqHs{}4;U3R^u*lmL}cUgddL(FIrP z`dICmeV-Oz=Kqj`PeS%dSpKs@_epvuZVum?7Ut@~Ru^4}J$_8}vzWN?m%tOzn{Igx zYt8`;0K^n3y;3|7KByakv+WW&2jsYVzRa?$H9+T8GJgp%|E=EoS3)uCEY+Joq)XP5{qS5+!}5B8r(^q4N_(7tD+r zq!HNnk!6GrB@&hEB^H%l(MBh0nJgtyxmM=`(~K6Wr^$-cn8mvPxTZDfN@y+B&p2W} z0Ka#k$!718#Xf|k60g}smuQ3m)LDC(=y)2na_=8}Iz!uZ3pS?_Rs zHb$`OM{-`FWQASK9^1&id$J30{-$J=QNqsfA9aF8tf6SNSj>L%A9ce)_-$`PYE z7em^qEb+`+_9;cn;xH*mL^83q#y@G%W1{AggXU{XGXa?2m4(>qYFFL?yjZ`B3o|s- zu6RGB=)W6@)s+?RJZ>oq8ni7&^@ z^jTu*#bITG7)(Y}Q`tWMS2dR^_gcZZ0e2~CC*SENAH6-53GzubWBLkaecn!;c#<}d z(9g~^ubv>!??R6OFw2Z=)o|v=4Kg1##3Fi*Z zRwcSm_!wdpZ@aG$kb1d;S-CY%KWd!S*fW*W(}qXrtWXp_j`#FKZ)VF8X0P?PIv+(Z z+bM5rn5%$()z4%A-aUhWSi2F77g^j+cnhxpBase42sCdH~t);3;gAfwU2YXvIWYR=~5(}9M z3!RdPG0De1rZlFg)HSqJ0*2j0DOr;zO;aK1)-GgH<0H6h22p8-E5anY0B7`-vj%^N zdO@~&dk}`5WvW`b41p`9`a|}!jG?(&4Imy*x%pTux?i*vE|dF!+?;SV0hJwOjinWb zg|%r<%#h?`qpg;%t9E5{tOLWoIZ0nl%s}7M`_>`4pRJMfpwutr>h9MSOjCaS4n!z? zYeg$!l5H_3Iij4+kD1G$(r+wq$G4r4w6jOATh`%6YB@lBWY4;RaTOms9cx7D^Hj6q z@G(C{ZwRPp$`+*a#9RJV=|HU#Wdze|57PzhZr@H0Sjum&NkJ@~@rKHG;Y)!Q^w^(rsl{zsA^-Jyp+@ zqix084*J_w49?Hzy@ye*3 zDL>&sM{^h|F*(@Sv0(5^fk`Zr!uyw$;BBQ#XT@DUBM(TT7xT=RMs4%l?u?t^D$DkP zKl<5I<)lF`+9cYuKD83?&{Ce0{`lMWVY8)h!=`#WN`(mL%(y~f(X*iHoTB3=+W(0$ z2J#MnhShH6%-S>I)Luzgiub-cwM7I^ZO)HX>(ZW`!Pn$6?Xh2736o3K zC1LrrK0yMOZ!`e2c#%9~eyQIer3I;@eDvq4fq`QFZ^eeu?i;x-)!3924DQAg*}eE$gZ!sdK;^rcev!iAs*W(fxzDp zuro~Vt0?XeLL|amOmmq_$b)r7w>rfJJIDtV(2g1Xtf^$PcPOy}!A`CXPU3UeW_v;0 zOtUtZ|JC0&>U)j;+a=IC*?DIL!mwV<&>M`K=;E2IWonMV%k(kH(lf9|=3u^gC%-40 zR2)9v{*7ZD109hi%@m<`SV&f`mTMLvU>Rk%@I6aNglVS@F-W@<5Hz*mDhzi&sOCA} z>h@*1H>xSOHNT|q-70OP<&6;fz8>>lkqJKY?S!_|j$pv*gL>R)HO6@*QD#MH?Hlt; zba7`?b6Yu8fU-dfS+$)3zxdjJ(4Jr3AoUad_@&q8Cq`ddD|0`TL_uT4C*umQpsF2# z>zoh73wPiw$VPN^Mb~bqymV-3kT1zoSX;l0&hF~Y4YJfIE?pZSukYV&1ZB60b@lRx z1rEd-%Hxa~T?J@%2)bzWQ%9ezl95{!Bt{pnN{C!iA0@u?a z2KpkqMfDb5;|#poLkIeje2UuZ%)!?as6bz~Pf>ooH1LYnxf%I-m(ty=3$c&+E{#Xl zj7+-oz%vG)UDtqz);bEGeb#`5){1pC>FviRI1}N`#NwLf!wu#om&vw~P}Ty|-Z+f* z-#7%>*zd2rGxN`sdk+>+n13EC(4|cj?gdN;&+8M}@ZgQZN2>Nz^V^1d{?s=E(?OyC z;ZXPga3sJz|LkH$aONKM(x-LOfmi1e_4>a=;}Z4izeMFyWbejd_@`Bd=y(^+XNnA| z6^Ou^`zKiv`vnpQ!8EFD_LyKUYn!E~sG>_B+C1woGHia^C_7#b(*GV9UKY}O${UB? zY@b$^{gptD&n>YQ*t|?lHosB$=b!O#%-r{G{r}?X_kiNl^14I#|E0;TeBL7fYzwa8 z+&EBZ&bPW}%{0fWDjCdw6Q1D~=WM_;M&LjNS4jZ$djOev~kVJ`IiElkx_e(vw(?%6}Hdg5JdBtBZ8G@ z^HOmnn0r9yme29|Mct(7&FJxe{_q^{Iw1Uv9pvWNn0#(|yuc^HCuF9XE@&TbA4Yhl z{5AU^6X&nsi+SqJse*ZaK;xLtz1RPt728bDGFb2hG0*0+Jd;UUdG^`+cl6IfWY@B_ z{ma!^B`%!~$MY9*0WkuwJdG^?a|JRLS!`3GtBV)8`R! z+j0D|n5b}$cFhfAMRXsH0pRF!LNJZ3;{)tC!Yn&NI+Sr$D0Q7dbW#M@we8nNw_%ui zeOp$MFHX!CLs3?8yyfhGWn?+pqJOFn*+6dagIb1as|#kL>}&eKU+#wJWC><+TSHuS zg{t*l6oTp`F-85-z~y8BX`m2X#?(^oH}KUIHGO@FY!%tz1veyswrD)Aj#xNpKL8d= z1oex0O)dgwbz)TCAnBznY0_e3wrELC4E!t97h>cub2;o7wwjUOLQF#YET|qbS&^9K zEFNVSca})63G~3DOS>*;KH}JX*IZ9bQG#@_gijrUFytzk>qDag6ia)Tex1z_{>rdU zT0vgK!7QVFb;noCP?uDAf3yNXUJ(A-?qxc~y9mF{tMj-TbcVcLsrd6%3Ci{Lp#7BM zX#N{At&ZnvSo*)vOPRhd^h-CcijyUt%IAl$V-<P({vV-!^Y|ZOeoK7x-l<=Tr%`(Fe1{eOZ25|P z=k+fAKjQs9@|o7hbbRxwQ@xQw2kbT%L+*9zn-J3O?OE^;@p?tUcaxn9*L;7g1<*aH#!5e%3Is9xy zt6xkhVasq;1TwUjiehMt#K%yWYIu)wjDn510DIRp1)PkVITPi=CwDCyH(@jM$NxOY z+EAF~Gv#x~E>EGjnW^s2E+c!f=7!31MiL6oWO(~T<~O8e zDXBzn&|YC=pf?}kv2{&1b4)t;ihr$NnZ?LxYs!3TrM;;;wd!FS_h<0S2E?*&jvMc< zwM;g{bM*Go9uH|ypmzN7ti(a$&cm@^5msGxH?Fm!FlPKjL9V7vM2E@PD!WBq7p{)s zXx4g|eUAizLRNcw&PhYk$(cy-_Y)53a~b6*J%a+a$rB#(@u5h5_&OFy%jQrhSV@tYLSj&IuD(Rf8BoCht(v3#Y?k7t7)U5P|( zZ9rSvi^u+oa&A6gR#{k=Cvv3-~UPxp61tH)QPZSolX2`T?1%+=2ghl9A-~qYcke!$k z{8)hX6+ob^PBGK|^4qCn$${&qn~YW2iuks-FDBP#gto!!Jh6Wyj=NQN;>|FVRy93wr{yd#=^Y>UZ=Ve}AVfcS#!k3TD1%fL+o4+W$Rg7$ZUN^%r)r}ndHD_YMtb3bae(i-f zv94A%^7lRWUi)J1&GIowzf%*k4h2PA#;y3U-YxL@L$hB_MHkC2&8U&+QNnBX;_{*3 z-(_EaATZqQE?&?_o7Y>yh%YL#%h0h5!29!{0JBU zC3^u5KDo6r1@5QPSXf4I8Z$35$06z^3C=npLHZ?(k4BPK>k9og1TVEa8q$|~BL$}W zUGoXEB~}%Y0Tz4{Nn z%DT)dK62Yt+PLuKg!hVpHkhqR22vb2A@G{@q**L=$pbL)|I+a8>p{l0%}{4QFy;rJ zBU?p09xxIo?xS0E%my$Q=S7rq*)GKHhr`^mdc#ePbcrrR=aa7T*?!|wVMk_wd{Qy* z6zvI%*jm#7ug(7$F*cN#7~`3a#Vrq{}p`vG(5 zq3NtZKC$3B&6BLXy3k|ELmkCz6C^8;HQhx+n%p7JkKT|#0?+167yEZ&!d=uK)hJr( zcA~q3L8>M9J3w&2Ln3eo_cc(Ywvx4BC?mwvAGEFieo$mb$)QV85y8XT7 z^pC<>+knZhK{VwS+K+I$W}MG9Sy#~0kM%>$&#XUR06Do~@2jEY-u8?gQMjt!_Do{U z)4soCju4Em7Z)3y4+$I^MIq_O>t(35Z2pQnLwPrjp)B^&wVJqlep7#{zJLuGjMp)$%Zeu%Ubj zgF9x;4PSjw!w>}n5YoDln5d2lSM~FIzP7Wj`sCIXyqf$OXVG_{Jt>4vlMuNiT?p<) ze7&24MD2(8;k#A;gMr{Z=5k3clAjatz50?Tr%5Gkm`1oK{jaM0h_K+WM=}{3?(U_H zfWIeehv)qY0sf8aIVY~V5$?wGa*|F__e~PaO)U8Un?ruMo+@rzu4?K?vAiqyOy9|g zl@Tm5kPFW-N$sE2>|kh4oad-$M;ywi?IAlqS&70}7D8R9_oV8DC@PaLK;*3Kw4a_6 zHlHsCbKQdk@}ezr_?}FWKSLKmWF8)K_t`lO%^3&itym?Ny`?OTp_el>M2}5lnQHiV z3^oQ@Xn8?BMnZjDT|U25vowu6MLk{pJ7pPjSxYJ=Wtn?YT148MoO;JPjx5sDiIFAy zeZCluQV1RkM+}$qh`@mOVvGPB(FnDRT)uy9L~nAjve!X{#0c9%re_+LR?UpLkj0%+V8ap8(dT@`WhmJD~E|v(GjWRT-gNm85!ga+)tCjFdWlavF2W zTqLn1vi9>^14q=KN zMJRiiD0g+EF|W;R&rk<0&`)4Sb}#IH##K!EN=|BUgR2@q-)=O&`c&6e{1w!a$mc)d zVpG{jf@>tkydjmyheg6?RT)gGh`sB}s$fXBLG-26KOz_NJ}GfRhS!rq2NdCSACV??D0di|pHy2`(wrw17qiBk z9b>?r9ka&uiF}_Zm>shu%8pf^$Xm^4OB-9oWPV7~H=8SLMF{VvQ3EEBL9a|}&lWw+H1qIBKN z1d8%9O(CVX5e{f{-3r_gawLk(&R9ZBO_{OgBD)F6F-*3mt_rrg%uN1CC$#MgU&Ne@ z$I2q$UDwhKzbBBkm4nqwzO|dcPbU5Eb=4Q%@U5m*duw3tyi_iyqlwvsngEH)rHZ=znY;PQp}`K$%x z*nZR352%+rLXl%hy)}_N@<}l!JE~VWa-_`rMOB+LXHS_|8(d-{e{@DklzhumdW(_* zB0U1gALUVY$Cmc)O-nLJ?HZ&X*8SMlVcz+wx~1ii=Tb&{Oy4^FM;+4@%fQBvE~YJ} zj^h_5jKR<%o=yyTo%u-|gOr(eqsMT1F>a0Qpiw1eA>GI$*X<-A^)B9XI=XH=XGeg% zjoQ_o{DJ4(h|_CS{#=0e39z9IE1fZ@cYGm9uZLt))r<2vnKQZUU92=k%Q@p&CK=x_ z*S16?TC?eWO8tXTeGH#RAz�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 zownΝ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%0D$0*}wD3)xU^j%+4$)4)xieAf52T_~{aJ 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�cVHO%$Xa#a@D9A` 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�eyFN#Ra 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 Rw{918N4cU*O1B= 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 64*1024*1024 { + return nil, fmt.Errorf("帧大小异常: %d", size) + } + data := make([]byte, size) + if _, err := io.ReadFull(c.video, data); err != nil { + return nil, err + } + return &VideoFrame{ + PTS: int64(ptsFlags &^ (flagConfig | flagKeyFrame)), + Config: ptsFlags&flagConfig != 0, + KeyFrame: ptsFlags&flagKeyFrame != 0, + Data: data, + }, nil +} + +// ReadSessionMeta 读取首个 session 头(分辨率),返回 meta +// 带 5s 超时:video socket 需要后续持续读帧,避免永久阻塞 +func (c *Connection) ReadSessionMeta() (*SessionMeta, error) { + c.video.SetReadDeadline(time.Now().Add(5 * time.Second)) + defer c.video.SetReadDeadline(time.Time{}) + // 读帧头,若是 session 返回 + for { + hdr := make([]byte, 12) + if _, err := io.ReadFull(c.video, hdr); err != nil { + return nil, err + } + ptsFlags := binary.BigEndian.Uint64(hdr[0:8]) + if ptsFlags&flagSession != 0 { + w := uint16(binary.BigEndian.Uint32(hdr[4:8])) + h := uint16(binary.BigEndian.Uint32(hdr[8:12])) + c.Width, c.Height = w, h + return &SessionMeta{Width: w, Height: h}, nil + } + // 非 session(config 帧),跳过数据 + size := binary.BigEndian.Uint32(hdr[8:12]) + if _, err := io.CopyN(io.Discard, c.video, int64(size)); err != nil { + return nil, err + } + } +} + +// SendControl 发送一条控制消息 +func (c *Connection) SendControl(msg []byte) error { + _, err := c.control.Write(msg) + return err +} + +// InjectTouch 注入触摸事件 +// action: 0=DOWN 1=UP 2=MOVE;x/y 为设备坐标(相对 Width/Height) +func (c *Connection) InjectTouch(action byte, x, y int32) error { + msg := make([]byte, 0, 31) + msg = append(msg, msgInjectTouch, action) + msg = appendU64(msg, 1) // pointerId + msg = appendI32(msg, x) + msg = appendI32(msg, y) + msg = appendU16(msg, c.Width) + msg = appendU16(msg, c.Height) + msg = appendU16(msg, 0xFFFF) // pressure + msg = appendI32(msg, 1) // actionButton + if action == 1 { // UP: buttons=0 + msg = appendI32(msg, 0) + } else { + msg = appendI32(msg, 1) + } + return c.SendControl(msg) +} + +// InjectKeycode 注入按键 +// action: 0=DOWN 1=UP 2=REPEAT;keycode: Android KeyEvent 码 +func (c *Connection) InjectKeycode(action byte, keycode int32) error { + msg := []byte{msgInjectKeycode, action} + msg = appendI32(msg, keycode) + msg = appendI32(msg, 0) // repeat + msg = appendI32(msg, 0) // metaState + return c.SendControl(msg) +} + +// InjectText 注入文本(UTF-8,u32 长度前缀) +func (c *Connection) InjectText(text string) error { + data := []byte(text) + msg := []byte{msgInjectText} + msg = appendU32(msg, uint32(len(data))) + msg = append(msg, data...) + return c.SendControl(msg) +} + +// ResetVideo 请求关键帧(新客户端加入时) +func (c *Connection) ResetVideo() error { + return c.SendControl([]byte{msgResetVideo}) +} + +// WakeScreen 唤醒屏幕(BACK_OR_SCREEN_ON:屏幕熄灭时点亮,锁屏时模拟返回键解锁)。 +// 设备熄屏后 MediaCodec 停止产帧,唤醒是恢复视频流的前提。 +func (c *Connection) WakeScreen() error { + // msgBackOrScreenOn=4:action(1B)=0(SCREEN_ON) + return c.SendControl([]byte{msgBackOrScreenOn, 0}) +} + +// Close 关闭连接 +func (c *Connection) Close() { + if c.control != nil { + c.control.Close() + } + if c.video != nil { + c.video.Close() + } +} + +// ---------- 字节序辅助 ---------- + +func appendU16(b []byte, v uint16) []byte { + return append(b, byte(v>>8), byte(v)) +} +func appendU32(b []byte, v uint32) []byte { + return append(b, byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) +} +func appendI32(b []byte, v int32) []byte { + return appendU32(b, uint32(v)) +} +func appendU64(b []byte, v uint64) []byte { + return append(b, byte(v>>56), byte(v>>48), byte(v>>40), byte(v>>32), + byte(v>>24), byte(v>>16), byte(v>>8), byte(v)) +} + +func trimNull(b []byte) string { + for i, c := range b { + if c == 0 { + return string(b[:i]) + } + } + return string(b) +} diff --git a/internal/scrcpy/scrcpy_test.go b/internal/scrcpy/scrcpy_test.go new file mode 100644 index 0000000..c20524e --- /dev/null +++ b/internal/scrcpy/scrcpy_test.go @@ -0,0 +1,62 @@ +package scrcpy + +import ( + "context" + "fmt" + "os" + "testing" + "time" +) + +// TestLiveSession 集成测试:需要一台 adb 已连接的真机/设备 +// 运行条件:环境变量 PG_TEST_SERIAL 指向一个 adb serial(USB 直连如 "4f2bdc30" 或 adb over WiFi 如 "127.0.0.1:5556"),设备在线 +// 手动运行:PG_TEST_SERIAL=4f2bdc30 go test ./internal/scrcpy/ -run TestLiveSession -v +func TestLiveSession(t *testing.T) { + serial := os.Getenv("PG_TEST_SERIAL") + if serial == "" { + t.Skip("未设置 PG_TEST_SERIAL(如 PG_TEST_SERIAL=127.0.0.1:5556)") + } + ctx := context.Background() + sess, err := StartSession(ctx, serial, 27184) + if err != nil { + t.Fatalf("StartSession: %v", err) + } + defer sess.Close() + + conn := sess.Connection() + t.Logf("设备: %s, 分辨率: %dx%d", conn.Device, conn.Width, conn.Height) + if conn.Width == 0 { + // 等待 session meta + meta, err := conn.ReadSessionMeta() + if err != nil { + t.Fatalf("ReadSessionMeta: %v", err) + } + t.Logf("session meta: %dx%d", meta.Width, meta.Height) + } + + // 读几帧验证视频流 + conn.video.SetReadDeadline(time.Now().Add(5 * time.Second)) + frames := 0 + for i := 0; i < 5; i++ { + f, err := conn.ReadVideo() + if err != nil { + t.Fatalf("ReadVideo(%d): %v", i, err) + } + frames++ + t.Logf("帧%d: %d 字节, config=%v keyframe=%v", i+1, len(f.Data), f.Config, f.KeyFrame) + if i == 0 && !f.Config { + t.Log("⚠️ 首帧不是 config(SPS/PPS),浏览器可能无法解码") + } + } + + // 注入触摸 + if err := conn.InjectTouch(0, 100, 100); err != nil { // DOWN + t.Fatalf("InjectTouch DOWN: %v", err) + } + time.Sleep(100 * time.Millisecond) + if err := conn.InjectTouch(1, 100, 100); err != nil { // UP + t.Fatalf("InjectTouch UP: %v", err) + } + t.Logf("✅ 触摸注入成功,共收到 %d 帧", frames) + _ = fmt.Sprint() +} diff --git a/internal/scrcpy/session.go b/internal/scrcpy/session.go new file mode 100644 index 0000000..9287695 --- /dev/null +++ b/internal/scrcpy/session.go @@ -0,0 +1,218 @@ +// Session 会话管理:通过 adb 启动 scrcpy-server 并建立连接 +// +// 流程(对应 doc/develop.md): +// 1. push scrcpy-server.jar 到设备 /data/local/tmp/ +// 2. adb forward tcp: localabstract:scrcpy +// 3. app_process 启动 Server(scid=-1 → socket 名 "scrcpy") +// 4. Dial 连接(video → control → metadata) +package scrcpy + +import ( + "bytes" + "context" + _ "embed" + "fmt" + "os" + "os/exec" + "strings" + "sync" + "time" + + "paygateway/internal/logger" +) + +// ServerJar 嵌入的 scrcpy-server.jar(编译时由脚本下载,go:generate) +// +//go:generate bash ../../scripts/download-scrcpy-server.sh +//go:embed scrcpy-server.jar +var ServerJar []byte + +// Session 单个设备的 scrcpy 会话 +type Session struct { + mu sync.Mutex + serial string // adb serial(host:port) + port int // 本机 forward 端口 + conn *Connection + cancel context.CancelFunc + done chan struct{} +} + +// StartSession 启动并连接设备。 +// 关键设计点: +// - 总超时 30s、socketReady 8s(USB 真机起 server 一般 <5s) +// - socketReady 用 /proc/net/unix 字节扫描,不依赖 shell grep(精简 ROM 无该命令) +// - 不重试 Dial:每次 Dial 消耗 server 一次 accept,重试会破坏协议状态机 +// - 任何失败必先 cleanupAdb:清 forward + pkill(避免端口耗光与旧 server 抢 accept) +func StartSession(parent context.Context, serial string, port int) (*Session, error) { + s := &Session{serial: serial, port: port, done: make(chan struct{})} + s.cancel = func() {} + + ctx, cancel := context.WithTimeout(parent, 30*time.Second) + defer cancel() + + t0 := time.Now() + if err := s.ensureServer(ctx); err != nil { + s.cleanupAdb() + return nil, err + } + tEnsure := time.Since(t0) + if !s.waitSocketReady(ctx, 8*time.Second) { + s.cleanupAdb() + return nil, fmt.Errorf("scrcpy server 未在 8s 监听 socket(设备可能锁屏/无 root 权限/app_process 不可用)") + } + tSocket := time.Since(t0) + time.Sleep(100 * time.Millisecond) // dial 前 dwell:让 server 进入 accept 队列 + conn, err := Dial(fmt.Sprintf("127.0.0.1:%d", port)) + if err != nil { + s.cleanupAdb() + return nil, fmt.Errorf("连接 scrcpy: %w", err) + } + logger.Info("scrcpy", "会话建立", "serial", s.serial, "port", s.port, + "ensure_ms", tEnsure.Milliseconds(), "socket_ms", tSocket.Milliseconds(), "dial_ms", time.Since(t0).Milliseconds()) + s.conn = conn + return s, nil +} + +// socketReady 通过 /proc/net/unix 检查 scrcpy abstract socket 是否监听 +// 不走 `grep scrcpy` 是因为精简 ROM 可能没有 grep;改为整文件读 + Go 字节扫描 +func (s *Session) socketReady() bool { + out, err := s.adb("shell", "cat /proc/net/unix") + if err != nil { + return false + } + return strings.Contains(out, "scrcpy") +} + +func (s *Session) waitSocketReady(ctx context.Context, timeout time.Duration) bool { + deadline := time.Now().Add(timeout) + for time.Now().Before(deadline) { + select { + case <-ctx.Done(): + return false + default: + } + if s.socketReady() { + return true + } + time.Sleep(100 * time.Millisecond) + } + return false +} + +func (s *Session) cleanupAdb() { + _, _ = s.adb("forward", "--remove", fmt.Sprintf("tcp:%d", s.port)) + _ = s.adbShell("pkill -f 'com.genymobile.scrcpy.Server' || true") +} + +// ensureServer 确保 jar 已推送 + server 进程运行 + forward 建立 +// ctx 用于约束每条 adb 子命令的超时:USB 拔了 adb 默认会卡死,必须超时 +func (s *Session) ensureServer(ctx context.Context) error { + out, _ := s.adbCtx(ctx, "shell", "test -f /data/local/tmp/scrcpy-server.jar && echo OK || echo MISSING") + if strings.Contains(out, "MISSING") { + if err := s.adbPushJar(ctx); err != nil { + return fmt.Errorf("推送 scrcpy-server: %w", err) + } + } + + _, _ = s.adbCtx(ctx, "forward", "--remove", fmt.Sprintf("tcp:%d", s.port)) + _, _ = s.adbCtx(ctx, "shell", "pkill -f 'com.genymobile.scrcpy.Server' || true") + + if _, err := s.adbCtx(ctx, "forward", fmt.Sprintf("tcp:%d", s.port), "localabstract:scrcpy"); err != nil { + return fmt.Errorf("adb forward: %w", err) + } + + // stay_awake=true:scrcpy 持有 WakeLock 保持设备屏幕唤醒。 + // 不加的话设备熄屏 → Android 不再向 MediaCodec 提供 Surface 帧 → 视频流静默停止, + // 而 control socket 仍通(鼠标注入"看似有效"但画面黑)——即控制台再次打开黑屏 bug。 + startCmd := "nohup sh -c 'CLASSPATH=/data/local/tmp/scrcpy-server.jar app_process / com.genymobile.scrcpy.Server 4.1 " + + "scid=-1 video=true audio=false control=true tunnel_forward=true cleanup=false stay_awake=true max_size=1080' > /dev/null 2>&1 &" + if _, err := s.adbCtx(ctx, "shell", startCmd); err != nil { + return fmt.Errorf("启动 scrcpy-server: %w", err) + } + return nil +} + +// adbPushJar 推送 jar 到设备 +// 每次 5s 超时;首次失败重试一次(USB 真机偶发 adb busy,重试一次基本必过) +func (s *Session) adbPushJar(ctx context.Context) error { + if len(ServerJar) == 0 { + return fmt.Errorf("scrcpy-server.jar 未嵌入(请先运行 go generate)") + } + tmp, err := os.CreateTemp("", "scrcpy-server-*.jar") + if err != nil { + return fmt.Errorf("创建临时文件: %w", err) + } + tmpPath := tmp.Name() + defer os.Remove(tmpPath) + if _, err := tmp.Write(ServerJar); err != nil { + tmp.Close() + return err + } + tmp.Close() + + doPush := func() error { + cctx, cancel := context.WithTimeout(ctx, 5*time.Second) + defer cancel() + cmd := exec.CommandContext(cctx, "adb", "-s", s.serial, "push", tmpPath, "/data/local/tmp/scrcpy-server.jar") + var errb bytes.Buffer + cmd.Stderr = &errb + if err := cmd.Run(); err != nil { + return fmt.Errorf("adb push: %w (%s)", err, strings.TrimSpace(errb.String())) + } + return nil + } + if err := doPush(); err != nil { + time.Sleep(200 * time.Millisecond) + return doPush() + } + return nil +} + +// adb 带固定 8s 超时执行;USB 拔了 adb 默认无超时会卡死整个 session +func (s *Session) adb(args ...string) (string, error) { + return s.adbCtx(context.Background(), args...) +} + +func (s *Session) adbCtx(ctx context.Context, args ...string) (string, error) { + cctx, cancel := context.WithTimeout(ctx, 8*time.Second) + defer cancel() + full := append([]string{"-s", s.serial}, args...) + cmd := exec.CommandContext(cctx, "adb", full...) + var out, errb bytes.Buffer + cmd.Stdout = &out + cmd.Stderr = &errb + if err := cmd.Run(); err != nil { + return "", fmt.Errorf("adb %s: %w (%s)", strings.Join(args, " "), err, strings.TrimSpace(errb.String())) + } + return out.String(), nil +} + +// Connection 返回活动连接 +func (s *Session) Connection() *Connection { return s.conn } + +func (s *Session) adbShell(cmd string) error { + _, err := s.adb("shell", cmd) + return err +} + +// Close 关闭会话(断开连接 + 停止 server) +// 幂等:deviceSession.videoLoop 退出时也可能间接调用,避免 close(done) 双触发 panic +func (s *Session) Close() { + s.mu.Lock() + defer s.mu.Unlock() + if s.conn != nil { + s.conn.Close() + s.conn = nil + } + if s.cancel != nil { + s.cancel() + s.cancel = nil + } + s.adb("forward", "--remove", fmt.Sprintf("tcp:%d", s.port)) + s.adbShell("pkill -f 'com.genymobile.scrcpy.Server' || true") + select { + case <-s.done: + default: + close(s.done) + } +} diff --git a/internal/store/accountdb.go b/internal/store/accountdb.go new file mode 100755 index 0000000..96ea2ed --- /dev/null +++ b/internal/store/accountdb.go @@ -0,0 +1,303 @@ +// Package store 账户数据库:独立 accounts.db,仅管理后台 API 可读写 +// +// 安全设计: +// - 独立 SQLite 文件,权限 0600 +// - 敏感字段(private_key / public_key / api_key)以 AES-256-GCM 密文存储 +// - AccountStore 接口不暴露给 order/channel 等模块,仅在 admin 包内部使用 +// - 其他模块通过 AccountPool(内存缓存)获取解密后的账户数据 +package store + +import ( + "context" + "database/sql" + "fmt" + "os" + "time" + + _ "modernc.org/sqlite" + + "paygateway/internal/account" +) + +// AccountStore 账户存储接口(仅限 Admin API 使用) +type AccountStore interface { + ListAccounts(ctx context.Context, channel string) ([]*account.AccountEncrypted, error) + GetAccount(ctx context.Context, id int64) (*account.AccountEncrypted, error) + CreateAccount(ctx context.Context, a *account.AccountEncrypted) (int64, error) + UpdateAccount(ctx context.Context, a *account.AccountEncrypted) error + DeleteAccount(ctx context.Context, id int64) error + SetAccountStatus(ctx context.Context, id int64, status string) error + ResetDailyUsed(ctx context.Context) error + InsertAccountLog(ctx context.Context, log *account.AccountLog) error + ListAccountLogs(ctx context.Context, accountID int64, limit int) ([]*account.AccountLog, error) + Close() error +} + +// AccountDB 独立账户数据库:仅存 pay_accounts + account_logs +// 文件权限 0600,WAL 模式,仅 Admin API 访问 +type AccountDB struct { + db *sql.DB +} + +// NewAccountDB 打开/创建独立账户数据库 +func NewAccountDB(path string) (*AccountDB, error) { + // 先创建空文件以确保可以设置权限 + fd, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0600) + if err != nil { + return nil, fmt.Errorf("创建账户数据库文件失败: %w", err) + } + fd.Close() + + // 强制设置权限为 0600(只有文件所有者可读写) + if err := os.Chmod(path, 0600); err != nil { + return nil, fmt.Errorf("设置账户数据库权限失败: %w", err) + } + + // 打开连接:WAL 模式防崩溃 + db, err := sql.Open("sqlite", path+"?_journal_mode=WAL&_busy_timeout=10000&_synchronous=FULL") + if err != nil { + return nil, fmt.Errorf("打开账户数据库失败: %w", err) + } + + // 连接池:单连接更安全 + db.SetMaxOpenConns(1) + db.SetMaxIdleConns(1) + + a := &AccountDB{db: db} + if err := a.migrate(); err != nil { + db.Close() + return nil, err + } + return a, nil +} + +// migrate 创建账户表 + 操作审计日志表 +func (a *AccountDB) migrate() error { + // 迁移旧库:daily_used / qr_content_enc / frozen_reason 列(幂等,已存在则报错忽略) + _, _ = a.db.Exec(`ALTER TABLE pay_accounts ADD COLUMN daily_used INTEGER NOT NULL DEFAULT 0`) + _, _ = a.db.Exec(`ALTER TABLE pay_accounts ADD COLUMN qr_content_enc TEXT NOT NULL DEFAULT ''`) + _, _ = a.db.Exec(`ALTER TABLE pay_accounts ADD COLUMN frozen_reason TEXT NOT NULL DEFAULT ''`) + stmts := []string{ + `CREATE TABLE IF NOT EXISTS pay_accounts ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + channel TEXT NOT NULL, + name TEXT NOT NULL, + app_id TEXT NOT NULL DEFAULT '', + mch_id TEXT NOT NULL DEFAULT '', + private_key_enc TEXT NOT NULL DEFAULT '', + public_key_enc TEXT NOT NULL DEFAULT '', + api_key_enc TEXT NOT NULL DEFAULT '', + qr_content_enc TEXT NOT NULL DEFAULT '', + weight INTEGER NOT NULL DEFAULT 50, + priority INTEGER NOT NULL DEFAULT 0, + status TEXT NOT NULL DEFAULT 'normal', + frozen_reason TEXT NOT NULL DEFAULT '', + auto_ban INTEGER NOT NULL DEFAULT 1, + daily_limit INTEGER NOT NULL DEFAULT 0, + daily_used INTEGER NOT NULL DEFAULT 0, + group_name TEXT NOT NULL DEFAULT 'default', + is_prod INTEGER NOT NULL DEFAULT 0, + remark TEXT NOT NULL DEFAULT '', + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL + )`, + `CREATE INDEX IF NOT EXISTS idx_accounts_channel ON pay_accounts(channel)`, + `CREATE INDEX IF NOT EXISTS idx_accounts_status ON pay_accounts(status)`, + + // 账户操作审计日志 + `CREATE TABLE IF NOT EXISTS account_logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + account_id INTEGER NOT NULL, + action TEXT NOT NULL, + detail TEXT NOT NULL DEFAULT '', + operator TEXT NOT NULL DEFAULT '', + created_at TEXT NOT NULL + )`, + `CREATE INDEX IF NOT EXISTS idx_account_logs_account ON account_logs(account_id)`, + + // WAL 模式 + `PRAGMA journal_mode=WAL`, + `PRAGMA synchronous=FULL`, + } + for _, stmt := range stmts { + if _, err := a.db.Exec(stmt); err != nil { + return fmt.Errorf("账户数据库迁移失败: %w", err) + } + } + return nil +} + +// ---------- 账户 CRUD ---------- + +// ListAccounts 列出账户(按通道筛选,空=全部) +func (a *AccountDB) ListAccounts(ctx context.Context, channel string) ([]*account.AccountEncrypted, error) { + var rows *sql.Rows + var err error + if channel == "" { + rows, err = a.db.QueryContext(ctx, + `SELECT id, channel, name, app_id, mch_id, private_key_enc, public_key_enc, api_key_enc, qr_content_enc, + weight, priority, status, frozen_reason, auto_ban, daily_limit, daily_used, group_name, is_prod, remark, created_at, updated_at + FROM pay_accounts ORDER BY channel, priority DESC, weight DESC`) + } else { + rows, err = a.db.QueryContext(ctx, + `SELECT id, channel, name, app_id, mch_id, private_key_enc, public_key_enc, api_key_enc, qr_content_enc, + weight, priority, status, frozen_reason, auto_ban, daily_limit, daily_used, group_name, is_prod, remark, created_at, updated_at + FROM pay_accounts WHERE channel=? ORDER BY priority DESC, weight DESC`, channel) + } + if err != nil { + return nil, err + } + defer rows.Close() + + var out []*account.AccountEncrypted + for rows.Next() { + e, err := scanAccountEncrypted(rows) + if err != nil { + return nil, err + } + out = append(out, e) + } + return out, rows.Err() +} + +// GetAccount 按 ID 获取账户 +func (a *AccountDB) GetAccount(ctx context.Context, id int64) (*account.AccountEncrypted, error) { + row := a.db.QueryRowContext(ctx, + `SELECT id, channel, name, app_id, mch_id, private_key_enc, public_key_enc, api_key_enc, qr_content_enc, + weight, priority, status, frozen_reason, auto_ban, daily_limit, daily_used, group_name, is_prod, remark, created_at, updated_at + FROM pay_accounts WHERE id=?`, id) + return scanAccountEncrypted(row) +} + +// CreateAccount 创建账户,返回新 ID +func (a *AccountDB) CreateAccount(ctx context.Context, acct *account.AccountEncrypted) (int64, error) { + now := time.Now() + res, err := a.db.ExecContext(ctx, + `INSERT INTO pay_accounts (channel, name, app_id, mch_id, private_key_enc, public_key_enc, api_key_enc, qr_content_enc, + weight, priority, status, frozen_reason, auto_ban, daily_limit, daily_used, group_name, is_prod, remark, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + acct.Channel, acct.Name, acct.AppID, acct.MchID, + acct.PrivateKeyEnc, acct.PublicKeyEnc, acct.ApiKeyEnc, acct.QRContentEnc, + acct.Weight, acct.Priority, acct.Status, acct.FrozenReason, boolToInt(acct.AutoBan), + acct.DailyLimit, acct.DailyUsed, acct.Group, boolToInt(acct.IsProd), acct.Remark, + now.Format(time.RFC3339Nano), now.Format(time.RFC3339Nano)) + if err != nil { + return 0, fmt.Errorf("创建账户失败: %w", err) + } + return res.LastInsertId() +} + +// UpdateAccount 更新账户(全部字段覆盖) +func (a *AccountDB) UpdateAccount(ctx context.Context, acct *account.AccountEncrypted) error { + _, err := a.db.ExecContext(ctx, + `UPDATE pay_accounts SET channel=?, name=?, app_id=?, mch_id=?, + private_key_enc=?, public_key_enc=?, api_key_enc=?, qr_content_enc=?, + weight=?, priority=?, status=?, frozen_reason=?, auto_ban=?, daily_limit=?, daily_used=?, group_name=?, is_prod=?, remark=?, updated_at=? + WHERE id=?`, + acct.Channel, acct.Name, acct.AppID, acct.MchID, + acct.PrivateKeyEnc, acct.PublicKeyEnc, acct.ApiKeyEnc, acct.QRContentEnc, + acct.Weight, acct.Priority, acct.Status, acct.FrozenReason, boolToInt(acct.AutoBan), + acct.DailyLimit, acct.DailyUsed, acct.Group, boolToInt(acct.IsProd), acct.Remark, + time.Now().Format(time.RFC3339Nano), acct.ID) + return err +} + +// DeleteAccount 删除账户 +func (a *AccountDB) DeleteAccount(ctx context.Context, id int64) error { + _, err := a.db.ExecContext(ctx, `DELETE FROM pay_accounts WHERE id=?`, id) + return err +} + +// SetAccountStatus 设置账户状态(启停/冻结/解冻;解冻/启停时清空 frozen_reason) +func (a *AccountDB) SetAccountStatus(ctx context.Context, id int64, status string) error { + _, err := a.db.ExecContext(ctx, + `UPDATE pay_accounts SET status=?, frozen_reason=CASE WHEN ?='frozen' THEN frozen_reason ELSE '' END, updated_at=? WHERE id=?`, + status, status, time.Now().Format(time.RFC3339Nano), id) + return err +} + +// SetAccountFrozen 冻结账户并记录原因(仅人工冻结路径使用;解冻统一走 SetAccountStatus) +func (a *AccountDB) SetAccountFrozen(ctx context.Context, id int64, reason string) error { + _, err := a.db.ExecContext(ctx, + `UPDATE pay_accounts SET status='frozen', frozen_reason=?, updated_at=? WHERE id=?`, + reason, time.Now().Format(time.RFC3339Nano), id) + return err +} + +// ResetDailyUsed 重置所有账户的日已用额度(每日 00:00 调用) +func (a *AccountDB) ResetDailyUsed(ctx context.Context) error { + _, err := a.db.ExecContext(ctx, + `UPDATE pay_accounts SET daily_used=0, updated_at=?`, + time.Now().Format(time.RFC3339Nano)) + return err +} + +// ---------- 账户操作审计日志 ---------- + +// InsertAccountLog 写入操作审计日志 +func (a *AccountDB) InsertAccountLog(ctx context.Context, log *account.AccountLog) error { + _, err := a.db.ExecContext(ctx, + `INSERT INTO account_logs (account_id, action, detail, operator, created_at) + VALUES (?, ?, ?, ?, ?)`, + log.AccountID, log.Action, log.Detail, log.Operator, time.Now().Format(time.RFC3339Nano)) + return err +} + +// ListAccountLogs 列出账户操作日志 +func (a *AccountDB) ListAccountLogs(ctx context.Context, accountID int64, limit int) ([]*account.AccountLog, error) { + rows, err := a.db.QueryContext(ctx, + `SELECT id, account_id, action, detail, operator, created_at + FROM account_logs WHERE account_id=? ORDER BY id DESC LIMIT ?`, accountID, limit) + if err != nil { + return nil, err + } + defer rows.Close() + + out := make([]*account.AccountLog, 0) + for rows.Next() { + var l account.AccountLog + var created string + if err := rows.Scan(&l.ID, &l.AccountID, &l.Action, &l.Detail, &l.Operator, &created); err != nil { + return nil, err + } + l.CreatedAt, _ = time.Parse(time.RFC3339Nano, created) + out = append(out, &l) + } + return out, rows.Err() +} + +// Close 关闭账户数据库 +func (a *AccountDB) Close() error { + return a.db.Close() +} + +// ---------- 辅助函数 ---------- + +// scanAccountEncrypted 扫描一行到 AccountEncrypted +func scanAccountEncrypted(s rowScanner) (*account.AccountEncrypted, error) { + var e account.AccountEncrypted + var autoBan, isProd int + var createdAt, updatedAt string + err := s.Scan(&e.ID, &e.Channel, &e.Name, &e.AppID, &e.MchID, + &e.PrivateKeyEnc, &e.PublicKeyEnc, &e.ApiKeyEnc, &e.QRContentEnc, + &e.Weight, &e.Priority, &e.Status, &e.FrozenReason, &autoBan, + &e.DailyLimit, &e.DailyUsed, &e.Group, &isProd, &e.Remark, &createdAt, &updatedAt) + if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, err + } + e.AutoBan = autoBan != 0 + e.IsProd = isProd != 0 + e.CreatedAt, _ = time.Parse(time.RFC3339Nano, createdAt) + e.UpdatedAt, _ = time.Parse(time.RFC3339Nano, updatedAt) + return &e, nil +} + +func boolToInt(b bool) int { + if b { + return 1 + } + return 0 +} diff --git a/internal/store/ledgerdb.go b/internal/store/ledgerdb.go new file mode 100755 index 0000000..fa7c10b --- /dev/null +++ b/internal/store/ledgerdb.go @@ -0,0 +1,237 @@ +package store + +import ( + "context" + "database/sql" + "fmt" + "os" + "time" + + _ "modernc.org/sqlite" +) + +// LedgerStore 账本存储接口(只增不改,独立数据库) +type LedgerStore interface { + AppendLedger(ctx context.Context, prevHash, payloadHash, entryHash, signature string, eventID int64) error + GetLastLedgerEntry(ctx context.Context) (*LedgerEntry, error) + CountLedger(ctx context.Context) (int64, error) + GetLedgerEntry(ctx context.Context, id int64) (*LedgerEntry, error) + ListLedger(ctx context.Context, limit int) ([]*LedgerEntry, error) + // HasLedgerEvent 按 eventID(订单ID)查询账本是否已有记录(崩溃恢复去重用) + HasLedgerEvent(ctx context.Context, eventID int64) (bool, error) + CreateSnapshot(ctx context.Context, date string, lastHash, signature string) error + GetLatestSnapshot(ctx context.Context) (*Snapshot, error) + ListSnapshots(ctx context.Context, limit int) ([]*Snapshot, error) + Close() error +} + +// LedgerDB 独立账本数据库:只存 audit_ledger + daily_snapshots +// 文件权限 0600,WAL 模式,触发器防 UPDATE/DELETE +type LedgerDB struct { + db *sql.DB +} + +// NewLedgerDB 打开/创建独立账本数据库 +func NewLedgerDB(path string) (*LedgerDB, error) { + // 先创建空文件以确保我们可以设置权限 + fd, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0600) + if err != nil { + return nil, fmt.Errorf("创建账本数据库文件失败: %w", err) + } + fd.Close() + + // 强制设置权限为 0600(只有文件所有者可读写) + if err := os.Chmod(path, 0600); err != nil { + return nil, fmt.Errorf("设置账本数据库权限失败: %w", err) + } + + // 打开连接:WAL 模式防崩溃 + 严格外键 + db, err := sql.Open("sqlite", path+"?_journal_mode=WAL&_busy_timeout=10000&_synchronous=FULL") + if err != nil { + return nil, fmt.Errorf("打开账本数据库失败: %w", err) + } + + // 连接池:账本写入频率低,单连接更安全 + db.SetMaxOpenConns(1) + db.SetMaxIdleConns(1) + + l := &LedgerDB{db: db} + if err := l.migrate(); err != nil { + db.Close() + return nil, err + } + return l, nil +} + +// migrate 创建账本表 + 触发器(只增不改) +func (l *LedgerDB) migrate() error { + stmts := []string{ + `CREATE TABLE IF NOT EXISTS audit_ledger ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + prev_hash TEXT NOT NULL, + payload_hash TEXT NOT NULL, + entry_hash TEXT NOT NULL, + signature TEXT NOT NULL, + event_id INTEGER, + created_at TEXT NOT NULL + )`, + // 禁止 UPDATE — 任何修改账本条目的尝试都被拒绝 + `CREATE TRIGGER IF NOT EXISTS trg_ledger_no_update BEFORE UPDATE ON audit_ledger + BEGIN SELECT RAISE(ABORT, 'audit_ledger is append-only: UPDATE denied'); END`, + // 禁止 DELETE — 任何删除账本条目的尝试都被拒绝 + `CREATE TRIGGER IF NOT EXISTS trg_ledger_no_delete BEFORE DELETE ON audit_ledger + BEGIN SELECT RAISE(ABORT, 'audit_ledger is append-only: DELETE denied'); END`, + `CREATE TABLE IF NOT EXISTS daily_snapshots ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + snapshot_date TEXT NOT NULL UNIQUE, + last_hash TEXT NOT NULL, + signature TEXT NOT NULL, + created_at TEXT NOT NULL + )`, + // 快照同样防篡改 + `CREATE TRIGGER IF NOT EXISTS trg_snapshot_no_update BEFORE UPDATE ON daily_snapshots + BEGIN SELECT RAISE(ABORT, 'daily_snapshots is append-only: UPDATE denied'); END`, + `CREATE TRIGGER IF NOT EXISTS trg_snapshot_no_delete BEFORE DELETE ON daily_snapshots + BEGIN SELECT RAISE(ABORT, 'daily_snapshots is append-only: DELETE denied'); END`, + // 启用 WAL 模式(持久化) + `PRAGMA journal_mode=WAL`, + // 同步模式 FULL — 每次写入都等待数据落盘 + `PRAGMA synchronous=FULL`, + } + for _, stmt := range stmts { + if _, err := l.db.Exec(stmt); err != nil { + return fmt.Errorf("账本数据库迁移失败: %w", err) + } + } + return nil +} + +// ---------- 账本操作(只增不改) ---------- + +func (l *LedgerDB) AppendLedger(ctx context.Context, prevHash, payloadHash, entryHash, signature string, eventID int64) error { + _, err := l.db.ExecContext(ctx, + `INSERT INTO audit_ledger (prev_hash, payload_hash, entry_hash, signature, event_id, created_at) + VALUES (?, ?, ?, ?, ?, ?)`, + prevHash, payloadHash, entryHash, signature, eventID, nowStr()) + return err +} + +func (l *LedgerDB) GetLastLedgerEntry(ctx context.Context) (*LedgerEntry, error) { + row := l.db.QueryRowContext(ctx, + `SELECT id, prev_hash, payload_hash, entry_hash, signature, event_id, created_at + FROM audit_ledger ORDER BY id DESC LIMIT 1`) + var e LedgerEntry + var created string + if err := row.Scan(&e.ID, &e.PrevHash, &e.PayloadHash, &e.EntryHash, &e.Signature, &e.EventID, &created); err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, err + } + e.CreatedAt, _ = time.Parse(time.RFC3339Nano, created) + return &e, nil +} + +func (l *LedgerDB) CountLedger(ctx context.Context) (int64, error) { + var n int64 + if err := l.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM audit_ledger`).Scan(&n); err != nil { + return 0, err + } + return n, nil +} + +func (l *LedgerDB) GetLedgerEntry(ctx context.Context, id int64) (*LedgerEntry, error) { + row := l.db.QueryRowContext(ctx, + `SELECT id, prev_hash, payload_hash, entry_hash, signature, event_id, created_at + FROM audit_ledger WHERE id=?`, id) + var e LedgerEntry + var created string + if err := row.Scan(&e.ID, &e.PrevHash, &e.PayloadHash, &e.EntryHash, &e.Signature, &e.EventID, &created); err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, err + } + e.CreatedAt, _ = time.Parse(time.RFC3339Nano, created) + return &e, nil +} + +// HasLedgerEvent 按 eventID(订单ID)查询账本是否已有记录(崩溃恢复去重用) +func (l *LedgerDB) HasLedgerEvent(ctx context.Context, eventID int64) (bool, error) { + var n int64 + if err := l.db.QueryRowContext(ctx, + `SELECT COUNT(*) FROM audit_ledger WHERE event_id=?`, eventID).Scan(&n); err != nil { + return false, err + } + return n > 0, nil +} + +func (l *LedgerDB) ListLedger(ctx context.Context, limit int) ([]*LedgerEntry, error) { + rows, err := l.db.QueryContext(ctx, + `SELECT id, prev_hash, payload_hash, entry_hash, signature, event_id, created_at + FROM audit_ledger ORDER BY id DESC LIMIT ?`, limit) + if err != nil { + return nil, err + } + defer rows.Close() + out := []*LedgerEntry{} + for rows.Next() { + var e LedgerEntry + var created string + if err := rows.Scan(&e.ID, &e.PrevHash, &e.PayloadHash, &e.EntryHash, &e.Signature, &e.EventID, &created); err != nil { + return nil, err + } + e.CreatedAt, _ = time.Parse(time.RFC3339Nano, created) + out = append(out, &e) + } + return out, rows.Err() +} + +// ---------- 快照操作 ---------- + +func (l *LedgerDB) ListSnapshots(ctx context.Context, limit int) ([]*Snapshot, error) { + rows, err := l.db.QueryContext(ctx, + `SELECT id, snapshot_date, last_hash, signature, created_at FROM daily_snapshots ORDER BY id DESC LIMIT ?`, limit) + if err != nil { + return nil, err + } + defer rows.Close() + out := []*Snapshot{} + for rows.Next() { + var sn Snapshot + var created string + if err := rows.Scan(&sn.ID, &sn.SnapshotDate, &sn.LastHash, &sn.Signature, &created); err != nil { + return nil, err + } + sn.CreatedAt, _ = time.Parse(time.RFC3339Nano, created) + out = append(out, &sn) + } + return out, rows.Err() +} + +func (l *LedgerDB) CreateSnapshot(ctx context.Context, date string, lastHash, signature string) error { + _, err := l.db.ExecContext(ctx, + `INSERT INTO daily_snapshots (snapshot_date, last_hash, signature, created_at) VALUES (?, ?, ?, ?)`, + date, lastHash, signature, nowStr()) + return err +} + +func (l *LedgerDB) GetLatestSnapshot(ctx context.Context) (*Snapshot, error) { + row := l.db.QueryRowContext(ctx, + `SELECT id, snapshot_date, last_hash, signature, created_at FROM daily_snapshots ORDER BY id DESC LIMIT 1`) + var sn Snapshot + var created string + if err := row.Scan(&sn.ID, &sn.SnapshotDate, &sn.LastHash, &sn.Signature, &created); err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, err + } + sn.CreatedAt, _ = time.Parse(time.RFC3339Nano, created) + return &sn, nil +} + +// Close 关闭账本数据库 +func (l *LedgerDB) Close() error { + return l.db.Close() +} diff --git a/internal/store/logdb.go b/internal/store/logdb.go new file mode 100644 index 0000000..f622527 --- /dev/null +++ b/internal/store/logdb.go @@ -0,0 +1,133 @@ +package store + +import ( + "context" + "database/sql" + "fmt" + "os" + "time" + + _ "modernc.org/sqlite" +) + +// LogDB 独立日志数据库:只存 app_logs,与订单主库分离,支持长期保存。 +// 文件权限 0600,WAL 模式。日志只增不删(由外部策略清理), +// 避免日志膨胀拖累订单主库的读写性能,且便于独立备份/迁移/清理。 +type LogDB struct { + db *sql.DB +} + +// NewLogDB 打开/创建独立日志数据库 +func NewLogDB(path string) (*LogDB, error) { + fd, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0600) + if err != nil { + return nil, fmt.Errorf("创建日志数据库文件失败: %w", err) + } + fd.Close() + if err := os.Chmod(path, 0600); err != nil { + return nil, fmt.Errorf("设置日志数据库权限失败: %w", err) + } + db, err := sql.Open("sqlite", path+"?_journal_mode=WAL&_busy_timeout=10000&_synchronous=NORMAL") + if err != nil { + return nil, fmt.Errorf("打开日志数据库失败: %w", err) + } + // 日志写入并发低,单连接足够且避免锁竞争 + db.SetMaxOpenConns(1) + db.SetMaxIdleConns(1) + + l := &LogDB{db: db} + if err := l.migrate(); err != nil { + db.Close() + return nil, err + } + return l, nil +} + +// migrate 创建日志表 + 索引(与主库 app_logs 同构) +func (l *LogDB) migrate() error { + stmts := []string{ + `CREATE TABLE IF NOT EXISTS app_logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + level TEXT NOT NULL, + scope TEXT, + message TEXT NOT NULL, + trace_id TEXT, + fields TEXT, + created_at TEXT NOT NULL + )`, + `CREATE INDEX IF NOT EXISTS idx_logs_level_time ON app_logs(level, created_at)`, + `CREATE INDEX IF NOT EXISTS idx_logs_scope_time ON app_logs(scope, created_at)`, + `CREATE INDEX IF NOT EXISTS idx_logs_trace ON app_logs(trace_id)`, + `PRAGMA journal_mode=WAL`, + } + for _, stmt := range stmts { + if _, err := l.db.Exec(stmt); err != nil { + return fmt.Errorf("日志数据库迁移失败: %w", err) + } + } + return nil +} + +// InsertLog 写入一条日志(实现 logger.DBWriter) +func (l *LogDB) InsertLog(ctx context.Context, level, scope, message, traceID, fields string) error { + _, err := l.db.ExecContext(ctx, + `INSERT INTO app_logs (level, scope, message, trace_id, fields, created_at) VALUES (?, ?, ?, ?, ?, ?)`, + level, scope, message, traceID, fields, nowStr()) + return err +} + +// QueryLogs 分页查询日志 +func (l *LogDB) QueryLogs(ctx context.Context, level, scope string, limit, offset int) ([]*LogRecord, error) { + q := `SELECT id, level, scope, message, trace_id, fields, created_at FROM app_logs WHERE 1=1` + args := []any{} + if level != "" { + q += " AND LOWER(level)=LOWER(?)" + args = append(args, level) + } + if scope != "" { + q += " AND scope=?" + args = append(args, scope) + } + q += " ORDER BY id DESC LIMIT ? OFFSET ?" + args = append(args, limit, offset) + rows, err := l.db.QueryContext(ctx, q, args...) + if err != nil { + return nil, err + } + defer rows.Close() + out := []*LogRecord{} + for rows.Next() { + var r LogRecord + var created string + if err := rows.Scan(&r.ID, &r.Level, &r.Scope, &r.Message, &r.TraceID, &r.Fields, &created); err != nil { + return nil, err + } + r.CreatedAt, _ = time.Parse(time.RFC3339Nano, created) + out = append(out, &r) + } + return out, rows.Err() +} + +// CountLogs 按条件统计日志数 +func (l *LogDB) CountLogs(ctx context.Context, level, scope string) (int64, error) { + q := `SELECT COUNT(*) FROM app_logs WHERE 1=1` + args := []any{} + if level != "" { + q += " AND LOWER(level)=LOWER(?)" + args = append(args, level) + } + if scope != "" { + q += " AND scope=?" + args = append(args, scope) + } + var n int64 + if err := l.db.QueryRowContext(ctx, q, args...).Scan(&n); err != nil { + return 0, err + } + return n, nil +} + +// Close 关闭日志数据库 +func (l *LogDB) Close() error { + return l.db.Close() +} diff --git a/internal/store/notify_tasks_test.go b/internal/store/notify_tasks_test.go new file mode 100644 index 0000000..367ad72 --- /dev/null +++ b/internal/store/notify_tasks_test.go @@ -0,0 +1,69 @@ +package store + +import ( + "context" + "path/filepath" + "testing" + "time" +) + +func newNotifyTestStore(t *testing.T) *SQLiteStore { + t.Helper() + st, err := NewSQLite(filepath.Join(t.TempDir(), "notify.db")) + if err != nil { + t.Fatalf("创建 SQLite 失败: %v", err) + } + return st +} + +// TestNotifyTask_CRUDPlusPayload 覆盖死信管理新增方法:GetNotifyTask / UpdateNotifyTaskPayload / DeleteNotifyTask。 +func TestNotifyTask_CRUDPlusPayload(t *testing.T) { + st := newNotifyTestStore(t) + ctx := context.Background() + + // CreateNotifyTask 需要一个真实 order_id(外键/展示用),这里先用 0 也不影响 notify_tasks 表。 + id, err := st.CreateNotifyTask(ctx, 123, `{"notify_url":"http://old.example/cb","out_trade_no":"T1"}`) + if err != nil { + t.Fatalf("CreateNotifyTask err: %v", err) + } + + task, err := st.GetNotifyTask(ctx, id) + if err != nil || task == nil { + t.Fatalf("GetNotifyTask = %v, %v; 期望非 nil", task, err) + } + if task.OrderID != 123 || task.Status != "pending" { + t.Fatalf("GetNotifyTask 字段不符: order_id=%d status=%s", task.OrderID, task.Status) + } + + // UpdateNotifyTaskPayload:换成最新回调地址 + newPayload := `{"notify_url":"http://new.example/cb","out_trade_no":"T1"}` + if err := st.UpdateNotifyTaskPayload(ctx, id, newPayload); err != nil { + t.Fatalf("UpdateNotifyTaskPayload err: %v", err) + } + got, _ := st.GetNotifyTask(ctx, id) + if got.Payload != newPayload { + t.Fatalf("payload 未更新: %s", got.Payload) + } + + // 重置为 pending(死信重发路径) + if err := st.UpdateNotifyTask(ctx, id, "pending", 0, time.Now()); err != nil { + t.Fatalf("UpdateNotifyTask err: %v", err) + } + got, _ = st.GetNotifyTask(ctx, id) + if got.Status != "pending" || got.RetryCount != 0 { + t.Fatalf("重置 pending 失败: status=%s retry=%d", got.Status, got.RetryCount) + } + + // DeleteNotifyTask:删除后 GetNotifyTask 返回 nil + if err := st.DeleteNotifyTask(ctx, id); err != nil { + t.Fatalf("DeleteNotifyTask err: %v", err) + } + if gone, _ := st.GetNotifyTask(ctx, id); gone != nil { + t.Fatalf("删除后仍能查到任务: %+v", gone) + } + + // GetNotifyTask 不存在的 id 返回 nil(非错误) + if miss, _ := st.GetNotifyTask(ctx, 999999); miss != nil { + t.Fatalf("不存在的 id 应返回 nil: %+v", miss) + } +} diff --git a/internal/store/personalstore.go b/internal/store/personalstore.go new file mode 100755 index 0000000..2289000 --- /dev/null +++ b/internal/store/personalstore.go @@ -0,0 +1,231 @@ +// Package store 个人免签通道存储:独立 personal.db +// +// 表: +// - personal_devices 监听端设备(含加密通信密钥) +// - personal_pushes 收款事件流水(审计用) +// - unmatched_payments 无订单收款(人工处理) +// +// 安全设计:独立 SQLite 文件 0600,WAL 模式,设备密钥 AES-256-GCM 加密存储。 +package store + +import ( + "context" + "database/sql" + "fmt" + "os" + + _ "modernc.org/sqlite" + + "paygateway/internal/personal" +) + +// PersonalStore 个人免签通道存储接口(仅 admin + personal 通道使用) +type PersonalStore interface { + // 设备 + ListDevices(ctx context.Context) ([]*personal.DeviceEncrypted, error) + GetDevice(ctx context.Context, deviceID string) (*personal.DeviceEncrypted, error) + GetDeviceByID(ctx context.Context, id int64) (*personal.DeviceEncrypted, error) + CreateDevice(ctx context.Context, d *personal.DeviceEncrypted) (int64, error) + UpdateDevice(ctx context.Context, d *personal.DeviceEncrypted) error + DeleteDevice(ctx context.Context, id int64) error + SetDeviceStatus(ctx context.Context, id int64, status string) error + SetDevicePushTime(ctx context.Context, deviceID string) error + SetDeviceAdb(ctx context.Context, id int64, host string, port int, wifiHost string, wifiPort int) error + SetDeviceSetupProgress(ctx context.Context, id int64, progress string) error + SetDeviceControlPlane(ctx context.Context, deviceID, tsIP string, adbListen bool, controlState string) error + + // 设备 ↔ 账户 多对多绑定(应用双开多账户:一台设备服务多个收款账户) + // 设备主账户仍存于 personal_devices.account_id(兼容旧逻辑),附加账户存 device_accounts 表 + AddDeviceAccount(ctx context.Context, deviceID int64, accountID int64) error + RemoveDeviceAccount(ctx context.Context, deviceID int64, accountID int64) error + AccountBound(ctx context.Context, accountID int64) (bool, error) + ListDeviceAccounts(ctx context.Context, deviceID int64) ([]int64, error) + + // 收款推送流水 + InsertPush(ctx context.Context, p *personal.Push) error + ListPushes(ctx context.Context, limit int) ([]*personal.Push, error) + CountPushes(ctx context.Context) (int64, error) + PushExists(ctx context.Context, deviceID, rawHash string) (bool, error) + + // 无订单收款 + CreateUnmatched(ctx context.Context, u *personal.UnmatchedPayment) (int64, error) + ListUnmatched(ctx context.Context, status string, limit int) ([]*personal.UnmatchedPayment, error) + CountUnmatched(ctx context.Context, status string) (int64, error) + ResolveUnmatched(ctx context.Context, id int64, status, handler string) error + + // 动态收款码 + EnsureQrcodeSchema(ctx context.Context) error + UpsertQrcode(ctx context.Context, q *personal.DeviceQrcode) (int64, error) + GetLatestQrcode(ctx context.Context, deviceID string) (*personal.DeviceQrcode, error) + GetLatestQrcodeByPlatform(ctx context.Context, deviceID, platform string) (*personal.DeviceQrcode, error) + GetLatestQrcodeByPlatformUser(ctx context.Context, deviceID, platform string, userID int) (*personal.DeviceQrcode, error) + ListQrcodes(ctx context.Context, limit int) ([]*personal.DeviceQrcode, error) + DeleteExpiredQrcodes(ctx context.Context) (int64, error) + DeleteQrcodeByDevice(ctx context.Context, deviceID string) error + DeleteQrcode(ctx context.Context, deviceID, platform string, userID int) error + + // 监听会话(事件驱动收款监听,docs/PLAN-EVENT-MONITOR.md) + + Close() error +} + +// PersonalDB 独立个人通道数据库 +type PersonalDB struct { + db *sql.DB +} + +// NewPersonalDB 打开/创建独立个人通道数据库(0600 + WAL) +func NewPersonalDB(path string) (*PersonalDB, error) { + fd, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0600) + if err != nil { + return nil, fmt.Errorf("创建个人通道数据库文件失败: %w", err) + } + fd.Close() + if err := os.Chmod(path, 0600); err != nil { + return nil, fmt.Errorf("设置个人通道数据库权限失败: %w", err) + } + db, err := sql.Open("sqlite", path+"?_journal_mode=WAL&_busy_timeout=10000&_synchronous=FULL") + if err != nil { + return nil, fmt.Errorf("打开个人通道数据库失败: %w", err) + } + db.SetMaxOpenConns(1) + db.SetMaxIdleConns(1) + p := &PersonalDB{db: db} + if err := p.migrate(); err != nil { + db.Close() + return nil, err + } + return p, nil +} + +// migrate 建表(幂等) +func (p *PersonalDB) migrate() error { + stmts := []string{ + `CREATE TABLE IF NOT EXISTS personal_devices ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + device_id TEXT NOT NULL UNIQUE, + name TEXT NOT NULL DEFAULT '', + account_id INTEGER NOT NULL DEFAULT 0, + provider TEXT NOT NULL DEFAULT '', + secret_enc TEXT NOT NULL DEFAULT '', + status TEXT NOT NULL DEFAULT 'offline', + last_heartbeat TEXT NOT NULL DEFAULT '', + last_push TEXT NOT NULL DEFAULT '', + ip TEXT NOT NULL DEFAULT '', + adb_host TEXT NOT NULL DEFAULT '', + adb_port INTEGER NOT NULL DEFAULT 0, + adb_wifi_host TEXT NOT NULL DEFAULT '', + adb_wifi_port INTEGER NOT NULL DEFAULT 0, + created_at TEXT NOT NULL + )`, + `CREATE INDEX IF NOT EXISTS idx_personal_devices_status ON personal_devices(status)`, + `CREATE INDEX IF NOT EXISTS idx_personal_devices_account ON personal_devices(account_id)`, + `CREATE TABLE IF NOT EXISTS personal_pushes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + device_id TEXT NOT NULL, + provider TEXT NOT NULL, + amount_minor INTEGER NOT NULL DEFAULT 0, + note TEXT NOT NULL DEFAULT '', + raw_hash TEXT NOT NULL DEFAULT '', + matched_order TEXT NOT NULL DEFAULT '', + match_result TEXT NOT NULL DEFAULT '', + risk_text TEXT NOT NULL DEFAULT '', + raw TEXT NOT NULL DEFAULT '', + created_at TEXT NOT NULL + )`, + `CREATE INDEX IF NOT EXISTS idx_personal_pushes_device ON personal_pushes(device_id, created_at)`, + `CREATE UNIQUE INDEX IF NOT EXISTS idx_personal_pushes_dedup ON personal_pushes(device_id, raw_hash) WHERE raw_hash != ''`, + `CREATE TABLE IF NOT EXISTS unmatched_payments ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + provider TEXT NOT NULL, + amount_minor INTEGER NOT NULL DEFAULT 0, + note TEXT NOT NULL DEFAULT '', + raw TEXT NOT NULL DEFAULT '', + status TEXT NOT NULL DEFAULT 'pending', + handler TEXT NOT NULL DEFAULT '', + payer TEXT NOT NULL DEFAULT '', + device_id TEXT NOT NULL DEFAULT '', + risk_tags TEXT NOT NULL DEFAULT '', + risk_level INTEGER NOT NULL DEFAULT 0, + created_at TEXT NOT NULL + )`, + `CREATE INDEX IF NOT EXISTS idx_unmatched_status ON unmatched_payments(status, created_at)`, + `CREATE TABLE IF NOT EXISTS device_accounts ( + device_id INTEGER NOT NULL, + account_id INTEGER NOT NULL, + created_at TEXT NOT NULL, + PRIMARY KEY (device_id, account_id) + )`, + `CREATE INDEX IF NOT EXISTS idx_device_accounts_account ON device_accounts(account_id)`, + `CREATE TABLE IF NOT EXISTS device_qrcodes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + device_id TEXT NOT NULL, + platform TEXT NOT NULL DEFAULT '', + qr_content TEXT NOT NULL DEFAULT '', + account TEXT NOT NULL DEFAULT '', + expire_in INTEGER NOT NULL DEFAULT 0, + expire_at TEXT NOT NULL DEFAULT '', + status TEXT NOT NULL DEFAULT 'active', + pending_fetch INTEGER NOT NULL DEFAULT 0, + created_at TEXT NOT NULL + )`, + `CREATE INDEX IF NOT EXISTS idx_device_qrcodes_device ON device_qrcodes(device_id)`, + `CREATE TABLE IF NOT EXISTS monitor_sessions ( + account_id INTEGER PRIMARY KEY, + device_id TEXT NOT NULL DEFAULT '', + provider TEXT NOT NULL DEFAULT '', + state TEXT NOT NULL DEFAULT 'idle', + active INTEGER NOT NULL DEFAULT 0, + retry INTEGER NOT NULL DEFAULT 0, + last_error TEXT NOT NULL DEFAULT '', + updated_at TEXT NOT NULL DEFAULT '' + )`, + `CREATE TABLE IF NOT EXISTS ledger_checksums ( + device_id TEXT NOT NULL, + account_id INTEGER NOT NULL DEFAULT 0, + fingerprint TEXT NOT NULL, + created_at TEXT NOT NULL DEFAULT '', + PRIMARY KEY (device_id, account_id, fingerprint) + )`, + `CREATE INDEX IF NOT EXISTS idx_ledger_checksums_dev ON ledger_checksums(device_id, account_id)`, + `PRAGMA journal_mode=WAL`, + `PRAGMA synchronous=FULL`, + } + for _, stmt := range stmts { + if _, err := p.db.Exec(stmt); err != nil { + return fmt.Errorf("个人通道数据库迁移失败: %w", err) + } + } + // 兼容迁移:老库补 risk_text 列(已存在则忽略错误) + _, _ = p.db.Exec(`ALTER TABLE personal_pushes ADD COLUMN risk_text TEXT NOT NULL DEFAULT ''`) + // 兼容迁移:unmatched_payments 补风控留痕列(payer/device/risk_tags/risk_level) + _, _ = p.db.Exec(`ALTER TABLE unmatched_payments ADD COLUMN payer TEXT NOT NULL DEFAULT ''`) + _, _ = p.db.Exec(`ALTER TABLE unmatched_payments ADD COLUMN device_id TEXT NOT NULL DEFAULT ''`) + _, _ = p.db.Exec(`ALTER TABLE unmatched_payments ADD COLUMN risk_tags TEXT NOT NULL DEFAULT ''`) + _, _ = p.db.Exec(`ALTER TABLE unmatched_payments ADD COLUMN risk_level INTEGER NOT NULL DEFAULT 0`) + // 索引在 ALTER 之后建(老库 payer 列由上面 ALTER 补齐;放 stmts 会让老库启动直接失败)——生产事故 2026-09-14 + _, _ = p.db.Exec(`CREATE INDEX IF NOT EXISTS idx_unmatched_payer ON unmatched_payments(payer, created_at)`) + // 兼容迁移:老库补 space 列(通知来源空间:0=主空间,999=双开(XSpace)) + _, _ = p.db.Exec(`ALTER TABLE personal_pushes ADD COLUMN space INTEGER NOT NULL DEFAULT 0`) + // 兼容迁移:老库补 adb 连接列(web 可视化操作设备用) + _, _ = p.db.Exec(`ALTER TABLE personal_devices ADD COLUMN adb_host TEXT NOT NULL DEFAULT ''`) + _, _ = p.db.Exec(`ALTER TABLE personal_devices ADD COLUMN adb_port INTEGER NOT NULL DEFAULT 0`) + // 兼容迁移:老库补 WiFi 备用通道列(USB 断线自动切换用) + _, _ = p.db.Exec(`ALTER TABLE personal_devices ADD COLUMN adb_wifi_host TEXT NOT NULL DEFAULT ''`) + _, _ = p.db.Exec(`ALTER TABLE personal_devices ADD COLUMN adb_wifi_port INTEGER NOT NULL DEFAULT 0`) + // 兼容迁移:老库补 setup_progress 列(引导向导完成度,JSON 数组字符串;缺省 '[]' = 未配置) + _, _ = p.db.Exec(`ALTER TABLE personal_devices ADD COLUMN setup_progress TEXT NOT NULL DEFAULT '[]'`) + // 兼容迁移:老库补 device_qrcodes.account 列(收款账户标识,昵称/微信号) + _, _ = p.db.Exec(`ALTER TABLE device_qrcodes ADD COLUMN account TEXT NOT NULL DEFAULT ''`) + // 兼容迁移:P2P 组网控制面(docs/P2P-ADB-RECOVERY.md Phase 1) + // ts_ip : 设备组网 IP(Tailscale 100.x.x.x,未上线为空) + // adb_listen : adbd TCP 监听是否就绪(心跳上报,0/1) + // control_state : 控制面状态:usb / tunnel_only + _, _ = p.db.Exec(`ALTER TABLE personal_devices ADD COLUMN ts_ip TEXT NOT NULL DEFAULT ''`) + _, _ = p.db.Exec(`ALTER TABLE personal_devices ADD COLUMN adb_listen INTEGER NOT NULL DEFAULT 0`) + _, _ = p.db.Exec(`ALTER TABLE personal_devices ADD COLUMN control_state TEXT NOT NULL DEFAULT 'tunnel_only'`) + return nil +} + +// Close 关闭数据库 +func (p *PersonalDB) Close() error { return p.db.Close() } diff --git a/internal/store/personalstore_device_accounts.go b/internal/store/personalstore_device_accounts.go new file mode 100644 index 0000000..1e07980 --- /dev/null +++ b/internal/store/personalstore_device_accounts.go @@ -0,0 +1,102 @@ +package store + +import "context" + +// ---------- 设备 ↔ 账户 多对多绑定(应用双开多账户)---------- +// +// 一台物理设备(双开微信/支付宝)可服务多个收款账户: +// - 主账户存 personal_devices.account_id(兼容旧逻辑 + 金额兜底主校验) +// - 附加账户存 device_accounts 表(每账户一行) +// 匹配时校验订单账户 ∈ {主账户} ∪ {附加账户集合}。 + +// AddDeviceAccount 绑定附加账户到设备(幂等:已绑返回 nil) +func (p *PersonalDB) AddDeviceAccount(ctx context.Context, deviceID int64, accountID int64) error { + _, err := p.db.ExecContext(ctx, + `INSERT OR IGNORE INTO device_accounts (device_id, account_id, created_at) VALUES (?, ?, ?)`, + deviceID, accountID, nowStr()) + return err +} + +// RemoveDeviceAccount 解绑附加账户(幂等) +func (p *PersonalDB) RemoveDeviceAccount(ctx context.Context, deviceID int64, accountID int64) error { + _, err := p.db.ExecContext(ctx, + `DELETE FROM device_accounts WHERE device_id=? AND account_id=?`, deviceID, accountID) + return err +} + +// AccountBound 查询账户是否仍被任何设备绑定 +func (p *PersonalDB) AccountBound(ctx context.Context, accountID int64) (bool, error) { + var n int + err := p.db.QueryRowContext(ctx, + `SELECT COUNT(*) FROM device_accounts WHERE account_id=?`, accountID).Scan(&n) + return n > 0, err +} + +// ListDeviceAccounts 列出设备全部附加账户 ID(不含主账户 account_id) +func (p *PersonalDB) ListDeviceAccounts(ctx context.Context, deviceID int64) ([]int64, error) { + rows, err := p.db.QueryContext(ctx, + `SELECT account_id FROM device_accounts WHERE device_id=? ORDER BY account_id`, deviceID) + if err != nil { + return nil, err + } + defer rows.Close() + var out []int64 + for rows.Next() { + var id int64 + if err := rows.Scan(&id); err != nil { + return nil, err + } + out = append(out, id) + } + return out, rows.Err() +} + +// DeviceIDsOfAccount 反向查询:绑定了该账户(附加绑定或主账户)的所有设备 ID(int64 主键)。 +// 一台设备承载多账户多通道(device_accounts 多对多),账户级判定必须走账户→设备反查, +// 不能按 personal_devices.provider 单值过滤(跨通道订单会误判设备离线)。 +func (p *PersonalDB) DeviceIDsOfAccount(ctx context.Context, accountID int64) ([]int64, error) { + rows, err := p.db.QueryContext(ctx, + `SELECT device_id FROM device_accounts WHERE account_id=? + UNION + SELECT id FROM personal_devices WHERE account_id=? + ORDER BY 1`, accountID, accountID) + if err != nil { + return nil, err + } + defer rows.Close() + var out []int64 + seen := make(map[int64]bool) + for rows.Next() { + var id int64 + if err := rows.Scan(&id); err != nil { + return nil, err + } + if !seen[id] { + seen[id] = true + out = append(out, id) + } + } + return out, rows.Err() +} + +// DeviceAccountIDs 返回设备全部绑定账户 ID(主账户 + 附加账户,去重)。 +// 供匹配引擎/守卫查询设备可服务的账户集合。参数 d 可传 nil(仅附加账户)。 +func DeviceAccountIDs(ctx context.Context, pdb PersonalStore, deviceID int64, primaryAccountID int64) []int64 { + extra, err := pdb.ListDeviceAccounts(ctx, deviceID) + if err != nil { + extra = nil + } + seen := make(map[int64]bool) + var out []int64 + add := func(id int64) { + if id > 0 && !seen[id] { + seen[id] = true + out = append(out, id) + } + } + add(primaryAccountID) + for _, id := range extra { + add(id) + } + return out +} diff --git a/internal/store/personalstore_devices.go b/internal/store/personalstore_devices.go new file mode 100755 index 0000000..e1921fc --- /dev/null +++ b/internal/store/personalstore_devices.go @@ -0,0 +1,147 @@ +package store + +import ( + "context" + "database/sql" + + "paygateway/internal/personal" +) + +// ---------- 设备 CRUD ---------- + +// ListDevices 列出全部设备 +func (p *PersonalDB) ListDevices(ctx context.Context) ([]*personal.DeviceEncrypted, error) { + rows, err := p.db.QueryContext(ctx, + `SELECT id, device_id, name, account_id, provider, secret_enc, status, last_heartbeat, last_push, ip, adb_host, adb_port, adb_wifi_host, adb_wifi_port, setup_progress, ts_ip, adb_listen, control_state, created_at + FROM personal_devices ORDER BY id`) + if err != nil { + return nil, err + } + defer rows.Close() + + var out []*personal.DeviceEncrypted + for rows.Next() { + e, err := scanDeviceEncrypted(rows) + if err != nil { + return nil, err + } + out = append(out, e) + } + return out, rows.Err() +} + +// GetDevice 按 device_id 获取设备 +func (p *PersonalDB) GetDevice(ctx context.Context, deviceID string) (*personal.DeviceEncrypted, error) { + row := p.db.QueryRowContext(ctx, + `SELECT id, device_id, name, account_id, provider, secret_enc, status, last_heartbeat, last_push, ip, adb_host, adb_port, adb_wifi_host, adb_wifi_port, setup_progress, ts_ip, adb_listen, control_state, created_at + FROM personal_devices WHERE device_id=?`, deviceID) + return scanDeviceEncrypted(row) +} + +// GetDeviceByID 按 ID 获取设备(配置二维码用) +func (p *PersonalDB) GetDeviceByID(ctx context.Context, id int64) (*personal.DeviceEncrypted, error) { + row := p.db.QueryRowContext(ctx, + `SELECT id, device_id, name, account_id, provider, secret_enc, status, last_heartbeat, last_push, ip, adb_host, adb_port, adb_wifi_host, adb_wifi_port, setup_progress, ts_ip, adb_listen, control_state, created_at + FROM personal_devices WHERE id=?`, id) + return scanDeviceEncrypted(row) +} + +// CreateDevice 创建设备,返回新 ID +func (p *PersonalDB) CreateDevice(ctx context.Context, d *personal.DeviceEncrypted) (int64, error) { + progress := d.SetupProgress + if progress == "" { + progress = "[]" + } + res, err := p.db.ExecContext(ctx, + `INSERT INTO personal_devices (device_id, name, account_id, provider, secret_enc, status, last_heartbeat, last_push, ip, adb_host, adb_port, adb_wifi_host, adb_wifi_port, setup_progress, created_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + d.DeviceID, d.Name, d.AccountID, d.Provider, d.SecretEnc, + d.Status, "", d.LastPush, d.IP, d.AdbHost, d.AdbPort, d.AdbWifiHost, d.AdbWifiPort, progress, nowStr()) + if err != nil { + return 0, err + } + return res.LastInsertId() +} + +// UpdateDevice 更新设备 +func (p *PersonalDB) UpdateDevice(ctx context.Context, d *personal.DeviceEncrypted) error { + progress := d.SetupProgress + if progress == "" { + progress = "[]" + } + _, err := p.db.ExecContext(ctx, + `UPDATE personal_devices SET name=?, account_id=?, provider=?, secret_enc=?, status=?, last_heartbeat=?, last_push=?, ip=?, adb_host=?, adb_port=?, adb_wifi_host=?, adb_wifi_port=?, setup_progress=? + WHERE id=?`, + d.Name, d.AccountID, d.Provider, d.SecretEnc, d.Status, "", d.LastPush, d.IP, d.AdbHost, d.AdbPort, d.AdbWifiHost, d.AdbWifiPort, progress, d.ID) + return err +} + +// SetDeviceAdb 更新设备的 adb 连接信息(USB serial / host:port / WiFi 备用通道) +func (p *PersonalDB) SetDeviceAdb(ctx context.Context, id int64, host string, port int, wifiHost string, wifiPort int) error { + _, err := p.db.ExecContext(ctx, + `UPDATE personal_devices SET adb_host=?, adb_port=?, adb_wifi_host=?, adb_wifi_port=? WHERE id=?`, host, port, wifiHost, wifiPort, id) + return err +} + +// DeleteDevice 删除设备 +func (p *PersonalDB) DeleteDevice(ctx context.Context, id int64) error { + _, err := p.db.ExecContext(ctx, `DELETE FROM personal_devices WHERE id=?`, id) + return err +} + +// SetDeviceStatus 设置设备状态 +func (p *PersonalDB) SetDeviceStatus(ctx context.Context, id int64, status string) error { + _, err := p.db.ExecContext(ctx, + `UPDATE personal_devices SET status=? WHERE id=?`, status, id) + return err +} + +// SetDevicePushTime 更新最后推送时间 +func (p *PersonalDB) SetDevicePushTime(ctx context.Context, deviceID string) error { + _, err := p.db.ExecContext(ctx, + `UPDATE personal_devices SET last_push=? WHERE device_id=?`, + nowStr(), deviceID) + return err +} + +// SetDeviceSetupProgress 更新引导向导完成度(JSON 数组字符串) +func (p *PersonalDB) SetDeviceSetupProgress(ctx context.Context, id int64, progress string) error { + if progress == "" { + progress = "[]" + } + _, err := p.db.ExecContext(ctx, + `UPDATE personal_devices SET setup_progress=? WHERE id=?`, progress, id) + return err +} + +// SetDeviceControlPlane 更新设备的 P2P 控制面状态(心跳上报 ts_ip + adb_listen;reconciler 维护 control_state) +func (p *PersonalDB) SetDeviceControlPlane(ctx context.Context, deviceID, tsIP string, adbListen bool, controlState string) error { + if controlState == "" { + controlState = personal.ControlStateTunnelOnly + } + _, err := p.db.ExecContext(ctx, + `UPDATE personal_devices SET ts_ip=?, adb_listen=?, control_state=? WHERE device_id=?`, + tsIP, boolToInt(adbListen), controlState, deviceID) + return err +} + +// scanDeviceEncrypted 扫描一行到 DeviceEncrypted +func scanDeviceEncrypted(s rowScanner) (*personal.DeviceEncrypted, error) { + var e personal.DeviceEncrypted + var adbListen int + var lastHeartbeat string // 遗留列(listener 弃用后不再维护,仅占位避免改 schema) + err := s.Scan(&e.ID, &e.DeviceID, &e.Name, &e.AccountID, &e.Provider, + &e.SecretEnc, &e.Status, &lastHeartbeat, &e.LastPush, &e.IP, &e.AdbHost, &e.AdbPort, + &e.AdbWifiHost, &e.AdbWifiPort, &e.SetupProgress, &e.TsIP, &adbListen, &e.ControlState, &e.CreatedAt) + if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, err + } + e.AdbListen = adbListen + if e.ControlState == "" { + e.ControlState = personal.ControlStateTunnelOnly + } + return &e, nil +} diff --git a/internal/store/personalstore_ledger.go b/internal/store/personalstore_ledger.go new file mode 100644 index 0000000..e07d05a --- /dev/null +++ b/internal/store/personalstore_ledger.go @@ -0,0 +1,70 @@ +package store + +import ( + "context" + "strings" +) + +// ledgerSeenTable ledger_checksums:账本页增量检测基线持久化(docs/PLAN-ADB-LEDGER-IMPL.md §6.2)。 +// 每 (device_id, account_id) 保存已见指纹集合(时间|金额),服务端重启后恢复, +// 保证重启期间的新收款不因基线重建而漏检。集合小(可见行 ≤ 数十),覆盖写即可。 +const ledgerSeenTable = `CREATE TABLE IF NOT EXISTS ledger_checksums ( + device_id TEXT NOT NULL, + account_id INTEGER NOT NULL DEFAULT 0, + fingerprint TEXT NOT NULL, + created_at TEXT NOT NULL DEFAULT '', + PRIMARY KEY (device_id, account_id, fingerprint) +)` + +// LoadSeen 加载某 (设备, 账户) 已见指纹集合。无记录返回空切片(nil, 非错误)。 +// 实现 ledgermon.SeenStore 接口(ledgermon 通过最小接口注入,避免与 PersonalStore 全接口耦合)。 +func (p *PersonalDB) LoadSeen(ctx context.Context, deviceID string, accountID int64) ([]string, error) { + rows, err := p.db.QueryContext(ctx, + `SELECT fingerprint FROM ledger_checksums WHERE device_id = ? AND account_id = ?`, + deviceID, accountID) + if err != nil { + return nil, err + } + defer rows.Close() + var out []string + for rows.Next() { + var fp string + if err := rows.Scan(&fp); err != nil { + return nil, err + } + out = append(out, fp) + } + return out, rows.Err() +} + +// SaveSeen 覆盖写某 (设备, 账户) 的已见指纹集合(先删后插,单连接串行安全)。 +func (p *PersonalDB) SaveSeen(ctx context.Context, deviceID string, accountID int64, fingerprints []string) error { + tx, err := p.db.BeginTx(ctx, nil) + if err != nil { + return err + } + defer tx.Rollback() + if _, err := tx.ExecContext(ctx, + `DELETE FROM ledger_checksums WHERE device_id = ? AND account_id = ?`, deviceID, accountID); err != nil { + return err + } + now := nowStr() + for _, fp := range fingerprints { + if strings.TrimSpace(fp) == "" { + continue + } + if _, err := tx.ExecContext(ctx, + `INSERT OR IGNORE INTO ledger_checksums (device_id, account_id, fingerprint, created_at) VALUES (?,?,?,?)`, + deviceID, accountID, fp, now); err != nil { + return err + } + } + return tx.Commit() +} + +// ClearSeen 清理某 (设备, 账户) 基线(设备解绑/账户变更时用) +func (p *PersonalDB) ClearSeen(ctx context.Context, deviceID string, accountID int64) error { + _, err := p.db.ExecContext(ctx, + `DELETE FROM ledger_checksums WHERE device_id = ? AND account_id = ?`, deviceID, accountID) + return err +} diff --git a/internal/store/personalstore_migration_test.go b/internal/store/personalstore_migration_test.go new file mode 100644 index 0000000..6a698ec --- /dev/null +++ b/internal/store/personalstore_migration_test.go @@ -0,0 +1,53 @@ +// 老库迁移回归测试:旧 schema(无 payer 列)→ NewPersonalDB 必须成功补列+建索引。 +// 生产事故 2026-09-14:idx_unmatched_payer 放主迁移列表,老库启动直接崩。 +package store + +import ( + "context" + "database/sql" + "path/filepath" + "testing" + + "paygateway/internal/personal" +) + +func TestPersonalDBMigratesOldUnmatchedSchema(t *testing.T) { + dir := t.TempDir() + dbPath := filepath.Join(dir, "personal.db") + + // 先手工建旧 schema(无 payer/device_id/risk_tags/risk_level 列) + raw, err := sql.Open("sqlite", dbPath) + if err != nil { + t.Fatalf("打开旧库失败: %v", err) + } + if _, err := raw.Exec(`CREATE TABLE unmatched_payments ( + id INTEGER PRIMARY KEY AUTOINCREMENT, provider TEXT NOT NULL, + amount_minor INTEGER NOT NULL DEFAULT 0, note TEXT NOT NULL DEFAULT '', + raw TEXT NOT NULL DEFAULT '', status TEXT NOT NULL DEFAULT 'pending', + handler TEXT NOT NULL DEFAULT '', created_at TEXT NOT NULL)`); err != nil { + t.Fatalf("建旧表失败: %v", err) + } + if _, err := raw.Exec(`INSERT INTO unmatched_payments (provider, amount_minor, created_at) VALUES ('alipay', 10, '2026-09-14T10:00:00+08:00')`); err != nil { + t.Fatalf("插旧数据失败: %v", err) + } + if err := raw.Close(); err != nil { + t.Fatal(err) + } + + pdb, err := NewPersonalDB(dbPath) + if err != nil { + t.Fatalf("旧库迁移应成功: %v", err) + } + defer pdb.Close() + + list, err := pdb.ListUnmatched(context.Background(), "", 10) + if err != nil { + t.Fatalf("ListUnmatched 应读到旧数据: %v", err) + } + if len(list) != 1 || list[0].AmountMinor != 10 { + t.Fatalf("旧行应保留: %+v", list) + } + if _, err := pdb.CreateUnmatched(context.Background(), &personal.UnmatchedPayment{Provider: "wechat", AmountMinor: 5}); err != nil { + t.Fatalf("新库写入(含风控列)应成功: %v", err) + } +} diff --git a/internal/store/personalstore_pushes.go b/internal/store/personalstore_pushes.go new file mode 100755 index 0000000..c37d2d0 --- /dev/null +++ b/internal/store/personalstore_pushes.go @@ -0,0 +1,146 @@ +package store + +import ( + "context" + "database/sql" + "time" + + "paygateway/internal/personal" +) + +// ---------- 收款推送流水 ---------- + +// InsertPush 写入收款事件流水(审计) +func (p *PersonalDB) InsertPush(ctx context.Context, pu *personal.Push) error { + _, err := p.db.ExecContext(ctx, + `INSERT INTO personal_pushes (device_id, provider, amount_minor, note, raw_hash, matched_order, match_result, risk_text, space, raw, created_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + pu.DeviceID, pu.Provider, pu.AmountMinor, pu.Note, pu.RawHash, + pu.MatchedOrder, pu.MatchResult, pu.RiskText, pu.Space, pu.Raw, nowStr()) + return err +} + +// ListPushes 列出收款事件流水 +func (p *PersonalDB) ListPushes(ctx context.Context, limit int) ([]*personal.Push, error) { + if limit <= 0 || limit > 500 { + limit = 50 + } + rows, err := p.db.QueryContext(ctx, + `SELECT id, device_id, provider, amount_minor, note, raw_hash, matched_order, match_result, risk_text, space, raw, created_at + FROM personal_pushes ORDER BY id DESC LIMIT ?`, limit) + if err != nil { + return nil, err + } + defer rows.Close() + + var out []*personal.Push + for rows.Next() { + var pu personal.Push + var created string + if err := rows.Scan(&pu.ID, &pu.DeviceID, &pu.Provider, &pu.AmountMinor, &pu.Note, + &pu.RawHash, &pu.MatchedOrder, &pu.MatchResult, &pu.RiskText, &pu.Space, &pu.Raw, &created); err != nil { + return nil, err + } + pu.CreatedAt, _ = time.Parse(time.RFC3339Nano, created) + out = append(out, &pu) + } + if out == nil { + out = []*personal.Push{} + } + return out, rows.Err() +} + +// CountPushes 统计收款事件数 +func (p *PersonalDB) CountPushes(ctx context.Context) (int64, error) { + var n int64 + err := p.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM personal_pushes`).Scan(&n) + return n, err +} + +// PushExists 幂等去重:同设备同通知原文哈希是否已处理 +func (p *PersonalDB) PushExists(ctx context.Context, deviceID, rawHash string) (bool, error) { + if rawHash == "" { + return false, nil + } + var n int64 + err := p.db.QueryRowContext(ctx, + `SELECT COUNT(*) FROM personal_pushes WHERE device_id=? AND raw_hash=?`, + deviceID, rawHash).Scan(&n) + if err != nil { + return false, err + } + return n > 0, nil +} + +// ---------- 无订单收款 ---------- + +// CreateUnmatched 创建无订单收款记录 +func (p *PersonalDB) CreateUnmatched(ctx context.Context, u *personal.UnmatchedPayment) (int64, error) { + res, err := p.db.ExecContext(ctx, + `INSERT INTO unmatched_payments (provider, amount_minor, note, raw, status, handler, payer, device_id, risk_tags, risk_level, created_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + u.Provider, u.AmountMinor, u.Note, u.Raw, u.Status, u.Handler, u.Payer, u.DeviceID, u.RiskTags, u.RiskLevel, nowStr()) + if err != nil { + return 0, err + } + return res.LastInsertId() +} + +// unmatchedCols 无订单收款查询列(与 ListUnmatched 扫描顺序一致) +var unmatchedCols = `id, provider, amount_minor, note, raw, status, handler, payer, device_id, risk_tags, risk_level, created_at` + +// ListUnmatched 列出无订单收款 +func (p *PersonalDB) ListUnmatched(ctx context.Context, status string, limit int) ([]*personal.UnmatchedPayment, error) { + if limit <= 0 || limit > 500 { + limit = 50 + } + var rows *sql.Rows + var err error + if status == "" { + rows, err = p.db.QueryContext(ctx, + `SELECT `+unmatchedCols+` FROM unmatched_payments ORDER BY id DESC LIMIT ?`, limit) + } else { + rows, err = p.db.QueryContext(ctx, + `SELECT `+unmatchedCols+` FROM unmatched_payments WHERE status=? ORDER BY id DESC LIMIT ?`, status, limit) + } + if err != nil { + return nil, err + } + defer rows.Close() + + var out []*personal.UnmatchedPayment + for rows.Next() { + var u personal.UnmatchedPayment + var created string + if err := rows.Scan(&u.ID, &u.Provider, &u.AmountMinor, &u.Note, &u.Raw, + &u.Status, &u.Handler, &u.Payer, &u.DeviceID, &u.RiskTags, &u.RiskLevel, &created); err != nil { + return nil, err + } + u.CreatedAt, _ = time.Parse(time.RFC3339Nano, created) + out = append(out, &u) + } + if out == nil { + out = []*personal.UnmatchedPayment{} + } + return out, rows.Err() +} + +// CountUnmatched 统计无订单收款数 +func (p *PersonalDB) CountUnmatched(ctx context.Context, status string) (int64, error) { + var n int64 + var err error + if status == "" { + err = p.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM unmatched_payments`).Scan(&n) + } else { + err = p.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM unmatched_payments WHERE status=?`, status).Scan(&n) + } + return n, err +} + +// ResolveUnmatched 处理无订单收款 +func (p *PersonalDB) ResolveUnmatched(ctx context.Context, id int64, status, handler string) error { + _, err := p.db.ExecContext(ctx, + `UPDATE unmatched_payments SET status=?, handler=? WHERE id=?`, + status, handler, id) + return err +} diff --git a/internal/store/personalstore_qrcodes.go b/internal/store/personalstore_qrcodes.go new file mode 100644 index 0000000..69f7663 --- /dev/null +++ b/internal/store/personalstore_qrcodes.go @@ -0,0 +1,194 @@ +package store + +import ( + "context" + "database/sql" + "fmt" + "time" + + "paygateway/internal/personal" +) + +// ---------- 设备动态收款码 CRUD ---------- + +// EnsureQrcodeSchema 迁移 device_qrcodes 表,确保含 user_id 列。 +// 首次部署或旧表升级时自动执行(幂等:列已存在则跳过)。 +func (p *PersonalDB) EnsureQrcodeSchema(ctx context.Context) error { + // 1. 检查 user_id 列是否已存在 + var count int + err := p.db.QueryRowContext(ctx, + `SELECT COUNT(*) FROM pragma_table_info('device_qrcodes') WHERE name='user_id'`).Scan(&count) + if err != nil { + return fmt.Errorf("检查 device_qrcodes 结构: %w", err) + } + if count > 0 { + return nil // 已迁移 + } + // 2. 迁移:旧表重命名 → 建新表 → 复制数据 → 删旧表 + // 旧唯一约束 (device_id, platform) → 新唯一约束 (device_id, platform, user_id) + tx, err := p.db.BeginTx(ctx, nil) + if err != nil { + return fmt.Errorf("开始迁移事务: %w", err) + } + defer tx.Rollback() + + _, err = tx.ExecContext(ctx, `ALTER TABLE device_qrcodes RENAME TO device_qrcodes_old`) + if err != nil { + return fmt.Errorf("重命名旧表: %w", err) + } + _, err = tx.ExecContext(ctx, ` + CREATE TABLE device_qrcodes ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + device_id TEXT NOT NULL, + platform TEXT NOT NULL DEFAULT '', + user_id INTEGER NOT NULL DEFAULT 0, + qr_content TEXT NOT NULL DEFAULT '', + account TEXT NOT NULL DEFAULT '', + expire_in INTEGER NOT NULL DEFAULT 0, + expire_at TEXT NOT NULL DEFAULT '', + status TEXT NOT NULL DEFAULT 'active', + pending_fetch INTEGER NOT NULL DEFAULT 0, + created_at TEXT NOT NULL, + UNIQUE(device_id, platform, user_id) + )`) + if err != nil { + return fmt.Errorf("建新表: %w", err) + } + _, err = tx.ExecContext(ctx, ` + INSERT INTO device_qrcodes (id, device_id, platform, user_id, qr_content, account, expire_in, expire_at, status, pending_fetch, created_at) + SELECT id, device_id, platform, 0, qr_content, account, expire_in, expire_at, status, pending_fetch, created_at + FROM device_qrcodes_old`) + if err != nil { + return fmt.Errorf("迁移数据: %w", err) + } + _, err = tx.ExecContext(ctx, `DROP TABLE device_qrcodes_old`) + if err != nil { + return fmt.Errorf("删除旧表: %w", err) + } + return tx.Commit() +} + +// UpsertQrcode 写入/更新设备指定平台+用户空间的收款码(每设备每平台每用户空间幂等覆盖)。 +// 同一设备可同时持有 wechat(主空间) + wechat(双开) + alipay(主空间) + alipay(双开) 四条互不覆盖。 +func (p *PersonalDB) UpsertQrcode(ctx context.Context, q *personal.DeviceQrcode) (int64, error) { + var id int64 + err := p.db.QueryRowContext(ctx, + `SELECT id FROM device_qrcodes WHERE device_id=? AND platform=? AND user_id=? ORDER BY id DESC LIMIT 1`, + q.DeviceID, q.Platform, q.UserID).Scan(&id) + if err == nil { + _, uerr := p.db.ExecContext(ctx, + `UPDATE device_qrcodes SET qr_content=?, account=?, expire_in=?, expire_at=?, status=?, created_at=? WHERE id=?`, + q.QRContent, q.Account, q.ExpireIn, q.ExpireAt, q.Status, q.CreatedAt, id) + return id, uerr + } + if err != sql.ErrNoRows { + return 0, err + } + res, ierr := p.db.ExecContext(ctx, + `INSERT INTO device_qrcodes (device_id, platform, user_id, qr_content, account, expire_in, expire_at, status, pending_fetch, created_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, 0, ?)`, + q.DeviceID, q.Platform, q.UserID, q.QRContent, q.Account, q.ExpireIn, q.ExpireAt, q.Status, q.CreatedAt) + if ierr != nil { + return 0, ierr + } + return res.LastInsertId() +} + +// GetLatestQrcode 获取设备最新收款码(任意平台、任意用户空间) +func (p *PersonalDB) GetLatestQrcode(ctx context.Context, deviceID string) (*personal.DeviceQrcode, error) { + row := p.db.QueryRowContext(ctx, + `SELECT id, device_id, platform, user_id, qr_content, account, expire_in, expire_at, status, pending_fetch, created_at + FROM device_qrcodes WHERE device_id=? ORDER BY id DESC LIMIT 1`, deviceID) + return scanQrcode(row) +} + +// GetLatestQrcodeByPlatform 获取设备指定平台的最新收款码(跨用户空间,取最新一条;用于旧版去重/兼容) +func (p *PersonalDB) GetLatestQrcodeByPlatform(ctx context.Context, deviceID, platform string) (*personal.DeviceQrcode, error) { + row := p.db.QueryRowContext(ctx, + `SELECT id, device_id, platform, user_id, qr_content, account, expire_in, expire_at, status, pending_fetch, created_at + FROM device_qrcodes WHERE device_id=? AND platform=? ORDER BY id DESC LIMIT 1`, deviceID, platform) + return scanQrcode(row) +} + +// GetLatestQrcodeByPlatformUser 获取设备指定平台+用户空间的最新收款码(续期调度用) +func (p *PersonalDB) GetLatestQrcodeByPlatformUser(ctx context.Context, deviceID, platform string, userID int) (*personal.DeviceQrcode, error) { + row := p.db.QueryRowContext(ctx, + `SELECT id, device_id, platform, user_id, qr_content, account, expire_in, expire_at, status, pending_fetch, created_at + FROM device_qrcodes WHERE device_id=? AND platform=? AND user_id=? ORDER BY id DESC LIMIT 1`, + deviceID, platform, userID) + return scanQrcode(row) +} + +// ListQrcodes 列出最新收款码(按 id 倒序) +func (p *PersonalDB) ListQrcodes(ctx context.Context, limit int) ([]*personal.DeviceQrcode, error) { + if limit <= 0 { + limit = 100 + } + rows, err := p.db.QueryContext(ctx, + `SELECT id, device_id, platform, user_id, qr_content, account, expire_in, expire_at, status, pending_fetch, created_at + FROM device_qrcodes ORDER BY id DESC LIMIT ?`, limit) + if err != nil { + return nil, err + } + defer rows.Close() + var out []*personal.DeviceQrcode + for rows.Next() { + q, err := scanQrcode(rows) + if err != nil { + return nil, err + } + out = append(out, q) + } + return out, rows.Err() +} + +func scanQrcode(row interface{ Scan(dest ...any) error }) (*personal.DeviceQrcode, error) { + q := &personal.DeviceQrcode{} + err := row.Scan(&q.ID, &q.DeviceID, &q.Platform, &q.UserID, &q.QRContent, &q.Account, &q.ExpireIn, &q.ExpireAt, &q.Status, &q.PendingFetch, &q.CreatedAt) + if err == sql.ErrNoRows { + return nil, nil + } + if err != nil { + return nil, err + } + return q, nil +} + +// DeleteExpiredQrcodes 清理过期收款码:删除已过期或 status=expired 的记录, +// 但每设备每平台每用户空间保留最新一条(防清空后无码可用)。 +// 返回删除的行数。 +func (p *PersonalDB) DeleteExpiredQrcodes(ctx context.Context) (int64, error) { + // 每设备每平台每用户空间最新一条的 id(无论状态都保留) + keep := `SELECT MAX(id) FROM device_qrcodes GROUP BY device_id, platform, user_id` + res, err := p.db.ExecContext(ctx, ` + DELETE FROM device_qrcodes + WHERE id NOT IN (`+keep+`) + AND (status = 'expired' OR expire_at <= ?)`, + time.Now().Format(time.RFC3339)) + if err != nil { + return 0, err + } + return res.RowsAffected() +} + +// DeleteQrcodeByDevice 删除指定设备的所有收款码记录(设备解绑/删除时用) +func (p *PersonalDB) DeleteQrcodeByDevice(ctx context.Context, deviceID string) error { + _, err := p.db.ExecContext(ctx, `DELETE FROM device_qrcodes WHERE device_id=?`, deviceID) + return err +} + +// DeleteQrcode 删除单条收款码记录(deviceID+platform+userID 唯一定位) +func (p *PersonalDB) DeleteQrcode(ctx context.Context, deviceID, platform string, userID int) error { + _, err := p.db.ExecContext(ctx, + `DELETE FROM device_qrcodes WHERE device_id=? AND platform=? AND user_id=?`, + deviceID, platform, userID) + return err +} + +// ExpireAtStr 计算过期时间字符串(RFC3339,now + expireIn 秒;expireIn<=0 默认 90s) +func ExpireAtStr(expireIn int64) string { + if expireIn <= 0 { + expireIn = 90 + } + return time.Now().Add(time.Duration(expireIn) * time.Second).Format(time.RFC3339) +} diff --git a/internal/store/sqlite.go b/internal/store/sqlite.go new file mode 100755 index 0000000..d894e86 --- /dev/null +++ b/internal/store/sqlite.go @@ -0,0 +1,387 @@ +package store + +import ( + "context" + "database/sql" + "fmt" + "time" + + "paygateway/internal/model" + + _ "modernc.org/sqlite" +) + +// SQLiteStore SQLite 存储实现 +type SQLiteStore struct { + db *sql.DB +} + +// NewSQLite 打开 SQLite 数据库并执行迁移 +func NewSQLite(path string) (*SQLiteStore, error) { + db, err := sql.Open("sqlite", path) + if err != nil { + return nil, fmt.Errorf("打开数据库失败: %w", err) + } + // 关键配置:WAL 模式 + busy timeout(并发安全) + db.SetMaxOpenConns(1) // SQLite 单写者,串行化保证一致性 + if _, err := db.Exec("PRAGMA journal_mode=WAL;"); err != nil { + return nil, fmt.Errorf("设置 WAL 失败: %w", err) + } + if _, err := db.Exec("PRAGMA busy_timeout=5000;"); err != nil { + return nil, fmt.Errorf("设置 busy_timeout 失败: %w", err) + } + if _, err := db.Exec("PRAGMA foreign_keys=ON;"); err != nil { + return nil, fmt.Errorf("设置外键失败: %w", err) + } + s := &SQLiteStore{db: db} + if err := s.migrate(); err != nil { + db.Close() + return nil, err + } + return s, nil +} + +// migrate 建表(幂等) +func (s *SQLiteStore) migrate() error { + // 迁移旧数据库:添加 account_id / pid / upstream_name 列(如果不存在) + _, _ = s.db.Exec(`ALTER TABLE orders ADD COLUMN account_id INTEGER NOT NULL DEFAULT 0`) + _, _ = s.db.Exec(`ALTER TABLE orders ADD COLUMN pid TEXT NOT NULL DEFAULT ''`) + _, _ = s.db.Exec(`ALTER TABLE orders ADD COLUMN upstream_name TEXT NOT NULL DEFAULT ''`) + // 迁移旧数据库:个人免签通道字段(列已存在时 ALTER 报错被忽略,新库直接走 CREATE TABLE) + _, _ = s.db.Exec(`ALTER TABLE orders ADD COLUMN note_code TEXT NOT NULL DEFAULT ''`) + _, _ = s.db.Exec(`ALTER TABLE orders ADD COLUMN qr_token TEXT NOT NULL DEFAULT ''`) + _, _ = s.db.Exec(`ALTER TABLE orders ADD COLUMN device_id TEXT NOT NULL DEFAULT ''`) + _, _ = s.db.Exec(`ALTER TABLE orders ADD COLUMN buyer_key TEXT NOT NULL DEFAULT ''`) + _, _ = s.db.Exec(`CREATE INDEX IF NOT EXISTS idx_orders_buyer_key ON orders(buyer_key) WHERE buyer_key != ''`) + _, _ = s.db.Exec(`ALTER TABLE orders ADD COLUMN fp_key TEXT NOT NULL DEFAULT ''`) + _, _ = s.db.Exec(`ALTER TABLE orders ADD COLUMN session_expires_at TEXT NOT NULL DEFAULT ''`) + _, _ = s.db.Exec(`CREATE INDEX IF NOT EXISTS idx_orders_fp_key ON orders(fp_key) WHERE fp_key != ''`) + stmts := []string{ + `CREATE TABLE IF NOT EXISTS orders ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + out_trade_no TEXT NOT NULL UNIQUE, + gateway_no TEXT NOT NULL UNIQUE, + channel TEXT NOT NULL, + channel_tx_id TEXT DEFAULT NULL, + account_id INTEGER NOT NULL DEFAULT 0, + pid TEXT NOT NULL DEFAULT '', + upstream_name TEXT NOT NULL DEFAULT '', + attach TEXT NOT NULL DEFAULT '', + amount_minor INTEGER NOT NULL, + currency TEXT NOT NULL DEFAULT 'CNY', + subject TEXT NOT NULL, + status TEXT NOT NULL, + notify_url TEXT, + return_url TEXT, + qr_code TEXT, + pay_url TEXT, + payer TEXT, + notified INTEGER NOT NULL DEFAULT 0, + notify_count INTEGER NOT NULL DEFAULT 0, + created_at TEXT NOT NULL, + session_expires_at TEXT NOT NULL DEFAULT '', + paid_at TEXT, + notified_at TEXT, + note_code TEXT NOT NULL DEFAULT '', + qr_token TEXT NOT NULL DEFAULT '', + device_id TEXT NOT NULL DEFAULT '', + buyer_key TEXT NOT NULL DEFAULT '', + fp_key TEXT NOT NULL DEFAULT '' + )`, + `CREATE UNIQUE INDEX IF NOT EXISTS idx_orders_channel_tx ON orders(channel, channel_tx_id) WHERE channel_tx_id IS NOT NULL`, + `CREATE INDEX IF NOT EXISTS idx_orders_status ON orders(status)`, + `CREATE INDEX IF NOT EXISTS idx_orders_created ON orders(created_at)`, + `CREATE INDEX IF NOT EXISTS idx_orders_channel ON orders(channel)`, + `CREATE INDEX IF NOT EXISTS idx_orders_pid ON orders(pid)`, + `CREATE INDEX IF NOT EXISTS idx_orders_note_code ON orders(note_code) WHERE note_code != ''`, + `CREATE INDEX IF NOT EXISTS idx_orders_qr_token ON orders(qr_token) WHERE qr_token != ''`, + // 调度器/池按账户查待支付订单(ListWaitPayByAccount / ListWaitPayAll 高频路径) + `CREATE INDEX IF NOT EXISTS idx_orders_account_status ON orders(account_id, status)`, + `CREATE TABLE IF NOT EXISTS payment_events ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + order_id INTEGER NOT NULL, + event_type TEXT NOT NULL, + channel_tx_id TEXT, + amount_minor INTEGER NOT NULL, + raw_payload TEXT, + created_at TEXT NOT NULL + )`, + `CREATE INDEX IF NOT EXISTS idx_events_order ON payment_events(order_id)`, + `CREATE TABLE IF NOT EXISTS notify_tasks ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + order_id INTEGER NOT NULL, + payload TEXT NOT NULL, + status TEXT NOT NULL DEFAULT 'pending', + retry_count INTEGER NOT NULL DEFAULT 0, + next_retry_at TEXT NOT NULL, + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL + )`, + `CREATE INDEX IF NOT EXISTS idx_notify_status ON notify_tasks(status, next_retry_at)`, + `CREATE TABLE IF NOT EXISTS callback_logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + task_id INTEGER NOT NULL, + http_status INTEGER, + response TEXT, + latency_ms INTEGER, + created_at TEXT NOT NULL + )`, + `CREATE TABLE IF NOT EXISTS exception_orders ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + out_trade_no TEXT NOT NULL UNIQUE, + pid TEXT NOT NULL DEFAULT '', + upstream_name TEXT NOT NULL DEFAULT '', + channel TEXT NOT NULL DEFAULT '', + amount_minor INTEGER NOT NULL DEFAULT 0, + order_status TEXT NOT NULL DEFAULT '', + channel_status TEXT NOT NULL DEFAULT 'UNKNOWN', + channel_tx_id TEXT NOT NULL DEFAULT '', + paid_amount INTEGER NOT NULL DEFAULT 0, + payer TEXT NOT NULL DEFAULT '', + reason TEXT NOT NULL DEFAULT '', + phase TEXT NOT NULL DEFAULT 'pending', + resolved_action TEXT NOT NULL DEFAULT '', + evidence TEXT NOT NULL DEFAULT '', + suggestion TEXT NOT NULL DEFAULT '', + handler TEXT NOT NULL DEFAULT '', + handler_note TEXT NOT NULL DEFAULT '', + resolved_at TEXT NOT NULL DEFAULT '', + updated_at TEXT NOT NULL, + created_at TEXT NOT NULL + )`, + `CREATE INDEX IF NOT EXISTS idx_exception_phase ON exception_orders(phase, created_at)`, + `CREATE INDEX IF NOT EXISTS idx_exception_reason ON exception_orders(reason)`, + `CREATE TABLE IF NOT EXISTS exception_logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + exception_id INTEGER NOT NULL, + action TEXT NOT NULL, + operator TEXT NOT NULL DEFAULT '', + detail TEXT NOT NULL DEFAULT '', + created_at TEXT NOT NULL + )`, + `CREATE INDEX IF NOT EXISTS idx_exception_logs ON exception_logs(exception_id, created_at)`, + `CREATE TABLE IF NOT EXISTS app_logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + level TEXT NOT NULL, + scope TEXT, + message TEXT NOT NULL, + trace_id TEXT, + fields TEXT, + created_at TEXT NOT NULL + )`, + `CREATE INDEX IF NOT EXISTS idx_logs_level_time ON app_logs(level, created_at)`, + `CREATE INDEX IF NOT EXISTS idx_logs_scope_time ON app_logs(scope, created_at)`, + `CREATE INDEX IF NOT EXISTS idx_logs_trace ON app_logs(trace_id)`, + `CREATE TABLE IF NOT EXISTS settings ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL DEFAULT '', + updated_at TEXT NOT NULL + )`, + } + for _, stmt := range stmts { + if _, err := s.db.Exec(stmt); err != nil { + return fmt.Errorf("迁移失败: %w", err) + } + } + return nil +} + +func nowStr() string { return time.Now().Format(time.RFC3339Nano) } + +// ---------- 订单 ---------- + +func (s *SQLiteStore) CreateOrder(ctx context.Context, o *model.Order) error { + _, err := s.db.ExecContext(ctx, ` + INSERT INTO orders (out_trade_no, gateway_no, channel, channel_tx_id, account_id, pid, upstream_name, attach, + amount_minor, currency, subject, status, notify_url, return_url, qr_code, pay_url, payer, notified, notify_count, created_at, session_expires_at, + note_code, qr_token, device_id, buyer_key, fp_key) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + o.OutTradeNo, o.GatewayNo, o.Channel, nullIfEmpty(o.ChannelTxID), o.AccountID, o.Pid, o.UpstreamName, o.Attach, + o.AmountMinor, o.Currency, o.Subject, o.Status, o.NotifyURL, o.ReturnURL, o.QrCode, o.PayURL, o.Payer, + o.Notified, o.NotifyCount, o.CreatedAt.Format(time.RFC3339Nano), nullIfTimeValue(o.SessionExpiresAt), + o.NoteCode, o.QrToken, o.DeviceID, o.BuyerKey, o.FPKey) + return err +} + +func nullIfTimeValue(t time.Time) any { + if t.IsZero() { + return "" + } + return t.Format(time.RFC3339Nano) +} + +func nullIfEmpty(s string) any { + if s == "" { + return nil + } + return s +} + +func nullIfTime(t *time.Time) any { + if t == nil { + return nil + } + return t.Format(time.RFC3339Nano) +} + +// ReplaceOrder 原子完成买家替换:同一事务内把 WAIT_PAY 订单改为 CLOSED 并写 replaced 事件。 +// replaced 事件与关单同事务持久化 ⇒ 观察者看到 CLOSED 时必能看到 replaced 事件, +// 从而可靠区分「并发替换者关闭」(有 replaced)与「清扫器超时关闭」(无 replaced)。 +// 返回 changed=true 表示本次调用赢得替换;false 表示订单已非 WAIT_PAY(被并发结算或替换者处理)。 +func (s *SQLiteStore) ReplaceOrder(ctx context.Context, outTradeNo string, orderID int64, amountMinor int64, rawJSON string) (bool, error) { + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return false, err + } + defer tx.Rollback() + + res, err := tx.ExecContext(ctx, + `UPDATE orders SET status=?, paid_at=COALESCE(paid_at, ?) WHERE out_trade_no=? AND status=?`, + model.StatusClosed, nowStr(), outTradeNo, model.StatusWaitPay) + if err != nil { + return false, err + } + n, err := res.RowsAffected() + if err != nil { + return false, err + } + if n == 0 { + // 非 WAIT_PAY(已结算 / 已被并发替换关闭)→ 未赢得替换 + return false, nil + } + if _, err := tx.ExecContext(ctx, + `INSERT INTO payment_events (order_id, event_type, channel_tx_id, amount_minor, raw_payload, created_at) + VALUES (?, ?, ?, ?, ?, ?)`, + orderID, "replaced", "", amountMinor, rawJSON, nowStr()); err != nil { + return false, err + } + return true, tx.Commit() +} + +// UpdateOrderAmount 更新订单金额(复用订单时以最新下单金额覆盖)。 +func (s *SQLiteStore) UpdateOrderAmount(ctx context.Context, outTradeNo string, amountMinor int64) error { + _, err := s.db.ExecContext(ctx, + `UPDATE orders SET amount_minor=? WHERE out_trade_no=? AND amount_minor<>?`, + amountMinor, outTradeNo, amountMinor) + return err +} + +func (s *SQLiteStore) GetOrder(ctx context.Context, outTradeNo string) (*model.Order, error) { + row := s.db.QueryRowContext(ctx, `SELECT id, out_trade_no, gateway_no, channel, channel_tx_id, account_id, pid, upstream_name, attach, amount_minor, + currency, subject, status, notify_url, return_url, qr_code, pay_url, payer, notified, notify_count, created_at, session_expires_at, paid_at, notified_at, + note_code, qr_token, device_id, buyer_key, fp_key + FROM orders WHERE out_trade_no = ?`, outTradeNo) + return scanOrder(row) +} + +func (s *SQLiteStore) DeleteOrder(ctx context.Context, outTradeNo string) (bool, error) { + res, err := s.db.ExecContext(ctx, + `DELETE FROM orders WHERE out_trade_no=? AND status=?`, + outTradeNo, model.StatusWaitPay) + if err != nil { + return false, err + } + n, err := res.RowsAffected() + return n > 0, err +} + +func (s *SQLiteStore) GetOrderByID(ctx context.Context, id int64) (*model.Order, error) { + row := s.db.QueryRowContext(ctx, `SELECT id, out_trade_no, gateway_no, channel, channel_tx_id, account_id, pid, upstream_name, attach, amount_minor, + currency, subject, status, notify_url, return_url, qr_code, pay_url, payer, notified, notify_count, created_at, session_expires_at, paid_at, notified_at, + note_code, qr_token, device_id, buyer_key, fp_key + FROM orders WHERE id = ?`, id) + return scanOrder(row) +} + +type rowScanner interface { + Scan(dest ...any) error +} + +func scanOrder(row rowScanner) (*model.Order, error) { + var ( + o model.Order + notified int + createdAt, sessionExpiresAt string + paidAt, nb, txID sql.NullString + ) + if err := row.Scan(&o.ID, &o.OutTradeNo, &o.GatewayNo, &o.Channel, &txID, &o.AccountID, &o.Pid, &o.UpstreamName, &o.Attach, &o.AmountMinor, + &o.Currency, &o.Subject, &o.Status, &o.NotifyURL, &o.ReturnURL, &o.QrCode, &o.PayURL, &o.Payer, + ¬ified, &o.NotifyCount, &createdAt, &sessionExpiresAt, &paidAt, &nb, + &o.NoteCode, &o.QrToken, &o.DeviceID, &o.BuyerKey, &o.FPKey); err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, err + } + o.Notified = notified != 0 + o.CreatedAt, _ = time.Parse(time.RFC3339Nano, createdAt) + o.SessionExpiresAt, _ = time.Parse(time.RFC3339Nano, sessionExpiresAt) + if paidAt.Valid { + t, _ := time.Parse(time.RFC3339Nano, paidAt.String) + o.PaidAt = &t + } + if nb.Valid { + t, _ := time.Parse(time.RFC3339Nano, nb.String) + o.NotifiedAt = &t + } + if txID.Valid { + o.ChannelTxID = txID.String + } + return &o, nil +} + +// UpdateOrderStatus 条件更新:from -> to;影响行数=0 表示状态非法(幂等/防覆盖) +// set 回调可修改订单字段,修改后自动写回数据库 +func (s *SQLiteStore) UpdateOrderStatus(ctx context.Context, outTradeNo, from, to string, set func(o *model.Order)) (bool, error) { + tx, err := s.db.BeginTx(ctx, nil) + if err != nil { + return false, err + } + defer tx.Rollback() + + var updateSQL string + var updateArgs []any + if from == to { + // 状态不变(如保存支付凭据),不修改 paid_at + updateSQL = `UPDATE orders SET status=? WHERE out_trade_no=? AND status=?` + updateArgs = []any{to, outTradeNo, from} + } else { + updateSQL = `UPDATE orders SET status=?, paid_at=COALESCE(paid_at, ?) WHERE out_trade_no=? AND status=?` + updateArgs = []any{to, nowStr(), outTradeNo, from} + } + res, err := tx.ExecContext(ctx, updateSQL, updateArgs...) + if err != nil { + return false, err + } + n, err := res.RowsAffected() + if err != nil { + return false, err + } + if n == 0 { + return false, nil + } + if set != nil { + // 读回最新(在事务内用单独的查询) + row := tx.QueryRowContext(ctx, `SELECT id, out_trade_no, gateway_no, channel, channel_tx_id, account_id, pid, upstream_name, attach, amount_minor, + currency, subject, status, notify_url, return_url, qr_code, pay_url, payer, notified, notify_count, created_at, session_expires_at, paid_at, notified_at, + note_code, qr_token, device_id, buyer_key, fp_key + FROM orders WHERE out_trade_no = ?`, outTradeNo) + upd, err := scanOrder(row) + if err != nil { + return false, err + } + if upd != nil { + set(upd) + // 写回修改的字段(含 channel/created_at/paid_at:订单换通道迁移/重置倒计时/复活清除支付时间用) + _, err := tx.ExecContext(ctx, + `UPDATE orders SET pay_url=?, qr_code=?, channel_tx_id=?, payer=?, account_id=?, notified=?, notify_count=?, notified_at=COALESCE(notified_at, ?), + note_code=?, qr_token=?, device_id=?, buyer_key=?, fp_key=?, channel=?, created_at=?, session_expires_at=?, paid_at=? WHERE out_trade_no=?`, + upd.PayURL, upd.QrCode, nullIfEmpty(upd.ChannelTxID), upd.Payer, upd.AccountID, upd.Notified, upd.NotifyCount, nowStr(), + upd.NoteCode, upd.QrToken, upd.DeviceID, upd.BuyerKey, upd.FPKey, upd.Channel, upd.CreatedAt.Format(time.RFC3339Nano), nullIfTimeValue(upd.SessionExpiresAt), nullIfTime(upd.PaidAt), outTradeNo) + if err != nil { + return false, err + } + } + } + return true, tx.Commit() +} diff --git a/internal/store/sqlite_exception.go b/internal/store/sqlite_exception.go new file mode 100755 index 0000000..7173b7e --- /dev/null +++ b/internal/store/sqlite_exception.go @@ -0,0 +1,183 @@ +package store + +import ( + "context" + "database/sql" + "fmt" + "time" + + "paygateway/internal/model" +) + +// ========== 人工异常处理池 ========== + +const exceptionCols = `id, out_trade_no, pid, upstream_name, channel, amount_minor, order_status, + channel_status, channel_tx_id, paid_amount, payer, reason, phase, resolved_action, + evidence, suggestion, handler, handler_note, resolved_at, updated_at, created_at` + +func scanException(row *sql.Row) (*model.ExceptionOrder, error) { + var e model.ExceptionOrder + var updatedAt, createdAt string + err := row.Scan(&e.ID, &e.OutTradeNo, &e.Pid, &e.UpstreamName, &e.Channel, &e.AmountMinor, + &e.OrderStatus, &e.ChannelStatus, &e.ChannelTxID, &e.PaidAmount, &e.Payer, &e.Reason, + &e.Phase, &e.ResolvedAction, &e.Evidence, &e.Suggestion, &e.Handler, &e.HandlerNote, + &e.ResolvedAt, &updatedAt, &createdAt) + if err != nil { + return nil, err + } + e.UpdatedAt = updatedAt + e.CreatedAt = createdAt + return &e, nil +} + +// CreateException 创建异常单(幂等:同 out_trade_no 已存在则返回已有) +func (s *SQLiteStore) CreateException(ctx context.Context, e *model.ExceptionOrder) error { + now := time.Now().Format(time.RFC3339) + if e.PaidAmount == 0 { + e.PaidAmount = e.AmountMinor + } + if e.Phase == "" { + e.Phase = model.ExPhasePending + } + if e.ChannelStatus == "" { + e.ChannelStatus = "UNKNOWN" + } + _, err := s.db.ExecContext(ctx, `INSERT INTO exception_orders + (out_trade_no, pid, upstream_name, channel, amount_minor, order_status, channel_status, + channel_tx_id, paid_amount, payer, reason, phase, resolved_action, evidence, suggestion, + handler, handler_note, resolved_at, updated_at, created_at) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) + ON CONFLICT(out_trade_no) DO NOTHING`, + e.OutTradeNo, e.Pid, e.UpstreamName, e.Channel, e.AmountMinor, e.OrderStatus, e.ChannelStatus, + e.ChannelTxID, e.PaidAmount, e.Payer, e.Reason, e.Phase, e.ResolvedAction, e.Evidence, e.Suggestion, + e.Handler, e.HandlerNote, e.ResolvedAt, now, now) + if err != nil { + return fmt.Errorf("创建异常单失败: %w", err) + } + return nil +} + +func (s *SQLiteStore) GetException(ctx context.Context, id int64) (*model.ExceptionOrder, error) { + row := s.db.QueryRowContext(ctx, `SELECT `+exceptionCols+` FROM exception_orders WHERE id = ?`, id) + return scanException(row) +} + +func (s *SQLiteStore) GetExceptionByOutTradeNo(ctx context.Context, outTradeNo string) (*model.ExceptionOrder, error) { + row := s.db.QueryRowContext(ctx, `SELECT `+exceptionCols+` FROM exception_orders WHERE out_trade_no = ?`, outTradeNo) + return scanException(row) +} + +func (s *SQLiteStore) ListExceptions(ctx context.Context, phase, reason string, limit, offset int) ([]*model.ExceptionOrder, error) { + if limit <= 0 { + limit = 50 + } + q := `SELECT ` + exceptionCols + ` FROM exception_orders WHERE 1=1` + args := []any{} + if phase != "" { + q += ` AND phase = ?` + args = append(args, phase) + } + if reason != "" { + q += ` AND reason = ?` + args = append(args, reason) + } + q += ` ORDER BY id DESC LIMIT ? OFFSET ?` + args = append(args, limit, offset) + + rows, err := s.db.QueryContext(ctx, q, args...) + if err != nil { + return nil, err + } + defer rows.Close() + var out []*model.ExceptionOrder + for rows.Next() { + var e model.ExceptionOrder + var updatedAt, createdAt string + if err := rows.Scan(&e.ID, &e.OutTradeNo, &e.Pid, &e.UpstreamName, &e.Channel, &e.AmountMinor, + &e.OrderStatus, &e.ChannelStatus, &e.ChannelTxID, &e.PaidAmount, &e.Payer, &e.Reason, + &e.Phase, &e.ResolvedAction, &e.Evidence, &e.Suggestion, &e.Handler, &e.HandlerNote, + &e.ResolvedAt, &updatedAt, &createdAt); err != nil { + return nil, err + } + e.UpdatedAt = updatedAt + e.CreatedAt = createdAt + out = append(out, &e) + } + return out, rows.Err() +} + +func (s *SQLiteStore) CountExceptions(ctx context.Context, phase string) (int64, error) { + q := `SELECT COUNT(*) FROM exception_orders` + args := []any{} + if phase != "" { + q += ` WHERE phase = ?` + args = append(args, phase) + } + var n int64 + err := s.db.QueryRowContext(ctx, q, args...).Scan(&n) + return n, err +} + +// UpdateExceptionPhase 条件更新阶段(防并发处理:pending -> processing 锁) +func (s *SQLiteStore) UpdateExceptionPhase(ctx context.Context, id int64, from, to string) (bool, error) { + res, err := s.db.ExecContext(ctx, `UPDATE exception_orders SET phase = ?, updated_at = ? WHERE id = ? AND phase = ?`, + to, time.Now().Format(time.RFC3339), id, from) + if err != nil { + return false, err + } + n, _ := res.RowsAffected() + return n > 0, nil +} + +// UpdateException 更新异常单(set 修改字段,updated_at 自动刷新) +func (s *SQLiteStore) UpdateException(ctx context.Context, id int64, set func(e *model.ExceptionOrder)) error { + e, err := s.GetException(ctx, id) + if err != nil { + return err + } + set(e) + _, err = s.db.ExecContext(ctx, `UPDATE exception_orders SET + channel_status=?, channel_tx_id=?, paid_amount=?, payer=?, reason=?, phase=?, + resolved_action=?, evidence=?, suggestion=?, handler=?, handler_note=?, resolved_at=?, updated_at=? + WHERE id=?`, + e.ChannelStatus, e.ChannelTxID, e.PaidAmount, e.Payer, e.Reason, e.Phase, + e.ResolvedAction, e.Evidence, e.Suggestion, e.Handler, e.HandlerNote, e.ResolvedAt, + time.Now().Format(time.RFC3339), id) + return err +} + +func (s *SQLiteStore) InsertExceptionLog(ctx context.Context, exceptionID int64, action, operator, detail string) error { + _, err := s.db.ExecContext(ctx, `INSERT INTO exception_logs (exception_id, action, operator, detail, created_at) + VALUES (?,?,?,?,?)`, exceptionID, action, operator, detail, time.Now().Format(time.RFC3339)) + return err +} + +func (s *SQLiteStore) ListExceptionLogs(ctx context.Context, exceptionID int64, limit int) ([]*model.ExceptionLog, error) { + if limit <= 0 { + limit = 100 + } + rows, err := s.db.QueryContext(ctx, `SELECT id, exception_id, action, operator, detail, created_at + FROM exception_logs WHERE exception_id = ? ORDER BY id DESC LIMIT ?`, exceptionID, limit) + if err != nil { + return nil, err + } + defer rows.Close() + var out []*model.ExceptionLog + for rows.Next() { + var l model.ExceptionLog + if err := rows.Scan(&l.ID, &l.ExceptionID, &l.Action, &l.Operator, &l.Detail, &l.CreatedAt); err != nil { + return nil, err + } + out = append(out, &l) + } + return out, rows.Err() +} + +// UpdateOrderHandled 订单人工处理标记(人工池处理后永久留痕) +func (s *SQLiteStore) UpdateOrderHandled(ctx context.Context, outTradeNo string, exceptionID int64, action, handler, note string) error { + _, err := s.db.ExecContext(ctx, `UPDATE orders SET + exception_id = ?, handled_by = ?, handled_action = ?, handled_note = ?, handled_at = ? + WHERE out_trade_no = ?`, + exceptionID, handler, action, note, time.Now().Format(time.RFC3339), outTradeNo) + return err +} diff --git a/internal/store/sqlite_rest.go b/internal/store/sqlite_rest.go new file mode 100755 index 0000000..4b7ec01 --- /dev/null +++ b/internal/store/sqlite_rest.go @@ -0,0 +1,697 @@ +package store + +import ( + "context" + "database/sql" + "strings" + "time" + + "paygateway/internal/model" +) + +// ---------- 订单查询 ---------- + +func (s *SQLiteStore) ListOrders(ctx context.Context, status, channel string, limit, offset int) ([]*model.Order, error) { + q := `SELECT ` + orderColumns + ` FROM orders WHERE 1=1` + args := []any{} + if status != "" { + q += " AND status=?" + args = append(args, status) + } + if channel != "" { + q += " AND channel=?" + args = append(args, channel) + } + q += " ORDER BY id DESC LIMIT ? OFFSET ?" + args = append(args, limit, offset) + rows, err := s.db.QueryContext(ctx, q, args...) + if err != nil { + return nil, err + } + defer rows.Close() + out := []*model.Order{} + for rows.Next() { + o, err := scanOrder(rows) + if err != nil { + return nil, err + } + out = append(out, o) + } + return out, rows.Err() +} + +func (s *SQLiteStore) ListExpiredOrders(ctx context.Context, before time.Time) ([]*model.Order, error) { + rows, err := s.db.QueryContext(ctx, + `SELECT `+orderColumns+` FROM orders WHERE status=? AND created_at <= ? ORDER BY id`, + model.StatusWaitPay, before.Format(time.RFC3339)) + if err != nil { + return nil, err + } + defer rows.Close() + out := []*model.Order{} + for rows.Next() { + o, err := scanOrder(rows) + if err != nil { + return nil, err + } + out = append(out, o) + } + return out, rows.Err() +} + +func (s *SQLiteStore) CountOrders(ctx context.Context, status string) (int64, error) { + q := `SELECT COUNT(*) FROM orders` + args := []any{} + if status != "" { + q += " WHERE status=?" + args = append(args, status) + } + var n int64 + if err := s.db.QueryRowContext(ctx, q, args...).Scan(&n); err != nil { + return 0, err + } + return n, nil +} + +func (s *SQLiteStore) SumAmount(ctx context.Context, status string) (int64, error) { + q := `SELECT COALESCE(SUM(amount_minor),0) FROM orders` + args := []any{} + if status != "" { + q += " WHERE status=?" + args = append(args, status) + } + var n int64 + if err := s.db.QueryRowContext(ctx, q, args...).Scan(&n); err != nil { + return 0, err + } + return n, nil +} + +func (s *SQLiteStore) ListOrdersFiltered(ctx context.Context, status, channel, keyword, from, to string, limit, offset int) ([]*model.Order, error) { + q := `SELECT ` + orderColumns + ` FROM orders WHERE 1=1` + args := []any{} + if status != "" { + q += " AND status=?" + args = append(args, status) + } + if channel != "" { + q += " AND channel=?" + args = append(args, channel) + } + if keyword != "" { + q += " AND (out_trade_no LIKE ? OR gateway_no LIKE ? OR channel_tx_id LIKE ? OR subject LIKE ?)" + kw := "%" + keyword + "%" + args = append(args, kw, kw, kw, kw) + } + if from != "" { + q += " AND substr(created_at,1,10) >= ?" + args = append(args, from) + } + if to != "" { + q += " AND substr(created_at,1,10) <= ?" + args = append(args, to) + } + q += " ORDER BY id DESC LIMIT ? OFFSET ?" + args = append(args, limit, offset) + rows, err := s.db.QueryContext(ctx, q, args...) + if err != nil { + return nil, err + } + defer rows.Close() + out := []*model.Order{} + for rows.Next() { + o, err := scanOrder(rows) + if err != nil { + return nil, err + } + out = append(out, o) + } + return out, rows.Err() +} + +func (s *SQLiteStore) CountOrdersFiltered(ctx context.Context, status, channel, keyword, from, to string) (int64, error) { + q := `SELECT COUNT(*) FROM orders WHERE 1=1` + args := []any{} + if status != "" { + q += " AND status=?" + args = append(args, status) + } + if channel != "" { + q += " AND channel=?" + args = append(args, channel) + } + if keyword != "" { + q += " AND (out_trade_no LIKE ? OR gateway_no LIKE ? OR channel_tx_id LIKE ? OR subject LIKE ?)" + kw := "%" + keyword + "%" + args = append(args, kw, kw, kw, kw) + } + if from != "" { + q += " AND substr(created_at,1,10) >= ?" + args = append(args, from) + } + if to != "" { + q += " AND substr(created_at,1,10) <= ?" + args = append(args, to) + } + var n int64 + if err := s.db.QueryRowContext(ctx, q, args...).Scan(&n); err != nil { + return 0, err + } + return n, nil +} + +func (s *SQLiteStore) CountOrdersByChannel(ctx context.Context, status, channel string) (int64, error) { + q := `SELECT COUNT(*) FROM orders WHERE channel=?` + args := []any{channel} + if status != "" { + q += " AND status=?" + args = append(args, status) + } + var n int64 + if err := s.db.QueryRowContext(ctx, q, args...).Scan(&n); err != nil { + return 0, err + } + return n, nil +} + +func (s *SQLiteStore) SumAmountByChannel(ctx context.Context, status, channel string) (int64, error) { + q := `SELECT COALESCE(SUM(amount_minor),0) FROM orders WHERE channel=?` + args := []any{channel} + if status != "" { + q += " AND status=?" + args = append(args, status) + } + var n int64 + if err := s.db.QueryRowContext(ctx, q, args...).Scan(&n); err != nil { + return 0, err + } + return n, nil +} + +func (s *SQLiteStore) CountOrdersByChannelToday(ctx context.Context, channel, date string) (int64, error) { + var n int64 + err := s.db.QueryRowContext(ctx, + `SELECT COUNT(*) FROM orders WHERE channel=? AND substr(created_at,1,10)=?`, + channel, date).Scan(&n) + return n, err +} + +func (s *SQLiteStore) SumAmountByChannelToday(ctx context.Context, channel, date string) (int64, error) { + var n int64 + err := s.db.QueryRowContext(ctx, + `SELECT COALESCE(SUM(amount_minor),0) FROM orders WHERE channel=? AND status='TRADE_SUCCESS' AND substr(created_at,1,10)=?`, + channel, date).Scan(&n) + return n, err +} + +// DailyStatsByChannel 日终对账:按通道+状态+日期统计订单数与金额 +// status 传 "" 表示全部状态;date 格式 2006-01-02(与 created_at 前 10 位比对) +func (s *SQLiteStore) DailyStatsByChannel(ctx context.Context, channel, status, date string) (int64, int64, error) { + q := `SELECT COUNT(*), COALESCE(SUM(amount_minor),0) FROM orders + WHERE channel=? AND substr(created_at,1,10)=?` + args := []any{channel, date} + if status != "" { + q += " AND status=?" + args = append(args, status) + } + var count, amount int64 + if err := s.db.QueryRowContext(ctx, q, args...).Scan(&count, &amount); err != nil { + return 0, 0, err + } + return count, amount, nil +} + +// OrdersTrend 按日期范围统计订单/金额趋势。start/end 为 "YYYY-MM-DD";空则回退到近 90 天。 +func (s *SQLiteStore) OrdersTrend(ctx context.Context, start, end string) ([]DailyStat, error) { + // 解析范围:缺省回退近 90 天(订单表长期保留,趋势数据随时可查,仅图表默认范围受限) + from, err := time.Parse("2006-01-02", start) + if err != nil { + from = time.Now().AddDate(0, 0, -89) + } + to, err := time.Parse("2006-01-02", end) + if err != nil { + to = time.Now() + } + if from.After(to) { + from, to = to, from + } + // 上限防护:单次最多拉 370 天,避免超大范围拖垮图表 + if to.Sub(from) > 370*24*time.Hour { + from = to.AddDate(0, 0, -369) + } + fromStr, toStr := from.Format("2006-01-02"), to.Format("2006-01-02") + + rows, err := s.db.QueryContext(ctx, ` + SELECT substr(created_at, 1, 10) AS d, + COUNT(*) AS total, + SUM(CASE WHEN status='TRADE_SUCCESS' THEN 1 ELSE 0 END) AS succ, + COALESCE(SUM(CASE WHEN status='TRADE_SUCCESS' THEN amount_minor ELSE 0 END), 0) AS amt + FROM orders + WHERE substr(created_at, 1, 10) >= ? AND substr(created_at, 1, 10) <= ? + GROUP BY d ORDER BY d ASC`, fromStr, toStr) + if err != nil { + return nil, err + } + defer rows.Close() + + // 用 map 填充缺失日期 + byDate := make(map[string]DailyStat) + for rows.Next() { + var d DailyStat + if err := rows.Scan(&d.Date, &d.TotalOrders, &d.SuccessOrders, &d.AmountMinor); err != nil { + return nil, err + } + byDate[d.Date] = d + } + if err := rows.Err(); err != nil { + return nil, err + } + + // 填充完整日期序列(缺失的天补 0) + out := make([]DailyStat, 0, int(to.Sub(from)/24/time.Hour)+1) + for d := from; !d.After(to); d = d.AddDate(0, 0, 1) { + date := d.Format("2006-01-02") + if v, ok := byDate[date]; ok { + out = append(out, v) + } else { + out = append(out, DailyStat{Date: date}) + } + } + return out, nil +} + +func (s *SQLiteStore) MarkNotified(ctx context.Context, outTradeNo string, notifyCount int) error { + _, err := s.db.ExecContext(ctx, + `UPDATE orders SET notified=1, notify_count=?, notified_at=COALESCE(notified_at, ?) WHERE out_trade_no=?`, + notifyCount, nowStr(), outTradeNo) + return err +} + +// ---------- 资金流水 ---------- + +func (s *SQLiteStore) InsertEvent(ctx context.Context, orderID int64, eventType, channelTxID string, amountMinor int64, rawPayload string) error { + _, err := s.db.ExecContext(ctx, + `INSERT INTO payment_events (order_id, event_type, channel_tx_id, amount_minor, raw_payload, created_at) + VALUES (?, ?, ?, ?, ?, ?)`, + orderID, eventType, channelTxID, amountMinor, rawPayload, nowStr()) + return err +} + +func (s *SQLiteStore) ListEvents(ctx context.Context, orderID int64, limit int) ([]*PaymentEvent, error) { + rows, err := s.db.QueryContext(ctx, + `SELECT id, order_id, event_type, channel_tx_id, amount_minor, raw_payload, created_at + FROM payment_events WHERE order_id=? ORDER BY id DESC LIMIT ?`, orderID, limit) + if err != nil { + return nil, err + } + defer rows.Close() + out := []*PaymentEvent{} // 空数组而非 nil,避免 JSON 序列化为 null + for rows.Next() { + var e PaymentEvent + var created string + if err := rows.Scan(&e.ID, &e.OrderID, &e.EventType, &e.ChannelTxID, &e.AmountMinor, &e.RawPayload, &created); err != nil { + return nil, err + } + e.CreatedAt, _ = time.Parse(time.RFC3339Nano, created) + out = append(out, &e) + } + return out, rows.Err() +} + +// ---------- 回调任务(outbox) ---------- + +func (s *SQLiteStore) CreateNotifyTask(ctx context.Context, orderID int64, payload string) (int64, error) { + res, err := s.db.ExecContext(ctx, + `INSERT INTO notify_tasks (order_id, payload, status, next_retry_at, created_at, updated_at) + VALUES (?, ?, 'pending', ?, ?, ?)`, + orderID, payload, nowStr(), nowStr(), nowStr()) + if err != nil { + return 0, err + } + return res.LastInsertId() +} + +func (s *SQLiteStore) GetPendingNotifyTasks(ctx context.Context, limit int) ([]*NotifyTask, error) { + rows, err := s.db.QueryContext(ctx, + `SELECT id, order_id, payload, status, retry_count, next_retry_at, created_at, updated_at + FROM notify_tasks WHERE status='pending' AND next_retry_at <= ? + ORDER BY id ASC LIMIT ?`, nowStr(), limit) + if err != nil { + return nil, err + } + defer rows.Close() + return scanTasks(rows) +} + +// GetNotifyTask 按 id 查询单个回调任务(死信重发定位用) +func (s *SQLiteStore) GetNotifyTask(ctx context.Context, id int64) (*NotifyTask, error) { + row := s.db.QueryRowContext(ctx, + `SELECT id, order_id, payload, status, retry_count, next_retry_at, created_at, updated_at + FROM notify_tasks WHERE id=?`, id) + var t NotifyTask + var nextRetry, created, updated string + if err := row.Scan(&t.ID, &t.OrderID, &t.Payload, &t.Status, &t.RetryCount, &nextRetry, &created, &updated); err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, err + } + t.NextRetryAt, _ = time.Parse(time.RFC3339Nano, nextRetry) + t.CreatedAt, _ = time.Parse(time.RFC3339Nano, created) + t.UpdatedAt, _ = time.Parse(time.RFC3339Nano, updated) + return &t, nil +} + +// UpdateNotifyTaskPayload 更新回调任务 payload(死信重发时按订单最新 notify_url 重建) +func (s *SQLiteStore) UpdateNotifyTaskPayload(ctx context.Context, id int64, payload string) error { + _, err := s.db.ExecContext(ctx, + `UPDATE notify_tasks SET payload=?, updated_at=? WHERE id=?`, payload, nowStr(), id) + return err +} + +// DeleteNotifyTask 删除回调任务(彻底移除死信,确认放弃该笔通知) +func (s *SQLiteStore) DeleteNotifyTask(ctx context.Context, id int64) error { + _, err := s.db.ExecContext(ctx, `DELETE FROM notify_tasks WHERE id=?`, id) + return err +} + +func (s *SQLiteStore) ListNotifyTasks(ctx context.Context, status string, limit int) ([]*NotifyTask, error) { + rows, err := s.db.QueryContext(ctx, + `SELECT id, order_id, payload, status, retry_count, next_retry_at, created_at, updated_at + FROM notify_tasks WHERE (?='' OR status=?) ORDER BY id DESC LIMIT ?`, + status, status, limit) + if err != nil { + return nil, err + } + defer rows.Close() + return scanTasks(rows) +} + +func (s *SQLiteStore) CountNotifyTasks(ctx context.Context, status string) (int64, error) { + q := `SELECT COUNT(*) FROM notify_tasks` + args := []any{} + if status != "" { + q += " WHERE status=?" + args = append(args, status) + } + var n int64 + if err := s.db.QueryRowContext(ctx, q, args...).Scan(&n); err != nil { + return 0, err + } + return n, nil +} + +func scanTasks(rows *sql.Rows) ([]*NotifyTask, error) { + out := []*NotifyTask{} + for rows.Next() { + var t NotifyTask + var nextRetry, created, updated string + if err := rows.Scan(&t.ID, &t.OrderID, &t.Payload, &t.Status, &t.RetryCount, &nextRetry, &created, &updated); err != nil { + return nil, err + } + t.NextRetryAt, _ = time.Parse(time.RFC3339Nano, nextRetry) + t.CreatedAt, _ = time.Parse(time.RFC3339Nano, created) + t.UpdatedAt, _ = time.Parse(time.RFC3339Nano, updated) + out = append(out, &t) + } + return out, rows.Err() +} + +func (s *SQLiteStore) UpdateNotifyTask(ctx context.Context, id int64, status string, retryCount int, nextRetryAt time.Time) error { + _, err := s.db.ExecContext(ctx, + `UPDATE notify_tasks SET status=?, retry_count=?, next_retry_at=?, updated_at=? WHERE id=?`, + status, retryCount, nextRetryAt.Format(time.RFC3339Nano), nowStr(), id) + return err +} + +// ---------- 日志 ---------- + +func (s *SQLiteStore) InsertLog(ctx context.Context, level, scope, message, traceID, fields string) error { + _, err := s.db.ExecContext(ctx, + `INSERT INTO app_logs (level, scope, message, trace_id, fields, created_at) VALUES (?, ?, ?, ?, ?, ?)`, + level, scope, message, traceID, fields, nowStr()) + return err +} + +func (s *SQLiteStore) QueryLogs(ctx context.Context, level, scope string, limit, offset int) ([]*LogRecord, error) { + q := `SELECT id, level, scope, message, trace_id, fields, created_at FROM app_logs WHERE 1=1` + args := []any{} + if level != "" { + q += " AND LOWER(level)=LOWER(?)" + args = append(args, level) + } + if scope != "" { + q += " AND scope=?" + args = append(args, scope) + } + q += " ORDER BY id DESC LIMIT ? OFFSET ?" + args = append(args, limit, offset) + rows, err := s.db.QueryContext(ctx, q, args...) + if err != nil { + return nil, err + } + defer rows.Close() + out := []*LogRecord{} + for rows.Next() { + var l LogRecord + var created string + if err := rows.Scan(&l.ID, &l.Level, &l.Scope, &l.Message, &l.TraceID, &l.Fields, &created); err != nil { + return nil, err + } + l.CreatedAt, _ = time.Parse(time.RFC3339Nano, created) + out = append(out, &l) + } + return out, rows.Err() +} + +func (s *SQLiteStore) CountLogs(ctx context.Context, level, scope string) (int64, error) { + q := `SELECT COUNT(*) FROM app_logs WHERE 1=1` + args := []any{} + if level != "" { + q += " AND LOWER(level)=LOWER(?)" + args = append(args, level) + } + if scope != "" { + q += " AND scope=?" + args = append(args, scope) + } + var n int64 + if err := s.db.QueryRowContext(ctx, q, args...).Scan(&n); err != nil { + return 0, err + } + return n, nil +} + +// ---------- 系统设置 ---------- + +func (s *SQLiteStore) GetSetting(ctx context.Context, key string) (string, error) { + var v string + err := s.db.QueryRowContext(ctx, `SELECT value FROM settings WHERE key=?`, key).Scan(&v) + if err == sql.ErrNoRows { + return "", nil + } + return v, err +} + +func (s *SQLiteStore) SetSetting(ctx context.Context, key, value string) error { + _, err := s.db.ExecContext(ctx, + `INSERT INTO settings (key, value, updated_at) VALUES (?, ?, ?) + ON CONFLICT(key) DO UPDATE SET value=excluded.value, updated_at=excluded.updated_at`, + key, value, nowStr()) + return err +} + +func (s *SQLiteStore) DeleteSetting(ctx context.Context, key string) error { + _, err := s.db.ExecContext(ctx, `DELETE FROM settings WHERE key = ?`, key) + return err +} + +func (s *SQLiteStore) GetAllSettings(ctx context.Context) (map[string]string, error) { + rows, err := s.db.QueryContext(ctx, `SELECT key, value FROM settings`) + if err != nil { + return nil, err + } + defer rows.Close() + out := make(map[string]string) + for rows.Next() { + var k, v string + if err := rows.Scan(&k, &v); err != nil { + return nil, err + } + out[k] = v + } + return out, rows.Err() +} + +// ---------- 个人免签匹配查询 ---------- + +// orderColumns 常用 SELECT 列(与 scanOrder 对应) +const orderColumns = `id, out_trade_no, gateway_no, channel, channel_tx_id, account_id, pid, upstream_name, attach, amount_minor, + currency, subject, status, notify_url, return_url, qr_code, pay_url, payer, notified, notify_count, created_at, session_expires_at, paid_at, notified_at, + note_code, qr_token, device_id, buyer_key, fp_key` + +// GetOrderByNoteCode 按备注码查询未支付订单(精确匹配,个人免签主匹配) +// since: 只查该时间点之后创建的订单(5 分钟窗口) +func (s *SQLiteStore) GetOrderByNoteCode(ctx context.Context, noteCode string, since time.Time) (*model.Order, error) { + row := s.db.QueryRowContext(ctx, + `SELECT `+orderColumns+` FROM orders + WHERE note_code=? AND status=? AND created_at >= ? ORDER BY id LIMIT 1`, + noteCode, model.StatusWaitPay, since.Format(time.RFC3339)) + return scanOrder(row) +} + +// GetOrdersByAmount 查询某时间窗口内同金额的未支付订单(金额兜底匹配) +func (s *SQLiteStore) GetOrdersByAmount(ctx context.Context, amountMinor int64, since time.Time) ([]*model.Order, error) { + rows, err := s.db.QueryContext(ctx, + `SELECT `+orderColumns+` FROM orders + WHERE amount_minor=? AND status=? AND created_at >= ? ORDER BY id`, + amountMinor, model.StatusWaitPay, since.Format(time.RFC3339)) + if err != nil { + return nil, err + } + defer rows.Close() + out := []*model.Order{} + for rows.Next() { + o, err := scanOrder(rows) + if err != nil { + return nil, err + } + out = append(out, o) + } + return out, rows.Err() +} + +// GetRecentSettledByAmount 查询某时间窗口内同金额的已支付订单(重复捕获判定用) +func (s *SQLiteStore) GetRecentSettledByAmount(ctx context.Context, amountMinor int64, since time.Time, accountIDs []int64) ([]*model.Order, error) { + query := `SELECT ` + orderColumns + ` FROM orders + WHERE amount_minor=? AND status=? AND created_at >= ?` + args := []interface{}{amountMinor, model.StatusSuccess, since.Format(time.RFC3339)} + // 按当前设备可服务账户集合限缩:同金额但属其他设备/账户的已支付订单不纳入, + // 避免「通知栏已入账的那笔」被账单页误判为无订单收款。 + if len(accountIDs) > 0 { + placeholders := make([]string, 0, len(accountIDs)) + for _, id := range accountIDs { + placeholders = append(placeholders, "?") + args = append(args, id) + } + query += ` AND account_id IN (` + strings.Join(placeholders, ",") + `)` + } + query += ` ORDER BY id DESC LIMIT 2` + rows, err := s.db.QueryContext(ctx, query, args...) + if err != nil { + return nil, err + } + defer rows.Close() + out := []*model.Order{} + for rows.Next() { + o, err := scanOrder(rows) + if err != nil { + return nil, err + } + out = append(out, o) + } + return out, rows.Err() +} + +// GetOrderByQrToken 按套码 token 查询订单(/mq/{token} 跳转校验) +func (s *SQLiteStore) GetOrderByQrToken(ctx context.Context, qrToken string) (*model.Order, error) { + row := s.db.QueryRowContext(ctx, + `SELECT `+orderColumns+` FROM orders WHERE qr_token=? ORDER BY id LIMIT 1`, qrToken) + return scanOrder(row) +} + +// ListWaitPayByAccount 查询指定收款账户下所有待支付订单 +// 个人免签:监听端离线联动用(设备离线 → 其绑定账户的 WAIT_PAY 订单进异常池人工核实) +func (s *SQLiteStore) ListWaitPayByAccount(ctx context.Context, accountID int64) ([]*model.Order, error) { + rows, err := s.db.QueryContext(ctx, + `SELECT `+orderColumns+` FROM orders + WHERE account_id=? AND status=? ORDER BY id`, + accountID, model.StatusWaitPay) + if err != nil { + return nil, err + } + defer rows.Close() + out := []*model.Order{} + for rows.Next() { + o, err := scanOrder(rows) + if err != nil { + return nil, err + } + out = append(out, o) + } + return out, rows.Err() +} + +// ListWaitPayAll 查询全部待支付订单(WaitPayPool 启动重建 + 周期对账的数据源) +func (s *SQLiteStore) ListWaitPayAll(ctx context.Context) ([]*model.Order, error) { + rows, err := s.db.QueryContext(ctx, + `SELECT `+orderColumns+` FROM orders + WHERE status=? ORDER BY id`, + model.StatusWaitPay) + if err != nil { + return nil, err + } + defer rows.Close() + out := []*model.Order{} + for rows.Next() { + o, err := scanOrder(rows) + if err != nil { + return nil, err + } + out = append(out, o) + } + return out, rows.Err() +} + +// ListWaitPayByBuyer 查询指定买家环境(buyer_key)下所有待支付订单 +// 买家级串行:一个买家环境同时只允许一笔待支付订单(防海量刷单/错配攻击) +func (s *SQLiteStore) ListWaitPayByBuyer(ctx context.Context, buyerKey string) ([]*model.Order, error) { + if buyerKey == "" { + return nil, nil + } + rows, err := s.db.QueryContext(ctx, + `SELECT `+orderColumns+` FROM orders + WHERE buyer_key=? AND status=? ORDER BY id`, + buyerKey, model.StatusWaitPay) + if err != nil { + return nil, err + } + defer rows.Close() + out := []*model.Order{} + for rows.Next() { + o, err := scanOrder(rows) + if err != nil { + return nil, err + } + out = append(out, o) + } + return out, rows.Err() +} + +// ListWaitPayByBuyerOrFP 按买家环境(buyer_key)或浏览器指纹(fp_key)查询待支付订单 +// 支付页 JS 上报指纹后订单持有 fp_key;同环境换 IP 再下单时 buyer_key 不同, +// 但 fp_key 相同 → 串行检查需同时匹配两维(防指纹绑定后串行被绕过) +func (s *SQLiteStore) ListWaitPayByBuyerOrFP(ctx context.Context, buyerKey, fpKey string) ([]*model.Order, error) { + if buyerKey == "" && fpKey == "" { + return nil, nil + } + rows, err := s.db.QueryContext(ctx, + `SELECT `+orderColumns+` FROM orders + WHERE status=? AND (buyer_key=? OR (fp_key != '' AND fp_key=?)) ORDER BY id`, + model.StatusWaitPay, buyerKey, fpKey) + if err != nil { + return nil, err + } + defer rows.Close() + out := []*model.Order{} + for rows.Next() { + o, err := scanOrder(rows) + if err != nil { + return nil, err + } + out = append(out, o) + } + return out, rows.Err() +} + +func (s *SQLiteStore) Close() error { return s.db.Close() } diff --git a/internal/store/store.go b/internal/store/store.go new file mode 100755 index 0000000..b2a3c32 --- /dev/null +++ b/internal/store/store.go @@ -0,0 +1,164 @@ +// Package store 订单存储抽象:接口 + SQLite 实现 +package store + +import ( + "context" + "time" + + "paygateway/internal/model" +) + +// Store 存储接口:资金数据必须走这里(SQLite/PG 可切换) +type Store interface { + // 订单 + CreateOrder(ctx context.Context, o *model.Order) error + // DeleteOrder 删除订单;仅当状态为 WAIT_BUYER_PAY 时生效并返回 true + DeleteOrder(ctx context.Context, outTradeNo string) (bool, error) + GetOrder(ctx context.Context, outTradeNo string) (*model.Order, error) + GetOrderByID(ctx context.Context, id int64) (*model.Order, error) + // 条件更新:from -> to,影响行数=0 表示状态非法(幂等/防覆盖) + UpdateOrderStatus(ctx context.Context, outTradeNo, from, to string, set func(o *model.Order)) (bool, error) + // ReplaceOrder 买家替换:原子关闭 WAIT_PAY -> CLOSED 并在同一事务写 replaced 事件(见实现,关单与事件同事务持久化)。 + ReplaceOrder(ctx context.Context, outTradeNo string, orderID int64, amountMinor int64, rawJSON string) (bool, error) + // UpdateOrderAmount 更新订单金额(复用订单时以最新下单金额覆盖,防实付与订单金额不一致导致回调失败) + UpdateOrderAmount(ctx context.Context, outTradeNo string, amountMinor int64) error + // ListExpiredOrders 查询超过 before 时间仍未支付的订单(自动关闭用) + ListExpiredOrders(ctx context.Context, before time.Time) ([]*model.Order, error) + // 个人免签:按备注码查询未支付订单(匹配引擎用) + GetOrderByNoteCode(ctx context.Context, noteCode string, since time.Time) (*model.Order, error) + // 个人免签:查询某时间窗口内同金额的未支付订单(金额兜底匹配) + GetOrdersByAmount(ctx context.Context, amountMinor int64, since time.Time) ([]*model.Order, error) + // 个人免签:查询某时间窗口内同金额的已支付订单(重复捕获判定:通知栏/账本双链 5s 内同时捕获同一笔收款,败者零 WAIT_PAY 候选) + // accountIDs 限定待匹配的账户集合(当前设备可服务的账户);同金额但属其他设备/账户的已支付订单不纳入, + // 避免「通知栏已入账的那笔」被账单页误判为无订单收款。空集合 = 不过滤全查。 + GetRecentSettledByAmount(ctx context.Context, amountMinor int64, since time.Time, accountIDs []int64) ([]*model.Order, error) + // 个人免签:按套码 token 查询订单 + GetOrderByQrToken(ctx context.Context, qrToken string) (*model.Order, error) + // 个人免签:查询指定收款账户下所有待支付订单(监听端离线联动:进异常池人工核实) + ListWaitPayByAccount(ctx context.Context, accountID int64) ([]*model.Order, error) + // 个人免签:查询全部待支付订单(WaitPayPool 启动重建/对账用) + ListWaitPayAll(ctx context.Context) ([]*model.Order, error) + // 个人免签:查询指定买家环境(buyer_key)下所有待支付订单(买家级串行:一个环境同时只允许一笔待支付) + ListWaitPayByBuyer(ctx context.Context, buyerKey string) ([]*model.Order, error) + // 个人免签:按买家环境(buyer_key)或浏览器指纹(fp_key)查询待支付订单(指纹绑定后串行检查兜底) + ListWaitPayByBuyerOrFP(ctx context.Context, buyerKey, fpKey string) ([]*model.Order, error) + + // ---------- 系统设置 ---------- + GetSetting(ctx context.Context, key string) (string, error) + SetSetting(ctx context.Context, key, value string) error + DeleteSetting(ctx context.Context, key string) error + GetAllSettings(ctx context.Context) (map[string]string, error) + MarkNotified(ctx context.Context, outTradeNo string, notifyCount int) error + ListOrders(ctx context.Context, status, channel string, limit, offset int) ([]*model.Order, error) + CountOrders(ctx context.Context, status string) (int64, error) + SumAmount(ctx context.Context, status string) (int64, error) + // 增强查询:keyword 搜索订单号,from/to 时间范围(yyyy-mm-dd) + ListOrdersFiltered(ctx context.Context, status, channel, keyword, from, to string, limit, offset int) ([]*model.Order, error) + CountOrdersFiltered(ctx context.Context, status, channel, keyword, from, to string) (int64, error) + CountOrdersByChannel(ctx context.Context, status, channel string) (int64, error) + SumAmountByChannel(ctx context.Context, status, channel string) (int64, error) + CountOrdersByChannelToday(ctx context.Context, channel, date string) (int64, error) + SumAmountByChannelToday(ctx context.Context, channel, date string) (int64, error) + // 日终对账:按通道+状态+日期统计(RESILIENCE P2-1) + DailyStatsByChannel(ctx context.Context, channel, status, date string) (count, amount int64, err error) + // 按天趋势:返回最近 days 天(含今天)每日订单统计 + OrdersTrend(ctx context.Context, start, end string) ([]DailyStat, error) + + // 资金流水(只增不改,审计基础) + InsertEvent(ctx context.Context, orderID int64, eventType, channelTxID string, amountMinor int64, rawPayload string) error + ListEvents(ctx context.Context, orderID int64, limit int) ([]*PaymentEvent, error) + + // 回调任务(outbox) + CreateNotifyTask(ctx context.Context, orderID int64, payload string) (int64, error) + GetPendingNotifyTasks(ctx context.Context, limit int) ([]*NotifyTask, error) + GetNotifyTask(ctx context.Context, id int64) (*NotifyTask, error) + UpdateNotifyTask(ctx context.Context, id int64, status string, retryCount int, nextRetryAt time.Time) error + // UpdateNotifyTaskPayload 更新回调任务 payload(死信重发时按订单最新 notify_url 重建) + UpdateNotifyTaskPayload(ctx context.Context, id int64, payload string) error + // DeleteNotifyTask 删除回调任务(彻底移除死信,确认放弃该笔通知) + DeleteNotifyTask(ctx context.Context, id int64) error + ListNotifyTasks(ctx context.Context, status string, limit int) ([]*NotifyTask, error) + CountNotifyTasks(ctx context.Context, status string) (int64, error) + + // 日志落库(管理界面查询) + InsertLog(ctx context.Context, level, scope, message, traceID, fields string) error + QueryLogs(ctx context.Context, level, scope string, limit, offset int) ([]*LogRecord, error) + CountLogs(ctx context.Context, level, scope string) (int64, error) + + // ---------- 人工异常处理池 ---------- + CreateException(ctx context.Context, e *model.ExceptionOrder) error + GetException(ctx context.Context, id int64) (*model.ExceptionOrder, error) + GetExceptionByOutTradeNo(ctx context.Context, outTradeNo string) (*model.ExceptionOrder, error) + ListExceptions(ctx context.Context, phase, reason string, limit, offset int) ([]*model.ExceptionOrder, error) + CountExceptions(ctx context.Context, phase string) (int64, error) + UpdateException(ctx context.Context, id int64, set func(e *model.ExceptionOrder)) error + UpdateExceptionPhase(ctx context.Context, id int64, from, to string) (bool, error) + InsertExceptionLog(ctx context.Context, exceptionID int64, action, operator, detail string) error + ListExceptionLogs(ctx context.Context, exceptionID int64, limit int) ([]*model.ExceptionLog, error) + // UpdateOrderHandled 订单人工处理标记(人工池处理后永久留痕) + UpdateOrderHandled(ctx context.Context, outTradeNo string, exceptionID int64, action, handler, note string) error + + Close() error +} + +// DailyStat 按天订单统计(趋势图用) +type DailyStat struct { + Date string `json:"date"` // 2006-01-02 + TotalOrders int64 `json:"total_orders"` // 当日全部订单 + SuccessOrders int64 `json:"success_orders"` // 当日成功订单 + AmountMinor int64 `json:"amount_minor"` // 当日成功金额(分) +} + +// PaymentEvent 资金流水 +type PaymentEvent struct { + ID int64 `json:"id"` + OrderID int64 `json:"order_id"` + EventType string `json:"event_type"` + ChannelTxID string `json:"channel_tx_id"` + AmountMinor int64 `json:"amount_minor"` + RawPayload string `json:"raw_payload"` + CreatedAt time.Time `json:"created_at"` +} + +// NotifyTask 回调任务(outbox) +type NotifyTask struct { + ID int64 `json:"id"` + OrderID int64 `json:"order_id"` + Payload string `json:"payload"` + Status string `json:"status"` // pending / success / dead + RetryCount int `json:"retry_count"` + NextRetryAt time.Time `json:"next_retry_at"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// LogRecord 日志记录 +type LogRecord struct { + ID int64 `json:"id"` + Level string `json:"level"` + Scope string `json:"scope"` + Message string `json:"message"` + TraceID string `json:"trace_id"` + Fields string `json:"fields"` + CreatedAt time.Time `json:"created_at"` +} + +// LedgerEntry 账本条目 +type LedgerEntry struct { + ID int64 `json:"id"` + PrevHash string `json:"prev_hash"` + PayloadHash string `json:"payload_hash"` + EntryHash string `json:"entry_hash"` + Signature string `json:"signature"` + EventID int64 `json:"event_id"` + CreatedAt time.Time `json:"created_at"` +} + +// Snapshot 每日快照 +type Snapshot struct { + ID int64 + SnapshotDate string + LastHash string + Signature string + CreatedAt time.Time +} diff --git a/internal/store/upstreamdb.go b/internal/store/upstreamdb.go new file mode 100755 index 0000000..f23a78f --- /dev/null +++ b/internal/store/upstreamdb.go @@ -0,0 +1,246 @@ +// Package store 上游数据库:独立 upstreams.db,仅管理后台 API 可读写 +// +// 安全设计: +// - 独立 SQLite 文件,权限 0600 +// - 敏感字段(易支付密钥 key)以 AES-256-GCM 密文存储 +// - UpstreamStore 接口不暴露给 order/epay 等模块,仅在 admin 包内部使用 +// - 其他模块通过 UpstreamPool(内存缓存)获取解密后的上游数据 +package store + +import ( + "context" + "database/sql" + "fmt" + "os" + "time" + + _ "modernc.org/sqlite" + + "paygateway/internal/upstream" +) + +// UpstreamStore 上游存储接口(仅限 Admin API 使用) +type UpstreamStore interface { + ListUpstreams(ctx context.Context) ([]*upstream.UpstreamEncrypted, error) + GetUpstream(ctx context.Context, id int64) (*upstream.UpstreamEncrypted, error) + CreateUpstream(ctx context.Context, u *upstream.UpstreamEncrypted) (int64, error) + UpdateUpstream(ctx context.Context, u *upstream.UpstreamEncrypted) error + DeleteUpstream(ctx context.Context, id int64) error + SetUpstreamStatus(ctx context.Context, id int64, status string) error + InsertUpstreamLog(ctx context.Context, log *upstream.UpstreamLog) error + ListUpstreamLogs(ctx context.Context, upstreamID int64, limit int) ([]*upstream.UpstreamLog, error) + Close() error +} + +// UpstreamDB 独立上游数据库:仅存 upstreams + upstream_logs +// 文件权限 0600,WAL 模式,仅 Admin API 访问 +type UpstreamDB struct { + db *sql.DB +} + +// NewUpstreamDB 打开/创建独立上游数据库 +func NewUpstreamDB(path string) (*UpstreamDB, error) { + // 先创建空文件以确保可以设置权限 + fd, err := os.OpenFile(path, os.O_CREATE|os.O_RDWR, 0600) + if err != nil { + return nil, fmt.Errorf("创建上游数据库文件失败: %w", err) + } + fd.Close() + + // 强制设置权限为 0600(只有文件所有者可读写) + if err := os.Chmod(path, 0600); err != nil { + return nil, fmt.Errorf("设置上游数据库权限失败: %w", err) + } + + // 打开连接:WAL 模式防崩溃 + db, err := sql.Open("sqlite", path+"?_journal_mode=WAL&_busy_timeout=10000&_synchronous=FULL") + if err != nil { + return nil, fmt.Errorf("打开上游数据库失败: %w", err) + } + + // 连接池:单连接更安全 + db.SetMaxOpenConns(1) + db.SetMaxIdleConns(1) + + u := &UpstreamDB{db: db} + if err := u.migrate(); err != nil { + db.Close() + return nil, err + } + return u, nil +} + +// migrate 创建上游表 + 操作审计日志表 +func (u *UpstreamDB) migrate() error { + stmts := []string{ + `CREATE TABLE IF NOT EXISTS upstreams ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + pid TEXT NOT NULL UNIQUE, + key_enc TEXT NOT NULL DEFAULT '', + base_url TEXT NOT NULL DEFAULT '', + notify_domains TEXT NOT NULL DEFAULT '', + status TEXT NOT NULL DEFAULT 'enabled', + remark TEXT NOT NULL DEFAULT '', + created_at TEXT NOT NULL, + updated_at TEXT NOT NULL + )`, + `CREATE INDEX IF NOT EXISTS idx_upstreams_status ON upstreams(status)`, + + // 上游操作审计日志 + `CREATE TABLE IF NOT EXISTS upstream_logs ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + upstream_id INTEGER NOT NULL, + action TEXT NOT NULL, + detail TEXT NOT NULL DEFAULT '', + operator TEXT NOT NULL DEFAULT '', + created_at TEXT NOT NULL + )`, + `CREATE INDEX IF NOT EXISTS idx_upstream_logs_upstream ON upstream_logs(upstream_id)`, + + // WAL 模式 + `PRAGMA journal_mode=WAL`, + `PRAGMA synchronous=FULL`, + } + for _, stmt := range stmts { + if _, err := u.db.Exec(stmt); err != nil { + return fmt.Errorf("上游数据库迁移失败: %w", err) + } + } + return nil +} + +// ---------- 上游 CRUD ---------- + +// ListUpstreams 列出全部上游 +func (u *UpstreamDB) ListUpstreams(ctx context.Context) ([]*upstream.UpstreamEncrypted, error) { + rows, err := u.db.QueryContext(ctx, + `SELECT id, name, pid, key_enc, base_url, notify_domains, status, remark, created_at, updated_at + FROM upstreams ORDER BY id`) + if err != nil { + return nil, err + } + defer rows.Close() + + var out []*upstream.UpstreamEncrypted + for rows.Next() { + e, err := scanUpstreamEncrypted(rows) + if err != nil { + return nil, err + } + out = append(out, e) + } + return out, rows.Err() +} + +// GetUpstream 按 ID 获取上游 +func (u *UpstreamDB) GetUpstream(ctx context.Context, id int64) (*upstream.UpstreamEncrypted, error) { + row := u.db.QueryRowContext(ctx, + `SELECT id, name, pid, key_enc, base_url, notify_domains, status, remark, created_at, updated_at + FROM upstreams WHERE id=?`, id) + return scanUpstreamEncrypted(row) +} + +// GetUpstreamByPid 按 pid 获取上游(启动迁移 default 用) +func (u *UpstreamDB) GetUpstreamByPid(ctx context.Context, pid string) (*upstream.UpstreamEncrypted, error) { + row := u.db.QueryRowContext(ctx, + `SELECT id, name, pid, key_enc, base_url, notify_domains, status, remark, created_at, updated_at + FROM upstreams WHERE pid=?`, pid) + return scanUpstreamEncrypted(row) +} + +// CreateUpstream 创建上游,返回新 ID +func (u *UpstreamDB) CreateUpstream(ctx context.Context, up *upstream.UpstreamEncrypted) (int64, error) { + now := time.Now() + res, err := u.db.ExecContext(ctx, + `INSERT INTO upstreams (name, pid, key_enc, base_url, notify_domains, status, remark, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`, + up.Name, up.Pid, up.KeyEnc, up.BaseURL, up.NotifyDomains, up.Status, up.Remark, + now.Format(time.RFC3339Nano), now.Format(time.RFC3339Nano)) + if err != nil { + return 0, fmt.Errorf("创建上游失败: %w", err) + } + return res.LastInsertId() +} + +// UpdateUpstream 更新上游(全部字段覆盖) +func (u *UpstreamDB) UpdateUpstream(ctx context.Context, up *upstream.UpstreamEncrypted) error { + _, err := u.db.ExecContext(ctx, + `UPDATE upstreams SET name=?, pid=?, key_enc=?, base_url=?, notify_domains=?, status=?, remark=?, updated_at=? + WHERE id=?`, + up.Name, up.Pid, up.KeyEnc, up.BaseURL, up.NotifyDomains, up.Status, up.Remark, + time.Now().Format(time.RFC3339Nano), up.ID) + return err +} + +// DeleteUpstream 删除上游 +func (u *UpstreamDB) DeleteUpstream(ctx context.Context, id int64) error { + _, err := u.db.ExecContext(ctx, `DELETE FROM upstreams WHERE id=?`, id) + return err +} + +// SetUpstreamStatus 设置上游状态(启用/禁用) +func (u *UpstreamDB) SetUpstreamStatus(ctx context.Context, id int64, status string) error { + _, err := u.db.ExecContext(ctx, + `UPDATE upstreams SET status=?, updated_at=? WHERE id=?`, + status, time.Now().Format(time.RFC3339Nano), id) + return err +} + +// ---------- 上游操作审计日志 ---------- + +// InsertUpstreamLog 写入操作审计日志 +func (u *UpstreamDB) InsertUpstreamLog(ctx context.Context, log *upstream.UpstreamLog) error { + _, err := u.db.ExecContext(ctx, + `INSERT INTO upstream_logs (upstream_id, action, detail, operator, created_at) + VALUES (?, ?, ?, ?, ?)`, + log.UpstreamID, log.Action, log.Detail, log.Operator, time.Now().Format(time.RFC3339Nano)) + return err +} + +// ListUpstreamLogs 列出上游操作日志 +func (u *UpstreamDB) ListUpstreamLogs(ctx context.Context, upstreamID int64, limit int) ([]*upstream.UpstreamLog, error) { + rows, err := u.db.QueryContext(ctx, + `SELECT id, upstream_id, action, detail, operator, created_at + FROM upstream_logs WHERE upstream_id=? ORDER BY id DESC LIMIT ?`, upstreamID, limit) + if err != nil { + return nil, err + } + defer rows.Close() + + out := make([]*upstream.UpstreamLog, 0) + for rows.Next() { + var l upstream.UpstreamLog + var created string + if err := rows.Scan(&l.ID, &l.UpstreamID, &l.Action, &l.Detail, &l.Operator, &created); err != nil { + return nil, err + } + l.CreatedAt, _ = time.Parse(time.RFC3339Nano, created) + out = append(out, &l) + } + return out, rows.Err() +} + +// Close 关闭上游数据库 +func (u *UpstreamDB) Close() error { + return u.db.Close() +} + +// ---------- 辅助函数 ---------- + +// scanUpstreamEncrypted 扫描一行到 UpstreamEncrypted +func scanUpstreamEncrypted(s rowScanner) (*upstream.UpstreamEncrypted, error) { + var e upstream.UpstreamEncrypted + var createdAt, updatedAt string + err := s.Scan(&e.ID, &e.Name, &e.Pid, &e.KeyEnc, &e.BaseURL, + &e.NotifyDomains, &e.Status, &e.Remark, &createdAt, &updatedAt) + if err != nil { + if err == sql.ErrNoRows { + return nil, nil + } + return nil, err + } + e.CreatedAt, _ = time.Parse(time.RFC3339Nano, createdAt) + e.UpdatedAt, _ = time.Parse(time.RFC3339Nano, updatedAt) + return &e, nil +} diff --git a/internal/tunnel/cloudflared.go b/internal/tunnel/cloudflared.go new file mode 100644 index 0000000..af559f4 --- /dev/null +++ b/internal/tunnel/cloudflared.go @@ -0,0 +1,22 @@ +package tunnel + +import ( + "fmt" + "os" + "os/exec" + "runtime" +) + +func cloudflaredPath() (string, error) { + if path := os.Getenv("CLOUDFLARED_PATH"); path != "" { + if _, err := os.Stat(path); err == nil { + return path, nil + } + return "", fmt.Errorf("CLOUDFLARED_PATH 不存在: %s", path) + } + path, err := exec.LookPath("cloudflared") + if err != nil { + return "", fmt.Errorf("未找到 cloudflared,请安装后设置 CLOUDFLARED_PATH(平台: %s/%s): %w", runtime.GOOS, runtime.GOARCH, err) + } + return path, nil +} diff --git a/internal/tunnel/config.go b/internal/tunnel/config.go new file mode 100755 index 0000000..c761c45 --- /dev/null +++ b/internal/tunnel/config.go @@ -0,0 +1,114 @@ +package tunnel + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + "time" +) + +// Config 隧道配置 +type Config struct { + LocalPort int // 本地服务端口(默认 8080) + CallbackPath string // 回调路径(如 /alipay/notify) + StartTimeout time.Duration // 等待隧道就绪超时(默认 30s) + Connections int // cloudflared 连接数(默认 4,高并发可调大) + OnURLChange func(url string) // URL 变化回调(隧道重启换域名时自动调用) + // Hostname 固定域名(如 paygateway.de5.net)。 + // 非空 = 命名隧道模式(cloudflared tunnel run,需 ~/.cloudflared/paygateway.yml + paygateway.json 凭据); + // 空 = 默认快速隧道(trycloudflare.com 随机域名)。 + Hostname string + // ConfigPath 命名隧道的 cloudflared 配置文件路径(默认 ~/.cloudflared/paygateway.yml)�� + // 仅 Hostname 非空时生效。 + ConfigPath string +} + +// DefaultConfig 默认配置 +func DefaultConfig() *Config { + return &Config{ + LocalPort: 8080, + CallbackPath: "/alipay/notify", + StartTimeout: 30 * time.Second, + Connections: 4, + } +} + +// SetDefaultConfigPath 返回默认的 cloudflared 配置文件路径(~/.cloudflared/paygateway.yml)。 +// 外部(main.go)可调用本函数设置 Config.ConfigPath;留空则 tunnel.New 使用此默认路径。 +func SetDefaultConfigPath() string { + home, err := os.UserHomeDir() + if err != nil { + return "paygateway.yml" + } + return filepath.Join(home, ".cloudflared", "paygateway.yml") +} + +// EnsureCredentials 从环境变量生成 cloudflared 凭据和配置文件。 +// +// 环境变量: +// - CF_TUNNEL_HOSTNAME — 固定域名(如 paygateway.de5.net),空 = 跳过 +// - CF_TUNNEL_CREDENTIALS — tunnel 凭据 JSON 的 base64(由 setup-domain.sh 或首次启动写入 .env) +// +// 写入文件: +// +// ~/.cloudflared/paygateway.json (凭据) +// ~/.cloudflared/paygateway.yml (隧道配置) +// +// 幂等:文件已存在则跳过写入,不影响运行中隧道。换环境时只需复制 .env 即可。 +func EnsureCredentials() error { + hostname := os.Getenv("CF_TUNNEL_HOSTNAME") + b64 := os.Getenv("CF_TUNNEL_CREDENTIALS") + if hostname == "" || b64 == "" { + return nil // 快速隧道模式或未配置,跳过 + } + raw, err := base64.StdEncoding.DecodeString(strings.TrimSpace(b64)) + if err != nil { + return fmt.Errorf("CF_TUNNEL_CREDENTIALS base64 解码失败: %w", err) + } + // 解析凭据提取 TunnelID + var cred struct { + TunnelID string `json:"TunnelID"` + } + if err := json.Unmarshal(raw, &cred); err != nil { + return fmt.Errorf("CF_TUNNEL_CREDENTIALS JSON 解析失败: %w", err) + } + if cred.TunnelID == "" { + return fmt.Errorf("CF_TUNNEL_CREDENTIALS 缺少 TunnelID") + } + home, err := os.UserHomeDir() + if err != nil { + return fmt.Errorf("获取 HOME 目录失败: %w", err) + } + cfgDir := filepath.Join(home, ".cloudflared") + credFile := filepath.Join(cfgDir, "paygateway.json") + ymlFile := filepath.Join(cfgDir, "paygateway.yml") + + // 创建目录 + if err := os.MkdirAll(cfgDir, 0700); err != nil { + return fmt.Errorf("创建 %s 失败: %w", cfgDir, err) + } + // 写凭据文件(幂等) + if _, err := os.Stat(credFile); os.IsNotExist(err) { + if err := os.WriteFile(credFile, raw, 0600); err != nil { + return fmt.Errorf("写入 %s 失败: %w", credFile, err) + } + } + // 写配置文件(幂等) + if _, err := os.Stat(ymlFile); os.IsNotExist(err) { + yml := fmt.Sprintf(`tunnel: %s +credentials-file: %s + +ingress: + - hostname: %s + service: http://localhost:8080 + - service: http_status:404 +`, cred.TunnelID, credFile, hostname) + if err := os.WriteFile(ymlFile, []byte(yml), 0600); err != nil { + return fmt.Errorf("写入 %s 失败: %w", ymlFile, err) + } + } + return nil +} diff --git a/internal/tunnel/tunnel.go b/internal/tunnel/tunnel.go new file mode 100755 index 0000000..48acaf2 --- /dev/null +++ b/internal/tunnel/tunnel.go @@ -0,0 +1,348 @@ +// Package tunnel Cloudflare Tunnel 管理:启动隧道、获取回调 URL、健康检查 +// +// 使用 trycloudflare.com 快速隧道(无需认证),每次启动获取随机域名。 +// 启动后通过 GetCallbackURL() 获取当前可用回调地址。 +package tunnel + +import ( + "bufio" + "context" + "fmt" + "io" + "net/http" + "os" + "os/exec" + "path/filepath" + "regexp" + "strings" + "sync" + "time" + + "paygateway/internal/logger" +) + +// Tunnel 单个隧道实例 +type Tunnel struct { + url string // https://xxx.trycloudflare.com 或 https:// + hostname string // 命名隧道的原始 hostname(paygateway.de5.net);快速隧道为空 + localPort int + connections int // ha-connections 数(重启时保留,避免传 0 给 cloudflared) + cmd *exec.Cmd + cancel context.CancelFunc + healthy bool + done chan struct{} + mu sync.RWMutex + onURLChange func(url string) // URL 变化回调 +} + +// New 创建并启动隧道 +// 阻塞直到隧道就绪或超时 +func New(ctx context.Context, cfg *Config) (*Tunnel, error) { + if cfg == nil { + cfg = DefaultConfig() + } + + tunCtx, cancel := context.WithCancel(ctx) + + // 使用编译时嵌入的 cloudflared 二进制 + cfPath, err := cloudflaredPath() + if err != nil { + cancel() + return nil, fmt.Errorf("获取 cloudflared 路径失败: %w", err) + } + + var cmd *exec.Cmd + if cfg.Hostname != "" { + // 命名隧道模式:cloudflared tunnel run --config + configPath := cfg.ConfigPath + if configPath == "" { + configPath = defaultConfigPath() + } + if _, err := os.Stat(configPath); err != nil { + cancel() + return nil, fmt.Errorf("命名隧道配置文件不存在: %s(请先创建 %s,含 ingress 路由)", configPath, configPath) + } + cmd = exec.CommandContext(tunCtx, cfPath, + "tunnel", "--no-autoupdate", + "--config", configPath, + "run", + ) + } else { + // 快速隧道模式:trycloudflare 随机域名 + cmd = exec.CommandContext(tunCtx, cfPath, + "tunnel", "--no-autoupdate", + "--url", fmt.Sprintf("http://localhost:%d", cfg.LocalPort), + "--ha-connections", fmt.Sprintf("%d", cfg.Connections), + ) + } + + stderr, err := cmd.StderrPipe() + if err != nil { + cancel() + return nil, fmt.Errorf("创建 stderr pipe 失败: %w", err) + } + + if err := cmd.Start(); err != nil { + cancel() + return nil, fmt.Errorf("启动 cloudflared 失败: %w", err) + } + + t := &Tunnel{ + localPort: cfg.LocalPort, + connections: cfg.Connections, + hostname: cfg.Hostname, + cmd: cmd, + cancel: cancel, + done: make(chan struct{}), + onURLChange: cfg.OnURLChange, + } + + if cfg.Hostname != "" { + // 命名隧道:URL 固定为 https://,就绪判定 = cloudflared 注册成功 + t.url = "https://" + cfg.Hostname + t.healthy = true + // 异步等待连接注册成功(读到 Registered tunnel connection 即视为就绪) + go t.waitNamedReady(stderr) + logger.Info("tunnel", "命名隧道已启动(等待连接注册)", "url", t.url, "config", cfg.ConfigPath) + if cfg.OnURLChange != nil { + cfg.OnURLChange(t.url) + } + return t, nil + } + + // 异步读取 stderr 获取域名 + domainCh := make(chan string, 1) + go t.readDomain(stderr, domainCh) + + // 等待域名就绪 + select { + case url := <-domainCh: + t.mu.Lock() + t.url = url + t.healthy = true + t.mu.Unlock() + logger.Info("tunnel", "隧道就绪", "url", url) + // 通知 URL 变化 + if cfg.OnURLChange != nil { + cfg.OnURLChange(url) + } + return t, nil + + case <-time.After(cfg.StartTimeout): + cancel() + cmd.Wait() + return nil, fmt.Errorf("隧道启动超时(%v)", cfg.StartTimeout) + + case <-ctx.Done(): + cancel() + cmd.Wait() + return nil, ctx.Err() + } +} + +// waitNamedReady 等待命名隧道连接注册成功;注册失败/进程退出时标记不健康。 +// 注册成功后继续排空 stderr:cloudflared 持续写日志,若停止读取 pipe 缓冲区写满会阻塞 +// cloudflared 主循环,导致隧道假死(健康检查失败 → 无限重启)。 +func (t *Tunnel) waitNamedReady(stderr io.ReadCloser) { + defer close(t.done) + scanner := bufio.NewScanner(stderr) + scanner.Buffer(make([]byte, 1024*64), 1024*64) + registered := false + for scanner.Scan() { + line := scanner.Text() + if !registered { + if strings.Contains(line, "Registered tunnel connection") { + logger.Info("tunnel", "命名隧道连接已注册", "url", t.URL()) + registered = true + } + if strings.Contains(line, "no more connections active and exiting") || + strings.Contains(line, "Fatal") { + t.mu.Lock() + t.healthy = false + t.mu.Unlock() + logger.Warn("tunnel", "命名隧道连接异常退出", "line", line) + return + } + } + } +} + +// readDomain 从 cloudflared stderr 中解析 trycloudflare 隧道域名 +// 注意:cloudflared 早期日志会输出 api.trycloudflare.com(官方 API 地址), +// 它不是隧道域名,必须跳过,否则 notify_url 会变成 https://api.trycloudflare.com/... 导致回调 404 +func (t *Tunnel) readDomain(stderr io.ReadCloser, domainCh chan<- string) { + defer close(t.done) + re := regexp.MustCompile(`https://([a-z0-9-]+\.trycloudflare\.com)`) + scanner := bufio.NewScanner(stderr) + scanner.Buffer(make([]byte, 1024*64), 1024*64) + for scanner.Scan() { + line := scanner.Text() + if matches := re.FindStringSubmatch(line); len(matches) >= 2 { + host := matches[1] + // 排除 Cloudflare 官方控制面域名(非隧道地址) + if host == "api.trycloudflare.com" || host == "dash.trycloudflare.com" { + continue + } + select { + case domainCh <- "https://" + host: + default: + } + return + } + } +} + +// URL 返回隧道公网地址(如 https://xxx.trycloudflare.com) +func (t *Tunnel) URL() string { + t.mu.RLock() + defer t.mu.RUnlock() + return t.url +} + +// CallbackURL 返回完整的回调地址 +func (t *Tunnel) CallbackURL(callbackPath string) string { + t.mu.RLock() + defer t.mu.RUnlock() + if t.url == "" { + return "" + } + return t.url + callbackPath +} + +// Healthy 返回隧道是否健康 +func (t *Tunnel) Healthy() bool { + t.mu.RLock() + defer t.mu.RUnlock() + return t.healthy +} + +// Stop 停止隧道 +func (t *Tunnel) Stop() { + if t.cancel != nil { + t.cancel() + } + _ = t.cmd.Wait() +} + +// HealthLoop 定期健康检查,自动重启 +func (t *Tunnel) HealthLoop(ctx context.Context, interval time.Duration) { + ticker := time.NewTicker(interval) + defer ticker.Stop() + + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + t.checkHealth(ctx) + } + } +} + +// checkHealth 检查隧道健康。 +// 注意:不走公网 URL 自环(网络封锁/ISP 劫持会使公网 /health 永远不可达 → 误判不健康 → 无限重启风暴)。 +// 改为:本地 8080 可达 + cloudflared 进程存活 = 健康(进程死了才重启,URL 变才触发回调)。 +func (t *Tunnel) checkHealth(ctx context.Context) { + t.mu.Lock() + wasHealthy := t.healthy + url := t.url + t.mu.Unlock() + if url == "" { + return + } + + // 1. 本地源站可达(不经隧道,绕开公网链路) + localOK := t.checkLocal(ctx) + // 2. cloudflared 进程存活 + procOK := t.cmd != nil && t.cmd.Process != nil && t.cmd.ProcessState == nil + healthy := localOK && procOK + + t.mu.Lock() + t.healthy = healthy + t.mu.Unlock() + + if wasHealthy && !healthy { + logger.Warn("tunnel", "隧道不健康,准备重启", "url", url, "local_ok", localOK, "proc_ok", procOK) + go t.restart(ctx) + } else if !wasHealthy && healthy { + logger.Info("tunnel", "隧道恢复健康", "url", url) + } +} + +// checkLocal 检测本地源站是否可达(http://localhost:/health) +func (t *Tunnel) checkLocal(ctx context.Context) bool { + req, err := http.NewRequestWithContext(ctx, "GET", + fmt.Sprintf("http://localhost:%d/health", t.localPort), nil) + if err != nil { + return false + } + resp, err := http.DefaultClient.Do(req) + if err != nil { + return false + } + defer resp.Body.Close() + return resp.StatusCode == http.StatusOK +} + +func (t *Tunnel) restart(ctx context.Context) { + // 已不再持有旧进程,立即标记为不健康 + t.mu.Lock() + t.healthy = false + t.mu.Unlock() + // 停止旧进程(调用 cancel 后 cmd 会退出,Wait 清理僵尸) + if t.cancel != nil { + t.cancel() + _ = t.cmd.Wait() + } + time.Sleep(2 * time.Second) + + // 退避重试:失败后递增等待(30s/60s/120s/240s… 最高 300s),直到成功或主 ctx 取消 + backoff := 30 * time.Second + maxBackoff := 300 * time.Second + for { + newT, err := New(ctx, &Config{ + LocalPort: t.localPort, + StartTimeout: 30 * time.Second, + Connections: t.connections, + OnURLChange: t.onURLChange, + Hostname: t.hostname, + }) + if err == nil { + // 替换自身 + t.mu.Lock() + oldURL := t.url + t.url = newT.url + t.healthy = newT.healthy + t.cmd = newT.cmd + t.cancel = newT.cancel + t.done = newT.done + t.mu.Unlock() + logger.Info("tunnel", "隧道重启成功", "url", newT.url) + // URL 变了,通知回调 + if newT.url != oldURL && newT.url != "" && t.onURLChange != nil { + t.onURLChange(newT.url) + } + return + } + logger.Error("tunnel", "隧道重启失败,即将重试", "err", err, "backoff_sec", int(backoff.Seconds())) + select { + case <-time.After(backoff): + backoff *= 2 + if backoff > maxBackoff { + backoff = maxBackoff + } + case <-ctx.Done(): + logger.Warn("tunnel", "隧道退出重试(主 ctx 已取消)") + return + } + } +} + +// defaultConfigPath 返回命名隧道默认配置文件路径(与 gateway 同用户 ~/.cloudflared/paygateway.yml) +func defaultConfigPath() string { + home, err := os.UserHomeDir() + if err != nil { + return "paygateway.yml" + } + return filepath.Join(home, ".cloudflared", "paygateway.yml") +} diff --git a/internal/upstream/model.go b/internal/upstream/model.go new file mode 100755 index 0000000..0523422 --- /dev/null +++ b/internal/upstream/model.go @@ -0,0 +1,57 @@ +// Package upstream 上游对接方管理:多商户(易支付 pid+key)独立加密存储 +// +// 安全设计: +// - upstreams.db 仅管理后台 API 可读写,其他模块不得直接访问 +// - 敏感字段(易支付密钥 key)AES-256-GCM 加密存储(复用 account 包的加密能力) +// - UpstreamPool 是内存中的唯一数据源,其他模块(epay 验签/回调签名)通过它获取解密后的密钥 +// - 每个上游独立 pid + key:下单验签、回调签名均按 pid 路由,互不伪造 +package upstream + +import "time" + +// UpstreamStatus 上游状态 +type UpstreamStatus string + +const ( + StatusEnabled UpstreamStatus = "enabled" // 启用:可下单、可回调 + StatusDisabled UpstreamStatus = "disabled" // 禁用:拒绝下单 +) + +// Upstream 上游对接方(内存中的完整表示,含解密后的密钥) +// upstreams.db 中存储的是加密版本,Admin API 解密后推送到 UpstreamPool +type Upstream struct { + ID int64 `json:"id"` + Name string `json:"name"` // 上游名称:"new-api" + Pid string `json:"pid"` // 易支付商户ID(验签/签名依据,唯一) + Key string `json:"-"` // 易支付密钥(仅内存,不落盘明文) + BaseURL string `json:"base_url"` // 对外服务地址(该上游配置用的地址) + NotifyDomains string `json:"notify_domains"` // 回调目标域名白名单(逗号分隔,可选) + Status UpstreamStatus `json:"status"` // enabled / disabled + Remark string `json:"remark,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// UpstreamEncrypted upstreams.db 中存储的加密版本 +type UpstreamEncrypted struct { + ID int64 `json:"id"` + Name string `json:"name"` + Pid string `json:"pid"` + KeyEnc string `json:"key_enc"` // AES-256-GCM 加密后的 hex 密文 + BaseURL string `json:"base_url"` + NotifyDomains string `json:"notify_domains"` + Status string `json:"status"` + Remark string `json:"remark,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` +} + +// UpstreamLog 上游操作审计日志 +type UpstreamLog struct { + ID int64 `json:"id"` + UpstreamID int64 `json:"upstream_id"` + Action string `json:"action"` // create / update / delete / enable / disable + Detail string `json:"detail"` // 操作详情 + Operator string `json:"operator"` // 操作人 + CreatedAt time.Time `json:"created_at"` +} diff --git a/internal/upstream/pool.go b/internal/upstream/pool.go new file mode 100755 index 0000000..eca3454 --- /dev/null +++ b/internal/upstream/pool.go @@ -0,0 +1,120 @@ +// Package upstream 上游对接方管理:多商户(易支付 pid+key)独立加密存储 +// +// UpstreamPool 是各模块获取上游数据的唯一入口: +// - epay 协议层(main.go 验签):按 pid 查对应密钥 +// - order.Service:下单记录 pid/upstream_name;回调签名按订单 pid 取密钥 +// - Admin API:增删改查后同步到 pool +// +// 设计约束: +// - 不直接访问 upstreams.db(仅 Admin API 有权限) +// - 所有密钥在内存中解密,不落盘 +package upstream + +import ( + "sync" +) + +// UpstreamPool 内存上游池(线程安全) +// 负责:pid → Upstream 索引(验签/签名路由) +type UpstreamPool struct { + mu sync.RWMutex + byID map[int64]*Upstream // id → Upstream + byPid map[string]*Upstream // pid → Upstream(易支付验签/签名路由) +} + +// NewPool 创建空上游池 +func NewPool() *UpstreamPool { + return &UpstreamPool{ + byID: make(map[int64]*Upstream), + byPid: make(map[string]*Upstream), + } +} + +// ---------- 管理接口(仅 Admin API 调用)---------- + +// Add 添加上游到池中(Admin API 解密后调用) +func (p *UpstreamPool) Add(u *Upstream) { + p.mu.Lock() + defer p.mu.Unlock() + p.byID[u.ID] = u + if u.Pid != "" { + p.byPid[u.Pid] = u + } +} + +// Update 更新池中上游(pid 变更时重建索引) +func (p *UpstreamPool) Update(u *Upstream) { + p.mu.Lock() + defer p.mu.Unlock() + if old, ok := p.byID[u.ID]; ok { + if old.Pid != "" && old.Pid != u.Pid { + delete(p.byPid, old.Pid) + } + } + p.byID[u.ID] = u + if u.Pid != "" { + p.byPid[u.Pid] = u + } +} + +// Remove 从池中移除上游 +func (p *UpstreamPool) Remove(id int64) { + p.mu.Lock() + defer p.mu.Unlock() + if old, ok := p.byID[id]; ok { + if old.Pid != "" { + delete(p.byPid, old.Pid) + } + delete(p.byID, id) + } +} + +// SetStatus 设置上游状态 +func (p *UpstreamPool) SetStatus(id int64, status UpstreamStatus) { + p.mu.Lock() + defer p.mu.Unlock() + if u, ok := p.byID[id]; ok { + u.Status = status + } +} + +// ---------- 查询接口(业务模块使用)---------- + +// ByPid 按易支付商户ID查找上游(验签/签名路由) +// 返回 nil 表示该 pid 未登记 +func (p *UpstreamPool) ByPid(pid string) *Upstream { + p.mu.RLock() + defer p.mu.RUnlock() + return p.byPid[pid] +} + +// ByID 按 ID 查找上游 +func (p *UpstreamPool) ByID(id int64) *Upstream { + p.mu.RLock() + defer p.mu.RUnlock() + return p.byID[id] +} + +// All 返回全部上游(按 ID 排序,Admin 列表用) +func (p *UpstreamPool) All() []*Upstream { + p.mu.RLock() + defer p.mu.RUnlock() + out := make([]*Upstream, 0, len(p.byID)) + for _, u := range p.byID { + out = append(out, u) + } + // 按 ID 升序 + for i := 1; i < len(out); i++ { + for j := i; j > 0 && out[j].ID < out[j-1].ID; j-- { + out[j], out[j-1] = out[j-1], out[j] + } + } + return out +} + +// Count 上游总数 +func (p *UpstreamPool) Count() int { + p.mu.RLock() + defer p.mu.RUnlock() + return len(p.byID) +} diff --git a/internal/web/note_test.go b/internal/web/note_test.go new file mode 100644 index 0000000..74057f0 --- /dev/null +++ b/internal/web/note_test.go @@ -0,0 +1,66 @@ +package web + +import ( + "strings" + "testing" + "time" + + "paygateway/internal/model" +) + +func TestBuildPersonalQRPage_NoteCode(t *testing.T) { + o := &model.Order{OutTradeNo: "T1", NoteCode: "1234", AmountMinor: 1000} + html := BuildPersonalQRPage(o, "/qr/real?token=x", "personal.alipay", time.Now().Add(5*time.Minute)) + if !strings.Contains(html, "1234") { + t.Fatalf("支付页应包含备注码 1234") + } + if !strings.Contains(html, "请备注") { + t.Fatalf("支付页应包含备注提示文案") + } + // 无备注码时不渲染该区块 + o2 := &model.Order{OutTradeNo: "T2", AmountMinor: 1000} + h2 := BuildPersonalQRPage(o2, "/qr/real?token=y", "personal.alipay", time.Now()) + if strings.Contains(h2, "请备注") { + t.Fatalf("无备注码订单不应渲染备注提示") + } +} + +func TestBuildSuccessPage(t *testing.T) { + paidAt := time.Date(2026, 9, 12, 10, 30, 0, 0, time.Local) + o := &model.Order{OutTradeNo: "S1", AmountMinor: 2350, Channel: "personal.alipay", ReturnURL: "https://merchant.example/order/1", PaidAt: &paidAt} + html := BuildSuccessPage(o, o.Channel) + for _, want := range []string{"支付成功", "已支付成功", "S1", "23.50", "2026-09-12 10:30:00", "返回商户"} { + if !strings.Contains(html, want) { + t.Fatalf("成功页应包含 %q", want) + } + } + // 无 return_url 时不渲染返回商户按钮 + o2 := &model.Order{OutTradeNo: "S2", AmountMinor: 100} + h2 := BuildSuccessPage(o2, "merchant.wechat") + if strings.Contains(h2, "返回商户") { + t.Fatalf("无 return_url 订单不应渲染返回商户按钮") + } + if !strings.Contains(h2, "微信支付成功") { + t.Fatalf("微信通道成功页应显示微信品牌文案") + } +} + +func TestBuildQRPage_HasSuccessPolling(t *testing.T) { + o := &model.Order{OutTradeNo: "TQ1", AmountMinor: 100} + html := BuildQRPage(o, "alipay-content", "merchant.alipay") + for _, want := range []string{"/qr/status?out_trade_no=", "TRADE_SUCCESS", "document.write"} { + if !strings.Contains(html, want) { + t.Fatalf("商家二维码页应包含支付成功轮询 %q", want) + } + } +} + +func TestBuildPersonalQRPage_HasSuccessPolling(t *testing.T) { + o := &model.Order{OutTradeNo: "TP1", AmountMinor: 100, NoteCode: "1234"} + html := BuildPersonalQRPage(o, "/qr/real?token=x", "personal.alipay", time.Now().Add(5*time.Minute)) + for _, want := range []string{"/qr/status?out_trade_no=", "TRADE_SUCCESS", "document.write"} { + if !strings.Contains(html, want) { + t.Fatalf("个人免签支付页应包含支付成功轮询 %q", want) + } + } +} diff --git a/internal/web/pages.go b/internal/web/pages.go new file mode 100755 index 0000000..9042767 --- /dev/null +++ b/internal/web/pages.go @@ -0,0 +1,483 @@ +// Package web 支付页面渲染:二维码页 / 跳转中转页 +package web + +import ( + "encoding/base64" + "fmt" + "html" + "net/url" + "strconv" + "strings" + "time" + + "paygateway/internal/logger" + "paygateway/internal/model" + + qrcode "github.com/skip2/go-qrcode" +) + +// BuildQRPage 构建支付二维码展示页面(服务端生成 base64,不依赖 CDN) +// channelName: alipay / wechat,用于页面品牌文案 +func BuildQRPage(order *model.Order, qrCode, channelName string) string { + qrPNG, err := qrcode.Encode(qrCode, qrcode.Medium, 280) + if err != nil { + logger.Error("web", "生成二维码失败", "err", err) + qrPNG = []byte{} + } + qrBase64 := base64.StdEncoding.EncodeToString(qrPNG) + + // 根据通道选择品牌文案(支持 "alipay"、"merchant.alipay"、"personal.alipay" 等格式) + title := "请使用支付宝扫码支付" + sub := "打开支付宝扫一扫完成付款" + imgAlt := "支付宝收款二维码" + tip := "提示:测试环境请使用沙箱版支付宝扫码支付" + if strings.HasSuffix(channelName, "wechat") { + title = "请使用微信扫码支付" + sub = "打开微信扫一扫完成付款" + imgAlt = "微信收款二维码" + tip = "提示:请使用微信扫一扫完成付款" + } + + // 轮询 /qr/status:订单到达 TRADE_SUCCESS 后原地切换为成功视图(免跳转闪烁) + successDoc := buildSuccessDoc(order, "支付成功") + + return fmt.Sprintf(` + + + + +%s + + + +

+ + +`, title, title, sub, qrBase64, imgAlt, order.OutTradeNo, order.MoneyYuan(), tip, order.OutTradeNo, + order.OutTradeNo, strconv.Quote(successDoc)) +} + +// BuildPersonalQRPage 个人免签支付页:真实收款码 + 标题上方内嵌关单倒计时条 +// +// 转账提示含备注码(order.NoteCode):备注码是收款匹配引擎的主手段 +// (noteMatchFromPool 精确匹配 → 失败才回退金额/时间窗口), +// 用户转账时填写备注码可避免同金额订单串单。订单无备注码时该区块不渲染。 +// 收款方名称由用户的原生付款页展示(防劫持核对在支付 App 内完成)。 +// expireAt 为零时(未开启自动关单)不显示倒计时;倒计时归零自动跳转 /qr/expired 重新下单页。 +// 支付方式切换不在此页提供:上游换支付模式入口(同号不同 type)下单时服务端自动迁移订单。 +func BuildPersonalQRPage(order *model.Order, qrImageSrc, channelName string, expireAt time.Time) string { + provider := "支付宝" + scanHint := "打开支付宝扫一扫,向商家收款码转账" + imgAlt := "收款二维码" + if strings.HasSuffix(channelName, "wechat") { + provider = "微信" + scanHint = "打开微信扫一扫,向商家收款码转账" + imgAlt = "微信收款二维码" + } + title := "请使用" + provider + "扫码转账" + + // 备注码提示区块(仅在订单有备注码时渲染;无备注码=金额/时间窗口兜底匹配) + noteBlock := "" + if order.NoteCode != "" { + noteBlock = `
转账时请备注 ` + html.EscapeString(order.NoteCode) + `,用于快速确认到账
` + } + + // 倒计时开关:expireAt 为零则整块隐藏 + expireRFC3339 := "" + if !expireAt.IsZero() { + expireRFC3339 = expireAt.Format(time.RFC3339) + } + expiredURL := "/qr/expired?out_trade_no=" + url.QueryEscape(order.OutTradeNo) + "&return_url=" + url.QueryEscape(order.ReturnURL) + + return fmt.Sprintf(` + + + + +%s + + + +
+
支付剩余时间 --:--,超时订单将自动关闭
+

%s

+
%s
+
%s
+
订单号: %s
金额: ¥%s
+ %s + +
支付完成后若未到账/未升级套餐,请联系平台管理人员处理;请勿重复支付。
若提示备注填写过于频繁,请勿再备注、直接转账即可;或更换账户/稍等片刻再支付。
+
+ + +`, title, title, scanHint, qrImageSrc, imgAlt, + order.OutTradeNo, order.MoneyYuan(), noteBlock, order.OutTradeNo, expireRFC3339, strconv.Quote(expiredURL), + order.OutTradeNo, order.OutTradeNo, order.OutTradeNo) +} + +// BuildExpiredPage 订单过期页:倒计时归零后跳转至此。 +// 过期订单是终态(不允许手动复活,防僵尸订单刷单),仅引导返回上游重新下单; +// 但上游同号重提会让服务端复活订单 → 本页 JS 轮询 /qr/status,探测到复活自动跳回支付页。 +func BuildExpiredPage(provider, outTradeNo, returnURL string) string { + title := "订单已过期" + reorder := "javascript:history.back()" + btnText := "返回重新下单" + if returnURL != "" { + reorder = returnURL + } + return fmt.Sprintf(` + + + + +%s + + + +
+
⏰
+
订单已过期
+
未在限定时间内完成%s支付
订单已自动关闭,未产生任何扣款
+
+
订单号%s
+
状态已关闭
+
+
+ %s +
+
若您已完成转账但页面未更新,请稍候刷新查看
+
+ + +`, title, provider, outTradeNo, reorder, btnText, outTradeNo) +} + +// BuildSuccessPage 支付成功状态页:付款人完成支付后展示(跳转落地页)。 +// +// 该页为终态展示,仅由服务端在订单状态确认为 TRADE_SUCCESS 后渲染 +// (见 handleQrSuccess 校验),不可由付款人任意触发(防伪造"支付成功"页)。 +// 与 BuildExpiredPage 同套卡片风格,绿色成功主题。 +// channelName: merchant.alipay / personal.alipay / merchant.wechat / personal.wechat ... 决定品牌文案。 +func BuildSuccessPage(order *model.Order, channelName string) string { + badge := "支付成功" + if strings.HasSuffix(channelName, "wechat") { + badge = "微信支付成功" + } else if strings.HasSuffix(channelName, "alipay") { + badge = "支付宝支付成功" + } + return buildSuccessDoc(order, badge) +} + +// buildSuccessDoc 生成完整支付成功文档(供 /qr/success 落地页与支付页原地切换共用, +// 保证付款人无论走跳转还是轮询原地切换看到的一致成功界面)。 +func buildSuccessDoc(order *model.Order, badge string) string { + paidText := "" + if order.PaidAt != nil && !order.PaidAt.IsZero() { + paidText = order.PaidAt.Format("2006-01-02 15:04:05") + } + returnBtn := "" + if order.ReturnURL != "" { + returnBtn = `返回商户` + } + + return fmt.Sprintf(` + + + + +%s + + + +
+
✅
+
%s
+

已支付成功

+
您的订单已支付成功,交易已完成
+
+
订单号%s
+
支付金额¥%s
+
支付时间%s
+
状态支付成功
+
+ %s +
建议保存本页截图(订单号+付款凭据),有疑问时方便平台快速查单
商家将尽快为您发货,请留意订单状态
+
+ +`, badge, badge, + order.OutTradeNo, order.MoneyYuan(), paidText, returnBtn) +} + +// QRPng 生成二维码 PNG(280x280,Medium 纠错)。失败返回空切片。 +func QRPng(content string) []byte { + png, err := qrcode.Encode(content, qrcode.Medium, 280) + if err != nil { + logger.Error("web", "生成二维码 PNG 失败", "err", err) + return nil + } + return png +} + +// QRPngDataURI 生成二维码 data URI(token 套码模式支付页内嵌用) +func QRPngDataURI(content string) string { + png := QRPng(content) + if len(png) == 0 { + return "" + } + return "data:image/png;base64," + base64.StdEncoding.EncodeToString(png) +} + +// BuildRedirectPage 支付中转页:从本站跳转支付通道,携带正确 Referer +func BuildRedirectPage(order *model.Order, payURL, title string) string { + return fmt.Sprintf(` + + + + +正在跳转到支付宝... + + + +
+
+

%s

+
订单号: %s
金额: ¥%s
+ 立即支付 + 如果未自动跳转,请点击上方按钮 +
+ + +`, title, order.OutTradeNo, order.MoneyYuan(), payURL, payURL) +} + +// IsMobileUA 判断是否为移动端 User-Agent +func IsMobileUA(ua string) bool { + uaLower := strings.ToLower(ua) + mobileKeywords := []string{ + "mobile", "android", "iphone", "ipad", "ipod", + "windows phone", "blackberry", "opera mini", "iemobile", + } + for _, kw := range mobileKeywords { + if strings.Contains(uaLower, kw) { + return true + } + } + return false +} diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..0b9dd63 --- /dev/null +++ b/run.sh @@ -0,0 +1,32 @@ +#!/bin/bash +# 支付网关启动脚本 +# 自动读取 .env 配置,从密钥文件加载密钥 + +cd "$(dirname "$0")" + +# 加载 .env +set -a +source .env +set +a + +# 从密钥文件加载私钥和公钥 +if [ -f "$ALIPAY_PRIVATE_KEY_FILE" ]; then + export ALIPAY_PRIVATE_KEY=$(cat "$ALIPAY_PRIVATE_KEY_FILE" | tr -d '\n' | tr -d ' ') + echo "✅ 已加载应用私钥" +fi + +if [ -f "$ALIPAY_PUBLIC_KEY_FILE" ]; then + export ALIPAY_PUBLIC_KEY=$(cat "$ALIPAY_PUBLIC_KEY_FILE" | tr -d '\n' | tr -d ' ') + echo "✅ 已加载支付宝公钥" +fi + +echo "🚀 启动支付网关..." +echo " 监听: ${GATEWAY_ADDR:-:8080}" +echo " 数据库: ${ORDER_DB:-data/paygateway.db}" + +# 自动下载并嵌入 cloudflared 二进制(如已存在则跳过) +echo "" +go generate ./internal/tunnel/ + +echo "" +go run ./cmd/gateway/ \ No newline at end of file diff --git a/scripts/adb-soak.sh b/scripts/adb-soak.sh new file mode 100644 index 0000000..d583352 --- /dev/null +++ b/scripts/adb-soak.sh @@ -0,0 +1,65 @@ +#!/bin/bash +# adb 设备稳定性压测(平板入库前验证,模拟 paygateway 运行时调用面) +# +# 用法: ./adb-soak.sh [轮数] [间隔秒] +# serial: WiFi adb 为 host:5555(容器/裸跑同协议同路径);USB 直连为纯设备号 +# +# 每轮覆盖 adbmgr 实际用到的操作: get-state(连接口径)→ shell → wm size +# → screencap → uiautomator dump+cat,结尾输出成功率与平均时延对比。 +set -u +SERIAL=${1:?'需要 serial 参数(host:5555 或 USB 设备号)'} +N=${2:-10} +IV=${3:-3} + +ok_state=0; ok_shell=0; ok_size=0; ok_shot=0; ok_dump=0 +sum_state=0; sum_shell=0; sum_size=0; sum_shot=0; sum_dump=0 + +ms() { echo $(( ($2 - $1) / 1000000 )); } # 起止纳秒 → 毫秒 + +for i in $(seq 1 "$N"); do + t0=$(date +%s%N) + case "$SERIAL" in + *:*) adb connect "$SERIAL" >/dev/null 2>&1 ;; + esac + st=$(adb -s "$SERIAL" get-state 2>/dev/null) + d=$(ms "$t0" "$(date +%s%N)") + if [ "$st" != "device" ]; then + echo "[$i/$N] ❌ 设备不可达(connect+state ${d}ms)" + sleep "$IV"; continue + fi + ok_state=$((ok_state+1)); sum_state=$((sum_state+d)) + + t0=$(date +%s%N); out=$(adb -s "$SERIAL" shell echo soak-ok 2>/dev/null) + d=$(ms "$t0" "$(date +%s%N)"); sum_shell=$((sum_shell+d)) + [ "$out" = "soak-ok" ] && ok_shell=$((ok_shell+1)) + + t0=$(date +%s%N); sz=$(adb -s "$SERIAL" shell wm size 2>/dev/null | grep -o '[0-9]\+x[0-9]\+' | tail -1) + d=$(ms "$t0" "$(date +%s%N)") + [ -n "$sz" ] && { ok_size=$((ok_size+1)); sum_size=$((sum_size+d)); } + + t0=$(date +%s%N); adb -s "$SERIAL" exec-out screencap -p > /tmp/opencode/soak_shot.png 2>/dev/null + d=$(ms "$t0" "$(date +%s%N)") + [ -s /tmp/opencode/soak_shot.png ] && { ok_shot=$((ok_shot+1)); sum_shot=$((sum_shot+d)); } + + t0=$(date +%s%N) + adb -s "$SERIAL" shell uiautomator dump /sdcard/soak_ui.xml >/dev/null 2>&1 + adb -s "$SERIAL" shell cat /sdcard/soak_ui.xml > /tmp/opencode/soak_ui.xml 2>/dev/null + d=$(ms "$t0" "$(date +%s%N)") + grep -q '<' /tmp/opencode/soak_ui.xml && { ok_dump=$((ok_dump+1)); sum_dump=$((sum_dump+d)); } + + + echo "[$i/$N] ✅ 分辨率=${sz:-未取到} shell=${d}ms dump+读取=${d}ms 截图成功=${ok_shot} dump成功=${ok_dump}" + sleep "$IV" +done + +echo "============ 汇总(共 $N 轮,覆盖 paygateway 实际调用面)============" +[ "$ok_size" -gt 0 ] && printf "wm size : %d/%d 平均 %dms\n" "$ok_size" "$N" $((sum_size/ok_size)) +[ "$ok_shot" -gt 0 ] && printf "screencap : %d/%d 平均 %dms\n" "$ok_shot" "$N" $((sum_shot/ok_shot)) +[ "$ok_dump" -gt 0 ] && printf "ui-dump : %d/%d 平均 %dms\n" "$ok_dump" "$N" $((sum_dump/ok_dump)) +if [ "$ok_state" -gt 0 ]; then + avg_state=$((sum_state/ok_state)); avg_shell=$((sum_shell/ok_state)) + printf "connect/state : %d/%d 平均 %dms\n" "$ok_state" "$N" "$avg_state" + printf "shell : %d/%d 平均 %dms\n" "$ok_shell" "$N" "$avg_shell" +fi +echo "(时延由设备性能与 WiFi 决定——同一台设备在容器内与宿主机跑一遍结果应一致," \ + "即可证明网络桥接无额外损耗)" diff --git a/scripts/download-cloudflared.sh b/scripts/download-cloudflared.sh new file mode 100644 index 0000000..febfb29 --- /dev/null +++ b/scripts/download-cloudflared.sh @@ -0,0 +1,78 @@ +#!/bin/bash +# download-cloudflared.sh — 下载 cloudflared 二进制到 internal/tunnel/ 供 //go:embed 使用 +# +# 用法: +# go generate ./internal/tunnel/ # 自动调用 +# bash scripts/download-cloudflared.sh # 手动调用 +# +# 下载策略(自动回退): +# 1. GitHub 直连 +# 2. GITHUB_MIRROR 环境变量指定的镜像(如有设置) +# 3. 内置镜像列表(ghproxy.cc / ghproxy.net / gh.api.99988866.xyz) +# +# 支持平台: linux amd64/arm64, darwin amd64/arm64 + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +PROJECT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" +TARGET_DIR="$PROJECT_DIR/internal/tunnel" +TARGET_FILE="$TARGET_DIR/cloudflared" + +# 如果二进制已存在且可执行,跳过下载 +if [ -x "$TARGET_FILE" ]; then + echo "✓ cloudflared 已存在,跳过下载" + exit 0 +fi + +OS="${GOOS:-$(go env GOOS)}" +ARCH="${GOARCH:-$(go env GOARCH)}" + +case "$OS/$ARCH" in +linux/amd64) REL_PATH="cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64" ;; +linux/arm64) REL_PATH="cloudflare/cloudflared/releases/latest/download/cloudflared-linux-arm64" ;; +darwin/arm64) REL_PATH="cloudflare/cloudflared/releases/latest/download/cloudflared-darwin-arm64" ;; +darwin/amd64) REL_PATH="cloudflare/cloudflared/releases/latest/download/cloudflared-darwin-amd64" ;; +*) + echo "❌ 不支持的平台: $OS/$ARCH" + exit 1 + ;; +esac + +# 构建下载 URL 列表:环境变量镜像优先,内置镜像回退 +URLS=() +if [ -n "${GITHUB_MIRROR:-}" ]; then + URLS+=("${GITHUB_MIRROR%/}/https://github.com/$REL_PATH") +fi +URLS+=( + "https://ghproxy.cc/https://github.com/$REL_PATH" + "https://ghproxy.net/https://github.com/$REL_PATH" + "https://gh.api.99988866.xyz/https://github.com/$REL_PATH" + "https://github.com/$REL_PATH" +) + +echo "⬇️ 正在下载 cloudflared ($OS/$ARCH) ..." + +for url in "${URLS[@]}"; do + echo " 尝试: $url" + set +e + # 镜像站常见 SSL 证书问题,统一用 -k;直连 GitHub 不用 -k + if echo "$url" | grep -q "github.com/$REL_PATH$" && [ -z "${GITHUB_MIRROR:-}" ]; then + curl -sSL --fail --connect-timeout 10 --max-time 120 "$url" -o "$TARGET_FILE" 2>/dev/null + else + curl -sSL --fail --connect-timeout 10 --max-time 120 -k "$url" -o "$TARGET_FILE" 2>/dev/null + fi + rc=$? + set -e + if [ $rc -eq 0 ] && [ -s "$TARGET_FILE" ]; then + chmod +x "$TARGET_FILE" + echo "✓ 下载成功: $TARGET_FILE ($(du -h "$TARGET_FILE" | cut -f1))" + exit 0 + fi + echo " ✗ 失败,换下一个..." +done + +echo "❌ 所有下载源均失败,请手动下载:" +echo " https://github.com/$REL_PATH" +echo " 放到 $TARGET_FILE 并 chmod +x" +exit 1 diff --git a/scripts/download-scrcpy-server.sh b/scripts/download-scrcpy-server.sh new file mode 100644 index 0000000..953d973 --- /dev/null +++ b/scripts/download-scrcpy-server.sh @@ -0,0 +1,25 @@ +#!/bin/bash +# 下载 scrcpy-server.jar(编译时嵌入,go generate 调用) +# 需要网络;下载失败时提示手动放置 +set -e +DIR="$(cd "$(dirname "$0")/.." && pwd)" +TARGET="$DIR/internal/scrcpy/scrcpy-server.jar" + +if [ -f "$TARGET" ] && [ -s "$TARGET" ]; then + echo "scrcpy-server.jar 已存在,跳过下载" + exit 0 +fi + +# scrcpy v4.1 release 的 server jar +URL="https://github.com/Genymobile/scrcpy/releases/download/v4.1/scrcpy-server-v4.1" + +echo "下载 scrcpy-server v4.1 → $TARGET" +if curl -fsSL -o "$TARGET" "$URL" 2>/dev/null && [ -s "$TARGET" ]; then + echo "✅ 下载完成 ($(du -h "$TARGET" | cut -f1))" +else + rm -f "$TARGET" + echo "❌ 下载失败,请手动下载:" + echo " $URL" + echo " 放置到: $TARGET" + exit 1 +fi diff --git a/scripts/e2e_usb_realdevice.sh b/scripts/e2e_usb_realdevice.sh new file mode 100755 index 0000000..c526346 --- /dev/null +++ b/scripts/e2e_usb_realdevice.sh @@ -0,0 +1,150 @@ +#!/usr/bin/env bash +# USB 真机端到端测试:选真机 → 调管理后台 push-config 下发配置 → 跑 e2e_personal.py 下单→入账 +# +# 架构说明(listener-app 已下线,改为纯 adb 控制真机微信/支付宝账本 ledgermon): +# - 网关通过 adb 控制红米 K60 真机,ledgermon 读账本行匹配订单入账 +# - 不再有手机端 APP,所有授权/安装步骤已移除 +# - 本脚本负责:ADB 连接 → 管理后台登录 → 设备选择 → 配置下发 → 下单测试 +# +# 使用前置: +# 1. 真机开了 USB 调试 + 已 adb pair +# 2. 网关在跑(./gateway)+ cloudflare 隧道在跑(server_url 可达) +# 3. 管理后台已创建同名监听设备(账户池 → 监听设备,确保 adb_host/adb_port 字段已填真机 ip,或脚本会问 --device-id) +# +# 用法: +# scripts/e2e_usb_realdevice.sh # 自动选 adb device 第一个真机 +# scripts/e2e_usb_realdevice.sh --serial 4f2bdc30 # 指定真机 +# scripts/e2e_usb_realdevice.sh --device-id 91df2463... # 指定服务端已创建的 device_id +# scripts/e2e_usb_realdevice.sh --admin-url http://127.0.0.1:8081 --gateway-url http://127.0.0.1:8080 +# +# 真实收款流程:下单后展示备注码,在真机上完成真实收款;ledgermon 通过 adb 读取账本行 +# 匹配订单入账,订单变 TRADE_SUCCESS 即为闭环走过。 +set -euo pipefail +IFS=$'\n\t' + +# ---------- 参数 ---------- +SERIAL="" +DEVICE_ID="" +ADMIN_URL="${ADMIN_URL:-http://127.0.0.1:8081}" +GATEWAY_URL="${GATEWAY_URL:-http://127.0.0.1:8080}" +ADMIN_USER="${ADMIN_USER:-admin}" +ADMIN_PASS="${ADMIN_PASS:-paygateway123}" +PAY_AMOUNT="${PAY_AMOUNT:-0.66}" + +usage() { sed -n '2,30p' "$0" | sed 's/^# \{0,1\}//'; exit "${1:-0}"; } + +while [[ $# -gt 0 ]]; do + case "$1" in + --serial) SERIAL="$2"; shift 2;; + --device-id) DEVICE_ID="$2"; shift 2;; + --admin-url) ADMIN_URL="$2"; shift 2;; + --gateway-url) GATEWAY_URL="$2"; shift 2;; + --amount) PAY_AMOUNT="$2"; shift 2;; + -h|--help) usage 0;; + *) echo "未知参数: $1"; usage 1;; + esac +done + +[[ -n "$ADMIN_PASS" && "$ADMIN_PASS" == "paygateway123" ]] && echo "⚠️ 使用默认管理员密码(ADMIN_PASS 未设),若改过请环境变量传值。" + +ROOT="$(cd "$(dirname "$0")/.." && pwd)" + +echo +echo "==== [1/7] 选择 USB 真机 ====" +ADB_CMD=(adb) +if [[ -n "$SERIAL" ]]; then + ADB_CMD+=( -s "$SERIAL" ) + if ! "${ADB_CMD[@]}" get-state >/dev/null 2>&1; then + echo " ❌ 设备 --serial $SERIAL 不可达" + exit 1 + fi + echo " ✅ 使用指定 serial: $SERIAL" +else + mapfile -t DEVS < <(adb devices | awk 'NR>1 && $2=="device" {print $1}') + if [[ ${#DEVS[@]} -eq 0 ]]; then + echo " ❌ adb devices 列表为空(USB 调试没开?或者用 --serial 指定)" + adb devices + exit 1 + fi + if [[ ${#DEVS[@]} -gt 1 ]]; then + echo " ⚠️ 检测到多台设备,请用 --serial 指定其中一台:" + printf ' %s\n' "${DEVS[@]}" + exit 1 + fi + SERIAL="${DEVS[0]}" + ADB_CMD+=( -s "$SERIAL" ) + echo " ✅ 自动选用: $SERIAL" +fi + +echo +echo "==== [2/7] 管理后台登录 ====" +LOGIN_RESP=$(curl -sS -X POST "$ADMIN_URL/api/auth/login" \ + -H "Content-Type: application/json" \ + -d "{\"username\":\"$ADMIN_USER\",\"password\":\"$ADMIN_PASS\"}" ) +TOKEN=$(echo "$LOGIN_RESP" | python3 -c 'import sys,json; print(json.load(sys.stdin).get("data",{}).get("token",""))') +if [[ -z "$TOKEN" ]]; then + echo " ❌ 登录失败:$LOGIN_RESP" + exit 1 +fi +echo " ✅ 已获取 JWT" + +echo +echo "==== [3/7] 检查关键授权(电池白名单)===" +# 注:listener-app 已下线,通知使用权检查已移除 +# ledgermon 通过 adb 直连账本,无需 App 通知监听权限 + +WHITELIST_OK=$("${ADB_CMD[@]}" shell dumpsys deviceidle whitelist | head -5 || true) +if [[ -n "$WHITELIST_OK" ]]; then + echo " ✅ 电池优化白名单:已检查(见上方)" +else + echo " ⚠️ 电池优化白名单:无法检查(无 root 权限,跳过)" +fi + +echo +echo "==== [4/7] 选取监听设备 ====" +DEVS_RESP=$(curl -sS "$ADMIN_URL/api/personal/devices" -H "Authorization: Bearer $TOKEN") +if [[ -n "$DEVICE_ID" ]]; then + MATCHED=$(echo "$DEVS_RESP" | python3 -c " +import sys, json +data = json.load(sys.stdin).get('data',{}).get('devices',[]) +target = '$DEVICE_ID' +m = [d for d in data if d.get('device_id')==target] +print(json.dumps(m[0]) if m else '')") + if [[ -z "$MATCHED" ]]; then + echo " ❌ 指定 --device-id $DEVICE_ID 在管理后台不存在" + exit 1 + fi + echo " ✅ 使用 device_id=$DEVICE_ID" +else + PICK=$(echo "$DEVS_RESP" | python3 -c " +import sys, json +data = json.load(sys.stdin).get('data',{}).get('devices',[]) +if not data: sys.exit('无监听设备,请先在管理后台创建') +# 优先 alipay provider +alipay = [d for d in data if d.get('provider')=='alipay'] +pick = alipay[0] if alipay else data[0] +print(pick.get('device_id'))") + DEVICE_ID="$PICK" + echo " ✅ 自动选取 device_id=$DEVICE_ID" +fi + +echo +echo "==== [5/7] 通过管理后台 push-config 下发配置 ====" +PUSH_RESP=$(curl -sS -X POST "$ADMIN_URL/api/personal/devices/$DEVICE_ID/push-config" \ + -H "Authorization: Bearer $TOKEN" \ + --max-time 30 ) +echo " raw: $PUSH_RESP" +echo "$PUSH_RESP" | python3 -c " +import sys, json +r = json.load(sys.stdin) +d = r.get('data', r) +if r.get('error'): sys.exit(' ❌ 下发失败: ' + r['error']) +print(' ✅ 配置下发成功')" + +echo +echo "==== [6/7] 跑下单测试 ====" +echo " ⓘ ledgermon 通过 adb 读取真机账本行匹配订单,无需模拟推送" +echo " 下单后会展示备注码;在真机上完成真实收款即可" +# TODO: 待 ledgermon e2e 测试脚本就绪后替换 +echo " ⚠️ e2e_personal.py 已随 listener-app 下线而删除,请手动执行下单验证" +exit 0 diff --git a/scripts/setup-domain.sh b/scripts/setup-domain.sh new file mode 100755 index 0000000..e2fce2f --- /dev/null +++ b/scripts/setup-domain.sh @@ -0,0 +1,242 @@ +#!/bin/bash +# Cloudflare 固定域名 + 隧道一键初始化(全自动) +# +# 功能:自动创建/确认(全部幂等): +# 1. Cloudflare zone(paygateway.de5.net 全托管) +# 2. DNS CNAME 记录(proxied → 隧道) +# 3. hostname route(域名 → 隧道映射) +# 4. DNSHe 子域名(不存在时自动注册) + NS 委派给 Cloudflare +# 5. 等待 zone 激活 +# 6. Universal SSL 证书(自动签发,toggle 触发) +# 7. HTTPS 公网验证 +# +# 前置条件: +# 1. go build -o gateway ./cmd/gateway/ (编译产物) +# 2. .env 包含以下变量: +# CF_EMAIL, CF_GLOBAL_KEY, CF_TUNNEL_HOSTNAME, CF_TUNNEL_CREDENTIALS +# DNSHE_API_KEY, DNSHE_API_SECRET (de5.net 免费子域名场景) +# +# 使用: +# bash scripts/setup-domain.sh +# +# 换环境流程:复制 .env → go build → 本脚本 → ./start.sh +# 全新域名:把 CF_TUNNEL_HOSTNAME 换成新前缀(如 myapp.de5.net),脚本全自动注册+配置。 + +set -euo pipefail + +cd "$(dirname "$0")/.." + +# 加载 .env +if [ -f .env ]; then + set -a + source .env + set +a +fi + +: "${CF_EMAIL:?请设置 CF_EMAIL}" +: "${CF_GLOBAL_KEY:?请设置 CF_GLOBAL_KEY}" +: "${CF_TUNNEL_HOSTNAME:?请设置 CF_TUNNEL_HOSTNAME}" +: "${CF_TUNNEL_CREDENTIALS:?请设置 CF_TUNNEL_CREDENTIALS}" + +# 从凭据中提取 TunnelID +TUNNEL_ID=$(echo "$CF_TUNNEL_CREDENTIALS" | base64 -d 2>/dev/null | python3 -c "import sys,json; print(json.load(sys.stdin)['TunnelID'])" 2>/dev/null || echo "") +if [ -z "$TUNNEL_ID" ]; then + echo "❌ 无法从 CF_TUNNEL_CREDENTIALS 中提取 TunnelID,请确认格式正确" + exit 1 +fi + +CF_AUTH=(-H "X-Auth-Email: $CF_EMAIL" -H "X-Auth-Key: $CF_GLOBAL_KEY" -H "Content-Type: application/json") +echo "=== Cloudflare 初始化: $CF_TUNNEL_HOSTNAME (隧道 $TUNNEL_ID) ===" + +# 1. 确保 zone 存在 +echo "--- 1/7 检查 zone: $CF_TUNNEL_HOSTNAME ---" +ZONE_ID=$(curl -sS -m 15 "https://api.cloudflare.com/client/v4/zones?name=$CF_TUNNEL_HOSTNAME" "${CF_AUTH[@]}" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['result'][0]['id'] if d.get('result') else '')" 2>/dev/null || echo "") +if [ -z "$ZONE_ID" ]; then + echo " zone 不存在,正在创建..." + ZONE_ID=$(curl -sS -m 20 -X POST "https://api.cloudflare.com/client/v4/zones" \ + "${CF_AUTH[@]}" \ + -d "{\"name\":\"$CF_TUNNEL_HOSTNAME\",\"type\":\"full\"}" | \ + python3 -c "import sys,json; d=json.load(sys.stdin); print(d['result']['id'] if d.get('success') else '')" 2>/dev/null || echo "") + if [ -z "$ZONE_ID" ]; then + echo "❌ zone 创建失败(可能是 TLD 或重复),跳过" + else + echo " ✅ zone 已创建 (ID: $ZONE_ID)" + fi +else + echo " ✅ zone 已存在 (ID: $ZONE_ID)" +fi + +if [ -z "$ZONE_ID" ]; then + echo "❌ 无 zone ID,无法继续。请检查域名是否可被 Cloudflare 托管" + exit 1 +fi + +# 2. 确保 DNS CNAME 记录(proxied=true) +echo "--- 2/7 检查 CNAME 记录 ---" +EXISTING=$(curl -sS -m 15 "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records?type=CNAME&name=$CF_TUNNEL_HOSTNAME" "${CF_AUTH[@]}" | \ + python3 -c "import sys,json; d=json.load(sys.stdin); r=d.get('result',[]); print(r[0]['id'] if r else '')" 2>/dev/null || echo "") +if [ -z "$EXISTING" ]; then + echo " CNAME 不存在,创建中..." + curl -sS -m 15 -X POST "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/dns_records" \ + "${CF_AUTH[@]}" \ + -d "{\"type\":\"CNAME\",\"name\":\"@\",\"content\":\"$TUNNEL_ID.cfargotunnel.com\",\"proxied\":true}" > /dev/null + echo " ✅ CNAME 已创建 (proxied=true)" +else + echo " ✅ CNAME 已存在" +fi + +# 3. 确保 hostname route +echo "--- 3/7 检查 hostname route ---" +ACT_ID=$(curl -sS -m 10 "https://api.cloudflare.com/client/v4/accounts?limit=1" "${CF_AUTH[@]}" | python3 -c "import sys,json; print(json.load(sys.stdin)['result'][0]['id'])" 2>/dev/null || echo "") +if [ -n "$ACT_ID" ]; then + ROUTE=$(curl -sS -m 15 "https://api.cloudflare.com/client/v4/accounts/$ACT_ID/zerotrust/routes/hostname" "${CF_AUTH[@]}" | \ + python3 -c "import sys,json; d=json.load(sys.stdin); r=[x for x in d.get('result',[]) if x['hostname']=='$CF_TUNNEL_HOSTNAME']; print(r[0]['id'] if r else '')" 2>/dev/null || echo "") + if [ -z "$ROUTE" ]; then + echo " hostname route 不存在,创建中..." + curl -sS -m 15 -X POST "https://api.cloudflare.com/client/v4/accounts/$ACT_ID/zerotrust/routes/hostname" \ + "${CF_AUTH[@]}" \ + -d "{\"hostname\":\"$CF_TUNNEL_HOSTNAME\",\"tunnel_id\":\"$TUNNEL_ID\",\"tun_type\":\"cfd_tunnel\",\"comment\":\"setup-domain.sh\"}" > /dev/null + echo " ✅ hostname route 已创建" + else + echo " ✅ hostname route 已存在" + fi +fi + +# 4. DNSHe NS 委派(de5.net 免费子域名场景) +# de5.net 的 DNS 在 DNSHe,需把 paygateway.de5.net 的 NS 委派给 Cloudflare。 +# 若 DNSHE_API_KEY 已配置则自动处理;未配置则提示手动。 +echo "--- 4/7 DNSHe NS 委派 ---" +if [ -n "${DNSHE_API_KEY:-}" ] && [ -n "${DNSHE_API_SECRET:-}" ]; then + DNSHE_BASE="${DNSHE_API_BASE:-https://api005.dnshe.com}" + DNSHE_AUTH=(-H "X-API-Key: $DNSHE_API_KEY" -H "X-API-Secret: $DNSHE_API_SECRET") + # 提取子域前缀(paygateway.de5.net → paygateway) + SUB_PREFIX="${CF_TUNNEL_HOSTNAME%%.*}" + ROOT_DOMAIN="${CF_TUNNEL_HOSTNAME#*.}" + echo " 子域: $SUB_PREFIX (根域: $ROOT_DOMAIN)" + # 列出子域名,找 subdomain_id + SUB_ID=$(curl -sS -m 15 "${DNSHE_AUTH[@]}" \ + "https://${DNSHE_BASE#https://}/index.php?m=domain_hub&endpoint=subdomains&action=list" 2>/dev/null | \ + python3 -c "import sys,json; d=json.load(sys.stdin); r=[x for x in d.get('subdomains',[]) if x['full_domain']=='$CF_TUNNEL_HOSTNAME']; print(r[0]['id'] if r else '')" 2>/dev/null || echo "") + if [ -z "$SUB_ID" ]; then + # 子域名不存在 → 自动注册(DNSHe API 支持) + echo " 子域名 $CF_TUNNEL_HOSTNAME 不存在,自动注册..." + SUB_ID=$(curl -sS -m 20 -X POST "${DNSHE_AUTH[@]}" \ + "https://${DNSHE_BASE#https://}/index.php?m=domain_hub&endpoint=subdomains&action=create" \ + -H "Content-Type: application/json" \ + -d "{\"subdomain\":\"$SUB_PREFIX\",\"domain\":\"$ROOT_DOMAIN\"}" 2>/dev/null | \ + python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('subdomain_id',''))" 2>/dev/null || echo "") + if [ -n "$SUB_ID" ]; then + echo " ✅ 子域名已注册: $CF_TUNNEL_HOSTNAME (id=$SUB_ID)" + else + echo " ❌ 子域名注册失败(配额可能已满,DNSHe 免费额度 3 个)" + fi + fi + if [ -n "$SUB_ID" ]; then + # 取 Cloudflare 分配的 NS(kevin/reza.ns.cloudflare.com) + CF_NS=$(curl -sS -m 15 "https://api.cloudflare.com/client/v4/zones/$ZONE_ID" "${CF_AUTH[@]}" 2>/dev/null | \ + python3 -c "import sys,json; r=json.load(sys.stdin).get('result',{}); ns=r.get('name_servers',[]); print(' '.join(ns) if ns else '')" 2>/dev/null || echo "") + if [ -z "$CF_NS" ]; then + echo " ⚠️ 无法获取 Cloudflare NS,跳过 DNSHe 自动委派" + else + echo " Cloudflare NS: $CF_NS" + # 检查当前 NS 记录是否已是 Cloudflare 的 + NS_CHECK=$(curl -sS -m 15 "${DNSHE_AUTH[@]}" \ + "https://${DNSHE_BASE#https://}/index.php?m=domain_hub&endpoint=dns_records&action=list&subdomain_id=$SUB_ID" 2>/dev/null | \ + python3 -c " +import sys,json +d=json.load(sys.stdin) +ns1,ns2='$CF_NS'.split()[:2] +ok=all(any(r.get('type')=='NS' and r.get('content')==ns for r in d.get('records',[])) for ns in (ns1,ns2)) +print('ok' if ok else 'need') +" 2>/dev/null || echo "need") + if [ "$NS_CHECK" = "ok" ]; then + echo " ✅ NS 委派已就绪 ($CF_NS)" + else + echo " NS 委派缺失,自动配置中..." + # 删除现有 CNAME/非 NS 记录(NS 委派与 CNAME 冲突) + curl -sS -m 15 "${DNSHE_AUTH[@]}" \ + "https://${DNSHE_BASE#https://}/index.php?m=domain_hub&endpoint=dns_records&action=list&subdomain_id=$SUB_ID" 2>/dev/null | \ + python3 -c " +import sys,json +d=json.load(sys.stdin) +for r in d.get('records',[]): + print(r.get('id')) +" 2>/dev/null | while read rid; do + curl -sS -m 15 -X POST "${DNSHE_AUTH[@]}" \ + "https://${DNSHE_BASE#https://}/index.php?m=domain_hub&endpoint=dns_records&action=delete" \ + -H "Content-Type: application/json" \ + -d "{\"record_id\":\"$rid\"}" > /dev/null 2>&1 + done + # 添加两条 NS 记录 + for ns in $CF_NS; do + curl -sS -m 20 -X POST "${DNSHE_AUTH[@]}" \ + "https://${DNSHE_BASE#https://}/index.php?m=domain_hub&endpoint=dns_records&action=create" \ + -H "Content-Type: application/json" \ + -d "{\"subdomain_id\": $SUB_ID, \"type\": \"NS\", \"content\": \"$ns\", \"ttl\": 600}" > /dev/null 2>&1 + done + echo " ✅ NS 委派已配置: $CF_NS" + fi + fi + else + echo " ⚠️ DNSHe 未找到子域名 $CF_TUNNEL_HOSTNAME,跳过(请确认已注册)" + fi +else + NS_HINT=$(curl -sS -m 10 "https://api.cloudflare.com/client/v4/zones/$ZONE_ID" "${CF_AUTH[@]}" 2>/dev/null | \ + python3 -c "import sys,json; print(' '.join(json.load(sys.stdin).get('result',{}).get('name_servers',[])))" 2>/dev/null || echo "") + echo " ⚠️ 未配置 DNSHE_API_KEY/SECRET,跳过自动委派" + echo " 如需手动:dns.he.net → $CF_TUNNEL_HOSTNAME → NS 指向 $NS_HINT" +fi + +# 5. 确保 DNS 委派传播 + zone active +echo "--- 5/7 等待 zone 激活 (最多 60s) ---" +for i in $(seq 1 12); do + STATUS=$(curl -sS -m 10 "https://api.cloudflare.com/client/v4/zones/$ZONE_ID" "${CF_AUTH[@]}" | \ + python3 -c "import sys,json; print(json.load(sys.stdin)['result']['status'])" 2>/dev/null || echo "pending") + if [ "$STATUS" = "active" ]; then + echo " ✅ zone 已激活" + break + fi + sleep 5 +done + +# 6. 确保 Universal SSL +echo "--- 6/7 检查 Universal SSL ---" +CERT=$(curl -sS -m 15 "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/ssl/certificate_packs?status=all" "${CF_AUTH[@]}" | \ + python3 -c "import sys,json; d=json.load(sys.stdin); r=[x for x in d.get('result',[]) if x.get('type')=='universal' and x.get('status')=='active']; print('ok' if r else '')" 2>/dev/null || echo "") +if [ -z "$CERT" ]; then + echo " SSL 未签发,尝试 toggle 触发..." + curl -sS -m 10 -X PATCH "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/ssl/universal/settings" \ + "${CF_AUTH[@]}" -d '{"enabled":false}' > /dev/null 2>&1 + sleep 10 + curl -sS -m 10 -X PATCH "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/ssl/universal/settings" \ + "${CF_AUTH[@]}" -d '{"enabled":true}' > /dev/null 2>&1 + echo " ⏳ Universal SSL toggle 完成,等待签发(通常 1-5 分钟)..." + for i in $(seq 1 30); do + STATUS=$(curl -sS -m 10 "https://api.cloudflare.com/client/v4/zones/$ZONE_ID/ssl/certificate_packs?status=all" "${CF_AUTH[@]}" | \ + python3 -c "import sys,json; d=json.load(sys.stdin); r=[x for x in d.get('result',[]) if x.get('status')=='active']; print(r[0]['status'] if r else 'pending')" 2>/dev/null || echo "pending") + if [ "$STATUS" = "active" ]; then + echo " ✅ Universal SSL 已签发!" + break + fi + sleep 10 + done +else + echo " ✅ Universal SSL 已签发" +fi + +# 7. 验证 HTTPS +echo "--- 7/7 验证 HTTPS ---" +sleep 5 +HTTP_CODE=$(curl -sS -m 15 -o /dev/null -w "%{http_code}" "https://$CF_TUNNEL_HOSTNAME/health" 2>/dev/null || echo "000") +if [ "$HTTP_CODE" = "200" ]; then + echo " ✅ https://$CF_TUNNEL_HOSTNAME/health → HTTP 200" + echo "" + echo "============================================" + echo " 初始化完成! 固定域名已就绪" + echo " URL: https://$CF_TUNNEL_HOSTNAME" + echo " 隧道: $TUNNEL_ID" + echo "============================================" +else + echo " ⚠️ HTTPS 返回 HTTP $HTTP_CODE,可能需等待 DNS 传播" + echo " 隧道进程需手动确认: ps aux | grep cloudflared" +fi diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..fc465b4 --- /dev/null +++ b/start.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# 支付网关启动脚本(生产环境裸跑场景,建议优先用 systemd:deploy/paygateway.service) +# +# 加载 .env(ACCOUNT_MASTER_KEY / GATEWAY_ADDR / 数据库路径等基础设施配置), +# 然后以编译好的二进制启动服务。带 pid 文件 + 已运行检查,避免重复启动。 + +cd "$(dirname "$0")" + +PID_FILE="./.gateway.pid" + +# 加载 .env +if [ -f .env ]; then + set -a + source .env + set +a +fi + +# 已运行检查:通过 pid 文件 + 进程存活双重验证 +if [ -f "$PID_FILE" ]; then + OLD_PID=$(cat "$PID_FILE" 2>/dev/null || echo "") + if [ -n "$OLD_PID" ] && kill -0 "$OLD_PID" 2>/dev/null; then + echo "❌ gateway 已在运行(pid=$OLD_PID,pid 文件 $PID_FILE)" >&2 + echo " 如需停止:kill $OLD_PID && rm $PID_FILE" >&2 + echo " 如需重启:先停再启,或用 ./stop.sh" >&2 + exit 1 + else + # pid 文件在但进程已死:清理残留 + rm -f "$PID_FILE" + fi +fi + +# 后台启动;保留 stderr 以便启动失败时能看到 +nohup ./gateway > /dev/null 2>&1 & +PID=$! +echo "$PID" > "$PID_FILE" + +# 等待短秒数验证进程未立刻退出(端口冲突 / 配置错误会快速 crash) +sleep 1 +if ! kill -0 "$PID" 2>/dev/null; then + rm -f "$PID_FILE" + echo "❌ gateway 启动后 1 秒内退出,请检查日志:tail -50 logs/paygateway.log" >&2 + exit 1 +fi + +echo "$PID" diff --git a/status.sh b/status.sh new file mode 100755 index 0000000..6e2e74b --- /dev/null +++ b/status.sh @@ -0,0 +1,20 @@ +#!/bin/bash +# 支付网关状态脚本(与 start.sh / .gateway.pid 配套;systemd 部署请用 systemctl status) +cd "$(dirname "$0")" + +PID_FILE="./.gateway.pid" + +if [ -f "$PID_FILE" ]; then + PID=$(cat "$PID_FILE" 2>/dev/null || echo "") + if [ -n "$PID" ] && kill -0 "$PID" 2>/dev/null; then + STARTED=$(ps -o lstart= -p "$PID" 2>/dev/null | sed 's/^ *//') + ELAPSED=$(ps -o etime= -p "$PID" 2>/dev/null | sed 's/^ *//') + echo "✅ 运行中 pid=$PID 启动于 $STARTED 已运行 $ELAPSED" + echo " 监听端口:$(ss -lntp 2>/dev/null | grep -E "pid=$PID" | awk '{print $4}' | tr '\n' ' ')" + exit 0 + fi +fi + +echo "⚠️ 未运行(pid 文件缺失或进程已死)" +echo " 通过 pgrep 兜底检查:$(pgrep -af 'paygateway/gateway' | head -1 || echo '无')" +exit 1 diff --git a/stop.sh b/stop.sh new file mode 100755 index 0000000..900dcf6 --- /dev/null +++ b/stop.sh @@ -0,0 +1,41 @@ +#!/bin/bash +# 支付网关停止脚本(与 start.sh / .gateway.pid 配套;systemd 部署请用 systemctl stop) +cd "$(dirname "$0")" + +PID_FILE="./.gateway.pid" + +if [ ! -f "$PID_FILE" ]; then + echo "无 pid 文件 $PID_FILE(可能未用 start.sh 启动,手动找进程用 pgrep -af 'paygateway/gateway')" + exit 0 +fi + +PID=$(cat "$PID_FILE" 2>/dev/null || echo "") +if [ -z "$PID" ]; then + rm -f "$PID_FILE" + echo "pid 文件为空,已清理" + exit 0 +fi + +if ! kill -0 "$PID" 2>/dev/null; then + rm -f "$PID_FILE" + echo "进程 $PID 已不在,pid 文件已清理" + exit 0 +fi + +# SIGTERM 优雅停止,等待最多 10s +kill "$PID" 2>/dev/null +for i in $(seq 1 50); do + if ! kill -0 "$PID" 2>/dev/null; then + break + fi + sleep 0.2 +done + +if kill -0 "$PID" 2>/dev/null; then + echo "10s 后仍未退出,发送 SIGKILL" + kill -9 "$PID" 2>/dev/null + sleep 0.3 +fi + +rm -f "$PID_FILE" +echo "已停止 $PID" diff --git a/web/admin/DESIGN.md b/web/admin/DESIGN.md new file mode 100644 index 0000000..b58c66d --- /dev/null +++ b/web/admin/DESIGN.md @@ -0,0 +1,132 @@ +# PayGateway Admin — Design System + +## 0. Reference Log +- **Primary reference**: Linear (clean, minimal, professional SaaS) +- **Secondary**: Vercel (breathable layout, typography), Supabase (subtle gradients, warm tones) +- **Tertiary**: Stripe (data density, clarity) + +## 1. Design Tokens + +### Color Palette + +#### Light Theme +``` +--bg-base: #f8f9fa (page background, very light slate) +--bg-surface: #ffffff (cards, panels) +--bg-subtle: #f1f3f5 (code blocks, stat backgrounds) +--bg-hover: rgba(0,0,0,0.04) (hover state backgrounds) + +--border: #e9ecef (card borders, dividers) +--border-strong:#dee2e6 (active borders) + +--text-primary: #1a1d21 (headings, primary text - not pure black) +--text-body: #3f4249 (body text - softer than black) +--text-muted: #697386 (labels, captions, secondary info) +--text-placeholder: #9ca3af (input placeholders) + +--accent: #5c6ac4 (indigo primary - Linear style, not generic blue) +--accent-hover: #4959b0 +--accent-light: #f0f1fb +--accent-gradient: linear-gradient(135deg, #5c6ac4, #7c7fe4) + +--success: #00875a (deep green) +--success-bg: #e3fcef +--warning: #de8000 (deep amber) +--warning-bg: #fff4e5 +--danger: #de350b (deep red) +--danger-bg: #ffebe6 +--info: #0065ff (clean blue) +--info-bg: #e5f0ff +``` + +#### Dark Theme +``` +--bg-base: #0d0f14 (deep navy black) +--bg-surface: #151820 (cards - slightly lighter than bg) +--bg-subtle: #1e2432 (code blocks, stat backgrounds) +--bg-hover: rgba(255,255,255,0.06) + +--border: #2a2e3b (card borders) +--border-strong: #3d4250 + +--text-primary: #e8eaed +--text-body: #c4c9d4 +--text-muted: #7c8194 +--text-placeholder: #525968 + +--accent: #818cf8 (lighter indigo for dark bg) +--accent-hover: #a5b4fc +--accent-light: #1e2432 +--accent-gradient: linear-gradient(135deg, #818cf8, #6366f1) +``` + +### Typography +- **Primary font**: `"Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif` +- **Mono font**: `"JetBrains Mono", "Fira Code", "Consolas", monospace` +- **Scale**: 11 / 12 / 13 / 14 / 15 / 16 / 18 / 20 / 24 / 30 / 36px +- **Weights**: 400 (regular), 500 (medium), 600 (semibold), 700 (bold) + +### Spacing (4px grid) +- `4px` xs, `8px` sm, `12px` md, `16px` lg, `24px` xl, `32px` 2xl, `48px` 3xl + +### Shadows +- Card: `0 1px 3px rgba(0,0,0,0.06), 0 0 0 1px rgba(0,0,0,0.04)` +- Hover: `0 4px 16px rgba(0,0,0,0.1), 0 0 0 1px rgba(0,0,0,0.04)` +- Elevated: `0 8px 32px rgba(0,0,0,0.12)` + +## 2. Component Tokens + +### Sidebar +- Width: 220px +- Background: `--bg-surface` +- Border: `1px solid var(--border)` +- Active item: `--accent-light` background + left accent border (3px) +- Nav item height: 36px +- Brand area: logo mark (32px) + name (14px semibold) + +### Topbar +- Height: 52px +- Background: `rgba(var(--bg-surface), 0.85)` + backdrop blur +- Border: `1px solid var(--border)` + +### Cards (pg-card) +- Border-radius: 10px +- Border: `1px solid var(--border)` +- Background: `--bg-surface` +- Padding header: 16px 20px +- Padding body: 20px 20px +- No shadow on default (borders provide enough definition) +- Subtle shadow on hover + +### Tables +- Row height: 48px +- Header height: 40px +- Cell padding: 12px 16px +- Border: `1px solid var(--border)` +- Row hover: `--bg-hover` +- Striped: alternate `--bg-base` background +- Font: 13px for data, 12px for secondary info + +### Buttons +- Border-radius: 6px +- Height: 34px (default), 30px (small) +- Transition: 150ms ease + +### Form Inputs +- Border-radius: 6px +- Height: 36px +- Border: `1px solid var(--border)` +- Focus: accent color border + subtle ring + +## 3. Motion +- Hover transitions: 120ms ease +- Page transitions: 180ms ease-out (fade + slight Y translate) +- Easing: `cubic-bezier(0.25, 0.1, 0.25, 1)` +- No decorative animation (Stripe/Linear style - motion serves function) + +## 4. Element Plus Overrides +- Component border-radius: 6px (uniform with design system) +- Tag border-radius: 4px +- Message/Notification border-radius: 10px +- Dialog border-radius: 12px +- Dropdown border-radius: 8px \ No newline at end of file diff --git a/web/admin/index.html b/web/admin/index.html new file mode 100755 index 0000000..58caa1f --- /dev/null +++ b/web/admin/index.html @@ -0,0 +1,13 @@ + + + + + + PayGateway 管理后台 + + + +
+ + + diff --git a/web/admin/package-lock.json b/web/admin/package-lock.json new file mode 100755 index 0000000..0947313 --- /dev/null +++ b/web/admin/package-lock.json @@ -0,0 +1,1443 @@ +{ + "name": "paygateway-admin", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "paygateway-admin", + "version": "0.1.0", + "dependencies": { + "@element-plus/icons-vue": "^2.3.1", + "echarts": "^6.1.0", + "element-plus": "^2.7.8", + "vue": "^3.4.38" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.1.2", + "vite": "^5.4.2" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "4.2.0", + "resolved": "https://registry.npmmirror.com/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz", + "integrity": "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@element-plus/icons-vue": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz", + "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==", + "license": "MIT", + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.8.0", + "resolved": "https://registry.npmmirror.com/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.12", + "resolved": "https://registry.npmmirror.com/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", + "license": "MIT" + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmmirror.com/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@popperjs/core": { + "name": "@sxzz/popperjs-es", + "version": "2.11.8", + "resolved": "https://registry.npmmirror.com/@sxzz/popperjs-es/-/popperjs-es-2.11.8.tgz", + "integrity": "sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.4.tgz", + "integrity": "sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.4.tgz", + "integrity": "sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.4.tgz", + "integrity": "sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.4.tgz", + "integrity": "sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.4.tgz", + "integrity": "sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.4.tgz", + "integrity": "sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.4.tgz", + "integrity": "sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.4.tgz", + "integrity": "sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.4.tgz", + "integrity": "sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.4.tgz", + "integrity": "sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.4.tgz", + "integrity": "sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.4.tgz", + "integrity": "sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.4.tgz", + "integrity": "sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.4.tgz", + "integrity": "sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.4.tgz", + "integrity": "sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.4.tgz", + "integrity": "sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.4.tgz", + "integrity": "sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.4.tgz", + "integrity": "sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.4.tgz", + "integrity": "sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.4.tgz", + "integrity": "sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.4.tgz", + "integrity": "sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.4.tgz", + "integrity": "sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.4.tgz", + "integrity": "sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.4.tgz", + "integrity": "sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.4.tgz", + "integrity": "sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.25", + "resolved": "https://registry.npmmirror.com/@types/lodash/-/lodash-4.17.25.tgz", + "integrity": "sha512-+K1NIO8I+F9/wNulfVvu23QYd0Pe9/OCqRrim4NoYIf1VoEDL90Ve4ClzpyqBLc7NpGGWRvYNCKZ1BE/Jpf8dQ==", + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmmirror.com/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.41", + "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.41.tgz", + "integrity": "sha512-q0Xtv/F9w2YO/7htQhtiL+Ev2WCJbe5N2hc+XfgyKkEKqWpSxknmT8QOuGdEKNdjPq0c3F7rNpFkTo3Kfrm7pg==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@vue/shared": "3.5.41", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.41", + "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.41.tgz", + "integrity": "sha512-oKacVfNglLvGjnS6BXOlGL7EyG2h8X03pqXCjzotRZUaXGjbrTJUnVAQjrCqUnS+lyu31nwQjZY/d817GmCnfw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.41", + "@vue/shared": "3.5.41" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.41", + "resolved": "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.41.tgz", + "integrity": "sha512-XJhip7R2wy6vX3knCxdZN4KracFaZUef58s1KYewqluedHIJaPIVfXoYT7MF1F8nCvv6k8bWWxDC8opMkg1VTQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.8", + "@vue/compiler-core": "3.5.41", + "@vue/compiler-dom": "3.5.41", + "@vue/compiler-ssr": "3.5.41", + "@vue/shared": "3.5.41", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.19", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.41", + "resolved": "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.41.tgz", + "integrity": "sha512-U3v5OejKEGqOI0Wy0+Sz7hGuIFZHA4LSXzrNM3IMIeDyJEBBfTpX26n3SDgToRpP2bLc9FfI2j/kSgcJ8Emq5A==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.41", + "@vue/shared": "3.5.41" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.41", + "resolved": "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.41.tgz", + "integrity": "sha512-rznsqKM0np0x18EjzF8x88MpEhdNsffbvFbckLL5+oUKz1BxAImEmO7J1ArRYSyo6aQaVoBDp7jEkT91OOxydA==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.41" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.41", + "resolved": "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.41.tgz", + "integrity": "sha512-Vcry58hiAKwGen9Z1jUZE0feFsNArPCMOImYI8el48A9Idf6DuQYD0U05zZIF2Iad1hGhPSvcbBbAOhNr55fhg==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.41", + "@vue/shared": "3.5.41" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.41", + "resolved": "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.41.tgz", + "integrity": "sha512-3vVBahVBS9+U6cmXBLyb8nE6/yYo4J/CGI9eVFs3KiMc0YHuudwKyShTD65jtJy/L9PUUxNAFu4cj4LiJ0UFbw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.41", + "@vue/runtime-core": "3.5.41", + "@vue/shared": "3.5.41", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.41", + "resolved": "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.41.tgz", + "integrity": "sha512-n6hx/pNFfbD6SuyeuMVkvqox8bwf/ET9JlA/kAz/imw8sw++wkqKe2mHX5KutjPpbKE4Z56yTHszoOjGMI9igQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.41", + "@vue/runtime-dom": "3.5.41", + "@vue/shared": "3.5.41" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.41", + "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.41.tgz", + "integrity": "sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA==", + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "14.3.0", + "resolved": "https://registry.npmmirror.com/@vueuse/core/-/core-14.3.0.tgz", + "integrity": "sha512-aHfz47g0ZhMtTVHmIzMVpJy8ePhhOy68GY5bv110+5DVtZ+W7BsOx+m61UNQqfrWyPztIHIanWa3E2tib3NFIw==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "14.3.0", + "@vueuse/shared": "14.3.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/@vueuse/metadata": { + "version": "14.3.0", + "resolved": "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-14.3.0.tgz", + "integrity": "sha512-BwxmbAzwAVF50+MW57GXOUEV61nFBGnlBvrTqj49PqWJu3uw7hdu72ztXeZ33RdZtDY6kO+bfCAE1PCn88Tktw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "14.3.0", + "resolved": "https://registry.npmmirror.com/@vueuse/shared/-/shared-14.3.0.tgz", + "integrity": "sha512-bZpge9eSXwa4ToSiqJ7j6KRwhAsneMFoSz3LMWKQDkqimm3D/tbFlrklrs/IOqC8tEcYmXQZJ6N0UrjhBirVCg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.21", + "resolved": "https://registry.npmmirror.com/dayjs/-/dayjs-1.11.21.tgz", + "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", + "license": "MIT" + }, + "node_modules/echarts": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/echarts/-/echarts-6.1.0.tgz", + "integrity": "sha512-q0yaFPggC9FUdsWH4blavRWFmxdrIodbkoKNAjJudAI6CA9gNPxHtV2RcZNEepZVlk4yvBYkOkbk6HIVpIyHZA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "2.3.0", + "zrender": "6.1.0" + } + }, + "node_modules/element-plus": { + "version": "2.14.4", + "resolved": "https://registry.npmmirror.com/element-plus/-/element-plus-2.14.4.tgz", + "integrity": "sha512-vMKR9tFcLeNrJgFXA3zhUn6YuRKUQW9d0btakBR8U1Iq8MfzkjMOGcgs5de2VgiLldHt69brmuBHpxc3bK4ZgQ==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^4.2.0", + "@element-plus/icons-vue": "^2.3.2", + "@floating-ui/dom": "^1.8.0", + "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.8", + "@types/lodash": "^4.17.24", + "@types/lodash-es": "^4.17.12", + "@vueuse/core": "14.3.0", + "async-validator": "^4.2.5", + "dayjs": "^1.11.20", + "lodash": "^4.18.1", + "lodash-es": "^4.18.1", + "lodash-unified": "^1.0.3", + "memoize-one": "^6.0.0", + "normalize-wheel-es": "^1.2.0", + "vue-component-type-helpers": "^3.3.9" + }, + "peerDependencies": { + "vue": "^3.3.7" + } + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmmirror.com/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmmirror.com/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.18.1", + "resolved": "https://registry.npmmirror.com/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", + "license": "MIT" + }, + "node_modules/lodash-unified": { + "version": "1.0.3", + "resolved": "https://registry.npmmirror.com/lodash-unified/-/lodash-unified-1.0.3.tgz", + "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", + "license": "MIT", + "peerDependencies": { + "@types/lodash-es": "*", + "lodash": "*", + "lodash-es": "*" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmmirror.com/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.17", + "resolved": "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.17.tgz", + "integrity": "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/normalize-wheel-es": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", + "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==", + "license": "BSD-3-Clause" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.62.4", + "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.62.4.tgz", + "integrity": "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.62.4", + "@rollup/rollup-android-arm64": "4.62.4", + "@rollup/rollup-darwin-arm64": "4.62.4", + "@rollup/rollup-darwin-x64": "4.62.4", + "@rollup/rollup-freebsd-arm64": "4.62.4", + "@rollup/rollup-freebsd-x64": "4.62.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.4", + "@rollup/rollup-linux-arm-musleabihf": "4.62.4", + "@rollup/rollup-linux-arm64-gnu": "4.62.4", + "@rollup/rollup-linux-arm64-musl": "4.62.4", + "@rollup/rollup-linux-loong64-gnu": "4.62.4", + "@rollup/rollup-linux-loong64-musl": "4.62.4", + "@rollup/rollup-linux-ppc64-gnu": "4.62.4", + "@rollup/rollup-linux-ppc64-musl": "4.62.4", + "@rollup/rollup-linux-riscv64-gnu": "4.62.4", + "@rollup/rollup-linux-riscv64-musl": "4.62.4", + "@rollup/rollup-linux-s390x-gnu": "4.62.4", + "@rollup/rollup-linux-x64-gnu": "4.62.4", + "@rollup/rollup-linux-x64-musl": "4.62.4", + "@rollup/rollup-openbsd-x64": "4.62.4", + "@rollup/rollup-openharmony-arm64": "4.62.4", + "@rollup/rollup-win32-arm64-msvc": "4.62.4", + "@rollup/rollup-win32-ia32-msvc": "4.62.4", + "@rollup/rollup-win32-x64-gnu": "4.62.4", + "@rollup/rollup-win32-x64-msvc": "4.62.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tslib": { + "version": "2.3.0", + "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz", + "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", + "license": "0BSD" + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.41", + "resolved": "https://registry.npmmirror.com/vue/-/vue-3.5.41.tgz", + "integrity": "sha512-2laE0p+aK+/AOPG/XL/WepOs/GlK755LJ1XECi9kDUrz1FKNw8rb2Xzlw9JS1rqEV55nb0ttsKxVlTCcd+R5cg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.41", + "@vue/compiler-sfc": "3.5.41", + "@vue/runtime-dom": "3.5.41", + "@vue/server-renderer": "3.5.41", + "@vue/shared": "3.5.41" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-component-type-helpers": { + "version": "3.3.9", + "resolved": "https://registry.npmmirror.com/vue-component-type-helpers/-/vue-component-type-helpers-3.3.9.tgz", + "integrity": "sha512-3c/UfMe0SqyEfcGTyH7mfshHagJ9QTCbppCb0/uGpHZpFug7+If3GeGZN7I0YheKEExemx3xldQPoO7PQSOLQg==", + "license": "MIT" + }, + "node_modules/zrender": { + "version": "6.1.0", + "resolved": "https://registry.npmmirror.com/zrender/-/zrender-6.1.0.tgz", + "integrity": "sha512-oEGMDB6pOP2S6OwRR4PdVv610zrjnA3Bh+JnSG12fYJlBKjtNAoEb5fSUoCOOINlH96I2fU38/A2UpRKs67xYQ==", + "license": "BSD-3-Clause", + "dependencies": { + "tslib": "2.3.0" + } + } + } +} diff --git a/web/admin/package.json b/web/admin/package.json new file mode 100755 index 0000000..0a4dc84 --- /dev/null +++ b/web/admin/package.json @@ -0,0 +1,21 @@ +{ + "name": "paygateway-admin", + "private": true, + "version": "0.1.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@element-plus/icons-vue": "^2.3.1", + "echarts": "^6.1.0", + "element-plus": "^2.7.8", + "vue": "^3.4.38" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.1.2", + "vite": "^5.4.2" + } +} diff --git a/web/admin/public/assets/favicon.svg b/web/admin/public/assets/favicon.svg new file mode 100644 index 0000000..41d8b51 --- /dev/null +++ b/web/admin/public/assets/favicon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/web/admin/public/assets/paygateway-mark-light.svg b/web/admin/public/assets/paygateway-mark-light.svg new file mode 100644 index 0000000..86ca7ec --- /dev/null +++ b/web/admin/public/assets/paygateway-mark-light.svg @@ -0,0 +1,11 @@ + + PayGateway + A routed payment gateway with a verifiable ledger spine. + + + + + + + + diff --git a/web/admin/public/assets/paygateway-mark.svg b/web/admin/public/assets/paygateway-mark.svg new file mode 100644 index 0000000..e973ddf --- /dev/null +++ b/web/admin/public/assets/paygateway-mark.svg @@ -0,0 +1,12 @@ + + PayGateway + A routed payment gateway with a verifiable ledger spine. + + + + + + + + + diff --git a/web/admin/src/App.vue b/web/admin/src/App.vue new file mode 100755 index 0000000..d76273d --- /dev/null +++ b/web/admin/src/App.vue @@ -0,0 +1,398 @@ + + + + + diff --git a/web/admin/src/DESIGN.md b/web/admin/src/DESIGN.md new file mode 100644 index 0000000..86a11f0 --- /dev/null +++ b/web/admin/src/DESIGN.md @@ -0,0 +1,232 @@ +# PayGateway Admin — Design System Specification + +> Inspired by Linear App / Vercel Dashboard — Ultra-minimal, precise, premium SaaS +> Reference: `linear.app.md` (Layer B) + `minimalist-skill.md` (Layer A) +> Status: v2.0 — Full redesign for Stripe/Linear/Vercel quality bar + +## 0. Research Log + +- **Layer A selected**: `minimalist-skill.md` — "minimal", "clean", "Linear-like" → premium utilitarian minimalism +- **Layer B selected**: `linear.app.md` — Linear's design tokens: near-black backgrounds, indigo-violet accent `#7170ff`, semi-transparent borders, Inter 510 signature weight, aggressive negative letter-spacing +- **Reference shortlist**: Linear (`linear.app.md`), Vercel (`vercel.md`), Supabase (`supabase.md`) — chosen: Linear for tokens + Vercel for login page inspiration +- **Design direction locked**: Linear-style dark-surface UI with indigo-violet accent. Light mode: off-white `#FAFAFA` bg, white cards, near-black text. Dark mode: near-black `#0D0D0F` bg, charcoal surfaces. Single accent `#7170ff` used sparingly. No gradients on large areas, no glassmorphism, no emoji icons. +- **Why Linear**: Unmatched precision — near-black canvas, semi-transparent borders (not dark borders on dark surfaces), 510 weight signature, aggressive tracking at display sizes. This is the reference that separates "Stripe-inspired" generic dark UI from real Linear-grade craft. + +--- + +## 1. Visual Direction + +**Atmosphere**: "Precision instrument" — darkness as native medium in dark mode, clean bone-white in light mode. Every element calibrated to the pixel. No decoration that doesn't serve function. The interface reads as engineered, not designed. + +**Signature moments**: +- Sidebar: ultra-narrow (200px), near-invisible surface with whisper-thin borders +- Active nav item: accent-colored left border strip (2px), subtle bg tint — no gradient +- KPI cards: pure numbers, no background color blocks, no icons in colored circles +- Charts: single-accent-color line charts, muted gray secondary elements + +**Banned elements** (per minimalist-skill): +- No `rounded-full` on cards, buttons, or primary elements +- No heavy drop shadows (none or ultra-subtle `rgba(0,0,0,0.04)`) +- No gradients on large backgrounds (accent allowed on small elements only) +- No emojis anywhere — SVG icons only +- No Inter/Roboto as generic defaults (Inter is fine IF used with 510 weight + cv01/ss03 features) + +--- + +## 2. Color Palette + +### Light Mode + +| Token | Hex | Role | +|-------|-----|------| +| `--bg-base` | `#FAFAFA` | Page background — warm bone | +| `--bg-surface` | `#FFFFFF` | Cards, sidebar, topbar | +| `--bg-subtle` | `#F4F4F5` | Table header, subtle bg | +| `--bg-hover` | `rgba(0,0,0,0.04)` | Hover states | +| `--bg-active` | `rgba(0,0,0,0.06)` | Active/press states | +| `--border` | `#E4E4E7` | Primary borders | +| `--border-subtle` | `rgba(0,0,0,0.06)` | Whisper-thin dividers | +| `--text-primary` | `#09090B` | Headings, primary labels | +| `--text-body` | `#3F3F46` | Body copy | +| `--text-muted` | `#71717A` | Secondary/metadata | +| `--text-placeholder` | `#A1A1AA` | Placeholder text | +| `--text-inverse` | `#FFFFFF` | Text on dark/accent | +| `--accent` | `#7170FF` | Linear violet — CTAs, active states, links | +| `--accent-hover` | `#8284FF` | Hover on accent | +| `--accent-light` | `rgba(113,112,255,0.08)` | Accent tint bg | +| `--accent-muted` | `rgba(113,112,255,0.15)` | Slightly stronger accent bg | +| `--success` | `#22A069` | Status: success/active | +| `--success-bg` | `rgba(34,160,105,0.08)` | Success pill bg | +| `--warning` | `#C27803` | Status: pending/warning | +| `--warning-bg` | `rgba(194,120,3,0.08)` | Warning pill bg | +| `--danger` | `#D92D20` | Status: error/danger | +| `--danger-bg` | `rgba(217,45,32,0.08)` | Danger pill bg | +| `--info` | `#0A6FD4` | Status: info/neutral | +| `--info-bg` | `rgba(10,111,212,0.08)` | Info pill bg | + +### Dark Mode + +| Token | Hex | Role | +|-------|-----|------| +| `--bg-base` | `#0D0D0F` | Page background — Linear black | +| `--bg-surface` | `#18181B` | Cards, panels (Level 3) | +| `--bg-subtle` | `#222226` | Subtle surface tint | +| `--bg-sidebar` | `#111114` | Sidebar — darkest panel | +| `--bg-hover` | `rgba(255,255,255,0.05)` | Hover states | +| `--bg-active` | `rgba(255,255,255,0.07)` | Active/press | +| `--border` | `rgba(255,255,255,0.08)` | Primary border — semi-transparent white | +| `--border-subtle` | `rgba(255,255,255,0.05)` | Whisper-thin | +| `--text-primary` | `#FAFAFA` | Primary text — near-white | +| `--text-body` | `#C4C4C8` | Body text — silver | +| `--text-muted` | `#71717A` | Muted / metadata | +| `--text-placeholder` | `#52525B` | Placeholder | +| `--text-inverse` | `#09090B` | Text on light | +| `--accent` | `#7170FF` | Violet — consistent across modes | +| `--accent-hover` | `#8284FF` | Hover accent | +| `--accent-light` | `rgba(113,112,255,0.12)` | Accent tint | +| `--accent-muted` | `rgba(113,112,255,0.20)` | Stronger accent tint | +| `--success` | `#22A069` | Success | +| `--success-bg` | `rgba(34,160,105,0.12)` | Success bg | +| `--warning` | `#C27803` | Warning | +| `--warning-bg` | `rgba(194,120,3,0.12)` | Warning bg | +| `--danger` | `#F85149` | Error/danger | +| `--danger-bg` | `rgba(248,81,73,0.12)` | Danger bg | +| `--info` | `#79B8FF` | Info | +| `--info-bg` | `rgba(121,184,255,0.10)` | Info bg | + +--- + +## 3. Typography + +- **Primary font**: `'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif` +- **Monospace**: `'JetBrains Mono', 'SF Mono', 'Consolas', monospace` +- **OpenType** (Linear signature): `font-feature-settings: 'cv01', 'ss03'` where feasible +- **Weights**: 400 (body/read), 510 (emphasis — Linear signature), 590 (strong emphasis — use sparingly) +- **Sizes**: + - Page title: 14px / 590 / -0.01em + - Card title: 14px / 590 / normal + - Body: 13px / 400 / normal + - Small/meta: 12px / 400 / normal + - Caption: 11px / 510 / normal + - Numbers/amounts: JetBrains Mono, tabular-nums +- **Never**: pure `#000000` or pure `#FFFFFF` for body text — always off-black/silver + +--- + +## 4. Spacing System + +Base: 8px grid +- Micro: 4px +- xs: 8px +- sm: 12px +- md: 16px +- lg: 20px +- xl: 24px +- 2xl: 32px +- Content gap (card grid): 16px +- Section gap: 16px +- Sidebar width: 200px (narrow, quiet) +- Topbar height: 48px +- Border radius: 8px (cards), 6px (inputs/buttons), 4px (tags/badges) + +--- + +## 5. Component Primitives + +### .pg-card +- Background: `var(--bg-surface)` +- Border: `1px solid var(--border)` — never shadow alone +- Border-radius: 8px +- No shadow on default; subtle shadow on hover in dark mode only: `0 2px 8px rgba(0,0,0,0.08)` +- Padding: 0 (header controls padding) / 20px body +- Hover: border darkens to `var(--border-subtle)` or accent + +### Buttons +- **Primary**: `var(--accent)` bg, white text, 6px radius, no shadow +- **Ghost**: transparent bg, `1px solid var(--border)`, `var(--text-body)` text, 6px radius +- **Text/Link**: no border, no bg, accent text +- **All**: height 32px (default), 28px (small), 24px (mini) — Inter 510 +- Hover: subtle bg tint — never gradient + +### Inputs +- Background: `var(--bg-subtle)` or transparent with border +- Border: `1px solid var(--border)` — focus: accent border with subtle glow +- Border-radius: 6px +- No heavy box-shadows + +### Tags/Badges +- Background: semantic color bg at 10% opacity +- Text: semantic color at full +- Border-radius: 4px +- Font: 11px / 510 / uppercase + letter-spacing 0.04em +- Padding: 2px 6px + +### Tables +- Header: `--bg-subtle`, 11px / 510 / uppercase + tracking, muted text +- Rows: no stripe, hover bg `var(--bg-hover)` +- Border: `none` — border-collapse on wrapper only +- Cell padding: 10px 12px + +### Sidebar +- Background: `var(--bg-surface)` (light) / `var(--bg-sidebar)` (dark) +- Border: `1px solid var(--border)` right side only +- Width: 200px +- Brand area: logo + name at top, no gradient background on logo +- Nav items: 34px height, 6px radius, left 2px accent border on active +- Active item: `--bg-hover` bg, `--accent` text + +### Topbar +- Background: `var(--bg-surface)` +- Border: `1px solid var(--border)` bottom +- Height: 48px +- Left: page title (14px / 590) +- Right: icon buttons (32px), theme toggle + +### Dialog/Modal +- Background: `var(--bg-surface)` +- Border: `1px solid var(--border)` or `rgba(255,255,255,0.08)` in dark +- Border-radius: 10px +- No heavy shadow — use border instead +- Backdrop: `rgba(0,0,0,0.4)` in dark / `rgba(0,0,0,0.15)` in light + +### Charts (ECharts) +- Background: transparent +- Axis lines: `var(--border)` in light / `rgba(255,255,255,0.06)` in dark +- Primary series: `--accent` (`#7170FF`) +- Secondary series: `--text-muted` +- No gradient fills on large areas — use flat color or subtle opacity +- Grid: minimal, border-only + +--- + +## 6. Motion + +- **Duration**: 120-150ms for micro-interactions, 200ms for layout transitions +- **Easing**: `cubic-bezier(0.16, 1, 0.3, 1)` (Linear's signature — quick start, gentle settle) +- **Page transitions**: opacity + translateY(4px) fade-in +- **Hover states**: background color shift only — no scale, no transform (except buttons active state: subtle scale(0.98)) +- **No ambient motion**: no floating orbs, no pulsing glows +- **Slop check**: if animation serves no informational purpose, remove it + +--- + +## 7. Accepted Debt + +- Inter is loaded without `cv01`/`ss03` OpenType features (CDN limitation) — accepted, Inter is still a clean choice +- JetBrains Mono loaded from Google Fonts (fallback to system mono is acceptable) +- No CSS container queries (broader compatibility over advanced layout) +- Element Plus component overrides use `:deep()` scoped styles — necessary for third-party library integration + +--- + +## 8. File Change Map + +| File | Change | +|------|--------| +| `theme.css` | Full rewrite — tokens, light/dark vars, Element Plus global overrides | +| `App.vue` | Refactor layout — narrower sidebar (200px), cleaner nav items, thinner topbar (48px), remove gradient logo bg | +| `Login.vue` | Strip all glassmorphism/orbs, flat white+accent or dark minimal card | +| `Dashboard.vue` | Strip KPI colored icon backgrounds, flat number display, clean chart palette | +| `Channels.vue` | Flat channel logos, clean stat grid | +| `Orders.vue` | Cleaner table, more minimal filter bar | +| Other views | Consistent pg-card styling, remove gradient backgrounds | \ No newline at end of file diff --git a/web/admin/src/api.js b/web/admin/src/api.js new file mode 100755 index 0000000..d1050c5 --- /dev/null +++ b/web/admin/src/api.js @@ -0,0 +1,214 @@ +// API 封装:管理后台 JSON 接口 +const BASE = '/api' + +function getToken() { + return sessionStorage.getItem('admin_token') +} + +async function request(path, params = {}, opts = {}) { + const qs = new URLSearchParams() + // 只有 GET 请求(无 opts.method)才加 query string + if (!opts.method || opts.method === 'GET') { + for (const [k, v] of Object.entries(params)) { + if (v !== undefined && v !== null && v !== '') qs.set(k, v) + } + } + const url = `${BASE}${path}${qs.toString() ? '?' + qs : ''}` + + // 添加 token 鉴权 + const headers = { 'Accept': 'application/json' } + const token = getToken() + if (token) headers['Authorization'] = 'Bearer ' + token + + const fetchOpts = { headers, ...opts } + // 合并 headers(opts 里的 headers 覆盖默认 headers) + if (opts.headers) { + fetchOpts.headers = { ...headers, ...opts.headers } + } + const res = await fetch(url, fetchOpts) + if (res.status === 401) { + // token 过期或未登录,清除并跳转登录页 + sessionStorage.removeItem('admin_token') + window.location.reload() + throw new Error('请重新登录') + } + if (!res.ok) { + const body = await res.json().catch(() => ({})) + throw new Error(body.error || `HTTP ${res.status}`) + } + const json = await res.json() + return json.data +} + +export const api = { + // 仪表盘(days: 趋势天数) + dashboard: (params = {}) => request('/dashboard', params), + // 易支付对接信息 + epayInfo: () => request('/epay-info'), + // 支付通道 + channels: () => request('/channels'), + // 系统设置 + settings: () => request('/settings'), + saveSettings: (data) => request('/settings', {}, { method: 'POST', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' } }), + // 安全密钥(完整值,需登录;调用记审计日志) + settingsKeys: () => request('/settings/keys'), + // 重置管理员:验证当前密码后清空凭据,回到首次注册态 + resetAdmin: (password) => request('/auth/reset', {}, { method: 'POST', body: JSON.stringify({ password }), headers: { 'Content-Type': 'application/json' } }), + toggleChannel: (name) => request(`/settings/channel/${name}/toggle`, {}, { method: 'POST' }), + toggleChannelMode: (name, mode) => request(`/settings/channel/${name}/mode/${mode}/toggle`, {}, { method: 'POST' }), + // 订单 + orders: (params) => request('/orders', params), + orderEvents: (id, limit = 20) => request(`/orders/${id}/events`, { limit }), + // 通知任务 + notifyTasks: (params) => request('/notify-tasks', params), + notifyStats: () => request('/notify-stats'), + // 日志 + logs: (params) => request('/logs', params), + // 账本 + ledger: (params) => request('/ledger', params), + ledgerVerify: () => request('/ledger/verify', {}, { method: 'POST' }), + ledgerSnapshots: (params) => request('/ledger/snapshots', params), + createSnapshot: () => request('/ledger/snapshot', {}, { method: 'POST' }), + ledgerPublicKey: () => request('/ledger/public-key'), + // 导出账本 CSV(下载文件,不走 JSON 封装) + ledgerExport: async () => { + const token = getToken() + const res = await fetch(BASE + '/ledger/export', { headers: { 'Authorization': 'Bearer ' + token } }) + if (!res.ok) { + const body = await res.json().catch(() => ({})) + throw new Error(body.error || '导出失败') + } + const blob = await res.blob() + const url = URL.createObjectURL(blob) + const a = document.createElement('a') + a.href = url + a.download = 'ledger_audit_' + new Date().toISOString().slice(0, 10) + '.csv' + document.body.appendChild(a) + a.click() + document.body.removeChild(a) + URL.revokeObjectURL(url) + }, + // 隧道 + tunnel: () => request('/tunnel'), + + // 账户池管理 + accounts: (params) => request('/accounts', params), + getAccount: (id) => request(`/accounts/${id}`), + // 完整密钥(明文,需登录):管理后台「查看/复制完整密钥」用 + accountSecret: (id) => request(`/accounts/${id}/secret`), + createAccount: (data) => request('/accounts', {}, { method: 'POST', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json' } }), + updateAccount: (id, data) => request(`/accounts/${id}`, {}, { method: 'PUT', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json' } }), + deleteAccount: (id) => request(`/accounts/${id}`, {}, { method: 'DELETE' }), + freezeAccount: (id) => request(`/accounts/${id}/freeze`, {}, { method: 'POST' }), + unfreezeAccount: (id) => request(`/accounts/${id}/unfreeze`, {}, { method: 'POST' }), + enableAccount: (id) => request(`/accounts/${id}/enable`, {}, { method: 'POST' }), + disableAccount: (id) => request(`/accounts/${id}/disable`, {}, { method: 'POST' }), + accountLogs: (id, limit) => request(`/accounts/${id}/logs`, { limit }), + + // 上传收款码图片 → 服务端解析二维码内容(docs/MIANQIAN.md §7.6 阶段一) + decodeQRCode: async (file) => { + const fd = new FormData() + fd.append('file', file) + const token = getToken() + const res = await fetch(BASE + '/accounts/decode-qrcode', { + method: 'POST', + headers: { 'Authorization': 'Bearer ' + token }, + body: fd + }) + if (res.status === 401) { + sessionStorage.removeItem('admin_token') + window.location.reload() + throw new Error('请重新登录') + } + const json = await res.json() + if (!res.ok) throw new Error(json.error || `HTTP ${res.status}`) + return json.data.content + }, + + // 上游对接方管理(多商户) + upstreams: () => request('/upstreams'), + getUpstream: (id) => request(`/upstreams/${id}`), + // 上游完整密钥(明文,需登录):对接指引「复制」真实可用密钥用 + upstreamSecret: (id) => request(`/upstreams/${id}/secret`), + createUpstream: (data) => request('/upstreams', {}, { method: 'POST', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json' } }), + updateUpstream: (id, data) => request(`/upstreams/${id}`, {}, { method: 'PUT', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json' } }), + deleteUpstream: (id) => request(`/upstreams/${id}`, {}, { method: 'DELETE' }), + enableUpstream: (id) => request(`/upstreams/${id}/enable`, {}, { method: 'POST' }), + disableUpstream: (id) => request(`/upstreams/${id}/disable`, {}, { method: 'POST' }), + upstreamLogs: (id, limit) => request(`/upstreams/${id}/logs`, { limit }), + + // 容灾对账中心(异常池 + dead 通知) + listExceptions: (params) => request('/reconciler/exceptions', params), + getException: (id) => request(`/reconciler/exceptions/${id}`), + requeryException: (id) => request(`/reconciler/exceptions/${id}/requery`, {}, { method: 'POST' }), + resolveException: (id, data) => request(`/reconciler/exceptions/${id}/resolve`, {}, { method: 'POST', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json' } }), + reopenException: (id) => request(`/reconciler/exceptions/${id}/reopen`, {}, { method: 'POST' }), + addException: (data) => request('/reconciler/exceptions', {}, { method: 'POST', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json' } }), + listDeadNotify: () => request('/reconciler/notify-dead'), + retryDeadNotify: (id) => request(`/reconciler/notify-dead/${id}/retry`, {}, { method: 'POST' }), + deleteDeadNotify: (id) => request(`/reconciler/notify-dead/${id}/delete`, {}, { method: 'POST' }), + // 日终对账报告(RESILIENCE P2-1) + dailyReport: (params) => request('/reconciler/daily-report', params), + + // 个人免签:监听端设备 + 推送流水 + 无订单收款(新设备统一走 USB 引导向导的 wizard-start) + personalDevices: () => request('/personal/devices'), + deletePersonalDevice: (id) => request(`/personal/devices/${id}`, {}, { method: 'DELETE' }), + freezePersonalDevice: (id) => request(`/personal/devices/${id}/freeze`, {}, { method: 'POST' }), + unfreezePersonalDevice: (id) => request(`/personal/devices/${id}/unfreeze`, {}, { method: 'POST' }), + restartPersonalDevice: (id) => request(`/personal/devices/${id}/restart`, {}, { method: 'POST' }), + personalPushes: (params) => request('/personal/pushes', params), + personalUnmatched: (params) => request('/personal/unmatched', params), + resolveUnmatched: (id, data) => request(`/personal/unmatched/${id}/resolve`, {}, { method: 'POST', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json' } }), + // 监听端配置二维码(docs/MIANQIAN.md §7.6):返回 PNG blob URL + personalConfigQR: async (id) => { + const token = getToken() + const res = await fetch(BASE + `/personal/devices/${id}/config-qr`, { headers: { 'Authorization': 'Bearer ' + token } }) + if (res.status === 401) { + sessionStorage.removeItem('admin_token') + window.location.reload() + throw new Error('请重新登录') + } + const body = await res.json().catch(() => ({})) + if (!res.ok) throw new Error(body.error || `HTTP ${res.status}`) + const blob = await res.blob() + return URL.createObjectURL(blob) + }, + // 设备控制台(web 可视化操作 USB 真机 / adb over WiFi 设备,docs/MIANQIAN.md §7.0 增强) + personalScreenshot: async (id) => { + const token = getToken() + const res = await fetch(BASE + `/personal/devices/${id}/screenshot`, { headers: { 'Authorization': 'Bearer ' + token } }) + if (res.status === 401) { + sessionStorage.removeItem('admin_token') + window.location.reload() + throw new Error('请重新登录') + } + const ct = res.headers.get('Content-Type') || '' + if (!res.ok || !ct.includes('image')) { + const body = await res.json().catch(() => ({})) + throw new Error(body.error || `HTTP ${res.status}`) + } + const blob = await res.blob() + return URL.createObjectURL(blob) + }, + personalDeviceAction: (id, data) => request(`/personal/devices/${id}/action`, {}, { method: 'POST', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json' } }), + personalSetAdb: (id, data) => request(`/personal/devices/${id}/adb`, {}, { method: 'POST', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json' } }), + // 设备 ↔ 账户 多对多绑定(应用双开多账户) + personalDeviceAccounts: (id) => request(`/personal/devices/${id}/accounts`), + personalAddDeviceAccount: (id, data) => request(`/personal/devices/${id}/accounts`, {}, { method: 'POST', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json' } }), + personalRemoveDeviceAccount: (id, accountId) => request(`/personal/devices/${id}/accounts/${accountId}`, {}, { method: 'DELETE' }), + // 批量运维(ids 为空 + all=true 作用于全部设备) + personalBatchRestart: (data) => request('/personal/devices/batch/restart', {}, { method: 'POST', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json' } }), + personalBatchStatus: (data) => request('/personal/devices/batch/status', {}, { method: 'POST', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json' } }), + // USB 真机引导向导 + personalUsbDetect: () => request('/personal/usb-detect'), + personalWizardStart: (data) => request('/personal/devices/wizard-start', {}, { method: 'POST', body: JSON.stringify(data), headers: { 'Content-Type': 'application/json' } }), + personalWizardWifi: (id) => request(`/personal/devices/${id}/wizard-wifi`, {}, { method: 'POST' }), + personalWizardVerify: (id) => request(`/personal/devices/${id}/wizard-verify`, {}, { method: 'POST' }), + personalWifiReconnect: (id) => request(`/personal/devices/${id}/wifi-reconnect`, {}, { method: 'POST' }), + // 动态收款码 + personalQrcodes: () => request('/personal/qrcodes'), + personalDeleteQrcode: (deviceId, platform, user = 0) => request(`/personal/qrcodes/${deviceId}/${platform}/${user}`, {}, { method: 'DELETE' }), + // 微信收款码:服务器 adb 驱动捕获(微信 SECURE 需服务器 adb 通道) + personalCaptureWechatQrcode: (deviceId, user = 0) => request(`/personal/qrcodes/${deviceId}/wechat/capture?user=${user}`, {}, { method: 'POST' }), + personalCaptureAlipayQrcode: (deviceId, user = 999) => request(`/personal/qrcodes/${deviceId}/alipay/capture?user=${user}`, {}, { method: 'POST' }), +} diff --git a/web/admin/src/components/DeviceStream.vue b/web/admin/src/components/DeviceStream.vue new file mode 100644 index 0000000..a6fe395 --- /dev/null +++ b/web/admin/src/components/DeviceStream.vue @@ -0,0 +1,265 @@ + + + + + diff --git a/web/admin/src/main.js b/web/admin/src/main.js new file mode 100755 index 0000000..f8691bd --- /dev/null +++ b/web/admin/src/main.js @@ -0,0 +1,18 @@ +import { createApp } from 'vue' +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import 'element-plus/theme-chalk/dark/css-vars.css' +import zhCn from 'element-plus/es/locale/lang/zh-cn' +import * as Icons from '@element-plus/icons-vue' +import App from './App.vue' +import './theme.css' + +const app = createApp(App) +app.use(ElementPlus, { locale: zhCn }) + +// 全局注册图标 +for (const [name, comp] of Object.entries(Icons)) { + app.component(name, comp) +} + +app.mount('#app') diff --git a/web/admin/src/store.js b/web/admin/src/store.js new file mode 100755 index 0000000..d8fdf6e --- /dev/null +++ b/web/admin/src/store.js @@ -0,0 +1,9 @@ +// 全局共享状态,解决页面间数据同步 +import { ref } from 'vue' + +// 每次通道启停/设置修改时递增,所有页面 watch 此值自动刷新 +export const refreshVersion = ref(0) + +export function bumpRefresh() { + refreshVersion.value++ +} \ No newline at end of file diff --git a/web/admin/src/theme.css b/web/admin/src/theme.css new file mode 100755 index 0000000..548b44a --- /dev/null +++ b/web/admin/src/theme.css @@ -0,0 +1,645 @@ +/* ====================================================== + * PayGateway Admin — Linear-Inspired Design System v2.0 + * Reference: linear.app.md (Layer B) + minimalist-skill.md (Layer A) + * Light + Dark theme · 8px grid · Inter + JetBrains Mono + * ====================================================== */ + +/* ---------- Fonts ---------- */ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;410;500;510;590;700&display=swap>'); +@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;600&display=swap>'); + +/* ---------- Light Theme ---------- */ +:root { + /* Backgrounds — warm off-white, bone */ + --bg-base: #FAFAFA; + --bg-surface: #FFFFFF; + --bg-subtle: #F4F4F5; + --bg-hover: rgba(0, 0, 0, 0.04); + --bg-active: rgba(0, 0, 0, 0.06); + --bg-sidebar: #FFFFFF; + --bg-topbar: #FFFFFF; + + /* Borders — ultra-thin, warm gray */ + --border: #E4E4E7; + --border-subtle: rgba(0, 0, 0, 0.06); + --border-strong: #D1D1D6; + + /* Text — calibrated grayscale */ + --text-primary: #09090B; + --text-body: #3F3F46; + --text-muted: #71717A; + --text-placeholder: #A1A1AA; + --text-inverse: #FFFFFF; + + /* Accent — Linear's indigo-violet (#7170FF) */ + --accent: #7170FF; + --accent-hover: #8284FF; + --accent-light: rgba(113, 112, 255, 0.08); + --accent-muted: rgba(113, 112, 255, 0.14); + + /* Semantic — muted, desaturated */ + --success: #16A34A; + --success-bg: rgba(22, 163, 74, 0.08); + --success-border: rgba(22, 163, 74, 0.18); + --warning: #B45309; + --warning-bg: rgba(180, 83, 9, 0.08); + --warning-border: rgba(180, 83, 9, 0.18); + --danger: #DC2626; + --danger-bg: rgba(220, 38, 38, 0.08); + --danger-border: rgba(220, 38, 38, 0.18); + --info: #2563EB; + --info-bg: rgba(37, 99, 235, 0.08); + --info-border: rgba(37, 99, 235, 0.18); + + /* Shadows — border-forward, no heavy shadows */ + --shadow-xs: 0 1px 2px rgba(0, 0, 0, 0.04); + --shadow-sm: 0 1px 4px rgba(0, 0, 0, 0.06), 0 0 0 1px rgba(0, 0, 0, 0.04); + --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.08), 0 0 0 1px rgba(0, 0, 0, 0.04); + + /* Layout */ + --sidebar-width: 200px; + --topbar-height: 48px; + --card-radius: 8px; + --input-radius: 6px; + --btn-radius: 6px; + + /* Element Plus — global primary override to Linear violet */ + --el-color-primary: #7170FF; + --el-color-primary-light-3: #9899FF; + --el-color-primary-light-5: #B5B5FF; + --el-color-primary-light-7: #D5D5FF; + --el-color-primary-light-8: #E8E8FF; + --el-color-primary-light-9: #F4F4FF; + --el-color-primary-dark-2: #5B5BD6; + --el-color-success: #22A069; + --el-color-warning: #C27803; + --el-color-danger: #D92D20; + --el-color-info: #0A6FD4; + --el-border-radius-base: 6px; + --el-border-radius-small: 4px; + --el-border-radius-round: 9999px; + --el-font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + --el-font-size-base: 13px; + --el-text-color-primary: var(--text-primary); + --el-text-color-regular: var(--text-body); + --el-text-color-secondary: var(--text-muted); + --el-text-color-placeholder: var(--text-placeholder); + --el-border-color: var(--border); + --el-border-color-light: var(--border); + --el-border-color-lighter: var(--border); + --el-fill-color-blank: var(--bg-surface); + --el-fill-color-light: var(--bg-subtle); + --el-fill-color-lighter: var(--bg-base); + --el-mask-color: rgba(0, 0, 0, 0.15); + --el-overlay-color: rgba(0, 0, 0, 0.15); +} + +/* ---------- Dark Theme (Linear Near-Black) ---------- */ +html.dark { + /* Backgrounds — Linear near-black */ + --bg-base: #0D0D0F; + --bg-surface: #18181B; + --bg-subtle: #222226; + --bg-hover: rgba(255, 255, 255, 0.05); + --bg-active: rgba(255, 255, 255, 0.08); + --bg-sidebar: #111114; + --bg-topbar: #18181B; + + /* Borders — semi-transparent white (Linear technique) */ + --border: rgba(255, 255, 255, 0.08); + --border-subtle: rgba(255, 255, 255, 0.05); + --border-strong: rgba(255, 255, 255, 0.14); + + /* Text — calibrated near-white/silver */ + --text-primary: #FAFAFA; + --text-body: #C4C4C8; + --text-muted: #71717A; + --text-placeholder: #52525B; + --text-inverse: #09090B; + + /* Accent — consistent across modes */ + --accent: #7170FF; + --accent-hover: #8284FF; + --accent-light: rgba(113, 112, 255, 0.12); + --accent-muted: rgba(113, 112, 255, 0.20); + + /* Semantic — slightly lighter in dark */ + --success: #22C55E; + --success-bg: rgba(34, 197, 94, 0.10); + --success-border: rgba(34, 197, 94, 0.22); + --warning: #D97706; + --warning-bg: rgba(217, 119, 6, 0.10); + --warning-border: rgba(217, 119, 6, 0.22); + --danger: #EF4444; + --danger-bg: rgba(239, 68, 68, 0.10); + --danger-border: rgba(239, 68, 68, 0.22); + --info: #3B82F6; + --info-bg: rgba(59, 130, 246, 0.10); + --info-border: rgba(59, 130, 246, 0.22); + + /* Shadows — subtle on dark surfaces */ + --shadow-sm: 0 1px 4px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.04); + --shadow-md: 0 2px 8px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.04); + + /* Element Plus dark overrides */ + --el-color-primary: #7170FF; + --el-color-primary-light-3: #9899FF; + --el-color-primary-light-5: #B5B5FF; + --el-color-primary-light-7: #D5D5FF; + --el-color-primary-light-8: #5B5BD6; + --el-color-primary-light-9: #18181B; + --el-color-primary-dark-2: #5B5BD6; + --el-bg-color: #18181B; + --el-bg-color-overlay: #222226; + --el-fill-color-blank: #18181B; + --el-text-color-primary: var(--text-primary); + --el-text-color-regular: var(--text-body); + --el-text-color-secondary: var(--text-muted); + --el-text-color-placeholder: var(--text-placeholder); + --el-border-color: var(--border); + --el-border-color-light: var(--border); + --el-border-color-lighter: var(--border); + --el-fill-color-light: #222226; + --el-fill-color-lighter: #18181B; + --el-mask-color: rgba(0, 0, 0, 0.65); + --el-overlay-color: rgba(0, 0, 0, 0.55); +} + +/* ---------- Base Reset ---------- */ +*, *::before, *::after { box-sizing: border-box; } + +html { + font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; + font-size: 14px; + line-height: 1.5; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: optimizeLegibility; + font-feature-settings: 'cv01', 'ss03'; +} + +html, body { height: 100%; margin: 0; padding: 0; } + +body { + background: var(--bg-base); + color: var(--text-body); + transition: background 0.18s ease, color 0.18s ease; +} + +/* ---------- Scrollbar ---------- */ +::-webkit-scrollbar { width: 5px; height: 5px; } +::-webkit-scrollbar-thumb { + background: var(--border-strong); + border-radius: 3px; +} +::-webkit-scrollbar-track { background: transparent; } +::-webkit-scrollbar-thumb:hover { background: var(--text-muted); } + +/* ---------- Text Selection ---------- */ +::selection { background: var(--accent-light); color: var(--accent); } +html.dark ::selection { background: rgba(113, 112, 255, 0.25); color: var(--text-primary); } + +/* ---------- Utility Classes ---------- */ +.font-mono { + font-family: 'JetBrains Mono', 'SF Mono', 'Consolas', monospace; + font-feature-settings: normal; + font-variant-numeric: tabular-nums; +} +.font-medium { font-weight: 510; } +.font-semibold { font-weight: 590; } +.font-bold { font-weight: 700; } + +.text-primary { color: var(--text-primary); } +.text-body { color: var(--text-body); } +.text-muted { color: var(--text-muted); } +.text-accent { color: var(--accent); } +.text-success { color: var(--success); } +.text-warning { color: var(--warning); } +.text-danger { color: var(--danger); } +.text-info { color: var(--info); } + +/* ---------- Card System ---------- */ +.pg-card { + background: var(--bg-surface); + border: 1px solid var(--border); + border-radius: var(--card-radius); + transition: background 0.15s ease, border-color 0.15s ease; +} +.pg-card:hover { + border-color: var(--border-strong); +} +html.dark .pg-card:hover { + border-color: rgba(255, 255, 255, 0.12); +} + +.pg-card-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 12px 16px; + border-bottom: 1px solid var(--border-subtle); + font-size: 13px; + font-weight: 590; + color: var(--text-primary); +} + +.pg-card-body { padding: 16px; } + +/* ---------- Element Plus — Table ---------- */ +.el-table { + --el-table-bg-color: transparent; + --el-table-tr-bg-color: transparent; + --el-table-header-bg-color: var(--bg-subtle); + --el-table-row-hover-bg-color: var(--bg-hover); + --el-table-border-color: var(--border-subtle); + --el-table-text-color: var(--text-body); + --el-table-header-text-color: var(--text-muted); + border: none !important; + border-radius: 0; +} +.el-table__inner-wrapper::before { display: none; } +.el-table th.el-table__cell { + font-size: 11px; + font-weight: 590; + letter-spacing: 0.05em; + text-transform: uppercase; + color: var(--text-muted) !important; + background: var(--bg-subtle) !important; + border-bottom: 1px solid var(--border-subtle) !important; + padding: 8px 12px !important; +} +.el-table td.el-table__cell { + font-size: 13px; + color: var(--text-body); + padding: 10px 12px !important; + border-bottom: 1px solid var(--border-subtle) !important; +} +.el-table__body tr:hover > td.el-table__cell { + background: var(--bg-hover) !important; +} +.el-table__body tr:last-child > td.el-table__cell { + border-bottom: none !important; +} +.el-table__empty-text { + color: var(--text-muted); + font-size: 13px; +} + +/* ---------- Element Plus — Tag ---------- */ +.el-tag { + border-radius: 4px !important; + font-size: 11px !important; + font-weight: 590 !important; + letter-spacing: 0.02em; + padding: 0 6px !important; + height: 20px !important; + line-height: 18px !important; + border: 1px solid !important; +} +.el-tag--success { + background: var(--success-bg) !important; + border-color: var(--success-border) !important; + color: var(--success) !important; +} +.el-tag--warning { + background: var(--warning-bg) !important; + border-color: var(--warning-border) !important; + color: var(--warning) !important; +} +.el-tag--danger { + background: var(--danger-bg) !important; + border-color: var(--danger-border) !important; + color: var(--danger) !important; +} +.el-tag--info { + background: var(--info-bg) !important; + border-color: var(--info-border) !important; + color: var(--info) !important; +} +html.dark .el-tag--success { + background: var(--success-bg) !important; + color: #4ADE80 !important; +} +html.dark .el-tag--danger { + background: var(--danger-bg) !important; + color: #F87171 !important; +} + +/* ---------- Element Plus — Button ---------- */ +.el-button { + border-radius: var(--btn-radius) !important; + font-weight: 510 !important; + font-size: 13px !important; + height: 32px !important; + padding: 0 14px !important; + transition: all 0.12s cubic-bezier(0.16, 1, 0.3, 1) !important; + border: 1px solid var(--border) !important; + background: var(--bg-surface) !important; + color: var(--text-body) !important; +} +.el-button:hover { + border-color: var(--border-strong) !important; + background: var(--bg-hover) !important; + color: var(--text-primary) !important; +} +.el-button:active { + transform: scale(0.98); +} +.el-button--primary { + background: var(--accent) !important; + border-color: var(--accent) !important; + color: var(--text-inverse) !important; +} +.el-button--primary:hover { + background: var(--accent-hover) !important; + border-color: var(--accent-hover) !important; + color: var(--text-inverse) !important; +} +.el-button--primary:active { + background: var(--accent) !important; + transform: scale(0.98); +} +.el-button--danger { + background: var(--danger) !important; + border-color: var(--danger) !important; + color: var(--text-inverse) !important; +} +.el-button--small { + height: 28px !important; + padding: 0 10px !important; + font-size: 12px !important; +} +.el-button--small.is-icon--inline { + width: 28px !important; + padding: 0 !important; +} +.el-button--text { + border: none !important; + background: transparent !important; + color: var(--accent) !important; + padding: 0 6px !important; +} +.el-button--text:hover { + background: var(--accent-light) !important; + color: var(--accent-hover) !important; +} + +/* ---------- Element Plus — Input ---------- */ +.el-input__wrapper { + border-radius: var(--input-radius) !important; + box-shadow: 0 0 0 1px var(--border) inset !important; + background: var(--bg-subtle) !important; + transition: box-shadow 0.12s ease !important; + padding: 0 12px !important; +} +.el-input__wrapper:hover { + box-shadow: 0 0 0 1px var(--border-strong) inset !important; +} +.el-input__wrapper.is-focus { + box-shadow: 0 0 0 2px var(--accent-light), 0 0 0 1px var(--accent) inset !important; + background: var(--bg-surface) !important; +} +.el-input__inner { + font-size: 13px !important; + color: var(--text-primary) !important; +} +.el-input__prefix .el-icon { + color: var(--text-muted) !important; +} + +/* ---------- Element Plus — Select ---------- */ +.el-select .el-input__wrapper { + border-radius: var(--input-radius) !important; +} + +/* ---------- Element Plus — Dialog ---------- */ +.el-dialog { + border-radius: 10px !important; + border: 1px solid var(--border) !important; + box-shadow: var(--shadow-md) !important; + overflow: hidden; +} +.el-dialog__header { + padding: 16px 20px 12px !important; + border-bottom: 1px solid var(--border-subtle) !important; + margin-right: 0 !important; +} +.el-dialog__title { + font-weight: 590; + font-size: 14px; + color: var(--text-primary); +} +.el-dialog__body { + padding: 20px !important; +} +.el-dialog__footer { + padding: 12px 20px 16px !important; + border-top: 1px solid var(--border-subtle) !important; +} + +/* ---------- Element Plus — Drawer ---------- */ +.el-drawer__header { + padding: 16px 20px 12px !important; + border-bottom: 1px solid var(--border-subtle) !important; + margin-bottom: 0 !important; + font-weight: 590; + font-size: 14px; + color: var(--text-primary); +} +.el-drawer__body { + padding: 16px 20px !important; +} + +/* ---------- Element Plus — Pagination ---------- */ +.el-pagination { + --el-pagination-hover-color: var(--accent); + font-weight: 510; + font-size: 13px; +} +.el-pagination.is-background .el-pager li:not(.is-disabled).is-active { + background: var(--accent) !important; + color: var(--text-inverse) !important; +} +.el-pager li { + font-weight: 510; + border-radius: 4px; +} + +/* ---------- Element Plus — Tabs ---------- */ +.el-tabs__nav-wrap::after { display: none; } +.el-tabs__item { + font-weight: 510; + font-size: 13px; + color: var(--text-muted); + transition: color 0.12s ease; +} +.el-tabs__item:hover { color: var(--text-primary); } +.el-tabs__item.is-active { + color: var(--accent); + font-weight: 590; +} +.el-tabs__active-bar { + background: var(--accent); + transition: all 0.18s cubic-bezier(0.16, 1, 0.3, 1); +} + +/* ---------- Element Plus — Alert ---------- */ +.el-alert { + border-radius: 6px !important; + border: 1px solid !important; + padding: 10px 14px !important; +} +.el-alert--success { + background: var(--success-bg) !important; + border-color: var(--success-border) !important; + color: var(--success) !important; +} +.el-alert--warning { + background: var(--warning-bg) !important; + border-color: var(--warning-border) !important; + color: var(--warning) !important; +} +.el-alert--error { + background: var(--danger-bg) !important; + border-color: var(--danger-border) !important; + color: var(--danger) !important; +} +.el-alert--info { + background: var(--info-bg) !important; + border-color: var(--info-border) !important; + color: var(--info) !important; +} +.el-alert__title { + font-size: 12px !important; + font-weight: 500 !important; +} +html.dark .el-alert--success { color: #4ADE80 !important; } +html.dark .el-alert--error { color: #F87171 !important; } + +/* ---------- Element Plus — Form ---------- */ +.el-form-item__label { + font-weight: 510; + font-size: 13px; + color: var(--text-body); +} + +/* ---------- Element Plus — Descriptions ---------- */ +.el-descriptions__cell { + padding: 8px 12px !important; +} +.el-descriptions__label { + color: var(--text-muted) !important; + font-weight: 510; + font-size: 12px; +} +.el-descriptions__content { + color: var(--text-body); + font-size: 13px; +} + +/* ---------- Element Plus — Progress ---------- */ +.el-progress__text { + font-weight: 590; + font-size: 11px !important; +} +.el-progress-bar__outer { + background: var(--bg-subtle) !important; +} + +/* ---------- Element Plus — Steps ---------- */ +.el-step__icon { border: none !important; } + +/* ---------- Element Plus — Timeline ---------- */ +.el-timeline-item__node { background: var(--accent) !important; border-color: var(--accent) !important; } + +/* ---------- Element Plus — Empty ---------- */ +.el-empty__description { color: var(--text-muted); font-size: 13px; } + +/* ---------- Element Plus — Tooltip ---------- */ +.el-popper.is-light { + border: 1px solid var(--border) !important; + border-radius: 6px !important; + background: var(--bg-surface) !important; + color: var(--text-body) !important; + font-size: 12px !important; + box-shadow: var(--shadow-sm) !important; +} +.el-popper.is-light .el-popper__arrow::before { + background: var(--bg-surface) !important; + border-color: var(--border) !important; +} + +/* ---------- Element Plus — Message / Notification ---------- */ +.el-message { + border-radius: 8px !important; + border: 1px solid var(--border) !important; + background: var(--bg-surface) !important; + box-shadow: var(--shadow-md) !important; +} +.el-notification { + border-radius: 8px !important; + border: 1px solid var(--border) !important; + background: var(--bg-surface) !important; +} + +/* ---------- Element Plus — Radio / Checkbox ---------- */ +.el-radio-button__inner { + border-radius: 4px !important; + font-weight: 510 !important; + font-size: 12px !important; +} +.el-radio-button__original-radio:checked + .el-radio-button__inner { + background-color: var(--accent) !important; + border-color: var(--accent) !important; + color: var(--text-inverse) !important; + box-shadow: none !important; +} + +/* ---------- Element Plus — Switch ---------- */ +.el-switch.is-checked .el-switch__core { + background-color: var(--accent) !important; + border-color: var(--accent) !important; +} + +/* ---------- Element Plus — Dropdown ---------- */ +.el-dropdown-menu { + border: 1px solid var(--border) !important; + border-radius: 8px !important; + background: var(--bg-surface) !important; + box-shadow: var(--shadow-md) !important; + padding: 4px !important; +} +.el-dropdown-menu__item { + font-size: 13px !important; + font-weight: 510 !important; + color: var(--text-body) !important; + border-radius: 4px !important; + padding: 6px 10px !important; +} +.el-dropdown-menu__item:hover { + background: var(--bg-hover) !important; + color: var(--text-primary) !important; +} + +/* ---------- Element Plus — DatePicker ---------- */ +.el-date-editor { + border-radius: var(--input-radius) !important; +} +.el-picker-panel { + border: 1px solid var(--border) !important; + border-radius: 8px !important; + background: var(--bg-surface) !important; +} + +/* ---------- Utility helpers ---------- */ +.mt8 { margin-top: 8px; } +.mt10 { margin-top: 10px; } +.mt12 { margin-top: 12px; } +.mt16 { margin-top: 16px; } +.mb8 { margin-bottom: 8px; } +.mb12 { margin-bottom: 12px; } +.mb16 { margin-bottom: 16px; } +.gap8 { gap: 8px; } +.gap12 { gap: 12px; } +.gap16 { gap: 16px; } \ No newline at end of file diff --git a/web/admin/src/views/Accounts.vue b/web/admin/src/views/Accounts.vue new file mode 100755 index 0000000..648f63d --- /dev/null +++ b/web/admin/src/views/Accounts.vue @@ -0,0 +1,468 @@ + + + + + \ No newline at end of file diff --git a/web/admin/src/views/Channels.vue b/web/admin/src/views/Channels.vue new file mode 100755 index 0000000..281fe3a --- /dev/null +++ b/web/admin/src/views/Channels.vue @@ -0,0 +1,296 @@ + + + + + \ No newline at end of file diff --git a/web/admin/src/views/Dashboard.vue b/web/admin/src/views/Dashboard.vue new file mode 100755 index 0000000..12c53f8 --- /dev/null +++ b/web/admin/src/views/Dashboard.vue @@ -0,0 +1,621 @@ + + + + + \ No newline at end of file diff --git a/web/admin/src/views/Ledger.vue b/web/admin/src/views/Ledger.vue new file mode 100755 index 0000000..9a633df --- /dev/null +++ b/web/admin/src/views/Ledger.vue @@ -0,0 +1,360 @@ + + + + + \ No newline at end of file diff --git a/web/admin/src/views/Login.vue b/web/admin/src/views/Login.vue new file mode 100755 index 0000000..76a395a --- /dev/null +++ b/web/admin/src/views/Login.vue @@ -0,0 +1,269 @@ + + + + + diff --git a/web/admin/src/views/Logs.vue b/web/admin/src/views/Logs.vue new file mode 100755 index 0000000..dca028f --- /dev/null +++ b/web/admin/src/views/Logs.vue @@ -0,0 +1,196 @@ + + + + + \ No newline at end of file diff --git a/web/admin/src/views/NotifyTasks.vue b/web/admin/src/views/NotifyTasks.vue new file mode 100755 index 0000000..6fdf8bc --- /dev/null +++ b/web/admin/src/views/NotifyTasks.vue @@ -0,0 +1,176 @@ + + + + + \ No newline at end of file diff --git a/web/admin/src/views/Orders.vue b/web/admin/src/views/Orders.vue new file mode 100755 index 0000000..2ed6e85 --- /dev/null +++ b/web/admin/src/views/Orders.vue @@ -0,0 +1,242 @@ + + + + + \ No newline at end of file diff --git a/web/admin/src/views/PersonalDevices.vue b/web/admin/src/views/PersonalDevices.vue new file mode 100755 index 0000000..544352d --- /dev/null +++ b/web/admin/src/views/PersonalDevices.vue @@ -0,0 +1,1051 @@ + + + + + \ No newline at end of file diff --git a/web/admin/src/views/Qrcodes.vue b/web/admin/src/views/Qrcodes.vue new file mode 100644 index 0000000..f635e99 --- /dev/null +++ b/web/admin/src/views/Qrcodes.vue @@ -0,0 +1,240 @@ + + + + + diff --git a/web/admin/src/views/Reconciler.vue b/web/admin/src/views/Reconciler.vue new file mode 100755 index 0000000..893aa74 --- /dev/null +++ b/web/admin/src/views/Reconciler.vue @@ -0,0 +1,330 @@ + + + + + \ No newline at end of file diff --git a/web/admin/src/views/Settings.vue b/web/admin/src/views/Settings.vue new file mode 100755 index 0000000..a76df0d --- /dev/null +++ b/web/admin/src/views/Settings.vue @@ -0,0 +1,255 @@ + + + + + \ No newline at end of file diff --git a/web/admin/src/views/Unmatched.vue b/web/admin/src/views/Unmatched.vue new file mode 100755 index 0000000..3bc680a --- /dev/null +++ b/web/admin/src/views/Unmatched.vue @@ -0,0 +1,177 @@ + + + + + \ No newline at end of file diff --git a/web/admin/src/views/Upstreams.vue b/web/admin/src/views/Upstreams.vue new file mode 100755 index 0000000..a184133 --- /dev/null +++ b/web/admin/src/views/Upstreams.vue @@ -0,0 +1,350 @@ + + + + + \ No newline at end of file diff --git a/web/admin/vite.config.js b/web/admin/vite.config.js new file mode 100755 index 0000000..43273b8 --- /dev/null +++ b/web/admin/vite.config.js @@ -0,0 +1,20 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +// 构建产物直接输出到 Go embed 目录(internal/admin/static) +export default defineConfig({ + plugins: [vue()], + build: { + outDir: '../../internal/admin/static', + emptyOutDir: true, + assetsDir: 'assets', + chunkSizeWarningLimit: 1024 + }, + server: { + port: 5173, + // 开发时代理到管理后台 API + proxy: { + '/api': 'http://localhost:8081' + } + } +})