=== LiteMall Frontend Source Code ===
=== Generated: 2026-05-28 12:58 ===

===== admin/index.html =====
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="0">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>LiteMall · 管理后台</title>
<link rel="stylesheet" href="/lib/element-plus.css?v=2">
<style>
:root {
  --primary: #5B7FFF;
  --brand: #5B7FFF;
  --primary-light: #EDF2FF;
  --bg: #F5F7FA;
  --card-bg: #FFF;
  --text: #1D2129;
  --text-secondary: #86909C;
  --border: #F2F3F5;
  --radius: 12px;
  --shadow: 0 2px 12px rgba(0,0,0,0.06);
}
[v-cloak]{display:none}
*{margin:0;padding:0;box-sizing:border-box}
body{font-family:-apple-system,'PingFang SC','Microsoft YaHei',sans-serif;background:var(--bg);color:var(--text);-webkit-font-smoothing:antialiased}

/* ====== 登录页 ====== */
.login-page{min-height:100vh;display:flex;align-items:center;justify-content:center;background:linear-gradient(135deg,#5B7FFF0a 0%,#5B7FFF1a 50%,#F5F7FA 100%)}
.login-wrap{width:440px;background:#FFF;border-radius:20px;box-shadow:0 20px 60px rgba(91,127,255,0.10),0 0 0 1px rgba(0,0,0,0.04);padding:48px 44px}
.login-logo{text-align:center;margin-bottom:36px}
.login-logo .icon{width:56px;height:56px;background:linear-gradient(135deg,#5B7FFF,#8B9FFF);border-radius:16px;display:inline-flex;align-items:center;justify-content:center;font-size:28px}
.login-logo h1{font-size:22px;font-weight:700;color:var(--text);margin-top:16px;letter-spacing:-0.3px}
.login-logo p{font-size:13px;color:var(--text-secondary);margin-top:4px}
.login-wrap .el-input{--el-input-border-radius:10px;--el-input-focus-border-color:var(--primary)}
.login-wrap .el-button--primary{--el-button-bg-color:var(--primary);--el-button-border-color:var(--primary);--el-button-hover-bg-color:#4A6FE8;--el-button-hover-border-color:#4A6FE8;border-radius:10px;height:44px;font-size:15px;font-weight:600;letter-spacing:1px}

/* ====== 主布局 ====== */
.app-layout{display:flex;height:100vh}
.sidebar{width:230px;background:linear-gradient(180deg,#1D2129 0%,#272E3B 100%);color:#FFF;display:flex;flex-direction:column;flex-shrink:0;transition:width 0.2s}
.sidebar-header{height:64px;display:flex;align-items:center;gap:10px;padding:0 20px;border-bottom:1px solid rgba(255,255,255,0.06)}
.sidebar-header .s-icon{width:36px;height:36px;background:linear-gradient(135deg,#5B7FFF,#8B9FFF);border-radius:10px;display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0}
.sidebar-header .s-title{font-size:15px;font-weight:700;letter-spacing:-0.3px}
.sidebar-nav{flex:1;overflow-y:auto;padding:12px 0}
.sidebar-nav .nav-group{padding:0 12px;margin-bottom:8px}
.sidebar-nav .nav-group-title{font-size:11px;text-transform:uppercase;color:rgba(255,255,255,0.3);padding:12px 12px 6px;font-weight:600;letter-spacing:1px}
.sidebar-nav .nav-item{display:flex;align-items:center;gap:10px;padding:10px 14px;border-radius:10px;cursor:pointer;font-size:14px;color:rgba(255,255,255,0.6);transition:all 0.15s;margin-bottom:2px}
.sidebar-nav .nav-item:hover{background:rgba(255,255,255,0.05);color:#FFF}
.sidebar-nav .nav-item.active{background:rgba(91,127,255,0.2);color:#FFF;font-weight:600}
.sidebar-nav .nav-item .el-icon{font-size:16px;width:20px;margin-right:2px}
.sidebar-nav .nav-item .badge{margin-left:auto;background:#F53F3F;color:#FFF;font-size:11px;padding:2px 7px;border-radius:10px;font-weight:600}

.main-area{flex:1;display:flex;flex-direction:column;overflow:hidden}
.topbar{height:64px;background:var(--card-bg);border-bottom:1px solid var(--border);display:flex;align-items:center;justify-content:space-between;padding:0 28px;flex-shrink:0}
.topbar .breadcrumb{font-size:14px;color:var(--text-secondary)}
.topbar .breadcrumb span{color:var(--text);font-weight:600}
.topbar .user-menu{display:flex;align-items:center;gap:16px}
.topbar .user-menu .notify{width:36px;height:36px;border-radius:50%;background:var(--bg);display:flex;align-items:center;justify-content:center;cursor:pointer;font-size:16px;position:relative}
.topbar .user-menu .notify .dot{width:7px;height:7px;background:#F53F3F;border-radius:50%;position:absolute;top:7px;right:9px}
.topbar .user-menu .avatar{display:flex;align-items:center;gap:8px;cursor:pointer;padding:4px 8px;border-radius:8px;transition:background 0.15s}
.topbar .user-menu .avatar:hover{background:var(--bg)}

.content{padding:24px 28px;overflow-y:auto;flex:1}
.page-header{margin-bottom:24px;display:flex;justify-content:space-between;align-items:center}
.page-header h2{font-size:22px;font-weight:700;letter-spacing:-0.4px}
.page-header .actions{display:flex;gap:10px}

/* ====== 卡片 ====== */
.stat-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(240px,1fr));gap:16px;margin-bottom:24px}
.stat-card{background:var(--card-bg);border-radius:var(--radius);padding:20px 24px;box-shadow:var(--shadow);display:flex;justify-content:space-between;align-items:flex-start;transition:transform 0.15s,box-shadow 0.15s;cursor:default}
.stat-card:hover{transform:translateY(-2px);box-shadow:0 4px 20px rgba(0,0,0,0.08)}
.stat-card .info .label{font-size:13px;color:var(--text-secondary);margin-bottom:8px}
.stat-card .info .value{font-size:32px;font-weight:800;letter-spacing:-1px;line-height:1}
.stat-card .info .change{font-size:12px;margin-top:6px}
.stat-card .info .change.up{color:#00B578}
.stat-card .info .change.down{color:#F53F3F}
.stat-card .icon-box{width:48px;height:48px;border-radius:14px;display:flex;align-items:center;justify-content:center;font-size:22px}

.card{background:var(--card-bg);border-radius:var(--radius);box-shadow:var(--shadow);overflow:hidden}
.card-header{padding:16px 20px;border-bottom:1px solid var(--border);display:flex;justify-content:space-between;align-items:center;font-weight:600;font-size:15px}
.card-body{padding:20px}

/* ====== 表格 ====== */
.table-card .el-table{--el-table-border-color:transparent}
.table-card .el-table th{background:#FAFBFC;color:var(--text-secondary);font-weight:600;font-size:12px;text-transform:uppercase;letter-spacing:0.5px}
.table-card .el-table td{font-size:13px}
.table-toolbar{display:flex;gap:10px;align-items:center;flex-wrap:wrap;margin-bottom:16px}
.table-toolbar .spacer{flex:1}
.toolbar-btn{height:34px;border-radius:8px;font-size:13px;font-weight:500}

/* ====== 弹窗 ====== */
.el-dialog{--el-dialog-border-radius:16px;--el-dialog-padding-primary:24px}
.el-dialog__header{margin-right:0;padding-bottom:16px;border-bottom:1px solid var(--border)}
.el-dialog__title{font-size:17px;font-weight:700}

/* ====== 状态标签 ====== */
.tag-pending{color:#FF7D00;background:#FFF7E6;border:1px solid #FFD591;border-radius:6px;padding:3px 10px;font-size:12px;font-weight:500}
.tag-shipping{color:#5B7FFF;background:#EDF2FF;border:1px solid #ADC6FF;border-radius:6px;padding:3px 10px;font-size:12px;font-weight:500}
.tag-done{color:#00B578;background:#E8FFF3;border:1px solid #87E8B5;border-radius:6px;padding:3px 10px;font-size:12px;font-weight:500}
.tag-close{color:#86909C;background:#F2F3F5;border:1px solid #E5E6EB;border-radius:6px;padding:3px 10px;font-size:12px;font-weight:500}
.tag-danger{color:#F53F3F;background:#FFECE8;border:1px solid #FFBCB5;border-radius:6px;padding:3px 10px;font-size:12px;font-weight:500}

/* ====== 空状态 ====== */
.empty-state{padding:60px 0;text-align:center;color:var(--text-secondary)}
.empty-state .emoji{font-size:48px;margin-bottom:12px}
.empty-state p{font-size:14px}

/* ====== 滚动条 ====== */
::-webkit-scrollbar{width:5px;height:5px}
::-webkit-scrollbar-track{background:transparent}
::-webkit-scrollbar-thumb{background:#D0D5DD;border-radius:10px}

/* ====== 表单 ====== */
.el-input{--el-input-border-radius:8px}
.el-select{--el-border-radius-base:8px}
.el-button{--el-border-radius-base:8px}
.el-tabs__item{font-size:14px;font-weight:500}

/* ====== 响应式 ====== */
@media(max-width:1200px){
  .sidebar{width:72px}
  .sidebar .s-title,.sidebar .nav-item span:not(.el-icon),.sidebar .nav-group-title{display:none}
  .sidebar .nav-item{justify-content:center;padding:12px;font-size:0}
  .sidebar .nav-item .el-icon{font-size:20px;margin:0}.sidebar-header{justify-content:center;padding:0 10px}
}
@media(max-width:768px){
  .sidebar{position:fixed;left:-260px;top:0;bottom:0;z-index:1000;transition:left .3s;width:240px}
  .sidebar.open{left:0}
  .sidebar-overlay{display:none;position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.4);z-index:999}
  .sidebar-overlay.show{display:block}
  .sidebar .nav-item span:not(.el-icon){display:inline!important;font-size:14px!important}
  .sidebar .nav-item .el-icon{font-size:18px!important}
  .topbar .menu-toggle{display:flex!important}
  .content{padding:16px}
  .stat-grid{grid-template-columns:1fr 1fr!important;gap:10px}
  .stat-card{padding:14px!important}
  .stat-card .info .value{font-size:22px!important}
  .el-dialog{width:90vw!important}
  .table-toolbar{flex-wrap:wrap}
  .card-body{overflow-x:auto}
}
@media(max-width:480px){
  .stat-grid{grid-template-columns:1fr 1fr!important;gap:8px}
  .stat-card{padding:12px!important}
  .stat-card .info .value{font-size:20px!important}
  .topbar{padding:0 12px}.content{padding:12px}
  .page-header h2{font-size:18px}
}
.topbar .menu-toggle{display:none;width:36px;height:36px;align-items:center;justify-content:center;cursor:pointer;border-radius:8px;font-size:18px;flex-shrink:0}
.topbar .menu-toggle:active{background:var(--bg)}
</style>
</head>
<body>
<div id="app" v-cloak>
  <!-- ============ 登录页 ============ -->
  <div v-if="!token" class="login-page">
    <div class="login-wrap">
      <div class="login-logo">
        <div class="icon"><el-icon size="28"><ShoppingBag /></el-icon></div>
        <h1>LiteMall</h1>
        <p>管理后台</p>
      </div>
      <el-form ref="loginRef" :model="loginForm" :rules="loginRules" size="large" @keyup.enter="doLogin">
        <el-form-item prop="phone">
          <el-input v-model="loginForm.phone" placeholder="管理员账号" clearable />
        </el-form-item>
        <el-form-item prop="password">
          <el-input v-model="loginForm.password" type="password" placeholder="密码" show-password />
        </el-form-item>
        <el-form-item style="margin-top:8px">
          <el-button type="primary" style="width:100%" @click="doLogin" :loading="loginLoading">登 录</el-button>
        </el-form-item>
      </el-form>
    </div>
  </div>

  <div v-else class="app-layout">
    <!-- 侧栏 -->
    <div class="sidebar-overlay" :class="{show:sidebarOpen}" @click="sidebarOpen=false"></div>
    <aside class="sidebar" :class="{open:sidebarOpen}">
      <div class="sidebar-header">
        <span class="s-icon"><el-icon size="20"><ShoppingBag /></el-icon></span>
        <span class="s-title">LiteMall</span>
      </div>
      <nav class="sidebar-nav">
        <div class="nav-group">
          <div class="nav-group-title">主菜单</div>
          <div class="nav-item" :class="{active:menu==='dashboard'}" @click="switchMenu('dashboard')"><el-icon><DataAnalysis /></el-icon><span>📊 仪表盘</span></div>
          <div class="nav-item" :class="{active:menu==='products'}" @click="switchMenu('products')"><el-icon><Goods /></el-icon><span>📦 商品管理</span></div>
          <div class="nav-item" :class="{active:menu==='orders'}" @click="switchMenu('orders')"><el-icon><Document /></el-icon><span>📋 订单管理</span><span class="badge" v-if="dashboard.pending_ship>0">{{dashboard.pending_ship}}</span></div>
          <div class="nav-item" :class="{active:menu==='users'}" @click="switchMenu('users')"><el-icon><User /></el-icon><span>👥 会员管理</span></div>
        </div>
        <div class="nav-group">
          <div class="nav-group-title">运营</div>
          <div class="nav-item" :class="{active:menu==='distribution'}" @click="switchMenu('distribution')"><el-icon><Share /></el-icon><span>🔗 分销管理</span></div>
          <div class="nav-item" :class="{active:menu==='marketing'}" @click="switchMenu('marketing')"><el-icon><Aim /></el-icon><span>🎯 营销工具</span></div>
          <div class="nav-item" :class="{active:menu==='ai'}" @click="switchMenu('ai')"><el-icon><MagicStick /></el-icon><span>🤖 AI客服</span></div>
          <div class="nav-item" :class="{active:menu==='stats'}" @click="switchMenu('stats')"><el-icon><TrendCharts /></el-icon><span>📈 数据统计</span></div>          <div class="nav-item" :class="{active:menu==='design'}" @click="switchMenu('design')"><el-icon><MagicStick /></el-icon><span>🎨 页面装修</span></div>
          <div class="nav-item" :class="{active:menu==='export'}" @click="switchMenu('export')"><el-icon><Upload /></el-icon><span>📤 数据导出</span></div>
          <div class="nav-item" :class="{active:menu==='stock_alerts'}" @click="switchMenu('stock_alerts')"><el-icon><WarningFilled /></el-icon><span>⚠️ 库存预警</span><span class="badge" v-if="dashboard.low_stock>0">{{dashboard.low_stock}}</span></div>
        </div>
        <div class="nav-group">
          <div class="nav-group-title">系统</div>
          <div class="nav-item" :class="{active:menu==='settings'}" @click="switchMenu('settings')"><el-icon><Setting /></el-icon><span>⚙️ 系统设置</span></div>
          <div class="nav-item" :class="{active:menu==='logs'}" @click="switchMenu('logs')"><el-icon><Edit /></el-icon><span>📝 操作日志</span></div>
          <div class="nav-item" :class="{active:menu==='notifications'}" @click="switchMenu('notifications')"><el-icon><Bell /></el-icon><span>🔔 通知管理</span></div>
        </div>
      </nav>
    </aside>

    <!-- 主区域 -->
    <div class="main-area">
      <header class="topbar">
        <div class="breadcrumb">{{pageTitle}}</div>
        <div class="user-menu">
          <div class="notify" title="通知"><span>🔔</span><span class="dot" v-if="dashboard.pending_ship>0"></span></div>
          <div class="menu-toggle" @click="sidebarOpen=!sidebarOpen">☰</div>
          <el-dropdown trigger="click" @command="onUserCmd">
            <div class="avatar"><span style="width:32px;height:32px;background:var(--primary);border-radius:50%;display:flex;align-items:center;justify-content:center;color:#fff;font-size:14px;font-weight:600">A</span><span style="font-size:14px;color:var(--text);font-weight:500">{{adminName}}</span><span style="font-size:10px;color:var(--text-secondary);margin-top:2px">▼</span></div>
            <template #dropdown>
              <el-dropdown-menu>
                <el-dropdown-item>👤 管理员</el-dropdown-item>
                <el-dropdown-item command="logout" divided>退出登录</el-dropdown-item>
              </el-dropdown-menu>
            </template>
          </el-dropdown>
        </div>
      </header>

      <div class="content" style="transition:opacity 0.2s">
        <!-- ========= 仪表盘 ========= -->
        <div v-show="menu==='dashboard'">
          <div class="page-header"><h2>数据概览</h2></div>
          <div class="stat-grid" v-loading="dashboardLoading" element-loading-text="加载中...">
            <div class="stat-card"><div class="info"><div class="label">今日订单</div><div class="value">{{dashboard.today_orders||0}}</div></div><div class="icon-box" style="background:#EDF2FF;color:#5B7FFF">📋</div></div>
            <div class="stat-card"><div class="info"><div class="label">今日销售额</div><div class="value">¥{{fmt(dashboard.today_sales)}}</div></div><div class="icon-box" style="background:#E8FFF3;color:#00B578">💰</div></div>
            <div class="stat-card"><div class="info"><div class="label">总用户数</div><div class="value">{{dashboard.total_users||0}}</div></div><div class="icon-box" style="background:#EDF2FF;color:#5B7FFF">👥</div></div>
            <div class="stat-card"><div class="info"><div class="label">待发货</div><div class="value">{{dashboard.pending_ship||0}}</div><div class="value change" v-if="dashboard.pending_ship>0" style="color:#FF7D00">需处理</div></div><div class="icon-box" style="background:#FFF7E6;color:#FF7D00">📮</div></div>
            <div class="stat-card" style="cursor:pointer" @click="dashboard.pending_refund>0?switchMenu('orders'):null"><div class="info"><div class="label">退款售后</div><div class="value">{{dashboard.pending_refund||0}}</div><div class="value change" v-if="dashboard.pending_refund>0" style="color:#F53F3F">需处理</div></div><div class="icon-box" style="background:#FFECE8;color:#F53F3F">🔄</div></div>
            <div class="stat-card" style="cursor:pointer" @click="dashboard.low_stock>0?switchMenu('stock_alerts'):null"><div class="info"><div class="label">库存不足</div><div class="value">{{dashboard.low_stock||0}}</div><div class="value change" v-if="dashboard.low_stock>0" style="color:#F53F3F">≤10件</div></div><div class="icon-box" style="background:#FFF0E0;color:#FF7D00">⚠️</div></div>
          </div>
          <div style="display:grid;grid-template-columns:1.5fr 1fr;gap:16px">
            <div class="card"><div class="card-header"><span>📈 近7日销售趋势</span></div><div class="card-body"><div id="chart" style="height:300px"></div></div></div>
            <div class="card">
              <div class="card-header"><span>⚡ 快捷操作</span></div>
              <div class="card-body" style="display:grid;grid-template-columns:1fr 1fr;gap:10px">
                <el-button style="height:80px;border-radius:12px;font-size:15px" @click="switchMenu('products')">📦<br>商品管理</el-button>
                <el-button style="height:80px;border-radius:12px;font-size:15px" @click="switchMenu('orders')">📋<br>订单管理</el-button>
                <el-button style="height:80px;border-radius:12px;font-size:15px" plain @click="switchMenu('users')">👥<br>会员管理</el-button>
                <el-button style="height:80px;border-radius:12px;font-size:15px" plain @click="switchMenu('settings')">⚙️<br>系统设置</el-button>
              </div>
            </div>
          </div>
        </div>

        <!-- ========= 商品管理 ========= -->
        <div v-show="menu==='products'">
          <div class="page-header"><h2>商品管理</h2><div class="actions"><el-button type="primary" @click="openProduct()">✚ 新增商品</el-button></div></div>
          <div class="card table-card">
            <div class="card-body">
              <div class="table-toolbar">
                <el-input v-model="pq.keyword" placeholder="搜索商品名称" clearable style="width:220px" @clear="loadProducts" @keyup.enter="loadProducts" size="small" />
                <el-select v-model="pq.status" placeholder="状态" clearable style="width:110px" @change="loadProducts" size="small">
                  <el-option label="上架" :value="1" /><el-option label="下架" :value="0" />
                </el-select>
                <span class="spacer"></span>
                <el-button size="small" @click="loadProducts">🔍 查询</el-button>
              <el-button size="small" @click="batchOnline" v-if="selectedProds.length">批量上架</el-button>
              <el-button size="small" @click="batchOffline" v-if="selectedProds.length">批量下架</el-button>
              </div>
              <el-table :data="productList" border stripe v-loading="productLoading" @selection-change="s=>selectedProds=s" empty-text="📦 暂无商品，点击右上角「新增商品」开始上架">
                <el-table-column label="商品" min-width="200">
                  <template #default="{row}"><div style="display:flex;align-items:center;gap:10px">
                    <img v-if="row.image" :src="row.image" style="width:52px;height:52px;border-radius:8px;object-fit:cover" loading="lazy" />
                    <div v-else style="width:52px;height:52px;border-radius:8px;background:var(--bg);display:flex;align-items:center;justify-content:center">🖼</div>
                    <div><div style="font-weight:600;font-size:14px">{{row.name}}</div><div style="font-size:12px;color:var(--text-secondary)">{{row.subtitle||'—'}}</div></div>
                  </div></template>
                </el-table-column>
                <el-table-column label="价格" width="110"><template #default="{row}"><span style="font-weight:700;color:#F53F3F">¥{{fmt(row.price)}}</span></template></el-table-column>
                <el-table-column label="库存" width="80" prop="stock" />
                <el-table-column label="销量" width="80" prop="sales" />
                <el-table-column label="状态" width="80"><template #default="{row}"><span :class="row.status?'tag-done':'tag-close'">{{row.status?'上架':'下架'}}</span></template></el-table-column>
                <el-table-column label="创建时间" width="140"><template #default="{row}">{{(row.created_at||'').slice(0,10)}}</template></el-table-column>
                <el-table-column label="操作" width="180" fixed="right">
                  <template #default="{row}">
                    <el-button link type="primary" size="small" @click="openProduct(row)">编辑</el-button>
                  <el-button link type="primary" size="small" @click="copyProduct(row)">复制</el-button>
                    <el-button link size="small" @click="toggleProduct(row)">{{row.status?'下架':'上架'}}</el-button>
                    <el-popconfirm title="确定删除此商品？" @confirm="delProduct(row.id)"><template #reference><el-button link type="danger" size="small">删除</el-button></template></el-popconfirm>
                  </template>
                </el-table-column>
              </el-table>
              <div v-if="productTotal>0" style="margin-top:16px;display:flex;justify-content:center">
                <el-pagination background layout="prev, pager, next" :total="productTotal" :page-size="20" @current-change="p=>{pq.page=p;loadProducts()}" small />
              </div>
            </div>
          </div>
          <!-- 商品弹窗 -->
          <el-dialog v-model="pd.show" :title="pd.edit?'编辑商品':'新增商品'" width="820px" :close-on-click-modal="false" @closed="pd.show=false">
            <el-form :model="pf" label-width="90px" size="default">
              <div style="display:flex;justify-content:flex-end;margin-bottom:8px"><el-button size="small" type="success" @click="showCollector=true" :disabled="pd.edit">📥 从链接采集</el-button></div>
            <el-tabs v-model="productTab">
                <el-tab-pane label="基本信息" name="base">
                  <div style="display:grid;grid-template-columns:1fr 1fr;gap:12px 24px">
                    <el-form-item label="商品名称"><el-input v-model="pf.name" placeholder="请输入商品名称" /></el-form-item>
                    <el-form-item label="副标题"><el-input v-model="pf.subtitle" placeholder="营销卖点，选填" /></el-form-item>
                    <el-form-item label="分类">
                      <el-select v-model="pf.category_id" placeholder="选择分类" style="width:100%" filterable><el-option v-for="c in categories" :key="c.id" :label="c.name" :value="c.id" /></el-select>
                    </el-form-item>
                    <el-form-item label="单位"><el-input v-model="pf.unit" placeholder="如：条/个/件" /></el-form-item>
                    <el-form-item label="售价"><el-input-number v-model="pf.price" :min="0" :precision="2" style="width:100%" /></el-form-item>
                    <el-form-item label="原价"><el-input-number v-model="pf.original_price" :min="0" :precision="2" style="width:100%" /></el-form-item>
                    <el-form-item label="库存"><el-input-number v-model="pf.stock" :min="0" style="width:100%" /></el-form-item>
                    <el-form-item label="排序"><el-input-number v-model="pf.sort" :min="0" style="width:100%" /></el-form-item>
                  </div>
                  <el-form-item label="商品图片"><el-input v-model="pf.image" placeholder="图片URL" /><span style="font-size:11px;color:var(--text-secondary)">支持直接粘贴图片链接</span></el-form-item>
                  <el-form-item label="商品属性">
                    <el-checkbox v-model="pf.is_new">新品</el-checkbox>
                    <el-checkbox v-model="pf.is_hot">热销</el-checkbox>
                    <el-checkbox v-model="pf.is_recommend">推荐</el-checkbox>
                  </el-form-item>
                </el-tab-pane>
                <el-tab-pane label="详情描述" name="desc">
                  <el-input v-model="pf.description" type="textarea" :rows="12" placeholder="支持HTML，可粘贴商品详情图文" />
                </el-tab-pane>
                <el-tab-pane label="规格管理" name="specs">
                  <div style="margin-bottom:12px">
                    <el-button type="primary" size="small" @click="specForm.show=true">✚ 新增规格</el-button>
                    <span style="font-size:12px;color:var(--text-secondary);margin-left:8px">先添加规格后再设置SKU</span>
                  </div>
                  <el-table :data="specList" border stripe size="small" empty-text="暂无规格">
                    <el-table-column label="规格名称" prop="name" width="140" />
                    <el-table-column label="规格值" min-width="200"><template #default="{row}">{{((row.spec_values||row.values||[])).join('、')||'—'}}</template></el-table-column>
                    <el-table-column label="操作" width="80">
                      <template #default="{row}">
                        <el-popconfirm title="确定删除？" @confirm="deleteSpec(row)"><template #reference><el-button link type="danger" size="small">删除</el-button></template></el-popconfirm>
                      </template>
                    </el-table-column>
                  </el-table>
                </el-tab-pane>
                <el-tab-pane label="SKU管理" name="skus">
                  <div style="margin-bottom:12px">
                    <el-button type="primary" size="small" @click="openSkuForm()">✚ 新增SKU</el-button>
                  </div>
                  <el-table :data="skuList" border stripe size="small" empty-text="暂无SKU（请先在规格管理添加规格）">
                    <el-table-column label="规格组合" min-width="140"><template #default="{row}">{{JSON.parse(row.specs||'[]').join(' / ')||'默认'}}</template></el-table-column>
                    <el-table-column label="价格" width="90"><template #default="{row}">¥{{fmt(row.price)}}</template></el-table-column>
                    <el-table-column label="库存" width="70" prop="stock" />
                    <el-table-column label="图片" width="80"><template #default="{row}"><img v-if="row.image" :src="row.image" style="width:36px;height:36px;border-radius:4px;object-fit:cover" loading="lazy" /></template></el-table-column>
                    <el-table-column label="操作" width="120">
                      <template #default="{row}">
                        <el-button link size="small" @click="openSkuForm(row)">编辑</el-button>
                        <el-popconfirm title="确定删除？" @confirm="deleteSku(row)"><template #reference><el-button link type="danger" size="small">删除</el-button></template></el-popconfirm>
                      </template>
                    </el-table-column>
                  </el-table>
                </el-tab-pane>
              </el-tabs>
            </el-form>
            <!-- 新增规格弹窗 -->
            <el-dialog v-model="specForm.show" title="新增规格" width="400px" append-to-body>
              <el-form label-width="80px" size="small">
                <el-form-item label="规格名称"><el-input v-model="specForm.name" placeholder="如：颜色" /></el-form-item>
                <el-form-item label="规格值"><el-input v-model="specForm.valuesStr" placeholder="用逗号分隔，如：红色,蓝色,白色" /></el-form-item>
              </el-form>
              <template #footer><el-button @click="specForm.show=false">取消</el-button><el-button type="primary" @click="saveSpec" :loading="specSaving">保存</el-button></template>
            </el-dialog>
            <!-- 新增/编辑SKU弹窗 -->
            <el-dialog v-model="skuForm.show" :title="skuForm.edit?'编辑SKU':'新增SKU'" width="440px" append-to-body>
              <el-form label-width="90px" size="small">
                <el-form-item label="规格组合">
                  <el-select v-model="skuForm.specs" multiple filterable placeholder="选择规格值（如：红色、大号）" style="width:100%">
                    <el-option v-for="v in allSpecValues" :key="v" :label="v" :value="v" />
                  </el-select>
                </el-form-item>
                <el-form-item label="价格"><el-input-number v-model="skuForm.price" :min="0" :precision="2" style="width:100%" /></el-form-item>
                <el-form-item label="库存"><el-input-number v-model="skuForm.stock" :min="0" style="width:100%" /></el-form-item>
                <el-form-item label="原价"><el-input-number v-model="skuForm.original_price" :min="0" :precision="2" style="width:100%" /></el-form-item>
                <el-form-item label="图片"><el-input v-model="skuForm.image" placeholder="图片URL，选填" /></el-form-item>
              </el-form>
              <template #footer><el-button @click="skuForm.show=false">取消</el-button><el-button type="primary" @click="saveSku" :loading="skuSaving">保存</el-button></template>
            </el-dialog>
            <template #footer><el-button @click="pd.show=false">取消</el-button><el-button type="primary" @click="saveProduct" :loading="pd.saving">保存</el-button></template>
          </el-dialog>
        </div>

        <!-- ========= 订单管理 ========= -->
        <div v-show="menu==='orders'">
          <div class="page-header"><h2>订单管理</h2></div>
          <div class="card table-card">
            <div class="card-body">
              <div class="table-toolbar">
                <el-radio-group v-model="orderTab" size="small" @change="loadOrders">
                  <el-radio-button value="">全部</el-radio-button>
                  <el-radio-button value="pending_pay">待付款</el-radio-button>
                  <el-radio-button value="pending_ship">待发货</el-radio-button>
                  <el-radio-button value="shipped">已发货</el-radio-button>
                  <el-radio-button value="completed">已完成</el-radio-button>
                  <el-radio-button value="closed">已关闭</el-radio-button>
                </el-radio-group>
                <span class="spacer"></span>
                <el-input v-model="orderKeyword" placeholder="订单号/收货人" clearable style="width:200px" size="small" @keyup.enter="loadOrders" />
                <el-button size="small" @click="loadOrders">🔍</el-button>
              </div>
              <el-table :data="orderList" border stripe v-loading="orderLoading" empty-text="📋 暂无订单，请等待用户下单">
                <el-table-column label="订单号" width="175"><template #default="{row}">{{row.order_no}}</template></el-table-column>
                <el-table-column label="收货人" width="100"><template #default="{row}">{{row.consignee||'—'}}</template></el-table-column>
                <el-table-column label="商品" min-width="180"><template #default="{row}">
                  <div v-for="i in (row.items||[]).slice(0,2)" :key="i.id" style="display:flex;align-items:center;gap:6px;margin:2px 0">
                    <img v-if="i.image" :src="i.image" style="width:28px;height:28px;border-radius:4px;object-fit:cover" loading="lazy" />
                    <span style="font-size:12px">{{i.product_name}} ×{{i.quantity}}</span>
                  </div>
                  <div v-if="(row.items||[]).length>2" style="font-size:11px;color:var(--text-secondary)">...还有{{row.items.length-2}}件</div>
                </template></el-table-column>
                <el-table-column label="金额" width="100"><template #default="{row}"><span style="font-weight:700">¥{{fmt(row.pay_amount)}}</span></template></el-table-column>
                <el-table-column label="状态" width="90"><template #default="{row}"><span :class="statusClass(row.status)">{{statusLabel(row.status)}}</span></template></el-table-column>
                <el-table-column label="时间" width="100"><template #default="{row}">{{(row.created_at||'').slice(5,16)}}</template></el-table-column>
                <el-table-column label="操作" width="150" fixed="right">
                  <template #default="{row}">
                    <el-button link type="primary" size="small" @click="viewOrder(row)">详情</el-button>
                    <el-button v-if="row.status==='pending_ship'" link type="primary" size="small" @click="openShip(row)">发货</el-button>
                  </template>
                </el-table-column>
              </el-table>
              <div v-if="orderTotal>0" style="margin-top:16px;display:flex;justify-content:center">
                <el-pagination background layout="prev,pager,next" :total="orderTotal" :page-size="20" @current-change="p=>{orderPage=p;loadOrders()}" small />
              </div>
            </div>
          </div>
          <!-- 订单详情弹窗 -->
          <el-dialog v-model="orderDetailShow" title="订单详情" width="680px"><div v-if="orderDetail">
            <el-descriptions :column="2" border size="small">
              <el-descriptions-item label="订单号">{{orderDetail.order_no}}</el-descriptions-item>
              <el-descriptions-item label="状态"><span :class="statusClass(orderDetail.status)">{{statusLabel(orderDetail.status)}}</span></el-descriptions-item>
              <el-descriptions-item label="收货人">{{orderDetail.consignee||'—'}}</el-descriptions-item>
              <el-descriptions-item label="手机号">{{orderDetail.phone||'—'}}</el-descriptions-item>
              <el-descriptions-item label="地址" :span="2">{{[orderDetail.province,orderDetail.city,orderDetail.district,orderDetail.address].filter(Boolean).join(' ')||'—'}}</el-descriptions-item>
              <el-descriptions-item label="运费">¥{{fmt(orderDetail.shipping_fee||0)}}</el-descriptions-item>
              <el-descriptions-item label="实付"><span style="font-weight:700;color:#F53F3F">¥{{fmt(orderDetail.pay_amount)}}</span></el-descriptions-item>
              <el-descriptions-item label="快递" v-if="orderDetail.shipping_company">{{orderDetail.shipping_company}} {{orderDetail.shipping_no||''}}</el-descriptions-item>
              <el-descriptions-item label="备注">{{orderDetail.remark||'—'}}</el-descriptions-item>
            </el-descriptions>
            <div style="margin-top:16px"><strong>商品明细</strong></div>
            <el-table :data="orderDetail.items" border size="small" style="margin-top:8px">
              <el-table-column label="商品" prop="product_name" />
              <el-table-column label="单价" width="90"><template #default="{row}">¥{{fmt(row.price)}}</template></el-table-column>
              <el-table-column label="数量" width="70" prop="quantity" />
              <el-table-column label="小计" width="100"><template #default="{row}">¥{{fmt(row.subtotal)}}</template></el-table-column>
            </el-table>
            <div v-if="orderDetail.logs?.length" style="margin-top:16px"><strong>操作日志</strong></div>
            <el-timeline v-if="orderDetail.logs?.length" style="margin-top:4px">
              <el-timeline-item v-for="l in orderDetail.logs" :key="l.id" :timestamp="l.created_at" size="small">{{l.content||l.action}}</el-timeline-item>
            </el-timeline>
          </div></el-dialog>
          <!-- 发货弹窗 -->
          <el-dialog v-model="shipShow" title="发货" width="420px">
            <el-form :model="shipForm" label-width="80px">
              <el-form-item label="快递公司"><el-select v-model="shipForm.company" placeholder="选择快递" filterable allow-create style="width:100%"><el-option v-for="sc in shipCompanies" :key="sc.code" :label="sc.name" :value="sc.name" /></el-select></el-form-item>
              <el-form-item label="快递单号"><el-input v-model="shipForm.no" placeholder="请输入运单号" /></el-form-item>
            </el-form>
            <template #footer><el-button @click="shipShow=false">取消</el-button><el-button type="primary" @click="doShip" :loading="shipLoading">确认发货</el-button></template>
          </el-dialog>
        </div>

        <!-- ========= 登录日志 ========= -->
        <div v-show="menu==='loginlogs'">
          <div class="page-header"><h2>登录日志</h2><div class="actions">
            <el-select v-model="loginLogResult" placeholder="结果" clearable size="small" style="width:100px" @change="loadLoginLogs">
              <el-option label="成功" value="success" /><el-option label="失败" value="fail" />
            </el-select>
            <el-button size="small" @click="loadLoginLogs">🔄 刷新</el-button>
          </div></div>
          <div class="card"><div class="card-body">
            <el-table :data="loginLogList" border v-loading="loginLogLoading" empty-text="暂无登录记录">
              <el-table-column label="时间" width="155" prop="created_at" />
              <el-table-column label="手机号" width="110" prop="phone" />
              <el-table-column label="结果" width="70"><template #default="{row}"><el-tag :type="row.result==='success'?'success':'danger'" size="small">{{row.result==='success'?'成功':'失败'}}</el-tag></template></el-table-column>
              <el-table-column label="原因" width="130" prop="reason" />
              <el-table-column label="IP地址" width="130" prop="ip" />
              <el-table-column label="UA" min-width="200" prop="user_agent" />
            </el-table>
            <div v-if="loginLogTotal>0" style="margin-top:16px;display:flex;justify-content:center">
              <el-pagination background layout="prev,pager,next" :total="loginLogTotal" :page-size="30" @current-change="p=>{loginLogPage=p;loadLoginLogs()}" small />
            </div>
          </div></div>
        </div>

        <!-- ========= 页面装修 ========= -->
        <div v-show="menu==='design'">
          <div class="page-header"><h2>页面装修</h2><div class="actions"><el-button size="small" @click="loadLayout">🔄 刷新</el-button><el-button type="primary" size="small" @click="saveLayout">💾 保存布局</el-button></div></div>
          <el-alert title="拖拽左侧组件到右侧预览区，可调整顺序和配置" type="info" show-icon :closable="false" style="margin-bottom:12px" />
          <div style="display:flex;gap:16px">
            <!-- 组件库 -->
            <div style="width:200px;flex-shrink:0">
              <div class="card"><div class="card-header">📦 组件库</div>
                <div class="card-body" style="padding:12px">
                  <div v-for="comp in compLib" :key="comp.type" draggable="true" @dragstart="dragStart($event,comp)" style="padding:10px;border:1px solid var(--border);border-radius:8px;margin-bottom:8px;cursor:grab;background:#fff;text-align:center;font-size:13px">
                    <div style="font-size:24px;margin-bottom:4px">{{comp.icon}}</div>
                    <div>{{comp.label}}</div>
                  </div>
                </div>
              </div>
            </div>
            <!-- 预览区 -->
            <div style="flex:1">
              <div class="card">
                <div class="card-header"><span>📱 手机首页预览</span><span style="font-size:12px;color:#999">拖拽调整顺序</span></div>
                <div class="card-body" style="min-height:400px;padding:12px;background:#f5f7fa;border-radius:0 0 12px 12px">
                  <div v-if="layoutItems.length" style="background:#fff;border-radius:12px;overflow:hidden;max-width:375px;margin:0 auto;box-shadow:0 2px 12px rgba(0,0,0,.08)">
                    <div v-for="(item,idx) in layoutItems" :key="item.id" draggable="true" @dragstart="dragItemStart($event,idx)" @dragover.prevent @drop="dropItem($event,idx)" style="position:relative;border-bottom:2px dashed transparent;cursor:move;transition:border-color .2s" @mouseenter="item._hover=true" @mouseleave="item._hover=false">
                      <!-- 轮播图占位 -->
                      <div v-if="item.type==='banner'" style="height:160px;background:linear-gradient(135deg,#667eea,#764ba2);display:flex;align-items:center;justify-content:center;color:#fff;font-size:14px">📷 轮播图 ({{item.props?.height||160}}px)</div>
                      <!-- 导航分类占位 -->
                      <div v-else-if="item.type==='nav_grid'" style="padding:16px;display:grid;grid-template-columns:repeat(4,1fr);gap:8px;text-align:center;color:#666;font-size:12px">
                        <div v-for="n in 4" :key="n"><div style="width:40px;height:40px;background:#f5f7fa;border-radius:10px;margin:0 auto 4px;display:flex;align-items:center;justify-content:center">📱</div>导航{{n}}</div>
                      </div>
                      <!-- 商品列表占位 -->
                      <div v-else-if="item.type==='product_list'" style="padding:12px">
                        <div style="font-weight:600;font-size:14px;margin-bottom:8px;color:#333">{{item.title}}</div>
                        <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">
                          <div v-for="n in 4" :key="n" style="background:#f5f7fa;border-radius:8px;height:120px;display:flex;flex-direction:column;align-items:center;justify-content:center;font-size:12px;color:#999">
                            <div style="font-size:24px">🛍️</div>
                            <div style="margin-top:4px">商品{{n}}</div>
                          </div>
                        </div>
                      </div>
                      <!-- 自定义模块占位 -->
                      <div v-else style="padding:30px;text-align:center;color:#999;background:#fafafa">
                        <div style="font-size:24px">{{item.icon||'📄'}}</div>
                        <div style="margin-top:4px">{{item.title||'自定义模块'}}</div>
                      </div>
                      <!-- 操作按钮 -->
                      <div v-if="item._hover" style="position:absolute;top:4px;right:4px;display:flex;gap:4px;z-index:10">
                        <el-button size="small" circle @click.stop="moveItem(idx,'up')" :disabled="idx===0" title="上移">▲</el-button>
                        <el-button size="small" circle @click.stop="moveItem(idx,'down')" :disabled="idx===layoutItems.length-1" title="下移">▼</el-button>
                        <el-button size="small" circle type="danger" @click.stop="removeItem(idx)" title="删除">✕</el-button>
                      </div>
                    </div>
                  </div>
                  <div v-else style="text-align:center;padding:60px 20px;color:#999">
                    <div style="font-size:48px;margin-bottom:12px">🎨</div>
                    <div>从左侧拖拽组件到此区域开始装修</div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>

        <!-- ========= 管理员管理 ========= -->

        <div v-show="menu==='admins'">
          <div class="page-header"><h2>管理员管理</h2><div class="actions"><el-button type="primary" size="small" @click="adminForm.show=true">✚ 新增管理员</el-button></div></div>
          <div class="card"><div class="card-body">
            <el-table :data="adminList" border v-loading="adminLoading" empty-text="暂无数据">
              <el-table-column label="ID" width="60" prop="id" />
              <el-table-column label="账号" min-width="120" prop="phone" />
              <el-table-column label="昵称" min-width="120" prop="nickname" />
              <el-table-column label="状态" width="80"><template #default="{row}"><el-tag :type="row.status?'success':'danger'" size="small">{{row.status?'正常':'禁用'}}</el-tag></template></el-table-column>
              <el-table-column label="创建时间" width="160" prop="created_at" />
              <el-table-column label="操作" width="160">
                <template #default="{row}">
                  <el-button link type="primary" size="small" v-if="row.id!==1" @click="toggleAdminStatus(row)">{{row.status?'禁用':'启用'}}</el-button>
                  <el-button link type="danger" size="small" v-if="row.id!==1" @click="delAdmin(row)">删除</el-button>
                  <span v-if="row.id===1" style="color:#999;font-size:12px">超级管理员</span>
                </template>
              </el-table-column>
            </el-table>
          </div></div>
          <el-dialog v-model="adminForm.show" title="新增管理员" width="400px">
            <el-form label-width="70px" size="small">
              <el-form-item label="手机号"><el-input v-model="adminForm.phone" placeholder="作为登录账号" /></el-form-item>
              <el-form-item label="密码"><el-input v-model="adminForm.password" type="password" show-password placeholder="至少6位" /></el-form-item>
              <el-form-item label="昵称"><el-input v-model="adminForm.nickname" /></el-form-item>
            </el-form>
            <template #footer><el-button @click="adminForm.show=false">取消</el-button><el-button type="primary" @click="createAdmin" :loading="adminSaving">创建</el-button></template>
          </el-dialog>
        </div>

        <!-- ========= 数据备份 ========= -->
        <div v-show="menu==='backup'">
          <div class="page-header"><h2>数据备份</h2><div class="actions"><el-button type="primary" size="small" @click="doBackup" :loading="backupRunning">💾 立即备份</el-button></div></div>
          <div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;margin-bottom:16px">
            <div class="card"><div class="card-header"><span>📊 备份统计</span></div><div class="card-body">
              <div style="display:flex;gap:24px">
                <div><div style="font-size:28px;font-weight:700">{{backupList.length}}</div><div style="color:#999;font-size:13px">总备份数</div></div>
                <div><div style="font-size:28px;font-weight:700">{{backupTotalSize}}</div><div style="color:#999;font-size:13px">总大小</div></div>
                <div><div style="font-size:28px;font-weight:700">{{backupKeepDays}}天</div><div style="color:#999;font-size:13px">保留期限</div></div>
              </div>
            </div></div>
            <div class="card"><div class="card-header"><span>⚡ 快速操作</span></div><div class="card-body">
              <p style="color:#666;font-size:13px">点击「立即备份」创建当前数据快照。系统自动保留最近{{backupKeepDays}}天的备份。</p>
            </div></div>
          </div>
          <div class="card"><div class="card-header"><span>📁 备份文件列表</span></div>
            <div class="card-body">
              <el-table :data="backupList" border v-loading="backupLoading" element-loading-text="加载中..." empty-text="暂无备份">
                <el-table-column label="文件名" min-width="220" prop="name" />
                <el-table-column label="大小" width="100"><template #default="{row}">{{(row.size/1024).toFixed(1)}}KB</template></el-table-column>
                <el-table-column label="创建时间" width="160" prop="mtime" />
                <el-table-column label="操作" width="120">
                  <template #default="{row}">
                    <el-button link type="primary" size="small" @click="restoreBackup(row)">恢复</el-button>
                  </template>
                </el-table-column>
              </el-table>
            </div>
          </div>
        </div>

        <!-- ========= 运行日志 ========= -->
        <div v-show="menu==='syslogs'">
          <div class="page-header"><h2>运行日志</h2><div class="actions"><el-button size="small" @click="loadSysLogs">🔄 刷新</el-button><el-button size="small" type="danger" plain @click="clearSysLogs">清除日志</el-button></div></div>
          <div class="card"><div class="card-header"><span>📋 最近{{sysLogTotal}}条日志</span></div>
            <div class="card-body" style="max-height:600px;overflow-y:auto;background:#1D2129;border-radius:8px;padding:16px">
              <div v-if="sysLogLines.length">
                <div v-for="(line,i) in sysLogLines" :key="i" style="font-family:'Courier New',monospace;font-size:12px;color:var(--text-secondary);padding:2px 0;border-bottom:1px solid rgba(255,255,255,0.05)">
                  <span style="color:#00B578">▶</span> {{line}}
                </div>
              </div>
              <div v-else style="text-align:center;color:#666;padding:40px">暂无日志</div>
            </div>
          </div>
        </div>

        <!-- ========= 会员管理 ========= -->
        <div v-show="menu==='users'">
          <div class="page-header"><h2>会员管理</h2><div class="actions"><el-button @click="showLevelDlg=true">等级配置</el-button><el-button @click="showPlanDlg=true">套餐管理</el-button></div></div>
          <div class="card table-card">
            <div class="card-body">
              <div class="table-toolbar">
                <el-input v-model="uq.keyword" placeholder="搜索昵称/手机号" clearable style="width:220px" size="small" @keyup.enter="loadUsers" />
                <span class="spacer"></span><el-button size="small" @click="loadUsers">🔍</el-button>
              </div>
              <el-table :data="userList" border stripe v-loading="userLoading" empty-text="暂无会员数据">
                <el-table-column label="用户" min-width="160"><template #default="{row}"><div style="display:flex;align-items:center;gap:8px"><span style="font-size:28px">{{row.avatar||'👤'}}</span><div><div style="font-weight:600">{{row.nickname||row.phone}}</div><div style="font-size:11px;color:var(--text-secondary)">{{row.phone}}</div></div></div></template></el-table-column>
                <el-table-column label="等级" width="90"><template #default="{row}"><span class="tag-done">{{row.level_name||'普通'}}</span></template></el-table-column>
                <el-table-column label="余额" width="90"><template #default="{row}">¥{{fmt(row.balance||0)}}</template></el-table-column>
                <el-table-column label="总消费" width="100"><template #default="{row}">¥{{fmt(row.total_consumed||0)}}</template></el-table-column>
                <el-table-column label="订单数" width="70" prop="order_count" />
                <el-table-column label="团队" width="60" prop="team_count" />
                <el-table-column label="注册" width="100"><template #default="{row}">{{(row.created_at||'').slice(0,10)}}</template></el-table-column>
              </el-table>
              <div v-if="userTotal>0" style="margin-top:16px;display:flex;justify-content:center">
                <el-pagination background layout="prev,pager,next" :total="userTotal" :page-size="20" @current-change="p=>{uq.page=p;loadUsers()}" small />
              </div>
            </div>
          </div>
          <!-- 等级弹窗 -->
          <el-dialog v-model="showLevelDlg" title="用户等级" width="600px">
            <el-table :data="levels" border size="small">
              <el-table-column label="名称" prop="name" /><el-table-column label="等级" prop="level" width="60" />
              <el-table-column label="经验值" prop="min_experience" width="90" /><el-table-column label="折扣" width="80"><template #default="{row}">{{(row.discount*100).toFixed(0)}}%</template></el-table-column>
              <el-table-column label="操作" width="120"><template #default="{row}"><el-button link size="small" @click="editLevel(row)">编辑</el-button><el-popconfirm title="确定?" @confirm="delLevel(row.id)"><template #reference><el-button link type="danger" size="small">删除</el-button></template></el-popconfirm></template></el-table-column>
            </el-table>
            <el-button type="primary" size="small" style="margin-top:10px" @click="editLevel({})">✚ 新增等级</el-button>
            <el-dialog v-model="levelForm.show" :title="levelForm.id?'编辑':'新增'" width="380px" append-to-body>
              <el-form label-width="90px" size="small">
                <el-form-item label="名称"><el-input v-model="levelForm.name" /></el-form-item>
                <el-form-item label="等级"><el-input-number v-model="levelForm.level" :min="1" /></el-form-item>
                <el-form-item label="经验值"><el-input-number v-model="levelForm.min_experience" :min="0" /></el-form-item>
                <el-form-item label="折扣"><el-slider v-model="levelForm.discountPct" :min="10" :max="100" show-input style="width:180px" /></el-form-item>
              </el-form>
              <template #footer><el-button @click="levelForm.show=false">取消</el-button><el-button type="primary" @click="saveLevel">保存</el-button></template>
            </el-dialog>
          </el-dialog>
          <!-- 套餐弹窗 -->
          <el-dialog v-model="showPlanDlg" title="会员套餐" width="650px">
            <el-table :data="plans" border size="small">
              <el-table-column label="名称" prop="name" /><el-table-column label="类型" width="80"><template #default="{row}">{{{monthly:'月卡',quarterly:'季卡',yearly:'年卡'}[row.type]}}</template></el-table-column>
              <el-table-column label="价格" width="80"><template #default="{row}">¥{{row.price}}</template></el-table-column>
              <el-table-column label="对应等级" width="90" prop="level_name" /><el-table-column label="经验" width="70" prop="give_experience" />
              <el-table-column label="操作" width="120"><template #default="{row}"><el-button link size="small" @click="editPlan(row)">编辑</el-button><el-popconfirm title="确定?" @confirm="delPlan(row.id)"><template #reference><el-button link type="danger" size="small">删除</el-button></template></el-popconfirm></template></el-table-column>
            </el-table>
            <el-button type="primary" size="small" style="margin-top:10px" @click="editPlan({})">✚ 新增套餐</el-button>
            <el-dialog v-model="planForm.show" :title="planForm.id?'编辑':'新增'" width="400px" append-to-body>
              <el-form label-width="90px" size="small">
                <el-form-item label="名称"><el-input v-model="planForm.name" /></el-form-item>
                <el-form-item label="类型"><el-select v-model="planForm.type"><el-option label="月卡" value="monthly" /><el-option label="季卡" value="quarterly" /><el-option label="年卡" value="yearly" /></el-select></el-form-item>
                <el-form-item label="价格"><el-input-number v-model="planForm.price" :min="0" :precision="2" /></el-form-item>
                <el-form-item label="对应等级"><el-select v-model="planForm.level_id"><el-option v-for="l in levels" :key="l.id" :label="l.name" :value="l.id" /></el-select></el-form-item>
                <el-form-item label="赠送经验"><el-input-number v-model="planForm.give_experience" :min="0" /></el-form-item>
              </el-form>
              <template #footer><el-button @click="planForm.show=false">取消</el-button><el-button type="primary" @click="savePlan">保存</el-button></template>
            </el-dialog>
          </el-dialog>
        </div>

        <!-- ========= 分销管理 ========= -->
        <div v-show="menu==='distribution'">
          <div class="page-header"><h2>分销管理</h2></div>
          <el-tabs v-model="distTab" @tab-change="onDistTab">
            <el-tab-pane label="分销商" name="d_users"><el-table :data="distUsers" border stripe v-loading="duLoad" empty-text="暂无分销商"><el-table-column label="用户" min-width="160"><template #default="{row}"><div style="font-weight:600">{{row.nickname||row.phone}}</div><div style="font-size:11px;color:var(--text-secondary)">{{row.phone}}</div></template></el-table-column><el-table-column label="团队" width="80" prop="team_count" /><el-table-column label="佣金总额" width="100"><template #default="{row}">¥{{fmt(row.total_commission||0)}}</template></el-table-column><el-table-column label="总消费" width="100"><template #default="{row}">¥{{fmt(row.total_consumed||0)}}</template></el-table-column><el-table-column label="注册" width="100"><template #default="{row}">{{(row.created_at||'').slice(0,10)}}</template></el-table-column></el-table></el-tab-pane>
            <el-tab-pane label="佣金记录" name="d_comm"><el-table :data="commissions" border stripe v-loading="commLoad" empty-text="暂无佣金记录"><el-table-column label="用户" width="120"><template #default="{row}">{{row.nickname||'—'}}</template></el-table-column><el-table-column label="金额" width="90"><template #default="{row}">¥{{fmt(row.product_amount||0)}}</template></el-table-column><el-table-column label="佣金" width="90"><template #default="{row}"><span style="font-weight:700;color:#00B578">¥{{fmt(row.commission||0)}}</span></template></el-table-column><el-table-column label="状态" width="90"><template #default="{row}"><span :class="row.status==='settled'?'tag-done':'tag-pending'">{{row.status==='settled'?'已结算':'待结算'}}</span></template></el-table-column><el-table-column label="操作" width="80"><template #default="{row}"><el-button v-if="row.status==='pending'" link type="primary" size="small" @click="settleComm(row.id)">结算</el-button></template></el-table-column></el-table></el-tab-pane>
            <el-tab-pane label="提现审核" name="d_wd"><el-table :data="withdraws" border stripe v-loading="wdLoad" empty-text="暂无提现申请"><el-table-column label="用户" width="120"><template #default="{row}">{{row.nickname||'—'}}</template></el-table-column><el-table-column label="金额" width="90"><template #default="{row}">¥{{fmt(row.amount||0)}}</template></el-table-column><el-table-column label="状态" width="90"><template #default="{row}"><span :class="row.status==='completed'?'tag-done':row.status==='approved'?'tag-shipping':'tag-pending'">{{{pending:'待审核',approved:'已通过',rejected:'已拒绝',completed:'已完成'}[row.status]}}</span></template></el-table-column><el-table-column label="操作" width="140" v-if="withdraws.some(w=>w.status==='pending')"><template #default="{row}"><el-button v-if="row.status==='pending'" link type="primary" size="small" @click="approveWd(row.id)">通过</el-button><el-button v-if="row.status==='pending'" link type="danger" size="small" @click="rejectWd(row.id)">拒绝</el-button></template></el-table-column></el-table></el-tab-pane>
            <el-tab-pane label="等级配置" name="d_lv"><el-table :data="distLevels" border><el-table-column label="名称" prop="name" /><el-table-column label="等级" prop="level" width="60" /><el-table-column label="团队" prop="min_team_count" width="70" /><el-table-column label="一级佣金" width="90"><template #default="{row}">{{(row.commission_rate*100).toFixed(1)}}%</template></el-table-column><el-table-column label="二级佣金" width="90"><template #default="{row}">{{(row.commission_rate2*100).toFixed(1)}}%</template></el-table-column><el-table-column label="操作" width="100"><template #default="{row}"><el-button link size="small" @click="editDistLv(row)">编辑</el-button></template></el-table-column></el-table></el-tab-pane>
          </el-tabs>
        </div>

        <!-- ========= 营销工具 ========= -->
        <div v-show="menu==='marketing'">
          <div class="page-header"><h2>营销工具</h2></div>
          <el-tabs v-model="marketTab">
            <el-tab-pane label="优惠券" name="coupons">
              <div style="margin-bottom:12px"><el-button type="primary" size="small" @click="editCoupon({})">✚ 新增</el-button></div>
              <el-table :data="coupons" border stripe><el-table-column label="名称" prop="name" /><el-table-column label="类型" width="80"><template #default="{row}">{{{full_reduce:'满减',discount:'折扣',cash:'代金'}[row.type]}}</template></el-table-column><el-table-column label="面值" width="80"><template #default="{row}">¥{{row.value}}</template></el-table-column><el-table-column label="满额" width="80"><template #default="{row}">¥{{row.min_amount||0}}</template></el-table-column><el-table-column label="已领/总量" width="90"><template #default="{row}">{{row.used_count||0}}/{{row.total_count||0}}</template></el-table-column><el-table-column label="有效期" width="80">{{(row.valid_days||30)}}天</el-table-column><el-table-column label="操作" width="140"><template #default="{row}"><el-button link size="small" @click="editCoupon(row)">编辑</el-button><el-popconfirm title="删除?" @confirm="delCoupon(row.id)"><template #reference><el-button link type="danger" size="small">删除</el-button></template></el-popconfirm></template></el-table-column></el-table>
            </el-tab-pane>
            <el-tab-pane label="秒杀" name="seckill">
              <div style="margin-bottom:12px"><el-button type="primary" size="small" @click="editSeckill({})">✚ 新增</el-button></div>
              <el-table :data="seckills" border stripe><el-table-column label="商品" min-width="160"><template #default="{row}">{{row.name}}</template></el-table-column><el-table-column label="秒杀价" width="90"><template #default="{row}">¥{{row.seckill_price}}</template></el-table-column><el-table-column label="库存" width="70" prop="stock" /><el-table-column label="开始" width="140" prop="start_time" /><el-table-column label="结束" width="140" prop="end_time" /><el-table-column label="操作" width="140"><template #default="{row}"><el-button link size="small" @click="editSeckill(row)">编辑</el-button><el-popconfirm title="删除?" @confirm="delSeckill(row.id)"><template #reference><el-button link type="danger" size="small">删除</el-button></template></el-popconfirm></template></el-table-column></el-table>
            </el-tab-pane>
          </el-tabs>
          <!-- 优惠券弹窗 -->
          <el-dialog v-model="couponForm.show" :title="couponForm.id?'编辑':'新增'" width="440px" append-to-body>
            <el-form label-width="100px" size="small">
              <el-form-item label="名称"><el-input v-model="couponForm.name" /></el-form-item>
              <el-form-item label="类型"><el-select v-model="couponForm.type"><el-option label="满减券" value="full_reduce" /><el-option label="折扣券" value="discount" /><el-option label="代金券" value="cash" /></el-select></el-form-item>
              <el-form-item label="面值"><el-input-number v-model="couponForm.value" :min="0" /></el-form-item>
              <el-form-item label="满额条件"><el-input-number v-model="couponForm.min_amount" :min="0" /></el-form-item>
              <el-form-item label="总量"><el-input-number v-model="couponForm.total_count" :min="0" /></el-form-item>
              <el-form-item label="有效期(天)"><el-input-number v-model="couponForm.valid_days" :min="1" /></el-form-item>
            </el-form>
            <template #footer><el-button @click="couponForm.show=false">取消</el-button><el-button type="primary" @click="saveCoupon">保存</el-button></template>
          </el-dialog>
          <!-- 秒杀弹窗 -->
          <el-dialog v-model="skForm.show" :title="skForm.id?'编辑':'新增'" width="480px" append-to-body>
            <el-form label-width="90px" size="small">
              <el-form-item label="商品"><el-select v-model="skForm.product_id" filterable><el-option v-for="p in productList" :key="p.id" :label="p.name" :value="p.id" /></el-select></el-form-item>
              <el-form-item label="秒杀价"><el-input-number v-model="skForm.seckill_price" :min="0" :precision="2" /></el-form-item>
              <el-form-item label="库存"><el-input-number v-model="skForm.stock" :min="0" /></el-form-item>
              <el-form-item label="开始时间"><el-date-picker v-model="skForm.start_time" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" style="width:100%" /></el-form-item>
              <el-form-item label="结束时间"><el-date-picker v-model="skForm.end_time" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" style="width:100%" /></el-form-item>
            </el-form>
            <template #footer><el-button @click="skForm.show=false">取消</el-button><el-button type="primary" @click="saveSeckill">保存</el-button></template>
          </el-dialog>
        </div>

        <!-- ========= AI客服 ========= -->
        <div v-show="menu==='ai'">
          <div class="page-header"><h2>AI客服知识库</h2><div class="actions"><el-button type="primary" @click="editKb({})">✚ 新增</el-button></div></div>
          <div class="card table-card"><div class="card-body">
            <div class="table-toolbar"><el-input v-model="kbQ" placeholder="搜索知识库..." clearable style="width:260px" size="small" @keyup.enter="loadKb" /><span class="spacer"></span><el-button size="small" @click="loadKb">🔍</el-button></div>
            <el-table :data="kbList" border stripe v-loading="kbLoad" empty-text="暂无知库条目">
              <el-table-column label="问题" min-width="180" prop="question" />
              <el-table-column label="答案" min-width="240"><template #default="{row}">{{(row.answer||'').substring(0,80)}}{{row.answer?.length>80?'...':''}}</template></el-table-column>
              <el-table-column label="分类" width="90" prop="category" />
              <el-table-column label="关键词" width="130" prop="keywords" />
              <el-table-column label="权重" width="60" prop="weight" />
              <el-table-column label="操作" width="140"><template #default="{row}"><el-button link size="small" @click="editKb(row)">编辑</el-button><el-popconfirm title="删除?" @confirm="delKb(row.id)"><template #reference><el-button link type="danger" size="small">删除</el-button></template></el-popconfirm></template></el-table-column>
            </el-table>
            <div v-if="kbTotal>0" style="margin-top:16px;display:flex;justify-content:center"><el-pagination background layout="prev,pager,next" :total="kbTotal" :page-size="20" @current-change="p=>{kbPage=p;loadKb()}" small /></div>
          </div></div>
          <el-dialog v-model="kbForm.show" :title="kbForm.id?'编辑':'新增'" width="520px" append-to-body>
            <el-form label-width="70px" size="small">
              <el-form-item label="分类"><el-input v-model="kbForm.category" placeholder="如：物流/退换货" /></el-form-item>
              <el-form-item label="问题"><el-input v-model="kbForm.question" /></el-form-item>
              <el-form-item label="答案"><el-input v-model="kbForm.answer" type="textarea" :rows="5" /></el-form-item>
              <el-form-item label="关键词"><el-input v-model="kbForm.keywords" placeholder="逗号分隔" /></el-form-item>
              <el-form-item label="权重"><el-input-number v-model="kbForm.weight" :min="0" /></el-form-item>
            </el-form>
            <template #footer><el-button @click="kbForm.show=false">取消</el-button><el-button type="primary" @click="saveKb">保存</el-button></template>
          </el-dialog>
        </div>

        <!-- ========= 系统设置 ========= (Ctrl+S快捷保存) -->
        <div v-show="menu==='settings'">
          <div class="page-header"><h2>系统设置</h2></div>
          <el-tabs v-model="setTab">
            <el-tab-pane label="基本信息" name="base">
              <div style="max-width:600px">
                <el-form label-width="100px" size="default">
                  <el-form-item label="站点名称"><el-input v-model="settings.site_name" /></el-form-item>
                  <el-form-item label="站点Logo"><el-input v-model="settings.site_logo" placeholder="图片URL" /></el-form-item>
                  <el-form-item label="站点描述"><el-input v-model="settings.site_description" type="textarea" :rows="2" /></el-form-item>
                  <el-form-item label="ICP备案号"><el-input v-model="settings.icp_beian" /></el-form-item>
                  <el-form-item label="客服电话"><el-input v-model="settings.contact_phone" /></el-form-item>
                  <el-form-item label="客服邮箱"><el-input v-model="settings.contact_email" /></el-form-item>
                  <el-form-item><el-button type="primary" @click="saveSettings">保存</el-button></el-form-item>
                </el-form>
              </div>
            </el-tab-pane>
            <el-tab-pane label="支付配置" name="payment">
              <div style="max-width:600px">
                <h3 style="margin:12px 0;font-size:14px">微信支付</h3>
                <el-form label-width="120px" size="default"><el-form-item label="AppId"><el-input v-model="settings.wechat_appid" /></el-form-item><el-form-item label="商户号"><el-input v-model="settings.wechat_mchid" /></el-form-item><el-form-item label="API密钥"><el-input v-model="settings.wechat_key" type="password" show-password /></el-form-item></el-form>
                <h3 style="margin:16px 0 12px;font-size:14px">支付宝</h3>
                <el-form label-width="120px" size="default"><el-form-item label="AppId"><el-input v-model="settings.alipay_appid" /></el-form-item><el-form-item label="应用私钥"><el-input v-model="settings.alipay_private_key" type="textarea" :rows="3" /></el-form-item><el-form-item label="支付宝公钥"><el-input v-model="settings.alipay_public_key" type="textarea" :rows="3" /></el-form-item><el-form-item><el-button type="primary" @click="saveSettings">保存</el-button></el-form-item></el-form>
              </div>
            </el-tab-pane>
            <el-tab-pane label="分享设置" name="share">
              <div style="max-width:600px"><el-form label-width="100px" size="default"><el-form-item label="分享标题"><el-input v-model="settings.share_title" /></el-form-item><el-form-item label="分享描述"><el-input v-model="settings.share_desc" type="textarea" /></el-form-item><el-form-item label="分享图片"><el-input v-model="settings.share_image" placeholder="图片URL" /></el-form-item><el-form-item><el-button type="primary" @click="saveSettings">保存</el-button></el-form-item></el-form></div>
            </el-tab-pane>
            <el-tab-pane label="轮播图" name="banners">
              <div style="margin-bottom:12px"><el-button type="primary" size="small" @click="editBanner({})">✚ 新增</el-button></div>
              <el-table :data="banners" border><el-table-column label="标题" prop="title" /><el-table-column label="图片" width="120"><template #default="{row}"><img v-if="row.image" :src="row.image" style="width:80px;height:44px;border-radius:6px;object-fit:cover" loading="lazy" /></template></el-table-column><el-table-column label="链接" prop="url" width="160" /><el-table-column label="排序" prop="sort" width="60" /><el-table-column label="操作" width="140"><template #default="{row}"><el-button link size="small" @click="editBanner(row)">编辑</el-button><el-popconfirm title="删除?" @confirm="delBanner(row.id)"><template #reference><el-button link type="danger" size="small">删除</el-button></template></el-popconfirm></template></el-table-column></el-table>
            </el-tab-pane>
            <el-tab-pane label="页面管理" name="pages">
              <el-table :data="pageList" border><el-table-column label="页面" prop="title" width="160" /><el-table-column label="类型" width="100" /><el-table-column label="操作" width="120"><template #default="{row}"><el-button link size="small" @click="editPage(row)">编辑</el-button></template></el-table-column></el-table>
              <el-dialog v-model="pageForm.show" :title="'编辑: '+pageForm.title" width="680px" append-to-body><el-input v-model="pageForm.content" type="textarea" :rows="16" /><template #footer><el-button @click="pageForm.show=false">取消</el-button><el-button type="primary" @click="savePage">保存</el-button></template></el-dialog>
            </el-tab-pane>
            <el-tab-pane label="模块开关" name="modules">
              <div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;max-width:800px">
                <div v-for="(label,key) in moduleLabels" :key="key" class="card" style="padding:20px">
                  <div style="display:flex;justify-content:space-between;align-items:center;gap:12px">
                    <div style="flex:1">
                      <div style="font-weight:600;font-size:15px">{{label}}</div>
                      <div style="font-size:12px;color:var(--el-text-color-secondary);margin-top:4px">{{moduleDescs[key]}}</div>
                    </div>
                    <el-switch :model-value="modules[key]===undefined?true:modules[key]" @change="toggleModule(key, $event)" />
                  </div>
                </div>
              </div>
            </el-tab-pane>
          </el-tabs>
          <!-- 轮播弹窗 -->
          <el-dialog v-model="bnForm.show" :title="bnForm.id?'编辑':'新增'" width="440px" append-to-body>
            <el-form label-width="60px" size="small"><el-form-item label="标题"><el-input v-model="bnForm.title" /></el-form-item><el-form-item label="图片"><el-input v-model="bnForm.image" placeholder="图片URL" /></el-form-item><el-form-item label="链接"><el-input v-model="bnForm.url" /></el-form-item><el-form-item label="排序"><el-input-number v-model="bnForm.sort" :min="0" /></el-form-item></el-form>
            <template #footer><el-button @click="bnForm.show=false">取消</el-button><el-button type="primary" @click="saveBanner">保存</el-button></template>
          </el-dialog>
        </div>
      </div>
    </div>
  </div>
</div>

<script src="/lib/vue.global.prod.js?v=2"></script>
<script src="/lib/element-plus.umd.js?v=2"></script>
<script src="/lib/icons-vue.umd.js?v=2"></script>
<script src="/lib/axios.min.js?v=2"></script>
<script src="/lib/echarts.min.js?v=2"></script>
<script>
const { createApp, ref, reactive, computed, watch, nextTick, onMounted } = Vue;
const api = axios.create({ baseURL: '/api', timeout: 15000 });
api.interceptors.request.use(c => { const t = localStorage.getItem('admin_token'); if (t) c.headers.Authorization = 'Bearer ' + t; return c; });
api.interceptors.response.use(r => r.data, e => ({ code: 500, message: (e.response?.data?.message) || e.message || '网络错误' }));

const app = createApp({
  setup() {
    // ========== 公共 ==========
    const sidebarOpen = ref(false);
    const token = ref(localStorage.getItem('admin_token'));
    const menu = ref('dashboard');
    const adminName = ref('管理员');
    const loginForm = reactive({ phone: 'admin', password: 'admin888', captcha: '' });
    const adminCaptchaSvg = ref('');
    const adminCaptchaId = ref('');
    async function loadAdminCaptcha() { const r = await api.get('/captcha/image'); if (r.code === 0) { adminCaptchaSvg.value = r.data.svg; adminCaptchaId.value = r.data.id; } }
    const loginRules = { phone: [{ required: true, message: '请输入账号' },{ min: 2, message: '账号至少2位' }], password: [{ required: true, message: '请输入密码' },{ min: 3, message: '密码至少3位' }] };
    const loginRef = ref(null); const loginLoading = ref(false);

    const pageTitle = computed(() => ({ dashboard: '数据概览', products: '商品管理', orders: '订单管理', users: '会员管理', distribution: '分销管理', marketing: '营销工具', ai: 'AI客服', stats: '数据统计', export: '数据导出', stock_alerts: '库存预警', logs: '操作日志', notifications: '通知管理', settings: '系统设置', admins: '管理员管理', backup: '数据备份', syslogs: '运行日志', loginlogs: '登录日志', design: '页面装修' }[menu.value]));

    function fmt(v) { return (+(v||0)).toFixed(2); }
    function statusLabel(s) { return { pending_pay: '待付款', pending_ship: '待发货', shipped: '已发货', received: '已收货', completed: '已完成', closed: '已关闭' }[s] || s; }
    function statusClass(s) { return { pending_pay: 'tag-pending', pending_ship: 'tag-shipping', shipped: 'tag-shipping', received: 'tag-done', completed: 'tag-done', closed: 'tag-close' }[s] || 'tag-close'; }

    async function doLogin() {
      if (!loginRef.value) return;
      if (loginLoading.value) return;
      const v = await loginRef.value.validate().catch(() => false);
      if (!v) return;
      try { if (adminCaptchaId.value && loginForm.captcha) { const vr = await api.post('/captcha/verify', { id: adminCaptchaId.value, text: loginForm.captcha }); if (vr.code !== 0) { ElMessage.warning(vr.message); loadAdminCaptcha(); loginLoading.value = false; return; } } loginLoading.value = true; const r = await api.post('/admin/login', loginForm); if (r.code === 0) { token.value = r.data.token; localStorage.setItem('admin_token', r.data.token); ElMessage.success('登录成功'); loadDashboard(); } else ElMessage.error(r.message); } catch(e) { ElMessage.error('网络错误'); }
      loginLoading.value = false;
    }

    function switchMenu(m) { menu.value = m; const loaders = { dashboard: loadDashboard, products: loadProducts, orders: loadOrders, users: loadUsers, distribution: loadDistUsers, marketing: loadCoupons, ai: loadKb, settings: loadSettings }; if (loaders[m]) loaders[m](); if (m==='settings') loadModules(); nextTick(() => { if (m === 'dashboard') setTimeout(renderChart, 400); }); }

    function onUserCmd(c) { if (c === 'logout') { localStorage.removeItem('admin_token'); token.value = null; } }

    // ========== 仪表盘 ==========
    const dashboard = reactive({ today_orders: 0, today_sales: 0, total_users: 0, total_sales: 0, pending_ship: 0 });
    let chartInst = null;
    async function loadDashboard() { try { const r = await api.get('/admin/dashboard'); if (r.code === 0) Object.assign(dashboard, r.data); const ch = await api.get('/admin/dashboard/chart?days=7'); if (ch.code === 0) renderChart(ch.data); } catch(e) { ElMessage.error('网络错误'); } }
    function renderChart(data) { nextTick(() => { const el = document.getElementById('chart'); if (!el) return; if (!chartInst) chartInst = echarts.init(el); chartInst.setOption({ tooltip: { trigger: 'axis' }, grid: { left: 40, right: 20, top: 20, bottom: 30 }, xAxis: { type: 'category', data: (data || []).map(d => d.d) }, yAxis: { type: 'value' }, series: [{ name: '订单', type: 'bar', data: (data || []).map(d => d.orders), itemStyle: { color: '#5B7FFF', borderRadius: [6, 6, 0, 0] } }, { name: '销售额', type: 'line', data: (data || []).map(d => d.sales), itemStyle: { color: '#00B578' }, smooth: true }] }); }); }

    // ========== 商品 ==========
    const pq = reactive({ page: 1, pageSize: 20, keyword: '', status: '' });
    const productList = ref([]); const productTotal = ref(0); const productLoading = ref(false);
    const categories = ref([]); const productTab = ref('base');
    const pd = reactive({ show: false, edit: false, saving: false });
    const pf = reactive({ id: 0, name: '', subtitle: '', category_id: null, image: '', price: 0, original_price: 0, stock: 0, unit: '件', sort: 0, description: '', is_new: 0, is_hot: 0, is_recommend: 0 });

    const selectedProds = ref([]);
    const shipCompanies = ref([]);
    async function loadShipCompanies() { const r = await api.get('/order/shipping-companies'); if (r.code === 0) shipCompanies.value = r.data || []; }
    async function copyProduct(row) { const r = await api.post('/admin/products/' + row.id + '/copy'); if (r.code === 0) { ElMessage.success('复制成功'); loadProducts(); } else ElMessage.error(r.message); }
    async function batchOnline() { const ids = selectedProds.value.map(p => p.id); if (!ids.length) return; const r = await api.post('/stats/batch/product-status', { ids, status: 1 });  console.log("SAVE_COUPON response", JSON.stringify(r)); if (r.code === 0) { ElMessage.success(r.message); loadProducts(); } }
    async function batchOffline() { const ids = selectedProds.value.map(p => p.id); if (!ids.length) return; const r = await api.post('/stats/batch/product-status', { ids, status: 0 });  console.log("SAVE_COUPON response", JSON.stringify(r)); if (r.code === 0) { ElMessage.success(r.message); loadProducts(); } }
    
    // ========== 规格/SKU管理 ==========
    const specList = ref([]);
    const skuList = ref([]);
    const specForm = reactive({ show: false, name: '', valuesStr: '' });
    const specSaving = ref(false);
    const skuForm = reactive({ show: false, edit: false, id: 0, specs: [], price: 0, stock: 0, original_price: 0, image: '' });
    const skuSaving = ref(false);

    const allSpecValues = computed(() => {
      const values = [];
      specList.value.forEach(s => {
        const arr = s.spec_values || s.values || [];
        if (Array.isArray(arr)) arr.forEach(v => { if (!values.includes(v)) values.push(v); });
      });
      return values;
    });

    async function loadSpecs(productId) {
      if (!productId) { specList.value = []; return; }
      try { const r = await api.get('/admin/products/' + productId); if (r.code === 0) { specList.value = r.data.specs || []; } } catch(e) { ElMessage.error('网络错误'); }
    }

    async function loadSkus(productId) {
      if (!productId) { skuList.value = []; return; }
      try { const r = await api.get('/admin/products/' + productId); if (r.code === 0) { skuList.value = r.data.skus || []; } } catch(e) { ElMessage.error('网络错误'); }
    }

    async function saveSpec() {
      if (!specForm.name) return ElMessage.warning('请输入规格名称');
      if (!specForm.valuesStr) return ElMessage.warning('请输入规格值');
      specSaving.value = true;
      try { const values = specForm.valuesStr.split(/[,，]/).map(v => v.trim()).filter(Boolean); const r = await api.post('/admin/products/' + pf.id + '/specs', { name: specForm.name, spec_values: values }); if (r.code === 0) { ElMessage.success('添加成功'); specForm.show = false; specForm.name = ''; specForm.valuesStr = ''; loadSpecs(pf.id); } else ElMessage.error(r.message); } catch(e) { ElMessage.error('网络错误'); }
      specSaving.value = false;
    }

    async function deleteSpec(row) { try { const r = await api.delete('/admin/products/' + pf.id + '/specs/' + row.id); if (r.code === 0) { ElMessage.success('已删除'); loadSpecs(pf.id); loadSkus(pf.id); } else ElMessage.error(r.message); } catch(e) { ElMessage.error('网络错误'); } }

    function openSkuForm(row) {
      if (row) {
        skuForm.edit = true;
        skuForm.id = row.id;
        skuForm.specs = JSON.parse(row.specs || '[]');
        skuForm.price = row.price;
        skuForm.stock = row.stock;
        skuForm.original_price = row.original_price || 0;
        skuForm.image = row.image || '';
      } else {
        skuForm.edit = false;
        skuForm.id = 0;
        skuForm.specs = [];
        skuForm.price = 0;
        skuForm.stock = 0;
        skuForm.original_price = 0;
        skuForm.image = '';
      }
      skuForm.show = true;
    }

    async function saveSku() {
      if (!skuForm.specs.length) return ElMessage.warning('请选择规格组合');
      if (!skuForm.price) return ElMessage.warning('请输入价格');
      skuSaving.value = true;
      try { const data = { specs: skuForm.specs, price: skuForm.price, stock: skuForm.stock, original_price: skuForm.original_price, image: skuForm.image }; let r; if (skuForm.edit) { r = await api.put('/admin/products/' + pf.id + '/skus/' + skuForm.id, data); } else { r = await api.post('/admin/products/' + pf.id + '/skus', data); } if (r.code === 0) { ElMessage.success('保存成功'); skuForm.show = false; loadSkus(pf.id); } else ElMessage.error(r.message); } catch(e) { ElMessage.error('网络错误'); }
      skuSaving.value = false;
    }

    async function deleteSku(row) { try { const r = await api.delete('/admin/products/' + pf.id + '/skus/' + row.id); if (r.code === 0) { ElMessage.success('已删除'); loadSkus(pf.id); } else ElMessage.error(r.message); } catch(e) { ElMessage.error('网络错误'); } }

    async function loadProducts() { productLoading.value = true; try { const r = await api.get('/admin/products', { params: pq }); if (r.code === 0) { productList.value = r.data.list || []; productTotal.value = r.data.total || 0; } } catch(e) { ElMessage.error('网络错误'); } productLoading.value = false; }
    async function loadCats() { try { const r = await api.get('/admin/categories'); if (r.code === 0) categories.value = r.data || []; } catch(e) { ElMessage.error('网络错误'); } }
    function openProduct(row) {
      if (row?.id) {
        pd.edit = true; pd.show = true;
        api.get('/admin/products/' + row.id).then(r => {
          if (r.code === 0) {
            Object.assign(pf, r.data);
            specList.value = r.data.specs || [];
            skuList.value = r.data.skus || [];
          }
        });
      } else {
        pd.edit = false;
        Object.assign(pf, { id: 0, name: '', subtitle: '', category_id: null, image: '', price: 0, original_price: 0, stock: 0, unit: '件', sort: 0, description: '', is_new: 0, is_hot: 0, is_recommend: 0 });
        specList.value = [];
        skuList.value = [];
        pd.show = true;
      }
    }
    async function saveProduct() { if (!pf.name) return ElMessage.warning('请输入商品名称'); if (pd.saving) return; pd.saving = true; try { const data = { ...pf }; const url = pd.edit ? '/admin/products/' + pf.id : '/admin/products'; const r = pd.edit ? await api.put(url, data) : await api.post(url, data); if (r.code === 0) { ElMessage.success('保存成功'); pd.show = false; loadProducts(); } else ElMessage.error(r.message); } catch(e) { ElMessage.error('网络错误'); } pd.saving = false; }
    async function toggleProduct(row) { const r = await api.put('/admin/products/' + row.id + '/status', { status: row.status ? 0 : 1 });  console.log("SAVE_COUPON response", JSON.stringify(r)); if (r.code === 0) { ElMessage.success(r.message); loadProducts(); } }
    async function delProduct(id) { const r = await api.delete('/admin/products/' + id);  console.log("SAVE_COUPON response", JSON.stringify(r)); if (r.code === 0) { ElMessage.success('已删除'); loadProducts(); } }

    // ========== 订单 ==========
    const orderTab = ref(''); const orderKeyword = ref(''); const orderList = ref([]); const orderTotal = ref(0); const orderPage = ref(1); const orderLoading = ref(false);
    const orderDetail = ref(null); const orderDetailShow = ref(false);
    const shipShow = ref(false); const shipForm = reactive({ company: '', no: '' }); const shipLoading = ref(false); let shipOrder = null;

    async function loadOrders() { orderLoading.value = true; try { const r = await api.get('/admin/orders', { params: { page: orderPage.value, status: orderTab.value, keyword: orderKeyword.value } }); if (r.code === 0) { orderList.value = r.data.list || []; orderTotal.value = r.data.total || 0; } } catch(e) { ElMessage.error('网络错误'); } orderLoading.value = false; }
    async function viewOrder(row) { const r = await api.get('/admin/orders/' + row.order_no); if (r.code === 0) { orderDetail.value = r.data; orderDetailShow.value = true; } }
    function openShip(row) { shipOrder = row; shipForm.company = ''; shipForm.no = ''; shipShow.value = true; }
    async function doShip() { shipLoading.value = true; const r = await api.post('/admin/orders/' + shipOrder.order_no + '/ship', { shipping_company: shipForm.company, shipping_no: shipForm.no }); shipLoading.value = false;  console.log("SAVE_COUPON response", JSON.stringify(r)); if (r.code === 0) { ElMessage.success('已发货'); shipShow.value = false; loadOrders(); } else ElMessage.error(r.message); }

    // ========== 会员 ==========
    const uq = reactive({ page: 1, pageSize: 20, keyword: '' }); const userList = ref([]); const userTotal = ref(0); const userLoading = ref(false);
    const levels = ref([]);
    const levelList = ref([]); const showLevelDlg = ref(false); const levelForm = reactive({ show: false, id: 0, name: '', level: 1, min_experience: 0, discountPct: 100 });
    const plans = ref([]); const showPlanDlg = ref(false); const planForm = reactive({ show: false, id: 0, name: '', type: 'monthly', price: 0, level_id: null, give_experience: 0 });

    async function loadUsers() { userLoading.value = true; try { const r = await api.get('/admin/users', { params: uq }); if (r.code === 0) { userList.value = r.data.list || []; userTotal.value = r.data.total || 0; } } catch(e) { ElMessage.error('网络错误'); } userLoading.value = false; }
    async function updateLevel(row) { const r = await api.put("/admin/levels/"+row.id,{discount:row.discount}); if(r.code===0)ElMessage.success("629862635df266f465b0"); }
    async function loadLevels() { try { const r = await api.get('/admin/levels'); if (r.code === 0) levels.value = r.data || []; } catch(e) { ElMessage.error('网络错误'); } }
    function editLevel(row) { Object.assign(levelForm, { show: true, id: row.id || 0, name: row.name || '', level: row.level || 1, min_experience: row.min_experience || 0, discountPct: ((row.discount || 1) * 100) }); }
    async function saveLevel() { const data = { name: levelForm.name, level: levelForm.level, min_experience: levelForm.min_experience, discount: levelForm.discountPct / 100 }; const r = levelForm.id ? await api.put('/admin/levels/' + levelForm.id, data) : await api.post('/admin/levels', data);  console.log("SAVE_COUPON response", JSON.stringify(r)); if (r.code === 0) { ElMessage.success('已保存'); levelForm.show = false; loadLevels(); } }
    async function delLevel(id) { const r = await api.delete('/admin/levels/' + id);  console.log("SAVE_COUPON response", JSON.stringify(r)); if (r.code === 0) { ElMessage.success('已删除'); loadLevels(); } }
    async function loadPlans() { try { const r = await api.get('/admin/plans'); if (r.code === 0) plans.value = r.data || []; } catch(e) { ElMessage.error('网络错误'); } }
    function editPlan(row) { Object.assign(planForm, { show: true, id: row.id || 0, name: row.name || '', type: row.type || 'monthly', price: row.price || 0, level_id: row.level_id, give_experience: row.give_experience || 0 }); }
    async function savePlan() { const r = planForm.id ? await api.put('/admin/plans/' + planForm.id, { ...planForm }) : await api.post('/admin/plans', { ...planForm });  console.log("SAVE_COUPON response", JSON.stringify(r)); if (r.code === 0) { ElMessage.success('已保存'); planForm.show = false; loadPlans(); } }
    async function delPlan(id) { const r = await api.delete('/admin/plans/' + id);  console.log("SAVE_COUPON response", JSON.stringify(r)); if (r.code === 0) { ElMessage.success('已删除'); loadPlans(); } }

    // ========== 分销 ==========
    const distTab = ref('d_users'); const distUsers = ref([]); const duLoad = ref(false); const commissions = ref([]); const commLoad = ref(false); const withdraws = ref([]); const wdLoad = ref(false); const distLevels = ref([]);
    const distLevelList = ref([]);
    const distLevelForm = reactive({ show: false, edit: false, id: null, name: "", level: 1, min_team_count: 0, min_team_sales: 0, rate1: 5, rate2: 2, rate3: 1 });
    const distLevelSaving = ref(false);
    async function loadDistUsers() { duLoad.value = true; try { const r = await api.get('/admin/distribution/users'); if (r.code === 0) distUsers.value = r.data.list || []; } catch(e) { ElMessage.error('网络错误'); } duLoad.value = false; }
    async function loadComms() { commLoad.value = true; try { const r = await api.get('/admin/distribution/commissions'); if (r.code === 0) commissions.value = r.data.list || []; } catch(e) { ElMessage.error('网络错误'); } commLoad.value = false; }
    async function loadWds() { wdLoad.value = true; try { const r = await api.get('/admin/distribution/withdraws'); if (r.code === 0) withdraws.value = r.data.list || []; } catch(e) { ElMessage.error('网络错误'); } wdLoad.value = false; }
    async function loadDistLevels() { try { const r = await api.get('/admin/distribution/levels'); if (r.code === 0) { distLevelList.value = r.data || []; distLevels.value = r.data || []; } } catch(e) { ElMessage.error('网络错误'); } }
    async function loadDistLvs() { return loadDistLevels(); }
    function editDistLevel(row) { Object.assign(distLevelForm, { show: true, edit: true, id: row.id, name: row.name, level: row.level, min_team_count: row.min_team_count, min_team_sales: row.min_team_sales, rate1: row.commission_rate*100, rate2: row.commission_rate2*100, rate3: row.commission_rate3*100 }); }
    async function saveDistLevel() { distLevelSaving.value = true; const data = { name: distLevelForm.name, level: distLevelForm.level, min_team_count: distLevelForm.min_team_count, min_team_sales: distLevelForm.min_team_sales, commission_rate: distLevelForm.rate1/100, commission_rate2: distLevelForm.rate2/100, commission_rate3: distLevelForm.rate3/100 }; if (distLevelForm.edit) { data.id = distLevelForm.id; const r = await api.put('/admin/distribution/levels/' + distLevelForm.id, data);  console.log("SAVE_COUPON response", JSON.stringify(r)); if (r.code === 0) { ElMessage.success('更新成功'); distLevelForm.show = false; loadDistLevels(); } else ElMessage.error(r.message); } else { const r = await api.post('/admin/distribution/levels', data); if (r.code === 0) { ElMessage.success('添加成功'); distLevelForm.show = false; loadDistLevels(); } else ElMessage.error(r.message); } distLevelSaving.value = false; }
    async function delDistLevel(id) { const r = await api.delete('/admin/distribution/levels/' + id);  console.log("SAVE_COUPON response", JSON.stringify(r)); if (r.code === 0) { ElMessage.success('已删除'); loadDistLevels(); } }
    async function settleComm(id) { const r = await api.post('/admin/distribution/commission/' + id + '/settle');  console.log("SAVE_COUPON response", JSON.stringify(r)); if (r.code === 0) { ElMessage.success('已结算'); loadComms(); } }
    async function approveWd(id) { const r = await api.post('/admin/distribution/withdraw/' + id + '/approve');  console.log("SAVE_COUPON response", JSON.stringify(r)); if (r.code === 0) { ElMessage.success('已通过'); loadWds(); } }
    async function rejectWd(id) { const r = await api.post('/admin/distribution/withdraw/' + id + '/reject');  console.log("SAVE_COUPON response", JSON.stringify(r)); if (r.code === 0) { ElMessage.success('已拒绝'); loadWds(); } }
    function onDistTab(tab) { const loaders = { d_users: loadDistUsers, d_comm: loadComms, d_wd: loadWds, d_lv: loadDistLvs }; if (loaders[tab]) loaders[tab](); }

    // ========== 营销 ==========
    const marketTab = ref('coupons'); const coupons = ref([]); const seckills = ref([]);
    const couponForm = reactive({ show: false, id: 0, name: '', type: 'full_reduce', value: 0, min_amount: 0, total_count: 100, valid_days: 30 });
    const skForm = reactive({ show: false, id: 0, product_id: null, seckill_price: 0, stock: 0, start_time: '', end_time: '' });
    async function loadCoupons() { try { const r = await api.get('/admin/coupons'); if (r.code === 0) coupons.value = r.data || []; } catch(e) { ElMessage.error('网络错误'); } }
    async function loadSeckills() { try { const r = await api.get('/admin/seckill'); if (r.code === 0) seckills.value = r.data || []; } catch(e) { ElMessage.error('网络错误'); } }
    function editCoupon(row) { Object.assign(couponForm, { show: true, id: row.id || 0, name: row.name || '', type: row.type || 'full_reduce', value: row.value || 0, min_amount: row.min_amount || 0, total_count: row.total_count || 100, valid_days: row.valid_days || 30 }); }
   async function saveCoupon() { const r = couponForm.id ? await api.put('/admin/coupons/' + couponForm.id, { ...couponForm }) : await api.post('/admin/coupons', { ...couponForm });  console.log("SAVE_COUPON response", JSON.stringify(r)); if (r.code === 0) { ElMessage.success('已保存'); couponForm.show = false; loadCoupons(); } }
    async function delCoupon(id) { const r = await api.delete('/admin/coupons/' + id);  console.log("SAVE_COUPON response", JSON.stringify(r)); if (r.code === 0) { ElMessage.success('已删除'); loadCoupons(); } }
    function editSeckill(row) { Object.assign(skForm, { show: true, id: row.id || 0, product_id: row.product_id || null, seckill_price: row.seckill_price || 0, stock: row.stock || 0, start_time: row.start_time || '', end_time: row.end_time || '' }); }
    async function saveSeckill() { const r = skForm.id ? await api.put('/admin/seckill/' + skForm.id, { ...skForm }) : await api.post('/admin/seckill', { ...skForm });  console.log("SAVE_COUPON response", JSON.stringify(r)); if (r.code === 0) { ElMessage.success('已保存'); skForm.show = false; loadSeckills(); } }
    async function delSeckill(id) { const r = await api.delete('/admin/seckill/' + id);  console.log("SAVE_COUPON response", JSON.stringify(r)); if (r.code === 0) { ElMessage.success('已删除'); loadSeckills(); } }

    // ========== AI知识库 ==========
    const kbList = ref([]); const kbQ = ref(''); const kbPage = ref(1); const kbTotal = ref(0); const kbLoad = ref(false);
    const kbForm = reactive({ show: false, id: 0, category: '', question: '', answer: '', keywords: '', weight: 0 });
    async function loadKb() { kbLoad.value = true; try { const r = await api.get('/admin/knowledge', { params: { page: kbPage.value, keyword: kbQ.value } }); if (r.code === 0) { kbList.value = r.data.list || []; kbTotal.value = r.data.total || 0; } } catch(e) { ElMessage.error('网络错误'); } kbLoad.value = false; }
    function editKb(row) { Object.assign(kbForm, { show: true, id: row.id || 0, category: row.category || '', question: row.question || '', answer: row.answer || '', keywords: row.keywords || '', weight: row.weight || 0 }); }
    async function saveKb() { const r = kbForm.id ? await api.put('/admin/knowledge/' + kbForm.id, { ...kbForm }) : await api.post('/admin/knowledge', { ...kbForm });  console.log("SAVE_COUPON response", JSON.stringify(r)); if (r.code === 0) { ElMessage.success('已保存'); kbForm.show = false; loadKb(); } }
    async function delKb(id) { const r = await api.delete('/admin/knowledge/' + id);  console.log("SAVE_COUPON response", JSON.stringify(r)); if (r.code === 0) { ElMessage.success('已删除'); loadKb(); } }

    // ========== 设置 ==========
    const setTab = ref('base'); const settings = reactive({}); const banners = ref([]); const pageList = ref([]);
    const bnForm = reactive({ show: false, id: 0, title: '', image: '', url: '', sort: 0 });
    const pageForm = reactive({ show: false, id: 0, title: '', content: '' });
    async function loadSettings() { try { const r = await api.get('/admin/config'); if (r.code === 0) { const flat = {}; Object.values(r.data).forEach(arr => arr.forEach(c => flat[c.key] = c.value)); Object.assign(settings, flat); } } catch(e) { ElMessage.error('网络错误'); } }
    async function saveSettings() { try { const configs = Object.entries(settings).map(([key, value]) => ({ key, value })); const r = await api.put('/admin/config', { configs }); if (r.code === 0) ElMessage.success('已保存'); } catch(e) { ElMessage.error('网络错误'); } }
    async function loadBanners() { try { const r = await api.get('/admin/banners'); if (r.code === 0) banners.value = r.data || []; } catch(e) { ElMessage.error('网络错误'); } }
    function editBanner(row) { Object.assign(bnForm, { show: true, id: row.id || 0, title: row.title || '', image: row.image || '', url: row.url || '', sort: row.sort || 0 }); }
    async function saveBanner() { const r = bnForm.id ? await api.put('/admin/banners/' + bnForm.id, { ...bnForm }) : await api.post('/admin/banners', { ...bnForm });  console.log("SAVE_COUPON response", JSON.stringify(r)); if (r.code === 0) { ElMessage.success('已保存'); bnForm.show = false; loadBanners(); } }
    async function delBanner(id) { const r = await api.delete('/admin/banners/' + id);  console.log("SAVE_COUPON response", JSON.stringify(r)); if (r.code === 0) { ElMessage.success('已删除'); loadBanners(); } }
    async function loadPages() { const r = await api.get('/admin/pages'); if (r.code === 0) pageList.value = r.data || []; }
    function editPage(row) { Object.assign(pageForm, { show: true, id: row.id, title: row.title, content: row.content || '' }); }
    async function savePage() { const r = await api.put('/admin/pages/' + pageForm.id, { title: pageForm.title, content: pageForm.content });  console.log("SAVE_COUPON response", JSON.stringify(r)); if (r.code === 0) { ElMessage.success('已保存'); pageForm.show = false; } }

    // ========== 登录日志 ==========
    const loginLogList = ref([]); const loginLogLoading = ref(false); const loginLogPage = ref(1); const loginLogTotal = ref(0); const loginLogResult = ref('');
    async function loadLoginLogs() { loginLogLoading.value = true; try { const r = await api.get('/system/login-logs', { params: { page: loginLogPage.value, result: loginLogResult.value } }); if (r.code === 0) { loginLogList.value = r.data.list || []; loginLogTotal.value = r.data.total || 0; } } catch(e) { ElMessage.error('网络错误'); } loginLogLoading.value = false; }

    // ========== 模块标签 ==========
    const moduleLabels = { membership:'会员体系', distribution:'分销体系', ai:'AI客服', payment:'在线支付', after_sale:'订单售后', coupon:'优惠券', seckill:'秒杀', group_buy:'拼团' };
    const moduleDescs = { membership:'会员等级、会员价格、升级规则', distribution:'三级分销、佣金、提现', ai:'智能客服、知识库自动回复', payment:'微信支付、支付宝接口', after_sale:'退款、退货、售后', coupon:'优惠券发放、使用', seckill:'限时秒杀活动', group_buy:'拼团购买活动' };

    // ========== 模块开关 ==========
    const modules = ref({});
    async function loadModules() {
      const r = await api.get('/system/modules');
      if (r.code === 0) modules.value = r.data;
    }
    async function toggleModule(key, val) {
      const r = await api.put('/system/config', { updates: [{ key: 'module_' + key, value: val ? '1' : '0' }] });
      if (r.code === 0) ElMessage.success(val ? '已开启' : '已关闭');
      else modules.value[key] = !val;
    }

    // ========== 运营模式 ==========
    const modeInfo = ref({ mode: 'test', label: '测试模式' });
    async function loadMode() {
      const r = await api.get('/system/mode');
      if (r.code === 0) modeInfo.value = r.data;
    }
    async function toggleMode() {
      const newMode = modeInfo.value.mode === 'production' ? 'test' : 'production';
      const r = await api.post('/system/switch-mode', { mode: newMode });
      if (r.code === 0) { loadMode(); ElMessage.success(r.message); }
    }

    // ========== 退款管理 ==========
    const refundList = ref([]); const refundLoading = ref(false);
    async function loadRefunds() { refundLoading.value = true; try { const r = await api.get('/payment/refunds', { params: { status: 'pending' } }); if (r.code === 0) refundList.value = r.data.list || []; else { if (r.data) refundList.value = r.data.list || []; } } catch(e) { ElMessage.error('网络错误'); } refundLoading.value = false; }
    async function reviewRefund(row, action) {
      let reason = '';
      if (action === 'reject') {
        try { const { value } = await ElMessageBox.prompt('请输入驳回原因', '驳回退款', { confirmButtonText: '确认', cancelButtonText: '取消' });
          reason = value; } catch(e) { return; }
      } else {
        try { await ElMessageBox.confirm('确认退款 ¥' + row.amount + ' 给用户 ' + row.nickname + '？'); } catch(e) { return; }
      }
      const r = await api.post('/payment/refund/review', { refund_id: row.id, action, reject_reason: reason });
       console.log("SAVE_COUPON response", JSON.stringify(r)); if (r.code === 0) { ElMessage.success(r.message); loadRefunds(); }
      else ElMessage.error(r.message);
    }

    // ========== 管理员管理 ==========
    const adminList = ref([]); const adminLoading = ref(false);
    const adminForm = reactive({ show: false, phone: '', password: '', nickname: '' }); const adminSaving = ref(false);
    async function loadAdmins() { adminLoading.value = true; try { const r = await api.get('/system/admins'); if (r.code === 0) adminList.value = r.data; } catch(e) { ElMessage.error('网络错误'); } adminLoading.value = false; }
    async function createAdmin() {
      if (!adminForm.phone || !adminForm.password) return ElMessage.warning('请填写完整');
      adminSaving.value = true;
      const r = await api.post('/system/admins', { ...adminForm });
      adminSaving.value = false;
       console.log("SAVE_COUPON response", JSON.stringify(r)); if (r.code === 0) { ElMessage.success('创建成功'); adminForm.show = false; adminForm.phone = ''; adminForm.password = ''; adminForm.nickname = ''; loadAdmins(); }
      else ElMessage.error(r.message);
    }
    async function toggleAdminStatus(row) {
      const r = await api.put('/system/admins/' + row.id + '/status', { status: row.status ? 0 : 1 });
       console.log("SAVE_COUPON response", JSON.stringify(r)); if (r.code === 0) { ElMessage.success(r.message); loadAdmins(); }
    }
    async function delAdmin(row) {
      try {
        await ElMessageBox.confirm('确定删除管理员 ' + row.nickname + ' 吗？');
        const r = await api.delete('/system/admins/' + row.id);
         console.log("SAVE_COUPON response", JSON.stringify(r)); if (r.code === 0) { ElMessage.success('已删除'); loadAdmins(); }
      } catch(e) {}
    }

    // ========== 数据备份 ==========
    const backupList = ref([]); const backupLoading = ref(false); const backupRunning = ref(false);
    const backupKeepDays = ref(7);
    function fmtSize(bytes) {
      if (!bytes) return '0B';
      if (bytes < 1024) return bytes + 'B';
      return (bytes/1024).toFixed(1) + 'KB';
    }
    const backupTotalSize = computed(() => fmtSize(backupList.value.reduce((s,b) => s + b.size, 0)));
    async function loadBackups() { backupLoading.value = true; try { const r = await api.get('/system/backups'); if (r.code === 0) backupList.value = r.data; } catch(e) { ElMessage.error('网络错误'); } backupLoading.value = false; }
    async function doBackup() {
      backupRunning.value = true;
      const r = await api.post('/system/backup');
      backupRunning.value = false;
       console.log("SAVE_COUPON response", JSON.stringify(r)); if (r.code === 0) { ElMessage.success('备份成功'); loadBackups(); }
      else ElMessage.error(r.message);
    }
    async function restoreBackup(row) {
      try {
        await ElMessageBox.confirm('确定恢复到「' + row.name + '」的数据吗？当前数据将被覆盖！', '警告', { confirmButtonText: '确认恢复', cancelButtonText: '取消', type: 'warning' });
        const r = await api.post('/system/backup/restore', { name: row.name });
         console.log("SAVE_COUPON response", JSON.stringify(r)); if (r.code === 0) { ElMessage.success('恢复成功，请重启服务'); }
        else ElMessage.error(r.message);
      } catch(e) {}
    }

    // ========== 运行日志 ==========
    const sysLogLines = ref([]); const sysLogTotal = ref(0);
    async function loadSysLogs() { try { const r = await api.get('/system/logs'); if (r.code === 0) { sysLogLines.value = r.data.lines || []; sysLogTotal.value = r.data.total || 0; } } catch(e) { ElMessage.error('网络错误'); } }
    async function clearSysLogs() {
      try {
        await ElMessageBox.confirm('确定清除所有运行日志吗？');
        const r = await api.delete('/system/logs');
         console.log("SAVE_COUPON response", JSON.stringify(r)); if (r.code === 0) { ElMessage.success('已清除'); loadSysLogs(); }
      } catch(e) {}
    }

    // ========== 商品采集 ==========
    const showCollector = ref(false);
    const collectUrl = ref('');
    const collectLoading = ref(false);
    const collectPlatform = ref('');
    const collectResult = ref(null);
    const collectApplied = ref(false);
    const collectorCfg = reactive({ api_url: '', app_key: '', secret: '' });
    const wc = reactive({ mp_name: '', appid: '', appsecret: '', token: '', qrcode: '' });
    const sms = reactive({ enabled: false, provider: 'aliyun', access_key: '', secret: '', sign: '' });
    const mail = reactive({ enabled: false, host: '', port: 465, user: '', pass: '', from: '' });
    
    const collectPlatformLabel = computed(() => {
      const labels = { taobao:'淘宝/天猫', '1688':'1688', pdd:'拼多多', jd:'京东', suning:'苏宁', unknown:'其他平台' };
      return labels[collectPlatform.value] || '未识别';
    });
    
    watch(collectUrl, (val) => {
      collectResult.value = null;
      collectApplied.value = false;
      if (!val) { collectPlatform.value = ''; return; }
      if (val.includes('taobao')||val.includes('tmall')) collectPlatform.value = 'taobao';
      else if (val.includes('1688')) collectPlatform.value = '1688';
      else if (val.includes('pinduoduo')||val.includes('yangkeduo')) collectPlatform.value = 'pdd';
      else if (val.includes('jd.com')) collectPlatform.value = 'jd';
      else if (val.includes('suning')) collectPlatform.value = 'suning';
      else collectPlatform.value = 'unknown';
    });
    
    async function doCollect() {
      if (!collectUrl.value) return ElMessage.warning('请输入商品链接');
      collectLoading.value = true;
      const r = await api.post('/collector/parse', { url: collectUrl.value });
      collectLoading.value = false;
      if (r.code === 0) { collectResult.value = r.data; ElMessage.success('采集成功'); }
      else ElMessage.error(r.message);
    }
    
    function applyCollect() {
      if (!collectResult.value) return;
      pf.name = collectResult.value.title || pf.name;
      pf.price = collectResult.value.price || pf.price;
      pf.original_price = collectResult.value.original_price || pf.original_price;
      pf.stock = collectResult.value.stock || pf.stock;
      pf.unit = collectResult.value.unit || pf.unit;
      pf.description = collectResult.value.description || pf.description;
      if (collectResult.value.images?.length) pf.image = collectResult.value.images[0];
      collectApplied.value = true;
      showCollector.value = false;
      ElMessage.success('已填充到商品表单');
    }
    
    async function loadCollectorCfg() {
      const r = await api.get('/collector/config');
      if (r.code === 0) {
        collectorCfg.api_url = r.data.collector_api_url || '';
        collectorCfg.app_key = r.data.collector_app_key || '';
        collectorCfg.secret = r.data.collector_secret || '';
      }
    }
    async function saveCollectorCfg() {
      const r = await api.post('/collector/config', collectorCfg);
      if (r.code === 0) ElMessage.success('配置已保存');
      else ElMessage.error(r.message);
    }
    async function saveWxCfg() { const r = await api.put('/system/config', { updates: [{key:'wechat_mp_name',value:wc.mp_name},{key:'wechat_appid',value:wc.appid},{key:'wechat_appsecret',value:wc.appsecret},{key:'wechat_token',value:wc.token},{key:'wechat_mp_qrcode',value:wc.qrcode}] }); if (r.code === 0) ElMessage.success('配置已保存'); else ElMessage.error(r.message); }
    async function saveSmsCfg() { const r = await api.put('/system/config', { updates: [{key:'sms_enabled',value:sms.enabled?"1":"0"},{key:'sms_provider',value:sms.provider},{key:'sms_access_key',value:sms.access_key},{key:'sms_secret',value:sms.secret},{key:'sms_sign',value:sms.sign}] }); if (r.code === 0) ElMessage.success('配置已保存'); else ElMessage.error(r.message); }
    async function saveMailCfg() { const r = await api.put('/system/config', { updates: [{key:'mail_enabled',value:mail.enabled?"1":"0"},{key:'mail_host',value:mail.host},{key:'mail_port',value:String(mail.port)},{key:'mail_user',value:mail.user},{key:'mail_pass',value:mail.pass},{key:'mail_from',value:mail.from}] }); if (r.code === 0) ElMessage.success('配置已保存'); else ElMessage.error(r.message); }
    async function loadWxConfig() {
      const r = await api.get('/system/config');
      if (r.code === 0 && r.data.wechat) {
        const w = r.data.wechat.reduce((m, c) => { m[c.key] = c.value; return m; }, {});
        wc.mp_name = w.wechat_mp_name||''; wc.appid = w.wechat_appid||''; wc.appsecret = w.wechat_appsecret||''; wc.token = w.wechat_token||''; wc.qrcode = w.wechat_mp_qrcode||'';
      }
      if (r.code === 0 && r.data.sms) {
        const s = r.data.sms.reduce((m, c) => { m[c.key] = c.value; return m; }, {});
        sms.enabled = s.sms_enabled==='1'; sms.provider = s.sms_provider||'aliyun'; sms.access_key = s.sms_access_key||''; sms.secret = s.sms_secret||''; sms.sign = s.sms_sign||'';
      }
      if (r.code === 0 && r.data.mail) {
        const m = r.data.mail.reduce((m, c) => { m[c.key] = c.value; return m; }, {});
        mail.enabled = m.mail_enabled==='1'; mail.host = m.mail_host||''; mail.port = +(m.mail_port||465); mail.user = m.mail_user||''; mail.pass = m.mail_pass||''; mail.from = m.mail_from||'';
      }
    }
    function updateCfg() {}
    async function testCollector() {
      const r = await api.post('/collector/test');
      if (r.code === 0) ElMessage.success('连接正常');
      else ElMessage.error(r.message);
    }

    // ========== 页面装修 ==========
    const compLib = [
      { type: 'banner', label: '轮播图', icon: '📷', props: { height: 160, autoplay: 3000 } },
      { type: 'nav_grid', label: '导航分类', icon: '📱', props: { columns: 4 } },
      { type: 'product_list', label: '商品列表', icon: '🛍️', props: { count: 10, source: 'recommend' } },
    ];
    const layoutItems = ref([]);

    async function loadLayout() { try { const r = await api.get('/pagebuilder/layout'); if (r.code === 0 && r.data.layout) layoutItems.value = r.data.layout.map((item, i) => ({ ...item, id: item.id || ('c' + i + '_' + Date.now()), _hover: false })); } catch(e) { ElMessage.error('网络错误'); } }
    async function saveLayout() {
      const items = layoutItems.value.map(({ _hover, ...rest }) => rest);
      const r = await api.post('/pagebuilder/layout', { name: '首页布局', layout: items });
      if (r.code === 0) ElMessage.success('布局已保存');
      else ElMessage.error(r.message);
    }
    let dragComp = null;
    function dragStart(e, comp) {
      dragComp = { ...comp, id: 'c' + Date.now(), props: { ...comp.props } };
      e.dataTransfer.effectAllowed = 'copy';
    }
    function dragItemStart(e, idx) {
      e.dataTransfer.setData('text/plain', idx);
      e.dataTransfer.effectAllowed = 'move';
    }
    function dropItem(e, targetIdx) {
      if (dragComp) {
        layoutItems.value.splice(targetIdx, 0, { ...dragComp, _hover: false });
        dragComp = null;
      } else {
        const srcIdx = +e.dataTransfer.getData('text/plain');
        if (!isNaN(srcIdx) && srcIdx !== targetIdx) {
          const item = layoutItems.value.splice(srcIdx, 1)[0];
          layoutItems.value.splice(targetIdx, 0, item);
        }
      }
    }
    function moveItem(idx, dir) {
      const target = dir === 'up' ? idx - 1 : idx + 1;
      if (target < 0 || target >= layoutItems.value.length) return;
      const arr = layoutItems.value;
      [arr[idx], arr[target]] = [arr[target], arr[idx]];
    }
    function removeItem(idx) { layoutItems.value.splice(idx, 1); }

    // ========== 数据统计 ==========
    const statsRange = ref('7');
    let salesChartInst = null, topChartInst = null, userChartInst = null, orderPieInst = null;

    async function loadStats() { try { const [trend, topProd, users, orders, overview] = await Promise.all([ api.get('/stats/sales-trend', { params: { range: statsRange.value } }), api.get('/stats/top-products'), api.get('/stats/user-growth', { params: { days: statsRange.value } }), api.get('/stats/order-status'), api.get('/stats/overview') ]); if (trend.code === 0) renderSalesChart(trend.data); if (topProd.code === 0) renderTopChart(topProd.data); if (users.code === 0) renderUserChart(users.data); if (orders.code === 0) renderOrderPie(orders.data); } catch(e) { ElMessage.error('网络错误'); } }

    function renderSalesChart(data) {
      nextTick(() => {
        const el = document.getElementById('salesChart');
        if (!el) return;
        if (!salesChartInst) salesChartInst = echarts.init(el);
        salesChartInst.setOption({
          tooltip: { trigger: 'axis' },
          grid: { left: 50, right: 20, top: 20, bottom: 30 },
          xAxis: { type: 'category', data: (data||[]).map(d => d.d) },
          yAxis: [{ type: 'value', name: '金额' }, { type: 'value', name: '订单' }],
          series: [
            { name: '销售额', type: 'line', data: (data||[]).map(d => d.sales_amount), itemStyle: { color: '#5B7FFF' }, smooth: true },
            { name: '订单数', type: 'bar', yAxisIndex: 1, data: (data||[]).map(d => d.order_count), itemStyle: { color: '#00B578', borderRadius: [4,4,0,0] } }
          ]
        });
      });
    }
    function renderTopChart(data) {
      nextTick(() => {
        const el = document.getElementById('topChart'); if (!el) return;
        if (!topChartInst) topChartInst = echarts.init(el);
        topChartInst.setOption({
          tooltip: { trigger: 'axis' }, grid: { left: 100, right: 20, top: 10, bottom: 30 },
          xAxis: { type: 'value' },
          yAxis: { type: 'category', data: (data||[]).map(d => d.product_name).reverse() },
          series: [{ type: 'bar', data: (data||[]).map(d => d.total_qty).reverse(), itemStyle: { color: '#FF7D00', borderRadius: [0,6,6,0] } }]
        });
      });
    }
    function renderUserChart(data) {
      nextTick(() => {
        const el = document.getElementById('userChart'); if (!el) return;
        if (!userChartInst) userChartInst = echarts.init(el);
        userChartInst.setOption({
          tooltip: { trigger: 'axis' }, grid: { left: 40, right: 20, top: 10, bottom: 30 },
          xAxis: { type: 'category', data: (data||[]).map(d => d.d) },
          yAxis: { type: 'value' },
          series: [{ type: 'bar', data: (data||[]).map(d => d.new_users), itemStyle: { color: '#8B9FFF', borderRadius: [4,4,0,0] } }]
        });
      });
    }
    function renderOrderPie(data) {
      nextTick(() => {
        const el = document.getElementById('orderPieChart'); if (!el) return;
        if (!orderPieInst) orderPieInst = echarts.init(el);
        const labels = { pending_pay:'待付款', pending_ship:'待发货', shipped:'已发货', received:'已收货', completed:'已完成', closed:'已关闭', refunding:'退款中' };
        orderPieInst.setOption({
          tooltip: { trigger: 'item' },
          series: [{
            type: 'pie', radius: ['40%','70%'], center: ['50%','55%'],
            data: (data||[]).filter(d => d.count > 0).map(d => ({ name: labels[d.status]||d.status, value: d.count })),
            itemStyle: { borderRadius: 6 }
          }]
        });
      });
    }

    // ========== 数据导出 ==========
    const exportOrderStatus = ref('');
    function exportOrders() {
      const params = new URLSearchParams({ status: exportOrderStatus.value });
      window.open('/api/stats/export/orders?' + params.toString(), '_blank');
      ElMessage.success('正在导出订单数据...');
    }
    function exportUsers() {
      window.open('/api/stats/export/users', '_blank');
      ElMessage.success('正在导出用户数据...');
    }
    function exportProducts() {
      window.open('/api/stats/export/products', '_blank');
      ElMessage.success('正在导出商品数据...');
    }

    // ========== 库存预警 ==========
    const lowStockProducts = ref([]); const lowStockSkus = ref([]); const stockLoading = ref(false);
    async function loadStockAlerts() { stockLoading.value = true; try { const r = await api.get('/stats/low-stock', { params: { threshold: 10 } }); if (r.code === 0) { lowStockProducts.value = r.data.products || []; lowStockSkus.value = r.data.skus || []; } } catch(e) { ElMessage.error('网络错误'); } stockLoading.value = false; }

    // ========== 操作日志 ==========
    const logList = ref([]); const logTotal = ref(0); const logPage = ref(1); const logLoading = ref(false); const logModule = ref('');
    async function loadLogs() { logLoading.value = true; try { const r = await api.get('/stats/operation-logs', { params: { page: logPage.value, module: logModule.value } }); if (r.code === 0) { logList.value = r.data.list || []; logTotal.value = r.data.total || 0; } } catch(e) { ElMessage.error('网络错误'); } logLoading.value = false; }

    // ========== 通知管理 ==========
    const notifList = ref([]); const notifTotal = ref(0); const notifPage = ref(1); const notifLoading = ref(false);
    const notifForm = reactive({ show: false, type: 'system', title: '', content: '' }); const notifSending = ref(false);
    async function loadNotifs() { notifLoading.value = true; try { const r = await api.get('/stats/notifications', { params: { page: notifPage.value } }); if (r.code === 0) { notifList.value = r.data.list || []; notifTotal.value = r.data.total || 0; } } catch(e) { ElMessage.error('网络错误'); } notifLoading.value = false; }
    async function sendNotif() {
      if (!notifForm.title) return ElMessage.warning('请输入标题');
      notifSending.value = true;
      const r = await api.post('/stats/send-notification', { ...notifForm, user_id: 'all' });
      notifSending.value = false;
       console.log("SAVE_COUPON response", JSON.stringify(r)); if (r.code === 0) { ElMessage.success('发送成功'); notifForm.show = false; loadNotifs(); }
      else ElMessage.error(r.message);
    }

    // 初始化
    onMounted(() => { try { if (token.value) { loadDashboard(); loadCats(); loadModules(); loadMode(); loadAdminCaptcha(); } } catch(e) { ElMessage.error('网络错误'); } });
    
    // watch for menu changes to load context-specific data
    watch(orderTab, (t) => { if (t === 'refunding') loadRefunds(); });
    watch(menu, (m) => {
      if (m === 'stats') { loadStats(); }
      if (m === 'stock_alerts') { loadStockAlerts(); }
      if (m === 'logs') { loadLogs(); }
      if (m === 'orders') { loadShipCompanies(); }
      if (m === 'distribution') { loadDistLevels(); }
      if (m === 'notifications') { loadNotifs(); }
      if (m === 'admins') { loadAdmins(); }
      if (m === 'loginlogs') { loadLoginLogs(); }
      if (m === 'backup') { loadBackups(); }
      if (m === 'syslogs') { loadSysLogs(); }
      if (m === 'design') { loadLayout(); }
      if (m === 'settings') { loadMode(); loadCollectorCfg(); loadWxConfig(); }
      nextTick(() => { if (m === 'stats') { setTimeout(loadStats, 300); } });
    });

    return {
      sidebarOpen, token, menu, adminName, pageTitle, loginForm, loginRules, loginRef, loginLoading, doLogin, switchMenu, onUserCmd, fmt, statusLabel, statusClass,
      dashboard, loadDashboard,
      pq, productList, productTotal, productLoading, categories, productTab, pd, pf, specList, skuList, specForm, specSaving, skuForm, skuSaving, allSpecValues, loadSpecs, loadSkus, saveSpec, deleteSpec, openSkuForm, saveSku, deleteSku, loadProducts, openProduct, saveProduct, toggleProduct, delProduct,
      orderTab, orderKeyword, orderList, orderTotal, orderPage, orderLoading, orderDetail, orderDetailShow, shipShow, shipForm, shipLoading, loadOrders, viewOrder, openShip, doShip,
      uq, userList, userTotal, userLoading, levels, showLevelDlg, levelForm, plans, showPlanDlg, planForm, loadUsers, loadLevels, editLevel, saveLevel, delLevel, loadPlans, editPlan, savePlan, delPlan,
      distTab, distUsers, duLoad, commissions, commLoad, withdraws, wdLoad, distLevels, loadDistUsers, settleComm, approveWd, rejectWd, onDistTab, loadDistLvs, loadComms, loadWds,
      distLevelList, distLevelForm, distLevelSaving, loadDistLevels, editDistLevel, saveDistLevel, delDistLevel,
      levelList, updateLevel,
      selectedProds, copyProduct, batchOnline, batchOffline, shipCompanies, loadShipCompanies,
      marketTab, coupons, seckills, couponForm, skForm, loadCoupons, editCoupon, saveCoupon, delCoupon, loadSeckills, editSeckill, saveSeckill, delSeckill,
      kbList, kbQ, kbPage, kbTotal, kbLoad, kbForm, loadKb, editKb, saveKb, delKb,
      setTab, settings, banners, pageList, bnForm, pageForm, loadSettings, saveSettings, loadBanners, editBanner, saveBanner, delBanner, loadPages, editPage, savePage,
      statsRange, loadStats,
      exportOrderStatus, exportOrders, exportUsers, exportProducts,
      lowStockProducts, lowStockSkus, stockLoading, loadStockAlerts,
      logList, logTotal, logPage, logLoading, logModule, loadLogs,
      notifList, notifTotal, notifPage, notifLoading, notifForm, notifSending, loadNotifs, sendNotif,
      moduleLabels, moduleDescs, modules, loadModules, toggleModule,
      loginLogList, loginLogLoading, loginLogPage, loginLogTotal, loginLogResult, loadLoginLogs,
      modeInfo, loadMode, toggleMode,
      adminList, adminLoading, adminForm, adminSaving, loadAdmins, createAdmin, toggleAdminStatus, delAdmin,
      backupList, backupLoading, backupRunning, backupKeepDays, backupTotalSize, loadBackups, doBackup, restoreBackup,
      sysLogLines, sysLogTotal, loadSysLogs, clearSysLogs,
      showCollector,collectUrl,collectLoading,collectPlatform,collectPlatformLabel,collectResult,collectApplied,collectorCfg,doCollect,applyCollect,wc,sms,mail,saveWxCfg,saveSmsCfg,saveMailCfg,loadWxConfig,updateCfg,loadCollectorCfg,saveCollectorCfg,testCollector,
      compLib, layoutItems, loadLayout, saveLayout, dragStart, dragItemStart, dropItem, moveItem, removeItem
    };
  }
});
// 注册Element Plus图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component);
}
app.use(ElementPlus).mount('#app');
</script>
</body>
</html>

===== mobile/index.html =====
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<meta name="format-detection" content="telephone=no,email=no">
<title>LiteMall 轻商城</title>
<!-- 仅使用MIT开源UI框架，所有视觉素材原创 -->
<link rel="stylesheet" href="/lib/vant.css">
<style>
/* ==================== 设计系统（原创配色） ==================== */
:root{
  /* 主色调：低饱和雾蓝，非CRMEB配色 */
  --primary:#4A7CF7;--primary-light:#EEF3FF;--primary-dark:#3A66D8;
  --secondary:#FF6B4A;--secondary-light:#FFF0EC;
  --success:#00B578;--warning:#FF8A00;--danger:#F53F3F;
  /* 中性色 */
  --bg:#F5F6FA;--card:#FFFFFF;--text:#1A1A2E;--text2:#6B7280;--text3:#B0B7C3;--text4:#D1D5DB;
  --border:#EBEDF0;--shadow:0 2px 12px rgba(0,0,0,.05);--shadow-sm:0 1px 4px rgba(0,0,0,.03);
  --radius:12px;--radius-sm:8px;--radius-lg:16px;
  /* 渐变-原创 */
  --gradient-primary:linear-gradient(135deg,#4A7CF7,#7C9FFF);
  --gradient-user:linear-gradient(135deg,#4A7CF7 0%,#7C9FFF 50%,#A8C0FF 100%);
  --safe-bottom:env(safe-area-inset-bottom)
}
*{margin:0;padding:0;box-sizing:border-box;-webkit-tap-highlight-color:transparent}
body{font-family:-apple-system,'PingFang SC','Microsoft YaHei','Helvetica Neue',sans-serif;background:var(--bg);color:var(--text);font-size:14px;line-height:1.5;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}
[v-cloak]{display:none}
/* ==================== 通用组件（原创风格） ==================== */
.price{color:var(--secondary);font-weight:700}
.price-symbol{font-size:12px;font-weight:400}
.text-elip{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.text-clamp2{display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
.text-clamp1{display:-webkit-box;-webkit-line-clamp:1;-webkit-box-orient:vertical;overflow:hidden}
/* 全尺寸适配 */
img,video,canvas{max-width:100%;height:auto}
.responsive-img{max-width:100%;height:auto;display:block}
.text-clamp3{display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}
/* 微交互 */
.tap{transition:all .15s ease;cursor:pointer}
.tap:active{transform:scale(.96);opacity:.75}
/* 卡片 */
.card{background:var(--card);border-radius:var(--radius);box-shadow:var(--shadow-sm);overflow:hidden}
.card-lg{border-radius:var(--radius-lg);box-shadow:var(--shadow)}
/* 商品网格 */
.goods-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px;padding:0 12px}
.goods-card{background:var(--card);border-radius:var(--radius-sm);overflow:hidden;box-shadow:var(--shadow-sm);transition:transform .2s,box-shadow .2s}
.goods-card:active{transform:translateY(-1px);box-shadow:var(--shadow)}
.goods-card img{width:100%;height:180px;object-fit:cover;display:block;background:var(--bg)}
.goods-card .info{padding:10px 10px 14px}
.goods-card .name{font-size:13px;color:var(--text);margin-bottom:4px}
.goods-card .tags{display:flex;gap:4px;margin-bottom:4px;flex-wrap:wrap}
.goods-card .tag{font-size:10px;padding:2px 6px;border-radius:3px;font-weight:500}
.goods-card .tag.hot{background:var(--secondary-light);color:var(--secondary)}
.goods-card .tag.new{background:var(--primary-light);color:var(--primary)}
/* 空状态 */
.empty{padding:80px 20px;text-align:center}
.empty .icon{font-size:56px;color:var(--text4);margin-bottom:12px}
.empty .txt{font-size:14px;color:var(--text2);margin-bottom:20px}
/* 骨架屏 */
.skeleton{background:linear-gradient(90deg,#f0f2f5 25%,#e8ebf0 50%,#f0f2f5 75%);background-size:200% 100%;animation:shimmer 1.5s infinite;border-radius:6px}
@keyframes shimmer{0%{background-position:200% 0}100%{background-position:-200% 0}}
.skeleton-lg{background:linear-gradient(90deg,#e8ebf0 25%,#dde0e6 50%,#e8ebf0 75%);background-size:200% 100%;animation:shimmer 1.8s ease-in-out infinite;border-radius:8px}
.skeleton-loading{position:relative;overflow:hidden}
.skeleton-loading::after{content:'';position:absolute;inset:0;background:linear-gradient(90deg,transparent,rgba(255,255,255,.3),transparent);animation:shimmer 1.5s infinite;background-size:200% 100%}
/* 悬浮底部 */
.float-bar{position:fixed;bottom:50px;left:0;right:0;background:var(--card);padding:10px 16px;display:flex;align-items:center;justify-content:space-between;box-shadow:0 -2px 12px rgba(0,0,0,.06);z-index:20}
/* 滚动分类标签 */
.scroll-tabs{display:flex;gap:0;overflow-x:auto;white-space:nowrap;padding:0 12px;background:var(--card);-webkit-overflow-scrolling:touch}
.scroll-tabs::-webkit-scrollbar{display:none}
.scroll-tab{display:inline-flex;padding:12px 14px;font-size:13px;color:var(--text2);border-bottom:2px solid transparent;transition:all .2s;flex-shrink:0}
.scroll-tab.active{color:var(--primary);border-bottom-color:var(--primary);font-weight:600}
/* 底部导航原创样式 - 不使用Vant tabbar原样 */
.m-tabbar{position:fixed;bottom:0;left:0;right:0;height:50px;background:var(--card);display:flex;align-items:center;border-top:1px solid var(--border);z-index:100;padding-bottom:env(safe-area-inset-bottom)}
.m-tabbar .tab-item{flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;height:100%;color:var(--text3);font-size:10px;transition:color .2s}
.m-tabbar .tab-item.active{color:var(--primary)}
.m-tabbar .tab-item .ti-icon{font-size:22px;line-height:1}
.m-tabbar .tab-item .ti-label{font-size:11px}
</style>
</head>
<body>
<div id="app" v-cloak>
  <!-- ==================== 1. 首页 ==================== -->
  <div v-show="cp==='home'" style="padding-bottom:100px">
    <!-- 顶部导航区（品牌+搜索+通知） -->
    <div style="background:var(--gradient-primary);padding:44px 12px 0;position:sticky;top:0;z-index:50">
      <div style="display:flex;align-items:center;gap:10px;padding-bottom:10px">
        <div style="font-size:20px;font-weight:800;color:#fff;letter-spacing:-.5px;flex-shrink:0">LM</div>
        <div style="flex:1;position:relative">
          <van-icon name="search" size="14" style="position:absolute;left:12px;top:50%;transform:translateY(-50%);color:rgba(255,255,255,.6);z-index:1" />
          <div style="background:rgba(255,255,255,.2);border-radius:20px;padding:8px 12px 8px 34px;font-size:13px;color:rgba(255,255,255,.6);cursor:pointer" @click="cp='search'">搜索商品/关键词</div>
        </div>
        <div style="width:36px;height:36px;display:flex;align-items:center;justify-content:center;position:relative;cursor:pointer" @click="vant.showToast('消息')" class="tap">
          <van-icon name="bell" size="20" color="#fff" />
          <span v-if="msgCount>0" style="position:absolute;top:4px;right:4px;width:7px;height:7px;background:var(--secondary);border-radius:50%;border:1.5px solid var(--primary)"></span>
        </div>
      </div>
    </div>
    <!-- 快捷分类标签 -->
    <div class="scroll-tabs" style="background:var(--card);border-bottom:1px solid var(--border)">
      <span v-for="c in cats" :key="c.id" class="scroll-tab" :class="{active:activeCat===c.id}" @click="activeCat=c.id;loadHomeCat(c.id)" class="tap">{{c.name}}</span>
    </div>
    <!-- 轮播Banner -->
    <van-swipe :autoplay="3000" indicator-color="#4A7CF7" style="margin:10px 12px;border-radius:var(--radius-sm);overflow:hidden">
      <van-swipe-item v-for="b in banners" :key="b.id"><img :src="b.image" style="width:100%;height:140px;display:block;background:var(--bg)" /></van-swipe-item>
      <van-swipe-item v-if="!banners.length"><div style="width:100%;height:140px;background:var(--gradient-primary);display:flex;align-items:center;justify-content:center;color:rgba(255,255,255,.8);font-size:16px;font-weight:600">LiteMall 优选</div></van-swipe-item>
    </van-swipe>
    <!-- 活动卡片（横向滑动） -->
    <div style="padding:0 12px;margin:10px 0">
      <div style="display:flex;gap:10px;overflow-x:auto;-webkit-overflow-scrolling:touch">
        <div v-for="act in activities" :key="act.title" style="flex-shrink:0;width:160px;padding:14px 16px;border-radius:var(--radius-sm);background:var(--gradient-primary);color:#fff" class="tap">
          <div style="font-size:15px;font-weight:700">{{act.title}}</div>
          <div style="font-size:12px;opacity:.85;margin-top:4px">{{act.desc}}</div>
        </div>
      </div>
    </div>
    <!-- 功能入口宫格 -->
    <div class="card" style="margin:0 12px 10px;padding:16px 0 8px">
      <div style="display:grid;grid-template-columns:repeat(5,1fr);gap:4px;text-align:center">
        <div v-for="(e,i) in entries" :key="i" class="tap" style="padding:4px 0" @click="vant.showToast(e.label)">
          <div style="width:40px;height:40px;background:var(--primary-light);border-radius:10px;display:flex;align-items:center;justify-content:center;margin:0 auto 6px;font-size:20px">{{e.icon}}</div>
          <div style="font-size:11px;color:var(--text2)">{{e.label}}</div>
        </div>
      </div>
    </div>
    <!-- 商品推荐分区 -->
    <div v-if="homeLoading" style="padding:0 12px">
      <div class="skeleton" style="height:20px;width:120px;margin:12px 0"></div>
      <div class="goods-grid"><div v-for="n in 4" :key="n"><div class="skeleton" style="height:180px"></div><div style="padding:8px"><div class="skeleton" style="height:14px;width:80%;margin-bottom:6px"></div><div class="skeleton" style="height:14px;width:50%"></div></div></div></div>
    </div>
    <div v-else>
      <!-- 新品上架 -->
      <div style="margin:12px 12px 0">
        <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px">
          <span style="font-size:16px;font-weight:700">🆕 新品上架</span>
          <span style="font-size:12px;color:var(--text3);padding:4px 10px" class="tap">更多 ›</span>
        </div>
        <div v-if="newProds.length" class="goods-grid">
          <div v-for="p in newProds" :key="p.id" class="goods-card" @click="showProduct(p.id)">
            <img :src="p.image" loading="lazy" onerror="this.src='data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%22300%22 height=%22300%22><rect fill=%22%23f5f6fa%22 width=%22300%22 height=%22300%22/><text x=%2250%%22 y=%2250%%22 text-anchor=%22middle%22 dy=%22.3em%22 fill=%22%23b0b7c3%22 font-size=%2220%22>📦</text></svg>'" />
            <div class="info">
              <div class="name text-clamp2">{{p.name}}</div>
              <div class="tags" v-if="p.is_new"><span class="tag new">新品</span></div>
              <div class="price"><span class="price-symbol">¥</span>{{(+p.price).toFixed(2)}}</div>
            </div>
          </div>
        </div>
        <div v-else class="empty" style="padding:30px 0"><div class="icon">📦</div><div class="txt">暂无新品</div></div>
      </div>
      <!-- 热销爆款 -->
      <div style="margin:16px 12px 0">
        <div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:10px">
          <span style="font-size:16px;font-weight:700">🔥 热销爆款</span>
          <span style="font-size:12px;color:var(--text3);padding:4px 10px" class="tap">更多 ›</span>
        </div>
        <div class="goods-grid">
          <div v-for="p in hotProds" :key="p.id" class="goods-card" @click="showProduct(p.id)">
            <img :src="p.image" loading="lazy" onerror="this.src='data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%22300%22 height=%22300%22><rect fill=%22%23f5f6fa%22 width=%22300%22 height=%22300%22/><text x=%2250%%22 y=%2250%%22 text-anchor=%22middle%22 dy=%22.3em%22 fill=%22%23b0b7c3%22 font-size=%2220%22>📦</text></svg>'" />
            <div class="info">
              <div class="name text-clamp2">{{p.name}}</div>
              <div class="tags"><span class="tag hot">爆款</span></div>
              <div class="price"><span class="price-symbol">¥</span>{{(+p.price).toFixed(2)}}</div>
            </div>
          </div>
        </div>
      </div>
      <!-- 优选好物 -->
      <div style="margin:16px 12px 40px">
        <div style="font-size:16px;font-weight:700;margin-bottom:10px">⭐ 优选好物</div>
        <div class="goods-grid">
          <div v-for="p in bestProds" :key="p.id" class="goods-card" @click="showProduct(p.id)">
            <img :src="p.image" />
            <div class="info">
              <div class="name text-clamp1">{{p.name}}</div>
              <div style="display:flex;justify-content:space-between;align-items:center;margin-top:4px">
                <div class="price"><span class="price-symbol">¥</span>{{(+p.price).toFixed(2)}}</div>
                <span style="font-size:11px;color:var(--text3)">已售{{p.sales||0}}</span>
              </div>
            </div>
          </div>
        </div>
      </div>
    </div>
    <!-- 底部版权 -->
    <div style="text-align:center;padding:20px 12px 30px;font-size:11px;color:var(--text3);border-top:1px solid var(--border)">
      <div>© 2026 LiteMall 轻商城</div>
      <div style="margin-top:4px">All rights reserved</div>
    </div>
  </div>

  <!-- ==================== 2. 商品分类 ==================== -->
  <div v-show="cp==='category'" style="padding-bottom:100px">
    <div style="display:flex;align-items:center;gap:10px;padding:44px 12px 10px;background:var(--card);position:sticky;top:0;z-index:50">
      <div class="tap" @click="cp='home'" style="font-size:20px">‹</div>
      <div style="flex:1;background:var(--bg);border-radius:20px;padding:8px 14px;font-size:13px;color:var(--text3);cursor:pointer" @click="cp='search'">搜索商品</div>
    </div>
    <div style="display:flex;height:calc(100vh - 100px)">
      <!-- 左侧分类 -->
      <div style="width:90px;flex-shrink:0;background:var(--bg);overflow-y:auto;padding:8px 0">
        <div v-for="c in cats" :key="c.id" class="tap" style="padding:14px 8px;text-align:center;font-size:13px;color:var(--text2);border-left:3px solid transparent;transition:all .2s;background:var(--bg)" :style="catIdx===c.id?'color:var(--primary);border-left-color:var(--primary);background:var(--card);font-weight:600':''" @click="catIdx=c.id;loadCatProds(c.id)">{{c.name}}</div>
      </div>
      <!-- 右侧内容 -->
      <div style="flex:1;overflow-y:auto;padding:12px;background:var(--card)">
        <div v-if="subCats.length" style="display:grid;grid-template-columns:repeat(3,1fr);gap:12px;margin-bottom:16px">
          <div v-for="sc in subCats" :key="sc.id" class="tap" style="text-align:center;padding:12px 4px" @click="loadCatProds(sc.id)">
            <div style="width:44px;height:44px;background:var(--primary-light);border-radius:12px;margin:0 auto 6px;display:flex;align-items:center;justify-content:center;font-size:20px">{{sc.icon||'📁'}}</div>
            <div style="font-size:11px;color:var(--text2)">{{sc.name}}</div>
          </div>
        </div>
        <div class="goods-grid" style="padding:0" v-if="catProds.length">
          <div v-for="p in catProds" :key="p.id" class="goods-card" @click="showProduct(p.id)">
            <img :src="p.image" />
            <div class="info"><div class="name text-clamp2">{{p.name}}</div><div class="price" style="margin-top:4px">¥{{(+p.price).toFixed(2)}}</div></div>
          </div>
        </div>
        <div v-if="!catProds.length && !subCats.length" class="empty"><div class="icon">📂</div><div class="txt">暂无分类内容</div><van-button type="primary" round size="small" @click="cp='home'">去首页</van-button></div>
      </div>
    </div>
    <!-- 底部购物车栏 -->
    <div class="float-bar">
      <div style="display:flex;align-items:center;gap:8px" @click="cp='cart'" class="tap">
        <van-icon name="cart-o" size="22" color="var(--primary)" />
        <span v-if="cart.length" style="background:var(--secondary);color:#fff;font-size:11px;padding:1px 6px;border-radius:10px;font-weight:600">{{cart.length}}</span>
      </div>
      <div style="display:flex;align-items:center;gap:12px">
        <div style="text-align:right;font-size:12px;color:var(--text2)">合计 <span class="price" style="font-size:16px">¥0.00</span></div>
        <van-button type="primary" round size="small" style="height:38px;padding:0 20px" @click="cp='cart'">去结算</van-button>
      </div>
    </div>
  </div>

  <!-- ==================== 3. 购物车 ==================== -->
  <div v-show="cp==='cart'" style="padding-bottom:100px">
    <div style="display:flex;justify-content:space-between;align-items:center;padding:44px 16px 12px;background:var(--card)">
      <span style="font-size:18px;font-weight:700">购物车</span>
      <span class="tap" style="font-size:13px;color:var(--text2)" @click="batchDelCart">批量删除</span>
    </div>
    <!-- 服务保障 -->
    <div style="display:flex;gap:12px;padding:8px 16px;background:var(--card);border-bottom:1px solid var(--border);font-size:11px;color:var(--text3)">
      <span>✓ 正品保障</span><span>✓ 极速发货</span><span>✓ 七天售后</span>
    </div>
    <!-- 商品列表 -->
    <div v-if="cart.length" style="padding:8px 12px">
      <div v-for="(item,i) in cart" :key="item.id" style="display:flex;align-items:center;background:var(--card);padding:12px;border-radius:var(--radius-sm);margin-bottom:8px;box-shadow:var(--shadow-sm)">
        <van-checkbox v-model="item.selected" checked-color="#4A7CF7" @change="calcCart" />
        <img :src="item.image" style="width:76px;height:76px;border-radius:8px;margin:0 10px;object-fit:cover;flex-shrink:0;background:var(--bg)" loading="lazy" onerror="this.src='data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%22300%22 height=%22300%22><rect fill=%22%23f5f6fa%22 width=%22300%22 height=%22300%22/><text x=%2250%%22 y=%2250%%22 text-anchor=%22middle%22 dy=%22.3em%22 fill=%22%23b0b7c3%22 font-size=%2220%22>📦</text></svg>'" />
        <div style="flex:1;min-width:0">
          <div class="text-elip" style="font-size:14px;font-weight:500;color:var(--text)">{{item.name}}</div>
          <div style="font-size:12px;color:var(--text3);margin:4px 0">{{item.sku_info||''}}</div>
          <div style="display:flex;justify-content:space-between;align-items:center">
            <div class="price" style="font-size:15px">¥{{(+item.price).toFixed(2)}}</div>
            <van-stepper v-model="item.quantity" :min="1" theme="round" button-size="22" />
          </div>
        </div>
      </div>
    </div>
    <!-- 空购物车 -->
    <div v-else class="empty" style="padding-top:100px">
      <div class="icon">🛒</div>
      <div class="txt" style="font-size:15px">购物车还是空的，快去挑选商品吧~</div>
      <van-button type="primary" round size="small" @click="cp='home'" class="tap">去逛逛</van-button>
      <!-- 热门推荐 -->
      <div style="text-align:left;margin-top:40px"><div style="font-size:14px;font-weight:600;margin-bottom:12px;color:var(--text)">🔥 热门推荐</div>
        <div class="goods-grid"><div v-for="p in hotProds.slice(0,4)" :key="p.id" class="goods-card" @click="showProduct(p.id)"><img :src="p.image" /><div class="info"><div class="name text-clamp2">{{p.name}}</div><div class="price">¥{{(+p.price).toFixed(2)}}</div></div></div></div>
      </div>
    </div>
    <!-- 底部结算栏 -->
    <div class="float-bar" v-if="cart.length">
      <van-checkbox v-model="cartAll" @change="toggleCartAll" checked-color="#4A7CF7">全选</van-checkbox>
      <div style="display:flex;align-items:center;gap:12px">
        <div style="text-align:right">
          <div style="font-size:11px;color:var(--text3)">合计</div>
          <div class="price" style="font-size:18px">¥{{cartTotal.toFixed(2)}}</div>
        </div>
        <van-button type="primary" round size="small" style="height:40px;padding:0 28px;font-weight:600" :disabled="!cartSelected.length" @click="goCheckout">去结算</van-button>
      </div>
    </div>
  </div>

  <!-- ==================== 4. 个人中心 ==================== -->
  <div v-show="cp==='user'" style="padding-bottom:50px">
    <!-- 用户信息区 -->
    <div style="background:var(--gradient-user);padding:50px 20px 30px;display:flex;align-items:center;gap:14px;color:#fff">
      <div style="width:60px;height:60px;border-radius:50%;background:rgba(255,255,255,.25);display:flex;align-items:center;justify-content:center;font-size:28px;flex-shrink:0;border:2px solid rgba(255,255,255,.3)" class="tap">👤</div>
      <div style="flex:1;min-width:0">
        <div style="font-size:18px;font-weight:600">{{usr.nickname||'用户'}}</div>
        <div style="font-size:13px;opacity:.85;margin-top:2px">{{usr.level_name||'普通会员'}}</div>
      </div>
    </div>
    <!-- 资产数据 -->
    <div style="display:flex;background:var(--card);margin:-14px 12px 0;border-radius:var(--radius);padding:14px 0;box-shadow:var(--shadow);position:relative;z-index:1">
      <div v-for="(a,i) in assets" :key="i" class="tap" style="flex:1;text-align:center;padding:4px 0" @click="vant.showToast(a.label)">
        <div style="font-size:20px;font-weight:700;color:var(--primary)">{{a.val}}</div>
        <div style="font-size:11px;color:var(--text3);margin-top:2px">{{a.label}}</div>
      </div>
    </div>
    <!-- 订单中心 -->
    <div class="card" style="margin:12px;padding:0">
      <div style="display:flex;justify-content:space-between;align-items:center;padding:14px 16px 8px">
        <span style="font-size:15px;font-weight:600">订单中心</span>
        <span class="tap" style="font-size:12px;color:var(--text3)" @click="goOrders('')">全部订单 ›</span>
      </div>
      <div style="display:grid;grid-template-columns:repeat(5,1fr);padding:8px 0 14px;text-align:center;font-size:11px">
        <div class="tap" @click="goOrders('pending_pay')"><div style="font-size:24px;margin-bottom:4px">📄</div><span style="color:var(--text2)">待付款</span></div>
        <div class="tap" @click="goOrders('pending_ship')"><div style="font-size:24px;margin-bottom:4px">📦</div><span style="color:var(--text2)">待发货</span></div>
        <div class="tap" @click="goOrders('shipped')"><div style="font-size:24px;margin-bottom:4px">🚚</div><span style="color:var(--text2)">待收货</span></div>
        <div class="tap" @click="goOrders('received')"><div style="font-size:24px;margin-bottom:4px">⭐</div><span style="color:var(--text2)">待评价</span></div>
        <div class="tap" @click="goOrders('refunding')"><div style="font-size:24px;margin-bottom:4px">🔄</div><span style="color:var(--text2)">售后</span></div>
      </div>
    </div>
    <!-- 综合功能 -->
    <div class="card" style="margin:12px;overflow:hidden">
      <van-cell title="我的地址" is-link icon="location-o" @click="loadAddr();showAddress=true" class="tap" />
      <van-cell title="我的收藏" is-link icon="star-o" @click="vant.showToast('收藏')" class="tap" />
      <van-cell title="浏览记录" is-link icon="clock-o" @click="vant.showToast('浏览记录')" class="tap" />
      <van-cell title="优惠券" is-link icon="coupon-o" @click="vant.showToast('优惠券')" class="tap" />
      <van-cell title="在线客服" is-link icon="chat-o" @click="vant.showToast('客服')" class="tap" />
      <van-cell title="系统设置" is-link icon="setting-o" @click="vant.showToast('设置')" class="tap" />
      <van-cell title="帮助中心" is-link icon="info-o" @click="vant.showToast('帮助')" class="tap" />
    </div>
    <div style="padding:0 16px 20px"><van-button round block plain @click="logout" class="tap">退出登录</van-button></div>
  </div>

  <!-- ==================== 5. 登录 ==================== -->
  <div v-show="cp==='login'" style="padding:60px 32px">
    <div style="text-align:center;margin-bottom:32px">
      <div style="font-size:36px;font-weight:800;color:var(--primary);letter-spacing:-1px">LiteMall</div>
      <div style="font-size:14px;color:var(--text3);margin-top:6px">轻商城 · 轻松购</div>
    </div>
    <!-- 登录方式切换 -->
    <div style="display:flex;margin-bottom:20px;border-bottom:1px solid var(--border)">
      <div v-for="(t,i) in loginTypes" :key="i" class="tap" style="flex:1;text-align:center;padding:10px 0;font-size:14px;color:var(--text3);border-bottom:2px solid transparent;transition:all .2s" :style="loginType===t.key?'color:var(--primary);border-bottom-color:var(--primary);font-weight:600':''" @click="loginType=t.key">{{t.label}}</div>
    </div>
    <!-- 手机密码登录 -->
    <van-cell-group inset v-if="loginType==='phone'">
      <van-field v-model="lgPhone" label="手机号" placeholder="请输入手机号" clearable maxlength="11" />
      <van-field v-model="lgPwd" type="password" label="密码" placeholder="请输入密码" clearable />
    </van-cell-group>
    <!-- 邮箱登录 -->
    <van-cell-group inset v-if="loginType==='email'">
      <van-field v-model="lgEmail" label="邮箱" placeholder="请输入邮箱地址" clearable />
      <van-field v-model="lgPwd" type="password" label="密码" placeholder="请输入密码" clearable />
    </van-cell-group>
    <!-- 短信登录 -->
    <van-cell-group inset v-if="loginType==='sms'">
      <van-field v-model="lgSmsPhone" label="手机号" placeholder="请输入手机号" clearable maxlength="11" />
      <div style="display:flex;gap:8px;padding:8px 16px">
        <van-field v-model="lgSmsCode" placeholder="输入短信验证码" style="flex:1" maxlength="6" />
        <van-button round size="small" style="height:44px;flex-shrink:0;padding:0 16px" :disabled="smsCountdown>0" @click="sendSmsCode" class="tap">{{smsCountdown>0?smsCountdown+'s':'获取验证码'}}</van-button>
      </div>
    </van-cell-group>
    <!-- 图形验证码（统一） -->
    <van-cell-group inset style="margin-top:12px">
      <div style="display:flex;gap:8px;padding:8px 16px">
        <van-field v-model="lgCaptcha" placeholder="输入图形验证码" style="flex:1" maxlength="4" />
        <div v-html="captchaSvg" style="flex-shrink:0;width:120px;height:40px;border-radius:6px;overflow:hidden;cursor:pointer" @click="loadCaptcha" class="tap"></div>
      </div>
    </van-cell-group>
    <div style="margin-top:20px">
      <van-button round block type="primary" size="large" style="height:48px;font-size:16px;font-weight:600" @click="handleLogin" class="tap" :loading="loginSubmitting">登 录</van-button>
      <van-button round block plain size="large" style="margin-top:12px;height:44px" @click="cp='register'" class="tap">没有账号？去注册</van-button>
    </div>
  </div>
  <div v-show="cp==='register'" style="padding:60px 32px">
    <div style="text-align:center;margin-bottom:40px"><div style="font-size:24px;font-weight:700;color:var(--primary)">注册账号</div></div>
    <van-cell-group inset><van-field v-model="rgPhone" label="手机号" placeholder="请输入手机号" clearable /><van-field v-model="rgPwd" type="password" label="密码" placeholder="至少6位" clearable /><van-field v-model="rgName" label="昵称" placeholder="选填" clearable /></van-cell-group>
    <div style="margin-top:24px"><van-button round block type="primary" size="large" style="height:48px;font-size:16px;font-weight:600" @click="handleRegister" class="tap" :loading="regSubmitting">注 册</van-button></div>
  </div>

  <!-- ==================== 商品详情 ==================== -->
  <van-overlay :show="cp==='product'" z-index="999" @click="cp=pgBack"><div style="background:var(--card);height:92vh;border-radius:16px 16px 0 0;overflow-y:auto;position:absolute;bottom:0;left:0;right:0" @click.stop>
    <van-nav-bar :title="prodLoading?'加载中...':(prod.name||'商品详情')" left-arrow @click-left="cp=pgBack">
      <template #right><span class="tap" style="font-size:18px">⋯</span></template>
    </van-nav-bar>
    <div v-if="prodLoading">
      <div class="skeleton-lg" style="width:100%;height:320px;border-radius:0"></div>
      <div style="padding:16px">
        <div class="skeleton-lg" style="height:24px;width:70%;margin-bottom:12px"></div>
        <div class="skeleton-lg" style="height:20px;width:40%;margin-bottom:16px"></div>
        <div class="skeleton-lg" style="height:16px;width:100%;margin-bottom:8px"></div>
        <div class="skeleton-lg" style="height:16px;width:80%;margin-bottom:8px"></div>
        <div class="skeleton-lg" style="height:16px;width:60%"></div>
      </div>
    </div>
    <template v-if="!prodLoading">
    <van-swipe indicator-color="#4A7CF7"><van-swipe-item v-for="img in prodImgs" :key="img"><img :src="img" style="width:100%;height:320px;object-fit:cover;display:block;background:var(--bg)" loading="lazy" onerror="this.src='data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%22300%22 height=%22300%22><rect fill=%22%23f5f6fa%22 width=%22300%22 height=%22300%22/><text x=%2250%%22 y=%2250%%22 text-anchor=%22middle%22 dy=%22.3em%22 fill=%22%23b0b7c3%22 font-size=%2220%22>📦</text></svg>'" /></van-swipe-item></van-swipe>
    <div style="padding:16px">
      <div style="font-size:18px;font-weight:700;line-height:1.4">{{prod.name}}</div>
      <div style="margin:12px 0;display:flex;align-items:baseline;gap:8px;flex-wrap:wrap">
        <span class="price" style="font-size:24px">¥{{(+curPrice).toFixed(2)}}</span>
        <span v-if="+curOriginalPrice>+curPrice" style="text-decoration:line-through;color:var(--text3);font-size:13px">¥{{(+curOriginalPrice).toFixed(2)}}</span>
        <span style="font-size:12px;color:var(--text3)">已售{{prod.sales||0}}</span>
        <span style="font-size:12px;color:var(--text3)">库存{{curStock}}</span>
      </div>
      <!-- 规格选择 -->
      <div v-if="prod.specs?.length" style="padding:12px 0;border-top:1px solid var(--border);border-bottom:1px solid var(--border);margin:8px 0">
        <div v-for="s in prod.specs" :key="s.name" style="margin-bottom:8px">
          <div style="font-size:13px;color:var(--text2);margin-bottom:6px">{{s.name}}</div>
          <div style="display:flex;gap:8px;flex-wrap:wrap">
            <span v-for="v in (s.values||[])" :key="v" class="tap" style="padding:6px 14px;border:1px solid var(--border);border-radius:6px;font-size:13px;transition:all .2s" :style="specSel[s.name]===v?'background:var(--primary);color:#fff;border-color:var(--primary)':''" @click="selectSpec(s.name,v)">{{v}}</span>
          </div>
        </div>
      </div>
      <!-- 数量选择 -->
      <div style="display:flex;align-items:center;justify-content:space-between;padding:10px 0;border-top:1px solid var(--border);margin:8px 0">
        <span style="font-size:14px;color:var(--text2)">数量</span>
        <div style="display:flex;align-items:center;gap:12px">
          <span class="tap" style="width:28px;height:28px;border:1px solid var(--border);border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:16px;font-weight:600" @click="prodQty>1?prodQty--:null">−</span>
          <span style="font-size:16px;font-weight:600;min-width:20px;text-align:center">{{prodQty}}</span>
          <span class="tap" style="width:28px;height:28px;border:1px solid var(--primary);border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:16px;font-weight:600;color:var(--primary)" @click="prodQty<curStock?prodQty++:null">+</span>
        </div>
      </div>
      <!-- 图文详情 -->
      <div v-if="prod.description" style="padding:12px 0;border-top:1px solid var(--border);margin:8px 0">
        <div style="font-size:15px;font-weight:600;margin-bottom:8px">商品详情</div>
        <div style="font-size:13px;color:var(--text2);line-height:1.8">{{prod.description}}</div>
      </div>
      <!-- 评价展示 -->
      <div v-if="reviews.length" style="padding:12px 0;border-top:1px solid var(--border);margin:8px 0">
        <div style="font-size:15px;font-weight:600;margin-bottom:10px">商品评价 ({{reviews.length}})</div>
        <div v-for="rv in reviews" :key="rv.id" style="padding:8px 0;border-bottom:1px solid var(--border)">
          <div style="display:flex;align-items:center;gap:6px;margin-bottom:4px">
            <span style="font-size:22px">{{rv.avatar||'👤'}}</span>
            <span style="font-size:13px;font-weight:500">{{rv.nickname||'用户'}}</span>
            <span style="font-size:11px;color:var(--text3)">{{'⭐'.repeat(rv.rating)}}</span>
          </div>
          <div style="font-size:13px;color:var(--text2);line-height:1.6">{{rv.content}}</div>
          <div v-if="rv.images?.length" style="display:flex;gap:6px;margin-top:4px">
            <img v-for="(img,idx) in rv.images.slice(0,3)" :key="idx" :src="img" style="width:60px;height:60px;border-radius:6px;object-fit:cover" loading="lazy" />
          </div>
        </div>
      </div>
      <div v-else style="padding:12px 0;border-top:1px solid var(--border);margin:8px 0;text-align:center">
        <div style="font-size:15px;font-weight:600;margin-bottom:10px">商品评价</div>
        <div class="empty" style="padding:20px 0"><div class="icon">📝</div><div class="txt">暂无评价</div></div>
      </div>
    </div>
    <div style="position:sticky;bottom:0;display:flex;gap:10px;padding:12px 16px;background:var(--card);border-top:1px solid var(--border)">
      <van-button round style="flex:1;height:44px;border-color:var(--primary);color:var(--primary)" plain @click="addCart" class="tap" :loading="addCartLoading">加入购物车</van-button>
      <van-button type="primary" round style="flex:1;height:44px;font-weight:600" @click="buyNow" class="tap" :loading="buyNowLoading">立即购买</van-button>
    </div>
    </template>
  </div></van-overlay>

  <!-- ==================== 支付弹窗 ==================== -->
  <van-popup v-model:show="showPayment" position="right" :style="{width:'100%',height:'100%'}">
    <van-nav-bar title="订单支付" left-arrow @click-left="closePayment" />
    <div style="padding:24px 20px;text-align:center">
      <!-- 支付金额 -->
      <div style="font-size:14px;color:var(--text3);margin-bottom:8px">支付金额</div>
      <div style="font-size:36px;font-weight:700;color:var(--secondary);margin-bottom:24px">
        <span style="font-size:18px">¥</span>{{payInfo?.amount?.toFixed(2)||'0.00'}}
      </div>
      <!-- 订单号 -->
      <div style="font-size:12px;color:var(--text3);margin-bottom:24px">订单号: {{payInfo?.order_no||''}}</div>
      
      <!-- 支付方式选择 -->
      <div style="margin-bottom:24px">
        <div style="font-size:14px;font-weight:600;color:var(--text);margin-bottom:12px;text-align:left">选择支付方式</div>
        <div style="display:flex;gap:12px">
          <div class="tap" style="flex:1;padding:16px 12px;border:2px solid;border-radius:var(--radius-sm);text-align:center;transition:all .2s"
            :style="payMethod==='wechat'?'border-color:var(--primary);background:var(--primary-light)':'border-color:var(--border)'"
            @click="switchPayMethod('wechat')">
            <div style="font-size:32px;margin-bottom:6px">💚</div>
            <div style="font-size:13px;font-weight:600">微信支付</div>
          </div>
          <div class="tap" style="flex:1;padding:16px 12px;border:2px solid;border-radius:var(--radius-sm);text-align:center;transition:all .2s"
            :style="payMethod==='alipay'?'border-color:var(--primary);background:var(--primary-light)':'border-color:var(--border)'"
            @click="switchPayMethod('alipay')">
            <div style="font-size:32px;margin-bottom:6px">💙</div>
            <div style="font-size:13px;font-weight:600">支付宝</div>
          </div>
        </div>
      </div>

      <!-- 加载状态 -->
      <div v-if="paying" style="padding:20px">
        <van-loading type="spinner" color="var(--primary)" size="24px" />
        <div style="margin-top:12px;font-size:13px;color:var(--text3)">处理中...</div>
      </div>

      <!-- 支付按钮区域 -->
      <div v-if="!paying" style="margin-top:12px">
        <!-- 模拟模式：显示模拟支付按钮 -->
        <div v-if="payInfo?.is_mock !== false">
          <van-button type="primary" round block style="height:48px;font-size:16px;font-weight:600" 
            :loading="paying" @click="doMockPay" class="tap">
            ✅ 模拟支付成功
          </van-button>
          <div style="margin-top:10px;font-size:11px;color:var(--text3)">
            ⚠️ 模拟模式，点击即可完成支付
          </div>
        </div>
        <!-- 生产模式：跳转支付 -->
        <div v-else>
          <van-button type="primary" round block style="height:48px;font-size:16px;font-weight:600" 
            @click="doRealPay" class="tap">
            前往支付 ›
          </van-button>
          <div style="margin-top:10px;font-size:11px;color:var(--text3)">
            将在新窗口打开支付页面
          </div>
        </div>
      </div>
    </div>
  </van-popup>

  <!-- ==================== 订单列表 ==================== -->
  <van-popup v-model:show="showOrders" position="right" :style="{width:'100%',height:'100%'}">
    <van-nav-bar title="我的订单" left-arrow @click-left="showOrders=false"><template #right><span style="font-size:12px;color:var(--text3)">共{{orders.length}}单</span></template></van-nav-bar>
    <van-tabs v-model="orderTab" @click-tab="loadOrders" sticky style="position:sticky;top:0;z-index:10">
      <van-tab title="全部" name="" /><van-tab title="待付款" name="pending_pay" /><van-tab title="待发货" name="pending_ship" /><van-tab title="待收货" name="shipped" /><van-tab title="已完成" name="completed" />
    </van-tabs>
    <div style="padding:8px 12px">
      <div v-for="o in orders" :key="o.id" class="card" style="margin-bottom:8px">
        <div style="display:flex;justify-content:space-between;padding:12px 14px 4px;font-size:12px"><span style="color:var(--text3)">{{o.order_no}}</span><span style="color:var(--primary);font-weight:600">{{stText(o.status)}}</span></div>
        <div v-for="item in (o.items||[])" :key="item.id" style="display:flex;gap:10px;padding:8px 14px"><img :src="item.image" style="width:56px;height:56px;border-radius:6px;object-fit:cover;flex-shrink:0;background:var(--bg)" /><div style="flex:1;min-width:0"><div class="text-clamp2" style="font-size:13px">{{item.product_name}}</div><div style="margin-top:4px;font-size:13px"><span class="price">¥{{(+item.price).toFixed(2)}}</span><span style="color:var(--text3)">×{{item.quantity}}</span></div></div></div>
        <div style="display:flex;justify-content:space-between;align-items:center;padding:8px 14px 12px;border-top:1px solid var(--border)">
          <div style="font-size:13px;color:var(--text3)">共{{o.items?.length||0}}件 · 合计 <span class="price" style="font-size:15px">¥{{(+o.pay_amount).toFixed(2)}}</span></div>
          <div style="display:flex;gap:6px">
            <van-button v-if="o.status==='pending_pay'" round size="mini" type="primary" @click="payOrder(o.order_no)" class="tap">去支付</van-button>
            <van-button v-if="o.status==='shipped'" round size="mini" plain @click="confirmOrder(o.order_no)" class="tap">确认收货</van-button>
            <van-button v-if="o.status==='received'" round size="mini" type="warning" plain @click="showReview(o)" class="tap">评价</van-button>
          </div>
        </div>
      </div>
      <div v-if="orderLoading" style="padding:20px;text-align:center"><van-loading type="spinner" color="var(--primary)" size="24" /><div style="margin-top:8px;font-size:13px;color:var(--text3)">加载中...</div></div>
      <div v-else-if="!orders.length" class="empty"><div class="icon">📋</div><div class="txt">暂无订单</div></div>
    </div>
  </van-popup>

  <!-- ==================== 评价弹窗 ==================== -->
  <van-dialog v-model:show="showReviewDlg" title="商品评价" show-cancel-button :before-close="submitReview" style="padding:4px 0">
    <div style="padding:12px 16px">
      <div style="text-align:center;margin-bottom:12px">
        <span v-for="n in 5" :key="n" class="tap" style="font-size:28px;cursor:pointer;margin:0 2px" :style="{opacity: n<=reviewRating?1:0.3}" @click="reviewRating=n">{{'⭐'}}</span>
        <div style="font-size:12px;color:var(--text3);margin-top:4px">点击星星评分</div>
      </div>
      <van-field v-model="reviewContent" type="textarea" placeholder="分享您的使用体验..." :rows="3" autosize />
    </div>
  </van-dialog>

  <!-- ==================== 结算流程 ==================== -->
  <van-popup v-model:show="showCheckout" position="right" :style="{width:'100%',height:'100%'}">
    <van-nav-bar title="确认订单" left-arrow @click-left="showCheckout=false" />
    <div style="padding:12px">
      <!-- 收货地址 -->
      <div class="card" style="padding:14px;margin-bottom:10px" @click="showAddrPicker=true" class="tap">
        <div v-if="checkoutAddr" style="display:flex;align-items:center;gap:8px">
          <van-icon name="location-o" size="20" color="var(--primary)" />
          <div style="flex:1">
            <div style="font-weight:600">{{checkoutAddr.name}} <span style="font-weight:400;color:var(--text3)">{{checkoutAddr.phone}}</span></div>
            <div style="font-size:13px;color:var(--text2);margin-top:2px">{{checkoutAddr.province}}{{checkoutAddr.city}}{{checkoutAddr.district}}{{checkoutAddr.detail}}</div>
          </div>
          <van-icon name="arrow" color="var(--text3)" />
        </div>
        <div v-else style="display:flex;align-items:center;gap:8px;color:var(--text3)">
          <van-icon name="location-o" size="20" />
          <span>请选择收货地址</span>
          <van-icon name="arrow" style="margin-left:auto" />
        </div>
      </div>
      <!-- 商品列表 -->
      <div v-for="(ci,idx) in checkoutItems" :key="idx" class="card" style="padding:12px;margin-bottom:8px;display:flex;gap:10px">
        <img :src="ci.image" style="width:64px;height:64px;border-radius:8px;object-fit:cover;flex-shrink:0;background:var(--bg)" />
        <div style="flex:1;min-width:0">
          <div class="text-clamp2" style="font-size:14px;font-weight:500">{{ci.name}}</div>
          <div style="font-size:12px;color:var(--text3);margin:4px 0">{{ci.sku_info||''}}</div>
          <div style="display:flex;justify-content:space-between;align-items:center">
            <span class="price" style="font-size:15px">¥{{(+ci.price).toFixed(2)}}</span>
            <span style="color:var(--text3);font-size:13px">×{{ci.quantity}}</span>
          </div>
        </div>
      </div>
      <!-- 备注 -->
      <van-field v-model="checkoutRemark" placeholder="订单备注（选填）" clearable style="margin-bottom:10px" />
    </div>
    <div class="float-bar" style="position:fixed;bottom:0">
      <div style="font-size:13px;color:var(--text3)">合计 <span class="price" style="font-size:20px">¥{{checkoutTotal.toFixed(2)}}</span></div>
      <van-button type="primary" round style="height:40px;padding:0 28px;font-weight:600" :loading="checkoutLoading" @click="submitOrder" class="tap">提交订单</van-button>
    </div>
  </van-popup>

  <!-- ==================== 结算选地址 ==================== -->
  <van-popup v-model:show="showAddrPicker" position="bottom" :style="{maxHeight:'60%'}">
    <van-picker title="选择收货地址" :columns="addrList.map(a=>({text:a.name+' '+a.phone+' '+a.province+a.city+a.district,value:a}))" @confirm="selectCheckoutAddr" @cancel="showAddrPicker=false" />
  </van-popup>

  <!-- ==================== 地址管理 ==================== -->
  <van-popup v-model:show="showAddress" position="right" :style="{width:'100%',height:'100%'}">
    <van-nav-bar title="收货地址" left-arrow @click-left="showAddress=false"><template #right><span class="tap" style="font-size:13px;color:var(--primary)" @click="addrForm.show=true">+新增</span></template></van-nav-bar>
    <div v-if="addrList.length" style="padding:12px">
      <div v-for="a in addrList" :key="a.id" class="card" style="padding:14px;margin-bottom:8px">
        <div style="display:flex;justify-content:space-between;align-items:center">
          <span style="font-weight:600">{{a.name}}</span>
          <span style="color:var(--text3)">{{a.phone}}</span>
        </div>
        <div style="font-size:13px;color:var(--text2);margin-top:4px">{{a.province}}{{a.city}}{{a.district}}{{a.detail}}</div>
        <div style="display:flex;justify-content:flex-end;gap:12px;margin-top:8px;font-size:12px">
          <span class="tap" style="color:var(--primary)" @click="setDefaultAddr(a)">{{a.is_default?'✓ 默认地址':'设为默认'}}</span>
          <span class="tap" style="color:var(--danger)" @click="delAddr(a.id)">删除</span>
        </div>
      </div>
    </div>
    <div v-else class="empty"><div class="icon">📍</div><div class="txt">暂无收货地址</div><van-button round size="small" type="primary" @click="addrForm.show=true" class="tap">添加地址</van-button></div>
    <!-- 新增地址表单 -->
    <van-dialog v-model:show="addrForm.show" title="新增地址" show-cancel-button @confirm="saveAddr">
      <van-field v-model="addrForm.name" label="收货人" placeholder="姓名" style="margin:4px 0" />
      <van-field v-model="addrForm.phone" label="手机号" placeholder="手机号" />
      <van-field v-model="addrForm.province" label="省份" placeholder="省/市" />
      <van-field v-model="addrForm.city" label="城市" placeholder="城市" />
      <van-field v-model="addrForm.district" label="区县" placeholder="区/县" />
      <van-field v-model="addrForm.detail" label="详细地址" placeholder="街道/门牌号" />
    </van-dialog>
  </van-popup>

  <!-- ==================== 搜索 ==================== -->
  <van-popup v-model:show="showSearch" position="top" :style="{height:'100%'}">
    <van-search v-model="kw" shape="round" placeholder="搜索商品" autofocus @search="doSearch" @cancel="showSearch=false" />
    <div v-if="searchRes.length" class="goods-grid" style="margin-top:8px">
      <div v-for="p in searchRes" :key="p.id" class="goods-card" @click="showProduct(p.id)"><img :src="p.image" /><div class="info"><div class="name text-clamp2">{{p.name}}</div><div class="price">¥{{(+p.price).toFixed(2)}}</div></div></div>
    </div>
    <div v-else-if="kw" class="empty"><div class="icon">🔍</div><div class="txt">未找到相关商品</div></div>
    <div v-else style="padding:20px;text-align:center;color:var(--text3)">输入关键词搜索商品</div>
  </van-popup>

  <!-- ==================== 未登录遮罩 ==================== -->
  <div v-if="!token && cp!=='login' && cp!=='register'" style="position:fixed;top:0;left:0;right:0;bottom:50px;background:rgba(255,255,255,.96);z-index:90;display:flex;flex-direction:column;align-items:center;justify-content:center">
    <div style="font-size:56px;color:var(--text4);margin-bottom:16px">🛍️</div>
    <div style="font-size:16px;color:var(--text2);margin-bottom:24px">登录后享受完整功能</div>
    <van-button type="primary" round style="height:44px;padding:0 32px;font-weight:600" @click="cp='login'" class="tap">去登录</van-button>
  </div>

  <!-- ==================== 底部导航 ==================== -->
  <div class="m-tabbar">
    <div v-for="(tb,i) in tabbarList" :key="i" class="tab-item" :class="{active:curTab===i}" @click="switchTab(tb.page,i)" class="tap">
      <div class="ti-icon">{{tb.icon}}</div>
      <div class="ti-label">{{tb.label}}</div>
    </div>
  </div>
</div>

<script src="/lib/vue.global.prod.js"></script>
<script src="/lib/vant.min.js"></script>
<script src="/lib/axios.min.js"></script>

<script>




const{createApp,ref,reactive,computed,onMounted,watch}=Vue;
const api=axios.create({baseURL:'/api',timeout:15000});
api.interceptors.response.use(r=>r.data,e=>({code:500,message:e.message}));
const app=createApp({setup(){
  const cp=ref('home'),pgBack=ref('home'),token=ref(localStorage.getItem('token')||'');
  const kw=ref(''),searchRes=ref([]),showSearch=ref(false);
  const banners=ref([]),cats=ref([]),activeCat=ref(0),homeLoading=ref(true);
  const newProds=ref([]),hotProds=ref([]),bestProds=ref([]);
  const catIdx=ref(0),catProds=ref([]),subCats=ref([]);
  const cart=ref([]),usr=ref({}),prod=ref({}),prodImgs=ref([]),specSel=reactive({});
  const prodQty=ref(1),curPrice=ref(0),curOriginalPrice=ref(0),curStock=ref(0),curSkuId=ref(null);
  const reviews=ref([]);
  const prodLoading=ref(false),orderLoading=ref(false),loginSubmitting=ref(false),regSubmitting=ref(false),addCartLoading=ref(false),buyNowLoading=ref(false);
const showReviewDlg=ref(false),reviewOrder=ref(null),reviewRating=ref(5),reviewContent=ref('');
  const showCheckout=ref(false),checkoutItems=ref([]),checkoutAddr=ref(null),checkoutTotal=ref(0),checkoutRemark=ref(''),checkoutLoading=ref(false),showAddrPicker=ref(false);
  const showOrders=ref(false),orders=ref([]),orderTab=ref(''),showAddress=ref(false),addrList=ref([]);
  const addrForm=reactive({show:false,name:'',phone:'',province:'',city:'',district:'',detail:''});
  async function loadAddr(){if(!token.value)return;try{const r=await api.get('/address/list',{headers:{Authorization:'Bearer '+token.value}});if(r.code===0)addrList.value=r.data||[]}catch(e){vant.showToast('网络错误')}}
  async function saveAddr(){if(!addrForm.name||!addrForm.phone)return vant.showToast('请填写完整信息');try{const r=await api.post('/address/save',{...addrForm},{headers:{Authorization:'Bearer '+token.value}});if(r.code===0){vant.showToast('保存成功');addrForm.show=false;loadAddr()}else vant.showToast(r.message)}catch(e){vant.showToast('网络错误')}}
  async function setDefaultAddr(a){try{const r=await api.post('/address/'+a.id+'/default',{},{headers:{Authorization:'Bearer '+token.value}});if(r.code===0){vant.showToast('已设为默认');loadAddr()}}catch(e){vant.showToast('网络错误')}}
  async function delAddr(id){try{const r=await api.delete('/address/'+id,{headers:{Authorization:'Bearer '+token.value}});if(r.code===0){vant.showToast('已删除');loadAddr()}}catch(e){vant.showToast('网络错误')}}
  const lgPhone=ref('13800001111'),lgPwd=ref('test123456'),rgPhone=ref(''),rgPwd=ref(''),rgName=ref('');
  const lgEmail=ref(''),lgSmsPhone=ref(''),lgSmsCode=ref(''),lgCaptcha=ref(''),captchaId=ref(''),captchaSvg=ref(''),smsCountdown=ref(0);
  const loginType=ref('phone');
  const loginTypes=[{key:'phone',label:'手机登录'},{key:'email',label:'邮箱登录'},{key:'sms',label:'短信登录'}];
  // 底部导航
  const curTab=ref(0);
  const tabbarList=[
    {icon:'🏠',label:'首页',page:'home'},
    {icon:'📂',label:'分类',page:'category'},
    {icon:'🛒',label:'购物车',page:'cart'},
    {icon:'👤',label:'我的',page:'user'}
  ];
  // 活动卡片
  const activities=[
    {title:'新人专享',desc:'首单立减10元'},
    {title:'限时特惠',desc:'全场满199减30'},
    {title:'积分兑换',desc:'好物0元兑'}
  ];
  // 功能入口
  const entries=[
    {icon:'⚡',label:'限时秒杀'},{icon:'🤝',label:'拼团'},{icon:'📄',label:'领券中心'},
    {icon:'⭐',label:'积分商城'},{icon:'✅',label:'每日签到'}
  ];
  // 资产数据
  const assets=computed(()=>[
    {label:'余额',val:'¥'+(+usr.value.balance||0).toFixed(0)},
    {label:'积分',val:'0'},{label:'优惠券',val:'0'},{label:'佣金',val:'¥0'}
  ]);
  const cartSelected=computed(()=>cart.value.filter(c=>c.selected));
  const cartTotal=computed(()=>cartSelected.value.reduce((s,c)=>s+(+c.price)*(c.quantity||1),0));
  const cartAll=ref(true);
  const msgCount=ref(0);

  function stText(s){return {pending_pay:'待付款',pending_ship:'待发货',shipped:'已发货',received:'待评价',completed:'已完成',closed:'已关闭',refunding:'退款中'}[s]||s}
  function calcCart(){cartAll.value=cart.value.every(c=>c.selected)}
  function toggleCartAll(){cart.value.forEach(c=>c.selected=cartAll.value)}
  function switchTab(p,i){curTab.value=i;cp.value=p;if(p==='cart'&&token.value)loadCart();if(p==='user'&&token.value){loadUser();loadOrdCnt()}}

  async function loadBanners(){try{const r=await api.get('/banner/list');if(r.code===0)banners.value=r.data||[]}catch(e){vant.showToast('网络错误')}}
  async function loadCats(){try{const r=await api.get('/category/list');if(r.code===0&&r.data.length){cats.value=r.data;activeCat.value=r.data[0].id}}catch(e){vant.showToast('网络错误')}}
  async function loadHomeData(){homeLoading.value=true;try{const[r1,r2,r3]=await Promise.all([api.get('/product/list',{params:{pageSize:6,sort:'new',order:'desc'}}),api.get('/product/list',{params:{pageSize:6,sort:'sales',order:'desc'}}),api.get('/product/list',{params:{pageSize:6,sort:'price',order:'desc'}})]);if(r1.code===0)newProds.value=r1.data.list||[];if(r2.code===0)hotProds.value=r2.data.list||[];if(r3.code===0)bestProds.value=r3.data.list||[]}catch(e){vant.showToast('网络错误')}homeLoading.value=false}
  async function loadHomeCat(id){try{const r=await api.get('/product/list',{params:{category_id:id,pageSize:6}});if(r.code===0)newProds.value=r.data.list||[]}catch(e){vant.showToast('网络错误')}}
  async function loadCatProds(id){if(!id&&cats.value.length)id=cats.value[0].id;catIdx.value=id;try{const r=await api.get('/product/list',{params:{category_id:id,pageSize:50}});if(r.code===0)catProds.value=r.data.list||[];const rc=await api.get('/category/list');if(rc.code===0)subCats.value=(rc.data||[]).filter(c=>c.pid===id)}catch(e){vant.showToast('网络错误')}}
  async function loadCart(){if(!token.value)return;try{const r=await api.get('/cart/list',{headers:{Authorization:'Bearer '+token.value}});if(r.code===0&&Array.isArray(r.data)){cart.value=r.data.map(c=>({...c,selected:true,image:c.product_image||c.sku_image||'',name:c.product_name||'',price:c.sku_price||c.price||0,sku_info:''}));calcCart()}}catch(e){vant.showToast('网络错误')}}
  async function delCartItem(item){try{const r=await api.delete('/cart/'+item.id,{headers:{Authorization:'Bearer '+token.value}});if(r.code===0){cart.value=cart.value.filter(c=>c.id!==item.id);calcCart()}}catch(e){vant.showToast('网络错误')}}
  async function batchDelCart(){const items=cartSelected.value;if(!items.length)return vant.showToast('请选择商品');try{vant.showDialog({message:'确定删除选中的'+items.length+'件商品？',showCancelButton:true}).then(async()=>{try{await Promise.all(items.map(i=>api.delete('/cart/'+i.id,{headers:{Authorization:'Bearer '+token.value}})));loadCart()}catch(e){vant.showToast('网络错误')}}).catch(()=>{})}catch(e){vant.showToast('网络错误')}}
  async function loadUser(){if(!token.value)return;try{const r=await api.get('/user/info',{headers:{Authorization:'Bearer '+token.value}});if(r.code===0)usr.value=r.data||{}}catch(e){vant.showToast('网络错误')}}
  async function loadOrdCnt(){if(!token.value)return;try{const r=await api.get('/order/list',{params:{pageSize:100},headers:{Authorization:'Bearer '+token.value}});if(r.code===0&&r.data.list)msgCount.value=r.data.list.filter(o=>o.status==='pending_ship'||o.status==='pending_pay').length}catch(e){}}
  async function loadOrds(st=''){if(!token.value)return;orderLoading.value=true;try{const r=await api.get('/order/list',{params:{status:st,pageSize:50},headers:{Authorization:'Bearer '+token.value}});if(r.code===0)orders.value=r.data.list||[]}catch(e){vant.showToast('网络错误')}orderLoading.value=false}
  function selectSpec(name,val){
    specSel[name]=val;
    // 检查所有规格是否已选
    const allSelected = prod.value.specs.every(s => specSel[s.name]);
    if (allSelected && prod.value.skus?.length) {
      const selectedSpecs = prod.value.specs.map(s => specSel[s.name]);
      const match = prod.value.skus.find(sk => {
        const skSpecs = JSON.parse(sk.specs || '[]');
        return skSpecs.length === selectedSpecs.length && skSpecs.every((v,i) => v === selectedSpecs[i]);
      });
      if (match) {
        curSkuId.value = match.id;
        curPrice.value = match.price;
        curOriginalPrice.value = match.original_price || prod.value.original_price;
        curStock.value = match.stock;
        prodQty.value = 1;
        return;
      }
    }
    // 无匹配SKU，显示商品默认价格
    curSkuId.value = null;
    curPrice.value = prod.value.price;
    curOriginalPrice.value = prod.value.original_price;
    curStock.value = prod.value.stock;
    prodQty.value = 1;
  }

  async function loadReviews(productId){
    try{const r=await api.get('/product/reviews/'+productId,{params:{pageSize:5}});
    if(r.code===0) reviews.value=r.data.list||[]}catch(e){vant.showToast('网络错误')}
  }

  async function showProduct(id){
    pgBack.value=cp.value;cp.value='product';prodLoading.value=true;prod.value={};reviews.value=[];
    try{const r=await api.get('/product/detail/'+id);
    if(r.code===0){
      prod.value=r.data;
      prodImgs.value=(r.data.images||[]).filter(Boolean).length?r.data.images:[r.data.image].filter(Boolean);
      Object.keys(specSel).forEach(k => delete specSel[k]);
      curPrice.value=r.data.price;
      curOriginalPrice.value=r.data.original_price;
      curStock.value=r.data.stock;
      curSkuId.value=null;
      prodQty.value=1;
      loadReviews(id);
    }}catch(e){vant.showToast('网络错误')}
    prodLoading.value=false;
  }

  function showReview(order){
    reviewOrder.value=order;
    reviewRating.value=5;
    reviewContent.value='';
    showReviewDlg.value=true;
  }

  async function submitReview(action,done){
    if(action==='confirm'){
      if(!reviewOrder.value) return done();
      const item=reviewOrder.value.items?.[0];
      if(!item) return done();
      try{const r=await api.post('/review/create',{
        product_id:item.product_id,
        order_id:reviewOrder.value.id,
        rating:reviewRating.value,
        content:reviewContent.value
      },{headers:{Authorization:'Bearer '+token.value}});
      if(r.code===0){
        vant.showToast('评价成功');
        reviewOrder.value=null;
        done();
      }else{
        vant.showToast(r.message);
        done(false);
      }}catch(e){vant.showToast('网络错误');done(false)}
    }else{
      done();
    }
  }

  async function goCheckout(){
    if(!cartSelected.value.length) return;
    const items=cartSelected.value.map(c=>({
      product_id:c.product_id,sku_id:c.sku_id||null,quantity:c.quantity||1,
      name:c.name,image:c.image,price:c.price,sku_info:c.sku_info||''
    }));
    checkoutItems.value=items;
    checkoutTotal.value=items.reduce((s,item)=>s+(+item.price)*(item.quantity),0);
    try{if(!addrList.value.length)await loadAddr()}catch(e){}
    const defAddr=addrList.value.find(a=>a.is_default)||addrList.value[0];
    checkoutAddr.value=defAddr||null;
    checkoutRemark.value='';checkoutLoading.value=false;
    showCheckout.value=true;
  }

  function selectCheckoutAddr({selectedOptions}){
    const addr=selectedOptions[0]?.value;
    if(addr){checkoutAddr.value=addr;showAddrPicker.value=false}
  }

  async function submitOrder(){
    if(!checkoutAddr.value) return vant.showToast('请选择收货地址');
    if(checkoutLoading.value) return;
    checkoutLoading.value=true;
    try{
      const items=checkoutItems.value.map(i=>({product_id:i.product_id,sku_id:i.sku_id||null,quantity:i.quantity,cart_id:i.id}));
      const r=await api.post('/order/create',{
        items,address_id:checkoutAddr.value.id,remark:checkoutRemark.value
      },{headers:{Authorization:'Bearer '+token.value}});
      if(r.code===0){
        const payR=await api.post('/order/'+r.data.orderNo+'/pay',{payment_method:'wechat'},{headers:{Authorization:'Bearer '+token.value}});
        if(payR.code===0){
          vant.showToast('支付成功');showCheckout.value=false;loadCart();loadOrdCnt();goOrders('pending_ship');
        }else{
          vant.showToast('下单成功，支付失败');showCheckout.value=false;loadCart();goOrders('pending_pay');
        }
      }else{vant.showToast(r.message||'下单失败')}
    }catch(e){vant.showToast('网络错误')}
    checkoutLoading.value=false;
  }
  async function addCart(){if(!token.value)return vant.showToast('请先登录');if(prod.value.specs?.length&&!curSkuId.value)return vant.showToast('请选择完整规格');if(addCartLoading.value)return;addCartLoading.value=true;try{const r=await api.post('/cart/add',{product_id:prod.value.id,sku_id:curSkuId.value,quantity:prodQty.value},{headers:{Authorization:'Bearer '+token.value}});if(r.code===0){vant.showToast('已加入购物车');loadCart()}else vant.showToast(r.message)}catch(e){vant.showToast('网络错误')}addCartLoading.value=false}
  async function buyNow(){if(!token.value)return vant.showToast('请先登录');if(prod.value.specs?.length&&!curSkuId.value)return vant.showToast('请选择完整规格');if(buyNowLoading.value)return;buyNowLoading.value=true;try{const r=await api.post('/order/create',{items:[{product_id:prod.value.id,sku_id:curSkuId.value,quantity:prodQty.value}]},{headers:{Authorization:'Bearer '+token.value}});if(r.code===0){vant.showToast('下单成功');loadCart();loadOrdCnt();goOrders('pending_pay')}else vant.showToast(r.message)}catch(e){vant.showToast('网络错误')}buyNowLoading.value=false}
  async function dupeGoCheckout(){if(!cartSelected.value.length)return;vant.showToast('结算开发中')}
  async function doCheckout(){if(!cartSelected.value.length)return;try{const items=cartSelected.value.map(c=>({product_id:c.product_id||c.id,sku_id:c.sku_id||null,quantity:c.quantity||1}));const r=await api.post('/order/create',{items},{headers:{Authorization:'Bearer '+token.value}});if(r.code===0){vant.showToast('下单成功');loadCart();loadOrdCnt();goOrders('pending_pay')}else vant.showToast(r.message)}catch(e){vant.showToast('网络错误')}}
  function goOrders(st){orderTab.value=st;loadOrds(st);showOrders.value=true}
  async function loadOrders(){await loadOrds(orderTab.value)}
  // 取消订单
  async function cancelOrder(on){try{const r=await api.post('/order/'+on+'/cancel',{close_reason:'用户取消'},{headers:{Authorization:'Bearer '+token.value}});if(r.code===0){vant.showToast('已取消');loadOrders()}else vant.showToast(r.message)}catch(e){vant.showToast('网络错误')}}
  // 确认收货
  async function confirmOrder(on){try{const r=await api.post('/order/'+on+'/confirm',{},{headers:{Authorization:'Bearer '+token.value}});if(r.code===0){vant.showToast('已确认收货');loadOrders()}else vant.showToast(r.message)}catch(e){vant.showToast('网络错误')}}
  // 去支付
  async function payOrder(on){try{const r=await api.post('/order/'+on+'/pay',{payment_method:'wechat'},{headers:{Authorization:'Bearer '+token.value}});if(r.code===0){vant.showToast('支付成功');loadOrders()}else vant.showToast(r.message)}catch(e){vant.showToast('网络错误')}}
  async function doSearch(){if(!kw.value)return;showSearch.value=false;cp.value='home';try{const r=await api.get('/product/list',{params:{keyword:kw.value}});if(r.code===0){newProds.value=r.data.list||[];homeLoading.value=false}}catch(e){vant.showToast('网络错误')}}
  async function loadCaptcha(){try{const r=await api.get('/captcha/image');if(r.code===0){captchaId.value=r.data.id;captchaSvg.value=r.data.svg}}catch(e){}}
  function sendSmsCode(){if(!lgSmsPhone.value||lgSmsPhone.value.length<11)return vant.showToast('请输入正确手机号');if(smsCountdown.value>0)return;smsCountdown.value=60;const t=setInterval(()=>{smsCountdown.value--;if(smsCountdown.value<=0){clearInterval(t);smsCountdown.value=0}},1000);api.post('/captcha/sms',{phone:lgSmsPhone.value}).then(r=>{if(r.code===0)vant.showToast(r.message.replace(':','验证码: '));else vant.showToast(r.message)}).catch(e=>{vant.showToast('网络错误')})}
  async function handleLogin(){
    if(loginSubmitting.value)return;
    loginSubmitting.value=true;
    try{
      if(lgCaptcha.value){const vr=await api.post('/captcha/verify',{id:captchaId.value,text:lgCaptcha.value});if(vr.code!==0){vant.showToast(vr.message);loadCaptcha();loginSubmitting.value=false;return}}
      let r;
      if(loginType.value==='phone'){r=await api.post('/user/login',{phone:lgPhone.value,password:lgPwd.value})}
      else if(loginType.value==='email'){r=await api.post('/user/login',{phone:lgEmail.value,password:lgPwd.value})}
      else if(loginType.value==='sms'){
        if(!lgSmsCode.value){vant.showToast('请输入短信验证码');loginSubmitting.value=false;return}
        r=await api.post('/user/sms-login',{phone:lgSmsPhone.value,sms_code:lgSmsCode.value})
      }
      if(r&&r.code===0){token.value=r.data.token;localStorage.setItem('token',r.data.token);usr.value=r.data.user||{};cp.value='home';vant.showToast('登录成功');loadCart();loadOrdCnt();loadUser()}else if(r)vant.showToast(r.message)
    }catch(e){vant.showToast('网络错误')}
    loginSubmitting.value=false;
  }  async function handleRegister(){if(!rgPhone.value)return vant.showToast('请输入手机号');if(!rgPwd.value||rgPwd.value.length<6)return vant.showToast('密码至少6位');if(regSubmitting.value)return;regSubmitting.value=true;try{const r=await api.post('/user/register',{phone:rgPhone.value,password:rgPwd.value,nickname:rgName.value||''});if(r.code===0){vant.showToast('注册成功');cp.value='login'}else vant.showToast(r.message)}catch(e){vant.showToast('网络错误')}regSubmitting.value=false}
  function logout(){token.value='';localStorage.removeItem('token');usr.value={};cp.value='home';vant.showToast('已退出')}

  // ========== 支付相关变量和函数 ==========
  const showPayment=ref(false),payMethod=ref('wechat'),payInfo=ref(null),paying=ref(false);
  async function createPayment(orderNo,method='wechat'){try{const r=await api.post('/payment/create',{order_no:orderNo,method},{headers:{Authorization:'Bearer '+token.value}});if(r.code===0){payInfo.value=r.data;showPayment.value=true}else{vant.showToast(r.message||'创建支付失败');return false}return true}catch(e){vant.showToast('网络错误');return false}}
  function switchPayMethod(m){payMethod.value=m}
  async function doMockPay(){paying.value=true;try{const r=await api.post('/payment/mock-notify',{order_no:payInfo.value?.order_no,trade_no:'MOCK'+Date.now(),payment_method:payMethod.value});if(r.code===0){vant.showToast('支付成功');showPayment.value=false;loadCart();loadOrdCnt();goOrders('pending_ship')}else{vant.showToast(r.message||'支付失败')}}catch(e){vant.showToast('网络错误')}paying.value=false}
  async function doRealPay(){if(!payInfo.value?.pay_url)return vant.showToast('暂无可用的支付链接');paying.value=true;startPaymentPoll();window.open(payInfo.value.pay_url,'_blank');setTimeout(()=>{paying.value=false},3000)}
  function startPaymentPoll(){const t=setInterval(async()=>{try{const r=await api.get('/payment/status/'+payInfo.value?.order_no);if(r.code===0&&r.data?.paid){clearInterval(t);vant.showToast('支付成功');showPayment.value=false;loadCart();loadOrdCnt();goOrders('pending_ship')}}catch(e){}},2000);setTimeout(()=>{clearInterval(t);paying.value=false},120000)}
  function closePayment(){showPayment.value=false;payInfo.value=null}

  onMounted(()=>{try{loadBanners();loadCats();loadHomeData();loadCaptcha();if(token.value){loadUser();loadCart();loadOrdCnt();loadAddr()}}catch(e){vant.showToast('网络错误')}});
  return{cp,pgBack,token,kw,searchRes,showSearch,banners,cats,activeCat,homeLoading,newProds,hotProds,bestProds,catIdx,catProds,subCats,cart,cartAll,cartSelected,cartTotal,usr,prod,prodImgs,specSel,prodQty,curPrice,curOriginalPrice,curStock,curSkuId,reviews,showReviewDlg,reviewOrder,reviewRating,reviewContent,showCheckout,checkoutItems,checkoutAddr,checkoutTotal,checkoutRemark,checkoutLoading,showAddrPicker,showOrders,orders,orderTab,showAddress,addrList,addrForm,loadAddr,saveAddr,setDefaultAddr,delAddr,selectSpec,showReview,submitReview,goCheckout,selectCheckoutAddr,submitOrder,lgPhone,lgPwd,rgPhone,rgPwd,rgName,curTab,tabbarList,activities,entries,assets,msgCount,stText,calcCart,toggleCartAll,switchTab,loadHomeCat,loadCatProds,showProduct,addCart,buyNow,goOrders,loadOrders,doSearch,handleLogin,handleRegister,logout,batchDelCart,loadCaptcha,sendSmsCode,loginType,loginTypes,lgEmail,lgSmsPhone,lgSmsCode,lgCaptcha,captchaId,captchaSvg,smsCountdown,showPayment,payMethod,payInfo,paying,payOrder,createPayment,switchPayMethod,doMockPay,doRealPay,closePayment,startPaymentPoll,prodLoading,orderLoading,loginSubmitting,regSubmitting,addCartLoading,buyNowLoading}
}});
app.use(vant).mount('#app');




</script>
</body>
</html>

