恢复修改的数据
This commit is contained in:
291
src/api/cases.ts
291
src/api/cases.ts
@@ -1,227 +1,90 @@
|
||||
import type { BusinessType, PageResult } from '@/types/api'
|
||||
import type { Case, CaseQuery, ScaleRange } from '@/types/business/cases'
|
||||
// 案例库接口(真实后端绑定)
|
||||
// 后端: vermis-maker com.ruoyi.vermins.controller.pc.web.ProjectCaseUserController
|
||||
// 前缀: /web/projectCase
|
||||
//
|
||||
// 使用约定(与项目 SKILL.md 一致):
|
||||
// 页面/组件禁止直接 import axios,一律经 store 调用本模块的 request<T>。
|
||||
// 本文件只放「函数签名 + 请求路径」,不做数据加工、不维护状态。
|
||||
import { request, serializeQuery } from '@/utils/request'
|
||||
import type {
|
||||
CaseSelectionModel,
|
||||
ProjectCaseAndCaseCountVo,
|
||||
ProjectCaseUserBo,
|
||||
ProjectCaseVo,
|
||||
} from '@/types/business/cases'
|
||||
|
||||
// TODO(api): 待接口文档到位后,将以下 mock 替换为 request<T> 调用
|
||||
|
||||
/** 案例 mock(可变,收藏状态在前端内存中翻转演示) */
|
||||
const MOCK_CASES: Case[] = [
|
||||
{
|
||||
id: 'case-001',
|
||||
title: '自动化生产线升级改造项目',
|
||||
category: 'manufacturing',
|
||||
cover: '',
|
||||
finishDate: '2026/8/10',
|
||||
teamSize: 50,
|
||||
scale: '300万',
|
||||
industry: '自动化',
|
||||
duration: '150个工作日',
|
||||
clientType: '企业',
|
||||
background: '传统产线效率低、人工依赖高,客户需要通过自动化升级提升产能与品控水平。',
|
||||
painPoints: '1、产线依赖人工巡检,异常响应慢;2、设备数据孤岛,缺乏统一监控;3、工艺参数无法追溯。',
|
||||
requirements: '建设产线数据采集与监控平台,完成关键工位自动化改造,打通 MES 系统接口。',
|
||||
deliverables: '1、产线自动化改造方案;2、PLC 控制程序与上位机软件;3、数据看板与运维手册;4、培训与质保服务。',
|
||||
video: '',
|
||||
isFavorite: false,
|
||||
},
|
||||
{
|
||||
id: 'case-002',
|
||||
title: '智能工厂物联网整体解决方案',
|
||||
category: 'iot',
|
||||
cover: '',
|
||||
finishDate: '2026/8/10',
|
||||
teamSize: 30,
|
||||
scale: '500万',
|
||||
industry: '工厂服务',
|
||||
duration: '200个工作日',
|
||||
clientType: '企业',
|
||||
background: '园区工厂多系统独立运行,能耗与设备管理粗放,需要整体数字化升级。',
|
||||
painPoints: '1、门禁/监控/能耗子系统数据不互通;2、能源浪费缺乏精细监测;3、决策缺少实时数据支撑。',
|
||||
requirements: '构建 IoT 平台接入设备数据,建设综合驾驶舱,实现能耗监测与智能告警。',
|
||||
deliverables: '1、IoT 接入平台;2、综合驾驶舱大屏;3、能耗分析系统;4、集成联调与培训服务。',
|
||||
video: '',
|
||||
isFavorite: false,
|
||||
},
|
||||
{
|
||||
id: 'case-003',
|
||||
title: '智能穿戴设备PCB设计与量产',
|
||||
category: 'hardware',
|
||||
cover: '',
|
||||
finishDate: '2026/8/10',
|
||||
teamSize: 50,
|
||||
scale: '10-50万',
|
||||
industry: '智能设备',
|
||||
duration: '30个工作日',
|
||||
clientType: '企业',
|
||||
background: '客户推出新一代智能穿戴产品,需要高集成度低功耗 PCB 设计并快速量产。',
|
||||
painPoints: '1、板载空间受限,器件布局密集;2、功耗要求严格;3、量产良率需爬坡。',
|
||||
requirements: '完成原理图与 PCB 设计、打样验证、SMT 贴片与整机装配量产。',
|
||||
deliverables: '1、原理图与 PCB 设计文件;2、样板与测试报告;3、量产良率报告;4、BOM 与工艺文件。',
|
||||
video: '',
|
||||
isFavorite: false,
|
||||
},
|
||||
{
|
||||
id: 'case-004',
|
||||
title: '智能园区综合管理平台开发',
|
||||
category: 'internet',
|
||||
cover: '',
|
||||
finishDate: '2026/8/10',
|
||||
teamSize: 20,
|
||||
scale: '200万',
|
||||
industry: '园区服务',
|
||||
duration: '200个工作日',
|
||||
clientType: '企业',
|
||||
background: '传统园区管理普遍"数据孤岛",安防、能耗、物业、招商等系统独立运行,缺乏联动,管理手段依赖人工巡检和纸质流程。',
|
||||
painPoints: '1、运营效率低下:安保依赖人工巡逻;2、系统孤岛严重:门禁、监控、停车、能耗等子系统独立建设;3、能源浪费突出;4、服务体验欠佳;5、决策缺乏依据;6、安全隐患犹存。',
|
||||
requirements: '打造集"感知、预警、分析、决策、服务"于一体的智能园区综合管理平台,打通园区各子系统壁垒,实现全场景数字化覆盖。',
|
||||
deliverables: '1、Web 端管理平台(驾驶舱/安防/能源/物业);2、移动端服务门户;3、IoT 平台与数据大屏;4、全套技术文档;5、培训与不少于1年免费质保。',
|
||||
video: '',
|
||||
isFavorite: false,
|
||||
},
|
||||
{
|
||||
id: 'case-005',
|
||||
title: '电商中台系统定制开发',
|
||||
category: 'internet',
|
||||
cover: '',
|
||||
finishDate: '2026/7/20',
|
||||
teamSize: 15,
|
||||
scale: '150万',
|
||||
industry: '电商零售',
|
||||
duration: '120个工作日',
|
||||
clientType: '企业',
|
||||
background: '品牌方多渠道订单分散,库存与会员数据不通,需要统一电商中台。',
|
||||
painPoints: '1、多平台库存不同步;2、会员体系割裂;3、营销活动上线周期长。',
|
||||
requirements: '建设商品/库存/订单/会员中台,开放 API 对接主流电商平台。',
|
||||
deliverables: '1、中台系统与管理后台;2、开放 API 与对接文档;3、数据迁移方案;4、上线陪跑服务。',
|
||||
video: '',
|
||||
isFavorite: false,
|
||||
},
|
||||
{
|
||||
id: 'case-006',
|
||||
title: '教培机构在线学习平台',
|
||||
category: 'internet',
|
||||
cover: '',
|
||||
finishDate: '2026/6/30',
|
||||
teamSize: 12,
|
||||
scale: '80万',
|
||||
industry: '教育培训',
|
||||
duration: '90个工作日',
|
||||
clientType: '企业',
|
||||
background: '教培机构需要线上授课、作业批改与学员管理一体化能力。',
|
||||
painPoints: '1、授课依赖第三方工具,数据不沉淀;2、续费转化缺少数据支撑。',
|
||||
requirements: '交付直播授课、题库作业、班级管理、数据分析完整平台(Web + 小程序)。',
|
||||
deliverables: '1、Web 学习平台;2、学员端小程序;3、题库与直播模块;4、运维部署手册。',
|
||||
video: '',
|
||||
isFavorite: false,
|
||||
},
|
||||
{
|
||||
id: 'case-007',
|
||||
title: '医院智能随访系统开发',
|
||||
category: 'internet',
|
||||
cover: '',
|
||||
finishDate: '2026/5/18',
|
||||
teamSize: 18,
|
||||
scale: '250万',
|
||||
industry: '医疗健康',
|
||||
duration: '180个工作日',
|
||||
clientType: '企业',
|
||||
background: '三甲医院需要对出院患者进行结构化随访,提升复诊率与科研数据质量。',
|
||||
painPoints: '1、电话随访效率低;2、随访数据非结构化,科研难复用。',
|
||||
requirements: '建设随访计划引擎、患者端触达与随访数据仓,对接 HIS 系统。',
|
||||
deliverables: '1、随访管理平台;2、患者端小程序;3、HIS 对接中间件;4、随访报表与数据字典。',
|
||||
video: '',
|
||||
isFavorite: false,
|
||||
},
|
||||
{
|
||||
id: 'case-008',
|
||||
title: '园区光伏能耗管理系统',
|
||||
category: 'iot',
|
||||
cover: '',
|
||||
finishDate: '2026/4/25',
|
||||
teamSize: 10,
|
||||
scale: '120万',
|
||||
industry: '能源环保',
|
||||
duration: '60个工作日',
|
||||
clientType: '企业',
|
||||
background: '园区部署屋顶光伏后需要实时监控发电与用电,实现削峰填谷。',
|
||||
painPoints: '1、发电数据分散在多品牌逆变器;2、能耗缺少分项计量。',
|
||||
requirements: '接入逆变器/电表数据,建设发电与能耗看板,输出节能策略。',
|
||||
deliverables: '1、能耗采集网关适配;2、监控看板系统;3、告警与报表;4、验收测试报告。',
|
||||
video: '',
|
||||
isFavorite: false,
|
||||
},
|
||||
]
|
||||
|
||||
const SCALE_BOUNDS: Record<ScaleRange, [number, number]> = {
|
||||
lt50w: [0, 50],
|
||||
'50to100w': [50, 100],
|
||||
'100to300w': [100, 300],
|
||||
'300to500w': [300, 500],
|
||||
gt500w: [500, Number.POSITIVE_INFINITY],
|
||||
}
|
||||
|
||||
/** 从展示值解析规模数值(万),如 '300万' → 300,'10-50万' → 10 */
|
||||
function parseScaleValue(scale: string): number {
|
||||
const value = Number.parseFloat(scale)
|
||||
return Number.isNaN(value) ? 0 : value
|
||||
}
|
||||
|
||||
function matchScale(item: Case, selected: ScaleRange[]): boolean {
|
||||
if (selected.length === 0) return true
|
||||
const value = parseScaleValue(item.scale)
|
||||
return selected.some((key) => {
|
||||
const [min, max] = SCALE_BOUNDS[key]
|
||||
return value >= min && value < max
|
||||
/**
|
||||
* 案例库分页列表(返回列表 + 业务数据统计)。
|
||||
*
|
||||
* <p>规模区间是嵌套对象(`scales[0].lowerLimit`),必须用 serializeQuery 序列化——
|
||||
* axios 默认的 `scales[0][lowerLimit]` 后端绑不上,实测 HTTP 500。</p>
|
||||
*
|
||||
* @param bo 筛选条件(businessTypes 按案例表 business_type 原值匹配)
|
||||
* @param pageNum 页码,从 1 开始(后端 PageQuery)
|
||||
* @param pageSize 每页条数
|
||||
*/
|
||||
export function getCaseList(
|
||||
bo: ProjectCaseUserBo,
|
||||
pageNum: number,
|
||||
pageSize: number,
|
||||
): Promise<ProjectCaseAndCaseCountVo> {
|
||||
return request<ProjectCaseAndCaseCountVo>({
|
||||
url: '/web/projectCase/list',
|
||||
method: 'get',
|
||||
params: { ...bo, pageNum, pageSize },
|
||||
paramsSerializer: { serialize: serializeQuery },
|
||||
})
|
||||
}
|
||||
|
||||
function matchDate(item: Case, range: [string, string] | null): boolean {
|
||||
if (!range) return true
|
||||
const time = new Date(item.finishDate).getTime()
|
||||
const start = new Date(range[0]).getTime()
|
||||
const end = new Date(range[1]).getTime()
|
||||
return time >= start && time <= end
|
||||
}
|
||||
|
||||
function applyFilters(query: CaseQuery): Case[] {
|
||||
return MOCK_CASES.filter((item) => {
|
||||
if (query.category !== 'all' && item.category !== query.category) return false
|
||||
if (query.industries.length > 0 && !query.industries.includes(item.industry)) return false
|
||||
if (!matchScale(item, query.scales)) return false
|
||||
if (!matchDate(item, query.dateRange)) return false
|
||||
if (query.keyword && !item.title.includes(query.keyword)) return false
|
||||
return true
|
||||
/**
|
||||
* 案例详情(含项目背景富文本与附件,并回填当前用户是否已收藏)。
|
||||
*
|
||||
* <p>注意:该接口每次调用都会把案例预览量 +1,勿在轮询 / 重试场景滥用。</p>
|
||||
*/
|
||||
export function getCaseDetail(id: string): Promise<ProjectCaseVo | null> {
|
||||
return request<ProjectCaseVo | null>({
|
||||
url: `/web/projectCase/${id}`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
/** 案例列表(筛选 + 分页) */
|
||||
export function getCaseList(query: CaseQuery): Promise<PageResult<Case>> {
|
||||
const filtered = applyFilters(query)
|
||||
const start = (query.page - 1) * query.pageSize
|
||||
return Promise.resolve({
|
||||
list: filtered.slice(start, start + query.pageSize),
|
||||
total: filtered.length,
|
||||
/**
|
||||
* 案例收藏 / 取消收藏。
|
||||
*
|
||||
* <p>后端为 toggle 语义:已收藏则取消收藏。需登录态,匿名调用会被后端拦截。</p>
|
||||
*/
|
||||
export function collectCase(id: string): Promise<void> {
|
||||
return request<void>({
|
||||
url: '/web/projectCase/collect',
|
||||
method: 'post',
|
||||
data: { id },
|
||||
})
|
||||
}
|
||||
|
||||
/** 案例详情(不存在返回 null) */
|
||||
export function getCaseDetail(id: string): Promise<Case | null> {
|
||||
const found = MOCK_CASES.find((item) => item.id === id) ?? null
|
||||
return Promise.resolve(found)
|
||||
/**
|
||||
* 筛选面板选项(业务类型统计 + 行业 + 规模档位)。
|
||||
*
|
||||
* <p>左侧筛选除完成时间外的选项都由本接口一次下发:业务类型与案例总数在
|
||||
* {@code projectCaseStatisticsVo},行业在 {@code businessIndustry},规模在
|
||||
* {@code upperAndLowerLimits}(label 已由后端拼好)。</p>
|
||||
*/
|
||||
export function getCaseCategories(): Promise<CaseSelectionModel> {
|
||||
return request<CaseSelectionModel>({
|
||||
url: '/web/projectCase/categories',
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
/** 收藏/取消收藏,返回最新案例数据 */
|
||||
export function toggleFavorite(id: string): Promise<Case> {
|
||||
const found = MOCK_CASES.find((item) => item.id === id)
|
||||
if (!found) return Promise.reject(new Error('案例不存在'))
|
||||
found.isFavorite = !found.isFavorite
|
||||
return Promise.resolve({ ...found })
|
||||
}
|
||||
|
||||
/** 业务类型计数(筛选面板角标) */
|
||||
export function getCaseCounts(): Promise<Partial<Record<BusinessType, number>>> {
|
||||
const counts: Partial<Record<BusinessType, number>> = {}
|
||||
for (const item of MOCK_CASES) {
|
||||
counts[item.category] = (counts[item.category] ?? 0) + 1
|
||||
}
|
||||
return Promise.resolve(counts)
|
||||
/**
|
||||
* 案例浏览王(首页「案例库精选」)。
|
||||
*
|
||||
* <p>不传 type 时返回「各业务类型的浏览王」各一条;传 type 时按案例表
|
||||
* business_type 的原值精确匹配,返回该类型全部案例。</p>
|
||||
*/
|
||||
export function getFeaturedCases(type?: string): Promise<ProjectCaseVo[]> {
|
||||
return request<ProjectCaseVo[]>({
|
||||
url: '/web/projectCase/mostViewed',
|
||||
method: 'get',
|
||||
params: type ? { type } : undefined,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user