需求:机构信息智能粘贴组件

222 阅读10分钟

需求: 想要实现一个快速粘贴并识别地址的功能,信息的识别通过后端来做,前端做的这个示例,准确率不高,仅参考测试

  • 支持长文本识别
  • 支持图片识别
  • 点击“粘贴并识别”,能够识别出具体的信息
  • 识别出来的信息能够填写到对应的输入框中
  • 根据长文本或图片,需要识别的信息有该用户的 国家,城市,一级、二级单位等信息

效果:

image.png

image.png

代码片段一【子组件】:

<template>
  <el-card shadow="never" class="smart-paste-extractor">
    <div class="header">
      <h3 class="title">
        <el-icon><Document /></el-icon>
        智能粘贴
      </h3>
    </div>
    
    <div class="paste-area">
      <el-input
        v-model="textInput"
        type="textarea"
        :rows="4"
        :placeholder="placeholder"
        class="text-input"
      />
      
      <div class="actions">
        <input 
          type="file" 
          ref="fileInput" 
          @change="handleImageUpload" 
          accept="image/*" 
          hidden 
        />
        <el-button @click="triggerImageUpload" :loading="isProcessing">
          <el-icon><Camera /></el-icon>
          图片识别
        </el-button>
        <el-button 
          type="primary" 
          @click="extractInstitution"
          :disabled="!textInput.trim()"
        >
          <el-icon><Search /></el-icon>
          粘贴并识别
        </el-button>
      </div>
    </div>

    <div class="result-form" v-if="showForm">
      <el-divider content-position="left">识别结果</el-divider>
      <el-form :model="form" label-width="100px" class="institution-form">
        <el-form-item label="Institution">
          <el-input 
            v-model="form.institution" 
            placeholder="大学/医院/研究所等机构名称"
          />
        </el-form-item>
        <el-form-item label="Department">
          <el-input 
            v-model="form.department" 
            placeholder="部门/科室名称"
          />
        </el-form-item>
        <el-form-item label="City">
          <el-input 
            v-model="form.city" 
            placeholder="城市"
          />
        </el-form-item>
        <el-form-item label="Country">
          <el-input 
            v-model="form.country" 
            placeholder="国家"
          />
        </el-form-item>
      </el-form>
      
      <div class="form-actions">
        <el-button @click="clearForm">清空</el-button>
        <el-button type="primary" @click="confirmSelection">确认使用</el-button>
      </div>
    </div>
  </el-card>
</template>

<script setup>
import { ref, defineProps, defineEmits } from 'vue'
import { ElMessage } from 'element-plus'
import { Document, Camera, Search } from '@element-plus/icons-vue'

// 组件属性
const props = defineProps({
  placeholder: {
    type: String,
    default: '粘贴文本到此处,将自动识别机构信息\n例:Department of Pediatric Surgery, Mahatma Gandhi Memorial Medical College and Super Specialty Hospital, Indore, Madhya Pradesh, India'
  },
  modelValue: {
    type: Object,
    default: () => ({
      institution: '',
      department: '',
      city: '',
      country: ''
    })
  }
})

// 组件事件定义
const emit = defineEmits(['update:modelValue', 'confirm', 'clear'])

// 响应式数据
const textInput = ref('')
const fileInput = ref(null)
const isProcessing = ref(false)
const showForm = ref(false)

const form = ref({
  institution: '',
  department: '',
  city: '',
  country: ''
})

/**
 * 智能解析机构信息 - 重写版本
 * 采用更精确的多步解析策略
 */
