This commit is contained in:
2025-04-09 13:30:50 +08:00
commit a4a995d24c
154 changed files with 9580 additions and 0 deletions

168
lib/call/call_view.dart Normal file
View File

@@ -0,0 +1,168 @@
import 'dart:async';
import 'package:cashier_reserve/common/base/ui.dart';
import 'package:cashier_reserve/common/channel/model/call_status_change_model.dart';
import 'package:cashier_reserve/common/manager/event_manager.dart';
class CallView extends StatefulWidget {
final CallStatusChangeModel statusModel;
final Function(String action)? onAction;
const CallView({super.key, required this.statusModel, this.onAction});
@override
State<StatefulWidget> createState() {
return _CallViewState();
}
}
class _CallViewState extends State<CallView> {
int _callDuration = 0;
Timer? _callDurationTimer;
bool _isAccept = false;
@override
void initState() {
super.initState();
EventManager.addListener<CallReceivedEvent>(this, (event) {
_acceptCall();
});
}
@override
void dispose() {
_cancelDurationTimer();
EventManager.cancelListener(this);
super.dispose();
}
@override
Widget build(BuildContext context) {
return PopScope(
canPop: false,
child: Scaffold(
backgroundColor: Colors.black.withOpacity(0.2),
body: GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
},
child: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
color: const Color(0x55000000),
child: Center(
child: Container(
width: MediaQuery.of(context).size.width * 0.8,
height: MediaQuery.of(context).size.height * 0.8,
decoration: BoxDecoration(
color: Colors.blue.withOpacity(0.9),
borderRadius: BorderRadius.circular(10),
),
child: _buildCallInfo(context),
),
),
),
),
),
);
}
String _getCallDuration() {
// 将秒数转换为时分秒
int hour = _callDuration ~/ 3600;
int minute = _callDuration % 3600 ~/ 60;
int second = _callDuration % 60;
return "${hour.toString().padLeft(2, '0')}:${minute.toString().padLeft(2, '0')}:${second.toString().padLeft(2, '0')}";
}
_acceptCall() {
if (_isAccept) {
return;
}
_isAccept = true;
_startDurationTimer();
}
_startDurationTimer() {
_callDurationTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
setState(() {
_callDuration++;
});
});
}
_cancelDurationTimer() {
_callDurationTimer?.cancel();
}
Widget _buildCallInfo(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
_buildIncomingInfo(context),
const SizedBox(height: 70),
_buildActionBtn(context),
],
);
}
Widget _buildIncomingInfo(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
"${widget.statusModel.number}",
style: const TextStyle(
fontSize: 35, color: Colors.white, fontWeight: FontWeight.bold),
),
const SizedBox(height: 10),
Text(
"${widget.statusModel.region}",
style: const TextStyle(fontSize: 15, color: Colors.white),
),
const SizedBox(height: 20),
Text(_getCallDuration(),
style: const TextStyle(fontSize: 20, color: Colors.white)),
],
);
}
Widget _buildActionBtn(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
InkWell(
onTap: () {
// 拒绝
if (widget.onAction != null) {
widget.onAction!("reject");
}
},
child: Container(
padding: const EdgeInsets.all(10),
child: Image.asset('images/call/reject.png', width: 60, height: 60),
),
),
if (!_isAccept) const SizedBox(width: 80),
if (!_isAccept) InkWell(
onTap: () {
// 接听
if (widget.onAction != null) {
widget.onAction!("accept");
}
_acceptCall();
},
child: Container(
padding: const EdgeInsets.all(10),
child: Image.asset('images/call/accept.png', width: 60, height: 60),
),
),
],
);
}
}

View File

@@ -0,0 +1,11 @@
import '../utils/func_tools.dart';
class BaseModel {
/// 发生错误时错误信息, 有可能会被后面的请求覆盖掉
String? msg;
String? getMsg() {
return isEmptyString(msg) ? '' : msg;
}
}

View File

@@ -0,0 +1,15 @@
import 'package:cashier_reserve/common/base/ui_model.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
export 'package:provider/provider.dart';
class MyProvider<T> {
static T of<T>(BuildContext context, {bool listen = true}) {
T provider = Provider.of<T>(context, listen: listen);
if (provider is BaseUIModel) {
BaseUIModel viewModel = provider;
viewModel.context = context;
}
return provider;
}
}

129
lib/common/base/ui.dart Normal file
View File

@@ -0,0 +1,129 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../push/push.dart';
import './ui_model.dart';
import './widgets.dart';
export 'package:flutter/material.dart';
class BaseUIController extends StatefulWidget {
final BaseUI stateWidget;
const BaseUIController({super.key, required this.stateWidget});
@override
State<BaseUIController> createState() {
// ignore: no_logic_in_create_state
return stateWidget;
}
}
abstract class BaseUI extends State<BaseUIController>
with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context) {
super.build(context);
final provider = getProvider(context, listen: true);
return Stack(
children: [
Scaffold(
backgroundColor: getBackgroundColor(),
appBar: getAppBar(context),
body: _buildBody(context),
bottomNavigationBar: getBottomNavigationBar(context),
floatingActionButton: getFloatActionButton(context),
),
if (provider.showProgressHUD)
Material(
color: Colors.black.withAlpha(50),
child: Center(
child: Card(
child: Container(
width: 100,
height: 100,
color: const Color(0xaa000000),
child: const Center(
child: SizedBox(
width: 40,
height: 40,
child: CircularProgressIndicator(),
),
),
),
),
),
),
],
);
}
dismissKeyboard(BuildContext context) {
FocusScope.of(context).requestFocus(FocusNode());
}
Widget? getFloatActionButton(BuildContext context) {
return null;
}
Widget? getBottomNavigationBar(BuildContext context) {
return null;
}
@protected
BaseUIModel getProvider(BuildContext context, {bool listen = true});
@protected
String? getTitleStr(BuildContext context);
Future pushToPage<T extends BaseUIModel>(
BuildContext context, BaseUIModel model) {
return YJPush.pushWidget(
context,
getBuild<T>(model),
);
}
Widget getBuild<T extends BaseUIModel>(BaseUIModel model) {
return ChangeNotifierProvider(
create: (BuildContext context) {
return model as T;
},
child: BaseUIController(
stateWidget: this,
),
);
}
AppBar? getAppBar(BuildContext context) {
return makeAppbar(context, getTitleStr(context));
}
Color getBackgroundColor() {
return appBackGroundColor;
}
bool hiddenBackBtn() {
return false;
}
Widget _buildBody(BuildContext context) {
return GestureDetector(
child: Container(
color: getBackgroundColor(),
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: buildBody(context),
),
onTap: () {
dismissKeyboard(context);
},
);
}
@protected
Widget buildBody(BuildContext context);
@override
bool get wantKeepAlive => false;
}

View File

@@ -0,0 +1,46 @@
import 'package:flutter/material.dart';
class BaseUIModel with ChangeNotifier {
static Function? staticNotifyListeners;
BuildContext? context;
bool showProgressHUD = false;
bool initialPage = true;
/// 判断页面是否被销毁
bool disposed = false;
dismissKeyboard(BuildContext? context) {
if (context == null) {
return;
}
FocusScope.of(context).requestFocus(FocusNode());
}
showLoadingHud() {
showProgressHUD = true;
notifyListeners();
}
dismissLoadingHud() {
showProgressHUD = false;
notifyListeners();
}
@override
void notifyListeners() {
if (!hasListeners) {
return;
}
if (disposed) {
return;
}
super.notifyListeners();
}
@override
void dispose() {
disposed = true;
super.dispose();
}
}

View File

@@ -0,0 +1,483 @@
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:cashier_reserve/common/base/ui.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
/// 应用主体背景颜色
const Color appBackGroundColor = Color.fromARGB(255, 247, 250, 255);
/// 主色调
const Color mdMainColor = Color(0xff2854BC);
/// fix for Flutter 2.5
/// https://github.com/flutter/flutter/issues/77904
Brightness? getAppBrightness(bool isLight) {
if (kIsWeb) {
return null;
}
if (Platform.isIOS) {
return isLight ? Brightness.dark : Brightness.light;
}
return isLight ? Brightness.light : Brightness.dark;
}
/// 微信风格Appbar
AppBar makeWechatStyleAppbar(BuildContext context, String title,
{List<Widget>? actions,
final Color backGroundColor = Colors.white,
final Color textColor = Colors.black,
final Brightness brightness = Brightness.light,
bool centerTitle = false,
PreferredSizeWidget? bottom}) {
return AppBar(
backgroundColor: backGroundColor,
elevation: 0,
centerTitle: centerTitle,
systemOverlayStyle: SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: getAppBrightness(false),
statusBarBrightness: getAppBrightness(false)),
actions: actions,
bottom: bottom,
title: Text(
title,
style: TextStyle(
fontSize: 17, color: textColor, fontWeight: FontWeight.bold),
),
leading: IconButton(
iconSize: 18,
tooltip: "返回",
color: Colors.black,
icon: Icon(
Icons.arrow_back_ios,
color: textColor,
),
onPressed: () {
Navigator.pop(context);
},
),
);
}
/// 通用Appbar
AppBar makeAppbar(BuildContext context, String? title,
{List<Widget>? actions,
Color? backgroundColor,
PreferredSizeWidget? bottom,
final bool showBack = true,
Widget? titleWidget,
Widget? backWidget,
VoidCallback? onBack,
Key? key}) {
backgroundColor ??= mdMainColor;
return AppBar(
key: key,
backgroundColor: backgroundColor,
elevation: 0,
centerTitle: true,
actions: actions,
bottom: bottom,
systemOverlayStyle: SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: getAppBrightness(false),
statusBarBrightness: getAppBrightness(false)),
title: titleWidget ??
Text(
title!,
style: const TextStyle(
fontSize: 17, color: Colors.white, fontWeight: FontWeight.bold),
),
leading: showBack
? (backWidget ??
IconButton(
iconSize: 18,
tooltip: "返回",
color: Colors.white,
splashColor: Colors.black,
hoverColor: Colors.black,
icon: const Icon(Icons.arrow_back_ios),
onPressed: onBack ??
() {
Navigator.pop(context);
},
))
: null,
);
}
/// ListView 无感上拉加载
class ListViewPullUpLoadMore {
final ScrollController scrollController;
final Future<void> Function() onLoadMore;
bool _isLoading = false;
ListViewPullUpLoadMore(this.scrollController, {required this.onLoadMore}) {
scrollController.addListener(() {
if (_isLoading) {
return;
}
if (scrollController.offset ==
scrollController.position.maxScrollExtent) {
/// 到底部,添加加载中标记,触发回调
_isLoading = true;
onLoadMore().then((_) {
/// 加载完毕,标记复位
_isLoading = false;
});
}
});
}
}
/// 绘制通用下划线
Widget makeDivider(BuildContext? context,
{Color? color, EdgeInsetsGeometry? padding, double? height}) {
color ??= Colors.black.withAlpha(50);
return Padding(
padding: padding ??
const EdgeInsets.only(left: 18.5, right: 18.5, top: 5, bottom: 5),
child: Divider(
color: color,
height: height ?? 0.5,
),
);
}
/// 卡片容器
Widget makeCardContainer(
{required BuildContext context,
required Widget child,
Key? key,
EdgeInsetsGeometry? padding,
EdgeInsetsGeometry? paddingIn,
BorderRadius? borderRadius,
Color? color,
double? elevation,
double? width,
bool nullWidth = false}) {
return SizedBox(
key: key,
width: nullWidth ? null : (width ?? MediaQuery.of(context).size.width),
child: Padding(
padding: padding ?? const EdgeInsets.only(left: 8, right: 8),
child: Card(
color: color,
elevation: elevation ?? 0,
shape: RoundedRectangleBorder(
borderRadius: borderRadius ?? BorderRadius.circular(0),
),
child: Padding(
padding:
paddingIn ?? const EdgeInsets.only(top: 10, bottom: 10),
child: child,
))));
}
/// 通用页面加载动画
Widget makeLoadBody(BuildContext context) {
return const Center(
child: CircularProgressIndicator(),
);
}
/// 通用网络图片
Widget makeNetImage(BuildContext context, String? imageUrl,
{BoxFit? fit, double? height, double? width}) {
imageUrl ??= "https://image.xiaomfzs.com/xmf-app/user_icon.png";
if (kIsWeb) {
return Image.network(imageUrl, fit: fit, height: height, width: width);
}
return CachedNetworkImage(
height: height,
width: width,
imageUrl: imageUrl,
fit: fit,
placeholder: (context, url) {
return Container(
color: Colors.black.withAlpha(50),
child: Center(
child: Image.asset("images/loading.png"),
));
},
errorWidget: (BuildContext context, String url, dynamic error) {
return const Icon(Icons.error);
},
);
}
/// 通用头像
Widget makeUserAvatar(BuildContext context, String? imgUrl,
{final double size = 46}) {
return SizedBox(
width: size,
height: size,
child: ClipRRect(
borderRadius: BorderRadius.circular(10000.0),
child: makeNetImage(context, imgUrl, fit: BoxFit.fill),
),
);
}
/// 底部按钮边距容器
Widget makeNavButtonPadding(Widget? widget) {
return Padding(
padding: const EdgeInsets.only(bottom: 14),
child: widget,
);
}
/// 底部按钮
Widget makeBottomNavButton(String text,
{required VoidCallback? onTap,
Color color = mdMainColor,
EdgeInsetsGeometry? padding,
TextStyle? textStyle,
double? elevation}) {
return Padding(
padding: padding ?? const EdgeInsets.only(left: 20, right: 20),
child: ElevatedButton(
style: TextButton.styleFrom(
backgroundColor: color, elevation: elevation ?? 1),
onPressed: onTap,
child: Text(
text,
style: textStyle ??
const TextStyle(color: Colors.white, fontWeight: FontWeight.bold),
),
),
);
}
/// 收起键盘容器
Widget makeDismissKeyboardContainer(BuildContext context, Widget child,
{VoidCallback? onDismissed}) {
return GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
if (onDismissed != null) {
onDismissed();
}
},
child: child,
);
}
/// 底部弹出按钮
Future<String?> showBottomMenu(
BuildContext context, Map<String, String> items) async {
return await showCupertinoModalPopup<String?>(
context: context,
builder: (ctx) {
return CupertinoActionSheet(
cancelButton: CupertinoActionSheetAction(
onPressed: () {
Navigator.pop(context);
},
child: const Text(
'取消',
style: TextStyle(fontSize: 14, color: Colors.red),
),
),
actions: <Widget>[
for (var kv in (items.entries))
CupertinoActionSheetAction(
onPressed: () {
Navigator.pop(context, kv.key);
},
child: Text(
kv.value,
style: const TextStyle(fontSize: 14, color: Colors.black),
),
),
],
);
});
}
/// 日期选择器
Future<DateTime?> showMyDatePicker(
BuildContext context, CupertinoDatePickerMode mode,
{DateTime? defaultTime, DateTime? minTime, DateTime? maxTime}) async {
final DateTime? date = await showModalBottomSheet<DateTime>(
context: context,
builder: (context) {
DateTime pickedDate = DateTime.now();
return SizedBox(
height: 250,
child: Column(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
CupertinoButton(
child: const Text('取消'),
onPressed: () {
Navigator.of(context).pop();
},
),
CupertinoButton(
child: const Text('确认'),
onPressed: () {
Navigator.pop(context, pickedDate);
},
),
],
),
const Divider(
height: 0,
thickness: 1,
),
Expanded(
child: CupertinoApp(
debugShowCheckedModeBanner: false,
locale: const Locale('zh', 'CN'),
localizationsDelegates: const [
GlobalCupertinoLocalizations.delegate,
],
supportedLocales: const [
Locale('zh', 'CN'),
Locale('en', 'US'),
],
home: CupertinoDatePicker(
mode: mode,
initialDateTime: defaultTime ?? DateTime.now(),
minimumDate: minTime,
maximumDate: maxTime,
onDateTimeChanged: (DateTime dateTime) {
pickedDate = dateTime;
},
),
),
),
],
),
);
},
);
return date;
}
/// appBar Search Widget
PreferredSizeWidget makeAppBarSearchWidget(
BuildContext context,
String tipText,
ValueChanged<String> onSubmitted, {
double height = 56,
double? width,
TextInputType? textInputType,
List<TextInputFormatter>? inputFormatters,
ValueChanged<String>? onChanged,
TextEditingController? controller,
}) {
return PreferredSize(
child: SizedBox(
height: height,
child: Padding(
padding:
const EdgeInsets.only(top: 5, left: 10, right: 10, bottom: 10),
child: Container(
width: width ?? MediaQuery.of(context).size.width,
decoration: BoxDecoration(
color: Colors.white.withAlpha(100),
borderRadius: BorderRadius.circular(20),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
margin: const EdgeInsets.only(left: 15),
child: const Icon(
Icons.search,
color: Colors.black45,
size: 20,
),
),
Container(
margin: const EdgeInsets.only(left: 5),
width: MediaQuery.of(context).size.width -
15 * 2 -
15 -
20 -
5 -
(width ?? 0),
child: TextField(
controller: controller,
textInputAction: TextInputAction.search,
onSubmitted: onSubmitted,
keyboardType: textInputType,
inputFormatters: inputFormatters,
onChanged: onChanged,
decoration: InputDecoration(
hintText: tipText,
hintStyle: const TextStyle(
fontSize: 15, color: Colors.black45),
border: InputBorder.none,
),
),
),
],
)),
),
),
preferredSize: const Size(0, 42));
}
Widget makeSearchWidget(
BuildContext context, String hintText, ValueChanged<String> onSubmitted) {
return Padding(
padding: const EdgeInsets.only(top: 5, left: 0, right: 0, bottom: 14),
child: Container(
height: 42,
width: MediaQuery.of(context).size.width,
decoration: BoxDecoration(
color: const Color.fromARGB(255, 247, 250, 255),
borderRadius: BorderRadius.circular(20),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
margin: const EdgeInsets.only(left: 15),
child: const Icon(
Icons.search,
color: Colors.black45,
size: 20,
),
),
Container(
margin: const EdgeInsets.only(left: 5),
width: MediaQuery.of(context).size.width - 15 * 2 - 82,
child: TextField(
textInputAction: TextInputAction.search,
onSubmitted: onSubmitted,
onChanged: (str) {
if (str == "") {
onSubmitted("");
}
},
decoration: InputDecoration(
hintText: hintText,
hintStyle:
const TextStyle(fontSize: 15, color: Colors.black45),
border: InputBorder.none,
),
),
),
],
)),
);
}
class AlwaysDisabledFocusNode extends FocusNode {
@override
bool get hasFocus => false;
}

View File

