Files
huishou/deploy/server/server.js
T
2026-07-27 13:37:48 +08:00

450 lines
17 KiB
JavaScript

const express = require('express')
const cors = require('cors')
const bodyParser = require('body-parser')
const path = require('path')
const fs = require('fs')
const multer = require('multer')
const app = express()
const port = process.env.PORT || 4000
app.use(cors({
origin: '*',
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization']
}))
app.use(bodyParser.json())
app.use(bodyParser.urlencoded({ extended: true }))
app.use(express.static(path.join(__dirname, '../dist/build/h5')))
app.use('/admin', express.static(path.join(__dirname, '../admin')))
app.use('/wx', express.static(path.join(__dirname, '../dist/build/mp-weixin')))
const iconDir = path.join(__dirname, 'icons')
if (!fs.existsSync(iconDir)) {
fs.mkdirSync(iconDir, { recursive: true })
}
const storage = multer.diskStorage({
destination: function (req, file, cb) {
cb(null, iconDir)
},
filename: function (req, file, cb) {
const ext = path.extname(file.originalname)
const filename = 'icon_' + Date.now() + ext
cb(null, filename)
}
})
const upload = multer({
storage: storage,
limits: { fileSize: 5 * 1024 * 1024 },
fileFilter: function (req, file, cb) {
const allowedTypes = ['image/png', 'image/jpeg', 'image/jpg', 'image/gif']
if (allowedTypes.includes(file.mimetype)) {
cb(null, true)
} else {
cb(new Error('Only PNG, JPG, JPEG, GIF images are allowed'))
}
}
})
app.use('/api/icons', express.static(iconDir))
const dataDir = path.join(__dirname, 'data')
if (!fs.existsSync(dataDir)) {
fs.mkdirSync(dataDir, { recursive: true })
}
const pricesPath = path.join(dataDir, 'prices.json')
const ordersPath = path.join(dataDir, 'orders.json')
const usersPath = path.join(dataDir, 'users.json')
const citiesPath = path.join(dataDir, 'cities.json')
const districtsPath = path.join(dataDir, 'districts.json')
const stationsPath = path.join(dataDir, 'stations.json')
const siteUsersPath = path.join(dataDir, 'site_users.json')
const regionsPath = path.join(dataDir, 'regions.json')
const adminPath = path.join(dataDir, 'admin.json')
const loadData = (filePath, defaultData) => {
try {
if (fs.existsSync(filePath)) {
const content = fs.readFileSync(filePath, 'utf-8')
return JSON.parse(content)
}
} catch (err) {
console.error('Failed to load data:', err)
}
return defaultData
}
const saveData = (filePath, data) => {
try {
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8')
} catch (err) {
console.error('Failed to save data:', err)
}
}
const defaultPrices = [
{ category: '001', name: '纸类', unit: 'kg', price: 1.2, icon: '' },
{ category: '002', name: '金属', unit: 'kg', price: 2.5, icon: '' },
{ category: '003', name: '易拉罐', unit: 'piece', price: 0.1, icon: '' },
{ category: '004', name: '塑料', unit: 'kg', price: 3.0, icon: '' },
{ category: '005', name: '织物', unit: 'kg', price: 0.8, icon: '' },
{ category: '006', name: '塑料瓶', unit: 'piece', price: 0.05, icon: '' }
]
const defaultOrders = []
const defaultUsers = [
{ id: '1', name: 'Admin', phone: '13800138000', password: '123456', role: 'admin', createTime: '2024-01-01' },
{ id: '2', name: 'Collector Wang', phone: '18698102228', password: '123456', role: 'collector', createTime: '2024-01-15' }
]
const defaultCities = [
{ id: '1', name: 'Tianjin' },
{ id: '2', name: 'Beijing' },
{ id: '3', name: 'Shanghai' },
{ id: '4', name: 'Guangzhou' }
]
const defaultDistricts = [
{ id: 'd1', cityId: '1', name: 'Gaoxin' },
{ id: 'd2', cityId: '1', name: 'Xiqing' },
{ id: 'd3', cityId: '1', name: 'Baoshui' },
{ id: 'd4', cityId: '2', name: 'Dongcheng' },
{ id: 'd5', cityId: '2', name: 'Xicheng' },
{ id: 'd6', cityId: '2', name: 'Chaoyang' },
{ id: 'd7', cityId: '2', name: 'Haidian' },
{ id: 'd8', cityId: '3', name: 'Huangpu' },
{ id: 'd9', cityId: '3', name: 'Xuhui' },
{ id: 'd10', cityId: '3', name: 'Pudong' },
{ id: 'd11', cityId: '4', name: 'Tianhe' },
{ id: 'd12', cityId: '4', name: 'Haizhu' },
{ id: 'd13', cityId: '4', name: 'Yuexiu' }
]
const defaultStations = [
{ id: 's1', cityId: '1', districtId: 'd1', name: 'Ronghui Jingyuan' },
{ id: 's2', cityId: '1', districtId: 'd1', name: 'Jinhui Jiangshan Mingzhu' },
{ id: 's3', cityId: '1', districtId: 'd2', name: 'Huaxing Li' },
{ id: 's4', cityId: '1', districtId: 'd2', name: 'Huaxing Nan Li' },
{ id: 's5', cityId: '1', districtId: 'd3', name: 'Yijing Lanting' }
]
const defaultSiteUsers = [
{ id: 'su1', stationId: 's1', name: 'Xiwei Laoshan', type: 'merchant' },
{ id: 'su2', stationId: 's1', name: 'New Merchant', type: 'merchant' },
{ id: 'su3', stationId: 's1', name: 'New Individual', type: 'individual' },
{ id: 'su4', stationId: 's2', name: 'New Merchant', type: 'merchant' },
{ id: 'su5', stationId: 's2', name: 'New Individual', type: 'individual' },
{ id: 'su6', stationId: 's3', name: 'New Merchant', type: 'merchant' },
{ id: 'su7', stationId: 's3', name: 'New Individual', type: 'individual' },
{ id: 'su8', stationId: 's4', name: 'New Merchant', type: 'merchant' },
{ id: 'su9', stationId: 's4', name: 'New Individual', type: 'individual' },
{ id: 'su10', stationId: 's5', name: 'New Merchant', type: 'merchant' },
{ id: 'su11', stationId: 's5', name: 'New Individual', type: 'individual' }
]
const defaultRegions = []
const defaultAdmin = [
{ id: 1, username: 'admin', password: '123456' }
]
let prices = loadData(pricesPath, defaultPrices)
let orders = loadData(ordersPath, defaultOrders)
let users = loadData(usersPath, defaultUsers)
let cities = loadData(citiesPath, defaultCities)
let districts = loadData(districtsPath, defaultDistricts)
let stations = loadData(stationsPath, defaultStations)
let siteUsers = loadData(siteUsersPath, defaultSiteUsers)
let regions = loadData(regionsPath, defaultRegions)
let admin = loadData(adminPath, defaultAdmin)
console.log('Database initialized')
console.log('Prices:', JSON.stringify(prices, null, 2))
app.get('/', (req, res) => {
res.sendFile(path.join(__dirname, '../dist/build/h5/index.html'))
})
app.get('/api', (req, res) => {
res.send(`
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Yi Fen Bao - Backend Service</title>
<style>
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 1000px; margin: 0 auto; padding: 40px 20px; background: #f5f7fa; }
.header { background: linear-gradient(135deg, #3366ff 0%, #254edb 100%); color: white; padding: 40px; border-radius: 16rpx; margin-bottom: 30px; }
.header h1 { margin: 0; font-size: 28px; }
.header p { margin: 10px 0 0; opacity: 0.9; }
.card { background: white; border-radius: 12rpx; padding: 30px; margin-bottom: 20px; box-shadow: 0 2rpx 12rpx rgba(0,0,0,0.05); }
.card h2 { margin: 0 0 20px; font-size: 20px; color: #333; }
.endpoint { display: flex; align-items: center; padding: 12px 0; border-bottom: 1rpx solid #f0f0f0; }
.endpoint:last-child { border-bottom: none; }
.method { padding: 4px 12px; border-radius: 4rpx; font-size: 12px; font-weight: 600; margin-right: 16px; }
.method.get { background: #e6f7ff; color: #1890ff; }
.method.post { background: #f6ffed; color: #52c41a; }
.method.put { background: #fff7e6; color: #fa8c16; }
.method.delete { background: #fff2f0; color: #ff4d4f; }
.path { color: #333; font-family: monospace; }
</style>
</head>
<body>
<div class="header">
<h1>Yi Fen Bao</h1>
<p>Backend Service Running</p>
</div>
<div class="card">
<h2>Frontend Applications</h2>
<div class="endpoint"><span class="method get">GET</span><span class="path">/</span>H5 App</div>
<div class="endpoint"><span class="method get">GET</span><span class="path">/admin</span>Admin Panel</div>
<div class="endpoint"><span class="method get">GET</span><span class="path">/wx</span>WeChat Mini Program</div>
</div>
<div class="card">
<h2>API Endpoints</h2>
<div class="endpoint"><span class="method get">GET</span><span class="path">/api/prices</span>Get price list</div>
<div class="endpoint"><span class="method post">POST</span><span class="path">/api/prices</span>Add price</div>
<div class="endpoint"><span class="method put">PUT</span><span class="path">/api/prices/:category</span>Update price</div>
<div class="endpoint"><span class="method delete">DELETE</span><span class="path">/api/prices/:category</span>Delete price</div>
<div class="endpoint"><span class="method get">GET</span><span class="path">/api/orders</span>Get orders</div>
<div class="endpoint"><span class="method post">POST</span><span class="path">/api/orders</span>Create order</div>
<div class="endpoint"><span class="method put">PUT</span><span class="path">/api/orders/:id</span>Update order</div>
<div class="endpoint"><span class="method delete">DELETE</span><span class="path">/api/orders/:id</span>Delete order</div>
<div class="endpoint"><span class="method get">GET</span><span class="path">/api/users</span>Get users</div>
<div class="endpoint"><span class="method post">POST</span><span class="path">/api/users/login</span>User login</div>
<div class="endpoint"><span class="method post">POST</span><span class="path">/api/users</span>Register user</div>
<div class="endpoint"><span class="method post">POST</span><span class="path">/api/admin/login</span>Admin login</div>
<div class="endpoint"><span class="method get">GET</span><span class="path">/api/cities</span>Get cities</div>
<div class="endpoint"><span class="method get">GET</span><span class="path">/api/districts/:cityId</span>Get districts</div>
<div class="endpoint"><span class="method get">GET</span><span class="path">/api/stations/:cityId/:districtId</span>Get stations</div>
<div class="endpoint"><span class="method get">GET</span><span class="path">/api/site_users/:stationId</span>Get site users</div>
</div>
</body>
</html>
`)
})
app.get('/api/prices', (req, res) => {
res.json({
date: new Date().toISOString().split('T')[0],
prices: prices
})
})
app.post('/api/prices', upload.single('icon'), (req, res) => {
const { category, name, unit, price } = req.body
let icon = ''
if (req.file) {
icon = '/api/icons/' + req.file.filename
}
const existing = prices.find(p => p.category === category)
if (existing) {
res.json({ success: false, message: 'Already exists' })
return
}
prices.push({ category, name, unit, price: parseFloat(price), icon })
saveData(pricesPath, prices)
res.json({ success: true, message: 'Added' })
})
app.put('/api/prices/:category', upload.single('icon'), (req, res) => {
const category = req.params.category
const { name, unit, price, icon } = req.body
const index = prices.findIndex(p => p.category === category)
if (index === -1) {
res.json({ success: false, message: 'Not found' })
return
}
let newIcon = icon || ''
if (req.file) {
newIcon = '/api/icons/' + req.file.filename
}
prices[index] = { ...prices[index], name, unit, price: parseFloat(price), icon: newIcon }
saveData(pricesPath, prices)
res.json({ success: true, message: 'Updated' })
})
app.delete('/api/prices/:category', (req, res) => {
const category = req.params.category
const index = prices.findIndex(p => p.category === category)
if (index === -1) {
res.json({ success: false, message: 'Not found' })
return
}
prices.splice(index, 1)
saveData(pricesPath, prices)
res.json({ success: true, message: 'Deleted' })
})
app.get('/api/orders', (req, res) => {
const processedOrders = orders.map(order => {
const items = order.items.map(item => {
const weight = item.weight || item.count || 0
const price = item.price || 0
return {
...item,
amount: item.amount !== undefined ? item.amount : weight * price
}
})
const totalAmount = order.totalAmount !== undefined
? order.totalAmount
: items.reduce((sum, item) => sum + (item.amount || 0), 0)
return {
...order,
items,
totalAmount
}
})
res.json({ data: processedOrders.sort((a, b) => new Date(b.createTime) - new Date(a.createTime)) })
})
app.get('/api/orders/:id', (req, res) => {
const order = orders.find(o => o.id === req.params.id)
res.json(order || {})
})
app.post('/api/orders', (req, res) => {
const { items, totalWeight, totalPrice, city, district, community, user, phone, address } = req.body
const id = Date.now().toString() + Math.random().toString(36).substr(2, 9)
const createTime = new Date().toISOString()
const processedItems = items.map(item => {
const weight = item.weight || item.count || 0
const price = item.price || 0
return {
...item,
amount: weight * price
}
})
const totalAmount = processedItems.reduce((sum, item) => sum + (item.amount || 0), 0)
const newOrder = { id, items: processedItems, totalAmount, totalWeight, totalPrice, city, district, community, user, phone, address, status: 'pending', createTime }
orders.push(newOrder)
saveData(ordersPath, orders)
res.json({ success: true, order: newOrder })
})
app.put('/api/orders/:id', (req, res) => {
const id = req.params.id
const index = orders.findIndex(o => o.id === id)
if (index === -1) {
res.json({ success: false, message: 'Not found' })
return
}
const { status, items, totalWeight, totalPrice } = req.body
if (status !== undefined) orders[index].status = status
if (items !== undefined) orders[index].items = items
if (totalWeight !== undefined) orders[index].totalWeight = totalWeight
if (totalPrice !== undefined) orders[index].totalPrice = totalPrice
saveData(ordersPath, orders)
res.json({ success: true, message: 'Updated' })
})
app.delete('/api/orders/:id', (req, res) => {
const id = req.params.id
const index = orders.findIndex(o => o.id === id)
if (index === -1) {
res.json({ success: false, message: 'Not found' })
return
}
orders.splice(index, 1)
saveData(ordersPath, orders)
res.json({ success: true, message: 'Deleted' })
})
app.get('/api/users', (req, res) => {
res.json(users.map(u => ({ id: u.id, name: u.name, phone: u.phone, role: u.role, createTime: u.createTime })))
})
app.post('/api/users/login', (req, res) => {
const { phone, password } = req.body
const user = users.find(u => u.phone === phone && u.password === password)
if (user) {
res.json({ success: true, data: { id: user.id, name: user.name, phone: user.phone, role: user.role } })
} else {
res.json({ success: false, message: 'Wrong credentials' })
}
})
app.post('/api/users', (req, res) => {
const { name, phone, password, role = 'collector' } = req.body
const existing = users.find(u => u.phone === phone)
if (existing) {
res.json({ success: false, message: 'Phone exists' })
return
}
const id = Date.now().toString()
const createTime = new Date().toISOString().split('T')[0]
const newUser = { id, name, phone, password, role, createTime }
users.push(newUser)
saveData(usersPath, users)
res.json({ success: true, data: newUser })
})
app.post('/api/admin/login', (req, res) => {
const { username, password } = req.body
const adminUser = admin.find(a => a.username === username && a.password === password)
if (adminUser) {
res.json({ success: true, data: { username: adminUser.username } })
} else {
res.json({ success: false, message: 'Wrong credentials' })
}
})
app.get('/api/cities', (req, res) => {
res.json(cities)
})
app.get('/api/districts/:cityId', (req, res) => {
res.json(districts.filter(d => d.cityId === req.params.cityId))
})
app.get('/api/stations/:cityId/:districtId', (req, res) => {
res.json(stations.filter(s => s.cityId === req.params.cityId && s.districtId === req.params.districtId))
})
app.get('/api/site_users/:stationId', (req, res) => {
res.json(siteUsers.filter(u => u.stationId === req.params.stationId))
})
app.get('/api/regions', (req, res) => {
res.json(regions)
})
app.post('/api/regions', (req, res) => {
const { city, district, community } = req.body
const existing = regions.find(r => r.city === city && r.district === district && r.community === community)
if (existing) {
res.json({ success: true, message: 'Exists' })
return
}
regions.push({ id: regions.length + 1, city, district, community })
saveData(regionsPath, regions)
res.json({ success: true, message: 'Added' })
})
app.delete('/api/regions/:id', (req, res) => {
const id = parseInt(req.params.id)
const index = regions.findIndex(r => r.id === id)
if (index === -1) {
res.json({ success: false, message: 'Not found' })
return
}
regions.splice(index, 1)
saveData(regionsPath, regions)
res.json({ success: true, message: 'Deleted' })
})
app.listen(port, () => {
console.log(`Server running on http://localhost:${port}`)
})