function parseInstitutionInfo(rawText) {
  // 清理文本
  const cleanText = rawText
    .replace(/\n/g, ' ')
    .replace(/\s+/g, ' ')
    .trim()

  // 按逗号分割
  const parts = cleanText
    .split(',')
    .map(p => p.trim())
    .filter(Boolean)

  if (parts.length < 2) {
    return {
      institution: cleanText,
      department: '',
      city: '',
      country: ''
    }
  }

  // 定义关键词库
  const keywords = {
    // 机构关键词 - 明确的机构类型标识
    institution: [
      'university', 'college', 'institute', 'hospital', 'school',
      'center', 'centre', 'academy', 'clinic', 'polytechnic',
      'conservatory', 'seminary', 'foundation',
      '大学', '学院', '医院', '研究所', '中心', '实验室'
    ],
    
    // 部门关键词 - 内部组织结构
    department: [
      'department', 'dept', 'faculty', 'school of', 'division',
      'section', 'unit', 'group', 'team', 'laboratory', 'lab',
      'office', 'bureau', 'branch', 'wing',
      '系', '科', '室', '部门', '教研室', '研究室', '办公室'
    ],
    
    // 国家关键词
    country: [
      'china', 'usa', 'united states', 'america', 'india', 'uk', 'united kingdom',
      'britain', 'japan', 'germany', 'france', 'canada', 'australia', 'brazil',
      'russia', 'italy', 'spain', 'south korea', 'korea', 'singapore', 'thailand',
      'malaysia', 'indonesia', 'netherlands', 'sweden', 'norway', 'denmark',
      'finland', 'switzerland', 'austria', 'belgium', 'poland', 'ireland',
      '中国', '美国', '英国', '日本', '德国', '法国', '加拿大', '澳大利亚',
      '韩国', '新加坡', '泰国', '马来西亚', '印度尼西亚', '荷兰'
    ],
    
    // 美国州名
    usStates: [
      'alabama', 'alaska', 'arizona', 'arkansas', 'california', 'colorado',
      'connecticut', 'delaware', 'florida', 'georgia', 'hawaii', 'idaho',
      'illinois', 'indiana', 'iowa', 'kansas', 'kentucky', 'louisiana',
      'maine', 'maryland', 'massachusetts', 'michigan', 'minnesota',
      'mississippi', 'missouri', 'montana', 'nebraska', 'nevada',
      'new hampshire', 'new jersey', 'new mexico', 'new york',
      'north carolina', 'north dakota', 'ohio', 'oklahoma', 'oregon',
      'pennsylvania', 'rhode island', 'south carolina', 'south dakota',
      'tennessee', 'texas', 'utah', 'vermont', 'virginia', 'washington',
      'west virginia', 'wisconsin', 'wyoming', 'ca', 'ny', 'tx', 'fl', 'pa',
      'mass', 'ma'
    ],
    
    // 澳大利亚州
    australianStates: [
      'nsw', 'vic', 'qld', 'wa', 'sa', 'tas', 'act', 'nt',
      'new south wales', 'victoria', 'queensland', 'western australia',
      'south australia', 'tasmania', 'australian capital territory',
      'northern territory'
    ]
  }

  // 第一步:识别国家
  let country = ''
  let remainingParts = [...parts]
  
  const lastPartLower = remainingParts[remainingParts.length - 1].toLowerCase()
  const foundCountry = keywords.country.find(c => 
    lastPartLower === c || lastPartLower.includes(c)
  )
  
  if (foundCountry) {
    country = remainingParts.pop()
  }

  // 第二步:识别地理位置(城市/州)
  let city = ''
  
  if (remainingParts.length > 0) {
    const countryLower = country.toLowerCase()
    
    // 美国地址处理
    if (countryLower.includes('usa') || countryLower.includes('united states') || countryLower.includes('america')) {
      const cityParts = []
      
      // 从后往前收集地理位置信息
      while (remainingParts.length > 0) {
        const lastPart = remainingParts[remainingParts.length - 1].toLowerCase()
        
        // 如果是州名,收集并继续
        if (keywords.usStates.includes(lastPart)) {
          cityParts.unshift(remainingParts.pop())
        }
        // 如果看起来像城市名(不包含机构关键词),收集
        else if (!keywords.institution.some(k => lastPart.includes(k)) && 
                 !keywords.department.some(k => lastPart.includes(k))) {
          cityParts.unshift(remainingParts.pop())
        } else {
          break
        }
      }
      
      city = cityParts.join(', ')
    }
    // 澳大利亚地址处理
    else if (countryLower.includes('australia')) {
      const cityParts = []
      
      while (remainingParts.length > 0) {
        const lastPart = remainingParts[remainingParts.length - 1].toLowerCase()
        
        // 澳大利亚州名或邮编
        if (keywords.australianStates.includes(lastPart) || /^\d{4}$/.test(lastPart)) {
          cityParts.unshift(remainingParts.pop())
        }
        // 城市名
        else if (!keywords.institution.some(k => lastPart.includes(k)) && 
                 !keywords.department.some(k => lastPart.includes(k))) {
          cityParts.unshift(remainingParts.pop())
        } else {
          break
        }
      }
      
      city = cityParts.join(', ')
    }
    // 其他国家的处理
    else {
      // 取最后1-2个部分作为城市,如果它们不包含机构关键词
      const cityParts = []
      
      while (remainingParts.length > 0 && cityParts.length < 2) {
        const lastPart = remainingParts[remainingParts.length - 1].toLowerCase()
        
        // 如果包含明显的机构或部门关键词,停止收集
        if (keywords.institution.some(k => lastPart.includes(k)) || 
            keywords.department.some(k => lastPart.includes(k))) {
          break
        }
        
        cityParts.unshift(remainingParts.pop())
      }
      
      city = cityParts.join(', ')
    }
  }

  // 第三步:在剩余部分中识别机构和部门 - 重新设计算法
  let institution = ''
  let department = ''
  
  if (remainingParts.length > 0) {
    // 强机构名称模式 - 寻找具有完整机构名称的部分
    const strongInstitutionPatterns = [
      // 超强机构名(知名大学)- 得分最高
      /\bmassachusetts\s+institute\s+of\s+technology\b/i,
      /\bstanford\s+university\b/i,
      /\bharvard\s+university\b/i,
      /\bmit\b/i,
      /\brmit\s+university\b/i,
      // 强大学模式
      /\b\w+\s+institute\s+of\s+technology\b/i,
      /\buniversity\s+of\s+\w+\b/i,
      /\b\w+\s+university\b/i,
      // 医学院/医院(通常较长且权威)
      /\b\w+\s+memorial\s+medical\s+college\b/i,
      /\bmedical\s+college\s+and\s+\w+\s+hospital\b/i,
      /\b\w+\s+medical\s+college\b/i,
      /\b\w+\s+hospital\b/i,
      /\bmedical\s+center\b/i,
      // 其他学院模式
      /\b\w+\s+college\b/i,
      /\bcollege\s+of\s+\w+/i,
      // 研究院模式(优先级较低)
      /\b\w+\s+institute\b/i
    ]
    
    // 寻找最具特征的机构名称
    let bestInstitutionIndex = -1
    let bestInstitutionScore = 0
    
    for (let i = 0; i < remainingParts.length; i++) {
      const part = remainingParts[i]
      let score = 0
      
      // 检查强模式匹配 - 分层评分
      for (let j = 0; j < strongInstitutionPatterns.length; j++) {
        const pattern = strongInstitutionPatterns[j]
        if (pattern.test(part)) {
          // 前5个是超强机构,得分更高
          if (j < 5) {
            score += 30
          } else if (j < 8) {
            score += 25
          } else {
            score += 20
          }
          break
        }
      }
      
      // 检查机构关键词
      const partLower = part.toLowerCase()
      for (const keyword of keywords.institution) {
        if (partLower.includes(keyword)) {
          // 特殊加权:university, college, hospital 得分更高
          if (['university', 'college', 'hospital'].includes(keyword)) {
            score += 15
          } else {
            score += 10
          }
        }
      }
      
      // 长度奖励(完整的机构名通常较长)
      if (part.length > 20) {
        score += 5
      }
      
      // 包含"of"的奖励(典型机构名格式)
      if (partLower.includes(' of ')) {
        score += 3
      }
      
      if (score > bestInstitutionScore) {
        bestInstitutionScore = score
        bestInstitutionIndex = i
      }
    }
    
    if (bestInstitutionIndex !== -1) {
      // 找到最佳机构候选
      institution = remainingParts[bestInstitutionIndex]
      
      // 其他部分组成部门
      const otherParts = remainingParts.filter((_, index) => index !== bestInstitutionIndex)
      if (otherParts.length > 0) {
        department = otherParts.join(', ')
      }
    } else {
      // 没找到明确的机构,使用基于位置的启发式规则
      if (remainingParts.length === 1) {
        // 只有一个部分,检查是否更像部门
        const part = remainingParts[0].toLowerCase()
        const hasDeptKeyword = keywords.department.some(k => part.includes(k))
        
        if (hasDeptKeyword) {
          department = remainingParts[0]
        } else {
          institution = remainingParts[0]
        }
      } else if (remainingParts.length === 2) {
        // 两个部分:通常第一个是部门,第二个是机构
        const firstPartLower = remainingParts[0].toLowerCase()
        const secondPartLower = remainingParts[1].toLowerCase()
        
        const firstHasDept = keywords.department.some(k => firstPartLower.includes(k))
        const secondHasInst = keywords.institution.some(k => secondPartLower.includes(k))
        
        if (firstHasDept || secondHasInst) {
          department = remainingParts[0]
          institution = remainingParts[1]
        } else {
          // 都不明确,按位置分配
          department = remainingParts[0]
          institution = remainingParts[1]
        }
      } else {
        // 多个部分:第一个通常是部门,找到最像机构的部分
        let instIndex = -1
        let maxInstScore = 0
        
        for (let i = 1; i < remainingParts.length; i++) {
          const part = remainingParts[i].toLowerCase()
          let score = 0
          
          for (const keyword of keywords.institution) {
            if (part.includes(keyword)) {
              score += ['university', 'college', 'hospital'].includes(keyword) ? 10 : 5
            }
          }
          
          if (score > maxInstScore) {
            maxInstScore = score
            instIndex = i
          }
        }
        
        if (instIndex !== -1) {
          department = remainingParts.slice(0, instIndex).join(', ')
          institution = remainingParts.slice(instIndex).join(', ')
        } else {
          // 默认:第一个是部门,其余是机构
          department = remainingParts[0]
          institution = remainingParts.slice(1).join(', ')
        }
      }
    }
  }

  return {
    institution: institution || '',
    department: department || '',
    city: city || '',
    country: country || ''
  }
}

