# DAIX.fun 控制面板技术文档

> 📋 **版本**：v1.0
> 📅 **日期**：2026-06-29

---

## 目录

1. [控制面板概览](#1-控制面板概览)
2. [开发者面板](#2-开发者面板)
3. [推广者面板](#3-推广者面板)
4. [用户面板](#4-用户面板)
5. [前端技术实现](#5-前端技术实现)
6. [API 接口规范](#6-api-接口规范)

---

## 1. 控制面板概览

DAIX.fun 控制面板是一个**纯前端应用**，通过 ethers.js 直接与 Polygon 链上合约交互，无需后端服务器。

### 架构

```
┌─────────────────────────────────────────────┐
│            daix.fun 前端                     │
│                                             │
│  ┌───────────┐ ┌───────────┐ ┌───────────┐ │
│  │ 开发者面板 │ │ 推广者面板 │ │  用户面板  │ │
│  └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ │
│        │              │              │       │
│        ▼              ▼              ▼       │
│  ┌──────────────────────────────────────┐   │
│  │         ethers.js 钱包连接层          │   │
│  └──────────────────┬───────────────────┘   │
│                     │                       │
│                     ▼                       │
│  ┌──────────────────────────────────────┐   │
│  │         Polygon 链上合约              │   │
│  │  DeveloperRegistry │ AppRegistry     │   │
│  │  LicenseNFT │ PromoterRegistry       │   │
│  │  LicenseQuery │ RevenueSplit         │   │
│  └──────────────────────────────────────┘   │
└─────────────────────────────────────────────┘
```

### 角色识别

前端根据用户钱包地址自动识别角色：

```javascript
async function detectRoles(address) {
  const roles = [];

  // 检查是否是开发者
  const isDev = await devRegistry.isRegistered(address);
  if (isDev) roles.push("developer");

  // 检查是否是推广者
  const isPromoter = await promoterRegistry.isPromoter(address);
  if (isPromoter) roles.push("promoter");

  // 检查是否有许可证
  const [tokenIds] = await query.getUserLicenses(address);
  if (tokenIds.length > 0) roles.push("user");

  return roles;
}
```

### 面板切换

```
未连接钱包 → 显示：浏览APP、链上统计
连接钱包后 → 根据角色显示对应面板：
  - 开发者：「👨‍💻 创作者中心」
  - 推广者：「📣 推广者中心」
  - 用户：「🎫 我的许可证」
  - 未注册：「成为创作者」「成为推广者」注册入口
```

---

## 2. 开发者面板

### 2.1 注册入口

**条件**：用户未注册为开发者

**UI 元素：**
```html
<section id="developer-register">
  <h2>👨‍💻 成为创作者</h2>
  <p>注册为开发者，在 DAIX.fun 发布你的 APP</p>
  <input type="text" id="dev-name" placeholder="开发者名称（如 CONVNET）">
  <input type="text" id="dev-metadata" placeholder="元数据 URI（可选）">
  <button onclick="registerDeveloper()">注册开发者</button>
</section>
```

**注册函数：**
```javascript
async function registerDeveloper() {
  const name = document.getElementById("dev-name").value.trim();
  const metadata = document.getElementById("dev-metadata").value.trim() || "";

  if (!name) { showToast("请输入开发者名称"); return; }

  try {
    showModal("注册中...", "正在提交交易，请在钱包中确认");

    const tx = await contracts.DeveloperRegistry.register(name, metadata);
    updateModal("等待确认...", "TX: " + tx.hash);

    await tx.wait();
    updateModal("✅ 注册成功！", "你现在可以发布 APP 了");

    await detectAndShowPanels();
  } catch (err) {
    updateModal("❌ 注册失败", err.message.slice(0, 100));
  }
}
```

### 2.2 开发者控制面板

**条件**：用户已注册为开发者

**UI 元素：**
```html
<section id="developer-panel">
  <h2>👨‍💻 创作者中心</h2>

  <!-- 个人信息 -->
  <div class="dev-info">
    <div>开发者名称：<span id="dev-display-name"></span></div>
    <div>钱包地址：<span id="dev-address"></span></div>
    <div>注册时间：<span id="dev-registered-at"></span></div>
  </div>

  <!-- 我的 APP 列表 -->
  <h3>我的 APP</h3>
  <div id="my-apps-list"></div>

  <!-- 发布新 APP -->
  <h3>发布新 APP</h3>
  <div class="form-grid">
    <input type="text" id="app-name" placeholder="APP 名称">
    <input type="text" id="app-package" placeholder="包名（com.example.app）">
    <textarea id="app-desc" placeholder="APP 描述"></textarea>
    <input type="text" id="app-apk" placeholder="APK 下载地址">
    <input type="text" id="app-icon" placeholder="图标地址">
    <input type="number" id="app-price" placeholder="价格（USDC）">
    <input type="number" id="app-trial" placeholder="试用天数" value="7">
    <input type="text" id="app-version" placeholder="版本号" value="v1.0.0">
    <button onclick="registerApp()">发布 APP</button>
  </div>
</section>
```

**核心函数：**
```javascript
// 加载我的 APP 列表
async function loadMyApps() {
  const appIds = await contracts.AppRegistry.getDeveloperApps(account);
  const list = document.getElementById("my-apps-list");
  list.innerHTML = "";

  for (const id of appIds) {
    const app = await readOnlyContracts.AppRegistry.getApp(Number(id));
    const card = document.createElement("div");
    card.className = "app-card";
    card.innerHTML = `
      <div class="app-icon">${APP_ICONS[app.packageName] || "📦"}</div>
      <div class="app-info">
        <div class="app-name">${app.name}</div>
        <div class="app-pkg">${app.packageName}</div>
        <div class="app-price">${ethers.formatUnits(app.price, 6)} USDC</div>
        <div class="app-status">${app.active ? "✅ 上架中" : "❌ 已下架"}</div>
      </div>
      <div class="app-actions">
        <button onclick="updateAppPrice(${id})">改价</button>
        <button onclick="deactivateApp(${id})">下架</button>
      </div>
    `;
    list.appendChild(card);
  }
}

// 发布新 APP
async function registerApp() {
  const name = document.getElementById("app-name").value.trim();
  const pkg = document.getElementById("app-package").value.trim();
  const desc = document.getElementById("app-desc").value.trim();
  const apk = document.getElementById("app-apk").value.trim();
  const icon = document.getElementById("app-icon").value.trim();
  const price = document.getElementById("app-price").value;
  const trial = document.getElementById("app-trial").value;
  const version = document.getElementById("app-version").value.trim();

  if (!name || !pkg || !desc || !apk || !price) {
    showToast("请填写完整信息");
    return;
  }

  try {
    showModal("发布中...", "正在提交交易");
    const priceWei = ethers.parseUnits(price, 6);
    const tx = await contracts.AppRegistry.registerApp(
      name, pkg, desc, apk, icon, priceWei, trial, version
    );
    await tx.wait();
    updateModal("✅ 发布成功！", "APP 已上架");
    await loadMyApps();
  } catch (err) {
    updateModal("❌ 发布失败", err.message.slice(0, 100));
  }
}
```

### 2.3 收益查看

```javascript
async function loadDevRevenue() {
  const balance = await readOnlyContracts.USDC.balanceOf(account);
  document.getElementById("dev-revenue").textContent =
    ethers.formatUnits(balance, 6) + " USDC";
}
```

---

## 3. 推广者面板

### 3.1 注册入口

**条件**：用户未注册为推广者

**UI 元素：**
```html
<section id="promoter-register">
  <h2>📣 成为推广者</h2>
  <p>注册为推广者，分享推广链接赚取佣金</p>
  <input type="text" id="promo-name" placeholder="推广者名称">
  <input type="text" id="promo-code" placeholder="推广码（全局唯一）">
  <button onclick="registerPromoter()">注册推广者</button>
</section>
```

**注册函数：**
```javascript
async function registerPromoter() {
  const name = document.getElementById("promo-name").value.trim();
  const code = document.getElementById("promo-code").value.trim();

  if (!name || !code) {
    showToast("请填写完整信息");
    return;
  }

  try {
    showModal("注册中...", "正在提交交易");
    const tx = await contracts.PromoterRegistry.register(name, code);
    await tx.wait();
    updateModal("✅ 注册成功！", "可立即生成推广链接");
    await detectAndShowPanels();
  } catch (err) {
    updateModal("❌ 注册失败", err.message.slice(0, 100));
  }
}
```

### 3.2 推广者控制面板

**条件**：用户已注册为推广者

**UI 元素：**
```html
<section id="promoter-panel">
  <h2>📣 推广者中心</h2>

  <!-- 个人信息 -->
  <div class="promo-info">
    <div>推广者名称：<span id="promo-display-name"></span></div>
    <div>推广码：<span id="promo-display-code"></span></div>
    <div>钱包地址：<span id="promo-address"></span></div>
  </div>

  <!-- 推广链接 -->
  <h3>你的推广链接</h3>
  <div class="referral-link">
    <code id="referral-url"></code>
    <button onclick="copyReferralLink()">📋 复制</button>
  </div>

  <!-- 收益统计 -->
  <h3>收益统计</h3>
  <div class="revenue-stats">
    <div>总收益：<span id="promo-revenue">-</span> USDC</div>
    <div>当前余额：<span id="promo-balance">-</span> USDC</div>
  </div>
</section>
```

**核心函数：**
```javascript
// 加载推广者信息
async function loadPromoterInfo() {
  const [name, code, registeredAt, active] =
    await contracts.PromoterRegistry.getPromoter(account);

  document.getElementById("promo-display-name").textContent = name;
  document.getElementById("promo-display-code").textContent = code;
  document.getElementById("promo-address").textContent = account;
  document.getElementById("referral-url").textContent =
    `https://daix.fun/?agent=${account}`;

  const balance = await readOnlyContracts.USDC.balanceOf(account);
  document.getElementById("promo-balance").textContent =
    ethers.formatUnits(balance, 6);
}

// 复制推广链接
function copyReferralLink() {
  const url = `https://daix.fun/?agent=${account}`;
  navigator.clipboard.writeText(url);
  showToast("推广链接已复制！");
}
```

---

## 4. 用户面板

### 4.1 我的许可证

**条件**：用户拥有至少一个 License NFT

**UI 元素：**
```html
<section id="my-licenses-section" style="display:none">
  <h2>🎫 我的许可证</h2>
  <div id="my-licenses-list"></div>
</section>
```

**核心函数：**
```javascript
async function loadMyLicenses() {
  const balance = await contracts.LicenseNFT.balanceOf(account);
  const count = Number(balance);
  const licenses = [];

  for (let i = 0; i < count; i++) {
    const tokenId = await contracts.LicenseNFT.tokenOfOwnerByIndex(account, i);
    const lic = await contracts.LicenseNFT.licenses(Number(tokenId));
    const app = await readOnlyContracts.AppRegistry.getApp(Number(lic.appId));

    licenses.push({
      tokenId: Number(tokenId),
      appId: Number(lic.appId),
      appName: app.name,
      appIcon: APP_ICONS[app.packageName] || "📦",
      code: "NFT-" + Number(tokenId).toString(16).toUpperCase().padStart(6, "0"),
      purchasedAt: new Date(Number(lic.mintedAt) * 1000).toLocaleString(),
      activated: lic.activated,
    });
  }

  renderLicenses(licenses);
}

function renderLicenses(licenses) {
  const section = document.getElementById("my-licenses-section");
  const list = document.getElementById("my-licenses-list");

  if (licenses.length === 0) {
    section.style.display = "none";
    return;
  }

  section.style.display = "block";
  list.innerHTML = licenses.map(l => `
    <div class="license-card">
      <div class="license-icon">${l.appIcon}</div>
      <div class="license-info">
        <div class="title">${l.appName} · #${l.tokenId}</div>
        <div class="code">激活码：${l.code}</div>
        <div class="date">购买时间：${l.purchasedAt}</div>
        <div class="status">${l.activated ? "✅ 已激活" : "⏳ 未激活"}</div>
      </div>
      <div class="license-actions">
        <button onclick="copyCode('${l.code}')">📋 复制激活码</button>
        <button onclick="downloadApk(${l.appId})">📥 下载 APK</button>
      </div>
    </div>
  `).join("");
}
```

---

## 5. 前端技术实现

### 5.1 钱包连接

```javascript
async function connectWallet() {
  if (!window.ethereum) {
    showToast("请安装 MetaMask");
    return;
  }

  provider = new ethers.BrowserProvider(window.ethereum);
  await provider.send("eth_requestAccounts", []);
  signer = await provider.getSigner();
  account = await signer.getAddress();

  // 初始化合约实例
  contracts = {
    DeveloperRegistry: new ethers.Contract(DEVELOPER_REGISTRY, DEV_ABI, signer),
    AppRegistry: new ethers.Contract(APP_REGISTRY, APP_ABI, signer),
    LicenseNFT: new ethers.Contract(LICENSE_NFT, NFT_ABI, signer),
    PromoterRegistry: new ethers.Contract(PROMOTER_REGISTRY, PROMO_ABI, signer),
  };

  // 只读合约（使用 provider，无需签名）
  readOnlyContracts = {
    AppRegistry: new ethers.Contract(APP_REGISTRY, APP_ABI, provider),
    LicenseNFT: new ethers.Contract(LICENSE_NFT, NFT_ABI, provider),
    USDC: new ethers.Contract(USDC_ADDR, USDC_ABI, provider),
  };

  await detectAndShowPanels();
}
```

### 5.2 角色检测与面板显示

```javascript
async function detectAndShowPanels() {
  // 隐藏所有面板
  document.getElementById("developer-register").style.display = "none";
  document.getElementById("developer-panel").style.display = "none";
  document.getElementById("promoter-register").style.display = "none";
  document.getElementById("promoter-panel").style.display = "none";
  document.getElementById("my-licenses-section").style.display = "none";

  // 检测角色
  const isDev = await contracts.DeveloperRegistry.isRegistered(account);
  const isPromoter = await contracts.PromoterRegistry.isPromoter(account);
  const [tokenIds] = await readOnlyContracts.LicenseQuery
    ? await readOnlyContracts.LicenseQuery.getUserLicenses(account)
    : [[], []];

  // 显示对应面板
  if (isDev) {
    document.getElementById("developer-panel").style.display = "block";
    await loadMyApps();
    await loadDevRevenue();
  } else {
    document.getElementById("developer-register").style.display = "block";
  }

  if (isPromoter) {
    document.getElementById("promoter-panel").style.display = "block";
    await loadPromoterInfo();
  } else {
    document.getElementById("promoter-register").style.display = "block";
  }

  if (tokenIds.length > 0) {
    await loadMyLicenses();
  }
}
```

### 5.3 i18n 国际化

前端支持中英文切换：

```javascript
const i18n = {
  zh: {
    "nav.home": "首页",
    "nav.apps": "APP",
    "nav.promoters": "推广者",
    "dev.title": "创作者中心",
    "dev.register": "成为创作者",
    "promo.title": "推广者中心",
    "promo.register": "成为推广者",
    // ...
  },
  en: {
    "nav.home": "Home",
    "nav.apps": "Apps",
    "nav.promoters": "Promoters",
    "dev.title": "Creator Center",
    "dev.register": "Become a Creator",
    "promo.title": "Promoter Center",
    "promo.register": "Become a Promoter",
    // ...
  }
};
```

---

## 6. API 接口规范

### 6.1 链上查询接口

所有数据直接从链上读取，无需后端 API。

| 查询 | 合约 | 方法 | 说明 |
|------|------|------|------|
| 开发者信息 | DeveloperRegistry | getDeveloper(address) | 获取开发者详情 |
| APP 信息 | AppRegistry | getApp(uint256) | 获取 APP 详情 |
| 我的 APP | AppRegistry | getDeveloperApps(address) | 获取开发者的所有 APP |
| 已上架 APP | AppRegistry | getActiveApps() | 获取所有上架 APP |
| 许可证验证 | LicenseQuery | verifyLicense(address,uint256) | 验证用户是否有许可证 |
| 用户许可证 | LicenseQuery | getUserLicenses(address) | 获取用户所有许可证 |
| 推广者信息 | PromoterRegistry | getPromoter(address) | 获取推广者详情 |
| 推广码查询 | PromoterRegistry | getByCode(string) | 通过推广码查地址 |
| USDC 余额 | USDC | balanceOf(address) | 查询 USDC 余额 |

### 6.2 链上写入接口

| 操作 | 合约 | 方法 | 前置条件 |
|------|------|------|----------|
| 注册开发者 | DeveloperRegistry | register(string,string) | 钱包已连接 |
| 发布 APP | AppRegistry | registerApp(...) | 已注册开发者 |
| 更新 APP | AppRegistry | updateApp(...) | 是 APP 所有者 |
| 下架 APP | AppRegistry | deactivateApp(uint256) | 是 APP 所有者 |
| 注册推广者 | PromoterRegistry | register(string,string) | 钱包已连接 |
| 购买许可证 | LicenseNFT | mintLicense(uint256,address) | USDC 余额足够 |
| 批量购买 | LicenseNFT | batchMint(uint256,uint256) | USDC 余额足够 |
| 激活许可证 | LicenseNFT | activateLicense(uint256,string) | 是 NFT 所有者 |
| 转让许可证 | LicenseNFT | transferFrom(address,address,uint256) | 是 NFT 所有者 |

### 6.3 文件上传接口

| 接口 | 方法 | 说明 |
|------|------|------|
| `/api/upload-public` | POST | 上传公开文件（APK、图标等） |

**请求：**
```
POST http://43.166.240.93:3030/api/upload-public
Content-Type: multipart/form-data

Body:
  file: (文件，≤10MB)
```

**响应：**
```json
{
  "success": true,
  "url": "/apk/storeguard-v1.apk"
}
```

---

*最后更新：2026-06-29 by CONVNET Team*