@@ -0,0 +1,54 @@
import 'dart:convert';
import 'package:cashier_reserve/common/channel/model/call_status_change_model.dart';
import 'package:cashier_reserve/common/print/print.dart';
import 'package:flutter/services.dart';
import '../manager/event_manager.dart';
import 'model/call_log_model.dart';
import 'names.dart';
class MyEventChannel {
static void startListener() {
onGetCallLogResult();
onCallStatusChange();
}
static void onGetCallLogResult() {
EventChannel channel = EventChannel(getChannelName(kCallLogCallback));
channel.receiveBroadcastStream().listen((Object? o) {
GetCallLogEvent event = GetCallLogEvent();
if (o is String) {
event.isSuccess = true;
List<dynamic> list = json.decode(o);
List<CallLogModel> callLogs = [];
for (var item in list) {
callLogs.add(CallLogModel.fromJson(item));
if (callLogs.length >= 100) {
break;
}
}
event.callLogs = callLogs.reversed.toList();
} else {
event.isSuccess = false;
}
EventManager.postEvent(event);
}, onError: (Object error) {
yjPrint("onGetCallLogResult error");
});
}
static void onCallStatusChange() {
EventChannel channel = EventChannel(getChannelName(kCallStatusChange));
channel.receiveBroadcastStream().listen((Object? o) {
yjPrint("onCallStatusChange: $o");
if (o is String) {
Map<String, dynamic> m = json.decode(o);
CallStatusChangeModel model = CallStatusChangeModel.fromJson(m);
EventManager.postEvent(CallStatusChangeEvent(model: model));
}
}, onError: (Object error) {
yjPrint("onCallStatusChange error");
});
}
}

View File

@@ -0,0 +1,46 @@
import 'package:flutter/services.dart';
import '../print/print.dart';
import 'names.dart';
class ChannelManager {
static Future<void> getCallLog(String param) async {
MethodChannel channel = MethodChannel(getChannelName(kGetCallLog));
try {
final result = await channel.invokeMethod(kGetCallLog, param);
yjPrint(result);
} on PlatformException catch (e) {
yjPrint('$kGetCallLog 发生异常:$e');
}
}
static Future<void> acceptCall() async {
MethodChannel channel = MethodChannel(getChannelName(kAcceptCall));
try {
final result = await channel.invokeMethod(kAcceptCall);
yjPrint(result);
} on PlatformException catch (e) {
yjPrint('$kAcceptCall 发生异常:$e');
}
}
static Future<void> rejectCall() async {
MethodChannel channel = MethodChannel(getChannelName(kRejectCall));
try {
final result = await channel.invokeMethod(kRejectCall);
yjPrint(result);
} on PlatformException catch (e) {
yjPrint('$kRejectCall 发生异常:$e');
}
}
static Future<void> endCall() async {
MethodChannel channel = MethodChannel(getChannelName(kEndCall));
try {
final result = await channel.invokeMethod(kEndCall);
yjPrint(result);
} on PlatformException catch (e) {
yjPrint('$kEndCall 发生异常:$e');
}
}
}

View File

@@ -0,0 +1,86 @@
import 'dart:convert';
/// number : "18092171236"
/// date : "2024-11-21 09:32:27"
/// duration : 0
/// type : 5
/// name : gong
CallLogModel callLogModelFromJson(String str) =>
CallLogModel.fromJson(json.decode(str));
String callLogModelToJson(CallLogModel data) => json.encode(data.toJson());
class CallLogModel {
CallLogModel({
String? number,
String? date,
num? duration,
num? type,
String? name,
String? time,
}) {
_number = number;
_date = date;
_duration = duration;
_type = type;
_name = name;
_time = time;
}
CallLogModel.fromJson(dynamic json) {
_number = json['number'];
_date = json['date'];
_duration = json['duration'];
_type = json['type'];
_name = json['name'];
_time = json['time'];
}
String? _number;
String? _date;
num? _duration;
num? _type;
String? _name;
String? _time;
CallLogModel copyWith({
String? number,
String? date,
num? duration,
num? type,
String? name,
String? time,
}) =>
CallLogModel(
number: number ?? _number,
date: date ?? _date,
duration: duration ?? _duration,
type: type ?? _type,
name: name ?? _name,
time: time ?? _time,
);
String? get number => _number;
String? get date => _date;
num? get duration => _duration;
num? get type => _type;
String? get name => _name;
String? get time => _time;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['number'] = _number;
map['date'] = _date;
map['duration'] = _duration;
map['type'] = _type;
map['name'] = _name;
map['time'] = _time;
return map;
}
}

View File

@@ -0,0 +1,54 @@
import 'dart:convert';
/// state : "Incoming"
/// number : "18020143310"
/// name : "gong"
/// region : "江苏省 南京市"
CallStatusChangeModel callStatusChangeModelFromJson(String str) => CallStatusChangeModel.fromJson(json.decode(str));
String callStatusChangeModelToJson(CallStatusChangeModel data) => json.encode(data.toJson());
class CallStatusChangeModel {
CallStatusChangeModel({
String? state,
String? number,
String? name,
String? region,}){
_state = state;
_number = number;
_name = name;
_region = region;
}
CallStatusChangeModel.fromJson(dynamic json) {
_state = json['state'];
_number = json['number'];
_name = json['name'];
_region = json['region'];
}
String? _state;
String? _number;
String? _name;
String? _region;
CallStatusChangeModel copyWith({ String? state,
String? number,
String? name,
String? region,
}) => CallStatusChangeModel( state: state ?? _state,
number: number ?? _number,
name: name ?? _name,
region: region ?? _region,
);
String? get state => _state;
String? get number => _number;
String? get name => _name;
String? get region => _region;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['state'] = _state;
map['number'] = _number;
map['name'] = _name;
map['region'] = _region;
return map;
}
}

View File

@@ -0,0 +1,14 @@
const String kChannelBaseName = 'com.czg.cashier_reserve/';
const String kGetCallLog = 'getCallLog';
const String kCallLogCallback = 'callLogCallback';
const String kCallStatusChange = 'callStatusChange';
const String kAcceptCall = 'acceptCall';
const String kEndCall = 'endCall';
const String kRejectCall = 'rejectCall';
String getChannelName(name) {
return kChannelBaseName + name;
}

View File

@@ -0,0 +1,45 @@
import 'package:cashier_reserve/common/print/print.dart';
import 'package:encrypt/encrypt.dart';
import 'package:pointycastle/asymmetric/api.dart';
class PwdEncrypt {
static const _rsaPubKey =
"MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBANL378k3RiZHWx5AfJqdH9xRNBmD9wGD2iRe41HdTNF8RUhNnHit5NpMNtGL0NPTSSpPjjI1kJfVorRvaQerUgkCAwEAAQ==";
static String encrypt(String pwd) {
return _encryptString(publicKeyString: _rsaPubKey, plainText: pwd);
}
/// 利用公钥进行加密
static String _encryptString(
{required String publicKeyString, required String plainText}) {
String key = '-----BEGIN PUBLIC KEY-----\n\r';
int length = 0;
const baseLen = 64;
while (length < publicKeyString.length) {
bool over = length + baseLen > publicKeyString.length;
key += publicKeyString.substring(
length, over ? publicKeyString.length : length + baseLen);
key += '\n\r';
length += baseLen;
}
key += '-----END PUBLIC KEY-----';
yjPrint(key);
final publicKey = _parsePublicKeyFromPem(key);
final encrypt = Encrypter(RSA(publicKey: publicKey));
final encryptedText = encrypt.encrypt(plainText);
return encryptedText.base64;
}
/// 通过PEM字符串解析公钥字符串
static RSAPublicKey _parsePublicKeyFromPem(String pemString) {
final key = RSAKeyParser().parse(pemString);
return key as RSAPublicKey;
}
}

View File

@@ -0,0 +1,182 @@
import 'package:flutter/foundation.dart' show SynchronousFuture;
import 'package:flutter/cupertino.dart';
class CupertinoLocalizationsDelegate
extends LocalizationsDelegate<CupertinoLocalizations> {
const CupertinoLocalizationsDelegate();
@override
bool isSupported(Locale locale) =>
<String>['en', 'zh'].contains(locale.languageCode);
@override
SynchronousFuture<_DefaultCupertinoLocalizations> load(Locale locale) {
return SynchronousFuture<_DefaultCupertinoLocalizations>(
_DefaultCupertinoLocalizations(locale.languageCode));
}
@override
bool shouldReload(CupertinoLocalizationsDelegate old) => false;
}
class _DefaultCupertinoLocalizations extends CupertinoLocalizations {
_DefaultCupertinoLocalizations(this._languageCode);
final DefaultCupertinoLocalizations _en =
const DefaultCupertinoLocalizations();
final String _languageCode;
final Map<String, Map<String, String>> _dict = <String, Map<String, String>>{
'en': <String, String>{
'alert': 'Alert',
'copy': 'Copy',
'paste': 'Paste',
'cut': 'Cut',
'selectAll': 'Select all',
'today': 'today'
},
'zh': <String, String>{
'alert': '警告',
'copy': '复制',
'paste': '粘贴',
'cut': '剪切',
'selectAll': '选择全部',
'today': '今天'
}
};
@override
String get alertDialogLabel => _get('alert')!;
@override
String get anteMeridiemAbbreviation => _en.anteMeridiemAbbreviation;
@override
String get postMeridiemAbbreviation => _en.postMeridiemAbbreviation;
@override
String get copyButtonLabel => _get('copy')!;
@override
String get cutButtonLabel => _get('cut')!;
@override
String get pasteButtonLabel => _get('paste')!;
@override
String get selectAllButtonLabel => _get('selectAll')!;
@override
DatePickerDateOrder get datePickerDateOrder => _en.datePickerDateOrder;
@override
DatePickerDateTimeOrder get datePickerDateTimeOrder =>
_en.datePickerDateTimeOrder;
@override
String datePickerHour(int hour) => _en.datePickerHour(hour);
@override
String datePickerHourSemanticsLabel(int hour) =>
_en.datePickerHourSemanticsLabel(hour);
@override
String datePickerMediumDate(DateTime date) => _en.datePickerMediumDate(date);
@override
String datePickerMinute(int minute) => _en.datePickerMinute(minute);
@override
String datePickerMinuteSemanticsLabel(int minute) =>
_en.datePickerMinuteSemanticsLabel(minute);
@override
String datePickerMonth(int monthIndex) => _en.datePickerMonth(monthIndex);
@override
String datePickerYear(int yearIndex) => _en.datePickerYear(yearIndex);
@override
String timerPickerHour(int hour) => _en.timerPickerHour(hour);
@override
String timerPickerHourLabel(int hour) => _en.timerPickerHourLabel(hour);
@override
String timerPickerMinute(int minute) => _en.timerPickerMinute(minute);
@override
String timerPickerMinuteLabel(int minute) =>
_en.timerPickerMinuteLabel(minute);
@override
String timerPickerSecond(int second) => _en.timerPickerSecond(second);
@override
String timerPickerSecondLabel(int second) =>
_en.timerPickerSecondLabel(second);
String? _get(String key) {
return _dict[_languageCode]![key];
}
@override
String get todayLabel => _get("today")!;
@override
String get modalBarrierDismissLabel => _en.modalBarrierDismissLabel;
@override
String tabSemanticsLabel({required int tabIndex, required int tabCount}) {
return _en.tabSemanticsLabel(tabIndex: tabIndex, tabCount: tabCount);
}
@override
List<String> get timerPickerHourLabels => _en.timerPickerHourLabels;
@override
List<String> get timerPickerMinuteLabels => _en.timerPickerMinuteLabels;
@override
List<String> get timerPickerSecondLabels => _en.timerPickerSecondLabels;
@override
String get searchTextFieldPlaceholderLabel => throw UnimplementedError();
@override
String datePickerDayOfMonth(int dayIndex, [int? weekDay]) {
return _en.datePickerDayOfMonth(dayIndex, weekDay);
}
@override
// TODO: implement noSpellCheckReplacementsLabel
String get noSpellCheckReplacementsLabel => throw UnimplementedError();
@override
String datePickerStandaloneMonth(int monthIndex) {
// TODO: implement datePickerStandaloneMonth
throw UnimplementedError();
}
@override
// TODO: implement lookUpButtonLabel
String get lookUpButtonLabel => throw UnimplementedError();
@override
// TODO: implement menuDismissLabel
String get menuDismissLabel => throw UnimplementedError();
@override
// TODO: implement searchWebButtonLabel
String get searchWebButtonLabel => throw UnimplementedError();
@override
// TODO: implement shareButtonLabel
String get shareButtonLabel => throw UnimplementedError();
@override
// TODO: implement clearButtonLabel
String get clearButtonLabel => throw UnimplementedError();
}

View File

@@ -0,0 +1,52 @@
import 'package:timeago/timeago.dart';
/// Chinese-China messages
class ZhCnMessages implements LookupMessages {
@override
String prefixAgo() => '';
@override
String prefixFromNow() => '';
@override
String suffixAgo() => '';
@override
String suffixFromNow() => '';
@override
String lessThanOneMinute(int seconds) => '不到一分钟';
@override
String aboutAMinute(int minutes) => '约 1 分钟';
@override
String minutes(int minutes) => '$minutes 分钟';
@override
String aboutAnHour(int minutes) => '约 1 小时';
@override
String hours(int hours) => '$hours 小时';
@override
String aDay(int hours) => '约 1 天';
@override
String days(int days) => '$days';
@override
String aboutAMonth(int days) => '约 1 个月';
@override
String months(int months) => '$months';
@override
String aboutAYear(int year) => '约 1 年';
@override
String years(int years) => '$years';
@override
String wordSeparator() => '';
}

View File

@@ -0,0 +1,129 @@
import 'package:cashier_reserve/common/print/print.dart';
import 'package:cashier_reserve/common/push/push.dart';
import 'package:cashier_reserve/data_model/login/login_result.dart';
import 'package:cashier_reserve/login/login_view.dart';
import 'package:cashier_reserve/model/reserve_model.dart';
import 'package:cashier_reserve/model/version_model.dart';
import 'package:cashier_reserve/update_version/update_version_view.dart';
import 'package:easy_refresh/easy_refresh.dart';
import 'package:package_info_plus/package_info_plus.dart';
import '../base/ui.dart';
import '../channel/channel_event.dart';
import '../utils/func_tools.dart';
import 'hive_manager.dart';
class AppManager {
static BuildContext? globalContext;
static bool _isAlertLogin = false;
static String _smsContent = "";
static Future<void> initThirdPackage() async {
MyEventChannel.startListener();
await HiveManager.initHive();
EasyRefresh.defaultHeaderBuilder = () => const ClassicHeader(
dragText: "下拉刷新",
readyText: "释放刷新",
armedText: "正在刷新",
processingText: "正在刷新",
processedText: "刷新完成",
noMoreText: "没有更多数据了",
failedText: "刷新失败",
messageText: "上次刷新时间:%T",
showText: true,
showMessage: true,
iconDimension: 30,
spacing: 10,
);
}
static void setGlobalContext(BuildContext context) {
globalContext = context;
}
static bool isLogin() {
String token = HiveManager.getUserToken();
if (isEmptyString(token)) {
gotoLogin();
return false;
}
return true;
}
static void gotoLogin() {
if (_isAlertLogin) {
return;
}
_isAlertLogin = true;
while (Navigator.of(globalContext!).canPop()) {
Navigator.of(globalContext!).pop();
}
YJPush.presentWidget(globalContext!, const LoginView());
}
static void checkAppVersion() async {
final res = await VersionModel.requestNewVersionInfo();
yjPrint(res);
if (res == null || res.version == null) {
return;
}
PackageInfo packageInfo = await PackageInfo.fromPlatform();
yjPrint("version == ${packageInfo.version}");
List<String> serverList = res.version!.split(".");
List<String> localList = packageInfo.version.split(".");
if (serverList.length != 3 || localList.length != 3) {
return;
}
int serverVersion = int.parse(serverList[0]) * 10000 + int.parse(serverList[1]) * 100 + int.parse(serverList[2]);
int localVersion = int.parse(localList[0]) * 10000 + int.parse(localList[1]) * 100 + int.parse(localList[2]);
if (serverVersion <= localVersion) {
return;
}
yjPrint("serverVersion == $serverVersion, localVersion == $localVersion");
YJPush.presentWidget(globalContext!, UpdateVersionView(versionModel: res,));
}
static void disposeLoginWidget() {
_isAlertLogin = false;
}
static bool isShowLoginView() {
return _isAlertLogin;
}
static String getUserToken() {
return HiveManager.getUserToken();
}
static void loginSuccess(LoginResult? r) {
HiveManager.setUserToken(r?.token ?? '');
HiveManager.setShopId((r?.shopId ?? '').toString());
HiveManager.setShopName(r?.shopName ?? '');
HiveManager.setShopLogo(r?.logo ?? '');
HiveManager.setUserInfo(r?.user?.toString() ?? '');
disposeLoginWidget();
Navigator.of(globalContext!).pop();
}
static Future<String> loadReserveSms() async {
if (_smsContent.isNotEmpty) {
return _smsContent;
}
_smsContent = await ReserveModel.getReserveSms();
return _smsContent;
}
}

View File

@@ -0,0 +1,69 @@
import 'dart:async';
import 'package:cashier_reserve/common/channel/model/call_log_model.dart';
import 'package:cashier_reserve/common/channel/model/call_status_change_model.dart';
import 'package:event_bus/event_bus.dart';
class EventManager {
static EventBus? _eventBus;
static final Map<dynamic, List<StreamSubscription>> _eventMap = {};
static EventBus? get eventBus => getEventBus();
static EventBus? getEventBus() {
_eventBus ??= EventBus();
return _eventBus;
}
static void postEvent(MyEvent event) {
getEventBus()!.fire(event);
}
static void addListener<T>(dynamic widget, void Function(T event) onData) {
StreamSubscription event = EventManager.eventBus!.on<T>().listen((T e) {
onData(e);
});
List<StreamSubscription>? list = _eventMap[widget];
list ??= [];
list.add(event);
_eventMap[widget] = list;
}
static void cancelListener(dynamic widget) {
List<StreamSubscription>? list = _eventMap[widget];
if (list == null) {
return;
}
for (var event in list) {
event.cancel();
}
}
}
class MyEvent {
String name = '';
}
class GetCallLogEvent extends MyEvent {
List<CallLogModel> callLogs;
bool isLoadMore = false;
bool isSuccess = false;
GetCallLogEvent({this.callLogs = const [], this.isLoadMore = false, this.isSuccess = false});
}
class CallStatusChangeEvent extends MyEvent {
/// state = IncomingNumberReceived 来电接听
/// state = OutGoing 呼出
/// state = End 通话结束
/// state = Incoming 来电
CallStatusChangeModel model;
CallStatusChangeEvent({required this.model});
}
class CallReceivedEvent extends MyEvent {
CallReceivedEvent();
}

View File

