feat: 使用全局ip与port配置摄像头
This commit is contained in:
		@@ -5,19 +5,7 @@
 | 
			
		||||
      <div class="card bg-base-200 shadow-xl">
 | 
			
		||||
        <div class="card-body">
 | 
			
		||||
          <h2 class="card-title text-primary">
 | 
			
		||||
            <svg
 | 
			
		||||
              class="w-6 h-6"
 | 
			
		||||
              fill="none"
 | 
			
		||||
              stroke="currentColor"
 | 
			
		||||
              viewBox="0 0 24 24"
 | 
			
		||||
            >
 | 
			
		||||
              <path
 | 
			
		||||
                stroke-linecap="round"
 | 
			
		||||
                stroke-linejoin="round"
 | 
			
		||||
                stroke-width="2"
 | 
			
		||||
                d="M12 6V4m0 2a2 2 0 100 4m0-4a2 2 0 110 4m-6 8a2 2 0 100-4m0 4a2 2 0 100 4m0-4v2m0-6V4m6 6v10m6-2a2 2 0 100-4m0 4a2 2 0 100 4m0-4v2m0-6V4"
 | 
			
		||||
              />
 | 
			
		||||
            </svg>
 | 
			
		||||
            <Settings class="w-6 h-6" />
 | 
			
		||||
            控制面板
 | 
			
		||||
          </h2>
 | 
			
		||||
 | 
			
		||||
@@ -45,19 +33,7 @@
 | 
			
		||||
            <div class="stats shadow">
 | 
			
		||||
              <div class="stat">
 | 
			
		||||
                <div class="stat-figure text-secondary">
 | 
			
		||||
                  <svg
 | 
			
		||||
                    class="w-8 h-8"
 | 
			
		||||
                    fill="none"
 | 
			
		||||
                    stroke="currentColor"
 | 
			
		||||
                    viewBox="0 0 24 24"
 | 
			
		||||
                  >
 | 
			
		||||
                    <path
 | 
			
		||||
                      stroke-linecap="round"
 | 
			
		||||
                      stroke-linejoin="round"
 | 
			
		||||
                      stroke-width="2"
 | 
			
		||||
                      d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"
 | 
			
		||||
                    />
 | 
			
		||||
                  </svg>
 | 
			
		||||
                  <Video class="w-8 h-8" />
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="stat-title">视频规格</div>
 | 
			
		||||
                <div class="stat-value text-secondary">
 | 
			
		||||
@@ -71,19 +47,7 @@
 | 
			
		||||
            <div class="stats shadow">
 | 
			
		||||
              <div class="stat">
 | 
			
		||||
                <div class="stat-figure text-accent">
 | 
			
		||||
                  <svg
 | 
			
		||||
                    class="w-8 h-8"
 | 
			
		||||
                    fill="none"
 | 
			
		||||
                    stroke="currentColor"
 | 
			
		||||
                    viewBox="0 0 24 24"
 | 
			
		||||
                  >
 | 
			
		||||
                    <path
 | 
			
		||||
                      stroke-linecap="round"
 | 
			
		||||
                      stroke-linejoin="round"
 | 
			
		||||
                      stroke-width="2"
 | 
			
		||||
                      d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"
 | 
			
		||||
                    />
 | 
			
		||||
                  </svg>
 | 
			
		||||
                  <Users class="w-8 h-8" />
 | 
			
		||||
                </div>
 | 
			
		||||
                <div class="stat-title">连接数</div>
 | 
			
		||||
                <div class="stat-value text-accent">
 | 
			
		||||