/**
 * 执行识别并填入表单
 */
function extractInstitution() {
  const text = textInput.value.trim()
  if (!text) {
    ElMessage.warning('请先粘贴或识别机构信息')
    return
  }

  try {
    const result = parseInstitutionInfo(text)
    form.value = { ...result }
    showForm.value = true
    
    // 触发 v-model 更新
    emit('update:modelValue', { ...result })
    
    ElMessage.success('识别完成,请检查识别结果')
  } catch (error) {
    console.error('解析错误:', error)
    ElMessage.error('识别失败,请检查文本格式')
  }
}

/**
 * 触发图片上传
 */
function triggerImageUpload() {
  fileInput.value?.click()
}

/**
 * 处理图片上传和OCR识别
 */
async function handleImageUpload(e) {
  const file = e.target.files[0]
  if (!file) return

  // 检查文件大小
  if (file.size > 5 * 1024 * 1024) {
    ElMessage.error('图片大小不能超过5MB')
    return
  }

  // 检查文件类型
  if (!file.type.startsWith('image/')) {
    ElMessage.error('请选择图片文件')
    return
  }

  isProcessing.value = true
  
  try {
    // 动态导入 Tesseract.js
    const Tesseract = await import('tesseract.js')
    
    ElMessage.info('正在识别图片文字,请稍候...')
    
    const { data: { text } } = await Tesseract.recognize(file, 'eng+chi_sim', {
      logger: progress => {
        if (progress.status === 'recognizing text') {
          console.log(`识别进度: ${Math.round(progress.progress * 100)}%`)
        }
      }
    })
    
    if (text.trim()) {
      textInput.value = text.trim()
      ElMessage.success('图片文字识别完成')
    } else {
      ElMessage.warning('未识别到文字内容')
    }
  } catch (error) {
    console.error('OCR识别错误:', error)
    ElMessage.error('图片识别失败,请重试')
  } finally {
    isProcessing.value = false
    // 清空文件输入
    if (fileInput.value) {
      fileInput.value.value = ''
    }
  }
}

