feat: 完善采购申请流程 - 添加审批、执行、列表筛选排序功能

This commit is contained in:
System Administrator
2026-03-28 00:34:32 +07:00
parent 841f19e3f8
commit d437580500
306 changed files with 42669 additions and 27143 deletions
@@ -0,0 +1,17 @@
-- 创建供应商收款信息表
CREATE TABLE IF NOT EXISTS supplier_payment_infos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
supplier_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,
FOREIGN KEY (supplier_id) REFERENCES suppliers(id) ON DELETE CASCADE
);
-- 创建索引
CREATE INDEX IF NOT EXISTS idx_supplier_payment_infos_supplier_id ON supplier_payment_infos(supplier_id);
CREATE INDEX IF NOT EXISTS idx_supplier_payment_infos_is_primary ON supplier_payment_infos(is_primary);
+228 -16
View File
@@ -22,7 +22,7 @@ const validate = (req, res, next) => {
dotenv.config();
const app = express();
const PORT = 3005;
const PORT = process.env.PORT || 3001;
// 中间件
app.use(cors());
@@ -32,6 +32,97 @@ app.use(express.urlencoded({ extended: true }));
// 静态文件服务 - 前端应用
app.use(express.static(path.join(__dirname, '../frontend/dist')));
// 创建供应商收款信息表
async function createSupplierPaymentInfosTable() {
try {
await db.query(`
CREATE TABLE IF NOT EXISTS supplier_payment_infos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
supplier_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,
FOREIGN KEY (supplier_id) REFERENCES suppliers(id) ON DELETE CASCADE
)
`);
console.log('供应商收款信息表创建成功');
} catch (error) {
console.error('创建供应商收款信息表失败:', error);
}
}
// 添加purchase_type字段到purchase_requests表
async function addPurchaseTypeColumn() {
try {
// 检查字段是否存在
const result = await db.query(`PRAGMA table_info(purchase_requests)`);
const hasPurchaseType = result.rows.some(row => row.name === 'purchase_type');
if (!hasPurchaseType) {
await db.query(`ALTER TABLE purchase_requests ADD COLUMN purchase_type TEXT DEFAULT 'inventory'`);
console.log('purchase_type字段添加成功');
} else {
console.log('purchase_type字段已存在');
}
} catch (error) {
console.error('添加purchase_type字段失败:', error);
}
}
// 添加brief_description字段到purchase_requests表
async function addBriefDescriptionColumn() {
try {
// 检查字段是否存在
const result = await db.query(`PRAGMA table_info(purchase_requests)`);
const hasBriefDescription = result.rows.some(row => row.name === 'brief_description');
if (!hasBriefDescription) {
await db.query(`ALTER TABLE purchase_requests ADD COLUMN brief_description TEXT`);
console.log('brief_description字段添加成功');
} else {
console.log('brief_description字段已存在');
}
} catch (error) {
console.error('添加brief_description字段失败:', error);
}
}
// 添加execute_date和execute_method字段到purchase_requests表
async function addExecuteColumns() {
try {
// 检查字段是否存在
const result = await db.query(`PRAGMA table_info(purchase_requests)`);
const hasExecuteDate = result.rows.some(row => row.name === 'execute_date');
const hasExecuteMethod = result.rows.some(row => row.name === 'execute_method');
if (!hasExecuteDate) {
await db.query(`ALTER TABLE purchase_requests ADD COLUMN execute_date TEXT`);
console.log('execute_date字段添加成功');
} else {
console.log('execute_date字段已存在');
}
if (!hasExecuteMethod) {
await db.query(`ALTER TABLE purchase_requests ADD COLUMN execute_method TEXT`);
console.log('execute_method字段添加成功');
} else {
console.log('execute_method字段已存在');
}
} catch (error) {
console.error('添加执行字段失败:', error);
}
}
// 初始化数据库表
createSupplierPaymentInfosTable();
addPurchaseTypeColumn();
addBriefDescriptionColumn();
addExecuteColumns();
// ==================== 健康检查 ====================
app.get('/api/health', (req, res) => {
res.json({
@@ -435,10 +526,34 @@ app.get('/api/suppliers', async (req, res) => {
LIMIT 50
`);
// 为每个供应商获取收款信息
const suppliersWithPaymentInfos = await Promise.all(
result.rows.map(async (supplier) => {
const paymentInfosResult = await db.query(
`SELECT * FROM supplier_payment_infos WHERE supplier_id = ? ORDER BY is_primary DESC`,
[supplier.id]
);
const paymentInfos = paymentInfosResult.rows.map(payment => ({
id: payment.id,
account_name: payment.account_name,
bank_account: payment.bank_account,
bank_name: payment.bank_name,
qr_code: payment.qr_code,
is_primary: payment.is_primary === 1
}));
return {
...supplier,
payment_infos: paymentInfos
};
})
);
res.json({
success: true,
data: result.rows,
count: result.rows.length
data: suppliersWithPaymentInfos,
count: suppliersWithPaymentInfos.length
});
} catch (error) {
console.error('获取供应商失败:', error);
@@ -478,6 +593,23 @@ app.get('/api/suppliers/:id', async (req, res) => {
is_primary: contact.is_primary === 1
}));
// 获取供应商的所有收款信息
const paymentInfosResult = await db.query(`
SELECT * FROM supplier_payment_infos
WHERE supplier_id = ?
ORDER BY is_primary DESC
`, [id]);
// 转换收款信息数据结构
const paymentInfos = paymentInfosResult.rows.map(payment => ({
id: payment.id,
account_name: payment.account_name,
bank_account: payment.bank_account,
bank_name: payment.bank_name,
qr_code: payment.qr_code,
is_primary: payment.is_primary === 1
}));
// 转换数据结构以匹配前端期望
const formattedSupplier = {
id: supplier.id,
@@ -486,6 +618,7 @@ app.get('/api/suppliers/:id', async (req, res) => {
supply_category: supplier.supply_category || '电力设备', // 默认为电力设备
country: supplier.country || 'Laos', // 默认为老挝
contacts: contacts.length > 0 ? contacts : [], // 使用从联系人表获取的联系人
payment_infos: paymentInfos.length > 0 ? paymentInfos : [], // 使用从收款信息表获取的收款信息
remark: supplier.remark || '', // 默认为空
total_purchase_amount: 0, // 默认为0
total_paid: 0, // 默认为0
@@ -517,7 +650,7 @@ app.get('/api/suppliers/:id', async (req, res) => {
app.post('/api/suppliers', async (req, res) => {
try {
const { name, supply_category, country, remark, contacts } = req.body;
const { name, supply_category, country, remark, contacts, payment_infos } = req.body;
// 从contacts中获取主联系人信息
const primaryContact = contacts?.find(c => c.is_primary) || contacts?.[0];
@@ -546,6 +679,17 @@ app.post('/api/suppliers', async (req, res) => {
}
}
// 插入收款信息数据
if (payment_infos && payment_infos.length > 0) {
for (const paymentInfo of payment_infos) {
await db.query(
`INSERT INTO supplier_payment_infos (supplier_id, account_name, bank_account, bank_name, qr_code, is_primary, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`,
[supplierId, paymentInfo.account_name, paymentInfo.bank_account, paymentInfo.bank_name, paymentInfo.qr_code, paymentInfo.is_primary ? 1 : 0]
);
}
}
res.json({
success: true,
message: '供应商创建成功',
@@ -556,6 +700,7 @@ app.post('/api/suppliers', async (req, res) => {
supply_category,
country,
contacts: contacts || [],
payment_infos: payment_infos || [],
remark,
total_purchase_amount: 0,
total_paid: 0,
@@ -576,7 +721,7 @@ app.post('/api/suppliers', async (req, res) => {
app.put('/api/suppliers/:id', async (req, res) => {
try {
const { id } = req.params;
const { name, supply_category, country, remark, contacts } = req.body;
const { name, supply_category, country, remark, contacts, payment_infos } = req.body;
// 从contacts中获取主联系人信息
const primaryContact = contacts?.find(c => c.is_primary) || contacts?.[0];
@@ -607,6 +752,20 @@ app.put('/api/suppliers/:id', async (req, res) => {
}
}
// 删除旧的收款信息数据
await db.query(`DELETE FROM supplier_payment_infos WHERE supplier_id = ?`, [id]);
// 插入新的收款信息数据
if (payment_infos && payment_infos.length > 0) {
for (const paymentInfo of payment_infos) {
await db.query(
`INSERT INTO supplier_payment_infos (supplier_id, account_name, bank_account, bank_name, qr_code, is_primary, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, datetime('now'), datetime('now'))`,
[id, paymentInfo.account_name, paymentInfo.bank_account, paymentInfo.bank_name, paymentInfo.qr_code, paymentInfo.is_primary ? 1 : 0]
);
}
}
res.json({
success: true,
message: '供应商更新成功',
@@ -617,6 +776,7 @@ app.put('/api/suppliers/:id', async (req, res) => {
supply_category,
country,
contacts: contacts || [],
payment_infos: payment_infos || [],
remark,
total_purchase_amount: 0,
total_paid: 0,
@@ -3507,12 +3667,14 @@ app.get('/api/executions/pending', async (req, res) => {
const reimbursements = await db.query('SELECT * FROM reimbursements WHERE status = ?', ['approved']);
const payments = await db.query('SELECT * FROM payment_requests WHERE status = ?', ['approved']);
const verifications = await db.query('SELECT * FROM verifications WHERE status = ?', ['approved']);
const purchaseRequests = await db.query('SELECT * FROM purchase_requests WHERE status = ?', ['approved']);
const pendingData = [
...advances.rows.map(item => ({ ...item, type: '预支申请', code: item.advance_code })),
...reimbursements.rows.map(item => ({ ...item, type: '报销申请', code: item.reimbursement_code })),
...payments.rows.map(item => ({ ...item, type: '付款申请', code: item.request_code })),
...verifications.rows.map(item => ({ ...item, type: '核销申请', code: item.verification_code }))
...verifications.rows.map(item => ({ ...item, type: '核销申请', code: item.verification_code })),
...purchaseRequests.rows.map(item => ({ ...item, type: '采购申请', code: item.request_code, amount: item.total_amount, date: item.request_date, reason: item.brief_description || item.remark || '采购申请' }))
];
res.json({ success: true, data: pendingData, count: pendingData.length });
@@ -3529,12 +3691,23 @@ app.get('/api/executions/executed', async (req, res) => {
const reimbursements = await db.query('SELECT * FROM reimbursements WHERE status = ?', ['executed']);
const payments = await db.query('SELECT * FROM payment_requests WHERE status = ?', ['executed']);
const verifications = await db.query('SELECT * FROM verifications WHERE status = ?', ['executed']);
const purchaseRequests = await db.query('SELECT * FROM purchase_requests WHERE status = ?', ['executed']);
const executedData = [
...advances.rows.map(item => ({ ...item, type: '预支申请', code: item.advance_code })),
...reimbursements.rows.map(item => ({ ...item, type: '报销申请', code: item.reimbursement_code })),
...payments.rows.map(item => ({ ...item, type: '付款申请', code: item.request_code })),
...verifications.rows.map(item => ({ ...item, type: '核销申请', code: item.verification_code }))
...verifications.rows.map(item => ({ ...item, type: '核销申请', code: item.verification_code })),
...purchaseRequests.rows.map(item => ({
...item,
type: '采购申请',
code: item.request_code,
amount: item.total_amount,
date: item.request_date,
reason: item.brief_description || item.remark || '采购申请',
executeDate: item.execute_date,
executeMethod: item.execute_method
}))
];
res.json({ success: true, data: executedData, count: executedData.length });
@@ -3610,6 +3783,9 @@ app.post('/api/executions', async (req, res) => {
throw error;
}
break;
case 'purchase':
await db.query('UPDATE purchase_requests SET status = ?, execute_date = ?, execute_method = ? WHERE id = ?', [status, executeDate, execute_method, apply_id]);
break;
}
res.json({ success: true, message: '执行操作成功' });
@@ -3910,6 +4086,24 @@ app.get('/api/purchase-requests/:id', async (req, res) => {
purchaseRequest.items = itemsResult.rows;
// 获取供应商的付款信息
if (purchaseRequest.supplier_id) {
const paymentInfosResult = await db.query(`
SELECT * FROM supplier_payment_infos
WHERE supplier_id = ?
ORDER BY is_primary DESC
`, [purchaseRequest.supplier_id]);
purchaseRequest.supplier_payment_infos = paymentInfosResult.rows.map(payment => ({
id: payment.id,
account_name: payment.account_name,
bank_account: payment.bank_account,
bank_name: payment.bank_name,
qr_code: payment.qr_code,
is_primary: payment.is_primary === 1
}));
}
res.json({
success: true,
data: purchaseRequest
@@ -3928,7 +4122,7 @@ app.post('/api/purchase-requests', async (req, res) => {
try {
const {
project_id, applicant, request_date, supplier_id, supplier_name,
expense_category, total_amount, currency, remark, attachments, items
expense_category, total_amount, currency, remark, attachments, items, purchase_type, brief_description
} = req.body;
const date = new Date();
@@ -3936,9 +4130,9 @@ app.post('/api/purchase-requests', async (req, res) => {
const result = await db.query(`
INSERT INTO purchase_requests
(request_code, project_id, applicant, request_date, supplier_id, supplier_name, expense_category, total_amount, currency, remark, attachments, status, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending', datetime('now'), datetime('now'))
`, [requestCode, project_id, applicant, request_date, supplier_id, supplier_name, expense_category, total_amount || 0, currency || 'CNY', remark, attachments ? JSON.stringify(attachments) : null]);
(request_code, project_id, applicant, request_date, supplier_id, supplier_name, expense_category, total_amount, currency, remark, attachments, status, purchase_type, brief_description, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'pending_edit', ?, ?, datetime('now'), datetime('now'))
`, [requestCode, project_id, applicant, request_date, supplier_id, supplier_name, expense_category, total_amount || 0, currency || 'CNY', remark, attachments ? JSON.stringify(attachments) : null, purchase_type || 'inventory', brief_description]);
const purchaseRequestId = result.lastID;
@@ -3972,15 +4166,16 @@ app.put('/api/purchase-requests/:id', async (req, res) => {
const { id } = req.params;
const {
project_id, applicant, request_date, supplier_id, supplier_name,
expense_category, total_amount, currency, remark, attachments, items
expense_category, total_amount, currency, remark, attachments, items, purchase_type, brief_description
} = req.body;
const result = await db.query(`
UPDATE purchase_requests
SET project_id = ?, applicant = ?, request_date = ?, supplier_id = ?, supplier_name = ?,
expense_category = ?, total_amount = ?, currency = ?, remark = ?, attachments = ?, updated_at = datetime('now')
expense_category = ?, total_amount = ?, currency = ?, remark = ?, attachments = ?,
purchase_type = ?, brief_description = ?, updated_at = datetime('now')
WHERE id = ?
`, [project_id, applicant, request_date, supplier_id, supplier_name, expense_category, total_amount, currency, remark, attachments ? JSON.stringify(attachments) : null, id]);
`, [project_id, applicant, request_date, supplier_id, supplier_name, expense_category, total_amount, currency, remark, attachments ? JSON.stringify(attachments) : null, purchase_type || 'inventory', brief_description, id]);
if (result.changes === 0) {
return res.status(404).json({ success: false, message: '采购申请不存在' });
@@ -4040,7 +4235,7 @@ app.post('/api/purchase-requests/:id/submit', async (req, res) => {
try {
const { id } = req.params;
const result = await db.query('UPDATE purchase_requests SET status = ?, updated_at = datetime(\'now\') WHERE id = ?', ['submitted', id]);
const result = await db.query('UPDATE purchase_requests SET status = ?, updated_at = datetime(\'now\') WHERE id = ?', ['pending', id]);
if (result.changes === 0) {
return res.status(404).json({ success: false, message: '采购申请不存在' });
@@ -4074,7 +4269,7 @@ app.post('/api/purchase-requests/:id/reject', async (req, res) => {
try {
const { id } = req.params;
const result = await db.query('UPDATE purchase_requests SET status = ?, updated_at = datetime(\'now\') WHERE id = ?', ['rejected', id]);
const result = await db.query('UPDATE purchase_requests SET status = ?, updated_at = datetime(\'now\') WHERE id = ?', ['pending_edit', id]);
if (result.changes === 0) {
return res.status(404).json({ success: false, message: '采购申请不存在' });
@@ -4111,6 +4306,23 @@ app.post('/api/purchase-requests/:id/execute', async (req, res) => {
}
});
app.post('/api/purchase-requests/:id/withdraw', async (req, res) => {
try {
const { id } = req.params;
const result = await db.query('UPDATE purchase_requests SET status = ?, updated_at = datetime(\'now\') WHERE id = ?', ['withdrawn', id]);
if (result.changes === 0) {
return res.status(404).json({ success: false, message: '采购申请不存在' });
}
res.json({ success: true, message: '撤回成功' });
} catch (error) {
console.error('撤回采购申请失败:', error);
res.status(500).json({ success: false, message: '撤回采购申请失败', error: error.message });
}
});
// ==================== 库存管理API ====================
app.get('/api/inventory', async (req, res) => {
try {
@@ -49,7 +49,7 @@ async function testPurchaseFlow() {
'Content-Type': 'application/json'
}
}, {
project_id: 1,
project_id: 3,
applicant: '测试申请人',
request_date: '2026-03-25',
expense_category: 'material',
@@ -171,7 +171,7 @@ async function testInventoryFlow() {
}
// 3. 创建出库记录
console.log('\n3. 创建出库记录...');
console.log('3. 创建出库记录...');
const outResponse = await request({
hostname: 'localhost',
port: 3005,
@@ -181,7 +181,7 @@ async function testInventoryFlow() {
'Content-Type': 'application/json'
}
}, {
project_id: 1,
project_id: 3,
product_id: 1,
quantity: 10,
operator: '测试操作员'
@@ -204,7 +204,7 @@ async function testCostStatistics() {
const costResponse = await request({
hostname: 'localhost',
port: 3005,
path: '/api/projects/1/cost-summary',
path: '/api/projects/3/cost-summary',
method: 'GET'
});
console.log('获取项目成本统计响应:', costResponse.status, costResponse.body);
@@ -10,9 +10,9 @@ export const API_CONFIG = {
// API端点
export const API_ENDPOINTS = {
auth: {
login: '/v1/auth/login',
logout: '/v1/auth/logout',
me: '/v1/auth/me',
login: '/auth/login',
logout: '/auth/logout',
me: '/auth/me',
},
products: '/products',
customers: '/customers',
File diff suppressed because it is too large Load Diff
@@ -4,7 +4,7 @@ import {
Card, Descriptions, Tag, Spin, Empty, Row, Col, Statistic, Table, Button, Divider, Typography, Badge
} from 'antd'
import {
ArrowLeftOutlined, ShopOutlined, FileTextOutlined, DollarOutlined, UserOutlined, PhoneOutlined
ArrowLeftOutlined, ShopOutlined, FileTextOutlined, DollarOutlined, UserOutlined, PhoneOutlined, BankOutlined
} from '@ant-design/icons'
const { Title, Text } = Typography
@@ -16,6 +16,15 @@ interface Contact {
is_primary?: boolean
}
interface PaymentInfo {
id: number
account_name: string
bank_account: string
bank_name: string
qr_code?: string
is_primary: boolean
}
interface Supplier {
id: number
code: string
@@ -23,6 +32,7 @@ interface Supplier {
supply_category: string
country: string
contacts: Contact[]
payment_infos: PaymentInfo[]
remark: string
total_purchase_amount: number
total_paid: number
@@ -142,7 +152,36 @@ const SupplierDetail: React.FC = () => {
{(supplier.contacts || []).length === 0 && <Empty description="暂无联系人" image={Empty.PRESENTED_IMAGE_SIMPLE} />}
</Card>
{/* ========== 卡片2关联项目 ========== */}
{/* ========== 卡片2收款信息 ========== */}
<Card title={<><BankOutlined /> </>} style={{ marginBottom: 24, borderRadius: 8 }} headStyle={{ background: '#f5f5f5', fontWeight: 600 }}>
<Row gutter={[16, 16]}>
{(supplier.payment_infos || []).map((payment, i) => (
<Col key={i} xs={24} sm={12} lg={8}>
<Card size="small" style={{ borderLeft: payment.is_primary ? '3px solid #1890ff' : '3px solid #d9d9d9', background: payment.is_primary ? '#f0f5ff' : '#fff' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
<Text strong>{payment.bank_name || '未命名'}</Text>
{payment.is_primary && <Tag color="blue" size="small"></Tag>}
</div>
<div style={{ color: '#666', fontSize: 13 }}>
{payment.account_name && <div>{payment.account_name}</div>}
{payment.bank_account && <div>{payment.bank_account}</div>}
{payment.qr_code && (
<div style={{ marginTop: 8 }}>
<Text type="secondary"></Text>
<div style={{ marginTop: 4 }}>
<img src={payment.qr_code} alt="收款码" style={{ maxWidth: '100px', maxHeight: '100px' }} />
</div>
</div>
)}
</div>
</Card>
</Col>
))}
</Row>
{(supplier.payment_infos || []).length === 0 && <Empty description="暂无收款信息" image={Empty.PRESENTED_IMAGE_SIMPLE} />}
</Card>
{/* ========== 卡片3:关联项目 ========== */}
<Card title={<><FileTextOutlined /> </>} style={{ marginBottom: 24, borderRadius: 8 }} headStyle={{ background: '#f5f5f5', fontWeight: 600 }}>
{projects.length > 0 ? (
<Table columns={projectColumns} dataSource={projects} rowKey="id" size="small" pagination={false} bordered />
@@ -348,7 +348,18 @@ const SupplierPage: React.FC = () => {
</Form.Item>
</div>
<Form.Item {...restField} name={[name, 'qr_code']} label="收款码" style={{ marginBottom: 0 }}>
<FileUpload maxCount={1} accept="image/*" />
<FileUpload
maxCount={1}
accept="image/*"
value={form.getFieldValue([name, 'qr_code']) ? [form.getFieldValue([name, 'qr_code'])] : []}
onChange={(urls) => {
form.setFieldsValue({
payment_infos: form.getFieldValue('payment_infos').map((info: any, i: number) => {
return i === Number(name) ? { ...info, qr_code: urls[0] || '' } : info
})
})
}}
/>
</Form.Item>
{fields.length > 0 && (
<Button type="link" danger onClick={() => remove(name)} style={{ marginTop: 8 }}></Button>
@@ -1,6 +1,6 @@
import React, { useState, useEffect } from 'react';
import { Card, Table, Tag, Button, Space, Modal, Form, Input, message, Tabs, Badge, Descriptions, Divider, List, Upload } from 'antd';
import { CheckOutlined, CloseOutlined, EyeOutlined, EditOutlined, UndoOutlined, FileImageOutlined } from '@ant-design/icons';
import { CheckOutlined, CloseOutlined, EyeOutlined, EditOutlined, UndoOutlined, FileImageOutlined, DeleteOutlined } from '@ant-design/icons';
const { TextArea } = Input;
@@ -31,6 +31,7 @@ const ApprovalManagement: React.FC = () => {
const [editModalVisible, setEditModalVisible] = useState(false);
const [historyModalVisible, setHistoryModalVisible] = useState(false);
const [selectedRecord, setSelectedRecord] = useState<any>(null);
const [fullDetail, setFullDetail] = useState<any>(null);
const [approvalType, setApprovalType] = useState<'approve' | 'reject'>('approve');
const [form] = Form.useForm();
const [editForm] = Form.useForm();
@@ -64,7 +65,7 @@ const ApprovalManagement: React.FC = () => {
const historyData = [];
for (const type of types) {
const response = await fetch(`http://localhost:3005/api/${type}`);
const response = await fetch(`/api/${type}`);
const data = await response.json();
if (data.success && data.data) {
@@ -156,7 +157,7 @@ const ApprovalManagement: React.FC = () => {
// 获取项目列表
const fetchProjects = async () => {
try {
const response = await fetch('http://localhost:3005/api/projects');
const response = await fetch('/api/projects');
if (response.ok) {
const data = await response.json();
if (data.success && Array.isArray(data.data)) {
@@ -174,29 +175,35 @@ const ApprovalManagement: React.FC = () => {
try {
console.log('开始获取待审批数据');
// 获取预支申请
const advancesRes = await fetch('http://localhost:3005/api/advances');
const advancesRes = await fetch('/api/advances');
console.log('Advances response status:', advancesRes.status);
const advancesData = await advancesRes.json();
console.log('Advances data:', advancesData);
// 获取报销申请
const reimbursementsRes = await fetch('http://localhost:3005/api/reimbursements');
const reimbursementsRes = await fetch('/api/reimbursements');
console.log('Reimbursements response status:', reimbursementsRes.status);
const reimbursementsData = await reimbursementsRes.json();
console.log('Reimbursements data:', reimbursementsData);
// 获取付款申请
const paymentsRes = await fetch('http://localhost:3005/api/payment-requests');
const paymentsRes = await fetch('/api/payment-requests');
console.log('Payments response status:', paymentsRes.status);
const paymentsData = await paymentsRes.json();
console.log('Payments data:', paymentsData);
// 获取核销申请
const verificationsRes = await fetch('http://localhost:3005/api/verifications');
const verificationsRes = await fetch('/api/verifications');
console.log('Verifications response status:', verificationsRes.status);
const verificationsData = await verificationsRes.json();
console.log('Verifications data:', verificationsData);
// 获取采购申请
const purchaseRes = await fetch('/api/purchase-requests');
console.log('Purchase requests response status:', purchaseRes.status);
const purchaseData = await purchaseRes.json();
console.log('Purchase requests data:', purchaseData);
// 合并数据
const allPendingData = [];
@@ -292,6 +299,29 @@ const ApprovalManagement: React.FC = () => {
});
}
// 添加采购申请
if (purchaseData.success && purchaseData.data) {
console.log('Purchase requests data length:', purchaseData.data.length);
purchaseData.data.forEach((item: any) => {
console.log('Purchase request item:', item);
if (item.status === 'pending') {
allPendingData.push({
key: `pur-${item.id}`,
id: item.id,
type: '采购申请',
code: item.request_code,
applicant: item.applicant,
amount: item.total_amount,
currency: item.currency,
date: item.request_date,
reason: item.brief_description || item.remark || '采购申请',
status: item.status,
rawData: item
});
}
});
}
console.log('Final pending data:', allPendingData);
setPendingData(allPendingData);
} catch (error) {
@@ -310,7 +340,7 @@ const ApprovalManagement: React.FC = () => {
// 获取类型标签
const getTypeTag = (type: string) => {
const colors: Record<string, string> = { '预支申请': 'blue', '报销申请': 'green', '付款申请': 'orange', '核销申请': 'purple' };
const colors: Record<string, string> = { '预支申请': 'blue', '报销申请': 'green', '付款申请': 'orange', '核销申请': 'purple', '采购申请': 'cyan' };
return <Tag color={colors[type] || 'default'}>{type}</Tag>;
};
@@ -334,10 +364,27 @@ const ApprovalManagement: React.FC = () => {
};
// 查看详情
const handleViewDetail = (record: any) => {
const handleViewDetail = async (record: any) => {
setSelectedRecord(record);
setApprovalType('approve');
form.resetFields();
// 获取完整详情
if (record.type === '采购申请') {
try {
const response = await fetch(`/api/purchase-requests/${record.id}`);
const data = await response.json();
if (data.success) {
setFullDetail(data.data);
}
} catch (error) {
console.error('获取采购申请详情失败:', error);
}
} else {
// 其他类型使用 rawData
setFullDetail(record.rawData);
}
setDetailModalVisible(true);
};
@@ -351,13 +398,15 @@ const ApprovalManagement: React.FC = () => {
const isReimbursement = selectedRecord.key.startsWith('reimb-');
const isPayment = selectedRecord.key.startsWith('pay-');
const isVerification = selectedRecord.key.startsWith('ver-');
const isPurchase = selectedRecord.key.startsWith('pur-');
const id = selectedRecord.id;
let url = '';
if (isAdvance) url = `http://localhost:3005/api/advances/${id}/approve`;
else if (isReimbursement) url = `http://localhost:3005/api/reimbursements/${id}/approve`;
else if (isPayment) url = `http://localhost:3005/api/payment-requests/${id}/approve`;
else if (isVerification) url = `http://localhost:3005/api/verifications/${id}/approve`;
if (isAdvance) url = `/api/advances/${id}/approve`;
else if (isReimbursement) url = `/api/reimbursements/${id}/approve`;
else if (isPayment) url = `/api/payment-requests/${id}/approve`;
else if (isVerification) url = `/api/verifications/${id}/approve`;
else if (isPurchase) url = `/api/purchase-requests/${id}/approve`;
// 发送API请求
const res = await fetch(url, {
@@ -391,13 +440,15 @@ const ApprovalManagement: React.FC = () => {
const isReimbursement = selectedRecord.key.startsWith('reimb-');
const isPayment = selectedRecord.key.startsWith('pay-');
const isVerification = selectedRecord.key.startsWith('ver-');
const isPurchase = selectedRecord.key.startsWith('pur-');
const id = selectedRecord.id;
let url = '';
if (isAdvance) url = `http://localhost:3005/api/advances/${id}/reject`;
else if (isReimbursement) url = `http://localhost:3005/api/reimbursements/${id}/reject`;
else if (isPayment) url = `http://localhost:3005/api/payment-requests/${id}/reject`;
else if (isVerification) url = `http://localhost:3005/api/verifications/${id}/reject`;
if (isAdvance) url = `/api/advances/${id}/reject`;
else if (isReimbursement) url = `/api/reimbursements/${id}/reject`;
else if (isPayment) url = `/api/payment-requests/${id}/reject`;
else if (isVerification) url = `/api/verifications/${id}/reject`;
else if (isPurchase) url = `/api/purchase-requests/${id}/reject`;
// 发送API请求
const res = await fetch(url, {
@@ -456,6 +507,7 @@ const ApprovalManagement: React.FC = () => {
if (key.startsWith('reimb-')) return 'reimbursements';
if (key.startsWith('pay-')) return 'payment-requests';
if (key.startsWith('ver-')) return 'verifications';
if (key.startsWith('pur-')) return 'purchase-requests';
return '';
};
@@ -588,12 +640,10 @@ const ApprovalManagement: React.FC = () => {
{ title: '状态', dataIndex: 'status', key: 'status', width: 100, render: (v: string) => getStatusTag(v) },
{ title: '编号', dataIndex: 'code', key: 'code', width: 140 },
{
title: '操作', key: 'action', width: 200,
title: '操作', key: 'action', width: 100,
render: (_: any, record: any) => (
<Space wrap>
<Space>
<Button size="small" type="primary" icon={<EyeOutlined />} onClick={() => handleViewDetail(record)}></Button>
<Button size="small" icon={<UndoOutlined />} onClick={() => handleWithdraw(record)}></Button>
<Button size="small" icon={<EditOutlined />} onClick={() => handleEdit(record)}></Button>
</Space>
)
}
@@ -618,14 +668,6 @@ const ApprovalManagement: React.FC = () => {
{ key: 'history', label: <span> <Badge count={approvalHistory.length} style={{ marginLeft: 8 }} /></span>, children: <Table columns={historyColumns} dataSource={approvalHistory} rowKey="id" loading={loading} pagination={{ pageSize: 10 }} scroll={{ x: 1400 }} /> },
];
// 获取完整的申请详情
const getFullDetail = () => {
if (!selectedRecord || !selectedRecord.rawData) return null;
return selectedRecord.rawData;
};
const fullDetail = getFullDetail();
return (
<div style={{ padding: 24 }}>
<div style={{ marginBottom: 24 }}>
@@ -731,7 +773,59 @@ const ApprovalManagement: React.FC = () => {
)}
</>
)}
{/* 采购申请特有字段 */}
{selectedRecord.type === '采购申请' && (
<>
<Descriptions.Item label="采购类型">
{fullDetail.purchase_type === 'project' ? '项目采购' : '库存采购'}
</Descriptions.Item>
{fullDetail.purchase_type === 'project' && fullDetail.project_id && (
<Descriptions.Item label="关联项目">{getProjectName(fullDetail.project_id)}</Descriptions.Item>
)}
<Descriptions.Item label="供应商">{fullDetail.supplier_name || '-'}</Descriptions.Item>
<Descriptions.Item label="支出分类">
{fullDetail.expense_category === 'material' ? '材料' :
fullDetail.expense_category === 'equipment' ? '设备' :
fullDetail.expense_category === 'pole' ? '电杆' : '其他'}
</Descriptions.Item>
<Descriptions.Item label="币种">{fullDetail.currency}</Descriptions.Item>
<Descriptions.Item label="申请日期">{fullDetail.request_date}</Descriptions.Item>
<Descriptions.Item label="事由" span={2}>{fullDetail.brief_description || '-'}</Descriptions.Item>
{fullDetail.remark && (
<Descriptions.Item label="备注" span={2}>{fullDetail.remark}</Descriptions.Item>
)}
</>
)}
</Descriptions>
{/* 采购申请商品明细 */}
{selectedRecord.type === '采购申请' && fullDetail.items && fullDetail.items.length > 0 && (
<>
<Divider></Divider>
<List
size="small"
bordered
dataSource={fullDetail.items}
renderItem={(item: any, index: number) => (
<List.Item>
<div style={{ width: '100%' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 8 }}>
<span><strong>{index + 1}. {item.product_name}</strong></span>
<span style={{ fontWeight: 'bold', color: '#1890ff' }}>
{fullDetail.currency} {item.total_price?.toLocaleString('zh-CN', {minimumFractionDigits: 2})}
</span>
</div>
<div style={{ fontSize: 13, color: '#666' }}>
: {item.specification || '-'} | : {item.unit || '-'} |
: {item.quantity} | : {fullDetail.currency} {item.unit_price?.toLocaleString('zh-CN', {minimumFractionDigits: 2})}
</div>
</div>
</List.Item>
)}
/>
</>
)}
{/* 明细清单 */}
{fullDetail.detail_items && fullDetail.detail_items.length > 0 && (
@@ -49,19 +49,24 @@ const ExecutionManagement: React.FC = () => {
const [detailModalVisible, setDetailModalVisible] = useState(false);
const [editModalVisible, setEditModalVisible] = useState(false);
const [selectedRecord, setSelectedRecord] = useState<any>(null);
const [fullDetail, setFullDetail] = useState<any>(null);
const [form] = Form.useForm();
const [editForm] = Form.useForm();
const [voucherFiles, setVoucherFiles] = useState<any[]>([]);
const [isRejecting, setIsRejecting] = useState(false);
// 待执行数据
const [pendingData, setPendingData] = useState([]);
// 已执行数据
const [executedData, setExecutedData] = useState([]);
// 已执行列表筛选状态
const [searchKeyword, setSearchKeyword] = useState('');
const [filterType, setFilterType] = useState<string | null>(null);
const [sortField, setSortField] = useState<string>('executeDate');
const [sortOrder, setSortOrder] = useState<'ascend' | 'descend'>('descend');
// 项目列表
const [projects, setProjects] = useState<any[]>([]);
@@ -70,7 +75,7 @@ const ExecutionManagement: React.FC = () => {
const fetchPendingData = async () => {
setLoading(true);
try {
const response = await fetch('http://localhost:3005/api/executions/pending');
const response = await fetch('/api/executions/pending');
if (response.ok) {
const data = await response.json();
if (data.success && Array.isArray(data.data)) {
@@ -100,7 +105,7 @@ const ExecutionManagement: React.FC = () => {
useEffect(() => {
const fetchProjects = async () => {
try {
const response = await fetch('http://localhost:3005/api/projects');
const response = await fetch('/api/projects');
if (response.ok) {
const data = await response.json();
if (data.success && Array.isArray(data.data)) {
@@ -120,7 +125,7 @@ const ExecutionManagement: React.FC = () => {
const fetchExecutedData = async () => {
setLoading(true);
try {
const response = await fetch('http://localhost:3005/api/executions/executed');
const response = await fetch('/api/executions/executed');
if (response.ok) {
const data = await response.json();
if (data.success && Array.isArray(data.data)) {
@@ -163,7 +168,7 @@ const ExecutionManagement: React.FC = () => {
};
const getTypeTag = (type: string) => {
const colors: Record<string, string> = { '预支申请': 'blue', '报销申请': 'green', '付款申请': 'orange', '核销申请': 'purple' };
const colors: Record<string, string> = { '预支申请': 'blue', '报销申请': 'green', '付款申请': 'orange', '核销申请': 'purple', '采购申请': 'cyan' };
return <Tag color={colors[type] || 'default'}>{type}</Tag>;
};
@@ -187,12 +192,31 @@ const ExecutionManagement: React.FC = () => {
// 查看详情
const handleViewDetail = (record: any) => {
const handleViewDetail = async (record: any) => {
setSelectedRecord(record);
form.resetFields();
form.setFieldsValue({ execute_date: dayjs(), execute_method: 'bank' });
setVoucherFiles([]);
setIsRejecting(false);
// 获取完整详情
if (record.type === '采购申请') {
try {
const response = await fetch(`/api/purchase-requests/${record.id}`);
const data = await response.json();
if (data.success) {
setFullDetail(data.data);
}
} catch (error) {
console.error('获取采购申请详情失败:', error);
// 如果获取失败,使用record中的数据
setFullDetail(record);
}
} else {
// 其他类型使用record中的数据
setFullDetail(record);
}
setDetailModalVisible(true);
};
@@ -212,7 +236,7 @@ const ExecutionManagement: React.FC = () => {
setLoading(true);
// 调用执行API
const executeResponse = await fetch('http://localhost:3005/api/executions', {
const executeResponse = await fetch('/api/executions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -230,7 +254,7 @@ const ExecutionManagement: React.FC = () => {
// 刷新已执行数据
const fetchExecutedData = async () => {
try {
const response = await fetch('http://localhost:3005/api/executions/executed');
const response = await fetch('/api/executions/executed');
if (response.ok) {
const data = await response.json();
if (data.success && Array.isArray(data.data)) {
@@ -275,12 +299,12 @@ const ExecutionManagement: React.FC = () => {
console.log('凭证文件URL列表:', voucherFileUrls);
// 调用执行API
const executeResponse = await fetch('http://localhost:3005/api/executions', {
const executeResponse = await fetch('/api/executions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
apply_id: selectedRecord.id,
apply_type: selectedRecord.type === '预支申请' ? 'advance' : selectedRecord.type === '报销申请' ? 'reimbursement' : selectedRecord.type === '付款申请' ? 'payment' : 'verification',
apply_type: selectedRecord.type === '预支申请' ? 'advance' : selectedRecord.type === '报销申请' ? 'reimbursement' : selectedRecord.type === '付款申请' ? 'payment' : selectedRecord.type === '采购申请' ? 'purchase' : 'verification',
action: 'execute',
execute_method: isRefundVerification ? 'refund' : values.execute_method,
voucher_files: voucherFileUrls,
@@ -293,7 +317,7 @@ const ExecutionManagement: React.FC = () => {
// 刷新已执行数据
const fetchExecutedData = async () => {
try {
const response = await fetch('http://localhost:3005/api/executions/executed');
const response = await fetch('/api/executions/executed');
if (response.ok) {
const data = await response.json();
if (data.success && Array.isArray(data.data)) {
@@ -333,7 +357,7 @@ const ExecutionManagement: React.FC = () => {
setLoading(true);
// 调用退回API
const rejectResponse = await fetch('http://localhost:3005/api/executions', {
const rejectResponse = await fetch('/api/executions', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
@@ -514,6 +538,45 @@ const ExecutionManagement: React.FC = () => {
}
];
// 筛选和排序已执行数据
const getFilteredExecutedData = () => {
let data = [...executedData];
// 按事由搜索
if (searchKeyword) {
data = data.filter(item =>
(item.reason || '').toLowerCase().includes(searchKeyword.toLowerCase()) ||
(item.code || '').toLowerCase().includes(searchKeyword.toLowerCase()) ||
(item.applicant || '').toLowerCase().includes(searchKeyword.toLowerCase())
);
}
// 按类型筛选
if (filterType) {
data = data.filter(item => item.type === filterType);
}
// 排序
data.sort((a, b) => {
let aValue = a[sortField];
let bValue = b[sortField];
// 处理日期排序
if (sortField === 'executeDate') {
aValue = a.execute_date || a.executeDate || '';
bValue = b.execute_date || b.executeDate || '';
}
if (sortOrder === 'ascend') {
return aValue > bValue ? 1 : -1;
} else {
return aValue < bValue ? 1 : -1;
}
});
return data;
};
const executedColumns = [
{ title: '事由', dataIndex: 'reason', key: 'reason', ellipsis: true, render: (v: string, r: any) => <a onClick={() => handleViewDetail(r)}>{v || '-'}</a> },
{ title: '类型', dataIndex: 'type', key: 'type', width: 100, render: (v: string) => getTypeTag(v) },
@@ -524,30 +587,80 @@ const ExecutionManagement: React.FC = () => {
{r.currency !== 'CNY' && r.amount_cny && <div style={{ color: '#999', fontSize: 12 }}> ¥{r.amount_cny.toLocaleString('zh-CN', {minimumFractionDigits: 2})}</div>}
</>
) },
{ title: '执行日期', dataIndex: 'executeDate', key: 'executeDate', width: 100, render: (v: string) => v || '-' },
{ title: '执行日期', dataIndex: 'executeDate', key: 'executeDate', width: 100, sorter: true, render: (v: string) => v || '-' },
{ title: '执行方式', dataIndex: 'executeMethod', key: 'executeMethod', width: 100, render: (v: string) => v || '-' },
{ title: '状态', dataIndex: 'status', key: 'status', width: 100, render: (v: string) => getStatusTag(v) },
{ title: '编号', dataIndex: 'code', key: 'code', width: 140 },
];
// 已执行列表的筛选和排序控件
const ExecutedListControls = () => (
<div style={{ marginBottom: 16, display: 'flex', gap: 16, flexWrap: 'wrap' }}>
<Input.Search
placeholder="搜索事由、编号或申请人"
value={searchKeyword}
onChange={(e) => setSearchKeyword(e.target.value)}
onSearch={(value) => setSearchKeyword(value)}
style={{ width: 250 }}
allowClear
/>
<Select
placeholder="筛选类型"
value={filterType}
onChange={(value) => setFilterType(value)}
style={{ width: 150 }}
allowClear
>
<Select.Option value="预支申请"></Select.Option>
<Select.Option value="报销申请"></Select.Option>
<Select.Option value="付款申请"></Select.Option>
<Select.Option value="核销申请"></Select.Option>
<Select.Option value="采购申请"></Select.Option>
</Select>
<Select
placeholder="排序方式"
value={`${sortField}_${sortOrder}`}
onChange={(value) => {
const [field, order] = (value as string).split('_');
setSortField(field);
setSortOrder(order as 'ascend' | 'descend');
}}
style={{ width: 180 }}
>
<Select.Option value="executeDate_descend"></Select.Option>
<Select.Option value="executeDate_ascend"></Select.Option>
<Select.Option value="amount_descend"></Select.Option>
<Select.Option value="amount_ascend"></Select.Option>
</Select>
</div>
);
const tabItems = [
{ key: 'pending', label: <span> <Badge count={pendingData.length} style={{ marginLeft: 8 }} /></span>, children: <Table columns={pendingColumns} dataSource={pendingData} loading={loading} pagination={{ pageSize: 10 }} scroll={{ x: 1300 }} /> },
{ key: 'executed', label: '已执行', children: <Table columns={executedColumns} dataSource={executedData} loading={loading} pagination={{ pageSize: 10 }} scroll={{ x: 1400 }} /> },
{ key: 'executed', label: '已执行', children: (
<>
<ExecutedListControls />
<Table
columns={executedColumns}
dataSource={getFilteredExecutedData()}
loading={loading}
pagination={{ pageSize: 10 }}
scroll={{ x: 1400 }}
onChange={(pagination, filters, sorter: any) => {
if (sorter.field) {
setSortField(sorter.field);
setSortOrder(sorter.order || 'descend');
}
}}
/>
</>
)},
];
// 获取完整的申请详情
const getFullDetail = () => {
if (!selectedRecord || !selectedRecord.rawData) return selectedRecord;
return selectedRecord.rawData;
};
const fullDetail = getFullDetail();
// 上传配置
const uploadProps = {
name: 'file',
action: 'http://localhost:3005/api/upload/single',
action: '/api/upload/single',
headers: {
authorization: 'authorization-text',
},
@@ -672,7 +785,80 @@ const ExecutionManagement: React.FC = () => {
)}
</>
)}
{/* 采购申请特有字段 */}
{selectedRecord.type === '采购申请' && (
<>
<Descriptions.Item label="采购类型">
{fullDetail.purchase_type === 'project' ? '项目采购' : '库存采购'}
</Descriptions.Item>
{fullDetail.purchase_type === 'project' && fullDetail.project_id && (
<Descriptions.Item label="关联项目">{getProjectName(fullDetail.project_id)}</Descriptions.Item>
)}
<Descriptions.Item label="供应商">{fullDetail.supplier_name || '-'}</Descriptions.Item>
<Descriptions.Item label="支出分类">
{fullDetail.expense_category === 'material' ? '材料' :
fullDetail.expense_category === 'equipment' ? '设备' :
fullDetail.expense_category === 'pole' ? '电杆' : '其他'}
</Descriptions.Item>
<Descriptions.Item label="币种">{fullDetail.currency}</Descriptions.Item>
<Descriptions.Item label="申请日期">{fullDetail.request_date}</Descriptions.Item>
<Descriptions.Item label="事由" span={2}>{fullDetail.brief_description || '-'}</Descriptions.Item>
{fullDetail.remark && (
<Descriptions.Item label="备注" span={2}>{fullDetail.remark}</Descriptions.Item>
)}
</>
)}
</Descriptions>
{/* 采购申请供应商收款信息 */}
{selectedRecord.type === '采购申请' && fullDetail.supplier_payment_infos && fullDetail.supplier_payment_infos.length > 0 && (
<>
<Divider></Divider>
<Descriptions bordered column={2} size="small">
{fullDetail.supplier_payment_infos.filter((p: any) => p.is_primary).map((payment: any, index: number) => (
<React.Fragment key={index}>
<Descriptions.Item label="收款户名">{payment.account_name || '-'}</Descriptions.Item>
<Descriptions.Item label="银行账号">{payment.bank_account || '-'}</Descriptions.Item>
<Descriptions.Item label="开户银行">{payment.bank_name || '-'}</Descriptions.Item>
{payment.qr_code && (
<Descriptions.Item label="收款码">
<img src={payment.qr_code} alt="收款码" style={{ width: 100, height: 100, objectFit: 'contain' }} />
</Descriptions.Item>
)}
</React.Fragment>
))}
</Descriptions>
</>
)}
{/* 采购申请商品明细 */}
{selectedRecord.type === '采购申请' && fullDetail.items && fullDetail.items.length > 0 && (
<>
<Divider></Divider>
<List
size="small"
bordered
dataSource={fullDetail.items}
renderItem={(item: any, index: number) => (
<List.Item>
<div style={{ width: '100%' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 8 }}>
<span><strong>{index + 1}. {item.product_name}</strong></span>
<span style={{ fontWeight: 'bold', color: '#1890ff' }}>
{fullDetail.currency} {item.total_price?.toLocaleString('zh-CN', {minimumFractionDigits: 2})}
</span>
</div>
<div style={{ fontSize: 13, color: '#666' }}>
: {item.specification || '-'} | : {item.unit || '-'} |
: {item.quantity} | : {fullDetail.currency} {item.unit_price?.toLocaleString('zh-CN', {minimumFractionDigits: 2})}
</div>
</div>
</List.Item>
)}
/>
</>
)}
{/* 明细清单 */}
{fullDetail.detail_items && fullDetail.detail_items.length > 0 && (
@@ -0,0 +1,4 @@
{
"status": "failed",
"failedTests": []
}
@@ -80,11 +80,11 @@ export default defineConfig({
})
],
server: {
port: 3002,
port: 3006,
host: true,
proxy: {
'/api': {
target: 'http://localhost:3005',
target: 'http://localhost:3001',
changeOrigin: true
}
}