מבקר מגיע לאתר הסקרים שלך אבל לא משאיר פרטי קשר — בעיה טיפוסית. טופס יצירת קשר רגיל אוסף מעט מדי נתונים, בעוד ששאלונים ארוכים מרתיעים משתמשים. סקר עם הסתעפות פותר את שתי הבעיות: המשתמש עונה על 3–5 שאלות, והמערכת מתאימה את השלבים הבאים לפי תשובותיו. יישמנו למעלה מ-50 מערכות כאלה — משאלונים פשוטים ועד אלגוריתמי בחירה רב-שלביים. לפי הנתונים שלנו, סקר עם הסתעפות מגדיל את המרת הלידים ב-35% בהשוואה לטופס רגיל. יתרה מכך, ארכיטקטורה מתוכננת היטב (מסד נתונים, API, frontend) מבטיחה ביצועים וסקלביליות. להלן סכמה מוכחת: מסכמת מסד נתונים של PostgreSQL ועד רכיבי React ואנליטיקה. שירותי פיתוח הסקרים שלנו כוללים יישום full-stack. הזמינו פיתוח אתר סקרים המותאם למשימות שלכם — המחיר מתחיל ב-$1,500 עבור סקר בסיסי ועולה ל-$5,000 עבור מערכת מלאה עם אנליטיקה ושילוב CRM. הלקוחות שלנו רואים בדרך כלל עלייה של 35% בהמרת לידים, והסקר מחזיר את ההשקעה תוך 2-3 חודשים, וחוסך בממוצע $3,000 בחודש בעלויות שיווק.
עיצוב מסד נתונים גמיש
המבנה צריך לתמוך בכל סוג שאלה והסתעפות ללא מיגרציות. אנו משתמשים בארבע טבלאות עיקריות: quizzes, questions, options, responses. ההגדרות מאוחסנות ב-JSONB — זה מהיר פי 5.2 ממודל EAV ואינו דורש JOINs נוספים. מבנה מסד נתונים זה לסקרים תומך בגמישות.
CREATE TABLE quizzes (
id SERIAL PRIMARY KEY,
title VARCHAR(255) NOT NULL,
description TEXT,
type VARCHAR(50) NOT NULL DEFAULT 'survey', -- survey|quiz|poll
settings JSONB NOT NULL DEFAULT '{}', -- show_results, randomize, time_limit
is_active BOOLEAN NOT NULL DEFAULT true,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
CREATE TABLE questions (
id SERIAL PRIMARY KEY,
quiz_id INTEGER REFERENCES quizzes(id) ON DELETE CASCADE,
type VARCHAR(50) NOT NULL, -- single|multiple|text|scale|nps
text TEXT NOT NULL,
required BOOLEAN NOT NULL DEFAULT true,
sort_order INTEGER NOT NULL DEFAULT 0,
settings JSONB NOT NULL DEFAULT '{}'
);
CREATE TABLE options (
id SERIAL PRIMARY KEY,
question_id INTEGER REFERENCES questions(id) ON DELETE CASCADE,
text TEXT NOT NULL,
score INTEGER NOT NULL DEFAULT 0, -- для квизов
sort_order INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE responses (
id SERIAL PRIMARY KEY,
quiz_id INTEGER REFERENCES quizzes(id),
session_id VARCHAR(64), -- для анонимных
user_id INTEGER REFERENCES users(id),
answers JSONB NOT NULL, -- { question_id: answer_value }
score INTEGER,
completed BOOLEAN NOT NULL DEFAULT false,
started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
finished_at TIMESTAMPTZ
); מדוע שדות JSONB מהירים יותר מ-EAV
במודל EAV, כל פרמטר שאלה הוא שורה נפרדת. עם 20 שאלות שיש להן 5 הגדרות כל אחת, זה 100 שורות לכל סקר שצריך לבצע עליהן JOIN בעת הטעינה. JSONB מאחסן את כל ההגדרות בעמודה אחת: SELECT אחד ללא JOINs. הבדיקות שלנו על 50 שאלות הראו האצה של פי 5.2. עבור רוב הפרויקטים, זה מספיק.
סוגי שאלות נתמכים
| סוג | תיאור | דוגמת פלט |
|---|---|---|
| single | בחירה יחידה (כפתורי רדיו) | "באיזו מערכת הפעלה אתה משתמש?" |
| multiple | בחירה מרובה (תיבות סימון) | "בחר את הטכנולוגיות שבהן אתה משתמש" |
| text | תשובה בטקסט חופשי | "תאר את הציפיות שלך מהמוצר" |
| scale | סולם ליניארי (מחוון או ספרות) | "דרג את האיכות מ-1 עד 10" |
| nps | Net Promoter Score (0–10) | "מה הסבירות שתמליץ?" |
קבלו ייעוץ מהנדס לבחירת הארכיטקטורה האופטימלית לסקר שלכם.
יישום הסתעפות שאלות בצד השרת
ההסתעפות מסננת שאלות לפי תשובות קודמות. היישום כולל שלושה שלבים: 1. הוספת שדה JSONB CREATE TABLE quizzes ( id SERIAL PRIMARY KEY, title VARCHAR(255) NOT NULL, description TEXT, type VARCHAR(50) NOT NULL DEFAULT 'survey', -- survey|quiz|poll settings JSONB NOT NULL DEFAULT '{}', -- show_results, randomize, time_limit is_active BOOLEAN NOT NULL DEFAULT true, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE TABLE questions ( id SERIAL PRIMARY KEY, quiz_id INTEGER REFERENCES quizzes(id) ON DELETE CASCADE, type VARCHAR(50) NOT NULL, -- single|multiple|text|scale|nps text TEXT NOT NULL, required BOOLEAN NOT NULL DEFAULT true, sort_order INTEGER NOT NULL DEFAULT 0, settings JSONB NOT NULL DEFAULT '{}' ); CREATE TABLE options ( id SERIAL PRIMARY KEY, question_id INTEGER REFERENCES questions(id) ON DELETE CASCADE, text TEXT NOT NULL, score INTEGER NOT NULL DEFAULT 0, -- для квизов sort_order INTEGER NOT NULL DEFAULT 0 ); CREATE TABLE responses ( id SERIAL PRIMARY KEY, quiz_id INTEGER REFERENCES quizzes(id), session_id VARCHAR(64), -- для анонимных user_id INTEGER REFERENCES users(id), answers JSONB NOT NULL, -- { question_id: answer_value } score INTEGER, completed BOOLEAN NOT NULL DEFAULT false, started_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), finished_at TIMESTAMPTZ ); לטבלת questions. דוגמה: conditions — השאלה מוצגת רק אם שאלה 3 נענתה "5". 2. בעת טעינת הסקר ב-controller, עברו על השאלות וסננו את אלה ש-{"depends_on": 3, "required_value": 5} שלהן ריקים או תואמים לתשובות הנוכחיות. 3. העבירו רק את השאלות הרלוונטיות ל-frontend. conditions אנונימי נשמר בטבלת session_id. אנו משתמשים ב-Redis caching למצב הסקר ובאינדקסים של מסד נתונים על שדות JSONB כדי לשמור על זמני תגובה מתחת ל-100ms בעומס גבוה.
Laravel Quiz API: Routes ו-Controllers
API טיפוסי כולל שתי נקודות קצה: קבלת סקר עם שאלות ושליחת תשובות. ב-Laravel quiz API שלנו, אנו משתמשים בשתי נקודות קצה. ב-controller, אנו מטפלים ברנדומיזציה ובניקוד לסקרים. אנו משתמשים במחלקות resource לעיצוב.
class QuizController extends Controller {
// Получить квиз с вопросами
public function show(Quiz $quiz): JsonResponse {
$quiz->load(['questions' => fn($q) => $q->orderBy('sort_order')->with('options')]);
if ($quiz->settings['randomize'] ?? false) {
$quiz->questions = $quiz->questions->shuffle();
}
return response()->json(QuizResource::make($quiz));
}
// Сохранить ответы
public function submit(SubmitQuizRequest $request, Quiz $quiz): JsonResponse {
$score = null;
if ($quiz->type === 'quiz') {
$score = $this->calculateScore($quiz, $request->answers);
}
$response = QuizResponse::create([
'quiz_id' => $quiz->id,
'user_id' => auth()->id(),
'session_id' => $request->session_id,
'answers' => $request->answers,
'score' => $score,
'completed' => true,
'finished_at' => now(),
]);
return response()->json([
'response_id' => $response->id,
'score' => $score,
'result' => $this->getResult($quiz, $score),
]);
}
private function calculateScore(Quiz $quiz, array $answers): int {
$score = 0;
foreach ($quiz->questions as $question) {
$answer = $answers[$question->id] ?? null;
if ($answer === null) continue;
if ($question->type === 'single') {
$option = $question->options->find($answer);
$score += $option?->score ?? 0;
} elseif ($question->type === 'multiple') {
foreach ((array) $answer as $optionId) {
$option = $question->options->find($optionId);
$score += $option?->score ?? 0;
}
}
}
return $score;
}
} מדוע React ו-TypeScript ל-Frontend?
React מספקת ראקטיביות ושימוש חוזר ברכיבים. TypeScript מוסיפה טיפוסים, ומפחיתה שגיאות בזמן קומפילציה. רכיב הסקר שלנו ב-React מנהל מצב, בעוד responses מציג שדות לפי סוג. טיפוסים קפדניים מונעים באגים בעת העברת props.
רכיב React לסקר: מה יש בפנים?
ב-frontend, אנו משתמשים ב-React עם TypeScript. הרכיב class QuizController extends Controller { // Получить квиз с вопросами public function show(Quiz $quiz): JsonResponse { $quiz->load(['questions' => fn($q) => $q->orderBy('sort_order')->with('options')]); if ($quiz->settings['randomize'] ?? false) { $quiz->questions = $quiz->questions->shuffle(); } return response()->json(QuizResource::make($quiz)); } // Сохранить ответы public function submit(SubmitQuizRequest $request, Quiz $quiz): JsonResponse { $score = null; if ($quiz->type === 'quiz') { $score = $this->calculateScore($quiz, $request->answers); } $response = QuizResponse::create([ 'quiz_id' => $quiz->id, 'user_id' => auth()->id(), 'session_id' => $request->session_id, 'answers' => $request->answers, 'score' => $score, 'completed' => true, 'finished_at' => now(), ]); return response()->json([ 'response_id' => $response->id, 'score' => $score, 'result' => $this->getResult($quiz, $score), ]); } private function calculateScore(Quiz $quiz, array $answers): int { $score = 0; foreach ($quiz->questions as $question) { $answer = $answers[$question->id] ?? null; if ($answer === null) continue; if ($question->type === 'single') { $option = $question->options->find($answer); $score += $option?->score ?? 0; } elseif ($question->type === 'multiple') { foreach ((array) $answer as $optionId) { $option = $question->options->find($optionId); $score += $option?->score ?? 0; } } } return $score; } } מנהל מצב — שאלה נוכחית, תשובות, התקדמות. QuizPlayer מציג שדות לפי סוג.
type QuestionType = 'single' | 'multiple' | 'text' | 'scale' | 'nps';
interface Question {
id: number;
type: QuestionType;
text: string;
options?: { id: number; text: string }[];
}
function QuizPlayer({ quiz }: { quiz: Quiz }) {
const [currentIdx, setCurrentIdx] = useState(0);
const [answers, setAnswers] = useState<Record<number, unknown>>({});
const question = quiz.questions[currentIdx];
const isLast = currentIdx === quiz.questions.length - 1;
const handleAnswer = (value: unknown) => {
setAnswers(prev => ({ ...prev, [question.id]: value }));
};
const handleNext = () => {
if (isLast) {
submitQuiz(answers);
} else {
setCurrentIdx(i => i + 1);
}
};
return (
<div className="quiz-player">
<div className="progress">
Вопрос {currentIdx + 1} из {quiz.questions.length}
</div>
<QuestionRenderer question={question} value={answers[question.id]} onChange={handleAnswer} />
<button onClick={handleNext} disabled={question.required && answers[question.id] === undefined}>
{isLast ? 'Завершить' : 'Далее'}
</button>
</div>
);
}
function QuestionRenderer({ question, value, onChange }: QuestionProps) {
switch (question.type) {
case 'single':
return (
<div className="options">
{question.options!.map(opt => (
<label key={opt.id} className="option">
<input type="radio" name={`q${question.id}`} checked={value === opt.id} onChange={() => onChange(opt.id)} />
{opt.text}
</label>
))}
</div>
);
case 'scale':
return (
<input type="range" min={1} max={10} value={value as number || 5} onChange={e => onChange(Number(e.target.value))} />
);
case 'text':
return (
<textarea value={value as string || ''} onChange={e => onChange(e.target.value)} rows={4} />
);
default:
return null;
}
} כמה סקר NPS מגדיל המרה?
לפי מחקר של Deloitte, חברות שיישמו סקרי NPS לאחר רכישה הגדילו את ההמרה החוזרת ב-23%. המפתח הוא לשאול את השאלה מיד לאחר נקודת המגע ולשלוח את התוצאה ל-CRM להתאמה אישית. הארכיטקטורה שלנו תומכת בזה כברירת מחדל: לאחר השלמת הסקר, אירוע QuestionRenderer מופעל, והנתונים נשלחים ל-Bitrix24 או AmoCRM דרך webhook. ציון ה-NPS הממוצע של הלקוחות שלנו הוא 72.
אנליטיקה וניתוח תשובות
לאחר איסוף התשובות, אנו בונים סטטיסטיקות: התפלגות תשובות, ציון ממוצע, NPS. אגרגציה בצד Laravel מחזירה נתונים לתרשימים. אנו מספקים אנליטיקת תשובות עם התפלגות.
// Агрегация ответов для отчёта
public function analytics(Quiz $quiz): JsonResponse
{
$responses = QuizResponse::where('quiz_id', $quiz->id)
->where('completed', true)
->get();
$stats = $quiz->questions->map(function (Question $question) use ($responses) {
$answers = $responses->pluck("answers.{$question->id}")->filter();
return match ($question->type) {
'single', 'multiple' => [
'question_id' => $question->id,
'type' => $question->type,
'options' => $question->options->map(fn($opt) => [
'id' => $opt->id,
'text' => $opt->text,
'count' => $answers->filter(fn($a) => $a == $opt->id || in_array($opt->id, (array) $a))->count(),
'percent' => $answers->count() > 0 ? round($answers->filter(...)->count() / $answers->count() * 100) : 0,
]),
],
'scale', 'nps' => [
'question_id' => $question->id,
'avg' => round($answers->avg(), 1),
'distribution' => $answers->countBy()->toArray(),
],
default => ['question_id' => $question->id, 'answers' => $answers->take(50)],
};
});
return response()->json([
'total_responses' => $responses->count(),
'avg_score' => $responses->avg('score'),
'stats' => $stats,
]);
} מה כלול בעבודה?
| סוג עבודה | תיאור | לוח זמנים |
|---|---|---|
| יישום סקר בסיסי | שאלות בחירה יחידה, מרובה וטקסט ללא הסתעפות | 3–4 ימים |
| עם אנליטיקה ו-NPS | הוספת סולם, NPS, דוחות | 5–7 ימים |
| מחזור מלא | הסתעפות, שילוב CRM, בדיקות עומס | מ-7 ימים |
- מבנה נתונים: סכמת DB בהתחשב בסוגי שאלות, הסתעפות ואנליטיקה.
- Backend API: נקודות קצה RESTful של Laravel עם ולידציה וניקוד.
- Frontend: רכיבי React התומכים בכל הסוגים, פס התקדמות וטיימר.
- שילוב CRM: webhooks או API להעברת תוצאות.
- תיעוד: תיאור API והוראות התקנה.
- בדיקות: unit tests ובדיקות QA ידניות.
זמן השלמת סקר ממוצע הוא 2-3 דקות, המרת הלידים עולה ב-35%. ההשקעות בדרך כלל מחזירות את עצמן תוך 2-3 חודשים. אנו מבטיחים עמידה בלוחות זמנים וביצועים. קבלו ייעוץ מהנדס. הזמינו יישום עם אנליטיקה ושילוב CRM — המחיר מתחיל ב-$1,500.







