diff --git a/src/api/studentTrainingAnswer.js b/src/api/studentTrainingAnswer.js index 04acd03..31d50a1 100644 --- a/src/api/studentTrainingAnswer.js +++ b/src/api/studentTrainingAnswer.js @@ -15,13 +15,6 @@ const newProductSurveyStepOneAnswers = { "selling-point-11": "有形层", }; -const productDevelopmentProcessStepOneAnswers = { - "1.1": "关键任务", "1.2": "关键任务", "2.1": "关键任务", "2.2": "非关键任务", "2.3": "关键任务", - "3.1": "关键任务", "3.2": "关键任务", "3.3": "非关键任务", "3.4": "关键任务", "3.5": "关键任务", "3.6": "关键任务", - "4.1": "关键任务", "4.2": "关键任务", "4.3": "关键任务", "4.4": "关键任务", - "5.1": "关键任务", "5.2": "关键任务", "5.3": "关键任务", "5.4": "关键任务", -}; - function demoResult(taskKey) { try { return JSON.parse(sessionStorage.getItem(getDemoAnswerKey(taskKey)) || "null"); } catch (error) { return null; } } @@ -102,6 +95,56 @@ export function checkNewProductSurveyStepOne(items) { }); } +export function checkMarketOpportunitySelectionStepOne(items) { + const expected = { + "search-count": "市场规模", "demand-supply-ratio": "竞争强度", "transaction-amount": "市场规模", "transaction-growth": "市场潜力", + "category-transaction-growth": "市场潜力", "online-products": "竞争强度", "online-merchants": "竞争强度", "organic-traffic-ratio": "运营难度", + }; + if (isStudentDemo()) { + const list = Array.isArray(items) ? items : []; + const incorrect = list.find((item) => expected[item?.id] !== item?.category); + if (list.length !== Object.keys(expected).length || incorrect) return Promise.reject(new Error("指标分类有误,请根据提示重新选择")); + return Promise.resolve({ code: 200, data: { correctCount: Object.keys(expected).length, totalCount: Object.keys(expected).length, allCorrect: true, message: "校验通过" } }); + } + return request({ url: "/api/student-training-answers/market-opportunity-selection/step-1/check", method: "post", data: { items }, headers: { repeatSubmit: false } }); +} + +export function checkMarketOpportunitySelectionStepTwo(payload) { + const weights = Array.isArray(payload?.weights) ? payload.weights : []; + const expectedIds = ["search-count", "transaction-amount", "transaction-growth", "category-transaction-growth", "demand-supply-ratio", "online-products", "online-merchants", "organic-traffic-ratio"]; + if (isStudentDemo()) { + const ids = new Set(weights.map((item) => item?.id)); + const total = weights.reduce((sum, item) => sum + Number(item?.weight || 0), 0); + if (weights.length !== expectedIds.length || expectedIds.some((id) => !ids.has(id)) || weights.some((item) => !Number.isInteger(Number(item?.weight)) || Number(item.weight) < 1 || Number(item.weight) > 100) || total !== 100) { + return Promise.reject(new Error("二级指标权重需为 1 至 100 的整数,且合计必须为 100%")); + } + return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); + } + return request({ url: "/api/student-training-answers/market-opportunity-selection/step-2/validate", method: "post", data: { weights }, headers: { repeatSubmit: false } }); +} + +export function checkMarketOpportunitySelectionStepThree(payload) { + const scores = Array.isArray(payload?.scores) ? payload.scores : []; + const expectedIds = ["smart-fitness-mirror", "smart-bathroom-mirror", "smart-beauty-mirror"]; + if (isStudentDemo()) { + const ids = new Set(scores.map((item) => item?.id)); + if (scores.length !== expectedIds.length || expectedIds.some((id) => !ids.has(id)) || scores.some((item) => !Number.isFinite(Number(item?.score)) || Number(item.score) < 0 || Number(item.score) > 100)) { + return Promise.reject(new Error("请为三类产品填写 0 至 100 分的综合得分")); + } + return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); + } + return request({ url: "/api/student-training-answers/market-opportunity-selection/step-3/validate", method: "post", data: { scores }, headers: { repeatSubmit: false } }); +} + +export function checkMarketOpportunitySelectionStepFour(payload) { + const visualizationUrl = String(payload?.visualizationUrl || "").trim(); + if (isStudentDemo()) { + if (!visualizationUrl) return Promise.reject(new Error("请上传数据可视化结果图")); + return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); + } + return request({ url: "/api/student-training-answers/market-opportunity-selection/step-4/validate", method: "post", data: { visualizationUrl }, headers: { repeatSubmit: false } }); +} + export function checkProductDevelopmentProcessStepOne(items) { if (isStudentDemo()) { const results = Array.isArray(items) @@ -372,3 +415,179 @@ export function checkProductDevelopmentFactorsStepFour(payload) { } return request({ url: "/api/student-training-answers/product-development-factors/step-4/validate", method: "post", data: payload, headers: { repeatSubmit: false } }); } + +export function checkConsumerBehaviorStepOne(payload) { + const hypotheses = Array.isArray(payload?.hypotheses) ? payload.hypotheses : []; + if (isStudentDemo()) { + const incomplete = hypotheses.find((item) => ["targetGroup", "keyFinding", "coreHypothesis"].some((field) => !String(item?.[field] || "").trim())); + if (!hypotheses.length || incomplete) { + return Promise.reject(new Error("请完整填写目标人群、实训8关键发现和问卷核心假设")); + } + return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); + } + return request({ + url: "/api/student-training-answers/consumer-behavior/step-1/validate", + method: "post", + data: { hypotheses }, + headers: { repeatSubmit: false }, + }); +} + +export function checkConsumerBehaviorStepTwo(payload) { + const needs = Array.isArray(payload?.needs) ? payload.needs : []; + const allowedTypes = ["基本型", "期望型", "兴奋型", "无差异型", "反向型"]; + if (isStudentDemo()) { + const incomplete = needs.find((item) => !String(item?.feature || "").trim() + || !allowedTypes.includes(item?.kanoType) || !String(item?.featureDirection || "").trim()); + if (!needs.length || needs.length > 20 || incomplete) { + return Promise.reject(new Error("请完整填写功能、KANO类型和对应功能方向(最多20行)")); + } + return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); + } + return request({ + url: "/api/student-training-answers/consumer-behavior/step-2/validate", + method: "post", + data: { needs }, + headers: { repeatSubmit: false }, + }); +} + +export function checkConsumerBehaviorStepThree(payload) { + if (isStudentDemo()) { + const importedDocumentUrl = String(payload?.importedDocumentUrl || "").trim(); + const questions = Array.isArray(payload?.questions) ? payload.questions : []; + const requiredSections = ["基本信息", "购买行为", "需求偏好", "开放建议"]; + const validQuestions = questions.filter((item) => String(item?.title || "").trim()); + const complete = requiredSections.every((section) => validQuestions.some((item) => item.section === section)) + && validQuestions.some((item) => item.section === "需求偏好" && item.type === "LIKERT"); + if (!importedDocumentUrl && !complete) { + return Promise.reject(new Error("请完整设计包含四个部分且含Likert五级量表的问卷,或导入Word问卷")); + } + return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); + } + return request({ + url: "/api/student-training-answers/consumer-behavior/step-3/validate", + method: "post", + data: payload, + headers: { repeatSubmit: false }, + }); +} + +export function previewConsumerBehaviorQuestionnaireWord(data) { + if (isStudentDemo()) { + return Promise.reject(new Error("演示模式不支持 Word 文档预览")); + } + return request({ + url: "/api/student-training-answers/consumer-behavior/step-3/word-preview", + method: "post", + data, + headers: { + "Content-Type": "multipart/form-data", + repeatSubmit: false, + }, + }); +} + +export function checkConsumerBehaviorStepFour(payload) { + if (isStudentDemo()) { + const rows = Array.isArray(payload?.rows) ? payload.rows : []; + if (rows.length < 5 || rows.some((item) => !String(item?.title || "").trim() || !String(item?.content || "").trim())) { + return Promise.reject(new Error("请完整填写 5 个问卷调研报告模块的标题和内容")); + } + return Promise.resolve({ code: 200, data: { valid: true, message: "校验通过" } }); + } + return request({ + url: "/api/student-training-answers/consumer-behavior/step-4/validate", + method: "post", + data: payload, + headers: { repeatSubmit: false }, + }); +} + +export function checkProductValuePositioningStepOne(payload) { + const rows = Array.isArray(payload?.rows) ? payload.rows : []; + const types = ["核心痛点1", "核心痛点2", "核心痛点3", "弱需求", "伪需求"]; + if (isStudentDemo()) { + const valid = rows.length === types.length && rows.every((row, index) => row?.id === index + 1 + && row?.type === types[index] + && Boolean(String(row?.description || "").trim()) + && Boolean(String(row?.scene || "").trim())); + return Promise.resolve({ + code: 200, + data: { + valid, + message: valid ? "核心痛点分类填写完整。" : "请完成 3 个核心痛点、弱需求和伪需求的描述与场景。", + }, + }); + } + return request({ + url: "/api/student-training-answers/product-value-positioning/step-1/validate", + method: "post", + data: { rows }, + headers: { repeatSubmit: false }, + }); +} + +export function checkProductValuePositioningStepTwo(payload) { + const customerProfile = payload?.customerProfile || {}; + const valueMap = payload?.valueMap || {}; + if (isStudentDemo()) { + const requiredValues = [ + customerProfile.jobs, customerProfile.pains, customerProfile.gains, + valueMap.products, valueMap.painRelievers, valueMap.gainCreators, + payload?.canvasUrl, + ]; + const valid = requiredValues.every((item) => Boolean(String(item || "").trim())); + return Promise.resolve({ + code: 200, + data: { + valid, + message: valid ? "价值主张画布填写完整。" : "请完整填写用户档案、产品价值地图并上传产品价值主张画布。", + }, + }); + } + return request({ + url: "/api/student-training-answers/product-value-positioning/step-2/validate", + method: "post", + data: payload, + headers: { repeatSubmit: false }, + }); +} + +export function checkProductValuePositioningStepThree(payload) { + const statements = Array.isArray(payload?.statements) ? payload.statements : []; + const requiredFields = ["targetUser", "productName", "productCategory", "coreValue", "differentiationReason"]; + if (isStudentDemo()) { + const valid = statements.length === 2 && statements.every((statement, index) => statement?.id === index + 1 + && requiredFields.every((field) => Boolean(String(statement?.[field] || "").trim()))); + return Promise.resolve({ + code: 200, + data: { valid, message: valid ? "两版定位陈述填写完整。" : "请完整填写两个产品价值定位陈述版本。" }, + }); + } + return request({ + url: "/api/student-training-answers/product-value-positioning/step-3/validate", + method: "post", + data: { statements }, + headers: { repeatSubmit: false }, + }); +} + +export function checkProductValuePositioningStepFour(payload) { + const sectionKeys = ["marketInsights", "marketOverview", "userAnalysis", "competitorAnalysis", "productRequirements"]; + if (isStudentDemo()) { + const valid = sectionKeys.every((key) => Array.isArray(payload?.[key]) && payload[key].length > 0 + && payload[key].every((row) => Boolean(String(row?.content || "").trim()))) + && Boolean(String(payload?.productPositioning || "").trim()); + return Promise.resolve({ + code: 200, + data: { valid, message: valid ? "MRD 文档填写完整。" : "请完整填写 MRD 的五组分析内容和产品定位。" }, + }); + } + return request({ + url: "/api/student-training-answers/product-value-positioning/step-4/validate", + method: "post", + data: payload, + headers: { repeatSubmit: false }, + }); +} diff --git a/src/router/index.js b/src/router/index.js index 7559532..b529aa0 100644 --- a/src/router/index.js +++ b/src/router/index.js @@ -286,7 +286,7 @@ export const dynamicRoutes = [ // 产品定位 { path: "index", - component: () => import("@/views/product/index.vue"), + component: () => import("@/views/product/market-opportunity-selection.vue"), name: "product", meta: { title: "市场机会识别与选品", icon: "产品规划", affix: true, pageKey: "market-opportunity-selection" }, }, diff --git a/src/views/demand/consumer-behavior.vue b/src/views/demand/consumer-behavior.vue index d406f80..98c3a99 100644 --- a/src/views/demand/consumer-behavior.vue +++ b/src/views/demand/consumer-behavior.vue @@ -49,6 +49,13 @@ :accept="uploadAccept" @change="handleFileChange" /> +
@@ -58,81 +65,170 @@
-
-

- 借助AI,生成智能产品购买决策因素调研问卷,提示词参考:设计问卷,用于分析用户购买智能产品的决策因素。问卷完成后复制到下述编辑框,并上传问卷pdf文档。 +

+

+ 基于实训 8 的用户画像结论,明确本次问卷调查的目标人群与核心假设,填写下表。

- - - 上传问卷 - -
-
- - {{ form.questionnaireFileName }} +
+ 增加一行 + 删除一行 +
+ 查看实训8
-
- 示例: -
+
+ + + + + + + + + + + +
目标人群实训8关键发现本次问卷需验证的核心假设