@@ -124,70 +88,6 @@
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <!-- 摄像头配置 -->
 | 
			
		||||
          <div class="card bg-base-100 shadow-sm mt-4">
 | 
			
		||||
            <div class="card-body">
 | 
			
		||||
              <h3 class="card-title text-sm text-info">
 | 
			
		||||
                <svg
 | 
			
		||||
                  class="w-5 h-5"
 | 
			
		||||
                  fill="none"
 | 
			
		||||
                  stroke="currentColor"
 | 
			
		||||
                  viewBox="0 0 24 24"
 | 
			
		||||
                >
 | 
			
		||||
                  <path
 | 
			
		||||
                    stroke-linecap="round"
 | 
			
		||||
                    stroke-linejoin="round"
 | 
			
		||||
                    stroke-width="2"
 | 
			
		||||
                    d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
 | 
			
		||||
                  />
 | 
			
		||||
                  <path
 | 
			
		||||
                    stroke-linecap="round"
 | 
			
		||||
                    stroke-linejoin="round"
 | 
			
		||||
                    stroke-width="2"
 | 
			
		||||
                    d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
 | 
			
		||||
                  />
 | 
			
		||||
                </svg>
 | 
			
		||||
                摄像头配置
 | 
			
		||||
              </h3>
 | 
			
		||||
              
 | 
			
		||||
              <div class="flex flex-row justify-around gap-4">
 | 
			
		||||
                <div class="grow">
 | 
			
		||||
                  <IpInputField
 | 
			
		||||
                    v-model="tempCameraConfig.address"
 | 
			
		||||
                    required
 | 
			
		||||
                  />
 | 
			
		||||
                </div>
 | 
			
		||||
 | 
			
		||||
                <div class="grow">
 | 
			
		||||
                  <PortInputField
 | 
			
		||||
                    v-model="tempCameraConfig.port"
 | 
			
		||||
                    required
 | 
			
		||||
                  />
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
 | 
			
		||||
              <div class="card-actions justify-end mt-4">
 | 
			
		||||
                <button
 | 
			
		||||
                  class="btn btn-ghost"
 | 
			
		||||
                  @click="resetCameraConfig"
 | 
			
		||||
                  :disabled="isDefaultCamera"
 | 
			
		||||
                >
 | 
			
		||||
                  <RotateCcw class="w-4 h-4" />
 | 
			
		||||
                  重置
 | 
			
		||||
                </button>
 | 
			
		||||
                <button
 | 
			
		||||
                  class="btn btn-primary"
 | 
			
		||||
                  @click="confirmCameraConfig"
 | 
			
		||||
                  :disabled="!isValidCameraConfig || !hasChangesCamera"
 | 
			
		||||
                  :class="{ loading: configuring }"
 | 
			
		||||
                >
 | 
			
		||||
                  <Save class="w-4 h-4" v-if="!configuring" />
 | 
			
		||||
                  {{ configuring ? "配置中..." : "保存配置" }}
 | 
			
		||||
                </button>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
 | 
			
		||||
          <!-- 操作按钮 -->
 | 
			
		||||
          <div class="card-actions justify-end mt-4">
 | 
			
		||||
            <button
 | 
			
		||||
@@ -195,26 +95,8 @@
 | 
			
		||||
              @click="refreshStatus"
 | 
			
		||||
              :disabled="loading"
 | 
			
		||||
            >
 | 
			
		||||
              <svg
 | 
			
		||||
                v-if="loading"
 | 
			
		||||
                class="animate-spin h-4 w-4 mr-2"
 | 
			
		||||
                fill="none"
 | 
			
		||||
                viewBox="0 0 24 24"
 | 
			
		||||
              >
 | 
			
		||||
                <circle
 | 
			
		||||
                  class="opacity-25"
 | 
			
		||||
                  cx="12"
 | 
			
		||||
                  cy="12"
 | 
			
		||||
                  r="10"
 | 
			
		||||
                  stroke="currentColor"
 | 
			
		||||
                  stroke-width="4"
 | 
			
		||||
                ></circle>
 | 
			
		||||
                <path
 | 
			
		||||
                  class="opacity-75"
 | 
			
		||||
                  fill="currentColor"
 | 
			
		||||
                  d="m4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
 | 
			
		||||
                ></path>
 | 
			
		||||
              </svg>
 | 
			
		||||
              <RefreshCw v-if="loading" class="animate-spin h-4 w-4 mr-2" />
 | 
			
		||||
              <RefreshCw v-else class="h-4 w-4 mr-2" />
 | 
			
		||||
              {{ loading ? "刷新中..." : "刷新状态" }}
 | 
			
		||||
            </button>
 | 
			
		||||
            <button
 | 
			
		||||
