Procházet zdrojové kódy

使用 gemini 优化 ui

reghao před 1 měsícem
rodič
revize
da7fc62f87
4 změnil soubory, kde provedl 672 přidání a 334 odebrání
  1. 254 190
      src/views/disk/CamDetail.vue
  2. 186 61
      src/views/disk/CamList.vue
  3. 17 0
      src/views/disk/Disk.vue
  4. 215 83
      src/views/disk/DiskShare.vue

+ 254 - 190
src/views/disk/CamDetail.vue

@@ -1,32 +1,43 @@
 <template>
   <div class="cam-detail-container">
-    <el-row :gutter="16">
+    <el-row :gutter="24">
       <el-col :md="17" :sm="24">
         <el-card class="video-main-card" shadow="never">
-          <div slot="header" class="card-header">
-            <div class="title-info">
-              <i class="el-icon-video-camera"></i>
-              <span class="cam-name">{{ camDetail ? camDetail.camName : '摄像头监控' }}</span>
-              <el-tag v-if="camDetail" :type="camDetail.onLive ? 'danger' : 'info'" size="mini" effect="dark" class="live-tag">
-                {{ camDetail.onLive ? '• LIVE 直播' : '回放中' }}
-              </el-tag>
+          <div class="video-header">
+            <div class="header-left">
+              <div class="cam-icon-box">
+                <i class="el-icon-video-camera" />
+              </div>
+              <div class="cam-meta">
+                <h2 class="cam-title">{{ camDetail ? camDetail.camName : '摄像头监控' }}</h2>
+                <div class="status-tags">
+                  <span v-if="camDetail" :class="['status-dot-tag', camDetail.onLive ? 'is-live' : 'is-playback']">
+                    <span class="pulse-dot" />
+                    {{ camDetail.onLive ? 'LIVE 实时画面' : '历史回放' }}
+                  </span>
+                  <span class="cam-id-tag">ID: {{ query.camId }}</span>
+                </div>
+              </div>
             </div>
-            <div class="header-actions">
-              <el-button icon="el-icon-share" type="primary" size="mini" round @click="onShareCam">分享</el-button>
+            <div class="header-right">
+              <el-button icon="el-icon-share" type="primary" round @click="onShareCam">授权分享</el-button>
+              <el-button icon="el-icon-refresh" circle @click="() => $router.go()" />
             </div>
           </div>
 
-          <div class="video-content-wrapper">
-            <div class="video-container">
+          <div class="player-viewport">
+            <div class="video-wrapper">
               <video
                 id="videoElement"
                 controls
                 autoplay
                 muted
-                playsinline
-                webkit-playsinline
                 class="video-element"
               />
+              <div v-if="!camDetail" class="video-loading">
+                <i class="el-icon-loading" />
+                <p>正在建立加密连接...</p>
+              </div>
             </div>
           </div>
         </el-card>
@@ -35,99 +46,88 @@
       <el-col :md="7" :sm="24">
         <el-card class="record-sidebar-card" shadow="never">
           <div slot="header" class="sidebar-header">
-            <div class="sidebar-title">
-              <i class="el-icon-collection"></i>
-              <span>历史录像</span>
-            </div>
-            <el-button type="primary" icon="el-icon-date" size="mini" plain @click="onSelectDate">切换日期</el-button>
+            <span class="sidebar-title">监控档案</span>
+            <el-button type="text" icon="el-icon-date" @click="onSelectDate">选择日期</el-button>
           </div>
 
-          <div class="record-list-info">
-            <div class="date-display">
-              <i class="el-icon-time"></i>
-              <span>{{ getYearMonthDay(calendarDate) }}</span>
-              <small>的监控档案</small>
+          <div class="current-date-bar">
+            <div class="date-info">
+              <span class="day">{{ calendarDate.getDate() }}</span>
+              <div class="month-year">
+                <span class="m">{{ calendarDate.getMonth() + 1 }}月</span>
+                <span class="y">{{ calendarDate.getFullYear() }}</span>
+              </div>
             </div>
+            <i class="el-icon-calendar" />
+          </div>
 
+          <div class="record-list-container">
             <el-table
+              v-loading="!dataList.length && loading"
               :data="dataList"
-              size="small"
-              height="460px"
-              class="custom-table"
-              :header-cell-style="{ background: '#f5f7fa', color: '#606266' }"
+              height="500"
+              class="record-table"
+              :show-header="false"
             >
-              <el-table-column prop="startTime" label="开始时间" width="90">
-                <template slot-scope="scope">
-                  <span class="time-text">{{ scope.row.startTime }}</span>
-                </template>
-              </el-table-column>
-              <el-table-column prop="duration" label="时长" align="center">
-                <template slot-scope="scope">
-                  <el-tag size="mini" type="info" plain>{{ scope.row.duration }}s</el-tag>
-                </template>
-              </el-table-column>
-              <el-table-column label="操作" width="80" align="right">
+              <el-table-column>
                 <template slot-scope="scope">
