Compare commits

...

5 Commits

Author SHA1 Message Date
zhangjf
bd5f8ab468 fix: 数据库脚本与实体类一致性修复 + fund-admin Nginx子路径部署支持
1. 数据库脚本修复:
   - fund_sys_init.sql: sys_config表添加group_code/group_name/sort_order/remark字段
   - fund_proj_init.sql: 添加requirement表(需求工单)
   - fund_exp_init.sql: 添加fund_expense表缺失字段
   - 删除重复的doc/requirement.sql

2. fund-admin Nginx子路径部署支持:
   - 新增.env.development/.env.production环境配置
   - vite.config.ts支持VITE_BASE动态base路径
   - router使用import.meta.env.BASE_URL
   - API baseURL使用环境变量
   - deploy-frontend-nginx.sh支持/fadmin部署前缀

3. 架构文档更新:
   - 数据库设计文档新增4.4实体类与SQL脚本同步规范
   - 新增4.5自动化检查机制
2026-02-23 00:12:39 +08:00
zhangjf
8490b7a2c6 fix: 全面修复网关路由与前端API路径一致性问题
问题分析:
1. 后端Controller路径不一致:
   - fund-cust: /api/v1/customer (不是/api/v1/cust/customer)
   - fund-proj: /api/v1/project (不是/api/v1/proj/project)
   - fund-sys: /api/v1/auth 和 /api/v1/sys/* 两种路径

2. 之前的错误修改导致路径不匹配

解决方案:
- 网关: StripPrefix=1 + PrefixPath=/api/v1
- 前端: baseURL=/fund,路径直接对应后端路径

网关路由配置:
- fund-sys: /fund/auth/**,/fund/sys/** -> /api/v1/auth/*,/api/v1/sys/*
- fund-cust: /fund/customer/** -> /api/v1/customer/*
- fund-proj: /fund/project/**,/fund/requirement/** -> /api/v1/project/*,/api/v1/requirement/*
- fund-exp: /fund/exp/** -> /api/v1/exp/*
- fund-receipt: /fund/receipt/** -> /api/v1/receipt/*
- fund-report: /fund/report/** -> /api/v1/report/*
- fund-file: /fund/file/** -> /api/v1/file/*

前端API路径规范:
- 认证: /auth/login -> /api/v1/auth/login
- 用户: /sys/user/page -> /api/v1/sys/user/page
- 客户: /customer/page -> /api/v1/customer/page
- 项目: /project/page -> /api/v1/project/page
- 支出: /exp/expense/page -> /api/v1/exp/expense/page
- 收款: /receipt/receivable/page -> /api/v1/receipt/receivable/page
- 报表: /report/stats -> /api/v1/report/stats
- 文件: /file/upload -> /api/v1/file/upload

修改文件:
- fund-gateway/application.yml: 路由配置调整
- TenantGatewayFilter.java: 白名单路径修正
- TokenAuthFilter.java: 白名单路径修正
- fund-admin/src/api/*.ts: 所有API路径修正
- fund-mobile/src/api/index.ts: 所有API路径修正
- FileUpload组件: 上传路径修正
2026-02-22 23:06:54 +08:00
zhangjf
3b0b10d5b2 fix: 修复前端API路径与网关路由配置匹配
问题:前端API包含/api/v1导致路径重复
- 前端: /fund/sys/api/v1/auth/login
- 网关StripPrefix=2后: /api/v1/auth/login
- PrefixPath=/api/v1后: /api/v1/api/v1/auth/login 

修复:移除前端API中的/api/v1前缀

fund-admin修改:
- auth.ts: /sys/auth/login
- user.ts: /sys/sys/user/page
- tenant.ts: /sys/sys/tenant/page
- customer.ts: /cust/cust/customer/page
- project.ts: /proj/proj/project/page
- expense.ts: /exp/exp/expense/page
- receivable.ts: /receipt/receipt/receivable/page
- report.ts: /report/report/dashboard/stats
- file.ts: /file/file/upload
- menu.ts: /sys/sys/menu/tree
- role.ts: /sys/sys/role/page
- dept.ts: /sys/sys/dept/list
- config.ts: /sys/sys/config/page
- FileUpload组件: /fund/file/file/upload

fund-mobile修改:
- index.ts: 统一移除/api/v1,添加模块名重复

路由流程示例:
- 前端请求: /fund/sys/auth/login
- StripPrefix=2: /auth/login
- PrefixPath=/api/v1: /api/v1/auth/login ✓
2026-02-22 22:56:00 +08:00
zhangjf
e6f3f85581 fix: 修复网关路由配置,添加PrefixPath补全API路径
问题:
- StripPrefix=2 后路径缺少 /api/v1 前缀
- 健康检查路径不正确,无法访问

修改:
1. application.yml: 所有路由添加 PrefixPath=/api/v1
2. TenantGatewayFilter: 更新白名单健康检查路径
3. TokenAuthFilter: 更新白名单健康检查路径

路由流程示例:
- 登录: /fund/sys/auth/login
  → StripPrefix=2 → /auth/login
  → PrefixPath=/api/v1 → /api/v1/auth/login ✓

- 健康检查: /fund/sys/sys/health
  → StripPrefix=2 → /sys/health
  → PrefixPath=/api/v1 → /api/v1/sys/health ✓

注意:健康检查路径需要重复模块名
- /fund/{module}/{module}/health
2026-02-22 22:48:32 +08:00
zhangjf
92d8234fcb fix: 更新网关Filter白名单路径,适配/fund前缀
修改文件:
- TenantGatewayFilter.java: 白名单路径添加/fund前缀
- TokenAuthFilter.java: 白名单路径添加/fund前缀

白名单路径变更:
- 旧: /sys/api/v1/auth/login
- 新: /fund/sys/auth/login

说明:
- Filter在网关层执行,使用原始请求路径(含/fund前缀)
- StripPrefix在Filter之后执行,转发时才剥离前缀
2026-02-22 22:35:45 +08:00
29 changed files with 401 additions and 257 deletions

View File

@ -1,34 +0,0 @@
-- 需求工单表
CREATE TABLE IF NOT EXISTS `requirement` (
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键需求ID',
`tenant_id` BIGINT NOT NULL COMMENT '租户ID',
`requirement_code` VARCHAR(50) NOT NULL COMMENT '需求编号',
`requirement_name` VARCHAR(200) NOT NULL COMMENT '需求名称',
`description` TEXT COMMENT '需求描述',
`project_id` BIGINT NOT NULL COMMENT '项目ID',
`customer_id` BIGINT NOT NULL COMMENT '客户ID',
`priority` VARCHAR(20) DEFAULT 'normal' COMMENT '优先级high-高normal-中low-低',
`estimated_hours` DECIMAL(8,2) DEFAULT 0.00 COMMENT '预估开发工时(小时)',
`actual_hours` DECIMAL(8,2) DEFAULT 0.00 COMMENT '实际开发工时(小时)',
`planned_start` DATE COMMENT '计划开始日期',
`planned_end` DATE COMMENT '计划结束日期',
`actual_start` DATE COMMENT '实际开始日期',
`actual_end` DATE COMMENT '实际结束日期',
`delivery_date` DATE COMMENT '交付日期',
`receivable_amount` DECIMAL(15,2) DEFAULT 0.00 COMMENT '应收款金额',
`receivable_date` DATE COMMENT '应收款日期',
`status` VARCHAR(20) DEFAULT 'pending' COMMENT '状态pending-待开发developing-开发中delivered-已交付completed-已完成',
`progress` INT DEFAULT 0 COMMENT '开发进度0-100',
`attachment_url` VARCHAR(500) COMMENT '附件URL',
`created_by` BIGINT COMMENT '创建人ID',
`created_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_by` BIGINT COMMENT '更新人ID',
`updated_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`deleted` TINYINT DEFAULT 0 COMMENT '逻辑删除0-未删除1-已删除',
PRIMARY KEY (`id`),
UNIQUE KEY `uk_tenant_code` (`tenant_id`, `requirement_code`),
INDEX `idx_tenant` (`tenant_id`),
INDEX `idx_project` (`project_id`),
INDEX `idx_customer` (`customer_id`),
INDEX `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='需求工单表';

View File

@ -22,6 +22,7 @@ CREATE TABLE IF NOT EXISTS expense_type (
sort_order INT DEFAULT 0 COMMENT '排序号', sort_order INT DEFAULT 0 COMMENT '排序号',
description VARCHAR(500) COMMENT '类型描述', description VARCHAR(500) COMMENT '类型描述',
status TINYINT NOT NULL DEFAULT 1 COMMENT '状态: 0-禁用, 1-启用', status TINYINT NOT NULL DEFAULT 1 COMMENT '状态: 0-禁用, 1-启用',
remark VARCHAR(500) COMMENT '备注',
created_by BIGINT COMMENT '创建人', created_by BIGINT COMMENT '创建人',
created_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', created_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_by BIGINT COMMENT '更新人', updated_by BIGINT COMMENT '更新人',

View File

@ -1,8 +1,9 @@
-- ============================================= -- =============================================
-- 资金服务平台 - 项目管理数据库初始化脚本 -- 资金服务平台 - 项目管理数据库初始化脚本
-- Database: fund_proj -- Database: fund_proj
-- Version: 1.0 -- Version: 1.1
-- Created: 2026-02-17 -- Created: 2026-02-17
-- Updated: 2026-02-22 (添加requirement表)
-- ============================================= -- =============================================
CREATE DATABASE IF NOT EXISTS fund_proj DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; CREATE DATABASE IF NOT EXISTS fund_proj DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
@ -38,6 +39,43 @@ CREATE TABLE IF NOT EXISTS project (
KEY idx_status (status) KEY idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='项目表'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='项目表';
-- =============================================
-- 2. 需求工单表 (requirement)
-- =============================================
CREATE TABLE IF NOT EXISTS requirement (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键需求ID',
tenant_id BIGINT NOT NULL COMMENT '租户ID',
requirement_code VARCHAR(50) NOT NULL COMMENT '需求编号',
requirement_name VARCHAR(200) NOT NULL COMMENT '需求名称',
description TEXT COMMENT '需求描述',
project_id BIGINT NOT NULL COMMENT '项目ID',
customer_id BIGINT NOT NULL COMMENT '客户ID',
priority VARCHAR(20) DEFAULT 'normal' COMMENT '优先级high-高normal-中low-低',
estimated_hours DECIMAL(8,2) DEFAULT 0.00 COMMENT '预估开发工时(小时)',
actual_hours DECIMAL(8,2) DEFAULT 0.00 COMMENT '实际开发工时(小时)',
planned_start DATE COMMENT '计划开始日期',
planned_end DATE COMMENT '计划结束日期',
actual_start DATE COMMENT '实际开始日期',
actual_end DATE COMMENT '实际结束日期',
delivery_date DATE COMMENT '交付日期',
receivable_amount DECIMAL(15,2) DEFAULT 0.00 COMMENT '应收款金额',
receivable_date DATE COMMENT '应收款日期',
status VARCHAR(20) DEFAULT 'pending' COMMENT '状态pending-待开发developing-开发中delivered-已交付completed-已完成',
progress INT DEFAULT 0 COMMENT '开发进度0-100',
attachment_url VARCHAR(500) COMMENT '附件URL',
created_by BIGINT COMMENT '创建人ID',
created_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_by BIGINT COMMENT '更新人ID',
updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
deleted TINYINT DEFAULT 0 COMMENT '逻辑删除0-未删除1-已删除',
PRIMARY KEY (id),
UNIQUE KEY uk_tenant_code (tenant_id, requirement_code),
KEY idx_tenant (tenant_id),
KEY idx_project (project_id),
KEY idx_customer (customer_id),
KEY idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='需求工单表';
-- ============================================= -- =============================================
-- 初始化测试数据(租户ID=1) -- 初始化测试数据(租户ID=1)
-- ============================================= -- =============================================

View File

@ -177,12 +177,17 @@ CREATE TABLE IF NOT EXISTS sys_dict (
-- ============================================= -- =============================================
CREATE TABLE IF NOT EXISTS sys_config ( CREATE TABLE IF NOT EXISTS sys_config (
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '配置ID', id BIGINT NOT NULL AUTO_INCREMENT COMMENT '配置ID',
tenant_id BIGINT NOT NULL DEFAULT 0 COMMENT '租户ID',
config_key VARCHAR(128) NOT NULL COMMENT '配置键', config_key VARCHAR(128) NOT NULL COMMENT '配置键',
config_value TEXT COMMENT '配置值', config_value TEXT COMMENT '配置值',
config_type VARCHAR(64) DEFAULT 'string' COMMENT '配置类型: string/number/boolean/json', config_type VARCHAR(64) DEFAULT 'string' COMMENT '配置类型: string/number/boolean/json',
description VARCHAR(500) COMMENT '配置描述', description VARCHAR(500) COMMENT '配置描述',
is_system TINYINT NOT NULL DEFAULT 0 COMMENT '是否系统配置: 0-否, 1-是(系统配置不参与多租户隔离)', is_system TINYINT NOT NULL DEFAULT 0 COMMENT '是否系统配置: 0-否, 1-是(系统配置不参与多租户隔离)',
status TINYINT NOT NULL DEFAULT 1 COMMENT '状态: 0-禁用, 1-启用', status TINYINT NOT NULL DEFAULT 1 COMMENT '状态: 0-禁用, 1-启用',
group_code VARCHAR(64) COMMENT '分组编码',
group_name VARCHAR(128) COMMENT '分组名称',
sort_order INT DEFAULT 0 COMMENT '排序号',
remark VARCHAR(500) COMMENT '备注',
created_by BIGINT COMMENT '创建人', created_by BIGINT COMMENT '创建人',
created_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', created_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
updated_by BIGINT COMMENT '更新人', updated_by BIGINT COMMENT '更新人',
@ -190,7 +195,8 @@ CREATE TABLE IF NOT EXISTS sys_config (
deleted TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记: 0-未删除, 1-已删除', deleted TINYINT NOT NULL DEFAULT 0 COMMENT '删除标记: 0-未删除, 1-已删除',
PRIMARY KEY (id), PRIMARY KEY (id),
UNIQUE KEY uk_config_key (config_key, deleted), UNIQUE KEY uk_config_key (config_key, deleted),
KEY idx_status (status) KEY idx_status (status),
KEY idx_group_code (group_code)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统配置表(不参与租户隔离)'; ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='系统配置表(不参与租户隔离)';
-- ============================================= -- =============================================

View File

@ -1,7 +1,7 @@
# 资金服务平台数据库设计文档 # 资金服务平台数据库设计文档
> 版本: v1.0 > 版本: v1.1
> 更新日期: 2026-02-13 > 更新日期: 2026-02-22
> 作者: zhangjf > 作者: zhangjf
> 数据库: MySQL 8.0 > 数据库: MySQL 8.0
> 字符集: utf8mb4 > 字符集: utf8mb4
@ -705,6 +705,98 @@
| updated_time | DATETIME | 更新时间DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP | | updated_time | DATETIME | 更新时间DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP |
| deleted | TINYINT | 逻辑删除标志DEFAULT 0 | | deleted | TINYINT | 逻辑删除标志DEFAULT 0 |
### 4.4 实体类与SQL脚本同步规范
> **强制要求**:修改实体类字段时,必须同步更新 SQL 初始化脚本
#### 4.4.1 同步修改流程
```mermaid
graph LR
A[修改实体类字段] --> B[同步修改SQL脚本]
B --> C[更新数据库设计文档]
C --> D[编译验证]
D --> E[提交代码]
```
| 操作类型 | 实体类修改 | SQL脚本修改 | 文档更新 |
|----------|------------|-------------|----------|
| 新增字段 | 添加属性 + Getter/Setter | ALTER TABLE ADD COLUMN | 更新表结构说明 |
| 删除字段 | 删除属性 + Getter/Setter | ALTER TABLE DROP COLUMN | 更新表结构说明 |
| 修改字段 | 修改属性类型 | ALTER TABLE MODIFY COLUMN | 更新字段说明 |
| 新增实体 | 创建Entity类 | CREATE TABLE | 新增表结构说明 |
#### 4.4.2 常见问题与预防
| 问题 | 原因 | 预防措施 |
|------|------|----------|
| Unknown column | 实体类有字段但数据库表无对应列 | 修改实体类后立即更新SQL脚本 |
| SQLSyntaxErrorException | SQL脚本与实体类不一致 | 提交前对比验证 |
| 字段类型不匹配 | Java类型与MySQL类型映射错误 | 参照4.2字段规范 |
#### 4.4.3 Java与MySQL类型映射
| Java类型 | MySQL类型 | 说明 |
|----------|-----------|------|
| Long | BIGINT | 主键、外键、ID |
| String | VARCHAR(n) | 根据长度选择n值 |
| String (长文本) | TEXT | 超过500字符 |
| Integer | INT | 数量、排序 |
| Boolean / Integer | TINYINT | 0/1状态值 |
| BigDecimal | DECIMAL(15,2) | 金额 |
| LocalDateTime | DATETIME | 日期时间 |
| LocalDate | DATE | 日期 |
### 4.5 自动化检查机制
#### 4.5.1 单元测试验证
建议在 `fund-common` 或各业务模块的测试目录中添加实体类与表结构一致性测试:
```java
@SpringBootTest
class EntitySchemaConsistencyTest {
@Autowired
private JdbcTemplate jdbcTemplate;
@Test
@DisplayName("验证SysConfig实体类与sys_config表结构一致")
void testSysConfigSchema() {
// 获取数据库表结构
var columns = jdbcTemplate.queryForList(
"SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS " +
"WHERE TABLE_NAME = 'sys_config' AND TABLE_SCHEMA = DATABASE()"
);
Set<String> dbColumns = columns.stream()
.map(row -> (String) row.get("COLUMN_NAME"))
.collect(Collectors.toSet());
// 获取实体类字段(通过反射)
Set<String> entityFields = Arrays.stream(SysConfig.class.getDeclaredFields())
.map(f -> CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, f.getName()))
.collect(Collectors.toSet());
// 验证一致性
assertThat(entityFields).isSubsetOf(dbColumns);
}
}
```
#### 4.5.2 CI/CD集成检查
在持续集成流程中添加数据库脚本验证步骤:
```yaml
# .gitlab-ci.yml 或 GitHub Actions
validate-schema:
stage: test
script:
- echo "验证SQL脚本语法"
- mysql --help > /dev/null 2>&1 && echo "MySQL client available"
# 可集成sqlfluff等SQL语法检查工具
```
--- ---
**文档结束** **文档结束**

View File

@ -0,0 +1,6 @@
# 开发环境配置
# 开发模式无部署前缀
VITE_BASE=/
# API基础路径开发模式使用代理
VITE_API_BASE_URL=

View File

@ -0,0 +1,6 @@
# 生产环境配置
# 部署路径前缀Nginx路由使用
VITE_BASE=/fadmin/
# API基础路径
VITE_API_BASE_URL=/fund

View File

@ -2,20 +2,20 @@ import { request } from './request'
// 登录 // 登录
export function login(data: { username: string; password: string }) { export function login(data: { username: string; password: string }) {
return request.post('/sys/api/v1/auth/login', data) return request.post('/auth/login', data)
} }
// 登出 // 登出
export function logout() { export function logout() {
return request.post('/sys/api/v1/auth/logout') return request.post('/auth/logout')
} }
// 获取当前用户信息 // 获取当前用户信息
export function getUserInfo() { export function getUserInfo() {
return request.get('/sys/api/v1/auth/info') return request.get('/auth/info')
} }
// 刷新Token // 刷新Token
export function refreshToken() { export function refreshToken() {
return request.post('/sys/api/v1/auth/refresh') return request.post('/auth/refresh')
} }

View File

@ -2,12 +2,12 @@ import { request } from './request'
// 获取参数分组列表 // 获取参数分组列表
export function getConfigGroups() { export function getConfigGroups() {
return request.get('/api/v1/sys/config/groups') return request.get('/sys/config/groups')
} }
// 获取参数列表 // 获取参数列表
export function getConfigList(groupCode?: string) { export function getConfigList(groupCode?: string) {
return request.get('/api/v1/sys/config/list', { return request.get('/sys/config/list', {
params: { groupCode } params: { groupCode }
}) })
} }
@ -19,17 +19,17 @@ export function getConfigPage(params: {
configKey?: string configKey?: string
groupCode?: string groupCode?: string
}) { }) {
return request.get('/api/v1/sys/config/page', { params }) return request.get('/sys/config/page', { params })
} }
// 获取参数详情 // 获取参数详情
export function getConfigById(id: number) { export function getConfigById(id: number) {
return request.get(`/api/v1/sys/config/${id}`) return request.get(`/sys/config/${id}`)
} }
// 根据key获取参数值 // 根据key获取参数值
export function getConfigByKey(configKey: string) { export function getConfigByKey(configKey: string) {
return request.get(`/api/v1/sys/config/key/${configKey}`) return request.get(`/sys/config/key/${configKey}`)
} }
// 创建参数 // 创建参数
@ -42,7 +42,7 @@ export function createConfig(data: {
groupName?: string groupName?: string
sortOrder?: number sortOrder?: number
}) { }) {
return request.post('/api/v1/sys/config', data) return request.post('/sys/config', data)
} }
// 更新参数 // 更新参数
@ -53,7 +53,7 @@ export function updateConfig(data: {
status?: number status?: number
sortOrder?: number sortOrder?: number
}) { }) {
return request.put('/api/v1/sys/config', data) return request.put('/sys/config', data)
} }
// 批量更新参数值 // 批量更新参数值
@ -61,10 +61,10 @@ export function batchUpdateConfig(configs: Array<{
id: number id: number
configValue: string configValue: string
}>) { }>) {
return request.put('/api/v1/sys/config/batch', configs) return request.put('/sys/config/batch', configs)
} }
// 删除参数 // 删除参数
export function deleteConfig(id: number) { export function deleteConfig(id: number) {
return request.delete(`/api/v1/sys/config/${id}`) return request.delete(`/sys/config/${id}`)
} }

View File

@ -1,42 +1,42 @@
import { request } from './request' import { request } from './request'
export function getCustomerList(params: { pageNum: number; pageSize: number; customerName?: string; status?: number }) { export function getCustomerList(params: { pageNum: number; pageSize: number; customerName?: string; status?: number }) {
return request.get('/cust/api/v1/customer/page', { params }) return request.get('/customer/page', { params })
} }
export function getCustomerById(id: number) { export function getCustomerById(id: number) {
return request.get(`/cust/api/v1/customer/${id}`) return request.get(`/customer/${id}`)
} }
export function createCustomer(data: any) { export function createCustomer(data: any) {
return request.post('/cust/api/v1/customer', data) return request.post('/customer', data)
} }
export function updateCustomer(id: number, data: any) { export function updateCustomer(id: number, data: any) {
return request.put(`/cust/api/v1/customer/${id}`, data) return request.put(`/customer/${id}`, data)
} }
export function deleteCustomer(id: number) { export function deleteCustomer(id: number) {
return request.delete(`/cust/api/v1/customer/${id}`) return request.delete(`/customer/${id}`)
} }
// 联系人相关 // 联系人相关
export function getContactList(params: { pageNum: number; pageSize: number; customerId: number }) { export function getContactList(params: { pageNum: number; pageSize: number; customerId: number }) {
return request.get('/cust/api/v1/customer/contact/page', { params }) return request.get('/customer/contact/page', { params })
} }
export function createContact(data: any) { export function createContact(data: any) {
return request.post('/cust/api/v1/customer/contact', data) return request.post('/customer/contact', data)
} }
export function updateContact(id: number, data: any) { export function updateContact(id: number, data: any) {
return request.put(`/cust/api/v1/customer/contact/${id}`, data) return request.put(`/customer/contact/${id}`, data)
} }
export function deleteContact(id: number) { export function deleteContact(id: number) {
return request.delete(`/cust/api/v1/customer/contact/${id}`) return request.delete(`/customer/contact/${id}`)
} }
export function setPrimaryContact(customerId: number, contactId: number) { export function setPrimaryContact(customerId: number, contactId: number) {
return request.put(`/cust/api/v1/customer/${customerId}/contact/${contactId}/primary`) return request.put(`/customer/${customerId}/contact/${contactId}/primary`)
} }

View File

@ -1,25 +1,25 @@
import { request } from './request' import { request } from './request'
export function getDeptList(params?: { deptName?: string; status?: number }) { export function getDeptList(params?: { deptName?: string; status?: number }) {
return request.get('/sys/api/v1/sys/dept/list', { params }) return request.get('/sys/dept/list', { params })
} }
export function getDeptTree() { export function getDeptTree() {
return request.get('/sys/api/v1/sys/dept/tree') return request.get('/sys/dept/tree')
} }
export function getDeptById(id: number) { export function getDeptById(id: number) {
return request.get(`/sys/api/v1/sys/dept/${id}`) return request.get(`/sys/dept/${id}`)
} }
export function createDept(data: any) { export function createDept(data: any) {
return request.post('/sys/api/v1/sys/dept', data) return request.post('/sys/dept', data)
} }
export function updateDept(data: any) { export function updateDept(data: any) {
return request.put('/sys/api/v1/sys/dept', data) return request.put('/sys/dept', data)
} }
export function deleteDept(id: number) { export function deleteDept(id: number) {
return request.delete(`/sys/api/v1/sys/dept/${id}`) return request.delete(`/sys/dept/${id}`)
} }

View File

@ -2,65 +2,65 @@ import { request } from './request'
// 支出类型 // 支出类型
export function getExpenseTypeList(params: { pageNum: number; pageSize: number; typeName?: string; status?: string }) { export function getExpenseTypeList(params: { pageNum: number; pageSize: number; typeName?: string; status?: string }) {
return request.get('/exp/api/v1/exp/expense-type/page', { params }) return request.get('/exp/expense-type/page', { params })
} }
export function getExpenseTypeTree() { export function getExpenseTypeTree() {
return request.get('/exp/api/v1/exp/expense-type/tree') return request.get('/exp/expense-type/tree')
} }
export function createExpenseType(data: any) { export function createExpenseType(data: any) {
return request.post('/exp/api/v1/exp/expense-type', data) return request.post('/exp/expense-type', data)
} }
export function updateExpenseType(id: number, data: any) { export function updateExpenseType(id: number, data: any) {
return request.put(`/exp/api/v1/exp/expense-type/${id}`, data) return request.put(`/exp/expense-type/${id}`, data)
} }
export function deleteExpenseType(id: number) { export function deleteExpenseType(id: number) {
return request.delete(`/exp/api/v1/exp/expense-type/${id}`) return request.delete(`/exp/expense-type/${id}`)
} }
// 支出管理 // 支出管理
export function getExpenseList(params: { pageNum: number; pageSize: number; title?: string; expenseType?: number; approvalStatus?: number; payStatus?: number }) { export function getExpenseList(params: { pageNum: number; pageSize: number; title?: string; expenseType?: number; approvalStatus?: number; payStatus?: number }) {
return request.get('/exp/api/v1/exp/expense/page', { params }) return request.get('/exp/expense/page', { params })
} }
export function getExpenseById(id: number) { export function getExpenseById(id: number) {
return request.get(`/exp/api/v1/exp/expense/${id}`) return request.get(`/exp/expense/${id}`)
} }
export function createExpense(data: any) { export function createExpense(data: any) {
return request.post('/exp/api/v1/exp/expense', data) return request.post('/exp/expense', data)
} }
export function updateExpense(id: number, data: any) { export function updateExpense(id: number, data: any) {
return request.put(`/exp/api/v1/exp/expense/${id}`, data) return request.put(`/exp/expense/${id}`, data)
} }
export function deleteExpense(id: number) { export function deleteExpense(id: number) {
return request.delete(`/exp/api/v1/exp/expense/${id}`) return request.delete(`/exp/expense/${id}`)
} }
// 审批流程 // 审批流程
export function submitExpense(id: number) { export function submitExpense(id: number) {
return request.post(`/exp/api/v1/exp/expense/${id}/submit`) return request.post(`/exp/expense/${id}/submit`)
} }
export function withdrawExpense(id: number) { export function withdrawExpense(id: number) {
return request.post(`/exp/api/v1/exp/expense/${id}/withdraw`) return request.post(`/exp/expense/${id}/withdraw`)
} }
export function approveExpense(id: number, comment: string) { export function approveExpense(id: number, comment: string) {
return request.put(`/exp/api/v1/exp/expense/${id}/approve?comment=${encodeURIComponent(comment)}`) return request.put(`/exp/expense/${id}/approve?comment=${encodeURIComponent(comment)}`)
} }
export function rejectExpense(id: number, comment: string) { export function rejectExpense(id: number, comment: string) {
return request.put(`/exp/api/v1/exp/expense/${id}/reject?comment=${encodeURIComponent(comment)}`) return request.put(`/exp/expense/${id}/reject?comment=${encodeURIComponent(comment)}`)
} }
export function confirmPayExpense(id: number, payChannel: string, payVoucher?: string) { export function confirmPayExpense(id: number, payChannel: string, payVoucher?: string) {
return request.put(`/exp/api/v1/exp/expense/${id}/confirm-pay?payChannel=${payChannel}&payVoucher=${payVoucher || ''}`) return request.put(`/exp/expense/${id}/confirm-pay?payChannel=${payChannel}&payVoucher=${payVoucher || ''}`)
} }
// 导出支出明细 // 导出支出明细
@ -76,7 +76,7 @@ export function exportExpense(params?: { title?: string; expenseType?: number; a
if (params?.payStatus !== undefined) queryParams.append('payStatus', String(params.payStatus)) if (params?.payStatus !== undefined) queryParams.append('payStatus', String(params.payStatus))
const queryString = queryParams.toString() const queryString = queryParams.toString()
const url = `${baseUrl}/exp/api/v1/exp/expense/export${queryString ? '?' + queryString : ''}` const url = `${baseUrl}/exp/expense/export${queryString ? '?' + queryString : ''}`
return fetch(url, { return fetch(url, {
headers: { headers: {

View File

@ -8,7 +8,7 @@ export function uploadFile(file: File, businessType?: string, businessId?: numbe
if (businessId) formData.append('businessId', String(businessId)) if (businessId) formData.append('businessId', String(businessId))
if (description) formData.append('description', description) if (description) formData.append('description', description)
return request.post('/file/api/v1/file/upload', formData, { return request.post('/file/upload', formData, {
headers: { headers: {
'Content-Type': 'multipart/form-data' 'Content-Type': 'multipart/form-data'
} }
@ -17,25 +17,25 @@ export function uploadFile(file: File, businessType?: string, businessId?: numbe
// 获取文件列表 // 获取文件列表
export function getFileList(params: { pageNum: number; pageSize: number; businessType?: string; businessId?: number; fileType?: string }) { export function getFileList(params: { pageNum: number; pageSize: number; businessType?: string; businessId?: number; fileType?: string }) {
return request.get('/file/api/v1/file/page', { params }) return request.get('/file/page', { params })
} }
// 根据业务查询文件 // 根据业务查询文件
export function getFilesByBusiness(businessType: string, businessId: number) { export function getFilesByBusiness(businessType: string, businessId: number) {
return request.get('/file/api/v1/file/list', { params: { businessType, businessId } }) return request.get('/file/list', { params: { businessType, businessId } })
} }
// 获取文件详情 // 获取文件详情
export function getFileById(id: number) { export function getFileById(id: number) {
return request.get(`/file/api/v1/file/${id}`) return request.get(`/file/${id}`)
} }
// 删除文件 // 删除文件
export function deleteFile(id: number) { export function deleteFile(id: number) {
return request.delete(`/file/api/v1/file/${id}`) return request.delete(`/file/${id}`)
} }
// 获取文件下载URL // 获取文件下载URL
export function getFileDownloadUrl(filePath: string) { export function getFileDownloadUrl(filePath: string) {
return `/file/api/v1/file/download/${filePath}` return `/file/download/${filePath}`
} }

View File

@ -1,30 +1,30 @@
import { request } from './request' import { request } from './request'
export function getMenuList(params?: { menuName?: string; status?: number }) { export function getMenuList(params?: { menuName?: string; status?: number }) {
return request.get('/sys/api/v1/sys/menu/tree', { params }) return request.get('/sys/menu/tree', { params })
} }
export function getMenuTree() { export function getMenuTree() {
return request.get('/sys/api/v1/sys/menu/tree') return request.get('/sys/menu/tree')
} }
export function getMenuById(id: number) { export function getMenuById(id: number) {
return request.get(`/sys/api/v1/sys/menu/${id}`) return request.get(`/sys/menu/${id}`)
} }
export function createMenu(data: any) { export function createMenu(data: any) {
return request.post('/sys/api/v1/sys/menu', data) return request.post('/sys/menu', data)
} }
export function updateMenu(data: any) { export function updateMenu(data: any) {
return request.put('/sys/api/v1/sys/menu', data) return request.put('/sys/menu', data)
} }
export function deleteMenu(id: number) { export function deleteMenu(id: number) {
return request.delete(`/sys/api/v1/sys/menu/${id}`) return request.delete(`/sys/menu/${id}`)
} }
export function getUserMenus(userId?: number) { export function getUserMenus(userId?: number) {
const id = userId || JSON.parse(localStorage.getItem('userInfo') || '{}').id const id = userId || JSON.parse(localStorage.getItem('userInfo') || '{}').id
return request.get(`/sys/api/v1/sys/menu/user/${id}`) return request.get(`/sys/menu/user/${id}`)
} }

View File

@ -1,42 +1,42 @@
import { request } from './request' import { request } from './request'
export function getProjectList(params: { pageNum: number; pageSize: number; projectName?: string; status?: string }) { export function getProjectList(params: { pageNum: number; pageSize: number; projectName?: string; status?: string }) {
return request.get('/proj/api/v1/project/page', { params }) return request.get('/project/page', { params })
} }
export function getProjectById(id: number) { export function getProjectById(id: number) {
return request.get(`/proj/api/v1/project/${id}`) return request.get(`/project/${id}`)
} }
export function createProject(data: any) { export function createProject(data: any) {
return request.post('/proj/api/v1/project', data) return request.post('/project', data)
} }
export function updateProject(id: number, data: any) { export function updateProject(id: number, data: any) {
return request.put(`/proj/api/v1/project/${id}`, data) return request.put(`/project/${id}`, data)
} }
export function deleteProject(id: number) { export function deleteProject(id: number) {
return request.delete(`/proj/api/v1/project/${id}`) return request.delete(`/project/${id}`)
} }
// 需求工单 // 需求工单
export function getRequirementList(params: { pageNum: number; pageSize: number; requirementName?: string; status?: string }) { export function getRequirementList(params: { pageNum: number; pageSize: number; requirementName?: string; status?: string }) {
return request.get('/proj/api/v1/requirement/page', { params }) return request.get('/requirement/page', { params })
} }
export function getRequirementById(id: number) { export function getRequirementById(id: number) {
return request.get(`/proj/api/v1/requirement/${id}`) return request.get(`/requirement/${id}`)
} }
export function createRequirement(data: any) { export function createRequirement(data: any) {
return request.post('/proj/api/v1/requirement', data) return request.post('/requirement', data)
} }
export function updateRequirement(id: number, data: any) { export function updateRequirement(id: number, data: any) {
return request.put(`/proj/api/v1/requirement/${id}`, data) return request.put(`/requirement/${id}`, data)
} }
export function deleteRequirement(id: number) { export function deleteRequirement(id: number) {
return request.delete(`/proj/api/v1/requirement/${id}`) return request.delete(`/requirement/${id}`)
} }

View File

@ -2,52 +2,52 @@ import { request } from './request'
// 应收款管理 // 应收款管理
export function getReceivableList(params: { pageNum: number; pageSize: number; projectId?: number; customerId?: number; status?: string; confirmStatus?: number }) { export function getReceivableList(params: { pageNum: number; pageSize: number; projectId?: number; customerId?: number; status?: string; confirmStatus?: number }) {
return request.get('/receipt/api/v1/receipt/receivable/page', { params }) return request.get('/receipt/receivable/page', { params })
} }
export function getReceivableById(id: number) { export function getReceivableById(id: number) {
return request.get(`/receipt/api/v1/receipt/receivable/${id}`) return request.get(`/receipt/receivable/${id}`)
} }
export function createReceivable(data: any) { export function createReceivable(data: any) {
return request.post('/receipt/api/v1/receipt/receivable', data) return request.post('/receipt/receivable', data)
} }
export function updateReceivable(id: number, data: any) { export function updateReceivable(id: number, data: any) {
return request.put(`/receipt/api/v1/receipt/receivable/${id}`, data) return request.put(`/receipt/receivable/${id}`, data)
} }
export function deleteReceivable(id: number) { export function deleteReceivable(id: number) {
return request.delete(`/receipt/api/v1/receipt/receivable/${id}`) return request.delete(`/receipt/receivable/${id}`)
} }
export function confirmReceivable(id: number) { export function confirmReceivable(id: number) {
return request.put(`/receipt/api/v1/receipt/receivable/${id}/confirm`) return request.put(`/receipt/receivable/${id}/confirm`)
} }
export function cancelConfirmReceivable(id: number) { export function cancelConfirmReceivable(id: number) {
return request.put(`/receipt/api/v1/receipt/receivable/${id}/cancel-confirm`) return request.put(`/receipt/receivable/${id}/cancel-confirm`)
} }
export function recordReceipt(id: number, data: any) { export function recordReceipt(id: number, data: any) {
return request.post(`/receipt/api/v1/receipt/receivable/${id}/receipt`, data) return request.post(`/receipt/receivable/${id}/receipt`, data)
} }
export function getReceiptRecords(receivableId: number) { export function getReceiptRecords(receivableId: number) {
return request.get(`/receipt/api/v1/receipt/receivable/${receivableId}/receipts`) return request.get(`/receipt/receivable/${receivableId}/receipts`)
} }
// 收款记录 // 收款记录
export function getReceiptList(params: { pageNum: number; pageSize: number; receivableId?: number }) { export function getReceiptList(params: { pageNum: number; pageSize: number; receivableId?: number }) {
return request.get('/receipt/api/v1/receipt/receipt/page', { params }) return request.get('/receipt/receipt/page', { params })
} }
export function getReceiptById(id: number) { export function getReceiptById(id: number) {
return request.get(`/receipt/api/v1/receipt/receipt/${id}`) return request.get(`/receipt/receipt/${id}`)
} }
export function createReceipt(data: any) { export function createReceipt(data: any) {
return request.post('/receipt/api/v1/receipt/receipt', data) return request.post('/receipt/receipt', data)
} }
// 导出应收款明细 // 导出应收款明细
@ -63,7 +63,7 @@ export function exportReceivable(params?: { projectId?: number; customerId?: num
if (params?.confirmStatus !== undefined) queryParams.append('confirmStatus', String(params.confirmStatus)) if (params?.confirmStatus !== undefined) queryParams.append('confirmStatus', String(params.confirmStatus))
const queryString = queryParams.toString() const queryString = queryParams.toString()
const url = `${baseUrl}/receipt/api/v1/receipt/receivable/export${queryString ? '?' + queryString : ''}` const url = `${baseUrl}/receipt/receivable/export${queryString ? '?' + queryString : ''}`
return fetch(url, { return fetch(url, {
headers: { headers: {

View File

@ -41,27 +41,27 @@ export interface ProjectFinance {
// 获取仪表盘统计数据 // 获取仪表盘统计数据
export function getDashboardStats() { export function getDashboardStats() {
return request.get<DashboardStats>('/report/api/v1/report/dashboard/stats') return request.get<DashboardStats>('/report/dashboard/stats')
} }
// 获取收支趋势 // 获取收支趋势
export function getTrend(period: 'week' | 'month' | 'quarter' = 'week') { export function getTrend(period: 'week' | 'month' | 'quarter' = 'week') {
return request.get<TrendItem[]>('/report/api/v1/report/trend', { params: { period } }) return request.get<TrendItem[]>('/report/trend', { params: { period } })
} }
// 获取项目状态分布 // 获取项目状态分布
export function getProjectStatusDistribution() { export function getProjectStatusDistribution() {
return request.get<DistributionItem[]>('/report/api/v1/report/project/status-distribution') return request.get<DistributionItem[]>('/report/project/status-distribution')
} }
// 获取支出类型分布 // 获取支出类型分布
export function getExpenseTypeDistribution() { export function getExpenseTypeDistribution() {
return request.get<DistributionItem[]>('/report/api/v1/report/expense/type-distribution') return request.get<DistributionItem[]>('/report/expense/type-distribution')
} }
// 获取项目收支分析 // 获取项目收支分析
export function getProjectFinance(params?: { status?: string; customerId?: number }) { export function getProjectFinance(params?: { status?: string; customerId?: number }) {
return request.get<ProjectFinance[]>('/report/api/v1/report/project/finance', { params }) return request.get<ProjectFinance[]>('/report/project/finance', { params })
} }
// 导出项目收支分析Excel // 导出项目收支分析Excel
@ -76,7 +76,7 @@ export function exportProjectFinance(params?: { status?: string; customerId?: nu
if (params?.customerId) queryParams.append('customerId', String(params.customerId)) if (params?.customerId) queryParams.append('customerId', String(params.customerId))
const queryString = queryParams.toString() const queryString = queryParams.toString()
const url = `${baseUrl}/report/api/v1/report/project/finance/export${queryString ? '?' + queryString : ''}` const url = `${baseUrl}/report/project/finance/export${queryString ? '?' + queryString : ''}`
// 使用fetch下载 // 使用fetch下载
return fetch(url, { return fetch(url, {

View File

@ -3,7 +3,7 @@ import { ElMessage } from 'element-plus'
// 创建axios实例 // 创建axios实例
const service: AxiosInstance = axios.create({ const service: AxiosInstance = axios.create({
baseURL: '/fund', baseURL: import.meta.env.VITE_API_BASE_URL || '/fund',
timeout: 15000, timeout: 15000,
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'

View File

@ -1,29 +1,29 @@
import { request } from './request' import { request } from './request'
export function getRoleList(params: { pageNum: number; pageSize: number; roleName?: string }) { export function getRoleList(params: { pageNum: number; pageSize: number; roleName?: string }) {
return request.get('/sys/api/v1/sys/role/page', { params }) return request.get('/sys/role/page', { params })
} }
export function getRoleById(id: number) { export function getRoleById(id: number) {
return request.get(`/sys/api/v1/sys/role/${id}`) return request.get(`/sys/role/${id}`)
} }
export function createRole(data: any) { export function createRole(data: any) {
return request.post('/sys/api/v1/sys/role', data) return request.post('/sys/role', data)
} }
export function updateRole(data: any) { export function updateRole(data: any) {
return request.put('/sys/api/v1/sys/role', data) return request.put('/sys/role', data)
} }
export function deleteRole(id: number) { export function deleteRole(id: number) {
return request.delete(`/sys/api/v1/sys/role/${id}`) return request.delete(`/sys/role/${id}`)
} }
export function getRoleMenus(id: number) { export function getRoleMenus(id: number) {
return request.get(`/sys/api/v1/sys/role/${id}/menus`) return request.get(`/sys/role/${id}/menus`)
} }
export function assignMenus(id: number, menuIds: number[]) { export function assignMenus(id: number, menuIds: number[]) {
return request.put(`/sys/api/v1/sys/role/${id}/menus`, menuIds) return request.put(`/sys/role/${id}/menus`, menuIds)
} }

View File

@ -32,34 +32,34 @@ export interface TenantDTO {
// 分页查询租户 // 分页查询租户
export function pageTenants(pageNum: number, pageSize: number, keyword?: string) { export function pageTenants(pageNum: number, pageSize: number, keyword?: string) {
return request.get<{ records: Tenant[], total: number }>(`/sys/api/v1/sys/tenant/page`, { return request.get<{ records: Tenant[], total: number }>(`/sys/tenant/page`, {
params: { pageNum, pageSize, keyword } params: { pageNum, pageSize, keyword }
}) })
} }
// 获取租户详情 // 获取租户详情
export function getTenantById(id: number) { export function getTenantById(id: number) {
return request.get<Tenant>(`/sys/api/v1/sys/tenant/${id}`) return request.get<Tenant>(`/sys/tenant/${id}`)
} }
// 创建租户 // 创建租户
export function createTenant(data: TenantDTO) { export function createTenant(data: TenantDTO) {
return request.post<number>('/sys/api/v1/sys/tenant', data) return request.post<number>('/sys/tenant', data)
} }
// 更新租户 // 更新租户
export function updateTenant(data: TenantDTO) { export function updateTenant(data: TenantDTO) {
return request.put<boolean>('/sys/api/v1/sys/tenant', data) return request.put<boolean>('/sys/tenant', data)
} }
// 删除租户 // 删除租户
export function deleteTenant(id: number) { export function deleteTenant(id: number) {
return request.delete<boolean>(`/sys/api/v1/sys/tenant/${id}`) return request.delete<boolean>(`/sys/tenant/${id}`)
} }
// 更新租户状态 // 更新租户状态
export function updateTenantStatus(id: number, status: number) { export function updateTenantStatus(id: number, status: number) {
return request.put<boolean>(`/sys/api/v1/sys/tenant/${id}/status`, null, { return request.put<boolean>(`/sys/tenant/${id}/status`, null, {
params: { status } params: { status }
}) })
} }

View File

@ -2,45 +2,45 @@ import { request } from './request'
// 用户列表 // 用户列表
export function getUserList(params: { pageNum: number; pageSize: number; username?: string; status?: number }) { export function getUserList(params: { pageNum: number; pageSize: number; username?: string; status?: number }) {
return request.get('/sys/api/v1/sys/user/page', { params }) return request.get('/sys/user/page', { params })
} }
// 获取用户详情 // 获取用户详情
export function getUserById(id: number) { export function getUserById(id: number) {
return request.get(`/sys/api/v1/sys/user/${id}`) return request.get(`/sys/user/${id}`)
} }
// 创建用户 // 创建用户
export function createUser(data: any) { export function createUser(data: any) {
return request.post('/sys/api/v1/sys/user', data) return request.post('/sys/user', data)
} }
// 更新用户 // 更新用户
export function updateUser(data: any) { export function updateUser(data: any) {
return request.put('/sys/api/v1/sys/user', data) return request.put('/sys/user', data)
} }
// 删除用户 // 删除用户
export function deleteUser(id: number) { export function deleteUser(id: number) {
return request.delete(`/sys/api/v1/sys/user/${id}`) return request.delete(`/sys/user/${id}`)
} }
// 更新用户状态 // 更新用户状态
export function updateUserStatus(id: number, status: number) { export function updateUserStatus(id: number, status: number) {
return request.put(`/sys/api/v1/sys/user/${id}/status?status=${status}`) return request.put(`/sys/user/${id}/status?status=${status}`)
} }
// 获取个人信息 // 获取个人信息
export function getProfile() { export function getProfile() {
return request.get('/sys/api/v1/sys/profile') return request.get('/sys/profile')
} }
// 更新个人信息 // 更新个人信息
export function updateProfile(data: { realName: string; phone?: string; email?: string; avatar?: string }) { export function updateProfile(data: { realName: string; phone?: string; email?: string; avatar?: string }) {
return request.put('/sys/api/v1/sys/profile', data) return request.put('/sys/profile', data)
} }
// 修改密码 // 修改密码
export function updatePassword(data: { oldPassword: string; newPassword: string; confirmPassword: string }) { export function updatePassword(data: { oldPassword: string; newPassword: string; confirmPassword: string }) {
return request.put('/sys/api/v1/sys/profile/password', data) return request.put('/sys/profile/password', data)
} }

View File

@ -90,7 +90,7 @@ const fileList = ref<UploadFile[]>([])
const uploadUrl = computed(() => { const uploadUrl = computed(() => {
const baseUrl = import.meta.env.VITE_API_URL || '' const baseUrl = import.meta.env.VITE_API_URL || ''
return `${baseUrl}/file/api/v1/file/upload` return `${baseUrl}/fund/file/upload`
}) })
const headers = computed(() => { const headers = computed(() => {

View File

@ -134,7 +134,7 @@ const routes: RouteRecordRaw[] = [
] ]
const router = createRouter({ const router = createRouter({
history: createWebHistory(), history: createWebHistory(import.meta.env.BASE_URL),
routes routes
}) })

View File

@ -1,53 +1,61 @@
import { defineConfig } from 'vite' import { defineConfig, loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue' import vue from '@vitejs/plugin-vue'
import path from 'path' import path from 'path'
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig(({ mode }) => {
plugins: [vue()], // 加载环境变量
resolve: { const env = loadEnv(mode, process.cwd())
alias: { const base = env.VITE_BASE || '/'
'@': path.resolve(__dirname, 'src')
} return {
}, // 部署路径前缀
server: { base,
port: 3000, plugins: [vue()],
proxy: { resolve: {
'/auth/': { alias: {
target: 'http://localhost:8100', '@': path.resolve(__dirname, 'src')
changeOrigin: true }
}, },
'/sys/': { server: {
target: 'http://localhost:8000', port: 3000,
changeOrigin: true proxy: {
}, '/auth/': {
'/cust/': { target: 'http://localhost:8100',
target: 'http://localhost:8000', changeOrigin: true
changeOrigin: true },
}, '/sys/': {
'/proj/': { target: 'http://localhost:8000',
target: 'http://localhost:8000', changeOrigin: true
changeOrigin: true },
}, '/cust/': {
'/req/': { target: 'http://localhost:8000',
target: 'http://localhost:8000', changeOrigin: true
changeOrigin: true },
}, '/proj/': {
'/exp/': { target: 'http://localhost:8000',
target: 'http://localhost:8000', changeOrigin: true
changeOrigin: true },
}, '/req/': {
'/receipt/': { target: 'http://localhost:8000',
target: 'http://localhost:8000', changeOrigin: true
changeOrigin: true },
}, '/exp/': {
'/file/': { target: 'http://localhost:8000',
target: 'http://localhost:8000', changeOrigin: true
changeOrigin: true },
}, '/receipt/': {
'/report/': { target: 'http://localhost:8000',
target: 'http://localhost:8000', changeOrigin: true
changeOrigin: true },
'/file/': {
target: 'http://localhost:8000',
changeOrigin: true
},
'/report/': {
target: 'http://localhost:8000',
changeOrigin: true
}
} }
} }
} }

View File

@ -38,18 +38,19 @@ public class TenantGatewayFilter implements GlobalFilter, Ordered {
public static final String HEADER_USERNAME = "X-Username"; public static final String HEADER_USERNAME = "X-Username";
// 白名单路径不需要X-Tenant-Id // 白名单路径不需要X-Tenant-Id
// 路径格式前端请求路径baseURL=/fund已添加这里只需相对路径
private static final List<String> TENANT_ID_WHITE_LIST = Arrays.asList( private static final List<String> TENANT_ID_WHITE_LIST = Arrays.asList(
"/sys/api/v1/auth/login", // 登录接口 "/fund/auth/login", // 登录接口
"/sys/api/v1/auth/logout", // 登出接口 "/fund/auth/logout", // 登出接口
"/actuator/health", // 健康检查 "/actuator/health", // 网关健康检查
"/sys/api/v1/sys/health", // 系统服务健康检查 "/fund/sys/health", // 系统服务健康检查
"/cust/api/v1/cust/health", // 客户服务健康检查 "/fund/cust/health", // 客户服务健康检查
"/proj/api/v1/proj/health", // 项目服务健康检查 "/fund/proj/health", // 项目服务健康检查
"/exp/api/v1/exp/health", // 支出服务健康检查 "/fund/exp/health", // 支出服务健康检查
"/receipt/api/v1/receipt/health", // 收款服务健康检查 "/fund/receipt/health", // 收款服务健康检查
"/report/api/v1/report/health", // 报表服务健康检查 "/fund/report/health", // 报表服务健康检查
"/req/api/v1/req/health", // 请求服务健康检查 "/fund/req/health", // 请求服务健康检查
"/file/api/v1/file/health" // 文件服务健康检查 "/fund/file/health" // 文件服务健康检查
); );
private final ObjectMapper objectMapper = new ObjectMapper(); private final ObjectMapper objectMapper = new ObjectMapper();

View File

@ -39,11 +39,17 @@ public class TokenAuthFilter implements GlobalFilter, Ordered {
private static final String TENANT_ID_HEADER = "X-Tenant-Id"; private static final String TENANT_ID_HEADER = "X-Tenant-Id";
// 白名单路径(不需要token验证) // 白名单路径(不需要token验证)
// 路径格式前端请求路径baseURL=/fund已添加
private static final List<String> WHITE_LIST = Arrays.asList( private static final List<String> WHITE_LIST = Arrays.asList(
"/sys/api/v1/auth/login", "/fund/auth/login",
"/sys/api/v1/sys/health", "/fund/sys/health",
"/cust/api/v1/cust/health", "/fund/cust/health",
"/proj/api/v1/proj/health" "/fund/proj/health",
"/fund/exp/health",
"/fund/receipt/health",
"/fund/report/health",
"/fund/req/health",
"/fund/file/health"
); );
private final ReactiveTokenService reactiveTokenService; private final ReactiveTokenService reactiveTokenService;

View File

@ -59,29 +59,37 @@ spring:
maxAge: 3600 maxAge: 3600
routes: routes:
# 系统管理服务 (使用负载均衡) # 系统管理服务
# 路径规则: /fund/auth/* -> /api/v1/auth/*
# /fund/sys/* -> /api/v1/sys/*
- id: fund-sys - id: fund-sys
uri: lb://fund-sys uri: lb://fund-sys
predicates: predicates:
- Path=/fund/sys/** - Path=/fund/auth/**,/fund/sys/**
filters: filters:
- StripPrefix=2 - StripPrefix=1
- PrefixPath=/api/v1
# 客户管理服务 # 客户管理服务
# 路径规则: /fund/customer/* -> /api/v1/customer/*
- id: fund-cust - id: fund-cust
uri: lb://fund-cust uri: lb://fund-cust
predicates: predicates:
- Path=/fund/cust/** - Path=/fund/customer/**,/fund/cust/health
filters: filters:
- StripPrefix=2 - StripPrefix=1
- PrefixPath=/api/v1
# 项目管理服务 # 项目管理服务
# 路径规则: /fund/project/* -> /api/v1/project/*
# /fund/requirement/* -> /api/v1/requirement/*
- id: fund-proj - id: fund-proj
uri: lb://fund-proj uri: lb://fund-proj
predicates: predicates:
- Path=/fund/proj/** - Path=/fund/project/**,/fund/requirement/**,/fund/proj/health
filters: filters:
- StripPrefix=2 - StripPrefix=1
- PrefixPath=/api/v1
# 用款申请服务 # 用款申请服务
- id: fund-req - id: fund-req
@ -89,7 +97,8 @@ spring:
predicates: predicates:
- Path=/fund/req/** - Path=/fund/req/**
filters: filters:
- StripPrefix=2 - StripPrefix=1
- PrefixPath=/api/v1
# 支出管理服务 # 支出管理服务
- id: fund-exp - id: fund-exp
@ -97,7 +106,8 @@ spring:
predicates: predicates:
- Path=/fund/exp/** - Path=/fund/exp/**
filters: filters:
- StripPrefix=2 - StripPrefix=1
- PrefixPath=/api/v1
# 收款管理服务 # 收款管理服务
- id: fund-receipt - id: fund-receipt
@ -105,7 +115,8 @@ spring:
predicates: predicates:
- Path=/fund/receipt/** - Path=/fund/receipt/**
filters: filters:
- StripPrefix=2 - StripPrefix=1
- PrefixPath=/api/v1
# 报表服务 # 报表服务
- id: fund-report - id: fund-report
@ -113,7 +124,8 @@ spring:
predicates: predicates:
- Path=/fund/report/** - Path=/fund/report/**
filters: filters:
- StripPrefix=2 - StripPrefix=1
- PrefixPath=/api/v1
# 文件服务 # 文件服务
- id: fund-file - id: fund-file
@ -121,7 +133,8 @@ spring:
predicates: predicates:
- Path=/fund/file/** - Path=/fund/file/**
filters: filters:
- StripPrefix=2 - StripPrefix=1
- PrefixPath=/api/v1
# 多租户路由配置 # 多租户路由配置
tenant: tenant:

View File

@ -2,67 +2,67 @@ import request from './request'
// 用户认证 // 用户认证
export function login(data: { username: string; password: string }) { export function login(data: { username: string; password: string }) {
return request.post('/sys/api/v1/auth/login', data) return request.post('/auth/login', data)
} }
export function getUserInfo() { export function getUserInfo() {
return request.get('/sys/api/v1/auth/info') return request.get('/auth/info')
} }
export function logout() { export function logout() {
return request.post('/sys/api/v1/auth/logout') return request.post('/auth/logout')
} }
// 项目管理 // 项目管理
export function getProjectList(params?: { pageNum: number; pageSize: number; projectName?: string }) { export function getProjectList(params?: { pageNum: number; pageSize: number; projectName?: string }) {
return request.get('/proj/api/v1/project/page', { params }) return request.get('/project/page', { params })
} }
export function getProjectById(id: number) { export function getProjectById(id: number) {
return request.get(`/proj/api/v1/project/${id}`) return request.get(`/project/${id}`)
} }
// 客户管理 // 客户管理
export function getCustomerList(params?: { pageNum: number; pageSize: number; customerName?: string }) { export function getCustomerList(params?: { pageNum: number; pageSize: number; customerName?: string }) {
return request.get('/cust/api/v1/customer/page', { params }) return request.get('/customer/page', { params })
} }
// 支出管理 // 支出管理
export function createExpense(data: any) { export function createExpense(data: any) {
return request.post('/exp/api/v1/exp/expense', data) return request.post('/exp/expense', data)
} }
export function getExpenseList(params: { pageNum: number; pageSize: number }) { export function getExpenseList(params: { pageNum: number; pageSize: number }) {
return request.get('/exp/api/v1/exp/expense/page', { params }) return request.get('/exp/expense/page', { params })
} }
// 应收款管理 // 应收款管理
export function getReceivableList(params: { pageNum: number; pageSize: number; status?: string }) { export function getReceivableList(params: { pageNum: number; pageSize: number; status?: string }) {
return request.get('/receipt/api/v1/receipt/receivable/page', { params }) return request.get('/receipt/receivable/page', { params })
} }
export function getUpcomingDueList(daysWithin: number = 7) { export function getUpcomingDueList(daysWithin: number = 7) {
return request.get(`/receipt/api/v1/receipt/receivable/upcoming-due?daysWithin=${daysWithin}`) return request.get(`/receipt/receivable/upcoming-due?daysWithin=${daysWithin}`)
} }
// 统计数据 // 统计数据
export function getTodayIncome() { export function getTodayIncome() {
return request.get('/receipt/api/v1/receipt/receivable/stats/today-income') return request.get('/receipt/receivable/stats/today-income')
} }
export function getTodayExpense() { export function getTodayExpense() {
return request.get('/exp/api/v1/exp/expense/stats/today-expense') return request.get('/exp/expense/stats/today-expense')
} }
export function getUnpaidAmount() { export function getUnpaidAmount() {
return request.get('/receipt/api/v1/receipt/receivable/stats/unpaid-amount') return request.get('/receipt/receivable/stats/unpaid-amount')
} }
export function getOverdueCount() { export function getOverdueCount() {
return request.get('/receipt/api/v1/receipt/receivable/stats/overdue-count') return request.get('/receipt/receivable/stats/overdue-count')
} }
// 支出类型 // 支出类型
export function getExpenseTypeTree() { export function getExpenseTypeTree() {
return request.get('/exp/api/v1/exp/expense-type/tree') return request.get('/exp/expense-type/tree')
} }

View File

@ -70,11 +70,28 @@ generate_nginx_conf() {
local project_name=$1 local project_name=$1
local gateway_host=$2 local gateway_host=$2
local output_file=$3 local output_file=$3
local location_prefix=$4 # 部署路径前缀,如 /fadmin
# 根据项目名确定部署前缀
if [ -z "${location_prefix}" ]; then
case "${project_name}" in
"fund-admin")
location_prefix="/fadmin"
;;
"fund-mobile")
location_prefix="/fmobile"
;;
*)
location_prefix=""
;;
esac
fi
cat > "${output_file}" << EOF cat > "${output_file}" << EOF
# ${project_name} Nginx配置 # ${project_name} Nginx配置
# 生成时间: $(date '+%Y-%m-%d %H:%M:%S') # 生成时间: $(date '+%Y-%m-%d %H:%M:%S')
# 网关地址: ${gateway_host} # 网关地址: ${gateway_host}
# 部署路径: ${location_prefix}
server { server {
listen 80; listen 80;
@ -112,27 +129,11 @@ server {
# 文件上传大小限制 # 文件上传大小限制
client_max_body_size 100m; client_max_body_size 100m;
} }
# 兼容旧版/api代理可选逐步迁移后可删除
location /api/ {
proxy_pass http://${gateway_host}/;
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto \$scheme;
# 超时配置
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# 文件上传大小限制
client_max_body_size 100m;
}
# Vue Router History模式支持 # Vue Router History模式支持
location / { location ${location_prefix}/ {
try_files \$uri \$uri/ /index.html; alias /usr/share/nginx/html/;
try_files \$uri \$uri/ ${location_prefix}/index.html;
} }
# 安全头 # 安全头