first
This commit is contained in:
96
src/api/auth.ts
Normal file
96
src/api/auth.ts
Normal file
@@ -0,0 +1,96 @@
|
||||
import type {
|
||||
LoginByAccountParams,
|
||||
LoginBySmsParams,
|
||||
LoginResult,
|
||||
RegisterParams,
|
||||
ResetPasswordParams,
|
||||
User,
|
||||
} from '@/types/business/user'
|
||||
import { isPhone, isValidPassword } from '@/utils/validate'
|
||||
|
||||
// TODO(api): 待接口文档到位后,将以下 mock 替换为 request<T> 调用(页面 → store → api 数据流不变)
|
||||
|
||||
const SIX_DIGIT_CODE = /^\d{6}$/
|
||||
|
||||
/** 发送短信验证码 */
|
||||
export function sendSmsCode(phone: string): Promise<void> {
|
||||
if (!isPhone(phone)) return Promise.reject(new Error('手机号格式不正确'))
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
/** 账号密码登录(mock:demo 账号可登录,其余提示先注册) */
|
||||
export function loginByAccount(params: LoginByAccountParams): Promise<LoginResult> {
|
||||
const { account, password } = params
|
||||
if (!account.trim() || !password) {
|
||||
return Promise.reject(new Error('请输入账号和密码'))
|
||||
}
|
||||
if (account === 'demo') {
|
||||
const user: User = {
|
||||
id: 'u1',
|
||||
nickname: '王永志',
|
||||
avatar: '',
|
||||
phone: '186****618',
|
||||
email: 'yongzh******4@163.com',
|
||||
passwordModifiedAt: '',
|
||||
}
|
||||
return Promise.resolve({ token: `mock-token-${Date.now()}`, user })
|
||||
}
|
||||
return Promise.reject(new Error('登录失败,请先注册账号'))
|
||||
}
|
||||
|
||||
/** 手机验证码登录(mock:验证码须为 6 位数字) */
|
||||
export function loginBySms(params: LoginBySmsParams): Promise<LoginResult> {
|
||||
const { phone, code } = params
|
||||
if (!isPhone(phone)) return Promise.reject(new Error('手机号格式不正确'))
|
||||
if (!SIX_DIGIT_CODE.test(code)) return Promise.reject(new Error('验证码格式不正确,应为6位数字'))
|
||||
const user: User = {
|
||||
id: 'u1',
|
||||
nickname: '王永志',
|
||||
avatar: '',
|
||||
phone: '186****618',
|
||||
email: 'yongzh******4@163.com',
|
||||
passwordModifiedAt: '',
|
||||
}
|
||||
return Promise.resolve({ token: `mock-token-${Date.now()}`, user })
|
||||
}
|
||||
|
||||
function validateAuthForm(
|
||||
phone: string,
|
||||
code: string,
|
||||
password: string,
|
||||
confirmPassword: string,
|
||||
agree: boolean,
|
||||
): string | null {
|
||||
if (!isPhone(phone)) return '手机号格式不正确'
|
||||
if (!SIX_DIGIT_CODE.test(code)) return '验证码格式不正确,应为6位数字'
|
||||
if (!isValidPassword(password)) return '密码长度应为6-20位'
|
||||
if (password !== confirmPassword) return '两次输入的密码不一致'
|
||||
if (!agree) return '请先阅读并同意《用户协议》和《隐私政策》'
|
||||
return null
|
||||
}
|
||||
|
||||
/** 注册 */
|
||||
export function register(params: RegisterParams): Promise<void> {
|
||||
const message = validateAuthForm(
|
||||
params.phone,
|
||||
params.code,
|
||||
params.password,
|
||||
params.confirmPassword,
|
||||
params.agree,
|
||||
)
|
||||
if (message) return Promise.reject(new Error(message))
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
/** 重置密码 */
|
||||
export function resetPassword(params: ResetPasswordParams): Promise<void> {
|
||||
const message = validateAuthForm(
|
||||
params.phone,
|
||||
params.code,
|
||||
params.password,
|
||||
params.confirmPassword,
|
||||
params.agree,
|
||||
)
|
||||
if (message) return Promise.reject(new Error(message))
|
||||
return Promise.resolve()
|
||||
}
|
||||
227
src/api/cases.ts
Normal file
227
src/api/cases.ts
Normal file
@@ -0,0 +1,227 @@
|
||||
import type { BusinessType, PageResult } from '@/types/api'
|
||||
import type { Case, CaseQuery, ScaleRange } 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
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
/** 案例列表(筛选 + 分页) */
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
/** 案例详情(不存在返回 null) */
|
||||
export function getCaseDetail(id: string): Promise<Case | null> {
|
||||
const found = MOCK_CASES.find((item) => item.id === id) ?? null
|
||||
return Promise.resolve(found)
|
||||
}
|
||||
|
||||
/** 收藏/取消收藏,返回最新案例数据 */
|
||||
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)
|
||||
}
|
||||
110
src/api/contract.ts
Normal file
110
src/api/contract.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
import type { FileInfo, PageResult } from '@/types/api'
|
||||
import type { Contract, ContractQuery } from '@/types/business/contract'
|
||||
|
||||
// TODO(api): 待接口文档到位后,将以下 mock 替换为 request<T> 调用
|
||||
|
||||
/** 构造 mock 文件信息 */
|
||||
function mockFile(name: string, size: number, uploadTime: string): FileInfo {
|
||||
return { name, size, uploadTime, url: '' }
|
||||
}
|
||||
|
||||
/** 详情 mock 所属合同 id */
|
||||
const DETAIL_ID = 'contract-001'
|
||||
|
||||
/** 合同列表 mock(3 条,覆盖全部签署状态) */
|
||||
const MOCK_CONTRACTS: Contract[] = [
|
||||
{
|
||||
id: 'contract-001',
|
||||
contractNo: 'C8K2M202408001',
|
||||
contractName: '智慧城市建设项目技术服务合同',
|
||||
contractType: '互联网软件通用/app开发',
|
||||
contractAmount: 201000,
|
||||
relatedProjectName: '智慧城市大数据平台建设项目',
|
||||
projectNo: 'P8K2M202408001',
|
||||
signStatus: '履行中',
|
||||
signDate: '2026-8-17 14:00',
|
||||
effectiveDate: '2026-8-17 14:00',
|
||||
expiryDate: '2026-8-17 14:00',
|
||||
files: [mockFile('项目需求规格说明书.pdf', 2516582, '2026-8-17 14:00')],
|
||||
},
|
||||
{
|
||||
id: 'contract-002',
|
||||
contractNo: 'C8K2M202408002',
|
||||
contractName: '智慧城市建设项目硬件电路设计合同',
|
||||
contractType: '硬件电路通用/电路版设计',
|
||||
contractAmount: 180000,
|
||||
relatedProjectName: '三甲医院指挥医疗信息化系统',
|
||||
projectNo: 'P8K2M202408001',
|
||||
signStatus: '已过期',
|
||||
signDate: '2026-8-17 14:00',
|
||||
effectiveDate: '2026-8-17 14:00',
|
||||
expiryDate: '2026-8-17 14:00',
|
||||
files: [mockFile('硬件电路设计合同.pdf', 1468006, '2026-8-17 14:00')],
|
||||
},
|
||||
{
|
||||
id: 'contract-003',
|
||||
contractNo: 'C8K2M202408003',
|
||||
contractName: '智慧城市建设项目物联网全案合同',
|
||||
contractType: '物联网全案通用/一套门禁卡',
|
||||
contractAmount: 96000,
|
||||
relatedProjectName: '社区康养智能服务系统',
|
||||
projectNo: 'P8K2M202408001',
|
||||
signStatus: '待签署',
|
||||
effectiveDate: '2026-8-17 14:00',
|
||||
expiryDate: '2026-8-17 14:00',
|
||||
files: [mockFile('物联网全案合同.pdf', 1258291, '2026-8-17 14:00')],
|
||||
},
|
||||
]
|
||||
|
||||
/** 合同详情 mock */
|
||||
const CONTRACT_DETAIL: Contract = {
|
||||
id: DETAIL_ID,
|
||||
contractNo: 'C8K2M202408001',
|
||||
contractName: '社区康养智能服务系统开发合同',
|
||||
contractType: '互联网软件开发',
|
||||
contractAmount: 201000,
|
||||
relatedProjectName: '社区康养智能服务系统',
|
||||
projectNo: '20260813ZZ251245',
|
||||
signStatus: '履行中',
|
||||
signDate: '2024-10-15',
|
||||
effectiveDate: '2024-10-15',
|
||||
expiryDate: '2024-10-15',
|
||||
files: [mockFile('项目需求规格说明书.pdf', 2516582, '2024-10-15')],
|
||||
}
|
||||
|
||||
/** 合同列表(keyword 筛选 + 分页) */
|
||||
export function getContractList(query: ContractQuery): Promise<PageResult<Contract>> {
|
||||
const filtered = MOCK_CONTRACTS.filter((item) => {
|
||||
if (
|
||||
query.keyword
|
||||
&& !item.contractName.includes(query.keyword)
|
||||
&& !item.contractNo.includes(query.keyword)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
const pageSize = query.pageSize > 0 ? query.pageSize : 8
|
||||
const start = (query.page - 1) * pageSize
|
||||
return Promise.resolve({
|
||||
list: filtered.slice(start, start + pageSize),
|
||||
total: filtered.length,
|
||||
})
|
||||
}
|
||||
|
||||
/** 合同详情(不存在返回 null) */
|
||||
export function getContractDetail(id: string): Promise<Contract | null> {
|
||||
if (id === DETAIL_ID) return Promise.resolve({ ...CONTRACT_DETAIL, files: [...CONTRACT_DETAIL.files] })
|
||||
const found = MOCK_CONTRACTS.find((item) => item.id === id) ?? null
|
||||
return Promise.resolve(found ? { ...found, files: [...found.files] } : null)
|
||||
}
|
||||
|
||||
/** 获取签署二维码(返回有效期) */
|
||||
export function getSignQr(_id: string): Promise<{ expiresInSeconds: number }> {
|
||||
return Promise.resolve({ expiresInSeconds: 272 })
|
||||
}
|
||||
|
||||
/** 刷新签署二维码(返回有效期) */
|
||||
export function refreshSignQr(_id: string): Promise<{ expiresInSeconds: number }> {
|
||||
return Promise.resolve({ expiresInSeconds: 272 })
|
||||
}
|
||||
25
src/api/demand.ts
Normal file
25
src/api/demand.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { Demand } from '@/types/business/demand'
|
||||
import { storage } from '@/utils/storage'
|
||||
|
||||
// TODO(api): 待接口文档到位后,将以下 mock 替换为 request<T> 调用(页面 → store → api 数据流不变)
|
||||
|
||||
const DRAFT_KEY = 'demand-draft'
|
||||
|
||||
/** 提交需求(mock:返回提报单 id) */
|
||||
export function submitDemand(demand: Demand): Promise<{ id: string }> {
|
||||
if (!demand.businessType || !demand.projectName || !demand.coreDescription) {
|
||||
return Promise.reject(new Error('需求信息不完整,无法提交'))
|
||||
}
|
||||
return Promise.resolve({ id: `demand-${Date.now()}` })
|
||||
}
|
||||
|
||||
/** 读取本地草稿(无草稿返回 null) */
|
||||
export function getDraftDemand(): Promise<Demand | null> {
|
||||
return Promise.resolve(storage.sessionGet<Demand>(DRAFT_KEY))
|
||||
}
|
||||
|
||||
/** 保存草稿到本地 */
|
||||
export function saveDraft(demand: Demand): Promise<void> {
|
||||
storage.sessionSet(DRAFT_KEY, { ...demand, status: 'draft' })
|
||||
return Promise.resolve()
|
||||
}
|
||||
316
src/api/finance.ts
Normal file
316
src/api/finance.ts
Normal file
@@ -0,0 +1,316 @@
|
||||
import type { PageResult } from '@/types/api'
|
||||
import type {
|
||||
Bill,
|
||||
BillQuery,
|
||||
FinanceStats,
|
||||
Invoice,
|
||||
InvoiceQuery,
|
||||
InvoiceTitle,
|
||||
InvoiceTitlePayload,
|
||||
PayMethod,
|
||||
Payment,
|
||||
PaymentResult,
|
||||
} from '@/types/business/finance'
|
||||
|
||||
// TODO(api): 待接口文档到位后,将以下 mock 替换为 request<T> 调用
|
||||
|
||||
/** 账单 mock(可变:支付后状态在前端内存中翻转演示) */
|
||||
const MOCK_BILLS: Bill[] = [
|
||||
{
|
||||
id: 'bill-001',
|
||||
planName: '第一期·预付款 比例33%',
|
||||
billNo: '20260813ZZ251245',
|
||||
tradeNo: 'T20260819153035678K2M001',
|
||||
projectName: '某大型银行数字化转型方案',
|
||||
amount: 20000,
|
||||
status: '已支付',
|
||||
createTime: '2026-8-17 14:00',
|
||||
},
|
||||
{
|
||||
id: 'bill-002',
|
||||
planName: '第二期·进度款 比例30%',
|
||||
billNo: '20260813ZZ251246',
|
||||
projectName: '智慧城市大数据平台建设项目',
|
||||
amount: 20100,
|
||||
status: '待支付',
|
||||
createTime: '2026-8-17 14:00',
|
||||
},
|
||||
{
|
||||
id: 'bill-003',
|
||||
planName: '第三期·验收款 比例27%',
|
||||
billNo: '20260813ZZ251247',
|
||||
projectName: '三甲医院指挥医疗信息化系统',
|
||||
amount: 20000,
|
||||
status: '未到期',
|
||||
createTime: '2026-8-17 14:00',
|
||||
},
|
||||
{
|
||||
id: 'bill-004',
|
||||
planName: '第四期·尾款 比例10%',
|
||||
billNo: '20260813ZZ251248',
|
||||
projectName: '智慧城市大数据平台建设项目',
|
||||
amount: 20100,
|
||||
status: '未到期',
|
||||
createTime: '2026-8-17 14:00',
|
||||
},
|
||||
{
|
||||
id: 'bill-005',
|
||||
planName: '第二期·进度款 比例30%',
|
||||
billNo: '20260813ZZ251249',
|
||||
projectName: '三甲医院指挥医疗信息化系统',
|
||||
amount: 20100,
|
||||
status: '待支付',
|
||||
createTime: '2026-8-16 10:30',
|
||||
},
|
||||
{
|
||||
id: 'bill-006',
|
||||
planName: '第一期·预付款 比例33%',
|
||||
billNo: '20260813ZZ251250',
|
||||
tradeNo: 'T20260819153035678K2M002',
|
||||
projectName: '某大型银行数字化转型方案',
|
||||
amount: 20000,
|
||||
status: '已支付',
|
||||
createTime: '2026-8-15 09:12',
|
||||
},
|
||||
]
|
||||
|
||||
/** 发票 mock(可变:申请开票后状态在前端内存中翻转演示) */
|
||||
const MOCK_INVOICES: Invoice[] = [
|
||||
{
|
||||
id: 'invoice-001',
|
||||
planName: '第一期·预付款 比例33%',
|
||||
billNo: '20260813ZZ251245',
|
||||
tradeNo: 'T20260819153035678K2M001',
|
||||
projectName: '某大型银行数字化转型方案',
|
||||
amount: 20000,
|
||||
status: '已开票',
|
||||
applyTime: '2026-8-17 14:00',
|
||||
},
|
||||
{
|
||||
id: 'invoice-002',
|
||||
planName: '第一期·预付款 比例33%',
|
||||
billNo: '20260813ZZ251250',
|
||||
tradeNo: 'T20260819153035678K2M002',
|
||||
projectName: '某大型银行数字化转型方案',
|
||||
amount: 20000,
|
||||
status: '开票中',
|
||||
applyTime: '2026-8-17 14:00',
|
||||
},
|
||||
{
|
||||
id: 'invoice-003',
|
||||
planName: '第二期·进度款 比例30%',
|
||||
billNo: '20260813ZZ251249',
|
||||
projectName: '三甲医院指挥医疗信息化系统',
|
||||
amount: 20100,
|
||||
status: '未开票',
|
||||
applyTime: '2026-8-17 14:00',
|
||||
},
|
||||
]
|
||||
|
||||
/** 发票抬头 mock(可变,CRUD 在前端内存中演示) */
|
||||
const MOCK_TITLES: InvoiceTitle[] = [
|
||||
{
|
||||
id: 'title-001',
|
||||
type: '企业单位',
|
||||
title: '山东日照有限有限公司',
|
||||
taxNo: '344683434513543543545535',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'title-002',
|
||||
type: '企业单位',
|
||||
title: '山东省一周练七次有限公司',
|
||||
taxNo: '6856754615216565476846',
|
||||
isDefault: false,
|
||||
},
|
||||
]
|
||||
|
||||
/** 支付进行单(billId → 当前二维码) */
|
||||
const MOCK_PAYMENTS = new Map<string, Payment>()
|
||||
|
||||
/** 已产生支付结果的账单(billId → 结果),预置已支付账单 */
|
||||
const MOCK_RESULTS = new Map<string, PaymentResult>()
|
||||
MOCK_RESULTS.set('bill-001', {
|
||||
method: 'wechat',
|
||||
amount: 20000,
|
||||
payTime: '2026-01-21 15:00:00',
|
||||
tradeNo: 'T20260819153035678K2M001',
|
||||
bills: [{ ...MOCK_BILLS[0] }],
|
||||
})
|
||||
MOCK_RESULTS.set('bill-006', {
|
||||
method: 'wechat',
|
||||
amount: 20000,
|
||||
payTime: '2026-01-21 15:00:00',
|
||||
tradeNo: 'T20260819153035678K2M002',
|
||||
bills: [{ ...MOCK_BILLS[5] }],
|
||||
})
|
||||
|
||||
const DEFAULT_STATS: FinanceStats = {
|
||||
totalExpense: 1726823900,
|
||||
month: '2026年1',
|
||||
pendingCount: 3,
|
||||
distribution: [
|
||||
{ name: '生产智造', value: 25, color: '#3370ff' },
|
||||
{ name: '物联网全案', value: 25, color: '#34c724' },
|
||||
{ name: '硬件电路', value: 25, color: '#ff8f1f' },
|
||||
{ name: '互联网软件', value: 25, color: '#f54a45' },
|
||||
],
|
||||
}
|
||||
|
||||
/** 财务统计(period 为展示周期参数,mock 恒返回原型数据) */
|
||||
export function getFinanceStats(period?: string): Promise<FinanceStats> {
|
||||
void period
|
||||
return Promise.resolve(structuredClone(DEFAULT_STATS))
|
||||
}
|
||||
|
||||
function matchBillKeyword(item: Bill, keyword: string): boolean {
|
||||
if (!keyword) return true
|
||||
return (
|
||||
item.planName.includes(keyword) ||
|
||||
item.billNo.includes(keyword) ||
|
||||
item.projectName.includes(keyword) ||
|
||||
(item.tradeNo?.includes(keyword) ?? false)
|
||||
)
|
||||
}
|
||||
|
||||
/** 账单列表(筛选 + 分页) */
|
||||
export function getBillList(query: BillQuery): Promise<PageResult<Bill>> {
|
||||
const filtered = MOCK_BILLS.filter((item) => {
|
||||
if (query.status !== 'all' && item.status !== query.status) return false
|
||||
return matchBillKeyword(item, query.keyword)
|
||||
})
|
||||
const start = (query.page - 1) * query.pageSize
|
||||
return Promise.resolve({
|
||||
list: filtered.slice(start, start + query.pageSize).map((item) => ({ ...item })),
|
||||
total: filtered.length,
|
||||
})
|
||||
}
|
||||
|
||||
/** 账单详情(不存在返回 null) */
|
||||
export function getBillDetail(id: string): Promise<Bill | null> {
|
||||
const found = MOCK_BILLS.find((item) => item.id === id) ?? null
|
||||
return Promise.resolve(found ? { ...found } : null)
|
||||
}
|
||||
|
||||
/** 创建支付单(生成二维码支付信息) */
|
||||
export function createPayment(billId: string, method: PayMethod): Promise<Payment> {
|
||||
const bill = MOCK_BILLS.find((item) => item.id === billId)
|
||||
if (!bill) return Promise.reject(new Error('账单不存在'))
|
||||
const payment: Payment = {
|
||||
billId,
|
||||
method,
|
||||
amount: bill.amount,
|
||||
qrExpiresInSeconds: 300,
|
||||
}
|
||||
MOCK_PAYMENTS.set(billId, payment)
|
||||
return Promise.resolve({ ...payment })
|
||||
}
|
||||
|
||||
/** 切换支付方式(刷新二维码) */
|
||||
export function switchPaymentMethod(billId: string, method: PayMethod): Promise<Payment> {
|
||||
return createPayment(billId, method)
|
||||
}
|
||||
|
||||
/** 查询支付结果(无支付记录且未支付返回 null;演示数据 payTime/tradeNo 固定) */
|
||||
export function getPayResult(billId: string): Promise<PaymentResult | null> {
|
||||
const existing = MOCK_RESULTS.get(billId)
|
||||
if (existing) return Promise.resolve(structuredClone(existing))
|
||||
|
||||
const payment = MOCK_PAYMENTS.get(billId)
|
||||
const bill = MOCK_BILLS.find((item) => item.id === billId)
|
||||
if (!payment || !bill) return Promise.resolve(null)
|
||||
|
||||
// 演示:存在支付单即视为完成支付(createPayment 由「完成支付」触发)
|
||||
const tradeNo = bill.tradeNo ?? 'T20260819153035678K2M001'
|
||||
if (bill.status !== '已支付') {
|
||||
bill.status = '已支付'
|
||||
bill.tradeNo = tradeNo
|
||||
}
|
||||
const result: PaymentResult = {
|
||||
method: payment.method,
|
||||
amount: payment.amount,
|
||||
payTime: '2026-01-21 15:00:00',
|
||||
tradeNo,
|
||||
bills: [{ ...bill }],
|
||||
}
|
||||
MOCK_RESULTS.set(billId, result)
|
||||
return Promise.resolve(structuredClone(result))
|
||||
}
|
||||
|
||||
function matchInvoiceKeyword(item: Invoice, keyword: string): boolean {
|
||||
if (!keyword) return true
|
||||
return (
|
||||
item.planName.includes(keyword) ||
|
||||
item.billNo.includes(keyword) ||
|
||||
item.projectName.includes(keyword) ||
|
||||
(item.tradeNo?.includes(keyword) ?? false)
|
||||
)
|
||||
}
|
||||
|
||||
/** 发票列表(筛选 + 分页) */
|
||||
export function getInvoiceList(query: InvoiceQuery): Promise<PageResult<Invoice>> {
|
||||
const filtered = MOCK_INVOICES.filter((item) => {
|
||||
if (query.status !== 'all' && item.status !== query.status) return false
|
||||
return matchInvoiceKeyword(item, query.keyword)
|
||||
})
|
||||
const start = (query.page - 1) * query.pageSize
|
||||
return Promise.resolve({
|
||||
list: filtered.slice(start, start + query.pageSize).map((item) => ({ ...item })),
|
||||
total: filtered.length,
|
||||
})
|
||||
}
|
||||
|
||||
/** 申请开票(演示:状态置为开票中),返回最新发票 */
|
||||
export function applyInvoice(id: string): Promise<Invoice> {
|
||||
const found = MOCK_INVOICES.find((item) => item.id === id)
|
||||
if (!found) return Promise.reject(new Error('发票记录不存在'))
|
||||
found.status = '开票中'
|
||||
return Promise.resolve({ ...found })
|
||||
}
|
||||
|
||||
/** 发票抬头列表 */
|
||||
export function getInvoiceTitles(): Promise<InvoiceTitle[]> {
|
||||
return Promise.resolve(MOCK_TITLES.map((item) => ({ ...item })))
|
||||
}
|
||||
|
||||
/** 新增发票抬头 */
|
||||
export function createInvoiceTitle(payload: InvoiceTitlePayload): Promise<InvoiceTitle> {
|
||||
const created: InvoiceTitle = {
|
||||
id: `title-${Date.now()}`,
|
||||
...payload,
|
||||
isDefault: false,
|
||||
}
|
||||
MOCK_TITLES.push(created)
|
||||
return Promise.resolve({ ...created })
|
||||
}
|
||||
|
||||
/** 编辑发票抬头 */
|
||||
export function updateInvoiceTitle(id: string, payload: InvoiceTitlePayload): Promise<InvoiceTitle> {
|
||||
const found = MOCK_TITLES.find((item) => item.id === id)
|
||||
if (!found) return Promise.reject(new Error('抬头不存在'))
|
||||
found.type = payload.type
|
||||
found.title = payload.title
|
||||
found.taxNo = payload.taxNo
|
||||
return Promise.resolve({ ...found })
|
||||
}
|
||||
|
||||
/** 删除发票抬头 */
|
||||
export function deleteInvoiceTitle(id: string): Promise<void> {
|
||||
const index = MOCK_TITLES.findIndex((item) => item.id === id)
|
||||
if (index < 0) return Promise.reject(new Error('抬头不存在'))
|
||||
MOCK_TITLES.splice(index, 1)
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
/** 设为默认抬头(唯一默认) */
|
||||
export function setDefaultTitle(id: string): Promise<InvoiceTitle[]> {
|
||||
for (const item of MOCK_TITLES) {
|
||||
item.isDefault = item.id === id
|
||||
}
|
||||
return Promise.resolve(MOCK_TITLES.map((item) => ({ ...item })))
|
||||
}
|
||||
|
||||
/** 导出报表(演示:稍后由页面提示成功) */
|
||||
export function exportReport(): Promise<void> {
|
||||
return Promise.resolve()
|
||||
}
|
||||
180
src/api/home.ts
Normal file
180
src/api/home.ts
Normal file
@@ -0,0 +1,180 @@
|
||||
import type { BusinessType } from '@/types/api'
|
||||
import type { Case } from '@/types/business/cases'
|
||||
import type { BusinessLine, HomeStats, ServiceType } from '@/types/business/home'
|
||||
|
||||
// TODO(api): 待接口文档到位后,将以下 mock 返回替换为 request<T> 调用(页面 → store → api 数据流不变)
|
||||
|
||||
const SERVICE_TYPES: ServiceType[] = [
|
||||
{ label: 'Web应用', desc: '响应式 Web 与后台管理系统定制开发' },
|
||||
{ label: '小程序', desc: '微信/支付宝小程序开发与上架' },
|
||||
{ label: 'APP', desc: 'iOS/Android 移动应用定制开发' },
|
||||
{ label: '二次开发', desc: '基于现有系统的功能扩展与重构' },
|
||||
{ label: '桌面工具', desc: '跨平台桌面端工具与自动化软件' },
|
||||
]
|
||||
|
||||
const SERVICE_CONTENT = `一、产品咨询与规划服务
|
||||
1、业务诊断与梳理:深入调研客户现有业务流程,识别数字化痛点与改进机会。
|
||||
2、竞品分析与市场调研:分析行业标杆产品,提炼差异化竞争优势。
|
||||
3、产品定位与MVP规划:明确核心用户群体,规划最小可行性产品(MVP)功能集,制定迭代路线图。
|
||||
4、技术可行性评估:评估所选技术栈的成熟度、扩展性及与现有系统的兼容性。
|
||||
二、UI/UX 设计服务
|
||||
1、用户研究与体验地图:构建用户画像,梳理用户旅程,优化关键触点体验。
|
||||
2、交互原型设计:制作高保真可交互原型,直观展示产品逻辑与操作流程,支持早期验证。
|
||||
3、视觉UI设计:遵循最新设计趋势与品牌规范,输出图标、界面、动效等视觉元素。
|
||||
4、设计规范与组件库:建立统一的设计系统(Design System),确保多端体验一致性,提升后续迭代效率。
|
||||
三、技术开发服务
|
||||
5、前端开发:基于 React/Vue/Flutter 等框架,实现响应式Web、H5、小程序及跨平台移动端应用。
|
||||
6、后端开发:采用 Spring Cloud、Node.js、Go 等微服务架构,构建高并发、高可用的业务接口与逻辑。
|
||||
7、数据库设计:根据业务特性设计关系型(MySQL)与非关系型(Redis、MongoDB)数据库集群方案。
|
||||
8、DevOps 与云服务部署:搭建 CI/CD 自动化流水线,基于 Docker/K8s 进行容器化部署,保障系统弹性伸缩。
|
||||
四、质量保障(QA)服务
|
||||
1、功能测试:覆盖单元测试、集成测试、系统测试及UAT(用户验收测试)支持。
|
||||
2、性能测试:进行压力测试、负载测试,定位系统瓶颈,优化响应速度。
|
||||
3、安全测试:实施渗透测试、漏洞扫描,保障数据安全与隐私合规。
|
||||
4、兼容性测试:覆盖主流浏览器、操作系统及移动设备型号。
|
||||
五、运维与持续运营服务
|
||||
1、7x24小时监控:实时监控系统健康度、流量及异常日志。
|
||||
2、数据运营分析:接入埋点系统,输出用户行为分析报告,指导产品优化。
|
||||
3、迭代升级:基于用户反馈与业务增长需求,提供持续的功能迭代与版本更新。`
|
||||
|
||||
const BUSINESS_LINES: BusinessLine[] = [
|
||||
{
|
||||
id: 'internet',
|
||||
name: '互联网软件',
|
||||
desc: 'Web/小程序/APP/二次开发/桌面工具等',
|
||||
slogan: '专业的软件定制开发服务,从需求到上线全流程保障',
|
||||
icon: '',
|
||||
serviceContent: SERVICE_CONTENT,
|
||||
serviceTypes: SERVICE_TYPES,
|
||||
video: '',
|
||||
},
|
||||
{
|
||||
id: 'hardware',
|
||||
name: '硬件电路',
|
||||
desc: '原理图设计/PCB设计/电气效验等',
|
||||
slogan: '专业的硬件电路设计服务,从方案到量产全流程保障',
|
||||
icon: '',
|
||||
serviceContent: SERVICE_CONTENT,
|
||||
serviceTypes: SERVICE_TYPES,
|
||||
video: '',
|
||||
},
|
||||
{
|
||||
id: 'iot',
|
||||
name: '物联网全案',
|
||||
desc: '软硬件开发+云端一体化/定制化开发等',
|
||||
slogan: '专业的物联网软硬一体化服务,从感知到云端全流程保障',
|
||||
icon: '',
|
||||
serviceContent: SERVICE_CONTENT,
|
||||
serviceTypes: SERVICE_TYPES,
|
||||
video: '',
|
||||
},
|
||||
{
|
||||
id: 'manufacturing',
|
||||
name: '生产制造',
|
||||
desc: 'PCB制版/SMT/整机装配/定制生产等',
|
||||
slogan: '专业的研发智造生产服务,从制版到交付全流程保障',
|
||||
icon: '',
|
||||
serviceContent: SERVICE_CONTENT,
|
||||
serviceTypes: SERVICE_TYPES,
|
||||
video: '',
|
||||
},
|
||||
]
|
||||
|
||||
const FEATURED_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,
|
||||
},
|
||||
]
|
||||
|
||||
/** 首页数据看板 */
|
||||
export function getHomeStats(): Promise<HomeStats> {
|
||||
return Promise.resolve({ pending: 3, inProgress: 3, total: 6 })
|
||||
}
|
||||
|
||||
/** 业务线列表(首页 4 卡片) */
|
||||
export function getBusinessLines(): Promise<BusinessLine[]> {
|
||||
return Promise.resolve(BUSINESS_LINES)
|
||||
}
|
||||
|
||||
/** 业务详情(业务详情页) */
|
||||
export function getBusinessDetail(type: BusinessType): Promise<BusinessLine> {
|
||||
const found = BUSINESS_LINES.find((line) => line.id === type)
|
||||
return Promise.resolve(found ?? BUSINESS_LINES[0]!)
|
||||
}
|
||||
|
||||
/** 首页案例精选(category=all 时返回全部) */
|
||||
export function getFeaturedCases(category: BusinessType | 'all' = 'all'): Promise<Case[]> {
|
||||
const list = category === 'all'
|
||||
? FEATURED_CASES
|
||||
: FEATURED_CASES.filter((item) => item.category === category)
|
||||
return Promise.resolve(list)
|
||||
}
|
||||
153
src/api/message.ts
Normal file
153
src/api/message.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
import type { PageResult } from '@/types/api'
|
||||
import type { Message, MessageQuery } from '@/types/business/message'
|
||||
|
||||
// TODO(api): 待接口文档到位后,将以下 mock 替换为 request<T> 调用
|
||||
|
||||
/** 消息 mock(可变,已读状态在前端内存中翻转演示) */
|
||||
const MOCK_MESSAGES: Message[] = [
|
||||
{
|
||||
id: 'msg-001',
|
||||
type: '系统通知',
|
||||
title: '【新功能】物联网全案"整机联调"看板正式上线',
|
||||
summary: '整机联调看板已上线,支持联调进度、问题闭环与交付物一览。',
|
||||
content: '尊敬的用户:\n\n为了提升物联网全案项目的交付透明度,平台已正式上线"整机联调"看板功能。\n\n一、功能亮点\n1、联调进度实时同步:设备接入、协议联调、场景验证各阶段进度一目了然;\n2、问题闭环跟踪:联调过程中产生的缺陷与阻塞项统一登记、分派、闭环;\n3、交付物一览:联调报告、测试记录、验收文档集中展示,随时查阅下载。\n\n二、使用方式\n进入「项目中心 → 项目详情 → 整机联调」即可查看对应看板。已有进行中的物联网全案项目将自动迁移历史数据,无需重新配置。\n\n三、温馨提示\n如在使用过程中遇到问题,可通过「在线客服」联系我们,或联系专属项目经理协助处理。\n\n智联智造平台产品团队\n2026年8月',
|
||||
publishTime: '2026-08-06 10:00',
|
||||
isRead: false,
|
||||
attachments: [
|
||||
{ name: '物联网全案服务介绍.pdf', size: 2.5 * 1024 * 1024, uploadTime: '2026-08-06 10:00', url: '' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'msg-002',
|
||||
type: '系统通知',
|
||||
title: '平台 v2.6 版本更新公告',
|
||||
summary: '新增需求提报模板、案例库对比功能,并优化了消息中心体验。',
|
||||
content: '尊敬的用户:\n\n平台 v2.6 版本已于近日发布,本次更新内容如下:\n\n一、新增功能\n1、需求提报模板:常用需求类型支持保存为模板,一键复用;\n2、案例库对比:最多选择 4 个案例进行横向对比,辅助决策;\n3、消息中心改版:分类标签、关键词搜索、全部已读一站式处理。\n\n二、体验优化\n1、列表页加载性能提升约 40%;\n2、移动端适配细节修复;\n3、附件上传支持 1GB 大文件。\n\n感谢您一直以来的支持,祝使用愉快!',
|
||||
publishTime: '2026-07-15 14:30',
|
||||
isRead: false,
|
||||
attachments: [],
|
||||
},
|
||||
{
|
||||
id: 'msg-003',
|
||||
type: '系统通知',
|
||||
title: '关于平台服务时间调整的通知',
|
||||
summary: '自 2026-09-01 起,在线客服服务时间调整为 9:00-21:00。',
|
||||
content: '尊敬的用户:\n\n为进一步提升服务质量,自 2026 年 9 月 1 日起,在线客服服务时间调整如下:\n\n服务时间:每日 9:00 - 21:00(法定节假日照常服务)\n非服务时段提交的留言,我们将在下一个服务时段内尽快回复。\n\n紧急事务可通过专属项目经理电话联系。\n\n感谢您的理解与支持!',
|
||||
publishTime: '2026-08-28 09:00',
|
||||
isRead: true,
|
||||
attachments: [],
|
||||
},
|
||||
{
|
||||
id: 'msg-004',
|
||||
type: '状态通知',
|
||||
title: '合同生效通知',
|
||||
summary: '您的合同已正式生效,合同编号 CZ-2026-0815 已开始生效。',
|
||||
content: '您好:\n\n您的合同已正式生效。\n\n合同编号:CZ-2026-0815\n合同名称:智能园区综合管理平台开发合同\n生效日期:2026-08-15\n\n合同已开始生效,可随时在「合同中心」查看详情及履约进度。\n\n如有疑问,请联系您的专属项目经理。',
|
||||
publishTime: '2026-08-15 16:42',
|
||||
isRead: false,
|
||||
attachments: [],
|
||||
},
|
||||
{
|
||||
id: 'msg-005',
|
||||
type: '状态通知',
|
||||
title: '项目进度更新:需求确认阶段已完成',
|
||||
summary: '「智能工厂物联网整体解决方案」需求确认已完成,即将进入方案设计。',
|
||||
content: '您好:\n\n您的项目进度已更新:\n\n项目名称:智能工厂物联网整体解决方案\n当前阶段:需求确认 ✅ 已完成\n下一阶段:方案设计\n\n项目组将于 3 个工作日内提交初步方案,届时会再次通知您。您也可以前往「项目中心」查看详细进度与里程碑。\n\n感谢您的配合!',
|
||||
publishTime: '2026-08-20 11:05',
|
||||
isRead: false,
|
||||
attachments: [],
|
||||
},
|
||||
{
|
||||
id: 'msg-006',
|
||||
type: '状态通知',
|
||||
title: '账单待办提醒',
|
||||
summary: '您有一笔待付款账单,账单编号 BILL-202608-023,请及时处理。',
|
||||
content: '您好:\n\n您有一笔待付款账单,请及时处理:\n\n账单编号:BILL-202608-023\n关联项目:电商中台系统定制开发\n账单金额:150,000.00 元\n到期日期:2026-09-10\n\n请前往「财务中心 → 账单管理」完成支付。如对账单有疑问,请通过在线客服联系我们。\n\n温馨提示:逾期支付可能影响项目排期,请留意处理时间。',
|
||||
publishTime: '2026-08-25 10:00',
|
||||
isRead: true,
|
||||
attachments: [],
|
||||
},
|
||||
{
|
||||
id: 'msg-007',
|
||||
type: '在线客服',
|
||||
title: '您好,请问有什么可以帮您的?',
|
||||
summary: '专属客服小助手已收到您的咨询,将尽快为您答复。',
|
||||
content: '您好,欢迎咨询智联智造平台!\n\n我是您的专属客服小助手,请问有什么可以帮您的?\n\n您可以咨询:\n1、物联网全案开发的流程与周期\n2、项目进度查询\n3、合同与账单相关问题\n4、平台操作指引\n\n我们将在服务时间内尽快回复您。',
|
||||
publishTime: '2026-08-21 10:25',
|
||||
isRead: false,
|
||||
attachments: [],
|
||||
},
|
||||
{
|
||||
id: 'msg-008',
|
||||
type: '在线客服',
|
||||
title: '您的咨询已有回复',
|
||||
summary: '关于"整机联调周期"的咨询已回复,点击查看详细内容。',
|
||||
content: '您好:\n\n关于您咨询的"整机联调周期"问题,客服回复如下:\n\n一般物联网全案项目的整机联调周期为 2-4 周,具体取决于设备数量、协议复杂度与场景验证范围。常规智能家居类项目约 2 周,工业场景多设备联调约 3-4 周。\n\n详细排期以项目计划为准,您可在「项目中心」查看最新里程碑。\n\n如还有其他问题,欢迎继续咨询!',
|
||||
publishTime: '2026-08-19 15:40',
|
||||
isRead: true,
|
||||
attachments: [],
|
||||
},
|
||||
{
|
||||
id: 'msg-009',
|
||||
type: '在线客服',
|
||||
title: '会话满意度回访',
|
||||
summary: '您上次的会话已结束,欢迎对本次服务进行评价。',
|
||||
content: '您好:\n\n您于 2026-08-18 的会话已结束,感谢您的咨询!\n\n欢迎对本次服务进行评价,您的反馈将帮助我们持续改进。如问题未得到解决,可直接回复本条消息,我们将重新为您跟进。\n\n祝您使用愉快!',
|
||||
publishTime: '2026-08-18 17:00',
|
||||
isRead: false,
|
||||
attachments: [],
|
||||
},
|
||||
]
|
||||
|
||||
function applyFilters(query: MessageQuery): Message[] {
|
||||
return MOCK_MESSAGES.filter((item) => {
|
||||
if (query.type !== 'all' && item.type !== query.type) return false
|
||||
if (query.keyword && !item.title.includes(query.keyword) && !item.summary.includes(query.keyword)) return false
|
||||
return true
|
||||
})
|
||||
}
|
||||
|
||||
/** 消息列表(筛选 + 分页) */
|
||||
export function getMessageList(query: MessageQuery): Promise<PageResult<Message>> {
|
||||
const filtered = applyFilters(query)
|
||||
const start = (query.page - 1) * query.pageSize
|
||||
return Promise.resolve({
|
||||
list: filtered.slice(start, start + query.pageSize),
|
||||
total: filtered.length,
|
||||
})
|
||||
}
|
||||
|
||||
/** 消息详情(不存在返回 null) */
|
||||
export function getMessageDetail(id: string): Promise<Message | null> {
|
||||
const found = MOCK_MESSAGES.find((item) => item.id === id) ?? null
|
||||
return Promise.resolve(found ? { ...found } : null)
|
||||
}
|
||||
|
||||
/** 全部标记已读 */
|
||||
export function markAllRead(): Promise<void> {
|
||||
for (const item of MOCK_MESSAGES) {
|
||||
item.isRead = true
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
/** 单条标记已读 */
|
||||
export function markRead(id: string): Promise<void> {
|
||||
const found = MOCK_MESSAGES.find((item) => item.id === id)
|
||||
if (found) found.isRead = true
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
/** 各分类消息总数(tab 角标) */
|
||||
export function getTabCounts(): Promise<{ all: number; 在线客服: number; 状态通知: number; 系统通知: number }> {
|
||||
const counts = { all: MOCK_MESSAGES.length, 在线客服: 0, 状态通知: 0, 系统通知: 0 }
|
||||
for (const item of MOCK_MESSAGES) {
|
||||
counts[item.type] += 1
|
||||
}
|
||||
return Promise.resolve(counts)
|
||||
}
|
||||
|
||||
/** 未读消息总数(头部角标) */
|
||||
export function getUnreadCount(): Promise<number> {
|
||||
return Promise.resolve(MOCK_MESSAGES.filter((item) => !item.isRead).length)
|
||||
}
|
||||
418
src/api/profile.ts
Normal file
418
src/api/profile.ts
Normal file
@@ -0,0 +1,418 @@
|
||||
import type { BusinessType, PageResult } from '@/types/api'
|
||||
import type { Case } from '@/types/business/cases'
|
||||
import type {
|
||||
Address,
|
||||
ChangePasswordParams,
|
||||
Feedback,
|
||||
FavoritesQuery,
|
||||
HelpFaqGroup,
|
||||
ListQuery,
|
||||
Profile,
|
||||
Qualification,
|
||||
QualificationInput,
|
||||
} from '@/types/business/profile'
|
||||
|
||||
// TODO(api): 待接口文档到位后,将以下 mock 替换为 request<T> 调用(页面 → store → api 数据流不变)
|
||||
|
||||
/** 账号资料 mock(可变,昵称/绑定等操作在前端内存中演示) */
|
||||
const MOCK_PROFILE: Profile = {
|
||||
nickname: '王永志',
|
||||
phone: '186****618',
|
||||
email: 'yongzh******4@163.com',
|
||||
passwordModifiedAt: '15天前',
|
||||
}
|
||||
|
||||
/** 获取账号资料 */
|
||||
export function getProfile(): Promise<Profile> {
|
||||
return Promise.resolve({ ...MOCK_PROFILE })
|
||||
}
|
||||
|
||||
/** 修改昵称 */
|
||||
export function updateNickname(name: string): Promise<Profile> {
|
||||
MOCK_PROFILE.nickname = name
|
||||
return Promise.resolve({ ...MOCK_PROFILE })
|
||||
}
|
||||
|
||||
/** 修改密码(验证码 + 新密码) */
|
||||
export function changePassword(params: ChangePasswordParams): Promise<void> {
|
||||
MOCK_PROFILE.passwordModifiedAt = '刚刚'
|
||||
void params
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
/** 绑定/换绑手机号 */
|
||||
export function bindPhone(phone: string, code: string): Promise<Profile> {
|
||||
void code
|
||||
MOCK_PROFILE.phone = phone
|
||||
return Promise.resolve({ ...MOCK_PROFILE })
|
||||
}
|
||||
|
||||
/** 绑定/换绑邮箱 */
|
||||
export function bindEmail(email: string, code: string): Promise<Profile> {
|
||||
void code
|
||||
MOCK_PROFILE.email = email
|
||||
return Promise.resolve({ ...MOCK_PROFILE })
|
||||
}
|
||||
|
||||
/** 取消绑定邮箱 */
|
||||
export function unbindEmail(): Promise<Profile> {
|
||||
MOCK_PROFILE.email = ''
|
||||
return Promise.resolve({ ...MOCK_PROFILE })
|
||||
}
|
||||
|
||||
/** 注销前安全身份验证 */
|
||||
export function verifyIdentity(phone: string, code: string): Promise<void> {
|
||||
void phone
|
||||
void code
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
/** 注销账号 */
|
||||
export function deleteAccount(): Promise<void> {
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
/** 收货地址 mock(isDefault 混合) */
|
||||
const MOCK_ADDRESSES: Address[] = [
|
||||
{
|
||||
id: 'addr-1',
|
||||
receiverName: '王永志',
|
||||
phone: '18556669516',
|
||||
region: '山东省日照市东港区',
|
||||
detailAddress: '凌云购物广场三楼',
|
||||
isDefault: true,
|
||||
},
|
||||
{
|
||||
id: 'addr-2',
|
||||
receiverName: '王永志',
|
||||
phone: '18556669516',
|
||||
region: '山东省日照市东港区',
|
||||
detailAddress: '海曲东路 12 号研发中心 A 座 8 层',
|
||||
isDefault: false,
|
||||
},
|
||||
{
|
||||
id: 'addr-3',
|
||||
receiverName: '李晓婷',
|
||||
phone: '13863331234',
|
||||
region: '山东省青岛市市南区',
|
||||
detailAddress: '香港中路 76 号 A 座 18 层',
|
||||
isDefault: false,
|
||||
},
|
||||
{
|
||||
id: 'addr-4',
|
||||
receiverName: '王永志',
|
||||
phone: '18556669516',
|
||||
region: '山东省济南市历下区',
|
||||
detailAddress: '经十路 9999 号舜耕大厦 12 层',
|
||||
isDefault: false,
|
||||
},
|
||||
]
|
||||
|
||||
function paginate<T>(list: T[], query: ListQuery): PageResult<T> {
|
||||
const start = (query.page - 1) * query.pageSize
|
||||
return {
|
||||
list: list.slice(start, start + query.pageSize),
|
||||
total: list.length,
|
||||
}
|
||||
}
|
||||
|
||||
/** 收货地址列表(分页) */
|
||||
export function getAddresses(query: ListQuery): Promise<PageResult<Address>> {
|
||||
return Promise.resolve(paginate(MOCK_ADDRESSES, query))
|
||||
}
|
||||
|
||||
/** 新增收货地址 */
|
||||
export function createAddress(input: Omit<Address, 'id' | 'isDefault'>): Promise<Address> {
|
||||
const created: Address = {
|
||||
...input,
|
||||
id: `addr-${Date.now()}`,
|
||||
isDefault: MOCK_ADDRESSES.length === 0,
|
||||
}
|
||||
MOCK_ADDRESSES.push(created)
|
||||
return Promise.resolve({ ...created })
|
||||
}
|
||||
|
||||
/** 编辑收货地址 */
|
||||
export function updateAddress(id: string, input: Omit<Address, 'id' | 'isDefault'>): Promise<Address> {
|
||||
const found = MOCK_ADDRESSES.find((item) => item.id === id)
|
||||
if (!found) return Promise.reject(new Error('地址不存在'))
|
||||
Object.assign(found, input)
|
||||
return Promise.resolve({ ...found })
|
||||
}
|
||||
|
||||
/** 删除收货地址 */
|
||||
export function deleteAddress(id: string): Promise<void> {
|
||||
const index = MOCK_ADDRESSES.findIndex((item) => item.id === id)
|
||||
if (index >= 0) MOCK_ADDRESSES.splice(index, 1)
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
/** 设为默认地址 */
|
||||
export function setDefaultAddress(id: string): Promise<Address[]> {
|
||||
for (const item of MOCK_ADDRESSES) {
|
||||
item.isDefault = item.id === id
|
||||
}
|
||||
return Promise.resolve(MOCK_ADDRESSES.map((item) => ({ ...item })))
|
||||
}
|
||||
|
||||
/** 我的收藏 mock(4 条已收藏案例,字段与案例库原型一致) */
|
||||
const MOCK_FAVORITES: Case[] = [
|
||||
{
|
||||
id: 'fav-1',
|
||||
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: true,
|
||||
},
|
||||
{
|
||||
id: 'fav-2',
|
||||
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: true,
|
||||
},
|
||||
{
|
||||
id: 'fav-3',
|
||||
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: true,
|
||||
},
|
||||
{
|
||||
id: 'fav-4',
|
||||
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: true,
|
||||
},
|
||||
]
|
||||
|
||||
/** 我的收藏(关键字/分类筛选 + 分页) */
|
||||
export function getFavorites(query: FavoritesQuery): Promise<PageResult<Case>> {
|
||||
const filtered = MOCK_FAVORITES.filter((item) => {
|
||||
if (query.category !== 'all' && item.category !== query.category) return false
|
||||
if (query.keyword && !item.title.includes(query.keyword)) return false
|
||||
return true
|
||||
})
|
||||
const start = (query.page - 1) * query.pageSize
|
||||
return Promise.resolve({
|
||||
list: filtered.slice(start, start + query.pageSize).map((item) => ({ ...item })),
|
||||
total: filtered.length,
|
||||
})
|
||||
}
|
||||
|
||||
/** 收藏分类计数(分类快捷按钮角标) */
|
||||
export function getCaseCounts(): Promise<Partial<Record<BusinessType, number>>> {
|
||||
const counts: Partial<Record<BusinessType, number>> = {
|
||||
internet: 1,
|
||||
hardware: 1,
|
||||
iot: 1,
|
||||
manufacturing: 1,
|
||||
}
|
||||
return Promise.resolve(counts)
|
||||
}
|
||||
|
||||
/** 资质 mock(状态混合:已认证/审核中/未通过) */
|
||||
const MOCK_QUALIFICATIONS: Qualification[] = [
|
||||
{
|
||||
id: 'qual-1',
|
||||
type: '体系认证',
|
||||
certName: 'ISO9001 质量管理体系认证证书',
|
||||
certNo: 'ISO202609080075',
|
||||
issuer: '中国质量认证中心',
|
||||
validFrom: '2026-9-10',
|
||||
validTo: '2027-9-10',
|
||||
status: '已认证',
|
||||
attachments: [
|
||||
{ name: '体系认证证书', size: 1_048_576, uploadTime: '2026-09-10', url: '' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'qual-2',
|
||||
type: '营业执照',
|
||||
certName: '营业执照',
|
||||
certNo: '91371102MA3XX8876Q',
|
||||
issuer: '日照市东港区市场监督管理局',
|
||||
validFrom: '2020-3-15',
|
||||
validTo: '2030-3-14',
|
||||
status: '审核中',
|
||||
attachments: [
|
||||
{ name: '营业执照', size: 820_000, uploadTime: '2026-09-12', url: '' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'qual-3',
|
||||
type: '检测报告',
|
||||
certName: '智能终端产品第三方检测报告',
|
||||
certNo: 'WT20260512001',
|
||||
issuer: '山东省产品质量检验研究院',
|
||||
validFrom: '2026-5-12',
|
||||
validTo: '2027-5-12',
|
||||
status: '未通过',
|
||||
attachments: [
|
||||
{ name: '检测报告', size: 2_097_152, uploadTime: '2026-05-15', url: '' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 'qual-4',
|
||||
type: '生产许可证',
|
||||
certName: '全国工业产品生产许可证',
|
||||
certNo: 'XK06-123-45678',
|
||||
issuer: '山东省市场监督管理局',
|
||||
validFrom: '2024-1-8',
|
||||
validTo: '2029-1-7',
|
||||
status: '已认证',
|
||||
attachments: [
|
||||
{ name: '生产许可证', size: 1_258_291, uploadTime: '2024-01-10', url: '' },
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
/** 资质列表(分页) */
|
||||
export function getQualifications(query: ListQuery): Promise<PageResult<Qualification>> {
|
||||
return Promise.resolve(paginate(MOCK_QUALIFICATIONS, query))
|
||||
}
|
||||
|
||||
/** 新增资质(提交后进入审核中) */
|
||||
export function createQualification(input: QualificationInput): Promise<Qualification> {
|
||||
const created: Qualification = {
|
||||
...input,
|
||||
id: `qual-${Date.now()}`,
|
||||
status: '审核中',
|
||||
}
|
||||
MOCK_QUALIFICATIONS.unshift(created)
|
||||
return Promise.resolve({ ...created })
|
||||
}
|
||||
|
||||
/** 编辑资质(未通过重新提交后回到审核中) */
|
||||
export function updateQualification(id: string, input: QualificationInput): Promise<Qualification> {
|
||||
const found = MOCK_QUALIFICATIONS.find((item) => item.id === id)
|
||||
if (!found) return Promise.reject(new Error('资质不存在'))
|
||||
found.type = input.type
|
||||
found.certName = input.certName
|
||||
found.certNo = input.certNo
|
||||
found.issuer = input.issuer
|
||||
found.validFrom = input.validFrom
|
||||
found.validTo = input.validTo
|
||||
found.attachments = input.attachments
|
||||
if (found.status === '未通过') found.status = '审核中'
|
||||
return Promise.resolve({ ...found })
|
||||
}
|
||||
|
||||
/** 删除资质 */
|
||||
export function deleteQualification(id: string): Promise<void> {
|
||||
const index = MOCK_QUALIFICATIONS.findIndex((item) => item.id === id)
|
||||
if (index >= 0) MOCK_QUALIFICATIONS.splice(index, 1)
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
/** 提交意见反馈 */
|
||||
export function submitFeedback(feedback: Feedback): Promise<void> {
|
||||
void feedback
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
/** 帮助中心 FAQ */
|
||||
export function getHelpFaqs(): Promise<HelpFaqGroup[]> {
|
||||
return Promise.resolve([
|
||||
{
|
||||
title: '1. 账户与登录',
|
||||
items: [
|
||||
{
|
||||
q: '如何注册账号?',
|
||||
a: '点击页面右上角"注册",输入手机号并获取验证码,设置 6-20 位登录密码即可完成注册。',
|
||||
},
|
||||
{
|
||||
q: '忘记密码怎么办?',
|
||||
a: '在登录页点击"忘记密码",通过绑定手机号验证码验证身份后即可重新设置密码。',
|
||||
},
|
||||
{
|
||||
q: '一个账号可以多地登录吗?',
|
||||
a: '同一账号同时仅允许在 1 台设备上保持在线,超出将自动踢出最早登录的设备。',
|
||||
},
|
||||
{
|
||||
q: '如何修改绑定手机号?',
|
||||
a: '进入 个人中心 → 账号安全 → 绑定手机,验证原手机号后即可绑定新号码。',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '2. 功能使用指南',
|
||||
items: [
|
||||
{
|
||||
q: '如何提交需求?',
|
||||
a: '进入"发布需求"页面,填写需求描述、预算、周期等信息并提交,专业顾问将在一个工作日内与您联系。',
|
||||
},
|
||||
{
|
||||
q: '如何查看我的消息?',
|
||||
a: '登录后点击右上角消息图标,或进入 个人中心 → 消息通知,即可查看系统通知与顾问消息。',
|
||||
},
|
||||
{
|
||||
q: '如何申请售后?',
|
||||
a: '在"我的订单"中找到对应项目,点击"申请售后"填写问题描述并上传附件,平台将尽快为您处理。',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
title: '3. 系统与权限',
|
||||
items: [
|
||||
{
|
||||
q: '无权限怎么办?',
|
||||
a: '请确认当前账号角色是否具备该模块权限;如仍无法访问,请联系专属客服或平台管理员为您开通。',
|
||||
},
|
||||
{
|
||||
q: '收不到通知怎么办?',
|
||||
a: '请检查浏览器通知权限与消息设置是否开启;若仍未收到,请联系客服核实账号通知配置。',
|
||||
},
|
||||
{
|
||||
q: '支持哪些浏览器?',
|
||||
a: '推荐使用 Chrome、Edge、Firefox、Safari 等主流浏览器的最新版本访问本平台。',
|
||||
},
|
||||
],
|
||||
},
|
||||
])
|
||||
}
|
||||
270
src/api/project.ts
Normal file
270
src/api/project.ts
Normal file
@@ -0,0 +1,270 @@
|
||||
import type { FileInfo, PageResult } from '@/types/api'
|
||||
import type {
|
||||
AfterSales,
|
||||
AfterSalesPayload,
|
||||
Project,
|
||||
ProjectQuery,
|
||||
} from '@/types/business/project'
|
||||
import { formatDate } from '@/utils/format'
|
||||
|
||||
// TODO(api): 待接口文档到位后,将以下 mock 替换为 request<T> 调用
|
||||
|
||||
/** 构造 mock 文件信息 */
|
||||
function mockFile(name: string, size: number, uploadTime: string): FileInfo {
|
||||
return { name, size, uploadTime, url: '' }
|
||||
}
|
||||
|
||||
/** 详情 mock 所属项目 id */
|
||||
const DETAIL_ID = 'project-004'
|
||||
|
||||
/** 项目列表 mock(6 条,覆盖全部状态,internet/iot 混合) */
|
||||
const MOCK_PROJECTS: Project[] = [
|
||||
{
|
||||
id: 'project-001',
|
||||
projectNo: 'P8K2M202408001',
|
||||
projectName: '某大型银行数字化转型方案',
|
||||
businessType: 'internet',
|
||||
progress: 64,
|
||||
status: '待立项',
|
||||
submitTime: '2026-8-17 14:00',
|
||||
milestones: [],
|
||||
team: [],
|
||||
files: [],
|
||||
paymentPlans: [],
|
||||
invoiceRecords: [],
|
||||
},
|
||||
{
|
||||
id: 'project-002',
|
||||
projectNo: 'P8K2M202408002',
|
||||
projectName: '智慧城市大数据平台建设项目',
|
||||
businessType: 'iot',
|
||||
progress: 64,
|
||||
status: '待验收',
|
||||
submitTime: '2026-8-17 14:00',
|
||||
milestones: [],
|
||||
team: [],
|
||||
files: [],
|
||||
paymentPlans: [],
|
||||
invoiceRecords: [],
|
||||
},
|
||||
{
|
||||
id: 'project-003',
|
||||
projectNo: 'P8K2M202408003',
|
||||
projectName: '三甲医院指挥医疗信息化系统',
|
||||
businessType: 'internet',
|
||||
progress: 64,
|
||||
status: '已完成',
|
||||
submitTime: '2026-8-17 14:00',
|
||||
milestones: [],
|
||||
team: [],
|
||||
files: [],
|
||||
paymentPlans: [],
|
||||
invoiceRecords: [],
|
||||
},
|
||||
{
|
||||
id: DETAIL_ID,
|
||||
projectNo: '20260813RJ251545',
|
||||
projectName: '社区康养智能服务系统',
|
||||
businessType: 'internet',
|
||||
progress: 64,
|
||||
status: '进行中',
|
||||
submitTime: '2026-8-17 14:00',
|
||||
milestones: [],
|
||||
team: [],
|
||||
files: [],
|
||||
paymentPlans: [],
|
||||
invoiceRecords: [],
|
||||
},
|
||||
{
|
||||
id: 'project-005',
|
||||
projectNo: 'P8K2M202408005',
|
||||
projectName: '智能仓储物流分拣系统',
|
||||
businessType: 'iot',
|
||||
progress: 64,
|
||||
status: '售后中',
|
||||
submitTime: '2026-8-17 14:00',
|
||||
milestones: [],
|
||||
team: [],
|
||||
files: [],
|
||||
paymentPlans: [],
|
||||
invoiceRecords: [],
|
||||
afterSales: {
|
||||
status: '处理中',
|
||||
type: 'BUG问题',
|
||||
description: '分拣识别偶发误判,需要排查',
|
||||
remark: '',
|
||||
attachments: [],
|
||||
createTime: '2026-8-19 17:00',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 'project-006',
|
||||
projectNo: 'P8K2M202408006',
|
||||
projectName: '新能源电站运维监控平台',
|
||||
businessType: 'internet',
|
||||
progress: 64,
|
||||
status: '已取消',
|
||||
submitTime: '2026-8-17 14:00',
|
||||
milestones: [],
|
||||
team: [],
|
||||
files: [],
|
||||
paymentPlans: [],
|
||||
invoiceRecords: [],
|
||||
},
|
||||
]
|
||||
|
||||
/** 项目详情 mock(社区康养智能服务系统) */
|
||||
const PROJECT_DETAIL: Project = {
|
||||
...MOCK_PROJECTS[3],
|
||||
progress: 65,
|
||||
startTime: '2026-8-13 11:04:12',
|
||||
endTime: '2026-10-30 18:00:00',
|
||||
milestones: [
|
||||
{ name: '需求确认', status: '已完成', time: '2026-8-13 11:04:12' },
|
||||
{ name: '原型设计', status: '已完成', time: '2026-8-13 11:04:12' },
|
||||
{ name: '开发实现', status: '进行中' },
|
||||
{ name: '测试验收', status: '待开始' },
|
||||
{ name: '项目交付', status: '待开始' },
|
||||
],
|
||||
team: [
|
||||
{ role: '项目经理', name: '张三', avatarChar: '张' },
|
||||
{ role: '项目经理', name: '张三', avatarChar: '张' },
|
||||
],
|
||||
files: [
|
||||
mockFile('项目需求规格说明书.pdf', 2516582, '2026-08-19 17:00'),
|
||||
mockFile('项目需求说明书.word', 1887437, '2026-08-19 17:00'),
|
||||
],
|
||||
paymentPlans: [
|
||||
{
|
||||
id: 'bill-001',
|
||||
planName: '第一期·预付款(比例33%)',
|
||||
billNo: '20260813ZZ251245',
|
||||
amount: 20100,
|
||||
status: '已支付',
|
||||
tradeNo: 'T20260819153035678K2M001',
|
||||
projectName: '社区康养智能服务系统',
|
||||
},
|
||||
{
|
||||
id: 'bill-002',
|
||||
planName: '第二期·付款(比例33%)',
|
||||
billNo: '20260813ZZ251245',
|
||||
amount: 20100,
|
||||
status: '待支付',
|
||||
projectName: '社区康养智能服务系统',
|
||||
},
|
||||
{
|
||||
id: 'bill-003',
|
||||
planName: '第三期·尾款(比例33%)',
|
||||
billNo: '20260813ZZ251245',
|
||||
amount: 20100,
|
||||
status: '未到期',
|
||||
projectName: '社区康养智能服务系统',
|
||||
},
|
||||
],
|
||||
invoiceRecords: [
|
||||
mockFile('增值税专用发票.pdf', 325120, '2026-08-19 17:00'),
|
||||
mockFile('电子回单.jpg', 186368, '2026-08-19 17:00'),
|
||||
],
|
||||
contractFile: mockFile('社区康养智能服务系统开发合同.pdf', 1039360, '2026-08-19 17:00'),
|
||||
afterSales: {
|
||||
status: '处理中',
|
||||
type: 'BUG问题',
|
||||
description: '具体请看清单',
|
||||
remark: '',
|
||||
attachments: [],
|
||||
createTime: '2026-8-19 17:00',
|
||||
},
|
||||
}
|
||||
|
||||
/** 提交时间快捷区间匹配 */
|
||||
function matchTimeShortcut(submitTime: string, shortcut: ProjectQuery['timeShortcut']): boolean {
|
||||
if (shortcut === 'all') return true
|
||||
// mock 时间为 '2026-8-17 14:00',替换为 / 保证各环境可解析
|
||||
const time = new Date(submitTime.replaceAll('-', '/')).getTime()
|
||||
const start = new Date()
|
||||
if (shortcut === '3m') start.setMonth(start.getMonth() - 3)
|
||||
if (shortcut === '6m') start.setMonth(start.getMonth() - 6)
|
||||
if (shortcut === '1y') start.setFullYear(start.getFullYear() - 1)
|
||||
return time >= start.getTime()
|
||||
}
|
||||
|
||||
/** 项目列表(筛选 + 分页,pageSize 固定 8) */
|
||||
export function getProjectList(query: ProjectQuery): Promise<PageResult<Project>> {
|
||||
const filtered = MOCK_PROJECTS.filter((item) => {
|
||||
if (
|
||||
query.keyword
|
||||
&& !item.projectName.includes(query.keyword)
|
||||
&& !item.projectNo.includes(query.keyword)
|
||||
) {
|
||||
return false
|
||||
}
|
||||
if (query.businessType !== 'all' && item.businessType !== query.businessType) return false
|
||||
if (query.status !== 'all' && item.status !== query.status) return false
|
||||
if (!matchTimeShortcut(item.submitTime, query.timeShortcut)) return false
|
||||
return true
|
||||
})
|
||||
const pageSize = query.pageSize > 0 ? query.pageSize : 8
|
||||
const start = (query.page - 1) * pageSize
|
||||
return Promise.resolve({
|
||||
list: filtered.slice(start, start + pageSize),
|
||||
total: filtered.length,
|
||||
})
|
||||
}
|
||||
|
||||
/** 项目详情(不存在返回 null;仅详情项目返回完整数据) */
|
||||
export function getProjectDetail(id: string): Promise<Project | null> {
|
||||
if (id === DETAIL_ID) return Promise.resolve({ ...PROJECT_DETAIL })
|
||||
const found = MOCK_PROJECTS.find((item) => item.id === id) ?? null
|
||||
return Promise.resolve(found ? { ...found } : null)
|
||||
}
|
||||
|
||||
/** 上传项目文件(追加进详情文件列表) */
|
||||
export function uploadProjectFile(file: File): Promise<FileInfo> {
|
||||
const info: FileInfo = {
|
||||
name: file.name,
|
||||
size: file.size,
|
||||
uploadTime: formatDate(new Date()),
|
||||
url: '',
|
||||
}
|
||||
PROJECT_DETAIL.files.push(info)
|
||||
return Promise.resolve({ ...info })
|
||||
}
|
||||
|
||||
/** 删除项目文件(按索引从详情文件列表移除) */
|
||||
export function deleteProjectFile(index: number): Promise<void> {
|
||||
if (index >= 0 && index < PROJECT_DETAIL.files.length) {
|
||||
PROJECT_DETAIL.files.splice(index, 1)
|
||||
}
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
/** 提交售后工单 */
|
||||
export function submitAfterSales(
|
||||
projectId: string,
|
||||
payload: AfterSalesPayload,
|
||||
): Promise<AfterSales> {
|
||||
const result: AfterSales = {
|
||||
status: '处理中',
|
||||
type: payload.type,
|
||||
description: payload.description,
|
||||
remark: payload.remark,
|
||||
attachments: [...payload.attachments],
|
||||
createTime: formatDate(new Date()),
|
||||
}
|
||||
if (projectId === DETAIL_ID) PROJECT_DETAIL.afterSales = result
|
||||
return Promise.resolve({ ...result, attachments: [...result.attachments] })
|
||||
}
|
||||
|
||||
/** 取消售后工单 */
|
||||
export function cancelAfterSales(projectId: string): Promise<AfterSales> {
|
||||
const result: AfterSales = {
|
||||
status: '无',
|
||||
type: '',
|
||||
description: '',
|
||||
remark: '',
|
||||
attachments: [],
|
||||
createTime: '',
|
||||
}
|
||||
if (projectId === DETAIL_ID) PROJECT_DETAIL.afterSales = result
|
||||
return Promise.resolve({ ...result })
|
||||
}
|
||||
72
src/api/service.ts
Normal file
72
src/api/service.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import type { ChatMessage } from '@/types/business/service'
|
||||
|
||||
// TODO(api): 待接口文档到位后,将以下 mock 替换为 request<T> 调用
|
||||
|
||||
let seq = 0
|
||||
|
||||
function nextId(prefix: string): string {
|
||||
seq += 1
|
||||
return `${prefix}-${Date.now()}-${seq}`
|
||||
}
|
||||
|
||||
function nowTime(): string {
|
||||
const date = new Date()
|
||||
const pad = (n: number): string => (n < 10 ? `0${n}` : String(n))
|
||||
return `今天 ${pad(date.getHours())}:${pad(date.getMinutes())}`
|
||||
}
|
||||
|
||||
/** 历史会话(欢迎语 + 用户提问 + 文件资料) */
|
||||
export function getChatHistory(): Promise<ChatMessage[]> {
|
||||
return Promise.resolve([
|
||||
{
|
||||
id: 'chat-001',
|
||||
role: 'assistant',
|
||||
content: '您好,欢迎咨询智联智造平台!我是您的专属客服小助手,请问有什么可以帮您的?',
|
||||
time: '今天 10:23',
|
||||
},
|
||||
{
|
||||
id: 'chat-002',
|
||||
role: 'user',
|
||||
content: '我想咨询一下物联网全案开发的流程和周期',
|
||||
time: '今天 10:23',
|
||||
},
|
||||
{
|
||||
id: 'chat-003',
|
||||
role: 'assistant',
|
||||
content: '',
|
||||
file: { name: '物联网全案服务介绍.pdf', size: 2.5 * 1024 * 1024 },
|
||||
time: '今天 10:24',
|
||||
},
|
||||
])
|
||||
}
|
||||
|
||||
/** 发送文本消息(echo 用户消息,携带当前时间) */
|
||||
export function sendChatMessage(content: string): Promise<ChatMessage> {
|
||||
return Promise.resolve({
|
||||
id: nextId('chat'),
|
||||
role: 'user',
|
||||
content,
|
||||
time: nowTime(),
|
||||
})
|
||||
}
|
||||
|
||||
/** 上传聊天文件(生成文件卡片消息) */
|
||||
export function uploadChatFile(file: File): Promise<ChatMessage> {
|
||||
return Promise.resolve({
|
||||
id: nextId('chat'),
|
||||
role: 'user',
|
||||
content: '',
|
||||
file: { name: file.name, size: file.size },
|
||||
time: nowTime(),
|
||||
})
|
||||
}
|
||||
|
||||
/** 客服自动回复 */
|
||||
export function mockAutoReply(): Promise<ChatMessage> {
|
||||
return Promise.resolve({
|
||||
id: nextId('chat'),
|
||||
role: 'assistant',
|
||||
content: '收到您的消息,专属客服稍后为您答复~',
|
||||
time: nowTime(),
|
||||
})
|
||||
}
|
||||
62
src/api/web/home.ts
Normal file
62
src/api/web/home.ts
Normal file
@@ -0,0 +1,62 @@
|
||||
// 用户web端首页接口(真实后端绑定)
|
||||
// 后端: vermis-maker com.ruoyi.vermins.controller.pc.web.UserSideWebHomePageController
|
||||
// 前缀: /web/home-page 鉴权: 公开接口,无需 token
|
||||
//
|
||||
// 使用约定(与项目 SKILL.md 一致):
|
||||
// 页面/组件禁止直接 import axios,一律经 store 调用本模块的 request<T>。
|
||||
// 本文件只放「函数签名 + 请求路径」,不写业务逻辑。
|
||||
import { request } from '@/utils/request'
|
||||
import type {
|
||||
ProjectStatusCount,
|
||||
ProjectSubmissionVo,
|
||||
BannerVo,
|
||||
AgreementFormVo,
|
||||
} from '@/types/web/home'
|
||||
|
||||
/** 项目状态统计 */
|
||||
export function getProjectStatusCount(): Promise<ProjectStatusCount> {
|
||||
return request<ProjectStatusCount>({
|
||||
url: '/web/home-page/project-status-count',
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
/** 业务服务类型列表 */
|
||||
export function getServiceTypeList(): Promise<ProjectSubmissionVo[]> {
|
||||
return request<ProjectSubmissionVo[]>({
|
||||
url: '/web/home-page/service-type-list',
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
/** 业务类型详情 */
|
||||
export function getServiceTypeDetail(id: number | string): Promise<ProjectSubmissionVo> {
|
||||
return request<ProjectSubmissionVo>({
|
||||
url: `/web/home-page/service-type-detail/${id}`,
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
/** 轮播图列表 */
|
||||
export function getCarouselList(): Promise<BannerVo[]> {
|
||||
return request<BannerVo[]>({
|
||||
url: '/web/home-page/carousel-list',
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
/** 平台介绍(富文本 HTML) */
|
||||
export function getPlatformIntro(): Promise<AgreementFormVo> {
|
||||
return request<AgreementFormVo>({
|
||||
url: '/web/home-page/platform-intro',
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
|
||||
/** 联系信息(富文本 HTML) */
|
||||
export function getContactInfo(): Promise<AgreementFormVo> {
|
||||
return request<AgreementFormVo>({
|
||||
url: '/web/home-page/contact-info',
|
||||
method: 'get',
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user