Skip to content
Open

Crm3 #202

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
239 changes: 239 additions & 0 deletions electron/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,239 @@
const { app, BrowserWindow, ipcMain } = require('electron');
const path = require('path');

function createWindow() {
const mainWindow = new BrowserWindow({
width: 1200,
height: 800,
webPreferences: {
preload: path.join(__dirname, 'preload.js'),
contextIsolation: true,
nodeIntegration: false,
},
});

if (process.env.NODE_ENV === 'development') {
mainWindow.loadURL('http://localhost:3000');
mainWindow.webContents.openDevTools();
} else {
mainWindow.loadFile(path.join(__dirname, '../dist/index.html'));
}
}

app.whenReady().then(() => {
createWindow();

app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});

app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit();
}
});

const { readFileSync, writeFileSync, existsSync } = require('fs');

const DATA_DIR = path.join(app.getPath('userData'), 'data');
const USERS_FILE = path.join(DATA_DIR, 'users.json');
const CUSTOMERS_FILE = path.join(DATA_DIR, 'customers.json');
const FOLLOWUP_RECORDS_FILE = path.join(DATA_DIR, 'followup_records.json');

function ensureDataDirectory() {
const fs = require('fs');
if (!existsSync(DATA_DIR)) {
fs.mkdirSync(DATA_DIR, { recursive: true });
}
if (!existsSync(USERS_FILE)) {
const initialUsers = [
{
id: 1,
username: 'admin',
password: 'admin123',
name: '系统管理员',
role: 'admin',
createdAt: new Date().toISOString(),
},
{
id: 2,
username: 'sales1',
password: 'sales123',
name: '张三',
role: 'sales',
createdAt: new Date().toISOString(),
},
{
id: 3,
username: 'sales2',
password: 'sales123',
name: '李四',
role: 'sales',
createdAt: new Date().toISOString(),
},
];
writeFileSync(USERS_FILE, JSON.stringify(initialUsers, null, 2));
}
if (!existsSync(CUSTOMERS_FILE)) {
writeFileSync(CUSTOMERS_FILE, JSON.stringify([], null, 2));
}
if (!existsSync(FOLLOWUP_RECORDS_FILE)) {
writeFileSync(FOLLOWUP_RECORDS_FILE, JSON.stringify([], null, 2));
}
}

function readJSONFile(filePath) {
try {
const data = readFileSync(filePath, 'utf-8');
return JSON.parse(data);
} catch (error) {
return [];
}
}

function writeJSONFile(filePath, data) {
writeFileSync(filePath, JSON.stringify(data, null, 2));
}

ipcMain.handle('login', (event, { username, password }) => {
ensureDataDirectory();
const users = readJSONFile(USERS_FILE);
const user = users.find(u => u.username === username && u.password === password);
if (user) {
return { success: true, user: { ...user, password: undefined } };
}
return { success: false, message: '用户名或密码错误' };
});

ipcMain.handle('getUsers', () => {
ensureDataDirectory();
const users = readJSONFile(USERS_FILE);
return users.map(u => ({ ...u, password: undefined }));
});

ipcMain.handle('getCustomers', (event, { salesId } = {}) => {
ensureDataDirectory();
let customers = readJSONFile(CUSTOMERS_FILE);
if (salesId) {
customers = customers.filter(c => c.salesId === salesId);
}
return customers;
});

ipcMain.handle('createCustomer', (event, customer) => {
ensureDataDirectory();
const customers = readJSONFile(CUSTOMERS_FILE);
const newCustomer = {
...customer,
id: customers.length > 0 ? Math.max(...customers.map(c => c.id)) + 1 : 1,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
};
customers.push(newCustomer);
writeJSONFile(CUSTOMERS_FILE, customers);
return newCustomer;
});

ipcMain.handle('updateCustomer', (event, { id, updates }) => {
ensureDataDirectory();
const customers = readJSONFile(CUSTOMERS_FILE);
const index = customers.findIndex(c => c.id === id);
if (index === -1) {
return { success: false, message: '客户不存在' };
}
customers[index] = {
...customers[index],
...updates,
updatedAt: new Date().toISOString(),
};
writeJSONFile(CUSTOMERS_FILE, customers);
return { success: true, customer: customers[index] };
});

