Skip to content
Guides4 min read

Potato 中的自定义 HTML 模板

使用 HTML 模板、CSS 样式和 JavaScript 交互创建自定义标注界面。

Potato Team

虽然 Potato 提供了内置的标注类型,但自定义 HTML 模板可以让你为独特的标注需求创建专用界面。本指南涵盖模板创建、样式设置和 JavaScript 集成。

何时使用自定义模板

自定义模板适用于:

  • 内置类型不支持的复杂布局
  • 特定领域的界面(医学、法律等)
  • 交互式可视化
  • 自定义输入组件
  • 品牌化标注体验

基本模板结构

模板配置

yaml
annotation_task_name: "Custom Template Annotation"
 
html_layout: "templates/my_template.html"

模板文件

html
<!-- templates/my_template.html -->
<div class="custom-annotation-container">
  <!-- Display the text -->
  <div class="content-display">
    <div class="text-content">
      {{text}}
    </div>
  </div>
 
  <!-- Custom annotation interface -->
  <div class="annotation-area">
    <!-- Potato will inject annotation schemes here -->
    {{annotation_schemes}}
  </div>
</div>

模板变量

可用变量

html
<!-- Item data -->
{{id}}           <!-- Item ID -->
{{text}}         <!-- Main text content -->
{{image_url}}    <!-- Image URL if present -->
{{audio_url}}    <!-- Audio URL if present -->
 
<!-- Metadata -->
{{metadata.field_name}}  <!-- Any metadata field -->

自定义模板样式

内联 CSS

