项目问卷是家装效果图服务的初次合作需求调研工具,通过精简的选择式问卷快速了解客户需求、服务偏好和协作习惯,帮助团队更精准地提供服务。
| 字段名 | 类型 | 必填 | 说明 | 示例值 |
|---|---|---|---|---|
| objectId | String | 是 | 主键ID | "survey001" |
| contact | Pointer | 是 | 提交联系人 | → ContactInfo |
| project | Pointer | 是 | 关联项目 | → Project |
| profile | Pointer | 否 | 提交员工(内部员工填写时使用) | → Profile |
| company | Pointer | 是 | 所属帐套 | → Company |
| type | String | 是 | 问卷类型 | "survey-project" |
| data | Object | 是 | 问卷结果 | {q1: "答案1", ...} |
| isCompleted | Boolean | 否 | 是否完整填写 | true |
| completedAt | Date | 否 | 完成时间 | 2024-12-01T10:00:00.000Z |
| isDeleted | Boolean | 否 | 软删除标记 | false |
| createdAt | Date | 自动 | 创建时间 | 2024-12-01T09:00:00.000Z |
| updatedAt | Date | 自动 | 更新时间 | 2024-12-01T10:00:00.000Z |
type 枚举值:
survey-project: 项目问卷survey-contact: 联系人问卷(暂未实现)survey-profile: 员工问卷(暂未实现)data 字段结构示例:
{
"q1_service_type": "效果图+技术配合",
"q2_space_count": "3",
"q2_space_types": "客厅/主卧/儿童房",
"q3_value_focus": ["细节写实度", "视觉吸引力"],
"q4_tech_support": "需要",
"q4_tech_focus": ["材质搭配", "灯光布局"],
"q5_cooperation_mode": "前期多沟通",
"q6_attention_points": ["软装色调易偏差"],
"q7_special_requirements": "业主喜欢暖色调,注意避免冷色",
"q8_has_reference": "有",
"contact_name": "李总",
"contact_phone": "13800138000"
}
// 路由: /wxwork/:cid/survey/project/:projectId
{
path: 'wxwork/:cid',
children: [
{
path: 'survey/project/:projectId',
loadComponent: () => import('../modules/project/pages/project-survey/project-survey.component'),
title: '项目需求调查'
}
]
}
组件包含三种状态,通过 currentState 控制:
type SurveyState = 'welcome' | 'questionnaire' | 'result';
currentState: SurveyState = 'welcome';
状态转换流程:
[欢迎页] --点击开始--> [答题页] --提交完成--> [结果页]
↑ ↓
└──────────────── 查看结果 ──────────────────┘
┌─────────────────────────────────┐
│ 问卷欢迎页 │
├─────────────────────────────────┤
│ [用户头像] │
│ 您好,李总 │
│ │
│ 《家装效果图服务初次合作需求调查表》 │
│ │
│ 尊敬的伙伴: │
│ 为让本次效果图服务更贴合您的工作节 │
│ 奏与核心需求,我们准备了简短选择式 │
│ 问卷,您的偏好将直接帮我们校准服务 │
│ 方向,感谢支持! │
│ │
│ • 预计用时: 3-5分钟 │
│ • 题目数量: 8题 │
│ • 题型: 选择题为主 │
│ │
│ [开始填写] │
└─────────────────────────────────┘
用户识别:
WxworkAuth.currentContact() 获取当前外部联系人contact.id 用于后续保存数据检查:
project == projectId AND contact == contactIdisCompleted == true,直接跳转到结果页开始按钮:
startSurvey()currentState = 'questionnaire'currentQuestionIndex = 0┌─────────────────────────────────┐
│ 进度: 1/8 ●●○○○○○○ │
├─────────────────────────────────┤
│ 一、基础需求 │
│ │
│ 1. 本次您需要的核心服务是? │
│ │
│ ○ 纯效果图渲染 │
│ ● 效果图+技术配合 │
│ ○ 其他补充: [____________] │
│ │
│ │
│ [← 上一题] [下一题 →] │
└─────────────────────────────────┘
interface Question {
id: string; // 题目ID,如 "q1", "q2"
section: string; // 章节,如 "基础需求", "核心侧重"
title: string; // 题目文本
type: 'single' | 'multiple' | 'text' | 'number'; // 题型
options?: string[]; // 选项列表
hasOther?: boolean; // 是否有"其他"选项
required?: boolean; // 是否必填
skipCondition?: (contact: any) => boolean; // 跳过条件
}
const questions: Question[] = [
// 一、基础需求
{
id: 'q1',
section: '基础需求',
title: '本次您需要的核心服务是?',
type: 'single',
options: ['纯效果图渲染', '效果图+技术配合'],
hasOther: true,
required: true
},
{
id: 'q2',
section: '基础需求',
title: '需覆盖的关键空间数量及类型?',
type: 'text',
placeholder: '例: 3个,客厅/主卧/儿童房',
required: true
},
// 二、核心侧重
{
id: 'q3',
section: '核心侧重',
title: '您更希望本次效果图突出哪些价值?(可多选2-3项)',
type: 'multiple',
options: ['细节写实度', '视觉吸引力', '风格适配性'],
hasOther: true,
required: true
},
{
id: 'q4',
section: '核心侧重',
title: '关于方案建议,是否需要我们技术团队配合?',
type: 'single',
options: ['需要', '暂不需要'],
required: true
},
// 三、协作节奏
{
id: 'q5',
section: '协作节奏',
title: '您偏好的服务协作方式是?',
type: 'single',
options: ['前期多沟通', '先出初版再修改', '灵活协调'],
required: true
},
// 四、特殊提醒
{
id: 'q6',
section: '特殊提醒',
title: '过往合作中,是否有需要特别注意的点?(可多选)',
type: 'multiple',
options: ['软装色调易偏差', '建模细节需盯控'],
hasOther: true
},
{
id: 'q7',
section: '特殊提醒',
title: '本次项目是否有特殊要求?(如业主禁忌、重点展示点)',
type: 'text',
placeholder: '请输入特殊要求...'
},
{
id: 'q8',
section: '特殊提醒',
title: '是否有参考素材?(如风格图、实景图)',
type: 'single',
options: ['有(后续群内发送)', '无(需求已清晰)']
},
// 联系信息(自动跳过)
{
id: 'contact_name',
section: '联系信息',
title: '对接人姓名',
type: 'text',
required: true,
skipCondition: (contact) => !!contact?.get('realname')
},
{
id: 'contact_phone',
section: '联系信息',
title: '对接人电话',
type: 'text',
required: true,
skipCondition: (contact) => !!contact?.get('mobile')
}
];
单选题:
answers[questionId]多选题:
文本题/数字题:
题目跳过:
skipCondition 返回 true,自动跳过该题进度指示:
currentQuestionIndex / totalQuestions导航按钮:
自动保存:
保存方式:
surveyLog.set('data', {
...surveyLog.get('data'),
[questionId]: answer
});
await surveyLog.save();
完成标记:
isCompleted = truecompletedAt = new Date()┌─────────────────────────────────┐
│ ✓ 问卷提交成功 │
├─────────────────────────────────┤
│ 感谢您的反馈! │
│ 我们将根据您的选择制定服务方案 │
│ │
│ 【您的答卷】 │
│ ━━━━━━━━━━━━━━━━━━━━━━━ │
│ 核心服务: 效果图+技术配合 │
│ 空间数量: 3个(客厅/主卧/儿童房) │
│ 价值侧重: 细节写实度、视觉吸引力 │
│ 技术配合: 需要(材质搭配、灯光布局) │
│ 协作方式: 前期多沟通 │
│ 注意事项: 软装色调易偏差 │
│ 特殊要求: 业主喜欢暖色调 │
│ 参考素材: 有(后续群内发送) │
│ ━━━━━━━━━━━━━━━━━━━━━━━ │
│ 对接人: 李总 │
│ 电话: 138****8000 │
│ │
│ [返回项目] │
└─────────────────────────────────┘
结果展示:
权限控制:
返回按钮:
在 project-detail.component.html 的客户联系人卡片区域添加问卷入口:
<!-- 客户信息卡片 -->
<div class="contact-card">
<div class="contact-info" (click)="openContactPanel()">
<img [src]="contact?.get('data')?.avatar || 'assets/default-avatar.png'" />
<div>
<h3>{{ contact?.get('realname') || contact?.get('name') }}</h3>
<p>{{ canViewCustomerPhone ? contact?.get('mobile') : '***' }}</p>
</div>
</div>
<!-- 问卷状态 -->
<div class="survey-status" (click)="handleSurveyClick($event)">
<ion-icon [name]="surveyStatus.icon"></ion-icon>
<span>{{ surveyStatus.text }}</span>
</div>
</div>
在 project-detail.component.ts 中添加:
// 问卷状态
surveyStatus: {
filled: boolean;
text: string;
icon: string;
surveyLog?: FmodeObject;
} = {
filled: false,
text: '发送问卷',
icon: 'document-text-outline'
};
async loadSurveyStatus() {
if (!this.project?.id || !this.contact?.id) return;
try {
const query = new Parse.Query('SurveyLog');
query.equalTo('project', this.project.toPointer());
query.equalTo('contact', this.contact.toPointer());
query.equalTo('type', 'survey-project');
query.equalTo('isCompleted', true);
const surveyLog = await query.first();
if (surveyLog) {
this.surveyStatus = {
filled: true,
text: '查看问卷',
icon: 'checkmark-circle',
surveyLog
};
}
} catch (err) {
console.error('查询问卷状态失败:', err);
}
}
async sendSurvey() {
if (!this.groupChat || !this.wxwork) return;
try {
const chatId = this.groupChat.get('chat_id');
const surveyUrl = `${window.location.origin}/wxwork/${this.cid}/survey/project/${this.project?.id}`;
await this.wxwork.ww.openExistedChatWithMsg({
chatId: chatId,
msg: {
msgtype: 'link',
link: {
title: '《家装效果图服务初次合作需求调查表》',
desc: '为让本次服务更贴合您的需求,请花3-5分钟填写简短问卷,感谢支持!',
url: surveyUrl,
imgUrl: `${window.location.origin}/assets/logo.jpg`
}
}
});
window?.fmode?.alert('问卷已发送到群聊!');
} catch (err) {
console.error('发送问卷失败:', err);
window?.fmode?.alert('发送失败,请重试');
}
}
// 新增模态框状态
showSurveyModal: boolean = false;
selectedSurveyLog: FmodeObject | null = null;
async viewSurvey() {
if (!this.surveyStatus.surveyLog) return;
this.selectedSurveyLog = this.surveyStatus.surveyLog;
this.showSurveyModal = true;
}
async handleSurveyClick(event: Event) {
event.stopPropagation();
if (this.surveyStatus.filled) {
// 已填写,查看结果
await this.viewSurvey();
} else {
// 未填写,发送问卷
await this.sendSurvey();
}
}
import { WxworkAuth } from 'fmode-ng/core';
async ngOnInit() {
// 1. 初始化企微授权
const cid = this.route.snapshot.paramMap.get('cid') || '';
this.wxAuth = new WxworkAuth({ cid, appId: 'crm' });
// 2. 获取当前外部联系人
try {
this.currentContact = await this.wxAuth.currentContact();
console.log('当前联系人:', this.currentContact);
} catch (error) {
console.error('获取联系人失败:', error);
window?.fmode?.alert('无法识别您的身份,请通过企微群聊进入');
return;
}
// 3. 检查是否已填写问卷
await this.checkExistingSurvey();
}
// 查询现有问卷
async checkExistingSurvey() {
const query = new Parse.Query('SurveyLog');
query.equalTo('project', this.projectId);
query.equalTo('contact', this.currentContact.toPointer());
query.equalTo('type', 'survey-project');
this.surveyLog = await query.first();
if (this.surveyLog?.get('isCompleted')) {
// 已完成,直接显示结果
this.currentState = 'result';
} else if (this.surveyLog) {
// 未完成,恢复进度
this.answers = this.surveyLog.get('data') || {};
this.currentState = 'questionnaire';
}
}
// 保存答案
async saveAnswer(questionId: string, answer: any) {
if (!this.surveyLog) {
// 首次保存,创建记录
const SurveyLog = Parse.Object.extend('SurveyLog');
this.surveyLog = new SurveyLog();
const company = new Parse.Object('Company');
company.id = localStorage.getItem('company') || '';
const project = new Parse.Object('Project');
project.id = this.projectId;
this.surveyLog.set('company', company.toPointer());
this.surveyLog.set('project', project.toPointer());
this.surveyLog.set('contact', this.currentContact.toPointer());
this.surveyLog.set('type', 'survey-project');
}
// 更新答案
const data = this.surveyLog.get('data') || {};
data[questionId] = answer;
this.surveyLog.set('data', data);
await this.surveyLog.save();
}
// 完成问卷
async completeSurvey() {
if (!this.surveyLog) return;
this.surveyLog.set('isCompleted', true);
this.surveyLog.set('completedAt', new Date());
await this.surveyLog.save();
// 切换到结果页
this.currentState = 'result';
}
如果问卷中填写了姓名/手机号,需要同步更新 ContactInfo 表:
async updateContactInfo() {
const data = this.surveyLog.get('data');
if (data.contact_name || data.contact_phone) {
if (data.contact_name && !this.currentContact.get('realname')) {
this.currentContact.set('realname', data.contact_name);
}
if (data.contact_phone && !this.currentContact.get('mobile')) {
this.currentContact.set('mobile', data.contact_phone);
}
await this.currentContact.save();
}
}
为让本次效果图服务更贴合您的工作节奏与核心需求,我们准备了简短选择式问卷,您的偏好将直接帮我们校准服务方向,感谢支持!
本次您需要的核心服务是? □ 纯效果图渲染(仅输出可视化图像) □ 效果图+技术配合(含方案相关建议) □ 其他补充:______
需覆盖的关键空间数量及类型? 数量:______个(例:3个,空间类型:客厅/主卧/儿童房)
您更希望本次效果图突出哪些价值?(可多选,选2-3项) □ 细节写实度(如空间尺寸匹配、材质还原,贴合落地需求) □ 视觉吸引力(如氛围营造、风格亮点,方便对接业主) □ 风格适配性(精准匹配预设调性,减少后期调整) □ 其他重点:______
关于方案建议,是否需要我们技术团队配合? □ 需要(侧重方向:□ 材质搭配 □ 灯光布局 □ 空间优化) □ 暂不需要(已有明确方案,仅需渲染)
过往合作中,是否有需要特别注意的点?(可多选) □ 软装色调易偏差 □ 建模细节需盯控 □ 其他:______
本次项目是否有特殊要求?(如业主禁忌、重点展示点)
是否有参考素材(如风格图、实景图)需同步? □ 有(后续群内发送) □ 无(需求已清晰)
感谢您的反馈!我们将根据您的选择制定服务方案,对接人:___(姓名),电话:___,有问题可随时联系~