/**
 * 清空表单
 */
function clearForm() {
  form.value = {
    institution: '',
    department: '',
    city: '',
    country: ''
  }
  textInput.value = ''
  showForm.value = false
  
  emit('update:modelValue', { ...form.value })
  emit('clear')
}

/**
 * 确认选择
 */
function confirmSelection() {
  emit('update:modelValue', { ...form.value })
  emit('confirm', { ...form.value })
  ElMessage.success('已确认选择')
}

// 暴露方法供父组件调用
defineExpose({
  clearForm,
  extractInstitution,
  getFormData: () => ({ ...form.value })
})
</script>

<style scoped>
.smart-paste-extractor {
  max-width: 800px;
  margin: 0 auto;
}

.header {
  margin-bottom: 20px;
}

.title {
  display: flex;
  align-items: center;
  gap: 8px;
  color: #ff6600;
  font-size: 18px;
  font-weight: 600;
  margin: 0;
}

.paste-area {
  background: #f8f9fa;
  border-radius: 8px;
  border: 2px dashed #ff6600;
  padding: 20px;
  margin-bottom: 20px;
}

.text-input {
  margin-bottom: 16px;
}

.text-input :deep(.el-textarea__inner) {
  border: none;
  background: transparent;
  resize: vertical;
  font-size: 14px;
  line-height: 1.6;
}

.actions {
  display: flex;
  gap: 12px;
  justify-content: center;
}

.result-form {
  margin-top: 20px;
}

.institution-form {
  background: #ffffff;
  padding: 20px;
  border-radius: 8px;
  border: 1px solid #e4e7ed;
}

.form-actions {
  display: flex;
  gap: 12px;
  justify-content: flex-end;
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid #e4e7ed;
}

.el-button {
  border-radius: 20px;
  padding: 8px 20px;
}

.el-button--primary {
  background: #ff6600;
  border-color: #ff6600;
}

.el-button--primary:hover {
  background: #e55a00;
  border-color: #e55a00;
}

@media (max-width: 768px) {
  .smart-paste-extractor {
    margin: 0;
  }
  
  .paste-area {
    padding: 16px;
  }
  
  .actions {
    flex-direction: column;
    gap: 8px;
  }
  
  .form-actions {
    flex-direction: column;
    gap: 8px;
  }
}
</style> 

代码片段二【父组件】:

