docs: 生成项目文档并优化项目结构
- 生成完整的 README.md 项目文档 - 统一图片路径为相对引用 - 完善 .gitignore 配置,添加常见忽略项 - 清理未使用的临时文件和图片资源 - 删除废弃的脚本文件
This commit is contained in:
@@ -60,3 +60,6 @@ backups/
|
||||
# Cache
|
||||
.cache/
|
||||
*.cache
|
||||
|
||||
# Workflow
|
||||
.work/
|
||||
|
||||
@@ -1,37 +1,87 @@
|
||||
# 轻远电力老挝ERP系统
|
||||
# 云海财务系统
|
||||
|
||||
## 系统访问
|
||||
- **前端地址**: http://43.161.248.209:3001/
|
||||
- **后端API**: http://43.161.248.209:3000/
|
||||
## 1. 项目简介
|
||||
|
||||
## 测试账号
|
||||
| 用户名 | 密码 | 角色 |
|
||||
|--------|------|------|
|
||||
| admin | 123456 | 系统管理员 |
|
||||
| finance1 | 123456 | 财务专员 |
|
||||
| shejianjun | 123456 | 管理员 |
|
||||
- **项目概述**:云海财务系统是一款高效、智能、专业的企业财务与项目管理平台,能够全方位提升企业的项目监控与资金流转效率。
|
||||
- **项目定位**:主要面向中大型企业、工程建设及跨国分公司,提供财务报销、项目预算与进度管理服务。
|
||||
- **核心价值**:实现项目与财务数据的无缝对接,简化审批流,实时监控预算使用情况,确保企业资金健康与合规。
|
||||
|
||||
## 功能模块
|
||||
1. **仪表板** - 数据概览和统计
|
||||
2. **项目管理** - 项目创建、查看、编辑
|
||||
3. **财务管理** - 预支申请、报销申请
|
||||
4. **报表中心** - 财务报表查看
|
||||
## 2. 整体架构与技术栈
|
||||
|
||||
## 技术栈
|
||||
- 前端:React + TypeScript + Ant Design + Vite
|
||||
- 后端:Node.js + Express
|
||||
- 数据库:PostgreSQL
|
||||
本系统采用前后端分离的现代化架构,通过 Docker 容器化部署以确保高可用性。
|
||||
|
||||
## 服务管理
|
||||
```bash
|
||||
# 查看服务状态
|
||||
netstat -tlnp | grep -E ':3000|:3001'
|
||||
### 技术栈展示:
|
||||
- **后端技术**:Node.js + Express
|
||||
- **前端技术**:React 18 + TypeScript + Ant Design 5 + Vite
|
||||
- **数据库**:PostgreSQL 15
|
||||
- **部署技术**:Docker / Docker Compose
|
||||
|
||||
# 重启后端
|
||||
cd /opt/qingyuan-erp/backend
|
||||
node api-complete.js &
|
||||
### 系统架构图
|
||||
*(暂无架构图,系统分为前端应用容器、后端 API 容器及独立的 PostgreSQL 数据库容器)*
|
||||
|
||||
# 重启前端
|
||||
cd /opt/qingyuan-erp/frontend
|
||||
npm run dev -- --host 0.0.0.0 &
|
||||
## 3. 项目目录概览
|
||||
|
||||
```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
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm ci --production
|
||||
RUN npm install --production
|
||||
|
||||
COPY . .
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
-- 1. 创建新的分类表(树状结构)
|
||||
CREATE TABLE IF NOT EXISTS category_tree (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
id SERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL, -- 分类名称
|
||||
parent_id INTEGER DEFAULT NULL, -- 父分类ID(NULL表示一级分类)
|
||||
level INTEGER DEFAULT 1, -- 层级(1=一级,2=二级)
|
||||
@@ -26,7 +26,7 @@ ALTER TABLE products RENAME TO products_backup;
|
||||
|
||||
-- 创建新的商品表
|
||||
CREATE TABLE products (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
id SERIAL PRIMARY KEY,
|
||||
name TEXT NOT NULL, -- 商品名称
|
||||
model TEXT, -- 型号
|
||||
category_id INTEGER, -- 二级分类ID(关联category_tree)
|
||||
@@ -206,17 +206,9 @@ FROM category_tree WHERE name = '其他材料' AND level = 1;
|
||||
-- ============================================
|
||||
-- 创建触发器:自动更新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)
|
||||
-- ============================================
|
||||
CREATE TABLE IF NOT EXISTS purchase_requests (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
id SERIAL PRIMARY KEY,
|
||||
request_code TEXT UNIQUE NOT NULL,
|
||||
project_id INTEGER NOT NULL,
|
||||
applicant TEXT NOT NULL,
|
||||
@@ -28,7 +28,7 @@ CREATE TABLE IF NOT EXISTS purchase_requests (
|
||||
-- 表2:采购明细表 (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,
|
||||
product_id INTEGER,
|
||||
product_name TEXT NOT NULL,
|
||||
@@ -46,7 +46,7 @@ CREATE TABLE IF NOT EXISTS purchase_request_items (
|
||||
-- 表3:库存记录表 (inventory_records)
|
||||
-- ============================================
|
||||
CREATE TABLE IF NOT EXISTS inventory_records (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
id SERIAL PRIMARY KEY,
|
||||
record_type TEXT NOT NULL,
|
||||
project_id INTEGER,
|
||||
purchase_request_id INTEGER,
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
-- 表1:跨境物流公司表 (logistics_companies)
|
||||
-- 设计方案章节:9.9 跨境物流公司表
|
||||
CREATE TABLE IF NOT EXISTS logistics_companies (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
id SERIAL PRIMARY KEY,
|
||||
code TEXT UNIQUE NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
address TEXT,
|
||||
@@ -31,7 +31,7 @@ CREATE INDEX IF NOT EXISTS idx_logistics_companies_status ON logistics_companies
|
||||
-- 表2:物流公司收款信息表 (logistics_company_payment_infos)
|
||||
-- 设计方案章节:9.10 物流公司收款信息表
|
||||
CREATE TABLE IF NOT EXISTS logistics_company_payment_infos (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
id SERIAL PRIMARY KEY,
|
||||
logistics_company_id INTEGER NOT NULL,
|
||||
account_name 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)
|
||||
-- 设计方案章节:9.4 物流单表
|
||||
CREATE TABLE IF NOT EXISTS logistics_records (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
id SERIAL PRIMARY KEY,
|
||||
code TEXT UNIQUE 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)
|
||||
-- 设计方案章节:9.5 验收单表
|
||||
CREATE TABLE IF NOT EXISTS verification_records (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
id SERIAL PRIMARY KEY,
|
||||
code TEXT UNIQUE NOT NULL,
|
||||
purchase_order_id INTEGER NOT NULL,
|
||||
logistics_record_id INTEGER,
|
||||
@@ -137,7 +137,7 @@ CREATE INDEX IF NOT EXISTS idx_verification_records_status ON verification_recor
|
||||
-- 表5:退库单表 (return_records)
|
||||
-- 设计方案章节:9.6 退库单表
|
||||
CREATE TABLE IF NOT EXISTS return_records (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
id SERIAL PRIMARY KEY,
|
||||
code TEXT UNIQUE 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)
|
||||
-- 设计方案章节:9.7 材料价格历史表
|
||||
CREATE TABLE IF NOT EXISTS material_price_history (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
id SERIAL PRIMARY KEY,
|
||||
product_id INTEGER NOT NULL,
|
||||
purchase_order_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)
|
||||
-- 设计方案章节:9.8 项目材料库存表
|
||||
CREATE TABLE IF NOT EXISTS project_material_inventory (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
id SERIAL PRIMARY KEY,
|
||||
project_id INTEGER NOT NULL,
|
||||
product_id INTEGER NOT NULL,
|
||||
product_name TEXT,
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
-- 创建分包商收款信息表
|
||||
CREATE TABLE IF NOT EXISTS subcontractor_payment_infos (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
id SERIAL PRIMARY KEY,
|
||||
subcontractor_id INTEGER NOT NULL,
|
||||
account_name TEXT NOT NULL,
|
||||
bank_account TEXT NOT NULL,
|
||||
bank_name TEXT NOT NULL,
|
||||
qr_code TEXT,
|
||||
is_primary INTEGER DEFAULT 0,
|
||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
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:
|
||||
postgres:
|
||||
image: postgres:15-alpine
|
||||
restart: unless-stopped
|
||||
container_name: yunhaifinance-postgres
|
||||
environment:
|
||||
POSTGRES_USER: ${DB_USER:-postgres}
|
||||
POSTGRES_PASSWORD: ${DB_PASSWORD:-changeme}
|
||||
POSTGRES_DB: ${DB_NAME:-company_finance}
|
||||
ports:
|
||||
- "5432:5432"
|
||||
- "10052:5432"
|
||||
volumes:
|
||||
- postgres_data:/var/lib/postgresql/data
|
||||
- yunhaifinance-postgres-data:/var/lib/postgresql/data
|
||||
- ./backend/migrations:/docker-entrypoint-initdb.d
|
||||
healthcheck:
|
||||
test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-postgres}"]
|
||||
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
|
||||
networks:
|
||||
- yunhaifinance-network
|
||||
|
||||
volumes:
|
||||
postgres_data:
|
||||
upload_data:
|
||||
yunhaifinance-postgres-data:
|
||||
|
||||
networks:
|
||||
yunhaifinance-network:
|
||||
+2
-2
@@ -3,12 +3,12 @@ FROM node:18-alpine AS build
|
||||
WORKDIR /app
|
||||
|
||||
COPY package*.json ./
|
||||
RUN npm ci
|
||||
RUN npm install
|
||||
|
||||
COPY . .
|
||||
RUN npm run build
|
||||
|
||||
FROM nginx:alpine
|
||||
FROM nginx:1.26-alpine
|
||||
|
||||
COPY --from=build /app/dist /usr/share/nginx/html
|
||||
COPY nginx.conf /etc/nginx/conf.d/default.conf
|
||||
|
||||
+3
-3
@@ -7,11 +7,11 @@
|
||||
<meta name="theme-color" content="#1890ff" />
|
||||
<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-title" content="轻远ERP" />
|
||||
<meta name="description" content="轻远电力老挝分公司财务管理系统" />
|
||||
<meta name="apple-mobile-web-app-title" content="云海财务系统" />
|
||||
<meta name="description" content="云海财务系统 - 企业管理平台" />
|
||||
<link rel="apple-touch-icon" href="/logo.png" />
|
||||
<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="Pragma" content="no-cache">
|
||||
<meta http-equiv="Expires" content="0">
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "轻远电力老挝ERP",
|
||||
"short_name": "轻远ERP",
|
||||
"description": "轻远电力老挝分公司财务管理系统",
|
||||
"name": "云海财务系统",
|
||||
"short_name": "云海财务",
|
||||
"description": "云海财务系统 - 企业管理平台",
|
||||
"start_url": "/",
|
||||
"display": "standalone",
|
||||
"orientation": "portrait",
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
import React from 'react'
|
||||
import { Space, Typography } from 'antd'
|
||||
import { ThunderboltOutlined } from '@ant-design/icons'
|
||||
import { CloudOutlined } from '@ant-design/icons'
|
||||
import { useLanguageStore } from '../../store/languageStore'
|
||||
|
||||
const { Text, Title } = Typography
|
||||
|
||||
interface CompanyLogoProps {
|
||||
collapsed?: boolean
|
||||
showText?: boolean
|
||||
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 sizeMap = {
|
||||
small: { fontSize: 14, iconSize: 20 },
|
||||
@@ -19,11 +20,12 @@ const CompanyLogo: React.FC<CompanyLogoProps> = ({ showText = true, size = 'medi
|
||||
}
|
||||
|
||||
const { fontSize, iconSize } = sizeMap[size]
|
||||
const displayContent = showText && !collapsed
|
||||
|
||||
return (
|
||||
<Space align="center" style={{ cursor: 'pointer' }}>
|
||||
{/* 图标 */}
|
||||
<ThunderboltOutlined
|
||||
<CloudOutlined
|
||||
style={{
|
||||
fontSize: iconSize,
|
||||
color: '#1890ff',
|
||||
@@ -32,7 +34,7 @@ const CompanyLogo: React.FC<CompanyLogoProps> = ({ showText = true, size = 'medi
|
||||
/>
|
||||
|
||||
{/* 公司名称 */}
|
||||
{showText && (
|
||||
{displayContent && (
|
||||
<div>
|
||||
<Title
|
||||
level={5}
|
||||
@@ -53,7 +55,7 @@ const CompanyLogo: React.FC<CompanyLogoProps> = ({ showText = true, size = 'medi
|
||||
marginTop: -2
|
||||
}}
|
||||
>
|
||||
Qingyuan Power Laos
|
||||
Yunhai Finance System
|
||||
</Text>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -224,6 +224,11 @@ const MainLayout: React.FC = () => {
|
||||
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])
|
||||
|
||||
@@ -461,6 +466,14 @@ const MainLayout: React.FC = () => {
|
||||
}}>
|
||||
<Outlet />
|
||||
</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>
|
||||
|
||||
<Modal
|
||||
|
||||
@@ -87,7 +87,7 @@ export default {
|
||||
},
|
||||
|
||||
login: {
|
||||
title: '轻远电力老挝ERP',
|
||||
title: '云海财务系统',
|
||||
subtitle: '项目管理与财务报销一体化平台',
|
||||
username: '用户名',
|
||||
password: '密码',
|
||||
@@ -2208,11 +2208,11 @@ export default {
|
||||
description: '系统信息与版本',
|
||||
systemInfo: '系统信息',
|
||||
systemName: '系统名称',
|
||||
systemNameValue: '轻远电力老挝ERP',
|
||||
systemNameValue: '云海财务系统',
|
||||
version: '系统版本',
|
||||
versionValue: 'V1.0.0',
|
||||
devTeam: '开发团队',
|
||||
devTeamValue: '轻远电力信息技术部',
|
||||
devTeamValue: '云海技术部',
|
||||
onlineDate: '上线日期',
|
||||
onlineDateValue: '2026年3月',
|
||||
techArchitecture: '技术架构',
|
||||
@@ -2237,7 +2237,7 @@ export default {
|
||||
connectionStatus: '连接状态',
|
||||
normal: '正常',
|
||||
lastBackup: '最近备份',
|
||||
footer: '© 2026 轻远电力老挝ERP系统 - 版本 V1.0.0',
|
||||
footer: '© 2026 云海财务系统系统 - 版本 V1.0.0',
|
||||
},
|
||||
|
||||
backup: {
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
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 {
|
||||
CloudServerOutlined,
|
||||
DatabaseOutlined,
|
||||
NodeIndexOutlined,
|
||||
CheckCircleOutlined,
|
||||
InfoCircleOutlined
|
||||
InfoCircleOutlined,
|
||||
RocketOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { useLanguageStore } from '../../store/languageStore';
|
||||
|
||||
@@ -15,11 +16,43 @@ const AboutPage: React.FC = () => {
|
||||
const { t, currentLanguage } = useLanguageStore();
|
||||
|
||||
return (
|
||||
<div style={{ padding: 24 }}>
|
||||
<div style={{ marginBottom: 24 }}>
|
||||
<Title level={3} style={{ marginBottom: 8 }}>{t('about.title')}</Title>
|
||||
<Paragraph type="secondary">{t('about.description')}</Paragraph>
|
||||
</div>
|
||||
<div style={{ padding: 24, maxWidth: 1400, margin: '0 auto' }}>
|
||||
<Card
|
||||
style={{
|
||||
marginBottom: 32,
|
||||
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}>
|
||||
<Col span={16}>
|
||||
|
||||
@@ -74,19 +74,39 @@ const LoginPage: React.FC = () => {
|
||||
return (
|
||||
<div style={{
|
||||
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',
|
||||
alignItems: '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
|
||||
className="login-card"
|
||||
style={{
|
||||
width: '100%',
|
||||
maxWidth: 480,
|
||||
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 } }}
|
||||
>
|
||||
@@ -102,13 +122,9 @@ const LoginPage: React.FC = () => {
|
||||
<div style={{
|
||||
textAlign: 'center',
|
||||
padding: '12px',
|
||||
background: '#f0f2f5',
|
||||
background: 'transparent',
|
||||
borderRadius: '8px',
|
||||
border: '2px solid #1890ff'
|
||||
}}>
|
||||
<div style={{ marginBottom: 8, color: '#1890ff', fontWeight: 'bold' }}>
|
||||
{t('login.selectLanguage')}
|
||||
</div>
|
||||
<LanguageSelector size="large" style={{ width: '200px' }} />
|
||||
</div>
|
||||
|
||||
@@ -127,51 +143,61 @@ const LoginPage: React.FC = () => {
|
||||
layout="vertical"
|
||||
onFinish={handleSubmit}
|
||||
autoComplete="off"
|
||||
initialValues={{ username: 'admin', password: QUICK_PASSWORD }}
|
||||
>
|
||||
<Form.Item
|
||||
name="username"
|
||||
label={t('login.username')}
|
||||
rules={[
|
||||
{ required: true, message: t('login.usernameRequired') },
|
||||
{ min: 3, message: t('login.usernameMin') }
|
||||
]}
|
||||
>
|
||||
<Input
|
||||
prefix={<UserOutlined />}
|
||||
prefix={<UserOutlined style={{ color: '#1890ff' }} />}
|
||||
placeholder={t('login.usernamePlaceholder')}
|
||||
size="large"
|
||||
style={{ borderRadius: 8 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item
|
||||
name="password"
|
||||
label={t('login.password')}
|
||||
rules={[
|
||||
{ required: true, message: t('login.passwordRequired') },
|
||||
{ min: 6, message: t('login.passwordMin') }
|
||||
]}
|
||||
>
|
||||
<Input.Password
|
||||
prefix={<LockOutlined />}
|
||||
prefix={<LockOutlined style={{ color: '#1890ff' }} />}
|
||||
placeholder={t('login.passwordPlaceholder')}
|
||||
size="large"
|
||||
style={{ borderRadius: 8 }}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
<Form.Item>
|
||||
<Form.Item style={{ marginBottom: 12 }}>
|
||||
<Button
|
||||
type="primary"
|
||||
htmlType="submit"
|
||||
loading={loading}
|
||||
size="large"
|
||||
block
|
||||
style={{ borderRadius: 8, height: 48, fontSize: 16 }}
|
||||
>
|
||||
{t('login.loginButton')}
|
||||
</Button>
|
||||
</Form.Item>
|
||||
|
||||
<div style={{ textAlign: 'right', marginBottom: 24 }}>
|
||||
<a href="#" style={{ color: '#1890ff', fontSize: 14 }}>忘记密码?</a>
|
||||
</div>
|
||||
</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%' }}>
|
||||
{testAccounts.map((account, index) => (
|
||||
@@ -180,14 +206,14 @@ const LoginPage: React.FC = () => {
|
||||
size="small"
|
||||
hoverable
|
||||
onClick={() => handleTestLogin(account.username)}
|
||||
style={{ cursor: 'pointer' }}
|
||||
style={{ cursor: 'pointer', borderRadius: 8, backgroundColor: '#fafafa', border: '1px solid #f0f0f0' }}
|
||||
>
|
||||
<Flex justify="space-between" align="center">
|
||||
<Space>
|
||||
{account.role === t('user.admin') && <DashboardOutlined style={{ color: 'red' }} />}
|
||||
{account.role === t('user.finance') && <DollarOutlined style={{ color: 'orange' }} />}
|
||||
{account.role === t('user.manager') && <ProjectOutlined style={{ color: 'blue' }} />}
|
||||
{account.role === t('user.employee') && <TeamOutlined style={{ color: 'green' }} />}
|
||||
{account.role === t('user.admin') && <DashboardOutlined style={{ color: '#f5222d' }} />}
|
||||
{account.role === t('user.finance') && <DollarOutlined style={{ color: '#fa8c16' }} />}
|
||||
{account.role === t('user.manager') && <ProjectOutlined style={{ color: '#1890ff' }} />}
|
||||
{account.role === t('user.employee') && <TeamOutlined style={{ color: '#52c41a' }} />}
|
||||
<Text strong>{account.name}</Text>
|
||||
</Space>
|
||||
<Text type="secondary">
|
||||
@@ -197,23 +223,6 @@ const LoginPage: React.FC = () => {
|
||||
</Card>
|
||||
))}
|
||||
</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>
|
||||
</Card>
|
||||
</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