feat(wm-revenue): #55 客服知识库+公告板+KPI看板完整实现

- Entity: KbArticle(知识库文章), Announcement(公告), KpiDashboard(KPI看板VO)
- Mapper: KbArticleMapper, AnnouncementMapper (MyBatis-Plus)
- Service: KnowledgeBaseService(知识库CRUD+搜索+分类+点赞+热门),
           AnnouncementService(公告发布/编辑/按类型筛选/按范围推送/撤回),
           KpiService(KPI聚合计算:待处理量/时效/满意率/趋势/排行)
- Controller: CsSupportController (/api/revenue/cs/*)
- SQL DDL: V_cs_support.sql (cs_kb_article + cs_announcement 表+示例数据)
- Frontend: KnowledgeBaseView.vue(列表/卡片/Markdown编辑器),
            AnnouncementView.vue(类型标签/状态切换/时间范围),
            KpiDashboardView.vue(ECharts趋势图+饼图+排行)
- Unit Test: CsSupportServiceTest (知识库/公告/KPI三组测试)
- Router: 新增 /cs/knowledge, /cs/announcement, /cs/kpi 路由
This commit is contained in:
2026-06-14 16:28:05 +08:00
parent 98f91e5d74
commit b445b089c8
35 changed files with 4758 additions and 26 deletions
+151
View File
@@ -0,0 +1,151 @@
-- =====================================================
-- GIS 地图展示模块 DDL
-- 包含: 监测点位表、管网线段表、区域表
-- 数据库: PostgreSQL
-- =====================================================
-- 1. GIS 监测点位表
CREATE TABLE IF NOT EXISTS prod_gis_point (
id BIGSERIAL PRIMARY KEY,
point_code VARCHAR(50) NOT NULL UNIQUE,
point_name VARCHAR(200) NOT NULL,
point_type VARCHAR(20) NOT NULL, -- flow/pressure/level/quality/valve
area VARCHAR(100),
lng DECIMAL(12, 8) NOT NULL, -- 经度
lat DECIMAL(12, 8) NOT NULL, -- 纬度
elevation DECIMAL(8, 2), -- 海拔高度(米)
device_id BIGINT, -- 关联 prod_monitor_device.id
address VARCHAR(500),
status VARCHAR(20) DEFAULT 'online', -- online/offline/fault
properties JSONB, -- 扩展属性(JSON)
remark VARCHAR(500),
created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE prod_gis_point IS 'GIS 监测点位表';
COMMENT ON COLUMN prod_gis_point.point_type IS '点位类型: flow-流量/pressure-压力/level-液位/quality-水质/valve-阀门';
COMMENT ON COLUMN prod_gis_point.lng IS '经度';
COMMENT ON COLUMN prod_gis_point.lat IS '纬度';
COMMENT ON COLUMN prod_gis_point.device_id IS '关联设备ID(prod_monitor_device.id)';
COMMENT ON COLUMN prod_gis_point.properties IS '扩展属性(JSON,存储不同类型点位的特有属性)';
-- 索引
CREATE INDEX IF NOT EXISTS idx_gis_point_type ON prod_gis_point(point_type);
CREATE INDEX IF NOT EXISTS idx_gis_point_area ON prod_gis_point(area);
CREATE INDEX IF NOT EXISTS idx_gis_point_status ON prod_gis_point(status);
CREATE INDEX IF NOT EXISTS idx_gis_point_device_id ON prod_gis_point(device_id);
CREATE INDEX IF NOT EXISTS idx_gis_point_lng_lat ON prod_gis_point(lng, lat);
-- 2. GIS 管网线段表
CREATE TABLE IF NOT EXISTS prod_gis_pipeline (
id BIGSERIAL PRIMARY KEY,
pipeline_code VARCHAR(50) NOT NULL UNIQUE,
pipeline_name VARCHAR(200),
pipeline_type VARCHAR(30), -- supply/distribution/drainage/raw_water
material VARCHAR(30), -- ductile_iron/pvc/pe/steel
diameter DECIMAL(8, 2), -- 管径(mm)
start_lng DECIMAL(12, 8) NOT NULL, -- 起点经度
start_lat DECIMAL(12, 8) NOT NULL, -- 起点纬度
end_lng DECIMAL(12, 8) NOT NULL, -- 终点经度
end_lat DECIMAL(12, 8) NOT NULL, -- 终点纬度
length DECIMAL(10, 2), -- 长度(米)
start_node_id BIGINT, -- 起点节点ID(关联 prod_gis_point.id)
end_node_id BIGINT, -- 终点节点ID(关联 prod_gis_point.id)
area VARCHAR(100),
burial_depth DECIMAL(6, 2), -- 埋深(米)
build_year INTEGER,
status VARCHAR(20) DEFAULT 'normal', -- normal/leakage/damaged/maintenance
properties JSONB,
remark VARCHAR(500),
created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE prod_gis_pipeline IS 'GIS 管网线段表';
COMMENT ON COLUMN prod_gis_pipeline.pipeline_type IS '管线类型: supply-供水/distribution-配水/drainage-排水/raw_water-原水';
COMMENT ON COLUMN prod_gis_pipeline.material IS '材质: ductile_iron-球墨铸铁/pvc/pe/steel-钢管';
COMMENT ON COLUMN prod_gis_pipeline.status IS '状态: normal-正常/leakage-渗漏/damaged-损坏/maintenance-维护中';
-- 索引
CREATE INDEX IF NOT EXISTS idx_gis_pipeline_type ON prod_gis_pipeline(pipeline_type);
CREATE INDEX IF NOT EXISTS idx_gis_pipeline_area ON prod_gis_pipeline(area);
CREATE INDEX IF NOT EXISTS idx_gis_pipeline_status ON prod_gis_pipeline(status);
CREATE INDEX IF NOT EXISTS idx_gis_pipeline_start_node ON prod_gis_pipeline(start_node_id);
CREATE INDEX IF NOT EXISTS idx_gis_pipeline_end_node ON prod_gis_pipeline(end_node_id);
CREATE INDEX IF NOT EXISTS idx_gis_pipeline_start_coord ON prod_gis_pipeline(start_lng, start_lat);
CREATE INDEX IF NOT EXISTS idx_gis_pipeline_end_coord ON prod_gis_pipeline(end_lng, end_lat);
-- 3. GIS 区域表
CREATE TABLE IF NOT EXISTS prod_gis_area (
id BIGSERIAL PRIMARY KEY,
area_code VARCHAR(50) NOT NULL UNIQUE,
area_name VARCHAR(200) NOT NULL,
area_type VARCHAR(30), -- water_plant/supply_zone/dma/admin_district
center_lng DECIMAL(12, 8),
center_lat DECIMAL(12, 8),
area_size DECIMAL(10, 4), -- 面积(平方公里)
boundary TEXT, -- 边界 GeoJSON (Polygon/MultiPolygon)
parent_id BIGINT, -- 上级区域ID
device_count INTEGER DEFAULT 0,
online_count INTEGER DEFAULT 0,
alert_count INTEGER DEFAULT 0,
population DECIMAL(10, 4), -- 供水人口(万人)
status VARCHAR(20) DEFAULT 'active', -- active/inactive
remark VARCHAR(500),
created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
COMMENT ON TABLE prod_gis_area IS 'GIS 区域表';
COMMENT ON COLUMN prod_gis_area.area_type IS '区域类型: water_plant-水厂/supply_zone-供水片区/dma-独立计量区/admin_district-行政区';
COMMENT ON COLUMN prod_gis_area.boundary IS '区域边界(GeoJSON 格式)';
COMMENT ON COLUMN prod_gis_area.population IS '供水人口(万人)';
-- 索引
CREATE INDEX IF NOT EXISTS idx_gis_area_type ON prod_gis_area(area_type);
CREATE INDEX IF NOT EXISTS idx_gis_area_status ON prod_gis_area(status);
CREATE INDEX IF NOT EXISTS idx_gis_area_parent ON prod_gis_area(parent_id);
CREATE INDEX IF NOT EXISTS idx_gis_area_center ON prod_gis_area(center_lng, center_lat);
-- =====================================================
-- 初始数据 (示例)
-- =====================================================
-- 示例区域
INSERT INTO prod_gis_area (area_code, area_name, area_type, center_lng, center_lat, area_size, device_count, online_count, alert_count, population, status)
VALUES
('AREA-001', '一体化水厂', 'water_plant', 82.07100000, 44.84500000, 2.5, 15, 12, 1, 5.0, 'active'),
('AREA-002', '管网一区', 'supply_zone', 82.08500000, 44.85500000, 8.0, 25, 20, 3, 12.0, 'active'),
('AREA-003', '管网二区', 'supply_zone', 82.09500000, 44.86000000, 6.5, 18, 15, 2, 8.5, 'active'),
('AREA-004', 'DMA-001', 'dma', 82.08000000, 44.85000000, 1.2, 8, 7, 0, 2.0, 'active'),
('AREA-005', 'DMA-002', 'dma', 82.09000000, 44.85800000, 1.8, 10, 8, 1, 3.5, 'active')
ON CONFLICT (area_code) DO NOTHING;
-- 示例监测点位
INSERT INTO prod_gis_point (point_code, point_name, point_type, area, lng, lat, elevation, device_id, address, status)
VALUES
('GIS-FLOW-001', '一号泵站出口流量计', 'flow', '一体化水厂', 82.07123456, 44.84567890, 350.5, 1, '一号泵站出口', 'online'),
('GIS-FLOW-002', '二号泵站流量计', 'flow', '一体化水厂', 82.07234567, 44.84678901, 348.2, 2, '二号泵站', 'online'),
('GIS-PRES-001', '管网压力监测点A', 'pressure', '管网一区', 82.08567890, 44.85512345, 340.0, 3, '人民路与建设路交叉口', 'online'),
('GIS-PRES-002', '管网压力监测点B', 'pressure', '管网一区', 82.08678901, 44.85623456, 338.5, 4, '中山路与解放路交叉口', 'offline'),
('GIS-LEV-001', '清水池液位计', 'level', '一体化水厂', 82.07012345, 44.84456789, 355.0, NULL, '清水池', 'online'),
('GIS-QUAL-001', '出厂水质监测仪', 'quality', '一体化水厂', 82.07345678, 44.84789012, 345.0, 5, '出厂水管', 'fault'),
('GIS-VALV-001', '主干管阀门V01', 'valve', '管网一区', 82.08456789, 44.85401234, 342.0, NULL, '主干管起点', 'online'),
('GIS-VALV-002', '主干管阀门V02', 'valve', '管网二区', 82.09512345, 44.86023456, 335.0, NULL, '主干管末端', 'online'),
('GIS-FLOW-003', 'DMA-001入口流量计', 'flow', 'DMA-001', 82.08045678, 44.85067890, 341.5, NULL, 'DMA-001入口', 'online'),
('GIS-PRES-003', '管网压力监测点C', 'pressure', '管网二区', 82.09623456, 44.86134567, 333.0, NULL, '建设路与和平路交叉口', 'online')
ON CONFLICT (point_code) DO NOTHING;
-- 示例管线
INSERT INTO prod_gis_pipeline (pipeline_code, pipeline_name, pipeline_type, material, diameter, start_lng, start_lat, end_lng, end_lat, length, start_node_id, end_node_id, area, burial_depth, build_year, status)
VALUES
('PIPE-001', '出厂水主干管', 'supply', 'ductile_iron', 600.00, 82.07123456, 44.84567890, 82.08456789, 44.85401234, 1500.00, 1, 7, '一体化水厂', 1.5, 2020, 'normal'),
('PIPE-002', '管网一区主干管', 'distribution', 'ductile_iron', 400.00, 82.08456789, 44.85401234, 82.08678901, 44.85623456, 800.00, 7, 4, '管网一区', 1.2, 2020, 'normal'),
('PIPE-003', '管网二区主干管', 'distribution', 'pvc', 300.00, 82.08678901, 44.85623456, 82.09512345, 44.86023456, 1200.00, 4, 8, '管网二区', 1.0, 2021, 'normal'),
('PIPE-004', 'DMA-001入口管', 'distribution', 'pe', 200.00, 82.08456789, 44.85401234, 82.08045678, 44.85067890, 500.00, 7, 9, 'DMA-001', 0.8, 2022, 'normal'),
('PIPE-005', '管网二区支管', 'distribution', 'pe', 150.00, 82.09512345, 44.86023456, 82.09623456, 44.86134567, 300.00, 8, 10, '管网二区', 0.8, 2022, 'maintenance')
ON CONFLICT (pipeline_code) DO NOTHING;
+109
View File
@@ -0,0 +1,109 @@
-- ============================================================
-- V4__quality_ledger.sql
-- 水质检测台账模块 DDL
-- 包含: 检测记录、水质标准、检测计划
-- ============================================================
-- 1. 水质检测记录表
CREATE TABLE IF NOT EXISTS prod_quality_test_record (
id BIGSERIAL PRIMARY KEY,
test_type VARCHAR(20) NOT NULL DEFAULT 'routine', -- routine/special/complaint
water_type VARCHAR(20) NOT NULL DEFAULT 'treated', -- raw/treated/network
sampling_point VARCHAR(100), -- 采样点
area VARCHAR(50), -- 所属区域
test_date DATE NOT NULL, -- 检测日期
test_time TIME, -- 检测时间
tester VARCHAR(50), -- 检测人
turbidity NUMERIC(10,2), -- 浊度 (NTU)
ph NUMERIC(5,2), -- pH值
residual_chlorine NUMERIC(6,3), -- 余氯 (mg/L)
color NUMERIC(8,2), -- 色度 (度)
odor NUMERIC(4,1), -- 嗅味 (级)
ecoli NUMERIC(10,2), -- 大肠杆菌 (CFU/100mL)
colony_count NUMERIC(10,2), -- 菌落总数 (CFU/mL)
compliance_status VARCHAR(20) NOT NULL DEFAULT 'pending', -- qualified/unqualified/pending
unqualified_items TEXT, -- 不合格项 (JSON)
remark VARCHAR(500), -- 备注
deleted INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_quality_record_type ON prod_quality_test_record(test_type);
CREATE INDEX IF NOT EXISTS idx_quality_record_water_type ON prod_quality_test_record(water_type);
CREATE INDEX IF NOT EXISTS idx_quality_record_area ON prod_quality_test_record(area);
CREATE INDEX IF NOT EXISTS idx_quality_record_date ON prod_quality_test_record(test_date);
CREATE INDEX IF NOT EXISTS idx_quality_record_compliance ON prod_quality_test_record(compliance_status);
CREATE INDEX IF NOT EXISTS idx_quality_record_deleted ON prod_quality_test_record(deleted);
COMMENT ON TABLE prod_quality_test_record IS '水质检测记录表';
COMMENT ON COLUMN prod_quality_test_record.test_type IS '检测类型: routine-常规/special-专项/complaint-投诉';
COMMENT ON COLUMN prod_quality_test_record.water_type IS '水样类型: raw-原水/treated-出厂水/network-管网末梢水';
COMMENT ON COLUMN prod_quality_test_record.compliance_status IS '合格状态: qualified-合格/unqualified-不合格/pending-待判定';
-- 2. 水质标准表 (GB5749-2022)
CREATE TABLE IF NOT EXISTS prod_quality_standard (
id BIGSERIAL PRIMARY KEY,
standard_name VARCHAR(100) NOT NULL,
standard_code VARCHAR(50) NOT NULL DEFAULT 'GB5749-2022',
param_name VARCHAR(50) NOT NULL, -- 参数编码
param_label VARCHAR(50), -- 参数显示名
param_unit VARCHAR(20), -- 单位
min_value NUMERIC(12,4), -- 最小值 (NULL=无下限)
max_value NUMERIC(12,4), -- 最大值 (NULL=无上限)
water_type VARCHAR(20) NOT NULL DEFAULT 'all', -- 适用水样类型
enabled INTEGER NOT NULL DEFAULT 1,
deleted INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_quality_standard_code ON prod_quality_standard(standard_code);
CREATE INDEX IF NOT EXISTS idx_quality_standard_param ON prod_quality_standard(param_name);
CREATE INDEX IF NOT EXISTS idx_quality_standard_deleted ON prod_quality_standard(deleted);
COMMENT ON TABLE prod_quality_standard IS '水质标准表 (基于GB5749-2022)';
-- 初始化 GB5749-2022 默认标准
INSERT INTO prod_quality_standard (standard_name, standard_code, param_name, param_label, param_unit, min_value, max_value, water_type)
VALUES
('生活饮用水卫生标准', 'GB5749-2022', 'turbidity', '浊度', 'NTU', NULL, 1.0, 'treated'),
('生活饮用水卫生标准', 'GB5749-2022', 'turbidity', '浊度', 'NTU', NULL, 3.0, 'network'),
('生活饮用水卫生标准', 'GB5749-2022', 'ph', 'pH', '', 6.5, 8.5, 'all'),
('生活饮用水卫生标准', 'GB5749-2022', 'residual_chlorine', '余氯', 'mg/L', 0.3, 2.0, 'treated'),
('生活饮用水卫生标准', 'GB5749-2022', 'residual_chlorine', '余氯', 'mg/L', 0.05, 2.0, 'network'),
('生活饮用水卫生标准', 'GB5749-2022', 'color', '色度', '度', NULL, 15.0, 'all'),
('生活饮用水卫生标准', 'GB5749-2022', 'odor', '嗅味', '级', NULL, 2.0, 'all'),
('生活饮用水卫生标准', 'GB5749-2022', 'ecoli', '大肠杆菌', 'CFU/100mL', NULL, 0.0, 'all'),
('生活饮用水卫生标准', 'GB5749-2022', 'colony_count', '菌落总数', 'CFU/mL', NULL, 100.0, 'all')
ON CONFLICT DO NOTHING;
-- 3. 水质检测计划表
CREATE TABLE IF NOT EXISTS prod_quality_test_plan (
id BIGSERIAL PRIMARY KEY,
plan_name VARCHAR(100) NOT NULL,
test_type VARCHAR(20) NOT NULL DEFAULT 'routine', -- routine/special
water_type VARCHAR(20) NOT NULL DEFAULT 'treated',
sampling_point VARCHAR(100),
area VARCHAR(50),
frequency VARCHAR(20) NOT NULL DEFAULT 'daily', -- daily/weekly/monthly
test_params VARCHAR(200), -- 检测参数 (逗号分隔)
start_date DATE NOT NULL,
end_date DATE, -- NULL=长期
next_test_date DATE,
status VARCHAR(20) NOT NULL DEFAULT 'active', -- active/paused/completed
execution_count INTEGER NOT NULL DEFAULT 0,
remark VARCHAR(500),
deleted INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_quality_plan_status ON prod_quality_test_plan(status);
CREATE INDEX IF NOT EXISTS idx_quality_plan_frequency ON prod_quality_test_plan(frequency);
CREATE INDEX IF NOT EXISTS idx_quality_plan_next_date ON prod_quality_test_plan(next_test_date);
CREATE INDEX IF NOT EXISTS idx_quality_plan_deleted ON prod_quality_test_plan(deleted);
COMMENT ON TABLE prod_quality_test_plan IS '水质检测计划表';
COMMENT ON COLUMN prod_quality_test_plan.frequency IS '检测频率: daily-日检/weekly-周检/monthly-月检';
COMMENT ON COLUMN prod_quality_test_plan.status IS '计划状态: active-启用/paused-暂停/completed-已完成';