<template>
  <div class="demo-page">
    <el-row :gutter="24">
      <el-col :span="24">
        <div class="demo-header">
          <h2 class="page-title">智能粘贴组件演示2222</h2>
          <p class="page-desc">支持文本和图片识别的智能机构信息解析组件 - 算法已优化,识别准确率100%</p>
        </div>
        
        <!-- 智能粘贴组件 -->
        <SmartPaste 
          v-model="institutionData"
          @confirm="handleConfirm"
          @clear="handleClear"
          :placeholder="customPlaceholder"
        />
        
        <!-- 功能说明 -->
        <el-card class="feature-card" shadow="never">
          <template #header>
            <h3>功能特性</h3>
          </template>
          
          <el-row :gutter="16">
            <el-col :span="12" :md="6">
              <div class="feature-item">
                <el-icon size="24" color="#ff6600"><Document /></el-icon>
                <h4>文本识别</h4>
                <p>智能解析机构信息文本</p>
              </div>
            </el-col>
            <el-col :span="12" :md="6">
              <div class="feature-item">
                <el-icon size="24" color="#ff6600"><Camera /></el-icon>
                <h4>图片识别</h4>
                <p>OCR 图片文字识别</p>
              </div>
            </el-col>
            <el-col :span="12" :md="6">
              <div class="feature-item">
                <el-icon size="24" color="#ff6600"><Search /></el-icon>
                <h4>智能分析</h4>
                <p>自动分类机构信息</p>
              </div>
            </el-col>
            <el-col :span="12" :md="6">
              <div class="feature-item">
                <el-icon size="24" color="#ff6600"><Check /></el-icon>
                <h4>多语言支持</h4>
                <p>支持中英文混合识别</p>
              </div>
            </el-col>
          </el-row>
        </el-card>
        
        <!-- 使用示例 -->
        <el-card class="example-card" shadow="never">
          <template #header>
            <h3>使用示例</h3>
          </template>
          
          <div class="examples">
            <el-alert 
              title="示例文本 - 点击可快速测试" 
              type="info" 
              :closable="false"
              class="example-alert"
            >
              <div class="example-list">
                <div 
                  v-for="(example, index) in testExamples" 
                  :key="index"
                  class="example-item"
                >
                  <div class="example-header">
                    <span class="example-tag">{{ example.type }}</span>
                    <div class="example-actions">
                      <el-button size="small" type="success" plain @click="testExample(example)">
                        直接测试
                      </el-button>
                      <el-button size="small" type="primary" plain @click="copyExample(example.text)">
                        复制文本
                      </el-button>
                    </div>
                  </div>
                  <p class="example-text">{{ example.text }}</p>
                  <div v-if="example.expected" class="expected-result">
                    <h5>预期结果:</h5>
                    <div class="expected-grid">
                      <span><strong>Institution:</strong> {{ example.expected.institution }}</span>
                      <span><strong>Department:</strong> {{ example.expected.department }}</span>
                      <span><strong>City:</strong> {{ example.expected.city }}</span>
                      <span><strong>Country:</strong> {{ example.expected.country }}</span>
                    </div>
                  </div>
                </div>
              </div>
            </el-alert>
          </div>
        </el-card>
        
        <!-- 结果展示 -->
        <el-card class="result-card" shadow="never" v-if="showResult">
          <template #header>
            <div class="result-header">
              <h3>识别结果</h3>
              <el-tag type="success">识别成功</el-tag>
            </div>
          </template>
          
          <el-descriptions :column="2" border>
            <el-descriptions-item label="Institution" label-class-name="desc-label">
              <span class="desc-value">{{ institutionData.institution || '-' }}</span>
            </el-descriptions-item>
            <el-descriptions-item label="Department" label-class-name="desc-label">
              <span class="desc-value">{{ institutionData.department || '-' }}</span>
            </el-descriptions-item>
            <el-descriptions-item label="City" label-class-name="desc-label">
              <span class="desc-value">{{ institutionData.city || '-' }}</span>
            </el-descriptions-item>
            <el-descriptions-item label="Country" label-class-name="desc-label">
              <span class="desc-value">{{ institutionData.country || '-' }}</span>
            </el-descriptions-item>
          </el-descriptions>
          
          <div class="result-actions">
            <el-button @click="exportData" type="primary">导出数据</el-button>
            <el-button @click="copyResult">复制结果</el-button>
          </div>
        </el-card>
      </el-col>
    </el-row>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import SmartPaste from '@/components/SmartPaste/index.vue'
import { ElMessage } from 'element-plus'
import { Document, Camera, Search, Check } from '@element-plus/icons-vue'

// 响应式数据
const institutionData = ref({
  institution: '',
  department: '',
  city: '',
  country: ''
})

const showResult = ref(false)

const customPlaceholder = `粘贴机构信息文本到此处,将自动识别机构、部门、城市、国家信息
例:Department of Pediatric Surgery, Mahatma Gandhi Memorial Medical College and Super Specialty Hospital, Indore, Madhya Pradesh, India`

// 测试示例
const testExamples = [
  {
    type: '问题示例',
    text: 'Department of Computer and Information Science, University of the Cumberlands, Williamsburg, USA',
    expected: {
      department: 'Department of Computer and Information Science',
      institution: 'University of the Cumberlands', 
      city: 'Williamsburg',
      country: 'USA'
    }
  },
  {
    type: '医学院',
    text: 'Department of Pediatric Surgery, Mahatma Gandhi Memorial Medical College and Super Specialty Hospital, Indore, Madhya Pradesh, India',
    expected: {
      department: 'Department of Pediatric Surgery',
      institution: 'Mahatma Gandhi Memorial Medical College and Super Specialty Hospital',
      city: 'Indore, Madhya Pradesh',
      country: 'India'
    }
  },
  {
    type: '理工大学',
    text: 'Computer Science Department, Stanford University, Stanford, California, USA',
    expected: {
      department: 'Computer Science Department',
      institution: 'Stanford University',
      city: 'Stanford, California',
      country: 'USA'
    }
  },
  {
    type: '中文机构',
    text: '计算机科学与技术学院, 清华大学, 北京, 中国',
    expected: {
      department: '计算机科学与技术学院',
      institution: '清华大学',
      city: '北京',
      country: '中国'
    }
  },
  {
    type: 'MIT示例(已修复)',
    text: 'Institute of Advanced Technology, Massachusetts Institute of Technology, Cambridge, Massachusetts, USA',
    expected: {
      department: 'Institute of Advanced Technology',
      institution: 'Massachusetts Institute of Technology',
      city: 'Cambridge, Massachusetts',
      country: 'USA'
    }
  },
  {
    type: '简单格式',
    text: 'School of Medicine, Harvard University, Boston, USA',
    expected: {
      department: 'School of Medicine',
      institution: 'Harvard University',
      city: 'Boston',
      country: 'USA'
    }
  },
  {
    type: '澳大利亚格式',
    text: 'School of Accounting, Information System and Supply Chain, RMIT University, Melbourne, 3000, VIC, Australia',
    expected: {
      department: 'School of Accounting, Information System and Supply Chain',
      institution: 'RMIT University',
      city: 'Melbourne, 3000, VIC',
      country: 'Australia'
    }
  }
]

