Преглед изворни кода

refactor(live-stream): update search form structure and improve input handling

- Reorganized search form inputs to enhance clarity and usability, replacing keyword with streamSn and name fields.
- Added lssId selection for better filtering options.
- Updated search parameters and reset functionality to align with new input structure, improving overall search experience.
yb пре 6 дана
родитељ
комит
8a201b9512
2 измењених фајлова са 365 додато и 24 уклоњено
  1. 31 24
      src/views/live-stream/index.vue
  2. 334 0
      tests/e2e/live-stream.spec.ts

+ 31 - 24
src/views/live-stream/index.vue

@@ -3,23 +3,20 @@
     <!-- 搜索表单 -->
     <div class="search-form">
       <el-form :model="searchForm" inline>
-        <el-form-item :label="t('关键词')">
-          <el-input
-            v-model.trim="searchForm.keyword"
-            placeholder="搜索名称/流水号"
-            clearable
-            @keyup.enter="handleSearch"
-          />
+        <el-form-item>
+          <el-input v-model.trim="searchForm.streamSn" placeholder="stream sn" clearable @keyup.enter="handleSearch" />
         </el-form-item>
-        <el-form-item :label="t('设备ID')">
-          <el-input v-model.trim="searchForm.cameraId" placeholder="搜索设备ID" clearable @keyup.enter="handleSearch" />
+        <el-form-item>
+          <el-input v-model.trim="searchForm.name" placeholder="name" clearable @keyup.enter="handleSearch" />
         </el-form-item>
-        <el-form-item :label="t('状态')">
-          <el-select v-model="searchForm.enabled" placeholder="全部" clearable style="width: 120px">
-            <el-option :label="t('启用')" :value="true" />
-            <el-option :label="t('禁用')" :value="false" />
+        <el-form-item>
+          <el-select v-model="searchForm.lssId" placeholder="LSS" clearable filterable style="width: 180px">
+            <el-option v-for="lss in lssOptions" :key="lss.lssId" :label="lss.lssId" :value="lss.lssId" />
           </el-select>
         </el-form-item>
+        <el-form-item>
+          <el-input v-model.trim="searchForm.cameraId" placeholder="设备ID" clearable @keyup.enter="handleSearch" />
+        </el-form-item>
         <el-form-item>
           <el-button type="primary" :icon="Search" @click="handleSearch">{{ t('查询') }}</el-button>
           <el-button :icon="RefreshRight" @click="handleReset">{{ t('重置') }}</el-button>