@@ -222,26 +104,8 @@
 | 
			
		||||
              @click="testConnection"
 | 
			
		||||
              :disabled="testing"
 | 
			
		||||
            >
 | 
			
		||||
              <svg
 | 
			
		||||
                v-if="testing"
 | 
			
		||||
                class="animate-spin h-4 w-4 mr-2"
 | 
			
		||||
                fill="none"
 | 
			
		||||
                viewBox="0 0 24 24"
 | 
			
		||||
              >
 | 
			
		||||
                <circle
 | 
			
		||||
                  class="opacity-25"
 | 
			
		||||
                  cx="12"
 | 
			
		||||
                  cy="12"
 | 
			
		||||
                  r="10"
 | 
			
		||||
                  stroke="currentColor"
 | 
			
		||||
                  stroke-width="4"
 | 
			
		||||
                ></circle>
 | 
			
		||||
                <path
 | 
			
		||||
                  class="opacity-75"
 | 
			
		||||
                  fill="currentColor"
 | 
			
		||||
                  d="m4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
 | 
			
		||||
                ></path>
 | 
			
		||||
              </svg>
 | 
			
		||||
              <RefreshCw v-if="testing" class="animate-spin h-4 w-4 mr-2" />
 | 
			
		||||
              <TestTube v-else class="h-4 w-4 mr-2" />
 | 
			
		||||
              {{ testing ? "测试中..." : "测试连接" }}
 | 
			
		||||
            </button>
 | 
			
		||||
          </div>
 | 
			
		||||
@@ -252,19 +116,7 @@
 | 
			
		||||
      <div class="card bg-base-200 shadow-xl">
 | 
			
		||||
        <div class="card-body">
 | 
			
		||||
          <h2 class="card-title text-primary">
 | 
			
		||||
            <svg
 | 
			
		||||
              class="w-6 h-6"
 | 
			
		||||
              fill="none"
 | 
			
		||||
              stroke="currentColor"
 | 
			
		||||
              viewBox="0 0 24 24"
 | 
			
		||||
            >
 | 
			
		||||
              <path
 | 
			
		||||
                stroke-linecap="round"
 | 
			
		||||
                stroke-linejoin="round"
 | 
			
		||||
                stroke-width="2"
 | 
			
		||||
                d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"
 | 
			
		||||
              />
 | 
			
		||||
            </svg>
 | 
			
		||||
            <Video class="w-6 h-6" />
 | 
			
		||||
            视频预览
 | 
			
		||||
          </h2>
 | 
			
		||||
 | 
			
		||||
@@ -294,20 +146,7 @@
 | 
			
		||||
              <div class="card bg-error text-white shadow-lg w-full max-w-lg">
 | 
			
		||||
                <div class="card-body">
 | 
			
		||||
                  <h3 class="card-title flex items-center gap-2">
 | 
			
		||||
                    <svg
 | 
			
		||||
                      xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
                      class="h-6 w-6"
 | 
			
		||||
                      fill="none"
 | 
			
		||||
                      viewBox="0 0 24 24"
 | 
			
		||||
                      stroke="currentColor"
 | 
			
		||||
                    >
 | 
			
		||||
                      <path
 | 
			
		||||
                        stroke-linecap="round"
 | 
			
		||||
                        stroke-linejoin="round"
 | 
			
		||||
                        stroke-width="2"
 | 
			
		||||
                        d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
 | 
			
		||||
                      />
 | 
			
		||||
                    </svg>
 | 
			
		||||
                    <AlertTriangle class="h-6 w-6" />
 | 
			
		||||
                    视频流加载失败
 | 
			
		||||
                  </h3>
 | 
			
		||||
                  <p>无法连接到视频服务器,请检查以下内容:</p>
 | 
			
		||||
@@ -334,19 +173,7 @@
 | 
			
		||||
              class="absolute inset-0 flex items-center justify-center text-white"
 | 
			
		||||
            >
 | 
			
		||||
              <div class="text-center">
 | 
			
		||||
                <svg
 | 
			
		||||
                  class="w-16 h-16 mx-auto mb-4 opacity-50"
 | 
			
		||||
                  fill="none"
 | 
			
		||||
                  stroke="currentColor"
 | 
			
		||||
                  viewBox="0 0 24 24"
 | 
			
		||||
                >
 | 
			
		||||
                  <path
 | 
			
		||||
                    stroke-linecap="round"
 | 
			
		||||
                    stroke-linejoin="round"
 | 
			
		||||
                    stroke-width="2"
 | 
			
		||||
                    d="M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z"
 | 
			
		||||
                  />
 | 
			
		||||
                </svg>
 | 
			
		||||
                <Video class="w-16 h-16 mx-auto mb-4 opacity-50" />
 | 
			
		||||
                <p class="text-lg opacity-75">{{ videoStatus }}</p>
 | 
			
		||||
                <p class="text-sm opacity-60 mt-2">
 | 
			
		||||
                  点击"播放视频流"按钮开始查看实时视频
 | 
			
		||||