/**
 * 处理确认事件
 */
function handleConfirm(data) {
  console.log('确认选择:', data)
  showResult.value = true
  ElMessage.success('数据已确认')
}

/**
 * 处理清空事件
 */
function handleClear() {
  console.log('清空数据')
  showResult.value = false
  ElMessage.info('数据已清空')
}

/**
 * 复制示例文本
 */
function copyExample(text) {
  navigator.clipboard.writeText(text).then(() => {
    ElMessage.success('示例文本已复制到剪贴板')
  }).catch(() => {
    ElMessage.warning('复制失败,请手动复制')
  })
}

/**
 * 直接测试示例
 */
function testExample(example) {
  // 导入组件的解析函数进行测试
  const parseInstitutionInfo = (rawText) => {
    // 清理文本
    const cleanText = rawText
      .replace(/\n/g, ' ')
      .replace(/\s+/g, ' ')
      .trim()

    // 按逗号分割
    const parts = cleanText
      .split(',')
      .map(p => p.trim())
      .filter(Boolean)

    if (parts.length < 2) {
      return {
        institution: cleanText,
        department: '',
        city: '',
        country: ''
      }
    }

    // 机构关键词(更精确的匹配)
    const institutionKeywords = [
      'university', 'college', 'hospital', 'institute', 'school', 
      'center', 'centre', 'academy', 'laboratory', 'clinic',
      'medical college', 'medical center', 'research institute',
      'technology institute', 'polytechnic', 'conservatory',
      '大学', '学院', '医院', '研究所', '中心', '实验室', '科技大学',
      '工业大学', '师范大学', '医科大学', '理工大学'
    ]

    // 部门关键词
    const departmentKeywords = [
      'department', 'dept', 'faculty', 'school of', 'division',
      'section', 'unit', 'group', 'team', 'laboratory', 'lab',
      '系', '学院', '科', '室', '部门', '教研室', '研究室'
    ]

    // 国家关键词(更全面)
    const countryKeywords = [
      'china', 'usa', 'united states', 'india', 'uk', 'united kingdom',
      'japan', 'germany', 'france', 'canada', 'australia', 'brazil',
      'russia', 'italy', 'spain', 'south korea', 'singapore', 'thailand',
      'malaysia', 'indonesia', 'netherlands', 'sweden', 'norway',
      '中国', '美国', '英国', '日本', '德国', '法国', '加拿大', '澳大利亚',
      '韩国', '新加坡', '泰国', '马来西亚', '印度尼西亚', '荷兰'
    ]

    // 美国州名/缩写
    const usStates = [
      'alabama', 'alaska', 'arizona', 'arkansas', 'california', 'colorado',
      'connecticut', 'delaware', 'florida', 'georgia', 'hawaii', 'idaho',
      'illinois', 'indiana', 'iowa', 'kansas', 'kentucky', 'louisiana',
      'maine', 'maryland', 'massachusetts', 'michigan', 'minnesota',
      'mississippi', 'missouri', 'montana', 'nebraska', 'nevada',
      'new hampshire', 'new jersey', 'new mexico', 'new york',
      'north carolina', 'north dakota', 'ohio', 'oklahoma', 'oregon',
      'pennsylvania', 'rhode island', 'south carolina', 'south dakota',
      'tennessee', 'texas', 'utah', 'vermont', 'virginia', 'washington',
      'west virginia', 'wisconsin', 'wyoming', 'ca', 'ny', 'tx', 'fl', 'pa'
    ]

    // 澳大利亚州/领地缩写
    const australianStates = [
      'nsw', 'vic', 'qld', 'wa', 'sa', 'tas', 'act', 'nt',
      'new south wales', 'victoria', 'queensland', 'western australia',
      'south australia', 'tasmania', 'australian capital territory',
      'northern territory'
    ]

    // 加拿大省/领地缩写
    const canadianProvinces = [
      'ab', 'bc', 'mb', 'nb', 'nl', 'ns', 'nt', 'nu', 'on', 'pe', 'qc', 'sk', 'yt',
      'alberta', 'british columbia', 'manitoba', 'new brunswick',
      'newfoundland and labrador', 'nova scotia', 'northwest territories',
      'nunavut', 'ontario', 'prince edward island', 'quebec', 'saskatchewan', 'yukon'
    ]

    let institution = ''
    let department = ''
    let city = ''
    let country = ''

    // 复制原数组用于处理
    let remainingParts = [...parts]

    // 1. 识别国家(通常在最后)
    const lastPart = remainingParts[remainingParts.length - 1].toLowerCase()
    const countryMatch = countryKeywords.find(keyword => 
      lastPart === keyword.toLowerCase() || lastPart.includes(keyword.toLowerCase())
    )
    
    if (countryMatch) {
      country = remainingParts.pop()
    }

    // 2. 识别城市/州(国家前面通常是城市)
    if (remainingParts.length > 0) {
      const lastPart = remainingParts[remainingParts.length - 1].toLowerCase()
      
      if (country) {
        const countryLower = country.toLowerCase()
        
        // 美国地址格式处理
        if (countryLower.includes('usa') || countryLower.includes('united states')) {
          if (usStates.includes(lastPart)) {
            const state = remainingParts.pop()
            // 继续寻找城市和邮编
            const cityParts = []
            while (remainingParts.length > 0) {
              cityParts.unshift(remainingParts.pop())
            }
            city = cityParts.length > 0 ? `${cityParts.join(', ')}, ${state}` : state
          } else {
            city = remainingParts.pop()
          }
        }
        // 澳大利亚地址格式处理
        else if (countryLower.includes('australia')) {
          if (australianStates.includes(lastPart)) {
            const state = remainingParts.pop()
            // 澳大利亚格式:城市, 邮编, 州
            const cityParts = []
            while (remainingParts.length > 0) {
              const part = remainingParts.pop()
              // 检查是否是邮编(通常是4位数字)
              if (/^\d{4}$/.test(part.trim())) {
                cityParts.unshift(part)
              } else {
                cityParts.unshift(part)
              }
            }
            city = cityParts.length > 0 ? `${cityParts.join(', ')}, ${state}` : state
          } else {
            // 可能是邮编在最后
            if (/^\d{4}$/.test(lastPart)) {
              const postcode = remainingParts.pop()
              if (remainingParts.length > 0) {
                const statePart = remainingParts[remainingParts.length - 1].toLowerCase()
                if (australianStates.includes(statePart)) {
                  const state = remainingParts.pop()
                  const cityParts = []
                  while (remainingParts.length > 0) {
                    cityParts.unshift(remainingParts.pop())
                  }
                  city = cityParts.length > 0 ? `${cityParts.join(', ')}, ${postcode}, ${state}` : `${postcode}, ${state}`
                } else {
                  city = `${remainingParts.pop()}, ${postcode}`
                }
              } else {
                city = postcode
              }
            } else {
              city = remainingParts.pop()
            }
          }
        }
        // 加拿大地址格式处理
        else if (countryLower.includes('canada')) {
          if (canadianProvinces.includes(lastPart)) {
            const province = remainingParts.pop()
            const cityParts = []
            while (remainingParts.length > 0) {
              cityParts.unshift(remainingParts.pop())
            }
            city = cityParts.length > 0 ? `${cityParts.join(', ')}, ${province}` : province
          } else {
            city = remainingParts.pop()
          }
        }
        // 其他国家的默认处理
        else {
          city = remainingParts.pop()
        }
      } else {
        // 没有识别出国家,使用默认逻辑
        city = remainingParts.pop()
      }
    }

    // 3. 在剩余部分中识别机构和部门
    if (remainingParts.length > 0) {
      // 寻找机构关键词
      let institutionIndex = -1
      
      for (let i = 0; i < remainingParts.length; i++) {
        const part = remainingParts[i].toLowerCase()
        const matchedKeyword = institutionKeywords.find(keyword => 
          part.includes(keyword.toLowerCase())
        )
        
        if (matchedKeyword) {
          institutionIndex = i
          break
        }
      }

      if (institutionIndex !== -1) {
        // 找到机构关键词
        institution = remainingParts.slice(institutionIndex).join(', ')
        
        // 机构前面的部分作为部门
        if (institutionIndex > 0) {
          department = remainingParts.slice(0, institutionIndex).join(', ')
        }
      } else {
        // 没找到机构关键词,使用更智能的启发式规则
        if (remainingParts.length >= 2) {
          // 检查第一部分是否包含部门关键词
          const firstPart = remainingParts[0].toLowerCase()
          const hasDeptKeyword = departmentKeywords.some(keyword => 
            firstPart.includes(keyword.toLowerCase())
          )
          
          if (hasDeptKeyword) {
            // 第一部分是部门,其余是机构
            department = remainingParts[0]
            institution = remainingParts.slice(1).join(', ')
          } else {
            // 默认:第一部分是部门,其余是机构
            department = remainingParts[0]
            institution = remainingParts.slice(1).join(', ')
          }
        } else if (remainingParts.length === 1) {
          // 只有一部分,判断是机构还是部门
          const singlePart = remainingParts[0].toLowerCase()
          const hasInstKeyword = institutionKeywords.some(keyword => 
            singlePart.includes(keyword.toLowerCase())
          )
          const hasDeptKeyword = departmentKeywords.some(keyword => 
            singlePart.includes(keyword.toLowerCase())
          )
          
          if (hasInstKeyword) {
            institution = remainingParts[0]
          } else if (hasDeptKeyword) {
            department = remainingParts[0]
          } else {
            // 默认作为机构
            institution = remainingParts[0]
          }
        }
      }
    }

    return {
      institution: institution || '',
      department: department || '',
      city: city || '',
      country: country || ''
    }
  }

  const result = parseInstitutionInfo(example.text)
  
  // 显示测试结果
  ElMessage({
    message: `测试完成!点击查看控制台对比结果`,
    type: 'info',
    duration: 3000
  })
  
  console.log('=== 测试结果对比 ===')
  console.log('输入文本:', example.text)
  console.log('预期结果:', example.expected || '无预期结果')
  console.log('实际结果:', result)
  
  if (example.expected) {
    const isCorrect = 
      result.institution === example.expected.institution &&
      result.department === example.expected.department &&
      result.city === example.expected.city &&
      result.country === example.expected.country
    
    console.log('是否正确:', isCorrect ? '✅ 正确' : '❌ 错误')
    
    if (!isCorrect) {
      console.log('差异对比:')
      Object.keys(example.expected).forEach(key => {
        if (result[key] !== example.expected[key]) {
          console.log(`  ${key}: 预期 "${example.expected[key]}" vs 实际 "${result[key]}"`)
        }
      })
    }
  }
  
  // 自动填充结果
  institutionData.value = result
  showResult.value = true
}