@@ -0,0 +1,61 @@
import 'package:hive/hive.dart';
import 'package:path_provider/path_provider.dart';
class HiveManager {
static Box? _userInfoBox;
static Future<void> initHive() async {
final dir = await getApplicationDocumentsDirectory();
Hive.defaultDirectory = dir.path;
_userInfoBox = Hive.box();
}
static void clearLoginInfo() {
_userInfoBox?.delete('token');
_userInfoBox?.delete('shopId');
_userInfoBox?.delete('shopName');
_userInfoBox?.delete('shopLogo');
_userInfoBox?.delete('userInfo');
}
static void setUserToken(String token) {
_userInfoBox?.put('token', token);
}
static String getUserToken() {
return _userInfoBox?.get('token') ?? '';
}
static void setShopId(String shopId) {
_userInfoBox?.put('shopId', shopId);
}
static String getShopId() {
return _userInfoBox?.get('shopId') ?? '';
}
static void setShopName(String shopName) {
_userInfoBox?.put('shopName', shopName);
}
static String getShopName() {
return _userInfoBox?.get('shopName') ?? '';
}
static void setShopLogo(String shopLogo) {
_userInfoBox?.put('shopLogo', shopLogo);
}
static String getShopLogo() {
return _userInfoBox?.get('shopLogo') ?? '';
}
static void setUserInfo(String userInfo) {
_userInfoBox?.put('userInfo', userInfo);
}
static String getUserInfo() {
return _userInfoBox?.get('userInfo') ?? '';
}
}

View File

@@ -0,0 +1,10 @@
import 'package:flutter/foundation.dart';
const bool inProduction = !kDebugMode;
void yjPrint(Object? object) {
if (kDebugMode) {
print(object);
}
}

87
lib/common/push/push.dart Normal file
View File

@@ -0,0 +1,87 @@
import 'package:flutter/cupertino.dart';
class YJPush {
static Future presentWidget(BuildContext context, Widget widget) {
return Navigator.of(context).push(PageRouteBuilder(
pageBuilder: (BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return widget;
},
transitionsBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
// 添加一个平移动画
return YJPush.createTransition(animation, child);
},
opaque: false,
barrierDismissible: true));
}
static void presentWidgetNoAnimation(BuildContext context, Widget widget) {
Navigator.of(context).push(PageRouteBuilder(
pageBuilder: (BuildContext context, Animation<double> animation,
Animation<double> secondaryAnimation) {
return widget;
},
transitionsBuilder: (
BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
// 添加一个平移动画
return YJPush.createNoAnimationTransition(animation, child);
},
opaque: false,
barrierDismissible: true));
}
/// 创建一个平移变换
/// 跳转过去查看源代码,可以看到有各种各样定义好的变换
static SlideTransition createTransition(
Animation<double> animation, Widget child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(0.0, 1.0),
end: const Offset(0.0, 0.0),
).animate(animation),
child: child, // child is the value returned by pageBuilder
);
}
/// 创建一个平移变换 没有动画
static SlideTransition createNoAnimationTransition(
Animation<double> animation, Widget child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(0.0, 0.0),
end: const Offset(0.0, 0.0),
).animate(animation),
child: child, // child is the value returned by pageBuilder
);
}
static Future pushWidget(BuildContext context, Widget widget) async {
Route route = CupertinoPageRoute(builder: (context) => widget);
return await Navigator.push(context, route);
}
static Future pushAndRemoveWidget(BuildContext context, Widget widget) async {
Route route = CupertinoPageRoute(builder: (context) => widget);
return await Navigator.pushAndRemoveUntil(context, route, (route) => false);
}
static SlideTransition createHTransition(
Animation<double> animation, Widget child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(1.0, 0.0),
end: const Offset(0.0, 0.0),
).animate(animation),
child: child, // child is the value returned by pageBuilder
);
}
}

View File

@@ -0,0 +1,115 @@
import 'dart:convert';
import 'package:cashier_reserve/common/manager/app_manager.dart';
import 'package:cashier_reserve/common/manager/hive_manager.dart';
import 'package:cashier_reserve/common/print/print.dart';
import 'package:dio/dio.dart';
import '../utils/utils.dart';
const String kBaseUrl = 'http://192.168.1.31';
// const String kBaseUrl = 'https://cashier.sxczgkj.com';
const kSuccessCode = 200;
const kNeedLoginCode = 401;
const Map<String, dynamic> _emptyMap = {};
class RequestManager {
/// HttpClient
static final Dio _c = Dio(BaseOptions(
baseUrl: kBaseUrl,
connectTimeout: const Duration(milliseconds: 5000),
receiveTimeout: const Duration(milliseconds: 5000)));
/// GET
static Future<dynamic> get(String url,
{Map<String, dynamic> params = _emptyMap, bool catchError = true}) {
if (url.contains("?")) {
url += "&shopId=${HiveManager.getShopId()}";
} else {
url += "?shopId=${HiveManager.getShopId()}";
}
// 处理传入的其他参数
if (params.isNotEmpty) {
String paramString = Uri(queryParameters: params).query;
url += "&$paramString";
}
return _doRequest("GET", url, catchError: catchError);
}
/// DELETE
static Future<dynamic> delete(String url, {bool catchError = true}) {
return _doRequest("DELETE", url, catchError: catchError);
}
/// POST
static Future<dynamic> post(String url, Map<String, dynamic>? body,
{bool catchError = true}) {
if (body != null) {
body["shopId"] = HiveManager.getShopId();
} else {
body = {"shopId": HiveManager.getShopId()};
}
return _doRequest("POST", url, body: body, catchError: catchError);
}
/// PUT
static Future<dynamic> put(String url, Map<String, dynamic>? body,
{bool catchError = true}) {
if (body != null) {
body["shopId"] = HiveManager.getShopId();
} else {
body = {"shopId": HiveManager.getShopId()};
}
return _doRequest("PUT", url, body: body, catchError: catchError);
}
static Future<dynamic> _doRequest(String method, String url,
{Map<String, dynamic>? body, required bool catchError}) async {
yjPrint("[RequestManager req]: $method$url】body === $body");
try {
final resp = await _c.request(url,
data: body,
options: Options(method: method, headers: {
"token": AppManager.getUserToken(),
"platformtype":'telephone'
}));
yjPrint("[RequestManager resp]: $method$url】body === $resp");
if (catchError) {
if (resp.statusCode == kNeedLoginCode) {
AppManager.gotoLogin();
return null;
}
if (resp.statusCode != kSuccessCode) {
_alertError("提示", resp.data ?? "未知错误");
return null;
}
}
return resp.data["data"];
} catch (e) {
yjPrint("[RequestManager error]: $method$url】error === $e");
// _alertError("网络错误", "请检查您的网络连接!");
if (e is DioException) {
DioException de = e;
if (de.response?.statusCode == kNeedLoginCode) {
AppManager.gotoLogin();
return null;
}
if (de.response?.data is Map) {
final data = de.response!.data as Map;
Utils.toast(data["message"], AppManager.globalContext!);
}
}
return null;
}
}
static _alertError(String title, String errorText) {
Utils.alert(AppManager.globalContext!, errorText, title: title);
}
}

View File

@@ -0,0 +1,8 @@
bool isEmptyString(String? str) {
if (str == null || str.isEmpty) {
return true;
}
return false;
}

View File

@@ -0,0 +1,47 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'func_tools.dart';
class Utils {
///大陆手机号码11位数匹配格式前三位固定格式+后8位任意数
static bool isPhone(String phone) {
return RegExp('^1\\d{10}\$').hasMatch(phone);
}
static void toast(String? text, BuildContext? context) {
if (isEmptyString(text)) {
return;
}
Fluttertoast.showToast(
msg: "$text",
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.CENTER,
timeInSecForIosWeb: 1,
// backgroundColor: Colors.red,
// textColor: Colors.white,
fontSize: 16.0);
}
static Future alert(BuildContext context, String? content, {String? title}) {
return showCupertinoDialog(
context: context,
builder: (BuildContext context) {
return CupertinoAlertDialog(
title: Text(title ?? '提示'),
content: Text(content!),
actions: <Widget>[
TextButton(
child: const Text('确定'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
});
}
}

View File

@@ -0,0 +1,606 @@
import 'dart:convert';
/// loginType : "merchant"
/// shopName : "双屿pirse(测77)"
/// logo : "https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/20240925/835c45e4729048e8921ba17d021029ec.jpg"
/// shopId : 11
/// mainId : 11
/// user : {"authorities":[{"authority":"tbProductGroup:del"},{"authority":"tbProductGroup:list"},{"authority":"roles:add"},{"authority":"roles:edit"},{"authority":"roles:del"},{"authority":"tbShopTable:list"},{"authority":"roles:list"}],"dataScopes":[18],"roles":["tbProductGroup:del","tbProductGroup:list","roles:add","roles:edit","roles:del","tbShopTable:list","roles:list"],"user":{"avatarName":null,"avatarPath":null,"createBy":"admin","createTime":"2024-05-27 14:10:09","dept":{"id":18,"name":"前厅"},"deptId":null,"email":null,"enabled":true,"gender":null,"id":40,"isAdmin":false,"jobs":[{"id":10,"name":"产品经理"}],"nickName":"双屿pirse(测77)","password":"$2a$10$j414tLJ/fdXXzXriW3y9A.QdP9Ak1.1hiGbvb1.zmQjPc5q.xoipy","phone":"13575788745","pwdResetTime":"2024-08-05 14:18:59","roles":[{"dataScope":"本级","id":2,"level":2,"name":"普通用户"}],"updateBy":"admin","updateTime":"2024-05-27 14:10:09","username":"13718478323"}}
/// token : "Bearer eyJhbGciOiJIUzUxMiJ9.eyJqdGkiOiI3ZWYzZmU2NWM0ZDU0ZjE5OWU5YmE4NTQ1NmUyZDZiZiIsInVzZXIiOiIxMzcxODQ3ODMyMyIsInNob3BJZCI6IjExIiwic3ViIjoiMTM3MTg0NzgzMjMifQ.9DY1f02WGTJ2e5w1MFJrUQ4KwEvl-QKWsSRALvpYYo6EsA3NercZAN56xL68e8K0eSsArk-9i1LFxb-PtBwmgw"
LoginResult loginResultFromJson(String str) =>
LoginResult.fromJson(json.decode(str));
String loginResultToJson(LoginResult data) => json.encode(data.toJson());
class LoginResult {
LoginResult({
num? loginType,
String? shopName,
String? logo,
num? shopId,
num? mainId,
User? user,
String? token,
}) {
_loginType = loginType;
_shopName = shopName;
_logo = logo;
_shopId = shopId;
_mainId = mainId;
_user = user;
_token = token;
}
LoginResult.fromJson(dynamic json) {
_loginType = json['loginType'];
_shopName = json['shopName'];
_logo = json['logo'];
_shopId = json['shopId'];
_mainId = json['mainId'];
_user = json['user'] != null ? User.fromJson(json['user']) : null;
_token = json['tokenInfo']['tokenValue'];
}
num? _loginType;
String? _shopName;
String? _logo;
num? _shopId;
num? _mainId;
User? _user;
String? _token;
LoginResult copyWith({
num? loginType,
String? shopName,
String? logo,
num? shopId,
num? mainId,
User? user,
String? token,
}) =>
LoginResult(
loginType: loginType ?? _loginType,
shopName: shopName ?? _shopName,
logo: logo ?? _logo,
shopId: shopId ?? _shopId,
mainId: mainId ?? _mainId,
user: user ?? _user,
token: token ?? _token,
);
num? get loginType => _loginType;
String? get shopName => _shopName;
String? get logo => _logo;
num? get shopId => _shopId;
num? get mainId => _mainId;
User? get user => _user;
String? get token => _token;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['loginType'] = _loginType;
map['shopName'] = _shopName;
map['logo'] = _logo;
map['shopId'] = _shopId;
map['mainId'] = _mainId;
if (_user != null) {
map['user'] = _user?.toJson();
}
map['token'] = _token;
return map;
}
}
/// authorities : [{"authority":"tbProductGroup:del"},{"authority":"tbProductGroup:list"},{"authority":"roles:add"},{"authority":"roles:edit"},{"authority":"roles:del"},{"authority":"tbShopTable:list"},{"authority":"roles:list"}]
/// dataScopes : [18]
/// roles : ["tbProductGroup:del","tbProductGroup:list","roles:add","roles:edit","roles:del","tbShopTable:list","roles:list"]
/// user : {"avatarName":null,"avatarPath":null,"createBy":"admin","createTime":"2024-05-27 14:10:09","dept":{"id":18,"name":"前厅"},"deptId":null,"email":null,"enabled":true,"gender":null,"id":40,"isAdmin":false,"jobs":[{"id":10,"name":"产品经理"}],"nickName":"双屿pirse(测77)","password":"$2a$10$j414tLJ/fdXXzXriW3y9A.QdP9Ak1.1hiGbvb1.zmQjPc5q.xoipy","phone":"13575788745","pwdResetTime":"2024-08-05 14:18:59","roles":[{"dataScope":"本级","id":2,"level":2,"name":"普通用户"}],"updateBy":"admin","updateTime":"2024-05-27 14:10:09","username":"13718478323"}
User userFromJson(String str) => User.fromJson(json.decode(str));
String userToJson(User data) => json.encode(data.toJson());
class User {
User({
List<Authorities>? authorities,
List<num>? dataScopes,
List<String>? roles,
UserInfo? userInfo,
}) {
_authorities = authorities;
_dataScopes = dataScopes;
_roles = roles;
_userInfo = userInfo;
}
User.fromJson(dynamic json) {
if (json['authorities'] != null) {
_authorities = [];
json['authorities'].forEach((v) {
_authorities?.add(Authorities.fromJson(v));
});
}
_dataScopes =
json['dataScopes'] != null ? json['dataScopes'].cast<num>() : [];
_roles = json['roles'] != null ? json['roles'].cast<String>() : [];
_userInfo = json['user'] != null ? UserInfo.fromJson(json['user']) : null;
}
List<Authorities>? _authorities;
List<num>? _dataScopes;
List<String>? _roles;
UserInfo? _userInfo;
User copyWith({
List<Authorities>? authorities,
List<num>? dataScopes,
List<String>? roles,
UserInfo? userInfo,
}) =>
User(
authorities: authorities ?? _authorities,
dataScopes: dataScopes ?? _dataScopes,
roles: roles ?? _roles,
userInfo: userInfo ?? _userInfo,
);
List<Authorities>? get authorities => _authorities;
List<num>? get dataScopes => _dataScopes;
List<String>? get roles => _roles;
UserInfo? get userInfo => _userInfo;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
if (_authorities != null) {
map['authorities'] = _authorities?.map((v) => v.toJson()).toList();
}
map['dataScopes'] = _dataScopes;
map['roles'] = _roles;
if (_userInfo != null) {
map['user'] = _userInfo?.toJson();
}
return map;
}
}
/// avatarName : null
/// avatarPath : null
/// createBy : "admin"
/// createTime : "2024-05-27 14:10:09"
/// dept : {"id":18,"name":"前厅"}
/// deptId : null
/// email : null
/// enabled : true
/// gender : null
/// id : 40
/// isAdmin : false
/// jobs : [{"id":10,"name":"产品经理"}]
/// nickName : "双屿pirse(测77)"
/// password : "$2a$10$j414tLJ/fdXXzXriW3y9A.QdP9Ak1.1hiGbvb1.zmQjPc5q.xoipy"
/// phone : "13575788745"
/// pwdResetTime : "2024-08-05 14:18:59"
/// roles : [{"dataScope":"本级","id":2,"level":2,"name":"普通用户"}]
/// updateBy : "admin"
/// updateTime : "2024-05-27 14:10:09"
/// username : "13718478323"
User userInfoFromJson(String str) => User.fromJson(json.decode(str));
String userInfoToJson(User data) => json.encode(data.toJson());
class UserInfo {
UserInfo({
dynamic avatarName,
dynamic avatarPath,
String? createBy,
String? createTime,
Dept? dept,
dynamic deptId,
dynamic email,
bool? enabled,
dynamic gender,
num? id,
bool? isAdmin,
List<Jobs>? jobs,
String? nickName,
String? password,
String? phone,
String? pwdResetTime,
List<Roles>? roles,
String? updateBy,
String? updateTime,
String? username,
}) {
_avatarName = avatarName;
_avatarPath = avatarPath;
_createBy = createBy;
_createTime = createTime;
_dept = dept;
_deptId = deptId;
_email = email;
_enabled = enabled;
_gender = gender;
_id = id;
_isAdmin = isAdmin;
_jobs = jobs;
_nickName = nickName;
_password = password;
_phone = phone;
_pwdResetTime = pwdResetTime;
_roles = roles;
_updateBy = updateBy;
_updateTime = updateTime;
_username = username;
}
UserInfo.fromJson(dynamic json) {
_avatarName = json['avatarName'];
_avatarPath = json['avatarPath'];
_createBy = json['createBy'];
_createTime = json['createTime'];
_dept = json['dept'] != null ? Dept.fromJson(json['dept']) : null;
_deptId = json['deptId'];
_email = json['email'];
_enabled = json['enabled'];
_gender = json['gender'];
_id = json['id'];
_isAdmin = json['isAdmin'];
if (json['jobs'] != null) {
_jobs = [];
json['jobs'].forEach((v) {
_jobs?.add(Jobs.fromJson(v));
});
}
_nickName = json['nickName'];
_password = json['password'];
_phone = json['phone'];
_pwdResetTime = json['pwdResetTime'];
if (json['roles'] != null) {
_roles = [];
json['roles'].forEach((v) {
_roles?.add(Roles.fromJson(v));
});
}
_updateBy = json['updateBy'];
_updateTime = json['updateTime'];
_username = json['username'];
}
dynamic _avatarName;
dynamic _avatarPath;
String? _createBy;
String? _createTime;
Dept? _dept;
dynamic _deptId;
dynamic _email;
bool? _enabled;
dynamic _gender;
num? _id;
bool? _isAdmin;
List<Jobs>? _jobs;
String? _nickName;
String? _password;
String? _phone;
String? _pwdResetTime;
List<Roles>? _roles;
String? _updateBy;
String? _updateTime;
String? _username;
UserInfo copyWith({
dynamic avatarName,
dynamic avatarPath,
String? createBy,
String? createTime,
Dept? dept,
dynamic deptId,
dynamic email,
bool? enabled,
dynamic gender,
num? id,
bool? isAdmin,
List<Jobs>? jobs,
String? nickName,
String? password,
String? phone,
String? pwdResetTime,
List<Roles>? roles,
String? updateBy,
String? updateTime,
String? username,
}) =>
UserInfo(
avatarName: avatarName ?? _avatarName,
avatarPath: avatarPath ?? _avatarPath,
createBy: createBy ?? _createBy,
createTime: createTime ?? _createTime,
dept: dept ?? _dept,
deptId: deptId ?? _deptId,
email: email ?? _email,
enabled: enabled ?? _enabled,
gender: gender ?? _gender,
id: id ?? _id,
isAdmin: isAdmin ?? _isAdmin,
jobs: jobs ?? _jobs,
nickName: nickName ?? _nickName,
password: password ?? _password,
phone: phone ?? _phone,
pwdResetTime: pwdResetTime ?? _pwdResetTime,
roles: roles ?? _roles,
updateBy: updateBy ?? _updateBy,
updateTime: updateTime ?? _updateTime,
username: username ?? _username,
);
dynamic get avatarName => _avatarName;
dynamic get avatarPath => _avatarPath;
String? get createBy => _createBy;
String? get createTime => _createTime;
Dept? get dept => _dept;
dynamic get deptId => _deptId;
dynamic get email => _email;
bool? get enabled => _enabled;
dynamic get gender => _gender;
num? get id => _id;
bool? get isAdmin => _isAdmin;
List<Jobs>? get jobs => _jobs;
String? get nickName => _nickName;
String? get password => _password;
String? get phone => _phone;
String? get pwdResetTime => _pwdResetTime;
List<Roles>? get roles => _roles;
String? get updateBy => _updateBy;
String? get updateTime => _updateTime;
String? get username => _username;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['avatarName'] = _avatarName;
map['avatarPath'] = _avatarPath;
map['createBy'] = _createBy;
map['createTime'] = _createTime;
if (_dept != null) {
map['dept'] = _dept?.toJson();
}
map['deptId'] = _deptId;
map['email'] = _email;
map['enabled'] = _enabled;
map['gender'] = _gender;
map['id'] = _id;
map['isAdmin'] = _isAdmin;
if (_jobs != null) {
map['jobs'] = _jobs?.map((v) => v.toJson()).toList();
}
map['nickName'] = _nickName;
map['password'] = _password;
map['phone'] = _phone;
map['pwdResetTime'] = _pwdResetTime;
if (_roles != null) {
map['roles'] = _roles?.map((v) => v.toJson()).toList();
}
map['updateBy'] = _updateBy;
map['updateTime'] = _updateTime;
map['username'] = _username;
return map;
}
}
/// dataScope : "本级"
/// id : 2
/// level : 2
/// name : "普通用户"
Roles rolesFromJson(String str) => Roles.fromJson(json.decode(str));
String rolesToJson(Roles data) => json.encode(data.toJson());
class Roles {
Roles({
String? dataScope,
num? id,
num? level,
String? name,
}) {
_dataScope = dataScope;
_id = id;
_level = level;
_name = name;
}
Roles.fromJson(dynamic json) {
_dataScope = json['dataScope'];
_id = json['id'];
_level = json['level'];
_name = json['name'];
}
String? _dataScope;
num? _id;
num? _level;
String? _name;
Roles copyWith({
String? dataScope,
num? id,
num? level,
String? name,
}) =>
Roles(
dataScope: dataScope ?? _dataScope,
id: id ?? _id,
level: level ?? _level,
name: name ?? _name,
);
String? get dataScope => _dataScope;
num? get id => _id;
num? get level => _level;
String? get name => _name;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['dataScope'] = _dataScope;
map['id'] = _id;
map['level'] = _level;
map['name'] = _name;
return map;
}
}
/// id : 10
/// name : "产品经理"
Jobs jobsFromJson(String str) => Jobs.fromJson(json.decode(str));
String jobsToJson(Jobs data) => json.encode(data.toJson());
class Jobs {
Jobs({
num? id,
String? name,
}) {
_id = id;
_name = name;
}
Jobs.fromJson(dynamic json) {
_id = json['id'];
_name = json['name'];
}
num? _id;
String? _name;
Jobs copyWith({
num? id,
String? name,
}) =>
Jobs(
id: id ?? _id,
name: name ?? _name,
);
num? get id => _id;
String? get name => _name;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['id'] = _id;
map['name'] = _name;
return map;
}
}
/// id : 18
/// name : "前厅"
Dept deptFromJson(String str) => Dept.fromJson(json.decode(str));
String deptToJson(Dept data) => json.encode(data.toJson());
class Dept {
Dept({
num? id,
String? name,
}) {
_id = id;
_name = name;
}
Dept.fromJson(dynamic json) {
_id = json['id'];
_name = json['name'];
}
num? _id;
String? _name;
Dept copyWith({
num? id,
String? name,
}) =>
Dept(
id: id ?? _id,
name: name ?? _name,
);
num? get id => _id;
String? get name => _name;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['id'] = _id;
map['name'] = _name;
return map;
}
}
/// authority : "tbProductGroup:del"
Authorities authoritiesFromJson(String str) =>
Authorities.fromJson(json.decode(str));
String authoritiesToJson(Authorities data) => json.encode(data.toJson());
class Authorities {
Authorities({
String? authority,
}) {
_authority = authority;
}
Authorities.fromJson(dynamic json) {
_authority = json['authority'];
}
String? _authority;
Authorities copyWith({
String? authority,
}) =>
Authorities(
authority: authority ?? _authority,
);
String? get authority => _authority;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['authority'] = _authority;
return map;
}
}

