Переглянути джерело

feat(frontend): 统一分析类页面卡片化 UI 并重做空状态

- 数据画像页重构:左侧对象列表固定、右侧独立滚动;档案头合并基础信息;
  亲密关系/通讯/交易卡片化,TOP10 横向排行并支持下钻
- 新增共享样式 analysis-card-ui.less,统一 13 个分析页的查询条件卡、
  内容卡片、表格、分页与选中态(主题色 #2a50ec、卡片间距 8px、圆角 14)
- 各页查询条件拆为「标题栏 + 行内表单」,选择对象控件统一 200×32
- 表格选中行统一为纯背景 #dbe7ff + 主题色文字,去掉边框与竖条
- 重做 EmptyState:文档+放大镜插画、浅色圆形底,适配 query/compact/warm
  与暗色主题
- 夜间/持续联系趋势图与悬浮提示改为主题配色;定期存取左栏汇总卡重排

不涉及业务逻辑与接口变更。
cc 2 днів тому
батько
коміт
5ffe4f5a6c
20 змінених файлів з 4279 додано та 1700 видалено
  1. 513 183
      ai-electron/frontend/src/call/views/continuous/index.vue
  2. 572 306
      ai-electron/frontend/src/call/views/night/index.vue
  3. 536 243
      ai-electron/frontend/src/call/views/sensitive/index.vue
  4. 156 105
      ai-electron/frontend/src/case/views/dm/components/IntimacyPanel.vue
  5. 497 367
      ai-electron/frontend/src/case/views/dm/portrait.vue
  6. 105 105
      ai-electron/frontend/src/core/components/EmptyState/src/EmptyState.vue
  7. 39 28
      ai-electron/frontend/src/trans/views/baseStation/index.vue
  8. 37 26
      ai-electron/frontend/src/trans/views/cashTrans/index.vue
  9. 52 42
      ai-electron/frontend/src/trans/views/coResidence/index.vue
  10. 33 22
      ai-electron/frontend/src/trans/views/expressDelivery/index.vue
  11. 48 27
      ai-electron/frontend/src/trans/views/faceToFace/index.vue
  12. 32 21
      ai-electron/frontend/src/trans/views/financialWealth/index.vue
  13. 154 23
      ai-electron/frontend/src/trans/views/fixedDeposit/index.vue
  14. 47 36
      ai-electron/frontend/src/trans/views/largeAmount/index.vue
  15. 55 41
      ai-electron/frontend/src/trans/views/monthRegular/index.vue
  16. 111 48
      ai-electron/frontend/src/trans/views/morningEveningCall/index.vue
  17. 29 18
      ai-electron/frontend/src/trans/views/peerAnalysis/index.vue
  18. 1182 0
      ai-electron/frontend/src/trans/views/styles/analysis-card-ui.less
  19. 43 32
      ai-electron/frontend/src/trans/views/tradeFrequency/index.vue
  20. 38 27
      ai-electron/frontend/src/trans/views/transSensitive/index.vue

+ 513 - 183
ai-electron/frontend/src/call/views/continuous/index.vue

@@ -1,60 +1,59 @@
 <template>
-  <div :class="prefixCls" class="continuous-page">
-    <div class="page-header">
-      <div class="page-title">持续联系</div>
-      <div class="page-subtitle">识别与被调查对象持续保持联系的人员,并支持下钻查看通信明细。</div>
-    </div>
-
-    <div class="filter-bar">
-      <div class="filter-item">
-        <span class="filter-label">选择对象:</span>
-        <Select
-          v-model:value="filters.target"
-          mode="multiple"
-          allow-clear
-          max-tag-count="responsive"
-          class="filter-input"
-          :options="targetOptions"
-          placeholder="全部"
-        />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">持续联系月数:</span>
-        <Input v-model:value="filters.monthValue" class="filter-input" :status="monthValueStatus" />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">联系日期:</span>
-        <RangePicker
-          v-model:value="filters.dateRange"
-          class="filter-input filter-date"
-          :format="DATE_FORMAT"
-          :value-format="DATE_FORMAT"
-          :placeholder="['开始日期', '结束日期']"
-        />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">对方姓名:</span>
-        <Input v-model:value="filters.otherName" class="filter-input" placeholder="请输入对方姓名" />
+  <div :class="prefixCls" class="continuous-page qj-continuous-page">
+    <section class="qj-filter-card">
+      <div class="qj-filter-head">
+        <div class="qj-card-title">查询条件</div>
+        <div class="qj-filter-actions">
+          <Button class="qj-btn-primary" type="primary" :loading="queryLoading" @click="handleSearch">查询</Button>
+          <Button class="qj-btn-reset" @click="handleReset">重置</Button>
+        </div>
       </div>
-      <div class="filter-item">
-        <span class="filter-label">对方号码:</span>
-        <Input v-model:value="filters.otherPhone" class="filter-input" placeholder="请输入对方号码" />
+      <div class="qj-filter-grid">
+        <div class="qj-field qj-field--wide">
+          <span class="qj-field__label">选择对象</span>
+          <Select
+            v-model:value="filters.target"
+            mode="multiple"
+            allow-clear
+            max-tag-count="responsive"
+            class="filter-input"
+            :options="targetOptions"
+            placeholder="全部"
+          />
+        </div>
+        <div class="qj-field">
+          <span class="qj-field__label">持续联系月数</span>
+          <Input v-model:value="filters.monthValue" class="filter-input" :status="monthValueStatus" />
+        </div>
+        <div class="qj-field qj-field--date">
+          <span class="qj-field__label">联系日期</span>
+          <RangePicker
+            v-model:value="filters.dateRange"
+            class="filter-input filter-date"
+            :format="DATE_FORMAT"
+            :value-format="DATE_FORMAT"
+            :placeholder="['开始日期', '结束日期']"
+          />
+        </div>
+        <div class="qj-field">
+          <span class="qj-field__label">对方姓名</span>
+          <Input v-model:value="filters.otherName" class="filter-input" placeholder="请输入对方姓名" />
+        </div>
+        <div class="qj-field">
+          <span class="qj-field__label">对方号码</span>
+          <Input v-model:value="filters.otherPhone" class="filter-input" placeholder="请输入对方号码" />
+        </div>
       </div>
-      <div class="filter-actions">
-        <Button type="primary" :loading="queryLoading" @click="handleSearch">查询</Button>
-        <Button @click="handleReset">重置</Button>
+    </section>
+
+    <section class="qj-table-card">
+      <div class="qj-table-head">
+        <div class="qj-card-title">持续联系对象汇总</div>
+        <div class="qj-table-sub">找出与被调查对象持续保持联系的人员</div>
+        <div class="qj-table-meta">共 {{ hasSearched ? total.toLocaleString('zh-CN') : 0 }} 条</div>
       </div>
-    </div>
-
-    <div class="content-panel">
-      <div class="table-card">
-        <div class="panel-header">
-          <div class="panel-title-wrap">
-            <div class="panel-title">持续联系对象汇总</div>
-            <div class="panel-desc">找出与被调查对象持续保持联系的人员。</div>
-          </div>
-        </div>
 
+      <div class="content-panel">
         <div v-if="!hasSearched" class="query-state-panel">
           <EmptyState
             variant="query"
@@ -81,19 +80,19 @@
             />
           </div>
         </div>
+      </div>
 
-        <div v-if="hasSearched" class="pagination-bar">
-          <AnalysisPagination
-            :total="total"
-            :current="pagination.page"
-            :page-size="pagination.limit"
-            :page-size-options="pageSizeOptions"
-            :loading="queryLoading"
-            @change="handlePaginationChange"
-          />
-        </div>
+      <div v-if="hasSearched" class="pagination-bar">
+        <AnalysisPagination
+          :total="total"
+          :current="pagination.page"
+          :page-size="pagination.limit"
+          :page-size-options="pageSizeOptions"
+          :loading="queryLoading"
+          @change="handlePaginationChange"
+        />
       </div>
-    </div>
+    </section>
 
     <CallRecordModal v-model:open="callRecordModalOpen" title="通信记录" :base-query="callRecordBaseQuery" />
   </div>
@@ -443,234 +442,565 @@
 
   @import '../../../trans/views/styles/trans-analysis-table-ui.less';
 
-  .@{prefix-cls}.continuous-page {
-    .trans-analysis-table-ui();
-
-    background: #fff;
-    padding: 0 8px 2px;
-    min-height: 100%;
-    height: 100%;
+  /* ==========================================================================
+   * 持续联系分析:与「数据画像」统一卡片语言
+   * 背景 #f4f6fa · 卡片圆角 14 · 细边框 #e7ebf3 · 卡片间距 8px · 主色 #2a50ec
+   * ========================================================================== */
+  .@{prefix-cls}.continuous-page.qj-continuous-page {
     display: flex;
     flex-direction: column;
-    overflow: hidden;
-    box-sizing: border-box;
+    gap: 8px;
     width: calc(100% + 24px);
+    height: 100%;
+    min-height: 0;
     margin: 0 -12px;
-    padding-left: 0;
-    padding-right: 0;
 
-    .filter-input {
-      width: 160px;
+    /* 共享 mixin 用 !important 把左右 padding 归零,这里需要以更高层级还原卡片两侧留白 */
+    padding: 8px 10px !important;
+    background: #f4f6fa;
+    box-sizing: border-box;
+    overflow: hidden;
+
+    /* ---------------- 查询条件卡 ---------------- */
+    .qj-filter-card {
+      flex: 0 0 auto;
+      border: 1px solid #e7ebf3;
+      border-radius: 14px;
+      background: #fff;
+      box-shadow: 0 1px 2px rgb(16 24 40 / 4%);
+      box-sizing: border-box;
+      overflow: hidden;
+    }
+
+    .qj-filter-head {
+      display: flex;
+      align-items: center;
+      gap: 10px;
+      min-height: 42px;
+      padding: 0 14px;
+      border-bottom: 1px solid #eff2f7;
     }
 
-    .filter-date {
-      width: 360px;
+    .qj-card-title {
+      position: relative;
+      padding-left: 11px;
+      color: #111827;
+      font-size: 15px;
+      font-weight: 600;
+      line-height: 22px;
+
+      &::before {
+        content: '';
+        position: absolute;
+        left: 0;
+        top: 4px;
+        bottom: 4px;
+        width: 3px;
+        border-radius: 2px;
+        background: #2a50ec;
+      }
     }
 
-    .filter-bar {
+    .qj-filter-actions {
       display: flex;
+      flex: 0 0 auto;
       align-items: center;
       gap: 8px;
-      padding: 6px;
-      min-height: 40px;
-      border: 0;
-      background: transparent;
+      margin-left: auto;
+    }
+
+    .qj-btn-primary.ant-btn {
+      height: 30px;
+      padding: 0 18px;
+      border: 1px solid #2a50ec;
+      border-radius: 8px;
+      background: #2a50ec;
+      box-shadow: none;
+      font-size: 12px;
+      font-weight: 500;
+
+      &:hover,
+      &:focus {
+        border-color: #4b6bf0;
+        background: #4b6bf0;
+      }
+
+      &:active {
+        border-color: #1e3ed0;
+        background: #1e3ed0;
+      }
+    }
+
+    .qj-btn-reset.ant-btn {
+      height: 30px;
+      padding: 0 14px;
+      border: 1px solid #e7ebf3;
+      border-radius: 8px;
+      background: #fff;
+      color: #5b6577;
+      font-size: 12px;
+      font-weight: 500;
+      box-shadow: none;
+
+      &:hover,
+      &:focus {
+        border-color: #bfcdfb;
+        background: #f8faff;
+        color: #2a50ec;
+      }
+    }
+
+    /* 行内表单:label 在左、控件在右,控件统一 32px 高、白底 */
+    .qj-filter-grid {
+      display: flex;
       flex-wrap: wrap;
-      flex-shrink: 0;
+      align-items: center;
+      gap: 8px 14px;
+      padding: 10px 14px 12px;
       box-sizing: border-box;
     }
 
-    .filter-item {
-      display: flex;
+    .qj-field {
+      display: inline-flex;
+      flex: 0 0 auto;
       align-items: center;
-      gap: 4px;
+      gap: 6px;
+      min-width: 0;
     }
 
-    .filter-label {
-      color: #6b7280;
+    .qj-field__label {
+      flex: 0 0 auto;
+      color: #5b6577;
       font-size: 12px;
+      line-height: 32px;
       white-space: nowrap;
     }
 
-    .filter-actions {
-      display: flex;
+    .filter-input,
+    .filter-date {
+      width: 150px;
+      min-width: 0;
+    }
+
+    .filter-date {
+      width: 240px;
+    }
+
+    .qj-field--wide .filter-input,
+    .qj-field--wide .filter-input.ant-select {
+      width: 200px;
+    }
+
+    .qj-field .ant-input,
+    .qj-field .ant-input-affix-wrapper,
+    .qj-field .ant-picker {
+      height: 32px;
+      padding: 0 10px;
+      border: 1px solid #e7ebf3;
+      border-radius: 8px;
+      background: #fff;
+      box-shadow: none;
+      font-size: 12px;
+      transition:
+        border-color 0.2s ease,
+        box-shadow 0.2s ease;
+    }
+
+    .qj-field .ant-input-affix-wrapper {
+      display: inline-flex;
       align-items: center;
-      gap: 8px;
-      margin-left: auto;
     }
 
-    .filter-input,
-    .filter-date,
-    .filter-actions :deep(.ant-btn) {
-      align-self: center;
+    .qj-field .ant-input-affix-wrapper > .ant-input {
+      height: 100%;
+      padding: 0;
+      border: 0;
+      border-radius: 0;
+      background: transparent;
+      box-shadow: none;
     }
 
-    .panel-title-wrap {
-      display: flex;
-      flex-direction: column;
-      gap: 4px;
-      min-width: 0;
+    .qj-field .ant-picker .ant-picker-input > input {
+      font-size: 12px;
     }
 
-    .panel-title {
-      font-size: 14px;
-      font-weight: 600;
-      color: #111827;
-      line-height: 22px;
+    .qj-field .ant-input:hover,
+    .qj-field .ant-input-affix-wrapper:hover,
+    .qj-field .ant-picker:hover,
+    .qj-field .ant-select:hover .ant-select-selector {
+      border-color: #bfcdfb;
+      background: #fff;
+    }
+
+    .qj-field .ant-input:focus,
+    .qj-field .ant-input-affix-wrapper-focused,
+    .qj-field .ant-picker-focused,
+    .qj-field .ant-select-focused .ant-select-selector {
+      border-color: #2a50ec !important;
+      background: #fff;
+      box-shadow: 0 0 0 2px rgb(42 80 236 / 12%) !important;
+    }
+
+    .qj-field .ant-input-status-error,
+    .qj-field .ant-input-affix-wrapper-status-error {
+      border-color: #e5484d;
     }
 
-    .panel-desc {
-      color: #6b7280;
+    .qj-field .ant-select {
+      width: 150px;
+      height: 32px;
+    }
+
+    .qj-field--wide .ant-select {
+      width: 200px;
+    }
+
+    .qj-field .ant-select .ant-select-selector {
+      height: 32px !important;
+      padding: 0 10px;
+      border: 1px solid #e7ebf3;
+      border-radius: 8px;
+      background: #fff;
+      box-shadow: none;
+    }
+
+    .qj-field .ant-select-single .ant-select-selection-item,
+    .qj-field .ant-select-single .ant-select-selection-placeholder {
+      line-height: 30px;
       font-size: 12px;
-      line-height: 20px;
     }
 
-    .content-panel {
+    .qj-field .ant-select-multiple .ant-select-selector {
       display: flex;
-      flex: 1 1 auto;
-      min-height: 0;
-      padding: 2px;
-      background: #edf2f7;
-      box-sizing: border-box;
+      align-items: center;
       overflow: hidden;
     }
 
-    .table-card {
-      flex: 1 1 auto;
+    .qj-field .ant-select-multiple .ant-select-selection-overflow {
+      flex-wrap: nowrap;
+      align-items: center;
+    }
+
+    .qj-field .ant-select-multiple .ant-select-selection-item {
+      height: 22px;
+      margin: 0 4px 0 0;
+      padding: 0 7px;
+      border-radius: 4px;
+      background: #eef2ff;
+      color: #2448c9;
+      font-size: 11px;
+      line-height: 22px;
+    }
+
+    .qj-field .ant-select-multiple .ant-select-selection-overflow-item-suffix {
       min-height: 0;
+    }
+
+    .qj-field .ant-select-multiple .ant-select-selection-search {
+      margin-inline-start: 0;
+    }
+
+    .qj-field .ant-select-multiple .ant-select-selection-placeholder {
+      left: 10px;
+      line-height: 30px;
+      font-size: 12px;
+    }
+
+    .qj-field .ant-select-arrow {
+      color: #98a2b3;
+      font-size: 11px;
+    }
+
+    /* ---------------- 表格卡 ---------------- */
+    .qj-table-card {
       display: flex;
+      flex: 1 1 auto;
       flex-direction: column;
-      overflow: hidden;
-      padding: 8px;
-      border: 0;
-      border-radius: 12px;
+      min-height: 0;
+      border: 1px solid #e7ebf3;
+      border-radius: 14px;
       background: #fff;
-      box-shadow: inset 0 0 0 1px #e2e8f0;
+      box-shadow: 0 1px 2px rgb(16 24 40 / 4%);
       box-sizing: border-box;
+      overflow: hidden;
     }
 
-    .panel-header {
-      flex: 0 0 auto;
+    .qj-table-head {
       display: flex;
       align-items: center;
-      justify-content: space-between;
-      gap: 12px;
-      padding: 0 0 8px;
-      width: 100%;
-      box-sizing: border-box;
-      border-bottom: 1px solid #f1f5f9;
+      gap: 10px;
+      flex: 0 0 auto;
+      min-height: 42px;
+      padding: 0 14px;
+      border-bottom: 1px solid #eff2f7;
     }
 
-    .table-panel {
-      flex: 1 1 auto;
-      min-height: 0;
+    .qj-table-sub {
+      color: #98a2b3;
+      font-size: 11px;
+      line-height: 16px;
+    }
+
+    .qj-table-meta {
+      margin-left: auto;
+      color: #98a2b3;
+      font-size: 11px;
+      line-height: 16px;
+      white-space: nowrap;
+      font-variant-numeric: tabular-nums;
+    }
+
+    .content-panel {
       display: flex;
+      flex: 1 1 auto;
       flex-direction: column;
-      overflow: hidden;
+      min-height: 0;
       padding: 0;
+      background: transparent;
+      box-sizing: border-box;
+      overflow: hidden;
     }
 
     .query-state-panel {
-      flex: 1 1 auto;
-      min-height: 0;
       display: flex;
+      flex: 1 1 auto;
       align-items: center;
       justify-content: center;
-      padding: 12px 0 0;
+      min-height: 0;
+      padding: 8px 0 16px;
     }
 
-    .table-wrapper {
+    .table-panel {
+      display: flex;
       flex: 1 1 auto;
+      flex-direction: column;
       min-height: 0;
+      padding: 0;
+      box-sizing: border-box;
+      overflow: hidden;
+    }
+
+    /* 表头固定 + body 内部滚动;高度由 BasicTable 的 calcTableHeight 内联控制 */
+    .table-wrapper {
       display: flex;
+      flex: 1 1 auto;
       flex-direction: column;
-
-      /* 必须 hidden:要的是「表头固定 + body 内部滚动」,整体滚动会把表头带走 */
+      min-height: 0;
       overflow: hidden;
+      border: 0;
+      background: transparent;
+      box-shadow: none;
     }
 
     .table-wrapper .jeesite-basic-table {
-      height: 100%;
-      min-height: 0;
       display: flex;
       flex-direction: column;
-    }
-
-    .table-wrapper > .ant-table-wrapper,
-    .table-wrapper .ant-spin-nested-loading,
-    .table-wrapper .ant-spin-container,
-    .table-wrapper .ant-table,
-    .table-wrapper .ant-table-container {
-      flex: 1 1 auto;
-      min-height: 0;
       height: 100%;
+      min-height: 0;
     }
 
+    /* 逐层传递高度,否则 .ant-table-wrapper 按内容撑开,body 的 max-height 会小于可用高度 */
+    .table-wrapper .ant-table-wrapper,
+    .table-wrapper .ant-spin-nested-loading,
     .table-wrapper .ant-spin-container,
     .table-wrapper .ant-table,
     .table-wrapper .ant-table-container {
       display: flex;
+      flex: 1 1 auto;
       flex-direction: column;
+      height: 100%;
+      min-height: 0;
     }
 
-    /*
-     * 表头固定 + body 内部滚动。
-     *
-     * 这里**不能**给 .ant-table-body 写 max-height:
-     * useTableScroll#calcTableHeight 会按容器实测高度写入内联 max-height(精确像素),
-     * 而 CSS 的 `max-height: ... !important` 优先级高于内联样式,会把它顶掉。
-     * 高度一失控,body 就被内容撑开、溢出到容器外 —— 表头跟着滚、滚动条也没了。
-     *
-     * 只保留 flex 参与分配 + overflow-y,高度交给 calcTableHeight 的内联值。
-     */
     .table-wrapper .ant-table-body {
       flex: 1 1 auto;
       min-height: 0;
+
+      /* 用容器高度兜底,避免 BasicTable 写入的 max-height 偏小在表格下方留下空白 */
+      max-height: 100% !important;
       overflow-y: auto;
     }
 
-    /* 表头不参与 flex 压缩,否则空间紧张时会被压扁 */
     .table-wrapper .ant-table-header {
       flex: 0 0 auto;
     }
 
+    .table-wrapper .ant-table {
+      font-size: 12px;
+    }
+
+    .table-wrapper .ant-table-thead > tr > th {
+      height: 40px;
+      padding: 0 12px;
+      border-right: 0;
+      border-bottom: 1px solid #eff2f7;
+      background: #f8fafc;
+      color: #5b6577;
+      font-size: 12px;
+      font-weight: 600;
+    }
+
+    .table-wrapper .ant-table-thead > tr > th::before {
+      display: none;
+    }
+
+    .table-wrapper .ant-table-thead > tr:first-child > th:first-child {
+      border-top-left-radius: 8px;
+    }
+
+    .table-wrapper .ant-table-thead > tr:first-child > th:last-child {
+      border-top-right-radius: 8px;
+    }
+
+    .table-wrapper .ant-table-tbody > tr > td {
+      height: 40px;
+      padding: 0 12px;
+      border-right: 0;
+      border-bottom: 1px solid #f4f6fa !important;
+      color: #1f2937;
+      font-size: 12px;
+    }
+
+    .table-wrapper .ant-table-tbody > tr:last-child > td {
+      border-bottom: 0 !important;
+    }
+
+    .table-wrapper .ant-table-tbody > tr:hover > td {
+      background: #eef4ff !important;
+    }
+
+    .table-wrapper .ant-table-tbody > tr > td .ant-btn-link {
+      height: auto;
+      padding: 0;
+      color: #2a50ec;
+      font-size: 12px;
+      font-weight: 500;
+
+      &:hover,
+      &:focus {
+        color: #4b6bf0;
+      }
+    }
+
+    .table-wrapper .ant-table-column-sorter {
+      color: #b6bfcc;
+    }
+
+    /* ---------------- 分页 ---------------- */
     .pagination-bar {
       display: flex;
       align-items: center;
       justify-content: flex-end;
-      gap: 8px;
       flex: 0 0 auto;
-      flex-wrap: wrap;
-      padding: 8px 0 0;
-      width: 100%;
+      padding: 8px 14px 10px;
+      border-top: 1px solid #eff2f7;
+      background: transparent;
       box-sizing: border-box;
-      border-top: 1px solid #f1f5f9;
     }
 
-    .pagination-total {
-      margin-right: 0;
-      color: #4b5563;
+    .pagination-bar .analysis-pagination {
+      color: #98a2b3;
+      font-size: 11px;
     }
 
-    .pagination-size {
-      :deep(.ant-select) {
-        width: 88px;
-      }
+    .pagination-bar .ant-pagination-item,
+    .pagination-bar .ant-pagination-prev,
+    .pagination-bar .ant-pagination-next {
+      min-width: 26px;
+      height: 26px;
+      border-radius: 6px;
+      border-color: #e7ebf3;
+      line-height: 24px;
+    }
+
+    .pagination-bar .ant-pagination-item a {
+      color: #5b6577;
+      font-size: 12px;
+      line-height: 24px;
+    }
+
+    .pagination-bar .ant-pagination-item-active {
+      border-color: #2a50ec;
+      background: #eef2ff;
     }
 
-    .pagination-input {
-      width: 56px;
+    .pagination-bar .ant-pagination-item-active a {
+      color: #2a50ec;
+      font-weight: 600;
+    }
+
+    .pagination-bar .ant-pagination-options {
+      margin-inline-start: 8px;
+    }
+
+    .pagination-bar .ant-select-selector {
+      border-radius: 6px;
+    }
+
+    .pagination-bar .ant-pagination-options-quick-jumper input {
+      height: 26px;
+      border-radius: 6px;
     }
 
+    /* ---------------- 响应式 ---------------- */
     @media screen and (max-width: 992px) {
-      .filter-actions {
-        width: 100%;
-        margin-left: 0;
+      .qj-field,
+      .qj-field--wide,
+      .qj-field--date {
+        flex: 1 1 100%;
+      }
+
+      .qj-table-sub {
+        display: none;
+      }
+    }
+
+    /* ---------------- 暗色主题 ---------------- */
+    html[data-theme='dark'] & {
+      background: #0f172a;
+
+      .qj-filter-card,
+      .qj-table-card {
+        border-color: #1f2937;
+        background: #111827;
+        box-shadow: none;
+      }
+
+      .qj-card-title {
+        color: #e5e7eb;
+      }
+
+      .qj-field__label,
+      .qj-table-sub,
+      .qj-table-meta {
+        color: #94a3b8;
+      }
+
+      .qj-filter-head,
+      .qj-table-head,
+      .pagination-bar {
+        border-color: #1f2937;
+      }
+
+      .qj-field .ant-input,
+      .qj-field .ant-input-affix-wrapper,
+      .qj-field .ant-picker,
+      .qj-field .ant-select-single .ant-select-selector,
+      .qj-field .ant-select-multiple .ant-select-selector {
+        border-color: #1f2937;
+        background: #0f172a;
+        color: #e5e7eb;
+      }
+
+      .table-wrapper .ant-table-thead > tr > th {
+        background: #0f172a;
+        border-color: #1f2937;
+        color: #94a3b8;
       }
 
-      .filter-input,
-      .filter-date {
-        width: 100%;
+      .table-wrapper .ant-table-tbody > tr > td {
+        border-color: #1f2937;
+        color: #e5e7eb;
       }
     }
   }

+ 572 - 306
ai-electron/frontend/src/call/views/night/index.vue

@@ -1,64 +1,63 @@
 <template>
-  <div ref="nightPageRef" :class="prefixCls" class="night-page">
-    <div class="page-header">
-      <div class="page-title">夜间联系</div>
-      <div class="page-subtitle">识别与对象在夜间联系频繁的人员或号码,并支持下钻查看时段趋势。</div>
-    </div>
-
-    <div class="filter-bar">
-      <div class="filter-item filter-item--target">
-        <span class="filter-label">选择对象:</span>
-        <Select
-          v-model:value="filters.target"
-          mode="multiple"
-          allow-clear
-          max-tag-count="responsive"
-          class="filter-input"
-          :options="targetOptions"
-          placeholder="全部"
-        />
-      </div>
-      <div class="filter-item filter-item--month">
-        <span class="filter-label">联系月数:</span>
-        <Input v-model:value="filters.monthValue" class="filter-input" :status="monthValueStatus" />
-      </div>
-      <div class="filter-item filter-item--date">
-        <span class="filter-label">联系日期:</span>
-        <RangePicker
-          v-model:value="filters.dateRange"
-          class="filter-input filter-date"
-          :format="DATE_FORMAT"
-          :value-format="DATE_FORMAT"
-          :placeholder="['开始日期', '结束日期']"
-        />
-      </div>
-      <div class="filter-item filter-item--night">
-        <span class="filter-label">夜间时间段:</span>
-        <Select v-model:value="filters.nightStart" class="filter-op" :options="nightHourOptions" />
-        <span class="filter-label">至</span>
-        <Select v-model:value="filters.nightEnd" class="filter-op" :options="nightHourOptions" />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">对方姓名:</span>
-        <Input v-model:value="filters.otherName" class="filter-input" placeholder="请输入对方姓名" />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">对方号码:</span>
-        <Input v-model:value="filters.otherPhone" class="filter-input" placeholder="请输入对方号码" />
+  <div ref="nightPageRef" :class="prefixCls" class="night-page qj-night-page">
+    <section class="qj-filter-card">
+      <div class="qj-filter-head">
+        <div class="qj-card-title">查询条件</div>
+        <div class="qj-filter-actions">
+          <Button class="qj-btn-primary" type="primary" :loading="queryLoading" @click="handleSearch">查询</Button>
+          <Button class="qj-btn-reset" @click="handleReset">重置</Button>
+        </div>
       </div>
-      <div class="filter-actions">
-        <Button type="primary" :loading="queryLoading" @click="handleSearch">查询</Button>
-        <Button @click="handleReset">重置</Button>
+      <div class="qj-filter-grid">
+        <div class="qj-field qj-field--target">
+          <span class="qj-field__label">选择对象</span>
+          <Select
+            v-model:value="filters.target"
+            mode="multiple"
+            allow-clear
+            max-tag-count="responsive"
+            class="filter-input"
+            :options="targetOptions"
+            placeholder="全部"
+          />
+        </div>
+        <div class="qj-field">
+          <span class="qj-field__label">对方姓名</span>
+          <Input v-model:value="filters.otherName" class="filter-input" placeholder="请输入对方姓名" />
+        </div>
+        <div class="qj-field">
+          <span class="qj-field__label">对方号码</span>
+          <Input v-model:value="filters.otherPhone" class="filter-input" placeholder="请输入对方号码" />
+        </div>
+        <div class="qj-field">
+          <span class="qj-field__label">联系日期</span>
+          <RangePicker
+            v-model:value="filters.dateRange"
+            class="filter-input filter-date"
+            :format="DATE_FORMAT"
+            :value-format="DATE_FORMAT"
+            :placeholder="['开始日期', '结束日期']"
+          />
+        </div>
+        <div class="qj-field">
+          <span class="qj-field__label">联系月数</span>
+          <Input v-model:value="filters.monthValue" class="filter-input filter-month" :status="monthValueStatus" />
+        </div>
+        <div class="qj-field">
+          <span class="qj-field__label">夜间时间段</span>
+          <Select v-model:value="filters.nightStart" class="filter-op" :options="nightHourOptions" />
+          <span class="qj-field__sep">至</span>
+          <Select v-model:value="filters.nightEnd" class="filter-op" :options="nightHourOptions" />
+        </div>
       </div>
-    </div>
+    </section>
 
     <div class="content-panel">
-      <div class="summary-panel">
-        <div class="panel-header">
-          <div class="panel-title-wrap">
-            <div class="panel-title">夜间联系对象汇总</div>
-            <div class="panel-desc">汇总满足当前筛选条件的对象的夜间联系情况,可点击单条下方显示趋势。</div>
-          </div>
+      <section class="qj-summary-card">
+        <div class="qj-card-head">
+          <div class="qj-card-title">夜间联系对象汇总</div>
+          <div class="qj-card-sub">汇总满足当前筛选条件的对象的夜间联系情况,点击单条查看时段趋势</div>
+          <div class="qj-card-meta">共 {{ (hasSearched ? total : 0).toLocaleString('zh-CN') }} 条</div>
         </div>
 
         <div v-if="!hasSearched" class="query-state-panel">
@@ -100,14 +99,13 @@
             @change="handlePaginationChange"
           />
         </div>
-      </div>
+      </section>
 
-      <div class="trend-panel">
-        <div class="panel-header">
-          <div class="panel-title-wrap">
-            <div class="panel-title">时段联系趋势</div>
-            <div class="panel-desc">上方选中对象时段趋势,可点击图表查看详情。</div>
-          </div>
+      <section class="qj-trend-card">
+        <div class="qj-card-head">
+          <div class="qj-card-title">时段联系趋势</div>
+          <div class="qj-card-sub">选中对象的夜间时段分布,可点击图表节点查看通信明细</div>
+          <div v-if="selectedRow" class="qj-card-meta"> {{ selectedRow.selfName }} · {{ selectedRow.otherName }} </div>
         </div>
         <div class="trend-panel-body">
           <div class="trend-chart-shell">
@@ -116,13 +114,13 @@
                 variant="compact"
                 title="暂无趋势数据"
                 :description="trendEmptyDescription"
-                :min-height="220"
+                :min-height="160"
               />
             </div>
             <div v-show="trendVisible" ref="chartRef" class="trend-chart"></div>
           </div>
         </div>
-      </div>
+      </section>
     </div>
 
     <CallRecordModal v-model:open="callRecordModalOpen" title="通信记录" :base-query="callRecordBaseQuery" />
@@ -147,7 +145,6 @@
   import { getPersonOptions } from '@/person/api/personApi';
   import CallRecordModal from '@/call/views/components/CallRecordModal.vue';
   import type { EChartsOption } from 'echarts';
-  import * as echarts from 'echarts';
   import type { CallNightDTO } from '@/call/api/types/dto';
   import type { CallNightQuery, CallRecordQuery } from '@/call/api/types/query';
 
@@ -716,22 +713,22 @@
     const countValue = formatTooltipCount(node.callCount ?? current?.value ?? 0);
 
     return [
-      '<div style="padding:14px 16px; min-width:208px; background:linear-gradient(180deg, rgba(19,33,62,0.98) 0%, rgba(15,23,42,0.98) 100%); border:1px solid rgba(148,163,184,0.22); border-radius:14px; box-shadow:0 16px 34px rgba(2,6,23,0.32);">',
-      '<div style="display:flex; align-items:center; justify-content:space-between; gap:12px; margin-bottom:10px;">',
-      `<span style="font-size:14px; line-height:20px; color:#f8fafc; font-weight:600;">${axisLabel}</span>`,
-      `<span style="font-size:12px; line-height:18px; color:#8ac5ff; cursor:pointer;" onclick='window.${NIGHT_TREND_TOOLTIP_HANDLER}(${dataIndex})'>查看详情</span>`,
+      '<div style="padding:11px 13px; min-width:188px; background:#ffffff; border:1px solid #e7ebf3; border-radius:10px; box-shadow:0 8px 24px rgba(16,24,40,0.12);">',
+      '<div style="display:flex; align-items:center; justify-content:space-between; gap:14px; margin-bottom:8px;">',
+      `<span style="font-size:12px; line-height:18px; color:#111827; font-weight:600;">${axisLabel}</span>`,
+      `<span style="font-size:11px; line-height:16px; color:#2a50ec; cursor:pointer;" onclick='window.${NIGHT_TREND_TOOLTIP_HANDLER}(${dataIndex})'>查看详情</span>`,
       '</div>',
       '<div style="display:flex; align-items:center; justify-content:space-between; gap:16px;">',
-      '<span style="display:flex; align-items:center; gap:8px; font-size:13px; line-height:20px; color:#cbd5e1;">',
-      '<span style="width:10px; height:10px; border-radius:50%; background:#62c4ff; box-shadow:0 0 0 4px rgba(255,255,255,0.06); display:inline-block;"></span>',
+      '<span style="display:flex; align-items:center; gap:7px; font-size:12px; line-height:18px; color:#5b6577;">',
+      '<span style="width:7px; height:7px; border-radius:50%; background:#2a50ec; display:inline-block;"></span>',
       '联系次数',
       '</span>',
-      `<span style="font-size:13px; line-height:20px; color:#eff6ff; font-weight:600;">${countValue} 次</span>`,
+      `<span style="font-size:12px; line-height:18px; color:#111827; font-weight:600;">${countValue} 次</span>`,
       '</div>',
-      '<div style="height:8px;"></div>',
+      '<div style="height:6px;"></div>',
       '<div style="display:flex; align-items:center; justify-content:space-between; gap:16px;">',
-      '<span style="font-size:13px; line-height:20px; color:#94a3b8;">联系对象</span>',
-      `<span style="font-size:13px; line-height:20px; color:#eff6ff; font-weight:600; max-width:120px; text-align:right; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">${safeOtherName}</span>`,
+      '<span style="font-size:12px; line-height:18px; color:#98a2b3;">联系对象</span>',
+      `<span style="font-size:12px; line-height:18px; color:#111827; font-weight:600; max-width:130px; text-align:right; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;">${safeOtherName}</span>`,
       '</div>',
       '</div>',
     ].join('');
@@ -798,7 +795,7 @@
         axisPointer: {
           type: 'line',
           lineStyle: {
-            color: 'rgba(63, 93, 230, 0.22)',
+            color: 'rgba(42, 80, 236, 0.32)',
             width: 2,
           },
         },
@@ -834,7 +831,7 @@
           },
         },
       },
-      grid: { left: '3%', right: '3%', top: 24, bottom: 8, outerBoundsMode: 'same', outerBoundsContain: 'axisLabel' },
+      grid: { left: '2%', right: '2%', top: 20, bottom: 4, outerBoundsMode: 'same', outerBoundsContain: 'axisLabel' },
       series: [
         {
           data: values,
@@ -842,30 +839,22 @@
           smooth: true,
           showSymbol: true,
           symbol: 'circle',
-          symbolSize: 4,
-          lineStyle: { color: '#1677ff', width: 3 },
+          symbolSize: 6,
+          lineStyle: { color: '#2a50ec', width: 2.5 },
           itemStyle: {
-            color: '#1677ff',
+            color: '#2a50ec',
             borderColor: '#ffffff',
             borderWidth: 2,
-            shadowBlur: 4,
-            shadowColor: 'rgba(22, 119, 255, 0.16)',
           },
           areaStyle: {
-            color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
-              { offset: 0, color: 'rgba(22, 119, 255, 0.18)' },
-              { offset: 1, color: 'rgba(22, 119, 255, 0.02)' },
-            ]),
+            color: 'rgba(42, 80, 236, 0.10)',
           },
           emphasis: {
             focus: 'series',
-            scale: true,
             itemStyle: {
-              color: '#003eb3',
+              color: '#1e3ed0',
               borderColor: '#ffffff',
-              borderWidth: 5,
-              shadowBlur: 26,
-              shadowColor: 'rgba(0, 62, 179, 0.58)',
+              borderWidth: 4,
             },
             lineStyle: {
               width: 4,
@@ -873,12 +862,11 @@
             label: {
               show: true,
               position: 'top',
-              color: '#003eb3',
+              color: '#1e3ed0',
               fontSize: 12,
-              fontWeight: 700,
+              fontWeight: 600,
               formatter: '{c}',
             },
-            scaleSize: 16,
           },
         },
       ],
@@ -911,376 +899,654 @@
 
   @prefix-cls: ~'jeesite-night';
 
-  .@{prefix-cls}.night-page {
-    .trans-analysis-table-ui();
-    background: #fff;
-    padding: 0 8px 2px;
-    min-height: 100%;
-    height: 100%;
+  /* ==========================================================================
+   * 夜间联系:与「持续联系」统一的卡片语言
+   * 背景 #f4f6fa · 卡片圆角 14 · 细边框 #e7ebf3 · 卡片间距 8px · 主色 #2a50ec
+   * ========================================================================== */
+  .@{prefix-cls}.night-page.qj-night-page {
     display: flex;
     flex-direction: column;
-    overflow: hidden;
-    box-sizing: border-box;
+    gap: 8px;
     width: calc(100% + 24px);
+    height: 100%;
+    min-height: 0;
     margin: 0 -12px;
-    padding-left: 0;
-    padding-right: 0;
 
-    :deep(.ant-btn) {
-      border-radius: 8px;
+    /* 共享 mixin 用 !important 把左右 padding 归零,这里以更高层级还原卡片两侧留白 */
+    padding: 8px 10px !important;
+    background: #f4f6fa;
+    box-sizing: border-box;
+    overflow: hidden;
+
+    /* ---------------- 查询条件卡 ---------------- */
+    .qj-filter-card {
+      flex: 0 0 auto;
+      border: 1px solid #e7ebf3;
+      border-radius: 14px;
+      background: #fff;
+      box-shadow: 0 1px 2px rgb(16 24 40 / 4%);
+      box-sizing: border-box;
+      overflow: hidden;
     }
 
-    :deep(.ant-btn-primary) {
-      box-shadow: none;
+    .qj-filter-head {
+      display: flex;
+      align-items: center;
+      gap: 10px;
+      min-height: 42px;
+      padding: 0 14px;
+      border-bottom: 1px solid #eff2f7;
     }
 
-    :deep(.ant-input),
-    :deep(.ant-input-affix-wrapper),
-    :deep(.ant-picker),
-    :deep(.ant-select-selector) {
-      border-radius: 8px;
+    .qj-card-title {
+      position: relative;
+      padding-left: 11px;
+      color: #111827;
+      font-size: 15px;
+      font-weight: 600;
+      line-height: 22px;
+      white-space: nowrap;
+
+      &::before {
+        content: '';
+        position: absolute;
+        left: 0;
+        top: 4px;
+        bottom: 4px;
+        width: 3px;
+        border-radius: 2px;
+        background: #2a50ec;
+      }
     }
 
-    .page-header {
-      display: none;
-      margin-bottom: 0;
-      flex-shrink: 0;
+    .qj-filter-actions {
+      display: flex;
+      flex: 0 0 auto;
+      align-items: center;
+      gap: 8px;
+      margin-left: auto;
     }
 
-    .page-title {
-      font-size: 16px;
-      font-weight: 600;
-      color: #111827;
-      padding-bottom: 8px;
-      border-bottom: 1px solid #e5e7eb;
+    .qj-btn-primary.ant-btn {
+      height: 30px;
+      padding: 0 18px;
+      border: 1px solid #2a50ec;
+      border-radius: 8px;
+      background: #2a50ec;
+      box-shadow: none;
+      font-size: 12px;
+      font-weight: 500;
+
+      &:hover,
+      &:focus {
+        border-color: #4b6bf0;
+        background: #4b6bf0;
+      }
+
+      &:active {
+        border-color: #1e3ed0;
+        background: #1e3ed0;
+      }
     }
 
-    .page-subtitle {
-      margin-top: 8px;
-      color: #6b7280;
+    .qj-btn-reset.ant-btn {
+      height: 30px;
+      padding: 0 14px;
+      border: 1px solid #e7ebf3;
+      border-radius: 8px;
+      background: #fff;
+      color: #5b6577;
       font-size: 12px;
-      line-height: 20px;
+      font-weight: 500;
+      box-shadow: none;
+
+      &:hover,
+      &:focus {
+        border-color: #bfcdfb;
+        background: #f8faff;
+        color: #2a50ec;
+      }
     }
 
-    .filter-bar {
+    /* 行内表单:label 在左、控件在右,控件统一 32px 高、白底 */
+    .qj-filter-grid {
       display: flex;
+      flex-wrap: wrap;
       align-items: center;
-      gap: 10px;
-      padding: 6px;
-      min-height: 40px;
-      border: 0;
-      border-radius: 0;
-      background: transparent;
-      flex-wrap: nowrap;
-      flex-shrink: 0;
-      overflow: hidden;
+      gap: 8px 14px;
+      padding: 10px 14px 12px;
       box-sizing: border-box;
     }
 
-    .filter-item {
-      display: flex;
+    .qj-field {
+      display: inline-flex;
+      flex: 0 0 auto;
       align-items: center;
-      gap: 4px;
-      flex: 0 1 auto;
+      gap: 6px;
       min-width: 0;
     }
 
-    .filter-label {
-      color: #6b7280;
+    .qj-field__label {
+      flex: 0 0 auto;
+      color: #5b6577;
       font-size: 12px;
+      line-height: 32px;
       white-space: nowrap;
     }
 
-    .filter-input {
-      width: 126px;
-      min-width: 0;
+    .qj-field__sep {
+      color: #98a2b3;
+      font-size: 12px;
+      white-space: nowrap;
     }
 
-    .filter-item--target .filter-input {
-      width: 142px;
+    /* 尺寸与「持续联系」对齐:普通输入/下拉 150,多选对象 200,日期区间 240 */
+    .filter-input {
+      width: 150px;
+      min-width: 0;
     }
 
-    .filter-item--month .filter-input {
-      width: 112px;
+    .filter-month {
+      width: 75px;
     }
 
     .filter-date {
-      width: 252px;
+      width: 240px;
     }
 
     .filter-op {
-      width: 78px;
-      min-width: 78px;
+      width: 75px;
+      min-width: 75px;
     }
 
-    .filter-actions {
-      display: flex;
+    .qj-field.qj-field--target .filter-input,
+    .qj-field.qj-field--target .ant-select {
+      width: 200px;
+    }
+
+    .qj-field .ant-input,
+    .qj-field .ant-input-affix-wrapper,
+    .qj-field .ant-picker {
+      height: 32px;
+      padding: 0 10px;
+      border: 1px solid #e7ebf3;
+      border-radius: 8px;
+      background: #fff;
+      box-shadow: none;
+      font-size: 12px;
+      transition:
+        border-color 0.2s ease,
+        box-shadow 0.2s ease;
+    }
+
+    .qj-field .ant-input-affix-wrapper {
+      display: inline-flex;
       align-items: center;
-      gap: 8px;
-      margin-left: auto;
-      flex-shrink: 0;
     }
 
-    .filter-input,
-    .filter-date,
-    .filter-op,
-    .filter-actions :deep(.ant-btn) {
-      align-self: center;
+    .qj-field .ant-input-affix-wrapper > .ant-input {
+      height: 100%;
+      padding: 0;
+      border: 0;
+      border-radius: 0;
+      background: transparent;
+      box-shadow: none;
     }
 
-    .content-panel {
-      display: flex;
-      flex-direction: column;
-      flex: 1 1 auto;
-      gap: 2px;
-      padding-left: 2px;
-      padding-right: 2px;
-      min-height: 0;
-      overflow: hidden;
-      background: #edf2f7;
-      box-sizing: border-box;
+    .qj-field .ant-picker .ant-picker-input > input {
+      font-size: 12px;
+    }
 
-      &::before,
-      &::after {
-        content: '';
-        display: block;
-        flex: 0 0 2px;
-        min-height: 2px;
-        background: #edf2f7;
-      }
+    .qj-field .ant-input:hover,
+    .qj-field .ant-input-affix-wrapper:hover,
+    .qj-field .ant-picker:hover,
+    .qj-field .ant-select:hover .ant-select-selector {
+      border-color: #bfcdfb;
+      background: #fff;
     }
 
-    .summary-panel,
-    .trend-panel {
-      flex: 1 1 0;
-      min-height: 0;
-      display: flex;
-      flex-direction: column;
-      overflow: hidden;
-      max-height: none;
-      padding: 8px;
-      border: 0;
-      border-radius: 12px;
+    .qj-field .ant-input:focus,
+    .qj-field .ant-input-affix-wrapper-focused,
+    .qj-field .ant-picker-focused,
+    .qj-field .ant-select-focused .ant-select-selector {
+      border-color: #2a50ec !important;
       background: #fff;
-      box-shadow: inset 0 0 0 1px #e2e8f0;
-      box-sizing: border-box;
+      box-shadow: 0 0 0 2px rgb(42 80 236 / 12%) !important;
     }
 
-    .summary-panel {
-      gap: 0;
-      min-height: 0;
+    .qj-field .ant-input-status-error,
+    .qj-field .ant-input-affix-wrapper-status-error {
+      border-color: #e5484d;
     }
 
-    .query-state-panel {
-      flex: 1 1 auto;
-      min-height: 0;
-      height: 100%;
+    .qj-field .ant-select {
+      width: 150px;
+      height: 32px;
+    }
+
+    .qj-field .filter-op.ant-select {
+      width: 75px;
+    }
+
+    .qj-field.qj-field--target .ant-select {
+      width: 200px;
+    }
+
+    .qj-field .ant-select .ant-select-selector {
+      height: 32px !important;
+      padding: 0 10px;
+      border: 1px solid #e7ebf3;
+      border-radius: 8px;
+      background: #fff;
+      box-shadow: none;
+    }
+
+    .qj-field .ant-select-single .ant-select-selection-item,
+    .qj-field .ant-select-single .ant-select-selection-placeholder {
+      line-height: 30px;
+      font-size: 12px;
+    }
+
+    .qj-field .ant-select-multiple .ant-select-selector {
       display: flex;
       align-items: center;
-      justify-content: center;
-      padding: 12px 0 0;
+      overflow: hidden;
     }
 
-    .table-panel {
+    .qj-field .ant-select-multiple .ant-select-selection-overflow {
+      flex-wrap: nowrap;
+      align-items: center;
+    }
+
+    .qj-field .ant-select-multiple .ant-select-selection-item {
+      height: 22px;
+      margin: 0 4px 0 0;
+      padding: 0 7px;
+      border-radius: 4px;
+      background: #eef2ff;
+      color: #2448c9;
+      font-size: 11px;
+      line-height: 22px;
+    }
+
+    .qj-field .ant-select-multiple .ant-select-selection-search {
+      margin-inline-start: 0;
+    }
+
+    .qj-field .ant-select-multiple .ant-select-selection-placeholder {
+      left: 10px;
+      line-height: 30px;
+      font-size: 12px;
+    }
+
+    .qj-field .ant-select-arrow {
+      color: #98a2b3;
+      font-size: 11px;
+    }
+
+    /* ---------------- 内容区 ---------------- */
+    .content-panel {
+      display: flex;
       flex: 1 1 auto;
+      flex-direction: column;
+      gap: 8px;
       min-height: 0;
+      padding: 0;
+      background: transparent;
+      box-sizing: border-box;
+      overflow: hidden;
+    }
+
+    .qj-summary-card,
+    .qj-trend-card {
       display: flex;
       flex-direction: column;
+      min-height: 0;
+      border: 1px solid #e7ebf3;
+      border-radius: 14px;
+      background: #fff;
+      box-shadow: 0 1px 2px rgb(16 24 40 / 4%);
+      box-sizing: border-box;
       overflow: hidden;
-      padding: 0;
     }
 
-    .summary-panel .table-wrapper {
+    .qj-summary-card {
       flex: 1 1 auto;
-      min-height: 0;
-      border: 0;
-      border-radius: 0;
-      overflow: hidden;
     }
 
-    .summary-panel .night-summary-table .ant-table-wrapper {
-      padding: 0;
+    .qj-trend-card {
+      flex: 0 0 268px;
     }
 
-    .panel-title-wrap {
+    .qj-card-head {
       display: flex;
-      flex-direction: column;
-      gap: 4px;
-      min-width: 0;
+      align-items: center;
+      gap: 10px;
+      flex: 0 0 auto;
+      min-height: 42px;
+      padding: 0 14px;
+      border-bottom: 1px solid #eff2f7;
     }
 
-    .panel-title {
-      font-size: 14px;
-      font-weight: 600;
-      color: #111827;
+    .qj-card-sub {
+      color: #98a2b3;
+      font-size: 11px;
+      line-height: 16px;
     }
 
-    .panel-desc {
-      color: #6b7280;
-      font-size: 12px;
-      line-height: 20px;
+    .qj-card-meta {
+      margin-left: auto;
+      color: #98a2b3;
+      font-size: 11px;
+      line-height: 16px;
+      white-space: nowrap;
+      font-variant-numeric: tabular-nums;
     }
 
-    .panel-header {
+    .query-state-panel {
       display: flex;
-      align-items: flex-start;
-      justify-content: space-between;
-      gap: 12px;
-      padding: 0 0 8px;
-      width: 100%;
+      flex: 1 1 auto;
+      align-items: center;
+      justify-content: center;
+      min-height: 0;
+      padding: 8px 0 16px;
+    }
+
+    .table-panel {
+      display: flex;
+      flex: 1 1 auto;
+      flex-direction: column;
+      min-height: 0;
+      padding: 0;
       box-sizing: border-box;
-      border-bottom: 1px solid #f1f5f9;
-      flex: 0 0 auto;
+      overflow: hidden;
     }
 
+    /* 表头固定 + body 内部滚动;逐层传递高度,避免 body 的 max-height 偏小留下空白 */
     .table-wrapper {
-      min-height: 0;
       display: flex;
+      flex: 1 1 auto;
       flex-direction: column;
+      min-height: 0;
+      overflow: hidden;
+      border: 0;
+      background: transparent;
+      box-shadow: none;
     }
 
     .table-wrapper .jeesite-basic-table {
-      height: 100%;
-      min-height: 0;
       display: flex;
       flex-direction: column;
+      height: 100%;
+      min-height: 0;
     }
 
     .table-wrapper .night-summary-table > div:first-child {
       display: none;
     }
 
-    .table-wrapper .night-summary-table > .ant-table-wrapper,
+    .table-wrapper .ant-table-wrapper,
     .table-wrapper .ant-spin-nested-loading,
-    .table-wrapper .ant-spin-container,
-    .table-wrapper .ant-table,
-    .table-wrapper .ant-table-container {
-      flex: 1 1 auto;
-      min-height: 0;
-      height: 100%;
-    }
-
     .table-wrapper .ant-spin-container,
     .table-wrapper .ant-table,
     .table-wrapper .ant-table-container {
       display: flex;
+      flex: 1 1 auto;
       flex-direction: column;
+      height: 100%;
+      min-height: 0;
     }
 
     .table-wrapper .ant-table-body {
+      flex: 1 1 auto;
+      min-height: 0;
       max-height: 100% !important;
+      overflow-y: auto;
+    }
+
+    .table-wrapper .ant-table-header {
+      flex: 0 0 auto;
+    }
+
+    .table-wrapper .ant-table {
+      font-size: 12px;
+    }
+
+    .table-wrapper .ant-table-thead > tr > th {
+      height: 40px;
+      padding: 0 12px;
+      border-right: 0;
+      border-bottom: 1px solid #eff2f7;
+      background: #f8fafc;
+      color: #5b6577;
+      font-size: 12px;
+      font-weight: 600;
+    }
+
+    .table-wrapper .ant-table-thead > tr > th::before {
+      display: none;
+    }
+
+    .table-wrapper .ant-table-thead > tr:first-child > th:first-child {
+      border-top-left-radius: 8px;
+    }
+
+    .table-wrapper .ant-table-thead > tr:first-child > th:last-child {
+      border-top-right-radius: 8px;
+    }
+
+    .table-wrapper .ant-table-tbody > tr > td {
+      height: 40px;
+      padding: 0 12px;
+      border-right: 0;
+      border-bottom: 1px solid #f4f6fa !important;
+      color: #1f2937;
+      font-size: 12px;
+    }
+
+    .table-wrapper .ant-table-tbody > tr:last-child > td {
+      border-bottom: 0 !important;
+    }
+
+    .table-wrapper .ant-table-tbody > tr {
+      cursor: pointer;
+    }
+
+    .table-wrapper .ant-table-tbody > tr:hover > td {
+      background: #f8faff !important;
+    }
+
+    .table-wrapper .ant-table-tbody > tr.summary-row-selected > td,
+    .table-wrapper .ant-table-tbody > tr.active-row > td {
+      background: #dbe7ff !important;
+      color: #1e3ed0;
+      font-weight: 600;
+      box-shadow: none;
+      border-radius: 0;
+    }
+
+    .table-wrapper .ant-table-tbody > tr.summary-row-selected:hover > td,
+    .table-wrapper .ant-table-tbody > tr.active-row:hover > td {
+      background: #d1e0ff !important;
+    }
+
+    .table-wrapper .ant-table-tbody > tr.summary-row-selected > td:first-child,
+    .table-wrapper .ant-table-tbody > tr.active-row > td:first-child {
+      color: #1e3ed0;
+      font-weight: 600;
     }
 
     .pagination-bar {
       display: flex;
       align-items: center;
       justify-content: flex-end;
-      gap: 8px;
       flex: 0 0 auto;
-      flex-wrap: wrap;
-      padding: 8px 0 0;
-      width: 100%;
+      padding: 8px 14px 10px;
+      border-top: 1px solid #eff2f7;
+      background: transparent;
       box-sizing: border-box;
-      color: #6b7280;
+    }
+
+    .pagination-bar .analysis-pagination {
+      color: #98a2b3;
+      font-size: 11px;
+    }
+
+    .pagination-bar .ant-pagination-item,
+    .pagination-bar .ant-pagination-prev,
+    .pagination-bar .ant-pagination-next {
+      min-width: 26px;
+      height: 26px;
+      border-radius: 6px;
+      border-color: #e7ebf3;
+      line-height: 24px;
+    }
+
+    .pagination-bar .ant-pagination-item a {
+      color: #5b6577;
       font-size: 12px;
-      border-top: 1px solid #f1f5f9;
+      line-height: 24px;
     }
 
-    .pagination-total {
-      margin-right: 0;
-      white-space: nowrap;
+    .pagination-bar .ant-pagination-item-active {
+      border-color: #2a50ec;
+      background: #eef2ff;
     }
 
-    .pagination-size,
-    .pagination-actions,
-    .pagination-jump {
-      display: flex;
-      align-items: center;
-      gap: 6px;
-      white-space: nowrap;
+    .pagination-bar .ant-pagination-item-active a {
+      color: #2a50ec;
+      font-weight: 600;
     }
 
-    .pagination-input {
-      width: 48px;
+    .pagination-bar .ant-pagination-options {
+      margin-inline-start: 8px;
     }
 
-    .trend-panel {
-      gap: 12px;
-      min-height: 0;
+    .pagination-bar .ant-select-selector {
+      border-radius: 6px;
+    }
+
+    .pagination-bar .ant-pagination-options-quick-jumper input {
+      height: 26px;
+      border-radius: 6px;
     }
 
+    /* ---------------- 趋势卡 ---------------- */
     .trend-panel-body {
-      flex: 1 1 auto;
-      min-height: 0;
       display: flex;
+      flex: 1 1 auto;
       flex-direction: column;
-      gap: 12px;
-      padding: 8px 0 0;
-      width: 100%;
+      min-height: 0;
+      padding: 10px 14px 12px;
       box-sizing: border-box;
     }
 
     .trend-chart-shell {
-      flex: 1 1 auto;
-      min-height: 0;
-      padding: 0;
-      border: 0;
-      border-radius: 0;
-      background: transparent;
-      box-shadow: none;
       display: flex;
+      flex: 1 1 auto;
       flex-direction: column;
+      min-height: 0;
       overflow: hidden;
     }
 
     .trend-empty {
-      height: 100%;
       display: flex;
-      flex-direction: column;
+      flex: 1 1 auto;
       align-items: center;
       justify-content: center;
-      gap: 10px;
-      border: 1px dashed #d7e3f4;
-      border-radius: 10px;
-      background: #fafcff;
-    }
-
-    .trend-empty-title {
-      font-size: 14px;
-      font-weight: 600;
-      color: #111827;
-    }
-
-    .trend-empty-tip {
-      color: #6b7280;
-      font-size: 12px;
-      line-height: 20px;
-      text-align: center;
+      min-height: 0;
     }
 
     .trend-chart {
       flex: 1 1 auto;
-      min-height: 180px;
       width: 100%;
+      min-height: 0;
     }
 
-    @media screen and (max-width: 1280px) {
-      .filter-bar {
-        flex-wrap: wrap;
-        overflow: visible;
+    /* ---------------- 响应式 ---------------- */
+    @media screen and (max-width: 1440px) {
+      .qj-trend-card {
+        flex-basis: 232px;
       }
+    }
 
-      .filter-actions {
-        width: 100%;
-        margin-left: 0;
+    @media screen and (max-width: 1280px) {
+      .qj-trend-card {
+        flex-basis: 210px;
       }
     }
 
     @media screen and (max-width: 992px) {
+      .qj-card-sub {
+        display: none;
+      }
+
+      .qj-field,
+      .qj-field--target {
+        flex: 1 1 100%;
+      }
+
+      .qj-field .ant-select,
+      .qj-field .filter-op.ant-select,
+      .qj-field.qj-field--target .ant-select,
       .filter-input,
       .filter-date {
         width: 100%;
       }
+
+      .qj-field .filter-op.ant-select {
+        flex: 1 1 0;
+        width: auto;
+      }
+    }
+
+    /* ---------------- 暗色主题 ---------------- */
+    html[data-theme='dark'] & {
+      background: #0f172a;
+
+      .qj-filter-card,
+      .qj-summary-card,
+      .qj-trend-card {
+        border-color: #1f2937;
+        background: #111827;
+        box-shadow: none;
+      }
+
+      .qj-card-title {
+        color: #e5e7eb;
+      }
+
+      .qj-field__label,
+      .qj-card-sub,
+      .qj-card-meta {
+        color: #94a3b8;
+      }
+
+      .qj-filter-head,
+      .qj-card-head,
+      .pagination-bar {
+        border-color: #1f2937;
+      }
+
+      .qj-field .ant-input,
+      .qj-field .ant-input-affix-wrapper,
+      .qj-field .ant-picker,
+      .qj-field .ant-select .ant-select-selector {
+        border-color: #1f2937;
+        background: #0f172a;
+        color: #e5e7eb;
+      }
+
+      .table-wrapper .ant-table-thead > tr > th {
+        border-color: #1f2937;
+        background: #0f172a;
+        color: #94a3b8;
+      }
+
+      .table-wrapper .ant-table-tbody > tr > td {
+        border-color: #1f2937;
+        color: #e5e7eb;
+      }
     }
   }
 </style>

+ 536 - 243
ai-electron/frontend/src/call/views/sensitive/index.vue

@@ -1,49 +1,54 @@
 <template>
-  <div :class="prefixCls" class="sensitive-page">
-    <div class="filter-bar">
-      <div class="filter-item">
-        <span class="filter-label">选择对象:</span>
-        <Select
-          v-model:value="filters.target"
-          mode="multiple"
-          allow-clear
-          max-tag-count="responsive"
-          class="filter-input"
-          :options="targetOptions"
-          placeholder="全部"
-          :getPopupContainer="getPagePopupContainer"
-        />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">联系日期:</span>
-        <RangePicker
-          v-model:value="filters.dateRange"
-          class="filter-input filter-date"
-          :format="DATE_FORMAT"
-          :value-format="DATE_FORMAT"
-          :placeholder="['开始日期', '结束日期']"
-          :getPopupContainer="getPagePopupContainer"
-        />
+  <div :class="prefixCls" class="sensitive-page qj-sensitive-page">
+    <section class="qj-filter-card">
+      <div class="qj-filter-head">
+        <div class="qj-card-title">查询条件</div>
+        <div class="qj-filter-actions">
+          <Button class="qj-btn-primary" type="primary" :loading="queryLoading" @click="handleSearch">查询</Button>
+          <Button class="qj-btn-reset" @click="handleReset">重置</Button>
+          <Button class="qj-btn-ghost" @click="handleOpenSpecialDateModal">特殊日期配置</Button>
+        </div>
       </div>
-      <div class="filter-actions">
-        <Button type="primary" :loading="queryLoading" @click="handleSearch">查询</Button>
-        <Button @click="handleReset">重置</Button>
-        <Button @click="handleOpenSpecialDateModal">特殊日期配置</Button>
+      <div class="qj-filter-grid">
+        <div class="qj-field qj-field--target">
+          <span class="qj-field__label">选择对象</span>
+          <Select
+            v-model:value="filters.target"
+            mode="multiple"
+            allow-clear
+            max-tag-count="responsive"
+            class="filter-input"
+            :options="targetOptions"
+            placeholder="全部"
+            :getPopupContainer="getPagePopupContainer"
+          />
+        </div>
+        <div class="qj-field">
+          <span class="qj-field__label">联系日期</span>
+          <RangePicker
+            v-model:value="filters.dateRange"
+            class="filter-input filter-date"
+            :format="DATE_FORMAT"
+            :value-format="DATE_FORMAT"
+            :placeholder="['开始日期', '结束日期']"
+            :getPopupContainer="getPagePopupContainer"
+          />
+        </div>
       </div>
-    </div>
+    </section>
 
     <div class="content-panel">
-      <div class="panel-left">
-        <div class="panel-header">
-          <div class="panel-title-wrap">
-            <div class="panel-title">联系对方汇总</div>
-            <div class="panel-desc">找出与对象在敏感日期保持联系的人员或号码。</div>
-          </div>
+      <section class="qj-panel-card qj-panel-left">
+        <div class="qj-card-head">
+          <div class="qj-card-title">联系对方汇总</div>
+          <div class="qj-card-sub">找出与对象在敏感日期保持联系的人员或号码</div>
+          <div class="qj-card-meta">{{ hasSearched ? `共 ${summaryTotal.toLocaleString('zh-CN')} 条` : '—' }}</div>
         </div>
-        <div class="panel-search">
+        <div class="qj-panel-toolbar">
           <Input
             :value="filters.keyword"
-            placeholder="请输入对方姓名/对方号码搜索"
+            class="qj-search"
+            placeholder="请输入对方姓名 / 对方号码搜索"
             allow-clear
             @update:value="handleKeywordUpdate"
             @pressEnter="handleKeywordEnter"
@@ -84,19 +89,20 @@
             @change="handleSummaryPaginationChange"
           />
         </div>
-      </div>
-
-      <div class="panel-right">
-        <div class="panel-header panel-header--actions">
-          <div class="panel-title-wrap">
-            <div class="panel-title">通话详情</div>
-            <div class="panel-desc">敏感日期通话明细</div>
+      </section>
+
+      <section class="qj-panel-card qj-panel-right">
+        <div class="qj-card-head">
+          <div class="qj-card-title">通话详情</div>
+          <div class="qj-card-sub">敏感日期通话明细</div>
+          <div v-if="selectedSummaryRow" class="qj-card-meta">
+            {{ selectedSummaryRow.personName }} · {{ selectedSummaryRow.otherName }}
           </div>
         </div>
         <div class="table-wrapper table-wrapper--detail">
-          <div v-if="detailLoading && !festivalData.length" class="panel-empty panel-empty--record"
-            >敏感日期通话明细加载中...</div
-          >
+          <div v-if="detailLoading && !festivalData.length" class="panel-empty panel-empty--record">
+            敏感日期通话明细加载中...
+          </div>
           <BasicTable
             v-else-if="festivalData.length"
             :columns="festivalColumns"
@@ -131,7 +137,7 @@
             @change="handleDetailPaginationChange"
           />
         </div>
-      </div>
+      </section>
     </div>
 
     <SpecialDateConfigModal v-model:open="specialDateModalOpen" summary="统一维护敏感日期联系分析使用的特殊日期配置" />
@@ -735,155 +741,369 @@
 
   @prefix-cls: ~'jeesite-sensitive';
 
-  .@{prefix-cls}.sensitive-page {
-    .trans-analysis-table-ui();
-
-    background: #fff;
-    padding: 0 8px 2px;
-    min-height: 100%;
-    height: 100%;
+  /* ==========================================================================
+   * 敏感日期联系:与「持续联系」统一的卡片语言
+   * 背景 #f4f6fa · 卡片圆角 14 · 细边框 #e7ebf3 · 间距 8px · 主色 #2a50ec
+   * ========================================================================== */
+  .@{prefix-cls}.sensitive-page.qj-sensitive-page {
     display: flex;
     flex-direction: column;
-    overflow: hidden;
-    box-sizing: border-box;
+    gap: 8px;
     width: calc(100% + 24px);
+    height: 100%;
+    min-height: 0;
     margin: 0 -12px;
-    padding-left: 0;
-    padding-right: 0;
+    padding: 8px 10px;
+    background: #f4f6fa;
+    box-sizing: border-box;
+    overflow: hidden;
 
-    :deep(.ant-btn) {
-      border-radius: 8px;
+    /* ---------------- 查询条件卡 ---------------- */
+    .qj-filter-card {
+      flex: 0 0 auto;
+      border: 1px solid #e7ebf3;
+      border-radius: 14px;
+      background: #fff;
+      box-shadow: 0 1px 2px rgb(16 24 40 / 4%);
+      box-sizing: border-box;
+      overflow: hidden;
     }
 
-    :deep(.ant-btn-primary) {
-      box-shadow: none;
+    .qj-filter-head {
+      display: flex;
+      align-items: center;
+      gap: 10px;
+      min-height: 42px;
+      padding: 0 14px;
+      border-bottom: 1px solid #eff2f7;
     }
 
-    :deep(.ant-input),
-    :deep(.ant-input-affix-wrapper),
-    :deep(.ant-picker),
-    :deep(.ant-select-selector) {
-      border-radius: 8px;
+    .qj-card-title {
+      position: relative;
+      padding-left: 11px;
+      color: #111827;
+      font-size: 15px;
+      font-weight: 600;
+      line-height: 22px;
+      white-space: nowrap;
+
+      &::before {
+        content: '';
+        position: absolute;
+        left: 0;
+        top: 4px;
+        bottom: 4px;
+        width: 3px;
+        border-radius: 2px;
+        background: #2a50ec;
+      }
     }
 
-    & > .filter-bar {
+    .qj-filter-actions {
       display: flex;
+      flex: 0 0 auto;
       align-items: center;
       gap: 8px;
-      padding: 6px;
-      min-height: 40px;
-      border: 0;
-      border-bottom: 1px solid #e5e7eb;
-      border-radius: 0;
-      background: transparent;
+      margin-left: auto;
+    }
+
+    .qj-btn-primary.ant-btn,
+    .qj-btn-ghost.ant-btn,
+    .qj-btn-reset.ant-btn {
+      height: 30px;
+      padding: 0 16px;
+      border-radius: 8px;
+      font-size: 12px;
+      font-weight: 500;
+      box-shadow: none;
+    }
+
+    .qj-btn-primary.ant-btn {
+      border: 1px solid #2a50ec;
+      background: #2a50ec;
+
+      &:hover,
+      &:focus {
+        border-color: #4b6bf0;
+        background: #4b6bf0;
+      }
+
+      &:active {
+        border-color: #1e3ed0;
+        background: #1e3ed0;
+      }
+    }
+
+    .qj-btn-reset.ant-btn,
+    .qj-btn-ghost.ant-btn {
+      border: 1px solid #e7ebf3;
+      background: #fff;
+      color: #5b6577;
+
+      &:hover,
+      &:focus {
+        border-color: #bfcdfb;
+        background: #f8faff;
+        color: #2a50ec;
+      }
+    }
+
+    /* 行内表单:label 在左、控件在右,控件统一 32px 高、白底 */
+    .qj-filter-grid {
+      display: flex;
       flex-wrap: wrap;
-      flex-shrink: 0;
+      align-items: center;
+      gap: 8px 14px;
+      padding: 10px 14px 12px;
       box-sizing: border-box;
     }
 
-    & > .filter-bar > .filter-item {
-      display: flex;
+    .qj-field {
+      display: inline-flex;
+      flex: 0 0 auto;
       align-items: center;
       gap: 6px;
+      min-width: 0;
     }
 
-    .filter-label {
-      color: #6b7280;
+    .qj-field__label {
+      flex: 0 0 auto;
+      color: #5b6577;
       font-size: 12px;
+      line-height: 32px;
       white-space: nowrap;
     }
 
     .filter-input {
-      width: 180px;
+      width: 240px;
+      min-width: 0;
     }
 
     .filter-date {
-      width: 360px;
+      width: 240px;
     }
 
-    & > .filter-bar > .filter-actions {
-      display: flex;
+    .qj-field.qj-field--target .filter-input,
+    .qj-field.qj-field--target .ant-select {
+      width: 240px;
+    }
+
+    .qj-field .ant-input,
+    .qj-field .ant-input-affix-wrapper,
+    .qj-field .ant-picker {
+      height: 32px;
+      padding: 0 10px;
+      border: 1px solid #e7ebf3;
+      border-radius: 8px;
+      background: #fff;
+      box-shadow: none;
+      font-size: 12px;
+      transition:
+        border-color 0.2s ease,
+        box-shadow 0.2s ease;
+    }
+
+    .qj-field .ant-input-affix-wrapper {
+      display: inline-flex;
       align-items: center;
-      gap: 8px;
-      margin-left: auto;
     }
 
-    & > .filter-bar .filter-input,
-    & > .filter-bar .filter-date,
-    & > .filter-bar .filter-actions :deep(.ant-btn) {
-      align-self: center;
+    .qj-field .ant-input-affix-wrapper > .ant-input {
+      height: 100%;
+      padding: 0;
+      border: 0;
+      border-radius: 0;
+      background: transparent;
+      box-shadow: none;
     }
 
-    .panel-header {
-      display: flex;
-      align-items: flex-start;
-      justify-content: flex-start;
-      gap: 12px;
-      padding: 0 0 8px;
-      width: 100%;
-      box-sizing: border-box;
-      border-bottom: 1px solid #f1f5f9;
-      flex: 0 0 auto;
+    .qj-field .ant-picker .ant-picker-input > input {
+      font-size: 12px;
+    }
+
+    .qj-field .ant-input:hover,
+    .qj-field .ant-input-affix-wrapper:hover,
+    .qj-field .ant-picker:hover,
+    .qj-field .ant-select:hover .ant-select-selector {
+      border-color: #bfcdfb;
+      background: #fff;
+    }
+
+    .qj-field .ant-input:focus,
+    .qj-field .ant-input-affix-wrapper-focused,
+    .qj-field .ant-picker-focused,
+    .qj-field .ant-select-focused .ant-select-selector {
+      border-color: #2a50ec !important;
+      background: #fff;
+      box-shadow: 0 0 0 2px rgb(42 80 236 / 12%) !important;
+    }
+
+    .qj-field .ant-select {
+      width: 240px;
+      height: 32px;
+    }
+
+    .qj-field .ant-select .ant-select-selector {
+      height: 32px !important;
+      padding: 0 10px;
+      border: 1px solid #e7ebf3;
+      border-radius: 8px;
+      background: #fff;
+      box-shadow: none;
     }
 
-    .panel-header.jeesite-basic-table-panel-setting-host {
-      border-bottom: 1px solid #f1f5f9;
+    .qj-field .ant-select-single .ant-select-selection-item,
+    .qj-field .ant-select-single .ant-select-selection-placeholder {
+      line-height: 30px;
+      font-size: 12px;
     }
 
-    .panel-header--actions {
+    .qj-field .ant-select-multiple .ant-select-selector {
+      display: flex;
       align-items: center;
+      overflow: hidden;
     }
 
-    .panel-title-wrap {
+    .qj-field .ant-select-multiple .ant-select-selection-overflow {
+      flex-wrap: nowrap;
+      align-items: center;
+    }
+
+    .qj-field .ant-select-multiple .ant-select-selection-item {
+      height: 22px;
+      margin: 0 4px 0 0;
+      padding: 0 7px;
+      border-radius: 4px;
+      background: #eef2ff;
+      color: #2448c9;
+      font-size: 11px;
+      line-height: 22px;
+    }
+
+    .qj-field .ant-select-multiple .ant-select-selection-search {
+      margin-inline-start: 0;
+    }
+
+    .qj-field .ant-select-multiple .ant-select-selection-placeholder {
+      left: 10px;
+      line-height: 30px;
+      font-size: 12px;
+    }
+
+    .qj-field .ant-select-arrow {
+      color: #98a2b3;
+      font-size: 11px;
+    }
+
+    /* ---------------- 内容区:左右两张卡 ---------------- */
+    .content-panel {
+      display: flex;
+      flex: 1 1 auto;
+      gap: 8px;
+      min-height: 0;
+      padding: 0;
+      background: transparent;
+      box-sizing: border-box;
+      overflow: hidden;
+    }
+
+    .qj-panel-card {
       display: flex;
       flex-direction: column;
-      gap: 4px;
+      min-height: 0;
       min-width: 0;
+      border: 1px solid #e7ebf3;
+      border-radius: 14px;
+      background: #fff;
+      box-shadow: 0 1px 2px rgb(16 24 40 / 4%);
+      box-sizing: border-box;
+      overflow: hidden;
     }
 
-    .panel-title {
-      font-size: 14px;
-      font-weight: 600;
-      color: #111827;
-      line-height: 22px;
+    .qj-panel-left {
+      flex: 0 0 42%;
     }
 
-    .panel-desc {
-      color: #6b7280;
-      font-size: 12px;
-      line-height: 20px;
+    .qj-panel-right {
+      flex: 1 1 auto;
     }
 
-    .panel-actions {
+    .qj-card-head {
       display: flex;
       align-items: center;
-      gap: 8px;
-      flex-shrink: 0;
+      gap: 10px;
+      flex: 0 0 auto;
+      min-height: 42px;
+      padding: 0 14px;
+      border-bottom: 1px solid #eff2f7;
     }
 
-    .panel-search {
-      padding: 0;
-      margin-bottom: 0;
-      width: 100%;
+    .qj-card-sub {
+      color: #98a2b3;
+      font-size: 11px;
+      line-height: 16px;
+    }
+
+    .qj-card-meta {
+      margin-left: auto;
+      color: #98a2b3;
+      font-size: 11px;
+      line-height: 16px;
+      white-space: nowrap;
+      font-variant-numeric: tabular-nums;
+    }
+
+    .qj-panel-toolbar {
+      flex: 0 0 auto;
+      padding: 8px 14px 0;
       box-sizing: border-box;
-      flex-shrink: 0;
     }
 
-    .content-panel > .panel-left > .panel-search,
-    .content-panel > .panel-right > .panel-search {
+    .qj-search.ant-input-affix-wrapper,
+    .qj-search.ant-input {
+      width: 100%;
+      height: 30px;
+      padding: 0 9px;
+      border: 1px solid #e7ebf3;
+      border-radius: 8px;
+      background: #fff;
+      box-shadow: none;
+      font-size: 12px;
+    }
+
+    .qj-search.ant-input-affix-wrapper > .ant-input {
+      height: 100%;
       padding: 0;
-      margin: 0;
+      border: 0;
+      border-radius: 0;
+      background: transparent;
+      box-shadow: none;
+      font-size: 12px;
     }
 
-    .panel-search :deep(.ant-input),
-    .panel-search :deep(.ant-input-affix-wrapper) {
-      width: 100%;
+    .qj-search.ant-input-affix-wrapper:focus,
+    .qj-search.ant-input-affix-wrapper-focused,
+    .qj-search.ant-input:focus {
+      border-color: #2a50ec;
+      box-shadow: 0 0 0 2px rgb(42 80 236 / 12%);
+    }
+
+    .query-state-panel {
+      display: flex;
+      flex: 1 1 auto;
+      align-items: center;
+      justify-content: center;
+      min-height: 0;
+      padding: 8px 0 16px;
     }
 
+    /* ---------------- 表格 ---------------- */
     .table-wrapper {
+      display: flex;
       flex: 1 1 auto;
+      flex-direction: column;
       min-height: 0;
-      margin: 0;
+      padding-top: 8px;
       width: 100%;
       box-sizing: border-box;
       border: 0;
@@ -892,39 +1112,39 @@
       overflow: hidden;
     }
 
-    .content-panel > .panel-left > .table-wrapper,
-    .content-panel > .panel-right > .table-wrapper {
-      margin: 0;
-    }
-
     .table-wrapper .jeesite-basic-table {
-      height: 100%;
       display: flex;
       flex-direction: column;
+      height: 100%;
+      min-height: 0;
     }
 
     .table-wrapper .jeesite-basic-table > div:first-child {
       display: none;
     }
 
+    /* 逐层传递高度,避免 table body 的 max-height 偏小在底部留下空白 */
     .table-wrapper .ant-table-wrapper,
     .table-wrapper .ant-spin-nested-loading,
-    .table-wrapper .ant-spin-container,
-    .table-wrapper .ant-table,
-    .table-wrapper .ant-table-container {
-      height: 100%;
-      min-height: 0;
-    }
-
     .table-wrapper .ant-spin-container,
     .table-wrapper .ant-table,
     .table-wrapper .ant-table-container {
       display: flex;
+      flex: 1 1 auto;
       flex-direction: column;
+      height: 100%;
+      min-height: 0;
     }
 
     .table-wrapper .ant-table-body {
+      flex: 1 1 auto;
+      min-height: 0;
       max-height: 100% !important;
+      overflow-y: auto;
+    }
+
+    .table-wrapper .ant-table-header {
+      flex: 0 0 auto;
     }
 
     .table-wrapper .ant-table-body::-webkit-scrollbar {
@@ -943,15 +1163,7 @@
 
     .table-wrapper--compact {
       flex: 0 0 auto;
-    }
-
-    .query-state-panel {
-      flex: 1 1 auto;
-      min-height: 0;
-      display: flex;
-      align-items: center;
-      justify-content: center;
-      padding: 12px 0 0;
+      padding-bottom: 4px;
     }
 
     .table-wrapper--compact .jeesite-basic-table,
@@ -961,6 +1173,7 @@
     .table-wrapper--compact .ant-table,
     .table-wrapper--compact .ant-table-container {
       height: auto;
+      min-height: 0;
     }
 
     .table-wrapper--compact .ant-table-body {
@@ -982,150 +1195,230 @@
       background: transparent !important;
     }
 
-    .table-wrapper :deep(.ant-table-thead > tr > th),
-    .table-wrapper :deep(.ant-table-tbody > tr > td) {
-      vertical-align: middle;
+    .table-wrapper .ant-table {
+      font-size: 12px;
     }
 
-    .panel-export {
-      flex-shrink: 0;
+    .table-wrapper .ant-table-thead > tr > th {
+      height: 40px;
+      padding: 0 12px;
+      border-right: 0;
+      border-bottom: 1px solid #eff2f7;
+      background: #f8fafc;
+      color: #5b6577;
+      font-size: 12px;
+      font-weight: 600;
     }
 
-    .pagination-bar {
-      display: flex;
-      align-items: center;
-      justify-content: flex-end;
-      gap: 8px;
-      color: #6b7280;
+    .table-wrapper .ant-table-thead > tr > th::before {
+      display: none;
+    }
+
+    .table-wrapper .ant-table-tbody > tr > td {
+      height: 40px;
+      padding: 0 12px;
+      border-right: 0;
+      border-bottom: 1px solid #f4f6fa !important;
+      color: #1f2937;
       font-size: 12px;
-      flex: 0 0 auto;
-      flex-wrap: wrap;
-      padding: 0;
-      width: 100%;
-      box-sizing: border-box;
-      border-top: 1px solid #f1f5f9;
-      background: transparent;
     }
 
-    .content-panel > .panel-left > .pagination-bar,
-    .content-panel > .panel-right > .pagination-bar {
-      padding: 0;
-      margin: 0;
+    .table-wrapper .ant-table-tbody > tr:last-child > td {
+      border-bottom: 0 !important;
     }
 
-    .pagination-total {
-      margin-right: 0;
-      white-space: nowrap;
+    .table-wrapper .ant-table-tbody > tr {
+      vertical-align: middle;
     }
 
-    .pagination-size,
-    .pagination-actions,
-    .pagination-jump {
-      display: flex;
-      align-items: center;
-      gap: 6px;
-      white-space: nowrap;
+    .table-wrapper .ant-table-thead > tr > th,
+    .table-wrapper .ant-table-tbody > tr > td {
+      vertical-align: middle;
     }
 
-    .pagination-input {
-      width: 48px;
+    .table-wrapper .ant-table-tbody > tr:hover > td {
+      background: #f8faff !important;
     }
 
-    .table-wrapper--detail {
-      margin-bottom: 0;
+    .table-wrapper .ant-table-tbody > tr.summary-row-selected > td,
+    .table-wrapper .ant-table-tbody > tr.active-row > td {
+      background: #dbe7ff !important;
+      color: #1e3ed0;
+      font-weight: 600;
+      box-shadow: none;
+      border-radius: 0;
     }
 
-    .analysis-flat-layout();
+    .table-wrapper .ant-table-tbody > tr.summary-row-selected:hover > td,
+    .table-wrapper .ant-table-tbody > tr.active-row:hover > td {
+      background: #d1e0ff !important;
+    }
 
-    padding: 0 8px 2px;
+    .table-wrapper .ant-table-tbody > tr.summary-row-selected > td:first-child,
+    .table-wrapper .ant-table-tbody > tr.active-row > td:first-child {
+      color: #1e3ed0;
+      font-weight: 600;
+    }
 
-    > .filter-bar {
-      padding: 6px;
-      min-height: 40px;
-      box-sizing: border-box;
+    .table-wrapper .ant-table-tbody > tr > td .ant-btn-link {
+      height: auto;
+      padding: 0;
+      color: #2a50ec;
+      font-size: 12px;
+      font-weight: 500;
     }
 
-    > .filter-bar > .filter-item {
+    .table-wrapper .ant-table-column-sorter {
+      color: #b6bfcc;
+    }
+
+    /* ---------------- 空态 / 分页 ---------------- */
+    .panel-empty {
       display: flex;
+      flex: 1 1 auto;
       align-items: center;
-      gap: 6px;
+      justify-content: center;
+      min-height: 0;
+      color: #98a2b3;
+      font-size: 12px;
     }
 
-    > .filter-bar > .filter-actions {
+    .pagination-bar {
       display: flex;
       align-items: center;
-      gap: 8px;
-      margin-left: auto;
+      justify-content: flex-end;
+      flex: 0 0 auto;
+      padding: 8px 14px 10px;
+      border-top: 1px solid #eff2f7;
+      background: transparent;
+      box-sizing: border-box;
     }
 
-    > .filter-bar .filter-input,
-    > .filter-bar .filter-date,
-    > .filter-bar .filter-actions :deep(.ant-btn) {
-      align-self: center;
+    .pagination-bar .analysis-pagination {
+      color: #98a2b3;
+      font-size: 11px;
     }
 
-    .content-panel {
-      display: flex;
-      gap: 2px;
-      margin-top: 0;
-      padding-top: 2px;
-      padding-left: 2px;
-      padding-right: 2px;
-      padding-bottom: 2px;
-      flex: 1 1 auto;
-      min-height: 0;
-      overflow: hidden;
-      background: #edf2f7;
-      box-sizing: border-box;
+    .pagination-bar .ant-pagination-item,
+    .pagination-bar .ant-pagination-prev,
+    .pagination-bar .ant-pagination-next {
+      min-width: 26px;
+      height: 26px;
+      border-radius: 6px;
+      border-color: #e7ebf3;
+      line-height: 24px;
     }
 
-    .content-panel > .panel-left,
-    .content-panel > .panel-right {
-      border: 0;
-      border-radius: 12px;
-      background: #fff;
-      box-shadow: inset 0 0 0 1px #e2e8f0;
-      min-height: 0;
-      display: flex;
-      flex-direction: column;
-      overflow: hidden;
-      padding: 8px;
-      box-sizing: border-box;
+    .pagination-bar .ant-pagination-item a {
+      color: #5b6577;
+      font-size: 12px;
+      line-height: 24px;
     }
 
-    .content-panel > .panel-left {
-      flex: 0 0 42%;
-      min-width: 0;
+    .pagination-bar .ant-pagination-item-active {
+      border-color: #2a50ec;
+      background: #eef2ff;
     }
 
-    .content-panel > .panel-right {
-      flex: 1 1 auto;
-      min-width: 0;
+    .pagination-bar .ant-pagination-item-active a {
+      color: #2a50ec;
+      font-weight: 600;
+    }
+
+    .pagination-bar .ant-pagination-options {
+      margin-inline-start: 8px;
     }
 
-    @media screen and (max-width: 900px) {
+    .pagination-bar .ant-select-selector {
+      border-radius: 6px;
+    }
+
+    .pagination-bar .ant-pagination-options-quick-jumper input {
+      height: 26px;
+      border-radius: 6px;
+    }
+
+    /* ---------------- 响应式 ---------------- */
+    @media screen and (max-width: 1280px) {
+      .qj-card-sub {
+        display: none;
+      }
+    }
+
+    @media screen and (max-width: 992px) {
       .content-panel {
         flex-direction: column;
       }
 
-      .panel-left {
-        flex-basis: auto;
+      .qj-panel-left {
+        flex: 0 0 auto;
+        min-height: 300px;
       }
-    }
 
-    @media screen and (max-width: 992px) {
-      .filter-actions {
-        width: 100%;
-        margin-left: 0;
+      .qj-panel-right {
+        flex: 1 1 auto;
       }
 
+      .qj-field,
+      .qj-field.qj-field--target {
+        flex: 1 1 100%;
+      }
+
+      .qj-field .ant-select,
+      .qj-field.qj-field--target .ant-select,
       .filter-input,
       .filter-date {
         width: 100%;
       }
+    }
+
+    /* ---------------- 暗色主题 ---------------- */
+    html[data-theme='dark'] & {
+      background: #0f172a;
+
+      .qj-filter-card,
+      .qj-panel-card {
+        border-color: #1f2937;
+        background: #111827;
+        box-shadow: none;
+      }
+
+      .qj-card-title {
+        color: #e5e7eb;
+      }
+
+      .qj-field__label,
+      .qj-card-sub,
+      .qj-card-meta {
+        color: #94a3b8;
+      }
 
+      .qj-filter-head,
+      .qj-card-head,
       .pagination-bar {
-        justify-content: flex-start;
+        border-color: #1f2937;
+      }
+
+      .qj-field .ant-input,
+      .qj-field .ant-input-affix-wrapper,
+      .qj-field .ant-picker,
+      .qj-field .ant-select .ant-select-selector,
+      .qj-search.ant-input-affix-wrapper,
+      .qj-search.ant-input {
+        border-color: #1f2937;
+        background: #0f172a;
+        color: #e5e7eb;
+      }
+
+      .table-wrapper .ant-table-thead > tr > th {
+        border-color: #1f2937;
+        background: #0f172a;
+        color: #94a3b8;
+      }
+
+      .table-wrapper .ant-table-tbody > tr > td {
+        border-color: #1f2937;
+        color: #e5e7eb;
       }
     }
   }

+ 156 - 105
ai-electron/frontend/src/case/views/dm/components/IntimacyPanel.vue

@@ -4,7 +4,7 @@
       <div class="section-title">亲密关系人</div>
       <div class="section-tools">
         <Input v-model:value="keyword" class="relation-search" placeholder="搜索被关联对象" allow-clear />
-        <Button type="link" class="section-action" @click="handleOpenConfigModal">编辑</Button>
+        <Button type="link" class="section-action" @click="handleOpenConfigModal"><EditOutlined />编辑</Button>
       </div>
     </div>
 
@@ -24,7 +24,7 @@
               >
                 <div class="intimacy-card__top">
                   <div class="intimacy-card__head">
-                    <PersonAvatar class="relation-avatar" :size="42" :alt="relation.otherName" />
+                    <PersonAvatar class="relation-avatar" :size="38" :alt="relation.otherName" />
                     <div class="intimacy-card__name" :title="relation.otherName">{{ relation.otherName }}</div>
                   </div>
                   <div class="intimacy-card__score-wrap">
@@ -188,6 +188,7 @@
 <script lang="ts" setup>
   import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue';
   import { Button, Input, InputNumber, Spin } from 'ant-design-vue';
+  import { EditOutlined } from '@ant-design/icons-vue';
   import { EmptyState } from '@/core/components/EmptyState';
   import { BasicModal } from '@/core/components/Modal';
   import { PersonAvatar } from '@/core/components/PersonAvatar';
@@ -946,11 +947,11 @@
 
 <style lang="less" scoped>
   .intimacy-panel {
-    --intimacy-content-height: 248px;
+    --intimacy-content-height: 180px;
 
     display: flex;
     flex-direction: column;
-    gap: 12px;
+    gap: 0;
     min-height: 0;
     min-width: 0;
     overflow: visible;
@@ -969,9 +970,11 @@
       align-items: center;
       justify-content: space-between;
       gap: 12px 16px;
+      min-height: 42px;
       margin-bottom: 0;
-      padding-bottom: 10px;
-      border-bottom: 1px solid #eef2f7;
+      padding: 0 14px;
+      border-bottom: 1px solid #eff2f7;
+      box-sizing: border-box;
     }
 
     .section-title {
@@ -979,16 +982,21 @@
       display: inline-flex;
       align-items: center;
       gap: 8px;
+      padding-left: 11px;
       font-size: 15px;
       font-weight: 600;
+      line-height: 22px;
       color: #111827;
 
       &::before {
         content: '';
+        position: absolute;
+        left: 0;
+        top: 4px;
+        bottom: 4px;
         width: 3px;
-        height: 14px;
-        border-radius: 999px;
-        background: #1677ff;
+        border-radius: 2px;
+        background: #2a50ec;
       }
     }
 
@@ -1000,24 +1008,64 @@
     }
 
     .relation-search {
-      width: 200px;
+      width: 208px;
       max-width: 100%;
+
+      .ant-input,
+      .ant-input-affix-wrapper {
+        height: 30px;
+        border-color: #e7ebf3;
+        border-radius: 8px;
+        background: #f8fafc;
+        box-shadow: none;
+        font-size: 12px;
+      }
+
+      .ant-input-affix-wrapper-focused,
+      .ant-input:focus {
+        border-color: #2a50ec;
+        background: #ffffff;
+        box-shadow: 0 0 0 2px rgba(42, 80, 236, 0.12);
+      }
+    }
+
+    .section-action.ant-btn {
+      height: auto;
+      padding: 0 2px;
+      border: 0;
+      background: transparent;
+      color: #2a50ec;
+      font-size: 12px;
+      font-weight: 500;
+      line-height: 20px;
+      box-shadow: none;
+
+      .anticon {
+        font-size: 12px;
+      }
+
+      &:hover,
+      &:focus {
+        border: 0;
+        background: transparent;
+        color: #4b6bf0;
+      }
     }
 
     .intimacy-content {
-      width: 100%;
-      max-width: 100%;
+      width: auto;
+      max-width: none;
       height: var(--intimacy-content-height);
       display: grid;
-      grid-template-columns: minmax(0, 1fr) minmax(320px, 36%);
+      grid-template-columns: minmax(0, 1fr) 370px;
       grid-template-rows: minmax(0, 1fr);
-      gap: 2px;
+      gap: 8px;
       grid-auto-rows: minmax(0, 1fr);
       min-height: 0;
       align-items: stretch;
       overflow: hidden;
-      padding: 2px;
-      // 去掉外层灰色底:直接透出所在卡片的白色背景
+      margin: 8px 14px 12px;
+      padding: 0;
       background: transparent;
       box-sizing: border-box;
     }
@@ -1044,9 +1092,9 @@
       display: flex;
       align-self: stretch;
       align-items: stretch;
-      border-radius: 12px;
-      background: #fff;
-      border: 1px solid #e2e8f0;
+      border-radius: 10px;
+      background: #fbfcfe;
+      border: 1px solid #eff2f7;
       overflow: hidden;
       box-sizing: border-box;
       box-shadow: none;
@@ -1062,39 +1110,32 @@
       display: flex;
       align-items: stretch;
       gap: 8px;
-      padding: 10px 36px 18px;
+      padding: 7px 34px 8px;
       overflow: auto hidden;
       scroll-behavior: smooth;
       box-sizing: border-box;
 
-      &::-webkit-scrollbar {
-        height: 6px;
-      }
-
-      &::-webkit-scrollbar-thumb {
-        border-radius: 999px;
-        background: #cbd5e1;
-      }
+      scrollbar-width: none;
 
-      &::-webkit-scrollbar-track {
-        background: transparent;
+      &::-webkit-scrollbar {
+        display: none;
       }
     }
 
     .intimacy-card {
-      flex: 0 0 244px;
+      flex: 0 0 240px;
       align-self: stretch;
       height: auto;
       min-height: 0;
       max-height: none;
-      padding: 10px 12px;
-      border: 1px solid #e5e7eb;
-      border-radius: 10px;
+      padding: 8px 10px;
+      border: 1px solid #e7ebf3;
+      border-radius: 9px;
       background: #fff;
       display: flex;
       flex-direction: column;
       justify-content: flex-start;
-      gap: 12px;
+      gap: 8px;
       text-align: left;
       cursor: pointer;
       box-sizing: border-box;
@@ -1104,15 +1145,15 @@
         background 0.2s ease;
 
       &:hover {
-        border-color: #91caff;
+        border-color: #bfcdfb;
         box-shadow: none;
         transform: none;
       }
 
       &.active {
-        border-color: #1677ff;
-        background: rgba(247, 251, 255, 0.72);
-        box-shadow: none;
+        border-color: #2a50ec;
+        background: #f8faff;
+        box-shadow: 0 0 0 2px rgba(42, 80, 236, 0.07);
       }
     }
 
@@ -1132,15 +1173,22 @@
     }
 
     .relation-avatar {
-      flex: 0 0 42px;
+      flex: 0 0 38px;
       display: inline-flex;
+
+      .jeesite-person-avatar__media {
+        background: #2a50ec;
+        border-color: #2a50ec;
+        box-shadow: none;
+        color: #fff;
+      }
     }
 
     .intimacy-card__name {
       min-width: 0;
-      font-size: 15px;
+      font-size: 13px;
       font-weight: 600;
-      line-height: 22px;
+      line-height: 18px;
       color: #111827;
       word-break: break-all;
     }
@@ -1150,30 +1198,29 @@
       display: flex;
       flex-direction: column;
       align-items: center;
-      gap: 8px;
+      gap: 4px;
     }
 
     .intimacy-card__score-badge {
-      min-width: 42px;
-      height: 42px;
-      padding: 0 10px;
-      border-radius: 12px;
-      // 纯色徽标:页面统一禁止渐变色
-      background: #1d4ed8;
-      color: #fff;
+      min-width: 0;
+      height: auto;
+      padding: 0;
+      border-radius: 0;
+      background: transparent;
+      color: #e5484d;
       display: inline-flex;
       align-items: center;
       justify-content: center;
-      font-size: 18px;
-      line-height: 1;
-      font-weight: 700;
+      font-size: 20px;
+      line-height: 24px;
+      font-weight: 600;
       box-shadow: none;
     }
 
     .intimacy-card__score {
-      font-size: 12px;
-      line-height: 18px;
-      color: #64748b;
+      font-size: 10px;
+      line-height: 14px;
+      color: #98a2b3;
       text-align: center;
       white-space: nowrap;
     }
@@ -1184,20 +1231,21 @@
       grid-template-rows: repeat(2, max-content);
       grid-auto-columns: max-content;
       align-content: end;
-      gap: 6px;
+      gap: 4px;
       margin-top: auto;
-      max-height: 58px;
+      max-height: 48px;
       overflow: visible;
+      padding-top: 7px;
     }
 
     .behavior-tag {
       display: inline-flex;
       align-items: center;
-      min-height: 26px;
-      padding: 0 10px;
+      min-height: 20px;
+      padding: 0 7px;
       border-radius: 999px;
       border: 1px solid transparent;
-      font-size: 11px;
+      font-size: 10px;
       line-height: 1.1;
       font-weight: 500;
       white-space: nowrap;
@@ -1236,10 +1284,10 @@
       display: flex;
       flex-direction: column;
       align-self: stretch;
-      padding: 10px 12px;
-      border: 1px solid #e2e8f0;
-      border-radius: 12px;
-      background: #fff;
+      padding: 8px 10px;
+      border: 1px solid #eff2f7;
+      border-radius: 10px;
+      background: #fbfcfe;
       overflow: hidden;
       box-sizing: border-box;
       box-shadow: none;
@@ -1249,17 +1297,17 @@
       display: flex;
       align-items: center;
       justify-content: space-between;
-      gap: 12px;
+      gap: 10px;
       min-width: 0;
-      padding-bottom: 8px;
-      border-bottom: 1px solid #eef2f7;
+      padding-bottom: 7px;
+      border-bottom: 1px solid #eff2f7;
     }
 
     .intimacy-detail__title {
       flex: 0 0 auto;
-      font-size: 15px;
+      font-size: 13px;
       font-weight: 600;
-      line-height: 22px;
+      line-height: 20px;
       color: #111827;
     }
 
@@ -1268,29 +1316,29 @@
       flex: 1 1 auto;
       justify-content: flex-end;
       align-items: center;
-      gap: 16px;
+      gap: 12px;
       min-width: 0;
       flex-wrap: wrap;
     }
 
     .intimacy-detail__meta {
       flex-shrink: 0;
-      font-size: 12px;
-      line-height: 20px;
-      color: #94a3b8;
+      font-size: 11px;
+      line-height: 18px;
+      color: #98a2b3;
     }
 
     .summary-item {
       display: inline-flex;
       align-items: center;
-      gap: 6px;
-      font-size: 13px;
+      gap: 5px;
+      font-size: 12px;
       min-width: 0;
     }
 
     .label {
       flex: 0 0 auto;
-      color: #6b7280;
+      color: #98a2b3;
       white-space: nowrap;
     }
 
@@ -1301,7 +1349,7 @@
     }
 
     .value--accent {
-      color: #1677ff;
+      color: #e5484d;
       font-weight: 600;
     }
 
@@ -1311,10 +1359,9 @@
       display: flex;
       flex-wrap: wrap;
       align-content: flex-start;
-      gap: 8px;
+      gap: 5px;
       margin-top: 6px;
-      padding-right: 4px;
-      padding-bottom: 4px;
+      padding: 0;
       overflow-y: auto;
       overflow-x: hidden;
       overscroll-behavior: contain;
@@ -1323,22 +1370,22 @@
     .detail-item {
       display: flex;
       align-items: flex-start;
-      gap: 10px;
-      flex: 1 1 calc(50% - 4px);
-      min-width: 220px;
-      padding: 8px 12px;
-      border-radius: 10px;
+      gap: 8px;
+      flex: 1 1 calc(50% - 3px);
+      min-width: 0;
+      padding: 5px 7px;
+      border-radius: 7px;
       background: #fff;
-      border: 1px solid #eef2f7;
+      border: 1px solid #eff2f7;
       box-sizing: border-box;
     }
 
     .detail-dot {
-      width: 8px;
-      height: 8px;
+      width: 6px;
+      height: 6px;
       margin-top: 6px;
       border-radius: 999px;
-      flex: 0 0 8px;
+      flex: 0 0 6px;
     }
 
     .detail-main {
@@ -1346,40 +1393,40 @@
       min-width: 0;
       display: flex;
       flex-direction: column;
-      gap: 5px;
+      gap: 2px;
     }
 
     .detail-title {
-      font-size: 14px;
+      font-size: 12px;
       font-weight: 600;
-      line-height: 20px;
-      color: #111827;
+      line-height: 17px;
+      color: #1f2937;
       word-break: break-all;
     }
 
     .detail-weight {
-      font-size: 12px;
-      font-weight: 500;
-      color: #6b7280;
+      font-size: 10px;
+      font-weight: 400;
+      color: #98a2b3;
     }
 
     .detail-desc {
-      font-size: 13px;
-      line-height: 20px;
-      color: #4b5563;
+      font-size: 11px;
+      line-height: 16px;
+      color: #6b7280;
     }
 
     .detail-value--clickable {
       display: inline;
       padding: 0 2px;
-      color: #1677ff;
+      color: #e5484d;
       font-weight: 600;
       cursor: pointer;
       border-radius: 4px;
       transition: background-color 0.2s ease;
 
       &:hover {
-        background: #e6f4ff;
+        background: #fdecec;
         text-decoration: underline;
       }
     }
@@ -1391,7 +1438,7 @@
       align-items: center;
       justify-content: center;
       gap: 0;
-      min-height: 220px;
+      min-height: 188px;
       border: 0;
       border-radius: 0;
       background: transparent;
@@ -1399,8 +1446,12 @@
     }
 
     .detail-empty {
-      margin-top: 16px;
-      min-height: 160px;
+      margin-top: 12px;
+      min-height: 150px;
+    }
+
+    .intimacy-empty {
+      margin: 10px 16px 14px;
     }
   }
 

Різницю між файлами не показано, бо вона завелика
+ 497 - 367
ai-electron/frontend/src/case/views/dm/portrait.vue


+ 105 - 105
ai-electron/frontend/src/core/components/EmptyState/src/EmptyState.vue

@@ -1,6 +1,13 @@
 <template>
   <div :class="['jeesite-empty-state', `jeesite-empty-state--${variant}`]" :style="rootStyle">
-    <div class="jeesite-empty-state__icon"></div>
+    <div class="jeesite-empty-state__icon">
+      <svg viewBox="0 0 48 48" aria-hidden="true">
+        <rect class="es-doc" x="10" y="6.5" width="26" height="33" rx="4.5" />
+        <path class="es-line" d="M16.5 16h13M16.5 21.5h13M16.5 27h7.5" />
+        <circle class="es-lens" cx="31" cy="31" r="7.2" />
+        <path class="es-handle" d="M36.4 36.4 41.5 41.5" />
+      </svg>
+    </div>
     <div class="jeesite-empty-state__title">{{ title }}</div>
     <div v-if="description" class="jeesite-empty-state__desc" :style="descStyle">{{ description }}</div>
   </div>
@@ -44,12 +51,15 @@
   .jeesite-empty-state {
     --jeesite-empty-min-height: 180px;
     --jeesite-empty-desc-width: 280px;
+    --jeesite-empty-icon-size: 76px;
+    --jeesite-empty-icon-glyph: 42px;
+    --jeesite-empty-surface: #f4f7ff;
 
     display: flex;
     flex-direction: column;
     align-items: center;
     justify-content: center;
-    gap: 10px;
+    gap: 8px;
     width: 100%;
     height: 100%;
     min-height: var(--jeesite-empty-min-height);
@@ -58,159 +68,149 @@
     text-align: center;
 
     &__icon {
-      position: relative;
+      display: flex;
       flex: 0 0 auto;
-      width: 52px;
-      height: 52px;
-      border-radius: 14px;
-      border: 1px solid #d9e2ee;
-      background: rgba(255, 255, 255, 0.98);
-      box-shadow:
-        inset 0 1px 0 rgba(255, 255, 255, 0.92),
-        0 10px 24px rgba(148, 163, 184, 0.14);
-
-      &::before,
-      &::after {
-        content: '';
-        position: absolute;
-        left: 50%;
-        transform: translateX(-50%);
-        border-radius: 999px;
-        background: #dbe4ef;
+      align-items: center;
+      justify-content: center;
+      width: var(--jeesite-empty-icon-size);
+      height: var(--jeesite-empty-icon-size);
+      border: 0;
+      border-radius: 50%;
+      background: var(--jeesite-empty-surface);
+      box-shadow: none;
+
+      svg {
+        width: var(--jeesite-empty-icon-glyph);
+        height: var(--jeesite-empty-icon-glyph);
+        overflow: visible;
       }
 
-      &::before {
-        top: 16px;
-        width: 18px;
-        height: 2px;
-        box-shadow: 0 8px 0 #e7eef6;
+      .es-doc {
+        fill: #fff;
+        stroke: #cbd5e1;
+        stroke-width: 1.5;
       }
 
-      &::after {
-        bottom: 12px;
-        width: 12px;
-        height: 12px;
-        border-radius: 6px;
-        border: 1px solid #e2eaf3;
-        background: #ffffff;
+      .es-line {
+        fill: none;
+        stroke: #dbe3ee;
+        stroke-width: 2;
+        stroke-linecap: round;
+      }
+
+      .es-lens {
+        fill: #fff;
+        stroke: #2a50ec;
+        stroke-width: 2;
+      }
+
+      .es-handle {
+        fill: none;
+        stroke: #2a50ec;
+        stroke-width: 2.4;
+        stroke-linecap: round;
       }
     }
 
     &__title {
-      font-size: 15px;
-      line-height: 1.35;
-      font-weight: 700;
-      color: #334155;
-      letter-spacing: 0.01em;
+      font-size: 14px;
+      line-height: 20px;
+      font-weight: 600;
+      color: #1f2937;
+      letter-spacing: 0;
     }
 
     &__desc {
       max-width: var(--jeesite-empty-desc-width);
       font-size: 12px;
-      line-height: 1.65;
-      color: #94a3b8;
+      line-height: 18px;
+      color: #98a2b3;
       word-break: break-word;
     }
 
     &--query {
-      gap: 14px;
+      gap: 10px;
       padding: clamp(12px, 2vw, 24px) clamp(12px, 1.6vw, 20px);
       overflow: hidden;
 
       .jeesite-empty-state__icon {
-        width: clamp(56px, 6.4vw, 92px);
-        height: clamp(56px, 6.4vw, 92px);
-        border-radius: clamp(16px, 1.8vw, 24px);
-        border-color: #c9dcff;
-        background: #fbfdff;
-        box-shadow:
-          inset 0 0 0 1px rgba(255, 255, 255, 0.82),
-          0 8px 16px rgba(37, 99, 235, 0.06);
-
-        &::before {
-          top: clamp(17px, 2vw, 28px);
-          width: clamp(18px, 2.2vw, 28px);
-          height: 2px;
-          background: #8fb0f2;
-          box-shadow:
-            0 clamp(6px, 0.8vw, 10px) 0 #a8c0f5,
-            0 clamp(12px, 1.6vw, 20px) 0 #d3e0fb;
-        }
-
-        &::after {
-          bottom: clamp(12px, 1.4vw, 20px);
-          width: clamp(12px, 1.4vw, 18px);
-          height: clamp(12px, 1.4vw, 18px);
-          border: 1px solid #bdd0f8;
-          border-radius: 6px;
-          background: #ffffff;
-          box-shadow: 0 3px 8px rgba(59, 130, 246, 0.08);
-        }
+        --jeesite-empty-icon-size: clamp(64px, 6vw, 84px);
+        --jeesite-empty-icon-glyph: clamp(34px, 3.4vw, 46px);
       }
 
       .jeesite-empty-state__title {
-        font-size: clamp(14px, 1.2vw, 16px);
-        color: #1f3f85;
-        letter-spacing: 0;
-        line-height: 1.4;
+        font-size: 15px;
+        color: #1f2937;
       }
 
       .jeesite-empty-state__desc {
         max-width: min(100%, var(--jeesite-empty-desc-width));
-        font-size: clamp(12px, 1vw, 13px);
-        line-height: 1.6;
-        color: #64748b;
+        font-size: 12px;
+        color: #98a2b3;
       }
     }
 
     &--compact {
-      gap: 8px;
+      gap: 6px;
       padding: 12px 16px;
 
       .jeesite-empty-state__icon {
-        width: 44px;
-        height: 44px;
-        border-radius: 12px;
-
-        &::before {
-          top: 13px;
-          width: 16px;
-          box-shadow: 0 7px 0 #e7eef6;
-        }
-
-        &::after {
-          bottom: 9px;
-          width: 10px;
-          height: 10px;
-        }
+        --jeesite-empty-icon-size: 52px;
+        --jeesite-empty-icon-glyph: 30px;
       }
 
       .jeesite-empty-state__title {
-        font-size: 14px;
+        font-size: 13px;
       }
 
       .jeesite-empty-state__desc {
-        font-size: 12px;
+        font-size: 11px;
+        line-height: 17px;
       }
     }
 
     &--warm {
       .jeesite-empty-state__icon {
-        border-color: #f3d6c5;
-        background: #fff7f2;
-        box-shadow:
-          inset 0 1px 0 rgba(255, 255, 255, 0.92),
-          0 10px 24px rgba(251, 146, 60, 0.12);
-
-        &::before {
-          background: #f7d5bf;
-          box-shadow: 0 8px 0 #fbe6d8;
+        --jeesite-empty-surface: #fff4ea;
+
+        .es-lens,
+        .es-handle {
+          stroke: #f79009;
+        }
+      }
+    }
+  }
+
+  html[data-theme='dark'] {
+    .jeesite-empty-state {
+      --jeesite-empty-surface: #1e293b;
+
+      .jeesite-empty-state__icon {
+        .es-doc {
+          fill: #0f172a;
+          stroke: #334155;
+        }
+
+        .es-line {
+          stroke: #334155;
         }
 
-        &::after {
-          border-color: #f8dfce;
-          background: #fff1e7;
+        .es-lens {
+          fill: #0f172a;
+          stroke: #60a5fa;
         }
+
+        .es-handle {
+          stroke: #60a5fa;
+        }
+      }
+
+      .jeesite-empty-state__title {
+        color: #e5e7eb;
+      }
+
+      .jeesite-empty-state__desc {
+        color: #94a3b8;
       }
     }
   }

+ 39 - 28
ai-electron/frontend/src/trans/views/baseStation/index.vue

@@ -6,35 +6,40 @@
     </div>
 
     <div class="filter-bar">
-      <div class="filter-item">
-        <span class="filter-label">选择对象:</span>
-        <Select
-          v-model:value="filters.target"
-          mode="multiple"
-          allow-clear
-          max-tag-count="responsive"
-          class="filter-input"
-          :options="targetOptions"
-          placeholder="请选择对象"
-        />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">通信时间:</span>
-        <RangePicker
-          v-model:value="filters.dateRange"
-          class="filter-date"
-          :format="DATE_FORMAT"
-          :value-format="DATE_FORMAT"
-          :placeholder="['开始日期', '结束日期']"
-        />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">基站名称:</span>
-        <Input v-model:value="filters.stationName" class="filter-input" placeholder="请输入基站名称" />
+      <div class="qj-filter-head">
+        <div class="qj-card-title">查询条件</div>
+        <div class="filter-actions">
+          <Button type="primary" :loading="queryLoading" @click="handleSearch">查询</Button>
+          <Button @click="handleReset">重置</Button>
+        </div>
       </div>
-      <div class="filter-actions">
-        <Button type="primary" :loading="queryLoading" @click="handleSearch">查询</Button>
-        <Button @click="handleReset">重置</Button>
+      <div class="qj-filter-grid">
+        <div class="filter-item">
+          <span class="filter-label">选择对象:</span>
+          <Select
+            v-model:value="filters.target"
+            mode="multiple"
+            allow-clear
+            max-tag-count="responsive"
+            class="filter-input"
+            :options="targetOptions"
+            placeholder="请选择对象"
+          />
+        </div>
+        <div class="filter-item">
+          <span class="filter-label">通信时间:</span>
+          <RangePicker
+            v-model:value="filters.dateRange"
+            class="filter-date"
+            :format="DATE_FORMAT"
+            :value-format="DATE_FORMAT"
+            :placeholder="['开始日期', '结束日期']"
+          />
+        </div>
+        <div class="filter-item">
+          <span class="filter-label">基站名称:</span>
+          <Input v-model:value="filters.stationName" class="filter-input" placeholder="请输入基站名称" />
+        </div>
       </div>
     </div>
 
@@ -401,6 +406,7 @@
 </script>
 <style lang="less">
   @import '../styles/trans-analysis-table-ui.less';
+  @import (reference) '../styles/analysis-card-ui.less';
 
   @prefix-cls: ~'jeesite-base-station';
 
@@ -446,4 +452,9 @@
       }
     }
   }
+
+  /* 统一卡片视觉:对齐「持续联系 / 夜间联系 / 敏感日期联系」 */
+  .@{prefix-cls}.base-station-page {
+    .analysis-card-ui();
+  }
 </style>

+ 37 - 26
ai-electron/frontend/src/trans/views/cashTrans/index.vue

@@ -6,34 +6,39 @@
     </div>
 
     <div class="filter-bar">
-      <div class="filter-item">
-        <span class="filter-label">选择对象:</span>
-        <Select
-          v-model:value="filters.target"
-          mode="multiple"
-          allow-clear
-          max-tag-count="responsive"
-          class="filter-input"
-          :options="targetOptions"
-          placeholder="全部"
-        />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">交易次数:</span>
-        <Select v-model:value="filters.countOp" class="filter-op" :options="opOptions" />
-        <Input v-model:value="filters.countValue" class="filter-mini" :status="summaryCountValueStatus" />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">交易日期:</span>
-        <RangePicker v-model:value="filters.dateRange" class="filter-date" />
+      <div class="qj-filter-head">
+        <div class="qj-card-title">查询条件</div>
+        <div class="toolbar-actions">
+          <div class="filter-actions">
+            <Button type="primary" :loading="summaryLoading" @click="handleSearch">查询</Button>
+            <Button @click="handleReset">重置</Button>
+          </div>
+          <div class="extra-actions">
+            <Button @click="openKeywordModal">关键词配置</Button>
+          </div>
+        </div>
       </div>
-      <div class="toolbar-actions">
-        <div class="filter-actions">
-          <Button type="primary" :loading="summaryLoading" @click="handleSearch">查询</Button>
-          <Button @click="handleReset">重置</Button>
+      <div class="qj-filter-grid">
+        <div class="filter-item">
+          <span class="filter-label">选择对象:</span>
+          <Select
+            v-model:value="filters.target"
+            mode="multiple"
+            allow-clear
+            max-tag-count="responsive"
+            class="filter-input"
+            :options="targetOptions"
+            placeholder="全部"
+          />
+        </div>
+        <div class="filter-item">
+          <span class="filter-label">交易次数:</span>
+          <Select v-model:value="filters.countOp" class="filter-op" :options="opOptions" />
+          <Input v-model:value="filters.countValue" class="filter-mini" :status="summaryCountValueStatus" />
         </div>
-        <div class="extra-actions">
-          <Button @click="openKeywordModal">关键词配置</Button>
+        <div class="filter-item">
+          <span class="filter-label">交易日期:</span>
+          <RangePicker v-model:value="filters.dateRange" class="filter-date" />
         </div>
       </div>
     </div>
@@ -1075,6 +1080,7 @@
 </script>
 <style lang="less">
   @import '../styles/trans-analysis-table-ui.less';
+  @import (reference) '../styles/analysis-card-ui.less';
   @prefix-cls: ~'jeesite-cash-trans';
 
   .@{prefix-cls}.cash-trans-page {
@@ -1260,4 +1266,9 @@
       }
     }
   }
+
+  /* 统一卡片视觉:对齐「持续联系 / 夜间联系 / 敏感日期联系」 */
+  .@{prefix-cls}.cash-trans-page {
+    .analysis-card-ui();
+  }
 </style>

+ 52 - 42
ai-electron/frontend/src/trans/views/coResidence/index.vue

@@ -8,40 +8,45 @@
     </div>
 
     <div class="filter-bar">
-      <div class="filter-item">
-        <span class="filter-label">选择对象:</span>
-        <Select
-          v-model:value="filters.target"
-          mode="multiple"
-          allow-clear
-          max-tag-count="responsive"
-          class="filter-input"
-          :options="targetOptions"
-          placeholder="请选择对象"
-        />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">同住时间:</span>
-        <RangePicker v-model:value="filters.dateRange" class="filter-date" />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">同住酒店:</span>
-        <Input v-model:value="filters.hotel" class="filter-input" />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">同住次数:</span>
-        <Select v-model:value="filters.countOp" class="filter-op" :options="countOpOptions" />
-        <Input v-model:value="filters.countValue" class="filter-micro" :status="countValueStatus" />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">疑似同住时间间隔:</span>
-        <span class="filter-fixed-op">&lt;=</span>
-        <Input v-model:value="filters.timeGapValue" class="filter-micro" :status="timeGapValueStatus" />
-        <span class="filter-unit">h</span>
+      <div class="qj-filter-head">
+        <div class="qj-card-title">查询条件</div>
+        <div class="filter-actions">
+          <Button type="primary" :loading="queryLoading" @click="handleSearch">查询</Button>
+          <Button @click="handleReset">重置</Button>
+        </div>
       </div>
-      <div class="filter-actions">
-        <Button type="primary" :loading="queryLoading" @click="handleSearch">查询</Button>
-        <Button @click="handleReset">重置</Button>
+      <div class="qj-filter-grid">
+        <div class="filter-item">
+          <span class="filter-label">选择对象:</span>
+          <Select
+            v-model:value="filters.target"
+            mode="multiple"
+            allow-clear
+            max-tag-count="responsive"
+            class="filter-input"
+            :options="targetOptions"
+            placeholder="请选择对象"
+          />
+        </div>
+        <div class="filter-item">
+          <span class="filter-label">同住时间:</span>
+          <RangePicker v-model:value="filters.dateRange" class="filter-date" />
+        </div>
+        <div class="filter-item">
+          <span class="filter-label">同住酒店:</span>
+          <Input v-model:value="filters.hotel" class="filter-input" />
+        </div>
+        <div class="filter-item">
+          <span class="filter-label">同住次数:</span>
+          <Select v-model:value="filters.countOp" class="filter-op" :options="countOpOptions" />
+          <Input v-model:value="filters.countValue" class="filter-micro" :status="countValueStatus" />
+        </div>
+        <div class="filter-item">
+          <span class="filter-label">疑似同住时间间隔:</span>
+          <span class="filter-fixed-op">&lt;=</span>
+          <Input v-model:value="filters.timeGapValue" class="filter-micro" :status="timeGapValueStatus" />
+          <span class="filter-unit">h</span>
+        </div>
       </div>
     </div>
 
@@ -62,8 +67,8 @@
         <div class="panel-header panel-header--sidebar">
           <div>
             <div class="panel-title">同住对象</div>
-            <div class="panel-desc">{{ personPanelDesc }}</div>
           </div>
+          <div v-if="hasSearched" class="panel-subtitle">{{ personList.length }} 人</div>
         </div>
 
         <div class="person-list-wrap">
@@ -907,6 +912,7 @@
 </script>
 <style lang="less">
   @import '../styles/trans-analysis-table-ui.less';
+  @import (reference) '../styles/analysis-card-ui.less';
 
   @prefix-cls: ~'jeesite-co-residence';
 
@@ -946,15 +952,14 @@
       border-radius: 12px;
       background: #fff;
       box-sizing: border-box;
-      overflow-x: auto;
-      overflow-y: hidden;
+      overflow: auto hidden;
 
       &::-webkit-scrollbar {
         height: 8px;
       }
 
       &::-webkit-scrollbar-thumb {
-        background: rgba(148, 163, 184, 0.6);
+        background: rgb(148 163 184 / 60%);
         border-radius: 999px;
       }
 
@@ -999,6 +1004,7 @@
 
     .right-content {
       .analysis-card-shell();
+
       flex: 1 1 auto;
       min-width: 0;
     }
@@ -1038,8 +1044,7 @@
     .person-list {
       display: flex;
       flex-direction: column;
-      overflow-x: hidden;
-      overflow-y: auto;
+      overflow: hidden auto;
       padding: 6px 0;
       width: 100%;
 
@@ -1048,7 +1053,7 @@
       }
 
       &::-webkit-scrollbar-thumb {
-        background: rgba(148, 163, 184, 0.6);
+        background: rgb(148 163 184 / 60%);
         border-radius: 999px;
       }
 
@@ -1175,11 +1180,11 @@
       background: #1677ff;
       border-radius: 50%;
       border: 2px solid #fff;
-      box-shadow: 0 0 0 3px rgba(22, 119, 255, 0.12);
+      box-shadow: 0 0 0 3px rgb(22 119 255 / 12%);
 
       &.suspected {
         background: #faad14;
-        box-shadow: 0 0 0 3px rgba(250, 173, 20, 0.16);
+        box-shadow: 0 0 0 3px rgb(250 173 20 / 16%);
       }
     }
 
@@ -1345,4 +1350,9 @@
       }
     }
   }
+
+  /* 统一卡片视觉:对齐「持续联系 / 夜间联系 / 敏感日期联系」 */
+  .@{prefix-cls}.co-residence-page {
+    .analysis-card-ui();
+  }
 </style>

+ 33 - 22
ai-electron/frontend/src/trans/views/expressDelivery/index.vue

@@ -6,29 +6,34 @@
     </div>
 
     <div class="filter-bar">
-      <div class="filter-item">
-        <span class="filter-label">被调查对象:</span>
-        <Select
-          v-model:value="filters.target"
-          mode="multiple"
-          allow-clear
-          max-tag-count="responsive"
-          class="filter-input"
-          :options="targetOptions"
-          placeholder="请选择对象"
-        />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">快递收发日期:</span>
-        <RangePicker v-model:value="filters.dateRange" class="filter-date" />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">地点:</span>
-        <Input v-model:value="filters.location" class="filter-input" placeholder="请输入地点" />
+      <div class="qj-filter-head">
+        <div class="qj-card-title">查询条件</div>
+        <div class="filter-actions">
+          <Button type="primary" :loading="queryLoading" @click="handleSearch">查询</Button>
+          <Button @click="handleReset">重置</Button>
+        </div>
       </div>
-      <div class="filter-actions">
-        <Button type="primary" :loading="queryLoading" @click="handleSearch">查询</Button>
-        <Button @click="handleReset">重置</Button>
+      <div class="qj-filter-grid">
+        <div class="filter-item">
+          <span class="filter-label">被调查对象:</span>
+          <Select
+            v-model:value="filters.target"
+            mode="multiple"
+            allow-clear
+            max-tag-count="responsive"
+            class="filter-input"
+            :options="targetOptions"
+            placeholder="请选择对象"
+          />
+        </div>
+        <div class="filter-item">
+          <span class="filter-label">快递收发日期:</span>
+          <RangePicker v-model:value="filters.dateRange" class="filter-date" />
+        </div>
+        <div class="filter-item">
+          <span class="filter-label">地点:</span>
+          <Input v-model:value="filters.location" class="filter-input" placeholder="请输入地点" />
+        </div>
       </div>
     </div>
 
@@ -391,6 +396,7 @@
 
 <style lang="less">
   @import '../styles/trans-analysis-table-ui.less';
+  @import (reference) '../styles/analysis-card-ui.less';
 
   @prefix-cls: ~'jeesite-express-delivery';
 
@@ -492,4 +498,9 @@
       }
     }
   }
+
+  /* 统一卡片视觉:对齐「持续联系 / 夜间联系 / 敏感日期联系」 */
+  .@{prefix-cls}.express-delivery-page {
+    .analysis-card-ui();
+  }
 </style>

+ 48 - 27
ai-electron/frontend/src/trans/views/faceToFace/index.vue

@@ -6,31 +6,36 @@
     </div>
 
     <div class="filter-bar">
-      <div class="filter-item">
-        <span class="filter-label">选择对象:</span>
-        <Select
-          v-model:value="filters.target"
-          mode="multiple"
-          allow-clear
-          max-tag-count="responsive"
-          class="filter-input"
-          :options="targetOptions"
-          placeholder="请选择对象"
-        />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">时间:</span>
-        <RangePicker v-model:value="filters.dateRange" class="filter-date" />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">时间间隔:</span>
-        <Select v-model:value="filters.intervalOp" class="filter-op" :options="intervalOpOptions" />
-        <Input v-model:value="filters.intervalValue" class="filter-micro" :status="intervalValueStatus" />
-        <span class="filter-unit">h</span>
+      <div class="qj-filter-head">
+        <div class="qj-card-title">查询条件</div>
+        <div class="filter-actions">
+          <Button type="primary" :loading="queryLoading" @click="handleSearch">查询</Button>
+          <Button @click="handleReset">重置</Button>
+        </div>
       </div>
-      <div class="filter-actions">
-        <Button type="primary" :loading="queryLoading" @click="handleSearch">查询</Button>
-        <Button @click="handleReset">重置</Button>
+      <div class="qj-filter-grid">
+        <div class="filter-item">
+          <span class="filter-label">选择对象:</span>
+          <Select
+            v-model:value="filters.target"
+            mode="multiple"
+            allow-clear
+            max-tag-count="responsive"
+            class="filter-input"
+            :options="targetOptions"
+            placeholder="请选择对象"
+          />
+        </div>
+        <div class="filter-item">
+          <span class="filter-label">时间:</span>
+          <RangePicker v-model:value="filters.dateRange" class="filter-date" />
+        </div>
+        <div class="filter-item">
+          <span class="filter-label">时间间隔:</span>
+          <Select v-model:value="filters.intervalOp" class="filter-op" :options="intervalOpOptions" />
+          <Input v-model:value="filters.intervalValue" class="filter-micro" :status="intervalValueStatus" />
+          <span class="filter-unit">h</span>
+        </div>
       </div>
     </div>
 
@@ -942,6 +947,7 @@
 </script>
 <style lang="less">
   @import '../styles/trans-analysis-table-ui.less';
+  @import (reference) '../styles/analysis-card-ui.less';
 
   @prefix-cls: ~'jeesite-face-to-face';
 
@@ -981,6 +987,7 @@
 
     .group-section {
       .analysis-card-shell();
+
       display: flex;
       flex-direction: column;
       flex-shrink: 0;
@@ -1010,7 +1017,7 @@
       }
 
       &.active {
-        background: rgba(247, 251, 255, 0.72);
+        background: rgb(247 251 255 / 72%);
         border-color: #1677ff;
         box-shadow: none;
       }
@@ -1054,6 +1061,7 @@
 
     .right-panel {
       .analysis-card-shell();
+
       flex: 1;
       min-width: 0;
       height: 100%;
@@ -1119,7 +1127,7 @@
       }
 
       &.active {
-        background: rgba(247, 251, 255, 0.72);
+        background: rgb(247 251 255 / 72%);
 
         .location-count {
           color: #1677ff;
@@ -1225,7 +1233,7 @@
     }
 
     .table-wrapper .ant-table-body::-webkit-scrollbar-thumb {
-      background: rgba(148, 163, 184, 0.6);
+      background: rgb(148 163 184 / 60%);
       border-radius: 999px;
     }
 
@@ -1293,4 +1301,17 @@
       }
     }
   }
+
+  /* 统一卡片视觉:对齐「持续联系 / 夜间联系 / 敏感日期联系」 */
+  .@{prefix-cls}.face-to-face-page {
+    .analysis-card-ui();
+  }
+
+  /* 碰面分析:左栏收窄,给右侧记录更多空间 */
+  .@{prefix-cls}.face-to-face-page {
+    .content-panel > .left-panel {
+      flex: 0 0 380px;
+      width: 380px;
+    }
+  }
 </style>

+ 32 - 21
ai-electron/frontend/src/trans/views/financialWealth/index.vue

@@ -6,29 +6,34 @@
     </div>
 
     <div class="filter-bar">
-      <div class="filter-item">
-        <span class="filter-label">选择对象:</span>
-        <Select
-          v-model:value="filters.target"
-          mode="multiple"
-          allow-clear
-          max-tag-count="responsive"
-          class="filter-input"
-          :options="targetOptions"
-          placeholder="全部"
-        />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">交易日期:</span>
-        <RangePicker v-model:value="filters.dateRange" class="filter-date" />
+      <div class="qj-filter-head">
+        <div class="qj-card-title">查询条件</div>
+        <div class="toolbar-actions">
+          <div class="filter-actions">
+            <Button type="primary" :loading="queryLoading" @click="handleSearch">查询</Button>
+            <Button @click="handleReset">重置</Button>
+          </div>
+          <div class="extra-actions">
+            <Button @click="openKeywordModal">关键词配置</Button>
+          </div>
+        </div>
       </div>
-      <div class="toolbar-actions">
-        <div class="filter-actions">
-          <Button type="primary" :loading="queryLoading" @click="handleSearch">查询</Button>
-          <Button @click="handleReset">重置</Button>
+      <div class="qj-filter-grid">
+        <div class="filter-item">
+          <span class="filter-label">选择对象:</span>
+          <Select
+            v-model:value="filters.target"
+            mode="multiple"
+            allow-clear
+            max-tag-count="responsive"
+            class="filter-input"
+            :options="targetOptions"
+            placeholder="全部"
+          />
         </div>
-        <div class="extra-actions">
-          <Button @click="openKeywordModal">关键词配置</Button>
+        <div class="filter-item">
+          <span class="filter-label">交易日期:</span>
+          <RangePicker v-model:value="filters.dateRange" class="filter-date" />
         </div>
       </div>
     </div>
@@ -674,6 +679,7 @@
 
 <style lang="less">
   @import '../styles/trans-analysis-table-ui.less';
+  @import (reference) '../styles/analysis-card-ui.less';
   @prefix-cls: ~'jeesite-financial-wealth';
 
   .@{prefix-cls}.financial-wealth-page {
@@ -791,4 +797,9 @@
       }
     }
   }
+
+  /* 统一卡片视觉:对齐「持续联系 / 夜间联系 / 敏感日期联系」 */
+  .@{prefix-cls}.financial-wealth-page {
+    .analysis-card-ui();
+  }
 </style>

+ 154 - 23
ai-electron/frontend/src/trans/views/fixedDeposit/index.vue

@@ -6,29 +6,34 @@
     </div>
 
     <div class="filter-bar">
-      <div class="filter-item">
-        <span class="filter-label">选择对象:</span>
-        <Select
-          v-model:value="filters.target"
-          mode="multiple"
-          allow-clear
-          max-tag-count="responsive"
-          class="filter-input"
-          :options="targetOptions"
-          placeholder="全部"
-        />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">交易日期:</span>
-        <RangePicker v-model:value="filters.dateRange" class="filter-date" />
+      <div class="qj-filter-head">
+        <div class="qj-card-title">查询条件</div>
+        <div class="toolbar-actions">
+          <div class="filter-actions">
+            <Button type="primary" :loading="queryLoading" @click="handleSearch">查询</Button>
+            <Button @click="handleReset">重置</Button>
+          </div>
+          <div class="extra-actions">
+            <Button @click="openKeywordModal">关键词配置</Button>
+          </div>
+        </div>
       </div>
-      <div class="toolbar-actions">
-        <div class="filter-actions">
-          <Button type="primary" :loading="queryLoading" @click="handleSearch">查询</Button>
-          <Button @click="handleReset">重置</Button>
+      <div class="qj-filter-grid">
+        <div class="filter-item">
+          <span class="filter-label">选择对象:</span>
+          <Select
+            v-model:value="filters.target"
+            mode="multiple"
+            allow-clear
+            max-tag-count="responsive"
+            class="filter-input"
+            :options="targetOptions"
+            placeholder="全部"
+          />
         </div>
-        <div class="extra-actions">
-          <Button @click="openKeywordModal">关键词配置</Button>
+        <div class="filter-item">
+          <span class="filter-label">交易日期:</span>
+          <RangePicker v-model:value="filters.dateRange" class="filter-date" />
         </div>
       </div>
     </div>
@@ -849,6 +854,7 @@
 
 <style lang="less">
   @import '../styles/trans-analysis-table-ui.less';
+  @import (reference) '../styles/analysis-card-ui.less';
   @prefix-cls: ~'jeesite-fixed-deposit';
 
   .@{prefix-cls}.fixed-deposit-page {
@@ -966,7 +972,7 @@
 
       &.active {
         border-color: #93c5fd;
-        background: rgba(244, 248, 255, 0.72);
+        background: rgb(244 248 255 / 72%);
         box-shadow: none;
       }
     }
@@ -1101,7 +1107,7 @@
     }
 
     .table-wrapper .ant-table-body::-webkit-scrollbar-thumb {
-      background: rgba(148, 163, 184, 0.6);
+      background: rgb(148 163 184 / 60%);
       border-radius: 999px;
     }
 
@@ -1227,4 +1233,129 @@
       }
     }
   }
+
+  /* 统一卡片视觉:对齐「持续联系 / 夜间联系 / 敏感日期联系」 */
+  .@{prefix-cls}.fixed-deposit-page {
+    .analysis-card-ui();
+  }
+
+  /* 左侧「银行卡定期存取汇总」卡片优化:与整体卡片语言对齐,并放宽信息密度 */
+  .@{prefix-cls}.fixed-deposit-page {
+    .content-layout > .left-panel {
+      flex: 0 0 380px;
+    }
+
+    .left-panel .panel-header {
+      align-items: flex-start;
+      min-height: 58px;
+      padding: 10px 14px;
+    }
+
+    .left-panel .panel-header > div:first-child {
+      flex-direction: column;
+      align-items: flex-start;
+      gap: 2px;
+    }
+
+    .left-panel .panel-subtitle {
+      color: #98a2b3;
+      font-size: 11px;
+      line-height: 20px;
+      white-space: nowrap;
+    }
+
+    .summary-list {
+      gap: 8px;
+      padding: 10px 14px 12px;
+    }
+
+    .summary-card {
+      padding: 10px 12px;
+      border: 1px solid #e7ebf3;
+      border-radius: 10px;
+      background: #fff;
+      transition:
+        border-color 0.2s ease,
+        background-color 0.2s ease;
+
+      &:hover {
+        border-color: #bfcdfb;
+        background: #f8faff;
+      }
+
+      &.active {
+        border-color: #2a50ec;
+        background: #f6f8ff;
+        box-shadow: 0 0 0 2px rgb(42 80 236 / 7%);
+      }
+    }
+
+    .summary-top {
+      margin-bottom: 6px;
+    }
+
+    .summary-name {
+      font-size: 14px;
+      font-weight: 600;
+      line-height: 20px;
+      color: #111827;
+    }
+
+    .summary-badge {
+      padding: 1px 7px;
+      border-radius: 5px;
+      background: #f4f6fa;
+      color: #5b6577;
+      font-size: 10px;
+      line-height: 18px;
+    }
+
+    .summary-card.active .summary-badge {
+      background: #eef2ff;
+      color: #2a50ec;
+    }
+
+    .summary-metrics {
+      display: grid;
+      grid-template-columns: repeat(2, minmax(0, 1fr));
+      gap: 6px 12px;
+      margin-top: 6px;
+      padding-top: 8px;
+      border-top: 1px solid #eff2f7;
+    }
+
+    .summary-metric {
+      flex-direction: column;
+      align-items: flex-start;
+      gap: 1px;
+      min-width: 0;
+      font-size: 12px;
+      line-height: 18px;
+    }
+
+    .summary-metric-label {
+      color: #98a2b3;
+      font-size: 11px;
+      line-height: 16px;
+      white-space: nowrap;
+    }
+
+    .summary-metric-value {
+      color: #1f2937;
+      font-size: 13px;
+      font-weight: 600;
+      line-height: 19px;
+      font-variant-numeric: tabular-nums;
+      overflow-wrap: anywhere;
+    }
+
+    .summary-empty {
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      min-height: 120px;
+      color: #98a2b3;
+      font-size: 12px;
+    }
+  }
 </style>

+ 47 - 36
ai-electron/frontend/src/trans/views/largeAmount/index.vue

@@ -6,43 +6,48 @@
     </div>
 
     <div class="filter-bar">
-      <div class="filter-item">
-        <span class="filter-label">选择对象:</span>
-        <Select
-          v-model:value="filters.target"
-          mode="multiple"
-          allow-clear
-          max-tag-count="responsive"
-          class="filter-input"
-          :options="targetOptions"
-          placeholder="全部"
-        />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">交易日期:</span>
-        <RangePicker v-model:value="filters.dateRange" class="filter-date" />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">交易次数:</span>
-        <Select v-model:value="filters.countOp" class="filter-op" :options="countOpOptions" />
-        <Input v-model:value="filters.countValue" class="filter-mini" :status="countValueStatus" />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">大额范围:</span>
-        <Select v-model:value="filters.amountOp" class="filter-op" :options="countOpOptions" />
-        <Input v-model:value="filters.amountValue" class="filter-mini" :status="amountValueStatus" />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">对方姓名:</span>
-        <Input v-model:value="filters.otherName" class="filter-input" placeholder="请输入对方姓名" />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">对方卡号:</span>
-        <Input v-model:value="filters.otherCardNo" class="filter-input" placeholder="请输入对方卡号" />
+      <div class="qj-filter-head">
+        <div class="qj-card-title">查询条件</div>
+        <div class="filter-actions">
+          <Button type="primary" :loading="queryLoading" @click="handleSearch">查询</Button>
+          <Button @click="handleReset">重置</Button>
+        </div>
       </div>
-      <div class="filter-actions">
-        <Button type="primary" :loading="queryLoading" @click="handleSearch">查询</Button>
-        <Button @click="handleReset">重置</Button>
+      <div class="qj-filter-grid">
+        <div class="filter-item">
+          <span class="filter-label">选择对象:</span>
+          <Select
+            v-model:value="filters.target"
+            mode="multiple"
+            allow-clear
+            max-tag-count="responsive"
+            class="filter-input"
+            :options="targetOptions"
+            placeholder="全部"
+          />
+        </div>
+        <div class="filter-item">
+          <span class="filter-label">交易日期:</span>
+          <RangePicker v-model:value="filters.dateRange" class="filter-date" />
+        </div>
+        <div class="filter-item">
+          <span class="filter-label">交易次数:</span>
+          <Select v-model:value="filters.countOp" class="filter-op" :options="countOpOptions" />
+          <Input v-model:value="filters.countValue" class="filter-mini" :status="countValueStatus" />
+        </div>
+        <div class="filter-item">
+          <span class="filter-label">大额范围:</span>
+          <Select v-model:value="filters.amountOp" class="filter-op" :options="countOpOptions" />
+          <Input v-model:value="filters.amountValue" class="filter-mini" :status="amountValueStatus" />
+        </div>
+        <div class="filter-item">
+          <span class="filter-label">对方姓名:</span>
+          <Input v-model:value="filters.otherName" class="filter-input" placeholder="请输入对方姓名" />
+        </div>
+        <div class="filter-item">
+          <span class="filter-label">对方卡号:</span>
+          <Input v-model:value="filters.otherCardNo" class="filter-input" placeholder="请输入对方卡号" />
+        </div>
       </div>
     </div>
 
@@ -759,6 +764,7 @@
 </script>
 <style lang="less">
   @import '../styles/trans-analysis-table-ui.less';
+  @import (reference) '../styles/analysis-card-ui.less';
 
   @prefix-cls: ~'jeesite-large-amount';
 
@@ -1147,4 +1153,9 @@
       }
     }
   }
+
+  /* 统一卡片视觉:对齐「持续联系 / 夜间联系 / 敏感日期联系」 */
+  .@{prefix-cls}.large-amount-page {
+    .analysis-card-ui();
+  }
 </style>

+ 55 - 41
ai-electron/frontend/src/trans/views/monthRegular/index.vue

@@ -6,39 +6,44 @@
     </div>
 
     <div class="filter-bar">
-      <div class="filter-item">
-        <span class="filter-label">选择对象:</span>
-        <Select
-          v-model:value="filters.target"
-          mode="multiple"
-          allow-clear
-          max-tag-count="responsive"
-          class="filter-input"
-          :options="targetOptions"
-          placeholder="全部"
-        />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">对方姓名:</span>
-        <Input v-model:value="filters.otherName" class="filter-input" placeholder="请输入对方姓名" allow-clear />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">对方卡号:</span>
-        <Input v-model:value="filters.otherCardNo" class="filter-input" placeholder="请输入对方卡号" allow-clear />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">连续转账月数:</span>
-        <Input v-model:value="filters.monthValue" class="filter-input" :status="monthValueStatus" />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">金额范围:</span>
-        <Input v-model:value="filters.amountMin" class="filter-mini" placeholder="" :status="amountMinStatus" />
-        <span class="filter-label">至</span>
-        <Input v-model:value="filters.amountMax" class="filter-mini" placeholder="" :status="amountMaxStatus" />
+      <div class="qj-filter-head">
+        <div class="qj-card-title">查询条件</div>
+        <div class="filter-actions">
+          <Button type="primary" :loading="queryLoading" @click="handleSearch">查询</Button>
+          <Button @click="handleReset">重置</Button>
+        </div>
       </div>
-      <div class="filter-actions">
-        <Button type="primary" :loading="queryLoading" @click="handleSearch">查询</Button>
-        <Button @click="handleReset">重置</Button>
+      <div class="qj-filter-grid">
+        <div class="filter-item">
+          <span class="filter-label">选择对象:</span>
+          <Select
+            v-model:value="filters.target"
+            mode="multiple"
+            allow-clear
+            max-tag-count="responsive"
+            class="filter-input"
+            :options="targetOptions"
+            placeholder="全部"
+          />
+        </div>
+        <div class="filter-item">
+          <span class="filter-label">对方姓名:</span>
+          <Input v-model:value="filters.otherName" class="filter-input" placeholder="请输入对方姓名" allow-clear />
+        </div>
+        <div class="filter-item">
+          <span class="filter-label">对方卡号:</span>
+          <Input v-model:value="filters.otherCardNo" class="filter-input" placeholder="请输入对方卡号" allow-clear />
+        </div>
+        <div class="filter-item">
+          <span class="filter-label">连续转账月数:</span>
+          <Input v-model:value="filters.monthValue" class="filter-input" :status="monthValueStatus" />
+        </div>
+        <div class="filter-item">
+          <span class="filter-label">金额范围:</span>
+          <Input v-model:value="filters.amountMin" class="filter-mini" placeholder="" :status="amountMinStatus" />
+          <span class="filter-label">至</span>
+          <Input v-model:value="filters.amountMax" class="filter-mini" placeholder="" :status="amountMaxStatus" />
+        </div>
       </div>
     </div>
 
@@ -495,11 +500,11 @@
           const index = params[0].dataIndex;
           const item = list[index];
           return `
-            <div style="padding: 8px;">
-              <div style="font-weight: bold; margin-bottom: 4px;">${labels[index]}</div>
-              <div>交易次数:${item.pairTotalTrans || 0}</div>
-              <div>交易总金额:${item.pairTotalAmount || 0}</div>
-              <div>持续月数:${item.pairContMonthCount || 0}</div>
+            <div style="padding: 10px 12px; min-width: 176px; background: #fff; border: 1px solid #e7ebf3; border-radius: 10px; box-shadow: 0 8px 24px rgba(16,24,40,0.12);">
+              <div style="font-size: 12px; font-weight: 600; color: #111827; margin-bottom: 6px;">${labels[index]}</div>
+              <div style="font-size: 12px; line-height: 20px; color: #5b6577;">交易次数:<b style="color:#111827;">${item.pairTotalTrans || 0}</b></div>
+              <div style="font-size: 12px; line-height: 20px; color: #5b6577;">交易总金额:<b style="color:#111827;">${item.pairTotalAmount || 0}</b></div>
+              <div style="font-size: 12px; line-height: 20px; color: #5b6577;">持续月数:<b style="color:#111827;">${item.pairContMonthCount || 0}</b></div>
             </div>
           `;
         },
@@ -508,12 +513,14 @@
         type: 'category',
         data: labels,
         axisTick: { show: false },
-        axisLine: { lineStyle: { color: '#d1d5db' } },
+        axisLine: { lineStyle: { color: '#e7ebf3' } },
+        axisLabel: { color: '#6b7280', fontSize: 12 },
       },
       yAxis: {
         type: 'value',
         axisTick: { show: false },
-        splitLine: { lineStyle: { color: '#e5e7eb' } },
+        axisLabel: { color: '#6b7280', fontSize: 12 },
+        splitLine: { lineStyle: { color: '#eef2f7', type: 'dashed' } },
       },
       grid: {
         left: '2%',
@@ -528,12 +535,13 @@
           data: list.length ? list.map((item) => Number(item.pairTotalTrans || 0)) : [0],
           type: 'bar',
           barWidth: 18,
-          itemStyle: { color: '#94a3b8', borderRadius: [6, 6, 0, 0] },
+          itemStyle: { color: '#2a50ec', borderRadius: [6, 6, 0, 0] },
           label: {
             show: true,
             position: 'top',
-            color: '#6b7280',
+            color: '#1f2937',
             fontSize: 12,
+            fontWeight: 600,
           },
         },
       ],
@@ -682,6 +690,7 @@
 </script>
 <style lang="less">
   @import '../styles/trans-analysis-table-ui.less';
+  @import (reference) '../styles/analysis-card-ui.less';
 
   @prefix-cls: ~'jeesite-month-regular';
 
@@ -795,4 +804,9 @@
       }
     }
   }
+
+  /* 统一卡片视觉:对齐「持续联系 / 夜间联系 / 敏感日期联系」 */
+  .@{prefix-cls}.month-regular-page {
+    .analysis-card-ui();
+  }
 </style>

+ 111 - 48
ai-electron/frontend/src/trans/views/morningEveningCall/index.vue

@@ -6,49 +6,54 @@
     </div>
 
     <div class="filter-bar">
-      <div class="filter-item">
-        <span class="filter-label">选择对象:</span>
-        <Select
-          v-model:value="filters.target"
-          mode="multiple"
-          allow-clear
-          max-tag-count="responsive"
-          class="filter-input"
-          :options="targetOptions"
-          placeholder="请选择对象"
-        />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">通话时间:</span>
-        <RangePicker
-          v-model:value="filters.dateRange"
-          class="filter-date"
-          :format="DATE_FORMAT"
-          :value-format="DATE_FORMAT"
-          :placeholder="['开始日期', '结束日期']"
-        />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">早上时间:</span>
-        <Input :value="'6点'" class="filter-time filter-time--fixed" disabled />
-        <span class="filter-label">至</span>
-        <Select v-model:value="filters.morningEnd" class="filter-time" :options="morningEndOptions" />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">晚上时间:</span>
-        <Select v-model:value="filters.eveningStart" class="filter-time" :options="eveningStartOptions" />
-        <span class="filter-label">至</span>
-        <Input :value="'6点'" class="filter-time filter-time--fixed" disabled />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">通话次数:</span>
-        <Select v-model:value="filters.countOp" class="filter-op" :options="opOptions" />
-        <Input v-model:value="filters.countValue" class="filter-micro" :status="countValueStatus" />
-        <span class="filter-unit">次</span>
+      <div class="qj-filter-head">
+        <div class="qj-card-title">查询条件</div>
+        <div class="filter-actions">
+          <Button type="primary" :loading="queryLoading" @click="handleSearch">查询</Button>
+          <Button @click="handleReset">重置</Button>
+        </div>
       </div>
-      <div class="filter-actions">
-        <Button type="primary" :loading="queryLoading" @click="handleSearch">查询</Button>
-        <Button @click="handleReset">重置</Button>
+      <div class="qj-filter-grid">
+        <div class="filter-item">
+          <span class="filter-label">选择对象:</span>
+          <Select
+            v-model:value="filters.target"
+            mode="multiple"
+            allow-clear
+            max-tag-count="responsive"
+            class="filter-input"
+            :options="targetOptions"
+            placeholder="请选择对象"
+          />
+        </div>
+        <div class="filter-item">
+          <span class="filter-label">通话时间:</span>
+          <RangePicker
+            v-model:value="filters.dateRange"
+            class="filter-date"
+            :format="DATE_FORMAT"
+            :value-format="DATE_FORMAT"
+            :placeholder="['开始日期', '结束日期']"
+          />
+        </div>
+        <div class="filter-item">
+          <span class="filter-label">早上时间:</span>
+          <Input :value="'6点'" class="filter-time filter-time--fixed" disabled />
+          <span class="filter-label">至</span>
+          <Select v-model:value="filters.morningEnd" class="filter-time" :options="morningEndOptions" />
+        </div>
+        <div class="filter-item">
+          <span class="filter-label">晚上时间:</span>
+          <Select v-model:value="filters.eveningStart" class="filter-time" :options="eveningStartOptions" />
+          <span class="filter-label">至</span>
+          <Input :value="'6点'" class="filter-time filter-time--fixed" disabled />
+        </div>
+        <div class="filter-item">
+          <span class="filter-label">通话次数:</span>
+          <Select v-model:value="filters.countOp" class="filter-op" :options="opOptions" />
+          <Input v-model:value="filters.countValue" class="filter-micro" :status="countValueStatus" />
+          <span class="filter-unit">次</span>
+        </div>
       </div>
     </div>
 
@@ -68,8 +73,17 @@
       <div class="left-panel">
         <div class="panel-header">
           <div>
-            <div class="panel-title">通话地点</div>
-            <div class="panel-desc">{{ leftPanelDesc }}</div>
+            <div class="panel-title">
+              通话地点
+              <Tooltip
+                :title="leftPanelDesc"
+                placement="bottomLeft"
+                :trigger="['hover', 'click']"
+                overlayClassName="qj-light-tooltip"
+              >
+                <InfoCircleOutlined class="panel-title-tip" />
+              </Tooltip>
+            </div>
           </div>
         </div>
 
@@ -155,7 +169,8 @@
 </template>
 <script lang="ts" setup name="ViewsMorningEveningCallIndex">
   import { computed, onMounted, ref } from 'vue';
-  import { Select, Input, Button, DatePicker } from 'ant-design-vue';
+  import { Select, Input, Button, DatePicker, Tooltip } from 'ant-design-vue';
+  import { InfoCircleOutlined } from '@ant-design/icons-vue';
   import AnalysisPagination from '@/core/components/AnalysisPagination/src/AnalysisPagination.vue';
   import { EmptyState } from '@/core/components/EmptyState';
   import { BasicTable } from '@/core/components/Table';
@@ -812,6 +827,7 @@
 </script>
 <style lang="less">
   @import '../styles/trans-analysis-table-ui.less';
+  @import (reference) '../styles/analysis-card-ui.less';
 
   @prefix-cls: ~'jeesite-morning-evening-call';
 
@@ -899,6 +915,7 @@
 
     .right-panel {
       .analysis-card-shell();
+
       flex: 1;
       min-width: 0;
     }
@@ -957,11 +974,11 @@
       background: transparent;
 
       &:hover {
-        background: rgba(245, 247, 255, 0.72);
+        background: rgb(245 247 255 / 72%);
       }
 
       &.active {
-        background: rgba(239, 246, 255, 0.72);
+        background: rgb(239 246 255 / 72%);
       }
 
       .location-name {
@@ -1053,7 +1070,7 @@
     }
 
     .table-wrapper .ant-table-body::-webkit-scrollbar-thumb {
-      background: rgba(148, 163, 184, 0.6);
+      background: rgb(148 163 184 / 60%);
       border-radius: 999px;
     }
 
@@ -1137,4 +1154,50 @@
       }
     }
   }
+
+  /* 统一卡片视觉:对齐「持续联系 / 夜间联系 / 敏感日期联系」 */
+  .@{prefix-cls}.morning-evening-call-page {
+    .analysis-card-ui();
+  }
+
+  /* 早晚通话地:左栏收窄,右侧通话记录获得更多空间 */
+  .@{prefix-cls}.morning-evening-call-page {
+    .content-panel > .left-panel {
+      flex: 0 0 400px;
+      width: 400px;
+    }
+  }
+
+  /* 面板标题右侧的信息图标:悬浮/点击查看说明 */
+  .panel-title-tip {
+    margin-left: 6px;
+    color: #98a2b3;
+    font-size: 12px;
+    cursor: pointer;
+    vertical-align: -1px;
+
+    &:hover {
+      color: #2a50ec;
+    }
+  }
+
+  /* 信息提示:浅色卡片风格,与页面整体统一 */
+  .ant-tooltip.qj-light-tooltip {
+    max-width: 320px;
+
+    .ant-tooltip-inner {
+      padding: 8px 11px;
+      border: 1px solid #e7ebf3;
+      border-radius: 8px;
+      background: #fff;
+      color: #1f2937;
+      font-size: 12px;
+      line-height: 18px;
+      box-shadow: 0 8px 24px rgb(16 24 40 / 12%);
+    }
+
+    .ant-tooltip-arrow::before {
+      background: #fff;
+    }
+  }
 </style>

+ 29 - 18
ai-electron/frontend/src/trans/views/peerAnalysis/index.vue

@@ -6,25 +6,30 @@
     </div>
 
     <div class="filter-bar">
-      <div class="filter-item">
-        <span class="filter-label">选择对象:</span>
-        <Select
-          v-model:value="filters.target"
-          mode="multiple"
-          allow-clear
-          max-tag-count="responsive"
-          class="filter-input"
-          :options="targetOptions"
-          placeholder="请选择对象"
-        />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">日期:</span>
-        <RangePicker v-model:value="filters.dateRange" class="filter-date" />
+      <div class="qj-filter-head">
+        <div class="qj-card-title">查询条件</div>
+        <div class="filter-actions">
+          <Button type="primary" :loading="queryLoading" @click="handleSearch">查询</Button>
+          <Button @click="handleReset">重置</Button>
+        </div>
       </div>
-      <div class="filter-actions">
-        <Button type="primary" :loading="queryLoading" @click="handleSearch">查询</Button>
-        <Button @click="handleReset">重置</Button>
+      <div class="qj-filter-grid">
+        <div class="filter-item">
+          <span class="filter-label">选择对象:</span>
+          <Select
+            v-model:value="filters.target"
+            mode="multiple"
+            allow-clear
+            max-tag-count="responsive"
+            class="filter-input"
+            :options="targetOptions"
+            placeholder="请选择对象"
+          />
+        </div>
+        <div class="filter-item">
+          <span class="filter-label">日期:</span>
+          <RangePicker v-model:value="filters.dateRange" class="filter-date" />
+        </div>
       </div>
     </div>
 
@@ -966,6 +971,7 @@
 </script>
 <style lang="less">
   @import '../styles/trans-analysis-table-ui.less';
+  @import (reference) '../styles/analysis-card-ui.less';
 
   @prefix-cls: ~'jeesite-peer-analysis';
 
@@ -1250,4 +1256,9 @@
       }
     }
   }
+
+  /* 统一卡片视觉:对齐「持续联系 / 夜间联系 / 敏感日期联系」 */
+  .@{prefix-cls}.peer-analysis-page {
+    .analysis-card-ui();
+  }
 </style>

+ 1182 - 0
ai-electron/frontend/src/trans/views/styles/analysis-card-ui.less

@@ -0,0 +1,1182 @@
+// 交易分析类页面统一卡片视觉(对齐「持续联系 / 夜间联系 / 敏感日期联系」)
+// 用法:在页面根选择器内调用 .analysis-card-ui();
+// 依赖模板结构调整:.filter-bar 内含 .qj-filter-head + .qj-filter-grid
+
+.analysis-card-ui() {
+  background: #f4f6fa;
+  padding: 8px 10px !important;
+  gap: 8px;
+
+  /* ---------------- 查询条件卡 ---------------- */
+  > .filter-bar {
+    display: flex;
+    flex-flow: column nowrap;
+    align-items: stretch;
+    gap: 0;
+    min-height: 0;
+    padding: 0;
+    border: 1px solid #e7ebf3;
+    border-radius: 14px;
+    background: #fff;
+    box-shadow: 0 1px 2px rgb(16 24 40 / 4%);
+    box-sizing: border-box;
+    overflow: hidden;
+    flex-shrink: 0;
+  }
+
+  .qj-filter-head {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    min-height: 42px;
+    padding: 0 14px;
+    border-bottom: 1px solid #eff2f7;
+    box-sizing: border-box;
+  }
+
+  .qj-card-title {
+    position: relative;
+    padding-left: 11px;
+    color: #111827;
+    font-size: 15px;
+    font-weight: 600;
+    line-height: 22px;
+    white-space: nowrap;
+
+    &::before {
+      content: '';
+      position: absolute;
+      left: 0;
+      top: 4px;
+      bottom: 4px;
+      width: 3px;
+      border-radius: 2px;
+      background: #2a50ec;
+    }
+  }
+
+  .qj-filter-actions,
+  .toolbar-actions,
+  .filter-actions {
+    display: flex;
+    flex: 0 0 auto;
+    align-items: center;
+    gap: 8px;
+    margin-left: auto;
+  }
+
+  .qj-filter-grid {
+    display: flex;
+    flex-wrap: wrap;
+    align-items: center;
+    gap: 8px 14px;
+    padding: 10px 14px 12px;
+    box-sizing: border-box;
+  }
+
+  .filter-item {
+    display: inline-flex;
+    flex: 0 0 auto;
+    align-items: center;
+    gap: 6px;
+    min-width: 0;
+  }
+
+  .filter-label {
+    flex: 0 0 auto;
+    color: #5b6577;
+    font-size: 12px;
+    line-height: 32px;
+    white-space: nowrap;
+  }
+
+  .filter-input {
+    width: 150px;
+    min-width: 0;
+  }
+
+  .filter-date {
+    width: 240px;
+  }
+
+  .filter-mini {
+    width: 90px;
+    min-width: 0;
+  }
+
+  .filter-op {
+    width: 84px;
+    min-width: 84px;
+  }
+
+  /* 首个条件为「选择对象」多选,宽度对齐持续联系页(200px) */
+  .qj-filter-grid > .filter-item:first-child .filter-input,
+  .qj-filter-grid > .filter-item:first-child .ant-select {
+    width: 200px;
+  }
+
+  /* 统一控件外观:32px、白底、8px 圆角 */
+  .qj-filter-grid {
+    .ant-input,
+    .ant-input-affix-wrapper,
+    .ant-picker {
+      height: 32px;
+      padding: 0 10px;
+      border: 1px solid #e7ebf3;
+      border-radius: 8px;
+      background: #fff;
+      box-shadow: none;
+      font-size: 12px;
+      transition:
+        border-color 0.2s ease,
+        box-shadow 0.2s ease;
+    }
+
+    .ant-input-affix-wrapper {
+      display: inline-flex;
+      align-items: center;
+    }
+
+    .ant-input-affix-wrapper > .ant-input {
+      height: 100%;
+      padding: 0;
+      border: 0;
+      border-radius: 0;
+      background: transparent;
+      box-shadow: none;
+    }
+
+    .ant-picker .ant-picker-input > input {
+      font-size: 12px;
+    }
+
+    .ant-input:hover,
+    .ant-input-affix-wrapper:hover,
+    .ant-picker:hover,
+    .ant-select:hover .ant-select-selector {
+      border-color: #bfcdfb;
+      background: #fff;
+    }
+
+    .ant-input:focus,
+    .ant-input-affix-wrapper-focused,
+    .ant-picker-focused,
+    .ant-select-focused .ant-select-selector {
+      border-color: #2a50ec !important;
+      background: #fff;
+      box-shadow: 0 0 0 2px rgb(42 80 236 / 12%) !important;
+    }
+
+    .ant-input-status-error,
+    .ant-input-affix-wrapper-status-error {
+      border-color: #e5484d;
+    }
+
+    .ant-select {
+      width: 150px;
+      height: 32px;
+    }
+
+    .qj-filter-grid > .filter-item:first-child .ant-select {
+      width: 200px;
+    }
+
+    .filter-mini.ant-select,
+    .filter-op.ant-select {
+      width: 84px;
+    }
+
+    .ant-select .ant-select-selector {
+      height: 32px !important;
+      padding: 0 10px;
+      border: 1px solid #e7ebf3;
+      border-radius: 8px;
+      background: #fff;
+      box-shadow: none;
+    }
+
+    .ant-select-single .ant-select-selection-item,
+    .ant-select-single .ant-select-selection-placeholder {
+      line-height: 30px;
+      font-size: 12px;
+    }
+
+    .ant-select-multiple .ant-select-selector {
+      display: flex;
+      align-items: center;
+      overflow: hidden;
+    }
+
+    .ant-select-multiple .ant-select-selection-overflow {
+      flex-wrap: nowrap;
+      align-items: center;
+    }
+
+    .ant-select-multiple .ant-select-selection-item {
+      height: 22px;
+      margin: 0 4px 0 0;
+      padding: 0 7px;
+      border-radius: 4px;
+      background: #eef2ff;
+      color: #2448c9;
+      font-size: 11px;
+      line-height: 22px;
+    }
+
+    .ant-select-multiple .ant-select-selection-search {
+      margin-inline-start: 0;
+    }
+
+    .ant-select-multiple .ant-select-selection-placeholder {
+      left: 10px;
+      line-height: 30px;
+      font-size: 12px;
+    }
+
+    .ant-select-arrow {
+      color: #98a2b3;
+      font-size: 11px;
+    }
+  }
+
+  .qj-btn-primary.ant-btn,
+  .filter-actions .ant-btn-primary,
+  .toolbar-actions .ant-btn-primary {
+    height: 30px;
+    padding: 0 18px;
+    border: 1px solid #2a50ec;
+    border-radius: 8px;
+    background: #2a50ec;
+    box-shadow: none;
+    font-size: 12px;
+    font-weight: 500;
+
+    &:hover,
+    &:focus {
+      border-color: #4b6bf0;
+      background: #4b6bf0;
+    }
+
+    &:active {
+      border-color: #1e3ed0;
+      background: #1e3ed0;
+    }
+  }
+
+  .filter-actions .ant-btn:not(.ant-btn-primary),
+  .toolbar-actions .ant-btn:not(.ant-btn-primary) {
+    height: 30px;
+    padding: 0 14px;
+    border: 1px solid #e7ebf3;
+    border-radius: 8px;
+    background: #fff;
+    color: #5b6577;
+    font-size: 12px;
+    font-weight: 500;
+    box-shadow: none;
+
+    &:hover,
+    &:focus {
+      border-color: #bfcdfb;
+      background: #f8faff;
+      color: #2a50ec;
+    }
+  }
+
+  /* ---------------- 内容区卡片 ---------------- */
+  > .content-panel,
+  > .content-layout {
+    display: flex;
+    flex: 1 1 auto;
+    gap: 8px;
+    min-height: 0;
+    padding: 0;
+    background: transparent;
+    box-sizing: border-box;
+    overflow: hidden;
+
+    &::before,
+    &::after {
+      display: none;
+    }
+  }
+
+  .content-panel > .panel-left,
+  .content-panel > .panel-right,
+  .content-panel > .left-panel,
+  .content-panel > .right-panel,
+  .content-panel > .summary-panel,
+  .content-panel > .trend-panel,
+  .content-panel > .table-card,
+  .content-layout > .panel-left,
+  .content-layout > .panel-right,
+  .content-layout > .left-panel,
+  .content-layout > .right-panel,
+  .content-layout > .summary-panel,
+  .content-layout > .trend-panel,
+  .content-layout > .table-card,
+  > .table-card,
+  > .panel {
+    display: flex;
+    flex: 1 1 auto;
+    flex-direction: column;
+    min-height: 0;
+    min-width: 0;
+    padding: 0;
+    border: 1px solid #e7ebf3;
+    border-radius: 14px;
+    background: #fff;
+    box-shadow: 0 1px 2px rgb(16 24 40 / 4%);
+    box-sizing: border-box;
+    overflow: hidden;
+  }
+
+  .content-panel > .panel-left,
+  .content-panel > .left-panel {
+    flex: 0 0 42%;
+  }
+
+  .content-layout > .left-panel {
+    flex: 0 0 320px;
+  }
+
+  .panel-header {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    flex: 0 0 auto;
+    width: 100%;
+    min-height: 42px;
+    padding: 0 14px;
+    border-bottom: 1px solid #eff2f7;
+    box-sizing: border-box;
+  }
+
+  .panel-header > div:first-child {
+    display: flex;
+    flex-flow: row wrap;
+    align-items: center;
+    gap: 10px;
+    min-width: 0;
+  }
+
+  .panel-title {
+    position: relative;
+    padding-left: 11px;
+    color: #111827;
+    font-size: 15px;
+    font-weight: 600;
+    line-height: 22px;
+
+    &::before {
+      content: '';
+      position: absolute;
+      left: 0;
+      top: 4px;
+      bottom: 4px;
+      width: 3px;
+      border-radius: 2px;
+      background: #2a50ec;
+    }
+  }
+
+  .panel-desc {
+    margin: 0;
+    padding-left: 11px;
+    color: #98a2b3;
+    font-size: 11px;
+    line-height: 16px;
+  }
+
+  .panel-subtitle {
+    margin-left: auto;
+    color: #98a2b3;
+    font-size: 11px;
+    line-height: 16px;
+    white-space: nowrap;
+    font-variant-numeric: tabular-nums;
+  }
+
+  .panel-header .panel-actions,
+  .panel-header > .ant-btn {
+    margin-left: auto;
+  }
+
+  .panel-search {
+    flex: 0 0 auto;
+    padding: 8px 14px 0;
+    margin: 0;
+    box-sizing: border-box;
+  }
+
+  .panel-search .ant-input,
+  .panel-search .ant-input-affix-wrapper {
+    width: 100%;
+    height: 30px;
+    padding: 0 9px;
+    border: 1px solid #e7ebf3;
+    border-radius: 8px;
+    background: #fff;
+    box-shadow: none;
+    font-size: 12px;
+  }
+
+  .panel-search .ant-input-affix-wrapper > .ant-input {
+    height: 100%;
+    padding: 0;
+    border: 0;
+    background: transparent;
+    box-shadow: none;
+  }
+
+  .panel-search .ant-input:focus,
+  .panel-search .ant-input-affix-wrapper-focused {
+    border-color: #2a50ec;
+    box-shadow: 0 0 0 2px rgb(42 80 236 / 12%);
+  }
+
+  /* ---------------- 卡片内的二级筛选条 ---------------- */
+  .inner-filter-bar {
+    display: flex;
+    flex-wrap: wrap;
+    align-items: center;
+    gap: 8px 12px;
+    flex: 0 0 auto;
+    padding: 8px 14px;
+    border-bottom: 1px solid #eff2f7;
+    background: #fbfcfe;
+    box-sizing: border-box;
+  }
+
+  .inner-filter-bar .filter-item {
+    display: inline-flex;
+    align-items: center;
+    gap: 6px;
+  }
+
+  .inner-filter-bar .filter-label {
+    color: #98a2b3;
+    font-size: 11px;
+    line-height: 30px;
+    white-space: nowrap;
+  }
+
+  .inner-filter-bar .filter-input {
+    width: 150px;
+  }
+
+  .inner-filter-bar .filter-op {
+    width: 70px;
+    min-width: 70px;
+  }
+
+  .inner-filter-bar .filter-micro {
+    width: 88px;
+  }
+
+  .inner-filter-bar .filter-actions {
+    margin-left: auto;
+  }
+
+  .inner-filter-bar .ant-input,
+  .inner-filter-bar .ant-input-affix-wrapper,
+  .inner-filter-bar .ant-picker {
+    height: 30px;
+    padding: 0 9px;
+    border: 1px solid #e7ebf3;
+    border-radius: 8px;
+    background: #fff;
+    box-shadow: none;
+    font-size: 12px;
+  }
+
+  .inner-filter-bar .ant-select {
+    height: 30px;
+  }
+
+  .inner-filter-bar .ant-select .ant-select-selector {
+    height: 30px !important;
+    padding: 0 9px;
+    border: 1px solid #e7ebf3;
+    border-radius: 8px;
+    background: #fff;
+    box-shadow: none;
+  }
+
+  .inner-filter-bar .ant-select-single .ant-select-selection-item,
+  .inner-filter-bar .ant-select-single .ant-select-selection-placeholder {
+    line-height: 28px;
+    font-size: 12px;
+  }
+
+  .inner-filter-bar .ant-btn {
+    height: 30px;
+    padding: 0 14px;
+    border-radius: 8px;
+    font-size: 12px;
+    font-weight: 500;
+    box-shadow: none;
+  }
+
+  .inner-filter-bar .ant-input:focus,
+  .inner-filter-bar .ant-picker-focused,
+  .inner-filter-bar .ant-select-focused .ant-select-selector {
+    border-color: #2a50ec !important;
+    box-shadow: 0 0 0 2px rgb(42 80 236 / 12%) !important;
+  }
+
+  .query-state-panel {
+    display: flex;
+    flex: 1 1 auto;
+    align-items: center;
+    justify-content: center;
+    min-height: 0;
+    padding: 8px 0 16px;
+  }
+
+  .table-panel {
+    display: flex;
+    flex: 1 1 auto;
+    flex-direction: column;
+    min-height: 0;
+    padding: 0;
+    box-sizing: border-box;
+    overflow: hidden;
+  }
+
+  /* ---------------- 表格 ---------------- */
+  .table-wrapper {
+    display: flex;
+    flex: 1 1 auto;
+    flex-direction: column;
+    min-height: 0;
+    width: 100%;
+    padding-top: 8px;
+    border: 0;
+    border-radius: 0;
+    background: transparent;
+    box-shadow: none;
+    box-sizing: border-box;
+    overflow: hidden;
+  }
+
+  .table-wrapper .jeesite-basic-table {
+    display: flex;
+    flex-direction: column;
+    height: 100%;
+    min-height: 0;
+  }
+
+  .table-wrapper .ant-table-wrapper,
+  .table-wrapper .ant-spin-nested-loading,
+  .table-wrapper .ant-spin-container,
+  .table-wrapper .ant-table,
+  .table-wrapper .ant-table-container {
+    display: flex;
+    flex: 1 1 auto;
+    flex-direction: column;
+    height: 100%;
+    min-height: 0;
+  }
+
+  .table-wrapper .ant-table-body {
+    flex: 1 1 auto;
+    min-height: 0;
+    max-height: 100% !important;
+    overflow-y: auto;
+  }
+
+  .table-wrapper .ant-table-header {
+    flex: 0 0 auto;
+  }
+
+  .table-wrapper .ant-table {
+    font-size: 12px;
+  }
+
+  .table-wrapper .ant-table-thead > tr > th {
+    height: 40px;
+    padding: 0 12px;
+    border-right: 0;
+    border-bottom: 1px solid #eff2f7;
+    background: #f8fafc;
+    color: #5b6577;
+    font-size: 12px;
+    font-weight: 600;
+  }
+
+  .table-wrapper .ant-table-thead > tr > th::before {
+    display: none;
+  }
+
+  .table-wrapper .ant-table-tbody > tr > td {
+    height: 40px;
+    padding: 0 12px;
+    border-right: 0;
+    border-bottom: 1px solid #f4f6fa !important;
+    color: #1f2937;
+    font-size: 12px;
+  }
+
+  .table-wrapper .ant-table-tbody > tr:last-child > td {
+    border-bottom: 0 !important;
+  }
+
+  .table-wrapper .ant-table-tbody > tr > td,
+  .table-wrapper .ant-table-thead > tr > th {
+    vertical-align: middle;
+  }
+
+  .table-wrapper .ant-table-tbody > tr:hover > td {
+    background: #f8faff !important;
+  }
+
+  /* 选中行:仅用背景色区分,不加边框 */
+  .table-wrapper .ant-table-tbody > tr.summary-row-selected > td,
+  .table-wrapper .ant-table-tbody > tr.active-row > td {
+    background: #dbe7ff !important;
+    color: #1e3ed0;
+    font-weight: 600;
+    box-shadow: none;
+    border-radius: 0;
+  }
+
+  .table-wrapper .ant-table-tbody > tr.summary-row-selected:hover > td,
+  .table-wrapper .ant-table-tbody > tr.active-row:hover > td {
+    background: #d1e0ff !important;
+  }
+
+  .table-wrapper .ant-table-tbody > tr.summary-row-selected > td:first-child,
+  .table-wrapper .ant-table-tbody > tr.active-row > td:first-child {
+    color: #1e3ed0;
+    font-weight: 600;
+  }
+
+  .table-wrapper .ant-table-tbody > tr.summary-row-selected > td .ant-btn-link,
+  .table-wrapper .ant-table-tbody > tr.active-row > td .ant-btn-link {
+    color: #1e3ed0;
+  }
+
+  .table-wrapper .ant-table-tbody > tr > td .ant-btn-link {
+    height: auto;
+    padding: 0;
+    color: #2a50ec;
+    font-size: 12px;
+    font-weight: 500;
+  }
+
+  .table-wrapper .ant-table-column-sorter {
+    color: #b6bfcc;
+  }
+
+  .table-wrapper::-webkit-scrollbar,
+  .table-wrapper .ant-table-body::-webkit-scrollbar {
+    width: 8px;
+    height: 8px;
+  }
+
+  .table-wrapper .ant-table-body::-webkit-scrollbar-thumb {
+    background: rgb(148 163 184 / 55%);
+    border-radius: 999px;
+  }
+
+  .table-wrapper .ant-table-body::-webkit-scrollbar-track {
+    background: transparent;
+  }
+
+  /* ---------------- 分页 ---------------- */
+  .pagination-bar {
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+    gap: 8px;
+    flex: 0 0 auto;
+    flex-wrap: wrap;
+    width: 100%;
+    padding: 8px 14px 10px;
+    border-top: 1px solid #eff2f7;
+    background: transparent;
+    box-sizing: border-box;
+  }
+
+  .pagination-bar .analysis-pagination {
+    color: #98a2b3;
+    font-size: 11px;
+  }
+
+  .pagination-bar .ant-pagination-item,
+  .pagination-bar .ant-pagination-prev,
+  .pagination-bar .ant-pagination-next {
+    min-width: 26px;
+    height: 26px;
+    border-radius: 6px;
+    border-color: #e7ebf3;
+    line-height: 24px;
+  }
+
+  .pagination-bar .ant-pagination-item a {
+    color: #5b6577;
+    font-size: 12px;
+    line-height: 24px;
+  }
+
+  .pagination-bar .ant-pagination-item-active {
+    border-color: #2a50ec;
+    background: #eef2ff;
+  }
+
+  .pagination-bar .ant-pagination-item-active a {
+    color: #2a50ec;
+    font-weight: 600;
+  }
+
+  .pagination-bar .ant-pagination-options {
+    margin-inline-start: 8px;
+  }
+
+  .pagination-bar .ant-pagination-options-quick-jumper input {
+    height: 26px;
+    border-radius: 6px;
+  }
+
+  .pagination-bar .ant-select-selector {
+    border-radius: 6px;
+  }
+
+  /* ---------------- 轨迹分析类页面(同行 / 同住 / 碰面 / 早晚通话 / 快递 / 基站) ---------------- */
+  > .page-content {
+    display: flex;
+    flex: 1 1 auto;
+    flex-direction: column;
+    gap: 8px;
+    min-height: 0;
+    padding: 0;
+    background: transparent;
+    box-sizing: border-box;
+    overflow: hidden;
+  }
+
+  .page-content > .group-section {
+    display: flex;
+    flex: 0 0 auto;
+    flex-direction: column;
+    min-height: 0;
+    border: 1px solid #e7ebf3;
+    border-radius: 14px;
+    background: #fff;
+    box-shadow: 0 1px 2px rgb(16 24 40 / 4%);
+    box-sizing: border-box;
+    overflow: hidden;
+  }
+
+  .page-content > .record-section {
+    display: flex;
+    flex: 1 1 auto;
+    flex-direction: column;
+    min-height: 0;
+    padding: 0;
+    border: 0;
+    background: transparent;
+  }
+
+  .record-section > .record-layout {
+    display: flex;
+    flex: 1 1 auto;
+    gap: 8px;
+    min-height: 0;
+  }
+
+  .record-layout > .record-panel,
+  .record-layout > .detail-panel {
+    display: flex;
+    flex: 1 1 0;
+    flex-direction: column;
+    min-width: 0;
+    min-height: 0;
+    padding: 0;
+    border: 1px solid #e7ebf3;
+    border-radius: 14px;
+    background: #fff;
+    box-shadow: 0 1px 2px rgb(16 24 40 / 4%);
+    box-sizing: border-box;
+    overflow: hidden;
+  }
+
+  .section-head {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    flex: 0 0 auto;
+    min-height: 42px;
+    padding: 0 14px;
+    border-bottom: 1px solid #eff2f7;
+    box-sizing: border-box;
+  }
+
+  .section-title {
+    position: relative;
+    padding-left: 11px;
+    color: #111827;
+    font-size: 15px;
+    font-weight: 600;
+    line-height: 22px;
+
+    &::before {
+      content: '';
+      position: absolute;
+      left: 0;
+      top: 4px;
+      bottom: 4px;
+      width: 3px;
+      border-radius: 2px;
+      background: #2a50ec;
+    }
+  }
+
+  .section-meta {
+    margin-left: auto;
+    color: #98a2b3;
+    font-size: 11px;
+    line-height: 16px;
+    white-space: nowrap;
+    font-variant-numeric: tabular-nums;
+  }
+
+  .group-section .peer-groups {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 8px;
+    max-height: 152px;
+    padding: 10px 14px 12px;
+    overflow-y: auto;
+    box-sizing: border-box;
+  }
+
+  .group-section .empty-state {
+    padding: 8px 14px 14px;
+  }
+
+  /* 同住分析:目标标签 + 左右分栏 */
+  > .target-tags {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 8px;
+    flex: 0 0 auto;
+    padding: 8px 14px;
+    border: 1px solid #e7ebf3;
+    border-radius: 14px;
+    background: #fff;
+    box-shadow: 0 1px 2px rgb(16 24 40 / 4%);
+    box-sizing: border-box;
+  }
+
+  .target-tag {
+    height: 26px;
+    padding: 0 12px;
+    border: 1px solid #e7ebf3;
+    border-radius: 999px;
+    background: #fff;
+    color: #5b6577;
+    font-size: 12px;
+    line-height: 24px;
+    cursor: pointer;
+    transition:
+      border-color 0.2s ease,
+      background-color 0.2s ease,
+      color 0.2s ease;
+  }
+
+  .content-panel > .left-sidebar,
+  .content-panel > .right-content {
+    display: flex;
+    flex: 1 1 auto;
+    flex-direction: column;
+    min-width: 0;
+    min-height: 0;
+    padding: 0;
+    border: 1px solid #e7ebf3;
+    border-radius: 14px;
+    background: #fff;
+    box-shadow: 0 1px 2px rgb(16 24 40 / 4%);
+    box-sizing: border-box;
+    overflow: hidden;
+  }
+
+  .content-panel > .left-sidebar {
+    flex: 0 0 260px;
+  }
+
+  .panel-header--sidebar {
+    align-items: center;
+  }
+
+  .panel-header--sidebar .panel-desc {
+    display: none;
+  }
+
+  .person-list-wrap,
+  .record-content,
+  .location-list-wrap {
+    display: flex;
+    flex: 1 1 auto;
+    flex-direction: column;
+    min-height: 0;
+    padding: 0;
+    overflow-y: auto;
+    box-sizing: border-box;
+  }
+
+  .person-list {
+    display: flex;
+    flex-direction: column;
+    gap: 6px;
+    padding: 8px 12px 10px;
+    box-sizing: border-box;
+  }
+
+  .record-content,
+  .location-list-wrap {
+    gap: 8px;
+    padding: 10px 14px 12px;
+  }
+
+  .person-item,
+  .location-item {
+    padding: 8px 10px;
+    border: 1px solid #e7ebf3;
+    border-radius: 8px;
+    background: #fff;
+    transition:
+      border-color 0.2s ease,
+      background-color 0.2s ease;
+  }
+
+  .left-panel > .location-list,
+  .right-panel > .location-list,
+  .right-content .record-list {
+    gap: 8px;
+    padding: 10px 14px 12px;
+    box-sizing: border-box;
+  }
+
+  .left-panel > .location-list,
+  .right-panel > .location-list {
+    display: flex;
+    flex: 1 1 auto;
+    flex-direction: column;
+    gap: 6px;
+    min-height: 0;
+    overflow-y: auto;
+  }
+
+  .location-list-wrap .location-list {
+    display: flex;
+    flex-direction: column;
+    gap: 6px;
+  }
+
+  /* 列表项 / 组合卡选中态:与表格选中一致 */
+  .person-item.active,
+  .location-item.active,
+  .peer-card.active,
+  .target-tag.active {
+    border-color: #2a50ec !important;
+    background: #f6f8ff !important;
+    color: #1e3ed0;
+    box-shadow: none;
+  }
+
+  .target-tag.active {
+    background: #e8efff !important;
+    font-weight: 600;
+  }
+
+  /* 列表选中项文字统一使用项目主色,避免混入旧的 #1677ff */
+  .person-item.active .person-name,
+  .person-item.active .person-count,
+  .person-item.active .person-badge,
+  .location-item.active .location-name,
+  .location-item.active .location-lac,
+  .location-item.active .location-cid,
+  .location-item.active .location-count,
+  .peer-card.active .member,
+  .peer-card.active .peer-meta {
+    color: #2a50ec;
+  }
+
+  /* 同行分析表格选中行使用独立类名 */
+  .table-wrapper .ant-table-tbody > tr.record-row-selected > td {
+    background: #dbe7ff !important;
+    color: #1e3ed0;
+    font-weight: 600;
+    box-shadow: none;
+    border-radius: 0;
+  }
+
+  .table-wrapper .ant-table-tbody > tr.record-row-selected:hover > td {
+    background: #d1e0ff !important;
+  }
+
+  .panel-empty {
+    display: flex;
+    flex: 1 1 auto;
+    align-items: center;
+    justify-content: center;
+    min-height: 0;
+    color: #98a2b3;
+    font-size: 12px;
+  }
+
+  /* 组合卡 / 地点条目:正文不使用加粗,避免列表看起来过重 */
+  .peer-card .member,
+  .peer-card .peer-meta,
+  .location-item .location-name,
+  .location-item .location-count {
+    font-weight: 400;
+  }
+
+  .peer-card.active .member,
+  .peer-card.active .peer-meta,
+  .location-item.active .location-name,
+  .location-item.active .location-count {
+    font-weight: 400;
+  }
+
+  /* 窄左栏头部:标题与说明同一行,长说明省略,避免头部被撑高 */
+  .left-panel > .panel-header > div:first-child,
+  .left-sidebar > .panel-header > div:first-child {
+    flex-wrap: nowrap;
+    min-width: 0;
+  }
+
+  .left-panel > .panel-header .panel-title,
+  .left-sidebar > .panel-header .panel-title {
+    flex: 0 0 auto;
+    white-space: nowrap;
+  }
+
+  .left-panel > .panel-header .panel-desc,
+  .left-sidebar > .panel-header .panel-desc {
+    flex: 1 1 auto;
+    min-width: 0;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+
+  .left-panel > .panel-header,
+  .left-sidebar > .panel-header {
+    min-height: 42px;
+  }
+
+  /* 空态时隐藏左栏说明,只保留标题;有数据时说明(多为计数)正常显示 */
+  .left-panel:has(.panel-empty) > .panel-header .panel-desc,
+  .left-sidebar:has(.panel-empty) > .panel-header .panel-desc {
+    display: none;
+  }
+
+  /* ---------------- 响应式 ---------------- */
+  @media screen and (max-width: 1280px) {
+    .panel-desc,
+    .panel-subtitle {
+      display: none;
+    }
+  }
+
+  @media screen and (max-width: 992px) {
+    .content-panel {
+      flex-direction: column;
+    }
+
+    .content-panel > .panel-left,
+    .content-panel > .left-panel {
+      flex: 0 0 auto;
+      min-height: 300px;
+    }
+
+    .filter-item {
+      flex: 1 1 100%;
+    }
+
+    .filter-input,
+    .filter-date,
+    .qj-filter-grid .ant-select {
+      width: 100%;
+    }
+
+    .qj-filter-grid .filter-mini,
+    .qj-filter-grid .filter-op {
+      flex: 1 1 0;
+      width: auto;
+    }
+  }
+
+  /* ---------------- 暗色主题 ---------------- */
+  html[data-theme='dark'] & {
+    background: #0f172a;
+
+    > .filter-bar,
+    .content-panel > .panel-left,
+    .content-panel > .panel-right,
+    .content-panel > .left-panel,
+    .content-panel > .right-panel,
+    .content-panel > .summary-panel,
+    .content-panel > .trend-panel,
+    .content-panel > .table-card,
+    > .table-card {
+      border-color: #1f2937;
+      background: #111827;
+      box-shadow: none;
+    }
+
+    .qj-card-title,
+    .panel-title {
+      color: #e5e7eb;
+    }
+
+    .filter-label,
+    .panel-desc,
+    .panel-subtitle {
+      color: #94a3b8;
+    }
+
+    .qj-filter-head,
+    .panel-header,
+    .pagination-bar {
+      border-color: #1f2937;
+    }
+
+    .qj-filter-grid {
+      .ant-input,
+      .ant-input-affix-wrapper,
+      .ant-picker,
+      .ant-select .ant-select-selector {
+        border-color: #1f2937;
+        background: #0f172a;
+        color: #e5e7eb;
+      }
+    }
+
+    .table-wrapper .ant-table-thead > tr > th {
+      border-color: #1f2937;
+      background: #0f172a;
+      color: #94a3b8;
+    }
+
+    .table-wrapper .ant-table-tbody > tr > td {
+      border-color: #1f2937;
+      color: #e5e7eb;
+    }
+
+    .table-wrapper .ant-table-tbody > tr.summary-row-selected > td,
+    .table-wrapper .ant-table-tbody > tr.active-row > td {
+      background: #1e3a8a !important;
+      color: #dbeafe;
+    }
+
+    .table-wrapper .ant-table-tbody > tr.summary-row-selected > td:first-child,
+    .table-wrapper .ant-table-tbody > tr.active-row > td:first-child {
+      color: #93b4ff;
+    }
+  }
+}

+ 43 - 32
ai-electron/frontend/src/trans/views/tradeFrequency/index.vue

@@ -6,39 +6,44 @@
     </div>
 
     <div class="filter-bar">
-      <div class="filter-item">
-        <span class="filter-label">选择对象:</span>
-        <Select
-          v-model:value="filters.target"
-          mode="multiple"
-          allow-clear
-          max-tag-count="responsive"
-          class="filter-input"
-          :options="targetOptions"
-          placeholder="全部"
-        />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">交易次数:</span>
-        <Select v-model:value="filters.countOp" class="filter-op" :options="countOpOptions" />
-        <Input v-model:value="filters.countValue" class="filter-mini" :status="countValueStatus" />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">交易日期:</span>
-        <RangePicker v-model:value="filters.dateRange" class="filter-date" />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">对方姓名:</span>
-        <Input v-model:value="filters.otherName" class="filter-input" placeholder="请输入对方姓名" />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">对方卡号:</span>
-        <Input v-model:value="filters.otherCardNo" class="filter-input" placeholder="请输入对方卡号" />
+      <div class="qj-filter-head">
+        <div class="qj-card-title">查询条件</div>
+        <div class="filter-actions">
+          <Button type="primary" :loading="queryLoading" @click="handleSearch">查询</Button>
+          <Button @click="handleReset">重置</Button>
+          <!-- <Button @click="handlePreviewModal">预览弹窗</Button> -->
+        </div>
       </div>
-      <div class="filter-actions">
-        <Button type="primary" :loading="queryLoading" @click="handleSearch">查询</Button>
-        <Button @click="handleReset">重置</Button>
-        <!-- <Button @click="handlePreviewModal">预览弹窗</Button> -->
+      <div class="qj-filter-grid">
+        <div class="filter-item">
+          <span class="filter-label">选择对象:</span>
+          <Select
+            v-model:value="filters.target"
+            mode="multiple"
+            allow-clear
+            max-tag-count="responsive"
+            class="filter-input"
+            :options="targetOptions"
+            placeholder="全部"
+          />
+        </div>
+        <div class="filter-item">
+          <span class="filter-label">交易次数:</span>
+          <Select v-model:value="filters.countOp" class="filter-op" :options="countOpOptions" />
+          <Input v-model:value="filters.countValue" class="filter-mini" :status="countValueStatus" />
+        </div>
+        <div class="filter-item">
+          <span class="filter-label">交易日期:</span>
+          <RangePicker v-model:value="filters.dateRange" class="filter-date" />
+        </div>
+        <div class="filter-item">
+          <span class="filter-label">对方姓名:</span>
+          <Input v-model:value="filters.otherName" class="filter-input" placeholder="请输入对方姓名" />
+        </div>
+        <div class="filter-item">
+          <span class="filter-label">对方卡号:</span>
+          <Input v-model:value="filters.otherCardNo" class="filter-input" placeholder="请输入对方卡号" />
+        </div>
       </div>
     </div>
 
@@ -477,6 +482,7 @@
 </script>
 <style lang="less">
   @import '../styles/trans-analysis-table-ui.less';
+  @import (reference) '../styles/analysis-card-ui.less';
 
   @prefix-cls: ~'jeesite-trade-frequency';
 
@@ -543,4 +549,9 @@
       }
     }
   }
+
+  /* 统一卡片视觉:对齐「持续联系 / 夜间联系 / 敏感日期联系」 */
+  .@{prefix-cls}.trade-frequency-page {
+    .analysis-card-ui();
+  }
 </style>

+ 38 - 27
ai-electron/frontend/src/trans/views/transSensitive/index.vue

@@ -6,32 +6,37 @@
     </div>
 
     <div class="filter-bar">
-      <div class="filter-item">
-        <span class="filter-label">选择对象:</span>
-        <Select
-          v-model:value="filters.target"
-          mode="multiple"
-          allow-clear
-          max-tag-count="responsive"
-          class="filter-input"
-          :options="targetOptions"
-          placeholder="全部"
-        />
-      </div>
-      <div class="filter-item">
-        <span class="filter-label">交易日期:</span>
-        <RangePicker
-          v-model:value="filters.dateRange"
-          class="filter-input filter-date"
-          :format="DATE_FORMAT"
-          :value-format="DATE_FORMAT"
-          :placeholder="['开始日期', '结束日期']"
-        />
+      <div class="qj-filter-head">
+        <div class="qj-card-title">查询条件</div>
+        <div class="filter-actions">
+          <Button type="primary" :loading="queryLoading" @click="handleSearch">查询</Button>
+          <Button @click="handleReset">重置</Button>
+          <Button @click="handleOpenSpecialDateModal">特殊日期配置</Button>
+        </div>
       </div>
-      <div class="filter-actions">
-        <Button type="primary" :loading="queryLoading" @click="handleSearch">查询</Button>
-        <Button @click="handleReset">重置</Button>
-        <Button @click="handleOpenSpecialDateModal">特殊日期配置</Button>
+      <div class="qj-filter-grid">
+        <div class="filter-item">
+          <span class="filter-label">选择对象:</span>
+          <Select
+            v-model:value="filters.target"
+            mode="multiple"
+            allow-clear
+            max-tag-count="responsive"
+            class="filter-input"
+            :options="targetOptions"
+            placeholder="全部"
+          />
+        </div>
+        <div class="filter-item">
+          <span class="filter-label">交易日期:</span>
+          <RangePicker
+            v-model:value="filters.dateRange"
+            class="filter-input filter-date"
+            :format="DATE_FORMAT"
+            :value-format="DATE_FORMAT"
+            :placeholder="['开始日期', '结束日期']"
+          />
+        </div>
       </div>
     </div>
 
@@ -678,6 +683,7 @@
 </script>
 <style lang="less">
   @import '../styles/trans-analysis-table-ui.less';
+  @import (reference) '../styles/analysis-card-ui.less';
 
   @prefix-cls: ~'jeesite-trans-sensitive';
 
@@ -823,7 +829,7 @@
       .ant-table-tbody
       > tr.jeesite-basic-table-row__selected
       > td.ant-table-cell-row-hover {
-      background: #e8f1ff !important;
+      background: #dbe7ff !important;
     }
 
     .table-wrapper .jeesite-basic-table .ant-table-tbody > tr.summary-row-selected > td.ant-table-column-sort,
@@ -862,7 +868,7 @@
       .ant-table-tbody
       > tr.jeesite-basic-table-row__selected
       > td.ant-table-cell-fix-right.ant-table-column-sort {
-      background: #e8f1ff !important;
+      background: #dbe7ff !important;
     }
 
     .table-wrapper .jeesite-basic-table .ant-table-tbody > tr:hover > td,
@@ -927,4 +933,9 @@
       }
     }
   }
+
+  /* 统一卡片视觉:对齐「持续联系 / 夜间联系 / 敏感日期联系」 */
+  .@{prefix-cls}.trans-sensitive-page {
+    .analysis-card-ui();
+  }
 </style>

Деякі файли не було показано, через те що забагато файлів було змінено