Ver código fonte

feat: 关系分析首页重构(选择模式+分析流)

Ultraworked with Sisyphus
liaoxg 3 meses atrás
pai
commit
111e445a73
1 arquivos alterados com 165 adições e 39 exclusões
  1. 165 39
      client/pages/relations/index.vue

+ 165 - 39
client/pages/relations/index.vue

@@ -38,14 +38,39 @@
         </view>
       </view>
 
-      <!-- ========== New Analysis Entry ========== -->
-      <view class="new-analysis-card" @click="goNewAnalysis">
-        <text class="new-analysis-icon">🔮</text>
-        <view class="new-analysis-info">
-          <text class="new-analysis-title">新建关系分析</text>
-          <text class="new-analysis-desc">选择成员并提问,AI 深度解读关系能量</text>
-        </view>
-        <text class="new-analysis-arrow">›</text>
+      <!-- ========== Inline Analysis Action ========== -->
+      <view class="analysis-action-card">
+        <template v-if="selectedIds.length === 0">
+          <text class="analysis-action-icon">🔮</text>
+          <view class="analysis-action-info">
+            <text class="analysis-action-title">关系能量分析</text>
+            <text class="analysis-action-desc">点击下方关系人卡片即可选择</text>
+          </view>
+        </template>
+        <template v-else>
+          <view class="analysis-action-selected">
+            <text class="analysis-action-title">已选 {{ selectedIds.length }} 人</text>
+            <view class="analysis-action-input-wrap">
+              <textarea
+                v-model="question"
+                class="analysis-action-input"
+                placeholder="输入你想咨询的问题..."
+                maxlength="500"
+                :auto-height="true"
+              />
+            </view>
+            <text class="analysis-action-count">{{ question.length }}/500</text>
+          </view>
+          <view
+            class="analysis-action-submit"
+            :class="{ disabled: !question.trim() || submitting }"
+            @click="handleStartAnalysis"
+          >
+            <text class="analysis-action-submit-text">
+              {{ submitting ? '分析中...' : '开始分析' }}
+            </text>
+          </view>
+        </template>
       </view>
 
       <!-- ========== Analysis Records Section ========== -->
@@ -99,20 +124,24 @@
           v-for="item in relations"
           :key="item.id"
           class="relation-card"
+          :class="{ selected: selectedIds.includes(item.id) }"
+          @click="toggleSelect(item.id)"
         >
           <view class="card-header">
             <view class="type-tag">
               <text class="type-tag-text">{{ relationTypeLabel(item.relationType) }}</text>
             </view>
+            <view v-if="selectedIds.includes(item.id)" class="card-checked">
+              <text class="card-checked-icon">✓</text>
+            </view>
           </view>
           <view class="card-body">
             <text class="card-name">{{ item.name || '未知' }}</text>
             <text class="card-birth">{{ item.birthDate || '' }}</text>
           </view>
           <view class="card-footer">
-            <view class="action-btn action-compare" @click="goCompare(item.id)">对比分析</view>
-            <view class="action-btn action-edit" @click="goEdit(item.id)">编辑</view>
-            <view class="action-btn action-delete" @click="handleDelete(item.id)">删除</view>
+            <view class="action-btn action-edit" @click.stop="goEdit(item.id)">编辑</view>
+            <view class="action-btn action-delete" @click.stop="handleDelete(item.id)">删除</view>
           </view>
         </view>
       </view>
@@ -131,6 +160,9 @@ const relations = ref([])
 const loading = ref(false)
 const recentRecords = ref([])
 const recordsLoading = ref(false)