View File

@@ -0,0 +1,58 @@
import 'dart:convert';
/// consumeOrders : 0
/// cancelOrders : 0
/// phoneNumber : "10086"
ReserveLogModel reserveLogModelFromJson(String str) =>
ReserveLogModel.fromJson(json.decode(str));
String reserveLogModelToJson(ReserveLogModel data) =>
json.encode(data.toJson());
class ReserveLogModel {
ReserveLogModel({
num? consumeOrders,
num? cancelOrders,
String? phoneNumber,
}) {
_consumeOrders = consumeOrders;
_cancelOrders = cancelOrders;
_phoneNumber = phoneNumber;
}
ReserveLogModel.fromJson(dynamic json) {
_consumeOrders = json['consumeOrders'];
_cancelOrders = json['cancelOrders'];
_phoneNumber = json['phoneNumber'];
}
num? _consumeOrders;
num? _cancelOrders;
String? _phoneNumber;
ReserveLogModel copyWith({
num? consumeOrders,
num? cancelOrders,
String? phoneNumber,
}) =>
ReserveLogModel(
consumeOrders: consumeOrders ?? _consumeOrders,
cancelOrders: cancelOrders ?? _cancelOrders,
phoneNumber: phoneNumber ?? _phoneNumber,
);
num? get consumeOrders => _consumeOrders;
num? get cancelOrders => _cancelOrders;
String? get phoneNumber => _phoneNumber;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['consumeOrders'] = _consumeOrders;
map['cancelOrders'] = _cancelOrders;
map['phoneNumber'] = _phoneNumber;
return map;
}
}

View File

@@ -0,0 +1,117 @@
import 'dart:convert';
/// capacityRange : null
/// createdAt : 1716791935255
/// id : 20
/// name : "大厅"
/// price : null
/// shopId : 11
/// sort : 0
/// updatedAt : 1716791935255
/// view : null
TableAreaModel tableAreaModelFromJson(String str) =>
TableAreaModel.fromJson(json.decode(str));
String tableAreaModelToJson(TableAreaModel data) => json.encode(data.toJson());
class TableAreaModel {
TableAreaModel({
dynamic capacityRange,
num? createdAt,
num? id,
String? name,
dynamic price,
num? shopId,
num? sort,
num? updatedAt,
dynamic view,
}) {
_capacityRange = capacityRange;
_createdAt = createdAt;
_id = id;
_name = name;
_price = price;
_shopId = shopId;
_sort = sort;
_updatedAt = updatedAt;
_view = view;
}
TableAreaModel.fromJson(dynamic json) {
_capacityRange = json['capacityRange'];
_createdAt = json['createdAt'];
_id = json['id'];
_name = json['name'];
_price = json['price'];
_shopId = json['shopId'];
_sort = json['sort'];
_updatedAt = json['updatedAt'];
_view = json['view'];
}
dynamic _capacityRange;
num? _createdAt;
num? _id;
String? _name;
dynamic _price;
num? _shopId;
num? _sort;
num? _updatedAt;
dynamic _view;
TableAreaModel copyWith({
dynamic capacityRange,
num? createdAt,
num? id,
String? name,
dynamic price,
num? shopId,
num? sort,
num? updatedAt,
dynamic view,
}) =>
TableAreaModel(
capacityRange: capacityRange ?? _capacityRange,
createdAt: createdAt ?? _createdAt,
id: id ?? _id,
name: name ?? _name,
price: price ?? _price,
shopId: shopId ?? _shopId,
sort: sort ?? _sort,
updatedAt: updatedAt ?? _updatedAt,
view: view ?? _view,
);
dynamic get capacityRange => _capacityRange;
num? get createdAt => _createdAt;
num? get id => _id;
String? get name => _name;
dynamic get price => _price;
num? get shopId => _shopId;
num? get sort => _sort;
num? get updatedAt => _updatedAt;
dynamic get view => _view;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['capacityRange'] = _capacityRange;
map['createdAt'] = _createdAt;
map['id'] = _id;
map['name'] = _name;
map['price'] = _price;
map['shopId'] = _shopId;
map['sort'] = _sort;
map['updatedAt'] = _updatedAt;
map['view'] = _view;
return map;
}
}

View File

@@ -0,0 +1,547 @@
import 'dart:convert';
/// amount : 0.00
/// areaId : 20
/// autoClear : 0
/// bookingInfo : {"arrivedTime":null,"bookingDate":"2024-11-22","bookingPerson":"王","bookingTableNum":0,"bookingTime":"2024-11-22 18:30:00","bookingType":"dinner","createTime":"2024-11-22 16:16:17","createUserName":"双屿pirse(测77)","delFlag":0,"dinerNum":6,"diningStandardPrice":0.00,"diningStandardUnit":"table","diningType":"普通用餐","focus":1,"gender":1,"id":1,"orderNo":"BK241122161617-cwba","phoneNumber":"13011223344","receiveMarketingSms":1,"remark":"今个高兴,和哥几个一醉方休","shopId":11,"shopTableId":151,"status":20,"timeoutMinute":60,"updateTime":"2024-11-22 16:27:47"}
/// createdAt : 1726283309239
/// endTime : "2024-11-06 18:03:33"
/// id : 151
/// isPredate : 1
/// maxCapacity : 4
/// name : "A1"
/// perhour : null
/// predateAmount : 0.00
/// productNum : 6
/// qrcode : "3000101848"
/// realAmount : 33.70
/// shopId : 11
/// sort : 0
/// status : "using"
/// totalAmount : 33.70
/// type : 0
/// updatedAt : 1728720069117
/// useNum : 1
/// useTime : "2024-11-22 16:28:59"
/// view : null
TableModel tableModelFromJson(String str) =>
TableModel.fromJson(json.decode(str));
String tableModelToJson(TableModel data) => json.encode(data.toJson());
class TableModel {
TableModel({
num? amount,
num? areaId,
num? autoClear,
BookingInfo? bookingInfo,
num? createdAt,
String? endTime,
num? id,
num? isPredate,
num? maxCapacity,
String? name,
dynamic perhour,
num? predateAmount,
num? productNum,
String? qrcode,
num? realAmount,
num? shopId,
num? sort,
String? status,
num? totalAmount,
num? type,
num? updatedAt,
num? useNum,
String? useTime,
dynamic view,
}) {
_amount = amount;
_areaId = areaId;
_autoClear = autoClear;
_bookingInfo = bookingInfo;
_createdAt = createdAt;
_endTime = endTime;
_id = id;
_isPredate = isPredate;
_maxCapacity = maxCapacity;
_name = name;
_perhour = perhour;
_predateAmount = predateAmount;
_productNum = productNum;
_qrcode = qrcode;
_realAmount = realAmount;
_shopId = shopId;
_sort = sort;
_status = status;
_totalAmount = totalAmount;
_type = type;
_updatedAt = updatedAt;
_useNum = useNum;
_useTime = useTime;
_view = view;
}
TableModel.fromJson(dynamic json) {
_amount = json['amount'];
_areaId = json['areaId'];
_autoClear = json['autoClear'];
_bookingInfo = json['bookingInfo'] != null
? BookingInfo.fromJson(json['bookingInfo'])
: null;
_createdAt = json['createdAt'];
_endTime = json['endTime'];
_id = json['id'];
_isPredate = json['isPredate'];
_maxCapacity = json['maxCapacity'];
_name = json['name'];
_perhour = json['perhour'];
_predateAmount = json['predateAmount'];
_productNum = json['productNum'];
_qrcode = json['qrcode'];
_realAmount = json['realAmount'];
_shopId = json['shopId'];
_sort = json['sort'];
_status = json['status'];
_totalAmount = json['totalAmount'];
_type = json['type'];
_updatedAt = json['updatedAt'];
_useNum = json['useNum'];
_useTime = json['useTime'];
_view = json['view'];
}
num? _amount;
num? _areaId;
num? _autoClear;
BookingInfo? _bookingInfo;
num? _createdAt;
String? _endTime;
num? _id;
num? _isPredate;
num? _maxCapacity;
String? _name;
dynamic _perhour;
num? _predateAmount;
num? _productNum;
String? _qrcode;
num? _realAmount;
num? _shopId;
num? _sort;
String? _status;
num? _totalAmount;
num? _type;
num? _updatedAt;
num? _useNum;
String? _useTime;
dynamic _view;
TableModel copyWith({
num? amount,
num? areaId,
num? autoClear,
BookingInfo? bookingInfo,
num? createdAt,
String? endTime,
num? id,
num? isPredate,
num? maxCapacity,
String? name,
dynamic perhour,
num? predateAmount,
num? productNum,
String? qrcode,
num? realAmount,
num? shopId,
num? sort,
String? status,
num? totalAmount,
num? type,
num? updatedAt,
num? useNum,
String? useTime,
dynamic view,
}) =>
TableModel(
amount: amount ?? _amount,
areaId: areaId ?? _areaId,
autoClear: autoClear ?? _autoClear,
bookingInfo: bookingInfo ?? _bookingInfo,
createdAt: createdAt ?? _createdAt,
endTime: endTime ?? _endTime,
id: id ?? _id,
isPredate: isPredate ?? _isPredate,
maxCapacity: maxCapacity ?? _maxCapacity,
name: name ?? _name,
perhour: perhour ?? _perhour,
predateAmount: predateAmount ?? _predateAmount,
productNum: productNum ?? _productNum,
qrcode: qrcode ?? _qrcode,
realAmount: realAmount ?? _realAmount,
shopId: shopId ?? _shopId,
sort: sort ?? _sort,
status: status ?? _status,
totalAmount: totalAmount ?? _totalAmount,
type: type ?? _type,
updatedAt: updatedAt ?? _updatedAt,
useNum: useNum ?? _useNum,
useTime: useTime ?? _useTime,
view: view ?? _view,
);
num? get amount => _amount;
num? get areaId => _areaId;
num? get autoClear => _autoClear;
BookingInfo? get bookingInfo => _bookingInfo;
num? get createdAt => _createdAt;
String? get endTime => _endTime;
num? get id => _id;
num? get isPredate => _isPredate;
num? get maxCapacity => _maxCapacity;
String? get name => _name;
dynamic get perhour => _perhour;
num? get predateAmount => _predateAmount;
num? get productNum => _productNum;
String? get qrcode => _qrcode;
num? get realAmount => _realAmount;
num? get shopId => _shopId;
num? get sort => _sort;
String? get status => _status;
num? get totalAmount => _totalAmount;
num? get type => _type;
num? get updatedAt => _updatedAt;
num? get useNum => _useNum;
String? get useTime => _useTime;
dynamic get view => _view;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['amount'] = _amount;
map['areaId'] = _areaId;
map['autoClear'] = _autoClear;
if (_bookingInfo != null) {
map['bookingInfo'] = _bookingInfo?.toJson();
}
map['createdAt'] = _createdAt;
map['endTime'] = _endTime;
map['id'] = _id;
map['isPredate'] = _isPredate;
map['maxCapacity'] = _maxCapacity;
map['name'] = _name;
map['perhour'] = _perhour;
map['predateAmount'] = _predateAmount;
map['productNum'] = _productNum;
map['qrcode'] = _qrcode;
map['realAmount'] = _realAmount;
map['shopId'] = _shopId;
map['sort'] = _sort;
map['status'] = _status;
map['totalAmount'] = _totalAmount;
map['type'] = _type;
map['updatedAt'] = _updatedAt;
map['useNum'] = _useNum;
map['useTime'] = _useTime;
map['view'] = _view;
return map;
}
}
/// arrivedTime : null
/// bookingDate : "2024-11-22"
/// bookingPerson : "王"
/// bookingTableNum : 0
/// bookingTime : "2024-11-22 18:30:00"
/// bookingType : "dinner"
/// createTime : "2024-11-22 16:16:17"
/// createUserName : "双屿pirse(测77)"
/// delFlag : 0
/// dinerNum : 6
/// diningStandardPrice : 0.00
/// diningStandardUnit : "table"
/// diningType : "普通用餐"
/// focus : 1
/// gender : 1
/// id : 1
/// orderNo : "BK241122161617-cwba"
/// phoneNumber : "13011223344"
/// receiveMarketingSms : 1
/// remark : "今个高兴,和哥几个一醉方休"
/// shopId : 11
/// shopTableId : 151
/// status : 20
/// timeoutMinute : 60
/// updateTime : "2024-11-22 16:27:47"
BookingInfo bookingInfoFromJson(String str) =>
BookingInfo.fromJson(json.decode(str));
String bookingInfoToJson(BookingInfo data) => json.encode(data.toJson());
class BookingInfo {
BookingInfo({
dynamic arrivedTime,
String? bookingDate,
String? bookingPerson,
num? bookingTableNum,
String? bookingTime,
String? bookingType,
String? createTime,
String? createUserName,
num? delFlag,
num? dinerNum,
num? diningStandardPrice,
String? diningStandardUnit,
String? diningType,
num? focus,
num? gender,
num? id,
String? orderNo,
String? phoneNumber,
num? receiveMarketingSms,
String? remark,
num? shopId,
num? shopTableId,
num? status,
num? timeoutMinute,
String? updateTime,
}) {
_arrivedTime = arrivedTime;
_bookingDate = bookingDate;
_bookingPerson = bookingPerson;
_bookingTableNum = bookingTableNum;
_bookingTime = bookingTime;
_bookingType = bookingType;
_createTime = createTime;
_createUserName = createUserName;
_delFlag = delFlag;
_dinerNum = dinerNum;
_diningStandardPrice = diningStandardPrice;
_diningStandardUnit = diningStandardUnit;
_diningType = diningType;
_focus = focus;
_gender = gender;
_id = id;
_orderNo = orderNo;
_phoneNumber = phoneNumber;
_receiveMarketingSms = receiveMarketingSms;
_remark = remark;
_shopId = shopId;
_shopTableId = shopTableId;
_status = status;
_timeoutMinute = timeoutMinute;
_updateTime = updateTime;
}
BookingInfo.fromJson(dynamic json) {
_arrivedTime = json['arrivedTime'];
_bookingDate = json['bookingDate'];
_bookingPerson = json['bookingPerson'];
_bookingTableNum = json['bookingTableNum'];
_bookingTime = json['bookingTime'];
_bookingType = json['bookingType'];
_createTime = json['createTime'];
_createUserName = json['createUserName'];
_delFlag = json['delFlag'];
_dinerNum = json['dinerNum'];
_diningStandardPrice = json['diningStandardPrice'];
_diningStandardUnit = json['diningStandardUnit'];
_diningType = json['diningType'];
_focus = json['focus'];
_gender = json['gender'];
_id = json['id'];
_orderNo = json['orderNo'];
_phoneNumber = json['phoneNumber'];
_receiveMarketingSms = json['receiveMarketingSms'];
_remark = json['remark'];
_shopId = json['shopId'];
_shopTableId = json['shopTableId'];
_status = json['status'];
_timeoutMinute = json['timeoutMinute'];
_updateTime = json['updateTime'];
}
dynamic _arrivedTime;
String? _bookingDate;
String? _bookingPerson;
num? _bookingTableNum;
String? _bookingTime;
String? _bookingType;
String? _createTime;
String? _createUserName;
num? _delFlag;
num? _dinerNum;
num? _diningStandardPrice;
String? _diningStandardUnit;
String? _diningType;
num? _focus;
num? _gender;
num? _id;
String? _orderNo;
String? _phoneNumber;
num? _receiveMarketingSms;
String? _remark;
num? _shopId;
num? _shopTableId;
num? _status;
num? _timeoutMinute;
String? _updateTime;
BookingInfo copyWith({
dynamic arrivedTime,
String? bookingDate,
String? bookingPerson,
num? bookingTableNum,
String? bookingTime,
String? bookingType,
String? createTime,
String? createUserName,
num? delFlag,
num? dinerNum,
num? diningStandardPrice,
String? diningStandardUnit,
String? diningType,
num? focus,
num? gender,
num? id,
String? orderNo,
String? phoneNumber,
num? receiveMarketingSms,
String? remark,
num? shopId,
num? shopTableId,
num? status,
num? timeoutMinute,
String? updateTime,
}) =>
BookingInfo(
arrivedTime: arrivedTime ?? _arrivedTime,
bookingDate: bookingDate ?? _bookingDate,
bookingPerson: bookingPerson ?? _bookingPerson,
bookingTableNum: bookingTableNum ?? _bookingTableNum,
bookingTime: bookingTime ?? _bookingTime,
bookingType: bookingType ?? _bookingType,
createTime: createTime ?? _createTime,
createUserName: createUserName ?? _createUserName,
delFlag: delFlag ?? _delFlag,
dinerNum: dinerNum ?? _dinerNum,
diningStandardPrice: diningStandardPrice ?? _diningStandardPrice,
diningStandardUnit: diningStandardUnit ?? _diningStandardUnit,
diningType: diningType ?? _diningType,
focus: focus ?? _focus,
gender: gender ?? _gender,
id: id ?? _id,
orderNo: orderNo ?? _orderNo,
phoneNumber: phoneNumber ?? _phoneNumber,
receiveMarketingSms: receiveMarketingSms ?? _receiveMarketingSms,
remark: remark ?? _remark,
shopId: shopId ?? _shopId,
shopTableId: shopTableId ?? _shopTableId,
status: status ?? _status,
timeoutMinute: timeoutMinute ?? _timeoutMinute,
updateTime: updateTime ?? _updateTime,
);
dynamic get arrivedTime => _arrivedTime;
String? get bookingDate => _bookingDate;
String? get bookingPerson => _bookingPerson;
num? get bookingTableNum => _bookingTableNum;
String? get bookingTime => _bookingTime;
String? get bookingType => _bookingType;
String? get createTime => _createTime;
String? get createUserName => _createUserName;
num? get delFlag => _delFlag;
num? get dinerNum => _dinerNum;
num? get diningStandardPrice => _diningStandardPrice;
String? get diningStandardUnit => _diningStandardUnit;
String? get diningType => _diningType;
num? get focus => _focus;
num? get gender => _gender;
num? get id => _id;
String? get orderNo => _orderNo;
String? get phoneNumber => _phoneNumber;
num? get receiveMarketingSms => _receiveMarketingSms;
String? get remark => _remark;
num? get shopId => _shopId;
num? get shopTableId => _shopTableId;
num? get status => _status;
num? get timeoutMinute => _timeoutMinute;
String? get updateTime => _updateTime;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['arrivedTime'] = _arrivedTime;
map['bookingDate'] = _bookingDate;
map['bookingPerson'] = _bookingPerson;
map['bookingTableNum'] = _bookingTableNum;
map['bookingTime'] = _bookingTime;
map['bookingType'] = _bookingType;
map['createTime'] = _createTime;
map['createUserName'] = _createUserName;
map['delFlag'] = _delFlag;
map['dinerNum'] = _dinerNum;
map['diningStandardPrice'] = _diningStandardPrice;
map['diningStandardUnit'] = _diningStandardUnit;
map['diningType'] = _diningType;
map['focus'] = _focus;
map['gender'] = _gender;
map['id'] = _id;
map['orderNo'] = _orderNo;
map['phoneNumber'] = _phoneNumber;
map['receiveMarketingSms'] = _receiveMarketingSms;
map['remark'] = _remark;
map['shopId'] = _shopId;
map['shopTableId'] = _shopTableId;
map['status'] = _status;
map['timeoutMinute'] = _timeoutMinute;
map['updateTime'] = _updateTime;
return map;
}
}