@@ -370,20 +197,7 @@
 | 
			
		||||
                  role="button"
 | 
			
		||||
                  class="btn btn-sm btn-outline btn-accent"
 | 
			
		||||
                >
 | 
			
		||||
                  <svg
 | 
			
		||||
                    class="w-4 h-4 mr-1"
 | 
			
		||||
                    xmlns="http://www.w3.org/2000/svg"
 | 
			
		||||
                    fill="none"
 | 
			
		||||
                    viewBox="0 0 24 24"
 | 
			
		||||
                    stroke="currentColor"
 | 
			
		||||
                  >
 | 
			
		||||
                    <path
 | 
			
		||||
                      stroke-linecap="round"
 | 
			
		||||
                      stroke-linejoin="round"
 | 
			
		||||
                      stroke-width="2"
 | 
			
		||||
                      d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
 | 
			
		||||
                    />
 | 
			
		||||
                  </svg>
 | 
			
		||||
                  <MoreHorizontal class="w-4 h-4 mr-1" />
 | 
			
		||||
                  更多功能
 | 
			
		||||
                </div>
 | 
			
		||||
                <ul
 | 
			
		||||
@@ -391,15 +205,22 @@
 | 
			
		||||
                  class="dropdown-content z-[1] menu p-2 shadow bg-base-200 rounded-box w-52"
 | 
			
		||||
                >
 | 
			
		||||
                  <li>
 | 
			
		||||
                    <a @click="openInNewTab(streamInfo.htmlUrl)"
 | 
			
		||||
                      >在新标签打开视频页面</a
 | 
			
		||||
                    >
 | 
			
		||||
                    <a @click="openInNewTab(streamInfo.htmlUrl)">
 | 
			
		||||
                      <ExternalLink class="w-4 h-4" />
 | 
			
		||||
                      在新标签打开视频页面
 | 
			
		||||
                    </a>
 | 
			
		||||
                  </li>
 | 
			
		||||
                  <li><a @click="takeSnapshot">获取并下载快照</a></li>
 | 
			
		||||
                  <li>
 | 
			
		||||
                    <a @click="copyToClipboard(streamInfo.mjpegUrl)"
 | 
			
		||||
                      >复制MJPEG地址</a
 | 
			
		||||
                    >
 | 
			
		||||
                    <a @click="takeSnapshot">
 | 
			
		||||
                      <Camera class="w-4 h-4" />
 | 
			
		||||
                      获取并下载快照
 | 
			
		||||
                    </a>
 | 
			
		||||
                  </li>
 | 
			
		||||
                  <li>
 | 
			
		||||
                    <a @click="copyToClipboard(streamInfo.mjpegUrl)">
 | 
			
		||||
                      <Copy class="w-4 h-4" />
 | 
			
		||||
                      复制MJPEG地址
 | 
			
		||||
                    </a>
 | 
			
		||||
                  </li>
 | 
			
		||||
                </ul>
 | 
			
		||||
              </div>
 | 
			
		||||
@@ -408,25 +229,7 @@
 | 
			
		||||
                @click="startStream"
 | 
			
		||||
                :disabled="isPlaying"
 | 
			
		||||
              >
 | 
			
		||||
                <svg
 | 
			
		||||
                  class="w-4 h-4 mr-1"
 | 
			
		||||
                  fill="none"
 | 
			
		||||
                  stroke="currentColor"
 | 
			
		||||
                  viewBox="0 0 24 24"
 | 
			
		||||
                >
 | 
			
		||||
                  <path
 | 
			
		||||
                    stroke-linecap="round"
 | 
			
		||||
                    stroke-linejoin="round"
 | 
			
		||||
                    stroke-width="2"
 | 
			
		||||
                    d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z"
 | 
			
		||||
                  />
 | 
			
		||||
                  <path
 | 
			
		||||
                    stroke-linecap="round"
 | 
			
		||||
                    stroke-linejoin="round"
 | 
			
		||||
                    stroke-width="2"
 | 
			
		||||
                    d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
 | 
			
		||||
                  />
 | 
			
		||||
                </svg>
 | 
			
		||||
                <Play class="w-4 h-4 mr-1" />
 | 
			
		||||
                播放视频流
 | 
			
		||||
              </button>
 | 
			
		||||
              <button
 | 
			
		||||
