<?php
/** @var string $baseUrl @var string $csrf @var array<string,mixed> $cfg */
$baseJson = json_encode($baseUrl, JSON_UNESCAPED_UNICODE);
$csrfJson = json_encode($csrf, JSON_UNESCAPED_UNICODE);
$manifestUrl = json_encode(trim((string) ($cfg['update_manifest_url'] ?? '')), JSON_UNESCAPED_UNICODE);
$loggedIn = is_logged_in();
$geetestIdJson = json_encode(portal_geetest_captcha_id(), JSON_UNESCAPED_UNICODE);
$portalJsBase = json_encode(rtrim($baseUrl, '/') . '/', JSON_UNESCAPED_UNICODE);
?>
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title><?php echo $loggedIn ? '主题管理后台' : '登录 · 主题管理'; ?></title>
  <link rel="stylesheet" href="https://unpkg.com/@arco-design/web-vue@2.58.0/dist/arco.css">
  <style>html,body,#app{height:100%;margin:0}.brand-title{font-weight:600;font-size:15px;padding:0 16px;line-height:48px;border-bottom:1px solid var(--color-border-2)}.main{padding:20px;background:#f2f3f5;min-height:calc(100vh - 48px)}.hero{background:linear-gradient(120deg,#165dff,#722ed1);color:#fff;border-radius:8px;padding:24px;margin-bottom:16px}.hero h2{margin:0 0 8px;font-size:22px}.hero p{margin:0;opacity:.9;font-size:14px}.color-row{display:flex;align-items:center;gap:12px;margin-bottom:12px}.color-row label{flex:1;font-size:13px;color:#4e5969;font-family:monospace}.auth-wrap{min-height:100vh;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,#e8f3ff,#f7f8fa);padding:24px}.auth-box{width:440px;max-width:100%;background:#fff;padding:28px;border-radius:8px;box-shadow:0 8px 24px rgba(0,0,0,.08)}.portal-geetest-mount{width:100%;min-height:46px;margin:2px 0 4px}.auth-sms-col .arco-btn{width:100%}</style>
