feat: 静态页面开发完成
This commit is contained in:
@@ -54,6 +54,39 @@ class ApiService {
|
||||
Future<ChatMessage> sendMessage(String content) async {
|
||||
await Future.delayed(const Duration(milliseconds: 1200));
|
||||
|
||||
final tableTrigger = RegExp(r'表格|列表|清单|明细');
|
||||
if (tableTrigger.hasMatch(content)) {
|
||||
return ChatMessage(
|
||||
id: 'msg_${DateTime.now().millisecondsSinceEpoch}',
|
||||
content: _tableReport(),
|
||||
type: MessageType.markdown,
|
||||
sender: MessageSender.ai,
|
||||
timestamp: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
final imageTrigger = RegExp(r'图片|照片|图像|实景|环境照|相册');
|
||||
if (imageTrigger.hasMatch(content)) {
|
||||
return ChatMessage(
|
||||
id: 'msg_${DateTime.now().millisecondsSinceEpoch}',
|
||||
content: _imageGallery(),
|
||||
type: MessageType.markdown,
|
||||
sender: MessageSender.ai,
|
||||
timestamp: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
final chartTrigger = RegExp(r'报表|数据|营收|趋势|占比|图表|统计|分析');
|
||||
if (chartTrigger.hasMatch(content)) {
|
||||
return ChatMessage(
|
||||
id: 'msg_${DateTime.now().millisecondsSinceEpoch}',
|
||||
content: _chartReport(),
|
||||
type: MessageType.markdown,
|
||||
sender: MessageSender.ai,
|
||||
timestamp: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
final responses = [
|
||||
'收到您的消息,我已记录并会尽快处理。\n\n**当前状态**:已受理\n**预计处理时间**:2小时内',
|
||||
'好的,我来帮您查询一下。\n\n| 项目 | 状态 | 时间 |\n|------|------|------|\n| 订单核销 | 已完成 | 10:30 |\n| 工单处理 | 进行中 | 11:00 |',
|
||||
@@ -71,6 +104,75 @@ class ApiService {
|
||||
);
|
||||
}
|
||||
|
||||
String _imageGallery() {
|
||||
return '''### 智念度假酒店 · 环境实景
|
||||
|
||||
**大堂吧** — 全息投影 + 智能引导
|
||||

|
||||
|
||||
**无边泳池** — 山景一线,270° 全景视野
|
||||

|
||||
|
||||
**SPA 养生** — 古法中医结合现代理疗
|
||||

|
||||
|
||||
> 提示:可继续询问"客房实景""餐厅照片"查看更多。''';
|
||||
}
|
||||
|
||||
String _tableReport() {
|
||||
return '''### 今日订单明细
|
||||
|
||||
共 **5 条** 待跟进订单:
|
||||
|
||||
| 订单号 | 客户 | 商品 | 金额 | 状态 |
|
||||
|---|---|---|---|---|
|
||||
| ZN20240507001 | 王先生 | 风景区成人票 ×2 | ¥128 | 🟡 待核销 |
|
||||
| ZN20240507002 | 李女士 | 豪华套房 ×1 | ¥888 | ⚪ 待支付 |
|
||||
| ZN20240507003 | 张 family | 亲子套票 ×3 | ¥388 | 🟢 已核销 |
|
||||
| ZN20240507004 | 陈先生 | SPA 养生套餐 | ¥598 | 🔴 退款中 |
|
||||
| ZN20240507005 | 赵女士 | 景区+酒店套餐 | ¥1588 | ⚫ 已退款 |
|
||||
|
||||
**汇总**
|
||||
|
||||
| 维度 | 数值 |
|
||||
|---|---|
|
||||
| 订单总数 | 5 |
|
||||
| 应收金额 | ¥3,590 |
|
||||
| 已退款 | ¥1,588 |
|
||||
| 净收入 | ¥2,002 |
|
||||
|
||||
> 提示:点击订单行可跳转至详情,或继续询问"图表"查看可视化分析。''';
|
||||
}
|
||||
|
||||
String _chartReport() {
|
||||
return '''### 今日数据报表
|
||||
|
||||
总订单 **128 笔**,核销率 **75%**,环比上周 **+12.4%**。
|
||||
|
||||
```chart
|
||||
type: bar
|
||||
title: 订单状态分布(笔)
|
||||
labels: 已核销,待处理,待支付,退款
|
||||
data: 96,32,18,2
|
||||
```
|
||||
|
||||
```chart
|
||||
type: line
|
||||
title: 近7日订单趋势
|
||||
labels: 周一,周二,周三,周四,周五,周六,周日
|
||||
data: 88,92,105,98,120,135,128
|
||||
```
|
||||
|
||||
```chart
|
||||
type: pie
|
||||
title: 营收构成占比
|
||||
labels: 客房,餐饮,SPA,门票
|
||||
data: 58,22,12,8
|
||||
```
|
||||
|
||||
> 提示:可继续询问"工单趋势""营收对比"等查看更多分析。''';
|
||||
}
|
||||
|
||||
Future<List<EventItem>> getEvents() async {
|
||||
await Future.delayed(const Duration(milliseconds: 600));
|
||||
return List.generate(8, (index) {
|
||||
@@ -121,7 +223,7 @@ class ApiService {
|
||||
description: '308房间空调制冷效果不佳,需要安排维修人员检查。',
|
||||
status: WorkOrderStatus.pending,
|
||||
creatorName: '前台小王',
|
||||
assigneeName: '维修老张',
|
||||
assigneeName: '维修部',
|
||||
createdAt: DateTime.now().subtract(const Duration(hours: 2)),
|
||||
priority: 'high',
|
||||
category: '设备维修',
|
||||
@@ -134,7 +236,7 @@ class ApiService {
|
||||
description: '后花园草坪需要定期修剪,预计耗时2小时。',
|
||||
status: WorkOrderStatus.processing,
|
||||
creatorName: '张老板',
|
||||
assigneeName: '园丁老李',
|
||||
assigneeName: '保洁部',
|
||||
createdAt: DateTime.now().subtract(const Duration(hours: 5)),
|
||||
priority: 'normal',
|
||||
category: '环境维护',
|
||||
@@ -147,7 +249,7 @@ class ApiService {
|
||||
description: '厨房排风系统需要深度清洁。',
|
||||
status: WorkOrderStatus.completed,
|
||||
creatorName: '厨师长',
|
||||
assigneeName: '清洁团队',
|
||||
assigneeName: '保洁部',
|
||||
createdAt: DateTime.now().subtract(const Duration(days: 1)),
|
||||
completedAt: DateTime.now().subtract(const Duration(hours: 3)),
|
||||
priority: 'normal',
|
||||
@@ -173,7 +275,7 @@ class ApiService {
|
||||
description: '下午有重要会议,需要提前调试投影设备。',
|
||||
status: WorkOrderStatus.processing,
|
||||
creatorName: '行政小刘',
|
||||
assigneeName: 'IT小王',
|
||||
assigneeName: '行政部',
|
||||
createdAt: DateTime.now().subtract(const Duration(hours: 1)),
|
||||
priority: 'urgent',
|
||||
category: '设备调试',
|
||||
@@ -192,6 +294,39 @@ class ApiService {
|
||||
return orders.firstWhere((o) => o.id == id, orElse: () => orders.first);
|
||||
}
|
||||
|
||||
Future<WorkOrder> acceptWorkOrder(String id) async {
|
||||
await Future.delayed(const Duration(milliseconds: 700));
|
||||
if (_random.nextInt(20) == 0) {
|
||||
throw Exception('网络异常,请稍后重试');
|
||||
}
|
||||
final order = await getWorkOrderDetail(id);
|
||||
return order.copyWith(
|
||||
status: WorkOrderStatus.processing,
|
||||
assigneeName: order.assigneeName ?? '维修部',
|
||||
);
|
||||
}
|
||||
|
||||
Future<WorkOrder> transferWorkOrder(String id, String department) async {
|
||||
await Future.delayed(const Duration(milliseconds: 700));
|
||||
if (_random.nextInt(20) == 0) {
|
||||
throw Exception('网络异常,请稍后重试');
|
||||
}
|
||||
final order = await getWorkOrderDetail(id);
|
||||
return order.copyWith(assigneeName: department);
|
||||
}
|
||||
|
||||
Future<WorkOrder> completeWorkOrder(String id) async {
|
||||
await Future.delayed(const Duration(milliseconds: 700));
|
||||
if (_random.nextInt(20) == 0) {
|
||||
throw Exception('网络异常,请稍后重试');
|
||||
}
|
||||
final order = await getWorkOrderDetail(id);
|
||||
return order.copyWith(
|
||||
status: WorkOrderStatus.completed,
|
||||
completedAt: DateTime.now(),
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<OrderItem>> getOrders(OrderStatus status) async {
|
||||
await Future.delayed(const Duration(milliseconds: 600));
|
||||
final allOrders = [
|
||||
|
||||
111
lib/services/vosk_voice_service.dart
Normal file
111
lib/services/vosk_voice_service.dart
Normal file
@@ -0,0 +1,111 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:vosk_flutter_service/vosk_flutter.dart';
|
||||
|
||||
class VoskVoiceService {
|
||||
VoskVoiceService._();
|
||||
static final VoskVoiceService instance = VoskVoiceService._();
|
||||
|
||||
static const String _modelAsset = 'assets/models/vosk-model-small-cn-0.22.zip';
|
||||
static const int _sampleRate = 16000;
|
||||
|
||||
final VoskFlutterPlugin _vosk = VoskFlutterPlugin.instance();
|
||||
Model? _model;
|
||||
Recognizer? _recognizer;
|
||||
SpeechService? _speechService;
|
||||
|
||||
bool _initializing = false;
|
||||
bool _listening = false;
|
||||
|
||||
final StreamController<String> _partialController = StreamController<String>.broadcast();
|
||||
final StreamController<String> _resultController = StreamController<String>.broadcast();
|
||||
final StreamController<String> _errorController = StreamController<String>.broadcast();
|
||||
|
||||
StreamSubscription<String>? _partialSub;
|
||||
StreamSubscription<String>? _resultSub;
|
||||
|
||||
Stream<String> get partialStream => _partialController.stream;
|
||||
Stream<String> get resultStream => _resultController.stream;
|
||||
Stream<String> get errorStream => _errorController.stream;
|
||||
bool get isListening => _listening;
|
||||
bool get isReady => _speechService != null;
|
||||
|
||||
Future<bool> ensureReady() async {
|
||||
if (_speechService != null) return true;
|
||||
if (_initializing) {
|
||||
while (_initializing) {
|
||||
await Future<void>.delayed(const Duration(milliseconds: 80));
|
||||
}
|
||||
return _speechService != null;
|
||||
}
|
||||
_initializing = true;
|
||||
try {
|
||||
final modelPath = await ModelLoader().loadFromAssets(_modelAsset);
|
||||
_model = await _vosk.createModel(modelPath);
|
||||
_recognizer = await _vosk.createRecognizer(
|
||||
model: _model!,
|
||||
sampleRate: _sampleRate,
|
||||
);
|
||||
_speechService = await _vosk.initSpeechService(_recognizer!);
|
||||
_partialSub = _speechService!.onPartial().listen((raw) {
|
||||
final text = _extractField(raw, 'partial');
|
||||
if (text != null) _partialController.add(text);
|
||||
});
|
||||
_resultSub = _speechService!.onResult().listen((raw) {
|
||||
final text = _extractField(raw, 'text');
|
||||
if (text != null) _resultController.add(text);
|
||||
});
|
||||
return true;
|
||||
} catch (e) {
|
||||
_errorController.add(e.toString());
|
||||
return false;
|
||||
} finally {
|
||||
_initializing = false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> start() async {
|
||||
if (!await ensureReady()) return false;
|
||||
if (_listening) return true;
|
||||
final ok = await _speechService!.start(
|
||||
onRecognitionError: (err) => _errorController.add(err.toString()),
|
||||
);
|
||||
_listening = ok ?? false;
|
||||
return _listening;
|
||||
}
|
||||
|
||||
Future<void> stop() async {
|
||||
if (!_listening || _speechService == null) return;
|
||||
await _speechService!.stop();
|
||||
_listening = false;
|
||||
}
|
||||
|
||||
Future<void> cancel() async {
|
||||
if (_speechService == null) return;
|
||||
await _speechService!.cancel();
|
||||
_listening = false;
|
||||
}
|
||||
|
||||
Future<void> dispose() async {
|
||||
await _partialSub?.cancel();
|
||||
await _resultSub?.cancel();
|
||||
await _speechService?.dispose();
|
||||
await _recognizer?.dispose();
|
||||
_speechService = null;
|
||||
_recognizer = null;
|
||||
_model = null;
|
||||
_listening = false;
|
||||
}
|
||||
|
||||
String? _extractField(String raw, String key) {
|
||||
try {
|
||||
final map = jsonDecode(raw);
|
||||
if (map is Map && map[key] is String) {
|
||||
final v = (map[key] as String).trim();
|
||||
return v;
|
||||
}
|
||||
} catch (_) {}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user