@@ -434,25 +237,7 @@
 | 
			
		||||
                @click="stopStream"
 | 
			
		||||
                :disabled="!isPlaying"
 | 
			
		||||
              >
 | 
			
		||||
                <svg
 | 
			
		||||
                  class="w-4 h-4 mr-1"
 | 
			
		||||
                  fill="none"
 | 
			
		||||
                  stroke="currentColor"
 | 
			
		||||
                  viewBox="0 0 24 24"
 | 
			
		||||
                >
 | 
			
		||||
                  <path
 | 
			
		||||
                    stroke-linecap="round"
 | 
			
		||||
                    stroke-linejoin="round"
 | 
			
		||||
                    stroke-width="2"
 | 
			
		||||
                    d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
 | 
			
		||||
                  />
 | 
			
		||||
                  <path
 | 
			
		||||
                    stroke-linecap="round"
 | 
			
		||||
                    stroke-linejoin="round"
 | 
			
		||||
                    stroke-width="2"
 | 
			
		||||
                    d="M9 10a1 1 0 011-1h4a1 1 0 011 1v4a1 1 0 01-1 1h-4a1 1 0 01-1-1v-4z"
 | 
			
		||||
                  />
 | 
			
		||||
                </svg>
 | 
			
		||||
                <Square class="w-4 h-4 mr-1" />
 | 
			
		||||
                停止视频流
 | 
			
		||||
              </button>
 | 
			
		||||
            </div>
 | 
			
		||||
@@ -464,19 +249,7 @@
 | 
			
		||||
      <div class="card bg-base-200 shadow-xl">
 | 
			
		||||
        <div class="card-body">
 | 
			
		||||
          <h2 class="card-title text-primary">
 | 
			
		||||
            <svg
 | 
			
		||||
              class="w-6 h-6"
 | 
			
		||||
              fill="none"
 | 
			
		||||
              stroke="currentColor"
 | 
			
		||||
              viewBox="0 0 24 24"
 | 
			
		||||
            >
 | 
			
		||||
              <path
 | 
			
		||||
                stroke-linecap="round"
 | 
			
		||||
                stroke-linejoin="round"
 | 
			
		||||
                stroke-width="2"
 | 
			
		||||
                d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z"
 | 
			
		||||
              />
 | 
			
		||||
            </svg>
 | 
			
		||||
            <FileText class="w-6 h-6" />
 | 
			
		||||
            操作日志
 | 
			
		||||
          </h2>
 | 
			
		||||
 | 
			
		||||
@@ -511,21 +284,30 @@
 | 
			
		||||
</template>
 | 
			
		||||
 | 
			
		||||
<script setup lang="ts">
 | 
			
		||||
import { ref, computed, reactive, watch, onMounted, onUnmounted } from "vue";
 | 
			
		||||
import { useStorage } from "@vueuse/core";
 | 
			
		||||
import { z } from "zod";
 | 
			
		||||
import { ref, onMounted, onUnmounted } from "vue";
 | 
			
		||||
import {
 | 
			
		||||
  Save,
 | 
			
		||||
  RotateCcw,
 | 
			
		||||
  Settings,
 | 
			
		||||
  Video,
 | 
			
		||||
  Users,
 | 
			
		||||
  RefreshCw,
 | 
			
		||||
  TestTube,
 | 
			
		||||
  Play,
 | 
			
		||||
  Square,
 | 
			
		||||
  ExternalLink,
 | 
			
		||||
  Camera,
 | 
			
		||||
  Copy,
 | 
			
		||||
  FileText,
 | 
			
		||||
  AlertTriangle,
 | 
			
		||||
  MoreHorizontal,
 | 
			
		||||
} from "lucide-vue-next";
 | 
			
		||||
import { VideoStreamClient, CameraConfigRequest } from "@/APIClient";
 | 
			
		||||
import { IpInputField, PortInputField } from "@/components/InputField";
 | 
			
		||||
import { useEquipments } from "@/stores/equipments";
 | 
			
		||||
 | 
			
		||||
const eqps = useEquipments();
 | 
			
		||||
 | 
			
		||||
// 状态管理
 | 
			
		||||
const loading = ref(false);
 | 
			
		||||
const testing = ref(false);
 | 
			
		||||
const configuring = ref(false);
 | 
			
		||||
const testingCamera = ref(false);
 | 
			
		||||
const isPlaying = ref(false);
 | 
			
		||||