</head>
<body>
<div id="app">
  <template v-if="!authed">
    <div class="auth-wrap">
      <div class="auth-box">
        <h2 style="margin:0 0 6px">Yuser 主题管理</h2>
        <p style="color:#86909c;font-size:13px;margin:0 0 20px">支持验证码 / 账号密码登录 · 绑定域名 · 申请正版授权</p>
        <a-tabs v-model:active-key="authTab" @change="onAuthTabChange">
          <a-tab-pane key="login" title="登录">
            <a-form layout="vertical" :model="loginForm">
              <a-form-item label="登录方式">
                <a-radio-group v-model="loginForm.mode" type="button" @change="onLoginModeChange">
                  <a-radio value="sms">短信验证码</a-radio>
                  <a-radio value="password">账号密码</a-radio>
                </a-radio-group>
              </a-form-item>
              <template v-if="loginForm.mode === 'sms'">
                <a-form-item label="手机号">
                  <a-row :gutter="8" style="width:100%">
                    <a-col :span="15">
                      <a-input v-model="loginForm.phone" placeholder="11 位中国大陆手机号" maxlength="11" allow-clear />
                    </a-col>
                    <a-col :span="9" class="auth-sms-col">
                      <a-button type="outline" long :disabled="smsCooldown.login > 0" :loading="smsSending && smsPurpose === 'login'" @click="sendSmsCode('login')">
                        {{ smsCooldown.login > 0 ? (smsCooldown.login + 's') : '获取验证码' }}
                      </a-button>
                    </a-col>
                  </a-row>
                </a-form-item>
                <a-form-item label="行为验证">
                  <div id="geetest-mount-login-sms" class="portal-geetest-mount"></div>
                </a-form-item>
                <a-form-item label="短信验证码">
                  <a-input v-model="loginForm.code" placeholder="6 位验证码" maxlength="6" allow-clear />
                </a-form-item>
              </template>
              <template v-else>
                <a-form-item label="账号"><a-input v-model="loginForm.account" placeholder="用户名 / 手机号 / 邮箱" allow-clear /></a-form-item>
                <a-form-item label="密码"><a-input-password v-model="loginForm.password" placeholder="登录密码" allow-clear /></a-form-item>
                <a-form-item label="行为验证">
                  <div id="geetest-mount-login-pwd" class="portal-geetest-mount"></div>
                </a-form-item>
              </template>
              <a-button type="primary" long :loading="authLoading" @click="doLogin" style="margin-top:12px">登录</a-button>
            </a-form>
          </a-tab-pane>
          <a-tab-pane key="register" title="注册">
            <a-form layout="vertical" :model="regForm">
              <a-form-item label="公司/站点名称"><a-input v-model="regForm.company" placeholder="您的公司或站点名" allow-clear /></a-form-item>
              <a-form-item label="用户名"><a-input v-model="regForm.username" placeholder="3-32 位字母、数字或下划线" allow-clear /></a-form-item>
              <a-form-item label="登录密码"><a-input-password v-model="regForm.password" placeholder="至少 6 位" allow-clear /></a-form-item>
              <a-form-item label="行为验证">
                <div id="geetest-mount-register" class="portal-geetest-mount"></div>
              </a-form-item>
              <a-form-item label="手机号">
                <a-row :gutter="8" style="width:100%">
                  <a-col :span="15">
                    <a-input v-model="regForm.phone" placeholder="11 位中国大陆手机号" maxlength="11" allow-clear />
                  </a-col>
                  <a-col :span="9" class="auth-sms-col">
                    <a-button type="outline" long :disabled="smsCooldown.register > 0" :loading="smsSending && smsPurpose === 'register'" @click="sendSmsCode('register')">
                      {{ smsCooldown.register > 0 ? (smsCooldown.register + 's') : '获取验证码' }}
                    </a-button>
                  </a-col>
                </a-row>
              </a-form-item>
              <a-form-item label="短信验证码"><a-input v-model="regForm.code" placeholder="6 位验证码" maxlength="6" allow-clear /></a-form-item>
              <a-button type="primary" long :loading="authLoading" @click="doRegister" style="margin-top:12px">注册并进入</a-button>
            </a-form>
          </a-tab-pane>
        </a-tabs>
      </div>
    </div>
  </template>

  <a-layout v-else style="height:100%">
    <a-layout-sider :width="220" collapsible>
      <div class="brand-title">Yuser 主题</div>
      <a-menu :selected-keys="[page]" @menu-item-click="k=>page=k">
        <a-menu-item key="overview"><template #icon><icon-home /></template>经营概览</a-menu-item>
        <a-menu-item key="domains"><template #icon><icon-link /></template>授权域名</a-menu-item>
        <a-menu-item key="colors"><template #icon><icon-palette /></template>主题配色</a-menu-item>
        <a-menu-item key="brand"><template #icon><icon-image /></template>站点品牌</a-menu-item>
        <a-menu-item key="updates"><template #icon><icon-refresh /></template>版本更新</a-menu-item>
      </a-menu>
    </a-layout-sider>
    <a-layout>
      <a-layout-header style="height:48px;background:#fff;border-bottom:1px solid var(--color-border-2);display:flex;align-items:center;justify-content:space-between;padding:0 16px">
        <span style="color:#86909c;font-size:13px">{{ titles[page] }} · {{ user.company || user.username || user.phone_masked }}</span>
        <a-button type="text" href="?logout=1">退出</a-button>
      </a-layout-header>
      <a-layout-content class="main">
        <template v-if="page==='overview'">
          <div class="hero">
            <h2>{{ overview.brand?.site_name || '云脉智慧平台' }}</h2>
            <p>{{ overview.brand?.welcome_subtitle || '管理主题配色、品牌与更新' }}</p>
          </div>
          <a-row :gutter="16">
            <a-col :span="8"><a-card><a-statistic title="主题版本" :value="overview.pack?.version||'—'" /></a-card></a-col>
            <a-col :span="8"><a-card><a-statistic title="已绑定域名" :value="domains.length" /></a-card></a-col>
            <a-col :span="8"><a-card><a-statistic title="已授权域名" :value="authorizedCount" /></a-card></a-col>
          </a-row>
          <a-alert v-if="authorizedCount===0" type="warning" style="margin-top:16px" title="尚未有已授权域名">请先在「授权域名」绑定站点，再在线支付购买正版授权（价格由 Yuser 官方配置下发）。</a-alert>
        </template>

        <template v-if="page==='domains'">
          <a-card title="绑定与购买授权">
            <a-alert v-if="payment.enabled" type="info" style="margin-bottom:16px">
              {{ payment.product_name }} — <strong>¥{{ payment.price }}</strong> / 单域名。价格由 Yuser 官方（www.finally.chat）下发；点击购买将向官方发起订单并跳转易支付，付清后自动开通。
            </a-alert>
            <a-alert v-else type="warning" style="margin-bottom:16px">官方尚未开启在线购买，请联系云脉智慧平台（www.finally.chat）。</a-alert>
            <a-form layout="inline" style="margin-bottom:16px">
              <a-form-item label="域名"><a-input v-model="newDomain" placeholder="example.com（不含 http）" style="width:280px" allow-clear /></a-form-item>
              <a-button type="primary" :loading="binding" @click="bindDomain">绑定域名</a-button>
            </a-form>
            <a-table :data="domains" row-key="id" :pagination="false">
              <template #columns>
                <a-table-column title="域名" data-index="domain"></a-table-column>
                <a-table-column title="状态" :width="120">
                  <template #cell="{ record }">
                    <a-tag v-if="record.status==='authorized'" color="green">已授权</a-tag>
                    <a-tag v-else-if="record.status==='pending'" color="orange">待支付</a-tag>
                    <a-tag v-else-if="record.status==='piracy'" color="red">未授权</a-tag>
                    <a-tag v-else>{{ record.status }}</a-tag>
                  </template>
                </a-table-column>
                <a-table-column title="最近访问" data-index="last_seen" :width="160"></a-table-column>
                <a-table-column title="操作" :width="200">
                  <template #cell="{ record }">
                    <a-space>
                      <a-button v-if="record.status!=='authorized' && payment.enabled" type="primary" size="small" @click="openPurchase(record)">购买授权</a-button>
                      <a-popconfirm v-if="record.status!=='authorized'" content="解除绑定？可重新添加该域名。" @ok="unbindDomain(record)">
                        <a-button type="text" status="danger" size="small">删除</a-button>
                      </a-popconfirm>
                      <span v-if="record.status==='authorized'" style="color:#86909c;font-size:12px">已开通</span>
                    </a-space>
                  </template>
                </a-table-column>
              </template>
            </a-table>
          </a-card>
          <a-modal v-model:visible="payModal" title="购买正版授权" @ok="submitPurchase" :ok-loading="paying" ok-text="去支付">
            <p v-if="purchaseRow">域名：<strong>{{ purchaseRow.domain }}</strong></p>
            <p v-if="payment.enabled">应付金额：<strong style="color:#f53f3f;font-size:18px">¥{{ payment.price }}</strong></p>
            <a-form-item label="支付方式" style="margin-top:12px">
              <a-radio-group v-model="payType">
                <a-radio v-for="t in payment.pay_types" :key="t.id" :value="t.id">{{ t.label }}</a-radio>
              </a-radio-group>
            </a-form-item>
          </a-modal>
        </template>

        <template v-if="page==='colors'">
          <a-card title="主题配色">
            <div v-for="(val,key) in tokens" :key="key" class="color-row">
              <label>{{ key }}</label>
              <input type="color" v-model="tokens[key]">
            </div>
            <a-space style="margin-top:16px">
              <a-button type="primary" :loading="saving" @click="saveTokens">保存</a-button>
              <a-button @click="resetTokens">恢复默认</a-button>
            </a-space>
          </a-card>
        </template>

        <template v-if="page==='brand'">
          <a-card title="站点品牌">
            <a-form :model="brand" layout="vertical" style="max-width:560px">
              <a-form-item label="站点名称"><a-input v-model="brand.site_name" /></a-form-item>
              <a-form-item label="欢迎标题"><a-input v-model="brand.welcome_title" /></a-form-item>
              <a-form-item label="欢迎副标题"><a-input v-model="brand.welcome_subtitle" /></a-form-item>
              <a-form-item label="Logo（https）"><a-input v-model="brand.logo_url" /></a-form-item>
              <a-form-item label="客服邮箱"><a-input v-model="brand.support_email" /></a-form-item>
              <a-form-item label="页脚文案"><a-textarea v-model="brand.footer_text" :auto-size="{minRows:2}" /></a-form-item>
              <a-button type="primary" :loading="saving" @click="saveBrand">保存</a-button>
            </a-form>
          </a-card>
        </template>

        <template v-if="page==='updates'">
          <a-card title="版本更新">
            <p>当前 <strong>{{ pack.version }}</strong></p>
            <a-button type="primary" :loading="checking" @click="checkUpdate" style="margin:12px 0">检查更新</a-button>
            <a-list v-if="releases.length" :bordered="true">
              <a-list-item v-for="r in releases" :key="r.version">
                <a-list-item-meta :title="'v'+r.version" :description="r.changelog" />
                <template #actions><a-button type="primary" size="small" @click="applyUpdate(r)">安装</a-button></template>
              </a-list-item>
            </a-list>
            <a-empty v-else-if="checked" description="已是最新" />
          </a-card>
        </template>
      </a-layout-content>
    </a-layout>
  </a-layout>
