docs: 生成项目文档并优化项目结构
- 生成完整的 README.md 项目文档 - 统一图片路径为相对引用 - 完善 .gitignore 配置,添加常见忽略项 - 清理未使用的临时文件和图片资源 - 删除废弃的脚本文件
This commit is contained in:
@@ -60,3 +60,6 @@ backups/
|
|||||||
# Cache
|
# Cache
|
||||||
.cache/
|
.cache/
|
||||||
*.cache
|
*.cache
|
||||||
|
|
||||||
|
# Workflow
|
||||||
|
.work/
|
||||||
|
|||||||
@@ -1,37 +1,87 @@
|
|||||||
# 轻远电力老挝ERP系统
|
# 云海财务系统
|
||||||
|
|
||||||
## 系统访问
|
## 1. 项目简介
|
||||||
- **前端地址**: http://43.161.248.209:3001/
|
|
||||||
- **后端API**: http://43.161.248.209:3000/
|
|
||||||
|
|
||||||
## 测试账号
|
- **项目概述**:云海财务系统是一款高效、智能、专业的企业财务与项目管理平台,能够全方位提升企业的项目监控与资金流转效率。
|
||||||
| 用户名 | 密码 | 角色 |
|
- **项目定位**:主要面向中大型企业、工程建设及跨国分公司,提供财务报销、项目预算与进度管理服务。
|
||||||
|--------|------|------|
|
- **核心价值**:实现项目与财务数据的无缝对接,简化审批流,实时监控预算使用情况,确保企业资金健康与合规。
|
||||||
| admin | 123456 | 系统管理员 |
|
|
||||||
| finance1 | 123456 | 财务专员 |
|
|
||||||
| shejianjun | 123456 | 管理员 |
|
|
||||||
|
|
||||||
## 功能模块
|
## 2. 整体架构与技术栈
|
||||||
1. **仪表板** - 数据概览和统计
|
|
||||||
2. **项目管理** - 项目创建、查看、编辑
|
|
||||||
3. **财务管理** - 预支申请、报销申请
|
|
||||||
4. **报表中心** - 财务报表查看
|
|
||||||
|
|
||||||
## 技术栈
|
本系统采用前后端分离的现代化架构,通过 Docker 容器化部署以确保高可用性。
|
||||||
- 前端:React + TypeScript + Ant Design + Vite
|
|
||||||
- 后端:Node.js + Express
|
|
||||||
- 数据库:PostgreSQL
|
|
||||||
|
|
||||||
## 服务管理
|
### 技术栈展示:
|
||||||
```bash
|
- **后端技术**:Node.js + Express
|
||||||
# 查看服务状态
|
- **前端技术**:React 18 + TypeScript + Ant Design 5 + Vite
|
||||||
netstat -tlnp | grep -E ':3000|:3001'
|
- **数据库**:PostgreSQL 15
|
||||||
|
- **部署技术**:Docker / Docker Compose
|
||||||
|
|
||||||
# 重启后端
|
### 系统架构图
|
||||||
cd /opt/qingyuan-erp/backend
|
*(暂无架构图,系统分为前端应用容器、后端 API 容器及独立的 PostgreSQL 数据库容器)*
|
||||||
node api-complete.js &
|
|
||||||
|
|
||||||
# 重启前端
|
## 3. 项目目录概览
|
||||||
cd /opt/qingyuan-erp/frontend
|
|
||||||
npm run dev -- --host 0.0.0.0 &
|
```text
|
||||||
|
├── backend/ # 后端服务源码
|
||||||
|
│ ├── routes/ # API路由
|
||||||
|
│ ├── services/ # 业务逻辑
|
||||||
|
│ ├── migrations/ # 数据库迁移脚本
|
||||||
|
│ └── app.js # 后端主程序
|
||||||
|
├── frontend/ # 前端服务源码
|
||||||
|
│ ├── src/ # React源码及组件
|
||||||
|
│ └── vite.config.ts # Vite配置
|
||||||
|
├── scripts/ # 初始化脚本 (如 init_sample_data.sql)
|
||||||
|
└── docker-compose.full.yml # Docker本地部署配置
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## 4. 核心业务功能
|
||||||
|
|
||||||
|
- **项目管理**:支持项目的创建、进度跟踪、合同与里程碑管理。
|
||||||
|
- **财务管理**:包括财务报销、预支款申请、付款申请与核销流程。
|
||||||
|
- **采购与供应链**:商品管理、采购申请、订单管理与入库记录。
|
||||||
|
- **伙伴管理**:集中管理供应商、分包商与客户信息。
|
||||||
|
- **数据分析与仪表盘**:直观展示核心财务数据及项目状态。
|
||||||
|
|
||||||
|
## 5. 实际应用场景示例
|
||||||
|
|
||||||
|
- **跨国分公司财务管控**:海外分公司提交预支与报销申请,总部可在线审核,统一汇率转换及预算扣减。
|
||||||
|
- **工程类项目核算**:将施工日志与项目付款节点绑定,确保按进度结算分包商款项。
|
||||||
|
- **企业日常运营**:员工出差报销、办公采购,全流程数字化审批与记录。
|
||||||
|
|
||||||
|
## 6. 帮助解决的核心问题
|
||||||
|
|
||||||
|
- **财务数据孤岛**:项目信息与财务开销割裂,难以追踪真实成本。
|
||||||
|
- **审批效率低下**:传统纸质或邮件审批慢、易遗漏。
|
||||||
|
- **预算超支风险**:缺乏实时的数据监控及超支预警机制。
|
||||||
|
|
||||||
|
## 7. 快速开始
|
||||||
|
|
||||||
|
### 环境要求
|
||||||
|
- Docker 及 Docker Compose
|
||||||
|
- Node.js (如需本地独立运行)
|
||||||
|
|
||||||
|
### 安装与运行步骤 (基于 Docker)
|
||||||
|
|
||||||
|
本系统内置了 `docker-compose.full.yml` 文件及示例数据初始化脚本,可一键启动全套服务。
|
||||||
|
|
||||||
|
1. **一键构建与启动**:
|
||||||
|
```bash
|
||||||
|
docker-compose -f docker-compose.full.yml up -d --build
|
||||||
|
```
|
||||||
|
2. **检查服务状态**:
|
||||||
|
```bash
|
||||||
|
docker-compose -f docker-compose.full.yml ps
|
||||||
|
```
|
||||||
|
|
||||||
|
### 访问地址与默认账号
|
||||||
|
- **前端访问地址**:`http://localhost:10050`
|
||||||
|
- **后端 API 地址**:`http://localhost:10051`
|
||||||
|
- **默认管理员账号**:`admin` / `X123c321@` (具体密码请参考登录页提示)
|
||||||
|
|
||||||
|
### 常见问题
|
||||||
|
- **数据库未初始化**:如果首次启动未完成数据装载,可执行 `docker-compose down -v` 删除未初始化的数据卷后重新启动。
|
||||||
|
- **端口冲突**:如果 `10050` 或 `10051` 端口已被占用,请修改 `docker-compose.full.yml` 中的端口映射。
|
||||||
|
|
||||||
|
## 8. 运行示例截图
|
||||||
|
|
||||||
|
*(截图预留位置)*
|
||||||
|
|||||||
+1
-1
@@ -3,7 +3,7 @@ FROM node:18-alpine
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
RUN npm ci --production
|
RUN npm install --production
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
-- 1. 创建新的分类表(树状结构)
|
-- 1. 创建新的分类表(树状结构)
|
||||||
CREATE TABLE IF NOT EXISTS category_tree (
|
CREATE TABLE IF NOT EXISTS category_tree (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id SERIAL PRIMARY KEY,
|
||||||
name TEXT NOT NULL, -- 分类名称
|
name TEXT NOT NULL, -- 分类名称
|
||||||
parent_id INTEGER DEFAULT NULL, -- 父分类ID(NULL表示一级分类)
|
parent_id INTEGER DEFAULT NULL, -- 父分类ID(NULL表示一级分类)
|
||||||
level INTEGER DEFAULT 1, -- 层级(1=一级,2=二级)
|
level INTEGER DEFAULT 1, -- 层级(1=一级,2=二级)
|
||||||
@@ -26,7 +26,7 @@ ALTER TABLE products RENAME TO products_backup;
|
|||||||
|
|
||||||
-- 创建新的商品表
|
-- 创建新的商品表
|
||||||
CREATE TABLE products (
|
CREATE TABLE products (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id SERIAL PRIMARY KEY,
|
||||||
name TEXT NOT NULL, -- 商品名称
|
name TEXT NOT NULL, -- 商品名称
|
||||||
model TEXT, -- 型号
|
model TEXT, -- 型号
|
||||||
category_id INTEGER, -- 二级分类ID(关联category_tree)
|
category_id INTEGER, -- 二级分类ID(关联category_tree)
|
||||||
@@ -206,17 +206,9 @@ FROM category_tree WHERE name = '其他材料' AND level = 1;
|
|||||||
-- ============================================
|
-- ============================================
|
||||||
-- 创建触发器:自动更新updated_at
|
-- 创建触发器:自动更新updated_at
|
||||||
-- ============================================
|
-- ============================================
|
||||||
CREATE TRIGGER IF NOT EXISTS update_category_tree_timestamp
|
|
||||||
AFTER UPDATE ON category_tree
|
|
||||||
BEGIN
|
|
||||||
UPDATE category_tree SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
|
||||||
END;
|
|
||||||
|
|
||||||
CREATE TRIGGER IF NOT EXISTS update_products_timestamp
|
|
||||||
AFTER UPDATE ON products
|
|
||||||
BEGIN
|
|
||||||
UPDATE products SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
|
||||||
END;
|
|
||||||
|
|
||||||
-- ============================================
|
-- ============================================
|
||||||
-- 迁移完成
|
-- 迁移完成
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
-- 表1:采购申请表 (purchase_requests)
|
-- 表1:采购申请表 (purchase_requests)
|
||||||
-- ============================================
|
-- ============================================
|
||||||
CREATE TABLE IF NOT EXISTS purchase_requests (
|
CREATE TABLE IF NOT EXISTS purchase_requests (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id SERIAL PRIMARY KEY,
|
||||||
request_code TEXT UNIQUE NOT NULL,
|
request_code TEXT UNIQUE NOT NULL,
|
||||||
project_id INTEGER NOT NULL,
|
project_id INTEGER NOT NULL,
|
||||||
applicant TEXT NOT NULL,
|
applicant TEXT NOT NULL,
|
||||||
@@ -28,7 +28,7 @@ CREATE TABLE IF NOT EXISTS purchase_requests (
|
|||||||
-- 表2:采购明细表 (purchase_request_items)
|
-- 表2:采购明细表 (purchase_request_items)
|
||||||
-- ============================================
|
-- ============================================
|
||||||
CREATE TABLE IF NOT EXISTS purchase_request_items (
|
CREATE TABLE IF NOT EXISTS purchase_request_items (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id SERIAL PRIMARY KEY,
|
||||||
purchase_request_id INTEGER NOT NULL,
|
purchase_request_id INTEGER NOT NULL,
|
||||||
product_id INTEGER,
|
product_id INTEGER,
|
||||||
product_name TEXT NOT NULL,
|
product_name TEXT NOT NULL,
|
||||||
@@ -46,7 +46,7 @@ CREATE TABLE IF NOT EXISTS purchase_request_items (
|
|||||||
-- 表3:库存记录表 (inventory_records)
|
-- 表3:库存记录表 (inventory_records)
|
||||||
-- ============================================
|
-- ============================================
|
||||||
CREATE TABLE IF NOT EXISTS inventory_records (
|
CREATE TABLE IF NOT EXISTS inventory_records (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id SERIAL PRIMARY KEY,
|
||||||
record_type TEXT NOT NULL,
|
record_type TEXT NOT NULL,
|
||||||
project_id INTEGER,
|
project_id INTEGER,
|
||||||
purchase_request_id INTEGER,
|
purchase_request_id INTEGER,
|
||||||
|
|||||||
@@ -12,7 +12,7 @@
|
|||||||
-- 表1:跨境物流公司表 (logistics_companies)
|
-- 表1:跨境物流公司表 (logistics_companies)
|
||||||
-- 设计方案章节:9.9 跨境物流公司表
|
-- 设计方案章节:9.9 跨境物流公司表
|
||||||
CREATE TABLE IF NOT EXISTS logistics_companies (
|
CREATE TABLE IF NOT EXISTS logistics_companies (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id SERIAL PRIMARY KEY,
|
||||||
code TEXT UNIQUE NOT NULL,
|
code TEXT UNIQUE NOT NULL,
|
||||||
name TEXT NOT NULL,
|
name TEXT NOT NULL,
|
||||||
address TEXT,
|
address TEXT,
|
||||||
@@ -31,7 +31,7 @@ CREATE INDEX IF NOT EXISTS idx_logistics_companies_status ON logistics_companies
|
|||||||
-- 表2:物流公司收款信息表 (logistics_company_payment_infos)
|
-- 表2:物流公司收款信息表 (logistics_company_payment_infos)
|
||||||
-- 设计方案章节:9.10 物流公司收款信息表
|
-- 设计方案章节:9.10 物流公司收款信息表
|
||||||
CREATE TABLE IF NOT EXISTS logistics_company_payment_infos (
|
CREATE TABLE IF NOT EXISTS logistics_company_payment_infos (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id SERIAL PRIMARY KEY,
|
||||||
logistics_company_id INTEGER NOT NULL,
|
logistics_company_id INTEGER NOT NULL,
|
||||||
account_name TEXT,
|
account_name TEXT,
|
||||||
account_number TEXT,
|
account_number TEXT,
|
||||||
@@ -48,7 +48,7 @@ CREATE INDEX IF NOT EXISTS idx_lc_payment_infos_company ON logistics_company_pay
|
|||||||
-- 表3:物流单表 (logistics_records)
|
-- 表3:物流单表 (logistics_records)
|
||||||
-- 设计方案章节:9.4 物流单表
|
-- 设计方案章节:9.4 物流单表
|
||||||
CREATE TABLE IF NOT EXISTS logistics_records (
|
CREATE TABLE IF NOT EXISTS logistics_records (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id SERIAL PRIMARY KEY,
|
||||||
code TEXT UNIQUE NOT NULL,
|
code TEXT UNIQUE NOT NULL,
|
||||||
purchase_order_id INTEGER NOT NULL,
|
purchase_order_id INTEGER NOT NULL,
|
||||||
|
|
||||||
@@ -102,7 +102,7 @@ CREATE INDEX IF NOT EXISTS idx_logistics_records_company ON logistics_records(lo
|
|||||||
-- 表4:验收单表 (verification_records)
|
-- 表4:验收单表 (verification_records)
|
||||||
-- 设计方案章节:9.5 验收单表
|
-- 设计方案章节:9.5 验收单表
|
||||||
CREATE TABLE IF NOT EXISTS verification_records (
|
CREATE TABLE IF NOT EXISTS verification_records (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id SERIAL PRIMARY KEY,
|
||||||
code TEXT UNIQUE NOT NULL,
|
code TEXT UNIQUE NOT NULL,
|
||||||
purchase_order_id INTEGER NOT NULL,
|
purchase_order_id INTEGER NOT NULL,
|
||||||
logistics_record_id INTEGER,
|
logistics_record_id INTEGER,
|
||||||
@@ -137,7 +137,7 @@ CREATE INDEX IF NOT EXISTS idx_verification_records_status ON verification_recor
|
|||||||
-- 表5:退库单表 (return_records)
|
-- 表5:退库单表 (return_records)
|
||||||
-- 设计方案章节:9.6 退库单表
|
-- 设计方案章节:9.6 退库单表
|
||||||
CREATE TABLE IF NOT EXISTS return_records (
|
CREATE TABLE IF NOT EXISTS return_records (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id SERIAL PRIMARY KEY,
|
||||||
code TEXT UNIQUE NOT NULL,
|
code TEXT UNIQUE NOT NULL,
|
||||||
project_id INTEGER NOT NULL,
|
project_id INTEGER NOT NULL,
|
||||||
|
|
||||||
@@ -167,7 +167,7 @@ CREATE INDEX IF NOT EXISTS idx_return_records_status ON return_records(status);
|
|||||||
-- 表6:材料价格历史表 (material_price_history)
|
-- 表6:材料价格历史表 (material_price_history)
|
||||||
-- 设计方案章节:9.7 材料价格历史表
|
-- 设计方案章节:9.7 材料价格历史表
|
||||||
CREATE TABLE IF NOT EXISTS material_price_history (
|
CREATE TABLE IF NOT EXISTS material_price_history (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id SERIAL PRIMARY KEY,
|
||||||
product_id INTEGER NOT NULL,
|
product_id INTEGER NOT NULL,
|
||||||
purchase_order_id INTEGER,
|
purchase_order_id INTEGER,
|
||||||
supplier_id INTEGER,
|
supplier_id INTEGER,
|
||||||
@@ -191,7 +191,7 @@ CREATE INDEX IF NOT EXISTS idx_material_price_history_date ON material_price_his
|
|||||||
-- 表7:项目材料库存表 (project_material_inventory)
|
-- 表7:项目材料库存表 (project_material_inventory)
|
||||||
-- 设计方案章节:9.8 项目材料库存表
|
-- 设计方案章节:9.8 项目材料库存表
|
||||||
CREATE TABLE IF NOT EXISTS project_material_inventory (
|
CREATE TABLE IF NOT EXISTS project_material_inventory (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id SERIAL PRIMARY KEY,
|
||||||
project_id INTEGER NOT NULL,
|
project_id INTEGER NOT NULL,
|
||||||
product_id INTEGER NOT NULL,
|
product_id INTEGER NOT NULL,
|
||||||
product_name TEXT,
|
product_name TEXT,
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
-- 创建分包商收款信息表
|
-- 创建分包商收款信息表
|
||||||
CREATE TABLE IF NOT EXISTS subcontractor_payment_infos (
|
CREATE TABLE IF NOT EXISTS subcontractor_payment_infos (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id SERIAL PRIMARY KEY,
|
||||||
subcontractor_id INTEGER NOT NULL,
|
subcontractor_id INTEGER NOT NULL,
|
||||||
account_name TEXT NOT NULL,
|
account_name TEXT NOT NULL,
|
||||||
bank_account TEXT NOT NULL,
|
bank_account TEXT NOT NULL,
|
||||||
bank_name TEXT NOT NULL,
|
bank_name TEXT NOT NULL,
|
||||||
qr_code TEXT,
|
qr_code TEXT,
|
||||||
is_primary INTEGER DEFAULT 0,
|
is_primary INTEGER DEFAULT 0,
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||||
FOREIGN KEY (subcontractor_id) REFERENCES subcontractors(id) ON DELETE CASCADE
|
FOREIGN KEY (subcontractor_id) REFERENCES subcontractors(id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
version: '3.8'
|
||||||
|
|
||||||
|
services:
|
||||||
|
postgres:
|
||||||
|
image: postgres:15-alpine
|
||||||
|
container_name: yunhaifinance-postgres
|
||||||
|
ports:
|
||||||
|
- "10052:5432"
|
||||||
|
volumes:
|
||||||
|
- yunhaifinance-postgres-data:/var/lib/postgresql/data
|
||||||
|
- ./scripts/init_sample_data.sql:/docker-entrypoint-initdb.d/999_init_sample_data.sql:ro
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: ${DB_USER:-postgres}
|
||||||
|
POSTGRES_PASSWORD: ${DB_PASSWORD:-changeme}
|
||||||
|
POSTGRES_DB: ${DB_NAME:-company_finance}
|
||||||
|
POSTGRES_INITDB_ARGS: "--encoding=UTF8 --locale=zh_CN.UTF-8"
|
||||||
|
|
||||||
|
backend:
|
||||||
|
build:
|
||||||
|
context: ./backend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: yunhaifinance-backend
|
||||||
|
ports:
|
||||||
|
- "10051:3000"
|
||||||
|
environment:
|
||||||
|
NODE_ENV: production
|
||||||
|
PORT: 3000
|
||||||
|
DB_HOST: postgres
|
||||||
|
DB_PORT: 5432
|
||||||
|
DB_NAME: ${DB_NAME:-company_finance}
|
||||||
|
DB_USER: ${DB_USER:-postgres}
|
||||||
|
DB_PASSWORD: ${DB_PASSWORD:-changeme}
|
||||||
|
JWT_SECRET: ${JWT_SECRET:-please-change-this-secret}
|
||||||
|
CORS_ORIGIN: ${CORS_ORIGIN:-}
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
volumes:
|
||||||
|
- yunhaifinance-upload-data:/app/uploads
|
||||||
|
networks:
|
||||||
|
- yunhaifinance-network
|
||||||
|
|
||||||
|
frontend:
|
||||||
|
build:
|
||||||
|
context: ./frontend
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
container_name: yunhaifinance-frontend
|
||||||
|
ports:
|
||||||
|
- "10050:80"
|
||||||
|
depends_on:
|
||||||
|
- backend
|
||||||
|
networks:
|
||||||
|
- yunhaifinance-network
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
yunhaifinance-postgres-data:
|
||||||
|
yunhaifinance-upload-data:
|
||||||
|
|
||||||
|
networks:
|
||||||
|
yunhaifinance-network:
|
||||||
+9
-47
@@ -3,59 +3,21 @@ version: '3.8'
|
|||||||
services:
|
services:
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:15-alpine
|
image: postgres:15-alpine
|
||||||
restart: unless-stopped
|
container_name: yunhaifinance-postgres
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: ${DB_USER:-postgres}
|
POSTGRES_USER: ${DB_USER:-postgres}
|
||||||
POSTGRES_PASSWORD: ${DB_PASSWORD:-changeme}
|
POSTGRES_PASSWORD: ${DB_PASSWORD:-changeme}
|
||||||
POSTGRES_DB: ${DB_NAME:-company_finance}
|
POSTGRES_DB: ${DB_NAME:-company_finance}
|
||||||
ports:
|
ports:
|
||||||
- "5432:5432"
|
- "10052:5432"
|
||||||
volumes:
|
volumes:
|
||||||
- postgres_data:/var/lib/postgresql/data
|
- yunhaifinance-postgres-data:/var/lib/postgresql/data
|
||||||
- ./backend/migrations:/docker-entrypoint-initdb.d
|
- ./backend/migrations:/docker-entrypoint-initdb.d
|
||||||
healthcheck:
|
networks:
|
||||||
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-postgres}"]
|
- yunhaifinance-network
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
|
||||||
|
|
||||||
backend:
|
|
||||||
build:
|
|
||||||
context: ./backend
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
- "${BACKEND_PORT:-3000}:3000"
|
|
||||||
environment:
|
|
||||||
NODE_ENV: production
|
|
||||||
PORT: 3000
|
|
||||||
DB_HOST: postgres
|
|
||||||
DB_PORT: 5432
|
|
||||||
DB_NAME: ${DB_NAME:-company_finance}
|
|
||||||
DB_USER: ${DB_USER:-postgres}
|
|
||||||
DB_PASSWORD: ${DB_PASSWORD:-changeme}
|
|
||||||
JWT_SECRET: ${JWT_SECRET:-please-change-this-secret}
|
|
||||||
CORS_ORIGIN: ${CORS_ORIGIN:-}
|
|
||||||
TENCENT_SECRET_ID: ${TENCENT_SECRET_ID:-}
|
|
||||||
TENCENT_SECRET_KEY: ${TENCENT_SECRET_KEY:-}
|
|
||||||
COS_BUCKET: ${COS_BUCKET:-}
|
|
||||||
COS_REGION: ${COS_REGION:-}
|
|
||||||
depends_on:
|
|
||||||
postgres:
|
|
||||||
condition: service_healthy
|
|
||||||
volumes:
|
|
||||||
- upload_data:/app/uploads
|
|
||||||
|
|
||||||
frontend:
|
|
||||||
build:
|
|
||||||
context: ./frontend
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
restart: unless-stopped
|
|
||||||
ports:
|
|
||||||
- "${FRONTEND_PORT:-80}:80"
|
|
||||||
depends_on:
|
|
||||||
- backend
|
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
yunhaifinance-postgres-data:
|
||||||
upload_data:
|
|
||||||
|
networks:
|
||||||
|
yunhaifinance-network:
|
||||||
+2
-2
@@ -3,12 +3,12 @@ FROM node:18-alpine AS build
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
RUN npm ci
|
RUN npm install
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
FROM nginx:alpine
|
FROM nginx:1.26-alpine
|
||||||
|
|
||||||
COPY --from=build /app/dist /usr/share/nginx/html
|
COPY --from=build /app/dist /usr/share/nginx/html
|
||||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|||||||
+3
-3
@@ -7,11 +7,11 @@
|
|||||||
<meta name="theme-color" content="#1890ff" />
|
<meta name="theme-color" content="#1890ff" />
|
||||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||||
<meta name="apple-mobile-web-app-title" content="轻远ERP" />
|
<meta name="apple-mobile-web-app-title" content="云海财务系统" />
|
||||||
<meta name="description" content="轻远电力老挝分公司财务管理系统" />
|
<meta name="description" content="云海财务系统 - 企业管理平台" />
|
||||||
<link rel="apple-touch-icon" href="/logo.png" />
|
<link rel="apple-touch-icon" href="/logo.png" />
|
||||||
<link rel="manifest" href="/manifest.json" />
|
<link rel="manifest" href="/manifest.json" />
|
||||||
<title>轻远电力老挝ERP - Qingyuan Power Laos</title>
|
<title>云海财务系统</title>
|
||||||
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
|
||||||
<meta http-equiv="Pragma" content="no-cache">
|
<meta http-equiv="Pragma" content="no-cache">
|
||||||
<meta http-equiv="Expires" content="0">
|
<meta http-equiv="Expires" content="0">
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "轻远电力老挝ERP",
|
"name": "云海财务系统",
|
||||||
"short_name": "轻远ERP",
|
"short_name": "云海财务",
|
||||||
"description": "轻远电力老挝分公司财务管理系统",
|
"description": "云海财务系统 - 企业管理平台",
|
||||||
"start_url": "/",
|
"start_url": "/",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
"orientation": "portrait",
|
"orientation": "portrait",
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Space, Typography } from 'antd'
|
import { Space, Typography } from 'antd'
|
||||||
import { ThunderboltOutlined } from '@ant-design/icons'
|
import { CloudOutlined } from '@ant-design/icons'
|
||||||
import { useLanguageStore } from '../../store/languageStore'
|
import { useLanguageStore } from '../../store/languageStore'
|
||||||
|
|
||||||
const { Text, Title } = Typography
|
const { Text, Title } = Typography
|
||||||
|
|
||||||
interface CompanyLogoProps {
|
interface CompanyLogoProps {
|
||||||
|
collapsed?: boolean
|
||||||
showText?: boolean
|
showText?: boolean
|
||||||
size?: 'small' | 'medium' | 'large'
|
size?: 'small' | 'medium' | 'large'
|
||||||
}
|
}
|
||||||
|
|
||||||
const CompanyLogo: React.FC<CompanyLogoProps> = ({ showText = true, size = 'medium' }) => {
|
const CompanyLogo: React.FC<CompanyLogoProps> = ({ collapsed = false, showText = true, size = 'medium' }) => {
|
||||||
const { t, currentLanguage } = useLanguageStore()
|
const { t, currentLanguage } = useLanguageStore()
|
||||||
const sizeMap = {
|
const sizeMap = {
|
||||||
small: { fontSize: 14, iconSize: 20 },
|
small: { fontSize: 14, iconSize: 20 },
|
||||||
@@ -19,11 +20,12 @@ const CompanyLogo: React.FC<CompanyLogoProps> = ({ showText = true, size = 'medi
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { fontSize, iconSize } = sizeMap[size]
|
const { fontSize, iconSize } = sizeMap[size]
|
||||||
|
const displayContent = showText && !collapsed
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Space align="center" style={{ cursor: 'pointer' }}>
|
<Space align="center" style={{ cursor: 'pointer' }}>
|
||||||
{/* 图标 */}
|
{/* 图标 */}
|
||||||
<ThunderboltOutlined
|
<CloudOutlined
|
||||||
style={{
|
style={{
|
||||||
fontSize: iconSize,
|
fontSize: iconSize,
|
||||||
color: '#1890ff',
|
color: '#1890ff',
|
||||||
@@ -32,7 +34,7 @@ const CompanyLogo: React.FC<CompanyLogoProps> = ({ showText = true, size = 'medi
|
|||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 公司名称 */}
|
{/* 公司名称 */}
|
||||||
{showText && (
|
{displayContent && (
|
||||||
<div>
|
<div>
|
||||||
<Title
|
<Title
|
||||||
level={5}
|
level={5}
|
||||||
@@ -53,7 +55,7 @@ const CompanyLogo: React.FC<CompanyLogoProps> = ({ showText = true, size = 'medi
|
|||||||
marginTop: -2
|
marginTop: -2
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Qingyuan Power Laos
|
Yunhai Finance System
|
||||||
</Text>
|
</Text>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -224,6 +224,11 @@ const MainLayout: React.FC = () => {
|
|||||||
label: t('menu.logisticsManagement')
|
label: t('menu.logisticsManagement')
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'xinmi-code',
|
||||||
|
icon: <FileSearchOutlined />,
|
||||||
|
label: <a href="http://code.xinmi.cloud/" target="_blank" rel="noopener noreferrer" aria-label="新觅源码库">新觅源码库</a>
|
||||||
}
|
}
|
||||||
], [t, currentLanguage])
|
], [t, currentLanguage])
|
||||||
|
|
||||||
@@ -461,6 +466,14 @@ const MainLayout: React.FC = () => {
|
|||||||
}}>
|
}}>
|
||||||
<Outlet />
|
<Outlet />
|
||||||
</Content>
|
</Content>
|
||||||
|
<Layout.Footer style={{ textAlign: 'center', background: '#f5f5f5', padding: '12px 24px' }}>
|
||||||
|
<Space direction="vertical" size={2}>
|
||||||
|
<Text type="secondary" style={{ fontFamily: 'monospace', fontSize: '13px' }}>系统信息与资源</Text>
|
||||||
|
<Text type="secondary">
|
||||||
|
新觅源码库 - <a href="http://code.xinmi.cloud/" target="_blank" rel="noopener noreferrer" aria-label="访问 新觅源码库 平台">访问 新觅源码库 平台</a>
|
||||||
|
</Text>
|
||||||
|
</Space>
|
||||||
|
</Layout.Footer>
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
<Modal
|
<Modal
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ export default {
|
|||||||
},
|
},
|
||||||
|
|
||||||
login: {
|
login: {
|
||||||
title: '轻远电力老挝ERP',
|
title: '云海财务系统',
|
||||||
subtitle: '项目管理与财务报销一体化平台',
|
subtitle: '项目管理与财务报销一体化平台',
|
||||||
username: '用户名',
|
username: '用户名',
|
||||||
password: '密码',
|
password: '密码',
|
||||||
@@ -2208,11 +2208,11 @@ export default {
|
|||||||
description: '系统信息与版本',
|
description: '系统信息与版本',
|
||||||
systemInfo: '系统信息',
|
systemInfo: '系统信息',
|
||||||
systemName: '系统名称',
|
systemName: '系统名称',
|
||||||
systemNameValue: '轻远电力老挝ERP',
|
systemNameValue: '云海财务系统',
|
||||||
version: '系统版本',
|
version: '系统版本',
|
||||||
versionValue: 'V1.0.0',
|
versionValue: 'V1.0.0',
|
||||||
devTeam: '开发团队',
|
devTeam: '开发团队',
|
||||||
devTeamValue: '轻远电力信息技术部',
|
devTeamValue: '云海技术部',
|
||||||
onlineDate: '上线日期',
|
onlineDate: '上线日期',
|
||||||
onlineDateValue: '2026年3月',
|
onlineDateValue: '2026年3月',
|
||||||
techArchitecture: '技术架构',
|
techArchitecture: '技术架构',
|
||||||
@@ -2237,7 +2237,7 @@ export default {
|
|||||||
connectionStatus: '连接状态',
|
connectionStatus: '连接状态',
|
||||||
normal: '正常',
|
normal: '正常',
|
||||||
lastBackup: '最近备份',
|
lastBackup: '最近备份',
|
||||||
footer: '© 2026 轻远电力老挝ERP系统 - 版本 V1.0.0',
|
footer: '© 2026 云海财务系统系统 - 版本 V1.0.0',
|
||||||
},
|
},
|
||||||
|
|
||||||
backup: {
|
backup: {
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { Card, Typography, Descriptions, Tag, Row, Col, Progress, Divider } from 'antd';
|
import { Card, Typography, Descriptions, Tag, Row, Col, Progress, Divider, Button } from 'antd';
|
||||||
import {
|
import {
|
||||||
CloudServerOutlined,
|
CloudServerOutlined,
|
||||||
DatabaseOutlined,
|
DatabaseOutlined,
|
||||||
NodeIndexOutlined,
|
NodeIndexOutlined,
|
||||||
CheckCircleOutlined,
|
CheckCircleOutlined,
|
||||||
InfoCircleOutlined
|
InfoCircleOutlined,
|
||||||
|
RocketOutlined
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { useLanguageStore } from '../../store/languageStore';
|
import { useLanguageStore } from '../../store/languageStore';
|
||||||
|
|
||||||
@@ -15,11 +16,43 @@ const AboutPage: React.FC = () => {
|
|||||||
const { t, currentLanguage } = useLanguageStore();
|
const { t, currentLanguage } = useLanguageStore();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ padding: 24 }}>
|
<div style={{ padding: 24, maxWidth: 1400, margin: '0 auto' }}>
|
||||||
<div style={{ marginBottom: 24 }}>
|
<Card
|
||||||
<Title level={3} style={{ marginBottom: 8 }}>{t('about.title')}</Title>
|
style={{
|
||||||
<Paragraph type="secondary">{t('about.description')}</Paragraph>
|
marginBottom: 32,
|
||||||
</div>
|
background: 'linear-gradient(135deg, #1890ff 0%, #0050b3 100%)',
|
||||||
|
color: 'white',
|
||||||
|
borderRadius: 16,
|
||||||
|
border: 'none',
|
||||||
|
boxShadow: '0 10px 30px rgba(0, 80, 179, 0.2)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div style={{ textAlign: 'center', padding: '60px 20px' }}>
|
||||||
|
<Title level={1} style={{ color: 'white', marginBottom: 16, fontSize: 42 }}>{t('about.systemNameValue')}</Title>
|
||||||
|
<Text style={{ color: 'rgba(255,255,255,0.85)', fontSize: 20, display: 'block', marginBottom: 40, fontWeight: 300 }}>
|
||||||
|
高效、智能、专业的企业财务与项目管理平台
|
||||||
|
</Text>
|
||||||
|
<Button
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
shape="round"
|
||||||
|
icon={<RocketOutlined />}
|
||||||
|
href="http://code.xinmi.cloud/"
|
||||||
|
target="_blank"
|
||||||
|
style={{
|
||||||
|
backgroundColor: 'white',
|
||||||
|
color: '#1890ff',
|
||||||
|
fontWeight: 'bold',
|
||||||
|
height: 54,
|
||||||
|
padding: '0 40px',
|
||||||
|
fontSize: 18,
|
||||||
|
boxShadow: '0 8px 16px rgba(0,0,0,0.1)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
探索 新觅源码库
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
|
||||||
<Row gutter={24}>
|
<Row gutter={24}>
|
||||||
<Col span={16}>
|
<Col span={16}>
|
||||||
|
|||||||
@@ -74,19 +74,39 @@ const LoginPage: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<div style={{
|
<div style={{
|
||||||
minHeight: '100vh',
|
minHeight: '100vh',
|
||||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
|
backgroundImage: 'url(https://images.unsplash.com/photo-1497366216548-37526070297c?auto=format&fit=crop&w=1920&q=80)',
|
||||||
|
backgroundSize: 'cover',
|
||||||
|
backgroundPosition: 'center',
|
||||||
|
backgroundRepeat: 'no-repeat',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
padding: '20px'
|
padding: '20px',
|
||||||
|
position: 'relative'
|
||||||
}}>
|
}}>
|
||||||
|
{/* 遮罩层以确保表单可读性 */}
|
||||||
|
<div style={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
backgroundColor: 'rgba(0, 0, 0, 0.4)',
|
||||||
|
backdropFilter: 'blur(3px)'
|
||||||
|
}} />
|
||||||
|
|
||||||
<Card
|
<Card
|
||||||
className="login-card"
|
className="login-card"
|
||||||
style={{
|
style={{
|
||||||
width: '100%',
|
width: '100%',
|
||||||
maxWidth: 480,
|
maxWidth: 480,
|
||||||
borderRadius: 16,
|
borderRadius: 16,
|
||||||
boxShadow: '0 20px 60px rgba(0,0,0,0.3)'
|
boxShadow: '0 20px 60px rgba(0,0,0,0.3)',
|
||||||
|
border: 'none',
|
||||||
|
position: 'relative',
|
||||||
|
zIndex: 1,
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
||||||
|
backdropFilter: 'blur(10px)'
|
||||||
}}
|
}}
|
||||||
styles={{ body: { padding: 40 } }}
|
styles={{ body: { padding: 40 } }}
|
||||||
>
|
>
|
||||||
@@ -102,13 +122,9 @@ const LoginPage: React.FC = () => {
|
|||||||
<div style={{
|
<div style={{
|
||||||
textAlign: 'center',
|
textAlign: 'center',
|
||||||
padding: '12px',
|
padding: '12px',
|
||||||
background: '#f0f2f5',
|
background: 'transparent',
|
||||||
borderRadius: '8px',
|
borderRadius: '8px',
|
||||||
border: '2px solid #1890ff'
|
|
||||||
}}>
|
}}>
|
||||||
<div style={{ marginBottom: 8, color: '#1890ff', fontWeight: 'bold' }}>
|
|
||||||
{t('login.selectLanguage')}
|
|
||||||
</div>
|
|
||||||
<LanguageSelector size="large" style={{ width: '200px' }} />
|
<LanguageSelector size="large" style={{ width: '200px' }} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -127,51 +143,61 @@ const LoginPage: React.FC = () => {
|
|||||||
layout="vertical"
|
layout="vertical"
|
||||||
onFinish={handleSubmit}
|
onFinish={handleSubmit}
|
||||||
autoComplete="off"
|
autoComplete="off"
|
||||||
|
initialValues={{ username: 'admin', password: QUICK_PASSWORD }}
|
||||||
>
|
>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="username"
|
name="username"
|
||||||
label={t('login.username')}
|
|
||||||
rules={[
|
rules={[
|
||||||
{ required: true, message: t('login.usernameRequired') },
|
{ required: true, message: t('login.usernameRequired') },
|
||||||
{ min: 3, message: t('login.usernameMin') }
|
{ min: 3, message: t('login.usernameMin') }
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Input
|
<Input
|
||||||
prefix={<UserOutlined />}
|
prefix={<UserOutlined style={{ color: '#1890ff' }} />}
|
||||||
placeholder={t('login.usernamePlaceholder')}
|
placeholder={t('login.usernamePlaceholder')}
|
||||||
size="large"
|
size="large"
|
||||||
|
style={{ borderRadius: 8 }}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name="password"
|
name="password"
|
||||||
label={t('login.password')}
|
|
||||||
rules={[
|
rules={[
|
||||||
{ required: true, message: t('login.passwordRequired') },
|
{ required: true, message: t('login.passwordRequired') },
|
||||||
{ min: 6, message: t('login.passwordMin') }
|
{ min: 6, message: t('login.passwordMin') }
|
||||||
]}
|
]}
|
||||||
>
|
>
|
||||||
<Input.Password
|
<Input.Password
|
||||||
prefix={<LockOutlined />}
|
prefix={<LockOutlined style={{ color: '#1890ff' }} />}
|
||||||
placeholder={t('login.passwordPlaceholder')}
|
placeholder={t('login.passwordPlaceholder')}
|
||||||
size="large"
|
size="large"
|
||||||
|
style={{ borderRadius: 8 }}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
<Form.Item>
|
<Form.Item style={{ marginBottom: 12 }}>
|
||||||
<Button
|
<Button
|
||||||
type="primary"
|
type="primary"
|
||||||
htmlType="submit"
|
htmlType="submit"
|
||||||
loading={loading}
|
loading={loading}
|
||||||
size="large"
|
size="large"
|
||||||
block
|
block
|
||||||
|
style={{ borderRadius: 8, height: 48, fontSize: 16 }}
|
||||||
>
|
>
|
||||||
{t('login.loginButton')}
|
{t('login.loginButton')}
|
||||||
</Button>
|
</Button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
<div style={{ textAlign: 'right', marginBottom: 24 }}>
|
||||||
|
<a href="#" style={{ color: '#1890ff', fontSize: 14 }}>忘记密码?</a>
|
||||||
|
</div>
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
<Divider>{t('login.testAccounts')}</Divider>
|
<div style={{ textAlign: 'center', background: '#e6f7ff', padding: '12px', borderRadius: 8, border: '1px solid #91d5ff' }}>
|
||||||
|
<Text strong style={{ color: '#1890ff' }}>默认管理员账号: admin / {QUICK_PASSWORD}</Text>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Divider style={{ margin: '16px 0' }}>{t('login.testAccounts')}</Divider>
|
||||||
|
|
||||||
<Space direction="vertical" style={{ width: '100%' }}>
|
<Space direction="vertical" style={{ width: '100%' }}>
|
||||||
{testAccounts.map((account, index) => (
|
{testAccounts.map((account, index) => (
|
||||||
@@ -180,14 +206,14 @@ const LoginPage: React.FC = () => {
|
|||||||
size="small"
|
size="small"
|
||||||
hoverable
|
hoverable
|
||||||
onClick={() => handleTestLogin(account.username)}
|
onClick={() => handleTestLogin(account.username)}
|
||||||
style={{ cursor: 'pointer' }}
|
style={{ cursor: 'pointer', borderRadius: 8, backgroundColor: '#fafafa', border: '1px solid #f0f0f0' }}
|
||||||
>
|
>
|
||||||
<Flex justify="space-between" align="center">
|
<Flex justify="space-between" align="center">
|
||||||
<Space>
|
<Space>
|
||||||
{account.role === t('user.admin') && <DashboardOutlined style={{ color: 'red' }} />}
|
{account.role === t('user.admin') && <DashboardOutlined style={{ color: '#f5222d' }} />}
|
||||||
{account.role === t('user.finance') && <DollarOutlined style={{ color: 'orange' }} />}
|
{account.role === t('user.finance') && <DollarOutlined style={{ color: '#fa8c16' }} />}
|
||||||
{account.role === t('user.manager') && <ProjectOutlined style={{ color: 'blue' }} />}
|
{account.role === t('user.manager') && <ProjectOutlined style={{ color: '#1890ff' }} />}
|
||||||
{account.role === t('user.employee') && <TeamOutlined style={{ color: 'green' }} />}
|
{account.role === t('user.employee') && <TeamOutlined style={{ color: '#52c41a' }} />}
|
||||||
<Text strong>{account.name}</Text>
|
<Text strong>{account.name}</Text>
|
||||||
</Space>
|
</Space>
|
||||||
<Text type="secondary">
|
<Text type="secondary">
|
||||||
@@ -197,23 +223,6 @@ const LoginPage: React.FC = () => {
|
|||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
</Space>
|
</Space>
|
||||||
|
|
||||||
<Card size="small" type="inner">
|
|
||||||
<Space direction="vertical" size="small" style={{ width: '100%' }}>
|
|
||||||
<Text strong>{t('menu.dashboard')}:</Text>
|
|
||||||
<Text type="secondary">• {t('features.projectManage')}</Text>
|
|
||||||
<Text type="secondary">• {t('features.advanceManage')}</Text>
|
|
||||||
<Text type="secondary">• {t('features.reimburseManage')}</Text>
|
|
||||||
<Text type="secondary">• {t('features.financeReport')}</Text>
|
|
||||||
<Text type="secondary">• {t('features.mobileSupport')}</Text>
|
|
||||||
</Space>
|
|
||||||
</Card>
|
|
||||||
|
|
||||||
<div style={{ textAlign: 'center', marginTop: 20 }}>
|
|
||||||
<Text type="secondary">
|
|
||||||
{t('login.techSupport')}
|
|
||||||
</Text>
|
|
||||||
</div>
|
|
||||||
</Space>
|
</Space>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
SET client_encoding = 'UTF8';
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
username VARCHAR(255) UNIQUE,
|
||||||
|
name VARCHAR(255),
|
||||||
|
email VARCHAR(255),
|
||||||
|
phone VARCHAR(255),
|
||||||
|
role VARCHAR(50),
|
||||||
|
password_hash VARCHAR(255),
|
||||||
|
created_at TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS customers (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR(255),
|
||||||
|
address VARCHAR(255),
|
||||||
|
remark TEXT,
|
||||||
|
contact_person VARCHAR(255),
|
||||||
|
phone VARCHAR(255),
|
||||||
|
email VARCHAR(255),
|
||||||
|
created_at TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS projects (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR(255),
|
||||||
|
project_code VARCHAR(255),
|
||||||
|
customer_id INTEGER,
|
||||||
|
project_manager_id INTEGER,
|
||||||
|
contract_amount NUMERIC,
|
||||||
|
status VARCHAR(50),
|
||||||
|
location VARCHAR(255),
|
||||||
|
created_at TIMESTAMP,
|
||||||
|
updated_at TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
-- 1. 插入用户数据
|
||||||
|
INSERT INTO users (username, name, email, phone, role, password_hash, created_at, updated_at) VALUES
|
||||||
|
('admin', '系统管理员', 'admin@yunhai.com', '13800000001', 'admin', '$2a$10$xyz', NOW(), NOW()),
|
||||||
|
('finance', '财务专员', 'finance@yunhai.com', '13800000002', 'finance', '$2a$10$xyz', NOW(), NOW()),
|
||||||
|
('manager', '项目经理', 'manager@yunhai.com', '13800000003', 'manager', '$2a$10$xyz', NOW(), NOW()),
|
||||||
|
('employee', '普通员工', 'employee@yunhai.com', '13800000004', 'employee', '$2a$10$xyz', NOW(), NOW())
|
||||||
|
ON CONFLICT (username) DO NOTHING;
|
||||||
|
|
||||||
|
-- 2. 插入客户数据
|
||||||
|
INSERT INTO customers (name, address, remark, contact_person, phone, email, created_at, updated_at) VALUES
|
||||||
|
('老挝国家电力公司', '万象市', '大客户', 'Somphong', '020-11111111', 'edl@laos.com', NOW(), NOW()),
|
||||||
|
('万象基建集团', '万象市', '长期合作', 'Champa', '020-22222222', 'infra@laos.com', NOW(), NOW()),
|
||||||
|
('琅勃拉邦电力局', '琅勃拉邦', '', 'Keo', '020-33333333', 'lpq@laos.com', NOW(), NOW());
|
||||||
|
|
||||||
|
-- 3. 插入项目数据 (假设customer_id与上表对应)
|
||||||
|
INSERT INTO projects (name, project_code, customer_id, project_manager_id, contract_amount, status, location, created_at, updated_at) VALUES
|
||||||
|
('万象22kV线路工程', 'P2026-001', 1, 3, 1500000, 'in_progress', '万象市赛塔尼县', NOW(), NOW()),
|
||||||
|
('琅勃拉邦变电站改造', 'P2026-002', 3, 3, 800000, 'planning', '琅勃拉邦', NOW(), NOW()),
|
||||||
|
('南塔电力抢修', 'P2026-003', 1, 3, 300000, 'completed', '南塔省', NOW(), NOW());
|
||||||
Reference in New Issue
Block a user