View File

@@ -0,0 +1,128 @@
import 'dart:convert';
/// createdAt : 1712455187626
/// id : 19
/// isUp : 0
/// message : "需要更新"
/// sel : 1
/// source : "PC"
/// type : "android"
/// updatedAt : 1725353572331
/// url : "https://cashier-oss.oss-cn-beijing.aliyuncs.com/upload/version/1.4.20.exe"
/// version : "1.4.21"
UpdateVersionModel updateVersionModelFromJson(String str) =>
UpdateVersionModel.fromJson(json.decode(str));
String updateVersionModelToJson(UpdateVersionModel data) =>
json.encode(data.toJson());
class UpdateVersionModel {
UpdateVersionModel({
num? createdAt,
num? id,
num? isUp,
String? message,
num? sel,
String? source,
String? type,
num? updatedAt,
String? url,
String? version,
}) {
_createdAt = createdAt;
_id = id;
_isUp = isUp;
_message = message;
_sel = sel;
_source = source;
_type = type;
_updatedAt = updatedAt;
_url = url;
_version = version;
}
UpdateVersionModel.fromJson(dynamic json) {
_createdAt = json['createdAt'];
_id = json['id'];
_isUp = json['isUp'];
_message = json['message'];
_sel = json['sel'];
_source = json['source'];
_type = json['type'];
_updatedAt = json['updatedAt'];
_url = json['url'];
_version = json['version'];
}
num? _createdAt;
num? _id;
num? _isUp;
String? _message;
num? _sel;
String? _source;
String? _type;
num? _updatedAt;
String? _url;
String? _version;
UpdateVersionModel copyWith({
num? createdAt,
num? id,
num? isUp,
String? message,
num? sel,
String? source,
String? type,
num? updatedAt,
String? url,
String? version,
}) =>
UpdateVersionModel(
createdAt: createdAt ?? _createdAt,
id: id ?? _id,
isUp: isUp ?? _isUp,
message: message ?? _message,
sel: sel ?? _sel,
source: source ?? _source,
type: type ?? _type,
updatedAt: updatedAt ?? _updatedAt,
url: url ?? _url,
version: version ?? _version,
);
num? get createdAt => _createdAt;
num? get id => _id;
num? get isUp => _isUp;
String? get message => _message;
num? get sel => _sel;
String? get source => _source;
String? get type => _type;
num? get updatedAt => _updatedAt;
String? get url => _url;
String? get version => _version;
Map<String, dynamic> toJson() {
final map = <String, dynamic>{};
map['createdAt'] = _createdAt;
map['id'] = _id;
map['isUp'] = _isUp;
map['message'] = _message;
map['sel'] = _sel;
map['source'] = _source;
map['type'] = _type;
map['updatedAt'] = _updatedAt;
map['url'] = _url;
map['version'] = _version;
return map;
}
}

195
lib/home/home_view.dart Normal file
View File

@@ -0,0 +1,195 @@
import 'package:cashier_reserve/common/base/provider.dart';
import 'package:cashier_reserve/common/base/ui.dart';
import 'package:cashier_reserve/common/base/ui_model.dart';
import 'package:cashier_reserve/home/home_view_model.dart';
import 'package:cashier_reserve/home/order_view.dart';
import 'package:cashier_reserve/home/order_view_model.dart';
import 'package:cashier_reserve/home/reserve_view.dart';
import 'package:cashier_reserve/home/reserve_view_model.dart';
import 'package:percent_indicator/circular_percent_indicator.dart';
class HomeView extends BaseUI {
@override
Widget buildBody(BuildContext context) {
return Container(
margin: EdgeInsets.only(top: MediaQuery.of(context).padding.top),
child: Row(
children: <Widget>[
_buildLeftBar(context),
_buildRightContainer(context),
],
),
);
}
@override
BaseUIModel getProvider(BuildContext context, {bool listen = true}) {
return MyProvider.of<HomeViewModel>(context, listen: listen);
}
@override
String? getTitleStr(BuildContext context) {
return "";
}
@override
AppBar? getAppBar(BuildContext context) {
return null;
}
Widget _buildLeftBar(BuildContext context) {
HomeViewModel provider =
getProvider(context, listen: false) as HomeViewModel;
const double leftBarWidth = 100;
double itemHeight = (MediaQuery.of(context).size.height -
leftBarWidth -
MediaQuery.of(context).padding.top) /
provider.tabTitles.length;
return Container(
color: const Color(0xff1D2227),
width: leftBarWidth,
child: SingleChildScrollView(
child: Column(
children: _buildDestinations(
context,
provider,
leftBarWidth,
itemHeight,
),
),
),
);
}
Widget _buildRightContainer(BuildContext context) {
HomeViewModel provider =
getProvider(context, listen: false) as HomeViewModel;
return Expanded(
child: Container(
color: Colors.amber,
child: MultiProvider(
providers: [
ChangeNotifierProvider<ReserveViewModel>(
create: (_) => ReserveViewModel(),
),
ChangeNotifierProvider<OrderViewModel>(
create: (_) => OrderViewModel(),
),
],
child: PageView(
physics: const NeverScrollableScrollPhysics(),
controller: provider.pageController,
children: _buildPageViews(context, provider),
),
),
));
}
List<Widget> _buildPageViews(BuildContext context, HomeViewModel provider) {
List<Widget> items = [];
items.add(Center(
child: BaseUIController(stateWidget: ReserveView()),
));
items.add(Center(
child: BaseUIController(stateWidget: OrderView()),
));
items.add(const Center(
child: Text("打印预定"),
));
items.add(const Center(
child: Text("历史订单"),
));
items.add(const Center(
child: Text("来电"),
));
items.add(const Center(
child: Text("客户"),
));
items.add(const Center(
child: Text("消息"),
));
items.add(Center(
child: Text("更多 ${provider.version}"),
));
return items;
}
List<Widget> _buildDestinations(BuildContext context, HomeViewModel provider,
double itemWidth, double itemHeight) {
List<Widget> items = List.generate(provider.tabTitles.length, (index) {
return SizedBox(
height: itemHeight,
child: InkWell(
onTap: () {
provider.setIndex(index);
},
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Image.asset(
provider.currentIndex == index
? "images/tabbar/${provider.tabIcons[index]}_select.png"
: "images/tabbar/${provider.tabIcons[index]}_normal.png",
width: 20,
height: 23,
),
const SizedBox(height: 3),
Text(
provider.tabTitles[index],
style: provider.currentIndex == index
? const TextStyle(color: Colors.white, fontSize: 12)
: const TextStyle(color: Colors.grey, fontSize: 12),
)
],
),
),
);
});
Widget topItem = SizedBox(
width: itemWidth,
height: itemWidth,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
CircularPercentIndicator(
animation: true,
radius: 25.0,
lineWidth: 4.0,
percent: 0.3622,
center: const Text(
"36%",
style: TextStyle(fontSize: 15, color: Colors.white),
),
progressColor: Colors.green,
),
const Text("占15空间27",
style: TextStyle(color: Colors.white, fontSize: 12)),
const Text("共130人",
style: TextStyle(color: Colors.white, fontSize: 12)),
Container(
margin: const EdgeInsets.only(top: 5),
width: 80,
height: 2,
color: Colors.grey,
)
],
),
);
// 将顶部的item放到最前面
items.insert(0, topItem);
return items;
}
}

View File

@@ -0,0 +1,147 @@
import 'package:cashier_reserve/call/call_view.dart';
import 'package:cashier_reserve/common/base/ui.dart';
import 'package:cashier_reserve/common/base/ui_model.dart';
import 'package:cashier_reserve/common/channel/channel_manager.dart';
import 'package:cashier_reserve/common/channel/model/call_status_change_model.dart';
import 'package:cashier_reserve/common/manager/app_manager.dart';
import 'package:cashier_reserve/common/manager/event_manager.dart';
import 'package:cashier_reserve/common/print/print.dart';
import 'package:cashier_reserve/common/push/push.dart';
import 'package:package_info_plus/package_info_plus.dart';
class HomeViewModel extends BaseUIModel {
int _currentIndex = 0;
int get currentIndex => _currentIndex;
final List<String> _tabTitles = [
"预定",
"订单",
"打印预定",
"历史订单",
"来电",
"客户",
"消息",
"更多"
];
final List<String> _tabIcons = [
"reserve",
"order",
"print",
"history",
"tel",
"customer",
"message",
"more"
];
List<String> get tabTitles => _tabTitles;
List<String> get tabIcons => _tabIcons;
PageController? _pageController;
PageController? get pageController => _pageController;
bool isShowCallView = false;
String version = "";
HomeViewModel() {
_pageController = PageController(initialPage: 0);
Future.delayed(const Duration(milliseconds: 700), () {
_checkLogin();
_checkAppVersion();
});
EventManager.addListener<CallStatusChangeEvent>(this, (event) {
yjPrint(
"HomeViewModel CallStatusChangeEvent state: ${event.model.state}");
/// state = IncomingNumberReceived 来电接听
/// state = OutGoing 呼出
/// state = End 通话结束
/// state = Incoming 来电
/// state = Idle 空闲
switch (event.model.state) {
case "IncomingNumberReceived":
EventManager.postEvent(CallReceivedEvent());
break;
case "OutGoing":
break;
case "Incoming":
showCallInfoView(event.model);
break;
default:
hideCallInfoView();
break;
}
});
_loadVersion();
}
@override
void dispose() {
_pageController?.dispose();
super.dispose();
}
_loadVersion() async {
PackageInfo packageInfo = await PackageInfo.fromPlatform();
version = packageInfo.version;
notifyListeners();
}
_checkLogin() {
bool flag = AppManager.isLogin();
yjPrint("is login $flag");
}
_checkAppVersion() {
AppManager.checkAppVersion();
}
void setIndex(int index) {
if (_currentIndex == index) {
return;
}
_currentIndex = index;
notifyListeners();
_pageController?.jumpToPage(index);
}
showCallInfoView(CallStatusChangeModel model) {
if (isShowCallView) {
return;
}
YJPush.presentWidget(
context!,
CallView(
statusModel: model,
onAction: (action) {
yjPrint("call view action: $action");
if (action == "accept") {
ChannelManager.acceptCall();
} else {
ChannelManager.rejectCall();
hideCallInfoView();
}
}));
isShowCallView = true;
}
hideCallInfoView() {
if (!isShowCallView) {
return;
}
Navigator.of(context!).pop();
isShowCallView = false;
ChannelManager.getCallLog("getCallLog");
}
}

29
lib/home/order_view.dart Normal file
View File

@@ -0,0 +1,29 @@
import 'package:cashier_reserve/common/base/ui.dart';
import 'package:cashier_reserve/common/base/ui_model.dart';
import 'package:cashier_reserve/home/order_view_model.dart';
import '../common/base/provider.dart';
class OrderView extends BaseUI {
@override
Widget buildBody(BuildContext context) {
return const Center(
child: Text("Order"),
);
}
@override
BaseUIModel getProvider(BuildContext context, {bool listen = true}) {
return MyProvider.of<OrderViewModel>(context, listen: listen);
}
@override
String? getTitleStr(BuildContext context) {
return "";
}
@override
AppBar? getAppBar(BuildContext context) {
return null;
}
}

View File

@@ -0,0 +1,3 @@
import 'package:cashier_reserve/common/base/ui_model.dart';
class OrderViewModel extends BaseUIModel {}

View File

