首页优化

develop
杨默涵 6 months ago
parent bc83edb7ce
commit dada33948a

8
.idea/.gitignore vendored

@ -0,0 +1,8 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 基于编辑器的 HTTP 客户端请求
/httpRequests/
# Datasource local storage ignored files
/dataSources/
/dataSources.local.xml

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="KubernetesApiProvider"><![CDATA[{}]]></component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_21" default="true" project-jdk-name="21" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/out" />
</component>
</project>

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/WaterManager.iml" filepath="$PROJECT_DIR$/.idea/WaterManager.iml" />
</modules>
</component>
</project>

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

@ -0,0 +1,448 @@
# 数智水管家数据库 ER 关系图
## 📊 完整 ER 图
```mermaid
erDiagram
%% 用户相关
water_user ||--o{ water_device : "拥有"
water_user ||--o{ water_service_order : "创建"
water_user ||--o{ water_evaluation : "评价"
water_user ||--o{ water_message : "接收"
water_user ||--o{ water_quality : "关联"
%% 员工相关
water_staff ||--o{ water_service_order : "处理"
water_staff ||--o{ water_evaluation : "被评价"
water_staff ||--o{ water_message : "接收"
%% 设备相关
water_device ||--o{ water_filter : "包含"
water_device ||--o{ water_quality : "检测"
%% 订单相关
water_service_order ||--o{ water_order_product : "包含"
water_service_order ||--o| water_evaluation : "评价"
water_service_order ||--o| water_quality : "关联"
%% 商品相关
water_product ||--o{ water_order_product : "被订购"
%% 用户表
water_user {
bigint user_id PK "用户ID"
varchar phone UK "手机号"
varchar password "密码"
varchar nick_name "昵称"
varchar avatar "头像"
char gender "性别"
varchar openid UK "微信openid"
decimal balance "余额"
int total_orders "订单总数"
char status "状态"
}
%% 员工表
water_staff {
bigint staff_id PK "员工ID"
bigint user_id FK "关联sys_user"
varchar employee_no UK "工号"
varchar staff_name "姓名"
varchar phone UK "手机号"
bigint dept_id FK "部门ID"
varchar position "职位"
int total_orders "工单总数"
int completed_orders "完成数"
decimal avg_rating "平均评分"
char status "状态"
}
%% 设备表
water_device {
bigint device_id PK "设备ID"
bigint user_id FK "用户ID"
varchar sn UK "设备编号"
varchar device_name "设备名称"
varchar device_type "设备类型"
char status "状态"
varchar install_address "安装地址"
decimal location_lat "纬度"
decimal location_lng "经度"
datetime bind_time "绑定时间"
}
%% 滤芯表
water_filter {
bigint filter_id PK "滤芯ID"
bigint device_id FK "设备ID"
varchar filter_name "滤芯名称"
varchar model "型号"
datetime install_time "安装时间"
int total_days "总天数"
int used_days "已用天数"
int life_percentage "剩余百分比"
date next_replace_time "预计更换时间"
char status "状态"
}
%% 水质检测表
water_quality {
bigint quality_id PK "检测ID"
bigint device_id FK "设备ID"
bigint user_id FK "用户ID"
bigint order_id FK "订单ID"
datetime test_time "检测时间"
varchar quality_level "水质等级"
decimal ph_value "pH值"
decimal turbidity "浊度"
decimal residual_chlorine "余氯"
decimal total_hardness "总硬度"
decimal tds "TDS"
char status "状态"
}
%% 商品表
water_product {
bigint product_id PK "商品ID"
varchar product_name "商品名称"
varchar product_desc "商品描述"
varchar category "商品分类"
decimal price "价格"
int stock "库存"
varchar icon "图标"
char status "状态"
}
%% 服务订单表(核心)
water_service_order {
bigint order_id PK "订单ID"
varchar order_no UK "订单号"
bigint user_id FK "用户ID"
bigint staff_id FK "员工ID"
varchar service_type "服务类型"
varchar order_source "订单来源"
varchar order_status "订单状态"
varchar payment_status "支付状态"
decimal amount "订单金额"
decimal actual_amount "实付金额"
varchar payment_method "支付方式"
varchar contact_name "联系人"
varchar contact_phone "联系电话"
varchar service_address "服务地址"
varchar priority "优先级"
datetime submit_time "提交时间"
datetime accept_time "接单时间"
datetime complete_time "完成时间"
}
%% 订单商品关联表
water_order_product {
bigint id PK "主键ID"
bigint order_id FK "订单ID"
bigint product_id FK "商品ID"
varchar product_name "商品名称"
decimal product_price "商品单价"
int quantity "数量"
decimal total_price "小计"
}
%% 订单评价表
water_evaluation {
bigint evaluation_id PK "评价ID"
bigint order_id FK UK "订单ID"
bigint user_id FK "用户ID"
bigint staff_id FK "员工ID"
decimal rating "综合评分"
decimal service_rating "服务态度"
decimal quality_rating "服务质量"
decimal speed_rating "服务速度"
text content "评价内容"
char is_anonymous "是否匿名"
}
%% 系统消息表
water_message {
bigint message_id PK "消息ID"
bigint user_id FK "用户ID"
bigint staff_id FK "员工ID"
varchar message_type "消息类型"
varchar title "标题"
text content "内容"
bigint related_id "关联ID"
char is_read "是否已读"
datetime send_time "发送时间"
}
%% 通知公告表
water_notice {
bigint notice_id PK "公告ID"
varchar notice_title "标题"
text notice_content "内容"
varchar notice_type "类型"
varchar target_type "目标对象"
char status "状态"
datetime publish_time "发布时间"
}
%% 系统配置表
water_config {
bigint config_id PK "配置ID"
varchar config_key UK "配置键"
varchar config_value "配置值"
varchar config_type "配置类型"
varchar config_desc "配置描述"
}
```
## 🔗 核心关联关系
### 1. 用户中心
```
用户 (water_user)
├── 1:N → 设备 (water_device)
│ └── 1:N → 滤芯 (water_filter)
│ └── 1:N → 水质检测 (water_quality)
├── 1:N → 订单 (water_service_order)
│ └── 1:N → 订单商品 (water_order_product)
│ └── 1:1 → 评价 (water_evaluation)
└── 1:N → 消息 (water_message)
```
### 2. 员工中心
```
员工 (water_staff)
├── 1:N → 工单 (water_service_order)
│ └── 1:1 → 评价 (water_evaluation)
└── 1:N → 消息 (water_message)
```
### 3. 订单中心
```
订单 (water_service_order)
├── N:1 → 用户 (water_user)
├── N:1 → 员工 (water_staff)
├── 1:N → 订单商品 (water_order_product)
│ └── N:1 → 商品 (water_product)
├── 1:1 → 评价 (water_evaluation)
└── 1:1 → 水质检测 (water_quality) [可选]
```
## 📋 业务流程图
### 订单流程
```mermaid
graph LR
A[用户下单] --> B[创建订单]
B --> C{需要支付?}
C -->|是| D[待支付]
C -->|否| E[已支付]
D --> F[用户支付]
F --> E
E --> G[员工接单]
G --> H[处理中]
H --> I[完成]
I --> J[用户评价]
style A fill:#e1f5ff
style B fill:#fff4e1
style E fill:#d4edda
style I fill:#d1ecf1
```
### 设备绑定流程
```mermaid
graph TD
A[用户扫描设备二维码] --> B[获取设备SN]
B --> C{设备是否存在?}
C -->|否| D[提示设备不存在]
C -->|是| E{设备是否已绑定?}
E -->|是| F[提示已被绑定]
E -->|否| G[绑定设备]
G --> H[创建滤芯记录]
H --> I[绑定成功]
style A fill:#e1f5ff
style G fill:#d4edda
style I fill:#d1ecf1
```
### 水质检测流程
```mermaid
graph LR
A[用户申请检测] --> B[创建检测订单]
B --> C[员工接单]
C --> D[上门检测]
D --> E[录入检测数据]
E --> F[生成检测报告]
F --> G[推送消息通知]
G --> H[用户查看报告]
style A fill:#e1f5ff
style F fill:#d4edda
style H fill:#d1ecf1
```
## 🎯 索引关系图
### 主要索引
```
water_user
├── PRIMARY KEY (user_id)
├── UNIQUE KEY (phone)
└── UNIQUE KEY (openid)
water_device
├── PRIMARY KEY (device_id)
├── UNIQUE KEY (sn)
├── INDEX (user_id)
└── INDEX (status)
water_service_order
├── PRIMARY KEY (order_id)
├── UNIQUE KEY (order_no)
├── INDEX (user_id)
├── INDEX (staff_id)
├── INDEX (order_status)
└── COMPOSITE INDEX (staff_id, order_status)
water_filter
├── PRIMARY KEY (filter_id)
├── INDEX (device_id)
└── INDEX (life_percentage)
```
## 🔄 数据流向图
```mermaid
graph TD
subgraph "用户端"
U1[用户注册/登录]
U2[绑定设备]
U3[查看设备状态]
U4[下单购买]
U5[评价服务]
end
subgraph "核心数据层"
D1[(water_user)]
D2[(water_device)]
D3[(water_filter)]
D4[(water_service_order)]
D5[(water_evaluation)]
D6[(water_quality)]
end
subgraph "员工端"
S1[员工登录]
S2[查看工单]
S3[接单处理]
S4[完成服务]
S5[查看绩效]
end
U1 --> D1
U2 --> D2
U3 --> D3
U4 --> D4
U5 --> D5
D1 --> S2
D4 --> S2
S3 --> D4
S4 --> D6
D5 --> S5
style D1 fill:#ffd4d4
style D2 fill:#ffe4d4
style D3 fill:#fff4d4
style D4 fill:#d4f4ff
style D5 fill:#e4d4ff
style D6 fill:#d4ffe4
```
## 📊 数据统计关系
### 视图:员工绩效
```sql
v_staff_performance
├── 数据来源
│ ├── water_staff (基础信息)
│ ├── water_service_order (订单统计)
│ └── water_evaluation (评价统计)
└── 统计指标
├── 总订单数 (total_orders)
├── 完成订单数 (completed_orders)
├── 完成率 (completion_rate)
├── 平均评分 (avg_rating)
└── 平均时长 (avg_duration)
```
### 视图:订单统计
```sql
v_order_statistics
├── 数据来源
│ └── water_service_order
└── 统计维度
├── 按日期 (order_date)
├── 按服务类型 (service_type)
├── 按订单状态 (order_status)
└── 按订单来源 (order_source)
```
## 💡 设计说明
### 1. 为什么使用统一订单表?
**优点**
- ✅ 减少数据冗余
- ✅ 统一状态管理
- ✅ 简化业务逻辑
- ✅ 便于统计分析
**实现方式**
- 通过 `order_source` 区分来源(用户/后台)
- 通过 `service_type` 区分服务类型
- 通过 `order_status` 管理流程状态
### 2. 如何保证数据一致性?
**策略**
- ✅ 使用外键约束(可选)
- ✅ 使用唯一索引防止重复
- ✅ 使用事务保证原子性
- ✅ 使用触发器自动更新冗余字段(可选)
### 3. 如何优化查询性能?
**措施**
- ✅ 合理设计索引(单列索引 + 联合索引)
- ✅ 使用 Redis 缓存热点数据
- ✅ 使用视图简化复杂查询
- ✅ 使用分页查询减少数据量
- ✅ 使用批量操作减少IO
---
**文档版本**v2.0
**更新日期**2025-11-13
**工具推荐**dbdiagram.io, draw.io, Navicat
## 🛠️ 在线查看 ER 图
推荐使用以下工具在线查看和编辑 ER 图:
1. **Mermaid Live Editor**: https://mermaid.live
2. **dbdiagram.io**: https://dbdiagram.io
3. **draw.io**: https://app.diagrams.net
将上述 Mermaid 代码复制到 Mermaid Live Editor 即可查看交互式 ER 图。

File diff suppressed because it is too large Load Diff

