feat(lead-detail): 在用户详情页添加表单数据展示区域

- 在 API 类型定义中新增 `UserFormItem` 和 `UserFormAnswer` 类型
- 添加 `getUserForms` API 函数用于获取指定用户的表单数据
- 在用户详情面板中新增表单数据展示区域,支持加载状态、错误处理和空状态
- 添加 `getQuestionLabel` 和 `getAnswerText` 工具函数来格式化表单答案的显示
- 当选中不同用户时,自动获取并展示对应的表单数据
- 为现有 `LeadItem` 类型的部分字段添加中文注释以提升代码可读性
This commit is contained in:
2026-02-06 18:34:08 +08:00
parent ca8869db66
commit 553cc05863
2 changed files with 117 additions and 10 deletions

View File

@@ -12,19 +12,19 @@ export type LeadItem = {
hook_user_id: string
id: number
avatar_url: string
sex: number
follow_up_user_id: string
wechat_add_time: string
sex: number // 性别 0: 未知, 1: 男, 2: 女
follow_up_user_id: string // 跟进人ID
wechat_add_time: string // 微信添加时间
auto_status: number
is_synced: number
is_synced: number // 是否同步到服务器 0: 未同步, 1: 已同步
created_at: string
nickname: string
phone: string
follow_up_name: string
source_type: number
status: number
wechat_status: number
extended_info: Record<string, unknown>
source_type: number //内外联系人 1: 外部联系人, 0: 内部联系人
status: number // 状态 0: 正常, 1: 禁用
wechat_status: number // 微信状态 0: 正常, 1: 已删除
extended_info: Record<string, unknown> //
updated_at: string
last_active_at: string | null
}
@@ -36,6 +36,29 @@ export type LeadListResponse = {
items: LeadItem[]
}
export type UserFormAnswer = {
question_label?: string
answer?: unknown
[key: string]: unknown
}
export type UserFormItem = {
form_id: string
form_title: string
answers: UserFormAnswer[]
created_at: string
updated_at: string
[key: string]: unknown
}
export const getUserForms = (userId: string) => {
return http.get<UserFormItem[], UserFormItem[]>('/api/v1/admin/user/form', {
params: {
user_id: userId,
},
})
}
export const getAdminUserStatus = (params: AdminUserStatusParams = {}) => {
return http.get<LeadListResponse, LeadListResponse>('/api/v1/admin/user/status', {
params: {

View File

@@ -134,6 +134,42 @@
<span class="text-slate-300 font-mono">{{ formatTime(selectedLead.updated_at) }}</span>
</div>
</div>
<!-- 表单数据区域 -->
<div class="pt-4 border-t border-slate-700/50">
<div class="text-xs font-bold text-slate-300 mb-3 flex items-center justify-between">
<span>表单数据</span>
<span v-if="userForms.length" class="badge badge-xs badge-neutral">{{ userForms.length }}</span>
</div>
<div v-if="formsLoading" class="flex justify-center py-4">
<span class="loading loading-spinner loading-xs text-slate-500"></span>
</div>
<div v-else-if="formsError" class="text-xs text-red-400 bg-red-900/10 p-2 rounded">
{{ formsError }}
</div>
<div v-else-if="userForms.length === 0" class="text-center text-slate-600 text-xs py-4">
暂无表单记录
</div>
<div v-else class="space-y-3 max-h-[400px] overflow-y-auto pr-1 custom-scrollbar">
<div v-for="form in userForms" :key="form.form_id" class="bg-slate-950/40 p-3 rounded-xl border border-slate-800/60 hover:border-slate-700 transition-colors">
<div class="flex items-center justify-between gap-2">
<div class="font-medium text-slate-200 text-xs">{{ form.form_title || '未命名表单' }}</div>
<div class="text-[10px] text-slate-500 font-mono">{{ formatTime(form.created_at).split(' ')[0] }}</div>
</div>
<div v-if="form.answers && form.answers.length" class="mt-3 grid grid-cols-1 gap-2">
<div v-for="(answer, index) in form.answers" :key="`${form.form_id}-${index}`" class="flex items-start gap-2 rounded-lg bg-slate-900/60 px-2.5 py-2">
<div class="text-[10px] text-slate-500 w-24 shrink-0">{{ getQuestionLabel(answer) }}</div>
<div class="text-[10px] text-slate-200 break-words flex-1 text-right">{{ getAnswerText(answer) }}</div>
</div>
</div>
<div v-else class="text-[10px] text-slate-600 mt-2">暂无答案</div>
</div>
</div>
</div>
</div>
</div>
</div>
@@ -156,8 +192,8 @@ import SidebarNav from './components/SidebarNav.vue';
import DashboardView from './components/DashboardView.vue';
import StrategyView from './components/StrategyView.vue';
import MonitorView from './components/MonitorView.vue';
import { getAdminUserStatus } from '@/api';
import type { LeadListResponse, LeadItem } from '@/api';
import { getAdminUserStatus, getUserForms } from '@/api';
import type { LeadListResponse, LeadItem, UserFormAnswer, UserFormItem } from '@/api';
const router = useRouter();
const route = useRoute();
@@ -211,6 +247,20 @@ const formatTime = (value: string | null) => {
return Number.isNaN(date.getTime()) ? value : date.toLocaleString();
};
const getQuestionLabel = (answer: UserFormAnswer) => {
const label = answer.question_label;
if (typeof label === 'string' && label.trim()) return label;
return '问题';
};
const getAnswerText = (answer: UserFormAnswer) => {
const value = answer.answer;
if (Array.isArray(value)) return value.join('、');
if (value === null || value === undefined || value === '') return '-';
if (typeof value === 'object') return JSON.stringify(value);
return String(value);
};
const fetchLeads = async () => {
if (leadsLoading.value) return;
leadsLoading.value = true;
@@ -227,6 +277,40 @@ const fetchLeads = async () => {
}
};
const userForms = ref<UserFormItem[]>([]);
const formsLoading = ref(false);
const formsError = ref('');
const fetchUserForms = async (userId: string) => {
formsLoading.value = true;
formsError.value = '';
userForms.value = [];
try {
const res = await getUserForms(userId);
console.log('getUserForms response:', res);
if (Array.isArray(res.data)) {
userForms.value = res.data;
} else {
userForms.value = [];
formsError.value = '表单数据格式错误';
}
console.log('userForms:', userForms.value);
} catch (error) {
formsError.value = error instanceof Error ? error.message : '获取表单失败';
console.error('Fetch forms error:', error);
} finally {
formsLoading.value = false;
}
};
watch(selectedLead, (newLead) => {
if (newLead) {
fetchUserForms(newLead.hook_user_id);
} else {
userForms.value = [];
}
});
// 1. 销售阶段 (SOP)
const salesStages = [
{ id: 'connect', name: '1. 浅建联 (加微/破冰)', desc: '初步接触,建立信任,发送欢迎语' },