@@ -0,0 +1,821 @@
import 'package:cashier_reserve/common/base/ui.dart';
import 'package:cashier_reserve/common/channel/model/call_log_model.dart';
import 'package:cashier_reserve/common/print/print.dart';
import 'package:cashier_reserve/data_model/reserve/reserve_log_model.dart';
import 'package:cashier_reserve/home/reserve_view_model.dart';
import 'package:easy_refresh/easy_refresh.dart';
import 'package:flutter/cupertino.dart';
class ReserveLeftContentView extends StatelessWidget {
final double contentWidth = 430;
final double inputItemHeight = 36;
final ReserveViewModel provider;
const ReserveLeftContentView({super.key, required this.provider});
@override
Widget build(BuildContext context) {
return SizedBox(
width: contentWidth,
// height: MediaQuery.of(context).size.height,
child: Stack(
children: [
_buildCallLog(context),
_buildBookingWidget(context),
],
),
);
}
Widget _buildBookingWidget(BuildContext context) {
return SizeTransition(
sizeFactor: provider.animationSizeFactor!,
axis: Axis.vertical,
axisAlignment: -1, // 设置为 -1使动画从下往上开始
child: Container(
color: const Color(0xfff5f5f5),
height: MediaQuery.of(context).size.height,
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(
height: 5,
),
Container(
margin: const EdgeInsets.only(left: 15),
child: const Text(
'创建订单',
style: TextStyle(fontSize: 15, color: Color(0xff333333)),
),
),
const SizedBox(
height: 5,
),
_buildNumAndTable(context),
const SizedBox(
height: 10,
),
_buildBookingInfo(context),
const SizedBox(
height: 10,
),
_buildBookingActions(context),
],
),
),
));
}
Widget _buildCallLog(BuildContext context) {
return Column(
children: [
Expanded(
child: SizedBox(
width: contentWidth,
child: EasyRefresh(
onRefresh: () async {
provider.loadCallLog();
yjPrint("onRefresh");
},
child: ListView.builder(
itemCount: provider.callLogs?.length ?? 0,
itemBuilder: (context, index) {
return _buildCallRecordItem(context, provider.callLogs?[index]);
},
),
),
)),
Container(
padding: const EdgeInsets.fromLTRB(20, 10, 20, 15),
child: InkWell(
onTap: () {
provider.showReserveInfoView();
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
color: const Color(0xff318AFE),
),
width: 300,
height: inputItemHeight,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Image.asset(
'images/reserve/create.png',
width: 20,
height: 20,
),
const SizedBox(
width: 5,
),
const Text(
"创建订单",
style: TextStyle(color: Colors.white, fontSize: 14),
),
],
),
),
),
)
],
);
}
/// _buildCallRecordItem 通话记录item
Widget _buildCallRecordItem(BuildContext context, CallLogModel? model) {
ReserveLogModel? reserveLogModel =
provider.getReserveLogModel(model?.number ?? "");
return Container(
padding: const EdgeInsets.fromLTRB(15, 15, 15, 5),
child: Column(
children: [
Row(
children: [
Column(
children: [
Image.asset(
(model?.type ?? 0) == 3
? "images/reserve/phone_fail.png"
: "images/reserve/phone_suc.png",
width: 24,
height: 24,
),
const SizedBox(height: 2),
Text(
model?.time ?? "",
style:
const TextStyle(color: Color(0xff999999), fontSize: 12),
),
],
),
const SizedBox(width: 10),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
model?.name ?? "未知电话",
style:
const TextStyle(color: Color(0xff333333), fontSize: 14),
),
const SizedBox(height: 5),
Row(
children: [
Text(
model?.number ?? "",
style: const TextStyle(
color: Color(0xff333333), fontSize: 14),
),
const SizedBox(
width: 15,
),
Text(
"已消费${reserveLogModel?.consumeOrders ?? '0'}",
style: const TextStyle(
color: Color(0xff333333), fontSize: 14),
),
const SizedBox(
width: 15,
),
Text(
"已撤${reserveLogModel?.cancelOrders ?? '0'}",
style: const TextStyle(
color: Color(0xff333333), fontSize: 14),
),
],
),
],
),
const Expanded(child: SizedBox()),
InkWell(
onTap: () {
provider.execCallLog(model);
},
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
border:
Border.all(color: const Color(0xff318AFE), width: 1),
),
padding: const EdgeInsets.fromLTRB(20, 7, 20, 7),
child: const Text(
"处理",
style: TextStyle(color: Color(0xff318AFE), fontSize: 14),
),
),
)
],
),
const SizedBox(
height: 10,
),
Container(
height: 1,
color: const Color(0xffededed),
),
],
),
);
}
/// _buildNumAndTable 就餐人数和桌台
Widget _buildNumAndTable(BuildContext context) {
return Container(
color: Colors.white,
padding: const EdgeInsets.fromLTRB(15, 15, 15, 15),
child: Column(
children: [
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
border: Border.all(color: const Color(0xffededed), width: 1),
),
padding: const EdgeInsets.fromLTRB(10, 5, 10, 5),
height: inputItemHeight,
child: Row(
children: [
const Text(
"就餐人数",
style: TextStyle(color: Color(0xff333333), fontSize: 14),
),
const SizedBox(
width: 10,
),
Expanded(
child: TextField(
controller: provider.bookingNumController,
decoration: InputDecoration(
hintText: "请输入就餐人数",
hintStyle: const TextStyle(
fontSize: 14,
color: Color(0xff999999),
),
contentPadding: EdgeInsets.zero,
border: OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(5),
),
),
keyboardType: TextInputType.number,
textAlign: TextAlign.right,
),
),
const SizedBox(
width: 10,
),
const Text(
"",
style: TextStyle(color: Color(0xff333333), fontSize: 14),
),
],
),
),
const SizedBox(
height: 10,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text(
"请选择桌台",
style: TextStyle(color: Color(0xff333333), fontSize: 14),
),
Text(provider.showTableName,
style:
const TextStyle(color: Color(0xff333333), fontSize: 14)),
],
),
const SizedBox(
height: 5,
),
],
),
);
}
/// _buildBookingInfo 预订信息
Widget _buildBookingInfo(BuildContext context) {
return Container(
color: Colors.white,
padding: const EdgeInsets.fromLTRB(15, 15, 15, 15),
child: Column(
children: [
_buildBookingPhone(context),
const SizedBox(
height: 5,
),
_buildBookingName(context),
const SizedBox(
height: 5,
),
_buildBookingTimeAndType(context),
const SizedBox(
height: 5,
),
_buildBookingAttribute(context),
const SizedBox(
height: 5,
),
_buildBookingTableNum(context),
const SizedBox(
height: 5,
),
_buildBookingStandard(context),
const SizedBox(
height: 5,
),
_buildBookingRemark(context),
],
),
);
}
/// _buildBookingActions 预订操作
Widget _buildBookingActions(BuildContext context) {
return Container(
color: Colors.white,
padding: const EdgeInsets.fromLTRB(15, 5, 15, 15),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_buildBookingActionBtn(context, "预约", () {
provider.commitReserveInfo();
}),
_buildBookingActionBtn(context, "预约并短信", () {
provider.commitReserveInfo(sendSms: true);
}),
_buildBookingActionBtn(context, "发路线", () {}),
_buildBookingActionBtn(context, "取消", () {
provider.hideReserveInfoView();
}),
],
),
);
}
/// _buildBookingPhone 预订电话
Widget _buildBookingPhone(BuildContext context) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
border: Border.all(color: const Color(0xffededed), width: 1),
),
padding: const EdgeInsets.fromLTRB(10, 5, 10, 5),
height: inputItemHeight,
child: Row(
children: [
const Text(
"电话",
style: TextStyle(color: Color(0xff333333), fontSize: 14),
),
const SizedBox(
width: 10,
),
Expanded(
child: TextField(
controller: provider.bookingPhoneController,
decoration: InputDecoration(
hintText: "请输入电话",
hintStyle: const TextStyle(
fontSize: 14,
color: Color(0xff999999),
),
contentPadding: EdgeInsets.zero,
border: OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(5),
),
),
keyboardType: TextInputType.number,
textAlign: TextAlign.left,
),
),
const SizedBox(
width: 10,
),
],
),
);
}
/// _buildBookingName 预订人
Widget _buildBookingName(BuildContext context) {
return SizedBox(
width: contentWidth - 30,
child: Row(
children: [
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
border: Border.all(color: const Color(0xffededed), width: 1),
),
padding: const EdgeInsets.fromLTRB(10, 5, 10, 5),
height: inputItemHeight,
width: contentWidth - 160,
child: Row(
children: [
const Text(
"姓名",
style: TextStyle(color: Color(0xff333333), fontSize: 14),
),
const SizedBox(
width: 10,
),
Expanded(
child: TextField(
controller: provider.bookingNameController,
decoration: InputDecoration(
hintText: "请输入姓名",
hintStyle: const TextStyle(
fontSize: 14,
color: Color(0xff999999),
),
contentPadding: EdgeInsets.zero,
border: OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(5),
),
),
textAlign: TextAlign.left,
),
),
const SizedBox(
width: 10,
),
],
),
),
const SizedBox(
width: 10,
),
// 先生、女士
SizedBox(
width: 120,
child: CupertinoSegmentedControl(
//子标签
children: {
1: Container(
width: 60,
height: inputItemHeight,
alignment: Alignment.center,
child: const Text(
"先生",
style: TextStyle(
fontSize: 14,
// color: Color(0xff333333),
decoration: TextDecoration.none),
),
),
2: Container(
width: 60,
height: inputItemHeight,
alignment: Alignment.center,
child: const Text(
"女士",
style: TextStyle(
fontSize: 14,
// color: Color(0xff333333),
decoration: TextDecoration.none),
),
),
},
//当前选中的索引
groupValue: provider.bookingGender,
//点击回调
onValueChanged: (int index) {
provider.updateBookingGender(index);
},
selectedColor: Colors.blue,
borderColor: Colors.blue,
unselectedColor: Colors.white,
),
),
],
),
);
}
/// _buildBookingTimeAndType 预订时间和类型
Widget _buildBookingTimeAndType(BuildContext context) {
final itemWidth = (contentWidth - 40) / 2;
return Row(
children: [
Container(
width: itemWidth,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
border: Border.all(color: const Color(0xffededed), width: 1),
),
height: inputItemHeight,
padding: const EdgeInsets.fromLTRB(10, 5, 10, 5),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const Text("时间",
style: TextStyle(color: Color(0xff666666), fontSize: 14)),
InkWell(
onTap: () async {
TimeOfDay? t = await showTimePicker(
context: context,
initialTime: TimeOfDay.now(),
);
yjPrint(t);
provider.updateBookingTime(t?.hour ?? 0, t?.minute ?? 0);
},
child: (provider.bookingSelectedTime == "")
? const Text(
"请选择时间",
style:
TextStyle(color: Color(0xff999999), fontSize: 14),
)
: Text(
provider.bookingSelectedTime,
style: const TextStyle(
color: Color(0xff333333), fontSize: 14),
),
)
],
),
),
const SizedBox(
width: 10,
),
Container(
width: itemWidth,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
border: Border.all(color: const Color(0xffededed), width: 1),
),
height: inputItemHeight,
padding: const EdgeInsets.fromLTRB(10, 5, 10, 5),
child: Row(
children: [
const Text("类型",
style: TextStyle(color: Color(0xff666666), fontSize: 14)),
const SizedBox(
width: 10,
),
Expanded(
child: TextField(
controller: provider.bookingTypeController,
decoration: InputDecoration(
hintText: "请输入类型",
hintStyle: const TextStyle(
fontSize: 14,
color: Color(0xff999999),
),
contentPadding: EdgeInsets.zero,
border: OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(5),
),
),
textAlign: TextAlign.right,
),
),
const SizedBox(
width: 10,
),
],
),
),
],
);
}
/// _buildBookingAttribute 预订属性
Widget _buildBookingAttribute(BuildContext context) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
border: Border.all(color: const Color(0xffededed), width: 1),
),
padding: const EdgeInsets.fromLTRB(10, 5, 10, 5),
height: inputItemHeight,
width: contentWidth - 30,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
_buildBookingAttrItem(context, "重点关注", provider.bookingFocus,
(value) {
provider.updateBookingAttr("focus", value);
}),
_buildBookingAttrItem(context, "接收营销短信", provider.bookingSms,
(value) {
provider.updateBookingAttr("sms", value);
}),
],
),
);
}
Widget _buildBookingAttrItem(BuildContext context, String name, bool isOpen,
Function(bool) onChanged) {
return Row(
children: [
Text(name,
style: const TextStyle(color: Color(0xff999999), fontSize: 14)),
const SizedBox(width: 5),
Transform.scale(
scale: 0.8, // 调整这个值来改变大小大于1放大小于1缩小
child: CupertinoSwitch(
value: isOpen, onChanged: onChanged, activeColor: Colors.blue),
),
],
);
}
/// _buildBookingTableNum 预订桌台数量
Widget _buildBookingTableNum(BuildContext context) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
border: Border.all(color: const Color(0xffededed), width: 1),
),
padding: const EdgeInsets.fromLTRB(10, 5, 10, 5),
height: inputItemHeight,
// width: contentWidth - 30,
child: Row(
children: [
const Text(
"摆台桌数(桌)",
style: TextStyle(color: Color(0xff333333), fontSize: 14),
),
const SizedBox(
width: 10,
),
Expanded(
child: TextField(
controller: provider.bookingTableNumController,
decoration: InputDecoration(
hintText: "请输入台桌数",
hintStyle: const TextStyle(
fontSize: 14,
color: Color(0xff999999),
),
contentPadding: EdgeInsets.zero,
border: OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(5),
),
),
textAlign: TextAlign.left,
),
),
const SizedBox(
width: 10,
),
],
),
);
}
Widget _buildBookingStandard(BuildContext context) {
final itemWidth = (contentWidth - 40) / 2;
return Row(
children: [
Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
border: Border.all(color: const Color(0xffededed), width: 1),
),
padding: const EdgeInsets.fromLTRB(10, 5, 10, 5),
width: itemWidth,
height: inputItemHeight,
child: Row(
children: [
const Text(
"餐标",
style: TextStyle(color: Color(0xff333333), fontSize: 14),
),
const SizedBox(
width: 10,
),
Expanded(
child: TextField(
controller: provider.bookingStandardController,
decoration: InputDecoration(
hintText: "请输入标准",
hintStyle: const TextStyle(
fontSize: 14,
color: Color(0xff999999),
),
contentPadding: EdgeInsets.zero,
border: OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(5),
),
),
textAlign: TextAlign.left,
),
),
const SizedBox(
width: 10,
),
],
),
),
const SizedBox(
width: 10,
),
SizedBox(
height: inputItemHeight,
width: itemWidth,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Radio<String>(
value: "table",
groupValue: provider.bookingStandardType,
onChanged: (String? value) {
provider.updateBookingStandard(value ?? "table");
},
),
const Text(
'元/桌',
style: TextStyle(fontSize: 12, color: Color(0xff666666)),
),
Radio<String>(
value: "person",
groupValue: provider.bookingStandardType,
onChanged: (String? value) {
provider.updateBookingStandard(value ?? "person");
},
),
const Text(
'元/人',
style: TextStyle(fontSize: 12, color: Color(0xff666666)),
),
],
),
)
],
);
}
/// _buildBookingRemark 备注
Widget _buildBookingRemark(BuildContext context) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
border: Border.all(color: const Color(0xffededed), width: 1),
),
padding: const EdgeInsets.fromLTRB(10, 5, 10, 5),
height: inputItemHeight,
child: Row(
children: [
const Text(
"备注",
style: TextStyle(color: Color(0xff333333), fontSize: 14),
),
const SizedBox(
width: 10,
),
Expanded(
child: TextField(
controller: provider.bookingRemarkController,
decoration: InputDecoration(
hintText: "请输入备注",
hintStyle: const TextStyle(
fontSize: 14,
color: Color(0xff999999),
),
contentPadding: EdgeInsets.zero,
border: OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(5),
),
),
textAlign: TextAlign.left,
),
),
const SizedBox(
width: 10,
),
],
),
);
}
Widget _buildBookingActionBtn(
BuildContext context, String title, Function() onTap) {
return InkWell(
onTap: onTap,
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
color: Colors.blue,
),
padding: const EdgeInsets.fromLTRB(20, 5, 20, 5),
child: Text(
title,
style: const TextStyle(color: Colors.white, fontSize: 14),
),
),
);
}
}

View File

@@ -0,0 +1,98 @@
import 'package:cashier_reserve/common/base/ui.dart';
import 'package:cashier_reserve/common/print/print.dart';
import 'package:cashier_reserve/data_model/reserve/table_area_model.dart';
import 'package:cashier_reserve/home/reserve_view_model.dart';
import 'reserve_right_table_list.dart';
class ReserveRightContentView extends StatelessWidget {
final ReserveViewModel provider;
final TabController? tabController;
const ReserveRightContentView(
{super.key, required this.provider, this.tabController});
@override
Widget build(BuildContext context) {
return SizedBox(
width: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildTabBar(context),
_buildTableListWidget(context),
],
),
);
}
Widget _buildTabBar(BuildContext context) {
if ((provider.tableAreaList?.length ?? 0) == 0) {
return Container();
}
return TabBar(
tabs:
provider.tableAreaList!.map((e) => Tab(text: e?.name ?? '')).toList(),
controller: tabController,
isScrollable: true,
labelColor: Colors.blue,
unselectedLabelColor: Colors.grey,
indicatorColor: Colors.blue,
indicatorSize: TabBarIndicatorSize.label,
indicatorWeight: 2,
labelPadding: const EdgeInsets.only(left: 10, right: 10),
onTap: (index) {
provider.pageController.jumpToPage(index);
},
);
}
Widget _buildTableListWidget(BuildContext context) {
if ((provider.tableAreaList?.length ?? 0) == 0) {
return Container();
}
return Expanded(
child: Container(
padding: const EdgeInsets.only(left: 15, right: 15),
color: const Color(0xFFF5F5F5),
child: PageView(
controller: provider.pageController,
physics: const NeverScrollableScrollPhysics(),
children: _buildTableList(context),
),
),
);
}
List<Widget> _buildTableList(BuildContext context) {
List<Widget> list = [];
for (int i = 0; i < provider.tableAreaList!.length; i++) {
List<TableAreaModel?> areas = [];
if (i == 0) {
/// 排除第一个全部
areas.addAll(provider.tableAreaList!.sublist(1));
} else {
areas.add(provider.tableAreaList![i]);
}
list.add(
ReserveRightTableList(
areas: areas,
getAreaTableListFunc: (areaId) {
return provider.tableMap[areaId] ?? [];
},
tableClickFunc: (table) {
yjPrint("table: ${table.name}");
provider.clickTable(table);
},
),
);
}
return list;
}
}

View File

@@ -0,0 +1,161 @@
import 'package:cashier_reserve/common/base/ui.dart';
import 'package:cashier_reserve/data_model/reserve/table_area_model.dart';
import 'package:cashier_reserve/data_model/reserve/table_model.dart';
typedef GetAreaTableListFunc = List<TableModel?> Function(String areaId);
typedef TableClickFunc = void Function(TableModel table);
class ReserveRightTableList extends StatelessWidget {
final List<TableAreaModel?> areas;
final GetAreaTableListFunc getAreaTableListFunc;
final TableClickFunc? tableClickFunc;
const ReserveRightTableList({
super.key,
required this.areas,
required this.getAreaTableListFunc,
this.tableClickFunc,
});
@override
Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: _buildTableAreaItem, itemCount: areas.length);
}
Widget _buildTableAreaItem(BuildContext context, int index) {
TableAreaModel? area = areas[index];
List<TableModel?> tables = getAreaTableListFunc(area?.id.toString() ?? "");
return Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildTableAreaTitle(context, area, tables),
const SizedBox(height: 5),
_buildTableList(context, tables),
const SizedBox(height: 10),
],
);
}
Widget _buildTableAreaTitle(
BuildContext context, TableAreaModel? area, List<TableModel?> tables) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Container(
width: 2,
height: 15,
color: Colors.blue,
),
const SizedBox(width: 10),
Text(
"${area?.name ?? ""}${tables.length}",
style: const TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
),
),
],
);
}
Widget _buildTableList(BuildContext context, List<TableModel?> tables) {
return Wrap(
children: tables.map((e) => _buildTableItem(context, e)).toList(),
);
}
Widget _buildTableItem(BuildContext context, TableModel? table) {
bool isBooking = table?.bookingInfo != null;
const itemNormalTextStyle = TextStyle(
color: Color(0xff333333),
fontSize: 12,
);
return GestureDetector(
onTap: () {
if (tableClickFunc != null) {
tableClickFunc!(table!);
}
},
child: Container(
width: 103,
height: 129,
margin: const EdgeInsets.fromLTRB(0, 0, 10, 0),
decoration: BoxDecoration(
color: isBooking ? const Color(0xffFFF4DF) : Colors.white,
borderRadius: BorderRadius.circular(5),
),
child: Column(
children: [
Container(
padding: const EdgeInsets.all(10),
child: Column(
children: [
Text(
table?.name ?? "",
style: const TextStyle(
color: Color(0xff333333),
fontSize: 16,
),
),
const SizedBox(height: 3),
if (!isBooking)
Text(
"${table?.maxCapacity ?? ""}",
style: itemNormalTextStyle,
),
if (isBooking)
Text(
"${(table?.bookingInfo?.createUserName?.length ?? 0) > 3 ? '${table?.bookingInfo?.createUserName?.substring(0, 3)}...' : table?.bookingInfo?.createUserName} 订",
style: itemNormalTextStyle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
if (isBooking) const SizedBox(height: 3),
if (isBooking)
Text(
"${table?.bookingInfo?.bookingPerson ?? ""}(${table?.bookingInfo?.gender == 1 ? '先生' : '女士'})",
style: itemNormalTextStyle,
),
if (isBooking) const SizedBox(height: 3),
if (isBooking)
Text(
"${table?.bookingInfo?.dinerNum ?? ""}人/${table?.bookingInfo?.phoneNumber?.substring(7) ?? ""}",
style: itemNormalTextStyle,
),
],
),
),
Expanded(child: Container()),
if (isBooking)
Container(
width: double.infinity,
alignment: Alignment.center,
decoration: const BoxDecoration(
color: Color(0xffF8AD13),
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(5),
bottomRight: Radius.circular(5),
),
),
child: const Row(
children: [
SizedBox(width: 5),
Text(
"",
style: TextStyle(
color: Colors.white,
fontSize: 11,
),
),
],
),
),
],
),
),
);
}
}

