upload_sql

This commit is contained in:
hangyu.tao 2026-01-26 11:15:12 +08:00
parent 5c50ff0c2b
commit 872434b09c
7 changed files with 717 additions and 15 deletions

241
data/migration.sql Normal file
View File

@ -0,0 +1,241 @@
-- ============================================================
-- CRM 系统数据库迁移脚本
-- 生成时间: 2026-01-26
-- 说明: 从JSON文件迁移到MySQL数据库
-- 数据统计: 50条客户记录, 16条跟进记录, 7条试用期记录
-- ============================================================
-- 创建数据库(如果不存在)
CREATE DATABASE IF NOT EXISTS `crm_db`
DEFAULT CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
USE `crm_db`;
-- ============================================================
-- 1. 客户信息表 (customers)
-- 来源: customers.json
-- ============================================================
DROP TABLE IF EXISTS `customers`;
CREATE TABLE `customers` (
`id` VARCHAR(64) NOT NULL COMMENT '客户唯一标识',
`created_at` DATETIME NOT NULL COMMENT '创建时间',
`customer_name` VARCHAR(255) NOT NULL COMMENT '客户名称',
`intended_product` VARCHAR(100) DEFAULT NULL COMMENT '意向产品/日期',
`version` VARCHAR(50) DEFAULT NULL COMMENT '版本号',
`description` TEXT COMMENT '问题描述',
`solution` TEXT COMMENT '解决方案',
`type` VARCHAR(50) DEFAULT NULL COMMENT '类型(反馈/功能问题/需求/咨询)',
`module` VARCHAR(100) DEFAULT NULL COMMENT '模块(数据生成/模型工坊/数据空间/数据集/镜像管理)',
`status_progress` VARCHAR(50) DEFAULT '进行中' COMMENT '状态进度(已完成/进行中/待排期)',
`reporter` VARCHAR(100) DEFAULT NULL COMMENT '报告人',
PRIMARY KEY (`id`),
INDEX `idx_customer_name` (`customer_name`),
INDEX `idx_created_at` (`created_at`),
INDEX `idx_status_progress` (`status_progress`),
INDEX `idx_type` (`type`),
INDEX `idx_module` (`module`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='客户信息表';
-- ============================================================
-- 2. 客户跟进表 (followups)
-- 来源: followups.json
-- ============================================================
DROP TABLE IF EXISTS `followups`;
CREATE TABLE `followups` (
`id` VARCHAR(64) NOT NULL COMMENT '跟进记录唯一标识',
`created_at` DATETIME NOT NULL COMMENT '创建时间',
`customer_name` VARCHAR(255) NOT NULL COMMENT '客户名称',
`deal_status` VARCHAR(50) DEFAULT '未成交' COMMENT '成交状态(已成交/未成交)',
`customer_level` VARCHAR(10) DEFAULT 'C' COMMENT '客户等级(A/B/C)',
`industry` VARCHAR(100) DEFAULT NULL COMMENT '行业',
`follow_up_time` DATETIME DEFAULT NULL COMMENT '跟进时间',
`notification_sent` TINYINT(1) DEFAULT 0 COMMENT '是否已发送通知(0:否 1:是)',
PRIMARY KEY (`id`),
INDEX `idx_customer_name` (`customer_name`),
INDEX `idx_created_at` (`created_at`),
INDEX `idx_deal_status` (`deal_status`),
INDEX `idx_customer_level` (`customer_level`),
INDEX `idx_follow_up_time` (`follow_up_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='客户跟进表';
-- ============================================================
-- 3. 客户试用期表 (trial_periods)
-- 来源: trial_periods.json
-- ============================================================
DROP TABLE IF EXISTS `trial_periods`;
CREATE TABLE `trial_periods` (
`id` VARCHAR(64) NOT NULL COMMENT '试用期记录唯一标识',
`customer_name` VARCHAR(255) NOT NULL COMMENT '客户名称',
`start_time` DATETIME NOT NULL COMMENT '试用开始时间',
`end_time` DATETIME NOT NULL COMMENT '试用结束时间',
`is_trial` TINYINT(1) DEFAULT 1 COMMENT '是否试用中(0:否 1:是)',
`created_at` DATETIME NOT NULL COMMENT '创建时间',
PRIMARY KEY (`id`),
INDEX `idx_customer_name` (`customer_name`),
INDEX `idx_start_time` (`start_time`),
INDEX `idx_end_time` (`end_time`),
INDEX `idx_is_trial` (`is_trial`),
INDEX `idx_created_at` (`created_at`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='客户试用期表';
-- ============================================================
-- 插入客户信息数据 (customers) - 共50条记录
-- ============================================================
INSERT INTO `customers` (`id`, `created_at`, `customer_name`, `intended_product`, `version`, `description`, `solution`, `type`, `module`, `status_progress`, `reporter`) VALUES
('ecc0cdb8cc0f4777bf465ed604615706', '2026-01-13 18:56:41', '杰克', '2026-01-13', '1.9.4', '客户希望:根据参考图将瑕疵迁移到当前图片上,表示目前生成瑕疵图都困难,迁移更吃力;\n反馈comfyui里通过图生图的方式还能生成比较满意的图片但是批量生成任务瑕疵图片效果不太好。', '已帮客户训练lora、输出对应场景的提示词已文档形式交付给客户\n', '反馈', '数据生成', '已完成', ''),
('d299725547a4497cab919f51bb9a9656', '2026-01-12 19:12:29', '予芯', '2025/12/22', '1.9.4', '训练失败看不到错误日志', '已解决', '功能问题', '模型工坊', '已完成', ''),
('33d02d8cef4e45ddb5ca5d9d62939739', '2026-01-12 19:12:29', '诺因智能', '2025/12/23', '1.9.4', '客户端上传大文件失败', '技术优化,修复中', '功能问题', '数据空间', '已完成', ''),
('1e27d5a740ef4204bcda26ae62f949b3', '2026-01-12 19:12:29', '良业', '2025-12-23', '1.9.4', '数据集发布感觉操作繁琐,如果在模型工坊里直接选择需要训练的图片会更方便', '', '反馈', '数据集', '已完成', ''),
('5e3ce4f5c2ca4bec84242e9539c34d2b', '2026-01-12 19:12:29', '斯蒂尔', '2025-12-25', '1.9.4', '1.试用账号图片生成速度比较慢(2分钟1张图)\n2.生图效果不理想', '试用资源有限', '反馈', '数据生成', '已完成', ''),
('880de2c25e1540c09408083debc28c36', '2026-01-12 19:12:29', '求之', '2025-12-26', '1.9.4', '1.训练过程不太透明,以及无法提前终止模型训练\n2.训练过程无法断点继续训练;\n3.试用账号资源有限,训练时长过长', '1、训练过程的透明化日志化和目前的易用化有一些产品设计矛盾之前主要还是在做易用化。\n接下来我们会在更透明更强的客户对过程的操控粒度上提升。\n2、试用资源有限', '反馈', '模型工坊', '进行中', ''),
('b67ef18dd5384eb1b1b45473b110a565', '2026-01-12 19:12:29', '诺因智能', '2025-12-26', '1.9.4', '训练进程显示什么时候上线', '训练日志可视化,我们大约需要一个月的时间可以满足上线使用', '需求', '模型工坊', '进行中', ''),
('126b78e8321248f0ab168eb1284c745d', '2026-01-12 19:12:29', '诺因智能', '2025-12-26', '1.9.4', '(igev模型)全量训练是指从0开始 增量是在现在的ckpt上做微调是吗 ', '全量训练:在加载开源基础的权重上进行增量训练\n增量训练在您的数据上训练的checkpoint上进行的增量训练', '咨询', '模型工坊', '已完成', ''),
('e47573d35b95439aaf63126148271885', '2026-01-12 19:12:29', '斯蒂尔', '2025-12-26', '1.9.4', '数据生成效果改进优化方式?试用账号配置卡数', '409024g然后生图是单卡单线程\n目前系统上挂载了2张GPU', '咨询', '数据生成', '已完成', ''),
('299d843ea730491997c4cae0e056baae', '2026-01-12 19:12:29', '良业', '2025-12-26', '1.9.4', '1、训练时需要添加测试图无法理解模型分析结果可以用训练集\n2、训练失败时需提示原因方便下一步操作。', '1、训练过程中根据训练集的数据来更新模型参数在通过验证集来验证\n2、训练失败有日志可以查看您滚动下屏幕至右侧可以看到哈', '咨询', '模型工坊', '已完成', ''),
('599c4a26023e4f48908de2145e716cdc', '2026-01-12 19:12:29', '良业', '2025-12-29', '1.9.4', '1.试了几个模型,没训练出来,感觉不出效果\n', '补充数据集', '咨询', '模型工坊', '已完成', ''),
('50dba87f8f3c4f2c9a013f302ee07816', '2026-01-12 19:12:29', '诺因智能', '2025-12-29', '1.9.4', '1.总轮数与拓展参数(num_steps)不一致的时候,以哪个为准结束训练', '目前StereoNet-V2.5支持使用steps参数其他的还是以epoch为准', '咨询', '模型工坊', '已完成', ''),
('564e214d8ea244a4a72b521cd0b42504', '2026-01-12 19:12:29', '良业', '2025-12-30', '1.9.4', '户外同一个草坪测试用yolo检测640*480图片检测时间<100ms分割精度<10像素出错率1/10万使用x3可以吗', '100ms可以', '咨询', '模型工坊', '已完成', ''),
('f9a2e729845d490aa3c2e74cc61260ac', '2026-01-12 19:12:29', '良业', '2025-12-30', '1.9.4', '训练任务无效果', '数据集少了,补数后需要保存快照', '咨询', '模型工坊', '已完成', ''),
('5ae2913365e34f75b37dfdb4eb835081', '2026-01-12 19:12:29', '诺因智能', '2025-12-30', '1.9.4', '现在平台上的V2.5就可以需要完成的ckpt这个预计什么时候可以更新啊', '', '咨询', '模型工坊', '已完成', ''),
('02c7e508f59140f996aeaa5ae729edf1', '2026-01-12 19:12:29', '诺因智能', '2025-12-31', '1.9.4', '境外网站的加速,下载部分数据集时速度会很慢', '', '反馈', '数据集', '进行中', ''),
('65871e42c7004a3abe1eb7e5c1d818fb', '2026-01-12 19:12:29', '诺因智能', '2025-12-31', '1.9.4', '诺因智能需要完整的模型 checkpoint而不仅仅是 backbone 部分', '', '需求', '模型工坊', '已完成', ''),
('82ae50b5051a4957b160407c7ccdfb0e', '2026-01-12 19:12:29', '诺因智能', '2025-12-31', '1.9.4', '模型推理的指标对比按钮无法选择', '勾选多个版本进行选择', '反馈', '模型工坊', '进行中', ''),
('45077caed030466dbb04b1f2cc6229b8', '2026-01-12 19:12:29', '诺因智能', '2025-12-31', '1.9.4', '推理评测可以增加定量参数推理的时候能否增加一下epe衡量模型精度的指标', '', '需求', '模型工坊', '进行中', ''),
('8db8d7cd971c4fb89a42a36a38d52c4f', '2026-01-12 19:12:29', '雷沃', '2026/1/9', '1.9.4', '工作流调试出现的是黑图', '重启comfyui服务后正常', '功能问题', '数据生成', '已完成', ''),
('f1c13410c97c44f8a530ba5429967c20', '2026-01-12 19:12:29', '杰克', '2026-01-09', '1.9.4', '数据空间上传zip失败', '', '功能问题', '数据空间', '已完成', ''),
('43eaae16007948a0a57543a0d88da065', '2026-01-13 16:57:24', '雷沃', '2026-01-13', '1.9.4', '客户想推一个镜像到咱们的仓库上传一个私有模型结果一直报错unauthorized: project lovol-trial not found ', '重新配置镜像仓库', '咨询', '镜像管理', '已完成', ''),
('395853baa43b496b94ea0b1ef763a288', '2026-01-13 17:22:11', '有你同创', '2026-01-12', '1.9.4', '数据空间上传数据集失败', '回复客户先使用二进制文件上传', '咨询', '数据空间', '已完成', ''),
('fa49ba80265340c59cf9ac4bd0fc58cc', '2026-01-14 15:53:30', '创客', '2026-01-12', '1.9.4', '客户表示仅使用平台的yolo模型进行板端推理其他功能不cover', '', '需求', '模型工坊', '已完成', ''),
('bbd0b902bde74b7485d54e78ed406065', '2026-01-14 16:49:58', '诺因智能', '2026-01-14', '1.9.4', '试用账号数据迁移至正式版本', '', '需求', '数据空间', '进行中', ''),
('dfaba68d526d4344873e653ea2263c61', '2026-01-15 15:21:07', '诺因智能', '2026-01-15', '1.9.4', '客户反馈深度估计数据集上传失败', '已触达客户数据集的命名格式要按照平台规范填写 ', '反馈', '数据集', '已完成', ''),
('36b757f470be40178309badfd73edc19', '2026-01-15 16:49:38', '智绘科技有限公司', '2026-01-15', '1.9.4', '体验数据闭环平台两周已开通robogo平台账号信息', '已开通', '咨询', '数据集', '已完成', ''),
('445bc822793c4a88a57628d211904147', '2026-01-16 13:35:12', '创客', '2026-01-16', '1.9.4', ' 推理评测结果指标低', '1、训练集太少建议增加训练集和训练轮数\n2、模型欠拟合模型学习的特征少导致推理评测时测试集指标不理想', '反馈', '模型工坊', '已完成', ''),
('78cedb9de2e3448e900e9023e6fc0055', '2026-01-16 14:48:52', '雷沃', '2026-01-16', '1.9.4', '【数据挖掘】客户表示以图搜图功能很有必要,有助于数据清洗和特定数据集的建立帮助很大,希望能叠加提取功能,能够利用以图搜图建了特定数据集 ', '规划中', '需求', '数据集', '进行中', ''),
('2fc4583c22f84e918a6cc5c5600e0d7a', '2026-01-16 14:56:41', '雷沃', '2026-01-16', '1.9.4', '客户希望:语义分割需要支持标注微调', '规划中', '需求', '数据集', '进行中', ''),
('8318da5ebb544e25b3115ceac7f84547', '2026-01-16 15:13:12', '雷沃', '2026-01-16', '1.9.4', '客户表示:数据生成,自动标注,模型训练 无进度条展示', '已回复客户,模型训练下个版本支持日志可视化;生成和自动标注在规划中', '需求', '数据生成', '进行中', ''),
('d7a4c3d31b044d6dacb0698722fe45a9', '2026-01-16 15:17:33', '雷沃', '2026-01-16', '1.9.4', '客户反馈:模型训练部分,只有轮数、宽度、高度、学习率可调,其他拓展参数填写不生效', '规划中', '反馈', '模型工坊', '进行中', ''),
('14ceace7c6504a80a31150585245fa8d', '2026-01-16 15:22:35', '雷沃', '2026-01-16', '1.9.4', '客户反馈部署推理只适配X5想支持s100板子', '已为客户引流robogo平台', '反馈', '模型工坊', '已完成', ''),
('cdbe6646c31f4dbba24371a233f2df55', '2026-01-16 15:28:25', '雷沃', '2026-01-16', '1.9.4', '客户反馈:版本快照部分,可以增加备注功能,更新数据集版本后添加更新内容,便于管理和版本回滚', '规划中', '反馈', '数据集', '进行中', ''),
('c478ccd0f8c94a818e576dc35a307e4b', '2026-01-16 15:29:19', '雷沃', '2026-01-16', '1.9.4', '客户反馈:调试工作流-调试环境有时打开很慢10分钟以上', '已回复客户试用账号资源有限', '反馈', '数据生成', '已完成', ''),
('f40d2463275f4789b0a6a553eb459c2e', '2026-01-16 15:32:04', '雷沃', '2026-01-16', '1.9.4', '客户反馈对车头数据集进行ai辅助标注框选的效果优于描边。', '已回复客户后面会上SAM3 ', '反馈', '数据集', '进行中', ''),
('d296ac26a2754f90bdb2cdb3ae98d69d', '2026-01-16 15:34:35', '雷沃', '2026-01-16', '1.9.4', '客户反馈建立行人数据集对不同的标签person、people、pedestrain进行标注person标签有标注people和pedestrain标注几乎没有以及标注失败', '由于Ground dino 未续费,临时回复客户:标注功能在维护升级', '反馈', '数据集', '已完成', ''),
('3b6b4fd6099b402d942648142d25c67f', '2026-01-16 15:39:17', '雷沃', '2026-01-16', '1.9.4', '客户反馈:数据生成的部分照片跟预期还是差距\n客户预期生成车\n实际生成墙体、路面等不相关的图片', '登录客户账号查看批量生成任务的prompt太单一 ', '反馈', '数据生成', '已完成', ''),
('56b11e93e2084fe7943894b7282c93fa', '2026-01-16 19:11:10', '雷沃', '2026-01-16', '1.9.4', '客户表示仍想继续试用数据闭环平台', '已为客户续费一周截止时间下周51月23号', '需求', '数据生成', '已完成', ''),
('c9ea17dd1d434cd2a0f3bc2a9330d7dc', '2026-01-19 11:03:00', '有你同创', '2026-01-16', '1.9.4', '客户反馈:\n1、数据集创建失败\n2、数据集创建数量限制了1000张图片', '协程死锁导致插入失败,调整批量大小和增加超时控制避免死锁', '功能问题', '数据集', '已完成', ''),
('2a88dc53377446958f317e5418f55940', '2026-01-20 11:24:38', '杰克', '2026-01-19', '1.9.4', '客户反馈Gemini根据提供的prompt的生图效果相对较好将gemini生图效果较好的prompt放到地瓜平台也试了下生成图片里面有很多的"杂质"', '已回复客户平台comfyui可以支持多种模型后面也会接入gemini', '反馈', '数据生成', '进行中', ''),
('2eaed1848b2b465e81c1b23582ecbea3', '2026-01-20 15:46:29', '智绘科技有限公司', '2026-01-20', '1.9.4', '客户反馈标注一些图像微调了dinox后再用微调的模型进行自动标注没输出结果', '已安抚客户:付费的模型目前只对付费用户开放,先向上申请下额度,引导客户体验下其他功能', '反馈', '数据集', '已完成', ''),
('4e0f9d1f0b3647e780cd031085027806', '2026-01-20 15:50:58', '有你同创', '2026-01-20', '1.9.4', '客户咨询:\n1、对于负样本图片的处理现在平台有支持吗主要增加一些负样本减少误检\n2、负样本怎么进行上传', '已回复客户:\n1、支持\n2、可以放在一个数据集的文件夹里面', '咨询', '数据集', '已完成', ''),
('74d96f1b6219460d80d5cf659862375e', '2026-01-20 18:01:12', '有你同创', '2026-01-20', '1.9.4', '客户反馈使用ground dino 1.6pro 标注不生效', '已安抚客户付费的模型目前暂时只对付费用户开放您可以使用1.0 swinB 进行标注。', '反馈', '数据集', '已完成', ''),
('0b0ed317c418455094a7132d495c153d', '2026-01-21 10:48:40', '杰克', '2026-01-21', '1.9.4', '客户反馈:数据空间的数据没办法移动到素材库,还得单独重新上传一遍到素材库', '已回复客户:因不同的数据隐私和数据安全考虑。后续开通正式账号,前提是给我们开调试账号的授权下,素材库和数据空间数据共享可以一起调优或者配合', '反馈', '数据空间', '已完成', ''),
('9498c7fcaa5442b09268ebcb08b380fa', '2026-01-21 16:01:38', '智绘科技有限公司', '2026-01-21', '1.9.4', '客户反馈:端侧推理的任务运行十几个小时仍未成功', '已解决并回复客户:试用资源有限,手动调整队列的优先级', '反馈', '模型工坊', '已完成', ''),
('747a4999a0cc426cb01ddbfed662433a', '2026-01-22 17:43:02', '有你同创', '2026-01-22', '1.9.4', '客户表示手动标注体验没有labelme 操作方便', '已回复客户手动标注已经在重新设计了整体交互后面会和labelme保持一致', '需求', '数据集', '待排期', ''),
('5a2dd11c118b41f588fa0dc7f4151977', '2026-01-23 15:29:49', '有你同创', '2026-01-23', '1.9.4', '客户反馈:查看训练实时日志报错', '已安抚客户:由于平台升级发版,引导客户重新跑训练试试', '反馈', '模型工坊', '已完成', ''),
('ce1437fae330472784464983294f1bbb', '2026-01-26 10:51:58', '有你同创', '2026-01-25', '1.9.5', '客户反馈平台会偶尔报TypeError: Failed to fetch dynamically imported module:', '已引导客户:清除浏览器缓存重试', '反馈', '数据生成', '已完成', '');
-- ============================================================
-- 插入客户跟进数据 (followups) - 共16条记录
-- ============================================================
INSERT INTO `followups` (`id`, `created_at`, `customer_name`, `deal_status`, `customer_level`, `industry`, `follow_up_time`, `notification_sent`) VALUES
('30fa49832f40408e9d0d6eff3f5587bf', '2026-01-14 15:54:18', '创客', '未成交', 'C', '', '2026-01-14 15:54:00', 1),
('ad2e4f8ff56446ecab988e9806df6be4', '2026-01-14 16:03:22', '雷沃', '未成交', 'C', '', '2026-01-14 16:03:00', 1),
('af653bee68584694b1b0165afe954380', '2026-01-14 16:03:37', '诺因智能', '已成交', 'B', '', '2026-01-14 16:03:00', 1),
('bacdf2c1c2ad443eb814d902ad219ae5', '2026-01-15 15:23:04', '雷沃', '未成交', 'C', '', '2026-01-15 18:00:00', 1),
('fc6dca1345a5480181210d0fa1ab5f05', '2026-01-16 13:23:51', '雷沃', '未成交', 'C', '', '2026-01-16 18:00:00', 1),
('54903e1b80484a44bc613e7e051517ab', '2026-01-16 13:24:36', '诺因智能', '已成交', 'A', '', '2026-01-16 18:00:00', 1),
('c66fc68cac124ceb84b7af6fc52a5c09', '2026-01-20 10:52:19', '有你同创', '未成交', 'C', '', '2026-01-20 11:00:00', 1),
('fc154f2a8e214c8aa728f0bf34b5539b', '2026-01-20 10:59:06', '杰克', '未成交', 'A', '', '2026-01-20 11:00:00', 1),
('2023f1d60fbf4f7cbb61800a92b8cd65', '2026-01-20 15:18:07', '杰克', '未成交', 'A', '', '2026-01-20 16:00:00', 1),
('9a801d40eb1641fb996f4cae5160259a', '2026-01-21 16:49:24', '杰克', '未成交', 'A', '', '2026-01-21 17:00:00', 1),
('9d30b4aaf1cd489ca3fb023ad2a68413', '2026-01-21 17:03:28', '创客', '未成交', 'C', '', '2026-01-21 18:00:00', 1),
('ad71a20316dc40b38b4e4d71ae07f266', '2026-01-22 16:01:28', '创客', '未成交', 'C', '', '2026-01-22 17:00:00', 1),
('b4f017c09a2248db958cfc6103261068', '2026-01-22 16:01:44', '有你同创', '未成交', 'C', '', '2026-01-22 17:00:00', 1),
('9f145b1fb4fd4331979e288cf16bb88c', '2026-01-22 16:02:05', '雷沃', '未成交', 'C', '', '2026-01-22 17:00:00', 1),
('87ccd2494c284782b532d4c224d855d8', '2026-01-23 16:05:19', '雷沃', '未成交', 'C', '', '2026-01-23 18:00:00', 1),
('03563c64d89b42ebb39d999a5965989f', '2026-01-23 16:05:37', '创客', '未成交', 'C', '', '2026-01-23 18:00:00', 1);
-- ============================================================
-- 插入客户试用期数据 (trial_periods) - 共7条记录
-- ============================================================
INSERT INTO `trial_periods` (`id`, `customer_name`, `start_time`, `end_time`, `is_trial`, `created_at`) VALUES
('41e38867a7d94745921f8ab9985533c3', '雷沃', '2026-01-05 17:18:00', '2026-01-23 18:20:00', 1, '2026-01-13 17:19:13'),
('2751cd4ae9f54bf5a336bc3c7e6b7b6a', '有你同创', '2026-01-07 17:22:00', '2026-01-25 18:30:00', 1, '2026-01-13 17:22:45'),
('d8c235c7c8424ba79b4fdee597e17368', '杰克', '2026-01-08 15:51:00', '2026-01-21 15:51:00', 1, '2026-01-14 15:51:43'),
('39d7ab1a0ae44fbaaf079416ea9a4bf6', '诺因智能', '2026-01-05 15:51:00', '2026-01-16 18:20:00', 1, '2026-01-14 15:52:00'),
('b24d7c84b1bd4e4bb993f562995258b4', '创客', '2026-01-12 15:53:00', '2026-01-23 15:53:00', 1, '2026-01-14 15:53:47'),
('b045c9165ed847f3ba0c0d078c25bfed', '智绘科技有限公司', '2026-01-15 16:49:00', '2026-01-30 18:00:00', 1, '2026-01-15 16:50:17'),
('27b2aaf88ac2484fa209750c14ec1047', '睿尔曼', '2026-01-22 14:57:00', '2026-02-06 18:00:00', 1, '2026-01-22 14:57:30');
-- ============================================================
-- 创建视图 (可选)
-- ============================================================
-- 活跃试用客户视图
CREATE OR REPLACE VIEW `v_active_trials` AS
SELECT
tp.id,
tp.customer_name,
tp.start_time,
tp.end_time,
DATEDIFF(tp.end_time, NOW()) AS days_remaining,
tp.created_at
FROM `trial_periods` tp
WHERE tp.is_trial = 1 AND tp.end_time > NOW();
-- 客户问题统计视图
CREATE OR REPLACE VIEW `v_customer_issues_summary` AS
SELECT
customer_name,
COUNT(*) AS total_issues,
SUM(CASE WHEN status_progress = '已完成' THEN 1 ELSE 0 END) AS completed_issues,
SUM(CASE WHEN status_progress = '进行中' THEN 1 ELSE 0 END) AS ongoing_issues,
SUM(CASE WHEN status_progress = '待排期' THEN 1 ELSE 0 END) AS pending_issues
FROM `customers`
GROUP BY customer_name
ORDER BY total_issues DESC;
-- 模块问题统计视图
CREATE OR REPLACE VIEW `v_module_issues_summary` AS
SELECT
module,
type,
COUNT(*) AS issue_count,
SUM(CASE WHEN status_progress = '已完成' THEN 1 ELSE 0 END) AS completed,
SUM(CASE WHEN status_progress = '进行中' THEN 1 ELSE 0 END) AS ongoing,
SUM(CASE WHEN status_progress = '待排期' THEN 1 ELSE 0 END) AS pending
FROM `customers`
WHERE module IS NOT NULL AND module != ''
GROUP BY module, type
ORDER BY module, issue_count DESC;
-- 客户跟进统计视图
CREATE OR REPLACE VIEW `v_followup_summary` AS
SELECT
customer_name,
COUNT(*) AS total_followups,
MAX(deal_status) AS latest_deal_status,
MAX(customer_level) AS customer_level,
MAX(follow_up_time) AS last_follow_up_time
FROM `followups`
GROUP BY customer_name
ORDER BY last_follow_up_time DESC;
-- 成交客户列表视图
CREATE OR REPLACE VIEW `v_closed_deals` AS
SELECT DISTINCT
f.customer_name,
f.customer_level,
f.industry,
f.follow_up_time AS deal_time
FROM `followups` f
WHERE f.deal_status = '已成交'
ORDER BY f.follow_up_time DESC;
-- ============================================================
-- 数据迁移完成
-- 统计信息:
-- - 客户记录: 50条
-- - 跟进记录: 16条
-- - 试用期记录: 7条
-- - 已成交客户: 诺因智能
-- ============================================================

View File

@ -816,11 +816,26 @@ td.overflow-cell {
color: var(--white); color: var(--white);
} }
.delete-btn:hover { .delete-btn:hover:not(.disabled) {
background-color: #c0392b; background-color: #c0392b;
transform: translateY(-1px); transform: translateY(-1px);
} }
/* Disabled delete button style */
.delete-btn.disabled,
.delete-followup-btn.disabled {
background-color: #bdc3c7;
color: #7f8c8d;
cursor: not-allowed;
opacity: 0.6;
}
.delete-btn.disabled:hover,
.delete-followup-btn.disabled:hover {
background-color: #bdc3c7;
transform: none;
}
/* Dashboard Stats */ /* Dashboard Stats */
.dashboard-stats { .dashboard-stats {
display: grid; display: grid;

View File

@ -5,7 +5,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CRM客户管理系统</title> <title>CRM客户管理系统</title>
<link rel="stylesheet" href="/static/css/style.css?v=1.1"> <link rel="stylesheet" href="/static/css/style.css?v=1.2">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2"></script> <script src="https://cdn.jsdelivr.net/npm/chartjs-plugin-datalabels@2"></script>
@ -285,7 +285,7 @@
<thead> <thead>
<tr> <tr>
<th>客户名称</th> <th>客户名称</th>
<th>是否试用</th> <th>状态</th>
<th>开始时间</th> <th>开始时间</th>
<th>结束时间</th> <th>结束时间</th>
<th>创建时间</th> <th>创建时间</th>
@ -993,9 +993,9 @@
</div> </div>
<script src="/static/js/trial-periods.js?v=1.1"></script> <script src="/static/js/trial-periods.js?v=1.2"></script>
<script src="/static/js/trial-periods-page.js?v=1.1"></script> <script src="/static/js/trial-periods-page.js?v=1.2"></script>
<script src="/static/js/main.js?v=1.1"></script> <script src="/static/js/main.js?v=1.2"></script>
</body> </body>
</html> </html>

View File

@ -17,6 +17,44 @@ async function authenticatedFetch(url, options = {}) {
return response; return response;
} }
// 解析 JWT Token 获取用户信息
function parseJwtToken(token) {
try {
if (!token) return null;
const base64Url = token.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const jsonPayload = decodeURIComponent(atob(base64).split('').map(function (c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
return JSON.parse(jsonPayload);
} catch (e) {
console.error('Error parsing JWT token:', e);
return null;
}
}
// 获取当前用户角色
function getUserRole() {
const token = localStorage.getItem('crmToken');
const payload = parseJwtToken(token);
return payload ? payload.role : null;
}
// 获取当前用户名
function getUsername() {
const token = localStorage.getItem('crmToken');
const payload = parseJwtToken(token);
return payload ? payload.username : null;
}
// 检查当前用户是否可以删除数据
// admin 用户 (role: viewer) 不能删除
// administrator 用户 (role: admin) 可以删除
function canDelete() {
const role = getUserRole();
return role === 'admin';
}
document.addEventListener('DOMContentLoaded', function () { document.addEventListener('DOMContentLoaded', function () {
// 登录守卫 // 登录守卫
const token = localStorage.getItem('crmToken'); const token = localStorage.getItem('crmToken');
@ -793,11 +831,17 @@ document.addEventListener('DOMContentLoaded', function () {
const actionTd = document.createElement('td'); const actionTd = document.createElement('td');
actionTd.classList.add('action-cell'); actionTd.classList.add('action-cell');
// 根据用户权限决定删除按钮是否可用
const deleteDisabled = !canDelete();
const deleteClass = deleteDisabled ? 'action-btn delete-btn disabled' : 'action-btn delete-btn';
const deleteTitle = deleteDisabled ? '无删除权限' : '删除';
actionTd.innerHTML = ` actionTd.innerHTML = `
<button class="action-btn edit-btn" data-id="${customer.id}"> <button class="action-btn edit-btn" data-id="${customer.id}">
<i class="fas fa-edit"></i> <i class="fas fa-edit"></i>
</button> </button>
<button class="action-btn delete-btn" data-id="${customer.id}"> <button class="${deleteClass}" data-id="${customer.id}" title="${deleteTitle}" ${deleteDisabled ? 'disabled' : ''}>
<i class="fas fa-trash"></i> <i class="fas fa-trash"></i>
</button> </button>
`; `;
@ -815,7 +859,7 @@ document.addEventListener('DOMContentLoaded', function () {
}); });
}); });
document.querySelectorAll('.delete-btn').forEach(btn => { document.querySelectorAll('.delete-btn:not(.disabled)').forEach(btn => {
btn.addEventListener('click', function () { btn.addEventListener('click', function () {
const customerId = this.getAttribute('data-id'); const customerId = this.getAttribute('data-id');
deleteCustomer(customerId); deleteCustomer(customerId);
@ -1845,7 +1889,7 @@ document.addEventListener('DOMContentLoaded', function () {
<button class="action-btn edit-followup-btn" data-id="${followUp.id}"> <button class="action-btn edit-followup-btn" data-id="${followUp.id}">
<i class="fas fa-edit"></i> <i class="fas fa-edit"></i>
</button> </button>
<button class="action-btn delete-followup-btn" data-id="${followUp.id}"> <button class="action-btn delete-followup-btn ${!canDelete() ? 'disabled' : ''}" data-id="${followUp.id}" title="${!canDelete() ? '无删除权限' : '删除'}" ${!canDelete() ? 'disabled' : ''}>
<i class="fas fa-trash"></i> <i class="fas fa-trash"></i>
</button> </button>
</td> </td>
@ -1862,8 +1906,8 @@ document.addEventListener('DOMContentLoaded', function () {
}); });
}); });
// Add delete event listeners // Add delete event listeners (only for users with delete permission)
document.querySelectorAll('.delete-followup-btn').forEach(btn => { document.querySelectorAll('.delete-followup-btn:not(.disabled)').forEach(btn => {
btn.addEventListener('click', function () { btn.addEventListener('click', function () {
const followUpId = this.getAttribute('data-id'); const followUpId = this.getAttribute('data-id');
deleteFollowUp(followUpId); deleteFollowUp(followUpId);

View File

@ -335,7 +335,7 @@ function renderTrialPeriodsTable() {
<button class="action-btn edit-btn" data-id="${period.id}" title="编辑"> <button class="action-btn edit-btn" data-id="${period.id}" title="编辑">
<i class="fas fa-edit"></i> <i class="fas fa-edit"></i>
</button> </button>
<button class="action-btn delete-btn" data-id="${period.id}" title="删除"> <button class="action-btn delete-btn ${!canDelete() ? 'disabled' : ''}" data-id="${period.id}" title="${!canDelete() ? '无删除权限' : '删除'}" ${!canDelete() ? 'disabled' : ''}>
<i class="fas fa-trash"></i> <i class="fas fa-trash"></i>
</button> </button>
</td> </td>
@ -352,7 +352,7 @@ function renderTrialPeriodsTable() {
}); });
}); });
tbody.querySelectorAll('.delete-btn').forEach(btn => { tbody.querySelectorAll('.delete-btn:not(.disabled)').forEach(btn => {
btn.addEventListener('click', function () { btn.addEventListener('click', function () {
const periodId = this.getAttribute('data-id'); const periodId = this.getAttribute('data-id');
deleteTrialPeriodFromPage(periodId); deleteTrialPeriodFromPage(periodId);

View File

@ -43,15 +43,30 @@ func (h *AuthHandler) Login(w http.ResponseWriter, r *http.Request) {
return return
} }
// 定义用户账户和角色
// admin: 只读用户,不能删除数据
// administrator: 管理员,拥有完全控制权限
type UserInfo struct {
Password string
Role string
}
users := map[string]UserInfo{
"admin": {Password: "digua666", Role: "viewer"}, // 只读用户
"administrator": {Password: "digua888", Role: "admin"}, // 管理员
}
// 验证用户名和密码 // 验证用户名和密码
if req.Username != "admin" || req.Password != "digua666" { user, exists := users[req.Username]
if !exists || user.Password != req.Password {
http.Error(w, "用户名或密码错误", http.StatusUnauthorized) http.Error(w, "用户名或密码错误", http.StatusUnauthorized)
return return
} }
// 生成 JWT Token // 生成 JWT Token,包含角色信息
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"username": req.Username, "username": req.Username,
"role": user.Role,
"exp": time.Now().Add(time.Hour * 24).Unix(), // 24小时过期 "exp": time.Now().Add(time.Hour * 24).Unix(), // 24小时过期
}) })

387
migrate_to_mysql.go Normal file
View File

@ -0,0 +1,387 @@
package main
import (
"database/sql"
"encoding/json"
"fmt"
"log"
"os"
"time"
_ "github.com/go-sql-driver/mysql"
)
// Customer 客户信息结构
type Customer struct {
ID string `json:"id"`
CreatedAt string `json:"createdAt"`
CustomerName string `json:"customerName"`
IntendedProduct string `json:"intendedProduct"`
Version string `json:"version"`
Description string `json:"description"`
Solution string `json:"solution"`
Type string `json:"type"`
Module string `json:"module"`
StatusProgress string `json:"statusProgress"`
Reporter string `json:"reporter"`
}
// Followup 客户跟进结构
type Followup struct {
ID string `json:"id"`
CreatedAt string `json:"createdAt"`
CustomerName string `json:"customerName"`
DealStatus string `json:"dealStatus"`
CustomerLevel string `json:"customerLevel"`
Industry string `json:"industry"`
FollowUpTime string `json:"followUpTime"`
NotificationSent bool `json:"notificationSent"`
}
// TrialPeriod 试用期结构
type TrialPeriod struct {
ID string `json:"id"`
CustomerName string `json:"customerName"`
StartTime string `json:"startTime"`
EndTime string `json:"endTime"`
IsTrial bool `json:"isTrial"`
CreatedAt string `json:"createdAt"`
}
// 数据库配置
type DBConfig struct {
Host string
Port int
User string
Password string
Database string
}
func main() {
// 默认数据库配置
config := DBConfig{
Host: "localhost",
Port: 3306,
User: "root",
Password: "", // 请修改为实际密码
Database: "crm_db",
}
// 从环境变量读取配置
if host := os.Getenv("DB_HOST"); host != "" {
config.Host = host
}
if user := os.Getenv("DB_USER"); user != "" {
config.User = user
}
if pwd := os.Getenv("DB_PASSWORD"); pwd != "" {
config.Password = pwd
}
if db := os.Getenv("DB_NAME"); db != "" {
config.Database = db
}
// JSON 文件路径
dataDir := "./data"
if len(os.Args) > 1 {
dataDir = os.Args[1]
}
// 连接数据库
dsn := fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&parseTime=True&loc=Local",
config.User, config.Password, config.Host, config.Port, config.Database)
db, err := sql.Open("mysql", dsn)
if err != nil {
log.Fatalf("连接数据库失败: %v", err)
}
defer db.Close()
// 测试连接
if err := db.Ping(); err != nil {
log.Fatalf("数据库连接测试失败: %v", err)
}
log.Println("✅ 数据库连接成功")
// 创建表
if err := createTables(db); err != nil {
log.Fatalf("创建表失败: %v", err)
}
log.Println("✅ 数据表创建成功")
// 迁移客户数据
customersFile := fmt.Sprintf("%s/customers.json", dataDir)
if err := migrateCustomers(db, customersFile); err != nil {
log.Printf("⚠️ 迁移客户数据失败: %v", err)
} else {
log.Println("✅ 客户数据迁移成功")
}
// 迁移跟进数据
followupsFile := fmt.Sprintf("%s/followups.json", dataDir)
if err := migrateFollowups(db, followupsFile); err != nil {
log.Printf("⚠️ 迁移跟进数据失败: %v", err)
} else {
log.Println("✅ 跟进数据迁移成功")
}
// 迁移试用期数据
trialsFile := fmt.Sprintf("%s/trial_periods.json", dataDir)
if err := migrateTrialPeriods(db, trialsFile); err != nil {
log.Printf("⚠️ 迁移试用期数据失败: %v", err)
} else {
log.Println("✅ 试用期数据迁移成功")
}
log.Println("🎉 数据迁移完成!")
}
func createTables(db *sql.DB) error {
queries := []string{
`CREATE TABLE IF NOT EXISTS customers (
id VARCHAR(64) NOT NULL PRIMARY KEY,
created_at DATETIME NOT NULL,
customer_name VARCHAR(255) NOT NULL,
intended_product VARCHAR(100),
version VARCHAR(50),
description TEXT,
solution TEXT,
type VARCHAR(50),
module VARCHAR(100),
status_progress VARCHAR(50) DEFAULT '进行中',
reporter VARCHAR(100),
INDEX idx_customer_name (customer_name),
INDEX idx_created_at (created_at),
INDEX idx_status_progress (status_progress)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`,
`CREATE TABLE IF NOT EXISTS followups (
id VARCHAR(64) NOT NULL PRIMARY KEY,
created_at DATETIME NOT NULL,
customer_name VARCHAR(255) NOT NULL,
deal_status VARCHAR(50) DEFAULT '未成交',
customer_level VARCHAR(10) DEFAULT 'C',
industry VARCHAR(100),
follow_up_time DATETIME,
notification_sent TINYINT(1) DEFAULT 0,
INDEX idx_customer_name (customer_name),
INDEX idx_created_at (created_at),
INDEX idx_follow_up_time (follow_up_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`,
`CREATE TABLE IF NOT EXISTS trial_periods (
id VARCHAR(64) NOT NULL PRIMARY KEY,
customer_name VARCHAR(255) NOT NULL,
start_time DATETIME NOT NULL,
end_time DATETIME NOT NULL,
is_trial TINYINT(1) DEFAULT 1,
created_at DATETIME NOT NULL,
INDEX idx_customer_name (customer_name),
INDEX idx_end_time (end_time)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci`,
}
for _, query := range queries {
if _, err := db.Exec(query); err != nil {
return fmt.Errorf("执行SQL失败: %v\nSQL: %s", err, query)
}
}
return nil
}
func parseTime(timeStr string) (time.Time, error) {
// 尝试多种时间格式
formats := []string{
time.RFC3339,
time.RFC3339Nano,
"2006-01-02T15:04:05.999999999Z07:00",
"2006-01-02T15:04:05Z",
"2006-01-02 15:04:05",
"2006-01-02",
}
for _, format := range formats {
if t, err := time.Parse(format, timeStr); err == nil {
return t, nil
}
}
return time.Time{}, fmt.Errorf("无法解析时间: %s", timeStr)
}
func migrateCustomers(db *sql.DB, filepath string) error {
data, err := os.ReadFile(filepath)
if err != nil {
return fmt.Errorf("读取文件失败: %v", err)
}
var customers []Customer
if err := json.Unmarshal(data, &customers); err != nil {
return fmt.Errorf("解析JSON失败: %v", err)
}
stmt, err := db.Prepare(`
INSERT INTO customers (id, created_at, customer_name, intended_product, version,
description, solution, type, module, status_progress, reporter)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
customer_name = VALUES(customer_name),
intended_product = VALUES(intended_product),
version = VALUES(version),
description = VALUES(description),
solution = VALUES(solution),
type = VALUES(type),
module = VALUES(module),
status_progress = VALUES(status_progress),
reporter = VALUES(reporter)
`)
if err != nil {
return fmt.Errorf("准备SQL语句失败: %v", err)
}
defer stmt.Close()
successCount := 0
for _, c := range customers {
createdAt, _ := parseTime(c.CreatedAt)
_, err := stmt.Exec(
c.ID, createdAt, c.CustomerName, c.IntendedProduct, c.Version,
c.Description, c.Solution, c.Type, c.Module, c.StatusProgress, c.Reporter,
)
if err != nil {
log.Printf("插入客户记录失败 [%s]: %v", c.CustomerName, err)
continue
}
successCount++
}
log.Printf(" 已迁移 %d/%d 条客户记录", successCount, len(customers))
return nil
}
func migrateFollowups(db *sql.DB, filepath string) error {
data, err := os.ReadFile(filepath)
if err != nil {
return fmt.Errorf("读取文件失败: %v", err)
}
var followups []Followup
if err := json.Unmarshal(data, &followups); err != nil {
return fmt.Errorf("解析JSON失败: %v", err)
}
stmt, err := db.Prepare(`
INSERT INTO followups (id, created_at, customer_name, deal_status, customer_level,
industry, follow_up_time, notification_sent)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
customer_name = VALUES(customer_name),
deal_status = VALUES(deal_status),
customer_level = VALUES(customer_level),
industry = VALUES(industry),
follow_up_time = VALUES(follow_up_time),
notification_sent = VALUES(notification_sent)
`)
if err != nil {
return fmt.Errorf("准备SQL语句失败: %v", err)
}
defer stmt.Close()
successCount := 0
for _, f := range followups {
createdAt, _ := parseTime(f.CreatedAt)
followUpTime, _ := parseTime(f.FollowUpTime)
notificationSent := 0
if f.NotificationSent {
notificationSent = 1
}
_, err := stmt.Exec(
f.ID, createdAt, f.CustomerName, f.DealStatus, f.CustomerLevel,
f.Industry, followUpTime, notificationSent,
)
if err != nil {
log.Printf("插入跟进记录失败 [%s]: %v", f.CustomerName, err)
continue
}
successCount++
}
log.Printf(" 已迁移 %d/%d 条跟进记录", successCount, len(followups))
return nil
}
func migrateTrialPeriods(db *sql.DB, filepath string) error {
data, err := os.ReadFile(filepath)
if err != nil {
return fmt.Errorf("读取文件失败: %v", err)
}
var trials []TrialPeriod
if err := json.Unmarshal(data, &trials); err != nil {
return fmt.Errorf("解析JSON失败: %v", err)
}
stmt, err := db.Prepare(`
INSERT INTO trial_periods (id, customer_name, start_time, end_time, is_trial, created_at)
VALUES (?, ?, ?, ?, ?, ?)
ON DUPLICATE KEY UPDATE
customer_name = VALUES(customer_name),
start_time = VALUES(start_time),
end_time = VALUES(end_time),
is_trial = VALUES(is_trial)
`)
if err != nil {
return fmt.Errorf("准备SQL语句失败: %v", err)
}
defer stmt.Close()
successCount := 0
for _, t := range trials {
startTime, _ := parseTime(t.StartTime)
endTime, _ := parseTime(t.EndTime)
createdAt, _ := parseTime(t.CreatedAt)
isTrial := 0
if t.IsTrial {
isTrial = 1
}
_, err := stmt.Exec(
t.ID, t.CustomerName, startTime, endTime, isTrial, createdAt,
)
if err != nil {
log.Printf("插入试用期记录失败 [%s]: %v", t.CustomerName, err)
continue
}
successCount++
}
log.Printf(" 已迁移 %d/%d 条试用期记录", successCount, len(trials))
return nil
}
func init() {
// 设置日志格式
log.SetFlags(log.Ltime)
// 打印使用说明
if len(os.Args) > 1 && (os.Args[1] == "-h" || os.Args[1] == "--help") {
fmt.Println(`CRM JSON MySQL 数据迁移工具
用法:
go run migrate_to_mysql.go [data目录路径]
环境变量:
DB_HOST - 数据库主机 (默认: localhost)
DB_USER - 数据库用户 (默认: root)
DB_PASSWORD - 数据库密码 (默认: )
DB_NAME - 数据库名称 (默认: crm_db)
示例:
# 使用默认配置
go run migrate_to_mysql.go ./data
# 使用环境变量配置
DB_HOST=127.0.0.1 DB_USER=root DB_PASSWORD=123456 go run migrate_to_mysql.go ./data
`)
os.Exit(0)
}
}