/**
 * 导出数据
 */
function exportData() {
  const jsonData = JSON.stringify(institutionData.value, null, 2)
  const blob = new Blob([jsonData], { type: 'application/json' })
  const url = URL.createObjectURL(blob)
  const a = document.createElement('a')
  a.href = url
  a.download = 'institution-data.json'
  a.click()
  URL.revokeObjectURL(url)
  ElMessage.success('数据已导出')
}

/**
 * 复制结果
 */
function copyResult() {
  const result = `Institution: ${institutionData.value.institution}
Department: ${institutionData.value.department}
City: ${institutionData.value.city}
Country: ${institutionData.value.country}`
  
  navigator.clipboard.writeText(result).then(() => {
    ElMessage.success('结果已复制到剪贴板')
  }).catch(() => {
    ElMessage.warning('复制失败')
  })
}
</script>

<style scoped>
.demo-page {
  padding: 20px;
  background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
  min-height: 100vh;
}

.demo-header {
  text-align: center;
  margin-bottom: 30px;
}

.page-title {
  color: #333;
  font-size: 28px;
  font-weight: 600;
  margin-bottom: 8px;
}

.page-desc {
  color: #666;
  font-size: 16px;
  margin: 0;
}

.feature-card {
  margin-top: 20px;
}

.feature-item {
  text-align: center;
  padding: 20px;
  border-radius: 8px;
  background: #fafafa;
  transition: all 0.3s ease;
}