+const selectedIds = ref([])
+const question = ref('')
+const submitting = ref(false)
 
 const isSuperMember = computed(() => {
   if (!userStore.isVip) return false
@@ -192,12 +224,45 @@ async function fetchRecentRecords() {
   }
 }
 
+function toggleSelect(id) {
+  const idx = selectedIds.value.indexOf(id)
+  if (idx > -1) {
+    selectedIds.value.splice(idx, 1)
+  } else {
+    selectedIds.value.push(id)
+  }
+}
+
+async function handleStartAnalysis() {
+  if (!question.value.trim() || submitting.value) return
+  submitting.value = true
+  try {
+    const res = await requestInstance.post('/relation/analysis/start', {
+      relationIds: selectedIds.value,
+      question: question.value.trim()
+    })
+    if (res && res.id) {
+      selectedIds.value = []
+      question.value = ''
+      uni.navigateTo({
+        url: '/pages/relations/analysis-chat?recordId=' + res.id
+      })
+    }
+  } catch (e) {
+    uni.showToast({ title: '启动分析失败', icon: 'none' })
+  } finally {
+    submitting.value = false
+  }
+}
+
 function goAddRelation() {
   uni.navigateTo({ url: '/pages/relations/add-relation' })
 }
 
 function goNewAnalysis() {
-  uni.navigateTo({ url: '/pages/relations/analysis-select' })
+  // Inline selection on index page — just a hint
+  uni.showToast({ title: '请点击下方关系人进行选择', icon: 'none' }
+  )
 }
 
 function goChat(recordId) {
@@ -208,10 +273,6 @@ function goAllRecords() {
   uni.navigateTo({ url: '/pages/relations/analysis-records' })
 }
 
-function goCompare(id) {
-  uni.navigateTo({ url: '/pages/relations/chart-compare?id=' + id })
-}
-
 function goEdit(id) {
   uni.navigateTo({ url: '/pages/relations/add-relation?id=' + id })
 }
@@ -252,7 +313,7 @@ function formatTime(time) {
 </script>
 
 <style scoped lang="scss">
-@import "@/styles/theme.scss";
+$brand-color: #1E3A5F;
 
 .page-relations {
   min-height: 100vh;
@@ -318,44 +379,86 @@ function formatTime(time) {
   opacity: 0.7;
 }
 
-/* New analysis entry card */
-.new-analysis-card {
-  display: flex;
-  align-items: center;
-  background: linear-gradient(135deg, var(--color-brand), darken(var(--color-brand), 4%));
+/* ========== Inline Analysis Action ========== */
+.analysis-action-card {
+  background: linear-gradient(135deg, $brand-color, darken($brand-color, 4%));
   border-radius: 16px;
   padding: 16px;
   margin-bottom: 4px;
-  gap: 12px;
 }
-.new-analysis-card:active {
+.analysis-action-card:active {
   transform: scale(0.98);
 }
-.new-analysis-icon {
+.analysis-action-icon {
   font-size: 28px;
   flex-shrink: 0;
 }
-.new-analysis-info {
+.analysis-action-info {
   flex: 1;
   min-width: 0;
+  margin-top: 4px;
 }
-.new-analysis-title {
+.analysis-action-title {
   display: block;
   font-size: 15px;
   font-weight: 700;
   color: var(--color-bg-card);
   margin-bottom: 3px;
 }
-.new-analysis-desc {
+.analysis-action-desc {
   display: block;
   font-size: 12px;
   color: rgba(255, 255, 255, 0.7);
 }
-.new-analysis-arrow {
-  font-size: 22px;
-  color: rgba(255, 255, 255, 0.6);
-  font-weight: 300;
-  flex-shrink: 0;
+.analysis-action-selected {
+  margin-bottom: 12px;
+}
+.analysis-action-input-wrap {
+  background: rgba(255, 255, 255, 0.1);
+  border-radius: 10px;
+  padding: 10px 12px;
+  margin-top: 10px;
+}
+.analysis-action-input {
+  width: 100%;
+  min-height: 60px;
+  font-size: 14px;
+  color: var(--color-bg-card);
+  line-height: 1.6;
+  background: transparent;
+}
+.analysis-action-input::placeholder {
+  color: rgba(255, 255, 255, 0.5);
+  font-size: 14px;
+}
+.analysis-action-count {
+  display: block;
+  text-align: right;
+  font-size: 11px;
+  color: rgba(255, 255, 255, 0.5);
+  margin-top: 4px;
+  padding-right: 2px;
+}
+.analysis-action-submit {
+  height: 44px;
+  background: var(--color-gold);
+  border-radius: 12px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+.analysis-action-submit:active {
+  transform: scale(0.97);
+  opacity: 0.9;
+}
+.analysis-action-submit.disabled {
+  opacity: 0.5;
+  pointer-events: none;
+}
+.analysis-action-submit-text {
+  color: var(--color-brand);
+  font-size: 16px;
+  font-weight: 700;
 }
 
 /* Records preview */
@@ -486,9 +589,21 @@ function formatTime(time) {
   padding: 16px;
   margin-bottom: 12px;
   box-shadow: var(--shadow-card);
+  border: 2px solid transparent;
+  transition: border-color 0.2s;
+}
+.relation-card:active {
+  transform: scale(0.98);
+}
+.relation-card.selected {
+  border-color: var(--color-gold);
+  background: linear-gradient(135deg, var(--color-bg-card), rgba(201, 168, 76, 0.06));
 }
 .card-header {
   margin-bottom: 10px;
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
 }
 .type-tag {
   display: inline-flex;
@@ -502,6 +617,21 @@ function formatTime(time) {
   font-weight: 500;
   color: var(--color-brand);
 }
+.card-checked {
+  width: 24px;
+  height: 24px;
+  border-radius: 50%;
+  background: var(--color-gold);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex-shrink: 0;
+}
+.card-checked-icon {
+  font-size: 13px;
+  color: var(--color-bg-card);
+  font-weight: 700;
+}
 .card-body {
   margin-bottom: 12px;
 }
@@ -535,10 +665,6 @@ function formatTime(time) {
   transform: scale(0.97);
   opacity: 0.9;
 }
-.action-compare {
-  background: var(--color-brand);
-  color: var(--color-bg-card);
-}
 .action-edit {
   background: var(--color-bg-card);
   border: 1px solid var(--color-border);
@@ -552,7 +678,7 @@ function formatTime(time) {
 
 /* ========== Upsell Banner ========== */
 .upsell-banner {
-  background: linear-gradient(135deg, var(--color-brand), darken(var(--color-brand), 4%));
+  background: linear-gradient(135deg, $brand-color, darken($brand-color, 4%));
   border-radius: 14px;
   padding: 14px 16px;
   margin-bottom: 16px;
@@ -628,7 +754,7 @@ function formatTime(time) {
 .btn-upgrade {
   width: 80%;
   height: 50px;
-  background: linear-gradient(135deg, var(--color-brand), darken(var(--color-brand), 4%));
+  background: linear-gradient(135deg, $brand-color, darken($brand-color, 4%));
   border-radius: 14px;
   display: flex;
   align-items: center;