AutomationScreensView.vue 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. <template>
  2. <div class="panel">
  3. <div class="toolbar">
  4. <div class="filters">
  5. <el-button @click="load">刷新</el-button>
  6. </div>
  7. </div>
  8. <el-table :data="screens.items" border stripe>
  9. <el-table-column prop="id" label="ID" width="80" />
  10. <el-table-column prop="interface_name" label="界面名称" min-width="180" />
  11. <el-table-column prop="description" label="描述" min-width="320" show-overflow-tooltip />
  12. <el-table-column label="类型" width="180">
  13. <template #default="{ row }">
  14. <el-tag v-if="row.is_windows_desktop" type="success">Windows 桌面</el-tag>
  15. <el-tag v-if="row.is_browser_webpage" type="primary">浏览器网页</el-tag>
  16. <el-tag v-if="!row.is_windows_desktop && !row.is_browser_webpage" type="info">窗口界面</el-tag>
  17. </template>
  18. </el-table-column>
  19. <el-table-column prop="element_count" label="元素" width="90" />
  20. <el-table-column prop="created_at" label="识别时间" min-width="180" />
  21. <el-table-column label="操作" width="180" fixed="right">
  22. <template #default="{ row }">
  23. <el-button size="small" @click="openDetail(row)">详情</el-button>
  24. <el-button size="small" type="danger" @click="remove(row)">删除</el-button>
  25. </template>
  26. </el-table-column>
  27. </el-table>
  28. <el-dialog v-model="dialog" title="界面详情" width="980px">
  29. <div v-if="detail" class="screen-detail-layout">
  30. <div>
  31. <div class="screenshot-stage small">
  32. <div v-if="detailImageSrc" class="screenshot-canvas" :style="canvasStyle">
  33. <img class="screenshot-image" :src="detailImageSrc" alt="已识别界面截图" />
  34. <button
  35. v-for="element in detail.elements || []"
  36. :key="element.id"
  37. class="element-marker"
  38. :style="markerStyle(element)"
  39. >
  40. {{ element.element_index }}
  41. </button>
  42. </div>
  43. </div>
  44. </div>
  45. <div>
  46. <el-descriptions :column="1" border>
  47. <el-descriptions-item label="名称">{{ detail.interface_name }}</el-descriptions-item>
  48. <el-descriptions-item label="描述">{{ detail.description || '-' }}</el-descriptions-item>
  49. <el-descriptions-item label="分辨率">{{ detail.width }} x {{ detail.height }}</el-descriptions-item>
  50. </el-descriptions>
  51. <el-table :data="detail.elements || []" height="360" border stripe style="margin-top: 12px">
  52. <el-table-column prop="element_index" label="#" width="54" />
  53. <el-table-column prop="name" label="名称" min-width="140" show-overflow-tooltip />
  54. <el-table-column label="坐标" width="120">
  55. <template #default="{ row }">{{ row.x }}, {{ row.y }}</template>
  56. </el-table-column>
  57. </el-table>
  58. </div>
  59. </div>
  60. </el-dialog>
  61. </div>
  62. </template>
  63. <script setup>
  64. import { computed, onMounted, ref } from 'vue'
  65. import { ElMessage, ElMessageBox } from 'element-plus'
  66. import { api } from '../api'
  67. const screens = ref({ items: [] })
  68. const detail = ref(null)
  69. const dialog = ref(false)
  70. const detailImageSrc = computed(() => {
  71. if (!detail.value?.image_base64) return ''
  72. return `data:${detail.value.mime_type || 'image/png'};base64,${detail.value.image_base64}`
  73. })
  74. const canvasStyle = computed(() => {
  75. if (!detail.value?.width || !detail.value?.height) return {}
  76. return { aspectRatio: `${detail.value.width} / ${detail.value.height}` }
  77. })
  78. async function load() {
  79. const { data } = await api.get('/api/automation/screens')
  80. screens.value = data
  81. }
  82. async function openDetail(row) {
  83. const { data } = await api.get(`/api/automation/screens/${row.id}`, { params: { include_image: true } })
  84. detail.value = data
  85. dialog.value = true
  86. }
  87. function markerStyle(element) {
  88. const left = detail.value?.width ? (element.x / detail.value.width) * 100 : element.x_percent
  89. const top = detail.value?.height ? (element.y / detail.value.height) * 100 : element.y_percent
  90. return { left: `${left}%`, top: `${top}%` }
  91. }
  92. async function remove(row) {
  93. await ElMessageBox.confirm(`确认删除界面“${row.interface_name}”?`, '删除界面', { type: 'warning' })
  94. await api.delete(`/api/automation/screens/${row.id}`)
  95. ElMessage.success('已删除')
  96. await load()
  97. }
  98. defineExpose({ load })
  99. onMounted(load)
  100. </script>