</div>
<script>window.PORTAL_GEETEST_ID = <?php echo $geetestIdJson; ?>;</script>
<script src="https://static.geetest.com/v4/gt4.js"></script>
<script src="<?php echo htmlspecialchars(rtrim($baseUrl, '/') . '/portal_geetest.js?v=3', ENT_QUOTES, 'UTF-8'); ?>"></script>
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<script src="https://unpkg.com/@arco-design/web-vue@2.58.0/dist/arco-vue.min.js"></script>
<script src="https://unpkg.com/@arco-design/web-vue@2.58.0/dist/arco-vue-icon.min.js"></script>
<script>
const { createApp, ref, reactive, computed, onMounted, watch } = Vue;
const BASE = <?php echo $baseJson; ?>;
const CSRF = <?php echo $csrfJson; ?>;
const MANIFEST = <?php echo $manifestUrl; ?>;
const INIT_AUTH = <?php echo $loggedIn ? 'true' : 'false'; ?>;

function toast(type, msg) {
  const text = msg || '操作失败';
  if (typeof ArcoVue !== 'undefined' && ArcoVue.Message && ArcoVue.Message[type]) {
    ArcoVue.Message[type](text);
    return;
  }
  alert(text);
}
function toastError(msg) { toast('error', msg); }
function toastSuccess(msg) { toast('success', msg); }