const hasVideoError = ref(false);
 | 
			
		||||
const videoStatus = ref('点击"播放视频流"按钮开始查看实时视频');
 | 
			
		||||
@@ -551,92 +333,6 @@ const streamInfo = ref({
 | 
			
		||||
  snapshotUrl: "",
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 摄像头配置类型定义
 | 
			
		||||
const cameraConfigSchema = z.object({
 | 
			
		||||
  address: z
 | 
			
		||||
    .string()
 | 
			
		||||
    .ip({ version: "v4", message: "请输入有效的IPv4地址" })
 | 
			
		||||
    .min(1, "请输入IP地址"),
 | 
			
		||||
  port: z
 | 
			
		||||
    .number()
 | 
			
		||||
    .int("端口必须是整数")
 | 
			
		||||
    .min(1, "端口必须大于0")
 | 
			
		||||
    .max(65535, "端口必须小于等于65535"),
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
type CameraConfig = z.infer<typeof cameraConfigSchema>;
 | 
			
		||||
 | 
			
		||||
// 默认摄像头配置
 | 
			
		||||
const defaultCameraConfig: CameraConfig = {
 | 
			
		||||
  address: "192.168.1.100",
 | 
			
		||||
  port: 8080,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 使用 VueUse 存储摄像头配置
 | 
			
		||||
const cameraConfig = useStorage<CameraConfig>(
 | 
			
		||||
  "camera-config",
 | 
			
		||||
  defaultCameraConfig,
 | 
			
		||||
  localStorage,
 | 
			
		||||
  {
 | 
			
		||||
    serializer: {
 | 
			
		||||
      read: (value: string) => {
 | 
			
		||||
        try {
 | 
			
		||||
          const parsed = JSON.parse(value);
 | 
			
		||||
          const result = cameraConfigSchema.safeParse(parsed);
 | 
			
		||||
          return result.success ? result.data : defaultCameraConfig;
 | 
			
		||||
        } catch {
 | 
			
		||||
          return defaultCameraConfig;
 | 
			
		||||
        }
 | 
			
		||||
      },
 | 
			
		||||
      write: (value: CameraConfig) => JSON.stringify(value),
 | 
			
		||||
    },
 | 
			
		||||
  },
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
// 临时摄像头配置(用于编辑)
 | 
			
		||||
const tempCameraConfig = reactive<CameraConfig>({
 | 
			
		||||
  address: cameraConfig.value.address,
 | 
			
		||||
  port: cameraConfig.value.port,
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 摄像头配置验证
 | 
			
		||||
const isValidCameraConfig = computed(() => {
 | 
			
		||||
  return tempCameraConfig.address && tempCameraConfig.port && 
 | 
			
		||||
         tempCameraConfig.port >= 1 && tempCameraConfig.port <= 65535 &&
 | 
			
		||||
         /^(\d{1,3}\.){3}\d{1,3}$/.test(tempCameraConfig.address);
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 检查摄像头配置是否有更改
 | 
			
		||||
const hasChangesCamera = computed(() => {
 | 
			
		||||
  return (
 | 
			
		||||
    tempCameraConfig.address !== cameraConfig.value.address || 
 | 
			
		||||
    tempCameraConfig.port !== cameraConfig.value.port
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
const isDefaultCamera = computed(() => {
 | 
			
		||||
  return (
 | 
			
		||||
    defaultCameraConfig.address === tempCameraConfig.address && 
 | 
			
		||||
    defaultCameraConfig.port === tempCameraConfig.port
 | 
			
		||||
  );
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
// 重置摄像头配置
 | 
			
		||||
const resetCameraConfig = () => {
 | 
			
		||||
  tempCameraConfig.address = defaultCameraConfig.address;
 | 
			
		||||
  tempCameraConfig.port = defaultCameraConfig.port;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 监听存储的摄像头配置变化,同步到临时配置
 | 
			
		||||
watch(
 | 
			
		||||
  cameraConfig,
 | 
			
		||||
  (newConfig) => {
 | 
			
		||||
    tempCameraConfig.address = newConfig.address;
 | 
			
		||||
    tempCameraConfig.port = newConfig.port;
 | 
			
		||||
  },
 | 
			
		||||
  { deep: true },
 | 
			
		||||
);
 | 
			
		||||
 | 
			
		||||
const currentVideoSource = ref("");
 | 
			
		||||
const logs = ref<Array<{ time: Date; level: string; message: string }>>([]);
 | 
			
		||||
 | 
			
		||||
@@ -656,71 +352,6 @@ const addLog = (level: string, message: string) => {
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 加载摄像头配置
 | 
			
		||||
const loadCameraConfig = async () => {
 | 
			
		||||
  try {
 | 
			
		||||
    addLog("info", "正在加载摄像头配置...");
 | 
			
		||||
 | 
			
		||||
    const config = await videoClient.getCameraConfig();
 | 
			
		||||
    if (config && config.address && config.port) {
 | 
			
		||||
      // 更新存储的配置
 | 
			
		||||
      cameraConfig.value = {
 | 
			
		||||
        address: config.address,
 | 
			
		||||
        port: config.port,
 | 
			
		||||
      };
 | 
			
		||||
      addLog("success", `摄像头配置加载成功: ${config.address}:${config.port}`);
 | 
			
		||||
    } else {
 | 
			
		||||
      addLog("warning", "未找到保存的摄像头配置,使用默认值");
 | 
			
		||||
    }
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    addLog("error", `加载摄像头配置失败: ${error}`);
 | 
			
		||||
    console.error("加载摄像头配置失败:", error);
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 确认摄像头配置
 | 
			
		||||
const confirmCameraConfig = async () => {
 | 
			
		||||
  if (!isValidCameraConfig.value) return;
 | 
			
		||||
 | 
			
		||||
  configuring.value = true;
 | 
			
		||||
  try {
 | 
			
		||||
    addLog(
 | 
			
		||||
      "info",
 | 
			
		||||
      `正在配置摄像头: ${tempCameraConfig.address}:${tempCameraConfig.port}`,
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    // 模拟保存延迟
 | 
			
		||||
    await new Promise((resolve) => setTimeout(resolve, 500));
 | 
			
		||||
 | 
			
		||||
    // 保存配置
 | 
			
		||||
    cameraConfig.value = {
 | 
			
		||||
      address: tempCameraConfig.address,
 | 
			
		||||
      port: tempCameraConfig.port,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    // 使用新配置调用API
 | 
			
		||||
    const result = await videoClient.configureCamera(
 | 
			
		||||
      CameraConfigRequest.fromJS({
 | 
			
		||||
        address: tempCameraConfig.address,
 | 
			
		||||
        port: tempCameraConfig.port,
 | 
			
		||||
      }),
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    if (result) {
 | 
			
		||||
      addLog("success", "摄像头配置保存成功");
 | 
			
		||||
      // 配置成功后刷新状态
 | 
			
		||||
      await refreshStatus();
 | 
			
		||||
    } else {
 | 
			
		||||
      addLog("error", "摄像头配置保存失败");
 | 
			
		||||
    }
 | 
			
		||||
  } catch (error) {
 | 
			
		||||
    addLog("error", `配置摄像头失败: ${error}`);
 | 
			
		||||
    console.error("配置摄像头失败:", error);
 | 
			
		||||
  } finally {
 | 
			
		||||
    configuring.value = false;
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// 格式化时间
 | 
			
		||||
const formatTime = (time: Date) => {
 | 
			
		||||
  return time.toLocaleTimeString();
 | 
			
		||||
@@ -798,6 +429,13 @@ const takeSnapshot = async () => {
 | 
			
		||||
const refreshStatus = async () => {
 | 
			
		||||
  loading.value = true;
 | 
			
		||||
  try {
 | 
			
		||||
    addLog("info", "正在配置并初始化摄像头...");
 | 
			
		||||
    const boardconfig = new CameraConfigRequest({
 | 
			
		||||
      address: eqps.boardAddr,
 | 
			
		||||
      port: eqps.boardPort,
 | 
			
		||||
    });
 | 
			
		||||
    await videoClient.configureCamera(boardconfig);
 | 
			
		||||
 | 
			
		||||
    addLog("info", "正在获取服务状态...");
 | 
			
		||||
 | 
			
		||||
    // 使用新的API方法名称
 | 
			
		||||
@@ -909,7 +547,6 @@ const stopStream = () => {
 | 
			
		||||
// 生命周期
 | 
			
		||||
onMounted(async () => {
 | 
			
		||||
  addLog("info", "HTTP 视频流页面已加载");
 | 
			
		||||
  await loadCameraConfig();
 | 
			
		||||
  await refreshStatus();
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user