Selaa lähdekoodia

feat(machine): add search filters and fix layout for internal scrolling

- Add search form with filters: Machine ID, Name, Enabled status, Date range
- Add search/reset buttons with Indigo theme styling
- Change table from border to stripe mode for better readability
- Fix layout to have sticky header/pagination with scrollable table only
- Update layout to prevent outer container scrolling (overflow: hidden)
- Apply Indigo color theme (#4f46e5) to buttons, pagination, and links

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
yb 2 viikkoa sitten
vanhempi
commit
440c6427e2
2 muutettua tiedostoa jossa 257 lisäystä ja 61 poistoa
  1. 6 7
      src/layout/index.vue
  2. 251 54
      src/views/machine/index.vue

+ 6 - 7
src/layout/index.vue

@@ -607,7 +607,8 @@ onUnmounted(() => {
 <style lang="scss" scoped>
 .layout {
   display: flex;
-  min-height: 100vh;
+  height: 100vh;
+  overflow: hidden;
   font-family: 'Inter', system-ui, -apple-system, sans-serif;
 
   // Overlay
@@ -821,6 +822,8 @@ onUnmounted(() => {
     display: flex;
     flex-direction: column;
     min-width: 0;
+    max-height: 100vh;
+    overflow: hidden;
     background: #f9fafb;
   }
 
@@ -947,12 +950,8 @@ onUnmounted(() => {
   // Content
   &__content {
     flex: 1;
-    padding: 1rem;
-    overflow: auto;
-
-    @media (min-width: 1024px) {
-      padding: 2rem;
-    }
+    padding: 0;
+    overflow: hidden;
   }
 
   // Icons

+ 251 - 54
src/views/machine/index.vue

@@ -1,55 +1,98 @@
 <template>
   <div class="page-container">
-    <!-- 操作按钮 -->
-    <div class="table-actions">
-      <el-button type="primary" :icon="Plus" data-id="btn-add-machine" @click="handleAdd">新增机器</el-button>
-      <el-button plain :icon="Refresh" data-id="btn-refresh" @click="getList">刷新列表</el-button>
+    <!-- 搜索表单 -->
+    <div class="search-form">
+      <el-form :model="searchForm" inline data-id="search-form">
+        <el-form-item label="机器ID">
+          <el-input
+            v-model="searchForm.machineId"
+            placeholder="请输入机器ID"
+            clearable
+            data-id="search-machine-id"
+            @keyup.enter="handleSearch"
+          />
+        </el-form-item>
+        <el-form-item label="名称">
+          <el-input
+            v-model="searchForm.name"
+            placeholder="请输入名称"
+            clearable
+            data-id="search-name"
+            @keyup.enter="handleSearch"
+          />
+        </el-form-item>
+        <el-form-item label="启用状态">
+          <el-select v-model="searchForm.enabled" placeholder="全部" clearable data-id="search-enabled">
+            <el-option label="全部" value="" />
+            <el-option label="已启用" :value="true" />
+            <el-option label="已禁用" :value="false" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="创建时间">
+          <el-date-picker
+            v-model="searchForm.dateRange"
+            type="daterange"
+            range-separator="至"
+            start-placeholder="开始日期"
+            end-placeholder="结束日期"
+            value-format="YYYY-MM-DD"
+            data-id="search-date-range"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" :icon="Search" data-id="btn-search" @click="handleSearch">查询</el-button>
+          <el-button :icon="RefreshRight" data-id="btn-reset" @click="handleReset">重置</el-button>
+          <el-button type="primary" :icon="Plus" data-id="btn-add-machine" @click="handleAdd">新增</el-button>
+        </el-form-item>
+      </el-form>
     </div>
 
     <!-- 数据表格 -->
-    <el-table v-loading="loading" :data="paginatedList" border data-id="machine-table">
-      <el-table-column type="index" label="序号" width="60" align="center" />
-      <el-table-column prop="machineId" label="机器ID" min-width="120" show-overflow-tooltip />
-      <el-table-column prop="name" label="名称" min-width="120" show-overflow-tooltip>
-        <template #default="{ row }">
-          <el-link type="primary" :data-id="`link-edit-${row.machineId}`" @click="handleEdit(row)">
-            {{ row.name }}
-          </el-link>
-        </template>
-      </el-table-column>
-      <el-table-column prop="location" label="位置" min-width="120" show-overflow-tooltip />
-      <el-table-column prop="description" label="描述" min-width="150" show-overflow-tooltip />
-      <el-table-column prop="cameraCount" label="摄像头数" width="100" align="center">
-        <template #default="{ row }">
-          <el-tag type="info">{{ row.cameraCount || 0 }}</el-tag>
-        </template>
-      </el-table-column>
-      <el-table-column prop="enabled" label="启用" width="80" align="center">
-        <template #default="{ row }">
-          <el-tag :type="row.enabled ? 'success' : 'info'">
-            {{ row.enabled ? '是' : '否' }}
-          </el-tag>
-        </template>
-      </el-table-column>
-      <el-table-column prop="createdAt" label="创建时间" width="170" align="center" />
-      <el-table-column label="操作" width="150" align="center" fixed="right">
-        <template #default="{ row }">
-          <el-button type="primary" link :icon="Edit" :data-id="`btn-edit-${row.machineId}`" @click="handleEdit(row)">
-            编辑
-          </el-button>
-          <el-button
-            type="danger"
-            link
-            :icon="Delete"
-            :disabled="deleteLoading"
-            :data-id="`btn-delete-${row.machineId}`"
-            @click="handleDelete(row)"
-          >
-            删除
-          </el-button>
-        </template>
-      </el-table-column>
-    </el-table>
+    <div class="table-wrapper">
+      <el-table v-loading="loading" :data="paginatedList" stripe data-id="machine-table" height="100%">
+        <el-table-column type="index" label="序号" width="60" align="center" />
+        <el-table-column prop="machineId" label="机器ID" min-width="120" show-overflow-tooltip />
+        <el-table-column prop="name" label="名称" min-width="120" show-overflow-tooltip>
+          <template #default="{ row }">
+            <el-link type="primary" :data-id="`link-edit-${row.machineId}`" @click="handleEdit(row)">
+              {{ row.name }}
+            </el-link>
+          </template>
+        </el-table-column>
+        <el-table-column prop="location" label="位置" min-width="120" show-overflow-tooltip />
+        <el-table-column prop="description" label="描述" min-width="150" show-overflow-tooltip />
+        <el-table-column prop="cameraCount" label="摄像头数" width="100" align="center">
+          <template #default="{ row }">
+            <el-tag type="info">{{ row.cameraCount || 0 }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="enabled" label="启用" width="80" align="center">
+          <template #default="{ row }">
+            <el-tag :type="row.enabled ? 'success' : 'info'">
+              {{ row.enabled ? '是' : '否' }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="createdAt" label="创建时间" width="170" align="center" />
+        <el-table-column label="操作" width="150" align="center" fixed="right">
+          <template #default="{ row }">
+            <el-button type="primary" link :icon="Edit" :data-id="`btn-edit-${row.machineId}`" @click="handleEdit(row)">
+              编辑
+            </el-button>
+            <el-button
+              type="danger"
+              link
+              :icon="Delete"
+              :disabled="deleteLoading"
+              :data-id="`btn-delete-${row.machineId}`"
+              @click="handleDelete(row)"
+            >
+              删除
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
 
     <!-- 分页 -->
     <div class="pagination-container">
@@ -101,7 +144,7 @@
 <script setup lang="ts">
 import { ref, reactive, onMounted, computed } from 'vue'
 import { ElMessage, ElMessageBox, type FormInstance, type FormRules } from 'element-plus'
-import { Refresh, Plus, Edit, Delete } from '@element-plus/icons-vue'
+import { Plus, Edit, Delete, Search, RefreshRight } from '@element-plus/icons-vue'
 import { listMachines, addMachine, updateMachine, deleteMachine } from '@/api/machine'
 import type { MachineDTO, MachineAddRequest, MachineUpdateRequest } from '@/types'
 
@@ -112,16 +155,57 @@ const machineList = ref<MachineDTO[]>([])
 const dialogVisible = ref(false)
 const formRef = ref<FormInstance>()
 
+// 搜索表单
+const searchForm = reactive<{
+  machineId: string
+  name: string
+  enabled: boolean | ''
+  dateRange: [string, string] | null
+}>({
+  machineId: '',
+  name: '',
+  enabled: '',
+  dateRange: null
+})
+
 // 分页相关
 const currentPage = ref(1)
-const pageSize = ref(10)
-const total = computed(() => machineList.value.length)
+const pageSize = ref(30)
+
+// 过滤后的数据
+const filteredList = computed(() => {
+  return machineList.value.filter((item) => {
+    // 机器ID过滤
+    if (searchForm.machineId && !item.machineId.toLowerCase().includes(searchForm.machineId.toLowerCase())) {
+      return false
+    }
+    // 名称过滤
+    if (searchForm.name && !item.name.toLowerCase().includes(searchForm.name.toLowerCase())) {
+      return false
+    }
+    // 启用状态过滤
+    if (searchForm.enabled !== '' && item.enabled !== searchForm.enabled) {
+      return false
+    }
+    // 时间范围过滤
+    if (searchForm.dateRange && searchForm.dateRange.length === 2) {
+      const itemDate = item.createdAt ? item.createdAt.split('T')[0] : ''
+      const [startDate, endDate] = searchForm.dateRange
+      if (itemDate < startDate || itemDate > endDate) {
+        return false
+      }
+    }
+    return true
+  })
+})
+
+const total = computed(() => filteredList.value.length)
 
 // 分页后的数据
 const paginatedList = computed(() => {
   const start = (currentPage.value - 1) * pageSize.value
   const end = start + pageSize.value
-  return machineList.value.slice(start, end)
+  return filteredList.value.slice(start, end)
 })
 
 const form = reactive<{
@@ -159,6 +243,18 @@ async function getList() {
   }
 }
 
+function handleSearch() {
+  currentPage.value = 1 // 搜索时重置到第一页
+}
+
+function handleReset() {
+  searchForm.machineId = ''
+  searchForm.name = ''
+  searchForm.enabled = ''
+  searchForm.dateRange = null
+  currentPage.value = 1
+}
+
 function handleAdd() {
   Object.assign(form, {
     id: undefined,
@@ -263,16 +359,117 @@ onMounted(() => {
 
 <style lang="scss" scoped>
 .page-container {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
   padding: 20px;
+  box-sizing: border-box;
+  overflow: hidden;
 }
 
-.table-actions {
-  margin-bottom: 15px;
+.search-form {
+  flex-shrink: 0;
+  margin-bottom: 16px;
+  padding: 16px;
+  background: #f5f7fa;
+
+  :deep(.el-form-item) {
+    margin-bottom: 0;
+    margin-right: 16px;
+  }
+
+  :deep(.el-input),
+  :deep(.el-select) {
+    width: 160px;
+  }
+
+  :deep(.el-date-editor--daterange) {
+    width: 280px;
+
+    .el-range-input {
+      width: 90px;
+    }
+
+    .el-range-separator {
+      width: 30px;
+    }
+  }
+
+  // Indigo 主题按钮
+  :deep(.el-button--primary) {
+    background-color: #4f46e5;
+    border-color: #4f46e5;
+
+    &:hover,
+    &:focus {
+      background-color: #6366f1;
+      border-color: #6366f1;
+    }
+  }
+}
+
+.table-wrapper {
+  flex: 1;
+  min-height: 0;
+  overflow: hidden;
 }
 
 .pagination-container {
+  flex-shrink: 0;
   display: flex;
   justify-content: flex-end;
-  margin-top: 16px;
+  padding-top: 16px;
+
+  // Indigo 主题分页
+  :deep(.el-pagination) {
+    .el-pager li.is-active {
+      background-color: #4f46e5;
+      color: #fff;
+    }
+
+    .el-pager li:hover {
+      color: #4f46e5;
+    }
+
+    .btn-prev:hover,
+    .btn-next:hover {
+      color: #4f46e5;
+    }
+  }
+}
+
+// 表格样式
+:deep(.el-table) {
+  // 斑马纹对比度
+  --el-table-row-hover-bg-color: #f0f0ff;
+
+  .el-table__row--striped td.el-table__cell {
+    background-color: #f8f9fc;
+  }
+
+  // 表头样式
+  .el-table__header th {
+    background-color: #f5f7fa;
+    color: #333;
+    font-weight: 600;
+  }
+
+  // 链接颜色
+  .el-link--primary {
+    color: #4f46e5;
+
+    &:hover {
+      color: #6366f1;
+    }
+  }
+
+  // 主要按钮颜色
+  .el-button--primary.is-link {
+    color: #4f46e5;
+
+    &:hover {
+      color: #6366f1;
+    }
+  }
 }
 </style>