- 生产 DB: 中电建 0 8 * * 1-5 → 0 8 * * * (每天早8点,含周末) - 生产 DB: 新矿 5 8 * * 1-5 → 5 8 * * * (每天早8:05,含周末) - 生产 DB: 重激活新矿采集器(paused→active) - seed.ts: 同步修正, 避免未来 db:init 再次写入 1-5 - PM2 已重启使新 cron 生效 说明: 1-5 = 周一至周五, 去掉后变为每天执行。 用户可在前端「采集器配置」下拉框自由切换: 每4小时 / 每6小时 / 每天早8点/晚7点 / 手动
297 lines
18 KiB
TypeScript
297 lines
18 KiB
TypeScript
import { PrismaClient } from '@prisma/client'
|
||
import * as bcrypt from 'bcrypt'
|
||
const prisma = new PrismaClient()
|
||
|
||
async function main() {
|
||
const hashed = await bcrypt.hash('admin123', 10)
|
||
|
||
// ---- 租户 ----
|
||
const demoCompany = await prisma.tenant.upsert({
|
||
where: { id: 'demo-tenant-001' },
|
||
update: {},
|
||
create: {
|
||
id: 'demo-tenant-001',
|
||
companyName: '示例科技公司', contactName: '张三', contactEmail: 'contact@example.com',
|
||
plan: 'enterprise', status: 'active',
|
||
validFrom: new Date('2026-01-01'), validUntil: new Date('2027-01-01'),
|
||
platformType: 'jubian', platformUrl: 'https://jubian.example.com',
|
||
features: JSON.stringify({ inquiry: true, imageGen: true, product: true, comparison: true }),
|
||
usageQuotas: JSON.stringify({ monthlyLeads: 10000, monthlyImages: 500, monthlyProducts: 5000, monthlyComparisons: 200, maxUsers: 50 }),
|
||
},
|
||
})
|
||
|
||
const farEast = await prisma.tenant.upsert({
|
||
where: { id: 'demo-tenant-002' },
|
||
update: {},
|
||
create: {
|
||
id: 'demo-tenant-002',
|
||
companyName: '远东电缆有限公司', contactName: '李四', contactEmail: 'li@fareast-cable.com',
|
||
plan: 'pro', status: 'active',
|
||
validFrom: new Date('2026-03-01'), validUntil: new Date('2026-05-31'),
|
||
platformType: 'custom', platformUrl: 'https://erp.fareast-cable.com',
|
||
features: JSON.stringify({ inquiry: true, imageGen: false, product: true, comparison: true }),
|
||
usageQuotas: JSON.stringify({ monthlyLeads: 5000, monthlyImages: 0, monthlyProducts: 2000, monthlyComparisons: 100, maxUsers: 20 }),
|
||
},
|
||
})
|
||
|
||
const shangshang = await prisma.tenant.upsert({
|
||
where: { id: 'demo-tenant-003' },
|
||
update: {},
|
||
create: {
|
||
id: 'demo-tenant-003',
|
||
companyName: '上上电缆集团', contactName: '王五', contactEmail: 'wang@shangshang.com',
|
||
plan: 'basic', status: 'active',
|
||
validFrom: new Date('2026-04-01'), validUntil: new Date('2027-04-01'),
|
||
features: JSON.stringify({ inquiry: true, imageGen: false, product: true, comparison: false }),
|
||
usageQuotas: JSON.stringify({ monthlyLeads: 1000, monthlyImages: 0, monthlyProducts: 500, monthlyComparisons: 0, maxUsers: 5 }),
|
||
},
|
||
})
|
||
|
||
const minxing = await prisma.tenant.upsert({
|
||
where: { id: 'demo-tenant-004' },
|
||
update: {},
|
||
create: {
|
||
id: 'demo-tenant-004',
|
||
companyName: '民兴电缆科技', contactName: '赵六', contactEmail: 'zhao@minxing.com',
|
||
plan: 'basic', status: 'expired',
|
||
validFrom: new Date('2025-01-01'), validUntil: new Date('2026-01-01'),
|
||
features: JSON.stringify({ inquiry: true, imageGen: false, product: false, comparison: false }),
|
||
usageQuotas: JSON.stringify({ monthlyLeads: 500, monthlyImages: 0, monthlyProducts: 0, monthlyComparisons: 0, maxUsers: 3 }),
|
||
},
|
||
})
|
||
|
||
// ---- 用户 ----
|
||
await prisma.user.upsert({
|
||
where: { email: 'admin@platform.com' },
|
||
update: {},
|
||
create: { email: 'admin@platform.com', username: 'admin', password: hashed, name: '管理员', role: 'admin', tenantId: demoCompany.id },
|
||
})
|
||
await prisma.user.upsert({
|
||
where: { email: 'user@example.com' },
|
||
update: {},
|
||
create: { email: 'user@example.com', username: 'user', password: hashed, name: '测试用户', role: 'user', tenantId: demoCompany.id },
|
||
})
|
||
await prisma.user.upsert({
|
||
where: { email: 'li@fareast-cable.com' },
|
||
update: {},
|
||
create: { email: 'li@fareast-cable.com', username: 'li.fareast', password: hashed, name: '李四', role: 'user', tenantId: farEast.id },
|
||
})
|
||
await prisma.user.upsert({
|
||
where: { email: 'wang@shangshang.com' },
|
||
update: {},
|
||
create: { email: 'wang@shangshang.com', username: 'wang', password: hashed, name: '王五', role: 'user', tenantId: shangshang.id },
|
||
})
|
||
await prisma.user.upsert({
|
||
where: { email: 'zhao@minxing.com' },
|
||
update: {},
|
||
create: { email: 'zhao@minxing.com', username: 'zhao', password: hashed, name: '赵六', role: 'user', tenantId: minxing.id },
|
||
})
|
||
await prisma.rule.createMany({
|
||
data: [
|
||
{ name: '综合评分权重', type: 'scoring', config: JSON.stringify({ weights: [{ field: '预估金额', weight: 30 }, { field: '匹配度', weight: 25 }, { field: '甲方级别', weight: 20 }, { field: '时效性', weight: 15 }, { field: '历史成交', weight: 10 }] }), enabled: true },
|
||
{ name: '防水材料关键词库', type: 'keyword', config: JSON.stringify({ keywords: ['防水卷材', '防水涂料', '聚氨酯', 'SBS改性沥青', '高分子防水'], category: '防水材料', priority: 8 }), enabled: true },
|
||
{ name: '电力设备关键词库', type: 'keyword', config: JSON.stringify({ keywords: ['变压器', '断路器', '电缆', '开关柜', 'GIS'], category: '电力设备', priority: 9 }), enabled: true },
|
||
{ name: '钢材关键词库', type: 'keyword', config: JSON.stringify({ keywords: ['钢板', '钢管', '型钢', '螺纹钢', 'H型钢'], category: '钢材', priority: 7 }), enabled: true },
|
||
{ name: '甲方分级标准', type: 'client_level', config: JSON.stringify({ levels: [{ name: 'S级', minAmount: 1000000, minCooperations: 3 }, { name: 'A级', minAmount: 500000, minCooperations: 1 }, { name: 'B级', minAmount: 100000 }, { name: 'C级', minAmount: 10000 }, { name: 'D级' }] }), enabled: true },
|
||
],
|
||
})
|
||
|
||
await prisma.report.createMany({
|
||
data: [
|
||
{ title: '5月第3周线索数据报告', type: '线索', status: '已完成', config: JSON.stringify({ dateRange: '2024-05-13~2024-05-19', sources: ['中国采购与招标网', '中国电建阳光采购网'], categories: ['电力设备', '钢材', 'IT设备'] }), channels: JSON.stringify(['飞书', '邮箱']) },
|
||
{ title: '5月询价发布统计', type: '询价', status: '已完成', config: JSON.stringify({ dateRange: '2024-05-01~2024-05-20' }) },
|
||
{ title: '本周商品上架报告', type: '商品', status: '生成中', config: JSON.stringify({ dateRange: '2024-05-20~2024-05-22' }) },
|
||
],
|
||
})
|
||
|
||
await prisma.auditLog.createMany({
|
||
data: [
|
||
{ action: '登录', detail: '用户 admin 登录系统', operator: 'admin', ip: '192.168.1.100' },
|
||
{ action: '修改规则', detail: '修改了评分权重配置', operator: 'admin', ip: '192.168.1.100' },
|
||
{ action: '导出报告', detail: '导出了5月第3周线索数据报告', operator: 'admin', ip: '192.168.1.100' },
|
||
{ action: '删除线索', detail: '删除了2条无效线索', operator: 'admin', ip: '192.168.1.100' },
|
||
{ action: '批量上架', detail: '批量上架了5个商品', operator: 'admin', ip: '192.168.1.100' },
|
||
{ action: '修改规则', detail: '新增了钢材品类关键词', operator: 'admin', ip: '192.168.1.100' },
|
||
{ action: '导出报告', detail: '导出了5月询价发布统计', operator: 'admin', ip: '192.168.1.100' },
|
||
{ action: '登录', detail: '用户 admin 登录系统', operator: 'admin', ip: '192.168.1.100' },
|
||
],
|
||
})
|
||
|
||
await prisma.crawlerConfig.deleteMany()
|
||
await prisma.crawlerConfig.createMany({
|
||
data: [
|
||
{ name: '中国电建阳光采购网', targetUrl: 'https://bid.powerchina.cn', type: 'bid_powerchina', sourceName: '中电建', status: 'active', schedule: '0 8 * * *', totalRuns: 286, successRuns: 275, failedRuns: 11, keywords: '["电缆","防火门","配电箱","钢材"]' },
|
||
{ name: '新矿集团供应链平台', targetUrl: 'https://xkgroup.kdcloud.com', type: 'bid_xkgroup', sourceName: '新矿集团', scriptPath: 'fetch-xkgroup.py', status: 'active', schedule: '5 8 * * *', totalRuns: 142, successRuns: 138, failedRuns: 4, keywords: '["电缆","钢材","阀门","管材"]' },
|
||
{ name: '中核集团电子采购平台', targetUrl: 'https://www.cnncecp.com', type: 'bid_cnncecp', sourceName: '中核集团', scriptPath: 'fetch-cnncecp.py', status: 'paused', totalRuns: 0, successRuns: 0, failedRuns: 0, keywords: '["电缆","防火门","钢材","阀门"]' },
|
||
],
|
||
})
|
||
|
||
await prisma.inquiry.createMany({
|
||
data: [
|
||
{ title: '2024年电力电缆框架采购项目', source: '中国电建阳光采购网', url: 'https://example.com/inquiry/001', budget: '500万元', deadline: '2024-12-31', buyer: '中国电力建设集团', category: '电力设备', status: 'pending', level: 'S', star: 5, region: '北京' },
|
||
{ title: '钢结构桥梁用钢板采购招标', source: '中国中铁采购电子商务平台', url: 'https://example.com/inquiry/002', budget: '1200万元', deadline: '2024-11-15', buyer: '中铁大桥局', category: '钢材', status: 'published', level: 'A', star: 4, region: '湖北武汉' },
|
||
{ title: '数据中心服务器及网络设备采购', source: '中国移动采购与招标网', url: 'https://example.com/inquiry/003', budget: '800万元', deadline: '2024-10-20', buyer: '中国移动通信集团', category: 'IT设备', status: 'pending', level: 'S', star: 5, region: '北京' },
|
||
],
|
||
})
|
||
|
||
const cableSKUs = [
|
||
{ name: 'YJV-0.6/1kV 3×4 电力电缆', spec: '0.6/1kV 3×4mm²', unit: '米', category: '电力电缆' },
|
||
{ name: 'YJV-0.6/1kV 3×6 电力电缆', spec: '0.6/1kV 3×6mm²', unit: '米', category: '电力电缆' },
|
||
{ name: 'YJV-0.6/1kV 3×10 电力电缆', spec: '0.6/1kV 3×10mm²', unit: '米', category: '电力电缆' },
|
||
{ name: 'YJV22-8.7/15kV 3×70 电力电缆', spec: '8.7/15kV 3×70mm²', unit: '米', category: '电力电缆' },
|
||
{ name: 'YJV22-8.7/15kV 3×95 电力电缆', spec: '8.7/15kV 3×95mm²', unit: '米', category: '电力电缆' },
|
||
{ name: 'YJV22-8.7/15kV 3×120 电力电缆', spec: '8.7/15kV 3×120mm²', unit: '米', category: '电力电缆' },
|
||
{ name: 'YJV22-8.7/15kV 3×150 电力电缆', spec: '8.7/15kV 3×150mm²', unit: '米', category: '电力电缆' },
|
||
{ name: 'YJV22-8.7/15kV 3×185 电力电缆', spec: '8.7/15kV 3×185mm²', unit: '米', category: '电力电缆' },
|
||
{ name: 'YJV22-8.7/15kV 3×240 电力电缆', spec: '8.7/15kV 3×240mm²', unit: '米', category: '电力电缆' },
|
||
{ name: 'YJV22-8.7/15kV 3×300 电力电缆', spec: '8.7/15kV 3×300mm²', unit: '米', category: '电力电缆' },
|
||
{ name: 'BV-450/750V 2.5mm² 家装电线', spec: '450/750V 2.5mm²', unit: '米', category: '家装电线' },
|
||
{ name: 'BV-450/750V 4mm² 家装电线', spec: '450/750V 4mm²', unit: '米', category: '家装电线' },
|
||
{ name: 'BV-450/750V 6mm² 家装电线', spec: '450/750V 6mm²', unit: '米', category: '家装电线' },
|
||
{ name: 'KVVP-0.45/0.75kV 14×1.5 控制电缆', spec: '0.45/0.75kV 14×1.5mm²', unit: '米', category: '控制电缆' },
|
||
{ name: 'RVVP-300/300V 4×0.75 屏蔽软电缆', spec: '300/300V 4×0.75mm²', unit: '米', category: '控制电缆' },
|
||
]
|
||
|
||
const suppliers = [
|
||
{ name: '远东电缆', brand: '远东电缆' },
|
||
{ name: '上上电缆', brand: '上上电缆' },
|
||
{ name: '民兴电缆', brand: '民兴电缆' },
|
||
{ name: '金杯电缆', brand: '金杯电缆' },
|
||
{ name: '金龙羽', brand: '金龙羽' },
|
||
]
|
||
|
||
// 价格矩阵:每行是一个商品,每列是对应供应商的价格
|
||
// 设计逻辑:每个供应商在不同商品上有价格优势,使得 2/3/4 家组合的最优方案不同
|
||
// 远东: 商品0-2最便宜 | 上上: 商品3-5最便宜 | 民兴: 商品6-8最便宜 | 金杯: 商品9-11最便宜 | 金龙羽: 商品12-14最便宜
|
||
const priceMatrix: number[][] = [
|
||
// SKU 远东 上上 民兴 金杯 金龙羽
|
||
/* YJV 3×4 电力电缆 */ [12.00, 12.80, 13.50, 13.20, 14.00],
|
||
/* YJV 3×6 电力电缆 */ [18.00, 19.20, 20.10, 19.50, 21.00],
|
||
/* YJV 3×10 电力电缆 */ [27.50, 29.00, 30.50, 29.80, 32.00],
|
||
/* YJV22 3×70 电力电缆 */ [190.00, 182.00, 195.00, 188.00, 200.00],
|
||
/* YJV22 3×95 电力电缆 */ [250.00, 238.00, 260.00, 248.00, 265.00],
|
||
/* YJV22 3×120 电力电缆 */[320.00, 305.00, 330.00, 315.00, 340.00],
|
||
/* YJV22 3×150 电力电缆 */[410.00, 395.00, 385.00, 400.00, 420.00],
|
||
/* YJV22 3×185 电力电缆 */[500.00, 480.00, 465.00, 475.00, 510.00],
|
||
/* YJV22 3×240 电力电缆 */[650.00, 630.00, 615.00, 625.00, 670.00],
|
||
/* YJV22 3×300 电力电缆 */[810.00, 790.00, 780.00, 770.00, 830.00],
|
||
/* BV 2.5mm² 家装电线 */ [1.85, 1.80, 1.75, 1.68, 1.90],
|
||
/* BV 4mm² 家装电线 */ [2.70, 2.60, 2.55, 2.45, 2.80],
|
||
/* BV 6mm² 家装电线 */ [4.30, 4.15, 4.10, 3.95, 4.50],
|
||
/* KVVP 控制电缆 */ [40.00, 38.50, 37.00, 36.00, 42.00],
|
||
/* RVVP 屏蔽软电缆 */ [23.00, 22.00, 21.50, 21.00, 24.00],
|
||
]
|
||
|
||
// 每个商品的数量
|
||
const quantities = [3000, 2500, 2000, 1500, 1200, 1000, 800, 600, 500, 400, 5000, 4000, 3000, 800, 600]
|
||
|
||
const productData: any[] = []
|
||
for (let si = 0; si < suppliers.length; si++) {
|
||
const s = suppliers[si]
|
||
for (let pi = 0; pi < cableSKUs.length; pi++) {
|
||
productData.push({
|
||
name: cableSKUs[pi].name,
|
||
spec: cableSKUs[pi].spec,
|
||
unit: cableSKUs[pi].unit,
|
||
category: cableSKUs[pi].category,
|
||
price: priceMatrix[pi][si],
|
||
quantity: quantities[pi],
|
||
status: 'draft',
|
||
brand: s.brand,
|
||
supplier: s.name,
|
||
minOrder: 100,
|
||
})
|
||
}
|
||
}
|
||
|
||
await prisma.product.deleteMany()
|
||
await prisma.product.createMany({ data: productData })
|
||
|
||
// Default system configs
|
||
const defaultConfigs = [
|
||
{ key: 'llm_provider', value: 'minimax', group: 'ai', description: '大模型供应商(minimax / openai)' },
|
||
{ key: 'llm_model', value: 'abab-6.5s-chat', group: 'ai', description: '对话模型名称' },
|
||
{ key: 'image_provider', value: 'minimax', group: 'ai', description: '图片生成供应商(minimax)' },
|
||
{ key: 'mmx_group_id', value: 'mg-20260301-abc123', group: 'ai', description: 'MiniMax 分组 ID' },
|
||
{ key: 'mmx_api_key', value: 'sk-mmx-xxxxxxxxxxxxxxxx', group: 'ai', description: 'MiniMax API 密钥' },
|
||
{ key: 'jubian_api_url', value: 'https://api.jubian.com/v1', group: 'platform', description: '巨变平台 API 地址' },
|
||
{ key: 'jubian_api_key', value: 'jb-sk-xxxxxxxxxxxxxxxx', group: 'platform', description: '巨变平台 API 密钥' },
|
||
{ key: 'custom_api_url', value: '', group: 'platform', description: '自建平台 API 地址' },
|
||
{ key: 'custom_api_key', value: '', group: 'platform', description: '自建平台 API 密钥' },
|
||
{ key: 'upload_max_size', value: '20', group: 'system', description: '上传文件大小限制(MB)' },
|
||
{ key: 'session_timeout', value: '120', group: 'system', description: '会话超时时间(分钟)' },
|
||
{ key: 'maintenance_mode', value: 'false', group: 'system', description: '系统维护模式(true/false)' },
|
||
]
|
||
for (const cfg of defaultConfigs) {
|
||
await prisma.systemConfig.upsert({
|
||
where: { key: cfg.key },
|
||
update: {},
|
||
create: cfg,
|
||
})
|
||
}
|
||
|
||
// Default plan config
|
||
await prisma.systemConfig.upsert({
|
||
where: { key: 'plan_config' },
|
||
update: {},
|
||
create: {
|
||
key: 'plan_config',
|
||
value: JSON.stringify({
|
||
basic: {
|
||
label: '基础版', price: 3000, description: '小型团队起步',
|
||
features: { inquiry: true, imageGen: false, product: true, comparison: false },
|
||
quotas: { monthlyLeads: 100, monthlyImages: 0, monthlyProducts: 100, monthlyComparisons: 0, maxUsers: 5 },
|
||
platformCount: 1, supportLevel: '社区支持',
|
||
},
|
||
pro: {
|
||
label: '专业版', price: 6000, description: '中型企业',
|
||
features: { inquiry: true, imageGen: true, product: true, comparison: true },
|
||
quotas: { monthlyLeads: 500, monthlyImages: 50, monthlyProducts: 500, monthlyComparisons: 100, maxUsers: 20 },
|
||
platformCount: 3, supportLevel: '标准支持',
|
||
},
|
||
enterprise: {
|
||
label: '旗舰版', price: 9000, description: '大型企业/多租户',
|
||
features: { inquiry: true, imageGen: true, product: true, comparison: true },
|
||
quotas: { monthlyLeads: 99999, monthlyImages: 99999, monthlyProducts: 99999, monthlyComparisons: 99999, maxUsers: 99999 },
|
||
platformCount: 999, supportLevel: '优先技术支持',
|
||
},
|
||
}),
|
||
group: 'system',
|
||
description: '订阅方案配置(JSON)',
|
||
},
|
||
})
|
||
|
||
console.log('Seed data created')
|
||
|
||
const categories = [
|
||
{ name: '防火门', keywords: ['防火门', '防火卷帘', '防火窗', '钢质防火', '木质防火'] },
|
||
{ name: '配电箱', keywords: ['配电箱', '配电柜', '配电盘', '照明配电箱', '动力配电箱', '电表箱'] },
|
||
{ name: '电线电缆', keywords: ['电线', '电缆', '导线', '电力线', '控制电缆', '光缆', '架空线'] },
|
||
{ name: '阀门', keywords: ['阀门', '闸阀', '球阀', '蝶阀', '截止阀', '止回阀', '调节阀'] },
|
||
{ name: '钢材', keywords: ['钢材', '钢管', '钢板', '钢筋', '型钢', '槽钢', '角钢', '工字钢', 'H型钢', '螺纹钢'] },
|
||
{ name: '涂料', keywords: ['涂料', '油漆', '防腐漆', '防火涂料', '乳胶漆', '防水涂料'] },
|
||
{ name: '混凝土', keywords: ['混凝土', '商混', '商品砼', '水泥', '砂浆', '砼'] },
|
||
{ name: '变压器', keywords: ['变压器', '箱变', '干式变', '油浸变', '电力变压器'] },
|
||
{ name: '水泵', keywords: ['水泵', '离心泵', '潜污泵', '消防泵', '排污泵', '循环泵'] },
|
||
{ name: '风机', keywords: ['风机', '通风机', '排风机', '轴流风机', '离心风机'] },
|
||
{ name: '管材', keywords: ['管材', '管道', 'PVC管', 'PE管', '镀锌管', '无缝管', '螺旋管', '给水管'] },
|
||
{ name: '开关', keywords: ['开关', '断路器', '接触器', '继电器', '熔断器', '隔离开关'] },
|
||
{ name: '灯具', keywords: ['灯具', '照明灯', '路灯', '防爆灯', 'LED灯', '太阳能灯'] },
|
||
{ name: '仪表', keywords: ['仪表', '仪器', '传感器', '变送器', '流量计', '压力表', '温度计'] },
|
||
{ name: '电机', keywords: ['电机', '电动机', '减速机', '减速器', '变频电机'] },
|
||
]
|
||
|
||
for (const cat of categories) {
|
||
await prisma.productCategory.upsert({
|
||
where: { name: cat.name },
|
||
update: { keywords: JSON.stringify(cat.keywords) },
|
||
create: { name: cat.name, keywords: JSON.stringify(cat.keywords) },
|
||
})
|
||
}
|
||
console.log('ProductCategory seed data created')
|
||
}
|
||
|
||
main()
|
||
.catch((e) => console.error(e))
|
||
.finally(() => prisma.$disconnect())
|