325
lib/home/reserve_view.dart Normal file
View File

@@ -0,0 +1,325 @@
import 'package:cashier_reserve/common/base/ui.dart';
import 'package:cashier_reserve/common/base/ui_model.dart';
import 'package:cashier_reserve/home/reserve_left_content_view.dart';
import 'package:cashier_reserve/home/reserve_right_content_view.dart';
import 'package:cashier_reserve/home/reserve_view_model.dart';
import '../common/base/provider.dart';
class ReserveView extends BaseUI with TickerProviderStateMixin {
TabController? tabController;
late AnimationController animationController;
late Animation<double> animationSizeFactor;
@override
void initState() {
super.initState();
animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 300),
);
animationSizeFactor = Tween<double>(begin: 0, end: 1).animate(
CurveTween(curve: Curves.easeInOut)
.chain(CurveTween(curve: Curves.decelerate))
.animate(animationController),
);
}
@override
void dispose() {
tabController?.dispose();
animationController.dispose();
animationSizeFactor.removeListener(() {});
super.dispose();
}
@override
Widget buildBody(BuildContext context) {
ReserveViewModel provider =
getProvider(context, listen: false) as ReserveViewModel;
provider.setAnimationController(animationController, animationSizeFactor);
return SizedBox(
width: double.infinity,
child: Column(
children: [
_buildTopDateBar(context, provider),
Expanded(child: _buildContentView(context, provider)),
],
),
);
}
@override
BaseUIModel getProvider(BuildContext context, {bool listen = true}) {
return MyProvider.of<ReserveViewModel>(context, listen: listen);
}
@override
String? getTitleStr(BuildContext context) {
return "";
}
@override
AppBar? getAppBar(BuildContext context) {
return null;
}
Widget _buildTopDateBar(BuildContext context, ReserveViewModel provider) {
return Padding(
padding: const EdgeInsets.fromLTRB(15, 5, 15, 5),
child: Row(
children: [
_buildDateItem(context, provider),
_buildDateSelectContent(context, provider),
const SizedBox(
width: 15,
),
InkWell(
onTap: () {
provider.testCallIncoming();
},
child: SizedBox(
width: 40,
height: 40,
child: Center(
child: Image.asset(
"images/reserve/lock.png",
width: 24,
height: 24,
),
),
),
),
const SizedBox(
width: 5,
),
InkWell(
onTap: () { },
child: SizedBox(
width: 40,
height: 40,
child: Center(
child: Image.asset(
"images/reserve/phone.png",
width: 24,
height: 24,
),
),
),
),
const SizedBox(
width: 5,
),
InkWell(
onTap: () {
provider.reloadPageData();
},
child: const SizedBox(
width: 40,
height: 40,
child: Center(
child: Text(
"刷新",
style: TextStyle(fontSize: 15, color: Colors.blue),
),
),
),
),
],
),
);
}
Widget _buildContentView(BuildContext context, ReserveViewModel provider) {
if ((provider.tableAreaList?.length ?? 0) > 0) {
if (tabController == null) {
tabController =
TabController(vsync: this, length: provider.tableAreaList!.length);
} else {
if (tabController!.length != provider.tableAreaList!.length) {
tabController!.dispose();
tabController = TabController(
vsync: this, length: provider.tableAreaList!.length);
}
}
}
return Row(
children: [
ReserveLeftContentView(
provider: provider,
),
Expanded(
child: ReserveRightContentView(
provider: provider,
tabController: tabController,
),
),
],
);
}
Widget _buildDateItem(BuildContext context, ReserveViewModel provider) {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
border: Border.all(color: const Color(0xffafafaf), width: 1),
),
height: 40,
padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
child: Row(
children: [
Image.asset(
"images/reserve/date.png",
width: 24,
height: 24,
),
const SizedBox(width: 7),
Text(
provider.getCurrentDate(),
style: const TextStyle(color: Color(0xff333333), fontSize: 14),
)
],
),
);
}
// Widget _buildCallRecordListWidget(BuildContext context, ReserveViewModel provider) {
// return ListView.builder(
// itemCount: provider.callRecordList.length,
// itemBuilder: (context, index) {
// return _buildCallRecordItem(context, provider.callRecordList[index]);
// },
// );
// }
Widget _buildDateSelectContent(
BuildContext context, ReserveViewModel provider) {
return Row(
children: [
_buildDateSelectItem(
context,
provider,
),
_buildDateSelectItem(
context,
provider,
offset: 1,
),
_buildDateSelectItem(
context,
provider,
offset: 2,
),
],
);
}
Widget _buildDateSelectItem(
BuildContext context,
ReserveViewModel provider, {
int offset = 0,
}) {
bool isSelect = provider.selectedDateIndex ~/ 2 == offset;
bool firstBtnSelect = provider.selectedDateIndex == (offset * 2);
bool secondBtnSelect = provider.selectedDateIndex == (offset * 2 + 1);
const double itemHeight = 45;
const double btnWidth = 60;
return Container(
height: itemHeight,
margin: const EdgeInsets.only(left: 15),
padding: const EdgeInsets.fromLTRB(10, 0, 0, 0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(5),
border: Border.all(
color:
isSelect ? const Color(0xff318AFE) : const Color(0xffafafaf),
width: 1),
),
child: Row(
children: [
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
provider.getOffsetDay(offset),
style: TextStyle(
color: isSelect
? const Color(0xff6A8DC6)
: const Color(0xff333333),
fontSize: 13,
),
),
Text(
provider.getOffsetWeekday(offset),
style: TextStyle(
color: isSelect
? const Color(0xff6A8DC6)
: const Color(0xff333333),
fontSize: 13,
),
),
],
),
const SizedBox(width: 10),
Container(
width: 1,
height: itemHeight,
color: const Color(0xffafafaf),
),
InkWell(
onTap: () {
provider.setSelectedDateIndex(offset * 2);
},
child: Container(
decoration: BoxDecoration(
color:
firstBtnSelect ? const Color(0xff318AFE) : Colors.white,
),
width: btnWidth,
child: Center(
child: Text(
"午餐",
style: TextStyle(
fontSize: 16,
color: firstBtnSelect
? Colors.white
: const Color(0xff333333)),
),
),
),
),
Container(
width: 1,
height: itemHeight,
color: const Color(0xffafafaf),
),
InkWell(
onTap: () {
provider.setSelectedDateIndex(offset * 2 + 1);
},
child: Container(
decoration: BoxDecoration(
color:
secondBtnSelect ? const Color(0xff318AFE) : Colors.white,
),
width: btnWidth,
child: Center(
child: Text(
"晚餐",
style: TextStyle(
fontSize: 16,
color: secondBtnSelect
? Colors.white
: const Color(0xff333333)),
),
),
),
),
],
));
}
}

View File

@@ -0,0 +1,470 @@
import 'dart:async';
import 'package:cashier_reserve/common/base/ui_model.dart';
import 'package:cashier_reserve/common/channel/model/call_log_model.dart';
import 'package:cashier_reserve/common/channel/channel_manager.dart';
import 'package:cashier_reserve/common/channel/model/call_status_change_model.dart';
import 'package:cashier_reserve/common/manager/app_manager.dart';
import 'package:cashier_reserve/common/manager/event_manager.dart';
import 'package:cashier_reserve/common/print/print.dart';
import 'package:cashier_reserve/common/utils/utils.dart';
import 'package:cashier_reserve/data_model/reserve/reserve_log_model.dart';
import 'package:cashier_reserve/data_model/reserve/table_area_model.dart';
import 'package:cashier_reserve/data_model/reserve/table_model.dart';
import 'package:cashier_reserve/model/reserve_model.dart';
import 'package:flutter/cupertino.dart';
import 'package:url_launcher/url_launcher.dart';
class ReserveViewModel extends BaseUIModel {
Map<int, String> weekdayMap = {
1: "星期一",
2: "星期二",
3: "星期三",
4: "星期四",
5: "星期五",
6: "星期六",
7: "星期日",
};
Map<int, String> dayInfoMap = {
0: "",
1: "",
2: "",
};
PageController pageController = PageController();
AnimationController? _animationController;
Animation<double>? _animationSizeFactor;
Animation<double>? get animationSizeFactor => _animationSizeFactor;
DateTime now = DateTime.now();
int selectedDateIndex = 0;
String selectedDate = "";
String bookingType = "lunch";
List<TableAreaModel?>? _tableAreaList;
List<TableAreaModel?>? get tableAreaList => _tableAreaList;
Map<num, TableAreaModel?> _tableAreaMap = {};
List<TableModel?>? _tableList;
List<TableModel?>? get tableList => _tableList;
Map<String, List<TableModel?>> tableMap = {};
List<CallLogModel?>? callLogs = [];
Map<String, ReserveLogModel?>? _reserveLogMap;
bool _isShowReserveInfoView = false;
/// bookingGender 预订人性别 1: 男 2: 女
int bookingGender = 1;
/// bookingNumController 就餐人数
TextEditingController bookingNumController = TextEditingController();
/// bookingPhoneController 联系电话
TextEditingController bookingPhoneController = TextEditingController();
/// bookingNameController 预订人姓名
TextEditingController bookingNameController = TextEditingController();
/// bookingTypeController 预订类型
TextEditingController bookingTypeController = TextEditingController();
/// bookingTableNumController 预订台桌数量
TextEditingController bookingTableNumController = TextEditingController();
/// bookingStandardController 预定餐标
TextEditingController bookingStandardController = TextEditingController();
/// bookingRemarkController 备注
TextEditingController bookingRemarkController = TextEditingController();
/// bookingSelectedTime 预订时间
String bookingSelectedTime = "";
/// bookingFocus 重点关注
bool bookingFocus = false;
/// bookingSms 短信通知
bool bookingSms = false;
/// bookingStandardType 餐标类型
String bookingStandardType = "table";
String showTableName = "";
TableModel? selectedTable;
Timer? periodicTimer;
ReserveViewModel() {
selectedDate = "${now.year}-${now.month}-${now.day}";
EventManager.addListener<GetCallLogEvent>(this, (event) {
if (event.isSuccess) {
if (!event.isSuccess) {
return;
}
callLogs = event.callLogs;
notifyListeners();
loadUserReserveLog();
}
});
loadCallLog();
loadTableAreaList();
loadAreaTableList(0);
periodicTimer = Timer.periodic(const Duration(seconds: 30), (timer) {
loadAreaTableList(0);
});
}
@override
void dispose() {
pageController.dispose();
bookingNumController.dispose();
bookingPhoneController.dispose();
bookingNameController.dispose();
bookingTypeController.dispose();
bookingTableNumController.dispose();
bookingStandardController.dispose();
bookingRemarkController.dispose();
periodicTimer?.cancel();
super.dispose();
}
reloadPageData() {
loadCallLog();
loadTableAreaList();
loadAreaTableList(0);
loadReserveSms();
}
void testCallIncoming() {
CallStatusChangeModel model = CallStatusChangeModel(
state: "Incoming",
number: "123456789",
name: "测试",
region: "陕西省 西安市",
);
EventManager.postEvent(CallStatusChangeEvent(model: model));
}
void loadCallLog() {
ChannelManager.getCallLog("getCallLog");
}
void loadUserReserveLog() async {
List<String> phoneNos = [];
for (var item in callLogs!) {
phoneNos.add(item!.number ?? '');
}
_reserveLogMap = await ReserveModel.getUserReserveLog(phoneNos);
notifyListeners();
}
void loadTableAreaList() async {
final r = await ReserveModel.getShopTableAreaList();
_tableAreaList = r;
_tableAreaList ??= [];
_tableAreaMap = {};
for (var item in _tableAreaList!) {
_tableAreaMap[item!.id!] = item;
}
_tableAreaList!.insert(0, TableAreaModel(id: 0, name: "全部"));
notifyListeners();
}
void loadAreaTableList(int areaId) async {
final r =
await ReserveModel.getAreaTableList(areaId, selectedDate, bookingType);
_tableList = r;
_tableList ??= [];
tableMap = {};
tableMap["0"] = _tableList!;
for (var item in _tableList!) {
String areaId = item!.areaId.toString();
if (tableMap[areaId] == null) {
tableMap[areaId] = [];
}
tableMap[areaId]!.add(item);
}
notifyListeners();
}
loadReserveSms() async {
AppManager.loadReserveSms();
}
void commitReserveInfo({bool sendSms = false}) async {
if (!_checkReserveInfo()) {
return;
}
Map<String, dynamic> params = {
"shopTableId": selectedTable!.id,
"bookingDate": selectedDate,
"bookingType": bookingType,
"dinerNum": bookingNumController.text,
"phoneNumber": bookingPhoneController.text,
"bookingPerson": bookingNameController.text,
"gender": bookingGender,
"bookingTime": '$selectedDate $bookingSelectedTime:00',
"diningType": bookingTypeController.text,
"focus": bookingFocus ? 1 : 0,
"receiveMarketingSms": bookingSms ? 1 : 0,
"bookingTableNum": bookingTableNumController.text,
"diningStandardPrice": bookingStandardController.text,
"diningStandardUnit": bookingStandardType,
"remark": bookingRemarkController.text,
};
await ReserveModel.commitReserveInfo(params);
Utils.toast("预定成功", context);
hideReserveInfoView();
loadAreaTableList(0);
if (sendSms) {
String smsContent = await AppManager.loadReserveSms();
if (smsContent.isNotEmpty) {
yjPrint("send sms: $smsContent, phone: ${bookingPhoneController.text}");
goSendSms(phone: bookingPhoneController.text, message: smsContent);
}
}
}
goSendSms({required String phone, required String message}) async {
final Uri smsUri = Uri(
scheme: 'sms',
path: phone,
queryParameters: {
'body': message,
},
);
if (await canLaunchUrl(smsUri)) {
await launchUrl(smsUri);
} else {
// 如果无法启动短信发送,可在此处添加相应的提示逻辑,比如打印错误信息等
yjPrint('Could not launch SMS');
}
}
void setSelectedDateIndex(int index) {
selectedDateIndex = index;
DateTime offsetDay = now.add(Duration(days: index ~/ 2));
selectedDate = "${offsetDay.year}-${offsetDay.month}-${offsetDay.day}";
bookingType = index % 2 == 0 ? "lunch" : "dinner";
notifyListeners();
loadAreaTableList(0);
}
void setAnimationController(
AnimationController controller, Animation<double> sizeFactor) {
_animationController = controller;
_animationSizeFactor = sizeFactor;
}
ReserveLogModel? getReserveLogModel(String phone) {
return _reserveLogMap?[phone];
}
showReserveInfoView() {
_resetReserveData();
_isShowReserveInfoView = true;
_animationController?.forward();
notifyListeners();
}
hideReserveInfoView() {
_isShowReserveInfoView = false;
_animationController?.reverse();
}
execCallLog(CallLogModel? callLog) {
showReserveInfoView();
bookingPhoneController.text = callLog?.number ?? "";
bookingNameController.text = callLog?.name ?? "";
notifyListeners();
}
updateBookingTime(int hour, int minute) {
bookingSelectedTime =
"${hour.toString().padLeft(2, '0')}:${minute.toString().padLeft(2, '0')}";
notifyListeners();
}
updateBookingGender(int gender) {
if (bookingGender == gender) {
return;
}
bookingGender = gender;
notifyListeners();
}
updateBookingAttr(String key, bool val) {
if (key == "focus") {
bookingFocus = val;
} else if (key == "sms") {
bookingSms = val;
}
notifyListeners();
}
updateBookingStandard(String standard) {
bookingStandardType = standard;
notifyListeners();
}
clickTable(TableModel table) {
if (_isShowReserveInfoView) {
if (table.bookingInfo != null) {
Utils.toast("当前台桌已预定", context);
return;
}
selectedTable = table;
TableAreaModel? area = _tableAreaMap[table.areaId!];
showTableName = "${area?.name ?? ""} - ${table.name}";
notifyListeners();
return;
}
if (table.bookingInfo == null) {
return;
}
_changeReserveStatus(table);
}
_resetReserveData() {
bookingGender = 1;
bookingNumController.text = "";
bookingPhoneController.text = "";
bookingNameController.text = "";
bookingTypeController.text = "";
bookingTableNumController.text = "";
bookingStandardController.text = "";
bookingRemarkController.text = "";
bookingSelectedTime = "";
bookingFocus = false;
bookingSms = false;
bookingStandardType = "table";
selectedTable = null;
showTableName = "";
}
_changeReserveStatus(TableModel table) async {
var result = await showCupertinoModalPopup(
context: context!,
builder: (context) {
return CupertinoActionSheet(
title: const Text('预定信息'),
actions: <Widget>[
CupertinoActionSheetAction(
onPressed: () {
Navigator.of(context).pop('arrive');
},
isDefaultAction: true,
child: const Text('到店'),
),
CupertinoActionSheetAction(
child: const Text('取消预定'),
onPressed: () {
Navigator.of(context).pop('cancelBooking');
},
// isDestructiveAction: true,
),
],
cancelButton: CupertinoActionSheetAction(
child: const Text('取消'),
onPressed: () {
Navigator.of(context).pop('cancel');
},
),
);
});
yjPrint('$result');
if (result == 'arrive') {
await ReserveModel.updateReserveStatus(table.bookingInfo?.id ?? 0, 10);
Utils.toast("到店成功", context);
loadAreaTableList(0);
} else if (result == 'cancelBooking') {
ReserveModel.updateReserveStatus(table.bookingInfo?.id ?? 0, -1);
Utils.toast("取消成功", context);
loadAreaTableList(0);
}
}
bool _checkReserveInfo() {
if (selectedTable == null) {
Utils.toast("请选择台桌", context);
return false;
}
if (bookingNumController.text.isEmpty) {
Utils.toast("请输入就餐人数", context);
return false;
}
if (bookingPhoneController.text.isEmpty) {
Utils.toast("请输入联系电话", context);
return false;
}
if (bookingNameController.text.isEmpty) {
Utils.toast("请输入预订人姓名", context);
return false;
}
if (bookingSelectedTime.isEmpty) {
Utils.toast("请选择预订时间", context);
return false;
}
if (bookingTypeController.text.isEmpty) {
Utils.toast("请输入预订类型", context);
return false;
}
return true;
}
String getCurrentDate() {
return "${now.year}/${now.month}/${now.day}";
}
String getOffsetDay(int offset) {
DateTime offsetDay = now.add(Duration(days: offset));
return "${dayInfoMap[offset]}/${offsetDay.day}";
}
String getOffsetWeekday(int offset) {
DateTime offsetDay = now.add(Duration(days: offset));
return weekdayMap[offsetDay.weekday] ?? "";
}
}