@@ -508,13 +505,15 @@ const sortState = reactive<{
 
 // 搜索表单
 const searchForm = reactive<{
-  keyword: string
+  streamSn: string
+  name: string
+  lssId: string
   cameraId: string
-  enabled: boolean | null
 }>({
-  keyword: '',
-  cameraId: '',
-  enabled: null
+  streamSn: '',
+  name: '',
+  lssId: '',
+  cameraId: ''
 })
 
 // 分页相关
@@ -561,11 +560,17 @@ async function getList() {
       page: currentPage.value,
       size: pageSize.value
     }
-    if (searchForm.keyword) {
-      params.keyword = searchForm.keyword
+    if (searchForm.streamSn) {
+      params.streamSn = searchForm.streamSn
+    }
+    if (searchForm.name) {
+      params.name = searchForm.name
+    }
+    if (searchForm.lssId) {
+      params.lssId = searchForm.lssId
     }
-    if (searchForm.enabled !== null) {
-      params.enabled = searchForm.enabled
+    if (searchForm.cameraId) {
+      params.cameraId = searchForm.cameraId
     }
     if (sortState.prop && sortState.order) {
       params.sortBy = sortState.prop
@@ -621,8 +626,10 @@ function handleSearch() {
 }
 
 function handleReset() {
-  searchForm.keyword = ''
-  searchForm.enabled = null
+  searchForm.streamSn = ''
+  searchForm.name = ''
+  searchForm.lssId = ''
+  searchForm.cameraId = ''
   currentPage.value = 1
   sortState.prop = ''
   sortState.order = null

+ 334 - 0
tests/e2e/live-stream.spec.ts

@@ -0,0 +1,334 @@
+import { test, expect, type Page } from '@playwright/test'
+
+// 测试账号配置
+const TEST_USERNAME = process.env.TEST_USERNAME || 'admin'
+const TEST_PASSWORD = process.env.TEST_PASSWORD || '123456'
+
+// 登录辅助函数
+async function login(page: Page) {
+  await page.goto('/login')
+  await page.evaluate(() => {
+    localStorage.clear()
+    document.cookie.split(';').forEach((c) => {
+      document.cookie = c.replace(/^ +/, '').replace(/=.*/, '=;expires=' + new Date().toUTCString() + ';path=/')
+    })
+  })
+  await page.reload()
+
+  await page.getByPlaceholder('用户名').fill(TEST_USERNAME)
+  await page.getByPlaceholder('密码').fill(TEST_PASSWORD)
+  await page.getByRole('button', { name: '登录' }).click()
+  await expect(page).not.toHaveURL(/\/login/, { timeout: 15000 })
+}
+
+test.describe('LiveStream 管理 - 搜索功能测试', () => {
+  /**
+   * 按 Stream SN 搜索
+   */
+  test('按 Stream SN 搜索 - 验证参数传递', async ({ page }) => {
+    await login(page)
+    await page.goto('/live-stream')
+
+    // 等待表格加载
+    await page.waitForSelector('tbody tr', { timeout: 10000 })
+
+    // 设置 API 拦截来验证参数
+    let requestBody: any = null
+    await page.route('**/admin/live-stream/list', async (route) => {
+      const request = route.request()
+      if (request.method() === 'POST') {
+        requestBody = request.postDataJSON()
+      }
+      await route.continue()
+    })
+
+    // 在 stream sn 搜索框输入
+    await page.getByPlaceholder('stream sn').fill('stream_123')
+
+    // 点击查询
+    await page.getByRole('button', { name: '查询' }).click()
+    await page.waitForTimeout(1000)
+
+    // 验证请求参数中包含 streamSn
+    expect(requestBody).not.toBeNull()
+    expect(requestBody.streamSn).toBe('stream_123')
+  })
+
+  /**
+   * 按 Name 搜索
+   */
+  test('按 Name 搜索 - 验证参数传递', async ({ page }) => {
+    await login(page)
+    await page.goto('/live-stream')
+
+    // 等待表格加载
+    await page.waitForSelector('tbody tr', { timeout: 10000 })
+
+    // 设置 API 拦截
+    let requestBody: any = null
+    await page.route('**/admin/live-stream/list', async (route) => {
+      const request = route.request()
+      if (request.method() === 'POST') {
+        requestBody = request.postDataJSON()
+      }
+      await route.continue()
+    })
+
+    // 在 name 搜索框输入
+    await page.getByPlaceholder('name').fill('测试流')
+
+    // 点击查询
+    await page.getByRole('button', { name: '查询' }).click()
+    await page.waitForTimeout(1000)
+
+    // 验证请求参数中包含 name
+    expect(requestBody).not.toBeNull()
+    expect(requestBody.name).toBe('测试流')
+  })
+
+  /**
+   * 按 LSS 搜索
+   */
+  test('按 LSS 搜索 - 验证参数传递', async ({ page }) => {
+    await login(page)
+    await page.goto('/live-stream')
+
+    // 等待表格加载
+    await page.waitForSelector('tbody tr', { timeout: 10000 })
+
+    // 设置 API 拦截
+    let requestBody: any = null
+    await page.route('**/admin/live-stream/list', async (route) => {
+      const request = route.request()
+      if (request.method() === 'POST') {
+        requestBody = request.postDataJSON()
+      }
+      await route.continue()
+    })
+
+    // 点击 LSS 下拉框
+    await page.locator('.el-select').filter({ hasText: 'LSS' }).click()
+    await page.waitForTimeout(300)
+
+    // 选择第一个 LSS 选项(如果有)
+    const firstOption = page.locator('.el-select-dropdown__item').first()
+    if (await firstOption.isVisible()) {
+      const lssValue = await firstOption.textContent()
+      await firstOption.click()
+
+      // 点击查询
+      await page.getByRole('button', { name: '查询' }).click()
+      await page.waitForTimeout(1000)
+
+      // 验证请求参数中包含 lssId
+      expect(requestBody).not.toBeNull()
+      expect(requestBody.lssId).toBe(lssValue?.trim())
+    }
+  })
+
+  /**
+   * 按设备ID搜索
+   */
+  test('按设备ID搜索 - 验证参数传递', async ({ page }) => {
+    await login(page)
+    await page.goto('/live-stream')
+
+    // 等待表格加载
+    await page.waitForSelector('tbody tr', { timeout: 10000 })
+
+    // 设置 API 拦截
+    let requestBody: any = null
+    await page.route('**/admin/live-stream/list', async (route) => {
+      const request = route.request()
+      if (request.method() === 'POST') {
+        requestBody = request.postDataJSON()
+      }
+      await route.continue()
+    })
+
+    // 在设备ID搜索框输入
+    await page.getByPlaceholder('设备ID').fill('EEE1')
+
+    // 点击查询
+    await page.getByRole('button', { name: '查询' }).click()
+    await page.waitForTimeout(1000)
+
+    // 验证请求参数中包含 cameraId
+    expect(requestBody).not.toBeNull()
+    expect(requestBody.cameraId).toBe('EEE1')
+  })
+
+  /**
+   * 组合搜索 - 所有字段
+   */
+  test('组合搜索 - 多字段同时搜索', async ({ page }) => {
+    await login(page)
+    await page.goto('/live-stream')
+
+    // 等待表格加载
+    await page.waitForSelector('tbody tr', { timeout: 10000 })
+
+    // 设置 API 拦截
+    let requestBody: any = null
+    await page.route('**/admin/live-stream/list', async (route) => {
+      const request = route.request()
+      if (request.method() === 'POST') {
+        requestBody = request.postDataJSON()
+      }
+      await route.continue()
+    })
+
+    // 填入所有搜索条件
+    await page.getByPlaceholder('stream sn').fill('stream_001')
+    await page.getByPlaceholder('name').fill('测试')
+    await page.getByPlaceholder('设备ID').fill('EEE1')
+
+    // 点击查询
+    await page.getByRole('button', { name: '查询' }).click()
+    await page.waitForTimeout(1000)
+
+    // 验证请求参数包含所有字段
+    expect(requestBody).not.toBeNull()
+    expect(requestBody.streamSn).toBe('stream_001')
+    expect(requestBody.name).toBe('测试')
+    expect(requestBody.cameraId).toBe('EEE1')
+  })
+
+  /**
+   * 重置搜索条件
+   */
+  test('重置搜索条件 - 清空所有输入', async ({ page }) => {
+    await login(page)
+    await page.goto('/live-stream')
+
+    // 填入搜索条件
+    await page.getByPlaceholder('stream sn').fill('test-sn')
+    await page.getByPlaceholder('name').fill('test-name')
+    await page.getByPlaceholder('设备ID').fill('test-device')
+
+    // 点击重置
+    await page.getByRole('button', { name: '重置' }).click()
+    await page.waitForTimeout(300)
+
+    // 验证搜索条件已清空
+    await expect(page.getByPlaceholder('stream sn')).toHaveValue('')
+    await expect(page.getByPlaceholder('name')).toHaveValue('')
+    await expect(page.getByPlaceholder('设备ID')).toHaveValue('')
+  })
+})
+
+test.describe('LiveStream 管理 - BUG 回归测试', () => {
+  /**
+   * BUG 回归测试:按设备ID搜索应该只返回匹配的记录
+   *
+   * 问题描述:
+   * - 前端已修复:searchForm.cameraId 参数现在会正确传递给 API
+   * - 后端待修复:API 目前未实现 cameraId 过滤,返回所有数据
+   *
+   * 预期行为:
+   * - 搜索设备ID "EEE1" 应该只返回 1 条记录
+   * - 该记录的设备ID列应该显示 "EEE1"
+   *
+   * 当前状态:测试会失败,等待后端实现过滤
+   */
+  test('BUG: 按设备ID搜索 EEE1 应该只返回 1 条匹配记录', async ({ page }) => {
+    await login(page)
+    await page.goto('/live-stream')
+
+    // 等待表格加载
+    await page.waitForSelector('tbody tr', { timeout: 10000 })
+    await page.waitForTimeout(500)
+
+    // 在设备ID搜索框输入 "EEE1"
+    await page.getByPlaceholder('设备ID').fill('EEE1')
+
+    // 点击查询按钮
+    await page.getByRole('button', { name: '查询' }).click()
+
+    // 等待搜索结果加载
+    await page.waitForTimeout(1000)
+
+    // 验证:应该只有 1 条记录
+    const tableRows = page.locator('tbody tr')
+    const rowCount = await tableRows.count()
+    expect(rowCount).toBe(1)
+
+    // 验证:该记录的设备ID应该是 "EEE1"
+    const firstRowDeviceId = await tableRows.first().locator('td').nth(3).textContent()
+    expect(firstRowDeviceId?.trim()).toBe('EEE1')
+
+    // 验证:分页显示 Total 1
+    await expect(page.locator('.el-pagination')).toContainText('Total 1')
+  })
+})
+
+test.describe('LiveStream 管理 - 页面功能测试', () => {
+  test('LiveStream 管理页面正确显示', async ({ page }) => {
+    await login(page)
+    await page.goto('/live-stream')
+
+    // 验证页面标题
+    await expect(page.locator('text=LiveStream 管理')).toBeVisible()
+
+    // 验证搜索表单元素
+    await expect(page.getByPlaceholder('stream sn')).toBeVisible()
+    await expect(page.getByPlaceholder('name')).toBeVisible()
+    await expect(page.getByPlaceholder('设备ID')).toBeVisible()
+    await expect(page.getByRole('button', { name: '查询' })).toBeVisible()
+    await expect(page.getByRole('button', { name: '重置' })).toBeVisible()
+    await expect(page.getByRole('button', { name: '新增' })).toBeVisible()
+
+    // 验证表头
+    await expect(page.locator('th:has-text("Stream SN"), th:has-text("stream sn")')).toBeVisible()
+    await expect(page.locator('th:has-text("Name"), th:has-text("名称")')).toBeVisible()
+    await expect(page.locator('th:has-text("LSS")')).toBeVisible()
+    await expect(page.locator('th:has-text("Device ID"), th:has-text("设备ID")')).toBeVisible()
+  })
+
+  test('打开新增 LiveStream 抽屉', async ({ page }) => {
+    await login(page)
+    await page.goto('/live-stream')
+
+    // 点击新增按钮
+    await page.getByRole('button', { name: '新增' }).click()
+
+    // 验证抽屉打开
+    const drawer = page.locator('.el-drawer').filter({ hasText: '新增 Live Stream' })
+    await expect(drawer).toBeVisible({ timeout: 5000 })
+
+    // 验证表单元素
+    await expect(drawer.locator('label:has-text("名称")')).toBeVisible()
+    await expect(drawer.locator('label:has-text("LSS 节点")')).toBeVisible()
+    await expect(drawer.locator('label:has-text("摄像头")')).toBeVisible()
+
+    // 关闭抽屉
+    await drawer.getByRole('button', { name: '取消' }).click()
+    await expect(drawer).not.toBeVisible({ timeout: 5000 })
+  })
+
+  test('分页功能正常', async ({ page }) => {
+    await login(page)
+    await page.goto('/live-stream')
+
+    // 等待表格加载
+    await page.waitForTimeout(1000)
+
+    // 验证分页组件存在
+    const pagination = page.locator('.el-pagination')
+    await expect(pagination).toBeVisible()
+
+    // 验证 Total 显示
+    await expect(pagination.locator('text=/Total \\d+/')).toBeVisible()
+  })
+
+  test('从侧边栏导航到 LiveStream 管理', async ({ page }) => {
+    await login(page)
+
+    // 点击侧边栏 LiveStream 管理菜单项
+    await page.getByText('LiveStream 管理').first().click()
+
+    // 验证跳转到 LiveStream 管理页面
+    await expect(page).toHaveURL(/\/live-stream/)
+    await expect(page.locator('text=LiveStream 管理')).toBeVisible()
+  })
+})