ipcMain.handle('deleteCustomer', (event, { id }) => {
ensureDataDirectory();
let customers = readJSONFile(CUSTOMERS_FILE);
const index = customers.findIndex(c => c.id === id);
if (index === -1) {
return { success: false, message: '客户不存在' };
}
customers = customers.filter(c => c.id !== id);
writeJSONFile(CUSTOMERS_FILE, customers);
return { success: true };
});

ipcMain.handle('getFollowupRecords', (event, { customerId } = {}) => {
ensureDataDirectory();
let records = readJSONFile(FOLLOWUP_RECORDS_FILE);
if (customerId) {
records = records.filter(r => r.customerId === customerId);
}
return records.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
});

ipcMain.handle('createFollowupRecord', (event, record) => {
ensureDataDirectory();
const records = readJSONFile(FOLLOWUP_RECORDS_FILE);
const newRecord = {
...record,
id: records.length > 0 ? Math.max(...records.map(r => r.id)) + 1 : 1,
createdAt: new Date().toISOString(),
};
records.push(newRecord);
writeJSONFile(FOLLOWUP_RECORDS_FILE, records);
return newRecord;
});

ipcMain.handle('getStatistics', () => {
ensureDataDirectory();
const customers = readJSONFile(CUSTOMERS_FILE);
const users = readJSONFile(USERS_FILE).filter(u => u.role === 'sales');

const totalCustomers = customers.length;
const totalPotential = customers.filter(c => c.status === 'potential').length;
const totalFollowing = customers.filter(c => c.status === 'following').length;
const totalClosed = customers.filter(c => c.status === 'closed').length;
const totalLost = customers.filter(c => c.status === 'lost').length;

const conversionRate = totalCustomers > 0
? (totalClosed / totalCustomers * 100).toFixed(2) + '%'
: '0%';

const totalRevenue = customers
.filter(c => c.status === 'closed' && c.dealAmount)
.reduce((sum, c) => sum + c.dealAmount, 0);

const salesPerformance = users.map(sales => {
const salesCustomers = customers.filter(c => c.salesId === sales.id);
const closedCustomers = salesCustomers.filter(c => c.status === 'closed');
const salesRevenue = closedCustomers
.filter(c => c.dealAmount)
.reduce((sum, c) => sum + c.dealAmount, 0);
const salesConversionRate = salesCustomers.length > 0
? (closedCustomers.length / salesCustomers.length * 100).toFixed(2) + '%'
: '0%';

return {
salesId: sales.id,
salesName: sales.name,
totalCustomers: salesCustomers.length,
closedCustomers: closedCustomers.length,
conversionRate: salesConversionRate,
revenue: salesRevenue,
};
});

return {
totalCustomers,
totalPotential,
totalFollowing,
totalClosed,
totalLost,
conversionRate,
totalRevenue,
salesPerformance,
};
});
28 changes: 28 additions & 0 deletions electron/preload.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const { contextBridge, ipcRenderer } = require('electron');

contextBridge.exposeInMainWorld('electronAPI', {
login: (username, password) =>
ipcRenderer.invoke('login', { username, password }),

getUsers: () => ipcRenderer.invoke('getUsers'),

getCustomers: (salesId) =>
ipcRenderer.invoke('getCustomers', { salesId }),

createCustomer: (customer) =>
ipcRenderer.invoke('createCustomer', customer),

updateCustomer: (id, updates) =>
ipcRenderer.invoke('updateCustomer', { id, updates }),

deleteCustomer: (id) =>
ipcRenderer.invoke('deleteCustomer', { id }),

getFollowupRecords: (customerId) =>
ipcRenderer.invoke('getFollowupRecords', { customerId }),

createFollowupRecord: (record) =>
ipcRenderer.invoke('createFollowupRecord', record),

getStatistics: () => ipcRenderer.invoke('getStatistics'),
});
12 changes: 12 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>CRM客户管理系统</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>
Loading