-                  <el-button
-                    type="primary"
-                    circle
-                    size="mini"
-                    icon="el-icon-caret-right"
-                    @click="handlePlay(scope.row.recordId)"
-                  ></el-button>
+                  <div class="record-item" @click="handlePlay(scope.row.recordId)">
+                    <div class="record-icon">
+                      <i class="el-icon-video-play" />
+                    </div>
+                    <div class="record-text">
+                      <div class="record-time">{{ scope.row.startTime }}</div>
+                      <div class="record-duration">持续时长 {{ scope.row.duration }}s</div>
+                    </div>
+                    <i class="el-icon-arrow-right arrow" />
+                  </div>
                 </template>
               </el-table-column>
             </el-table>
-
-            <div class="sidebar-footer">
-              <el-button type="warning" icon="el-icon-magic-stick" size="medium" class="full-width-btn" @click="onButtonSubmit">提交</el-button>
-            </div>
+            <el-empty v-if="!dataList.length" description="当日暂无录像" :image-size="60" />
           </div>
         </el-card>
       </el-col>
     </el-row>
 
     <el-dialog
-      title="选择监控日期"
-      append-to-body
+      title="切换监控日期"
       :visible.sync="showCalenderDialog"
-      width="600px"
-      custom-class="custom-calendar-dialog"
+      width="400px"
+      append-to-body
+      custom-class="pikpak-dialog"
     >
-      <div class="calendar-legend">
-        <span class="legend-item"><i class="dot active"></i> 有录像</span>
-        <span class="legend-item"><i class="dot"></i> 无数据</span>
-      </div>
-      <el-calendar v-model="calendarDate">
-        <div slot="dateCell" slot-scope="{ date, data }" class="custom-date-cell" @click="handleCellClick(date, data)">
-          <span :class="{ 'has-record': dealMyDate(data.day) }">
-            {{ data.day.split("-").slice(2).join() }}
-          </span>
-          <div v-if="dealMyDate(data.day)" class="record-dot"></div>
+      <el-calendar v-model="calendarDate" class="mini-calendar">
+        <div slot="dateCell" slot-scope="{ date, data }" class="cell-wrapper" @click="handleCellClick(date, data)">
+          <span :class="{ 'has-data': dealMyDate(data.day) }">{{ data.day.split('-').slice(2).join() }}</span>
+          <div v-if="dealMyDate(data.day)" class="data-dot" />
         </div>
       </el-calendar>
     </el-dialog>
 
     <el-dialog
-      title="分享摄像头权限"
+      title="授权给好友"
       :visible.sync="showCreateShareDialog"
-      width="420px"
-      center
+      width="380px"
+      append-to-body
+      custom-class="pikpak-dialog"
     >
-      <div class="share-dialog-body">
-        <el-form ref="createAlbumForm" :model="createShareForm" label-position="top">
-          <el-form-item label="授权给联系人">
-            <div class="user-check-list">
-              <el-checkbox-group v-model="createShareForm.shareToList">
-                <el-checkbox v-for="user in userContactList" :key="user.userIdStr" :label="user.username" border size="small" class="user-checkbox" />
-              </el-checkbox-group>
-            </div>
-          </el-form-item>
-        </el-form>
-        <div slot="footer" class="dialog-footer">
-          <el-button type="primary" block @click="createShare" style="width: 100%">确认分享</el-button>
+      <div class="share-box">
+        <p class="share-tip">请选择有权查看此摄像头画面的好友</p>
+        <div class="user-select-grid">
+          <el-checkbox-group v-model="createShareForm.shareToList">
+            <el-checkbox v-for="user in userContactList" :key="user.userIdStr" :label="user.username" border>
+              <div class="user-cell">
+                <el-avatar :size="24" icon="el-icon-user" />
+                <span>{{ user.username }}</span>
+              </div>
+            </el-checkbox>
+          </el-checkbox-group>
         </div>
       </div>
+      <div slot="footer">
+        <el-button type="primary" round block style="width: 100%" @click="createShare">确认授权</el-button>
+      </div>
     </el-dialog>
   </div>
 </template>
@@ -135,7 +135,7 @@
 <script>
 import flvjs from 'flv.js'
 