344
lib/login/login_view.dart Normal file
View File

@@ -0,0 +1,344 @@
import 'dart:convert';
import 'package:cashier_reserve/common/encrypt/pwd.dart';
import 'package:cashier_reserve/common/manager/app_manager.dart';
import 'package:cashier_reserve/common/print/print.dart';
import 'package:cashier_reserve/common/utils/utils.dart';
import 'package:cashier_reserve/model/login_model.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class LoginView extends StatefulWidget {
const LoginView({super.key});
@override
State<StatefulWidget> createState() {
return _LoginViewState();
}
}
class _LoginViewState extends State<LoginView> {
int loginType = 0; //0商户 1员工
int segmentIndex = 0;
TextEditingController merchantText = TextEditingController();
TextEditingController accountText = TextEditingController();
TextEditingController passwordText = TextEditingController();
TextEditingController codeText = TextEditingController();
String authCodeUuid = "";
String authCodeImg = "";
DateTime? lastPopTime;
@override
void initState() {
super.initState();
accountText.text = "18049104914";
passwordText.text = "czg123456";
_loadAuthCode();
}
void _loadAuthCode() async {
var res = await LoginModel.getLoginAuthCode();
if (res is Map) {
Map<String, dynamic> result = res as Map<String, dynamic>;
setState(() {
yjPrint("[result]: $result");
authCodeUuid = result["uuid"] ?? "";
String img = result["code"] ?? "";
if (img.startsWith("data:image/png;base64,")) {
authCodeImg = img.substring("data:image/png;base64,".length);
}
});
} else {
yjPrint("获取验证码失败");
}
}
void _goLogin() async {
if (loginType == 1) {
if (merchantText.text.isEmpty) {
Utils.toast("请输入商户号", context);
return;
}
}
if (accountText.text.isEmpty) {
Utils.toast("请输入账号", context);
return;
}
if (passwordText.text.isEmpty) {
Utils.toast("请输入密码", context);
return;
}
if (codeText.text.isEmpty) {
Utils.toast("请输入验证码", context);
return;
}
// String pwd = PwdEncrypt.encrypt(passwordText.text);
String pwd = passwordText.text;
yjPrint("pwd === $pwd");
var params = {
"username": loginType == 0 ? accountText.text : merchantText.text,
"staffUserName": loginType == 1 ? accountText.text : '',
"password": pwd,
"code": codeText.text,
"uuid": authCodeUuid,
"loginType": loginType,
};
final r = await LoginModel.login(params);
if (r == null) {
_loadAuthCode();
return;
}
AppManager.loginSuccess(r);
}
@override
void dispose() {
merchantText.dispose();
accountText.dispose();
passwordText.dispose();
codeText.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, result) async {
// 点击返回键的操作
if (lastPopTime == null ||
DateTime.now().difference(lastPopTime!) >
const Duration(seconds: 2)) {
lastPopTime = DateTime.now();
// Utils.toast('再按一次退出', context);
} else {
lastPopTime = DateTime.now();
// 退出app
await SystemChannels.platform.invokeMethod('SystemNavigator.pop');
}
},
child: Scaffold(
backgroundColor: Colors.black.withOpacity(0.2),
body: GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
},
child: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
color: const Color(0x55000000),
child: Center(
child: Container(
padding: const EdgeInsets.fromLTRB(30, 30, 30, 30),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
),
width: 400,
// height: 400,
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildTitle(context),
const SizedBox(
height: 30,
),
_buildSegment(context),
const SizedBox(
height: 5,
),
_buildInputTextField(context, merchantText,
hintText: "请输入商户号",
icon: Icons.store_sharp,
isHidden: loginType == 0),
_buildInputTextField(context, accountText,
hintText: "请输入账号", icon: Icons.people),
_buildInputTextField(context, passwordText,
hintText: "请输入密码",
icon: Icons.lock,
isPassword: true),
_buildInputTextField(context, codeText,
hintText: "请输入验证码",
icon: Icons.admin_panel_settings_sharp,
isCode: true),
_buildLoginBtn(context),
],
),
),
),
),
),
),
),
);
}
Widget _buildLoginBtn(BuildContext context) {
return InkWell(
onTap: () {
_goLogin();
},
child: Container(
margin: const EdgeInsets.only(top: 25),
height: 35,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(5),
),
child: const Center(
child: Text(
"登录",
style: TextStyle(fontSize: 15, color: Colors.white),
),
),
),
);
}
Widget _buildTitle(BuildContext context) {
return const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"银收客订餐系统",
style: TextStyle(
fontSize: 25,
color: Color(0xff333333),
decoration: TextDecoration.none),
)
],
);
}
Widget _buildSegment(BuildContext context) {
return CupertinoSegmentedControl(
//子标签
children: {
0: Container(
width: 60,
height: 30,
alignment: Alignment.center,
child: const Text(
"商户",
style: TextStyle(
fontSize: 14,
// color: Color(0xff333333),
decoration: TextDecoration.none),
),
),
1: Container(
width: 60,
height: 30,
alignment: Alignment.center,
child: const Text(
"员工",
style: TextStyle(
fontSize: 14,
// color: Color(0xff333333),
decoration: TextDecoration.none),
),
),
},
//当前选中的索引
groupValue: segmentIndex,
//点击回调
onValueChanged: (int index) {
setState(() {
segmentIndex = index;
loginType = index == 0 ? 0 : 1;
});
},
selectedColor: Colors.blue,
borderColor: Colors.blue,
unselectedColor: Colors.white,
);
}
Widget _buildInputTextField(
BuildContext context,
TextEditingController controller, {
String hintText = "",
IconData icon = Icons.people,
bool isPassword = false,
bool isHidden = false,
bool isCode = false,
}) {
if (isHidden) {
return Container();
}
return Column(
children: [
const SizedBox(
height: 10,
),
Row(
children: [
Expanded(
child: Container(
decoration: BoxDecoration(
border: Border.all(color: const Color(0xffefefef), width: 1),
borderRadius: BorderRadius.circular(5),
),
padding: const EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 35,
child: TextField(
controller: controller,
obscureText: isPassword,
decoration: InputDecoration(
icon: Icon(
icon,
color: const Color(0xffa6a6a6),
),
hintText: hintText,
hintStyle: const TextStyle(
fontSize: 14,
color: Color(0xff999999),
),
contentPadding: EdgeInsets.zero,
border: OutlineInputBorder(
borderSide: BorderSide.none,
borderRadius: BorderRadius.circular(5),
),
),
),
),
),
if (isCode)
InkWell(
onTap: () {
_loadAuthCode();
},
child: Container(
margin: const EdgeInsets.only(left: 15),
width: 100,
height: 35,
child: (authCodeImg.isNotEmpty)
? Image.memory(base64ToUint8List(authCodeImg))
: Container(),
),
)
],
),
],
);
}
Uint8List base64ToUint8List(String base64String) {
const decoder = Base64Decoder();
return decoder.convert(base64String);
}
}

61
lib/main.dart Normal file
View File

@@ -0,0 +1,61 @@
import 'dart:io';
import 'package:cashier_reserve/common/manager/app_manager.dart';
import 'package:cashier_reserve/root_view.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'common/base/widgets.dart';
import 'common/local/cupertino_localizations_delegate.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
/// 状态栏沉浸
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
statusBarIconBrightness: getAppBrightness(false),
statusBarBrightness: getAppBrightness(false)));
await AppManager.initThirdPackage();
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
List<Locale> an = [
const Locale('zh', 'CN'),
const Locale('en', 'US'),
];
List<Locale> ios = [
const Locale('en', 'US'),
const Locale('zh', 'CN'),
];
return MaterialApp(
locale: const Locale('zh'),
localizationsDelegates: const [
CupertinoLocalizationsDelegate(),
// 添加Material Design本地化委托
GlobalMaterialLocalizations.delegate,
// 添加Cupertino本地化委托用于iOS风格组件的本地化
GlobalCupertinoLocalizations.delegate,
// 添加通用的Flutter本地化委托
GlobalWidgetsLocalizations.delegate,
],
title: '订餐系统',
supportedLocales: Platform.isIOS ? ios : an,
debugShowCheckedModeBanner: false,
theme: ThemeData(
platform: TargetPlatform.iOS,
useMaterial3: false,
),
home: const RootView(),
);
}
}

View File

@@ -0,0 +1,17 @@
import 'package:cashier_reserve/common/request/request_manager.dart';
import 'package:cashier_reserve/data_model/login/login_result.dart';
/// LoginModel 登录相关请求
class LoginModel {
/// 获取 登录验证码
static Future<dynamic> getLoginAuthCode() async {
final r = await RequestManager.get("/account/admin/auth/captcha");
return r;
}
static Future<LoginResult?> login(Map<String, dynamic> params) async {
final r = await RequestManager.post("/account/admin/auth/login", params);
return r == null ? null : LoginResult.fromJson(r);
}
}

View File

@@ -0,0 +1,74 @@
import 'package:cashier_reserve/common/request/request_manager.dart';
import 'package:cashier_reserve/data_model/reserve/reserve_log_model.dart';
import 'package:cashier_reserve/data_model/reserve/table_area_model.dart';
import 'package:cashier_reserve/data_model/reserve/table_model.dart';
/// ReserveModel 台桌预定相关请求
class ReserveModel {
/// getShopTableAreaList 获取店铺桌台区域列表
static Future<List<TableAreaModel?>> getShopTableAreaList() async {
final r = await RequestManager.get("/api/booking/shop-table/area");
if (r == null) {
return [];
}
List<TableAreaModel?> list = [];
for (var item in r as List) {
list.add(TableAreaModel.fromJson(item));
}
return list;
}
/// getAreaTableList 获取区域桌台列表
static Future<List<TableModel?>> getAreaTableList(num areaId, String date, String type) async {
Map<String, dynamic> paramData = {
"bookingDate": date,
"bookingType": type,
};
if (areaId > 0) {
paramData["areaId"] = areaId;
}
final r = await RequestManager.get("/api/booking/shop-table/list", params: paramData);
if (r == null) {
return [];
}
List<TableModel?> list = [];
for (var item in r as List) {
list.add(TableModel.fromJson(item));
}
return list;
}
/// commitReserveInfo 提交预定信息
static Future<void> commitReserveInfo(Map<String, dynamic> params) async {
await RequestManager.post("/api/booking/shop-table", params);
}
static Future<Map<String, ReserveLogModel?>> getUserReserveLog(List<String> phoneNos) async {
final r = await RequestManager.post("/api/booking/shop-table/summary", {
"phoneNos": phoneNos,
});
Map<String, ReserveLogModel?> map = {};
if (r == null) {
return map;
}
for (var item in r.entries) {
map[item.key] = ReserveLogModel.fromJson(item.value);
}
return map;
}
static Future<String> getReserveSms() async {
final r = await RequestManager.get("/api/booking/shop-table/sms");
return r.toString();
}
static Future<void> updateReserveStatus(num reserveId, num status) async {
await RequestManager.post("/api/booking/shop-table/mark-status", {
"id": reserveId,
"status": status,
});
}
}

View File

@@ -0,0 +1,12 @@
import 'package:cashier_reserve/common/request/request_manager.dart';
import 'package:cashier_reserve/data_model/version/update_version_model.dart';
class VersionModel {
static Future<UpdateVersionModel?> requestNewVersionInfo() async {
final r = await RequestManager.get("/api/tbVersion/findBySource", params: {"source": "电话机"});
if (r is Map) {
return UpdateVersionModel.fromJson(r);
}
return null;
}
}

55
lib/root_view.dart Normal file
View File

@@ -0,0 +1,55 @@
import 'package:cashier_reserve/common/manager/app_manager.dart';
import 'package:cashier_reserve/home/home_view.dart';
import 'package:cashier_reserve/home/home_view_model.dart';
import 'package:flutter/services.dart';
import 'common/base/provider.dart';
import 'common/base/ui.dart';
import 'common/utils/utils.dart';
class RootView extends StatefulWidget {
const RootView({super.key});
@override
State<RootView> createState() => _RootView();
}
class _RootView extends State<RootView>
with AutomaticKeepAliveClientMixin, RouteAware {
@override
bool get wantKeepAlive => true;
DateTime? lastPopTime;
@override
void didChangeDependencies() {
super.didChangeDependencies();
}
@override
Widget build(BuildContext context) {
AppManager.setGlobalContext(context);
super.build(context);
return PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, result) async {
// 点击返回键的操作
if (lastPopTime == null ||
DateTime.now().difference(lastPopTime!) > const Duration(seconds: 2)) {
lastPopTime = DateTime.now();
Utils.toast('再按一次退出', context);
} else {
lastPopTime = DateTime.now();
// 退出app
await SystemChannels.platform.invokeMethod('SystemNavigator.pop');
}
},
child: MultiProvider(
providers: [
ChangeNotifierProvider<HomeViewModel>(
create: (_) => HomeViewModel(),
)
],
child: BaseUIController(stateWidget: HomeView()),
));
}
}

View File

@@ -0,0 +1,224 @@
import 'package:cashier_reserve/common/print/print.dart';
import 'package:cashier_reserve/data_model/version/update_version_model.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_app_installer/flutter_app_installer.dart';
import 'package:path_provider/path_provider.dart';
import 'package:percent_indicator/percent_indicator.dart';
class UpdateVersionView extends StatefulWidget {
final UpdateVersionModel versionModel;
const UpdateVersionView({super.key, required this.versionModel});
@override
State<StatefulWidget> createState() {
return _UpdateVersionViewState();
}
}
class _UpdateVersionViewState extends State<UpdateVersionView> {
DateTime? lastPopTime;
double progress = 0;
String progressText = "0%";
bool isDownload = false;
// 用于控制下载任务取消的CancelToken
CancelToken? _cancelToken;
@override
void dispose() {
_cancelToken?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return PopScope(
canPop: (widget.versionModel.isUp ?? 0) != 0,
onPopInvokedWithResult: (didPop, result) async {
// 点击返回键的操作
if (lastPopTime == null ||
DateTime.now().difference(lastPopTime!) >
const Duration(seconds: 2)) {
lastPopTime = DateTime.now();
// Utils.toast('再按一次退出', context);
} else {
lastPopTime = DateTime.now();
// 退出app
await SystemChannels.platform.invokeMethod('SystemNavigator.pop');
}
},
child: Scaffold(
backgroundColor: Colors.black.withOpacity(0.2),
body: GestureDetector(
onTap: () {
FocusScope.of(context).requestFocus(FocusNode());
},
child: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
color: const Color(0x55000000),
child: Center(
child: Container(
padding: const EdgeInsets.fromLTRB(30, 30, 30, 30),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
),
width: 400,
// height: 400,
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"发现新版本",
style:
TextStyle(fontSize: 22, color: Color(0xff333333)),
),
const SizedBox(
height: 20,
),
Text(
widget.versionModel.message ?? "",
style: const TextStyle(
fontSize: 16, color: Color(0xff666666)),
),
const SizedBox(
height: 20,
),
isDownload
? LinearPercentIndicator(
padding: EdgeInsets.zero,
animation: false,
lineHeight: 20.0,
// animationDuration: 2500,
percent: progress,
center: Text(progressText),
// linearStrokeCap: LinearStrokeCap.roundAll,
progressColor: Colors.green,
)
: Container(),
isDownload
? const SizedBox(
height: 20,
)
: Container(),
_buildActionViews(context),
],
),
),
),
),
),
),
),
);
}
Widget _buildActionViews(BuildContext context) {
bool isMustUpdate = widget.versionModel.isUp == 1;
return Row(mainAxisAlignment: MainAxisAlignment.center, children: [
isMustUpdate
? Container()
: ElevatedButton(
onPressed: () {
Navigator.of(context).pop();
},
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all(Colors.white),
foregroundColor:
WidgetStateProperty.all(const Color(0xff666666)),
minimumSize: WidgetStateProperty.all(const Size(100, 40)),
shape: WidgetStateProperty.all(const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20)),
)),
),
child: const Text("跳过"),
),
isMustUpdate
? Container()
: const SizedBox(
width: 20,
),
ElevatedButton(
onPressed: () {
startDownload();
},
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all(Colors.blue),
foregroundColor: WidgetStateProperty.all(Colors.white),
minimumSize:
WidgetStateProperty.all(Size(isMustUpdate ? 200 : 100, 40)),
shape: WidgetStateProperty.all(const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(20)),
)),
),
child: const Text("立即更新"),
),
]);
}
startDownload() async {
setState(() {
isDownload = true;
});
String fileUrl = widget.versionModel.url ?? "";
if (fileUrl.isEmpty) {
yjPrint("下载地址为空");
return;
}
_cancelToken?.cancel();
setState(() {
progress = 0.0;
progressText = "0.0%";
});
yjPrint("fileUrl: $fileUrl");
Dio dio = Dio();
try {
String savePath = await getPhoneLocalPath();
String appName = "file.apk";
_cancelToken = CancelToken(); // 创建CancelToken对象
await dio.download(
fileUrl, "$savePath$appName", onReceiveProgress: (received, total) {
if (total != -1) {
///当前下载的百分比例
double currentProgress = received / total;
setState(() {
progressText = "${(currentProgress * 100).toStringAsFixed(2)}%";
progress = double.parse(currentProgress.toStringAsFixed(2));
});
yjPrint(progressText);
}
}, cancelToken: _cancelToken);
yjPrint("文件下载成功");
_cancelToken = null;
installApk("$savePath$appName");
} catch (e) {
yjPrint("文件下载失败:$e");
}
}
Future<String> getPhoneLocalPath() async {
final directory = await getExternalStorageDirectory();
return directory?.path ?? '';
}
installApk(String apkPath) async {
final FlutterAppInstaller flutterAppInstaller = FlutterAppInstaller();
flutterAppInstaller.installApk(
filePath: apkPath,
);
}
}