first
This commit is contained in:
Executable
+745
@@ -0,0 +1,745 @@
|
||||
/**
|
||||
* OKR 模块 Mock 数据
|
||||
* ============================================================
|
||||
* 本文件仅用于前端开发阶段的模拟数据。
|
||||
* 接口对接完成后,将各页面中的 mockXxx() 函数替换为真实的 request.post() 调用即可,
|
||||
* 页面渲染逻辑无需改动。
|
||||
*
|
||||
* ── 数据结构说明 ────────────────────────────────────────────
|
||||
*
|
||||
* Objective(目标 O)字段说明:
|
||||
* id string 目标唯一ID,服务端生成
|
||||
* title string 目标标题,如"完成 App 2.0 发布"
|
||||
* period string 周期标识,如 "Q1" "Q2" "2026-05" (月度)
|
||||
* period_type string 周期类型: "quarter"(季度) | "month"(月度)
|
||||
* dept_code string 所属部门 code(对应 /api/dept/listed 返回的 dept_code)
|
||||
* dept_name string 所属部门名称(冗余字段,方便前端展示)
|
||||
* owner_code string 负责人 user_code(对应 /api/user/listed 返回的 user_code)
|
||||
* owner_name string 负责人姓名(冗余字段)
|
||||
* progress number 总进度 0~1,由所有 KR 进度平均值计算,服务端可返回也可前端算
|
||||
* status string 状态: "not_started" | "in_progress" | "done" | "canceled"
|
||||
* created_at string 创建时间 ISO 格式
|
||||
* key_results KeyResult[] 嵌套的关键结果列表(接口返回时一并带上,减少请求次数)
|
||||
*
|
||||
* KeyResult(关键结果 KR)字段说明:
|
||||
* id string KR 唯一ID
|
||||
* objective_id string 所属目标 ID
|
||||
* title string KR 标题,如"完成核心路径开发及发布"
|
||||
* start_value number 起始值,默认 0
|
||||
* target_value number 目标值,如 100
|
||||
* current_value number 当前值,由负责人手动更新
|
||||
* unit string 单位,如 "%" "次" "万元",可为空
|
||||
* progress number 进度 0~1 = current_value / target_value
|
||||
* priority string 优先级: "P0" | "P1" | "P2"
|
||||
* owner_code string 负责人 user_code
|
||||
* owner_name string 负责人姓名
|
||||
* dept_code string 所属部门 code
|
||||
* dept_name string 所属部门名称
|
||||
* period string 周期,同 Objective.period
|
||||
* start_date string 开始日期 "YYYY-MM-DD"
|
||||
* end_date string 截止日期 "YYYY-MM-DD"
|
||||
* ai_comment string AI 点评(预留字段,暂为空)
|
||||
* steps Step[] 协作步骤列表(嵌套在 KR 内,与 KR 一并返回)
|
||||
*
|
||||
* Step(协作步骤)字段说明:
|
||||
* id string 步骤唯一ID
|
||||
* kr_id string 所属 KR ID
|
||||
* title string 步骤标题,如"完成接口文档"
|
||||
* description string 分工说明,说清楚这步要做什么
|
||||
* collaborator_code string 协作人 user_code
|
||||
* collaborator_name string 协作人姓名
|
||||
* status string "todo" | "in_progress" | "done"
|
||||
* due_date string 截止日期 YYYY-MM-DD(可选)
|
||||
*
|
||||
* 步骤接口(待服务端实现):
|
||||
* POST /api/okr/kr/step/add
|
||||
* 请求体: { kr_id, title, description, collaborator_code, due_date? }
|
||||
* 返回: { code: 0, data: { id: "STEP_xxx" } }
|
||||
* POST /api/okr/kr/step/modify
|
||||
* 请求体: { id, title?, description?, collaborator_code?, status?, due_date? }
|
||||
* 返回: { code: 0 }
|
||||
* POST /api/okr/kr/step/remove
|
||||
* 请求体: { id }
|
||||
* 返回: { code: 0 }
|
||||
*
|
||||
* Review(复盘)字段说明:
|
||||
* id string 复盘唯一ID
|
||||
* objective_id string 关联目标 ID
|
||||
* objective_title string 目标标题(冗余)
|
||||
* period string 所属周期
|
||||
* owner_code string 负责人
|
||||
* owner_name string 负责人姓名
|
||||
* dept_name string 部门名称
|
||||
* score number 目标完成评分 0~1.0(0.7 算达成,对标飞书)
|
||||
* kr_scores [{ kr_id, score }] 各 KR 评分
|
||||
* went_well string 做得好的(复盘问答1)
|
||||
* to_improve string 做得不好的(复盘问答2)
|
||||
* next_steps string 下次如何改进(复盘问答3)
|
||||
* reviewed_at string 复盘时间
|
||||
*
|
||||
* ── 接口约定(待服务端实现)──────────────────────────────────
|
||||
* 所有接口统一 POST,token 放 Header: Authorization: Bearer {token}
|
||||
*
|
||||
* 目标接口:
|
||||
* POST /api/okr/objective/listed
|
||||
* 请求体: { period?: string, dept_code?: string, owner_code?: string }
|
||||
* 返回: { code: 0, data: Objective[] }
|
||||
*
|
||||
* POST /api/okr/objective/add
|
||||
* 请求体: { title, period, period_type, dept_code, owner_code }
|
||||
* 返回: { code: 0, data: { id: "O_xxx" } }
|
||||
*
|
||||
* POST /api/okr/objective/modify
|
||||
* 请求体: { id, title?, period?, dept_code?, owner_code?, status? }
|
||||
* 返回: { code: 0 }
|
||||
*
|
||||
* POST /api/okr/objective/remove
|
||||
* 请求体: { id }
|
||||
* 返回: { code: 0 }
|
||||
*
|
||||
* 关键结果接口:
|
||||
* POST /api/okr/kr/add
|
||||
* 请求体: { objective_id, title, start_value, target_value, unit, priority, owner_code, start_date, end_date }
|
||||
* 返回: { code: 0, data: { id: "KR_xxx" } }
|
||||
*
|
||||
* POST /api/okr/kr/modify
|
||||
* 请求体: { id, title?, current_value?, priority?, start_date?, end_date?, owner_code? }
|
||||
* 返回: { code: 0 }
|
||||
*
|
||||
* POST /api/okr/kr/remove
|
||||
* 请求体: { id }
|
||||
* 返回: { code: 0 }
|
||||
*
|
||||
* 复盘接口:
|
||||
* POST /api/okr/review/listed
|
||||
* 请求体: { period?: string }
|
||||
* 返回: { code: 0, data: Review[] }
|
||||
*
|
||||
* POST /api/okr/review/modify
|
||||
* 请求体: { objective_id, score, kr_scores, went_well, to_improve, next_steps }
|
||||
* 返回: { code: 0 }
|
||||
*
|
||||
* 用户/部门(复用已有接口,无需新增):
|
||||
* POST /api/user/listed 获取用户列表
|
||||
* POST /api/dept/listed 获取部门列表
|
||||
* ============================================================
|
||||
*/
|
||||
|
||||
import { resolvePublicFetchUrl } from '@/utils/apiPath'
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Mock 部门数据(对应真实 /api/dept/listed)
|
||||
// ─────────────────────────────────────────────
|
||||
export const MOCK_DEPTS = [
|
||||
{ dept_code: 'DEPT_RD', full_name: '研发部' },
|
||||
{ dept_code: 'DEPT_MK', full_name: '市场部' },
|
||||
{ dept_code: 'DEPT_CS', full_name: '客服部' },
|
||||
{ dept_code: 'DEPT_FN', full_name: '财务部' },
|
||||
]
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Mock 用户数据(对应真实 /api/user/listed)
|
||||
// ─────────────────────────────────────────────
|
||||
export const MOCK_USERS = [
|
||||
{ user_code: 'USER_ZBB', realname: '周北北', dept_code: 'DEPT_RD', leader_level: 2, phone: '13800001001', is_valid: true },
|
||||
{ user_code: 'USER_ZXS', realname: '邹小杉', dept_code: 'DEPT_RD', leader_level: 1, phone: '13800001002', is_valid: true },
|
||||
{ user_code: 'USER_YXN', realname: '于小宁', dept_code: 'DEPT_MK', leader_level: 1, phone: '13800002001', is_valid: true },
|
||||
{ user_code: 'USER_HPP', realname: '黄泡泡', dept_code: 'DEPT_MK', leader_level: 2, phone: '13800002002', is_valid: true },
|
||||
{ user_code: 'USER_WXM', realname: '王小铭', dept_code: 'DEPT_CS', leader_level: 1, phone: '13800003001', is_valid: true },
|
||||
{ user_code: 'USER_LDJ', realname: '刘大建', dept_code: 'DEPT_CS', leader_level: 2, phone: '13800003002', is_valid: false }, // 已离职
|
||||
]
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Mock OKR 目标+关键结果数据
|
||||
// 注:key_results 嵌套在各 Objective 内,接口建议一起返回
|
||||
// ─────────────────────────────────────────────
|
||||
export const MOCK_OBJECTIVES = [
|
||||
// ── 研发部 ────────────────────────────────
|
||||
{
|
||||
id: 'O_001',
|
||||
title: '完成 App 2.0 发布',
|
||||
period: 'Q2',
|
||||
period_type: 'quarter',
|
||||
dept_code: 'DEPT_RD',
|
||||
dept_name: '研发部',
|
||||
owner_code: 'USER_ZBB',
|
||||
owner_name: '周北北',
|
||||
progress: 0.47, // 由 KR 进度平均计算:(1.0 + 0.35 + 0.06) / 3 ≈ 0.47
|
||||
status: 'in_progress',
|
||||
created_at: '2026-04-01T08:00:00',
|
||||
key_results: [
|
||||
{
|
||||
id: 'KR_001_1',
|
||||
objective_id: 'O_001',
|
||||
title: '完成关键路径功能开发及发布',
|
||||
start_value: 0, target_value: 100, current_value: 100, unit: '%',
|
||||
progress: 1.0,
|
||||
priority: 'P0',
|
||||
owner_code: 'USER_ZBB', owner_name: '周北北',
|
||||
dept_code: 'DEPT_RD', dept_name: '研发部',
|
||||
period: 'Q2',
|
||||
start_date: '2026-04-01', end_date: '2026-05-15',
|
||||
ai_comment: '',
|
||||
steps: [
|
||||
{ id: 'STEP_001_1', kr_id: 'KR_001_1', title: '完成登录/注册模块开发', description: '覆盖手机号+密码、短信验证码两种方式,通过测试用例', collaborator_code: 'USER_ZXS', collaborator_name: '邹小杉', status: 'done', due_date: '2026-04-15' },
|
||||
{ id: 'STEP_001_2', kr_id: 'KR_001_1', title: '完成首页核心展示模块', description: '按设计稿还原,接入真实接口,兼容主流机型', collaborator_code: 'USER_ZBB', collaborator_name: '周北北', status: 'done', due_date: '2026-04-25' },
|
||||
{ id: 'STEP_001_3', kr_id: 'KR_001_1', title: '完成提交并通过应用商店审核', description: '准备上架材料,跟进苹果/安卓审核流程', collaborator_code: 'USER_ZBB', collaborator_name: '周北北', status: 'done', due_date: '2026-05-15' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'KR_001_2',
|
||||
objective_id: 'O_001',
|
||||
title: '支持 App 2.0 迭代需求,完善 App 代码覆盖率至 80%',
|
||||
start_value: 50, target_value: 80, current_value: 60, unit: '%',
|
||||
progress: 0.35,
|
||||
priority: 'P1',
|
||||
owner_code: 'USER_ZXS', owner_name: '邹小杉',
|
||||
dept_code: 'DEPT_RD', dept_name: '研发部',
|
||||
period: 'Q2',
|
||||
start_date: '2026-04-01', end_date: '2026-06-30',
|
||||
ai_comment: '',
|
||||
steps: [
|
||||
{ id: 'STEP_002_1', kr_id: 'KR_001_2', title: '梳理当前未覆盖的核心路径', description: '整理测试缺口清单,优先覆盖支付/下单流程', collaborator_code: 'USER_ZXS', collaborator_name: '邹小杉', status: 'done', due_date: '2026-04-10' },
|
||||
{ id: 'STEP_002_2', kr_id: 'KR_001_2', title: '补充单元测试至 70%', description: '重点补充 utils / hooks 层,目标覆盖率 70%', collaborator_code: 'USER_ZBB', collaborator_name: '周北北', status: 'in_progress', due_date: '2026-05-31' },
|
||||
{ id: 'STEP_002_3', kr_id: 'KR_001_2', title: '集成测试补充至 80%', description: '结合 E2E 框架,覆盖主流程端到端测试', collaborator_code: 'USER_ZXS', collaborator_name: '邹小杉', status: 'todo', due_date: '2026-06-30' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'KR_001_3',
|
||||
objective_id: 'O_001',
|
||||
title: '完成性能优化,首屏加载时长降至 2 秒以内',
|
||||
start_value: 5, target_value: 2, current_value: 4.7, unit: '秒',
|
||||
progress: 0.1,
|
||||
priority: 'P1',
|
||||
owner_code: 'USER_ZXS', owner_name: '邹小杉',
|
||||
dept_code: 'DEPT_RD', dept_name: '研发部',
|
||||
period: 'Q2',
|
||||
start_date: '2026-05-01', end_date: '2026-06-30',
|
||||
ai_comment: '',
|
||||
steps: [
|
||||
{ id: 'STEP_003_1', kr_id: 'KR_001_3', title: '完成首屏资源分析报告', description: '使用 Lighthouse 分析,找出加载瓶颈', collaborator_code: 'USER_ZXS', collaborator_name: '邹小杉', status: 'in_progress', due_date: '2026-05-15' },
|
||||
{ id: 'STEP_003_2', kr_id: 'KR_001_3', title: '图片懒加载 + CDN 接入', description: '接入 OSS CDN,首屏图片走懒加载', collaborator_code: 'USER_ZBB', collaborator_name: '周北北', status: 'todo', due_date: '2026-06-15' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'O_002',
|
||||
title: '提升 App 性能稳定性',
|
||||
period: 'Q2',
|
||||
period_type: 'quarter',
|
||||
dept_code: 'DEPT_RD',
|
||||
dept_name: '研发部',
|
||||
owner_code: 'USER_ZXS',
|
||||
owner_name: '邹小杉',
|
||||
progress: 0.30,
|
||||
status: 'in_progress',
|
||||
created_at: '2026-04-02T09:00:00',
|
||||
key_results: [
|
||||
{
|
||||
id: 'KR_002_1',
|
||||
objective_id: 'O_002',
|
||||
title: 'Crash 率降至 0.1% 以下',
|
||||
start_value: 0.8, target_value: 0.1, current_value: 0.3, unit: '%',
|
||||
progress: 0.71,
|
||||
priority: 'P0',
|
||||
owner_code: 'USER_ZXS', owner_name: '邹小杉',
|
||||
dept_code: 'DEPT_RD', dept_name: '研发部',
|
||||
period: 'Q2',
|
||||
start_date: '2026-04-01', end_date: '2026-06-30',
|
||||
ai_comment: '',
|
||||
},
|
||||
{
|
||||
id: 'KR_002_2',
|
||||
objective_id: 'O_002',
|
||||
title: 'API 平均响应时间 ≤ 300ms',
|
||||
start_value: 800, target_value: 300, current_value: 700, unit: 'ms',
|
||||
progress: 0.20,
|
||||
priority: 'P1',
|
||||
owner_code: 'USER_ZBB', owner_name: '周北北',
|
||||
dept_code: 'DEPT_RD', dept_name: '研发部',
|
||||
period: 'Q2',
|
||||
start_date: '2026-04-01', end_date: '2026-06-30',
|
||||
ai_comment: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ── 市场部 ────────────────────────────────
|
||||
{
|
||||
id: 'O_003',
|
||||
title: '拓展市场份额',
|
||||
period: 'Q2',
|
||||
period_type: 'quarter',
|
||||
dept_code: 'DEPT_MK',
|
||||
dept_name: '市场部',
|
||||
owner_code: 'USER_YXN',
|
||||
owner_name: '于小宁',
|
||||
progress: 0.25,
|
||||
status: 'in_progress',
|
||||
created_at: '2026-04-03T10:00:00',
|
||||
key_results: [
|
||||
{
|
||||
id: 'KR_003_1',
|
||||
objective_id: 'O_003',
|
||||
title: '调研 100+ 行业客户,完成需求报告',
|
||||
start_value: 0, target_value: 100, current_value: 28, unit: '家',
|
||||
progress: 0.28,
|
||||
priority: 'P0',
|
||||
owner_code: 'USER_YXN', owner_name: '于小宁',
|
||||
dept_code: 'DEPT_MK', dept_name: '市场部',
|
||||
period: 'Q2',
|
||||
start_date: '2026-04-01', end_date: '2026-05-31',
|
||||
ai_comment: '',
|
||||
steps: [
|
||||
{ id: 'STEP_004_1', kr_id: 'KR_003_1', title: '设计调研问卷', description: '覆盖采购决策、价格敏感度、现有方案痛点三个维度', collaborator_code: 'USER_HPP', collaborator_name: '黄泡泡', status: 'done', due_date: '2026-04-05' },
|
||||
{ id: 'STEP_004_2', kr_id: 'KR_003_1', title: '完成 50 家客户访谈', description: '电话/上门访谈,每份记录上传共享文档', collaborator_code: 'USER_YXN', collaborator_name: '于小宁', status: 'in_progress', due_date: '2026-05-15' },
|
||||
{ id: 'STEP_004_3', kr_id: 'KR_003_1', title: '整理并输出需求分析报告', description: '汇总共性需求,按优先级排序,产出 PPT 与文档', collaborator_code: 'USER_HPP', collaborator_name: '黄泡泡', status: 'todo', due_date: '2026-05-31' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'KR_003_2',
|
||||
objective_id: 'O_003',
|
||||
title: '推进关键信息并推出七项关键策略',
|
||||
start_value: 0, target_value: 7, current_value: 1, unit: '项',
|
||||
progress: 0.14,
|
||||
priority: 'P1',
|
||||
owner_code: 'USER_HPP', owner_name: '黄泡泡',
|
||||
dept_code: 'DEPT_MK', dept_name: '市场部',
|
||||
period: 'Q2',
|
||||
start_date: '2026-04-15', end_date: '2026-06-30',
|
||||
ai_comment: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'O_004',
|
||||
title: '完成品牌 App 功能宣发',
|
||||
period: 'Q1',
|
||||
period_type: 'quarter',
|
||||
dept_code: 'DEPT_MK',
|
||||
dept_name: '市场部',
|
||||
owner_code: 'USER_HPP',
|
||||
owner_name: '黄泡泡',
|
||||
progress: 0.70,
|
||||
status: 'in_progress',
|
||||
created_at: '2026-01-05T08:00:00',
|
||||
key_results: [
|
||||
{
|
||||
id: 'KR_004_1',
|
||||
objective_id: 'O_004',
|
||||
title: '策划并执行 3 场线上发布活动',
|
||||
start_value: 0, target_value: 3, current_value: 2, unit: '场',
|
||||
progress: 0.67,
|
||||
priority: 'P0',
|
||||
owner_code: 'USER_HPP', owner_name: '黄泡泡',
|
||||
dept_code: 'DEPT_MK', dept_name: '市场部',
|
||||
period: 'Q1',
|
||||
start_date: '2026-01-10', end_date: '2026-03-31',
|
||||
ai_comment: '',
|
||||
},
|
||||
{
|
||||
id: 'KR_004_2',
|
||||
objective_id: 'O_004',
|
||||
title: '创建并发布功能宣发内容,有效触达 10 万用户',
|
||||
start_value: 0, target_value: 100000, current_value: 73000, unit: '人',
|
||||
progress: 0.73,
|
||||
priority: 'P1',
|
||||
owner_code: 'USER_YXN', owner_name: '于小宁',
|
||||
dept_code: 'DEPT_MK', dept_name: '市场部',
|
||||
period: 'Q1',
|
||||
start_date: '2026-01-10', end_date: '2026-03-31',
|
||||
ai_comment: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ── 客服部 ────────────────────────────────
|
||||
{
|
||||
id: 'O_005',
|
||||
title: '提升客户满意度与响应效率',
|
||||
period: 'Q2',
|
||||
period_type: 'quarter',
|
||||
dept_code: 'DEPT_CS',
|
||||
dept_name: '客服部',
|
||||
owner_code: 'USER_WXM',
|
||||
owner_name: '王小铭',
|
||||
progress: 0.43,
|
||||
status: 'in_progress',
|
||||
created_at: '2026-04-04T09:00:00',
|
||||
key_results: [
|
||||
{
|
||||
id: 'KR_005_1',
|
||||
objective_id: 'O_005',
|
||||
title: '改进客户满意度问卷,客满分达到 4.5/5.0',
|
||||
start_value: 3.8, target_value: 4.5, current_value: 4.1, unit: '分',
|
||||
progress: 0.43,
|
||||
priority: 'P0',
|
||||
owner_code: 'USER_WXM', owner_name: '王小铭',
|
||||
dept_code: 'DEPT_CS', dept_name: '客服部',
|
||||
period: 'Q2',
|
||||
start_date: '2026-04-01', end_date: '2026-06-30',
|
||||
ai_comment: '',
|
||||
},
|
||||
{
|
||||
id: 'KR_005_2',
|
||||
objective_id: 'O_005',
|
||||
title: '全面梳理问题库,完善常见问题(FAQ)至 200 条',
|
||||
start_value: 80, target_value: 200, current_value: 120, unit: '条',
|
||||
progress: 0.33,
|
||||
priority: 'P1',
|
||||
owner_code: 'USER_WXM', owner_name: '王小铭',
|
||||
dept_code: 'DEPT_CS', dept_name: '客服部',
|
||||
period: 'Q2',
|
||||
start_date: '2026-04-01', end_date: '2026-05-31',
|
||||
ai_comment: '',
|
||||
},
|
||||
{
|
||||
id: 'KR_005_3',
|
||||
objective_id: 'O_005',
|
||||
title: '优化工单响应流程,工单平均处理时长降至 4 小时',
|
||||
start_value: 12, target_value: 4, current_value: 8, unit: '小时',
|
||||
progress: 0.50,
|
||||
priority: 'P2',
|
||||
owner_code: 'USER_WXM', owner_name: '王小铭',
|
||||
dept_code: 'DEPT_CS', dept_name: '客服部',
|
||||
period: 'Q2',
|
||||
start_date: '2026-04-15', end_date: '2026-06-30',
|
||||
ai_comment: '',
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Mock 复盘数据(已完成季度的复盘记录)
|
||||
// ─────────────────────────────────────────────
|
||||
export const MOCK_REVIEWS = [
|
||||
{
|
||||
id: 'RV_001',
|
||||
objective_id: 'O_004',
|
||||
objective_title: '完成品牌 App 功能宣发',
|
||||
period: 'Q1',
|
||||
owner_code: 'USER_HPP',
|
||||
owner_name: '黄泡泡',
|
||||
dept_name: '市场部',
|
||||
score: 0.7, // 0.7 = 达成,对标飞书评分标准
|
||||
kr_scores: [
|
||||
{ kr_id: 'KR_004_1', score: 0.7 },
|
||||
{ kr_id: 'KR_004_2', score: 0.7 },
|
||||
],
|
||||
went_well: '活动执行节奏稳定,用户反馈积极,创意素材质量较高。',
|
||||
to_improve: '线上活动宣传预热不足,导致首日流量不及预期。',
|
||||
next_steps: '提前 2 周做预热推广,增加 KOL 合作渠道覆盖。',
|
||||
reviewed_at: '2026-04-10T14:00:00',
|
||||
},
|
||||
]
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// Mock API 函数(替换为真实接口时只改这里)
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* 获取目标列表(含嵌套 KR)
|
||||
* 真实接口:POST /api/okr/objective/listed
|
||||
* @param {{ period?: string, dept_code?: string, owner_code?: string }} params
|
||||
*/
|
||||
export async function mockGetObjectives(params = {}) {
|
||||
await _fakeDelay(300)
|
||||
let list = [...MOCK_OBJECTIVES]
|
||||
if (params.period) list = list.filter(o => o.period === params.period)
|
||||
if (params.dept_code) list = list.filter(o => o.dept_code === params.dept_code)
|
||||
if (params.owner_code) list = list.filter(o => o.owner_code === params.owner_code)
|
||||
return list
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取复盘列表
|
||||
* 真实接口:POST /api/okr/review/listed
|
||||
* @param {{ period?: string }} params
|
||||
*/
|
||||
export async function mockGetReviews(params = {}) {
|
||||
await _fakeDelay(200)
|
||||
let list = [...MOCK_REVIEWS]
|
||||
if (params.period) list = list.filter(r => r.period === params.period)
|
||||
return list
|
||||
}
|
||||
|
||||
let _rvIdCounter = 100
|
||||
|
||||
/** 新建复盘(写回 MOCK_REVIEWS) */
|
||||
export async function mockAddReview(data) {
|
||||
await _fakeDelay(150)
|
||||
const id = `RV_${++_rvIdCounter}`
|
||||
MOCK_REVIEWS.unshift({ id, ...data })
|
||||
return { id }
|
||||
}
|
||||
|
||||
/** 修改复盘(写回 MOCK_REVIEWS) */
|
||||
export async function mockModifyReview(patch) {
|
||||
await _fakeDelay(150)
|
||||
const rv = MOCK_REVIEWS.find(r => r.id === patch.id)
|
||||
if (rv) Object.assign(rv, patch)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户列表 —— 调用真实接口 /api/user/listed
|
||||
* 成功后同步覆盖 MOCK_USERS,并把 mock 目标的负责人也替换成真实用户
|
||||
*/
|
||||
export async function mockGetUsers() {
|
||||
try {
|
||||
const res = await fetch(resolvePublicFetchUrl('/api/user/listed'), {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', token: localStorage.getItem('token') || '' },
|
||||
body: JSON.stringify({ page: 1, limit: 500 }),
|
||||
}).then(r => r.json())
|
||||
const list = Array.isArray(res?.data) ? res.data : (res?.data?.list || [])
|
||||
if (list.length > 0) {
|
||||
const normalized = list.map(u => ({
|
||||
...u,
|
||||
user_code: String(u.user_code ?? u.userCode ?? ''),
|
||||
realname: u.realname || u.username || u.user_code || '',
|
||||
}))
|
||||
// 覆盖本地 mock 用户表,之后所有查询都用真实用户
|
||||
MOCK_USERS.splice(0, MOCK_USERS.length, ...normalized)
|
||||
// 把 mock 目标里的假 owner_code / owner_name 替换成真实用户
|
||||
// 循环分配:第 i 个目标用第 i % n 个真实用户
|
||||
MOCK_OBJECTIVES.forEach((obj, i) => {
|
||||
const u = normalized[i % normalized.length]
|
||||
obj.owner_code = u.user_code
|
||||
obj.owner_name = u.realname || u.username || u.user_code
|
||||
obj.key_results?.forEach((kr, j) => {
|
||||
const ku = normalized[(i + j + 1) % normalized.length]
|
||||
kr.owner_code = ku.user_code
|
||||
kr.owner_name = ku.realname || ku.username || ku.user_code
|
||||
// 同步更新步骤协作人(循环分配真实用户)
|
||||
kr.steps?.forEach((step, k) => {
|
||||
const su = normalized[(i + j + k + 2) % normalized.length]
|
||||
step.collaborator_code = su.user_code
|
||||
step.collaborator_name = su.realname || su.username || su.user_code
|
||||
})
|
||||
})
|
||||
})
|
||||
MOCK_REVIEWS.forEach((rv) => {
|
||||
const obj = MOCK_OBJECTIVES.find(o => o.id === rv.objective_id)
|
||||
if (obj) {
|
||||
rv.owner_code = obj.owner_code
|
||||
rv.owner_name = obj.owner_name
|
||||
rv.dept_name = obj.dept_name
|
||||
}
|
||||
})
|
||||
return normalized
|
||||
}
|
||||
} catch {}
|
||||
return [...MOCK_USERS]
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取部门列表 —— 调用真实接口 /api/dept/listed
|
||||
*/
|
||||
export async function mockGetDepts() {
|
||||
try {
|
||||
// 先拿公司列表,再拿部门
|
||||
const compRes = await fetch(resolvePublicFetchUrl('/api/company/listed'), {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', token: localStorage.getItem('token') || '' },
|
||||
body: JSON.stringify({}),
|
||||
}).then(r => r.json())
|
||||
const companies = Array.isArray(compRes?.data) ? compRes.data : (compRes?.data?.list || [])
|
||||
if (companies.length > 0) {
|
||||
const deptRes = await fetch(resolvePublicFetchUrl('/api/dept/listed'), {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', token: localStorage.getItem('token') || '' },
|
||||
body: JSON.stringify({ company_code: companies[0]?.company_code || companies[0]?.code }),
|
||||
}).then(r => r.json())
|
||||
const list = Array.isArray(deptRes?.data) ? deptRes.data : (deptRes?.data?.list || [])
|
||||
if (list.length > 0) {
|
||||
// 统一字段:dept_code / dept_name(注意:必须先展开 d 再覆盖,否则 ...d 会把上面两行冲掉)
|
||||
return list.map(d => {
|
||||
const code = d.dept_code ?? d.code
|
||||
const name = d.dept_name ?? d.name ?? d.full_name ?? ''
|
||||
return {
|
||||
...d,
|
||||
dept_code: code != null && code !== '' ? String(code) : '',
|
||||
dept_name: name,
|
||||
full_name: d.full_name || name,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
// 降级到 mock
|
||||
return [...MOCK_DEPTS]
|
||||
}
|
||||
|
||||
// ── Objective / KR CRUD(mock,对接时替换为 request.post)────────────
|
||||
|
||||
/** 修改 Objective(写回 MOCK_OBJECTIVES 共享源数据,各页面下次读取都能感知) */
|
||||
export async function mockModifyObjective(patch) {
|
||||
await _fakeDelay(150)
|
||||
const obj = MOCK_OBJECTIVES.find(o => o.id === patch.id)
|
||||
if (obj) Object.assign(obj, patch)
|
||||
}
|
||||
|
||||
/** 修改 KR(写回 MOCK_OBJECTIVES 中对应的 key_results 条目) */
|
||||
export async function mockModifyKr(patch) {
|
||||
await _fakeDelay(150)
|
||||
for (const obj of MOCK_OBJECTIVES) {
|
||||
const kr = (obj.key_results || []).find(k => k.id === patch.id)
|
||||
if (kr) { Object.assign(kr, patch); return }
|
||||
}
|
||||
}
|
||||
|
||||
/** 删除 Objective */
|
||||
export async function mockRemoveObjective({ id }) {
|
||||
await _fakeDelay(150)
|
||||
const idx = MOCK_OBJECTIVES.findIndex(o => o.id === id)
|
||||
if (idx !== -1) MOCK_OBJECTIVES.splice(idx, 1)
|
||||
}
|
||||
|
||||
/** 删除 KR */
|
||||
export async function mockRemoveKr({ id }) {
|
||||
await _fakeDelay(150)
|
||||
for (const obj of MOCK_OBJECTIVES) {
|
||||
const idx = (obj.key_results || []).findIndex(k => k.id === id)
|
||||
if (idx !== -1) { obj.key_results.splice(idx, 1); return }
|
||||
}
|
||||
}
|
||||
|
||||
let _objIdCounter = 100
|
||||
let _krIdCounter = 100
|
||||
|
||||
/** 新建 Objective */
|
||||
export async function mockAddObjective(data) {
|
||||
await _fakeDelay(200)
|
||||
const id = `O_${++_objIdCounter}`
|
||||
const obj = { id, key_results: [], progress: 0, status: 'not_started', created_at: new Date().toISOString(), ...data }
|
||||
MOCK_OBJECTIVES.push(obj)
|
||||
return { id }
|
||||
}
|
||||
|
||||
/** 新建 KR */
|
||||
export async function mockAddKr(data) {
|
||||
await _fakeDelay(200)
|
||||
const id = `KR_${++_krIdCounter}`
|
||||
const kr = { id, steps: [], progress: 0, current_value: data.start_value ?? 0, ...data }
|
||||
const obj = MOCK_OBJECTIVES.find(o => o.id === data.objective_id)
|
||||
if (obj) obj.key_results = [...(obj.key_results || []), kr]
|
||||
return { id }
|
||||
}
|
||||
|
||||
// ── 步骤 CRUD(mock,对接时替换为 request.post)─────────────────────
|
||||
let _stepIdCounter = 100
|
||||
|
||||
export async function mockAddStep({ kr_id, title, description, collaborator_code, collaborator_name, due_date }) {
|
||||
await _fakeDelay(150)
|
||||
const id = `STEP_${++_stepIdCounter}`
|
||||
const step = { id, kr_id, title: title || '', description: description || '', collaborator_code: collaborator_code || '', collaborator_name: collaborator_name || '', status: 'todo', due_date: due_date || '' }
|
||||
// 写入 mock 数据
|
||||
for (const obj of MOCK_OBJECTIVES) {
|
||||
const kr = obj.key_results?.find(k => k.id === kr_id)
|
||||
if (kr) { kr.steps = [...(kr.steps || []), step]; break }
|
||||
}
|
||||
return { id }
|
||||
}
|
||||
|
||||
export async function mockModifyStep({ id, ...patch }) {
|
||||
await _fakeDelay(150)
|
||||
for (const obj of MOCK_OBJECTIVES) {
|
||||
for (const kr of (obj.key_results || [])) {
|
||||
const step = (kr.steps || []).find(s => s.id === id)
|
||||
if (step) { Object.assign(step, patch); return }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function mockRemoveStep({ id }) {
|
||||
await _fakeDelay(150)
|
||||
for (const obj of MOCK_OBJECTIVES) {
|
||||
for (const kr of (obj.key_results || [])) {
|
||||
const idx = (kr.steps || []).findIndex(s => s.id === id)
|
||||
if (idx !== -1) { kr.steps.splice(idx, 1); return }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户参与的所有步骤(含所属 KR/O 信息)
|
||||
* @param {string} userCode
|
||||
*/
|
||||
export function mockGetMySteps(userCode) {
|
||||
const result = []
|
||||
for (const obj of MOCK_OBJECTIVES) {
|
||||
for (const kr of (obj.key_results || [])) {
|
||||
for (const step of (kr.steps || [])) {
|
||||
if (step.collaborator_code === userCode) {
|
||||
result.push({ ...step, kr_title: kr.title, kr_id: kr.id, obj_title: obj.title, obj_id: obj.id, period: kr.period, end_date: kr.end_date })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// 模拟网络延迟
|
||||
function _fakeDelay(ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
// ─────────────────────────────────────────────
|
||||
// 工具函数(前端通用)
|
||||
// ─────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* 根据进度值返回对应颜色
|
||||
* 0~0.3: 红色(落后) 0.3~0.7: 橙色(进行中) 0.7~1.0: 绿色(良好)
|
||||
*/
|
||||
export function progressColor(val) {
|
||||
if (val >= 0.7) return '#52c41a'
|
||||
if (val >= 0.3) return '#fa8c16'
|
||||
return '#ff4d4f'
|
||||
}
|
||||
|
||||
/**
|
||||
* 进度百分比文字
|
||||
*/
|
||||
export function progressText(val) {
|
||||
return Math.round((val ?? 0) * 100) + '%'
|
||||
}
|
||||
|
||||
/**
|
||||
* 优先级配置
|
||||
*/
|
||||
export const PRIORITY_MAP = {
|
||||
P0: { label: 'P0', color: '#ff4d4f', bg: '#fff1f0', border: '#ffa39e' },
|
||||
P1: { label: 'P1', color: '#fa8c16', bg: '#fff7e6', border: '#ffd591' },
|
||||
P2: { label: 'P2', color: '#1890ff', bg: '#e6f7ff', border: '#91d5ff' },
|
||||
}
|
||||
|
||||
/**
|
||||
* 周期选项(筛选用)
|
||||
*/
|
||||
export const PERIOD_OPTIONS = [
|
||||
{ label: '全部', value: '' },
|
||||
{ label: 'Q1', value: 'Q1' },
|
||||
{ label: 'Q2', value: 'Q2' },
|
||||
{ label: 'Q3', value: 'Q3' },
|
||||
{ label: 'Q4', value: 'Q4' },
|
||||
]
|
||||
|
||||
/**
|
||||
* 根据用户名称生成首字母头像颜色(固定映射,同一人颜色不变)
|
||||
*/
|
||||
const AVATAR_COLORS = ['#5B5BD6', '#2196F3', '#4CAF50', '#FF9800', '#E91E63', '#9C27B0', '#00BCD4', '#FF5722']
|
||||
export function avatarColor(name = '') {
|
||||
let hash = 0
|
||||
for (let i = 0; i < name.length; i++) hash = name.charCodeAt(i) + ((hash << 5) - hash)
|
||||
return AVATAR_COLORS[Math.abs(hash) % AVATAR_COLORS.length]
|
||||
}
|
||||
|
||||
/**
|
||||
* 取用户姓名首字(用于头像显示)
|
||||
*/
|
||||
export function avatarChar(name = '') {
|
||||
return name ? name[0] : '?'
|
||||
}
|
||||
Reference in New Issue
Block a user