|
|
|
|
@ -50,60 +50,38 @@
|
|
|
|
|
<p class="section-eyebrow">Step 01</p>
|
|
|
|
|
<h3>{{ getStepName(1) }}</h3>
|
|
|
|
|
</div>
|
|
|
|
|
<span class="step-chip">案例信息提取</span>
|
|
|
|
|
<span class="step-chip">卖点分类练习</span>
|
|
|
|
|
</div>
|
|
|
|
|
<p class="step-desc">请根据实训要求中的案例一信息,分别从核心层、有形层、延伸层提取信息,并填写对应信息;</p>
|
|
|
|
|
<p class="step-desc">查看案例产品详情,仔细阅读主图及详情页信息,对下列产品卖点进行三层次分类。</p>
|
|
|
|
|
|
|
|
|
|
<label class="field-block">
|
|
|
|
|
<span>该款智能产品平台类目是:</span>
|
|
|
|
|
<input v-model="step1Form.category" class="input-field input-single" />
|
|
|
|
|
</label>
|
|
|
|
|
|
|
|
|
|
<label class="field-block">
|
|
|
|
|
<span>核心层:从上图可以提炼出该款智能产品解决的用户痛点为:</span>
|
|
|
|
|
<textarea v-model="step1Form.corePain" class="input-field" rows="4" />
|
|
|
|
|
</label>
|
|
|
|
|
|
|
|
|
|
<label class="field-block">
|
|
|
|
|
<span>有形层:从产品主图可以看出,关于产品有形层的主要卖点为:</span>
|
|
|
|
|
<textarea v-model="step1Form.tangibleSellingPoint" class="input-field" rows="4" />
|
|
|
|
|
</label>
|
|
|
|
|
|
|
|
|
|
<label class="field-block">
|
|
|
|
|
<span>价格带为:</span>
|
|
|
|
|
<div class="price-options">
|
|
|
|
|
<button
|
|
|
|
|
v-for="option in priceOptions"
|
|
|
|
|
:key="option.value"
|
|
|
|
|
type="button"
|
|
|
|
|
:class="['price-option', { active: step1Form.priceBand === option.value }]"
|
|
|
|
|
@click="step1Form.priceBand = option.value"
|
|
|
|
|
>
|
|
|
|
|
{{ step1Form.priceBand === option.value ? "√" : "" }}{{ option.label }}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</label>
|
|
|
|
|
|
|
|
|
|
<p class="step-desc">延伸层:从产品主图可以看出,关于产品品牌保证发货等延伸层相关描述为:</p>
|
|
|
|
|
<div class="form-grid three">
|
|
|
|
|
<label class="field-block">
|
|
|
|
|
<span>店铺名称:</span>
|
|
|
|
|
<input v-model="step1Form.storeName" class="input-field input-single" />
|
|
|
|
|
</label>
|
|
|
|
|
<label class="field-block">
|
|
|
|
|
<span>店铺星级:</span>
|
|
|
|
|
<input v-model="step1Form.storeRating" class="input-field input-single" />
|
|
|
|
|
</label>
|
|
|
|
|
<label class="field-block">
|
|
|
|
|
<span>发货优势:</span>
|
|
|
|
|
<input v-model="step1Form.shippingAdvantage" class="input-field input-single" />
|
|
|
|
|
</label>
|
|
|
|
|
<div class="training-table-wrap classification-table-wrap">
|
|
|
|
|
<table class="training-data-table classification-table">
|
|
|
|
|
<thead>
|
|
|
|
|
<tr>
|
|
|
|
|
<th>序号</th>
|
|
|
|
|
<th>卖点</th>
|
|
|
|
|
<th>三层次分类</th>
|
|
|
|
|
</tr>
|
|
|
|
|
</thead>
|
|
|
|
|
<tbody>
|
|
|
|
|
<tr v-for="(point, index) in STEP_ONE_SELLING_POINTS" :key="point.id" :class="feedbackClass(point.id)">
|
|
|
|
|
<td class="classification-number">{{ index + 1 }}</td>
|
|
|
|
|
<td class="classification-selling-point">{{ point.text }}</td>
|
|
|
|
|
<td class="classification-select-cell">
|
|
|
|
|
<el-select v-model="classifications[point.id]" placeholder="请选择分类" :disabled="checking" @change="clearFeedback(point.id)">
|
|
|
|
|
<el-option v-for="category in LAYER_OPTIONS" :key="category" :label="category" :value="category" />
|
|
|
|
|
</el-select>
|
|
|
|
|
<p v-if="checkResult?.itemsById?.[point.id]" class="classification-feedback">
|
|
|
|
|
{{ checkResult.itemsById[point.id].message }}
|
|
|
|
|
</p>
|
|
|
|
|
</td>
|
|
|
|
|
</tr>
|
|
|
|
|
</tbody>
|
|
|
|
|
</table>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<label class="field-block">
|
|
|
|
|
<span>根据该产品主图、详情、评价等信息,在产品介绍、外观设计和功能上您有何改进建议?</span>
|
|
|
|
|
<textarea v-model="step1Form.improvement" class="input-field" rows="4" />
|
|
|
|
|
</label>
|
|
|
|
|
<p v-if="checkResult" class="classification-summary" :class="{ 'is-success': checkResult.allCorrect }">
|
|
|
|
|
本次判题:{{ checkResult.correctCount }} / {{ checkResult.totalCount }} 项分类正确
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
</section>
|
|
|
|
|
|
|
|
|
|
@ -216,12 +194,12 @@
|
|
|
|
|
<el-icon><ArrowLeft /></el-icon>
|
|
|
|
|
上一步
|
|
|
|
|
</button>
|
|
|
|
|
<button type="button" class="btn-nav btn-save" :disabled="saving" @click="saveCurrentProgress">
|
|
|
|
|
<button type="button" class="btn-nav btn-save" :disabled="saving || checking" @click="saveCurrentProgress">
|
|
|
|
|
<el-icon><CircleCheck /></el-icon>
|
|
|
|
|
保存当前进度
|
|
|
|
|
</button>
|
|
|
|
|
<button type="button" class="btn-nav btn-primary" :disabled="currentStep === steps.length" @click="nextStep">
|
|
|
|
|
下一步
|
|
|
|
|
<button type="button" class="btn-nav btn-primary" :disabled="currentStep === steps.length || saving || checking" @click="nextStep">
|
|
|
|
|
{{ checking ? "正在判题" : "下一步" }}
|
|
|
|
|
<el-icon><ArrowRight /></el-icon>
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
@ -263,7 +241,7 @@ import {
|
|
|
|
|
TrendCharts,
|
|
|
|
|
} from "@element-plus/icons-vue";
|
|
|
|
|
import { getTrainingTaskByKey } from "@/api/trainingTask";
|
|
|
|
|
import { getStudentTrainingAnswer, saveStudentTrainingAnswer } from "@/api/studentTrainingAnswer";
|
|
|
|
|
import { checkNewProductSurveyStepOne, getStudentTrainingAnswer, saveStudentTrainingAnswer } from "@/api/studentTrainingAnswer";
|
|
|
|
|
import useUserStore from "@/store/modules/user";
|
|
|
|
|
import { extractTrainingStepNames } from "@/views/training/taskKeyMap";
|
|
|
|
|
import TrainingAiSidebar from "@/views/components/TrainingAiSidebar.vue";
|
|
|
|
|
@ -276,14 +254,15 @@ const userStore = useUserStore();
|
|
|
|
|
|
|
|
|
|
const taskConfig = ref(null);
|
|
|
|
|
const saving = ref(false);
|
|
|
|
|
const checking = ref(false);
|
|
|
|
|
const draftReady = ref(false);
|
|
|
|
|
const currentStep = ref(1);
|
|
|
|
|
|
|
|
|
|
const defaultSteps = [
|
|
|
|
|
{ no: 1, name: "产品三层次理论应用", icon: Search },
|
|
|
|
|
{ no: 1, name: "产品卖点的三层次分类", icon: Search },
|
|
|
|
|
{ no: 2, name: "选定调查平台与产品", icon: DataAnalysis },
|
|
|
|
|
{ no: 3, name: "AI新产品三层次分析", icon: TrendCharts },
|
|
|
|
|
{ no: 4, name: "AI技术价值分析", icon: Operation },
|
|
|
|
|
{ no: 3, name: "AI产品三层次分析", icon: TrendCharts },
|
|
|
|
|
{ no: 4, name: "AI应用价值分析", icon: Operation },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const defaultGoals = [
|
|
|
|
|
@ -313,23 +292,22 @@ const steps = computed(() => {
|
|
|
|
|
}));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const priceOptions = [
|
|
|
|
|
{ label: "1000元以下的基础款", value: "basic" },
|
|
|
|
|
{ label: "1000-3000元的进阶全能款", value: "advanced" },
|
|
|
|
|
{ label: "3000元以上的旗舰高端款", value: "flagship" },
|
|
|
|
|
const LAYER_OPTIONS = ["核心层", "有形层", "延伸层"];
|
|
|
|
|
|
|
|
|
|
const STEP_ONE_SELLING_POINTS = [
|
|
|
|
|
{ id: "selling-point-1", text: "生日礼物女孩子毕业儿童3到6岁10小女童" },
|
|
|
|
|
{ id: "selling-point-2", text: "AI智能机器人毛绒玩具" },
|
|
|
|
|
{ id: "selling-point-3", text: "豆包+DeepSeek六大混合模型,多语种AI对话" },
|
|
|
|
|
{ id: "selling-point-4", text: "覆盖百科、算术、语文、诗词、AI翻译等,早教能力强,还具备情绪感知安抚功能" },
|
|
|
|
|
{ id: "selling-point-5", text: "安全亲肤,零甲醛零荧光剂,母婴级不掉毛" },
|
|
|
|
|
{ id: "selling-point-6", text: "柔软手感,舒心无忧,内赠USB充电线" },
|
|
|
|
|
{ id: "selling-point-7", text: "嘴巴会动,翅膀煽动,脖子扭动,触摸感应触发,说话跳舞同步" },
|
|
|
|
|
{ id: "selling-point-8", text: "可自由定制角色/专属音色,打造宝宝专属小伙伴,适配哄睡、早教、陪伴多场景" },
|
|
|
|
|
{ id: "selling-point-9", text: "快递免运费,预计7小时内发货" },
|
|
|
|
|
{ id: "selling-point-10", text: "大促价保、假一赔四、极速退款、7天无理由退换" },
|
|
|
|
|
{ id: "selling-point-11", text: "店铺优惠后169元起" },
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
const createStep1Form = () => ({
|
|
|
|
|
category: "",
|
|
|
|
|
corePain: "",
|
|
|
|
|
tangibleSellingPoint: "",
|
|
|
|
|
priceBand: "basic",
|
|
|
|
|
storeName: "",
|
|
|
|
|
storeRating: "",
|
|
|
|
|
shippingAdvantage: "",
|
|
|
|
|
improvement: "",
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const createStep2Rows = () => [
|
|
|
|
|
{ scene: "", platform: "", keyword: "", category: "", sales: "" },
|
|
|
|
|
{ scene: "", platform: "", keyword: "", category: "", sales: "" },
|
|
|
|
|
@ -369,7 +347,8 @@ const createStep4Form = () => ({
|
|
|
|
|
extendedValue: "",
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const step1Form = ref(createStep1Form());
|
|
|
|
|
const classifications = ref({});
|
|
|
|
|
const checkResult = ref(null);
|
|
|
|
|
const step2Rows = ref(createStep2Rows());
|
|
|
|
|
const step3Rows = ref(createStep3Rows());
|
|
|
|
|
const step4Form = ref(createStep4Form());
|
|
|
|
|
@ -405,7 +384,7 @@ onMounted(async () => {
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
watch(
|
|
|
|
|
[step1Form, step2Rows, step3Rows, step4Form, currentStep],
|
|
|
|
|
[classifications, checkResult, step2Rows, step3Rows, step4Form, currentStep],
|
|
|
|
|
() => persistDraft(),
|
|
|
|
|
{ deep: true }
|
|
|
|
|
);
|
|
|
|
|
@ -495,12 +474,51 @@ function applySavedAnswer(value) {
|
|
|
|
|
function applyStep1Answer(value) {
|
|
|
|
|
const parsed = safeParse(value);
|
|
|
|
|
if (!parsed || typeof parsed !== "object") return false;
|
|
|
|
|
const source = parsed.step1Form || parsed;
|
|
|
|
|
const fields = Object.keys(createStep1Form());
|
|
|
|
|
const hasValue = fields.some((key) => source[key] !== undefined && source[key] !== null && source[key] !== "");
|
|
|
|
|
if (!hasValue) return false;
|
|
|
|
|
step1Form.value = { ...createStep1Form(), ...pickFields(source, fields) };
|
|
|
|
|
return true;
|
|
|
|
|
const source = parsed.classifications || parsed.step1Form || parsed;
|
|
|
|
|
const restored = STEP_ONE_SELLING_POINTS.reduce((result, point) => {
|
|
|
|
|
if (LAYER_OPTIONS.includes(source?.[point.id])) {
|
|
|
|
|
result[point.id] = source[point.id];
|
|
|
|
|
}
|
|
|
|
|
return result;
|
|
|
|
|
}, {});
|
|
|
|
|
classifications.value = restored;
|
|
|
|
|
checkResult.value = normalizeCheckResult(parsed.checkResult);
|
|
|
|
|
return Object.keys(restored).length > 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function normalizeCheckResult(value) {
|
|
|
|
|
if (!value || typeof value !== "object" || !Array.isArray(value.items)) return null;
|
|
|
|
|
const itemsById = value.items.reduce((result, item) => {
|
|
|
|
|
if (item?.id) result[item.id] = item;
|
|
|
|
|
return result;
|
|
|
|
|
}, {});
|
|
|
|
|
return {
|
|
|
|
|
items: value.items,
|
|
|
|
|
itemsById,
|
|
|
|
|
correctCount: Number(value.correctCount || 0),
|
|
|
|
|
totalCount: Number(value.totalCount || value.items.length),
|
|
|
|
|
allCorrect: Boolean(value.allCorrect),
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function feedbackClass(id) {
|
|
|
|
|
const item = checkResult.value?.itemsById?.[id];
|
|
|
|
|
if (!item) return "";
|
|
|
|
|
return item.correct ? "classification-row--correct" : "classification-row--incorrect";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function clearFeedback(id) {
|
|
|
|
|
if (checkResult.value?.itemsById?.[id]) {
|
|
|
|
|
checkResult.value = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function hasCompletedClassifications() {
|
|
|
|
|
return STEP_ONE_SELLING_POINTS.every((point) => LAYER_OPTIONS.includes(classifications.value[point.id]));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function buildCheckItems() {
|
|
|
|
|
return STEP_ONE_SELLING_POINTS.map((point) => ({ id: point.id, category: classifications.value[point.id] }));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function applyStep2Answer(value) {
|
|
|
|
|
@ -564,7 +582,7 @@ function buildAnswerPayload(status) {
|
|
|
|
|
return {
|
|
|
|
|
saveAction,
|
|
|
|
|
currentStep: currentStep.value,
|
|
|
|
|
step1Answer: JSON.stringify(step1Form.value),
|
|
|
|
|
step1Answer: JSON.stringify({ classifications: classifications.value, checkResult: checkResult.value }),
|
|
|
|
|
step2Answer: JSON.stringify(step2Rows.value),
|
|
|
|
|
step3Answer: JSON.stringify(step3Rows.value),
|
|
|
|
|
step4Answer: JSON.stringify(step4Form.value),
|
|
|
|
|
@ -591,7 +609,34 @@ function submitTask() {
|
|
|
|
|
return saveAnswer("SUBMITTED");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function nextStep() {
|
|
|
|
|
async function nextStep() {
|
|
|
|
|
if (currentStep.value !== 1) {
|
|
|
|
|
advanceStep();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if (!hasCompletedClassifications()) {
|
|
|
|
|
proxy?.$modal?.msgWarning("请完成全部卖点分类后再进入下一步");
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
checking.value = true;
|
|
|
|
|
try {
|
|
|
|
|
const res = await checkNewProductSurveyStepOne(buildCheckItems());
|
|
|
|
|
checkResult.value = normalizeCheckResult(res?.data);
|
|
|
|
|
await saveAnswer("IN_PROGRESS", true);
|
|
|
|
|
if (checkResult.value?.allCorrect) {
|
|
|
|
|
proxy?.$modal?.msgSuccess("分类全部正确,已进入下一步");
|
|
|
|
|
advanceStep();
|
|
|
|
|
} else {
|
|
|
|
|
proxy?.$modal?.msgWarning("存在分类错误,请根据提示修改后重新判题");
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
proxy?.$modal?.msgError(error?.message || "判题失败,请稍后重试");
|
|
|
|
|
} finally {
|
|
|
|
|
checking.value = false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function advanceStep() {
|
|
|
|
|
if (currentStep.value < steps.value.length) {
|
|
|
|
|
currentStep.value += 1;
|
|
|
|
|
}
|
|
|
|
|
@ -604,7 +649,8 @@ function prevStep() {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function resetExample() {
|
|
|
|
|
step1Form.value = createStep1Form();
|
|
|
|
|
classifications.value = {};
|
|
|
|
|
checkResult.value = null;
|
|
|
|
|
step2Rows.value = createStep2Rows();
|
|
|
|
|
step3Rows.value = createStep3Rows();
|
|
|
|
|
step4Form.value = createStep4Form();
|
|
|
|
|
@ -619,7 +665,8 @@ function buildOutcome() {
|
|
|
|
|
taskId: taskConfig.value?.id || "",
|
|
|
|
|
currentStep: currentStep.value,
|
|
|
|
|
steps: steps.value.map((item) => item.name),
|
|
|
|
|
step1Form: step1Form.value,
|
|
|
|
|
classifications: classifications.value,
|
|
|
|
|
checkResult: checkResult.value,
|
|
|
|
|
step2Rows: step2Rows.value,
|
|
|
|
|
step3Rows: step3Rows.value,
|
|
|
|
|
step4Form: step4Form.value,
|
|
|
|
|
@ -1249,6 +1296,61 @@ function exportOutcome() {
|
|
|
|
|
background: rgba(13, 64, 88, 0.62);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.classification-table-wrap {
|
|
|
|
|
margin-top: 20px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.classification-table th:first-child,
|
|
|
|
|
.classification-table td:first-child {
|
|
|
|
|
width: 84px;
|
|
|
|
|
text-align: center;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.classification-table th:last-child,
|
|
|
|
|
.classification-table td:last-child {
|
|
|
|
|
width: 240px;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.classification-number {
|
|
|
|
|
color: #9bdcff;
|
|
|
|
|
font-weight: 800;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.classification-selling-point {
|
|
|
|
|
line-height: 1.65;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.classification-select-cell :deep(.el-select) {
|
|
|
|
|
width: 100%;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.classification-row--correct td {
|
|
|
|
|
background: rgba(23, 181, 121, 0.12);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.classification-row--incorrect td {
|
|
|
|
|
background: rgba(239, 91, 91, 0.13);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.classification-feedback,
|
|
|
|
|
.classification-summary {
|
|
|
|
|
margin: 8px 0 0;
|
|
|
|
|
color: #ffb1b1;
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
line-height: 1.45;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.classification-row--correct .classification-feedback,
|
|
|
|
|
.classification-summary.is-success {
|
|
|
|
|
color: #86e7bd;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.classification-summary {
|
|
|
|
|
margin-top: 16px;
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
font-weight: 700;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@media (max-width: 1100px) {
|
|
|
|
|
.new-product-value-page {
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
@ -1312,6 +1414,10 @@ function exportOutcome() {
|
|
|
|
|
.price-options {
|
|
|
|
|
grid-template-columns: 1fr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
.classification-table {
|
|
|
|
|
min-width: 720px;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
</style>
|
|
|
|
|
|
|
|
|
|
|