-import { createShare, getCamDetail, getCamList, getRecordByMonth, getRecordUrl, submitActivity } from '@/api/disk'
+import { createShare, getCamDetail, getRecordByMonth, getRecordUrl } from '@/api/disk'
 import { getUserContact } from '@/api/user'
 
 export default {
@@ -318,24 +318,6 @@ export default {
     onSelectDate() {
       this.showCalenderDialog = true
     },
-    onButtonSubmit() {
-      this.$confirm('确认提交?', '提示', {
-        confirmButtonText: '确定',
-        cancelButtonText: '取消',
-        type: 'warning'
-      }).then(() => {
-        submitActivity().then(resp => {
-          this.$message.info(resp.msg)
-        }).catch(error => {
-          this.$message.error(error.message)
-        })
-      }).catch(() => {
-        this.$message({
-          type: 'info',
-          message: '已取消'
-        })
-      })
-    },
     onShareCam() {
       this.createShareForm.albumId = this.camDetail.camId
       getUserContact(1).then(resp => {
@@ -365,167 +347,249 @@ export default {
 
 <style scoped>
 .cam-detail-container {
-  padding: 16px;
-  background-color: #f0f2f5;
-  min-height: 100vh;
+  padding: 24px;
 }
 
-/* 播放器卡片美化 */
+/* 播放器卡片 */
 .video-main-card {
-  border-radius: 8px;
+  border-radius: 20px;
   border: none;
+  background: #fff;
 }
-.card-header {
+
+.video-header {
   display: flex;
   justify-content: space-between;
   align-items: center;
+  padding: 10px 0 20px;
+}
+
+.header-left {
+  display: flex;
+  align-items: center;
+  gap: 16px;
 }
-.title-info {
+
+.cam-icon-box {
+  width: 48px;
+  height: 48px;
+  background: #e8f0fe;
+  border-radius: 12px;
   display: flex;
+  justify-content: center;
   align-items: center;
+  color: #1a73e8;
+  font-size: 24px;
+}
+
+.cam-title {
+  font-size: 18px;
+  font-weight: 700;
+  margin: 0 0 6px 0;
+  color: #1a1a1a;
 }
-.title-info i {
-  font-size: 20px;
-  color: #409eff;
-  margin-right: 8px;
+
+.status-tags {
+  display: flex;
+  gap: 8px;
+  align-items: center;
 }
-.cam-name {
+
+.status-dot-tag {
+  display: flex;
+  align-items: center;
+  padding: 2px 10px;
+  border-radius: 20px;
+  font-size: 12px;
   font-weight: 600;
-  font-size: 16px;
-  margin-right: 12px;
 }
-.live-tag {
-  letter-spacing: 1px;
+
+.is-live {
+  background: #fef0f0;
+  color: #f56c6c;
+}
+
+.is-playback {
+  background: #f4f4f5;
+  color: #909399;
+}
+
+.pulse-dot {
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  margin-right: 6px;
+  background: currentColor;
+}
+
+.is-live .pulse-dot {
+  animation: pulse 1.5s infinite;
+}
+
+@keyframes pulse {
+  0% { transform: scale(1); opacity: 1; }
+  50% { transform: scale(1.5); opacity: 0.5; }
+  100% { transform: scale(1); opacity: 1; }
 }
 
-.video-content-wrapper {
-  background-color: #000;
-  border-radius: 4px;
+.cam-id-tag {
+  font-size: 11px;
+  color: #c0c4cc;
+}
+
+.player-viewport {
+  background: #000;
+  border-radius: 16px;
   overflow: hidden;
+  box-shadow: inset 0 0 40px rgba(0,0,0,0.5);
 }
-.video-container {
+
+.video-wrapper {
   position: relative;
   width: 100%;
-  padding-top: 56.25%; /* 16:9 Aspect Ratio */
+  padding-top: 56.25%;
 }
+
 .video-element {
   position: absolute;
   top: 0;
   left: 0;
   width: 100%;
   height: 100%;
-  object-fit: contain;
 }
 
-/* 侧边栏美化 */
+/* 侧边栏档案 */
 .record-sidebar-card {
-  border-radius: 8px;
+  border-radius: 20px;
   border: none;
   height: 100%;
 }
+
 .sidebar-header {
   display: flex;
   justify-content: space-between;
   align-items: center;
 }
+
 .sidebar-title {
+  font-weight: 700;
+  font-size: 16px;
+}
+
+.current-date-bar {
   display: flex;
+  justify-content: space-between;
   align-items: center;
-  font-weight: 600;
-  color: #303133;
-}
-.sidebar-title i {
-  margin-right: 6px;
-  color: #909399;
+  padding: 16px;
+  background: #f8f9fa;
+  border-radius: 12px;
+  margin-bottom: 20px;
 }
-.date-display {
-  padding: 12px;
-  background: #fdf6ec;
-  border-radius: 4px;
-  margin-bottom: 12px;
-  color: #e6a23c;
+
+.date-info {
   display: flex;
   align-items: center;
-  gap: 8px;
-}
-.date-display small {
-  color: #909399;
+  gap: 12px;
 }
-.time-text {
-  font-family: 'Monaco', 'Courier New', monospace;
-  font-weight: 500;
+
+.date-info .day {
+  font-size: 32px;
+  font-weight: 800;
+  color: #1a73e8;
 }
-.sidebar-footer {
-  margin-top: 16px;
+
+.month-year {
+  display: flex;
+  flex-direction: column;
+  line-height: 1.2;
 }
-.full-width-btn {
-  width: 100%;
+
+.month-year .m { font-weight: 700; font-size: 14px; }
+.month-year .y { font-size: 11px; color: #909399; }
+
+.current-date-bar i {
+  font-size: 24px;
+  color: #dcdfe6;
 }
 
-/* 日历组件美化 */
-.custom-calendar-dialog >>> .el-dialog__body {
-  padding: 0 20px 20px 20px;
+.record-list-container {
+  margin: 0 -10px;
 }
-.calendar-legend {
-  text-align: right;
-  font-size: 12px;
-  margin-bottom: 10px;
+
+.record-item {
+  display: flex;
+  align-items: center;
+  padding: 12px;
+  cursor: pointer;
+  border-radius: 10px;
+  transition: all 0.2s;
 }
-.legend-item {
-  margin-left: 15px;
-  color: #909399;
+
+.record-item:hover {
+  background: #f0f7ff;
 }
-.dot {
-  display: inline-block;
-  width: 8px;
-  height: 8px;
+
+.record-icon {
+  width: 36px;
+  height: 36px;
   border-radius: 50%;
-  background: #dcdfe6;
+  background: #fff;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  margin-right: 12px;
+  box-shadow: 0 2px 6px rgba(0,0,0,0.05);
 }
-.dot.active {
-  background: #f56c6c;
+
+.record-icon i { color: #1a73e8; font-size: 18px; }
+
+.record-text { flex: 1; }
+.record-time { font-weight: 600; font-size: 14px; color: #303133; }
+.record-duration { font-size: 11px; color: #909399; margin-top: 2px; }
+
+.arrow { color: #dcdfe6; font-size: 14px; }
+
+/* 日历弹窗美化 */
+::v-deep .pikpak-dialog {
+  border-radius: 20px;
 }
 
-.custom-date-cell {
-  height: 100%;
+::v-deep .mini-calendar .el-calendar-table .el-calendar-day {
+  height: 50px;
+  padding: 4px;
+}
+
+.cell-wrapper {
   display: flex;
   flex-direction: column;
   align-items: center;
   justify-content: center;
-  position: relative;
+  height: 100%;
 }
-.has-record {
-  color: #f56c6c;
-  font-weight: bold;
+
+.has-data {
+  color: #1a73e8;
+  font-weight: 800;
 }
-.record-dot {
+
+.data-dot {
   width: 4px;
   height: 4px;
-  background: #f56c6c;
+  background: #1a73e8;
   border-radius: 50%;
-  margin-top: 2px;
+  margin-top: 4px;
 }
 
-/* 分享列表美化 */
-.user-check-list {
-  max-height: 200px;
-  overflow-y: auto;
-  border: 1px solid #ebeef5;
+/* 分享列表 */
+.share-tip { font-size: 13px; color: #909399; margin-bottom: 16px; }
+.user-select-grid { max-height: 300px; overflow-y: auto; }
+
+::v-deep .user-select-grid .el-checkbox {
+  width: 100%;
+  margin: 0 0 10px 0 !important;
+  border-radius: 10px;
   padding: 10px;
-  border-radius: 4px;
-}
-.user-checkbox {
-  margin-bottom: 8px;
-  margin-left: 0 !important;
-  margin-right: 8px;
 }
 
-/* 表格滚动条美化 */
-.custom-table >>> .el-table__body-wrapper::-webkit-scrollbar {
-  width: 6px;
-}
-.custom-table >>> .el-table__body-wrapper::-webkit-scrollbar-thumb {
-  background-color: #ddd;
-  border-radius: 3px;
-}
+.user-cell { display: flex; align-items: center; gap: 10px; }
 </style>

+ 186 - 61
src/views/disk/CamList.vue

@@ -1,36 +1,53 @@
 <template>
   <div class="cam-list-container">
-    <el-row :gutter="20">
+    <div class="page-header">
+      <div class="title-section">
+        <h2 class="page-title">我的监控</h2>
+        <el-badge :value="onlineCount" type="success" class="online-badge">
+          <span class="sub-title">实时监控设备</span>
+        </el-badge>
+      </div>
+      <el-button type="primary" icon="el-icon-plus" round class="add-cam-btn">添加设备</el-button>
+    </div>
+
+    <el-row :gutter="24">
       <el-col
         v-for="item in dataList"
         :key="item.id"
-        :xs="24" :sm="12" :md="8" :lg="6"
-        style="margin-bottom: 20px;"
+        :xs="24"
+        :sm="12"
+        :md="8"
+        :lg="6"
+        style="margin-bottom: 24px;"
       >
-        <el-card :body-style="{ padding: '0px' }" class="cam-card">
-          <div class="cam-preview">
-            <i class="el-icon-video-camera"></i>
-            <el-tag
-              size="mini"
-              :type="item.state ? 'success' : 'danger'"
-              class="state-tag"
-            >
+        <el-card :body-style="{ padding: '0px' }" class="cam-card" shadow="never">
+          <div class="cam-preview-wrapper" @click="handlePlay(item)">
+            <div class="cam-preview-overlay">
+              <i class="el-icon-video-play play-icon" />
+            </div>
+            <div class="cam-placeholder">
+              <i class="el-icon-video-camera" />
+            </div>
+
+            <div class="status-indicator" :class="{ 'is-online': item.state }">
+              <span class="dot" />
               {{ item.state ? '在线' : '离线' }}
-            </el-tag>
+            </div>
           </div>
 
-          <div style="padding: 14px;">
-            <div class="cam-info">
-              <span class="cam-name">{{ item.camName }}</span>
-              <span class="cam-id">ID: {{ item.camId }}</span>
+          <div class="cam-content">
+            <div class="cam-main-info">
+              <h3 class="cam-name">{{ item.camName }}</h3>
+              <p class="cam-id">设备 ID: {{ item.camId }}</p>
             </div>
 
-            <div class="bottom clearfix">
-              <time class="time">添加时间: {{ item.addAt }}</time>
-              <el-divider></el-divider>
-              <div class="actions">
-                <el-button type="text" size="small" @click="handlePlay(item)">查看</el-button>
-                <el-button type="text" size="small" style="color: #F56C6C">设置</el-button>
+            <div class="cam-footer">
+              <span class="add-time">{{ item.addAt }}</span>
+              <div class="action-group">
+                <el-tooltip content="设备设置" placement="top">
+                  <el-button icon="el-icon-setting" circle size="mini" class="setting-btn" />
+                </el-tooltip>
+                <el-button type="primary" size="mini" round @click="handlePlay(item)">实时画面</el-button>
               </div>
             </div>
           </div>
@@ -38,7 +55,7 @@
       </el-col>
     </el-row>
 
-    <el-empty v-if="dataList.length === 0" description="暂无摄像头数据"></el-empty>
+    <el-empty v-if="dataList.length === 0" description="未发现绑定的监控设备" :image-size="200" />
   </div>
 </template>
 
@@ -49,31 +66,37 @@ export default {
   name: 'CamList',
   data() {
     return {
-      dataList: []
+      dataList: [],
+      loading: false
+    }
+  },
+  computed: {
+    onlineCount() {
+      return this.dataList.filter(i => i.state).length
     }
   },
   created() {
-    document.title = '摄像头列表'
     this.getData()
   },
   methods: {
     getData() {
+      this.loading = true
       getCamList().then(resp => {
         if (resp.code === 0) {
           this.dataList = resp.data
-        } else {
-          this.$message.warning(resp.msg)
         }
-      }).catch(err => {
-        this.$message.error("获取数据失败")
+      }).finally(() => {
+        this.loading = false
       })
     },
     handlePlay(item) {
+      if (!item.state) {
+        this.$message.info('设备已离线,无法查看实时画面')
+        return
+      }
       this.$router.push({
         path: '/disk/cam/detail',
-        query: {
-          camId: item.camId
-        }
+        query: { camId: item.camId }
       })
     }
   }
@@ -82,70 +105,172 @@ export default {
 
 <style scoped>
 .cam-list-container {
-  padding: 20px;
-  background-color: #f5f7fa;
-  min-height: 100vh;
+  padding: 10px;
+}
+
+/* 页面头部 */
+.page-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-end;
+  margin-bottom: 30px;
+}
+
+.page-title {
+  font-size: 24px;
+  font-weight: 800;
+  color: #1a1a1a;
+  margin: 0;
 }
 
+.sub-title {
+  font-size: 13px;
+  color: #909399;
+}
+
+.add-cam-btn {
+  font-weight: 600;
+  box-shadow: 0 4px 12px rgba(26, 115, 232, 0.2);
+}
+
+/* 卡片整体 */
 .cam-card {
-  transition: transform 0.3s;
-  border-radius: 8px;
+  border: none;
+  border-radius: 20px;
+  overflow: hidden;
+  transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
+  background: #fff;
+  border: 1px solid #f0f0f0;
 }
 
 .cam-card:hover {
-  transform: translateY(-5px);
-  box-shadow: 0 4px 12px rgba(0,0,0,0.1);
+  transform: translateY(-8px);
+  box-shadow: 0 15px 30px rgba(0,0,0,0.08);
 }
 
-.cam-preview {
-  height: 150px;
-  background-color: #2c3e50;
+/* 预览图区域 */
+.cam-preview-wrapper {
+  height: 180px;
+  position: relative;
+  overflow: hidden;
+  cursor: pointer;
+  background: linear-gradient(135deg, #2c3e50 0%, #000000 100%);
+}
+
+.cam-placeholder {
+  height: 100%;
   display: flex;
   justify-content: center;
   align-items: center;
-  position: relative;
 }
 
-.cam-preview i {
-  font-size: 48px;
-  color: #909399;
+.cam-placeholder i {
+  font-size: 50px;
+  color: rgba(255, 255, 255, 0.2);
 }
 
-.state-tag {
+/* Hover 时的播放按钮 */
+.cam-preview-overlay {
   position: absolute;
-  top: 10px;
-  right: 10px;
+  inset: 0;
+  background: rgba(0, 0, 0, 0.4);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  opacity: 0;
+  transition: opacity 0.3s;
+  z-index: 2;
 }
 
-.cam-info {
+.cam-preview-wrapper:hover .cam-preview-overlay {
+  opacity: 1;
+}
+
+.play-icon {
+  font-size: 40px;
+  color: #fff;
+}
+
+/* 状态标签 */
+.status-indicator {
+  position: absolute;
+  top: 12px;
+  left: 12px;
+  padding: 4px 10px;
+  background: rgba(0, 0, 0, 0.5);
+  backdrop-filter: blur(4px);
+  border-radius: 20px;
+  color: #fff;
+  font-size: 11px;
   display: flex;
-  justify-content: space-between;
   align-items: center;
-  margin-bottom: 8px;
+  z-index: 3;
+}
+
+.dot {
+  width: 6px;
+  height: 6px;
+  border-radius: 50%;
+  margin-right: 6px;
+  background: #f56c6c; /* 默认离线红 */
+}
+
+.is-online .dot {
+  background: #67c23a;
+  box-shadow: 0 0 8px #67c23a;
+  animation: breathe 2s infinite;
+}
+
+@keyframes breathe {
+  0% { opacity: 1; }
+  50% { opacity: 0.4; }
+  100% { opacity: 1; }
+}
+
+/* 内容区域 */
+.cam-content {
+  padding: 16px;
 }
 
 .cam-name {
   font-size: 16px;
-  font-weight: bold;
+  font-weight: 700;
   color: #303133;
+  margin: 0 0 4px 0;
 }
 
 .cam-id {
   font-size: 12px;
-  color: #999;
+  color: #909399;
+  margin: 0;
 }
 
-.time {
-  font-size: 12px;
-  color: #9499a0;
+.cam-footer {
+  margin-top: 16px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
 }
 
-.el-divider--horizontal {
-  margin: 12px 0;
+.add-time {
+  font-size: 12px;
+  color: #c0c4cc;
 }
 
-.actions {
+.action-group {
   display: flex;
-  justify-content: space-between;
+  align-items: center;
+  gap: 8px;
+}
+
+.setting-btn {
+  border: none;
+  background: #f5f7fa;
+  color: #606266;
+}
+
+.setting-btn:hover {
+  background: #e4e7ed;
+  color: #1a73e8;
 }
 </style>

+ 17 - 0
src/views/disk/Disk.vue

@@ -73,6 +73,14 @@
     >
       <div class="setting-content">
         <el-form label-position="top">
+          <el-form-item label="离线下载通知">
+            <el-switch
+              v-model="settingForm.enableNotify"
+              active-text="开启"
+              inactive-text="关闭"
+              @change="handleNotifyChange"
+            />
+          </el-form-item>
           <el-form-item label="外观设置">
             <el-radio-group v-model="settingForm.theme" size="small">
               <el-radio-button label="light">浅色模式</el-radio-button>
@@ -104,6 +112,7 @@
 <script>
 import { userMixin } from 'assets/js/mixin'
 import { getAuthedUser } from '@/utils/auth'
+import { submitActivity } from '@/api/disk'
 
 export default {
   name: 'Disk',
@@ -113,6 +122,7 @@ export default {
       user: null,
       showSettingDialog: false,
       settingForm: {
+        enableNotify: false,
         theme: 'light',
         defaultView: 'list',
         lang: 'zh'
@@ -123,6 +133,13 @@ export default {
     this.user = getAuthedUser()
   },
   methods: {
+    handleNotifyChange(val) {
+      submitActivity().then(resp => {
+        this.$message.info(resp.msg)
+      }).catch(error => {
+        this.$message.error(error.message)
+      })
+    },
     saveSettings() {
       this.$message.success('设置已保存')
       this.showSettingDialog = false

+ 215 - 83
src/views/disk/DiskShare.vue

@@ -1,98 +1,118 @@
 <template>
-  <div style="padding-right: 5px">
-    <el-table
-      ref="multipleTable"
-      :data="dataList"
-      :show-header="true"
-      style="width: 100%"
-    >
-      <el-table-column
-        prop="albumType"
-        label="合集类型"
-      />
-      <el-table-column
-        prop="albumName"
-        label="合集名"
-      />
-      <el-table-column
-        prop="shareAt"
-        label="分享时间"
-      />
-      <el-table-column
-        label="被分享用户"
-      >
-        <template slot-scope="scope">
-          <span style="color: red">{{ scope.row.num }} 位</span>
-          <el-button
-            type="text"
-            @click="getShareToList(scope.row)"
-          >
-            查看
-          </el-button>
-        </template>
-      </el-table-column>
-      <el-table-column
-        fixed="right"
-        label="操作"
+  <div class="share-container">
+    <div class="page-header">
+      <div class="header-left">
+        <h2 class="page-title">我的分享</h2>
+        <span class="sub-title">管理你已分享给好友的内容</span>
+      </div>
+    </div>
+
+    <div class="table-card">
+      <el-table
+        ref="multipleTable"
+        v-loading="loading"
+        :data="dataList"
+        class="pikpak-table"
+        style="width: 100%"
       >
-        <template slot-scope="scope">
-          <el-button
-            size="mini"
-            type="danger"
-            @click="deleteShare(scope.row)"
-          >删除</el-button>
-        </template>
-      </el-table-column>
-    </el-table>
-    <el-pagination
-      background
-      hide-on-single-page
-      :small="screenWidth <= 768"
-      layout="prev, pager, next"
-      :page-size="pageSize"
-      :current-page="currentPage"
-      :total="totalSize"
-      @current-change="handleCurrentChange"
-      @prev-click="handleCurrentChange"
-      @next-click="handleCurrentChange"
-    />
+        <el-table-column label="合集名称" min-width="250">
+          <template slot-scope="scope">
+            <div class="album-info-cell">
+              <i class="el-icon-folder-opened album-icon" />
+              <span class="album-name">{{ scope.row.albumName }}</span>
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column prop="albumType" label="类型" width="120">
+          <template slot-scope="scope">
+            <el-tag :type="getTagType(scope.row.albumType)" size="medium" effect="light" round>
+              {{ scope.row.albumType }}
+            </el-tag>
+          </template>
+        </el-table-column>
+
+        <el-table-column prop="shareAt" label="分享时间" width="180" />
+
+        <el-table-column label="被分享用户" width="180">
+          <template slot-scope="scope">
+            <div class="share-count-cell">
+              <span class="user-num">{{ scope.row.num }} 位用户</span>
+              <el-button
+                type="text"
+                icon="el-icon-view"
+                class="view-btn"
+                @click="getShareToList(scope.row)"
+              >
+                查看
+              </el-button>
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="操作" width="120" align="right" fixed="right">
+          <template slot-scope="scope">
+            <el-button
+              size="mini"
+              type="text"
+              class="delete-btn"
+              icon="el-icon-delete"
+              @click="deleteShare(scope.row)"
+            >取消分享</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <div class="pagination-wrapper">
+        <el-pagination
+          background
+          hide-on-single-page
+          :small="screenWidth <= 768"
+          layout="total, prev, pager, next"
+          :page-size="pageSize"
+          :current-page="currentPage"
+          :total="totalSize"
+          @current-change="handleCurrentChange"
+        />
+      </div>
+    </div>
 
     <el-dialog
+      title="分享名单"
       :visible.sync="showShareToDialog"
-      width="50%"
-      center
+      width="400px"
+      append-to-body
+      custom-class="pikpak-dialog"
     >
-      <el-table
-        :data="shareToList"
-        :show-header="true"
-        style="width: 50%"
-      >
-        <el-table-column
-          prop="username"
-          label="用户名"
-        />
-      </el-table>
+      <div class="share-user-list">
+        <div v-if="shareToList.length === 0" class="empty-list">暂无分享用户</div>
+        <div v-for="(user, index) in shareToList" :key="index" class="user-item">
+          <el-avatar :size="32" icon="el-icon-user" />
+          <span class="user-name">{{ user.username }}</span>
+          <el-tag size="mini" type="info">已查看</el-tag>
+        </div>
+      </div>
+      <span slot="footer" class="dialog-footer">
+        <el-button type="primary" round block style="width: 100%" @click="showShareToDialog = false">确 定</el-button>
+      </span>
     </el-dialog>
   </div>
 </template>
 
 <script>
-
 import { deleteShare, getShareList, getShareToList } from '@/api/disk'
 
 export default {
   name: 'DiskShare',
   data() {
     return {
-      // 屏幕宽度, 为了控制分页条的大小
       screenWidth: document.body.clientWidth,
       currentPage: 1,
       pageSize: 10,
       totalSize: 0,
       dataList: [],
-      // ****************************************************************************************************************
+      loading: false,
       showShareToDialog: false,
-      currentShareId: null,
       shareToList: []
     }
   },
@@ -105,6 +125,7 @@ export default {
       this.getData()
     },
     getData() {
+      this.loading = true
       getShareList(this.currentPage).then(resp => {
         if (resp.code === 0) {
           this.dataList = resp.data.list
@@ -112,11 +133,16 @@ export default {
         } else {
           this.$message.error(resp.msg)
         }
+      }).finally(() => {
+        this.loading = false
       })
     },
+    getTagType(type) {
+      const map = { '视频': 'danger', '图片': 'success', '文档': 'warning' }
+      return map[type] || 'info'
+    },
     getShareToList(row) {
-      this.shareId = row.shareId
-      getShareToList(this.shareId).then(resp => {
+      getShareToList(row.shareId).then(resp => {
         if (resp.code === 0) {
           this.shareToList = resp.data
           this.showShareToDialog = true
@@ -124,28 +150,134 @@ export default {
       })
     },
     deleteShare(row) {
-      this.$confirm('确定要删除分享 ' + row.albumName + '?', '提示', {
+      this.$confirm(`确定要取消针对 "${row.albumName}" 的分享吗?`, '取消分享', {
         confirmButtonText: '确定',
         cancelButtonText: '取消',
-        type: 'warning'
+        confirmButtonClass: 'el-button--danger',
+        type: 'warning',
+        roundButton: true
       }).then(() => {
         deleteShare(row.shareId).then(resp => {
           if (resp.code === 0) {
-            this.$router.go(0)
+            this.$message.success('已取消分享')
+            this.getData() // 刷新数据而不是刷新整个页面
           } else {
             this.$message.error(resp.msg)
           }
         })
-      }).catch(() => {
-        this.$message({
-          type: 'info',
-          message: '已取消'
-        })
       })
     }
   }
 }
 </script>
 
-<style>
+<style scoped>
+.share-container {
+  padding: 10px;
+}
+
+.page-header {
+  margin-bottom: 24px;
+}
+
+.page-title {
+  font-size: 20px;
+  font-weight: 700;
+  color: #1a1a1a;
+  margin: 0;
+}
+
+.sub-title {
+  font-size: 13px;
+  color: #909399;
+  margin-top: 4px;
+  display: block;
+}
+
+/* 表格样式美化 */
+.table-card {
+  background: #fff;
+  border-radius: 12px;
+}
+
+.pikpak-table ::v-deep .el-table__header th {
+  background-color: transparent;
+  color: #5f6368;
+  font-weight: 600;
+  border-bottom: 1px solid #f0f0f0;
+}
+
+.pikpak-table ::v-deep .el-table__row {
+  transition: background 0.2s;
+}
+
+.album-info-cell {
+  display: flex;
+  align-items: center;
+}
+
+.album-icon {
+  font-size: 24px;
+  color: #ffca28;
+  margin-right: 12px;
+}
+
+.album-name {
+  font-weight: 500;
+  color: #333;
+}
+
+.share-count-cell {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.user-num {
+  color: #f56c6c;
+  font-weight: 600;
+}
+
+.delete-btn {
+  color: #909399;
+}
+.delete-btn:hover {
+  color: #f56c6c;
+}
+
+.pagination-wrapper {
+  margin-top: 24px;
+  display: flex;
+  justify-content: center;
+}
+
+/* 用户列表弹窗 */
+.share-user-list {
+  max-height: 300px;
+  overflow-y: auto;
+}
+
+.user-item {
+  display: flex;
+  align-items: center;
+  padding: 12px 0;
+  border-bottom: 1px solid #f5f7fa;
+}
+
+.user-name {
+  flex: 1;
+  margin-left: 12px;
+  font-size: 14px;
+  color: #333;
+}
+
+.empty-list {
+  text-align: center;
+  padding: 30px;
+  color: #999;
+}
+
+::v-deep .pikpak-dialog {
+  border-radius: 16px;
+}
 </style>