- 需求模块:完整 CRUD、状态流转(采纳/拒绝/关闭)、详情抽屉、产品→项目级联选择 - 加班记录:产品→项目→版本三级联动、月份筛选(MonthPicker)、CSV 导出 - 成员管理:左右布局(部门树+成员列表)、手机号脱敏、初始密码自动生成及规则设置 - 角色管理:卡片列表、系统角色保护、CRUD - 通用组件:FilterSelect 下拉、MonthPicker 月份选择器、Pagination 分页 - 样式统一:状态标签加 border、日期输入现代化、筛选组件风格一致 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
354 lines
12 KiB
TypeScript
354 lines
12 KiB
TypeScript
'use client';
|
|
|
|
import { create } from 'zustand';
|
|
import type { Requirement, RequirementStatus, Effort, DictItem, SourceType, SourceTarget } from '@/lib/requirement';
|
|
import type { Priority } from '@/lib/derive';
|
|
|
|
interface RequirementState {
|
|
requirements: Requirement[];
|
|
sourceTargets: SourceTarget[];
|
|
types: DictItem[];
|
|
platforms: DictItem[];
|
|
loading: boolean;
|
|
fetchRequirements: () => void;
|
|
createRequirement: (data: Omit<Requirement, 'id' | 'code' | 'createdAt'>) => void;
|
|
updateRequirement: (id: string, data: Partial<Requirement>) => void;
|
|
deleteRequirement: (id: string) => void;
|
|
addSourceTarget: (name: string, sourceType: SourceType) => void;
|
|
updateSourceTarget: (id: string, name: string) => void;
|
|
deleteSourceTarget: (id: string) => void;
|
|
addType: (name: string) => void;
|
|
updateType: (id: string, name: string) => void;
|
|
deleteType: (id: string) => void;
|
|
addPlatform: (name: string) => void;
|
|
updatePlatform: (id: string, name: string) => void;
|
|
deletePlatform: (id: string) => void;
|
|
}
|
|
|
|
// --- Preset dictionaries ---
|
|
|
|
const PRESET_SOURCE_TARGETS: SourceTarget[] = [
|
|
// customer type
|
|
{ id: 'st-1', name: '海底捞', sourceType: 'customer', createdAt: '2024-01-01' },
|
|
{ id: 'st-2', name: '西贝', sourceType: 'customer', createdAt: '2024-01-01' },
|
|
{ id: 'st-3', name: '喜茶', sourceType: 'customer', createdAt: '2024-01-01' },
|
|
{ id: 'st-4', name: '奈雪的茶', sourceType: 'customer', createdAt: '2024-01-01' },
|
|
{ id: 'st-5', name: '麦当劳', sourceType: 'customer', createdAt: '2024-01-01' },
|
|
// operation type
|
|
{ id: 'st-6', name: '运营部', sourceType: 'operation', createdAt: '2024-01-01' },
|
|
{ id: 'st-7', name: '销售部', sourceType: 'operation', createdAt: '2024-01-01' },
|
|
{ id: 'st-8', name: '客服部', sourceType: 'operation', createdAt: '2024-01-01' },
|
|
// internal
|
|
{ id: 'st-9', name: '产品部', sourceType: 'internal', createdAt: '2024-01-01' },
|
|
{ id: 'st-10', name: '技术部', sourceType: 'internal', createdAt: '2024-01-01' },
|
|
// management
|
|
{ id: 'st-11', name: '张总', sourceType: 'management', createdAt: '2024-01-01' },
|
|
{ id: 'st-12', name: '李总', sourceType: 'management', createdAt: '2024-01-01' },
|
|
// aftersale
|
|
{ id: 'st-13', name: '实施部', sourceType: 'aftersale', createdAt: '2024-01-01' },
|
|
{ id: 'st-14', name: '客服部', sourceType: 'aftersale', createdAt: '2024-01-01' },
|
|
// market
|
|
{ id: 'st-15', name: '市场部', sourceType: 'market', createdAt: '2024-01-01' },
|
|
// competitor
|
|
{ id: 'st-16', name: '美团收银', sourceType: 'competitor', createdAt: '2024-01-01' },
|
|
{ id: 'st-17', name: '客如云', sourceType: 'competitor', createdAt: '2024-01-01' },
|
|
];
|
|
|
|
const PRESET_TYPES: DictItem[] = [
|
|
{ id: 'type-1', name: '新功能', createdAt: '2024-01-01' },
|
|
{ id: 'type-2', name: 'BUG修复', createdAt: '2024-01-01' },
|
|
{ id: 'type-3', name: '数据需求/埋点需求', createdAt: '2024-01-01' },
|
|
{ id: 'type-4', name: '优化需求', createdAt: '2024-01-01' },
|
|
{ id: 'type-5', name: '模块重构', createdAt: '2024-01-01' },
|
|
{ id: 'type-6', name: '缺陷修复', createdAt: '2024-01-01' },
|
|
{ id: 'type-7', name: '功能优化', createdAt: '2024-01-01' },
|
|
{ id: 'type-8', name: '插单/紧急需求', createdAt: '2024-01-01' },
|
|
];
|
|
|
|
const PRESET_PLATFORMS: DictItem[] = [
|
|
{ id: 'platform-1', name: 'Web', createdAt: '2024-01-01' },
|
|
{ id: 'platform-2', name: 'App(iOS)', createdAt: '2024-01-01' },
|
|
{ id: 'platform-3', name: 'App(Android)', createdAt: '2024-01-01' },
|
|
{ id: 'platform-4', name: '小程序', createdAt: '2024-01-01' },
|
|
{ id: 'platform-5', name: 'H5', createdAt: '2024-01-01' },
|
|
];
|
|
|
|
// --- Mock requirements ---
|
|
|
|
const MOCK_REQUIREMENTS: Requirement[] = [
|
|
{
|
|
id: 'req-1',
|
|
code: 'REQ-001',
|
|
title: '考勤打卡支持人脸识别',
|
|
description: '员工打卡增加人脸识别验证方式,防止代打卡',
|
|
productId: 'mock-1',
|
|
projectId: 'p1',
|
|
versionId: 'v2',
|
|
sourceType: 'customer',
|
|
sourceTarget: '海底捞',
|
|
platforms: ['platform-2', 'platform-3'],
|
|
typeId: 'type-1',
|
|
status: 'developing',
|
|
priority: 'P1',
|
|
effort: 'L',
|
|
creator: '张三',
|
|
createdAt: '2024-04-10',
|
|
},
|
|
{
|
|
id: 'req-2',
|
|
code: 'REQ-002',
|
|
title: '电子合同批量签署',
|
|
description: '支持企业批量发起合同签署,提升入职效率',
|
|
productId: 'mock-1',
|
|
projectId: 'p2',
|
|
versionId: 'v13',
|
|
sourceType: 'operation',
|
|
sourceTarget: '运营部',
|
|
platforms: ['platform-1'],
|
|
typeId: 'type-1',
|
|
status: 'pending_review',
|
|
priority: 'P2',
|
|
effort: 'XL',
|
|
creator: '李四',
|
|
createdAt: '2024-05-02',
|
|
},
|
|
{
|
|
id: 'req-3',
|
|
code: 'REQ-003',
|
|
title: '支付渠道对账异常修复',
|
|
description: '修复微信支付对账金额不一致的问题',
|
|
productId: 'mock-2',
|
|
projectId: 'p4',
|
|
sourceType: 'customer',
|
|
sourceTarget: '西贝',
|
|
platforms: ['platform-1', 'platform-4'],
|
|
typeId: 'type-2',
|
|
status: 'testing',
|
|
priority: 'P0',
|
|
effort: 'M',
|
|
creator: '王五',
|
|
createdAt: '2024-05-15',
|
|
},
|
|
{
|
|
id: 'req-4',
|
|
code: 'REQ-004',
|
|
title: '会员积分过期提醒',
|
|
description: '积分到期前7天推送小程序模板消息通知',
|
|
productId: 'mock-2',
|
|
projectId: 'p5',
|
|
versionId: 'v13',
|
|
sourceType: 'management',
|
|
sourceTarget: '张总',
|
|
platforms: ['platform-4', 'platform-5'],
|
|
typeId: 'type-4',
|
|
status: 'planned',
|
|
priority: 'P2',
|
|
effort: 'S',
|
|
creator: '张三',
|
|
createdAt: '2024-05-20',
|
|
},
|
|
{
|
|
id: 'req-5',
|
|
code: 'REQ-005',
|
|
title: '值班交接单增加拍照上传',
|
|
description: '值班交接时支持拍照上传现场情况',
|
|
productId: 'mock-1',
|
|
projectId: 'p3',
|
|
sourceType: 'internal',
|
|
sourceTarget: '产品部',
|
|
platforms: ['platform-2', 'platform-3'],
|
|
typeId: 'type-1',
|
|
status: 'adopted',
|
|
priority: 'P3',
|
|
effort: 'M',
|
|
creator: '李四',
|
|
createdAt: '2024-05-28',
|
|
},
|
|
{
|
|
id: 'req-6',
|
|
code: 'REQ-006',
|
|
title: '考勤报表导出性能优化',
|
|
description: '大数据量下考勤报表导出超时,需优化查询和导出逻辑',
|
|
productId: 'mock-1',
|
|
projectId: 'p1',
|
|
versionId: 'v2',
|
|
sourceType: 'customer',
|
|
sourceTarget: '喜茶',
|
|
platforms: ['platform-1'],
|
|
typeId: 'type-7',
|
|
status: 'developing',
|
|
priority: 'P1',
|
|
effort: 'M',
|
|
creator: '王五',
|
|
createdAt: '2024-06-01',
|
|
},
|
|
{
|
|
id: 'req-7',
|
|
code: 'REQ-007',
|
|
title: '支付模块埋点需求',
|
|
description: '支付成功率、失败原因分布等核心指标埋点',
|
|
productId: 'mock-2',
|
|
projectId: 'p4',
|
|
sourceType: 'aftersale',
|
|
sourceTarget: '客服部',
|
|
platforms: ['platform-1', 'platform-4', 'platform-5'],
|
|
typeId: 'type-3',
|
|
status: 'released',
|
|
priority: 'P2',
|
|
effort: 'S',
|
|
creator: '张三',
|
|
createdAt: '2024-04-25',
|
|
},
|
|
{
|
|
id: 'req-8',
|
|
code: 'REQ-008',
|
|
title: '会员模块重构-拆分储值与积分服务',
|
|
description: '将储值卡和积分系统拆分为独立微服务,降低耦合度',
|
|
productId: 'mock-2',
|
|
projectId: 'p5',
|
|
sourceType: 'competitor',
|
|
sourceTarget: '美团收银',
|
|
platforms: ['platform-1'],
|
|
typeId: 'type-5',
|
|
status: 'pending_review',
|
|
priority: 'P3',
|
|
effort: 'XL',
|
|
creator: '李四',
|
|
createdAt: '2024-06-05',
|
|
},
|
|
];
|
|
|
|
// --- localStorage helpers ---
|
|
|
|
const STORAGE_KEY = 'ftb_requirements_v2';
|
|
|
|
function saveLocal(state: { requirements: Requirement[]; sourceTargets: SourceTarget[]; types: DictItem[]; platforms: DictItem[] }) {
|
|
try {
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
|
|
} catch {}
|
|
}
|
|
|
|
function loadLocal(): { requirements: Requirement[]; sourceTargets: SourceTarget[]; types: DictItem[]; platforms: DictItem[] } | null {
|
|
try {
|
|
const raw = localStorage.getItem(STORAGE_KEY);
|
|
if (raw) return JSON.parse(raw);
|
|
} catch {}
|
|
return null;
|
|
}
|
|
|
|
// --- Store ---
|
|
|
|
export const useRequirementStore = create<RequirementState>((set, get) => {
|
|
return {
|
|
requirements: MOCK_REQUIREMENTS,
|
|
sourceTargets: PRESET_SOURCE_TARGETS,
|
|
types: PRESET_TYPES,
|
|
platforms: PRESET_PLATFORMS,
|
|
loading: false,
|
|
|
|
fetchRequirements: () => {
|
|
const cached = loadLocal();
|
|
if (cached) {
|
|
set({ requirements: cached.requirements, sourceTargets: cached.sourceTargets, types: cached.types, platforms: cached.platforms });
|
|
}
|
|
},
|
|
|
|
createRequirement: (data) => {
|
|
const { requirements, sourceTargets, types, platforms } = get();
|
|
const code = `REQ-${String(requirements.length + 1).padStart(3, '0')}`;
|
|
const newReq: Requirement = {
|
|
...data,
|
|
id: `req-${Date.now()}`,
|
|
code,
|
|
createdAt: new Date().toISOString().slice(0, 10),
|
|
};
|
|
const updated = [...requirements, newReq];
|
|
set({ requirements: updated });
|
|
saveLocal({ requirements: updated, sourceTargets, types, platforms });
|
|
},
|
|
|
|
updateRequirement: (id, data) => {
|
|
const { requirements, sourceTargets, types, platforms } = get();
|
|
const updated = requirements.map((r) => (r.id === id ? { ...r, ...data } : r));
|
|
set({ requirements: updated });
|
|
saveLocal({ requirements: updated, sourceTargets, types, platforms });
|
|
},
|
|
|
|
deleteRequirement: (id) => {
|
|
const { requirements, sourceTargets, types, platforms } = get();
|
|
const updated = requirements.filter((r) => r.id !== id);
|
|
set({ requirements: updated });
|
|
saveLocal({ requirements: updated, sourceTargets, types, platforms });
|
|
},
|
|
|
|
// --- SourceTarget dict ---
|
|
addSourceTarget: (name, sourceType) => {
|
|
const { requirements, sourceTargets, types, platforms } = get();
|
|
const item: SourceTarget = { id: `st-${Date.now()}`, name, sourceType, createdAt: new Date().toISOString().slice(0, 10) };
|
|
const updated = [...sourceTargets, item];
|
|
set({ sourceTargets: updated });
|
|
saveLocal({ requirements, sourceTargets: updated, types, platforms });
|
|
},
|
|
|
|
updateSourceTarget: (id, name) => {
|
|
const { requirements, sourceTargets, types, platforms } = get();
|
|
const updated = sourceTargets.map((s) => (s.id === id ? { ...s, name } : s));
|
|
set({ sourceTargets: updated });
|
|
saveLocal({ requirements, sourceTargets: updated, types, platforms });
|
|
},
|
|
|
|
deleteSourceTarget: (id) => {
|
|
const { requirements, sourceTargets, types, platforms } = get();
|
|
const updated = sourceTargets.filter((s) => s.id !== id);
|
|
set({ sourceTargets: updated });
|
|
saveLocal({ requirements, sourceTargets: updated, types, platforms });
|
|
},
|
|
|
|
// --- Type dict ---
|
|
addType: (name) => {
|
|
const { requirements, sourceTargets, types, platforms } = get();
|
|
const item: DictItem = { id: `type-${Date.now()}`, name, createdAt: new Date().toISOString().slice(0, 10) };
|
|
const updated = [...types, item];
|
|
set({ types: updated });
|
|
saveLocal({ requirements, sourceTargets, types: updated, platforms });
|
|
},
|
|
|
|
updateType: (id, name) => {
|
|
const { requirements, sourceTargets, types, platforms } = get();
|
|
const updated = types.map((t) => (t.id === id ? { ...t, name } : t));
|
|
set({ types: updated });
|
|
saveLocal({ requirements, sourceTargets, types: updated, platforms });
|
|
},
|
|
|
|
deleteType: (id) => {
|
|
const { requirements, sourceTargets, types, platforms } = get();
|
|
const updated = types.filter((t) => t.id !== id);
|
|
set({ types: updated });
|
|
saveLocal({ requirements, sourceTargets, types: updated, platforms });
|
|
},
|
|
|
|
// --- Platform dict ---
|
|
addPlatform: (name) => {
|
|
const { requirements, sourceTargets, types, platforms } = get();
|
|
const item: DictItem = { id: `platform-${Date.now()}`, name, createdAt: new Date().toISOString().slice(0, 10) };
|
|
const updated = [...platforms, item];
|
|
set({ platforms: updated });
|
|
saveLocal({ requirements, sourceTargets, types, platforms: updated });
|
|
},
|
|
|
|
updatePlatform: (id, name) => {
|
|
const { requirements, sourceTargets, types, platforms } = get();
|
|
const updated = platforms.map((p) => (p.id === id ? { ...p, name } : p));
|
|
set({ platforms: updated });
|
|
saveLocal({ requirements, sourceTargets, types, platforms: updated });
|
|
},
|
|
|
|
deletePlatform: (id) => {
|
|
const { requirements, sourceTargets, types, platforms } = get();
|
|
const updated = platforms.filter((p) => p.id !== id);
|
|
set({ platforms: updated });
|
|
saveLocal({ requirements, sourceTargets, types, platforms: updated });
|
|
},
|
|
};
|
|
});
|