@ -0,0 +1,523 @@
-- =============================================
-- 数智水管家数据库设计 v2.0
-- 创建日期2025-11-13
-- 适配框架RuoYi v4.x+
-- 数据库版本MySQL 5.7+
-- 表数量12张核心表 + 2个视图
-- =============================================
-- 创建数据库
CREATE DATABASE IF NOT EXISTS `water_manager`
DEFAULT CHARACTER SET utf8mb4
DEFAULT COLLATE utf8mb4_general_ci;
USE `water_manager`;
-- =============================================
-- 1. 用户表
-- =============================================
DROP TABLE IF EXISTS `water_user`;
CREATE TABLE `water_user` (
`user_id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`phone` VARCHAR(11) DEFAULT NULL COMMENT '手机号',
`password` VARCHAR(128) DEFAULT NULL COMMENT '密码BCrypt加密',
`nick_name` VARCHAR(50) DEFAULT NULL COMMENT '昵称',
`avatar` VARCHAR(255) DEFAULT NULL COMMENT '头像URL',
`gender` CHAR(1) DEFAULT '0' COMMENT '性别0未知 1男 2女',
`openid` VARCHAR(128) DEFAULT NULL COMMENT '微信openid',
`unionid` VARCHAR(128) DEFAULT NULL COMMENT '微信unionid',
`balance` DECIMAL(10,2) DEFAULT 0.00 COMMENT '账户余额',
`total_orders` INT(11) DEFAULT 0 COMMENT '订单总数(冗余)',
`status` CHAR(1) DEFAULT '0' COMMENT '状态0正常 1停用',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
`del_flag` CHAR(1) DEFAULT '0' COMMENT '删除标志0存在 2删除',
PRIMARY KEY (`user_id`),
UNIQUE KEY `uk_phone` (`phone`),
UNIQUE KEY `uk_openid` (`openid`),
KEY `idx_status` (`status`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';
-- =============================================
-- 2. 员工表
-- =============================================
DROP TABLE IF EXISTS `water_staff`;
CREATE TABLE `water_staff` (
`staff_id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '员工ID',
`user_id` BIGINT(20) DEFAULT NULL COMMENT '关联sys_user的user_id',
`employee_no` VARCHAR(20) NOT NULL COMMENT '工号',
`staff_name` VARCHAR(50) NOT NULL COMMENT '姓名',
`phone` VARCHAR(11) NOT NULL COMMENT '手机号',
`dept_id` BIGINT(20) DEFAULT NULL COMMENT '部门ID关联sys_dept',
`position` VARCHAR(50) DEFAULT NULL COMMENT '职位',
`status` CHAR(1) DEFAULT '0' COMMENT '状态0正常 1停用',
`total_orders` INT(11) DEFAULT 0 COMMENT '工单总数(冗余)',
`completed_orders` INT(11) DEFAULT 0 COMMENT '完成工单数(冗余)',
`avg_rating` DECIMAL(3,2) DEFAULT 5.00 COMMENT '平均评分(冗余)',
`avg_duration` INT(11) DEFAULT 0 COMMENT '平均服务时长-分钟(冗余)',
`create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建者',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新者',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
`del_flag` CHAR(1) DEFAULT '0' COMMENT '删除标志0存在 2删除',
PRIMARY KEY (`staff_id`),
UNIQUE KEY `uk_employee_no` (`employee_no`),
UNIQUE KEY `uk_phone` (`phone`),
KEY `idx_user_id` (`user_id`),
KEY `idx_dept_id` (`dept_id`),
KEY `idx_status` (`status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工表';
-- =============================================
-- 3. 设备表
-- =============================================
DROP TABLE IF EXISTS `water_device`;
CREATE TABLE `water_device` (
`device_id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '设备ID',
`user_id` BIGINT(20) NOT NULL COMMENT '用户ID',
`sn` VARCHAR(50) NOT NULL COMMENT '设备编号(唯一)',
`device_name` VARCHAR(100) DEFAULT NULL COMMENT '设备名称',
`device_type` VARCHAR(50) DEFAULT '智能净水器' COMMENT '设备类型',
`model` VARCHAR(50) DEFAULT NULL COMMENT '设备型号',
`status` CHAR(1) DEFAULT '1' COMMENT '状态0离线 1在线 2故障',
`install_address` VARCHAR(255) DEFAULT NULL COMMENT '安装地址',
`location_lat` DECIMAL(10,7) DEFAULT NULL COMMENT '纬度',
`location_lng` DECIMAL(10,7) DEFAULT NULL COMMENT '经度',
`bind_time` DATETIME DEFAULT NULL COMMENT '绑定时间',
`last_online_time` DATETIME DEFAULT NULL COMMENT '最后在线时间',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
`del_flag` CHAR(1) DEFAULT '0' COMMENT '删除标志0存在 2删除',
PRIMARY KEY (`device_id`),
UNIQUE KEY `uk_sn` (`sn`),
KEY `idx_user_id` (`user_id`),
KEY `idx_status` (`status`),
KEY `idx_bind_time` (`bind_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='设备表';
-- =============================================
-- 4. 滤芯表
-- =============================================
DROP TABLE IF EXISTS `water_filter`;
CREATE TABLE `water_filter` (
`filter_id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '滤芯ID',
`device_id` BIGINT(20) NOT NULL COMMENT '设备ID',
`filter_name` VARCHAR(50) NOT NULL COMMENT '滤芯名称PP棉滤芯',
`filter_type` VARCHAR(50) DEFAULT NULL COMMENT '滤芯类型',
`model` VARCHAR(50) DEFAULT NULL COMMENT '滤芯型号',
`install_time` DATETIME DEFAULT NULL COMMENT '安装时间',
`total_days` INT(11) DEFAULT 180 COMMENT '总使用天数默认180天',
`used_days` INT(11) DEFAULT 0 COMMENT '已使用天数',
`life_percentage` INT(11) DEFAULT 100 COMMENT '剩余寿命百分比',
`next_replace_time` DATE DEFAULT NULL COMMENT '预计更换时间',
`status` CHAR(1) DEFAULT '0' COMMENT '状态0正常 1需更换',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
`del_flag` CHAR(1) DEFAULT '0' COMMENT '删除标志0存在 2删除',
PRIMARY KEY (`filter_id`),
KEY `idx_device_id` (`device_id`),
KEY `idx_status` (`status`),
KEY `idx_life_percentage` (`life_percentage`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='滤芯表';
-- =============================================
-- 5. 水质检测记录表
-- =============================================
DROP TABLE IF EXISTS `water_quality`;
CREATE TABLE `water_quality` (
`quality_id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '检测ID',
`device_id` BIGINT(20) NOT NULL COMMENT '设备ID',
`user_id` BIGINT(20) NOT NULL COMMENT '用户ID',
`order_id` BIGINT(20) DEFAULT NULL COMMENT '关联订单ID如果是服务订单',
`test_time` DATETIME NOT NULL COMMENT '检测时间',
`test_address` VARCHAR(255) DEFAULT NULL COMMENT '检测地址',
`quality_level` VARCHAR(20) DEFAULT NULL COMMENT '水质等级(优质/良好/一般/较差)',
`quality_desc` VARCHAR(500) DEFAULT NULL COMMENT '水质描述',
-- 检测指标
`ph_value` DECIMAL(4,2) DEFAULT NULL COMMENT 'pH值6.5-8.5',
`turbidity` DECIMAL(6,2) DEFAULT NULL COMMENT '浊度 NTU≤1',
`residual_chlorine` DECIMAL(6,2) DEFAULT NULL COMMENT '余氯 mg/L0.05-4',
`total_hardness` DECIMAL(8,2) DEFAULT NULL COMMENT '总硬度 mg/L≤450',
`tds` DECIMAL(8,2) DEFAULT NULL COMMENT 'TDS溶解性固体 mg/L≤1000',
`cod` DECIMAL(8,2) DEFAULT NULL COMMENT 'COD化学需氧量 mg/L',
`ammonia_nitrogen` DECIMAL(6,2) DEFAULT NULL COMMENT '氨氮 mg/L',
`report_url` VARCHAR(255) DEFAULT NULL COMMENT '检测报告URL',
`status` CHAR(1) DEFAULT '0' COMMENT '状态0检测中 1已完成',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
`del_flag` CHAR(1) DEFAULT '0' COMMENT '删除标志0存在 2删除',
PRIMARY KEY (`quality_id`),
KEY `idx_device_id` (`device_id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_order_id` (`order_id`),
KEY `idx_test_time` (`test_time`),
KEY `idx_quality_level` (`quality_level`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='水质检测记录表';
-- =============================================
-- 6. 商品表
-- =============================================
DROP TABLE IF EXISTS `water_product`;
CREATE TABLE `water_product` (
`product_id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '商品ID',
`product_name` VARCHAR(100) NOT NULL COMMENT '商品名称',
`product_desc` VARCHAR(500) DEFAULT NULL COMMENT '商品描述',
`category` VARCHAR(50) DEFAULT NULL COMMENT '商品分类purifier净水器/meter水表/parts配件/service服务',
`price` DECIMAL(10,2) DEFAULT 0.00 COMMENT '商品价格',
`stock` INT(11) DEFAULT 0 COMMENT '库存数量',
`image_url` VARCHAR(255) DEFAULT NULL COMMENT '商品图片URL',
`icon` VARCHAR(50) DEFAULT NULL COMMENT '图标Emoji',
`status` CHAR(1) DEFAULT '0' COMMENT '状态0上架 1下架',
`sort_order` INT(11) DEFAULT 0 COMMENT '排序',
`create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建者',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新者',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
`del_flag` CHAR(1) DEFAULT '0' COMMENT '删除标志0存在 2删除',
PRIMARY KEY (`product_id`),
KEY `idx_category` (`category`),
KEY `idx_status` (`status`),
KEY `idx_sort_order` (`sort_order`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='商品表';
-- =============================================
-- 7. 服务订单表(核心表)
-- =============================================
DROP TABLE IF EXISTS `water_service_order`;
CREATE TABLE `water_service_order` (
`order_id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '订单ID',
`order_no` VARCHAR(50) NOT NULL COMMENT '订单号(唯一)',
`user_id` BIGINT(20) NOT NULL COMMENT '用户ID',
`staff_id` BIGINT(20) DEFAULT NULL COMMENT '负责员工ID',
-- 订单类型与来源
`service_type` VARCHAR(50) NOT NULL COMMENT '服务类型(设备安装/滤芯更换/维修/订购/水质检测)',
`order_source` VARCHAR(20) DEFAULT 'user' COMMENT '订单来源user用户下单/admin后台派单',
-- 订单状态
`order_status` VARCHAR(20) DEFAULT 'pending' COMMENT '订单状态pending待支付/paid已支付/accepted已接单/processing处理中/completed已完成/cancelled已取消',
`payment_status` VARCHAR(20) DEFAULT 'unpaid' COMMENT '支付状态unpaid未支付/paid已支付/refunded已退款',
-- 金额信息
`amount` DECIMAL(10,2) DEFAULT 0.00 COMMENT '订单金额',
`actual_amount` DECIMAL(10,2) DEFAULT 0.00 COMMENT '实付金额',
`discount_amount` DECIMAL(10,2) DEFAULT 0.00 COMMENT '优惠金额',
`payment_method` VARCHAR(20) DEFAULT NULL COMMENT '支付方式wechat微信/balance余额/alipay支付宝',
-- 服务信息
`contact_name` VARCHAR(50) DEFAULT NULL COMMENT '联系人',
`contact_phone` VARCHAR(11) DEFAULT NULL COMMENT '联系电话',
`service_address` VARCHAR(255) DEFAULT NULL COMMENT '服务地址',
`location_lat` DECIMAL(10,7) DEFAULT NULL COMMENT '纬度',
`location_lng` DECIMAL(10,7) DEFAULT NULL COMMENT '经度',
`description` TEXT DEFAULT NULL COMMENT '服务描述/问题描述',
`priority` VARCHAR(20) DEFAULT 'normal' COMMENT '优先级normal普通/high紧急',
-- 配送信息
`delivery_distance` DECIMAL(10,2) DEFAULT NULL COMMENT '配送距离(公里)',
`estimated_duration` INT(11) DEFAULT NULL COMMENT '预计时长(分钟)',
`actual_duration` INT(11) DEFAULT NULL COMMENT '实际时长(分钟)',
-- 时间轴
`submit_time` DATETIME DEFAULT NULL COMMENT '提交时间',
`payment_time` DATETIME DEFAULT NULL COMMENT '支付时间',
`accept_time` DATETIME DEFAULT NULL COMMENT '接单时间',
`start_time` DATETIME DEFAULT NULL COMMENT '开始配送时间',
`complete_time` DATETIME DEFAULT NULL COMMENT '完成时间',
`cancel_time` DATETIME DEFAULT NULL COMMENT '取消时间',
`create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建者',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新者',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
`del_flag` CHAR(1) DEFAULT '0' COMMENT '删除标志0存在 2删除',
PRIMARY KEY (`order_id`),
UNIQUE KEY `uk_order_no` (`order_no`),
KEY `idx_user_id` (`user_id`),
KEY `idx_staff_id` (`staff_id`),
KEY `idx_order_status` (`order_status`),
KEY `idx_service_type` (`service_type`),
KEY `idx_submit_time` (`submit_time`),
KEY `idx_staff_status` (`staff_id`, `order_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='服务订单表';
-- =============================================
-- 8. 订单商品关联表
-- =============================================
DROP TABLE IF EXISTS `water_order_product`;
CREATE TABLE `water_order_product` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`order_id` BIGINT(20) NOT NULL COMMENT '订单ID',
`product_id` BIGINT(20) NOT NULL COMMENT '商品ID',
`product_name` VARCHAR(100) NOT NULL COMMENT '商品名称(冗余)',
`product_price` DECIMAL(10,2) NOT NULL COMMENT '商品单价(冗余)',
`quantity` INT(11) DEFAULT 1 COMMENT '购买数量',
`total_price` DECIMAL(10,2) NOT NULL COMMENT '小计金额',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `idx_order_id` (`order_id`),
KEY `idx_product_id` (`product_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单商品关联表';
-- =============================================
-- 9. 订单评价表
-- =============================================
DROP TABLE IF EXISTS `water_evaluation`;
CREATE TABLE `water_evaluation` (
`evaluation_id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '评价ID',
`order_id` BIGINT(20) NOT NULL COMMENT '订单ID',
`user_id` BIGINT(20) NOT NULL COMMENT '用户ID',
`staff_id` BIGINT(20) DEFAULT NULL COMMENT '员工ID',
`rating` DECIMAL(2,1) DEFAULT 5.0 COMMENT '综合评分1-5星',
`service_rating` DECIMAL(2,1) DEFAULT 5.0 COMMENT '服务态度评分',
`quality_rating` DECIMAL(2,1) DEFAULT 5.0 COMMENT '服务质量评分',
`speed_rating` DECIMAL(2,1) DEFAULT 5.0 COMMENT '服务速度评分',
`content` TEXT DEFAULT NULL COMMENT '评价内容',
`images` VARCHAR(1000) DEFAULT NULL COMMENT '评价图片JSON数组',
`is_anonymous` CHAR(1) DEFAULT '0' COMMENT '是否匿名0否 1是',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`del_flag` CHAR(1) DEFAULT '0' COMMENT '删除标志0存在 2删除',
PRIMARY KEY (`evaluation_id`),
UNIQUE KEY `uk_order_id` (`order_id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_staff_id` (`staff_id`),
KEY `idx_rating` (`rating`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单评价表';
-- =============================================
-- 10. 系统消息表
-- =============================================
DROP TABLE IF EXISTS `water_message`;
CREATE TABLE `water_message` (
`message_id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '消息ID',
`user_id` BIGINT(20) DEFAULT NULL COMMENT '用户ID用户消息',
`staff_id` BIGINT(20) DEFAULT NULL COMMENT '员工ID员工消息',
`message_type` VARCHAR(50) DEFAULT NULL COMMENT '消息类型order订单/device设备/system系统/service服务',
`title` VARCHAR(100) NOT NULL COMMENT '消息标题',
`content` TEXT NOT NULL COMMENT '消息内容',
`related_id` BIGINT(20) DEFAULT NULL COMMENT '关联ID如订单ID、设备ID',
`is_read` CHAR(1) DEFAULT '0' COMMENT '是否已读0未读 1已读',
`send_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '发送时间',
`read_time` DATETIME DEFAULT NULL COMMENT '阅读时间',
`del_flag` CHAR(1) DEFAULT '0' COMMENT '删除标志0存在 2删除',
PRIMARY KEY (`message_id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_staff_id` (`staff_id`),
KEY `idx_message_type` (`message_type`),
KEY `idx_is_read` (`is_read`),
KEY `idx_send_time` (`send_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统消息表';
-- =============================================
-- 11. 通知公告表
-- =============================================
DROP TABLE IF EXISTS `water_notice`;
CREATE TABLE `water_notice` (
`notice_id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '公告ID',
`notice_title` VARCHAR(100) NOT NULL COMMENT '公告标题',
`notice_content` TEXT NOT NULL COMMENT '公告内容',
`notice_type` VARCHAR(20) DEFAULT 'info' COMMENT '公告类型info通知/warning警告/urgent紧急',
`target_type` VARCHAR(20) DEFAULT 'all' COMMENT '目标对象all全部/user用户/staff员工',
`status` CHAR(1) DEFAULT '0' COMMENT '状态0发布 1草稿 2下架',
`publish_time` DATETIME DEFAULT NULL COMMENT '发布时间',
`expire_time` DATETIME DEFAULT NULL COMMENT '过期时间',
`create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建者',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新者',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
`del_flag` CHAR(1) DEFAULT '0' COMMENT '删除标志0存在 2删除',
PRIMARY KEY (`notice_id`),
KEY `idx_status` (`status`),
KEY `idx_target_type` (`target_type`),
KEY `idx_publish_time` (`publish_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='通知公告表';
-- =============================================
-- 12. 系统配置表
-- =============================================
DROP TABLE IF EXISTS `water_config`;
CREATE TABLE `water_config` (
`config_id` BIGINT(20) NOT NULL AUTO_INCREMENT COMMENT '配置ID',
`config_key` VARCHAR(100) NOT NULL COMMENT '配置键',
`config_value` VARCHAR(500) NOT NULL COMMENT '配置值',
`config_type` VARCHAR(50) DEFAULT 'string' COMMENT '配置类型string/number/boolean/json',
`config_desc` VARCHAR(500) DEFAULT NULL COMMENT '配置描述',
`create_by` VARCHAR(64) DEFAULT NULL COMMENT '创建者',
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_by` VARCHAR(64) DEFAULT NULL COMMENT '更新者',
`update_time` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`remark` VARCHAR(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`config_id`),
UNIQUE KEY `uk_config_key` (`config_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统配置表';
-- =============================================
-- 视图1员工绩效视图
-- =============================================
DROP VIEW IF EXISTS `v_staff_performance`;
CREATE OR REPLACE VIEW `v_staff_performance` AS
SELECT
s.staff_id,
s.employee_no,
s.staff_name,
s.phone,
s.dept_id,
s.position,
-- 订单统计
COUNT(o.order_id) AS total_orders,
SUM(CASE WHEN o.order_status = 'completed' THEN 1 ELSE 0 END) AS completed_orders,
SUM(CASE WHEN o.order_status = 'cancelled' THEN 1 ELSE 0 END) AS cancelled_orders,
ROUND(SUM(CASE WHEN o.order_status = 'completed' THEN 1 ELSE 0 END) * 100.0 / NULLIF(COUNT(o.order_id), 0), 2) AS completion_rate,
-- 评价统计
IFNULL(AVG(e.rating), 5.00) AS avg_rating,
COUNT(e.evaluation_id) AS evaluation_count,
-- 时效统计
IFNULL(AVG(o.actual_duration), 0) AS avg_duration,
-- 服务类型统计
SUM(CASE WHEN o.service_type = '设备安装' THEN 1 ELSE 0 END) AS install_count,
SUM(CASE WHEN o.service_type = '滤芯更换' THEN 1 ELSE 0 END) AS filter_count,
SUM(CASE WHEN o.service_type = '维修' THEN 1 ELSE 0 END) AS repair_count,
SUM(CASE WHEN o.service_type = '水质检测' THEN 1 ELSE 0 END) AS quality_count,
-- 等级评定
CASE
WHEN AVG(e.rating) >= 4.8 AND COUNT(o.order_id) >= 50 THEN '金牌员工'
WHEN AVG(e.rating) >= 4.5 AND COUNT(o.order_id) >= 30 THEN '优秀员工'
WHEN AVG(e.rating) >= 4.0 THEN '普通员工'
ELSE '待提升'
END AS staff_level
FROM water_staff s
LEFT JOIN water_service_order o ON s.staff_id = o.staff_id AND o.del_flag = '0'
LEFT JOIN water_evaluation e ON o.order_id = e.order_id AND e.del_flag = '0'
WHERE s.del_flag = '0'
GROUP BY s.staff_id;
-- =============================================
-- 视图2订单统计视图
-- =============================================
DROP VIEW IF EXISTS `v_order_statistics`;
CREATE OR REPLACE VIEW `v_order_statistics` AS
SELECT
DATE(submit_time) AS order_date,
service_type,
order_status,
order_source,
COUNT(*) AS order_count,
SUM(amount) AS total_amount,
SUM(actual_amount) AS total_actual_amount,
SUM(discount_amount) AS total_discount,
AVG(actual_duration) AS avg_duration,
AVG(delivery_distance) AS avg_distance,
COUNT(DISTINCT user_id) AS user_count,
COUNT(DISTINCT staff_id) AS staff_count
FROM water_service_order
WHERE del_flag = '0'
GROUP BY DATE(submit_time), service_type, order_status, order_source;
-- =============================================
-- 初始化系统配置数据
-- =============================================
INSERT INTO `water_config` (`config_key`, `config_value`, `config_type`, `config_desc`, `create_by`, `remark`) VALUES
('order.auto_complete_hours', '24', 'number', '订单自动完成时间(小时)', 'admin', '订单在处理中状态超过指定小时后自动完成'),
('filter.warning_threshold', '30', 'number', '滤芯寿命预警阈值(%', 'admin', '滤芯寿命低于此百分比时发送预警'),
('service.quality_test_price', '99.00', 'number', '水质检测服务价格', 'admin', '水质检测服务的默认价格'),
('delivery.base_fee', '5.00', 'number', '配送费起步价', 'admin', '配送服务的起步价格'),
('filter.pp_days', '180', 'number', 'PP棉滤芯使用天数', 'admin', 'PP棉滤芯的默认使用天数'),
('filter.cto_days', '180', 'number', '活性炭滤芯使用天数', 'admin', '活性炭滤芯的默认使用天数'),
('filter.ro_days', '720', 'number', 'RO膜使用天数', 'admin', 'RO反渗透膜的默认使用天数'),
('device.offline_minutes', '60', 'number', '设备离线判定时间(分钟)', 'admin', '设备超过指定分钟未上线判定为离线');
-- =============================================
-- 初始化商品数据
-- =============================================
INSERT INTO `water_product` (`product_name`, `product_desc`, `category`, `price`, `stock`, `icon`, `status`, `sort_order`, `create_by`) VALUES
('智能净水器', 'RO反渗透5级过滤', 'purifier', 1299.00, 100, '💧', '0', 1, 'admin'),
('智能水表', '远程抄表,实时监测', 'meter', 299.00, 200, '📊', '0', 2, 'admin'),
('净水器滤芯-PP棉', 'PP棉滤芯5微米过滤', 'parts', 59.00, 500, '🔧', '0', 3, 'admin'),
('净水器滤芯-活性炭', '前置活性炭滤芯,吸附异味', 'parts', 89.00, 500, '🔧', '0', 4, 'admin'),
('净水器滤芯-RO膜', 'RO反渗透膜0.0001微米过滤', 'parts', 199.00, 300, '🔧', '0', 5, 'admin'),
('水质检测服务', '上门检测,专业报告', 'service', 99.00, 9999, '🔬', '0', 6, 'admin'),
('前置过滤器', '保护全屋用水安全', 'purifier', 399.00, 80, '🛡️', '0', 7, 'admin'),
('软水机', '去除水垢,软化水质', 'purifier', 2599.00, 50, '', '0', 8, 'admin');
-- =============================================
-- 初始化通知公告数据
-- =============================================
INSERT INTO `water_notice` (`notice_title`, `notice_content`, `notice_type`, `target_type`, `status`, `publish_time`, `create_by`) VALUES
('欢迎使用数智水管家', '感谢您使用数智水管家系统,我们将为您提供优质的水质管理服务。如有任何问题,请随时联系我们的客服团队。', 'info', 'all', '0', NOW(), 'admin'),
('关于临时停水通知', '因设备维护需要部分区域将在本周末11月16-17日临时停水请提前做好储水准备。', 'warning', 'user', '0', NOW(), 'admin'),
('春节供水保障公告', '春节期间我们将安排员工值班确保供水服务正常进行。紧急情况请拨打24小时客服热线。', 'info', 'all', '0', NOW(), 'admin');
-- =============================================
-- 示例数据:用户
-- =============================================
INSERT INTO `water_user` (`phone`, `password`, `nick_name`, `gender`, `balance`, `total_orders`, `status`) VALUES
('13800138000', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE/sXHZWzUzn', '张三', '1', 100.00, 5, '0'),
('13800138001', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE/sXHZWzUzn', '李四', '2', 50.00, 3, '0'),
('13800138002', '$2a$10$7JB720yubVSZvUI0rEqK/.VqGOZTH.ulu33dHOiBE/sXHZWzUzn', '王五', '1', 200.00, 8, '0');
-- =============================================
-- 示例数据:员工
-- =============================================
INSERT INTO `water_staff` (`employee_no`, `staff_name`, `phone`, `position`, `status`, `total_orders`, `completed_orders`, `avg_rating`, `create_by`) VALUES
('EMP001', '工程师张', '13900139000', '高级工程师', '0', 50, 48, 4.8, 'admin'),
('EMP002', '工程师李', '13900139001', '工程师', '0', 30, 28, 4.5, 'admin'),
('EMP003', '工程师王', '13900139002', '初级工程师', '0', 20, 18, 4.2, 'admin');
-- =============================================
-- 示例数据:设备
-- =============================================
INSERT INTO `water_device` (`user_id`, `sn`, `device_name`, `device_type`, `model`, `status`, `install_address`, `bind_time`, `last_online_time`) VALUES
(1, 'JSQ20240115001', '净水器-001', '智能净水器', 'RO-5级复合滤芯', '1', 'XX小区3栋2单元501', '2024-01-10 14:30:00', NOW()),
(1, 'JSQ20240112002', '净水器-002', '智能净水器', 'RO-5级复合滤芯', '1', 'XX小区3栋2单元501', '2024-01-08 10:15:00', NOW()),
(2, 'JSQ20240105003', '净水器-003', '智能净水器', 'RO-5级复合滤芯', '0', 'XX路88号', '2024-01-05 09:00:00', '2024-01-14 18:00:00');
-- =============================================
-- 示例数据:滤芯
-- =============================================
INSERT INTO `water_filter` (`device_id`, `filter_name`, `filter_type`, `model`, `install_time`, `total_days`, `used_days`, `life_percentage`, `next_replace_time`, `status`) VALUES
(1, 'PP棉滤芯', 'PP棉', 'PP-001', '2024-01-10', 180, 35, 65, '2024-06-08', '0'),
(1, '前置活性炭', '活性炭', 'CTO-001', '2024-01-10', 180, 35, 58, '2024-06-08', '0'),
(1, 'RO反渗透膜', 'RO膜', 'RO-001', '2024-01-10', 720, 35, 72, '2026-01-05', '0'),
(1, '后置活性炭', '活性炭', 'GAC-001', '2024-01-10', 180, 35, 61, '2024-06-08', '0'),
(2, 'PP棉滤芯', 'PP棉', 'PP-001', '2024-01-08', 180, 45, 45, '2024-06-06', '0'),
(2, '前置活性炭', '活性炭', 'CTO-001', '2024-01-08', 180, 45, 38, '2024-06-06', '0'),
(2, 'RO反渗透膜', 'RO膜', 'RO-001', '2024-01-08', 720, 45, 52, '2026-01-03', '0'),
(2, '后置活性炭', '活性炭', 'GAC-001', '2024-01-08', 180, 45, 42, '2024-06-06', '0');
-- =============================================
-- 示例数据:水质检测
-- =============================================
INSERT INTO `water_quality` (`device_id`, `user_id`, `test_time`, `test_address`, `quality_level`, `quality_desc`, `ph_value`, `turbidity`, `residual_chlorine`, `total_hardness`, `tds`, `status`) VALUES
(1, 1, '2024-01-15 10:00:00', 'XX小区3栋2单元501', '优质', '水质指标稳定,符合饮用标准', 7.2, 0.5, 0.3, 150, 220, '1'),
(2, 1, '2024-01-10 14:00:00', 'XX小区3栋2单元501', '良好', '上次检测显示浊度接近上限,建议再次检测确认', 7.0, 1.2, 0.25, 180, 250, '1'),
(3, 2, '2024-01-08 09:00:00', 'XX路88号', '较差', '余氯低于安全值,建议暂停饮用并联系工作人员', 6.8, 0.8, 0.04, 200, 300, '1');
-- =============================================
-- 创建完成提示
-- =============================================
SELECT '数据库创建完成!' AS message;
SELECT '核心表数量12张' AS summary;
SELECT '视图数量2个' AS summary;
SELECT '已初始化示例数据' AS summary;

@ -94,6 +94,18 @@
"navigationBarTitleText": "水质检测"
}
},
{
"path": "pages/water-quality-detail/water-quality-detail",
"style": {
"navigationBarTitleText": "检测报告"
}
},
{
"path": "pages/water-quality-alert/water-quality-alert",
"style": {
"navigationBarTitleText": "水质预警"
}
},
{
"path": "pages/customer-service/customer-service",
"style": {

@ -38,6 +38,32 @@
</view>
</view>
<!-- 最新水质情况 -->
<view class="water-quality-section">
<view class="section-title">最新水质情况</view>
<view v-if="latestWaterQuality" class="water-quality-card">
<view class="quality-header">
<text class="quality-level" :class="getWaterLevelClass(latestWaterQuality.level)">{{ latestWaterQuality.level }}</text>
<text class="quality-date">{{ latestWaterQuality.date }} 检测</text>
</view>
<text class="quality-desc">{{ latestWaterQuality.description }}</text>
<view class="indicator-grid">
<view class="indicator-item" v-for="indicator in latestWaterQuality.result" :key="indicator.name">
<text class="indicator-name">{{ indicator.name }}</text>
<text class="indicator-value">{{ indicator.value }} {{ indicator.unit }}</text>
<text class="indicator-status" :class="getIndicatorStatusClass(indicator)">{{ getIndicatorStatusText(indicator) }}</text>
</view>
</view>
<view class="quality-actions">
<button class="quality-btn" @click="handleViewWaterQuality"></button>
<button class="quality-btn outline" @click="handleWaterQualityDetail"></button>
</view>
</view>
<view v-else class="water-quality-empty">
<text>暂无最新水质数据</text>
</view>
</view>
<!-- 设备信息 -->
<view class="info-section">
<view class="section-title">设备信息</view>
@ -76,7 +102,7 @@
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { ref, onMounted } from 'vue'
//
const deviceDetail = ref({
@ -116,6 +142,16 @@ const deviceDetail = ref({
]
})
const latestWaterQuality = ref(null)
const waterQualityStandards = {
'pH值': { min: 6.5, max: 8.5, unit: '' },
'浊度': { max: 1, unit: 'NTU' },
'余氯': { min: 0.05, max: 4, unit: 'mg/L' },
'总硬度': { max: 450, unit: 'mg/L' },
'TDS': { max: 1000, unit: 'mg/L' }
}
//
const getStatusClass = (status) => {
return status === 'online' ? 'status-online' : 'status-offline'
@ -178,6 +214,31 @@ const handleViewWaterQuality = () => {
uni.navigateTo({ url })
}
const handleWaterQualityDetail = () => {
if (!latestWaterQuality.value) {
uni.showToast({
title: '暂无水质报告',
icon: 'none'
})
return
}
const recordId = latestWaterQuality.value.id || `latest_${deviceDetail.value.sn}`
try {
uni.setStorageSync(`waterQualityRecord_${recordId}`, {
...latestWaterQuality.value,
id: recordId
})
uni.navigateTo({
url: `/pages/water-quality-detail/water-quality-detail?recordId=${recordId}`
})
} catch (e) {
uni.showToast({
title: '打开报告失败',
icon: 'none'
})
}
}
//
onMounted(() => {
//
@ -188,6 +249,9 @@ onMounted(() => {
if (sn) {
loadDeviceDetail(sn)
loadLatestWaterQuality(sn)
} else {
loadLatestWaterQuality(deviceDetail.value.sn)
}
// 寿30
@ -260,6 +324,71 @@ const loadDeviceDetail = (sn) => {
}
}
const loadLatestWaterQuality = (sn) => {
let record = null
try {
const allRecords = uni.getStorageSync('waterQualityRecords')
if (Array.isArray(allRecords) && allRecords.length) {
const completedRecords = allRecords
.filter(item => item.sn === sn && item.status !== 'processing')
.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
if (completedRecords.length) {
record = completedRecords[0]
}
}
const alerts = uni.getStorageSync('waterQualityAlerts')
if (!record && Array.isArray(alerts)) {
record = alerts.find(item => item.sn === sn) || null
}
if (!record) {
const stored = uni.getStorageSync(`waterQualityLatest_${sn}`)
if (stored && typeof stored === 'object') {
record = stored
}
}
} catch (e) {
record = null
}
if (!record) {
record = {
id: `latest_${sn}`,
name: deviceDetail.value.name,
sn,
level: '优质',
date: '2024-01-15',
description: '水质指标稳定,符合饮用标准',
result: [
{ name: 'pH值', value: '7.2', unit: '' },
{ name: '浊度', value: '0.4', unit: 'NTU' },
{ name: '余氯', value: '0.2', unit: 'mg/L' },
{ name: 'TDS', value: '220', unit: 'mg/L' }
]
}
}
if (!Array.isArray(record.result)) {
record.result = []
}
if (!record.description) {
record.description = record.level ? `当前水质等级为${record.level},指标表现正常。` : '暂无水质描述'
}
if (!record.date) {
record.date = '暂无检测时间'
}
const latestRecord = {
id: record.id || `latest_${sn}`,
name: record.name || deviceDetail.value.name,
sn,
level: record.level || '一般',
date: record.date,
description: record.description,
result: record.result
}
latestWaterQuality.value = latestRecord
try {
uni.setStorageSync(`waterQualityLatest_${sn}`, latestRecord)
} catch (e) {}
}
// 寿
const updateFilterLife = () => {
deviceDetail.value.filters.forEach(filter => {
@ -269,6 +398,41 @@ const updateFilterLife = () => {
}
})
}
const getWaterLevelClass = (level) => {
if (level === '优质') return 'level-good'
if (level === '良好') return 'level-normal'
if (level === '一般') return 'level-warning'
return 'level-danger'
}
const getIndicatorStatusClass = (indicator) => {
const standard = waterQualityStandards[indicator.name]
if (!standard) return 'status-default'
const value = parseFloat(indicator.value)
if (isNaN(value)) return 'status-default'
if (standard.min !== undefined && value < standard.min) {
return 'status-low'
}
if (standard.max !== undefined && value > standard.max) {
return 'status-high'
}
return 'status-normal'
}
const getIndicatorStatusText = (indicator) => {
const standard = waterQualityStandards[indicator.name]
if (!standard) return '未知'
const value = parseFloat(indicator.value)
if (isNaN(value)) return '未知'
if (standard.min !== undefined && value < standard.min) {
return '偏低'
}
if (standard.max !== undefined && value > standard.max) {
return '偏高'
}
return '正常'
}
</script>
<style lang="scss" scoped>
@ -334,7 +498,8 @@ const updateFilterLife = () => {
}
.filter-section,
.info-section {
.info-section,
.water-quality-section {
background: #ffffff;
margin: 0 30rpx 20rpx;
padding: 30rpx;
@ -436,6 +601,139 @@ const updateFilterLife = () => {
flex: 1;
}
.water-quality-card {
display: flex;
flex-direction: column;
gap: 24rpx;
background: linear-gradient(180deg, rgba(64, 158, 255, 0.12) 0%, rgba(64, 158, 255, 0.05) 100%);
border-radius: 16rpx;
padding: 24rpx;
}
.quality-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.quality-level {
font-size: 28rpx;
font-weight: bold;
padding: 6rpx 18rpx;
border-radius: 20rpx;
background: #ffffff;
}
.level-good {
color: #67c23a;
}
.level-normal {
color: #409eff;
}
.level-warning {
color: #e6a23c;
}
.level-danger {
color: #f56c6c;
}
.quality-date {
font-size: 24rpx;
color: #606266;
}
.quality-desc {
font-size: 26rpx;
color: #303133;
}
.indicator-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
}
.indicator-item {
display: flex;
flex-direction: column;
gap: 8rpx;
background: #ffffff;
border-radius: 14rpx;
padding: 20rpx;
box-shadow: 0 6rpx 16rpx rgba(64, 158, 255, 0.08);
}
.indicator-name {
font-size: 26rpx;
color: #606266;
}
.indicator-value {
font-size: 30rpx;
font-weight: bold;
color: #303133;
}
.indicator-status {
font-size: 22rpx;
padding: 4rpx 16rpx;
border-radius: 16rpx;
align-self: flex-start;
}
.status-normal {
background: rgba(103, 194, 58, 0.15);
color: #67c23a;
}
.status-low {
background: rgba(230, 162, 60, 0.15);
color: #e6a23c;
}
.status-high {
background: rgba(245, 108, 108, 0.15);
color: #f56c6c;
}
.status-default {
background: rgba(144, 147, 153, 0.15);
color: #909399;
}
.quality-actions {
display: flex;
gap: 20rpx;
}
.quality-btn {
flex: 1;
height: 72rpx;
line-height: 72rpx;
border-radius: 36rpx;
font-size: 26rpx;
font-weight: 500;
border: none;
background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
color: #ffffff;
}
.quality-btn.outline {
background: #ffffff;
color: #409eff;
border: 2rpx solid #409eff;
}
.water-quality-empty {
padding: 80rpx 0;
text-align: center;
color: #909399;
font-size: 26rpx;
}
.action-section {
padding: 30rpx;
display: flex;

@ -1,33 +1,64 @@
<template>
<view class="index-container">
<!-- 顶部搜索栏 -->
<view class="search-section">
<view class="search-wrapper" @click="handleSearch">
<text class="search-icon">🔍</text>
<text class="search-placeholder">搜索服务内容</text>
<!-- 设备预警提示 -->
<view v-if="!isStaff" class="alert-section">
<view class="alert-card">
<view class="section-title-wrapper">
<text class="section-title">问题设备</text>
<view v-if="problemDeviceOverflow" class="alert-dot"></view>
</view>
</view>
<!-- 近七天用水量图表 -->
<view v-if="!isStaff" class="chart-section">
<view class="section-title">近七天用水量</view>
<view class="chart-container">
<canvas
canvas-id="waterChart"
id="waterChart"
class="chart-canvas"
@touchstart="touchStart"
@touchmove="touchMove"
@touchend="touchEnd"
></canvas>
<view v-if="displayedProblemDevices.length" class="alert-list">
<view
class="alert-item"
v-for="(device, index) in displayedProblemDevices"
:key="index"
@click="handleProblemDeviceClick(device)"
>
<view class="alert-header">
<text class="alert-name">{{ device.name }}</text>
<text class="alert-tag" :class="getTagClass(device.tagType)">{{ device.tag }}</text>
</view>
<view class="alert-body">
<text class="alert-desc">{{ device.description }}</text>
<text class="alert-address">位置{{ device.address }}</text>
</view>
<view class="alert-footer">
<text class="alert-link">查看问题设备列表 </text>
</view>
</view>
</view>
<view class="chart-legend">
<view class="legend-item" v-for="(item, index) in waterData" :key="index">
<text class="legend-date">{{ item.date }}</text>
<text class="legend-value">{{ item.value }}</text>
<view v-else class="alert-empty">
<text>暂无问题设备</text>
</view>
</view>
<view class="alert-card">
<view class="section-title">水质预警</view>
<view v-if="waterQualityAlerts.length" class="alert-list">
<view
class="alert-item"
v-for="(item, index) in displayedWaterQualityAlerts"
:key="index"
@click="handleWaterQualityAlertClick(item)"
>
<view class="alert-header">
<text class="alert-name">{{ item.name }}</text>
<text class="alert-tag" :class="getTagClass(item.tagType)">{{ item.level }}</text>
</view>
<view class="alert-body">
<text class="alert-desc">{{ item.description }}</text>
<text class="alert-address">上次检测{{ item.lastChecked }}</text>
</view>
<view class="alert-footer">
<text class="alert-link">查看水质预警详情 </text>
</view>
</view>
</view>
<view v-else class="alert-empty">
<text>暂无水质异常水质表现良好</text>
</view>
</view>
</view>
<!-- 轮播图 -->
<view class="banner-section">
@ -82,27 +113,67 @@
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import { ref, computed, onMounted, watch } from 'vue'
//
const userRole = ref('user')
const isStaff = computed(() => userRole.value === 'staff')
//
const waterData = ref([
{ date: '1/9', value: 12.5 },
{ date: '1/10', value: 15.2 },
{ date: '1/11', value: 13.8 },
{ date: '1/12', value: 14.6 },
{ date: '1/13', value: 16.3 },
{ date: '1/14', value: 15.9 },
{ date: '1/15', value: 14.2 }
//
const problemDevices = ref([
{
name: '净水器-003',
sn: 'JSQ20240105003',
tag: '离线',
tagType: 'danger',
description: '设备已离线超过 8 小时,请检查电源与网络连接',
address: 'XX路88号'
},
{
name: '净水器-002',
sn: 'JSQ20240112002',
tag: '滤芯预警',
tagType: 'warning',
description: '主滤芯寿命低于 30%,建议尽快预约更换',
address: 'XX小区3栋2单元501'
}
])
// Canvas
const chartCtx = ref(null)
const chartWidth = ref(690) // 690rpx px
const chartHeight = ref(400)
const displayedProblemDevices = computed(() => problemDevices.value.slice(0, 2))
const problemDeviceOverflow = computed(() => problemDevices.value.length > 2)
const displayedWaterQualityAlerts = computed(() => waterQualityAlerts.value.slice(0, 2))
watch(problemDevices, (newVal) => {
try {
uni.setStorageSync('problemDevices', newVal || [])
} catch (e) {}
}, { deep: true })
watch(waterQualityAlerts, (newVal) => {
try {
uni.setStorageSync('waterQualityAlerts', newVal || [])
} catch (e) {}
}, { deep: true })
//
const waterQualityAlerts = ref([
{
name: '净水器-002',
sn: 'JSQ20240112002',
level: '一般',
tagType: 'warning',
description: '上次检测显示浊度接近上限,建议再次检测确认',
lastChecked: '2024-01-10'
},
{
name: '净水器-003',
sn: 'JSQ20240105003',
level: '较差',
tagType: 'danger',
description: '余氯低于安全值,建议暂停饮用并联系工作人员',
lastChecked: '2024-01-08'
}
])
//
const banners = ref([
@ -135,14 +206,6 @@ const stats = ref([
{ label: '待审核', value: '5' }
])
//
const handleSearch = () => {
uni.showToast({
title: '搜索功能开发中',
icon: 'none'
})
}
//
const handleBannerClick = (banner) => {
uni.showToast({
@ -178,6 +241,30 @@ const handleServiceClick = (service) => {
}
}
const handleProblemDeviceClick = (device) => {
try {
uni.setStorageSync('problemDevices', problemDevices.value || [])
if (device && device.sn) {
uni.setStorageSync('problemDeviceSelectedSn', device.sn)
}
} catch (e) {}
uni.navigateTo({
url: '/pages/device-problem/device-problem'
})
}
const handleWaterQualityAlertClick = (alert) => {
try {
uni.setStorageSync('waterQualityAlerts', waterQualityAlerts.value || [])
if (alert && alert.sn) {
uni.setStorageSync('waterQualityAlertSelectedSn', alert.sn)
}
} catch (e) {}
uni.navigateTo({
url: '/pages/water-quality-alert/water-quality-alert'
})
}
//
const handleNoticeClick = (notice) => {
uni.showToast({
@ -186,115 +273,27 @@ const handleNoticeClick = (notice) => {
})
}
// 线
const drawChart = () => {
if (!chartCtx.value || isStaff.value) return
//
uni.getSystemInfo({
success: (res) => {
const pixelRatio = res.pixelRatio || 1
const canvasWidth = (chartWidth.value / 750) * res.windowWidth
const canvasHeight = (chartHeight.value / 750) * res.windowWidth
const ctx = chartCtx.value
const padding = 60
const w = canvasWidth - padding * 2
const h = canvasHeight - padding * 2
//
const values = waterData.value.map(item => item.value)
const maxValue = Math.max(...values)
const minValue = Math.min(...values)
const range = maxValue - minValue || 1
//
ctx.setFillStyle('#ffffff')
ctx.fillRect(0, 0, canvasWidth, canvasHeight)
// 线
ctx.setStrokeStyle('#e4e7ed')
ctx.setLineWidth(1)
for (let i = 0; i <= 4; i++) {
const y = padding + (h / 4) * i
ctx.beginPath()
ctx.moveTo(padding, y)
ctx.lineTo(padding + w, y)
ctx.stroke()
}
// Y
ctx.setFillStyle('#909399')
ctx.setFontSize(20)
ctx.setTextAlign('right')
for (let i = 0; i <= 4; i++) {
const value = maxValue - (range / 4) * i
const y = padding + (h / 4) * i + 5
ctx.fillText(value.toFixed(1), padding - 10, y)
}
// 线
ctx.setStrokeStyle('#409eff')
ctx.setLineWidth(3)
ctx.beginPath()
waterData.value.forEach((item, index) => {
const x = padding + (w / (waterData.value.length - 1)) * index
const y = padding + h - ((item.value - minValue) / range) * h
if (index === 0) {
ctx.moveTo(x, y)
} else {
ctx.lineTo(x, y)
}
})
ctx.stroke()
//
waterData.value.forEach((item, index) => {
const x = padding + (w / (waterData.value.length - 1)) * index
const y = padding + h - ((item.value - minValue) / range) * h
//
ctx.setFillStyle('#409eff')
ctx.beginPath()
ctx.arc(x, y, 6, 0, Math.PI * 2)
ctx.fill()
//
ctx.setFillStyle('#ffffff')
ctx.beginPath()
ctx.arc(x, y, 3, 0, Math.PI * 2)
ctx.fill()
})
// X
ctx.setFillStyle('#606266')
ctx.setFontSize(12)
ctx.setTextAlign('center')
waterData.value.forEach((item, index) => {
const x = padding + (w / (waterData.value.length - 1)) * index
const y = canvasHeight - 20
ctx.fillText(item.date, x, y)
})
//
ctx.draw()
const handleDeviceDetail = (sn) => {
if (!sn) {
uni.showToast({
title: '设备信息缺失',
icon: 'none'
})
return
}
const url = `/pages/device-detail/device-detail?sn=${sn}`
uni.navigateTo({
url,
fail: () => {
uni.redirectTo({ url })
}
})
}
//
const touchStart = (e) => {
//
}
const touchMove = (e) => {
//
}
const touchEnd = (e) => {
//
const getTagClass = (type) => {
if (type === 'danger') return 'tag-danger'
if (type === 'warning') return 'tag-warning'
return 'tag-info'
}
//
@ -303,29 +302,21 @@ onMounted(() => {
if (role) {
userRole.value = role
}
//
if (!isStaff.value) {
setTimeout(() => {
// #ifdef MP-WEIXIN
chartCtx.value = uni.createCanvasContext('waterChart')
// #endif
// #ifndef MP-WEIXIN
const query = uni.createSelectorQuery()
query.select('#waterChart').fields({ node: true, size: true }).exec((res) => {
if (res[0]) {
const canvas = res[0].node
chartCtx.value = canvas.getContext('2d')
drawChart()
}
})
// #endif
// #ifdef MP-WEIXIN
drawChart()
// #endif
}, 300)
const storedProblemDevices = uni.getStorageSync('problemDevices')
if (Array.isArray(storedProblemDevices) && storedProblemDevices.length) {
problemDevices.value = storedProblemDevices
} else {
try {
uni.setStorageSync('problemDevices', problemDevices.value || [])
} catch (e) {}
}
const storedWaterAlerts = uni.getStorageSync('waterQualityAlerts')
if (Array.isArray(storedWaterAlerts) && storedWaterAlerts.length) {
waterQualityAlerts.value = storedWaterAlerts
} else {
try {
uni.setStorageSync('waterQualityAlerts', waterQualityAlerts.value || [])
} catch (e) {}
}
})
</script>
@ -337,29 +328,6 @@ onMounted(() => {
padding-bottom: 120rpx;
}
.search-section {
padding: 20rpx 30rpx;
background: #ffffff;
}
.search-wrapper {
display: flex;
align-items: center;
background: #f5f5f5;
border-radius: 50rpx;
padding: 20rpx 30rpx;
}
.search-icon {
font-size: 32rpx;
margin-right: 12rpx;
}
.search-placeholder {
font-size: 28rpx;
color: #909399;
}
.banner-section {
margin: 20rpx 30rpx;
}
@ -389,10 +357,6 @@ onMounted(() => {
margin: 40rpx 30rpx;
}
.chart-section {
margin: 20rpx 30rpx;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
@ -400,83 +364,146 @@ onMounted(() => {
margin-bottom: 24rpx;
}
.chart-container {
.service-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 30rpx;
background: #ffffff;
padding: 40rpx 30rpx;
border-radius: 20rpx;
padding: 30rpx;
margin-bottom: 20rpx;
}
.chart-canvas {
width: 100%;
height: 400rpx;
}
.chart-legend {
background: #ffffff;
border-radius: 20rpx;
padding: 30rpx;
.service-item {
display: flex;
justify-content: space-around;
flex-direction: column;
align-items: center;
}
.legend-item {
.service-icon-wrapper {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin-bottom: 16rpx;
}
.service-icon {
font-size: 48rpx;
}
.legend-date {
.service-name {
font-size: 24rpx;
color: #909399;
margin-bottom: 8rpx;
color: #606266;
}
.legend-value {
font-size: 28rpx;
color: #409eff;
font-weight: bold;
.section-title-wrapper {
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #303133;
margin-bottom: 24rpx;
.alert-dot {
width: 16rpx;
height: 16rpx;
border-radius: 50%;
background: #f56c6c;
position: absolute;
top: 6rpx;
right: 0;
}
.service-grid {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 30rpx;
.alert-section {
margin: 20rpx 30rpx 0;
display: flex;
flex-direction: column;
gap: 20rpx;
}
.alert-card {
background: #ffffff;
padding: 40rpx 30rpx;
border-radius: 20rpx;
padding: 32rpx 30rpx;
}
.service-item {
.alert-list {
display: flex;
flex-direction: column;
align-items: center;
gap: 20rpx;
}
.service-icon-wrapper {
width: 100rpx;
height: 100rpx;
border-radius: 50%;
.alert-item {
background: #f9fafc;
padding: 24rpx 28rpx;
border-radius: 16rpx;
display: flex;
flex-direction: column;
gap: 16rpx;
}
.alert-header {
display: flex;
justify-content: space-between;
align-items: center;
justify-content: center;
margin-bottom: 16rpx;
}
.service-icon {
font-size: 48rpx;
.alert-name {
font-size: 30rpx;
font-weight: bold;
color: #303133;
}
.service-name {
font-size: 24rpx;
.alert-tag {
padding: 6rpx 18rpx;
border-radius: 24rpx;
font-size: 22rpx;
}
.tag-danger {
background: rgba(245, 108, 108, 0.15);
color: #f56c6c;
}
.tag-warning {
background: rgba(230, 162, 60, 0.15);
color: #e6a23c;
}
.tag-info {
background: rgba(64, 158, 255, 0.15);
color: #409eff;
}
.alert-body {
display: flex;
flex-direction: column;
gap: 8rpx;
color: #606266;
font-size: 26rpx;
}
.alert-address {
color: #909399;
font-size: 24rpx;
}
.alert-footer {
display: flex;
justify-content: flex-start;
}
.alert-link {
font-size: 26rpx;
color: #409eff;
}
.alert-empty {
padding: 40rpx 0;
text-align: center;
font-size: 26rpx;
color: #909399;
}
.notice-section {

@ -0,0 +1,281 @@
<template>
<view class="alert-page">
<view class="notice-bar">
<text class="notice-icon">💧</text>
<text class="notice-text">以下设备近期水质指标异常请及时复检或联系工作人员</text>
</view>
<scroll-view class="alert-list" scroll-y>
<view v-if="alerts.length === 0" class="empty-state">
<text class="empty-icon"></text>
<text class="empty-text">暂无水质预警</text>
</view>
<view
v-else
class="alert-card"
v-for="(item, index) in alerts"
:key="index"
>
<view class="card-header">
<text class="device-name">{{ item.name }}</text>
<text class="badge" :class="getBadgeClass(item.tagType)">{{ item.level || item.tag }}</text>
</view>
<view class="card-content">
<text class="row">设备编号{{ item.sn }}</text>
<text class="row">最近检测值{{ item.description }}</text>
<text class="row">最近检测时间{{ item.lastChecked || '暂无记录' }}</text>
</view>
<view class="card-actions">
<button class="action-btn detail-btn" @click="goDeviceDetail(item.sn)"></button>
<button class="action-btn report-btn" @click="goWaterQualityDetail(item)"></button>
<button class="action-btn contact-btn" @click="contactStaff(item)"></button>
</view>
</view>
</scroll-view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
const alerts = ref([])
onMounted(() => {
try {
const stored = uni.getStorageSync('waterQualityAlerts')
alerts.value = Array.isArray(stored) ? stored : []
if (alerts.value.length === 0) {
loadDefaultAlerts()
}
} catch (e) {
loadDefaultAlerts()
}
})
const loadDefaultAlerts = () => {
alerts.value = [
{
name: '净水器-002',
sn: 'JSQ20240112002',
level: '一般',
tagType: 'warning',
description: '浊度 0.95 NTU接近上限值',
lastChecked: '2024-01-10'
},
{
name: '净水器-003',
sn: 'JSQ20240105003',
level: '较差',
tagType: 'danger',
description: '余氯 0.01 mg/L低于安全范围',
lastChecked: '2024-01-08'
}
]
try {
uni.setStorageSync('waterQualityAlerts', alerts.value)
} catch (e) {}
}
const goDeviceDetail = (sn) => {
if (!sn) {
uni.showToast({
title: '设备信息缺失',
icon: 'none'
})
return
}
const url = `/pages/device-detail/device-detail?sn=${sn}`
uni.navigateTo({
url,
fail: () => {
uni.redirectTo({ url })
}
})
}
const goWaterQualityDetail = (item) => {
if (!item || !item.sn) {
uni.showToast({
title: '暂无检测记录',
icon: 'none'
})
return
}
try {
const recordId = `alert_${item.sn}`
const record = {
id: recordId,
date: item.lastChecked || '2024-01-10',
address: item.address || '暂无地址信息',
level: item.level || '一般',
conclusionTip: item.description || '近期水质指标需关注,请安排复检。',
warmTip: '建议立即预约水质复检,如需帮助可联系工作人员。',
result: item.result || [
{ name: '浊度', value: '0.95', unit: 'NTU' },
{ name: '余氯', value: '0.01', unit: 'mg/L' },
{ name: 'TDS', value: '650', unit: 'mg/L' }
]
}
uni.setStorageSync(`waterQualityRecord_${recordId}`, record)
uni.navigateTo({
url: `/pages/water-quality-detail/water-quality-detail?recordId=${recordId}`
})
} catch (e) {
uni.showToast({
title: '加载报告失败',
icon: 'none'
})
}
}
const contactStaff = (item) => {
uni.showToast({
title: '已通知工作人员跟进',
icon: 'none'
})
}
const getBadgeClass = (type) => {
if (type === 'danger') return 'badge-danger'
if (type === 'warning') return 'badge-warning'
return 'badge-info'
}
</script>
<style lang="scss" scoped>
.alert-page {
min-height: 100vh;
background: #f5f5f5;
display: flex;
flex-direction: column;
}
.notice-bar {
display: flex;
align-items: center;
padding: 24rpx 30rpx;
background: #ecf5ff;
color: #409eff;
}
.notice-icon {
font-size: 32rpx;
margin-right: 12rpx;
}
.notice-text {
font-size: 26rpx;
}
.alert-list {
flex: 1;
padding: 20rpx 30rpx 40rpx;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 200rpx 0;
color: #909399;
}
.empty-icon {
font-size: 120rpx;
margin-bottom: 30rpx;
opacity: 0.5;
}
.empty-text {
font-size: 28rpx;
}
.alert-card {
background: #ffffff;
border-radius: 20rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.04);
display: flex;
flex-direction: column;
gap: 20rpx;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
.device-name {
font-size: 30rpx;
font-weight: 600;
color: #303133;
}
.badge {
font-size: 22rpx;
padding: 6rpx 16rpx;
border-radius: 24rpx;
}
.badge-danger {
background: rgba(245, 108, 108, 0.15);
color: #f56c6c;
}
.badge-warning {
background: rgba(230, 162, 60, 0.15);
color: #e6a23c;
}
.badge-info {
background: rgba(64, 158, 255, 0.15);
color: #409eff;
}
.card-content {
display: flex;
flex-direction: column;
gap: 12rpx;
font-size: 26rpx;
color: #606266;
}
.row {
font-size: 26rpx;
}
.card-actions {
display: flex;
gap: 16rpx;
}
.action-btn {
flex: 1;
height: 72rpx;
line-height: 72rpx;
border-radius: 36rpx;
font-size: 26rpx;
border: none;
}
.detail-btn {
background: #ffffff;
color: #409eff;
border: 2rpx solid #409eff;
}
.report-btn {
background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
color: #ffffff;
}
.contact-btn {
background: #fef0f0;
color: #f56c6c;
}
</style>

@ -0,0 +1,462 @@
<template>
<view class="detail-container">
<view v-if="record" class="content">
<view class="header-card">
<view class="status-row">
<view class="status-badge" :class="getStatusClass(record.status)">
<text>{{ record.statusText || '检测中' }}</text>
</view>
<text class="report-date">{{ record.date }}</text>
</view>
<view class="quality-row">
<text class="quality-label">水质等级</text>
<text class="quality-value" :style="{ color: getQualityColor(record.level) }">
{{ record.level || '待评估' }}
</text>
</view>
<view class="address-row">
<text class="address-icon">📍</text>
<text class="address-text">{{ record.address || '暂无地址信息' }}</text>
</view>
</view>
<view class="section-card">
<view class="section-header">
<text class="section-title">检测指标</text>
<text class="section-subtitle">采样时间{{ record.date }}</text>
</view>
<view v-if="hasResult" class="indicator-grid">
<view class="indicator-item" v-for="(item, index) in record.result" :key="index">
<text class="indicator-name">{{ item.name }}</text>
<text class="indicator-value">{{ item.value }}</text>
<text class="indicator-unit">{{ item.unit }}</text>
<text class="indicator-standard">标准范围{{ getStandardRange(item.name) }}</text>
</view>
</view>
<view v-else class="empty-indicator">
<text>检测数据生成中请稍后查看</text>
</view>
</view>
<view class="section-card">
<view class="section-header">
<text class="section-title">检测结论</text>
</view>
<view class="conclusion-block">
<text class="conclusion-text">
{{ record.conclusion || defaultConclusion }}
</text>
</view>
<view v-if="tips.length" class="tips-list">
<view class="tips-item" v-for="(tip, index) in tips" :key="index">
<text class="tips-icon">💡</text>
<text class="tips-text">{{ tip }}</text>
</view>
</view>
</view>
<view class="section-card">
<view class="section-header">
<text class="section-title">服务操作</text>
</view>
<view class="action-group">
<button class="action-btn primary" @click="handleDownload"></button>
<button class="action-btn secondary" @click="handleContact"></button>
</view>
</view>
</view>
<view v-else class="empty-state">
<text class="empty-icon">📄</text>
<text class="empty-title">未找到检测报告</text>
<text class="empty-desc">请返回上一页重新选择检测记录</text>
<button class="back-btn" @click="handleBack"></button>
</view>
</view>
</template>
<script setup>
import { computed, ref } from 'vue'
import { onLoad, onUnload } from '@dcloudio/uni-app'
const record = ref(null)
onLoad(() => {
const cachedRecord = uni.getStorageSync('waterQualityCurrentRecord')
if (cachedRecord) {
record.value = cachedRecord
}
})
onUnload(() => {
uni.removeStorageSync('waterQualityCurrentRecord')
})
const indicatorStandards = {
'pH值': {
range: '6.5 - 8.5'
},
'浊度': {
range: '≤ 1 NTU出厂 / ≤ 3 NTU末梢'
},
'余氯': {
range: '0.3 - 0.5 mg/L'
},
'总硬度': {
range: '≤ 450 mg/L以CaCO₃计'
},
'TDS': {
range: '≤ 1000 mg/L'
}
}
const hasResult = computed(() => {
return record.value && Array.isArray(record.value.result) && record.value.result.length > 0
})
const defaultConclusion = '水质整体表现良好,符合日常饮用和使用需求。如需进一步检测或设备维护,请联系工作人员。'
const tips = computed(() => {
if (!record.value) {
return []
}
const list = []
if (record.value.level === '优质') {
list.push('持续保持当前设备状态,建议每月查看一次检测结果。')
}
if (record.value.level === '良好') {
list.push('建议关注余氯与浊度变化,适时预约滤芯检测。')
}
if (record.value.level === '一般' || record.value.level === '较差') {
list.push('建议尽快预约设备维护,检查滤芯或供水管路。')
list.push('如有异味或水色异常,请立即停止饮用并联系工作人员。')
}
if (!list.length) {
list.push('检测结果将用于优化设备运行,请保持关注。')
}
return list
})
const getStatusClass = (status) => {
if (status === 'completed') {
return 'status-completed'
}
if (status === 'processing') {
return 'status-processing'
}
return 'status-pending'
}
const getQualityColor = (level) => {
const colorMap = {
'优质': '#67c23a',
'良好': '#409eff',
'一般': '#e6a23c',
'较差': '#f56c6c'
}
return colorMap[level] || '#909399'
}
const getStandardRange = (name) => {
return indicatorStandards[name]?.range || '参考标准更新中'
}
const handleDownload = () => {
uni.showToast({
title: '正在生成报告...',
icon: 'none'
})
setTimeout(() => {
uni.showToast({
title: '报告生成成功',
icon: 'success'
})
}, 800)
}
const handleContact = () => {
uni.showModal({
title: '联系工作人员',
content: '是否拨打客服热线 400-800-1234',
success: (res) => {
if (res.confirm) {
uni.makePhoneCall({
phoneNumber: '4008001234',
fail: () => {
uni.showToast({
title: '拨号失败,请稍后重试',
icon: 'none'
})
}
})
}
}
})
}
const handleBack = () => {
uni.navigateBack({
fail: () => {
uni.reLaunch({
url: '/pages/water-quality/water-quality'
})
}
})
}
</script>
<style lang="scss" scoped>
.detail-container {
min-height: 100vh;
background: #f5f5f5;
padding: 30rpx 30rpx 120rpx;
box-sizing: border-box;
}
.content {
display: flex;
flex-direction: column;
gap: 24rpx;
}
.header-card {
background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
border-radius: 24rpx;
padding: 40rpx 34rpx;
color: #ffffff;
box-shadow: 0 18rpx 40rpx -20rpx rgba(64, 158, 255, 0.8);
}
.status-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
}
.status-badge {
padding: 12rpx 24rpx;
border-radius: 28rpx;
font-size: 24rpx;
background: rgba(255, 255, 255, 0.2);
}
.status-badge.status-completed {
background: rgba(103, 194, 58, 0.2);
color: #67c23a;
}
.status-badge.status-processing {
background: rgba(230, 162, 60, 0.2);
color: #e6a23c;
}
.status-badge.status-pending {
background: rgba(255, 255, 255, 0.2);
color: #ffffff;
}
.report-date {
font-size: 26rpx;
}
.quality-row {
display: flex;
align-items: baseline;
gap: 20rpx;
margin-bottom: 20rpx;
}
.quality-label {
font-size: 26rpx;
opacity: 0.8;
}
.quality-value {
font-size: 48rpx;
font-weight: bold;
}
.address-row {
display: flex;
align-items: center;
gap: 12rpx;
font-size: 26rpx;
opacity: 0.9;
}
.section-card {
background: #ffffff;
border-radius: 20rpx;
padding: 34rpx 30rpx;
box-shadow: 0 10rpx 20rpx -16rpx rgba(0, 0, 0, 0.12);
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #303133;
}
.section-subtitle {
font-size: 24rpx;
color: #909399;
}
.indicator-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
}
.indicator-item {
background: #f5f7fa;
border-radius: 16rpx;
padding: 24rpx;
display: flex;
flex-direction: column;
gap: 10rpx;
}
.indicator-name {
font-size: 26rpx;
color: #606266;
}
.indicator-value {
font-size: 36rpx;
font-weight: bold;
color: #303133;
}
.indicator-unit {
font-size: 24rpx;
color: #909399;
}
.indicator-standard {
font-size: 24rpx;
color: #606266;
}
.empty-indicator {
text-align: center;
font-size: 26rpx;
color: #909399;
padding: 40rpx 20rpx;
}
.conclusion-block {
background: linear-gradient(135deg, rgba(64, 158, 255, 0.08) 0%, rgba(102, 177, 255, 0.12) 100%);
border-radius: 18rpx;
padding: 30rpx;
margin-bottom: 24rpx;
}
.conclusion-text {
font-size: 28rpx;
color: #303133;
line-height: 1.6;
}
.tips-list {
display: flex;
flex-direction: column;
gap: 18rpx;
}
.tips-item {
display: flex;
align-items: center;
gap: 16rpx;
background: #f9fafc;
border-radius: 14rpx;
padding: 20rpx 24rpx;
}
.tips-icon {
font-size: 32rpx;
}
.tips-text {
font-size: 26rpx;
color: #606266;
}
.action-group {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.action-btn {
height: 88rpx;
line-height: 88rpx;
border-radius: 44rpx;
font-size: 30rpx;
font-weight: bold;
border: none;
&::after {
border: none;
}
}
.action-btn.primary {
background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
color: #ffffff;
}
.action-btn.secondary {
background: #ecf5ff;
color: #409eff;
}
.empty-state {
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16rpx;
color: #909399;
}
.empty-icon {
font-size: 120rpx;
margin-bottom: 10rpx;
}
.empty-title {
font-size: 32rpx;
color: #303133;
}
.empty-desc {
font-size: 26rpx;
margin-bottom: 20rpx;
}
.back-btn {
width: 260rpx;
height: 80rpx;
line-height: 80rpx;
border-radius: 40rpx;
background: #409eff;
color: #ffffff;
font-size: 28rpx;
border: none;
&::after {
border: none;
}
}
</style>

@ -57,9 +57,13 @@
import { ref } from 'vue'
import { onLoad } from '@dcloudio/uni-app'
//
const records = ref([
const records = ref([])
const currentSn = ref('')
const defaultRecords = [
{
id: 'WQ20240115001',
sn: 'JSQ20240115001',
date: '2024-01-15',
address: 'XX小区3栋2单元501',
status: 'completed',
@ -73,6 +77,8 @@ const records = ref([
]
},
{
id: 'WQ20240110002',
sn: 'JSQ20240105003',
date: '2024-01-10',
address: 'XX路88号',
status: 'completed',
@ -85,33 +91,38 @@ const records = ref([
]
},
{
id: 'WQ20240108003',
sn: 'JSQ20240115001',
date: '2024-01-08',
address: 'XX小区3栋2单元501',
status: 'processing',
statusText: '检测中',
level: '-'
}
])
// SN
const currentSn = ref('')
]
const loadRecords = (sn) => {
let storedRecords = []
try {
storedRecords = uni.getStorageSync('waterQualityRecords') || []
if (!Array.isArray(storedRecords) || storedRecords.length === 0) {
storedRecords = defaultRecords
uni.setStorageSync('waterQualityRecords', storedRecords)
}
} catch (e) {
storedRecords = defaultRecords
}
// SN
const snToAddress = {
'JSQ20240115001': 'XX小区3栋2单元501',
'JSQ20240112002': 'XX小区3栋2单元501',
'JSQ20240105003': 'XX路88号'
if (sn) {
records.value = storedRecords.filter(record => record.sn === sn)
} else {
records.value = storedRecords
}
}
// sn
onLoad((options) => {
if (options && options.sn) {
currentSn.value = options.sn
const address = snToAddress[currentSn.value]
if (address) {
records.value = records.value.filter(r => r.address === address)
}
}
currentSn.value = options?.sn || ''
loadRecords(currentSn.value)
})
//
@ -137,12 +148,18 @@ const handleRecordClick = (record) => {
}
}
//
const handleViewReport = (record) => {
uni.showToast({
title: '查看检测报告',
icon: 'none'
})
try {
uni.setStorageSync('waterQualityCurrentRecord', record)
uni.navigateTo({
url: '/pages/water-quality-detail/water-quality-detail'
})
} catch (error) {
uni.showToast({
title: '打开报告失败,请稍后重试',
icon: 'none'
})
}
}
//

@ -1 +1 @@
{"version":3,"file":"app.js","sources":["App.vue","main.js"],"sourcesContent":["<script setup>\r\nimport { onLaunch, onShow, onHide } from '@dcloudio/uni-app'\r\nimport { setTabBar } from './utils/tabBar.js'\r\n\r\nonLaunch(() => {\r\n\tconsole.log('App Launch - 数智水管家')\r\n\t// 根据存储的角色设置 tabBar\r\n\tconst userRole = uni.getStorageSync('userRole') || 'user'\r\n\tsetTabBar(userRole)\r\n})\r\n\r\nonShow(() => {\r\n\tconsole.log('App Show')\r\n})\r\n\r\nonHide(() => {\r\n\tconsole.log('App Hide')\r\n})\r\n</script>\r\n\r\n<style>\r\n/* 全局样式 */\r\npage {\r\n\tbackground-color: #f5f5f5;\r\n\tfont-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;\r\n}\r\n</style>\r\n","import App from './App'\n\n// #ifndef VUE3\nimport Vue from 'vue'\nimport './uni.promisify.adaptor'\nVue.config.productionTip = false\nApp.mpType = 'app'\nconst app = new Vue({\n ...App\n})\napp.$mount()\n// #endif\n\n// #ifdef VUE3\nimport { createSSRApp } from 'vue'\nexport function createApp() {\n const app = createSSRApp(App)\n return {\n app\n }\n}\n// #endif"],"names":["onLaunch","uni","setTabBar","onShow","onHide","createSSRApp","App"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIAA,kBAAAA,SAAS,MAAM;AACdC,oBAAAA,MAAA,MAAA,OAAA,gBAAY,oBAAoB;AAEhC,YAAM,WAAWA,cAAG,MAAC,eAAe,UAAU,KAAK;AACnDC,mBAAAA,UAAU,QAAQ;AAAA,IACnB,CAAC;AAEDC,kBAAAA,OAAO,MAAM;AACZF,oBAAAA,MAAY,MAAA,OAAA,iBAAA,UAAU;AAAA,IACvB,CAAC;AAEDG,kBAAAA,OAAO,MAAM;AACZH,oBAAAA,MAAY,MAAA,OAAA,iBAAA,UAAU;AAAA,IACvB,CAAC;;;;;ACFM,SAAS,YAAY;AAC1B,QAAM,MAAMI,cAAY,aAACC,SAAG;AAC5B,SAAO;AAAA,IACL;AAAA,EACD;AACH;;;"}
{"version":3,"file":"app.js","sources":["App.vue","main.js"],"sourcesContent":["<script setup>\r\nimport { onLaunch, onShow, onHide } from '@dcloudio/uni-app'\r\nimport { setTabBar } from './utils/tabBar.js'\r\n\r\nonLaunch(() => {\r\n\tconsole.log('App Launch - 数智水管家')\r\n\t// 根据存储的角色设置 tabBar\r\n\tconst userRole = uni.getStorageSync('userRole') || 'user'\r\n\tsetTabBar(userRole)\r\n})\r\n\r\nonShow(() => {\r\n\tconsole.log('App Show')\r\n})\r\n\r\nonHide(() => {\r\n\tconsole.log('App Hide')\r\n})\r\n</script>\r\n\r\n<style>\r\n/* 全局样式 */\r\npage {\r\n\tbackground-color: #f5f5f5;\r\n\tfont-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;\r\n}\r\n</style>\r\n","import App from './App'\n\n// #ifndef VUE3\nimport Vue from 'vue'\nimport './uni.promisify.adaptor'\nVue.config.productionTip = false\nApp.mpType = 'app'\nconst app = new Vue({\n ...App\n})\napp.$mount()\n// #endif\n\n// #ifdef VUE3\nimport { createSSRApp } from 'vue'\nexport function createApp() {\n const app = createSSRApp(App)\n return {\n app\n }\n}\n// #endif"],"names":["onLaunch","uni","setTabBar","onShow","onHide","createSSRApp","App"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAIAA,kBAAAA,SAAS,MAAM;AACdC,oBAAAA,MAAA,MAAA,OAAA,gBAAY,oBAAoB;AAEhC,YAAM,WAAWA,cAAG,MAAC,eAAe,UAAU,KAAK;AACnDC,mBAAAA,UAAU,QAAQ;AAAA,IACnB,CAAC;AAEDC,kBAAAA,OAAO,MAAM;AACZF,oBAAAA,MAAY,MAAA,OAAA,iBAAA,UAAU;AAAA,IACvB,CAAC;AAEDG,kBAAAA,OAAO,MAAM;AACZH,oBAAAA,MAAY,MAAA,OAAA,iBAAA,UAAU;AAAA,IACvB,CAAC;;;;;ACFM,SAAS,YAAY;AAC1B,QAAM,MAAMI,cAAY,aAACC,SAAG;AAC5B,SAAO;AAAA,IACL;AAAA,EACD;AACH;;;"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

@ -18,6 +18,8 @@ if (!Math) {
"./pages/purchase/purchase.js";
"./pages/device/device.js";
"./pages/water-quality/water-quality.js";
"./pages/water-quality-detail/water-quality-detail.js";
"./pages/water-quality-alert/water-quality-alert.js";
"./pages/customer-service/customer-service.js";
"./pages/order-detail/order-detail.js";
"./pages/device-detail/device-detail.js";

@ -15,6 +15,8 @@
"pages/purchase/purchase",
"pages/device/device",
"pages/water-quality/water-quality",
"pages/water-quality-detail/water-quality-detail",
"pages/water-quality-alert/water-quality-alert",
"pages/customer-service/customer-service",
"pages/order-detail/order-detail",
"pages/device-detail/device-detail",

@ -6938,9 +6938,9 @@ function isConsoleWritable() {
return isWritable;
}
function initRuntimeSocketService() {
const hosts = "172.27.224.1,10.158.12.22,127.0.0.1,192.168.17.1,192.168.86.1,192.168.128.1";
const hosts = "10.158.4.197,127.0.0.1,192.168.17.1,192.168.86.1,172.26.96.1";
const port = "8090";
const id = "mp-weixin_YjRqzk";
const id = "mp-weixin_p8QEiR";
const lazy = typeof swan !== "undefined";
let restoreError = lazy ? () => {
} : initOnError();
@ -7909,6 +7909,11 @@ const onLoad = /* @__PURE__ */ createLifeCycleHook(
2
/* HookFlags.PAGE */
);
const onUnload = /* @__PURE__ */ createLifeCycleHook(
ON_UNLOAD,
2
/* HookFlags.PAGE */
);
exports._export_sfc = _export_sfc;
exports.computed = computed;
exports.createSSRApp = createSSRApp;
@ -7922,6 +7927,8 @@ exports.onLaunch = onLaunch;
exports.onLoad = onLoad;
exports.onMounted = onMounted;
exports.onShow = onShow;
exports.onUnload = onUnload;
exports.ref = ref;
exports.t = t;
exports.watch = watch;
//# sourceMappingURL=../../.sourcemap/mp-weixin/common/vendor.js.map

@ -39,6 +39,14 @@ const _sfc_main = {
}
]
});
const latestWaterQuality = common_vendor.ref(null);
const waterQualityStandards = {
"pH值": { min: 6.5, max: 8.5, unit: "" },
"浊度": { max: 1, unit: "NTU" },
"余氯": { min: 0.05, max: 4, unit: "mg/L" },
"总硬度": { max: 450, unit: "mg/L" },
"TDS": { max: 1e3, unit: "mg/L" }
};
const getStatusClass = (status) => {
return status === "online" ? "status-online" : "status-offline";
};
@ -90,6 +98,30 @@ const _sfc_main = {
const url = sn ? `/pages/water-quality/water-quality?sn=${sn}` : "/pages/water-quality/water-quality";
common_vendor.index.navigateTo({ url });
};
const handleWaterQualityDetail = () => {
if (!latestWaterQuality.value) {
common_vendor.index.showToast({
title: "暂无水质报告",
icon: "none"
});
return;
}
const recordId = latestWaterQuality.value.id || `latest_${deviceDetail.value.sn}`;
try {
common_vendor.index.setStorageSync(`waterQualityRecord_${recordId}`, {
...latestWaterQuality.value,
id: recordId
});
common_vendor.index.navigateTo({
url: `/pages/water-quality-detail/water-quality-detail?recordId=${recordId}`
});
} catch (e) {
common_vendor.index.showToast({
title: "打开报告失败",
icon: "none"
});
}
};
common_vendor.onMounted(() => {
const pages = getCurrentPages();
const currentPage = pages[pages.length - 1];
@ -97,6 +129,9 @@ const _sfc_main = {
const sn = options.sn;
if (sn) {
loadDeviceDetail(sn);
loadLatestWaterQuality(sn);
} else {
loadLatestWaterQuality(deviceDetail.value.sn);
}
const timer = setInterval(() => {
updateFilterLife();
@ -160,6 +195,69 @@ const _sfc_main = {
deviceDetail.value = deviceMap[sn];
}
};
const loadLatestWaterQuality = (sn) => {
let record = null;
try {
const allRecords = common_vendor.index.getStorageSync("waterQualityRecords");
if (Array.isArray(allRecords) && allRecords.length) {
const completedRecords = allRecords.filter((item) => item.sn === sn && item.status !== "processing").sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime());
if (completedRecords.length) {
record = completedRecords[0];
}
}
const alerts = common_vendor.index.getStorageSync("waterQualityAlerts");
if (!record && Array.isArray(alerts)) {
record = alerts.find((item) => item.sn === sn) || null;
}
if (!record) {
const stored = common_vendor.index.getStorageSync(`waterQualityLatest_${sn}`);
if (stored && typeof stored === "object") {
record = stored;
}
}
} catch (e) {
record = null;
}
if (!record) {
record = {
id: `latest_${sn}`,
name: deviceDetail.value.name,
sn,
level: "优质",
date: "2024-01-15",
description: "水质指标稳定,符合饮用标准",
result: [
{ name: "pH值", value: "7.2", unit: "" },
{ name: "浊度", value: "0.4", unit: "NTU" },
{ name: "余氯", value: "0.2", unit: "mg/L" },
{ name: "TDS", value: "220", unit: "mg/L" }
]
};
}
if (!Array.isArray(record.result)) {
record.result = [];
}
if (!record.description) {
record.description = record.level ? `当前水质等级为${record.level},指标表现正常。` : "暂无水质描述";
}
if (!record.date) {
record.date = "暂无检测时间";
}
const latestRecord = {
id: record.id || `latest_${sn}`,
name: record.name || deviceDetail.value.name,
sn,
level: record.level || "一般",
date: record.date,
description: record.description,
result: record.result
};
latestWaterQuality.value = latestRecord;
try {
common_vendor.index.setStorageSync(`waterQualityLatest_${sn}`, latestRecord);
} catch (e) {
}
};
const updateFilterLife = () => {
deviceDetail.value.filters.forEach((filter) => {
if (filter.remain > 0) {
@ -167,8 +265,47 @@ const _sfc_main = {
}
});
};
const getWaterLevelClass = (level) => {
if (level === "优质")
return "level-good";
if (level === "良好")
return "level-normal";
if (level === "一般")
return "level-warning";
return "level-danger";
};
const getIndicatorStatusClass = (indicator) => {
const standard = waterQualityStandards[indicator.name];
if (!standard)
return "status-default";
const value = parseFloat(indicator.value);
if (isNaN(value))
return "status-default";
if (standard.min !== void 0 && value < standard.min) {
return "status-low";
}
if (standard.max !== void 0 && value > standard.max) {
return "status-high";
}
return "status-normal";
};
const getIndicatorStatusText = (indicator) => {
const standard = waterQualityStandards[indicator.name];
if (!standard)
return "未知";
const value = parseFloat(indicator.value);
if (isNaN(value))
return "未知";
if (standard.min !== void 0 && value < standard.min) {
return "偏低";
}
if (standard.max !== void 0 && value > standard.max) {
return "偏高";
}
return "正常";
};
return (_ctx, _cache) => {
return {
return common_vendor.e({
a: common_vendor.t(deviceDetail.value.statusText),
b: common_vendor.n(getStatusClass(deviceDetail.value.status)),
c: common_vendor.t(deviceDetail.value.name),
@ -183,15 +320,34 @@ const _sfc_main = {
g: index
};
}),
e: common_vendor.t(deviceDetail.value.sn),
f: common_vendor.t(deviceDetail.value.type),
g: common_vendor.t(deviceDetail.value.filterModel),
h: common_vendor.t(deviceDetail.value.address),
i: common_vendor.t(deviceDetail.value.bindTime),
j: common_vendor.o(handleReplaceFilter),
k: common_vendor.o(handleDeviceConfig),
l: common_vendor.o(handleViewWaterQuality)
};
e: latestWaterQuality.value
}, latestWaterQuality.value ? {
f: common_vendor.t(latestWaterQuality.value.level),
g: common_vendor.n(getWaterLevelClass(latestWaterQuality.value.level)),
h: common_vendor.t(latestWaterQuality.value.date),
i: common_vendor.t(latestWaterQuality.value.description),
j: common_vendor.f(latestWaterQuality.value.result, (indicator, k0, i0) => {
return {
a: common_vendor.t(indicator.name),
b: common_vendor.t(indicator.value),
c: common_vendor.t(indicator.unit),
d: common_vendor.t(getIndicatorStatusText(indicator)),
e: common_vendor.n(getIndicatorStatusClass(indicator)),
f: indicator.name
};
}),
k: common_vendor.o(handleViewWaterQuality),
l: common_vendor.o(handleWaterQualityDetail)
} : {}, {
m: common_vendor.t(deviceDetail.value.sn),
n: common_vendor.t(deviceDetail.value.type),
o: common_vendor.t(deviceDetail.value.filterModel),
p: common_vendor.t(deviceDetail.value.address),
q: common_vendor.t(deviceDetail.value.bindTime),
r: common_vendor.o(handleReplaceFilter),
s: common_vendor.o(handleDeviceConfig),
t: common_vendor.o(handleViewWaterQuality)
});
};
}
};

@ -1 +1 @@
<view class="device-detail-container data-v-abb22ecf"><scroll-view class="detail-content data-v-abb22ecf" scroll-y><view class="status-card data-v-abb22ecf"><view class="device-icon-wrapper data-v-abb22ecf"><text class="device-icon data-v-abb22ecf">💧</text></view><text class="{{['device-status-text', 'data-v-abb22ecf', b]}}">{{a}}</text><text class="device-name data-v-abb22ecf">{{c}}</text></view><view class="filter-section data-v-abb22ecf"><view class="section-title data-v-abb22ecf">滤芯寿命</view><view class="filter-list data-v-abb22ecf"><view wx:for="{{d}}" wx:for-item="filter" wx:key="g" class="filter-item data-v-abb22ecf"><view class="filter-header data-v-abb22ecf"><text class="filter-name data-v-abb22ecf">{{filter.a}}</text><text class="filter-remain data-v-abb22ecf">剩余 {{filter.b}}%</text></view><view class="progress-bar data-v-abb22ecf"><view class="progress-fill data-v-abb22ecf" style="{{'width:' + filter.c + ';' + ('background:' + filter.d)}}"></view></view><view class="filter-info data-v-abb22ecf"><text class="filter-model data-v-abb22ecf">型号:{{filter.e}}</text><text class="filter-time data-v-abb22ecf">预计更换:{{filter.f}}</text></view></view></view></view><view class="info-section data-v-abb22ecf"><view class="section-title data-v-abb22ecf">设备信息</view><view class="info-list data-v-abb22ecf"><view class="info-item data-v-abb22ecf"><text class="info-label data-v-abb22ecf">设备编号:</text><text class="info-value data-v-abb22ecf">{{e}}</text></view><view class="info-item data-v-abb22ecf"><text class="info-label data-v-abb22ecf">设备类型:</text><text class="info-value data-v-abb22ecf">{{f}}</text></view><view class="info-item data-v-abb22ecf"><text class="info-label data-v-abb22ecf">滤芯型号:</text><text class="info-value data-v-abb22ecf">{{g}}</text></view><view class="info-item data-v-abb22ecf"><text class="info-label data-v-abb22ecf">设备地址:</text><text class="info-value data-v-abb22ecf">{{h}}</text></view><view class="info-item data-v-abb22ecf"><text class="info-label data-v-abb22ecf">绑定时间:</text><text class="info-value data-v-abb22ecf">{{i}}</text></view></view></view><view class="action-section data-v-abb22ecf"><button class="action-btn primary-btn data-v-abb22ecf" bindtap="{{j}}">更换滤芯</button><button class="action-btn secondary-btn data-v-abb22ecf" bindtap="{{k}}">设备配置</button><button class="action-btn tertiary-btn data-v-abb22ecf" bindtap="{{l}}">水质查看</button></view></scroll-view></view>
<view class="device-detail-container data-v-abb22ecf"><scroll-view class="detail-content data-v-abb22ecf" scroll-y><view class="status-card data-v-abb22ecf"><view class="device-icon-wrapper data-v-abb22ecf"><text class="device-icon data-v-abb22ecf">💧</text></view><text class="{{['device-status-text', 'data-v-abb22ecf', b]}}">{{a}}</text><text class="device-name data-v-abb22ecf">{{c}}</text></view><view class="filter-section data-v-abb22ecf"><view class="section-title data-v-abb22ecf">滤芯寿命</view><view class="filter-list data-v-abb22ecf"><view wx:for="{{d}}" wx:for-item="filter" wx:key="g" class="filter-item data-v-abb22ecf"><view class="filter-header data-v-abb22ecf"><text class="filter-name data-v-abb22ecf">{{filter.a}}</text><text class="filter-remain data-v-abb22ecf">剩余 {{filter.b}}%</text></view><view class="progress-bar data-v-abb22ecf"><view class="progress-fill data-v-abb22ecf" style="{{'width:' + filter.c + ';' + ('background:' + filter.d)}}"></view></view><view class="filter-info data-v-abb22ecf"><text class="filter-model data-v-abb22ecf">型号:{{filter.e}}</text><text class="filter-time data-v-abb22ecf">预计更换:{{filter.f}}</text></view></view></view></view><view class="water-quality-section data-v-abb22ecf"><view class="section-title data-v-abb22ecf">最新水质情况</view><view wx:if="{{e}}" class="water-quality-card data-v-abb22ecf"><view class="quality-header data-v-abb22ecf"><text class="{{['quality-level', 'data-v-abb22ecf', g]}}">{{f}}</text><text class="quality-date data-v-abb22ecf">{{h}} 检测</text></view><text class="quality-desc data-v-abb22ecf">{{i}}</text><view class="indicator-grid data-v-abb22ecf"><view wx:for="{{j}}" wx:for-item="indicator" wx:key="f" class="indicator-item data-v-abb22ecf"><text class="indicator-name data-v-abb22ecf">{{indicator.a}}</text><text class="indicator-value data-v-abb22ecf">{{indicator.b}} {{indicator.c}}</text><text class="{{['indicator-status', 'data-v-abb22ecf', indicator.e]}}">{{indicator.d}}</text></view></view><view class="quality-actions data-v-abb22ecf"><button class="quality-btn data-v-abb22ecf" bindtap="{{k}}">查看全部检测</button><button class="quality-btn outline data-v-abb22ecf" bindtap="{{l}}">查看报告</button></view></view><view wx:else class="water-quality-empty data-v-abb22ecf"><text class="data-v-abb22ecf">暂无最新水质数据</text></view></view><view class="info-section data-v-abb22ecf"><view class="section-title data-v-abb22ecf">设备信息</view><view class="info-list data-v-abb22ecf"><view class="info-item data-v-abb22ecf"><text class="info-label data-v-abb22ecf">设备编号:</text><text class="info-value data-v-abb22ecf">{{m}}</text></view><view class="info-item data-v-abb22ecf"><text class="info-label data-v-abb22ecf">设备类型:</text><text class="info-value data-v-abb22ecf">{{n}}</text></view><view class="info-item data-v-abb22ecf"><text class="info-label data-v-abb22ecf">滤芯型号:</text><text class="info-value data-v-abb22ecf">{{o}}</text></view><view class="info-item data-v-abb22ecf"><text class="info-label data-v-abb22ecf">设备地址:</text><text class="info-value data-v-abb22ecf">{{p}}</text></view><view class="info-item data-v-abb22ecf"><text class="info-label data-v-abb22ecf">绑定时间:</text><text class="info-value data-v-abb22ecf">{{q}}</text></view></view></view><view class="action-section data-v-abb22ecf"><button class="action-btn primary-btn data-v-abb22ecf" bindtap="{{r}}">更换滤芯</button><button class="action-btn secondary-btn data-v-abb22ecf" bindtap="{{s}}">设备配置</button><button class="action-btn tertiary-btn data-v-abb22ecf" bindtap="{{t}}">水质查看</button></view></scroll-view></view>

@ -77,7 +77,8 @@
color: #ffffff;
}
.filter-section.data-v-abb22ecf,
.info-section.data-v-abb22ecf {
.info-section.data-v-abb22ecf,
.water-quality-section.data-v-abb22ecf {
background: #ffffff;
margin: 0 30rpx 20rpx;
padding: 30rpx;
@ -163,6 +164,117 @@
font-weight: 500;
flex: 1;
}
.water-quality-card.data-v-abb22ecf {
display: flex;
flex-direction: column;
gap: 24rpx;
background: linear-gradient(180deg, rgba(64, 158, 255, 0.12) 0%, rgba(64, 158, 255, 0.05) 100%);
border-radius: 16rpx;
padding: 24rpx;
}
.quality-header.data-v-abb22ecf {
display: flex;
justify-content: space-between;
align-items: center;
}
.quality-level.data-v-abb22ecf {
font-size: 28rpx;
font-weight: bold;
padding: 6rpx 18rpx;
border-radius: 20rpx;
background: #ffffff;
}
.level-good.data-v-abb22ecf {
color: #67c23a;
}
.level-normal.data-v-abb22ecf {
color: #409eff;
}
.level-warning.data-v-abb22ecf {
color: #e6a23c;
}
.level-danger.data-v-abb22ecf {
color: #f56c6c;
}
.quality-date.data-v-abb22ecf {
font-size: 24rpx;
color: #606266;
}
.quality-desc.data-v-abb22ecf {
font-size: 26rpx;
color: #303133;
}
.indicator-grid.data-v-abb22ecf {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
}
.indicator-item.data-v-abb22ecf {
display: flex;
flex-direction: column;
gap: 8rpx;
background: #ffffff;
border-radius: 14rpx;
padding: 20rpx;
box-shadow: 0 6rpx 16rpx rgba(64, 158, 255, 0.08);
}
.indicator-name.data-v-abb22ecf {
font-size: 26rpx;
color: #606266;
}
.indicator-value.data-v-abb22ecf {
font-size: 30rpx;
font-weight: bold;
color: #303133;
}
.indicator-status.data-v-abb22ecf {
font-size: 22rpx;
padding: 4rpx 16rpx;
border-radius: 16rpx;
align-self: flex-start;
}
.status-normal.data-v-abb22ecf {
background: rgba(103, 194, 58, 0.15);
color: #67c23a;
}
.status-low.data-v-abb22ecf {
background: rgba(230, 162, 60, 0.15);
color: #e6a23c;
}
.status-high.data-v-abb22ecf {
background: rgba(245, 108, 108, 0.15);
color: #f56c6c;
}
.status-default.data-v-abb22ecf {
background: rgba(144, 147, 153, 0.15);
color: #909399;
}
.quality-actions.data-v-abb22ecf {
display: flex;
gap: 20rpx;
}
.quality-btn.data-v-abb22ecf {
flex: 1;
height: 72rpx;
line-height: 72rpx;
border-radius: 36rpx;
font-size: 26rpx;
font-weight: 500;
border: none;
background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
color: #ffffff;
}
.quality-btn.outline.data-v-abb22ecf {
background: #ffffff;
color: #409eff;
border: 2rpx solid #409eff;
}
.water-quality-empty.data-v-abb22ecf {
padding: 80rpx 0;
text-align: center;
color: #909399;
font-size: 26rpx;
}
.action-section.data-v-abb22ecf {
padding: 30rpx;
display: flex;

@ -5,18 +5,57 @@ const _sfc_main = {
setup(__props) {
const userRole = common_vendor.ref("user");
const isStaff = common_vendor.computed(() => userRole.value === "staff");
const waterData = common_vendor.ref([
{ date: "1/9", value: 12.5 },
{ date: "1/10", value: 15.2 },
{ date: "1/11", value: 13.8 },
{ date: "1/12", value: 14.6 },
{ date: "1/13", value: 16.3 },
{ date: "1/14", value: 15.9 },
{ date: "1/15", value: 14.2 }
const problemDevices = common_vendor.ref([
{
name: "净水器-003",
sn: "JSQ20240105003",
tag: "离线",
tagType: "danger",
description: "设备已离线超过 8 小时,请检查电源与网络连接",
address: "XX路88号"
},
{
name: "净水器-002",
sn: "JSQ20240112002",
tag: "滤芯预警",
tagType: "warning",
description: "主滤芯寿命低于 30%,建议尽快预约更换",
address: "XX小区3栋2单元501"
}
]);
const displayedProblemDevices = common_vendor.computed(() => problemDevices.value.slice(0, 2));
const problemDeviceOverflow = common_vendor.computed(() => problemDevices.value.length > 2);
const displayedWaterQualityAlerts = common_vendor.computed(() => waterQualityAlerts.value.slice(0, 2));
common_vendor.watch(problemDevices, (newVal) => {
try {
common_vendor.index.setStorageSync("problemDevices", newVal || []);
} catch (e) {
}
}, { deep: true });
common_vendor.watch(waterQualityAlerts, (newVal) => {
try {
common_vendor.index.setStorageSync("waterQualityAlerts", newVal || []);
} catch (e) {
}
}, { deep: true });
const waterQualityAlerts = common_vendor.ref([
{
name: "净水器-002",
sn: "JSQ20240112002",
level: "一般",
tagType: "warning",
description: "上次检测显示浊度接近上限,建议再次检测确认",
lastChecked: "2024-01-10"
},
{
name: "净水器-003",
sn: "JSQ20240105003",
level: "较差",
tagType: "danger",
description: "余氯低于安全值,建议暂停饮用并联系工作人员",
lastChecked: "2024-01-08"
}
]);
const chartCtx = common_vendor.ref(null);
const chartWidth = common_vendor.ref(690);
const chartHeight = common_vendor.ref(400);
const banners = common_vendor.ref([
{ title: "水质监测实时数据", bgColor: "linear-gradient(135deg, #409eff 0%, #66b1ff 100%)" },
{ title: "供水服务便民措施", bgColor: "linear-gradient(135deg, #67c23a 0%, #85ce61 100%)" },
@ -40,12 +79,6 @@ const _sfc_main = {
{ label: "在线用户", value: "256" },
{ label: "待审核", value: "5" }
]);
const handleSearch = () => {
common_vendor.index.showToast({
title: "搜索功能开发中",
icon: "none"
});
};
const handleBannerClick = (banner) => {
common_vendor.index.showToast({
title: banner.title,
@ -75,118 +108,101 @@ const _sfc_main = {
});
}
};
const handleNoticeClick = (notice) => {
common_vendor.index.showToast({
title: notice.title,
icon: "none"
const handleProblemDeviceClick = (device) => {
try {
common_vendor.index.setStorageSync("problemDevices", problemDevices.value || []);
if (device && device.sn) {
common_vendor.index.setStorageSync("problemDeviceSelectedSn", device.sn);
}
} catch (e) {
}
common_vendor.index.navigateTo({
url: "/pages/device-problem/device-problem"
});
};
const drawChart = () => {
if (!chartCtx.value || isStaff.value)
return;
common_vendor.index.getSystemInfo({
success: (res) => {
res.pixelRatio || 1;
const canvasWidth = chartWidth.value / 750 * res.windowWidth;
const canvasHeight = chartHeight.value / 750 * res.windowWidth;
const ctx = chartCtx.value;
const padding = 60;
const w = canvasWidth - padding * 2;
const h = canvasHeight - padding * 2;
const values = waterData.value.map((item) => item.value);
const maxValue = Math.max(...values);
const minValue = Math.min(...values);
const range = maxValue - minValue || 1;
ctx.setFillStyle("#ffffff");
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
ctx.setStrokeStyle("#e4e7ed");
ctx.setLineWidth(1);
for (let i = 0; i <= 4; i++) {
const y = padding + h / 4 * i;
ctx.beginPath();
ctx.moveTo(padding, y);
ctx.lineTo(padding + w, y);
ctx.stroke();
}
ctx.setFillStyle("#909399");
ctx.setFontSize(20);
ctx.setTextAlign("right");
for (let i = 0; i <= 4; i++) {
const value = maxValue - range / 4 * i;
const y = padding + h / 4 * i + 5;
ctx.fillText(value.toFixed(1), padding - 10, y);
}
ctx.setStrokeStyle("#409eff");
ctx.setLineWidth(3);
ctx.beginPath();
waterData.value.forEach((item, index) => {
const x = padding + w / (waterData.value.length - 1) * index;
const y = padding + h - (item.value - minValue) / range * h;
if (index === 0) {
ctx.moveTo(x, y);
} else {
ctx.lineTo(x, y);
}
});
ctx.stroke();
waterData.value.forEach((item, index) => {
const x = padding + w / (waterData.value.length - 1) * index;
const y = padding + h - (item.value - minValue) / range * h;
ctx.setFillStyle("#409eff");
ctx.beginPath();
ctx.arc(x, y, 6, 0, Math.PI * 2);
ctx.fill();
ctx.setFillStyle("#ffffff");
ctx.beginPath();
ctx.arc(x, y, 3, 0, Math.PI * 2);
ctx.fill();
});
ctx.setFillStyle("#606266");
ctx.setFontSize(12);
ctx.setTextAlign("center");
waterData.value.forEach((item, index) => {
const x = padding + w / (waterData.value.length - 1) * index;
const y = canvasHeight - 20;
ctx.fillText(item.date, x, y);
});
ctx.draw();
const handleWaterQualityAlertClick = (alert) => {
try {
common_vendor.index.setStorageSync("waterQualityAlerts", waterQualityAlerts.value || []);
if (alert && alert.sn) {
common_vendor.index.setStorageSync("waterQualityAlertSelectedSn", alert.sn);
}
} catch (e) {
}
common_vendor.index.navigateTo({
url: "/pages/water-quality-alert/water-quality-alert"
});
};
const touchStart = (e) => {
};
const touchMove = (e) => {
const handleNoticeClick = (notice) => {
common_vendor.index.showToast({
title: notice.title,
icon: "none"
});
};
const touchEnd = (e) => {
const getTagClass = (type) => {
if (type === "danger")
return "tag-danger";
if (type === "warning")
return "tag-warning";
return "tag-info";
};
common_vendor.onMounted(() => {
const role = common_vendor.index.getStorageSync("userRole");
if (role) {
userRole.value = role;
}
if (!isStaff.value) {
setTimeout(() => {
chartCtx.value = common_vendor.index.createCanvasContext("waterChart");
drawChart();
}, 300);
const storedProblemDevices = common_vendor.index.getStorageSync("problemDevices");
if (Array.isArray(storedProblemDevices) && storedProblemDevices.length) {
problemDevices.value = storedProblemDevices;
} else {
try {
common_vendor.index.setStorageSync("problemDevices", problemDevices.value || []);
} catch (e) {
}
}
const storedWaterAlerts = common_vendor.index.getStorageSync("waterQualityAlerts");
if (Array.isArray(storedWaterAlerts) && storedWaterAlerts.length) {
waterQualityAlerts.value = storedWaterAlerts;
} else {
try {
common_vendor.index.setStorageSync("waterQualityAlerts", waterQualityAlerts.value || []);
} catch (e) {
}
}
});
return (_ctx, _cache) => {
return common_vendor.e({
a: common_vendor.o(handleSearch),
b: !isStaff.value
}, !isStaff.value ? {
c: common_vendor.o(touchStart),
d: common_vendor.o(touchMove),
e: common_vendor.o(touchEnd),
f: common_vendor.f(waterData.value, (item, index, i0) => {
a: !isStaff.value
}, !isStaff.value ? common_vendor.e({
b: problemDeviceOverflow.value
}, problemDeviceOverflow.value ? {} : {}, {
c: displayedProblemDevices.value.length
}, displayedProblemDevices.value.length ? {
d: common_vendor.f(displayedProblemDevices.value, (device, index, i0) => {
return {
a: common_vendor.t(item.date),
b: common_vendor.t(item.value),
c: index
a: common_vendor.t(device.name),
b: common_vendor.t(device.tag),
c: common_vendor.n(getTagClass(device.tagType)),
d: common_vendor.t(device.description),
e: common_vendor.t(device.address),
f: index,
g: common_vendor.o(($event) => handleProblemDeviceClick(device), index)
};
})
} : {}, {
e: waterQualityAlerts.value.length
}, waterQualityAlerts.value.length ? {
f: common_vendor.f(displayedWaterQualityAlerts.value, (item, index, i0) => {
return {
a: common_vendor.t(item.name),
b: common_vendor.t(item.level),
c: common_vendor.n(getTagClass(item.tagType)),
d: common_vendor.t(item.description),
e: common_vendor.t(item.lastChecked),
f: index,
g: common_vendor.o(($event) => handleWaterQualityAlertClick(item), index)
};
})
} : {}) : {}, {
g: common_vendor.f(banners.value, (banner, index, i0) => {
return {
a: common_vendor.t(banner.title),

@ -1 +1 @@
<view class="index-container data-v-1cf27b2a"><view class="search-section data-v-1cf27b2a"><view class="search-wrapper data-v-1cf27b2a" bindtap="{{a}}"><text class="search-icon data-v-1cf27b2a">🔍</text><text class="search-placeholder data-v-1cf27b2a">搜索服务内容</text></view></view><view wx:if="{{b}}" class="chart-section data-v-1cf27b2a"><view class="section-title data-v-1cf27b2a">近七天用水量</view><view class="chart-container data-v-1cf27b2a"><block wx:if="{{r0}}"><canvas canvas-id="waterChart" id="waterChart" class="chart-canvas data-v-1cf27b2a" bindtouchstart="{{c}}" bindtouchmove="{{d}}" bindtouchend="{{e}}"></canvas></block></view><view class="chart-legend data-v-1cf27b2a"><view wx:for="{{f}}" wx:for-item="item" wx:key="c" class="legend-item data-v-1cf27b2a"><text class="legend-date data-v-1cf27b2a">{{item.a}}</text><text class="legend-value data-v-1cf27b2a">{{item.b}}吨</text></view></view></view><view class="banner-section data-v-1cf27b2a"><swiper class="banner-swiper data-v-1cf27b2a" indicator-dots="{{true}}" autoplay="{{true}}" interval="{{3000}}" duration="{{500}}" circular="{{true}}"><swiper-item wx:for="{{g}}" wx:for-item="banner" wx:key="c" class="data-v-1cf27b2a" bindtap="{{banner.d}}"><view class="banner-item data-v-1cf27b2a" style="{{'background:' + banner.b}}"><text class="banner-text data-v-1cf27b2a">{{banner.a}}</text></view></swiper-item></swiper></view><view class="service-section data-v-1cf27b2a"><view class="section-title data-v-1cf27b2a">快捷服务</view><view class="service-grid data-v-1cf27b2a"><view wx:for="{{h}}" wx:for-item="service" wx:key="d" class="service-item data-v-1cf27b2a" bindtap="{{service.e}}"><view class="service-icon-wrapper data-v-1cf27b2a" style="{{'background:' + service.b}}"><text class="service-icon data-v-1cf27b2a">{{service.a}}</text></view><text class="service-name data-v-1cf27b2a">{{service.c}}</text></view></view></view><view class="notice-section data-v-1cf27b2a"><view class="section-title data-v-1cf27b2a">公告通知</view><view class="notice-list data-v-1cf27b2a"><view wx:for="{{i}}" wx:for-item="notice" wx:key="c" class="notice-item data-v-1cf27b2a" bindtap="{{notice.d}}"><view class="notice-dot data-v-1cf27b2a"></view><view class="notice-content data-v-1cf27b2a"><text class="notice-title data-v-1cf27b2a">{{notice.a}}</text><text class="notice-time data-v-1cf27b2a">{{notice.b}}</text></view><text class="notice-arrow data-v-1cf27b2a"></text></view></view></view><view wx:if="{{j}}" class="stats-section data-v-1cf27b2a"><view class="section-title data-v-1cf27b2a">数据统计</view><view class="stats-grid data-v-1cf27b2a"><view wx:for="{{k}}" wx:for-item="stat" wx:key="c" class="stats-item data-v-1cf27b2a"><text class="stats-value data-v-1cf27b2a">{{stat.a}}</text><text class="stats-label data-v-1cf27b2a">{{stat.b}}</text></view></view></view></view>
<view class="index-container data-v-1cf27b2a"><view wx:if="{{a}}" class="alert-section data-v-1cf27b2a"><view class="alert-card data-v-1cf27b2a"><view class="section-title-wrapper data-v-1cf27b2a"><text class="section-title data-v-1cf27b2a">问题设备</text><view wx:if="{{b}}" class="alert-dot data-v-1cf27b2a"></view></view><view wx:if="{{c}}" class="alert-list data-v-1cf27b2a"><view wx:for="{{d}}" wx:for-item="device" wx:key="f" class="alert-item data-v-1cf27b2a" bindtap="{{device.g}}"><view class="alert-header data-v-1cf27b2a"><text class="alert-name data-v-1cf27b2a">{{device.a}}</text><text class="{{['alert-tag', 'data-v-1cf27b2a', device.c]}}">{{device.b}}</text></view><view class="alert-body data-v-1cf27b2a"><text class="alert-desc data-v-1cf27b2a">{{device.d}}</text><text class="alert-address data-v-1cf27b2a">位置:{{device.e}}</text></view><view class="alert-footer data-v-1cf27b2a"><text class="alert-link data-v-1cf27b2a">查看问题设备列表 </text></view></view></view><view wx:else class="alert-empty data-v-1cf27b2a"><text class="data-v-1cf27b2a">暂无问题设备</text></view></view><view class="alert-card data-v-1cf27b2a"><view class="section-title data-v-1cf27b2a">水质预警</view><view wx:if="{{e}}" class="alert-list data-v-1cf27b2a"><view wx:for="{{f}}" wx:for-item="item" wx:key="f" class="alert-item data-v-1cf27b2a" bindtap="{{item.g}}"><view class="alert-header data-v-1cf27b2a"><text class="alert-name data-v-1cf27b2a">{{item.a}}</text><text class="{{['alert-tag', 'data-v-1cf27b2a', item.c]}}">{{item.b}}</text></view><view class="alert-body data-v-1cf27b2a"><text class="alert-desc data-v-1cf27b2a">{{item.d}}</text><text class="alert-address data-v-1cf27b2a">上次检测:{{item.e}}</text></view><view class="alert-footer data-v-1cf27b2a"><text class="alert-link data-v-1cf27b2a">查看水质预警详情 </text></view></view></view><view wx:else class="alert-empty data-v-1cf27b2a"><text class="data-v-1cf27b2a">暂无水质异常,水质表现良好。</text></view></view></view><view class="banner-section data-v-1cf27b2a"><swiper class="banner-swiper data-v-1cf27b2a" indicator-dots="{{true}}" autoplay="{{true}}" interval="{{3000}}" duration="{{500}}" circular="{{true}}"><swiper-item wx:for="{{g}}" wx:for-item="banner" wx:key="c" class="data-v-1cf27b2a" bindtap="{{banner.d}}"><view class="banner-item data-v-1cf27b2a" style="{{'background:' + banner.b}}"><text class="banner-text data-v-1cf27b2a">{{banner.a}}</text></view></swiper-item></swiper></view><view class="service-section data-v-1cf27b2a"><view class="section-title data-v-1cf27b2a">快捷服务</view><view class="service-grid data-v-1cf27b2a"><view wx:for="{{h}}" wx:for-item="service" wx:key="d" class="service-item data-v-1cf27b2a" bindtap="{{service.e}}"><view class="service-icon-wrapper data-v-1cf27b2a" style="{{'background:' + service.b}}"><text class="service-icon data-v-1cf27b2a">{{service.a}}</text></view><text class="service-name data-v-1cf27b2a">{{service.c}}</text></view></view></view><view class="notice-section data-v-1cf27b2a"><view class="section-title data-v-1cf27b2a">公告通知</view><view class="notice-list data-v-1cf27b2a"><view wx:for="{{i}}" wx:for-item="notice" wx:key="c" class="notice-item data-v-1cf27b2a" bindtap="{{notice.d}}"><view class="notice-dot data-v-1cf27b2a"></view><view class="notice-content data-v-1cf27b2a"><text class="notice-title data-v-1cf27b2a">{{notice.a}}</text><text class="notice-time data-v-1cf27b2a">{{notice.b}}</text></view><text class="notice-arrow data-v-1cf27b2a"></text></view></view></view><view wx:if="{{j}}" class="stats-section data-v-1cf27b2a"><view class="section-title data-v-1cf27b2a">数据统计</view><view class="stats-grid data-v-1cf27b2a"><view wx:for="{{k}}" wx:for-item="stat" wx:key="c" class="stats-item data-v-1cf27b2a"><text class="stats-value data-v-1cf27b2a">{{stat.a}}</text><text class="stats-label data-v-1cf27b2a">{{stat.b}}</text></view></view></view></view>

@ -28,25 +28,6 @@
background: #f5f5f5;
padding-bottom: 120rpx;
}
.search-section.data-v-1cf27b2a {
padding: 20rpx 30rpx;
background: #ffffff;
}
.search-wrapper.data-v-1cf27b2a {
display: flex;
align-items: center;
background: #f5f5f5;
border-radius: 50rpx;
padding: 20rpx 30rpx;
}
.search-icon.data-v-1cf27b2a {
font-size: 32rpx;
margin-right: 12rpx;
}
.search-placeholder.data-v-1cf27b2a {
font-size: 28rpx;
color: #909399;
}
.banner-section.data-v-1cf27b2a {
margin: 20rpx 30rpx;
}
@ -71,47 +52,6 @@
.service-section.data-v-1cf27b2a {
margin: 40rpx 30rpx;
}
.chart-section.data-v-1cf27b2a {
margin: 20rpx 30rpx;
}
.section-title.data-v-1cf27b2a {
font-size: 32rpx;
font-weight: bold;
color: #303133;
margin-bottom: 24rpx;
}
.chart-container.data-v-1cf27b2a {
background: #ffffff;
border-radius: 20rpx;
padding: 30rpx;
margin-bottom: 20rpx;
}
.chart-canvas.data-v-1cf27b2a {
width: 100%;
height: 400rpx;
}
.chart-legend.data-v-1cf27b2a {
background: #ffffff;
border-radius: 20rpx;
padding: 30rpx;
display: flex;
justify-content: space-around;
}
.legend-item.data-v-1cf27b2a {
display: flex;
flex-direction: column;
align-items: center;
}
.legend-date.data-v-1cf27b2a {
font-size: 24rpx;
color: #909399;
margin-bottom: 8rpx;
}
.legend-value.data-v-1cf27b2a {
font-size: 28rpx;
color: #409eff;
font-weight: bold;
}
.section-title.data-v-1cf27b2a {
font-size: 32rpx;
font-weight: bold;
@ -147,6 +87,97 @@
font-size: 24rpx;
color: #606266;
}
.section-title-wrapper.data-v-1cf27b2a {
display: flex;
align-items: center;
justify-content: space-between;
position: relative;
}
.alert-dot.data-v-1cf27b2a {
width: 16rpx;
height: 16rpx;
border-radius: 50%;
background: #f56c6c;
position: absolute;
top: 6rpx;
right: 0;
}
.alert-section.data-v-1cf27b2a {
margin: 20rpx 30rpx 0;
display: flex;
flex-direction: column;
gap: 20rpx;
}
.alert-card.data-v-1cf27b2a {
background: #ffffff;
border-radius: 20rpx;
padding: 32rpx 30rpx;
}
.alert-list.data-v-1cf27b2a {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.alert-item.data-v-1cf27b2a {
background: #f9fafc;
padding: 24rpx 28rpx;
border-radius: 16rpx;
display: flex;
flex-direction: column;
gap: 16rpx;
}
.alert-header.data-v-1cf27b2a {
display: flex;
justify-content: space-between;
align-items: center;
}
.alert-name.data-v-1cf27b2a {
font-size: 30rpx;
font-weight: bold;
color: #303133;
}
.alert-tag.data-v-1cf27b2a {
padding: 6rpx 18rpx;
border-radius: 24rpx;
font-size: 22rpx;
}
.tag-danger.data-v-1cf27b2a {
background: rgba(245, 108, 108, 0.15);
color: #f56c6c;
}
.tag-warning.data-v-1cf27b2a {
background: rgba(230, 162, 60, 0.15);
color: #e6a23c;
}
.tag-info.data-v-1cf27b2a {
background: rgba(64, 158, 255, 0.15);
color: #409eff;
}
.alert-body.data-v-1cf27b2a {
display: flex;
flex-direction: column;
gap: 8rpx;
color: #606266;
font-size: 26rpx;
}
.alert-address.data-v-1cf27b2a {
color: #909399;
font-size: 24rpx;
}
.alert-footer.data-v-1cf27b2a {
display: flex;
justify-content: flex-start;
}
.alert-link.data-v-1cf27b2a {
font-size: 26rpx;
color: #409eff;
}
.alert-empty.data-v-1cf27b2a {
padding: 40rpx 0;
text-align: center;
font-size: 26rpx;
color: #909399;
}
.notice-section.data-v-1cf27b2a {
margin: 40rpx 30rpx;
}

@ -0,0 +1,129 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const _sfc_main = {
__name: "water-quality-alert",
setup(__props) {
const alerts = common_vendor.ref([]);
common_vendor.onMounted(() => {
try {
const stored = common_vendor.index.getStorageSync("waterQualityAlerts");
alerts.value = Array.isArray(stored) ? stored : [];
if (alerts.value.length === 0) {
loadDefaultAlerts();
}
} catch (e) {
loadDefaultAlerts();
}
});
const loadDefaultAlerts = () => {
alerts.value = [
{
name: "净水器-002",
sn: "JSQ20240112002",
level: "一般",
tagType: "warning",
description: "浊度 0.95 NTU接近上限值",
lastChecked: "2024-01-10"
},
{
name: "净水器-003",
sn: "JSQ20240105003",
level: "较差",
tagType: "danger",
description: "余氯 0.01 mg/L低于安全范围",
lastChecked: "2024-01-08"
}
];
try {
common_vendor.index.setStorageSync("waterQualityAlerts", alerts.value);
} catch (e) {
}
};
const goDeviceDetail = (sn) => {
if (!sn) {
common_vendor.index.showToast({
title: "设备信息缺失",
icon: "none"
});
return;
}
const url = `/pages/device-detail/device-detail?sn=${sn}`;
common_vendor.index.navigateTo({
url,
fail: () => {
common_vendor.index.redirectTo({ url });
}
});
};
const goWaterQualityDetail = (item) => {
if (!item || !item.sn) {
common_vendor.index.showToast({
title: "暂无检测记录",
icon: "none"
});
return;
}
try {
const recordId = `alert_${item.sn}`;
const record = {
id: recordId,
date: item.lastChecked || "2024-01-10",
address: item.address || "暂无地址信息",
level: item.level || "一般",
conclusionTip: item.description || "近期水质指标需关注,请安排复检。",
warmTip: "建议立即预约水质复检,如需帮助可联系工作人员。",
result: item.result || [
{ name: "浊度", value: "0.95", unit: "NTU" },
{ name: "余氯", value: "0.01", unit: "mg/L" },
{ name: "TDS", value: "650", unit: "mg/L" }
]
};
common_vendor.index.setStorageSync(`waterQualityRecord_${recordId}`, record);
common_vendor.index.navigateTo({
url: `/pages/water-quality-detail/water-quality-detail?recordId=${recordId}`
});
} catch (e) {
common_vendor.index.showToast({
title: "加载报告失败",
icon: "none"
});
}
};
const contactStaff = (item) => {
common_vendor.index.showToast({
title: "已通知工作人员跟进",
icon: "none"
});
};
const getBadgeClass = (type) => {
if (type === "danger")
return "badge-danger";
if (type === "warning")
return "badge-warning";
return "badge-info";
};
return (_ctx, _cache) => {
return common_vendor.e({
a: alerts.value.length === 0
}, alerts.value.length === 0 ? {} : {
b: common_vendor.f(alerts.value, (item, index, i0) => {
return {
a: common_vendor.t(item.name),
b: common_vendor.t(item.level || item.tag),
c: common_vendor.n(getBadgeClass(item.tagType)),
d: common_vendor.t(item.sn),
e: common_vendor.t(item.description),
f: common_vendor.t(item.lastChecked || "暂无记录"),
g: common_vendor.o(($event) => goDeviceDetail(item.sn), index),
h: common_vendor.o(($event) => goWaterQualityDetail(item), index),
i: common_vendor.o(($event) => contactStaff(), index),
j: index
};
})
});
};
}
};
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-8e31728d"]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/water-quality-alert/water-quality-alert.js.map

@ -0,0 +1,4 @@
{
"navigationBarTitleText": "水质预警",
"usingComponents": {}
}

@ -0,0 +1 @@
<view class="alert-page data-v-8e31728d"><view class="notice-bar data-v-8e31728d"><text class="notice-icon data-v-8e31728d">💧</text><text class="notice-text data-v-8e31728d">以下设备近期水质指标异常,请及时复检或联系工作人员</text></view><scroll-view class="alert-list data-v-8e31728d" scroll-y><view wx:if="{{a}}" class="empty-state data-v-8e31728d"><text class="empty-icon data-v-8e31728d">✅</text><text class="empty-text data-v-8e31728d">暂无水质预警</text></view><block wx:else><view wx:for="{{b}}" wx:for-item="item" wx:key="j" class="alert-card data-v-8e31728d"><view class="card-header data-v-8e31728d"><text class="device-name data-v-8e31728d">{{item.a}}</text><text class="{{['badge', 'data-v-8e31728d', item.c]}}">{{item.b}}</text></view><view class="card-content data-v-8e31728d"><text class="row data-v-8e31728d">设备编号:{{item.d}}</text><text class="row data-v-8e31728d">最近检测值:{{item.e}}</text><text class="row data-v-8e31728d">最近检测时间:{{item.f}}</text></view><view class="card-actions data-v-8e31728d"><button class="action-btn detail-btn data-v-8e31728d" bindtap="{{item.g}}">查看设备</button><button class="action-btn report-btn data-v-8e31728d" bindtap="{{item.h}}">查看报告</button><button class="action-btn contact-btn data-v-8e31728d" bindtap="{{item.i}}">联系工作人员</button></view></view></block></scroll-view></view>

@ -0,0 +1,137 @@
/**
* 这里是uni-app内置的常用样式变量
*
* uni-app 官方扩展插件及插件市场https://ext.dcloud.net.cn上很多三方插件均使用了这些样式变量
* 如果你是插件开发者建议你使用scss预处理并在插件代码中直接使用这些变量无需 import 这个文件方便用户通过搭积木的方式开发整体风格一致的App
*
*/
/**
* 如果你是App开发者插件使用者你可以通过修改这些变量来定制自己的插件主题实现自定义主题功能
*
* 如果你的项目同样使用了scss预处理你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
*/
/* 颜色变量 */
/* 行为相关颜色 */
/* 文字基本颜色 */
/* 背景颜色 */
/* 边框颜色 */
/* 尺寸变量 */
/* 文字尺寸 */
/* 图片尺寸 */
/* Border Radius */
/* 水平间距 */
/* 垂直间距 */
/* 透明度 */
/* 文章场景相关 */
.alert-page.data-v-8e31728d {
min-height: 100vh;
background: #f5f5f5;
display: flex;
flex-direction: column;
}
.notice-bar.data-v-8e31728d {
display: flex;
align-items: center;
padding: 24rpx 30rpx;
background: #ecf5ff;
color: #409eff;
}
.notice-icon.data-v-8e31728d {
font-size: 32rpx;
margin-right: 12rpx;
}
.notice-text.data-v-8e31728d {
font-size: 26rpx;
}
.alert-list.data-v-8e31728d {
flex: 1;
padding: 20rpx 30rpx 40rpx;
}
.empty-state.data-v-8e31728d {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 200rpx 0;
color: #909399;
}
.empty-icon.data-v-8e31728d {
font-size: 120rpx;
margin-bottom: 30rpx;
opacity: 0.5;
}
.empty-text.data-v-8e31728d {
font-size: 28rpx;
}
.alert-card.data-v-8e31728d {
background: #ffffff;
border-radius: 20rpx;
padding: 30rpx;
margin-bottom: 20rpx;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.04);
display: flex;
flex-direction: column;
gap: 20rpx;
}
.card-header.data-v-8e31728d {
display: flex;
justify-content: space-between;
align-items: center;
}
.device-name.data-v-8e31728d {
font-size: 30rpx;
font-weight: 600;
color: #303133;
}
.badge.data-v-8e31728d {
font-size: 22rpx;
padding: 6rpx 16rpx;
border-radius: 24rpx;
}
.badge-danger.data-v-8e31728d {
background: rgba(245, 108, 108, 0.15);
color: #f56c6c;
}
.badge-warning.data-v-8e31728d {
background: rgba(230, 162, 60, 0.15);
color: #e6a23c;
}
.badge-info.data-v-8e31728d {
background: rgba(64, 158, 255, 0.15);
color: #409eff;
}
.card-content.data-v-8e31728d {
display: flex;
flex-direction: column;
gap: 12rpx;
font-size: 26rpx;
color: #606266;
}
.row.data-v-8e31728d {
font-size: 26rpx;
}
.card-actions.data-v-8e31728d {
display: flex;
gap: 16rpx;
}
.action-btn.data-v-8e31728d {
flex: 1;
height: 72rpx;
line-height: 72rpx;
border-radius: 36rpx;
font-size: 26rpx;
border: none;
}
.detail-btn.data-v-8e31728d {
background: #ffffff;
color: #409eff;
border: 2rpx solid #409eff;
}
.report-btn.data-v-8e31728d {
background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
color: #ffffff;
}
.contact-btn.data-v-8e31728d {
background: #fef0f0;
color: #f56c6c;
}

@ -0,0 +1,162 @@
"use strict";
const common_vendor = require("../../common/vendor.js");
const defaultConclusion = "水质整体表现良好,符合日常饮用和使用需求。如需进一步检测或设备维护,请联系工作人员。";
const _sfc_main = {
__name: "water-quality-detail",
setup(__props) {
const record = common_vendor.ref(null);
common_vendor.onLoad(() => {
const cachedRecord = common_vendor.index.getStorageSync("waterQualityCurrentRecord");
if (cachedRecord) {
record.value = cachedRecord;
}
});
common_vendor.onUnload(() => {
common_vendor.index.removeStorageSync("waterQualityCurrentRecord");
});
const indicatorStandards = {
"pH值": {
range: "6.5 - 8.5"
},
"浊度": {
range: "≤ 1 NTU出厂 / ≤ 3 NTU末梢"
},
"余氯": {
range: "0.3 - 0.5 mg/L"
},
"总硬度": {
range: "≤ 450 mg/L以CaCO₃计"
},
"TDS": {
range: "≤ 1000 mg/L"
}
};
const hasResult = common_vendor.computed(() => {
return record.value && Array.isArray(record.value.result) && record.value.result.length > 0;
});
const tips = common_vendor.computed(() => {
if (!record.value) {
return [];
}
const list = [];
if (record.value.level === "优质") {
list.push("持续保持当前设备状态,建议每月查看一次检测结果。");
}
if (record.value.level === "良好") {
list.push("建议关注余氯与浊度变化,适时预约滤芯检测。");
}
if (record.value.level === "一般" || record.value.level === "较差") {
list.push("建议尽快预约设备维护,检查滤芯或供水管路。");
list.push("如有异味或水色异常,请立即停止饮用并联系工作人员。");
}
if (!list.length) {
list.push("检测结果将用于优化设备运行,请保持关注。");
}
return list;
});
const getStatusClass = (status) => {
if (status === "completed") {
return "status-completed";
}
if (status === "processing") {
return "status-processing";
}
return "status-pending";
};
const getQualityColor = (level) => {
const colorMap = {
"优质": "#67c23a",
"良好": "#409eff",
"一般": "#e6a23c",
"较差": "#f56c6c"
};
return colorMap[level] || "#909399";
};
const getStandardRange = (name) => {
var _a;
return ((_a = indicatorStandards[name]) == null ? void 0 : _a.range) || "参考标准更新中";
};
const handleDownload = () => {
common_vendor.index.showToast({
title: "正在生成报告...",
icon: "none"
});
setTimeout(() => {
common_vendor.index.showToast({
title: "报告生成成功",
icon: "success"
});
}, 800);
};
const handleContact = () => {
common_vendor.index.showModal({
title: "联系工作人员",
content: "是否拨打客服热线 400-800-1234",
success: (res) => {
if (res.confirm) {
common_vendor.index.makePhoneCall({
phoneNumber: "4008001234",
fail: () => {
common_vendor.index.showToast({
title: "拨号失败,请稍后重试",
icon: "none"
});
}
});
}
}
});
};
const handleBack = () => {
common_vendor.index.navigateBack({
fail: () => {
common_vendor.index.reLaunch({
url: "/pages/water-quality/water-quality"
});
}
});
};
return (_ctx, _cache) => {
return common_vendor.e({
a: record.value
}, record.value ? common_vendor.e({
b: common_vendor.t(record.value.statusText || "检测中"),
c: common_vendor.n(getStatusClass(record.value.status)),
d: common_vendor.t(record.value.date),
e: common_vendor.t(record.value.level || "待评估"),
f: getQualityColor(record.value.level),
g: common_vendor.t(record.value.address || "暂无地址信息"),
h: common_vendor.t(record.value.date),
i: hasResult.value
}, hasResult.value ? {
j: common_vendor.f(record.value.result, (item, index, i0) => {
return {
a: common_vendor.t(item.name),
b: common_vendor.t(item.value),
c: common_vendor.t(item.unit),
d: common_vendor.t(getStandardRange(item.name)),
e: index
};
})
} : {}, {
k: common_vendor.t(record.value.conclusion || defaultConclusion),
l: tips.value.length
}, tips.value.length ? {
m: common_vendor.f(tips.value, (tip, index, i0) => {
return {
a: common_vendor.t(tip),
b: index
};
})
} : {}, {
n: common_vendor.o(handleDownload),
o: common_vendor.o(handleContact)
}) : {
p: common_vendor.o(handleBack)
});
};
}
};
const MiniProgramPage = /* @__PURE__ */ common_vendor._export_sfc(_sfc_main, [["__scopeId", "data-v-9475c2ac"]]);
wx.createPage(MiniProgramPage);
//# sourceMappingURL=../../../.sourcemap/mp-weixin/pages/water-quality-detail/water-quality-detail.js.map

@ -0,0 +1,4 @@
{
"navigationBarTitleText": "检测报告",
"usingComponents": {}
}

@ -0,0 +1 @@
<view class="detail-container data-v-9475c2ac"><view wx:if="{{a}}" class="content data-v-9475c2ac"><view class="header-card data-v-9475c2ac"><view class="status-row data-v-9475c2ac"><view class="{{['status-badge', 'data-v-9475c2ac', c]}}"><text class="data-v-9475c2ac">{{b}}</text></view><text class="report-date data-v-9475c2ac">{{d}}</text></view><view class="quality-row data-v-9475c2ac"><text class="quality-label data-v-9475c2ac">水质等级</text><text class="quality-value data-v-9475c2ac" style="{{'color:' + f}}">{{e}}</text></view><view class="address-row data-v-9475c2ac"><text class="address-icon data-v-9475c2ac">📍</text><text class="address-text data-v-9475c2ac">{{g}}</text></view></view><view class="section-card data-v-9475c2ac"><view class="section-header data-v-9475c2ac"><text class="section-title data-v-9475c2ac">检测指标</text><text class="section-subtitle data-v-9475c2ac">采样时间:{{h}}</text></view><view wx:if="{{i}}" class="indicator-grid data-v-9475c2ac"><view wx:for="{{j}}" wx:for-item="item" wx:key="e" class="indicator-item data-v-9475c2ac"><text class="indicator-name data-v-9475c2ac">{{item.a}}</text><text class="indicator-value data-v-9475c2ac">{{item.b}}</text><text class="indicator-unit data-v-9475c2ac">{{item.c}}</text><text class="indicator-standard data-v-9475c2ac">标准范围:{{item.d}}</text></view></view><view wx:else class="empty-indicator data-v-9475c2ac"><text class="data-v-9475c2ac">检测数据生成中,请稍后查看。</text></view></view><view class="section-card data-v-9475c2ac"><view class="section-header data-v-9475c2ac"><text class="section-title data-v-9475c2ac">检测结论</text></view><view class="conclusion-block data-v-9475c2ac"><text class="conclusion-text data-v-9475c2ac">{{k}}</text></view><view wx:if="{{l}}" class="tips-list data-v-9475c2ac"><view wx:for="{{m}}" wx:for-item="tip" wx:key="b" class="tips-item data-v-9475c2ac"><text class="tips-icon data-v-9475c2ac">💡</text><text class="tips-text data-v-9475c2ac">{{tip.a}}</text></view></view></view><view class="section-card data-v-9475c2ac"><view class="section-header data-v-9475c2ac"><text class="section-title data-v-9475c2ac">服务操作</text></view><view class="action-group data-v-9475c2ac"><button class="action-btn primary data-v-9475c2ac" bindtap="{{n}}">下载检测报告</button><button class="action-btn secondary data-v-9475c2ac" bindtap="{{o}}">联系工作人员</button></view></view></view><view wx:else class="empty-state data-v-9475c2ac"><text class="empty-icon data-v-9475c2ac">📄</text><text class="empty-title data-v-9475c2ac">未找到检测报告</text><text class="empty-desc data-v-9475c2ac">请返回上一页重新选择检测记录。</text><button class="back-btn data-v-9475c2ac" bindtap="{{p}}">返回</button></view></view>

@ -0,0 +1,237 @@
/**
* 这里是uni-app内置的常用样式变量
*
* uni-app 官方扩展插件及插件市场https://ext.dcloud.net.cn上很多三方插件均使用了这些样式变量
* 如果你是插件开发者建议你使用scss预处理并在插件代码中直接使用这些变量无需 import 这个文件方便用户通过搭积木的方式开发整体风格一致的App
*
*/
/**
* 如果你是App开发者插件使用者你可以通过修改这些变量来定制自己的插件主题实现自定义主题功能
*
* 如果你的项目同样使用了scss预处理你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
*/
/* 颜色变量 */
/* 行为相关颜色 */
/* 文字基本颜色 */
/* 背景颜色 */
/* 边框颜色 */
/* 尺寸变量 */
/* 文字尺寸 */
/* 图片尺寸 */
/* Border Radius */
/* 水平间距 */
/* 垂直间距 */
/* 透明度 */
/* 文章场景相关 */
.detail-container.data-v-9475c2ac {
min-height: 100vh;
background: #f5f5f5;
padding: 30rpx 30rpx 120rpx;
box-sizing: border-box;
}
.content.data-v-9475c2ac {
display: flex;
flex-direction: column;
gap: 24rpx;
}
.header-card.data-v-9475c2ac {
background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
border-radius: 24rpx;
padding: 40rpx 34rpx;
color: #ffffff;
box-shadow: 0 18rpx 40rpx -20rpx rgba(64, 158, 255, 0.8);
}
.status-row.data-v-9475c2ac {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
}
.status-badge.data-v-9475c2ac {
padding: 12rpx 24rpx;
border-radius: 28rpx;
font-size: 24rpx;
background: rgba(255, 255, 255, 0.2);
}
.status-badge.status-completed.data-v-9475c2ac {
background: rgba(103, 194, 58, 0.2);
color: #67c23a;
}
.status-badge.status-processing.data-v-9475c2ac {
background: rgba(230, 162, 60, 0.2);
color: #e6a23c;
}
.status-badge.status-pending.data-v-9475c2ac {
background: rgba(255, 255, 255, 0.2);
color: #ffffff;
}
.report-date.data-v-9475c2ac {
font-size: 26rpx;
}
.quality-row.data-v-9475c2ac {
display: flex;
align-items: baseline;
gap: 20rpx;
margin-bottom: 20rpx;
}
.quality-label.data-v-9475c2ac {
font-size: 26rpx;
opacity: 0.8;
}
.quality-value.data-v-9475c2ac {
font-size: 48rpx;
font-weight: bold;
}
.address-row.data-v-9475c2ac {
display: flex;
align-items: center;
gap: 12rpx;
font-size: 26rpx;
opacity: 0.9;
}
.section-card.data-v-9475c2ac {
background: #ffffff;
border-radius: 20rpx;
padding: 34rpx 30rpx;
box-shadow: 0 10rpx 20rpx -16rpx rgba(0, 0, 0, 0.12);
}
.section-header.data-v-9475c2ac {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
}
.section-title.data-v-9475c2ac {
font-size: 32rpx;
font-weight: bold;
color: #303133;
}
.section-subtitle.data-v-9475c2ac {
font-size: 24rpx;
color: #909399;
}
.indicator-grid.data-v-9475c2ac {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 20rpx;
}
.indicator-item.data-v-9475c2ac {
background: #f5f7fa;
border-radius: 16rpx;
padding: 24rpx;
display: flex;
flex-direction: column;
gap: 10rpx;
}
.indicator-name.data-v-9475c2ac {
font-size: 26rpx;
color: #606266;
}
.indicator-value.data-v-9475c2ac {
font-size: 36rpx;
font-weight: bold;
color: #303133;
}
.indicator-unit.data-v-9475c2ac {
font-size: 24rpx;
color: #909399;
}
.indicator-standard.data-v-9475c2ac {
font-size: 24rpx;
color: #606266;
}
.empty-indicator.data-v-9475c2ac {
text-align: center;
font-size: 26rpx;
color: #909399;
padding: 40rpx 20rpx;
}
.conclusion-block.data-v-9475c2ac {
background: linear-gradient(135deg, rgba(64, 158, 255, 0.08) 0%, rgba(102, 177, 255, 0.12) 100%);
border-radius: 18rpx;
padding: 30rpx;
margin-bottom: 24rpx;
}
.conclusion-text.data-v-9475c2ac {
font-size: 28rpx;
color: #303133;
line-height: 1.6;
}
.tips-list.data-v-9475c2ac {
display: flex;
flex-direction: column;
gap: 18rpx;
}
.tips-item.data-v-9475c2ac {
display: flex;
align-items: center;
gap: 16rpx;
background: #f9fafc;
border-radius: 14rpx;
padding: 20rpx 24rpx;
}
.tips-icon.data-v-9475c2ac {
font-size: 32rpx;
}
.tips-text.data-v-9475c2ac {
font-size: 26rpx;
color: #606266;
}
.action-group.data-v-9475c2ac {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.action-btn.data-v-9475c2ac {
height: 88rpx;
line-height: 88rpx;
border-radius: 44rpx;
font-size: 30rpx;
font-weight: bold;
border: none;
}
.action-btn.data-v-9475c2ac::after {
border: none;
}
.action-btn.primary.data-v-9475c2ac {
background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
color: #ffffff;
}
.action-btn.secondary.data-v-9475c2ac {
background: #ecf5ff;
color: #409eff;
}
.empty-state.data-v-9475c2ac {
height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 16rpx;
color: #909399;
}
.empty-icon.data-v-9475c2ac {
font-size: 120rpx;
margin-bottom: 10rpx;
}
.empty-title.data-v-9475c2ac {
font-size: 32rpx;
color: #303133;
}
.empty-desc.data-v-9475c2ac {
font-size: 26rpx;
margin-bottom: 20rpx;
}
.back-btn.data-v-9475c2ac {
width: 260rpx;
height: 80rpx;
line-height: 80rpx;
border-radius: 40rpx;
background: #409eff;
color: #ffffff;
font-size: 28rpx;
border: none;
}
.back-btn.data-v-9475c2ac::after {
border: none;
}

@ -3,8 +3,12 @@ const common_vendor = require("../../common/vendor.js");
const _sfc_main = {
__name: "water-quality",
setup(__props) {
const records = common_vendor.ref([
const records = common_vendor.ref([]);
const currentSn = common_vendor.ref("");
const defaultRecords = [
{
id: "WQ20240115001",
sn: "JSQ20240115001",
date: "2024-01-15",
address: "XX小区3栋2单元501",
status: "completed",
@ -18,6 +22,8 @@ const _sfc_main = {
]
},
{
id: "WQ20240110002",
sn: "JSQ20240105003",
date: "2024-01-10",
address: "XX路88号",
status: "completed",
@ -30,27 +36,35 @@ const _sfc_main = {
]
},
{
id: "WQ20240108003",
sn: "JSQ20240115001",
date: "2024-01-08",
address: "XX小区3栋2单元501",
status: "processing",
statusText: "检测中",
level: "-"
}
]);
const currentSn = common_vendor.ref("");
const snToAddress = {
"JSQ20240115001": "XX小区3栋2单元501",
"JSQ20240112002": "XX小区3栋2单元501",
"JSQ20240105003": "XX路88号"
};
common_vendor.onLoad((options) => {
if (options && options.sn) {
currentSn.value = options.sn;
const address = snToAddress[currentSn.value];
if (address) {
records.value = records.value.filter((r) => r.address === address);
];
const loadRecords = (sn) => {
let storedRecords = [];
try {
storedRecords = common_vendor.index.getStorageSync("waterQualityRecords") || [];
if (!Array.isArray(storedRecords) || storedRecords.length === 0) {
storedRecords = defaultRecords;
common_vendor.index.setStorageSync("waterQualityRecords", storedRecords);
}
} catch (e) {
storedRecords = defaultRecords;
}
if (sn) {
records.value = storedRecords.filter((record) => record.sn === sn);
} else {
records.value = storedRecords;
}
};
common_vendor.onLoad((options) => {
currentSn.value = (options == null ? void 0 : options.sn) || "";
loadRecords(currentSn.value);
});
const getStatusClass = (status) => {
return status === "completed" ? "status-completed" : "status-processing";
@ -66,14 +80,21 @@ const _sfc_main = {
};
const handleRecordClick = (record) => {
if (record.status === "completed") {
handleViewReport();
handleViewReport(record);
}
};
const handleViewReport = (record) => {
common_vendor.index.showToast({
title: "查看检测报告",
icon: "none"
});
try {
common_vendor.index.setStorageSync("waterQualityCurrentRecord", record);
common_vendor.index.navigateTo({
url: "/pages/water-quality-detail/water-quality-detail"
});
} catch (error) {
common_vendor.index.showToast({
title: "打开报告失败,请稍后重试",
icon: "none"
});
}
};
const handleNewTest = () => {
common_vendor.index.showModal({
@ -114,7 +135,7 @@ const _sfc_main = {
};
})
} : {}, {
i: common_vendor.o(($event) => handleViewReport(), index),
i: common_vendor.o(($event) => handleViewReport(record), index),
j: index,
k: common_vendor.o(($event) => handleRecordClick(record), index)
});

Loading…
Cancel
Save