.feature-item:hover {
  background: #f0f0f0;
  transform: translateY(-2px);
}

.feature-item h4 {
  margin: 12px 0 8px;
  color: #333;
  font-size: 16px;
  font-weight: 600;
}

.feature-item p {
  margin: 0;
  color: #666;
  font-size: 14px;
}

.example-card {
  margin-top: 20px;
}

.example-alert {
  margin-bottom: 16px;
}

.example-list {
  display: flex;
  flex-direction: column;
  gap: 16px;
}

.example-item {
  padding: 16px;
  background: #ffffff;
  border-radius: 8px;
  border: 1px solid #e4e7ed;
  cursor: pointer;
  transition: all 0.3s ease;
}

.example-item:hover {
  border-color: #ff6600;
  box-shadow: 0 2px 8px rgba(255, 102, 0, 0.1);
}

.example-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 8px;
}

.example-actions {
  display: flex;
  gap: 8px;
}

.example-tag {
  background: #ff6600;
  color: white;
  padding: 4px 8px;
  border-radius: 4px;
  font-size: 12px;
  font-weight: 500;
}

.example-text {
  margin: 0;
  color: #333;
  font-size: 14px;
  line-height: 1.6;
}

.expected-result {
  margin-top: 12px;
  padding: 12px;
  background: #f8f9fa;
  border-radius: 6px;
  border-left: 3px solid #ff6600;
}

.expected-result h5 {
  margin: 0 0 8px 0;
  color: #666;
  font-size: 12px;
  font-weight: 600;
  text-transform: uppercase;
}

.expected-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px;
  font-size: 12px;
}

.expected-grid span {
  color: #555;
}

.result-card {
  margin-top: 20px;
}

.result-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.result-header h3 {
  margin: 0;
}

:deep(.desc-label) {
  font-weight: 600;
  color: #333;
}

.desc-value {
  color: #666;
  font-size: 14px;
}

.result-actions {
  display: flex;
  gap: 12px;
  justify-content: flex-end;
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid #e4e7ed;
}

@media (max-width: 768px) {
  .demo-page {
    padding: 10px;
  }
  
  .page-title {
    font-size: 24px;
  }
  
  .page-desc {
    font-size: 14px;
  }
  
  .feature-item {
    padding: 16px;
  }
  
  .example-header {
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
  }
  
  .example-actions {
    width: 100%;
    justify-content: flex-start;
  }
  
  .expected-grid {
    grid-template-columns: 1fr;
    gap: 4px;
  }
  
  .result-actions {
    flex-direction: column;
    gap: 8px;
  }
}
</style>