feat: 版本详情完整功能 + 数据串联 + 登录模块
- 版本详情:关联需求Tab(从需求池添加已采纳需求/移除释放) - 版本详情:调研/产品方案/UI设计Tab(计划CRUD、完成提交成果、耗时统计) - 版本详情:超期校验(结束日期超版本截止需填写原因) - 版本详情:概览统计卡片(加班时长/排名/原因占比) - 数据串联:产品→项目→版本→需求→加班全链路贯通 - 版本管理:规划中版本可删除,删除释放关联需求 - 数据清理:仅保留翻台宝/值班,需求池10条值班待评审需求 - 修复:列表页overflow裁剪菜单问题(4个页面统一修复) - 新增:登录模块、AuthGuard、VersionPlan store Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
91
apps/web/stores/useAuthStore.ts
Normal file
91
apps/web/stores/useAuthStore.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
'use client';
|
||||
|
||||
import { create } from 'zustand';
|
||||
|
||||
interface AuthUser {
|
||||
id: string;
|
||||
name: string;
|
||||
roleId: string;
|
||||
departmentId: string;
|
||||
phone: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
interface AuthState {
|
||||
user: AuthUser | null;
|
||||
isAuthenticated: boolean;
|
||||
login: (phone: string, password: string, remember: boolean) => boolean;
|
||||
logout: () => void;
|
||||
checkAuth: () => void;
|
||||
}
|
||||
|
||||
const SESSION_KEY = 'ftb_auth_session';
|
||||
const PERSIST_KEY = 'ftb_auth_persist';
|
||||
|
||||
export const useAuthStore = create<AuthState>((set) => ({
|
||||
user: null,
|
||||
isAuthenticated: false,
|
||||
|
||||
login: (phone, password, remember) => {
|
||||
const membersRaw = localStorage.getItem('ftb_members_v1');
|
||||
let members: any[] = [];
|
||||
if (membersRaw) {
|
||||
const parsed = JSON.parse(membersRaw);
|
||||
members = parsed.members || [];
|
||||
}
|
||||
|
||||
// fallback: 如果 localStorage 没有数据或为空,使用默认 mock
|
||||
if (members.length === 0) {
|
||||
members = [
|
||||
{ id: 'm-1', name: '张三', departmentId: 'dept-1', roleId: 'role-pm', phone: '13800138001', email: 'zhangsan@company.com', password: 'Ftb@2024' },
|
||||
{ id: 'm-2', name: '李四', departmentId: 'dept-2-1', roleId: 'role-dev', phone: '13900139002', email: 'lisi@company.com', password: 'Ftb@2024' },
|
||||
{ id: 'm-3', name: '王五', departmentId: 'dept-2-2', roleId: 'role-dev', phone: '13700137003', email: 'wangwu@company.com', password: 'Ftb@2024' },
|
||||
{ id: 'm-4', name: '赵六', departmentId: 'dept-2-3', roleId: 'role-test', phone: '13600136004', email: 'zhaoliu@company.com', password: 'Ftb@2024' },
|
||||
{ id: 'm-5', name: '孙七', departmentId: 'dept-3', roleId: 'role-design', phone: '13500135005', email: 'sunqi@company.com', password: 'Ftb@2024' },
|
||||
{ id: 'm-6', name: '周八', departmentId: 'dept-4', roleId: 'role-pm', phone: '13400134006', email: 'zhouba@company.com', password: 'Ftb@2024' },
|
||||
{ id: 'm-7', name: '吴九', departmentId: 'dept-2-1', roleId: 'role-dev', phone: '13300133007', email: 'wujiu@company.com', password: 'Ftb@2024' },
|
||||
{ id: 'm-8', name: '陈十', departmentId: 'dept-1', roleId: 'role-admin', phone: '13200132008', email: 'chenshi@company.com', password: 'Ftb@2024' },
|
||||
];
|
||||
}
|
||||
|
||||
const member = members.find((m: any) => m.phone === phone && (m.password || 'Ftb@2024') === password);
|
||||
if (!member) return false;
|
||||
|
||||
const user: AuthUser = {
|
||||
id: member.id,
|
||||
name: member.name,
|
||||
roleId: member.roleId,
|
||||
departmentId: member.departmentId,
|
||||
phone: member.phone,
|
||||
email: member.email,
|
||||
};
|
||||
|
||||
set({ user, isAuthenticated: true });
|
||||
sessionStorage.setItem(SESSION_KEY, JSON.stringify(user));
|
||||
if (remember) {
|
||||
localStorage.setItem(PERSIST_KEY, JSON.stringify(user));
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
logout: () => {
|
||||
set({ user: null, isAuthenticated: false });
|
||||
sessionStorage.removeItem(SESSION_KEY);
|
||||
localStorage.removeItem(PERSIST_KEY);
|
||||
},
|
||||
|
||||
checkAuth: () => {
|
||||
const session = sessionStorage.getItem(SESSION_KEY);
|
||||
if (session) {
|
||||
set({ user: JSON.parse(session), isAuthenticated: true });
|
||||
return;
|
||||
}
|
||||
const persist = localStorage.getItem(PERSIST_KEY);
|
||||
if (persist) {
|
||||
const user = JSON.parse(persist);
|
||||
set({ user, isAuthenticated: true });
|
||||
sessionStorage.setItem(SESSION_KEY, JSON.stringify(user));
|
||||
return;
|
||||
}
|
||||
},
|
||||
}));
|
||||
@@ -40,43 +40,10 @@ const MOCK_OVERVIEW: ProductOverview[] = [
|
||||
createdAt: '2024-01-01',
|
||||
updatedAt: '2024-06-01',
|
||||
projects: [
|
||||
{ id: 'p1', name: '考勤', description: '员工打卡、排班管理', createdAt: '2024-01-10' },
|
||||
{ id: 'p2', name: '电子合同', description: '入职、续签、离职合同电子化', createdAt: '2024-02-15' },
|
||||
{ id: 'p3', name: '值班', description: '门店值班排班与交接', createdAt: '2024-03-01' },
|
||||
],
|
||||
versions: [
|
||||
{ id: 'v1', name: '考勤V1.0', status: 'released', releaseDate: '2024-02-01', createdAt: '2024-01-20', currentStage: 'released', startDate: '2024-01-20', members: [{ role: 'product', name: '张三' }, { role: 'frontend', name: '赵六' }, { role: 'backend', name: '孙八' }, { role: 'testing', name: '周九' }], progress: [{ role: 'product', percent: 100, daysSpent: 3 }, { role: 'frontend', percent: 100, daysSpent: 5 }, { role: 'backend', percent: 100, daysSpent: 4 }, { role: 'testing', percent: 100, daysSpent: 2 }] },
|
||||
{ id: 'v2', name: '考勤V1.2', status: 'developing', releaseDate: null, createdAt: '2024-04-15', currentStage: 'dev', startDate: '2024-04-15', expectedReleaseDate: '2024-06-10', priority: 'P1', links: { research: 'https://docs.example.com/kaoqin-v12', prototype: 'https://axure.example.com/kaoqin-v12' }, members: [{ role: 'product', name: '张三' }, { role: 'ui', name: '王五' }, { role: 'frontend', name: '赵六' }, { role: 'backend', name: '孙八' }, { role: 'testing', name: '周九' }], progress: [{ role: 'product', percent: 100, daysSpent: 5 }, { role: 'ui', percent: 100, daysSpent: 7 }, { role: 'frontend', percent: 60, daysSpent: 10 }, { role: 'backend', percent: 40, daysSpent: 12 }, { role: 'testing', percent: 0, daysSpent: 0 }] },
|
||||
{ id: 'v3', name: '考勤V1.3', status: 'planned', releaseDate: null, createdAt: '2024-06-01' },
|
||||
{ id: 'v4', name: '电子合同V1.0', status: 'released', releaseDate: '2024-03-20', createdAt: '2024-03-01', currentStage: 'released', startDate: '2024-03-01', members: [{ role: 'product', name: '李四' }, { role: 'frontend', name: '钱七' }, { role: 'backend', name: '孙八' }, { role: 'testing', name: '周九' }], progress: [{ role: 'product', percent: 100, daysSpent: 4 }, { role: 'frontend', percent: 100, daysSpent: 8 }, { role: 'backend', percent: 100, daysSpent: 6 }, { role: 'testing', percent: 100, daysSpent: 3 }] },
|
||||
{ id: 'v5', name: '电子合同V2.0', status: 'released', releaseDate: '2024-06-01', createdAt: '2024-05-20', currentStage: 'released', startDate: '2024-05-01', members: [{ role: 'product', name: '李四' }, { role: 'ui', name: '王五' }, { role: 'frontend', name: '钱七' }, { role: 'backend', name: '孙八' }, { role: 'testing', name: '周九' }], progress: [{ role: 'product', percent: 100, daysSpent: 5 }, { role: 'ui', percent: 100, daysSpent: 6 }, { role: 'frontend', percent: 100, daysSpent: 12 }, { role: 'backend', percent: 100, daysSpent: 10 }, { role: 'testing', percent: 100, daysSpent: 4 }] },
|
||||
{ id: 'v6', name: '电子合同V2.1', status: 'developing', releaseDate: null, createdAt: '2024-06-05', currentStage: 'ui_design', startDate: '2024-06-05', expectedReleaseDate: '2024-07-15', priority: 'P2', links: { research: 'https://docs.example.com/contract-v21' }, members: [{ role: 'product', name: '李四' }, { role: 'ui', name: '王五' }, { role: 'frontend', name: '钱七' }, { role: 'backend', name: '孙八' }, { role: 'testing', name: '周九' }], progress: [{ role: 'product', percent: 100, daysSpent: 3 }, { role: 'ui', percent: 50, daysSpent: 4 }, { role: 'frontend', percent: 0, daysSpent: 0 }, { role: 'backend', percent: 0, daysSpent: 0 }, { role: 'testing', percent: 0, daysSpent: 0 }] },
|
||||
{ id: 'v7', name: '值班V1.0', status: 'released', releaseDate: '2024-04-01', createdAt: '2024-03-15', currentStage: 'released', startDate: '2024-03-15', members: [{ role: 'product', name: '张三' }, { role: 'frontend', name: '赵六' }, { role: 'testing', name: '周九' }], progress: [{ role: 'product', percent: 100, daysSpent: 3 }, { role: 'frontend', percent: 100, daysSpent: 8 }, { role: 'testing', percent: 100, daysSpent: 3 }] },
|
||||
{ id: 'v8', name: '值班V1.1', status: 'released', releaseDate: '2024-05-15', createdAt: '2024-05-01', currentStage: 'released', startDate: '2024-05-01', members: [{ role: 'product', name: '张三' }, { role: 'frontend', name: '赵六' }, { role: 'backend', name: '孙八' }, { role: 'testing', name: '周九' }], progress: [{ role: 'product', percent: 100, daysSpent: 2 }, { role: 'frontend', percent: 100, daysSpent: 5 }, { role: 'backend', percent: 100, daysSpent: 4 }, { role: 'testing', percent: 100, daysSpent: 2 }] },
|
||||
{ id: 'v9', name: '值班V1.2', status: 'developing', releaseDate: null, createdAt: '2024-06-02', currentStage: 'testing', startDate: '2024-06-02', expectedReleaseDate: '2024-06-20', priority: 'P0', members: [{ role: 'product', name: '张三' }, { role: 'frontend', name: '赵六' }, { role: 'backend', name: '孙八' }, { role: 'testing', name: '周九' }], progress: [{ role: 'product', percent: 100, daysSpent: 2 }, { role: 'frontend', percent: 100, daysSpent: 6 }, { role: 'backend', percent: 100, daysSpent: 5 }, { role: 'testing', percent: 30, daysSpent: 2 }] },
|
||||
{ id: 'v10', name: '值班V2.0', status: 'planned', releaseDate: null, createdAt: '2024-06-08' },
|
||||
],
|
||||
_count: { requirements: 12, projects: 3, versions: 10 },
|
||||
},
|
||||
{
|
||||
id: 'mock-2',
|
||||
name: '智慧收银',
|
||||
description: '收银台 POS + 会员支付一体化',
|
||||
createdAt: '2024-03-01',
|
||||
updatedAt: '2024-06-01',
|
||||
projects: [
|
||||
{ id: 'p4', name: '支付', description: '微信/支付宝/现金多渠道收银', createdAt: '2024-03-10' },
|
||||
{ id: 'p5', name: '会员', description: '储值卡、积分、优惠券', createdAt: '2024-04-01' },
|
||||
],
|
||||
versions: [
|
||||
{ id: 'v11', name: '支付V1.0', status: 'released', releaseDate: '2024-04-20', createdAt: '2024-04-01', currentStage: 'released', startDate: '2024-04-01', members: [{ role: 'product', name: '李四' }, { role: 'frontend', name: '钱七' }, { role: 'backend', name: '吴十' }, { role: 'testing', name: '郑十一' }], progress: [{ role: 'product', percent: 100, daysSpent: 4 }, { role: 'frontend', percent: 100, daysSpent: 8 }, { role: 'backend', percent: 100, daysSpent: 7 }, { role: 'testing', percent: 100, daysSpent: 3 }] },
|
||||
{ id: 'v12', name: '支付V1.1', status: 'released', releaseDate: '2024-06-01', createdAt: '2024-05-25', currentStage: 'released', startDate: '2024-05-10', members: [{ role: 'product', name: '李四' }, { role: 'frontend', name: '钱七' }, { role: 'backend', name: '吴十' }, { role: 'testing', name: '郑十一' }], progress: [{ role: 'product', percent: 100, daysSpent: 3 }, { role: 'frontend', percent: 100, daysSpent: 7 }, { role: 'backend', percent: 100, daysSpent: 6 }, { role: 'testing', percent: 100, daysSpent: 3 }] },
|
||||
{ id: 'v13', name: '支付V2.0', status: 'developing', releaseDate: null, createdAt: '2024-06-05', currentStage: 'dev', startDate: '2024-06-05', expectedReleaseDate: '2024-07-20', priority: 'P1', links: { research: 'https://docs.example.com/pay-v20', prototype: 'https://axure.example.com/pay-v20', ui: 'https://figma.com/pay-v20' }, members: [{ role: 'product', name: '李四' }, { role: 'ui', name: '王五' }, { role: 'frontend', name: '钱七' }, { role: 'backend', name: '吴十' }, { role: 'testing', name: '郑十一' }], progress: [{ role: 'product', percent: 100, daysSpent: 4 }, { role: 'ui', percent: 100, daysSpent: 5 }, { role: 'frontend', percent: 30, daysSpent: 5 }, { role: 'backend', percent: 20, daysSpent: 4 }, { role: 'testing', percent: 0, daysSpent: 0 }] },
|
||||
{ id: 'v14', name: '会员V1.0', status: 'released', releaseDate: '2024-05-10', createdAt: '2024-04-20', currentStage: 'released', startDate: '2024-04-20', members: [{ role: 'product', name: '张三' }, { role: 'frontend', name: '赵六' }, { role: 'backend', name: '吴十' }, { role: 'testing', name: '郑十一' }], progress: [{ role: 'product', percent: 100, daysSpent: 4 }, { role: 'frontend', percent: 100, daysSpent: 9 }, { role: 'backend', percent: 100, daysSpent: 7 }, { role: 'testing', percent: 100, daysSpent: 3 }] },
|
||||
{ id: 'v15', name: '会员V1.1', status: 'developing', releaseDate: null, createdAt: '2024-06-03', currentStage: 'integration', startDate: '2024-06-03', expectedReleaseDate: '2024-06-25', priority: 'P2', members: [{ role: 'product', name: '张三' }, { role: 'ui', name: '王五' }, { role: 'frontend', name: '赵六' }, { role: 'backend', name: '吴十' }, { role: 'testing', name: '郑十一' }], progress: [{ role: 'product', percent: 100, daysSpent: 3 }, { role: 'ui', percent: 100, daysSpent: 4 }, { role: 'frontend', percent: 90, daysSpent: 8 }, { role: 'backend', percent: 85, daysSpent: 7 }, { role: 'testing', percent: 0, daysSpent: 0 }] },
|
||||
{ id: 'v16', name: '会员V2.0', status: 'planned', releaseDate: null, createdAt: '2024-06-08' },
|
||||
],
|
||||
_count: { requirements: 8, projects: 2, versions: 6 },
|
||||
versions: [],
|
||||
_count: { requirements: 10, projects: 1, versions: 0 },
|
||||
},
|
||||
];
|
||||
|
||||
@@ -111,6 +78,7 @@ interface ProductState {
|
||||
createProject: (productId: string, data: { name: string; description?: string }) => void;
|
||||
createVersion: (productId: string, data: { name: string; status: string }) => void;
|
||||
updateVersion: (productId: string, versionId: string, data: Partial<VersionItem>) => void;
|
||||
deleteVersion: (productId: string, versionId: string) => void;
|
||||
}
|
||||
|
||||
export const useProductStore = create<ProductState>((set, get) => ({
|
||||
@@ -259,12 +227,13 @@ export const useProductStore = create<ProductState>((set, get) => ({
|
||||
},
|
||||
|
||||
createVersion: (productId, data) => {
|
||||
const newVersion = {
|
||||
const newVersion: VersionItem = {
|
||||
id: `ver-${Date.now()}`,
|
||||
name: data.name,
|
||||
status: data.status,
|
||||
releaseDate: null,
|
||||
createdAt: new Date().toISOString(),
|
||||
...(data as any),
|
||||
};
|
||||
const updated = get().overview.map((p) => {
|
||||
if (p.id !== productId) return p;
|
||||
@@ -291,9 +260,22 @@ export const useProductStore = create<ProductState>((set, get) => ({
|
||||
set({ overview: updated });
|
||||
saveLocal(updated);
|
||||
},
|
||||
|
||||
deleteVersion: (productId, versionId) => {
|
||||
const updated = get().overview.map((p) => {
|
||||
if (p.id !== productId) return p;
|
||||
return {
|
||||
...p,
|
||||
versions: p.versions.filter((v) => v.id !== versionId),
|
||||
_count: { ...p._count, versions: Math.max(0, (p._count?.versions ?? 0) - 1) } as any,
|
||||
};
|
||||
});
|
||||
set({ overview: updated });
|
||||
saveLocal(updated);
|
||||
},
|
||||
}));
|
||||
|
||||
const STORAGE_KEY = 'ftb_products_overview_v2';
|
||||
const STORAGE_KEY = 'ftb_products_overview_v4';
|
||||
|
||||
function saveLocal(data: ProductOverview[]) {
|
||||
try { localStorage.setItem(STORAGE_KEY, JSON.stringify(data)); } catch {}
|
||||
|
||||
@@ -76,151 +76,21 @@ const PRESET_PLATFORMS: DictItem[] = [
|
||||
// --- 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',
|
||||
},
|
||||
{ id: 'req-1', code: 'REQ-001', title: '值班排班自动生成', description: '根据员工可用时间自动排班,支持轮换规则', productId: 'mock-1', projectId: 'p3', status: 'pending_review', priority: 'P1', effort: 'L', sourceType: 'internal', sourceTarget: '运营部', platforms: ['platform-1'], typeId: 'type-1', creator: '张三', createdAt: '2024-06-01' },
|
||||
{ id: 'req-2', code: 'REQ-002', title: '值班交接确认', description: '交接班时双方确认签到,记录交接内容', productId: 'mock-1', projectId: 'p3', status: 'pending_review', priority: 'P1', effort: 'M', sourceType: 'internal', sourceTarget: '门店经理', platforms: ['platform-1'], typeId: 'type-1', creator: '张三', createdAt: '2024-06-01' },
|
||||
{ id: 'req-3', code: 'REQ-003', title: '值班异常提醒', description: '迟到、缺勤自动推送提醒给店长', productId: 'mock-1', projectId: 'p3', status: 'pending_review', priority: 'P0', effort: 'M', sourceType: 'customer', sourceTarget: '连锁品牌A', platforms: ['platform-1', 'platform-2'], typeId: 'type-1', creator: '李四', createdAt: '2024-06-02' },
|
||||
{ id: 'req-4', code: 'REQ-004', title: '值班日历视图', description: '以日历形式展示当月值班安排', productId: 'mock-1', projectId: 'p3', status: 'pending_review', priority: 'P2', effort: 'L', sourceType: 'internal', sourceTarget: '产品部', platforms: ['platform-1'], typeId: 'type-2', creator: '张三', createdAt: '2024-06-03' },
|
||||
{ id: 'req-5', code: 'REQ-005', title: '值班换班申请', description: '员工可发起换班申请,经理审批', productId: 'mock-1', projectId: 'p3', status: 'pending_review', priority: 'P2', effort: 'M', sourceType: 'customer', sourceTarget: '连锁品牌B', platforms: ['platform-1'], typeId: 'type-1', creator: '李四', createdAt: '2024-06-03' },
|
||||
{ id: 'req-6', code: 'REQ-006', title: '值班统计报表', description: '按月/周统计各员工值班时长、缺勤次数', productId: 'mock-1', projectId: 'p3', status: 'pending_review', priority: 'P2', effort: 'L', sourceType: 'internal', sourceTarget: '运营部', platforms: ['platform-1'], typeId: 'type-3', creator: '张三', createdAt: '2024-06-04' },
|
||||
{ id: 'req-7', code: 'REQ-007', title: '多门店值班管理', description: '区域经理可查看管辖所有门店值班情况', productId: 'mock-1', projectId: 'p3', status: 'pending_review', priority: 'P1', effort: 'XL', sourceType: 'management', sourceTarget: '区域总监', platforms: ['platform-1', 'platform-3'], typeId: 'type-1', creator: '张三', createdAt: '2024-06-05' },
|
||||
{ id: 'req-8', code: 'REQ-008', title: '值班备注功能', description: '排班时可添加备注(如特殊事项提醒)', productId: 'mock-1', projectId: 'p3', status: 'pending_review', priority: 'P3', effort: 'S', sourceType: 'internal', sourceTarget: '产品部', platforms: ['platform-1'], typeId: 'type-2', creator: '李四', createdAt: '2024-06-05' },
|
||||
{ id: 'req-9', code: 'REQ-009', title: '法定节假日排班规则', description: '节假日自动应用特殊排班规则(加班费标记)', productId: 'mock-1', projectId: 'p3', status: 'pending_review', priority: 'P1', effort: 'M', sourceType: 'operation', sourceTarget: '合规部', platforms: ['platform-1'], typeId: 'type-1', creator: '张三', createdAt: '2024-06-06' },
|
||||
{ id: 'req-10', code: 'REQ-010', title: '值班模板管理', description: '创建值班模板供快速复用(如早中晚三班)', productId: 'mock-1', projectId: 'p3', status: 'pending_review', priority: 'P2', effort: 'M', sourceType: 'internal', sourceTarget: '产品部', platforms: ['platform-1'], typeId: 'type-1', creator: '张三', createdAt: '2024-06-06' },
|
||||
];
|
||||
|
||||
// --- localStorage helpers ---
|
||||
|
||||
const STORAGE_KEY = 'ftb_requirements_v2';
|
||||
const STORAGE_KEY = 'ftb_requirements_v3';
|
||||
|
||||
function saveLocal(state: { requirements: Requirement[]; sourceTargets: SourceTarget[]; types: DictItem[]; platforms: DictItem[] }) {
|
||||
try {
|
||||
|
||||
62
apps/web/stores/useVersionPlanStore.ts
Normal file
62
apps/web/stores/useVersionPlanStore.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
'use client';
|
||||
import { create } from 'zustand';
|
||||
import type { VersionPlan, PlanType } from '@/lib/version-plan';
|
||||
|
||||
const STORAGE_KEY = 'ftb_version_plans_v1';
|
||||
|
||||
const MOCK_PLANS: VersionPlan[] = [];
|
||||
|
||||
function saveLocal(plans: VersionPlan[]) {
|
||||
try { localStorage.setItem(STORAGE_KEY, JSON.stringify(plans)); } catch {}
|
||||
}
|
||||
|
||||
function loadLocal(): VersionPlan[] | null {
|
||||
try {
|
||||
const raw = localStorage.getItem(STORAGE_KEY);
|
||||
if (raw) return JSON.parse(raw);
|
||||
} catch {}
|
||||
return null;
|
||||
}
|
||||
|
||||
interface VersionPlanState {
|
||||
plans: VersionPlan[];
|
||||
fetchPlans: () => void;
|
||||
createPlan: (data: Omit<VersionPlan, 'id' | 'createdAt'>) => void;
|
||||
updatePlan: (id: string, data: Partial<VersionPlan>) => void;
|
||||
completePlan: (id: string, result: { resultType: 'link' | 'file'; resultUrl?: string; resultFileName?: string; resultFileData?: string }) => void;
|
||||
deletePlan: (id: string) => void;
|
||||
}
|
||||
|
||||
export const useVersionPlanStore = create<VersionPlanState>((set, get) => ({
|
||||
plans: MOCK_PLANS,
|
||||
|
||||
fetchPlans: () => {
|
||||
const cached = loadLocal();
|
||||
if (cached) set({ plans: cached });
|
||||
},
|
||||
|
||||
createPlan: (data) => {
|
||||
const plan: VersionPlan = { ...data, id: `plan-${Date.now()}`, createdAt: new Date().toISOString().slice(0, 10) };
|
||||
const plans = [...get().plans, plan];
|
||||
set({ plans });
|
||||
saveLocal(plans);
|
||||
},
|
||||
|
||||
updatePlan: (id, data) => {
|
||||
const plans = get().plans.map((p) => p.id === id ? { ...p, ...data } : p);
|
||||
set({ plans });
|
||||
saveLocal(plans);
|
||||
},
|
||||
|
||||
completePlan: (id, result) => {
|
||||
const plans = get().plans.map((p) => p.id === id ? { ...p, ...result, status: 'completed' as const, completedAt: new Date().toISOString() } : p);
|
||||
set({ plans });
|
||||
saveLocal(plans);
|
||||
},
|
||||
|
||||
deletePlan: (id) => {
|
||||
const plans = get().plans.filter((p) => p.id !== id);
|
||||
set({ plans });
|
||||
saveLocal(plans);
|
||||
},
|
||||
}));
|
||||
Reference in New Issue
Block a user