Skip to content
Guides4 min read

Potato 中的自定义 HTML 模板

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

Potato Team·

Potato 中的自定义 HTML 模板

虽然 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-types