createApp({
  setup() {
    const authed = ref(INIT_AUTH);
    const authTab = ref('login');
    const authLoading = ref(false);
    const loginForm = reactive({ mode: 'sms', phone: '', code: '', account: '', password: '' });
    const regForm = reactive({ phone: '', code: '', company: '', username: '', password: '' });
    const user = reactive({ company: '', username: '', phone_masked: '' });
    const smsSending = ref(false);
    const smsPurpose = ref('');
    const smsCooldown = reactive({ login: 0, register: 0 });
    const page = ref('overview');
    const titles = { overview: '经营概览', domains: '授权域名', colors: '主题配色', brand: '站点品牌', updates: '版本更新' };
    const overview = reactive({ pack: {}, brand: {} });
    const pack = reactive({ version: '' });
    const domains = ref([]);
    const newDomain = ref('');
    const binding = ref(false);
    const tokens = ref({});
    const brand = reactive({ site_name: '', logo_url: '', footer_text: '', support_email: '', welcome_title: '', welcome_subtitle: '' });
    const saving = ref(false);
    const checking = ref(false);
    const checked = ref(false);
    const releases = ref([]);
    const payment = reactive({ enabled: false, product_name: '', price: '0', pay_types: [], notify_hint: '' });
    const payModal = ref(false);
    const purchaseRow = ref(null);
    const payType = ref('alipay');
    const paying = ref(false);
    const authorizedCount = computed(() => domains.value.filter(d => d.status === 'authorized').length);

    async function api(q, opts = {}) {
      try {
        const r = await fetch(BASE + '/index.php?action=' + q, opts);
        const text = await r.text();
        try {
          return JSON.parse(text);
        } catch (e) {
          return { ok: false, error: '服务器返回非 JSON（' + r.status + '）' };
        }
      } catch (e) {
        return { ok: false, error: '网络请求失败：' + (e.message || e) };
      }
    }

    const geetestMounts = {
      'login-sms': 'geetest-mount-login-sms',
      'login-pwd': 'geetest-mount-login-pwd',
      register: 'geetest-mount-register',
    };

    function activeGeetestScene() {
      if (authTab.value === 'register') {
        return 'register';
      }
      return loginForm.mode === 'password' ? 'login-pwd' : 'login-sms';
    }

    function clearGeetestScene(scene) {
      if (typeof PortalGeetest !== 'undefined') {
        PortalGeetest.reset(scene);
      }
    }

    async function mountActiveGeetest() {
      if (typeof PortalGeetest === 'undefined') {
        return;
      }
      await Vue.nextTick();
      setTimeout(async () => {
        const scene = activeGeetestScene();
        const elId = geetestMounts[scene];
        if (!elId || !document.getElementById(elId)) {
          return;
        }
        try {
          await PortalGeetest.mount(scene, elId);
        } catch (e) {
          console.warn('[portal-geetest]', e);
          toastError(e.message || '极验加载失败');
        }
      }, 280);
    }

    function onAuthTabChange() {
      mountActiveGeetest();
    }
    function onLoginModeChange() {
      mountActiveGeetest();
    }

    async function readGeetest(scene) {
      if (typeof PortalGeetest === 'undefined') {
        toastError('极验未加载，请刷新页面');
        return null;
      }
      const p = PortalGeetest.getPayload(scene);
      if (!p) {
        toastError('请先点击极验条完成行为验证');
        return null;
      }
      return p;
    }

    function startSmsCooldown(purpose, seconds) {
      const wait = Math.max(1, seconds || 60);
      smsCooldown[purpose] = wait;
      const timer = setInterval(() => {
        smsCooldown[purpose]--;
        if (smsCooldown[purpose] <= 0) clearInterval(timer);
      }, 1000);
    }
    async function sendSmsCode(purpose) {
      const form = purpose === 'login' ? loginForm : regForm;
      const phone = (form.phone || '').trim();
      if (!/^1\d{10}$/.test(phone)) {
        toastError('请输入正确的 11 位手机号');
        return;
      }
      if (smsCooldown[purpose] > 0) return;
      const scene = purpose === 'login' ? 'login-sms' : 'register';
      const geetest = await readGeetest(scene);
      if (!geetest) return;
      smsPurpose.value = purpose;
      smsSending.value = true;
      const j = await api('portal_send_sms_code', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ phone, purpose, geetest }),
      });
      smsSending.value = false;
      smsPurpose.value = '';
      clearGeetestScene(scene);
      if (!j.ok) return toastError(j.error || '发送失败');
      toastSuccess('验证码已发送');
      startSmsCooldown(purpose, j.retry_after || 60);
    }
    async function doLogin() {
      const mode = loginForm.mode || 'sms';
      const geetest = await readGeetest(mode === 'password' ? 'login-pwd' : 'login-sms');
      if (!geetest) return;
      let body = { mode, geetest };
      if (mode === 'password') {
        const account = (loginForm.account || '').trim();
        const password = loginForm.password || '';
        if (!account || !password) {
          toastError('请填写账号和密码');
          return;
        }
        body.account = account;
        body.password = password;
      } else {
        const phone = (loginForm.phone || '').trim();
        const code = (loginForm.code || '').trim();
        if (!phone || !code) {
          toastError('请填写手机号和验证码');
          return;
        }
        body.phone = phone;
        body.code = code;
      }
      authLoading.value = true;
      const j = await api('portal_login', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify(body),
      });
      authLoading.value = false;
      clearGeetestScene(mode === 'password' ? 'login-pwd' : 'login-sms');
      if (!j.ok) return toastError(j.error || '登录失败');
      if (j.user) Object.assign(user, j.user);
      authed.value = true;
      toastSuccess('登录成功');
      await loadPortal();
    }
    async function doRegister() {
      const phone = (regForm.phone || '').trim();
      const code = (regForm.code || '').trim();
      const company = (regForm.company || '').trim();
      const username = (regForm.username || '').trim();
      const password = regForm.password || '';
      if (!company || !username || !password || !phone || !code) {
        toastError('请填写完整注册信息');
        return;
      }
      if (password.length < 6) {
        toastError('密码至少 6 位');
        return;
      }
      const geetest = await readGeetest('register');
      if (!geetest) return;
      authLoading.value = true;
      const j = await api('portal_register', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ phone, code, company, username, password, geetest }),
      });
      authLoading.value = false;
      clearGeetestScene('register');
      if (!j.ok) return toastError(j.error || '注册失败');
      if (j.user) Object.assign(user, j.user);
      authed.value = true;
      toastSuccess('注册成功，数据已同步至 Yuser 授权中心');
      await loadPortal();
    }
    async function loadPortal() {
      const j = await api('portal_me');
      if (j.user) Object.assign(user, j.user);
      domains.value = j.domains || [];
      if (j.payment) Object.assign(payment, j.payment);
      const o = await api('overview');
      Object.assign(overview, o);
      Object.assign(pack, o.pack || {});
      Object.assign(brand, o.brand || {});
    }
    function portalReturnUrl(orderNo) {
      const u = new URL(window.location.href);
      u.searchParams.set('pay_return', '1');
      u.searchParams.set('order', orderNo || '');
      return u.toString();
    }
    async function checkPayReturn() {
      const q = new URLSearchParams(window.location.search);
      if (q.get('pay_return') !== '1') return;
      const orderNo = q.get('order') || '';
      if (orderNo) {
        const j = await api('portal_order_status&order_no=' + encodeURIComponent(orderNo));
        if (j.ok && j.order && j.order.status === 'paid') {
          toastSuccess('支付成功，域名已开通正版授权');
        } else if (j.ok && j.order && j.order.status === 'cancelled') {
          toast('warning', '该支付单已失效，请重新点击「购买授权」');
        } else if (j.ok && j.order) {
          toast('warning', '支付确认中，请稍后刷新；若已失败请重新点击「购买授权」');
        }
      }
      const clean = new URL(window.location.href);
      clean.searchParams.delete('pay_return');
      clean.searchParams.delete('order');
      window.history.replaceState({}, '', clean.pathname + (clean.search || ''));
      loadPortal();
    }
    function openPurchase(row) {
      purchaseRow.value = row;
      payType.value = (payment.pay_types && payment.pay_types[0]) ? payment.pay_types[0].id : 'alipay';
      payModal.value = true;
    }
    async function submitPurchase() {
      if (!purchaseRow.value) return;
      if (!payment.enabled) return toastError('暂未开启在线购买');
      paying.value = true;
      const j = await api('portal_create_order', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          csrf: CSRF,
          domain_id: purchaseRow.value.id,
          pay_type: payType.value,
          return_url: window.location.href.split('?')[0],
        }),
      });
      paying.value = false;
      if (!j.ok) return toastError(j.error || '创建订单失败');
      payModal.value = false;
      if (!j.pay_url) return toastError('未获取支付地址');
      window.location.href = j.pay_url;
    }
    async function bindDomain() {
      binding.value = true;
      const j = await api('portal_bind_domain', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ csrf: CSRF, domain: newDomain.value }) });
      binding.value = false;
      if (!j.ok) return toastError(j.error || '绑定失败');
      toastSuccess(payment.enabled ? '绑定成功，请点击「购买授权」完成支付' : '绑定成功');
      newDomain.value = '';
      loadPortal();
    }
    async function unbindDomain(record) {
      const j = await api('portal_unbind_domain', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ csrf: CSRF, domain_id: record.id }),
      });
      if (!j.ok) return toastError(j.error || '删除失败');
      toastSuccess('已解除绑定');
      loadPortal();
    }
    async function loadTokens() {
      const j = await api('tokens_get');
      tokens.value = { ...(j.tokens || {}) };
    }
    async function saveTokens() {
      saving.value = true;
      const j = await api('tokens_save', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ csrf: CSRF, tokens: tokens.value }) });
      saving.value = false;
      if (!j.ok) return toastError(j.error || '失败');
      toastSuccess('已保存');
    }
    async function resetTokens() {
      const j = await api('tokens_reset', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ csrf: CSRF }) });
      if (j.tokens) tokens.value = j.tokens;
    }
    async function saveBrand() {
      saving.value = true;
      const j = await api('brand_save', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ csrf: CSRF, brand }) });
      saving.value = false;
      if (!j.ok) return toastError(j.error || '失败');
      toastSuccess('已保存');
    }
    async function checkUpdate() {
      checking.value = true; checked.value = true;
      const j = await api('update_check');
      checking.value = false;
      releases.value = j.releases || [];
      if (j.pack) Object.assign(pack, j.pack);
    }
    async function applyUpdate(r) {
      if (!confirm('安装将覆盖主题文件，继续？')) return;
      const j = await api('update_apply', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ csrf: CSRF, version: r.version, package_url: r.package_url, sha256: r.sha256 }) });
      if (!j.ok) return toastError(j.error || '失败');
      toastSuccess('已更新');
      checkUpdate();
    }
    watch(page, p => {
      if (p === 'colors') loadTokens();
      if (p === 'updates') checkUpdate();
    });
    onMounted(() => {
      if (authed.value) {
        loadPortal().then(() => checkPayReturn());
      } else {
        mountActiveGeetest();
      }
    });
    return {
      authed, authTab, authLoading, loginForm, regForm, user, smsSending, smsPurpose, smsCooldown,
      sendSmsCode, onAuthTabChange, onLoginModeChange,
      page, titles, overview, pack, domains, newDomain, binding,
      tokens, brand, saving, checking, checked, releases, authorizedCount, payment, payModal, purchaseRow, payType, paying,
      doLogin, doRegister, bindDomain, unbindDomain, openPurchase, submitPurchase, saveTokens, resetTokens, saveBrand, checkUpdate, applyUpdate
    };
  }
}).use(ArcoVue).use(ArcoVueIcon)
  .component('icon-home', ArcoVueIcon.IconHome)
  .component('icon-link', ArcoVueIcon.IconLink)
  .component('icon-palette', ArcoVueIcon.IconPalette)
  .component('icon-image', ArcoVueIcon.IconImage)
  .component('icon-refresh', ArcoVueIcon.IconRefresh)
  .mount('#app');
</script>
</body>
</html>