html
<style>
  .custom-annotation-container {
    max-width: 800px;
    margin: 0 auto;
    padding: 20px;
    font-family: -apple-system, BlinkMacSystemFont, sans-serif;
  }
 
  .task-header {
    background: linear-gradient(135deg, #6E56CF, #9F7AEA);
    color: white;
    padding: 15px 20px;
    border-radius: 8px;
    margin-bottom: 20px;
  }
 
  .content-display {
    background: #F8FAFC;
    border: 1px solid #E2E8F0;
    border-radius: 8px;
    padding: 20px;
    margin-bottom: 20px;
  }
 
  .text-content {
    font-size: 16px;
    line-height: 1.6;
    color: #1E293B;
  }
 
  .metadata {
    margin-top: 15px;
    padding-top: 15px;
    border-top: 1px solid #E2E8F0;
    font-size: 14px;
    color: #64748B;
  }
 
  .annotation-area {
    background: white;
    border: 1px solid #E2E8F0;
    border-radius: 8px;
    padding: 20px;
  }
 
  /* Custom highlight styles */
  .highlight-positive {
    background-color: #D1FAE5;
    padding: 2px 4px;
    border-radius: 3px;
  }
 
  .highlight-negative {
    background-color: #FEE2E2;
    padding: 2px 4px;
    border-radius: 3px;
  }
</style>

JavaScript 集成

基本交互

html
<script>
  document.addEventListener('DOMContentLoaded', function() {
    // Get the text content element
    const textContent = document.querySelector('.text-content');
 
    // Add click-to-highlight functionality
    textContent.addEventListener('mouseup', function() {
      const selection = window.getSelection();
      if (selection.toString().trim()) {
        highlightSelection(selection);
      }
    });
 
    function highlightSelection(selection) {
      const range = selection.getRangeAt(0);
      const span = document.createElement('span');
      span.className = 'user-highlight';
      range.surroundContents(span);
    }
  });
</script>

高级模板

并排比较

html
<div class="comparison-container">
  <style>
    .comparison-container {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 20px;
    }
 
    .comparison-item {
      border: 1px solid #E2E8F0;
      border-radius: 8px;
      padding: 15px;
    }
 
    .comparison-item.selected {
      border-color: #6E56CF;
      box-shadow: 0 0 0 2px rgba(110, 86, 207, 0.2);
    }
 
    .item-label {
      font-weight: 600;
      margin-bottom: 10px;
      color: #6E56CF;
    }
  </style>
 
  <div class="comparison-item" data-option="A" onclick="selectOption('A')">
    <div class="item-label">Option A</div>
    <div class="item-content">{{option_a}}</div>
  </div>
 
  <div class="comparison-item" data-option="B" onclick="selectOption('B')">
    <div class="item-label">Option B</div>
    <div class="item-content">{{option_b}}</div>
  </div>
 
  <script>
    function selectOption(option) {
      // Update visual selection
      document.querySelectorAll('.comparison-item').forEach(el => {
        el.classList.remove('selected');
      });
      document.querySelector(`[data-option="${option}"]`).classList.add('selected');
    }
  </script>
</div>

交互式高亮

html
<div class="highlight-annotation">
  <style>
    .highlight-toolbar {
      display: flex;
      gap: 10px;
      margin-bottom: 15px;
    }
 
    .highlight-btn {
      padding: 8px 16px;
      border: 1px solid #E2E8F0;
      border-radius: 6px;
      cursor: pointer;
      background: white;
    }
 
    .highlight-btn.active {
      background: #6E56CF;
      color: white;
      border-color: #6E56CF;
    }
 
    .highlightable-text {
      line-height: 1.8;
    }
 
    .highlight-positive { background: #D1FAE5; }
    .highlight-negative { background: #FEE2E2; }
    .highlight-neutral { background: #FEF3C7; }
  </style>
 
  <div class="highlight-toolbar">
    <button class="highlight-btn" data-color="positive" onclick="setHighlightMode('positive')">
      Positive
    </button>
    <button class="highlight-btn" data-color="negative" onclick="setHighlightMode('negative')">
      Negative
    </button>
    <button class="highlight-btn" data-color="neutral" onclick="setHighlightMode('neutral')">
      Neutral
    </button>
    <button class="highlight-btn" onclick="clearHighlights()">
      Clear All
    </button>
  </div>
 
  <div class="highlightable-text" id="textContent">
    {{text}}
  </div>
 
  <script>
    let currentMode = null;
    let highlights = [];
 
    function setHighlightMode(mode) {
      currentMode = mode;
      document.querySelectorAll('.highlight-btn').forEach(btn => {
        btn.classList.toggle('active', btn.dataset.color === mode);
      });
    }
 
    document.getElementById('textContent').addEventListener('mouseup', function() {
      if (!currentMode) return;
 
      const selection = window.getSelection();
      if (selection.toString().trim()) {
        const range = selection.getRangeAt(0);
        const span = document.createElement('span');
        span.className = `highlight-${currentMode}`;
 
        // Store highlight data
        highlights.push({
          text: selection.toString(),
          type: currentMode,
          start: range.startOffset,
          end: range.endOffset
        });
 
        range.surroundContents(span);
        selection.removeAllRanges();
      }
    });
 
    function clearHighlights() {
      highlights = [];
      document.getElementById('textContent').innerHTML = '{{text}}';
    }
  </script>
</div>

标签页界面

html
<div class="tabbed-interface">
  <style>
    .tab-buttons {
      display: flex;
      border-bottom: 2px solid #E2E8F0;
    }
 
    .tab-btn {
      padding: 12px 24px;
      border: none;
      background: none;
      cursor: pointer;
      font-size: 14px;
      font-weight: 500;
      color: #64748B;
      border-bottom: 2px solid transparent;
      margin-bottom: -2px;
    }
 
    .tab-btn.active {
      color: #6E56CF;
      border-bottom-color: #6E56CF;
    }
 
    .tab-content {
      display: none;
      padding: 20px 0;
    }
 
    .tab-content.active {
      display: block;
    }
  </style>
 
  <div class="tab-buttons">
    <button class="tab-btn active" onclick="showTab('text')">Text</button>
    <button class="tab-btn" onclick="showTab('metadata')">Metadata</button>
    <button class="tab-btn" onclick="showTab('context')">Context</button>
  </div>
 
  <div class="tab-content active" id="tab-text">
    <div class="text-content">{{text}}</div>
  </div>
 
  <div class="tab-content" id="tab-metadata">
    <pre>{{metadata | json}}</pre>
  </div>
 
  <div class="tab-content" id="tab-context">
    <p><strong>Source:</strong> {{metadata.source}}</p>
    <p><strong>Date:</strong> {{metadata.date}}</p>
    <p><strong>Author:</strong> {{metadata.author}}</p>
  </div>
 
  <script>
    function showTab(tabName) {
      document.querySelectorAll('.tab-btn').forEach(btn => {
        btn.classList.remove('active');
      });
      document.querySelectorAll('.tab-content').forEach(content => {
        content.classList.remove('active');
      });
 
      event.target.classList.add('active');
      document.getElementById(`tab-${tabName}`).classList.add('active');
    }
  </script>
</div>

最佳实践

  1. 保持模板简洁:复杂逻辑应放在配置中,而非模板中
  2. 测试响应式:确保模板在不同屏幕尺寸下正常工作
  3. 无障碍设计:使用适当的对比度、标签和键盘导航
  4. 注意性能:减少 JavaScript 使用并优化图片
  5. 提供回退:为缺失数据提供回退方案
  6. 编写文档:为模板添加注释以便维护

完整文档请参阅 /docs/core-concepts/annotation-schemes