diff --git a/src/api/studentTrainingAnswer.js b/src/api/studentTrainingAnswer.js
index a7bb5bd..3b40677 100644
--- a/src/api/studentTrainingAnswer.js
+++ b/src/api/studentTrainingAnswer.js
@@ -1737,3 +1737,19 @@ export function checkConsumerScenarioStepFour(payload) {
if (isStudentDemo()) { const valid = rows.length === consumerScenarioJourneyOrder.length && rows.every((row) => consumerScenarioJourneyOrder.includes(row?.stageId) && String(row?.painPoint || "").trim() && String(row?.optimizationSuggestion || "").trim()) && new Set(rows.map((row) => row.stageId)).size === consumerScenarioJourneyOrder.length; return Promise.resolve({ code: 200, data: { valid, message: valid ? "五个阶段的优化分析填写完整。" : "请完整填写五个阶段的痛点和优化建议。" } }); }
return request({ url: "/api/student-training-answers/consumer-scenario/step-4/validate", method: "post", data: { rows }, headers: { repeatSubmit: false } });
}
+
+export function checkFeatureConversionStepOne(payload) {
+ const rows = Array.isArray(payload?.rows) ? payload.rows : [];
+ if (isStudentDemo()) { const valid = rows.length >= 8 && rows.length <= 12 && rows.every((row) => ["voice", "insight", "definition", "description", "acceptance"].every((key) => String(row?.[key] || "").trim())); return Promise.resolve({ code: 200, data: { valid, message: valid ? "需求转化表填写完整。" : "请完整填写 8 至 12 行需求转化信息。" } }); }
+ return request({ url: "/api/student-training-answers/feature-conversion/step-1/validate", method: "post", data: { rows }, headers: { repeatSubmit: false } });
+}
+export function checkFeatureConversionStepTwo(payload) {
+ const sourceDefinitions = Array.isArray(payload?.sourceDefinitions) ? payload.sourceDefinitions : []; const rows = Array.isArray(payload?.rows) ? payload.rows : [];
+ if (isStudentDemo()) { const valid = rows.length >= 8 && rows.length <= 12 && sourceDefinitions.length === rows.length && rows.every((row) => ["reach", "impact", "confidence", "effort"].every((key) => Number.isFinite(Number(row?.[key])))); return Promise.resolve({ code: 200, data: { valid, message: valid ? "RICE 评分填写完整。" : "请完整填写 RICE 评分。" } }); }
+ return request({ url: "/api/student-training-answers/feature-conversion/step-2/validate", method: "post", data: { sourceDefinitions, rows }, headers: { repeatSubmit: false } });
+}
+export function checkFeatureConversionStepThree(payload) {
+ const scoreRows = Array.isArray(payload?.scoreRows) ? payload.scoreRows : []; const rows = Array.isArray(payload?.rows) ? payload.rows : [];
+ if (isStudentDemo()) { const valid = scoreRows.length >= 8 && scoreRows.length <= 12 && rows.length === scoreRows.length && rows.every((row, index) => row?.name === scoreRows[index]?.name && ["P0", "P1", "P2", "P3"].includes(row?.priority) && String(row?.reason || "").trim()); return Promise.resolve({ code: 200, data: { valid, message: valid ? "功能优先级填写完整。" : "请完整填写功能优先级与理由。" } }); }
+ return request({ url: "/api/student-training-answers/feature-conversion/step-3/validate", method: "post", data: { scoreRows, rows }, headers: { repeatSubmit: false } });
+}
diff --git a/src/views/demand/feature-conversion.vue b/src/views/demand/feature-conversion.vue
index be98f90..5013d93 100644
--- a/src/views/demand/feature-conversion.vue
+++ b/src/views/demand/feature-conversion.vue
@@ -50,49 +50,13 @@
{{ activeStepName }}
-
-
-
- 需求转化:从“用户语言”到“工程语言”
- 用户往往只描述现象或愿望,研发团队需要将其转化为可执行、可验收的技术指标。
-
-
-
需求转化过程示例:
-
1. 场景还原与痛点提炼
-
不要只听用户说“我想要个能陪孩子的机器人”,而要深入场景。
-
用户原话:“孩子在家总看电视,我希望机器人能陪他玩,还能教我孩子学英语。”
-
场景洞察:家长(30-40 岁)双职工,下班晚,担心孩子视力及教育缺失;孩子(3-6 岁)注意力短,需要强互动。
-
转化动作:将“陪玩”转化为多模态交互能力(语音 + 视觉 + 运动);将“学英语”转化为内容生态接入与个性化推荐算法。
-
2. 标准化需求定义(PRD 要素)
-
将洞察转化为开发团队能看懂的文档,必须包含验收标准。
-
需求名称:主动式英语启蒙互动。
-
功能描述:当摄像头识别到孩子注视屏幕超过 10 分钟,或检测到孩子无聊发呆时,机器人主动发起英语对话或游戏邀请。
-
验收标准:
-
识别响应时间 < 1 秒;
-
对话自然度(MOS 分)> 4.0;
-
内容准确率由教育专家评估 > 90%。
-
-
- 原始需求基础材料:
-
-
- 家长需求反馈:
-
- - {{ item }}
-
-
-
- 孩子需求反馈:
-
- - {{ item }}
-
-
+
+
用户反馈通常混杂重复描述、情绪化表达、无关信息。请阅读案例资料中原始用户反馈,逐条进行场景洞察拆解,分析用户在什么时间、什么情境下产生该需求,背后的深层动机是什么,然后将用户语言转化为可执行的产品功能定义,填写以下需求转化表。
+
+ 增加一行
+ 删除一行
+
-
-
请根据本题需求,填写以下需求转化表:
@@ -108,15 +72,22 @@
| {{ index + 1 }} |
- |
- |
- |
- |
- |
+ |
+ |
+ |
+ |
+ |
+
至少 {{ MIN_CONVERSION_ROWS }} 行,最多 {{ MAX_CONVERSION_ROWS }} 行
+
+
可参考以下方法:
+
1. 自行阅读案例信息,将用户原声转化为需求,再通过需求分析转化为产品功能。
+
2. 使用通用 AI 大模型辅助完成场景洞察与功能转化,参考提示词见案例文档。
+
+
{{ conversionValidationMessage }}
@@ -126,74 +97,47 @@
{{ activeStepName }}
-
- 对于智能硬件,建议采用RICE 模型(Reach 覆盖范围、Impact 影响程度、Confidence 置信度、Effort 投入成本)进行量化打分,但必须加一个前置过滤器:合规与安全。并使用KANO模型辅助决策。
-
-
- 必备属性(Must-be):如“跌倒检测”、“语音唤醒”。做不到用户会极度不满,做到了是理所应当。优先保障,不计成本投入稳定性。
-
-
- 期望属性(Performance):如“续航时间”、“识别准确率”。做得越好用户越满意。在 BOM 成本允许范围内最大化。
-
-
- 魅力属性(Delighters):如“情绪识别主动安慰”。没有用户无所谓,有了是惊喜。在核心功能稳定后,作为差异化卖点迭代。
-
- 智能硬件需求优先级评估打分表模板:
-
-
-
-
-
- | 字段 |
- 说明 |
- 填写示例 |
- 来源/依据 |
-
-
-
-
- | {{ row.field }} |
- |
- |
- {{ row.source }} |
-
-
-
+
+
将步骤一的功能纳入 RICE 评估模型,进行量化打分。RICE 模型包含覆盖用户数、影响程度、置信度、开发成本四个维度。
+
请根据上一步骤需求转化后的功能定义,针对具体需求进行量化打分,填写以下打分表。
-
-
请根据上一步骤需求转化后的功能定义,针对具体需求进行量化打分,填写以下打分表:
+
计算公式:(Reach × Impact × Confidence) / Effort,结果保留两位小数。需求 ID 与功能名称由步骤一自动同步。
+
{{ scoreValidationMessage }}
@@ -203,29 +147,34 @@
{{ activeStepName }}
-
- 假设项目首期 BOM 成本控制在 300 元以内,研发周期 4 个月。根据上一步骤完成的需求打分表,填写以下内容:
-
-
-
-
-
-
-
-
-
+ 基于步骤二的 RICE 评分结果,输出 P0-P3 四级功能清单,并说明每一等级的划分理由。
+
+
+
+
+ | 功能名称 |
+ 优先级 |
+ 理由 |
+
+
+
+
+ | {{ row.name || "请先完成步骤二 RICE 评分" }} |
+
+
+ |
+ |
+
+
+
+
+
+
{{ level.value }}{{ level.description }}
+
+ {{ priorityValidationMessage }}
@@ -281,9 +230,18 @@ import TrainingAiSidebar from "@/views/components/TrainingAiSidebar.vue";
import TrainingMaterialButton from "@/views/components/TrainingMaterialButton.vue";
import TrainingTaskBrief from "@/views/components/TrainingTaskBrief.vue";
import { getTrainingTaskByKey } from "@/api/trainingTask";
-import { getStudentTrainingAnswer, saveStudentTrainingAnswer } from "@/api/studentTrainingAnswer";
+import { checkFeatureConversionStepOne, checkFeatureConversionStepTwo, checkFeatureConversionStepThree, getStudentTrainingAnswer, saveStudentTrainingAnswer } from "@/api/studentTrainingAnswer";
const TASK_KEY = "feature-conversion";
+const MIN_CONVERSION_ROWS = 8;
+const MAX_CONVERSION_ROWS = 12;
+const kanoTypes = ["基本型", "期望型", "兴奋型", "无差异型", "反向型"];
+const priorityLevels = [
+ { value: "P0", description: "(首期必做,生存线):不做会导致产品无法上市或差评退货的基本功能。" },
+ { value: "P1", description: "(首期核心,竞争力):在成本允许下最大化期望属性,是产品区别于竞品的核心竞争力。" },
+ { value: "P2", description: "(二期迭代,差异化):能显著提升用户体验,但成本高或属于不允许的兴奋型功能。" },
+ { value: "P3", description: "(暂缓/砍掉):伪需求或偏离核心场景的功能建议。" },
+];
const { proxy } = getCurrentInstance();
const defaultRequirement =
@@ -350,9 +308,15 @@ const childFeedback = [
const taskConfig = ref(null);
const saving = ref(false);
-
-function createRows() {
- return Array.from({ length: 5 }, (_, index) => ({
+const checkingConversion = ref(false);
+const checkingScore = ref(false);
+const checkingPriority = ref(false);
+const conversionValidationMessage = ref("");
+const scoreValidationMessage = ref("");
+const priorityValidationMessage = ref("");
+
+function createRows(count = MIN_CONVERSION_ROWS) {
+ return Array.from({ length: count }, (_, index) => ({
id: index + 1,
voice: "",
insight: "",
@@ -362,19 +326,25 @@ function createRows() {
}));
}
-function createScoreRows() {
- return Array.from({ length: 5 }, (_, index) => ({
+function createScoreRows(sourceRows = createRows()) {
+ return sourceRows.map((sourceRow, index) => ({
uid: index + 1,
- requirementId: "",
- name: "",
- source: "",
+ requirementId: formatRequirementId(index),
+ name: sourceRow.definition || "",
kano: "",
reach: "",
impact: "",
confidence: "",
effort: "",
- rice: "",
- compliance: "",
+ }));
+}
+
+function createPriorityRows(sourceRows = createScoreRows()) {
+ return sourceRows.map((sourceRow, index) => ({
+ uid: index + 1,
+ name: sourceRow.name || "",
+ priority: "",
+ reason: "",
}));
}
@@ -388,11 +358,13 @@ function createPriorityDecision() {
}
function createForm() {
+ const rows = createRows();
return {
activeStep: 1,
productName: "",
- rows: createRows(),
- scoreRows: createScoreRows(),
+ rows,
+ scoreRows: createScoreRows(rows),
+ priorityRows: createPriorityRows(createScoreRows(rows)),
priorityDecision: createPriorityDecision(),
};
}
@@ -408,7 +380,6 @@ const hasConfiguredSteps = computed(() => flowSteps.value.length > 0);
const activeStepName = computed(() => flowSteps.value[form.value.activeStep - 1] || "");
const progressItems = computed(() => [
- { text: form.value.productName ? "已填写:产品名称" : "待填写:产品名称", status: form.value.productName ? "done" : "" },
{ text: hasTableContent.value ? "进行中:需求转化表" : "待完成:需求转化表", status: hasTableContent.value ? "doing" : "" },
{ text: hasScoreContent.value ? "进行中:优先级打分表" : "待完成:优先级打分表", status: hasScoreContent.value ? "doing" : "" },
{ text: hasDecisionContent.value ? "进行中:优先级排序决策" : "待完成:优先级排序决策", status: hasDecisionContent.value ? "doing" : "" },
@@ -423,14 +394,14 @@ const hasTableContent = computed(() =>
const hasScoreContent = computed(() =>
form.value.scoreRows.some((row) =>
- ["requirementId", "name", "source", "kano", "reach", "impact", "confidence", "effort", "rice", "compliance"].some((key) =>
+ ["kano", "reach", "impact", "confidence", "effort"].some((key) =>
String(row[key] || "").trim()
)
)
);
const hasDecisionContent = computed(() =>
- ["p0", "p1", "p2", "p3"].some((key) => String(form.value.priorityDecision?.[key] || "").trim())
+ form.value.priorityRows.some((row) => String(row.priority || "").trim() || String(row.reason || "").trim())
);
onMounted(async () => {
@@ -482,31 +453,37 @@ function normalizeActiveStep(value) {
return Math.min(Math.max(parsed, 1), flowSteps.value.length);
}
-function setCurrentStep(step) {
- form.value.activeStep = normalizeActiveStep(step);
+async function setCurrentStep(step) {
+ const targetStep = normalizeActiveStep(step);
+ if (targetStep > form.value.activeStep && [1, 2, 3].includes(form.value.activeStep) && !(await persist("IN_PROGRESS"))) return;
+ if (form.value.activeStep === 1 && targetStep > 1) syncScoreRows();
+ if (targetStep > 2 && form.value.activeStep <= 2) syncPriorityRows();
+ form.value.activeStep = targetStep;
}
function isSectionVisible(section) {
return !hasConfiguredSteps.value || form.value.activeStep === section;
}
-function goPreviousStep() {
- setCurrentStep(form.value.activeStep - 1);
+async function goPreviousStep() {
+ await setCurrentStep(form.value.activeStep - 1);
}
-function goNextStep() {
- setCurrentStep(form.value.activeStep + 1);
+async function goNextStep() {
+ await setCurrentStep(form.value.activeStep + 1);
}
function applySavedAnswer(value) {
try {
const parsed = JSON.parse(value);
if (!parsed || typeof parsed !== "object") return;
+ const rows = normalizeRows(parsed.rows);
form.value = {
...createForm(),
...parsed,
- rows: normalizeRows(parsed.rows),
- scoreRows: normalizeScoreRows(parsed.scoreRows),
+ rows,
+ scoreRows: normalizeScoreRows(parsed.scoreRows, rows),
+ priorityRows: normalizePriorityRows(parsed.priorityRows, normalizeScoreRows(parsed.scoreRows, rows)),
priorityDecision: normalizePriorityDecision(parsed.priorityDecision),
activeStep: normalizeActiveStep(parsed.activeStep),
};
@@ -517,22 +494,125 @@ function applySavedAnswer(value) {
function normalizeRows(rows = []) {
const savedRows = Array.isArray(rows) ? rows : [];
- return createRows().map((row, index) => ({
+ const normalizedCount = Math.min(Math.max(savedRows.length, MIN_CONVERSION_ROWS), MAX_CONVERSION_ROWS);
+ return createRows(normalizedCount).map((row, index) => ({ ...row, ...(savedRows[index] || {}), id: row.id }));
+}
+
+function addConversionRow() {
+ if (form.value.rows.length >= MAX_CONVERSION_ROWS) {
+ proxy?.$modal?.msgWarning(`最多只能填写 ${MAX_CONVERSION_ROWS} 行`);
+ return;
+ }
+ const nextId = Math.max(0, ...form.value.rows.map((row) => Number(row.id) || 0)) + 1;
+ form.value.rows.push({ id: nextId, voice: "", insight: "", definition: "", description: "", acceptance: "" });
+ syncScoreRows();
+}
+
+function removeConversionRow() {
+ if (form.value.rows.length <= MIN_CONVERSION_ROWS) {
+ proxy?.$modal?.msgWarning(`至少需要填写 ${MIN_CONVERSION_ROWS} 行`);
+ return;
+ }
+ form.value.rows.pop();
+ syncScoreRows();
+}
+
+async function ensureConversionValid() {
+ if (checkingConversion.value) return false;
+ checkingConversion.value = true;
+ try {
+ const response = await checkFeatureConversionStepOne({ rows: form.value.rows });
+ const result = response?.data || {};
+ conversionValidationMessage.value = result.message || (result.valid ? "需求转化表填写完整。" : `请完整填写 ${MIN_CONVERSION_ROWS}-${MAX_CONVERSION_ROWS} 行需求转化信息。`);
+ return result.valid === true;
+ } catch (error) {
+ conversionValidationMessage.value = error?.message || "需求转化表校验失败,请稍后重试。";
+ return false;
+ } finally {
+ checkingConversion.value = false;
+ }
+}
+
+function formatRequirementId(index) {
+ return `F${String(index + 1).padStart(2, "0")}`;
+}
+
+function normalizeScoreRows(rows = [], sourceRows = form.value.rows) {
+ const savedRows = Array.isArray(rows) ? rows : [];
+ return createScoreRows(sourceRows).map((row, index) => ({
...row,
...(savedRows[index] || {}),
- id: row.id,
+ uid: row.uid,
+ requirementId: row.requirementId,
+ name: row.name,
}));
}
-function normalizeScoreRows(rows = []) {
+function syncScoreRows() {
+ form.value.scoreRows = normalizeScoreRows(form.value.scoreRows, form.value.rows);
+}
+
+function normalizePriorityRows(rows = [], sourceRows = form.value.scoreRows) {
const savedRows = Array.isArray(rows) ? rows : [];
- return createScoreRows().map((row, index) => ({
+ return createPriorityRows(sourceRows).map((row, index) => ({
...row,
...(savedRows[index] || {}),
uid: row.uid,
+ name: row.name,
}));
}
+function syncPriorityRows() {
+ form.value.priorityRows = normalizePriorityRows(form.value.priorityRows, form.value.scoreRows);
+}
+
+function calculateRiceScore(row) {
+ const reach = Number(row.reach);
+ const impact = Number(row.impact);
+ const confidence = Number(row.confidence);
+ const effort = Number(row.effort);
+ if (![reach, impact, confidence, effort].every(Number.isFinite) || effort <= 0) return "--";
+ return ((reach * impact * (confidence / 100)) / effort).toFixed(2);
+}
+
+async function ensureScoreValid() {
+ if (checkingScore.value) return false;
+ checkingScore.value = true;
+ try {
+ const response = await checkFeatureConversionStepTwo({
+ sourceDefinitions: form.value.rows.map((row) => row.definition),
+ rows: form.value.scoreRows.map((row) => ({ ...row, riceScore: calculateRiceScore(row) })),
+ });
+ const result = response?.data || {};
+ scoreValidationMessage.value = result.message || (result.valid ? "RICE 评分填写完整。" : "请完整填写 RICE 评分表。");
+ return result.valid === true;
+ } catch (error) {
+ scoreValidationMessage.value = error?.message || "RICE 评分校验失败,请稍后重试。";
+ return false;
+ } finally {
+ checkingScore.value = false;
+ }
+}
+
+async function ensurePriorityValid() {
+ if (checkingPriority.value) return false;
+ checkingPriority.value = true;
+ try {
+ const response = await checkFeatureConversionStepThree({
+ scoreRows: form.value.scoreRows.map((row) => ({ ...row, riceScore: calculateRiceScore(row) })),
+ rows: form.value.priorityRows,
+ });
+ const result = response?.data || {};
+ priorityValidationMessage.value = result.message || (result.valid ? "功能优先级清单填写完整。" : "请完成每项功能的优先级和划分理由。");
+ return result.valid === true;
+ } catch (error) {
+ priorityValidationMessage.value = error?.message || "功能优先级清单校验失败,请稍后重试。";
+ return false;
+ } finally {
+ checkingPriority.value = false;
+ }
+}
+
function normalizePriorityDecision(value = {}) {
return {
...createPriorityDecision(),
@@ -550,6 +630,7 @@ function buildOutcome() {
activeStep: form.value.activeStep,
rows: form.value.rows,
scoreRows: form.value.scoreRows,
+ priorityRows: form.value.priorityRows,
priorityDecision: form.value.priorityDecision,
updatedAt: new Date().toISOString(),
};
@@ -566,10 +647,14 @@ function buildAnswerPayload(status) {
async function persist(status = "IN_PROGRESS") {
if (saving.value) return;
+ if (status !== "RESET" && form.value.activeStep === 1 && !(await ensureConversionValid())) return false;
+ if (status !== "RESET" && form.value.activeStep === 2 && !(await ensureScoreValid())) return false;
+ if (status !== "RESET" && form.value.activeStep === 3 && !(await ensurePriorityValid())) return false;
saving.value = true;
try {
await saveStudentTrainingAnswer(TASK_KEY, buildAnswerPayload(status));
proxy?.$modal?.msgSuccess(status === "SUBMITTED" ? "提交成功" : "保存成功");
+ return true;
} finally {
saving.value = false;
}
@@ -789,6 +874,35 @@ function exportOutcome() {
margin: 4px 0;
}
+.conversion-intro {
+ display: flex;
+ align-items: flex-start;
+ justify-content: space-between;
+ gap: 22px;
+ margin-bottom: 16px;
+ padding: 18px 20px;
+ border-left: 3px solid #51dfff;
+ border-radius: 0 14px 14px 0;
+ background: linear-gradient(90deg, rgba(7, 95, 137, 0.28), rgba(4, 31, 49, 0.18));
+
+ > p {
+ max-width: 840px;
+ color: #c6dee9;
+ font-size: 14px;
+ line-height: 1.85;
+ }
+}
+
+.conversion-row-actions {
+ display: flex;
+ flex: 0 0 auto;
+ gap: 10px;
+
+ .el-button {
+ min-width: 112px;
+ }
+}
+
.indent {
padding-left: 24px;
}
@@ -878,6 +992,40 @@ function exportOutcome() {
color: #8fa4b1;
}
+.conversion-limit-tip {
+ margin: 12px 0 0 !important;
+ color: #ff958a;
+ font-size: 13px;
+ font-weight: 800;
+}
+
+.conversion-helper {
+ margin-top: 26px;
+ padding: 16px 18px;
+ border: 1px solid rgba(68, 163, 208, 0.3);
+ border-radius: 14px;
+ color: #b7d1de;
+ background: rgba(3, 27, 43, 0.4);
+
+ h5 {
+ margin: 0 0 4px;
+ color: #e8f9ff;
+ font-size: 14px;
+ }
+
+ p {
+ font-size: 13px;
+ line-height: 1.8;
+ }
+}
+
+.conversion-validation-message {
+ margin: 14px 0 0 !important;
+ color: #ffbbb0;
+ font-size: 13px;
+ font-weight: 800;
+}
+
.priority-content {
max-width: 100%;
}
@@ -1587,6 +1735,204 @@ function exportOutcome() {
margin-top: 36px;
}
+.rice-intro {
+ margin-bottom: 16px;
+ padding: 18px 20px;
+ border-left: 3px solid #53dcff;
+ border-radius: 0 14px 14px 0;
+ background: linear-gradient(90deg, rgba(7, 95, 137, 0.28), rgba(4, 31, 49, 0.16));
+
+ p {
+ margin: 0;
+ color: #c7dfe9;
+ font-size: 14px;
+ line-height: 1.85;
+ }
+
+ p + p {
+ margin-top: 5px;
+ }
+}
+
+.priority-score-table {
+ min-width: 1100px;
+
+ th,
+ td {
+ text-align: center;
+ vertical-align: middle;
+ }
+
+ td {
+ height: 66px;
+ padding: 8px;
+ }
+
+ .auto-score-cell {
+ color: #bde8f6;
+ background: rgba(7, 62, 88, 0.5);
+ font-weight: 800;
+ }
+
+ .auto-score-cell--name {
+ min-width: 180px;
+ color: #d9f4ff;
+ font-weight: 700;
+ line-height: 1.55;
+ word-break: break-word;
+ }
+
+ select,
+ input {
+ width: 100%;
+ min-height: 36px;
+ padding: 0 9px;
+ border: 1px solid rgba(74, 157, 194, 0.64);
+ border-radius: 8px;
+ outline: none;
+ color: #eefbff;
+ background: rgba(6, 38, 58, 0.78);
+ font: inherit;
+ font-size: 13px;
+ box-sizing: border-box;
+
+ &:focus {
+ border-color: rgba(92, 224, 255, 0.76);
+ box-shadow: 0 0 0 3px rgba(34, 207, 255, 0.1);
+ }
+ }
+
+ .metric-input {
+ position: relative;
+
+ input {
+ padding-right: 24px;
+ }
+
+ span {
+ position: absolute;
+ top: 50%;
+ right: 9px;
+ color: #94bece;
+ font-size: 12px;
+ transform: translateY(-50%);
+ pointer-events: none;
+ }
+ }
+
+ .rice-score-cell {
+ color: #64e1ff;
+ font-size: 16px;
+ font-weight: 900;
+ background: rgba(9, 94, 137, 0.28);
+ }
+}
+
+.priority-decision-table {
+ width: 100%;
+ min-width: 860px;
+ border-collapse: collapse;
+ table-layout: fixed;
+
+ th,
+ td {
+ border: 1px solid rgba(77, 167, 220, 0.2);
+ text-align: center;
+ vertical-align: middle;
+ }
+
+ th {
+ padding: 12px;
+ color: #f4fcff;
+ background: linear-gradient(100deg, #0787bd, #075276);
+ font-size: 14px;
+ font-weight: 800;
+ }
+
+ td {
+ height: 76px;
+ padding: 10px;
+ color: #d7f1ff;
+ background: rgba(1, 14, 26, 0.48);
+ }
+
+ th:nth-child(1),
+ td:nth-child(1) {
+ width: 22%;
+ }
+
+ th:nth-child(2),
+ td:nth-child(2) {
+ width: 18%;
+ }
+
+ select {
+ width: min(100%, 130px);
+ min-height: 36px;
+ padding: 0 9px;
+ border: 1px solid rgba(74, 157, 194, 0.64);
+ border-radius: 8px;
+ outline: none;
+ color: #eefbff;
+ background: rgba(6, 38, 58, 0.78);
+ font: inherit;
+ }
+
+ textarea {
+ display: block;
+ width: 100%;
+ height: 72px;
+ min-height: 46px;
+ padding: 10px;
+ border: 1px solid rgba(45, 95, 126, 0.95);
+ border-radius: 10px;
+ outline: none;
+ color: #eef5ff;
+ background: rgba(4, 24, 38, 0.72);
+ font: inherit;
+ font-size: 13px;
+ line-height: 1.55;
+ resize: vertical;
+ box-sizing: border-box;
+
+ &:focus {
+ border-color: rgba(92, 224, 255, 0.7);
+ box-shadow: 0 0 0 3px rgba(0, 174, 255, 0.13);
+ }
+ }
+}
+
+.priority-legend {
+ display: grid;
+ gap: 6px;
+ margin-top: 18px;
+ padding: 16px 18px;
+ border: 1px solid rgba(89, 184, 226, 0.22);
+ border-radius: 14px;
+ background: rgba(5, 37, 55, 0.44);
+
+ p {
+ margin: 0;
+ color: #bfd9e5;
+ font-size: 13px;
+ line-height: 1.65;
+ }
+
+ strong {
+ display: inline-block;
+ min-width: 29px;
+ margin-right: 8px;
+ color: #68e3ff;
+ }
+}
+
+.rice-formula {
+ margin: 12px 0 0 !important;
+ color: #ff9a8d;
+ font-size: 13px;
+ font-weight: 800;
+}
+
@media (max-width: 1100px) {
.feature-conversion-page {
grid-template-columns: 1fr;
@@ -1645,6 +1991,16 @@ function exportOutcome() {
.step-navigation {
flex-direction: column;
}
+
+ .conversion-intro,
+ .conversion-row-actions {
+ align-items: stretch;
+ flex-direction: column;
+ }
+
+ .conversion-row-actions .el-button {
+ width: 100%;
+ }
}
.workbench-head--actions-only {
justify-content: flex-end;