Commit a18da124 by chenxianqi

增加kefu_workbench

parent 5110254e
Showing with 18697 additions and 3 deletions
...@@ -4,5 +4,4 @@ kefu_server ...@@ -4,5 +4,4 @@ kefu_server
kefu_server.tar.gz kefu_server.tar.gz
conf/app.back.conf conf/app.back.conf
node_modules node_modules
app copy.conf app copy.conf
kefu_workbench \ No newline at end of file
\ No newline at end of file
The file could not be displayed because it is too large.
{"inputs":["/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/.dart_tool/flutter_build/d85c8290f0ec9a206fd1ee4abe234e3d/app.dill","/Users/chenxianqi/flutter/bin/cache/artifacts/engine/darwin-x64/vm_isolate_snapshot.bin","/Users/chenxianqi/flutter/bin/cache/artifacts/engine/darwin-x64/isolate_snapshot.bin","/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/pubspec.yaml","/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/assets/fonts/iconfont/iconfont.ttf","/Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/cupertino_icons-0.1.3/assets/CupertinoIcons.ttf","/Users/chenxianqi/flutter/bin/cache/artifacts/material_fonts/MaterialIcons-Regular.ttf"],"outputs":["/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/build/app/intermediates/flutter/debug/vm_snapshot_data","/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/build/app/intermediates/flutter/debug/isolate_snapshot_data","/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/build/app/intermediates/flutter/debug/kernel_blob.bin","/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/build/app/intermediates/flutter/debug/flutter_assets/assets/fonts/iconfont/iconfont.ttf","/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/build/app/intermediates/flutter/debug/flutter_assets/packages/cupertino_icons/assets/CupertinoIcons.ttf","/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/build/app/intermediates/flutter/debug/flutter_assets/fonts/MaterialIcons-Regular.ttf","/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/build/app/intermediates/flutter/debug/flutter_assets/AssetManifest.json","/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/build/app/intermediates/flutter/debug/flutter_assets/FontManifest.json","/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/build/app/intermediates/flutter/debug/flutter_assets/LICENSE"]}
\ No newline at end of file
/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/build/app/intermediates/flutter/debug/flutter_assets/assets/fonts/iconfont/iconfont.ttf /Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/build/app/intermediates/flutter/debug/flutter_assets/packages/cupertino_icons/assets/CupertinoIcons.ttf /Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/build/app/intermediates/flutter/debug/flutter_assets/fonts/MaterialIcons-Regular.ttf /Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/build/app/intermediates/flutter/debug/flutter_assets/AssetManifest.json /Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/build/app/intermediates/flutter/debug/flutter_assets/FontManifest.json /Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/build/app/intermediates/flutter/debug/flutter_assets/LICENSE: /Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/pubspec.yaml /Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/assets/fonts/iconfont/iconfont.ttf /Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/cupertino_icons-0.1.3/assets/CupertinoIcons.ttf /Users/chenxianqi/flutter/bin/cache/artifacts/material_fonts/MaterialIcons-Regular.ttf
\ No newline at end of file
{"inputs":["/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/.dart_tool/flutter_build/e5a864af08558a7379ccad92ff7a9e95/armeabi-v7a/app.so"],"outputs":["/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/build/app/intermediates/flutter/release/armeabi-v7a/app.so"]}
\ No newline at end of file
{"inputs":["/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/.dart_tool/flutter_build/e5a864af08558a7379ccad92ff7a9e95/arm64-v8a/app.so"],"outputs":["/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/build/app/intermediates/flutter/release/arm64-v8a/app.so"]}
\ No newline at end of file
{"inputs":["/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/.dart_tool/flutter_build/e5a864af08558a7379ccad92ff7a9e95/x86_64/app.so"],"outputs":["/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/build/app/intermediates/flutter/release/x86_64/app.so"]}
\ No newline at end of file
{"inputs":["/Users/chenxianqi/flutter/packages/flutter_tools/lib/src/build_system/targets/dart.dart","/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/.dart_tool/flutter_build/e5a864af08558a7379ccad92ff7a9e95/app.dill","/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/.packages","/Users/chenxianqi/flutter/bin/cache/dart-sdk/bin/dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/LICENSE","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/AUTHORS","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/README.md","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/pubspec.yaml","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/sdk_ext/loader.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/sdk_ext/vmservice_io.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/sdk_ext/server.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/window.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/channel_buffers.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/plugins.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/text.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/pointer.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/painting.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/ui.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/hooks.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/semantics.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/hash_codes.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/lerp.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/geometry.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/compositing.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/isolate_name_server.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/natives.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/developer/extension.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/developer/timeline.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/developer/profiler.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/developer/service.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/developer/developer.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/iterable.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/collection.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/maps.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/hash_set.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/linked_hash_map.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/queue.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/list.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/splay_tree.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/iterator.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/linked_hash_set.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/set.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/hash_map.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/collections.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/linked_list.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/type.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/duration.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/iterable.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/invocation.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/date_time.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/stopwatch.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/annotations.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/bigint.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/map.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/comparable.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/core.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/print.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/sink.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/string.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/identical.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/object.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/list.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/pattern.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/expando.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/int.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/null.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/regexp.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/symbol.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/num.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/exceptions.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/double.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/iterator.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/string_sink.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/function.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/set.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/bool.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/errors.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/uri.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/stacktrace.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/string_buffer.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/isolate/isolate.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/isolate/capability.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/js/js_dart2js.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/stream_impl.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/stream_pipe.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/async_error.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/async.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/future.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/stream_controller.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/schedule_microtask.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/stream_transformers.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/deferred_load.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/future_impl.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/stream.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/timer.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/broadcast_stream_controller.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/zone.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/wasm/wasm.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/namespace_impl.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/link.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/process.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/string_transformer.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/file_impl.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/secure_server_socket.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/io_service.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/embedder_config.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/stdio.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/service_object.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/file_system_entity.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/eventhandler.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/io.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/directory.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/file.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/directory_impl.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/overrides.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/socket.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/common.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/security_context.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/io_resource_info.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/bytes_builder.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/platform.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/sync_socket.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/secure_socket.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/io_sink.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/network_profiling.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/data_transformer.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/platform_impl.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_interceptors/interceptors.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/internal/iterable.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/internal/internal.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/internal/sort.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/internal/print.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/internal/async_cast.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/internal/list.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/internal/symbol.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/internal/cast.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/internal/linked_list.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/html/html_dart2js.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/math/jenkins_smi_hash.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/math/point.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/math/random.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/math/rectangle.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/math/math.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/js_util/js_util_dart2js.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ffi/native_type.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ffi/annotations.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ffi/ffi.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ffi/struct.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ffi/dynamic_library.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_http/http_parser.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_http/http_session.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_http/websocket.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_http/overrides.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_http/http_date.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_http/http.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_http/http_headers.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_http/crypto.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_http/websocket_impl.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_http/http_impl.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/typed_data/typed_data.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/typed_data/unmodifiable_typed_data.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_embedder.yaml","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_empty.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/base64.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/byte_conversion.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/ascii.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/utf.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/json.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/string_conversion.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/latin1.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/html_escape.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/converter.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/convert.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/chunked_conversion.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/line_splitter.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/codec.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/encoding.dart","/Users/chenxianqi/flutter/bin/cache/artifacts/engine/android-arm-release/darwin-x64/gen_snapshot"],"outputs":["/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/.dart_tool/flutter_build/e5a864af08558a7379ccad92ff7a9e95/armeabi-v7a/app.so"]}
\ No newline at end of file
{"inputs":["/Users/chenxianqi/flutter/packages/flutter_tools/lib/src/build_system/targets/dart.dart","/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/.dart_tool/flutter_build/e5a864af08558a7379ccad92ff7a9e95/app.dill","/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/.packages","/Users/chenxianqi/flutter/bin/cache/dart-sdk/bin/dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/LICENSE","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/AUTHORS","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/README.md","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/pubspec.yaml","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/sdk_ext/loader.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/sdk_ext/vmservice_io.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/sdk_ext/server.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/window.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/channel_buffers.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/plugins.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/text.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/pointer.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/painting.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/ui.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/hooks.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/semantics.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/hash_codes.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/lerp.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/geometry.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/compositing.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/isolate_name_server.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/natives.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/developer/extension.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/developer/timeline.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/developer/profiler.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/developer/service.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/developer/developer.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/iterable.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/collection.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/maps.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/hash_set.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/linked_hash_map.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/queue.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/list.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/splay_tree.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/iterator.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/linked_hash_set.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/set.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/hash_map.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/collections.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/linked_list.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/type.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/duration.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/iterable.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/invocation.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/date_time.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/stopwatch.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/annotations.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/bigint.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/map.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/comparable.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/core.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/print.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/sink.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/string.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/identical.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/object.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/list.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/pattern.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/expando.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/int.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/null.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/regexp.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/symbol.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/num.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/exceptions.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/double.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/iterator.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/string_sink.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/function.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/set.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/bool.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/errors.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/uri.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/stacktrace.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/string_buffer.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/isolate/isolate.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/isolate/capability.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/js/js_dart2js.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/stream_impl.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/stream_pipe.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/async_error.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/async.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/future.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/stream_controller.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/schedule_microtask.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/stream_transformers.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/deferred_load.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/future_impl.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/stream.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/timer.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/broadcast_stream_controller.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/zone.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/wasm/wasm.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/namespace_impl.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/link.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/process.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/string_transformer.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/file_impl.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/secure_server_socket.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/io_service.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/embedder_config.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/stdio.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/service_object.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/file_system_entity.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/eventhandler.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/io.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/directory.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/file.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/directory_impl.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/overrides.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/socket.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/common.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/security_context.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/io_resource_info.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/bytes_builder.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/platform.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/sync_socket.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/secure_socket.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/io_sink.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/network_profiling.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/data_transformer.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/platform_impl.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_interceptors/interceptors.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/internal/iterable.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/internal/internal.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/internal/sort.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/internal/print.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/internal/async_cast.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/internal/list.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/internal/symbol.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/internal/cast.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/internal/linked_list.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/html/html_dart2js.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/math/jenkins_smi_hash.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/math/point.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/math/random.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/math/rectangle.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/math/math.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/js_util/js_util_dart2js.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ffi/native_type.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ffi/annotations.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ffi/ffi.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ffi/struct.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ffi/dynamic_library.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_http/http_parser.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_http/http_session.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_http/websocket.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_http/overrides.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_http/http_date.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_http/http.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_http/http_headers.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_http/crypto.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_http/websocket_impl.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_http/http_impl.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/typed_data/typed_data.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/typed_data/unmodifiable_typed_data.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_embedder.yaml","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_empty.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/base64.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/byte_conversion.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/ascii.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/utf.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/json.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/string_conversion.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/latin1.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/html_escape.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/converter.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/convert.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/chunked_conversion.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/line_splitter.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/codec.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/encoding.dart","/Users/chenxianqi/flutter/bin/cache/artifacts/engine/android-arm64-release/darwin-x64/gen_snapshot"],"outputs":["/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/.dart_tool/flutter_build/e5a864af08558a7379ccad92ff7a9e95/arm64-v8a/app.so"]}
\ No newline at end of file
{"inputs":["/Users/chenxianqi/flutter/packages/flutter_tools/lib/src/build_system/targets/dart.dart","/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/.dart_tool/flutter_build/e5a864af08558a7379ccad92ff7a9e95/app.dill","/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/.packages","/Users/chenxianqi/flutter/bin/cache/dart-sdk/bin/dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/LICENSE","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/AUTHORS","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/README.md","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/pubspec.yaml","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/sdk_ext/loader.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/sdk_ext/vmservice_io.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/sdk_ext/server.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/window.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/channel_buffers.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/plugins.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/text.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/pointer.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/painting.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/ui.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/hooks.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/semantics.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/hash_codes.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/lerp.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/geometry.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/compositing.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/isolate_name_server.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ui/natives.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/developer/extension.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/developer/timeline.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/developer/profiler.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/developer/service.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/developer/developer.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/iterable.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/collection.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/maps.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/hash_set.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/linked_hash_map.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/queue.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/list.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/splay_tree.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/iterator.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/linked_hash_set.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/set.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/hash_map.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/collections.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/collection/linked_list.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/type.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/duration.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/iterable.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/invocation.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/date_time.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/stopwatch.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/annotations.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/bigint.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/map.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/comparable.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/core.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/print.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/sink.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/string.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/identical.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/object.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/list.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/pattern.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/expando.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/int.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/null.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/regexp.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/symbol.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/num.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/exceptions.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/double.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/iterator.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/string_sink.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/function.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/set.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/bool.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/errors.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/uri.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/stacktrace.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/core/string_buffer.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/isolate/isolate.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/isolate/capability.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/js/js_dart2js.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/stream_impl.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/stream_pipe.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/async_error.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/async.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/future.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/stream_controller.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/schedule_microtask.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/stream_transformers.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/deferred_load.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/future_impl.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/stream.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/timer.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/broadcast_stream_controller.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/async/zone.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/wasm/wasm.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/namespace_impl.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/link.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/process.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/string_transformer.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/file_impl.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/secure_server_socket.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/io_service.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/embedder_config.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/stdio.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/service_object.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/file_system_entity.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/eventhandler.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/io.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/directory.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/file.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/directory_impl.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/overrides.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/socket.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/common.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/security_context.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/io_resource_info.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/bytes_builder.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/platform.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/sync_socket.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/secure_socket.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/io_sink.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/network_profiling.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/data_transformer.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/io/platform_impl.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_interceptors/interceptors.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/internal/iterable.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/internal/internal.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/internal/sort.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/internal/print.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/internal/async_cast.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/internal/list.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/internal/symbol.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/internal/cast.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/internal/linked_list.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/html/html_dart2js.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/math/jenkins_smi_hash.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/math/point.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/math/random.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/math/rectangle.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/math/math.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/js_util/js_util_dart2js.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ffi/native_type.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ffi/annotations.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ffi/ffi.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ffi/struct.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/ffi/dynamic_library.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_http/http_parser.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_http/http_session.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_http/websocket.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_http/overrides.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_http/http_date.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_http/http.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_http/http_headers.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_http/crypto.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_http/websocket_impl.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_http/http_impl.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/typed_data/typed_data.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/typed_data/unmodifiable_typed_data.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_embedder.yaml","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/_empty.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/base64.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/byte_conversion.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/ascii.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/utf.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/json.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/string_conversion.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/latin1.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/html_escape.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/converter.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/convert.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/chunked_conversion.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/line_splitter.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/codec.dart","/Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/convert/encoding.dart","/Users/chenxianqi/flutter/bin/cache/artifacts/engine/android-x64-release/darwin-x64/gen_snapshot"],"outputs":["/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/.dart_tool/flutter_build/e5a864af08558a7379ccad92ff7a9e95/x86_64/app.so"]}
\ No newline at end of file
{"inputs":["/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/.dart_tool/flutter_build/e5a864af08558a7379ccad92ff7a9e95/app.dill","/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/pubspec.yaml","/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/assets/fonts/iconfont/iconfont.ttf","/Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/cupertino_icons-0.1.3/assets/CupertinoIcons.ttf","/Users/chenxianqi/flutter/bin/cache/artifacts/material_fonts/MaterialIcons-Regular.ttf"],"outputs":["/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/build/app/intermediates/flutter/release/flutter_assets/assets/fonts/iconfont/iconfont.ttf","/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/build/app/intermediates/flutter/release/flutter_assets/packages/cupertino_icons/assets/CupertinoIcons.ttf","/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/build/app/intermediates/flutter/release/flutter_assets/fonts/MaterialIcons-Regular.ttf","/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/build/app/intermediates/flutter/release/flutter_assets/AssetManifest.json","/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/build/app/intermediates/flutter/release/flutter_assets/FontManifest.json","/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/build/app/intermediates/flutter/release/flutter_assets/LICENSE"]}
\ No newline at end of file
The file could not be displayed because it is too large.
gen_snapshot.d: /Users/chenxianqi/flutter/bin/cache/artifacts/engine/android-arm64-release/darwin-x64/gen_snapshot
gen_snapshot.d: /Users/chenxianqi/flutter/bin/cache/artifacts/engine/android-arm-release/darwin-x64/gen_snapshot
/Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/build/app/intermediates/flutter/release/flutter_assets/assets/fonts/iconfont/iconfont.ttf /Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/build/app/intermediates/flutter/release/flutter_assets/packages/cupertino_icons/assets/CupertinoIcons.ttf /Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/build/app/intermediates/flutter/release/flutter_assets/fonts/MaterialIcons-Regular.ttf /Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/build/app/intermediates/flutter/release/flutter_assets/AssetManifest.json /Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/build/app/intermediates/flutter/release/flutter_assets/FontManifest.json /Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/build/app/intermediates/flutter/release/flutter_assets/LICENSE: /Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/pubspec.yaml /Users/chenxianqi/go/src/kf_server/ui/kefu_workbench/assets/fonts/iconfont/iconfont.ttf /Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/cupertino_icons-0.1.3/assets/CupertinoIcons.ttf /Users/chenxianqi/flutter/bin/cache/artifacts/material_fonts/MaterialIcons-Regular.ttf
\ No newline at end of file
gen_snapshot.d: /Users/chenxianqi/flutter/bin/cache/artifacts/engine/android-x64-release/darwin-x64/gen_snapshot
The file could not be displayed because it is too large.
{"inputs":["/Users/chenxianqi/go/src/kefu_server/ui/kefu_workbench/.dart_tool/flutter_build/f64812039dd7f0a983e4aba2edf4eef0/app.dill","/Users/chenxianqi/flutter/bin/cache/artifacts/engine/darwin-x64/vm_isolate_snapshot.bin","/Users/chenxianqi/flutter/bin/cache/artifacts/engine/darwin-x64/isolate_snapshot.bin","/Users/chenxianqi/go/src/kefu_server/ui/kefu_workbench/pubspec.yaml","/Users/chenxianqi/go/src/kefu_server/ui/kefu_workbench/assets/fonts/iconfont/iconfont.ttf","/Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/cupertino_icons-0.1.3/assets/CupertinoIcons.ttf","/Users/chenxianqi/flutter/bin/cache/artifacts/material_fonts/MaterialIcons-Regular.ttf"],"outputs":["/Users/chenxianqi/go/src/kefu_server/ui/kefu_workbench/build/app/intermediates/flutter/debug/vm_snapshot_data","/Users/chenxianqi/go/src/kefu_server/ui/kefu_workbench/build/app/intermediates/flutter/debug/isolate_snapshot_data","/Users/chenxianqi/go/src/kefu_server/ui/kefu_workbench/build/app/intermediates/flutter/debug/kernel_blob.bin","/Users/chenxianqi/go/src/kefu_server/ui/kefu_workbench/build/app/intermediates/flutter/debug/flutter_assets/assets/fonts/iconfont/iconfont.ttf","/Users/chenxianqi/go/src/kefu_server/ui/kefu_workbench/build/app/intermediates/flutter/debug/flutter_assets/packages/cupertino_icons/assets/CupertinoIcons.ttf","/Users/chenxianqi/go/src/kefu_server/ui/kefu_workbench/build/app/intermediates/flutter/debug/flutter_assets/fonts/MaterialIcons-Regular.ttf","/Users/chenxianqi/go/src/kefu_server/ui/kefu_workbench/build/app/intermediates/flutter/debug/flutter_assets/AssetManifest.json","/Users/chenxianqi/go/src/kefu_server/ui/kefu_workbench/build/app/intermediates/flutter/debug/flutter_assets/FontManifest.json","/Users/chenxianqi/go/src/kefu_server/ui/kefu_workbench/build/app/intermediates/flutter/debug/flutter_assets/LICENSE"]}
\ No newline at end of file
/Users/chenxianqi/go/src/kefu_server/ui/kefu_workbench/build/app/intermediates/flutter/debug/flutter_assets/assets/fonts/iconfont/iconfont.ttf /Users/chenxianqi/go/src/kefu_server/ui/kefu_workbench/build/app/intermediates/flutter/debug/flutter_assets/packages/cupertino_icons/assets/CupertinoIcons.ttf /Users/chenxianqi/go/src/kefu_server/ui/kefu_workbench/build/app/intermediates/flutter/debug/flutter_assets/fonts/MaterialIcons-Regular.ttf /Users/chenxianqi/go/src/kefu_server/ui/kefu_workbench/build/app/intermediates/flutter/debug/flutter_assets/AssetManifest.json /Users/chenxianqi/go/src/kefu_server/ui/kefu_workbench/build/app/intermediates/flutter/debug/flutter_assets/FontManifest.json /Users/chenxianqi/go/src/kefu_server/ui/kefu_workbench/build/app/intermediates/flutter/debug/flutter_assets/LICENSE: /Users/chenxianqi/go/src/kefu_server/ui/kefu_workbench/pubspec.yaml /Users/chenxianqi/go/src/kefu_server/ui/kefu_workbench/assets/fonts/iconfont/iconfont.ttf /Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/cupertino_icons-0.1.3/assets/CupertinoIcons.ttf /Users/chenxianqi/flutter/bin/cache/artifacts/material_fonts/MaterialIcons-Regular.ttf
\ No newline at end of file
{
"configVersion": 2,
"packages": [
{
"name": "archive",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/archive-2.0.11",
"packageUri": "lib/",
"languageVersion": "2.0"
},
{
"name": "args",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/args-1.5.2",
"packageUri": "lib/",
"languageVersion": "2.0"
},
{
"name": "async",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/async-2.4.0",
"packageUri": "lib/",
"languageVersion": "2.0"
},
{
"name": "boolean_selector",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/boolean_selector-1.0.5",
"packageUri": "lib/",
"languageVersion": "2.0"
},
{
"name": "charcode",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/charcode-1.1.2",
"packageUri": "lib/",
"languageVersion": "1.0"
},
{
"name": "collection",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/collection-1.14.11",
"packageUri": "lib/",
"languageVersion": "2.0"
},
{
"name": "connectivity",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/connectivity-0.4.8+2",
"packageUri": "lib/",
"languageVersion": "2.0"
},
{
"name": "connectivity_macos",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/connectivity_macos-0.1.0+2",
"packageUri": "lib/",
"languageVersion": "2.1"
},
{
"name": "connectivity_platform_interface",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/connectivity_platform_interface-1.0.3",
"packageUri": "lib/",
"languageVersion": "2.0"
},
{
"name": "convert",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/convert-2.1.1",
"packageUri": "lib/",
"languageVersion": "1.17"
},
{
"name": "crypto",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/crypto-2.1.3",
"packageUri": "lib/",
"languageVersion": "2.1"
},
{
"name": "csslib",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/csslib-0.16.1",
"packageUri": "lib/",
"languageVersion": "2.1"
},
{
"name": "cupertino_icons",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/cupertino_icons-0.1.3",
"packageUri": "lib/",
"languageVersion": "2.0"
},
{
"name": "dio",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/dio-3.0.9",
"packageUri": "lib/",
"languageVersion": "2.4"
},
{
"name": "file_picker",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/file_picker-1.6.3+1",
"packageUri": "lib/",
"languageVersion": "2.0"
},
{
"name": "flutter",
"rootUri": "file:///Users/chenxianqi/flutter/packages/flutter",
"packageUri": "lib/",
"languageVersion": "2.2"
},
{
"name": "flutter_advanced_networkimage",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/flutter_advanced_networkimage-0.6.4",
"packageUri": "lib/",
"languageVersion": "2.3"
},
{
"name": "flutter_html",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/flutter_html-0.11.1",
"packageUri": "lib/",
"languageVersion": "1.19"
},
{
"name": "flutter_local_notifications",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/flutter_local_notifications-1.4.1",
"packageUri": "lib/",
"languageVersion": "2.0"
},
{
"name": "flutter_local_notifications_platform_interface",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/flutter_local_notifications_platform_interface-1.0.1",
"packageUri": "lib/",
"languageVersion": "2.1"
},
{
"name": "flutter_localizations",
"rootUri": "file:///Users/chenxianqi/flutter/packages/flutter_localizations",
"packageUri": "lib/",
"languageVersion": "2.2"
},
{
"name": "flutter_mimc",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/flutter_mimc-1.0.2",
"packageUri": "lib/",
"languageVersion": "2.1"
},
{
"name": "flutter_plugin_android_lifecycle",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/flutter_plugin_android_lifecycle-1.0.6",
"packageUri": "lib/",
"languageVersion": "2.1"
},
{
"name": "flutter_svg",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/flutter_svg-0.17.3+1",
"packageUri": "lib/",
"languageVersion": "2.2"
},
{
"name": "flutter_test",
"rootUri": "file:///Users/chenxianqi/flutter/packages/flutter_test",
"packageUri": "lib/",
"languageVersion": "2.2"
},
{
"name": "flutter_web_plugins",
"rootUri": "file:///Users/chenxianqi/flutter/packages/flutter_web_plugins",
"packageUri": "lib/",
"languageVersion": "2.0"
},
{
"name": "fluttertoast",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/git/FlutterToast-8b7d690a0123f1c3c9866edbc0acfab937f56a23/",
"packageUri": "lib/",
"languageVersion": "2.0"
},
{
"name": "html",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/html-0.14.0+3",
"packageUri": "lib/",
"languageVersion": "2.3"
},
{
"name": "http",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/http-0.12.0+4",
"packageUri": "lib/",
"languageVersion": "2.4"
},
{
"name": "http_parser",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/http_parser-3.1.4",
"packageUri": "lib/",
"languageVersion": "2.3"
},
{
"name": "image",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/image-2.1.4",
"packageUri": "lib/",
"languageVersion": "2.0"
},
{
"name": "image_picker",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/image_picker-0.6.5",
"packageUri": "lib/",
"languageVersion": "2.0"
},
{
"name": "intl",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/intl-0.16.0",
"packageUri": "lib/",
"languageVersion": "2.0"
},
{
"name": "matcher",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/matcher-0.12.6",
"packageUri": "lib/",
"languageVersion": "2.2"
},
{
"name": "meta",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/meta-1.1.8",
"packageUri": "lib/",
"languageVersion": "1.12"
},
{
"name": "nested",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/nested-0.0.4",
"packageUri": "lib/",
"languageVersion": "2.2"
},
{
"name": "path",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/path-1.6.4",
"packageUri": "lib/",
"languageVersion": "2.0"
},
{
"name": "path_drawing",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/path_drawing-0.4.1",
"packageUri": "lib/",
"languageVersion": "1.19"
},
{
"name": "path_parsing",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/path_parsing-0.1.4",
"packageUri": "lib/",
"languageVersion": "1.19"
},
{
"name": "path_provider",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/path_provider-1.6.5",
"packageUri": "lib/",
"languageVersion": "2.0"
},
{
"name": "path_provider_macos",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/path_provider_macos-0.0.4",
"packageUri": "lib/",
"languageVersion": "2.1"
},
{
"name": "path_provider_platform_interface",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/path_provider_platform_interface-1.0.1",
"packageUri": "lib/",
"languageVersion": "2.0"
},
{
"name": "pedantic",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/pedantic-1.8.0+1",
"packageUri": "lib/",
"languageVersion": "2.1"
},
{
"name": "permission_handler",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/permission_handler-4.0.0",
"packageUri": "lib/",
"languageVersion": "2.1"
},
{
"name": "petitparser",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/petitparser-2.4.0",
"packageUri": "lib/",
"languageVersion": "2.4"
},
{
"name": "photo_view",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/photo_view-0.9.2",
"packageUri": "lib/",
"languageVersion": "2.6"
},
{
"name": "platform",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/platform-2.2.1",
"packageUri": "lib/",
"languageVersion": "1.24"
},
{
"name": "plugin_platform_interface",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/plugin_platform_interface-1.0.2",
"packageUri": "lib/",
"languageVersion": "2.1"
},
{
"name": "provider",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/provider-4.0.5",
"packageUri": "lib/",
"languageVersion": "2.2"
},
{
"name": "quiver",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/quiver-2.0.5",
"packageUri": "lib/",
"languageVersion": "2.0"
},
{
"name": "shared_preferences",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences-0.5.6+3",
"packageUri": "lib/",
"languageVersion": "2.0"
},
{
"name": "shared_preferences_macos",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences_macos-0.0.1+6",
"packageUri": "lib/",
"languageVersion": "2.1"
},
{
"name": "shared_preferences_platform_interface",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences_platform_interface-1.0.3",
"packageUri": "lib/",
"languageVersion": "2.0"
},
{
"name": "shared_preferences_web",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences_web-0.1.2+4",
"packageUri": "lib/",
"languageVersion": "2.0"
},
{
"name": "sky_engine",
"rootUri": "file:///Users/chenxianqi/flutter/bin/cache/pkg/sky_engine",
"packageUri": "lib/",
"languageVersion": "1.11"
},
{
"name": "source_span",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/source_span-1.5.5",
"packageUri": "lib/",
"languageVersion": "1.8"
},
{
"name": "stack_trace",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/stack_trace-1.9.3",
"packageUri": "lib/",
"languageVersion": "1.23"
},
{
"name": "stream_channel",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/stream_channel-2.0.0",
"packageUri": "lib/",
"languageVersion": "2.0"
},
{
"name": "string_scanner",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/string_scanner-1.0.5",
"packageUri": "lib/",
"languageVersion": "2.0"
},
{
"name": "term_glyph",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/term_glyph-1.1.0",
"packageUri": "lib/",
"languageVersion": "1.8"
},
{
"name": "test_api",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/test_api-0.2.11",
"packageUri": "lib/",
"languageVersion": "2.2"
},
{
"name": "typed_data",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/typed_data-1.1.6",
"packageUri": "lib/",
"languageVersion": "2.0"
},
{
"name": "vector_math",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/vector_math-2.0.8",
"packageUri": "lib/",
"languageVersion": "2.0"
},
{
"name": "xml",
"rootUri": "file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/xml-3.5.0",
"packageUri": "lib/",
"languageVersion": "2.3"
},
{
"name": "kefu_workbench",
"rootUri": "../",
"packageUri": "lib/",
"languageVersion": "2.1"
}
],
"generated": "2020-04-19T14:08:08.884195Z",
"generator": "pub",
"generatorVersion": "2.7.0"
}
# This is a generated file; do not edit or check into version control.
connectivity=/Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/connectivity-0.4.8+2/
connectivity_macos=/Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/connectivity_macos-0.1.0+2/
file_picker=/Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/file_picker-1.6.3+1/
flutter_local_notifications=/Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/flutter_local_notifications-1.4.1/
flutter_mimc=/Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/flutter_mimc-1.0.2/
flutter_plugin_android_lifecycle=/Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/flutter_plugin_android_lifecycle-1.0.6/
fluttertoast=/Users/chenxianqi/flutter/.pub-cache/git/FlutterToast-8b7d690a0123f1c3c9866edbc0acfab937f56a23/
image_picker=/Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/image_picker-0.6.5/
path_provider=/Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/path_provider-1.6.5/
path_provider_macos=/Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/path_provider_macos-0.0.4/
permission_handler=/Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/permission_handler-4.0.0/
shared_preferences=/Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences-0.5.6+3/
shared_preferences_macos=/Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences_macos-0.0.1+6/
shared_preferences_web=/Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences_web-0.1.2+4/
{"_info":"// This is a generated file; do not edit or check into version control.","dependencyGraph":[{"name":"connectivity","dependencies":["connectivity_macos"]},{"name":"connectivity_macos","dependencies":[]},{"name":"file_picker","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"flutter_local_notifications","dependencies":[]},{"name":"flutter_mimc","dependencies":[]},{"name":"flutter_plugin_android_lifecycle","dependencies":[]},{"name":"fluttertoast","dependencies":[]},{"name":"image_picker","dependencies":["flutter_plugin_android_lifecycle"]},{"name":"path_provider","dependencies":["path_provider_macos"]},{"name":"path_provider_macos","dependencies":[]},{"name":"permission_handler","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_macos","shared_preferences_web"]},{"name":"shared_preferences_macos","dependencies":[]},{"name":"shared_preferences_web","dependencies":[]}]}
\ No newline at end of file
build
\ No newline at end of file
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: 27321ebbad34b0a3fafe99fac037102196d655ff
channel: stable
project_type: app
# Generated by pub on 2020-04-19 22:08:08.863122.
archive:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/archive-2.0.11/lib/
args:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/args-1.5.2/lib/
async:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/async-2.4.0/lib/
boolean_selector:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/boolean_selector-1.0.5/lib/
charcode:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/charcode-1.1.2/lib/
collection:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/collection-1.14.11/lib/
connectivity:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/connectivity-0.4.8+2/lib/
connectivity_macos:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/connectivity_macos-0.1.0+2/lib/
connectivity_platform_interface:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/connectivity_platform_interface-1.0.3/lib/
convert:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/convert-2.1.1/lib/
crypto:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/crypto-2.1.3/lib/
csslib:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/csslib-0.16.1/lib/
cupertino_icons:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/cupertino_icons-0.1.3/lib/
dio:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/dio-3.0.9/lib/
file_picker:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/file_picker-1.6.3+1/lib/
flutter:file:///Users/chenxianqi/flutter/packages/flutter/lib/
flutter_advanced_networkimage:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/flutter_advanced_networkimage-0.6.4/lib/
flutter_html:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/flutter_html-0.11.1/lib/
flutter_local_notifications:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/flutter_local_notifications-1.4.1/lib/
flutter_local_notifications_platform_interface:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/flutter_local_notifications_platform_interface-1.0.1/lib/
flutter_localizations:file:///Users/chenxianqi/flutter/packages/flutter_localizations/lib/
flutter_mimc:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/flutter_mimc-1.0.2/lib/
flutter_plugin_android_lifecycle:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/flutter_plugin_android_lifecycle-1.0.6/lib/
flutter_svg:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/flutter_svg-0.17.3+1/lib/
flutter_test:file:///Users/chenxianqi/flutter/packages/flutter_test/lib/
flutter_web_plugins:file:///Users/chenxianqi/flutter/packages/flutter_web_plugins/lib/
fluttertoast:file:///Users/chenxianqi/flutter/.pub-cache/git/FlutterToast-8b7d690a0123f1c3c9866edbc0acfab937f56a23/lib/
html:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/html-0.14.0+3/lib/
http:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/http-0.12.0+4/lib/
http_parser:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/http_parser-3.1.4/lib/
image:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/image-2.1.4/lib/
image_picker:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/image_picker-0.6.5/lib/
intl:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/intl-0.16.0/lib/
matcher:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/matcher-0.12.6/lib/
meta:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/meta-1.1.8/lib/
nested:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/nested-0.0.4/lib/
path:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/path-1.6.4/lib/
path_drawing:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/path_drawing-0.4.1/lib/
path_parsing:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/path_parsing-0.1.4/lib/
path_provider:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/path_provider-1.6.5/lib/
path_provider_macos:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/path_provider_macos-0.0.4/lib/
path_provider_platform_interface:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/path_provider_platform_interface-1.0.1/lib/
pedantic:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/pedantic-1.8.0+1/lib/
permission_handler:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/permission_handler-4.0.0/lib/
petitparser:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/petitparser-2.4.0/lib/
photo_view:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/photo_view-0.9.2/lib/
platform:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/platform-2.2.1/lib/
plugin_platform_interface:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/plugin_platform_interface-1.0.2/lib/
provider:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/provider-4.0.5/lib/
quiver:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/quiver-2.0.5/lib/
shared_preferences:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences-0.5.6+3/lib/
shared_preferences_macos:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences_macos-0.0.1+6/lib/
shared_preferences_platform_interface:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences_platform_interface-1.0.3/lib/
shared_preferences_web:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/shared_preferences_web-0.1.2+4/lib/
sky_engine:file:///Users/chenxianqi/flutter/bin/cache/pkg/sky_engine/lib/
source_span:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/source_span-1.5.5/lib/
stack_trace:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/stack_trace-1.9.3/lib/
stream_channel:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/stream_channel-2.0.0/lib/
string_scanner:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/string_scanner-1.0.5/lib/
term_glyph:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/term_glyph-1.1.0/lib/
test_api:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/test_api-0.2.11/lib/
typed_data:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/typed_data-1.1.6/lib/
vector_math:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/vector_math-2.0.8/lib/
xml:file:///Users/chenxianqi/flutter/.pub-cache/hosted/pub.flutter-io.cn/xml-3.5.0/lib/
kefu_workbench:lib/
{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Flutter",
"request": "launch",
"type": "dart"
}
]
}
\ No newline at end of file
客服系统APP客户端
gradle-wrapper.jar
/.gradle
/captures/
/gradlew
/gradlew.bat
/local.properties
GeneratedPluginRegistrant.java
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>android</name>
<comment>Project android created by Buildship.</comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
</natures>
</projectDescription>
connection.project.dir=
eclipse.preferences.version=1
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8/"/>
<classpathentry kind="con" path="org.eclipse.buildship.core.gradleclasspathcontainer"/>
<classpathentry kind="output" path="bin/default"/>
</classpath>
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>app</name>
<comment>Project app created by Buildship.</comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.buildship.core.gradleprojectbuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.buildship.core.gradleprojectnature</nature>
</natures>
</projectDescription>
connection.project.dir=..
eclipse.preferences.version=1
def localProperties = new Properties()
def localPropertiesFile = rootProject.file('local.properties')
if (localPropertiesFile.exists()) {
localPropertiesFile.withReader('UTF-8') { reader ->
localProperties.load(reader)
}
}
def flutterRoot = localProperties.getProperty('flutter.sdk')
if (flutterRoot == null) {
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
}
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
if (flutterVersionCode == null) {
flutterVersionCode = '1'
}
def flutterVersionName = localProperties.getProperty('flutter.versionName')
if (flutterVersionName == null) {
flutterVersionName = '1.0'
}
apply plugin: 'com.android.application'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}
android {
compileSdkVersion 28
lintOptions {
disable 'InvalidPackage'
}
defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId "com.keith.kefu_workbench"
minSdkVersion 21
targetSdkVersion 28
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
signingConfigs {
release {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile keystoreProperties['storeFile'] ? file(keystoreProperties['storeFile']) : null
storePassword keystoreProperties['storePassword']
}
}
buildTypes {
release {
// TODO: Add your own signing config for the release build.
// Signing with the debug keys for now, so `flutter run --release` works.
signingConfig signingConfigs.release
// signingConfig signingConfigs.debug
}
}
}
flutter {
source '../..'
}
dependencies {
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.1.1'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.keith.kefu_workbench">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.keith.kefu_workbench">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<application
android:name="io.flutter.app.FlutterApplication"
android:label="在线客服"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
package com.keith.kefu_workbench;
import androidx.annotation.NonNull;
import io.flutter.embedding.android.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugins.GeneratedPluginRegistrant;
public class MainActivity extends FlutterActivity {
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
GeneratedPluginRegistrant.registerWith(flutterEngine);
}
}
<?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/black" />
<!-- You can insert your own image assets here -->
<item>
<bitmap
android:gravity="center"
android:src="@mipmap/icon_launcher"
/>
</item>
</layer-list>
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
Flutter draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item>
</style>
</resources>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.keith.kefu_workbench">
<!-- Flutter needs it to communicate with the running application
to allow setting breakpoints, to provide hot reload, etc.
-->
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
}
}
allprojects {
repositories {
google()
jcenter()
}
}
rootProject.buildDir = '../build'
subprojects {
project.buildDir = "${rootProject.buildDir}/${project.name}"
}
subprojects {
project.evaluationDependsOn(':app')
}
task clean(type: Delete) {
delete rootProject.buildDir
}
org.gradle.jvmargs=-Xmx1536M
android.enableR8=true
android.useAndroidX=true
android.enableJetifier=true
#Fri Jun 23 08:50:38 CEST 2017
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
storePassword=123456
keyPassword=123456
keyAlias=kefu
storeFile=/Users/chenxianqi/product/kefu_workbench/key.jks
\ No newline at end of file
include ':app'
def flutterProjectRoot = rootProject.projectDir.parentFile.toPath()
def plugins = new Properties()
def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins')
if (pluginsFile.exists()) {
pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) }
}
plugins.each { name, path ->
def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile()
include ":$name"
project(":$name").projectDir = pluginDirectory
}
/* Logo 字体 */
@font-face {
font-family: "iconfont logo";
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834');
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'),
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg');
}
.logo {
font-family: "iconfont logo";
font-size: 160px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
/* tabs */
.nav-tabs {
position: relative;
}
.nav-tabs .nav-more {
position: absolute;
right: 0;
bottom: 0;
height: 42px;
line-height: 42px;
color: #666;
}
#tabs {
border-bottom: 1px solid #eee;
}
#tabs li {
cursor: pointer;
width: 100px;
height: 40px;
line-height: 40px;
text-align: center;
font-size: 16px;
border-bottom: 2px solid transparent;
position: relative;
z-index: 1;
margin-bottom: -1px;
color: #666;
}
#tabs .active {
border-bottom-color: #f00;
color: #222;
}
.tab-container .content {
display: none;
}
/* 页面布局 */
.main {
padding: 30px 100px;
width: 960px;
margin: 0 auto;
}
.main .logo {
color: #333;
text-align: left;
margin-bottom: 30px;
line-height: 1;
height: 110px;
margin-top: -50px;
overflow: hidden;
*zoom: 1;
}
.main .logo a {
font-size: 160px;
color: #333;
}
.helps {
margin-top: 40px;
}
.helps pre {
padding: 20px;
margin: 10px 0;
border: solid 1px #e7e1cd;
background-color: #fffdef;
overflow: auto;
}
.icon_lists {
width: 100% !important;
overflow: hidden;
*zoom: 1;
}
.icon_lists li {
width: 100px;
margin-bottom: 10px;
margin-right: 20px;
text-align: center;
list-style: none !important;
cursor: default;
}
.icon_lists li .code-name {
line-height: 1.2;
}
.icon_lists .icon {
display: block;
height: 100px;
line-height: 100px;
font-size: 42px;
margin: 10px auto;
color: #333;
-webkit-transition: font-size 0.25s linear, width 0.25s linear;
-moz-transition: font-size 0.25s linear, width 0.25s linear;
transition: font-size 0.25s linear, width 0.25s linear;
}
.icon_lists .icon:hover {
font-size: 100px;
}
.icon_lists .svg-icon {
/* 通过设置 font-size 来改变图标大小 */
width: 1em;
/* 图标和文字相邻时,垂直对齐 */
vertical-align: -0.15em;
/* 通过设置 color 来改变 SVG 的颜色/fill */
fill: currentColor;
/* path 和 stroke 溢出 viewBox 部分在 IE 下会显示
normalize.css 中也包含这行 */
overflow: hidden;
}
.icon_lists li .name,
.icon_lists li .code-name {
color: #666;
}
/* markdown 样式 */
.markdown {
color: #666;
font-size: 14px;
line-height: 1.8;
}
.highlight {
line-height: 1.5;
}
.markdown img {
vertical-align: middle;
max-width: 100%;
}
.markdown h1 {
color: #404040;
font-weight: 500;
line-height: 40px;
margin-bottom: 24px;
}
.markdown h2,
.markdown h3,
.markdown h4,
.markdown h5,
.markdown h6 {
color: #404040;
margin: 1.6em 0 0.6em 0;
font-weight: 500;
clear: both;
}
.markdown h1 {
font-size: 28px;
}
.markdown h2 {
font-size: 22px;
}
.markdown h3 {
font-size: 16px;
}
.markdown h4 {
font-size: 14px;
}
.markdown h5 {
font-size: 12px;
}
.markdown h6 {
font-size: 12px;
}
.markdown hr {
height: 1px;
border: 0;
background: #e9e9e9;
margin: 16px 0;
clear: both;
}
.markdown p {
margin: 1em 0;
}
.markdown>p,
.markdown>blockquote,
.markdown>.highlight,
.markdown>ol,
.markdown>ul {
width: 80%;
}
.markdown ul>li {
list-style: circle;
}
.markdown>ul li,
.markdown blockquote ul>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown>ul li p,
.markdown>ol li p {
margin: 0.6em 0;
}
.markdown ol>li {
list-style: decimal;
}
.markdown>ol li,
.markdown blockquote ol>li {
margin-left: 20px;
padding-left: 4px;
}
.markdown code {
margin: 0 3px;
padding: 0 5px;
background: #eee;
border-radius: 3px;
}
.markdown strong,
.markdown b {
font-weight: 600;
}
.markdown>table {
border-collapse: collapse;
border-spacing: 0px;
empty-cells: show;
border: 1px solid #e9e9e9;
width: 95%;
margin-bottom: 24px;
}
.markdown>table th {
white-space: nowrap;
color: #333;
font-weight: 600;
}
.markdown>table th,
.markdown>table td {
border: 1px solid #e9e9e9;
padding: 8px 16px;
text-align: left;
}
.markdown>table th {
background: #F7F7F7;
}
.markdown blockquote {
font-size: 90%;
color: #999;
border-left: 4px solid #e9e9e9;
padding-left: 0.8em;
margin: 1em 0;
}
.markdown blockquote p {
margin: 0;
}
.markdown .anchor {
opacity: 0;
transition: opacity 0.3s ease;
margin-left: 8px;
}
.markdown .waiting {
color: #ccc;
}
.markdown h1:hover .anchor,
.markdown h2:hover .anchor,
.markdown h3:hover .anchor,
.markdown h4:hover .anchor,
.markdown h5:hover .anchor,
.markdown h6:hover .anchor {
opacity: 1;
display: inline-block;
}
.markdown>br,
.markdown>p>br {
clear: both;
}
.hljs {
display: block;
background: white;
padding: 0.5em;
color: #333333;
overflow-x: auto;
}
.hljs-comment,
.hljs-meta {
color: #969896;
}
.hljs-string,
.hljs-variable,
.hljs-template-variable,
.hljs-strong,
.hljs-emphasis,
.hljs-quote {
color: #df5000;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-type {
color: #a71d5d;
}
.hljs-literal,
.hljs-symbol,
.hljs-bullet,
.hljs-attribute {
color: #0086b3;
}
.hljs-section,
.hljs-name {
color: #63a35c;
}
.hljs-tag {
color: #333333;
}
.hljs-title,
.hljs-attr,
.hljs-selector-id,
.hljs-selector-class,
.hljs-selector-attr,
.hljs-selector-pseudo {
color: #795da3;
}
.hljs-addition {
color: #55a532;
background-color: #eaffea;
}
.hljs-deletion {
color: #bd2c00;
background-color: #ffecec;
}
.hljs-link {
text-decoration: underline;
}
/* 代码高亮 */
/* PrismJS 1.15.0
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */
/**
* prism.js default theme for JavaScript, CSS and HTML
* Based on dabblet (http://dabblet.com)
* @author Lea Verou
*/
code[class*="language-"],
pre[class*="language-"] {
color: black;
background: none;
text-shadow: 0 1px white;
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
text-align: left;
white-space: pre;
word-spacing: normal;
word-break: normal;
word-wrap: normal;
line-height: 1.5;
-moz-tab-size: 4;
-o-tab-size: 4;
tab-size: 4;
-webkit-hyphens: none;
-moz-hyphens: none;
-ms-hyphens: none;
hyphens: none;
}
pre[class*="language-"]::-moz-selection,
pre[class*="language-"] ::-moz-selection,
code[class*="language-"]::-moz-selection,
code[class*="language-"] ::-moz-selection {
text-shadow: none;
background: #b3d4fc;
}
pre[class*="language-"]::selection,
pre[class*="language-"] ::selection,
code[class*="language-"]::selection,
code[class*="language-"] ::selection {
text-shadow: none;
background: #b3d4fc;
}
@media print {
code[class*="language-"],
pre[class*="language-"] {
text-shadow: none;
}
}
/* Code blocks */
pre[class*="language-"] {
padding: 1em;
margin: .5em 0;
overflow: auto;
}
:not(pre)>code[class*="language-"],
pre[class*="language-"] {
background: #f5f2f0;
}
/* Inline code */
:not(pre)>code[class*="language-"] {
padding: .1em;
border-radius: .3em;
white-space: normal;
}
.token.comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: slategray;
}
.token.punctuation {
color: #999;
}
.namespace {
opacity: .7;
}
.token.property,
.token.tag,
.token.boolean,
.token.number,
.token.constant,
.token.symbol,
.token.deleted {
color: #905;
}
.token.selector,
.token.attr-name,
.token.string,
.token.char,
.token.builtin,
.token.inserted {
color: #690;
}
.token.operator,
.token.entity,
.token.url,
.language-css .token.string,
.style .token.string {
color: #9a6e3a;
background: hsla(0, 0%, 100%, .5);
}
.token.atrule,
.token.attr-value,
.token.keyword {
color: #07a;
}
.token.function,
.token.class-name {
color: #DD4A68;
}
.token.regex,
.token.important,
.token.variable {
color: #e90;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>IconFont Demo</title>
<link rel="shortcut icon" href="https://gtms04.alicdn.com/tps/i4/TB1_oz6GVXXXXaFXpXXJDFnIXXX-64-64.ico" type="image/x-icon"/>
<link rel="stylesheet" href="https://g.alicdn.com/thx/cube/1.3.2/cube.min.css">
<link rel="stylesheet" href="demo.css">
<link rel="stylesheet" href="iconfont.css">
<script src="iconfont.js"></script>
<!-- jQuery -->
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/7bfddb60-08e8-11e9-9b04-53e73bb6408b.js"></script>
<!-- 代码高亮 -->
<script src="https://a1.alicdn.com/oss/uploads/2018/12/26/a3f714d0-08e6-11e9-8a15-ebf944d7534c.js"></script>
</head>
<body>
<div class="main">
<h1 class="logo"><a href="https://www.iconfont.cn/" title="iconfont 首页" target="_blank">&#xe86b;</a></h1>
<div class="nav-tabs">
<ul id="tabs" class="dib-box">
<li class="dib active"><span>Unicode</span></li>
<li class="dib"><span>Font class</span></li>
<li class="dib"><span>Symbol</span></li>
</ul>
<a href="https://www.iconfont.cn/manage/index?manage_type=myprojects&projectId=1598621" target="_blank" class="nav-more">查看项目</a>
</div>
<div class="tab-container">
<div class="content unicode" style="display: block;">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont">&#xe62a;</span>
<div class="name">密码</div>
<div class="code-name">&amp;#xe62a;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe674;</span>
<div class="name">在线客服</div>
<div class="code-name">&amp;#xe674;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe637;</span>
<div class="name">客服</div>
<div class="code-name">&amp;#xe637;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe600;</span>
<div class="name">Back</div>
<div class="code-name">&amp;#xe600;</div>
</li>
<li class="dib">
<span class="icon iconfont">&#xe60d;</span>
<div class="name">账号</div>
<div class="code-name">&amp;#xe60d;</div>
</li>
</ul>
<div class="article markdown">
<h2 id="unicode-">Unicode 引用</h2>
<hr>
<p>Unicode 是字体在网页端最原始的应用方式,特点是:</p>
<ul>
<li>兼容性最好,支持 IE6+,及所有现代浏览器。</li>
<li>支持按字体的方式去动态调整图标大小,颜色等等。</li>
<li>但是因为是字体,所以不支持多色。只能使用平台里单色的图标,就算项目里有多色图标也会自动去色。</li>
</ul>
<blockquote>
<p>注意:新版 iconfont 支持多色图标,这些多色图标在 Unicode 模式下将不能使用,如果有需求建议使用symbol 的引用方式</p>
</blockquote>
<p>Unicode 使用步骤如下:</p>
<h3 id="-font-face">第一步:拷贝项目下面生成的 <code>@font-face</code></h3>
<pre><code class="language-css"
>@font-face {
font-family: 'iconfont';
src: url('iconfont.eot');
src: url('iconfont.eot?#iefix') format('embedded-opentype'),
url('iconfont.woff2') format('woff2'),
url('iconfont.woff') format('woff'),
url('iconfont.ttf') format('truetype'),
url('iconfont.svg#iconfont') format('svg');
}
</code></pre>
<h3 id="-iconfont-">第二步:定义使用 iconfont 的样式</h3>
<pre><code class="language-css"
>.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
</code></pre>
<h3 id="-">第三步:挑选相应图标并获取字体编码,应用于页面</h3>
<pre>
<code class="language-html"
>&lt;span class="iconfont"&gt;&amp;#x33;&lt;/span&gt;
</code></pre>
<blockquote>
<p>"iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。</p>
</blockquote>
</div>
</div>
<div class="content font-class">
<ul class="icon_lists dib-box">
<li class="dib">
<span class="icon iconfont icon-icon2"></span>
<div class="name">
密码
</div>
<div class="code-name">.icon-icon2
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-zaixiankefu"></span>
<div class="name">
在线客服
</div>
<div class="code-name">.icon-zaixiankefu
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-kefu"></span>
<div class="name">
客服
</div>
<div class="code-name">.icon-kefu
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-Back"></span>
<div class="name">
Back
</div>
<div class="code-name">.icon-Back
</div>
</li>
<li class="dib">
<span class="icon iconfont icon-zhanghao"></span>
<div class="name">
账号
</div>
<div class="code-name">.icon-zhanghao
</div>
</li>
</ul>
<div class="article markdown">
<h2 id="font-class-">font-class 引用</h2>
<hr>
<p>font-class 是 Unicode 使用方式的一种变种,主要是解决 Unicode 书写不直观,语意不明确的问题。</p>
<p>与 Unicode 使用方式相比,具有如下特点:</p>
<ul>
<li>兼容性良好,支持 IE8+,及所有现代浏览器。</li>
<li>相比于 Unicode 语意明确,书写更直观。可以很容易分辨这个 icon 是什么。</li>
<li>因为使用 class 来定义图标,所以当要替换图标时,只需要修改 class 里面的 Unicode 引用。</li>
<li>不过因为本质上还是使用的字体,所以多色图标还是不支持的。</li>
</ul>
<p>使用步骤如下:</p>
<h3 id="-fontclass-">第一步:引入项目下面生成的 fontclass 代码:</h3>
<pre><code class="language-html">&lt;link rel="stylesheet" href="./iconfont.css"&gt;
</code></pre>
<h3 id="-">第二步:挑选相应图标并获取类名,应用于页面:</h3>
<pre><code class="language-html">&lt;span class="iconfont icon-xxx"&gt;&lt;/span&gt;
</code></pre>
<blockquote>
<p>"
iconfont" 是你项目下的 font-family。可以通过编辑项目查看,默认是 "iconfont"。</p>
</blockquote>
</div>
</div>
<div class="content symbol">
<ul class="icon_lists dib-box">
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-icon2"></use>
</svg>
<div class="name">密码</div>
<div class="code-name">#icon-icon2</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-zaixiankefu"></use>
</svg>
<div class="name">在线客服</div>
<div class="code-name">#icon-zaixiankefu</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-kefu"></use>
</svg>
<div class="name">客服</div>
<div class="code-name">#icon-kefu</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-Back"></use>
</svg>
<div class="name">Back</div>
<div class="code-name">#icon-Back</div>
</li>
<li class="dib">
<svg class="icon svg-icon" aria-hidden="true">
<use xlink:href="#icon-zhanghao"></use>
</svg>
<div class="name">账号</div>
<div class="code-name">#icon-zhanghao</div>
</li>
</ul>
<div class="article markdown">
<h2 id="symbol-">Symbol 引用</h2>
<hr>
<p>这是一种全新的使用方式,应该说这才是未来的主流,也是平台目前推荐的用法。相关介绍可以参考这篇<a href="">文章</a>
这种用法其实是做了一个 SVG 的集合,与另外两种相比具有如下特点:</p>
<ul>
<li>支持多色图标了,不再受单色限制。</li>
<li>通过一些技巧,支持像字体那样,通过 <code>font-size</code>, <code>color</code> 来调整样式。</li>
<li>兼容性较差,支持 IE9+,及现代浏览器。</li>
<li>浏览器渲染 SVG 的性能一般,还不如 png。</li>
</ul>
<p>使用步骤如下:</p>
<h3 id="-symbol-">第一步:引入项目下面生成的 symbol 代码:</h3>
<pre><code class="language-html">&lt;script src="./iconfont.js"&gt;&lt;/script&gt;
</code></pre>
<h3 id="-css-">第二步:加入通用 CSS 代码(引入一次就行):</h3>
<pre><code class="language-html">&lt;style&gt;
.icon {
width: 1em;
height: 1em;
vertical-align: -0.15em;
fill: currentColor;
overflow: hidden;
}
&lt;/style&gt;
</code></pre>
<h3 id="-">第三步:挑选相应图标并获取类名,应用于页面:</h3>
<pre><code class="language-html">&lt;svg class="icon" aria-hidden="true"&gt;
&lt;use xlink:href="#icon-xxx"&gt;&lt;/use&gt;
&lt;/svg&gt;
</code></pre>
</div>
</div>
</div>
</div>
<script>
$(document).ready(function () {
$('.tab-container .content:first').show()
$('#tabs li').click(function (e) {
var tabContent = $('.tab-container .content')
var index = $(this).index()
if ($(this).hasClass('active')) {
return
} else {
$('#tabs li').removeClass('active')
$(this).addClass('active')
tabContent.hide().eq(index).fadeIn()
}
})
})
</script>
</body>
</html>
@font-face {font-family: "iconfont";
src: url('iconfont.eot?t=1578640873961'); /* IE9 */
src: url('iconfont.eot?t=1578640873961#iefix') format('embedded-opentype'), /* IE6-IE8 */
url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAWwAAsAAAAACqAAAAVkAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCDSAqIFIcJATYCJAMYCw4ABCAFhG0HVhs9CVFUTsJkPw7cWI5ZUshqHQ/5PoLn+W/Tzn0zUDJDRL2upCpCdh0qZiQ1MZI1rcqf7f+apoCSFKagUVYBjRT6vH85oPSAjJ0U85N7M2rdB0gI6WxMbdGxq8QrqqZC1br7An6BcaxIgZqeQqEmFAA7UjN2M2oz0u4RXudBNbo3HoYABnHkIA2vL+/QRAeOEkCGWwZBsxNGXxALNp914kIdmY8Fm/pR3QDmFT8vzygvNlBYNODE1gMvGuwf8nC85vwi3bMoX+8vCZjLQANyAB1k+mR6IpWqnYOG4f+FKk0B7NhQ6A956Psw62GVh+O/fIGOA/KCHf/lgY4FQUOBWOEeu+Q271jgIUkQ3ae+3DJFs3gQ4bQKD6JxOp7PQgFg3eMBcSAFAHIH4KUQ0OAHmQjqf7ifj68hqJEV4it6wNf52HXd7mcLDLBPk5KSjg8etOfCgwS1wdvI/3JpsraxpD+GbN0l6+6778Y6d3SzS2lpJ6+3wyQSyUmLbPHo9q5ThafdO507DBm4xOMuLixybQ8R01mb8EvtXQ+aLy0K23otbdluWXuv8oE7MWvuVtqWvsSTusUTucdE98WbCWeVnFq7NnxJpOfklpupB5eETFkPTtQjpReGdDJc2yNY4Uk1txWLFBZNGd4SnB7TdBdHy8qi/gfvOu+47hXer3NdRBE1vLGkq4HZLzm4/Y4HrpI8Y6/XXVphZETNu+CaIlwM7tBtgMd9ujBr4KDijacuZfTf7hw0Our1djK9pZ3uGO14spYTf1H5ux97qQmbV09X09SW1WpCz/4FBfrg8BaNF2QuSG8YPpg6jkjHlEGT1nyOM692v2rG/j6m/ug0aT16aAdpL9lO0poR5etJ505SH5G76mlPpwYFptNiPBGUo9xrNIjEZ49fo7gO+o38WOffIW6Jf/3Gjq8bTU+pO1T7NmvZzM/h4fYTZ87csNuvn+2dPuGe48iLzAaVWyyijX70w4ej+hFdYvUW+poPfxzR8u5Wanu2sF5uxF8V9zgP2hJrl78a+aziiBYT1KpVKhVPWLU6rVatDhirXg5u80mPJ91ze4SsVE+0FWV7JHhCngQXVSib96Tnkx65PVUj+EmIJ7i49QrtiVr5353eDVs1aNmgVcOR61u2jIpu2WIDC0RHJYllm3pAMKx7OhDpmtqu3OBYY2qlU/ZHNfua/t3zUc603+UuhZhNz522yZv3Yt9axZFj9ZoK5RyLxrn9Mijfe7Lo24xu8Nfu+InECo22QfOgESQNYdN9w8CKfFd+mZwpVnNCtUY/cStV6RJ1K05r1FdzThcvl6/KTS5vWKbGlMdKlJIp5fXLsINid4YDwJe19drYQqb2VnPKUh+1aUk0b5fKJ3pA0NTOV738a35omv/4Nb/UdvzL0hd0trP8Rilk64/ZSlykB3aIWHVv4eRQBLDyLb7wG3AZuAJ+wvkg/tiXN5TcBMHWsRpQlCEWNGykMHVuDlgwqQhWbNQBg2wKLjcJZiwVRPcBsviWgRDAblD4cRo0ArjJ1Lm/g4UwXoCVANHAoKME39AkTYa0G4RMMWvYt8AhSnw9jsXtcvIrG4lLYdteSD85jKwalssr2+/d2OdwH6eMS43t+FjHgaF46GCPY9cVHB2KzSpeDo7j+y4/f+DUJy37iQdtGhDCEDGMBtKXAmQICsFHz1aPaQ++/xXGIMGFCBcW9I0/YUIirO0j5cijLIF58/ikBc9l5jhSDLaMF00dZKCZKHggDpFkXGwLyOjp/WwYRcySwVs07+zIF8sNpHqWx/d6r/IWwADOsooSTXSxiFXKYLn352zNqWTlFvkO60k13aE+Kac51STfSFICAA==') format('woff2'),
url('iconfont.woff?t=1578640873961') format('woff'),
url('iconfont.ttf?t=1578640873961') format('truetype'), /* chrome, firefox, opera, Safari, Android, iOS 4.2+ */
url('iconfont.svg?t=1578640873961#iconfont') format('svg'); /* iOS 4.1- */
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-icon2:before {
content: "\e62a";
}
.icon-zaixiankefu:before {
content: "\e674";
}
.icon-kefu:before {
content: "\e637";
}
.icon-Back:before {
content: "\e600";
}
.icon-zhanghao:before {
content: "\e60d";
}
!function(d){var t,a='<svg><symbol id="icon-icon2" viewBox="0 0 1024 1024"><path d="M813.431725 388.476709l0-95.606475c0-153.48058-131.225717-277.965779-293.173133-277.965779-161.944346 0-293.170063 124.484176-293.170063 277.965779l0 94.015234c-71.494311 4.897543-127.878482 61.447491-127.878482 130.494053l0 359.726921c0 72.216765 61.756529 130.796952 137.960095 130.796952l552.494273 0c76.203566 0 137.960095-58.580187 137.960095-130.796952L927.624511 517.379521C927.624511 452.80276 878.305245 399.159002 813.431725 388.476709zM581.427744 723.688283l0 121.141034L477.951021 844.829318l0-119.194706c-34.651194-17.092283-58.304917-51.442626-58.304917-91.036389 0-56.644091 48.39522-102.558817 108.159372-102.558817 59.765175 0 108.162442 45.913703 108.162442 102.558817C635.967918 672.739914 614.016976 706.019879 581.427744 723.688283zM720.466404 386.535497 319.028499 386.535497l0-76.632331c0-105.090477 89.839121-190.349279 200.74607-190.349279 110.797455 0 200.690812 85.258803 200.690812 190.349279L720.46538 386.535497z" ></path></symbol><symbol id="icon-zaixiankefu" viewBox="0 0 1024 1024"><path d="M622.101607 333.394183c-86.513376 0-156.644643 70.131267-156.644643 156.644643s70.131267 156.644643 156.644643 156.644643c86.508259 0 156.641573-70.131267 156.641573-156.644643S708.61089 333.394183 622.101607 333.394183L622.101607 333.394183zM593.45415 503.129982c-31.840172 0-57.651025-25.815969-57.651025-57.656141 0-31.841196 25.810852-57.651025 57.651025-57.651025 31.841196 0 57.656141 25.809829 57.656141 57.651025C651.110291 477.314013 625.295346 503.129982 593.45415 503.129982L593.45415 503.129982zM593.45415 503.129982" ></path><path d="M327.267649 70.102614c0 9.682523 3.989871 19.306718 10.834766 26.152637 6.845918 6.850012 16.470113 10.834766 26.152637 10.834766 9.682523 0 19.306718-3.984754 26.152637-10.834766 6.844895-6.845918 10.834766-16.470113 10.834766-26.152637 0-9.682523-3.989871-19.302625-10.834766-26.152637-6.845918-6.845918-16.470113-10.829649-26.152637-10.829649-9.682523 0-19.306718 3.983731-26.152637 10.829649C331.257519 50.801013 327.267649 60.420091 327.267649 70.102614L327.267649 70.102614zM327.267649 70.102614" ></path><path d="M486.091934 70.102614c0 9.682523 3.983731 19.306718 10.829649 26.152637 6.844895 6.850012 16.470113 10.834766 26.152637 10.834766s19.306718-3.984754 26.152637-10.834766c6.844895-6.845918 10.834766-16.470113 10.834766-26.152637 0-9.682523-3.989871-19.302625-10.834766-26.152637-6.845918-6.845918-16.470113-10.829649-26.152637-10.829649s-19.307741 3.983731-26.152637 10.829649C490.075665 50.801013 486.091934 60.420091 486.091934 70.102614L486.091934 70.102614zM486.091934 70.102614" ></path><path d="M644.909056 70.102614c0 9.682523 3.984754 19.306718 10.829649 26.152637 6.845918 6.850012 16.470113 10.834766 26.152637 10.834766 9.682523 0 19.307741-3.984754 26.152637-10.834766 6.851035-6.845918 10.834766-16.470113 10.834766-26.152637 0-9.682523-3.983731-19.302625-10.834766-26.152637-6.844895-6.845918-16.470113-10.829649-26.152637-10.829649-9.682523 0-19.306718 3.983731-26.152637 10.829649C648.89381 50.801013 644.909056 60.420091 644.909056 70.102614L644.909056 70.102614zM644.909056 70.102614" ></path><path d="M573.58666 201.591321c-91.611487 0-176.626743 26.587542-246.816339 70.650083-8.921183-14.350846-20.205181-28.852118-33.003672-37.290301-22.679535-14.951527-70.63371-29.170366-100.644211-49.682539 21.079084-19.47761 34.285874-47.353494 34.285874-78.316693 0-58.875922-47.729048-106.605993-106.605993-106.605993-58.875922 0-106.600877 47.730071-106.600877 106.600877 0 58.877969 47.730071 106.605993 106.605993 106.605993 15.161305 0 29.579689-3.174296 42.641169-8.876158 10.044774 8.315386 30.762631 23.364127 58.525951 32.44597 38.086433 12.465916 57.645908 29.574572 62.49024 65.194838-90.214674 72.39789-147.084916 176.114067-147.084916 285.127899 0 240.907769 195.297988 436.205756 436.205756 436.205756 240.912885 0 436.210873-195.299011 436.210873-436.205756C1009.792417 382.16291 814.499545 201.591321 573.58666 201.591321L573.58666 201.591321zM526.555507 924.434354c-3.808746 7.070023-21.757536 8.701173-40.248679 7.070023-18.491143-1.63115-61.45977-10.878768-104.97075-41.333384-43.515073-30.458709-63.637366-59.82862-73.427336-79.955007-9.78997-20.122293-16.703427-56.221466 0-54.937217 7.070023 0.547469 12.279674 8.267291 21.211091 22.299889 21.576411 33.906228 49.808406 56.841589 61.772902 66.089208 11.967566 9.248641 53.715389 42.148959 91.606371 53.570079C528.141632 910.996297 530.364253 917.363308 526.555507 924.434354L526.555507 924.434354zM636.96513 846.112544c-153.199171 0-277.387611-124.193557-277.387611-277.392728 0-153.197124 124.18844-277.387611 277.387611-277.387611s277.392728 124.190487 277.392728 277.387611C914.357857 721.918987 790.164301 846.112544 636.96513 846.112544L636.96513 846.112544zM636.96513 846.112544" ></path></symbol><symbol id="icon-kefu" viewBox="0 0 1024 1024"><path d="M139.6 372.4v232.7H46.5V372.4h93.1m0-46.6H46.5c-25.6 0-46.5 21-46.5 46.6v232.7c0 25.6 20.9 46.5 46.5 46.5h93.1c25.6 0 46.5-20.9 46.5-46.5V372.4c0.1-25.6-20.9-46.6-46.5-46.6zM977.5 372.4v232.7h-93.1V372.4h93.1m0-46.6h-93.1c-25.6 0-46.5 20.9-46.5 46.5V605c0 25.6 20.9 46.5 46.5 46.5h93.1c25.6 0 46.5-20.9 46.5-46.5V372.4c0-25.6-20.9-46.6-46.5-46.6zM512 791.3c51.3 0 93.1 41.8 93.1 93.1s-41.8 93.1-93.1 93.1-93.1-41.8-93.1-93.1 41.8-93.1 93.1-93.1m0-46.6c-77.1 0-139.6 62.5-139.6 139.6S434.9 1024 512 1024s139.6-62.5 139.6-139.6S589.1 744.7 512 744.7zM418.9 325.8h-93.1v93.1h93.1v-93.1zM698.2 325.8h-93.1v93.1h93.1v-93.1zM672.3 558.5c-32.3 55.4-91.7 93.1-160.3 93.1s-128-37.7-160.3-93.1h-52.8c35.9 82.1 117.7 139.6 213.1 139.6s177.1-57.5 213.1-139.6h-52.8z" ></path><path d="M117.5 325.8C175.2 163.4 330 46.5 512 46.5s336.8 116.8 394.5 279.3h49.6C896.8 137 720.4 0 512 0S127.2 137 67.9 325.8h49.6zM886.7 651.6c-48.2 96.6-132.4 171.8-235.1 208.3v49.6C780.4 869 884.9 774 938.4 651.6h-51.7z" ></path></symbol><symbol id="icon-Back" viewBox="0 0 1024 1024"><path d="M211.7332791 556.4304248l444.3042524 444.36709648a62.84360061 62.84360061 0 0 0 88.92369367-88.86084959L345.02455497 512l399.9366702-399.93667169a62.84360061 62.84360061 0 0 0-88.86084959-88.86084959l-444.36709648 444.30425239a62.84360061 62.84360061 0 0 0 0 88.92369369z" ></path></symbol><symbol id="icon-zhanghao" viewBox="0 0 1024 1024"><path d="M391.136179 588.720987C191.832264 588.720987 42.993669 717.867879 42.993669 905.407919v20.203177C42.993669 1023.643389 202.487948 1023.643389 404.434473 1023.643389h214.477609c193.93345 0 361.440804 0 361.440805-98.032293v-20.203177c0-187.54004-148.838595-316.686931-348.142511-316.686932H391.136179z m109.796169-49.357128c157.874615 0 286.339543-120.87808 286.339543-269.546185 0-148.582859-128.464927-269.460939-286.339543-269.460939-157.78937 0-286.254297 120.87808-286.254297 269.460939 0 148.668104 128.464927 269.546185 286.254297 269.546185z" ></path></symbol></svg>',e=(t=document.getElementsByTagName("script"))[t.length-1].getAttribute("data-injectcss");if(e&&!d.__iconfont__svg__cssinject__){d.__iconfont__svg__cssinject__=!0;try{document.write("<style>.svgfont {display: inline-block;width: 1em;height: 1em;fill: currentColor;vertical-align: -0.1em;font-size:16px;}</style>")}catch(t){console&&console.log(t)}}!function(t){if(document.addEventListener)if(~["complete","loaded","interactive"].indexOf(document.readyState))setTimeout(t,0);else{var e=function(){document.removeEventListener("DOMContentLoaded",e,!1),t()};document.addEventListener("DOMContentLoaded",e,!1)}else document.attachEvent&&(n=t,o=d.document,i=!1,(a=function(){try{o.documentElement.doScroll("left")}catch(t){return void setTimeout(a,50)}c()})(),o.onreadystatechange=function(){"complete"==o.readyState&&(o.onreadystatechange=null,c())});function c(){i||(i=!0,n())}var n,o,i,a}(function(){var t,e,c,n,o,i;(t=document.createElement("div")).innerHTML=a,a=null,(e=t.getElementsByTagName("svg")[0])&&(e.setAttribute("aria-hidden","true"),e.style.position="absolute",e.style.width=0,e.style.height=0,e.style.overflow="hidden",c=e,(n=document.body).firstChild?(o=c,(i=n.firstChild).parentNode.insertBefore(o,i)):n.appendChild(c))})}(window);
\ No newline at end of file
{
"id": "1598621",
"name": "客服APP",
"font_family": "iconfont",
"css_prefix_text": "icon-",
"description": "",
"glyphs": [
{
"icon_id": "611345",
"name": "密码",
"font_class": "icon2",
"unicode": "e62a",
"unicode_decimal": 58922
},
{
"icon_id": "1212307",
"name": "在线客服",
"font_class": "zaixiankefu",
"unicode": "e674",
"unicode_decimal": 58996
},
{
"icon_id": "2325917",
"name": "客服",
"font_class": "kefu",
"unicode": "e637",
"unicode_decimal": 58935
},
{
"icon_id": "7977458",
"name": "Back",
"font_class": "Back",
"unicode": "e600",
"unicode_decimal": 58880
},
{
"icon_id": "10281814",
"name": "账号",
"font_class": "zhanghao",
"unicode": "e60d",
"unicode_decimal": 58893
}
]
}
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<!--
2013-9-30: Created.
-->
<svg>
<metadata>
Created by iconfont
</metadata>
<defs>
<font id="iconfont" horiz-adv-x="1024" >
<font-face
font-family="iconfont"
font-weight="500"
font-stretch="normal"
units-per-em="1024"
ascent="896"
descent="-128"
/>
<missing-glyph />
<glyph glyph-name="icon2" unicode="&#58922;" d="M813.431725 507.523291l0 95.606475c0 153.48058-131.225717 277.965779-293.173133 277.965779-161.944346 0-293.170063-124.484176-293.170063-277.965779l0-94.015234c-71.494311-4.897543-127.878482-61.447491-127.878482-130.494053l0-359.726921c0-72.216765 61.756529-130.796952 137.960095-130.796952l552.494273 0c76.203566 0 137.960095 58.580187 137.960095 130.796952L927.624511 378.620479C927.624511 443.19724 878.305245 496.840998 813.431725 507.523291zM581.427744 172.311717l0-121.141034L477.951021 51.170682l0 119.194706c-34.651194 17.092283-58.304917 51.442626-58.304917 91.036389 0 56.644091 48.39522 102.558817 108.159372 102.558817 59.765175 0 108.162442-45.913703 108.162442-102.558817C635.967918 223.260086 614.016976 189.980121 581.427744 172.311717zM720.466404 509.464503 319.028499 509.464503l0 76.632331c0 105.090477 89.839121 190.349279 200.74607 190.349279 110.797455 0 200.690812-85.258803 200.690812-190.349279L720.46538 509.464503z" horiz-adv-x="1024" />
<glyph glyph-name="zaixiankefu" unicode="&#58996;" d="M622.101607 562.605817c-86.513376 0-156.644643-70.131267-156.644643-156.644643s70.131267-156.644643 156.644643-156.644643c86.508259 0 156.641573 70.131267 156.641573 156.644643S708.61089 562.605817 622.101607 562.605817L622.101607 562.605817zM593.45415 392.870018c-31.840172 0-57.651025 25.815969-57.651025 57.656141 0 31.841196 25.810852 57.651025 57.651025 57.651025 31.841196 0 57.656141-25.809829 57.656141-57.651025C651.110291 418.685987 625.295346 392.870018 593.45415 392.870018L593.45415 392.870018zM593.45415 392.870018M327.267649 825.897386c0-9.682523 3.989871-19.306718 10.834766-26.152637 6.845918-6.850012 16.470113-10.834766 26.152637-10.834766 9.682523 0 19.306718 3.984754 26.152637 10.834766 6.844895 6.845918 10.834766 16.470113 10.834766 26.152637 0 9.682523-3.989871 19.302625-10.834766 26.152637-6.845918 6.845918-16.470113 10.829649-26.152637 10.829649-9.682523 0-19.306718-3.983731-26.152637-10.829649C331.257519 845.198987 327.267649 835.579909 327.267649 825.897386L327.267649 825.897386zM327.267649 825.897386M486.091934 825.897386c0-9.682523 3.983731-19.306718 10.829649-26.152637 6.844895-6.850012 16.470113-10.834766 26.152637-10.834766s19.306718 3.984754 26.152637 10.834766c6.844895 6.845918 10.834766 16.470113 10.834766 26.152637 0 9.682523-3.989871 19.302625-10.834766 26.152637-6.845918 6.845918-16.470113 10.829649-26.152637 10.829649s-19.307741-3.983731-26.152637-10.829649C490.075665 845.198987 486.091934 835.579909 486.091934 825.897386L486.091934 825.897386zM486.091934 825.897386M644.909056 825.897386c0-9.682523 3.984754-19.306718 10.829649-26.152637 6.845918-6.850012 16.470113-10.834766 26.152637-10.834766 9.682523 0 19.307741 3.984754 26.152637 10.834766 6.851035 6.845918 10.834766 16.470113 10.834766 26.152637 0 9.682523-3.983731 19.302625-10.834766 26.152637-6.844895 6.845918-16.470113 10.829649-26.152637 10.829649-9.682523 0-19.306718-3.983731-26.152637-10.829649C648.89381 845.198987 644.909056 835.579909 644.909056 825.897386L644.909056 825.897386zM644.909056 825.897386M573.58666 694.408679c-91.611487 0-176.626743-26.587542-246.816339-70.650083-8.921183 14.350846-20.205181 28.852118-33.003672 37.290301-22.679535 14.951527-70.63371 29.170366-100.644211 49.682539 21.079084 19.47761 34.285874 47.353494 34.285874 78.316693 0 58.875922-47.729048 106.605993-106.605993 106.605993-58.875922 0-106.600877-47.730071-106.600877-106.600877 0-58.877969 47.730071-106.605993 106.605993-106.605993 15.161305 0 29.579689 3.174296 42.641169 8.876158 10.044774-8.315386 30.762631-23.364127 58.525951-32.44597 38.086433-12.465916 57.645908-29.574572 62.49024-65.194838-90.214674-72.39789-147.084916-176.114067-147.084916-285.127899 0-240.907769 195.297988-436.205756 436.205756-436.205756 240.912885 0 436.210873 195.299011 436.210873 436.205756C1009.792417 513.83709 814.499545 694.408679 573.58666 694.408679L573.58666 694.408679zM526.555507-28.434354c-3.808746-7.070023-21.757536-8.701173-40.248679-7.070023-18.491143 1.63115-61.45977 10.878768-104.97075 41.333384-43.515073 30.458709-63.637366 59.82862-73.427336 79.955007-9.78997 20.122293-16.703427 56.221466 0 54.937217 7.070023-0.547469 12.279674-8.267291 21.211091-22.299889 21.576411-33.906228 49.808406-56.841589 61.772902-66.089208 11.967566-9.248641 53.715389-42.148959 91.606371-53.570079C528.141632-14.996297 530.364253-21.363308 526.555507-28.434354L526.555507-28.434354zM636.96513 49.887456c-153.199171 0-277.387611 124.193557-277.387611 277.392728 0 153.197124 124.18844 277.387611 277.387611 277.387611s277.392728-124.190487 277.392728-277.387611C914.357857 174.081013 790.164301 49.887456 636.96513 49.887456L636.96513 49.887456zM636.96513 49.887456" horiz-adv-x="1024" />
<glyph glyph-name="kefu" unicode="&#58935;" d="M139.6 523.6v-232.7H46.5V523.6h93.1m0 46.6H46.5c-25.6 0-46.5-21-46.5-46.6v-232.7c0-25.6 20.9-46.5 46.5-46.5h93.1c25.6 0 46.5 20.9 46.5 46.5V523.6c0.1 25.6-20.9 46.6-46.5 46.6zM977.5 523.6v-232.7h-93.1V523.6h93.1m0 46.6h-93.1c-25.6 0-46.5-20.9-46.5-46.5V291c0-25.6 20.9-46.5 46.5-46.5h93.1c25.6 0 46.5 20.9 46.5 46.5V523.6c0 25.6-20.9 46.6-46.5 46.6zM512 104.7c51.3 0 93.1-41.8 93.1-93.1s-41.8-93.1-93.1-93.1-93.1 41.8-93.1 93.1 41.8 93.1 93.1 93.1m0 46.6c-77.1 0-139.6-62.5-139.6-139.6S434.9-128 512-128s139.6 62.5 139.6 139.6S589.1 151.3 512 151.3zM418.9 570.2h-93.1v-93.1h93.1v93.1zM698.2 570.2h-93.1v-93.1h93.1v93.1zM672.3 337.5c-32.3-55.4-91.7-93.1-160.3-93.1s-128 37.7-160.3 93.1h-52.8c35.9-82.1 117.7-139.6 213.1-139.6s177.1 57.5 213.1 139.6h-52.8zM117.5 570.2C175.2 732.6 330 849.5 512 849.5s336.8-116.8 394.5-279.3h49.6C896.8 759 720.4 896 512 896S127.2 759 67.9 570.2h49.6zM886.7 244.4c-48.2-96.6-132.4-171.8-235.1-208.3v-49.6C780.4 27 884.9 122 938.4 244.4h-51.7z" horiz-adv-x="1024" />
<glyph glyph-name="Back" unicode="&#58880;" d="M211.7332791 339.56957520000003l444.3042524-444.36709648a62.84360061 62.84360061 0 0 1 88.92369367 88.86084959L345.02455497 384l399.9366702 399.93667169a62.84360061 62.84360061 0 0 1-88.86084959 88.86084959l-444.36709648-444.30425239a62.84360061 62.84360061 0 0 1 0-88.92369369z" horiz-adv-x="1024" />
<glyph glyph-name="zhanghao" unicode="&#58893;" d="M391.136179 307.279013C191.832264 307.279013 42.993669 178.132121 42.993669-9.407919v-20.203177C42.993669-127.643389 202.487948-127.643389 404.434473-127.643389h214.477609c193.93345 0 361.440804 0 361.440805 98.032293v20.203177c0 187.54004-148.838595 316.686931-348.142511 316.686932H391.136179z m109.796169 49.357128c157.874615 0 286.339543 120.87808 286.339543 269.546185 0 148.582859-128.464927 269.460939-286.339543 269.460939-157.78937 0-286.254297-120.87808-286.254297-269.460939 0-148.668104 128.464927-269.546185 286.254297-269.546185z" horiz-adv-x="1024" />
</font>
</defs></svg>
*.mode1v3
*.mode2v3
*.moved-aside
*.pbxuser
*.perspectivev3
**/*sync/
.sconsign.dblite
.tags*
**/.vagrant/
**/DerivedData/
Icon?
**/Pods/
**/.symlinks/
profile
xcuserdata
**/.generated/
Flutter/App.framework
Flutter/Flutter.framework
Flutter/Flutter.podspec
Flutter/Generated.xcconfig
Flutter/app.flx
Flutter/app.zip
Flutter/flutter_assets/
Flutter/flutter_export_environment.sh
ServiceDefinitions.json
Runner/GeneratedPluginRegistrant.*
# Exceptions to above rules.
!default.mode1v3
!default.mode2v3
!default.pbxuser
!default.perspectivev3
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>App</string>
<key>CFBundleIdentifier</key>
<string>io.flutter.flutter.app</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>App</string>
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>8.0</string>
</dict>
</plist>
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"
#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def parse_KV_file(file, separator='=')
file_abs_path = File.expand_path(file)
if !File.exists? file_abs_path
return [];
end
generated_key_values = {}
skip_line_start_symbols = ["#", "/"]
File.foreach(file_abs_path) do |line|
next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ }
plugin = line.split(pattern=separator)
if plugin.length == 2
podname = plugin[0].strip()
path = plugin[1].strip()
podpath = File.expand_path("#{path}", file_abs_path)
generated_key_values[podname] = podpath
else
puts "Invalid plugin specification: #{line}"
end
end
generated_key_values
end
target 'Runner' do
# Flutter Pod
copied_flutter_dir = File.join(__dir__, 'Flutter')
copied_framework_path = File.join(copied_flutter_dir, 'Flutter.framework')
copied_podspec_path = File.join(copied_flutter_dir, 'Flutter.podspec')
unless File.exist?(copied_framework_path) && File.exist?(copied_podspec_path)
# Copy Flutter.framework and Flutter.podspec to Flutter/ to have something to link against if the xcode backend script has not run yet.
# That script will copy the correct debug/profile/release version of the framework based on the currently selected Xcode configuration.
# CocoaPods will not embed the framework on pod install (before any build phases can generate) if the dylib does not exist.
generated_xcode_build_settings_path = File.join(copied_flutter_dir, 'Generated.xcconfig')
unless File.exist?(generated_xcode_build_settings_path)
raise "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
generated_xcode_build_settings = parse_KV_file(generated_xcode_build_settings_path)
cached_framework_dir = generated_xcode_build_settings['FLUTTER_FRAMEWORK_DIR'];
unless File.exist?(copied_framework_path)
FileUtils.cp_r(File.join(cached_framework_dir, 'Flutter.framework'), copied_flutter_dir)
end
unless File.exist?(copied_podspec_path)
FileUtils.cp(File.join(cached_framework_dir, 'Flutter.podspec'), copied_flutter_dir)
end
end
# Keep pod path relative so it can be checked into Podfile.lock.
pod 'Flutter', :path => 'Flutter'
# Plugin Pods
# Prepare symlinks folder. We use symlinks to avoid having Podfile.lock
# referring to absolute paths on developers' machines.
system('rm -rf .symlinks')
system('mkdir -p .symlinks/plugins')
plugin_pods = parse_KV_file('../.flutter-plugins')
plugin_pods.each do |name, path|
symlink = File.join('.symlinks', 'plugins', name)
File.symlink(path, symlink)
pod name, :path => File.join(symlink, 'ios')
end
end
# Prevent Cocoapods from embedding a second Flutter framework and causing an error with the new Xcode build system.
install! 'cocoapods', :disable_input_output_paths => true
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
config.build_settings['ENABLE_BITCODE'] = 'NO'
end
end
end
PODS:
- connectivity (0.0.1):
- Flutter
- Reachability
- connectivity_macos (0.0.1):
- Flutter
- file_picker (0.0.1):
- Flutter
- Flutter (1.0.0)
- flutter_local_notifications (0.0.1):
- Flutter
- flutter_mimc (0.0.1):
- Flutter
- flutter_plugin_android_lifecycle (0.0.1):
- Flutter
- fluttertoast (0.0.2):
- Flutter
- image_picker (0.0.1):
- Flutter
- path_provider (0.0.1):
- Flutter
- path_provider_macos (0.0.1):
- Flutter
- permission_handler (4.0.0):
- Flutter
- Reachability (3.2)
- shared_preferences (0.0.1):
- Flutter
- shared_preferences_macos (0.0.1):
- Flutter
- shared_preferences_web (0.0.1):
- Flutter
DEPENDENCIES:
- connectivity (from `.symlinks/plugins/connectivity/ios`)
- connectivity_macos (from `.symlinks/plugins/connectivity_macos/ios`)
- file_picker (from `.symlinks/plugins/file_picker/ios`)
- Flutter (from `Flutter`)
- flutter_local_notifications (from `.symlinks/plugins/flutter_local_notifications/ios`)
- flutter_mimc (from `.symlinks/plugins/flutter_mimc/ios`)
- flutter_plugin_android_lifecycle (from `.symlinks/plugins/flutter_plugin_android_lifecycle/ios`)
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
- image_picker (from `.symlinks/plugins/image_picker/ios`)
- path_provider (from `.symlinks/plugins/path_provider/ios`)
- path_provider_macos (from `.symlinks/plugins/path_provider_macos/ios`)
- permission_handler (from `.symlinks/plugins/permission_handler/ios`)
- shared_preferences (from `.symlinks/plugins/shared_preferences/ios`)
- shared_preferences_macos (from `.symlinks/plugins/shared_preferences_macos/ios`)
- shared_preferences_web (from `.symlinks/plugins/shared_preferences_web/ios`)
SPEC REPOS:
https://github.com/CocoaPods/Specs.git:
- Reachability
EXTERNAL SOURCES:
connectivity:
:path: ".symlinks/plugins/connectivity/ios"
connectivity_macos:
:path: ".symlinks/plugins/connectivity_macos/ios"
file_picker:
:path: ".symlinks/plugins/file_picker/ios"
Flutter:
:path: Flutter
flutter_local_notifications:
:path: ".symlinks/plugins/flutter_local_notifications/ios"
flutter_mimc:
:path: ".symlinks/plugins/flutter_mimc/ios"
flutter_plugin_android_lifecycle:
:path: ".symlinks/plugins/flutter_plugin_android_lifecycle/ios"
fluttertoast:
:path: ".symlinks/plugins/fluttertoast/ios"
image_picker:
:path: ".symlinks/plugins/image_picker/ios"
path_provider:
:path: ".symlinks/plugins/path_provider/ios"
path_provider_macos:
:path: ".symlinks/plugins/path_provider_macos/ios"
permission_handler:
:path: ".symlinks/plugins/permission_handler/ios"
shared_preferences:
:path: ".symlinks/plugins/shared_preferences/ios"
shared_preferences_macos:
:path: ".symlinks/plugins/shared_preferences_macos/ios"
shared_preferences_web:
:path: ".symlinks/plugins/shared_preferences_web/ios"
SPEC CHECKSUMS:
connectivity: 6e94255659cc86dcbef1d452ad3e0491bb1b3e75
connectivity_macos: e2e9731b6b22dda39eb1b128f6969d574460e191
file_picker: 408623be2125b79a4539cf703be3d4b3abe5e245
Flutter: 0e3d915762c693b495b44d77113d4970485de6ec
flutter_local_notifications: 9e4738ce2471c5af910d961a6b7eadcf57c50186
flutter_mimc: 55bda5d123df8355bcd86d12f2fb64bd71d2b40b
flutter_plugin_android_lifecycle: 47de533a02850f070f5696a623995e93eddcdb9b
fluttertoast: b644586ef3b16f67fae9a1f8754cef6b2d6b634b
image_picker: e3eacd46b94694dde7cf2705955cece853aa1a8f
path_provider: fb74bd0465e96b594bb3b5088ee4a4e7bb1f2a9d
path_provider_macos: f760a3c5b04357c380e2fddb6f9db6f3015897e0
permission_handler: 0fb88b8a3c23e31e5de45b8f1bb8b7de28cf8941
Reachability: 33e18b67625424e47b6cde6d202dce689ad7af96
shared_preferences: 430726339841afefe5142b9c1f50cb6bd7793e01
shared_preferences_macos: f3f29b71ccbb56bf40c9dd6396c9acf15e214087
shared_preferences_web: 141cce0c3ed1a1c5bf2a0e44f52d31eeb66e5ea9
PODFILE CHECKSUM: 3dbe063e9c90a5d7c9e4e76e70a821b9e2c1d271
COCOAPODS: 1.8.4
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 46;
objects = {
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; };
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; };
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; };
97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
C582A10A44FBB31889B8E8A3 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 8D031A86EAECCDF45063FF96 /* libPods-Runner.a */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
dstPath = "";
dstSubfolderSpec = 10;
files = (
3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */,
9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = "<group>"; };
50C20700CB977EB8F5CA74AB /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
6CCAA4C423EC548000AB28AA /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Main.strings"; sourceTree = "<group>"; };
6CCAA4C523EC548000AB28AA /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/LaunchScreen.strings"; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = "<group>"; };
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = "<group>"; };
80CFB14B53CD382F49BA0021 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
8D031A86EAECCDF45063FF96 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = "<group>"; };
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = "<group>"; };
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
B122118B84D99B952FD810A0 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
97C146EB1CF9000F007C117D /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */,
3B80C3941E831B6300D905FE /* App.framework in Frameworks */,
C582A10A44FBB31889B8E8A3 /* libPods-Runner.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
390B2B3FD8C40C4A95BEF20D /* Frameworks */ = {
isa = PBXGroup;
children = (
8D031A86EAECCDF45063FF96 /* libPods-Runner.a */,
);
name = Frameworks;
sourceTree = "<group>";
};
5F2CAB419F612C3E63E9578E /* Pods */ = {
isa = PBXGroup;
children = (
B122118B84D99B952FD810A0 /* Pods-Runner.debug.xcconfig */,
80CFB14B53CD382F49BA0021 /* Pods-Runner.release.xcconfig */,
50C20700CB977EB8F5CA74AB /* Pods-Runner.profile.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
};
9740EEB11CF90186004384FC /* Flutter */ = {
isa = PBXGroup;
children = (
3B80C3931E831B6300D905FE /* App.framework */,
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
9740EEBA1CF902C7004384FC /* Flutter.framework */,
9740EEB21CF90195004384FC /* Debug.xcconfig */,
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
9740EEB31CF90195004384FC /* Generated.xcconfig */,
);
name = Flutter;
sourceTree = "<group>";
};
97C146E51CF9000F007C117D = {
isa = PBXGroup;
children = (
9740EEB11CF90186004384FC /* Flutter */,
97C146F01CF9000F007C117D /* Runner */,
97C146EF1CF9000F007C117D /* Products */,
5F2CAB419F612C3E63E9578E /* Pods */,
390B2B3FD8C40C4A95BEF20D /* Frameworks */,
);
sourceTree = "<group>";
};
97C146EF1CF9000F007C117D /* Products */ = {
isa = PBXGroup;
children = (
97C146EE1CF9000F007C117D /* Runner.app */,
);
name = Products;
sourceTree = "<group>";
};
97C146F01CF9000F007C117D /* Runner */ = {
isa = PBXGroup;
children = (
7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */,
7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */,
97C146FA1CF9000F007C117D /* Main.storyboard */,
97C146FD1CF9000F007C117D /* Assets.xcassets */,
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
97C147021CF9000F007C117D /* Info.plist */,
97C146F11CF9000F007C117D /* Supporting Files */,
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
);
path = Runner;
sourceTree = "<group>";
};
97C146F11CF9000F007C117D /* Supporting Files */ = {
isa = PBXGroup;
children = (
97C146F21CF9000F007C117D /* main.m */,
);
name = "Supporting Files";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
97C146ED1CF9000F007C117D /* Runner */ = {
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
2F7C530D54C85DD073D284EC /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
061FEF3A66C9ECB64420444E /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
dependencies = (
);
name = Runner;
productName = Runner;
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
97C146E61CF9000F007C117D /* Project object */ = {
isa = PBXProject;
attributes = {
LastUpgradeCheck = 1020;
ORGANIZATIONNAME = "The Chromium Authors";
TargetAttributes = {
97C146ED1CF9000F007C117D = {
CreatedOnToolsVersion = 7.3.1;
DevelopmentTeam = X99DHX69K6;
};
};
};
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
compatibilityVersion = "Xcode 3.2";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
"zh-Hans",
);
mainGroup = 97C146E51CF9000F007C117D;
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
97C146ED1CF9000F007C117D /* Runner */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
97C146EC1CF9000F007C117D /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
061FEF3A66C9ECB64420444E /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
2F7C530D54C85DD073D284EC /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin";
};
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
97C146EA1CF9000F007C117D /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */,
97C146F31CF9000F007C117D /* main.m in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXVariantGroup section */
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C146FB1CF9000F007C117D /* Base */,
6CCAA4C423EC548000AB28AA /* zh-Hans */,
);
name = Main.storyboard;
sourceTree = "<group>";
};
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
isa = PBXVariantGroup;
children = (
97C147001CF9000F007C117D /* Base */,
6CCAA4C523EC548000AB28AA /* zh-Hans */,
);
name = LaunchScreen.storyboard;
sourceTree = "<group>";
};
/* End PBXVariantGroup section */
/* Begin XCBuildConfiguration section */
249021D3217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Profile;
};
249021D4217E4FDB00AE95B9 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CURRENT_PROJECT_VERSION = 4.5.2;
DEVELOPMENT_TEAM = X99DHX69K6;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
MARKETING_VERSION = 4.5.2;
PRODUCT_BUNDLE_IDENTIFIER = com.cmp520.WanChe;
PRODUCT_NAME = "$(TARGET_NAME)";
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
};
97C147031CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
};
name = Debug;
};
97C147041CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
CLANG_CXX_LIBRARY = "libc++";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
};
name = Release;
};
97C147061CF9000F007C117D /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CURRENT_PROJECT_VERSION = 4.5.2;
DEVELOPMENT_TEAM = X99DHX69K6;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
MARKETING_VERSION = 4.5.2;
PRODUCT_BUNDLE_IDENTIFIER = com.cmp520.WanChe;
PRODUCT_NAME = "$(TARGET_NAME)";
VERSIONING_SYSTEM = "apple-generic";
};
name = Debug;
};
97C147071CF9000F007C117D /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CURRENT_PROJECT_VERSION = 4.5.2;
DEVELOPMENT_TEAM = X99DHX69K6;
ENABLE_BITCODE = NO;
FRAMEWORK_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
INFOPLIST_FILE = Runner/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
LIBRARY_SEARCH_PATHS = (
"$(inherited)",
"$(PROJECT_DIR)/Flutter",
);
MARKETING_VERSION = 4.5.2;
PRODUCT_BUNDLE_IDENTIFIER = com.cmp520.WanChe;
PRODUCT_NAME = "$(TARGET_NAME)";
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147031CF9000F007C117D /* Debug */,
97C147041CF9000F007C117D /* Release */,
249021D3217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
isa = XCConfigurationList;
buildConfigurations = (
97C147061CF9000F007C117D /* Debug */,
97C147071CF9000F007C117D /* Release */,
249021D4217E4FDB00AE95B9 /* Profile */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 97C146E61CF9000F007C117D /* Project object */;
}
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
</Workspace>
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1020"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
BuildableName = "Runner.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "group:Runner.xcodeproj">
</FileRef>
<FileRef
location = "group:Pods/Pods.xcodeproj">
</FileRef>
</Workspace>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDEDidComputeMac32BitWarning</key>
<true/>
</dict>
</plist>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>BuildSystemType</key>
<string>Original</string>
<key>PreviewsEnabled</key>
<false/>
</dict>
</plist>
#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
@interface AppDelegate : FlutterAppDelegate
@end
#import "AppDelegate.h"
#import "GeneratedPluginRegistrant.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
if(![[NSUserDefaults standardUserDefaults]objectForKey:@"Notification"]){
[[UIApplication sharedApplication] cancelAllLocalNotifications];
[[NSUserDefaults standardUserDefaults]setBool:YES forKey:@"Notification"];
}
if(@available(iOS 10.0, *)) {
[UNUserNotificationCenter currentNotificationCenter].delegate = (id<UNUserNotificationCenterDelegate>) self;
}
// Override point for customization after application launch.
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
@end
{
"images" : [
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "20x20",
"idiom" : "iphone",
"filename" : "Icon-App-20x20@3x.png",
"scale" : "3x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "iphone",
"filename" : "Icon-App-29x29@3x.png",
"scale" : "3x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "iphone",
"filename" : "Icon-App-40x40@3x.png",
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-App-60x60@3x.png",
"scale" : "3x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@1x.png",
"scale" : "1x"
},
{
"size" : "20x20",
"idiom" : "ipad",
"filename" : "Icon-App-20x20@2x.png",
"scale" : "2x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@1x.png",
"scale" : "1x"
},
{
"size" : "29x29",
"idiom" : "ipad",
"filename" : "Icon-App-29x29@2x.png",
"scale" : "2x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@1x.png",
"scale" : "1x"
},
{
"size" : "40x40",
"idiom" : "ipad",
"filename" : "Icon-App-40x40@2x.png",
"scale" : "2x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@1x.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-App-76x76@2x.png",
"scale" : "2x"
},
{
"size" : "83.5x83.5",
"idiom" : "ipad",
"filename" : "Icon-App-83.5x83.5@2x.png",
"scale" : "2x"
},
{
"size" : "1024x1024",
"idiom" : "ios-marketing",
"filename" : "Icon-App-1024x1024@1x.png",
"scale" : "1x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
{
"images" : [
{
"idiom" : "universal",
"filename" : "LaunchImage.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "LaunchImage@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}
# Launch Screen Assets
You can customize the launch screen with your own desired assets by replacing the image files in this directory.
You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15505" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15509"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController id="01J-lp-oVM" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Ydg-fD-yQy"/>
<viewControllerLayoutGuide type="bottom" id="xbc-2k-c8Z"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4">
<rect key="frame" x="157" y="381.5" width="100" height="133.5"/>
</imageView>
</subviews>
<color key="backgroundColor" white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerX" secondItem="Ze5-6b-2t3" secondAttribute="centerX" id="1a2-6s-vTC"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="centerY" secondItem="Ze5-6b-2t3" secondAttribute="centerY" id="4X2-HB-R7a"/>
</constraints>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="76.811594202898561" y="251.11607142857142"/>
</scene>
</scenes>
<resources>
<image name="LaunchImage" width="100" height="133.5"/>
</resources>
</document>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
<scene sceneID="tne-QT-ifu">
<objects>
<viewController id="BYZ-38-t0r" customClass="FlutterViewController" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="y3c-jy-aDJ"/>
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
</scene>
</scenes>
</document>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>在线客服</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>kefu_workbench</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSAppleMusicUsageDescription</key>
<string>App需要您的同意,才能访问媒体资料库,为您播放能力.</string>
<key>NSCalendarsUsageDescription</key>
<string>App需要您的同意,才能访问您的日历,以便提供签到等服务.</string>
<key>NSCameraUsageDescription</key>
<string>使用扫码,拍照等功能需要您授权APP使用相机功能.</string>
<key>NSContactsUsageDescription</key>
<string>请允许APP访问您的通讯录.</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>App需要您的同意,才能始终访问位置,以便为您自动识别您当前的车牌归属地.</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>App需要您的同意,才能始终访问位置,以便为您自动识别您当前的车牌归属地.</string>
<key>NSMotionUsageDescription</key>
<string>App需要您的同意,才能访问运动与健身.</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>请允许APP保存图片到相册.</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>App需要您的同意,才能访问相册.</string>
<key>NSSpeechRecognitionUsageDescription</key>
<string>App需要您的同意,才能访问语音识别,以便使用客服.</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>
#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char* argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
No preview for this file type
No preview for this file type
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:kefu_workbench/resources/localizations.dart';
import 'package:kefu_workbench/routes.dart';
import 'package:provider/provider.dart';
import 'core_flutter.dart';
import 'provider/global.dart';
import 'provider/home.dart';
Widget createApp() {
return MultiProvider(
providers: [
ChangeNotifierProvider.value(value: GlobalProvide.getInstance()),
ChangeNotifierProvider.value(value: HomeProvide.getInstance()),
],
child: _MyApp()
);
}
class _MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<_MyApp> with WidgetsBindingObserver {
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
GlobalProvide.getInstance().setAppLifecycleState(state);
}
@override
initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
Widget build(BuildContext context) {
return Consumer<GlobalProvide>(builder: (context, globalState, ___){
return MaterialApp(
title: Configs.APP_NAME,
debugShowCheckedModeBanner: false,
theme: globalState.getCurrentTheme,
home: Builder(builder: (context) {
ToPx().init(context);
GlobalProvide.getInstance().setRooContext(context);
return Routers.buildPage("/home", arguments: {"data": "not arguments"});
}),
onGenerateRoute: (RouteSettings settings) {
// 是否是全屏modal
bool fullscreenDialog = false;
// 是否有动画
bool isAnimate = true;
// 判断是否是全屏Modal
if (settings.arguments != null) {
var isModal = (settings.arguments as Map)['modal'] ?? false;
isAnimate = (settings.arguments as Map)['isAnimate'] ?? true;
fullscreenDialog = (isModal == 'true' ? true : isModal) ?? false;
}
if (isAnimate) {
return CupertinoPageRoute<Object>(
fullscreenDialog: fullscreenDialog,
builder: (BuildContext context) {
return Routers.buildPage(settings.name,
arguments: settings.arguments);
});
} else {
return PageRouteBuilder(
transitionDuration: Duration(milliseconds: 0),
pageBuilder: (BuildContext context, __, _) {
return Routers.buildPage(settings.name,
arguments: settings.arguments);
});
}
},
localizationsDelegates: [
ChineseCupertinoLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
supportedLocales: <Locale>[
const Locale('zh', 'CH'),
const Locale('en', 'US')
],
);
});
}
}
import 'package:kefu_workbench/provider/global.dart';
class Configs {
/// 开发模式
static const bool DEBUG = true;
/// APP名称
static const APP_NAME = "客服系统";
/// api host
static String get HOST => GlobalProvide.getInstance().prefs.getString("host") + '/api';
}
library core_flutter;
export 'package:flutter/material.dart';
export 'package:flutter/cupertino.dart' hide RefreshCallback;
export 'package:flutter/widgets.dart';
export 'dart:async';
export 'dart:io';
export 'package:flutter/foundation.dart';
export 'configs.dart';
export 'page_context.dart';
export 'widgets/index.dart';
export 'utils//index.dart';
export 'models/index.dart';
export 'provider/themes.dart';
export 'services/services.dart';
// ignore_for_file: non_constant_identifier_names
// ignore_for_file: camel_case_types
// ignore_for_file: prefer_single_quotes
// This file is automatically generated. DO NOT EDIT, all your changes would be lost.
class JSONField {
final String name;
const JSONField(this.name);
}
import 'package:flutter/services.dart';
import 'app.dart';
import 'core_flutter.dart';
import 'provider/global.dart';
void main() async{
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle(statusBarColor: Color.fromRGBO(0, 0, 0, 0.0)));
SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown]);
await GlobalProvide.getInstance().init();
return runApp(createApp());
}
class AdminModel {
int id;
String avatar;
String username;
String nickname;
String password;
String phone;
String token;
String autoReply;
int online;
int root;
int currentConUser;
int lastActivity;
int updateAt;
int createAt;
AdminModel(
{this.id,
this.avatar,
this.username,
this.nickname,
this.password,
this.phone,
this.token,
this.autoReply,
this.online,
this.root,
this.currentConUser,
this.lastActivity,
this.updateAt,
this.createAt});
AdminModel.fromJson(Map<String, dynamic> json) {
id = json['id'];
avatar = json['avatar'];
username = json['username'];
nickname = json['nickname'];
password = json['password'];
phone = json['phone'];
token = json['token'];
autoReply = json['auto_reply'];
online = json['online'];
root = json['root'];
currentConUser = json['current_con_user'];
lastActivity = json['last_activity'];
updateAt = json['update_at'];
createAt = json['create_at'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['avatar'] = this.avatar;
data['username'] = this.username;
data['nickname'] = this.nickname;
data['password'] = this.password;
data['phone'] = this.phone;
data['token'] = this.token;
data['auto_reply'] = this.autoReply;
data['online'] = this.online;
data['root'] = this.root;
data['current_con_user'] = this.currentConUser;
data['last_activity'] = this.lastActivity;
data['update_at'] = this.updateAt;
data['create_at'] = this.createAt;
return data;
}
}
\ No newline at end of file
class CompanyModel {
int id;
String title;
String logo;
String service;
String email;
String tel;
String address;
int updateAt;
CompanyModel(
{this.id,
this.title,
this.logo,
this.service,
this.email,
this.tel,
this.address,
this.updateAt});
CompanyModel.fromJson(Map<String, dynamic> json) {
id = json['id'];
title = json['title'];
logo = json['logo'];
service = json['service'];
email = json['email'];
tel = json['tel'];
address = json['address'];
updateAt = json['update_at'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['title'] = this.title;
data['logo'] = this.logo;
data['service'] = this.service;
data['email'] = this.email;
data['tel'] = this.tel;
data['address'] = this.address;
data['update_at'] = this.updateAt;
return data;
}
}
\ No newline at end of file
class ContactModel {
int id;
int cid;
int fromAccount;
int toAccount;
String lastMessage;
int isSessionEnd;
String lastMessageType;
int uid;
String avatar;
String address;
String nickname;
String phone;
int platform;
int online;
int read;
int updateAt;
String remarks;
int lastActivity;
int createAt;
int contactCreateAt;
ContactModel(
{this.id,
this.cid,
this.fromAccount,
this.toAccount,
this.lastMessage,
this.isSessionEnd,
this.lastMessageType,
this.uid,
this.avatar,
this.address,
this.nickname,
this.phone,
this.platform,
this.online,
this.read,
this.updateAt,
this.remarks,
this.lastActivity,
this.createAt,
this.contactCreateAt});
ContactModel.fromJson(Map<String, dynamic> json) {
id = json['id'];
cid = json['cid'];
fromAccount = json['from_account'];
toAccount = json['to_account'];
lastMessage = json['last_message'];
isSessionEnd = json['is_session_end'];
lastMessageType = json['last_message_type'];
uid = json['uid'];
avatar = json['avatar'];
address = json['address'];
nickname = json['nickname'];
phone = json['phone'];
platform = json['platform'];
online = json['online'];
read = json['read'];
updateAt = json['update_at'];
remarks = json['remarks'];
lastActivity = json['last_activity'];
createAt = json['create_at'];
contactCreateAt = json['contact_create_at'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['cid'] = this.cid;
data['from_account'] = this.fromAccount;
data['to_account'] = this.toAccount;
data['last_message'] = this.lastMessage;
data['is_session_end'] = this.isSessionEnd;
data['last_message_type'] = this.lastMessageType;
data['uid'] = this.uid;
data['avatar'] = this.avatar;
data['address'] = this.address;
data['nickname'] = this.nickname;
data['phone'] = this.phone;
data['platform'] = this.platform;
data['online'] = this.online;
data['read'] = this.read;
data['update_at'] = this.updateAt;
data['remarks'] = this.remarks;
data['last_activity'] = this.lastActivity;
data['create_at'] = this.createAt;
data['contact_create_at'] = this.contactCreateAt;
return data;
}
}
\ No newline at end of file
class FlowModel {
String count;
String platform;
String title;
FlowModel({this.count, this.platform, this.title});
FlowModel.fromJson(Map<String, dynamic> json) {
count = json['count'];
platform = json['platform'];
title = json['title'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['count'] = this.count;
data['platform'] = this.platform;
data['title'] = this.title;
return data;
}
}
\ No newline at end of file
class ImConfigs {
int uploadMode;
String uploadSecret;
String uploadHost;
int openWorkorder;
ImConfigs(
{this.uploadMode,
this.uploadSecret,
this.uploadHost,
this.openWorkorder});
ImConfigs.fromJson(Map<String, dynamic> json) {
uploadMode = json['upload_mode'];
uploadSecret = json['upload_secret'];
uploadHost = json['upload_host'];
openWorkorder = json['open_workorder'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['upload_mode'] = this.uploadMode;
data['upload_secret'] = this.uploadSecret;
data['upload_host'] = this.uploadHost;
data['open_workorder'] = this.openWorkorder;
return data;
}
}
import 'dart:convert';
class ImMessageModel {
String bizType;
String version;
dynamic payload;
int fromAccount;
int toAccount;
int timestamp;
int read;
int key;
int transferAccount;
bool isShowCancel = false;
int uploadProgress;
String avatar;
String nickname;
bool isShowDate = false;
ImMessageModel(
{this.bizType,
this.key,
this.isShowDate = false,
this.uploadProgress,
this.version,
this.avatar,
this.nickname,
this.isShowCancel = false,
this.payload,
this.fromAccount,
this.toAccount,
this.timestamp,
this.read,
this.transferAccount});
ImMessageModel.fromJson(Map<String, dynamic> json) {
this.bizType = json['biz_type'];
this.version = json['version'];
this.isShowCancel = json['is_show_cancel'] ?? false;
this.payload = json['payload'];
this.key = json['key'];
this.fromAccount = json['from_account'];
this.toAccount = json['to_account'];
this.timestamp = json['timestamp'];
this.avatar = json['avatar'];
this.read = json['read'];
this.uploadProgress = json['upload_progress'] ?? 0;
this.nickname = json['nickname'];
this.transferAccount = json['transfer_account'];
}
String toBase64() {
return base64Encode(utf8.encode(json.encode(toJson())));
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['biz_type'] = this.bizType;
data['version'] = this.version;
data['key'] = this.key;
data['upload_progress'] = this.uploadProgress;
data['is_show_cancel'] = this.isShowCancel;
data['payload'] = this.payload;
data['from_account'] = this.fromAccount;
data['to_account'] = this.toAccount;
data['timestamp'] = this.timestamp;
data['avatar'] = this.avatar;
data['read'] = this.read;
data['nickname'] = this.nickname;
data['transfer_account'] = this.transferAccount;
return data;
}
}
class ImTokenInfoModel {
String appId;
String appPackage;
String appAccount;
String miUserId;
String miUserSecurityKey;
String token;
String feDomainName;
String relayDomainName;
int miChid;
int regionBucket;
ImTokenInfoModel(
{this.appId,
this.appPackage,
this.appAccount,
this.miUserId,
this.miUserSecurityKey,
this.token,
this.feDomainName,
this.relayDomainName,
this.miChid,
this.regionBucket});
ImTokenInfoModel.fromJson(Map<String, dynamic> json) {
this.appId = json['appId'];
this.appPackage = json['appPackage'];
this.appAccount = json['appAccount'];
this.miUserId = json['miUserId'];
this.miUserSecurityKey = json['miUserSecurityKey'];
this.token = json['token'];
this.feDomainName = json['feDomainName'];
this.relayDomainName = json['relayDomainName'];
this.miChid = json['miChid'];
this.regionBucket = json['regionBucket'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['appId'] = this.appId;
data['appPackage'] = this.appPackage;
data['appAccount'] = this.appAccount;
data['miUserId'] = this.miUserId;
data['miUserSecurityKey'] = this.miUserSecurityKey;
data['token'] = this.token;
data['feDomainName'] = this.feDomainName;
data['relayDomainName'] = this.relayDomainName;
data['miChid'] = this.miChid;
data['regionBucket'] = this.regionBucket;
return data;
}
}
export 'im_message_model.dart';
export 'im_token_info_model.dart';
export 'user_model.dart';
export 'knowledge_model.dart';
export 'robot_model.dart';
export 'admin_model.dart';
export 'im_configs.dart';
export 'contact_model.dart';
export 'shortcut_model.dart';
export 'platform_model.dart';
export 'system_info_model.dart';
export 'uploads_config_model.dart';
export 'company_model.dart';
export 'services_statistical_model.dart';
export 'qiniu_model.dart';
export 'flow_model.dart';
export 'service_count_model.dart';
export 'statistical_count_model.dart';
\ No newline at end of file
class KnowledgeModel {
String title;
String subTitle;
String content;
int id;
int uid;
int platform;
int updateAt;
int createAt;
KnowledgeModel(
{this.title,
this.subTitle,
this.content,
this.id,
this.uid,
this.platform,
this.updateAt,
this.createAt});
KnowledgeModel.fromJson(Map<String, dynamic> json) {
this.title = json['title'];
this.subTitle = json['sub_title'];
this.content = json['content'];
this.id = json['id'];
this.uid = json['uid'];
this.platform = json['platform'];
this.updateAt = json['update_at'];
this.createAt = json['create_at'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['title'] = this.title;
data['sub_title'] = this.subTitle;
data['content'] = this.content;
data['id'] = this.id;
data['uid'] = this.uid;
data['platform'] = this.platform;
data['update_at'] = this.updateAt;
data['create_at'] = this.createAt;
return data;
}
}
class PlatformModel {
int id;
String title;
String alias;
int system;
PlatformModel({this.id, this.title, this.alias, this.system});
PlatformModel.fromJson(Map<String, dynamic> json) {
id = json['id'];
title = json['title'];
alias = json['alias'];
system = json['system'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['title'] = this.title;
data['alias'] = this.alias;
data['system'] = this.system;
return data;
}
}
\ No newline at end of file
class QiniuModel {
int id;
String bucket;
String accessKey;
String secretKey;
String host;
int updateAt;
QiniuModel(
{this.id,
this.bucket,
this.accessKey,
this.secretKey,
this.host,
this.updateAt});
QiniuModel.fromJson(Map<String, dynamic> json) {
id = json['id'];
bucket = json['bucket'];
accessKey = json['access_key'];
secretKey = json['secret_key'];
host = json['host'];
updateAt = json['update_at'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['bucket'] = this.bucket;
data['access_key'] = this.accessKey;
data['secret_key'] = this.secretKey;
data['host'] = this.host;
data['update_at'] = this.updateAt;
return data;
}
}
\ No newline at end of file
class RobotModel {
int id;
String nickname;
String avatar;
String welcome;
String understand;
String artificial;
String keyword;
String timeoutText;
String noServices;
String loogTimeWaitText;
int isRun;
int system;
int platform;
int updateAt;
int createAt;
RobotModel(
{this.id,
this.nickname,
this.avatar,
this.welcome,
this.understand,
this.artificial,
this.keyword,
this.timeoutText,
this.noServices,
this.loogTimeWaitText,
this.isRun,
this.system,
this.platform,
this.updateAt,
this.createAt});
RobotModel.fromJson(Map<String, dynamic> json) {
id = json['id'];
nickname = json['nickname'];
avatar = json['avatar'];
welcome = json['welcome'];
understand = json['understand'];
artificial = json['artificial'];
keyword = json['keyword'];
timeoutText = json['timeout_text'];
noServices = json['no_services'];
loogTimeWaitText = json['loog_time_wait_text'];
isRun = json['switch'];
system = json['system'];
platform = json['platform'];
updateAt = json['update_at'];
createAt = json['create_at'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['nickname'] = this.nickname;
data['avatar'] = this.avatar;
data['welcome'] = this.welcome;
data['understand'] = this.understand;
data['artificial'] = this.artificial;
data['keyword'] = this.keyword;
data['timeout_text'] = this.timeoutText;
data['no_services'] = this.noServices;
data['loog_time_wait_text'] = this.loogTimeWaitText;
data['switch'] = this.isRun;
data['system'] = this.system;
data['platform'] = this.platform;
data['update_at'] = this.updateAt;
data['create_at'] = this.createAt;
return data;
}
}
class ServicesCountModel {
String count;
String id;
String nickname;
String username;
ServicesCountModel({this.count, this.id, this.nickname, this.username});
ServicesCountModel.fromJson(Map<String, dynamic> json) {
count = json['count'];
id = json['id'];
nickname = json['nickname'];
username = json['username'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['count'] = this.count;
data['id'] = this.id;
data['nickname'] = this.nickname;
data['username'] = this.username;
return data;
}
}
\ No newline at end of file
class ServicesStatisticalModel {
String createAt;
String id;
String isReception;
String nickname;
String platform;
String serviceAccount;
String transferAccount;
String userAccount;
ServicesStatisticalModel(
{this.createAt,
this.id,
this.isReception,
this.nickname,
this.platform,
this.serviceAccount,
this.transferAccount,
this.userAccount});
ServicesStatisticalModel.fromJson(Map<String, dynamic> json) {
createAt = json['create_at'];
id = json['id'];
isReception = json['is_reception'];
nickname = json['nickname'];
platform = json['platform'];
serviceAccount = json['service_account'];
transferAccount = json['transfer_account'];
userAccount = json['user_account'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['create_at'] = this.createAt;
data['id'] = this.id;
data['is_reception'] = this.isReception;
data['nickname'] = this.nickname;
data['platform'] = this.platform;
data['service_account'] = this.serviceAccount;
data['transfer_account'] = this.transferAccount;
data['user_account'] = this.userAccount;
return data;
}
}
\ No newline at end of file
class ShortcutModel {
int id;
int uid;
String title;
String content;
int updateAt;
int createAt;
ShortcutModel(
{this.id,
this.uid,
this.title,
this.content,
this.updateAt,
this.createAt});
ShortcutModel.fromJson(Map<String, dynamic> json) {
id = json['id'];
uid = json['uid'];
title = json['title'];
content = json['content'];
updateAt = json['update_at'];
createAt = json['create_at'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['uid'] = this.uid;
data['title'] = this.title;
data['content'] = this.content;
data['update_at'] = this.updateAt;
data['create_at'] = this.createAt;
return data;
}
}
\ No newline at end of file
class StatisticalCountModel {
String date;
List<StatisticalItems> statisticalItems;
StatisticalCountModel({this.date, this.statisticalItems});
StatisticalCountModel.fromJson(Map<String, dynamic> json) {
date = json['date'];
if (json['list'] != null) {
statisticalItems = new List<StatisticalItems>();
json['list'].forEach((v) {
statisticalItems.add(new StatisticalItems.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['date'] = this.date;
if (this.statisticalItems != null) {
data['list'] =
this.statisticalItems.map((v) => v.toJson()).toList();
}
return data;
}
}
class StatisticalItems {
String count;
String id;
String title;
StatisticalItems({this.count, this.id, this.title});
StatisticalItems.fromJson(Map<String, dynamic> json) {
count = json['count'];
id = json['id'];
title = json['title'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['count'] = this.count;
data['id'] = this.id;
data['title'] = this.title;
return data;
}
}
\ No newline at end of file
class SystemInfoModel {
int id;
String title;
String logo;
String copyRight;
int uploadMode;
int updateAt;
SystemInfoModel(
{this.id,
this.title,
this.logo,
this.copyRight,
this.uploadMode,
this.updateAt});
SystemInfoModel.fromJson(Map<String, dynamic> json) {
id = json['id'];
title = json['title'];
logo = json['logo'];
copyRight = json['copy_right'];
uploadMode = json['upload_mode'];
updateAt = json['update_at'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['title'] = this.title;
data['logo'] = this.logo;
data['copy_right'] = this.copyRight;
data['upload_mode'] = this.uploadMode;
data['update_at'] = this.updateAt;
return data;
}
}
\ No newline at end of file
class UploadsConfigModel {
int id;
String name;
UploadsConfigModel({this.id, this.name});
UploadsConfigModel.fromJson(Map<String, dynamic> json) {
id = json['id'];
name = json['name'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['name'] = this.name;
return data;
}
}
\ No newline at end of file
class UserModel {
String avatar;
String address;
String nickname;
String token;
String phone;
String remarks;
int id;
int uid;
int platform;
int online;
int updateAt;
int lastActivity;
int createAt;
UserModel(
{this.avatar,
this.address,
this.nickname,
this.token,
this.phone,
this.remarks,
this.id,
this.uid,
this.platform,
this.online,
this.updateAt,
this.lastActivity,
this.createAt});
UserModel.fromJson(Map<String, dynamic> json) {
this.avatar = json['avatar'];
this.address = json['address'];
this.nickname = json['nickname'];
this.token = json['token'];
this.phone = json['phone'];
this.remarks = json['remarks'];
this.id = json['id'];
this.uid = json['uid'];
this.platform = json['platform'];
this.online = json['online'];
this.updateAt = json['update_at'];
this.lastActivity = json['last_activity'];
this.createAt = json['create_at'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['avatar'] = this.avatar;
data['address'] = this.address;
data['nickname'] = this.nickname;
data['token'] = this.token;
data['phone'] = this.phone;
data['remarks'] = this.remarks;
data['id'] = this.id;
data['uid'] = this.uid;
data['platform'] = this.platform;
data['online'] = this.online;
data['update_at'] = this.updateAt;
data['last_activity'] = this.lastActivity;
data['create_at'] = this.createAt;
return data;
}
}
class WorkOrderModel {
int id;
int uid;
int tid;
String title;
String content;
String phone;
String email;
int status;
int lastReply;
int cid;
int closeAt;
String remark;
int updateAt;
int delete;
int createAt;
String uNickname;
String uAvatar;
String aNickname;
WorkOrderModel(
{this.id,
this.uid,
this.tid,
this.title,
this.content,
this.phone,
this.email,
this.status,
this.lastReply,
this.cid,
this.closeAt,
this.remark,
this.updateAt,
this.delete,
this.createAt,
this.uNickname,
this.uAvatar,
this.aNickname});
WorkOrderModel.fromJson(Map<String, dynamic> json) {
id = json['id'];
uid = json['uid'];
tid = json['tid'];
title = json['title'];
content = json['content'];
phone = json['phone'];
email = json['email'];
status = json['status'];
lastReply = json['last_reply'];
cid = json['cid'];
closeAt = json['close_at'];
remark = json['remark'];
updateAt = json['update_at'];
delete = json['delete'];
createAt = json['create_at'];
uNickname = json['u_nickname'];
uAvatar = json['u_avatar'];
aNickname = json['a_nickname'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['uid'] = this.uid;
data['tid'] = this.tid;
data['title'] = this.title;
data['content'] = this.content;
data['phone'] = this.phone;
data['email'] = this.email;
data['status'] = this.status;
data['last_reply'] = this.lastReply;
data['cid'] = this.cid;
data['close_at'] = this.closeAt;
data['remark'] = this.remark;
data['update_at'] = this.updateAt;
data['delete'] = this.delete;
data['create_at'] = this.createAt;
data['u_nickname'] = this.uNickname;
data['u_avatar'] = this.uAvatar;
data['a_nickname'] = this.aNickname;
return data;
}
}
\ No newline at end of file
class WorkOrderCommentModel {
int id;
int uid;
int aid;
int wid;
String content;
String uAvatar;
String uNickname;
String aAvatar;
String aNickname;
int createAt;
WorkOrderCommentModel(
{this.id,
this.uid,
this.aid,
this.wid,
this.content,
this.uAvatar,
this.uNickname,
this.aAvatar,
this.aNickname,
this.createAt});
WorkOrderCommentModel.fromJson(Map<String, dynamic> json) {
id = json['id'];
uid = json['uid'];
aid = json['aid'];
wid = json['wid'];
content = json['content'];
uAvatar = json['u_avatar'];
uNickname = json['u_nickname'];
aAvatar = json['a_avatar'];
aNickname = json['a_nickname'];
createAt = json['create_at'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['uid'] = this.uid;
data['aid'] = this.aid;
data['wid'] = this.wid;
data['content'] = this.content;
data['u_avatar'] = this.uAvatar;
data['u_nickname'] = this.uNickname;
data['a_avatar'] = this.aAvatar;
data['a_nickname'] = this.aNickname;
data['create_at'] = this.createAt;
return data;
}
}
class WorkOrderCountsModel {
int status0;
int status1;
int status2;
int status3;
int deleteCount;
WorkOrderCountsModel(
{this.status0,
this.status1,
this.status2,
this.status3,
this.deleteCount});
WorkOrderCountsModel.fromJson(Map<String, dynamic> json) {
status0 = json['status0'];
status1 = json['status1'];
status2 = json['status2'];
status3 = json['status3'];
deleteCount = json['delete_count'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['status0'] = this.status0;
data['status1'] = this.status1;
data['status2'] = this.status2;
data['status3'] = this.status3;
data['delete_count'] = this.deleteCount;
return data;
}
}
\ No newline at end of file
class WorkOrderTypeModel {
int id;
String title;
int createAt;
int count;
WorkOrderTypeModel({this.id, this.title, this.createAt, this.count});
WorkOrderTypeModel.fromJson(Map<String, dynamic> json) {
id = json['id'];
title = json['title'];
count = json['count'];
createAt = json['create_at'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['title'] = this.title;
data['count'] = this.count;
data['create_at'] = this.createAt;
return data;
}
}
import 'core_flutter.dart';
typedef Build = Widget Function(BuildContext context);
class PageContext extends StatelessWidget {
PageContext({
@required this.builder,
this.onClickRefresh,
this.onClickPage
});
final Build builder;
final VoidCallback onClickRefresh;
final VoidCallback onClickPage;
@override
Widget build(BuildContext context) {
ThemeData _themeData = Theme.of(context);
return GestureDetector(
onTap: (){
FocusScope.of(context).requestFocus(FocusNode());
if(onClickPage != null) onClickPage();
},
child: Theme(
data: _themeData.copyWith(
// 重写字体大写
textTheme: TextTheme(
title:_themeData.textTheme.title.copyWith(fontSize: ToPx.size(30),),
body1: _themeData.textTheme.body1.copyWith(fontSize: ToPx.size(28),),
body2: _themeData.textTheme.body2.copyWith(fontSize: ToPx.size(28),),
caption: _themeData.textTheme.caption.copyWith(fontSize: ToPx.size(26),),
display1: _themeData.textTheme.display1.copyWith(fontSize: ToPx.size(34),),
)
),
child: Builder(builder: (context) => builder(context))
),
);
}
}
import 'package:dio/dio.dart';
import '../core_flutter.dart';
class AdminProvide with ChangeNotifier {
AdminService adminService = AdminService.getInstance();
static AdminProvide instance;
// 单例
static AdminProvide getInstance() {
if (instance != null) {
return instance;
}
instance = AdminProvide();
return instance;
}
AdminProvide(){
scrollController = ScrollController();
searchTextEditingController = TextEditingController();
getAdmins();
// 监听滚动
scrollController?.addListener(() => _onScrollViewControllerAddListener());
}
int pageOn = 0;
int pageSize = 25;
bool isLoadEnd = false;
bool isLoading = false;
String keyword = "";
ScrollController scrollController;
TextEditingController searchTextEditingController;
List<AdminModel> admins = [];
int usersTotal = 0;
// 监听滚动条
void _onScrollViewControllerAddListener() async{
try {
ScrollPosition position = scrollController.position;
if (position.pixels + 10.0 > position.maxScrollExtent &&
!isLoadEnd && !isLoading) {
// 判断网络
if (!await checkNetWork()) {
UX.showToast('您的网络异常,请检查您的网络!', position: ToastPosition.top);
return;
}
getAdmins();
}
}catch(e){
printf(e);
}
}
/// 获取列表数据
Future<void> getAdmins() async{
if(isLoadEnd) return;
pageOn = pageOn +1;
isLoading = true;
notifyListeners();
Response response = await adminService.getAdmins(pageOn: pageOn, pageSize: pageSize, keyword: keyword);
isLoading = false;
notifyListeners();
if (response.data["code"] == 200) {
List<AdminModel> _admins= (response.data["data"]['list'] as List).map((i) => AdminModel.fromJson(i)).toList();
usersTotal = response.data["data"]['total'];
if(_admins.length < pageSize){
isLoadEnd = true;
}
if(pageOn > 1){
admins.addAll(_admins);
}else{
admins = _admins;
}
notifyListeners();
} else {
UX.showToast("${response.data["message"]}");
}
}
/// 更新单个数据
Future<void> getUser(int id) async{
Response response = await adminService.getItem(id: id);
if (response.data["code"] == 200) {
AdminModel _admin = AdminModel.fromJson(response.data["data"]);
int index = admins.indexWhere((k) => k.id == id);
if(index != null){
admins[index] = _admin;
notifyListeners();
}
}
}
/// 找出一个
AdminModel getItem(int id){
return admins.firstWhere((k) => k.id == id);
}
/// 删除单个数据
void deleteItem(int id){
usersTotal--;
admins.removeWhere((i) => i.id == id);
}
// search
void onSearch() async{
pageOn = 0;
keyword = searchTextEditingController.value.text.trim();
isLoadEnd = false;
notifyListeners();
await getAdmins();
}
// onRefresh
Future<bool> onRefresh() async{
pageOn = 0;
keyword = "";
isLoadEnd = false;
searchTextEditingController.clear();
notifyListeners();
await getAdmins();
UX.showToast("刷新成功", position: ToastPosition.top);
return true;
}
/// add
void goAdd(BuildContext context) async{
Navigator.pushNamed(context, "/admin_add").then((isSuccess){
if(isSuccess == true){
pageOn = 0;
isLoadEnd = false;
notifyListeners();
getAdmins();
}
});
}
@override
void dispose() {
instance = null;
scrollController?.dispose();
searchTextEditingController?.dispose();
super.dispose();
}
}
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:flutter/services.dart';
import 'package:flutter_mimc/flutter_mimc.dart';
import 'package:kefu_workbench/provider/global.dart';
import '../core_flutter.dart';
import '../models/index.dart';
class ChatProvide with ChangeNotifier {
static ChatProvide instance;
StreamSubscription _listener;
// 单例
static ChatProvide getInstance({bool isReadOnly = false, int serviceId, int accountId, int userId}) {
if (instance != null) {
return instance;
}
instance = ChatProvide(isReadOnly: isReadOnly, serviceId: serviceId, accountId: accountId, userId: userId);
return instance;
}
ChatProvide({bool isReadOnly = false, int serviceId, int accountId, int userId}){
this.isReadOnly = isReadOnly;
this.serviceId = serviceId;
this.accountId = accountId;
scrollController = ScrollController();
GlobalProvide globalState =GlobalProvide.getInstance();
if(isReadOnly){
globalState.getMessageRecord(serviceId: serviceId, accountId: accountId, isFirstLoad: true);
}else{
focusNode = FocusNode();
focusNode.addListener((){
if(focusNode.hasFocus){
onHideEmoJiPanel();
onToggleShortcutPanel(false);
onToggleTransferPanel(false);
toScrollEnd();
}
});
GlobalProvide globalProvide =GlobalProvide.getInstance();
globalProvide.chatProvideIsDispose = false;
if(globalProvide.shortcuts.length == 0){
globalProvide.getShortcuts();
}
_listener = globalProvide.flutterMImc.addEventListenerHandleMessage().listen((MIMCMessage msg) async{
ImMessageModel message = ImMessageModel.fromJson(json.decode(utf8.decode(base64Decode(msg.payload))));
if(!["pong", "contacts", "cancel"].contains(message.bizType)){
toScrollEnd();
}
});
/// 获取业务数据
if(userId != null && userId != 0){
}
}
scrollController.addListener(() => _onScrollViewControllerAddListener());
}
// 监听滚动条
void _onScrollViewControllerAddListener() async {
try {
await Future.delayed(Duration(milliseconds: 300));
GlobalProvide globalState = GlobalProvide.getInstance();
ScrollPosition position = scrollController?.position;
// 判断是否到底部
if (position.pixels + 15.0 > position.maxScrollExtent &&
!globalState.isLoadRecordEnd &&
!globalState.isLoadingMorRecord) {
globalState.setIsLoadingMorRecord(true);
await Future.delayed(Duration(milliseconds: 1000));
await globalState.getMessageRecord(serviceId: serviceId, accountId: accountId);
globalState.setIsLoadingMorRecord(false);
}
} catch (e) {
debugPrint(e);
}
}
/// 选中客服
void onSelectedSeviceUser(AdminModel admin){
GlobalProvide globalState = GlobalProvide.getInstance();
UX.alert(globalState.rooContext,
content: "将该客户转接给 " + (admin.nickname ?? admin.username)+' ?',
confirmText: "转接",
cancelText: "取消",
onConfirm: () async{
Response response = await globalState.messageService.transformerUser(userAccount: globalState.currentContact.fromAccount, toAccount: admin.id);
if (response.data["code"] == 200) {
onToggleTransferPanel(false);
UX.showToast("转接成功");
globalState.getContacts();
} else {
UX.showToast(response.data['message']);
}
}
);
}
/// 选中快捷语
void onSelectedShortcut(String shortcut){
editingController.text = shortcut;
editingController.selection = TextSelection.collapsed(offset: shortcut.length);
}
// 是否是紧查看记录
bool isReadOnly = false;
// 客服ID账号
int serviceId;
// 用户ID账号
int accountId;
/// 是否显示表情面板
bool isShowEmoJiPanel = false;
/// 是否显示快捷语面板
bool isShowShortcutPanel = false;
/// 是否显示转接面板
bool isShowTransferPanel = false;
/// 可转接的客服
List<AdminModel> get serviceOnlineUsers{
GlobalProvide globalState = GlobalProvide.getInstance();
return globalState.serviceOnlineUsers.where((admin) => admin.id != globalState.serviceUser.id && admin.online != 0).toList();
}
/// 输入键盘相关
FocusNode focusNode;
TextEditingController editingController = TextEditingController();
/// 滚动条控制器
ScrollController scrollController;
/// 是否显示loading
bool get isChatFullLoading => GlobalProvide.getInstance().isChatFullLoading;
/// 发送消息
void onSubmit(){
GlobalProvide globalState = GlobalProvide.getInstance();
var text = editingController.value.text.trim();
if(text.isEmpty) return;
if(globalState.currentContact == null || globalState.currentContact.isSessionEnd == 1){
UX.showToast("当前会话已结束!");
return;
}
if(isShowShortcutPanel){
isShowShortcutPanel = false;
}
GlobalProvide.getInstance().sendTextMessage(text);
editingController.clear();
notifyListeners();
toScrollEnd();
}
/// 输入框改动
void onInputChanged(String value){
GlobalProvide globalState = GlobalProvide.getInstance();
globalState.sendPongMessage("");
}
/// 选择图片
void onPickImage(BuildContext context) async{
GlobalProvide globalState = GlobalProvide.getInstance();
if(globalState.currentContact == null || globalState.currentContact.isSessionEnd == 1){
UX.showToast("当前会话已结束!");
return;
}
File _file = await uploadImage<File>(context);
if (_file == null) return;
globalState.sendPhotoMessage(_file);
toScrollEnd();
}
/// 显示或隐藏转接面板
void onToggleTransferPanel(bool isShow) async{
GlobalProvide globalState = GlobalProvide.getInstance();
if(globalState.currentContact == null || globalState.currentContact.isSessionEnd == 1){
if(isShow)UX.showToast("当前会话已结束!");
return;
}
if(isShow){
onHideEmoJiPanel();
onToggleShortcutPanel(false);
globalState.getOnlineAdmins();
FocusScope.of(globalState.rooContext).requestFocus(FocusNode());
await Future.delayed(Duration(milliseconds: 50));
}
isShowTransferPanel = isShow;
notifyListeners();
}
/// 滚动条至底部
void toScrollEnd() async {
scrollController?.jumpTo(0);
}
/// 显示或隐藏快捷语
void onToggleShortcutPanel(bool isShow) async{
GlobalProvide globalState = GlobalProvide.getInstance();
if(globalState.currentContact == null || globalState.currentContact.isSessionEnd == 1) {
if(isShow)UX.showToast("当前会话已结束!");
return;
}
if(isShow){
onHideEmoJiPanel();
onToggleTransferPanel(false);
FocusScope.of(globalState.rooContext).requestFocus(FocusNode());
await Future.delayed(Duration(milliseconds: 50));
}
isShowShortcutPanel = isShow;
notifyListeners();
}
/// 撤会消息
void onCancelMessage(ImMessageModel msg){
GlobalProvide globalState = GlobalProvide.getInstance();
globalState.sendCancelMessage(msg);
globalState.deleteMessage(msg.toAccount, msg.key);
}
/// 结束会话
void onShowEndMessageAlert(BuildContext context){
GlobalProvide globalState = GlobalProvide.getInstance();
UX.alert(context,
content: "您确定结束当前会话吗?\r\n强制结束可能会被客户投诉!",
confirmText: "结束",
cancelText: "取消",
onConfirm: () => globalState.sendEndMessage()
);
}
/// 操作消息
void onOperation(BuildContext context, ImMessageModel message){
GlobalProvide globalState = GlobalProvide.getInstance();
bool isLocalImage = message.payload != null && !message.payload.contains(RegExp(r'^(http://|https://)'));
bool isPhoto = message.bizType == "photo";
Widget _delete() {
return CupertinoDialogAction(
child: const Text('删除'),
onPressed: () {
if(message.fromAccount != globalState.serviceUser.id && message.fromAccount != globalState.robot.id){
globalState.deleteMessage(message.fromAccount, message.key);
}else{
globalState.deleteMessage(message.toAccount, message.key);
}
globalState.messageService.removeMeessge(
toAccount: message.toAccount,
fromAccount: message.fromAccount,
key: message.key,
);
Navigator.pop(context);
},
);
}
Widget _cancel() {
return CupertinoDialogAction(
child: const Text('撤回'),
onPressed: () {
onCancelMessage(message);
Navigator.pop(context);
},
);
}
Widget _close() {
return CupertinoDialogAction(
child: const Text('取消'),
isDestructiveAction: true,
onPressed: () {
Navigator.pop(context);
},
);
}
Widget _copy() {
return CupertinoDialogAction(
child: Text(isPhoto ? "复制图片链接" : '复制'),
onPressed: () {
Clipboard.setData(ClipboardData(text: message.payload));
Navigator.pop(context);
UX.showToast("消息已复制到粘贴板");
},
);
}
List<Widget> actions = [];
if (message.isShowCancel) actions.add(_cancel());
if(!isReadOnly) actions.add(_delete());
if (message.bizType == "text") actions.add(_copy());
if (isPhoto && !isLocalImage) {
actions.add(_copy());
}
actions.add(_close());
showCupertinoDialog(
context: context,
builder: (_) {
return CupertinoAlertDialog(
title: Text(
'消息操作',
style: TextStyle(fontSize: ToPx.size(32)),
),
content: isPhoto
? SizedBox(
width: ToPx.size(200),
height: ToPx.size(200),
child: CachedNetworkImage(
width: ToPx.size(200),
height: ToPx.size(200),
bgColor: Colors.transparent,
fit: BoxFit.contain,
src: "${message.payload}"),
)
: Text(
message.bizType == "knowledge" ? "相关问题列表..." : message.payload,
maxLines: 8,
overflow: TextOverflow.ellipsis,
style: TextStyle(height: 1.5),
),
actions: actions);
});
}
/// 隐藏表情背面板
void onHideEmoJiPanel(){
isShowEmoJiPanel = false;
notifyListeners();
}
/// 显示表情背面板
void onShowEmoJiPanel() async{
onToggleTransferPanel(false);
GlobalProvide globalState = GlobalProvide.getInstance();
if(globalState.currentContact == null || globalState.currentContact.isSessionEnd == 1){
UX.showToast("当前会话已结束!");
return;
}
isShowShortcutPanel = false;
notifyListeners();
FocusScope.of(GlobalProvide.getInstance().rooContext).requestFocus(FocusNode());
await Future.delayed(Duration(milliseconds: 50));
isShowEmoJiPanel = true;
notifyListeners();
}
@override
void dispose() {
GlobalProvide globalProvide =GlobalProvide.getInstance();
globalProvide.chatProvideIsDispose = true;
globalProvide.updateCurrentServiceUser(0);
focusNode?.dispose();
editingController?.dispose();
scrollController?.dispose();
_listener?.cancel();
instance = null;
printf("销毁了ChatProvide");
super.dispose();
}
}
import 'package:dio/dio.dart';
import 'package:kefu_workbench/provider/global.dart';
import '../core_flutter.dart';
class ChatReCordProvide with ChangeNotifier {
AdminService adminService = AdminService.getInstance();
PublicService publicService = PublicService.getInstance();
static ChatReCordProvide instance;
// 单例
static ChatReCordProvide getInstance() {
if (instance != null) {
return instance;
}
instance = ChatReCordProvide();
return instance;
}
ChatReCordProvide(){
scrollController = ScrollController();
searchTextEditingController = TextEditingController();
getAdmins();
// 监听滚动
scrollController?.addListener(() => _onScrollViewControllerAddListener());
DateTime _dateTime = DateTime.now();
date = "${_dateTime.year}-${_dateTime.month < 10 ? '0'+_dateTime.month.toString() : _dateTime.month}-${_dateTime.day < 10 ? '0'+_dateTime.day.toString() : _dateTime.day}";
}
int pageOn = 0;
int pageSize = 25;
int total = 0;
bool isLoadEnd = false;
bool isLoading = false;
String date;
bool isDeWeighting = false;
bool isReception = false;
ScrollController scrollController;
TextEditingController searchTextEditingController;
List<AdminModel> admins = [AdminModel(id: 0, nickname: "全部")];
List<ServicesStatisticalModel> servicesStatisticals = [];
AdminModel selectedAdmin = AdminModel(id: 0, nickname: "全部");
String getAdminNickName(int id){
return admins.firstWhere((i)=>i.id == id).nickname;
}
// 监听滚动条
void _onScrollViewControllerAddListener() async{
try {
ScrollPosition position = scrollController.position;
if (position.pixels + 10.0 > position.maxScrollExtent &&
!isLoadEnd && !isLoading) {
// 判断网络
if (!await checkNetWork()) {
UX.showToast('您的网络异常,请检查您的网络!', position: ToastPosition.top);
return;
}
getAdmins();
}
}catch(e){
printf(e);
}
}
// 选择客服
void onSelected(AdminModel admin){
if(selectedAdmin != null && admin.id == selectedAdmin.id) return;
selectedAdmin = admin;
admins = [];
notifyListeners();
_reload();
}
/// 获取客服列表数据
Future<void> getAdmins() async{
Response response = await adminService.getAdmins(pageOn: 1, pageSize: 1000, keyword: "");
if (response.data["code"] == 200) {
var _admins= (response.data["data"]['list'] as List).map((i){
AdminModel _admin = AdminModel.fromJson(i);
return _admin;
}).toList();
admins = [];
admins.add(AdminModel(id: 0, nickname: "全部"));
admins.addAll(_admins);
getServicesStatistical();
notifyListeners();
} else {
UX.showToast("${response.data["message"]}");
}
}
/// 获取服务记录
Future<void> getServicesStatistical() async{
if(isLoadEnd) return;
pageOn = pageOn +1;
isLoading = true;
notifyListeners();
Response response = await publicService.getServicesStatistical(pageOn: pageOn, pageSize: pageSize, cid: selectedAdmin?.id, date: date, isDeWeighting: isDeWeighting,isReception: isReception);
isLoading = false;
notifyListeners();
if (response.statusCode == 200) {
total = response.data['data']['total'];
List<ServicesStatisticalModel> _servicesStatisticals= (response.data['data']['list'] as List).map((i) => ServicesStatisticalModel.fromJson(i)).toList();
if(_servicesStatisticals.length < pageSize){
isLoadEnd = true;
}
if(pageOn > 1){
servicesStatisticals.addAll(_servicesStatisticals);
}else{
servicesStatisticals = _servicesStatisticals;
}
notifyListeners();
} else {
UX.showToast("${response.data["message"]}");
}
}
// 选择注册日期
void onSelectDate(BuildContext context) async{
var _date = await UX.selectDatePicker(context, oldDate: date);
if (_date == null || _date == date) return;
date = _date;
pageOn = 0;
isLoadEnd = false;
notifyListeners();
getServicesStatistical();
}
// 设置是否去重
void setDeWeighting(bool isDeWeighting){
this.isDeWeighting = isDeWeighting;
notifyListeners();
_reload();
}
// 仅显示未接待
void setReception(bool isReception){
this.isReception = isReception;
notifyListeners();
_reload();
}
// onRefresh
Future<bool> onRefresh() async{
await _reload();
UX.showToast("刷新成功", position: ToastPosition.top);
return true;
}
// reload
Future<void> _reload() async{
pageOn = 0;
isLoadEnd = false;
searchTextEditingController.clear();
notifyListeners();
await getAdmins();
}
@override
void dispose() {
instance = null;
scrollController?.dispose();
searchTextEditingController?.dispose();
super.dispose();
}
}
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:flutter_mimc/flutter_mimc.dart';
import 'package:kefu_workbench/models/work_order_counts_model.dart';
import 'package:kefu_workbench/models/work_order_type.dart';
import 'package:kefu_workbench/services/api.dart';
import 'package:kefu_workbench/services/workorder_service.dart';
import '../core_flutter.dart';
import 'package:shared_preferences/shared_preferences.dart';
/// 创建消息辅助对象
/// [sendMessage] 发送对象
/// [imMessage] 本地显示对象
class MessageHandle {
MessageHandle({this.sendMessage, this.localMessage});
MIMCMessage sendMessage;
ImMessageModel localMessage;
MessageHandle clone() {
return MessageHandle(
sendMessage: MIMCMessage.fromJson(sendMessage.toJson()),
localMessage: ImMessageModel.fromJson(localMessage.toJson()),
);
}
}
/// GlobalProvide
class GlobalProvide with ChangeNotifier {
/// root context
BuildContext rooContext;
/// 当前路由路径
String currentRoutePath = "home";
/// ChatProvide是否已销毁
bool chatProvideIsDispose = true;
/// 当前主题
ThemeProvide get themeProvide => ThemeProvide.getInstance();
ThemeData get getCurrentTheme => themeProvide.getCurrentTheme();
/// MessageService
MessageService messageService = MessageService.getInstance();
/// AdminService
AdminService adminService = AdminService.getInstance();
/// PublicService
PublicService publicService = PublicService.getInstance();
/// ContactService
ContactService contactService = ContactService.getInstance();
/// ShortcutService
ShortcutService shortcutService = ShortcutService.getInstance();
/// PlatformService
PlatformService platformService = PlatformService.getInstance();
/// GlobalProvide实例
static GlobalProvide instance;
/// 聊天列表数据
List<ContactModel> contacts = [];
/// 快捷语
List<ShortcutModel> shortcuts = [];
/// app运行状态
AppLifecycleState appLifecycleState;
/// 客服信息
AdminModel serviceUser;
/// 当前服务谁
ContactModel currentContact;
/// IM 签名对象
ImTokenInfoModel imTokenInfo;
/// 缓存对象
SharedPreferences prefs;
/// 机器人对象
RobotModel robot;
/// 上传配置对象
ImConfigs imConfigs;
/// IM 插件对象
FlutterMIMC flutterMImc;
/// 聊天记录
Map<dynamic, List<ImMessageModel>> messagesRecords = {};
/// 当前聊天用户的消息记录
List<ImMessageModel> currentUserMessagesRecords(int accountId){
return messagesRecords[accountId ?? currentContact.fromAccount] ?? [];
}
/// 是否显示loading
bool isChatFullLoading = false;
// 是否是获取历史表数据
bool isGetHistoryMessage = false;
/// 平台数据
List<PlatformModel> platforms = [];
/// 工单统计
WorkOrderCountsModel workOrderCounts;
int get newWorkHandleCounts{
try{
return workOrderCounts.status0 + workOrderCounts.status1;
}catch(_){
return 0;
}
}
/// 工单类型
List<WorkOrderTypeModel> workOrderTypes= [];
/// 显示对方输入中...
bool isPong = false;
String advanceText = "";
/// 当前用户ID
int toAccount;
/// 设置app运行状态
/// AppLifecycleState.paused 暂停
/// AppLifecycleState.inactive 不活跃
/// AppLifecycleState.resumed 已恢复
void setAppLifecycleState(AppLifecycleState state){
this.appLifecycleState = state;
}
/// 设置当前路由路径
void setCurrentRoutePath(String path){
this.currentRoutePath = path;
}
/// set rooContext
void setRooContext(BuildContext context){
rooContext = context;
}
// 单列 获取对象
static GlobalProvide getInstance() {
if (instance == null) {
instance = GlobalProvide();
}
return instance;
}
/// 初始化
/// return bool
Future<void> init() async {
await _prefsInstance();
await getMe();
if(isLogin){
await _getOnlineRobot();
await getConfigs();
await _registerImAccount();
await _flutterMImcInstance();
await _addMimcEvent();
await getContacts(isFullLoading: true);
_upImLastActivity();
getShortcuts();
getPlatforms();
getWorkOrderCounts();
getWorkorderTypes();
}
}
/// is login
bool get isLogin{
String authorization = prefs.getString("Authorization");
String serviceUserString = prefs.getString("serviceUser");
if(serviceUserString != null){
serviceUser = AdminModel.fromJson(json.decode(serviceUserString));
}
if(authorization != null && serviceUser != null){
return true;
}else{
printf("未登录~");
return false;
}
}
/// APP应用级别退出登录
/// 重置一些默认初始化
void applicationLogout() async{
await AuthService.getInstance().logout();
prefs.remove("serviceUser");
prefs.remove("Authorization");
setServiceUser(null);
flutterMImc?.logout();
}
/// 获取个人信息
Future<void> getMe() async {
try{
Response response = await adminService.getMe();
if (response.data["code"] == 200) {
serviceUser = AdminModel.fromJson(response.data['data']);
setServiceUser(serviceUser);
if(serviceUser.online != 0){
flutterMImc?.login();
}
} else {
applicationLogout();
}
}catch(_){
applicationLogout();
}
}
/// 可转接的客服
List<AdminModel> serviceOnlineUsers = [];
/// 获取客服
void getOnlineAdmins() async{
Response response = await adminService.getAdmins(pageOn: 1, pageSize: 1000, online: 3);
if (response.data["code"] == 200) {
serviceOnlineUsers = (response.data['data']['list'] as List).map((i) => AdminModel.fromJson(i)).toList();
notifyListeners();
} else {
UX.showToast(response.data['message']);
}
}
/// 获取快捷语
Future<void> getShortcuts() async {
Response response = await shortcutService.getShortcuts();
if (response.data["code"] == 200) {
shortcuts = (response.data['data'] as List).map((i) => ShortcutModel.fromJson(i)).toList();
notifyListeners();
} else {
UX.showToast(response.data['message']);
}
}
/// 设置serviceUser
void setServiceUser(AdminModel user){
serviceUser = user;
notifyListeners();
if(user == null) return;
prefs.setString("serviceUser", jsonEncode(user.toJson()));
}
/// 实例化 FlutterMImc
Future<void> _flutterMImcInstance() async {
if(imTokenInfo == null) return;
try{
String tokenString = '{"code": 200, "message": "success", "data": ${jsonEncode(imTokenInfo?.toJson())}}';
flutterMImc = await FlutterMIMC.stringTokenInit(tokenString, debug: true);
if(serviceUser != null && serviceUser.online != 0){
flutterMImc.login();
}
} catch(_) {
Navigator.pushNamedAndRemoveUntil(rooContext, "/login", ModalRoute.withName('/'), arguments: {"isAnimate": false});
}
}
/// 注册IM账号
Future<void> _registerImAccount() async {
try {
if(serviceUser == null) return;
Response response = await publicService.registerImAccount(accountId: serviceUser.id);
if (response.data["code"] == 200) {
imTokenInfo =ImTokenInfoModel.fromJson(response.data["data"]["token"]["data"]);
} else {
// 1秒重
await Future.delayed(Duration(milliseconds: 1000));
_registerImAccount();
}
} catch (e) {
debugPrint(e);
}
}
/// 获取一个在线机器人
Future<void> _getOnlineRobot() async {
try {
Response response = await RobotService.getInstance().getOnlineRobot();
if (response.data["code"] == 200) {
robot = RobotModel.fromJson(response.data["data"]);
} else {
// 1秒重
await Future.delayed(Duration(milliseconds: 1000));
_registerImAccount();
}
} catch (e) {
debugPrint(e);
}
}
/// 实例化 SharedPreferences
Future<void> _prefsInstance() async {
if(prefs != null) return;
prefs = await SharedPreferences.getInstance();
}
/// 设置当前服务谁
setCurrentContact(ContactModel contact){
if(!chatProvideIsDispose) return;
currentContact = contact;
toAccount = currentContact.fromAccount;
Navigator.pushNamed(rooContext, "/chat", arguments: {
"userId": currentContact.uid
}).then((_){
getContacts();
});
isLoadRecordEnd = false;
notifyListeners();
getMessageRecord(isFirstLoad: true);
getContacts();
updateCurrentServiceUser(currentContact.fromAccount);
}
/// 更新当前服务谁
void updateCurrentServiceUser(int account){
adminService.updateCurrentServiceUser(accountId: account);
}
/// 更新客服上线状态
Future<void> updateUserOnlineStatus({int online}) async{
Response response = await adminService.updateUserOnlineStatus(status: online);
if (response.data["code"] == 200) {
getMe();
if(online == 0){
UX.showToast("当前状态为离线");
flutterMImc.logout();
updateCurrentServiceUser(0);
}else if(online == 1){
UX.showToast("当前状态为在线");
flutterMImc.login();
}else{
UX.showToast("当前状态为离开");
if(!await flutterMImc.isOnline()) flutterMImc.login();
}
} else {
UX.showToast("${response.data["message"]}");
}
}
/// 设置上下线
void setOnline({int online}){
if(serviceUser.online == online) return;
UX.alert(rooContext,
content: "您确定" + (online == 0 ? "下线" : online == 1 ? "上线": "设置繁忙") +"吗!",
onConfirm: (){
updateUserOnlineStatus(online: online);
});
}
/// 是否加载聊天记录完毕
bool isLoadRecordEnd = false;
/// 是否在加载更多聊天记录
bool isLoadingMorRecord = false;
/// 设置是否在加载中状态
void setIsLoadingMorRecord(bool isLoading){
isLoadingMorRecord = isLoading;
notifyListeners();
}
/// 获取服务器消息列表
Future<void> getMessageRecord({int timestamp, int pageSize = 20, int accountId, bool isFirstLoad = false, int serviceId}) async {
try {
await Future.delayed(Duration(milliseconds: 200));
int timer = timestamp ?? 0;
int _accountId = accountId ?? currentContact.fromAccount;
if(isFirstLoad){
isLoadRecordEnd = false;
isGetHistoryMessage = false;
notifyListeners();
}
if(isFirstLoad && currentUserMessagesRecords(accountId).length <= 0){
isChatFullLoading = true;
notifyListeners();
}
if(currentUserMessagesRecords(accountId).length > 0 && !isFirstLoad){
timer = currentUserMessagesRecords(accountId)[0].timestamp;
}
Response response = await messageService.getMessageRecord(timestamp: timer, pageSize: pageSize, account: _accountId, service: serviceId ?? serviceUser.id, isHistory: isGetHistoryMessage);
isChatFullLoading = false;
if (response.data["code"] == 200) {
List<ImMessageModel> messages = (response.data['data']['list'] as List).map((i) => _handlerMessage(ImMessageModel.fromJson(i))).toList();
for (var i = 0; i < messages.length; i++) {
messages[i].payload = utf8.decode(base64Decode(messages[i].payload));
}
if(isFirstLoad){
messagesRecords[_accountId] = messages;
}else{
messagesRecords[_accountId].insertAll(0, messages);
}
if((response.data['data']['list'] as List).length < pageSize && isGetHistoryMessage){
isLoadRecordEnd = true;
}
if((response.data['data']['list'] as List).length < pageSize){
isGetHistoryMessage = true;
}
} else {
UX.showToast("${response.data["message"]}");
}
notifyListeners();
} catch (e) {
debugPrint(e);
}
}
/// 上报IM最后活动时间
Future<void> _upImLastActivity() async {
Timer.periodic(Duration(milliseconds: 20000), (_timer) {
printf("上报IM最后活动时间");
if (serviceUser != null) publicService.upImLastActivity();
});
}
/// 获取上传文件配置
Future<void> getConfigs() async {
Response response = await publicService.getConfigs();
if (response.data["code"] == 200) {
imConfigs = ImConfigs.fromJson(response.data["data"]);
} else {
await Future.delayed(Duration(milliseconds: 1000));
getConfigs();
}
}
/// 创建消息
/// [toAccount] 接收方账号
/// [msgType] 消息类型
/// [content] 消息内容
MessageHandle createMessage(
{int toAccount, String msgType, dynamic content}) {
MIMCMessage message = MIMCMessage();
String millisecondsSinceEpoch = DateTime.now().millisecondsSinceEpoch.toString();
int timestamp = int.parse(millisecondsSinceEpoch.substring(0, millisecondsSinceEpoch.length - 3));
message.timestamp = timestamp;
message.bizType = msgType;
message.toAccount = toAccount.toString();
message.fromAccount = serviceUser.id.toString();
Map<String, dynamic> payloadMap = {
"from_account": serviceUser.id,
"to_account": toAccount,
"biz_type": msgType,
"version": "0",
"key": DateTime.now().millisecondsSinceEpoch,
"platform": Platform.isAndroid ? 6 : 2,
"timestamp": timestamp,
"read": 0,
"transfer_account": 0,
"payload": "$content"
};
message.payload = base64Encode(utf8.encode(json.encode(payloadMap)));
return MessageHandle(
sendMessage: message,
localMessage: ImMessageModel.fromJson(payloadMap)..isShowCancel = true);
}
/// 发送消息处理
void sendMessage(MessageHandle msgHandle) async {
// 发送失败提示
if (!await flutterMImc.isOnline()) {
MessageHandle tipsMsg = createMessage(toAccount: toAccount, msgType: "system", content: "您的网络异常,发送失败了~");
pushLocalMessage(tipsMsg.localMessage, toAccount);
return;
}
Timer.periodic(Duration(milliseconds: 150), (timer){
flutterMImc.sendMessage(msgHandle.sendMessage);
timer.cancel();
});
/// 消息入库(远程)
MessageHandle cloneMsgHandle = msgHandle.clone();
String type = cloneMsgHandle.localMessage.bizType;
if (type == "contacts" ||
type == "pong" ||
type == "welcome" ||
type == "handshake") return;
cloneMsgHandle.sendMessage.toAccount = robot.id.toString();
cloneMsgHandle.sendMessage.payload = ImMessageModel(
bizType: "into",
payload: cloneMsgHandle.localMessage.toBase64(),
).toBase64();
flutterMImc.sendMessage(cloneMsgHandle.sendMessage);
if (type != "photo") pushLocalMessage(cloneMsgHandle.localMessage, cloneMsgHandle.localMessage.toAccount);
notifyListeners();
await Future.delayed(Duration(milliseconds: 10000));
cloneMsgHandle.localMessage.isShowCancel = false;
notifyListeners();
}
// 更新某个消息
void updateMessage(ImMessageModel msg) {
int index = currentUserMessagesRecords(toAccount).indexWhere((i) => i.key == msg.key);
currentUserMessagesRecords(toAccount)[index] = msg;
notifyListeners();
}
/// 获取平台数据
Future<void> getPlatforms() async{
Response response = await platformService.getPlatforms();
if(response.statusCode == 200){
platforms = (response.data['data'] as List).map((i)=>PlatformModel.fromJson(i)).toList();
}else{
UX.showToast(response.data["message"]);
}
notifyListeners();
}
/// 获取工单统计
Future<void> getWorkOrderCounts() async{
Response response = await WorkOrderService.getInstance().getWorkOrderCounts();
if(response.statusCode == 200){
workOrderCounts = WorkOrderCountsModel.fromJson(response.data['data']);
}else{
UX.showToast(response.data["message"]);
}
notifyListeners();
await Future.delayed(Duration(milliseconds: 5000));
getWorkOrderCounts();
}
// 获取工单类型数据
Future<void> getWorkorderTypes() async {
if(workOrderCounts == null){
await getWorkOrderCounts();
}
Response response = await WorkOrderService.getInstance().getWorkorderTypes();
if (response.data["code"] == 200) {
int _total = 0;
var _workOrderTypes = (response.data["data"] as List).map((i) => WorkOrderTypeModel.fromJson(i)).toList();
for(var i=0; i<_workOrderTypes.length; i++){
_total += _workOrderTypes[i].count;
}
workOrderTypes = [];
workOrderTypes.add(WorkOrderTypeModel(id: 0, title: "全部工单", count: _total));
workOrderTypes.addAll(_workOrderTypes);
workOrderTypes.add(WorkOrderTypeModel(id: -1, title: "已结单", count: workOrderCounts.status3));
workOrderTypes.add(WorkOrderTypeModel(id: -2, title: "回收站", count: workOrderCounts.deleteCount));
notifyListeners();
} else {
UX.showToast("${response.data["message"]}");
}
}
/// 根据平台ID获取平台标题
String getPlatformTitle(int id){
PlatformModel platform = platforms.singleWhere((p) => p.id == id);
if(platform != null){
return platform.title;
}
return "未知平台";
}
/// 根据平台标题获取平台ID
int getPlatformId(String title){
PlatformModel platform = platforms.singleWhere((p) => p.title == title);
if(platform != null){
return platform.id;
}
return 1;
}
/// 获取工作台聊天列表
bool isContactShowLoading = false;
Future<void> getContacts({bool isFullLoading = false}) async{
if(isFullLoading && contacts.length <= 0){
isContactShowLoading = true;
notifyListeners();
}
Response response = await contactService.getContacts();
isContactShowLoading = false;
if(response.statusCode == 200){
contacts = (response.data['data'] as List).map((i){
ContactModel contact = ContactModel.fromJson(i);
if(currentContact != null && contact.fromAccount == currentContact.fromAccount){
currentContact = contact;
}
return ContactModel.fromJson(i);
}).toList();
}else{
UX.showToast(response.data["message"]);
}
notifyListeners();
}
/// 移除的单个聊天列表
void removeSingleContact(int cid){
contactService.removeSingle(cid);
contacts.removeWhere((c) => c.cid == cid);
}
/// 撤回消息
void sendCancelMessage(ImMessageModel msg) {
GlobalProvide globalState = GlobalProvide.getInstance();
MessageHandle msgHandle = createMessage(toAccount: toAccount, msgType: "cancel", content: msg.key);
globalState.messageService.removeMeessge(
toAccount: toAccount,
fromAccount: serviceUser.id,
key: msg.key,
);
sendMessage(msgHandle);
}
/// 结束会话
void sendEndMessage() {
if(currentContact == null || toAccount == null) return;
MessageHandle msgHandle = createMessage(toAccount: toAccount, msgType: "end", content: "");
sendMessage(msgHandle);
currentContact.isSessionEnd = 1;
notifyListeners();
}
/// 删除本地消息
void deleteMessage(int account, int key){
try{
int index = messagesRecords[account].indexWhere((i) => i.key == key);
messagesRecords[account].removeAt(index);
notifyListeners();
}catch(e){
printf(e);
}
}
// 处理头像昵称
ImMessageModel _handlerMessage(ImMessageModel msg) {
const String defaultAvatar = 'http://qiniu.cmp520.com/avatar_degault_3.png';
msg.avatar = defaultAvatar;
if (msg.fromAccount != serviceUser.id && msg.fromAccount != robot.id && msg.fromAccount > 5000) {
/// 这里如果是接入业务平台可替换成用户头像和昵称
/// if (uid == myUid) msg.avatar = MyAvatar
/// if (uid == myUid) msg.nickname = MyNickname
msg.nickname = msg.nickname ?? "访客${msg.fromAccount}";
} else {
if (serviceUser != null && serviceUser.id == msg.fromAccount) {
msg.nickname = serviceUser.nickname ?? "客服";
msg.avatar = serviceUser.avatar != null && serviceUser.avatar.isNotEmpty
? serviceUser.avatar
: defaultAvatar;
} else {
String _localServiceUserStr =
prefs.getString("service_user_" + msg.fromAccount.toString());
if (_localServiceUserStr != null) {
AdminModel _localServiceUser =
AdminModel.fromJson(json.decode(_localServiceUserStr));
msg.nickname = _localServiceUser.nickname ?? "客服";
msg.avatar = _localServiceUser.avatar != null &&
_localServiceUser.avatar.isNotEmpty
? _localServiceUser.avatar
: defaultAvatar;
} else if (robot != null && robot.id == msg.fromAccount) {
msg.nickname = robot.nickname ?? "客服";
msg.avatar = robot.avatar != null && robot.avatar.isNotEmpty
? robot.avatar
: defaultAvatar;
} else {
String _localRobotStr =
prefs.getString("robot_" + msg.fromAccount.toString());
if (_localRobotStr != null) {
RobotModel _localRobot = RobotModel.fromJson(json.decode(_localRobotStr));
msg.nickname = _localRobot.nickname ?? "机器人";
msg.avatar =
_localRobot.avatar != null && _localRobot.avatar.isNotEmpty
? _localRobot.avatar
: defaultAvatar;
} else {
msg.nickname = "未知";
msg.avatar = defaultAvatar;
}
}
}
}
return msg;
}
/// mimc事件监听
StreamSubscription _subStatus;
StreamSubscription _subHandleMessage;
Future<void> _addMimcEvent() async {
_subStatus?.cancel();
_subHandleMessage?.cancel();
try {
/// 状态发生改变
_subStatus = flutterMImc
?.addEventListenerStatusChanged()
?.listen((bool login) async {
debugPrint("状态发生改变===$login");
});
/// 消息监听
_subHandleMessage = flutterMImc
?.addEventListenerHandleMessage()
?.listen((MIMCMessage msg) async {
ImMessageModel message = ImMessageModel.fromJson(
json.decode(utf8.decode(base64Decode(msg.payload))));
if(message.fromAccount == serviceUser.id && message.bizType == "pong") return;
if(message.bizType == "into") return;
if(message.fromAccount == serviceUser.id){
pushLocalMessage(message, message.toAccount);
if(message.bizType == "cancel"){
message.key = int.parse(message.payload);
deleteMessage(message.toAccount, message.key);
}
return;
}
switch (message.bizType) {
case "transfer":
getContacts();
break;
case "contacts":
contacts = (json.decode(message.payload) as List).map((i){
ContactModel contact = ContactModel.fromJson(i);
if(currentContact != null && contact.fromAccount == currentContact.fromAccount){
currentContact = contact;
}
return ContactModel.fromJson(i);
}).toList();
break;
case "text":
case "photo":
if(!(currentContact != null && message.fromAccount != currentContact.fromAccount)){
advanceText = "";
notifyListeners();
}
break;
case "end":
case "timeout":
getContacts();
if(currentContact != null && message.fromAccount != currentContact.fromAccount) return;
advanceText = "";
break;
case "pong":
if(currentContact != null && message.fromAccount != currentContact.fromAccount) return;
advanceText = message.payload;
notifyListeners();
if (!isPong){
isPong = true;
notifyListeners();
await Future.delayed(Duration(milliseconds: 1500));
isPong = false;
notifyListeners();
}
break;
case "cancel":
message.key = int.parse(message.payload);
deleteMessage(message.fromAccount, message.key);
break;
}
pushLocalMessage(message, message.fromAccount);
notifyListeners();
/// 通知
if(currentRoutePath == "chat" && toAccount == message.fromAccount) return;
if(appLifecycleState == AppLifecycleState.paused || currentRoutePath != "home"){
if(!["text", "photo", "handshake"].contains(message.bizType)) return;
ImMessageModel newMsg = _handlerMessage(message);
String payload = message.payload;
String title = "${newMsg.nickname}发来消息";
if(message.bizType == "photo"){
payload = "图片文件";
}
if(message.bizType == "handshake"){
title = "系统消息";
payload = "${newMsg.nickname}接入人工...";
}
LocalNotifications.getInstance().showNotifications(title: "$title", body: "$payload", channelId: "${newMsg.fromAccount}");
}
});
} catch (e) {
debugPrint(e);
}
}
/// 处理接收消息
void pushLocalMessage(ImMessageModel message, int account, {bool isInsert = false}){
if(message.bizType == 'pong' || message.bizType == "handshake" || message.bizType == "contacts"){
return;
}
ImMessageModel newMsg = _handlerMessage(message);
List<ImMessageModel> messages = messagesRecords[account] ?? [];
if(isInsert){
messages.insert(0, newMsg);
}else{
messages.add(newMsg);
}
messagesRecords[account] = messages;
notifyListeners();
}
/// 发送文本消息
void sendTextMessage(String text){
MessageHandle msgHandle = createMessage(toAccount: toAccount, msgType: "text", content: text);
sendMessage(msgHandle);
}
/// 上传文件
Future<String> uploadFile(File file,{
Function(int, int) onSendProgress,
VoidCallback fail
}) async{
String filePath = file.path;
String fileName = "${DateTime.now().microsecondsSinceEpoch}_" +
(filePath.lastIndexOf('/') > -1
? filePath.substring(filePath.lastIndexOf('/') + 1)
: filePath);
FormData formData = new FormData.fromMap({
"fileType": "image",
"fileName": "file",
"file_name": fileName,
"key": fileName,
"token": imConfigs.uploadSecret ?? "",
"file": MultipartFile.fromFileSync(file.path, filename: fileName)
});
String uploadUrl;
/// 系统自带
if (imConfigs.uploadMode == 1) {
uploadUrl = API_UPLOAD_FILE;
}
/// 七牛上传
else if (imConfigs.uploadMode == 2) {
uploadUrl = API_QINIU_UPLOAD_FILE;
/// 其他
} else {}
Response response = await PublicService.instance.http.post(uploadUrl, data: formData, onSendProgress: onSendProgress);
if (response.statusCode == 200) {
switch (imConfigs.uploadMode) {
case 1:
return imConfigs.uploadHost + "/" + response.data["data"];
break;
case 2:
return imConfigs.uploadHost + "/" + response.data["key"];
break;
default:
return null;
}
} else {
if(fail != null) fail();
return null;
}
}
/// 发送图片
void sendPhotoMessage(File file) async {
MessageHandle msgHandle;
try {
if (file == null) return;
msgHandle = createMessage(toAccount: toAccount, msgType: "photo", content: file.path);
pushLocalMessage(msgHandle.localMessage, toAccount);
notifyListeners();
String imgUrl = await uploadFile(file, onSendProgress:(int sent, int total) {
msgHandle.localMessage.uploadProgress = (sent / total * 100).ceil();
notifyListeners();
});
/// 上传失败
if(imgUrl == null){
deleteMessage(msgHandle.localMessage.toAccount, msgHandle.localMessage.key);
MessageHandle systemMsgHandle = createMessage(
toAccount: toAccount, msgType: "system", content: "图片上传失败~");
pushLocalMessage(systemMsgHandle.localMessage, toAccount);
return;
}
/// 上传成功
msgHandle.localMessage.isShowCancel = true;
msgHandle.localMessage.payload = imgUrl;
notifyListeners();
ImMessageModel sendMsg = ImMessageModel.fromJson(json
.decode(utf8.decode(base64Decode(msgHandle.sendMessage.payload))));
sendMsg.payload = imgUrl;
msgHandle.sendMessage.payload =
base64Encode(utf8.encode(json.encode(sendMsg.toJson())));
sendMessage(msgHandle.clone()..localMessage.payload = imgUrl);
await Future.delayed(Duration(milliseconds: 10000));
msgHandle.localMessage.isShowCancel = false;
notifyListeners();
} catch (e) {
deleteMessage(msgHandle.localMessage.toAccount, msgHandle.localMessage.key);
MessageHandle systemMsgHandle = createMessage(
toAccount: toAccount, msgType: "system", content: "图片上传失败~");
pushLocalMessage(systemMsgHandle.localMessage, toAccount);
}
}
// 消息内容变,ping, pong
bool isSendPong = false;
void sendPongMessage(String value) async {
if (isSendPong) return;
if(currentContact == null) return;
isSendPong = true;
MessageHandle _msgHandle = createMessage(toAccount: toAccount, msgType: "pong", content: value);
sendMessage(_msgHandle);
await Future.delayed(Duration(milliseconds: 200));
isSendPong = false;
notifyListeners();
}
@override
void dispose() {
printf("GlobalProvide被销毁了");
_subStatus?.cancel();
_subHandleMessage?.cancel();
super.dispose();
}
}
\ No newline at end of file
import 'package:kefu_workbench/provider/global.dart';
import '../core_flutter.dart';
class HomeProvide with ChangeNotifier {
static HomeProvide instance;
// 单例
static HomeProvide getInstance() {
if (instance != null) {
return instance;
}
instance = HomeProvide();
return instance;
}
HomeProvide(){
/// 检测通知权限
checkPermission(GlobalProvide.getInstance().rooContext, permissionGroupType: PermissionGroup.notification);
}
/// 所有未读消息
int get contactReadCount{
int count = 0;
for(var i =0; i<GlobalProvide.getInstance().contacts.length; i++){
count = count + GlobalProvide.getInstance().contacts[i].read;
}
return count;
}
/// 选中用户
void selectContact(ContactModel contact){
GlobalProvide.getInstance().setCurrentContact(contact);
}
/// 刷新
Future<bool> onRefresh() async{
await getContacts();
UX.showToast("刷新成功~", position: ToastPosition.top);
return true;
}
/// MessageService
MessageService messageService = MessageService.getInstance();
/// 退出登录
void logout(BuildContext context) {
UX.alert(context, content: "您确定退出登录吗?", onConfirm: () {
Navigator.pop(context);
Navigator.pushNamedAndRemoveUntil(context, "/login", ModalRoute.withName('/'), arguments: {"isAnimate": false});
UX.showToast("已退出登录~");
GlobalProvide.getInstance().applicationLogout();
});
}
/// 获取聊天列表
Future<void> getContacts({bool isFullLoading = false}) async{
await GlobalProvide.getInstance().getContacts();
}
@override
void dispose() {
instance = null;
super.dispose();
}
}
import 'package:dio/dio.dart';
import '../core_flutter.dart';
class KnowledgeProvide with ChangeNotifier {
static KnowledgeProvide instance;
// 单例
static KnowledgeProvide getInstance() {
if (instance != null) {
return instance;
}
instance = KnowledgeProvide();
return instance;
}
KnowledgeProvide(){
scrollController = ScrollController();
getKnowledges();
// 监听滚动
scrollController?.addListener(() => _onScrollViewControllerAddListener());
}
int pageOn = 0;
int pageSize = 25;
bool isLoadEnd = false;
bool isLoading = false;
ScrollController scrollController;
List<KnowledgeModel> knowledges = [];
String keyword = "";
/// 获取列表数据
Future<void> getKnowledges() async{
if(isLoadEnd) return;
pageOn = pageOn +1;
isLoading = true;
notifyListeners();
Response response = await KnowledgeService.getInstance().getList(pageOn: pageOn, pageSize: pageSize, keyword: keyword);
isLoading = false;
notifyListeners();
if (response.data["code"] == 200) {
List<KnowledgeModel> _knowledges = (response.data["data"]['list'] as List).map((i) => KnowledgeModel.fromJson(i)).toList();
if(_knowledges.length < pageSize){
isLoadEnd = true;
}
if(pageOn > 1){
knowledges.addAll(_knowledges);
}else{
knowledges = _knowledges;
}
notifyListeners();
} else {
UX.showToast("${response.data["message"]}");
}
}
/// 更新单个数据
Future<void> getKnowledge(int id) async{
Response response = await KnowledgeService.getInstance().getItem(id: id);
if (response.data["code"] == 200) {
KnowledgeModel _knowledge = KnowledgeModel.fromJson(response.data["data"]);
int index = knowledges.indexWhere((k) => k.id == id);
if(index != null){
knowledges[index] = _knowledge;
notifyListeners();
}
}
}
/// 删除单个数据
void deleteItem(int id){
knowledges.removeWhere((i) => i.id == id);
}
/// 找出一个
KnowledgeModel getItem(int id){
return knowledges.firstWhere((k) => k.id == id);
}
// onRefresh
Future<bool> onRefresh() async{
pageOn = 0;
isLoadEnd = false;
notifyListeners();
await getKnowledges();
UX.showToast("刷新成功", position: ToastPosition.top);
return true;
}
// 监听滚动条
void _onScrollViewControllerAddListener() async{
try {
ScrollPosition position = scrollController.position;
if (position.pixels + 10.0 > position.maxScrollExtent &&
!isLoadEnd && !isLoading) {
// 判断网络
if (!await checkNetWork()) {
UX.showToast('您的网络异常,请检查您的网络!', position: ToastPosition.top);
return;
}
getKnowledges();
}
}catch(e){
printf(e);
}
}
/// add
void goAdd(BuildContext context) async{
Navigator.pushNamed(context, "/knowledge_add").then((isSuccess){
if(isSuccess == true){
pageOn = 0;
isLoadEnd = false;
notifyListeners();
getKnowledges();
}
});
}
@override
void dispose() {
printf("销毁了KnowledgeProvide");
instance = null;
scrollController?.dispose();
super.dispose();
}
}
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:kefu_workbench/provider/global.dart';
import '../core_flutter.dart';
class LoginProvide with ChangeNotifier {
AuthService authService = AuthService.getInstance();
TextEditingController accountCtr;
TextEditingController hostCtr;
TextEditingController passwordCtr;
bool isSavePassword = false;
void setIsSavePassword(bool isSave) {
isSavePassword = isSave;
notifyListeners();
}
Future<void> login(BuildContext context) async {
String host = hostCtr.value.text.trim();
String account = accountCtr.value.text.trim();
String password = passwordCtr.value.text.trim();
GlobalProvide.getInstance().prefs.setString("host", host);
GlobalProvide.getInstance().prefs.setString("account", account);
if (isSavePassword) {
GlobalProvide.getInstance().prefs.setString("password", password);
} else {
GlobalProvide.getInstance().prefs.remove("password");
}
if(account.isEmpty || account == ""){
UX.showToast("请输入服务器地址~");
return;
}
if(account.isEmpty || account == ""){
UX.showToast("请输入用户名~");
return;
}
if(password.isEmpty || password == ""){
UX.showToast("请输入密码~");
return;
}
UX.showLoading(context, content: "登录中...");
Response response = await authService.login(username: account, password: password);
UX.hideLoading(context);
if(response.statusCode == 200){
AdminModel user = AdminModel.fromJson(response.data['data']);
GlobalProvide.getInstance().setServiceUser(user);
GlobalProvide.getInstance().prefs.setString("serviceUser", json.encode(response.data['data']));
GlobalProvide.getInstance().prefs.setString("Authorization", user.token);
UX.showToast("登录成功~");
GlobalProvide.getInstance().init();
Navigator.pushNamedAndRemoveUntil(context, "/home", ModalRoute.withName('/'), arguments: {"isAnimate": false});
}else{
UX.showToast(response.data["message"], position: ToastPosition.top);
}
}
@override
void dispose() {
accountCtr?.dispose();
passwordCtr?.dispose();
super.dispose();
}
}
import '../core_flutter.dart';
class PlatformProvide with ChangeNotifier {
static PlatformProvide instance;
// 单例
static PlatformProvide getInstance() {
if (instance != null) {
return instance;
}
instance = PlatformProvide();
return instance;
}
@override
void dispose() {
instance = null;
super.dispose();
}
}
import 'package:dio/dio.dart';
import '../core_flutter.dart';
class RobotProvide with ChangeNotifier {
RobotService robotService = RobotService.getInstance();
static RobotProvide instance;
// 单例
static RobotProvide getInstance() {
if (instance != null) {
return instance;
}
instance = RobotProvide();
return instance;
}
RobotProvide(){
getRobots();
}
bool isLoading = false;
List<RobotModel> robots = [];
/// 获取列表数据
Future<void> getRobots() async{
isLoading = true;
notifyListeners();
Response response = await robotService.getList();
isLoading = false;
notifyListeners();
if (response.data["code"] == 200) {
robots = (response.data["data"] as List).map((i) => RobotModel.fromJson(i)).toList();
notifyListeners();
} else {
UX.showToast("${response.data["message"]}");
}
}
/// 更新单个数据
Future<void> getRobot(int id) async{
Response response = await RobotService.getInstance().getItem(id: id);
if (response.data["code"] == 200) {
RobotModel _robot = RobotModel.fromJson(response.data["data"]);
int index = robots.indexWhere((k) => k.id == id);
if(index != null){
robots[index] = _robot;
notifyListeners();
}
}
}
/// 找出一个
RobotModel getItem(int id){
return robots.firstWhere((k) => k.id == id);
}
/// 删除单个数据
void deleteItem(int id){
robots.removeWhere((i) => i.id == id);
}
// onRefresh
Future<bool> onRefresh() async{
await getRobots();
UX.showToast("刷新成功", position: ToastPosition.top);
return true;
}
/// add
void goAdd(BuildContext context) async{
Navigator.pushNamed(context, "/robot_add").then((isSuccess){
if(isSuccess == true){
getRobots();
}
});
}
@override
void dispose() {
instance = null;
super.dispose();
}
}
import 'package:dio/dio.dart';
import '../core_flutter.dart';
class ShortcutProvide with ChangeNotifier {
ShortcutService shortcutService = ShortcutService.getInstance();
static ShortcutProvide instance;
// 单例
static ShortcutProvide getInstance() {
if (instance != null) {
return instance;
}
instance = ShortcutProvide();
return instance;
}
ShortcutProvide(){
scrollController = ScrollController();
searchTextEditingController = TextEditingController();
getShortcuts();
}
int pageOn = 0;
int pageSize = 25;
bool isLoading = false;
ScrollController scrollController;
TextEditingController searchTextEditingController;
List<ShortcutModel> shortcuts = [];
/// 获取列表数据
Future<void> getShortcuts() async{
isLoading = true;
notifyListeners();
Response response = await shortcutService.getShortcuts();
isLoading = false;
notifyListeners();
if (response.data["code"] == 200) {
shortcuts = (response.data['data'] as List).map((i) => ShortcutModel.fromJson(i)).toList();
notifyListeners();
} else {
UX.showToast(response.data['message']);
}
}
/// 找出一个
ShortcutModel getItem(int id){
return shortcuts.firstWhere((k) => k.id == id);
}
/// 删除单个数据
void deleteItem(int id){
shortcuts.removeWhere((i) => i.id == id);
}
// onRefresh
Future<bool> onRefresh() async{
searchTextEditingController.clear();
notifyListeners();
await getShortcuts();
UX.showToast("刷新成功", position: ToastPosition.top);
return true;
}
/// edit
void goEdit(BuildContext context, ShortcutModel shortcut) async{
Navigator.pushNamed(context, "/shortcut_edit", arguments: {"shortcut": shortcut}).then((isSuccess){
if(isSuccess == true){
getShortcuts();
}
});
}
/// add
void goAdd(BuildContext context) async{
Navigator.pushNamed(context, "/shortcut_add").then((isSuccess){
if(isSuccess == true){
getShortcuts();
}
});
}
@override
void dispose() {
instance = null;
scrollController?.dispose();
searchTextEditingController?.dispose();
super.dispose();
}
}
import 'package:flutter/material.dart';
enum ThemeType { first, second }
class ThemeProvide with ChangeNotifier {
// 实例
static ThemeProvide instance;
// 单例
static ThemeProvide getInstance() {
if (instance != null) {
return instance;
}
instance = ThemeProvide();
return instance;
}
//当前主题
ThemeType themeType = ThemeType.first;
// 第一个主题
ThemeData _firstTheme() {
return ThemeData(
primaryColor: Color(0xff3e444a), // 主题色
dividerColor: Color(0xfff3f3f3), // 描边等
accentColor: Color(0xff999999), // 文本前景色
indicatorColor: Color(0xff3e444a), // 选中颜色
buttonColor: Color(0xff3e444a), // 按钮颜色
disabledColor: Color(0xffcccccc), // 禁用颜色
primaryColorLight: Color(0xffffffff), // 比较亮的颜色
primaryColorDark: Color(0xff333333), // 比较暗的颜色
scaffoldBackgroundColor: Color(0xffF3F3F3),
backgroundColor: Color(0xffF3F3F3),
popupMenuTheme: PopupMenuThemeData(
color: Color(0xff3e444a)
),
textTheme: TextTheme(
// 文本主题
title: TextStyle(
// 标题
color: Color(0xff333333),
fontWeight: FontWeight.w500),
body1: TextStyle(
// 正文1
color: Color(0xff666666),
fontWeight: FontWeight.w400),
body2: TextStyle(
// 正文2
color: Color(0xff999999),
fontWeight: FontWeight.w400),
caption: TextStyle(
// 描述
color: Color(0xff999999),
fontWeight: FontWeight.w400
),
display1: TextStyle(
color: Color(0xffffffff),
fontWeight: FontWeight.w400),
),
appBarTheme: AppBarTheme(
// 导航条主题
color: Color(0xffffffff),
elevation: 0.3,
brightness: Brightness.dark
),
buttonTheme: ButtonThemeData(splashColor: Color(0xfff3f3f3)));
}
// 第二个主题
ThemeData _secondTheme() {
return ThemeData(
primaryColor: Color(0xff000000), // 主题色
dividerColor: Color(0xfff3f3f3), // 描边等
primaryColorLight: Color(0xfffc4919), // 比较亮的颜色
accentColor: Colors.black54, // 文本前景色
indicatorColor: Color(0xff0cb8fd), // 选中颜色
buttonColor: Color(0xff0cb8fd), // 按钮颜色
scaffoldBackgroundColor: Color(0xffF3F3F3),
textTheme: TextTheme(
// 文本主题
title: TextStyle(
// 标题
color: Color(0xff333333),
fontWeight: FontWeight.w400),
body1: TextStyle(
// 正文1
color: Color(0xff666666),
fontWeight: FontWeight.w500),
body2: TextStyle(
// 正文1
color: Color(0xffcccccc),
fontWeight: FontWeight.w400
),
caption: TextStyle(
// 描述
color: Color(0xff999999),
fontWeight: FontWeight.w400
),
display1: TextStyle(
// 用作appbar title
color: Color(0xff333333),
fontWeight: FontWeight.w500),
),
buttonTheme: ButtonThemeData(),
appBarTheme: AppBarTheme(
// 导航条主题
elevation: 0.3,
brightness: Brightness.light),
);
}
/// 获取当前主题
ThemeData getCurrentTheme() {
switch (themeType) {
case ThemeType.first:
return _firstTheme();
break;
case ThemeType.second:
return _secondTheme();
break;
default:
return _firstTheme();
}
}
void setTheme(ThemeType themeType) {
themeType = themeType;
notifyListeners();
}
}
import 'package:dio/dio.dart';
import '../core_flutter.dart';
class UserProvide with ChangeNotifier {
UserService userService = UserService.getInstance();
static UserProvide instance;
// 单例
static UserProvide getInstance() {
if (instance != null) {
return instance;
}
instance = UserProvide();
return instance;
}
UserProvide(){
scrollController = ScrollController();
searchTextEditingController = TextEditingController();
getUsers();
// 监听滚动
scrollController?.addListener(() => _onScrollViewControllerAddListener());
}
int pageOn = 0;
int pageSize = 25;
bool isLoadEnd = false;
bool isLoading = false;
String keyword = "";
ScrollController scrollController;
TextEditingController searchTextEditingController;
List<UserModel> users = [];
int usersTotal = 0;
// 监听滚动条
void _onScrollViewControllerAddListener() async{
try {
ScrollPosition position = scrollController.position;
if (position.pixels + 10.0 > position.maxScrollExtent &&
!isLoadEnd && !isLoading) {
// 判断网络
if (!await checkNetWork()) {
UX.showToast('您的网络异常,请检查您的网络!', position: ToastPosition.top);
return;
}
getUsers();
}
}catch(e){
printf(e);
}
}
/// 获取列表数据
Future<void> getUsers() async{
if(isLoadEnd) return;
pageOn = pageOn +1;
isLoading = true;
notifyListeners();
Response response = await userService.getList(pageOn: pageOn, pageSize: pageSize, keyword: keyword);
isLoading = false;
notifyListeners();
if (response.data["code"] == 200) {
List<UserModel> _users = (response.data["data"]['list'] as List).map((i) => UserModel.fromJson(i)).toList();
usersTotal = response.data["data"]['total'];
if(_users.length < pageSize){
isLoadEnd = true;
}
if(pageOn > 1){
users.addAll(_users);
}else{
users = _users;
}
notifyListeners();
} else {
UX.showToast("${response.data["message"]}");
}
}
/// 更新单个数据
Future<void> getUser(int id) async{
Response response = await userService.getItem(id: id);
if (response.data["code"] == 200) {
UserModel _user = UserModel.fromJson(response.data["data"]);
int index = users.indexWhere((k) => k.id == id);
if(index != null){
users[index] = _user;
notifyListeners();
}
}
}
/// 找出一个
UserModel getItem(int id){
return users.firstWhere((k) => k.id == id);
}
/// 删除单个数据
void deleteItem(int id){
usersTotal--;
users.removeWhere((i) => i.id == id);
}
// search
void onSearch() async{
keyword = searchTextEditingController.value.text.trim();
if(keyword.isEmpty) return;
pageOn = 0;
isLoadEnd = false;
notifyListeners();
await getUsers();
}
// onRefresh
Future<bool> onRefresh() async{
pageOn = 0;
keyword = "";
isLoadEnd = false;
searchTextEditingController.clear();
notifyListeners();
await getUsers();
UX.showToast("刷新成功", position: ToastPosition.top);
return true;
}
@override
void dispose() {
instance = null;
scrollController?.dispose();
searchTextEditingController?.dispose();
super.dispose();
}
}
import 'package:dio/dio.dart';
import 'package:kefu_workbench/models/work_order_type.dart';
import 'package:kefu_workbench/provider/global.dart';
import 'package:kefu_workbench/services/system_service.dart';
import 'package:kefu_workbench/services/workorder_service.dart';
import '../core_flutter.dart';
class WorkOrderSettingProvide with ChangeNotifier {
static WorkOrderSettingProvide instance;
WorkOrderService workOrderService = WorkOrderService.getInstance();
TextEditingController textEditingController = TextEditingController();
// 单例
static WorkOrderSettingProvide getInstance() {
if (instance != null) {
return instance;
}
instance = WorkOrderSettingProvide();
return instance;
}
GlobalProvide globalProvide = GlobalProvide.getInstance();
WorkOrderSettingProvide() {
_init();
}
void _init() async{
globalProvide.getWorkorderTypes();
this.isOpenWorkorder = globalProvide.imConfigs.openWorkorder == 1;
}
List<WorkOrderTypeModel> workorderTypes;
bool isOpenWorkorder = false;
bool isLoading = false;
List<WorkOrderTypeModel> get getWorkorderTypes{
return globalProvide.workOrderTypes.sublist(1, globalProvide.workOrderTypes.length - 2);
}
Widget _formWidget(BuildContext context, {String value = "", Function(String) onChanged, String hintText = ""}){
ThemeData themeData = Theme.of(context);
textEditingController.text = value;
return Material(
color: Colors.transparent,
child: Container(
padding: EdgeInsets.symmetric(horizontal: ToPx.size(10)),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(width: ToPx.size(1),color: Colors.grey.withAlpha(100)),
borderRadius: BorderRadius.all(Radius.circular(ToPx.size(10))),
),
child: TextFormField(
onChanged: onChanged,
controller: textEditingController,
cursorColor: themeData.primaryColor,
decoration: InputDecoration(
hintText: "$hintText",
border: InputBorder.none,
hintStyle: TextStyle(
fontSize: ToPx.size(28),
color: Colors.grey.withAlpha(150),
),
counterStyle: TextStyle(
color: Colors.grey.withAlpha(200)),
contentPadding:
EdgeInsets.symmetric(vertical: 3.0),
counterText: ""),
style: TextStyle(
color: Colors.black87.withAlpha(180),
),
),
),
);
}
// 添加工单
void createWorkOrder(BuildContext context) async{
String _content = "";
UX.alert(
context,
title: "温馨提示!",
confirmText: "提交",
isConfirmPop: false,
content: _formWidget(context, onChanged: (value)=>_content = value, hintText: "请输入类型名称~"),
onConfirm: () async{
if(_content.isEmpty){
UX.showToast("请输入类型名称~");
return;
}
Response response = await workOrderService.addType({
"title": _content,
});
if (response.data['code'] == 200) {
Navigator.pop(context);
globalProvide.getWorkorderTypes();
}else{
UX.showToast("${response.data["message"]}");
}
}
);
}
void setOpenWorkorder(BuildContext context, bool isSwitch){
String title = "您确定打开工单功能吗?";
int openWorkorder = 1;
if(!isSwitch){
title = "您确定关闭工单功能吗?";
openWorkorder = 0;
}
UX.alert(
context,
title: "温馨提示!",
confirmText: "提交",
isConfirmPop: false,
content: title,
onCancel: (){
this.isOpenWorkorder = !isSwitch;
notifyListeners();
},
onConfirm: () async{
Response response = await SystemService.getInstance().toggleOpenWorkorder(openWorkorder);
if (response.data['code'] == 200) {
Navigator.pop(context);
globalProvide.getConfigs();
this.isOpenWorkorder = isSwitch;
if(isSwitch){
UX.showToast("工单单功已启动");
}else{
UX.showToast("工单功能已关闭");
}
notifyListeners();
}else{
UX.showToast("${response.data["message"]}");
this.isOpenWorkorder = !isSwitch;
notifyListeners();
}
}
);
}
void operating(BuildContext context, WorkOrderTypeModel workOrderType) async{
ThemeData themeData = Theme.of(context);
WorkOrderTypeModel _workOrderType = WorkOrderTypeModel.fromJson(workOrderType.toJson());
int index = await showCupertinoModalPopup(
context: context,
builder: (_){
return CupertinoActionSheet(
actions: <Widget>[
CupertinoActionSheetAction(
child: Text('修改', style: themeData.textTheme.body2),
onPressed: () {
Navigator.pop(context, 0);
},
),
CupertinoActionSheetAction(
child: Text('删除', style: themeData.textTheme.body2),
onPressed: () {
Navigator.pop(context, 1);
},
),
],
cancelButton: SizedBox(
child: CupertinoActionSheetAction(
isDestructiveAction: true,
child: Text('取消', style: themeData.textTheme.body2),
onPressed: () {
Navigator.pop(context, false);
},
),
)
);
}
);
if(index == 0){
UX.alert(
context,
title: "温馨提示!",
confirmText: "更新",
isConfirmPop: false,
content: _formWidget(context, value: _workOrderType.title, onChanged: (value)=>_workOrderType.title = value, hintText: "请输入类型名称~"),
onConfirm: () async{
if(workOrderType.title == _workOrderType.title) {
Navigator.pop(context);
return;
}
if(_workOrderType.title.isEmpty){
UX.showToast("请输入类型名称~");
return;
}
Response response = await workOrderService.updateType(_workOrderType.toJson());
if (response.data['code'] == 200) {
Navigator.pop(context);
globalProvide.getWorkorderTypes();
UX.showToast("更新成功~");
}else{
UX.showToast("${response.data["message"]}");
}
}
);
}else if(index == 1){
UX.alert(
context,
title: "温馨提示!",
content: "您确定要删除该分类吗?",
onConfirm: () async{
Response response = await workOrderService.delype(_workOrderType.id);
if (response.data['code'] == 200) {
globalProvide.getWorkorderTypes();
}else{
UX.showToast("${response.data["message"]}");
}
}
);
}
}
@override
void dispose() {
instance = null;
textEditingController?.dispose();
super.dispose();
}
}
import 'package:dio/dio.dart';
import 'package:kefu_workbench/models/work_order.dart';
import 'package:kefu_workbench/provider/global.dart';
import 'package:kefu_workbench/services/workorder_service.dart';
import '../core_flutter.dart';
class WorkOrderProvide with ChangeNotifier {
static WorkOrderProvide instance;
WorkOrderService workOrderService = WorkOrderService.getInstance();
// 单例
static WorkOrderProvide getInstance() {
if (instance != null) {
return instance;
}
instance = WorkOrderProvide();
return instance;
}
GlobalProvide globalProvide = GlobalProvide.getInstance();
WorkOrderProvide() {
_init();
}
void _init() async{
await getWorkOrders(pageOn: 1);
}
bool isLoadEnd = false;
bool isLoading = false;
Map<String, dynamic> workOrderRequest = {
"del": 0,
"page_on": 0,
"page_size": 15,
"status": "0,1,2",
"tid": 0
};
Map<int,List<WorkOrderModel>> workOrdersMap = {};
List<WorkOrderModel> getWorkOrder(int tid){
try{
return workOrdersMap[tid] ?? [];
}catch(_){
return [];
}
}
int workOrdersTotal = 0;
int tabControllerIndex = 0;
void changeTabControllerIndex(int index){
tabControllerIndex = index;
isLoadEnd = false;
// workOrdersMap[tid] = [];
notifyListeners();
}
String get workOrderRequestStatus{
if(tabControllerIndex == globalProvide.workOrderTypes.length-1 && globalProvide.workOrderTypes.length > 1){
return '0,1,2,3';
}
if(tabControllerIndex == globalProvide.workOrderTypes.length-2 && globalProvide.workOrderTypes.length > 1){
return '3';
}
return "0,1,2";
}
// 获取工单列表
Future<void> getWorkOrders({int pageOn = 1}) async {
if(this.isLoadEnd || this.isLoading) return;
workOrderRequest["page_on"] = pageOn;
var tid = globalProvide.workOrderTypes[tabControllerIndex].id;
workOrderRequest["tid"] = tid;
workOrderRequest['status'] = this.workOrderRequestStatus;
workOrderRequest['del'] = tid == -2 ? 1 : 0;
this.isLoading = true;
notifyListeners();
Response response = await workOrderService.getWorkOrders(workOrderRequest);
if (response.data["code"] == 200) {
List<WorkOrderModel> _workOrders = (response.data["data"]['list'] as List).map((i) => WorkOrderModel.fromJson(i)).toList();
workOrdersTotal = response.data["data"]['total'];
if(_workOrders.length < workOrderRequest["page_size"]){
isLoadEnd = true;
}
if(pageOn > 1){
workOrdersMap[tid].addAll(_workOrders);
}else{
workOrdersMap[tid] = [];
workOrdersMap[tid] = _workOrders;
}
await Future.delayed(Duration(milliseconds: 500));
this.isLoading = false;
notifyListeners();
} else {
UX.showToast("${response.data["message"]}");
}
}
@override
void dispose() {
instance = null;
super.dispose();
}
}
import 'package:dio/dio.dart';
import 'package:kefu_workbench/models/work_order.dart';
import 'package:kefu_workbench/models/work_order_comment.dart';
import 'package:kefu_workbench/provider/global.dart';
import 'package:kefu_workbench/services/workorder_service.dart';
import '../core_flutter.dart';
class WorkOrderDetailProvide with ChangeNotifier {
WorkOrderService workOrderService = WorkOrderService.getInstance();
static WorkOrderDetailProvide instance;
// 单例
static WorkOrderDetailProvide getInstance() {
if (instance != null) {
return instance;
}
instance = WorkOrderDetailProvide();
return instance;
}
GlobalProvide globalState = GlobalProvide.getInstance();
WorkOrderModel workOrder;
List<WorkOrderCommentModel> workOrderComments = [];
bool isUploadFail = false;
bool isLoading = false;
int uploadProgress = 0;
String html = "";
TextEditingController replyInputCtr = TextEditingController();
ScrollController customScrollView = ScrollController();
/// 选择图片
void onPickFile(BuildContext context) async{
File _file = await uploadImage<File>(context);
if (_file == null) return;
isUploadFail = false;
notifyListeners();
String url = await globalState.uploadFile(_file, onSendProgress: (int sent, int total){
debugPrint("uploadProgress=${(sent / total * 100).ceil()}%");
uploadProgress = (sent / total * 100).ceil();
if(uploadProgress == 100) uploadProgress = 0;
notifyListeners();
},fail: (){
isUploadFail = true;
notifyListeners();
});
String suffix = url.substring(url.lastIndexOf(".") + 1);
if ("jpg,jpeg,png,JPG,JPEG,PNG".contains(suffix)) {
html =
"<br><img style='max-width:45%;margin-top:5px;' preview='1' src='" +
url +
"' />";
} else {
html =
"<br><img style='width:20px;height:20px;top:3px; right:3px;position: relative;' preview='1' src='http://qiniu.cmp520.com/fj.png' />";
html += "<a target='_blank' style='color: #2e9dfc;' href='" +
url +
"'>下载附件</a>";
}
notifyListeners();
}
void uploadFile(){
}
// 回复
void reply() async{
String content = replyInputCtr.value.text + html;
if (content.isEmpty) {
UX.showToast("请输输入内容!");
return;
}
Response response = await workOrderService.reply({
"wid": workOrder.id,
"content": content,
});
if (response.data['code'] == 200) {
await getWorkOrder(workOrder.id, isLoading: false);
await getWorkOrderComments(workOrder.id);
replyInputCtr.clear();
html = "";
await Future.delayed(Duration(milliseconds: 500));
customScrollView.animateTo(customScrollView.position.maxScrollExtent,
duration: Duration(milliseconds: 300), curve: Curves.ease);
}
}
// 关闭工单
void closeWorkOrder(BuildContext context) async{
ThemeData themeData = Theme.of(context);
String _content = "";
UX.alert(
context,
title: "温馨提示!",
confirmText: "关闭",
isConfirmPop: false,
content: Material(
color: Colors.transparent,
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(vertical: ToPx.size(10)),
child: Text("请输入关闭原因!"),
),
Container(
padding: EdgeInsets.symmetric(horizontal: ToPx.size(10)),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(width: ToPx.size(1),color: Colors.grey.withAlpha(100)),
borderRadius: BorderRadius.all(Radius.circular(ToPx.size(10))),
),
child: TextFormField(
onChanged: (String value){
_content = value;
},
cursorColor: themeData.primaryColor,
decoration: InputDecoration(
hintText: "请输入关闭原因~",
border: InputBorder.none,
hintStyle: TextStyle(
fontSize: ToPx.size(28),
color: Colors.grey.withAlpha(150),
),
counterStyle: TextStyle(
color: Colors.grey.withAlpha(200)),
contentPadding:
EdgeInsets.symmetric(vertical: 3.0),
counterText: ""),
style: TextStyle(
color: Colors.black87.withAlpha(180),
),
),
)
],
),
),
onConfirm: () async{
if(_content.isEmpty){
UX.showToast("请输入关闭原因~");
return;
}
Response response = await workOrderService.close({
"wid": workOrder.id,
"remark": _content,
});
if (response.data['code'] == 200) {
UX.showToast("工单已关闭");
Navigator.pop(context);
await getWorkOrder(workOrder.id, isLoading: false);
}else{
UX.showToast("${response.data["message"]}");
}
}
);
}
// 删除工单
void deleteWorkOrder(BuildContext context) async{
UX.alert(
context,
title: "温馨提示!",
confirmText: "删除",
isConfirmPop: false,
content: "您确定要删除该工单吗?",
onConfirm: () async{
Response response = await workOrderService.delWorkOrder(workOrder.id);
if (response.data['code'] == 200) {
UX.showToast("工单删除成功~");
Navigator.pop(context);
await getWorkOrder(workOrder.id, isLoading: false);
}else{
UX.showToast("${response.data["message"]}");
}
}
);
}
// 获取工单
Future<void> getWorkOrder(int id, {bool isLoading = true}) async{
if(this.isLoading || id == null) return;
this.isLoading = isLoading;
notifyListeners();
Response response = await workOrderService.getWorkOrder(id);
if (response.data["code"] == 200) {
workOrder = WorkOrderModel.fromJson(response.data['data']);
this.isLoading = false;
notifyListeners();
} else {
UX.showToast("${response.data["message"]}");
}
}
// 获取工单coments
Future<void> getWorkOrderComments(int id) async{
if(id == null) return;
Response response = await workOrderService.getWorkOrderComments(id);
if (response.data["code"] == 200) {
workOrderComments = (response.data['data'] as List)
.map((i) => WorkOrderCommentModel.fromJson(i))
.toList();
notifyListeners();
} else {
UX.showToast("${response.data["message"]}");
}
}
@override
void dispose() {
instance = null;
replyInputCtr?.dispose();
customScrollView?.dispose();
super.dispose();
}
}
const List<String> emojis = ["😀","😁","😂","🤣","😃","😄","😅","😆","😉","😊","😋","😎","😍","😘","😗","😙","😚","🙂","🤗","🤩","🤔","🤨","😐","😑","😶","🙄","😏","😣","😥","😮","🤐","😯","😪","😫","😴","😌","😛","😜","😝","🤤","😒","😓","😔","😕","🙃","🤑","😲","🙁","😖","😞","😟","😤","😢","😭","😦","😧","😨","😩","🤯","😬","😰","😱","😳","🤪","😵","😡","😠","🤬","😷","🤒","🤕","🤢","🤮","🤧","😇","🤠","🤡","🤥","🤫","🤭","🧐","🤓","😈","👿","👹","👺","💀","👻","👽","🤖","💩","😺","😸","😹","😻","😼","😽","🙀","😿","😾","🤲","👐","🙌","👏","🤝","👍","👎","👊","✊","🤛","🤜","🤞","✌️","🤟","🤘","👌","👈","👉","👆","👇","☝️","✋","🤚","🖐","🖖","👋","🤙","💪","🖕","✍️","🙏"];
\ No newline at end of file
import 'package:kefu_workbench/core_flutter.dart';
class _CupertinoLocalizationsDelegate extends LocalizationsDelegate<CupertinoLocalizations> {
const _CupertinoLocalizationsDelegate();
@override
bool isSupported(Locale locale) => locale.languageCode == 'zh';
@override
Future<CupertinoLocalizations> load(Locale locale) => ChineseCupertinoLocalizations.load(locale);
@override
bool shouldReload(_CupertinoLocalizationsDelegate old) => false;
@override
String toString() => 'DefaultCupertinoLocalizations.delegate(zh_CH)';
}
/// US English strings for the cupertino widgets.
class ChineseCupertinoLocalizations implements CupertinoLocalizations {
/// Constructs an object that defines the cupertino widgets' localized strings
/// for US English (only).
///
/// [LocalizationsDelegate] implementations typically call the static [load]
/// function, rather than constructing this class directly.
ChineseCupertinoLocalizations(Locale local);
static const List<String> _shortWeekdays = <String>[
'周一',
'周二',
'周三',
'周四',
'周五',
'周六',
'周日',
];
static const List<String> _shortMonths = <String>[
'Jan',
'Feb',
'Mar',
'Apr',
'May',
'Jun',
'Jul',
'Aug',
'Sep',
'Oct',
'Nov',
'Dec',
];
static const List<String> _months = <String>[
'01月',
'02月',
'03月',
'04月',
'05月',
'06月',
'07月',
'08月',
'09月',
'10月',
'11月',
'12月',
];
@override
String datePickerYear(int yearIndex) => yearIndex.toString() + "年";
@override
String datePickerMonth(int monthIndex) => _months[monthIndex - 1];
@override
String datePickerDayOfMonth(int dayIndex) => dayIndex.toString() + "日";
@override
String datePickerHour(int hour) => hour.toString();
@override
String datePickerHourSemanticsLabel(int hour) => hour.toString() + " 小时";
@override
String datePickerMinute(int minute) => minute.toString().padLeft(2, '0');
@override
String datePickerMinuteSemanticsLabel(int minute) {
return '1 分钟';
}
@override
String datePickerMediumDate(DateTime date) {
return '${_shortWeekdays[date.weekday - DateTime.monday]} '
'${_shortMonths[date.month - DateTime.january]} '
'${date.day.toString().padRight(2)}';
}
// @override
String get todayLabel => "今天";
@override
DatePickerDateOrder get datePickerDateOrder => DatePickerDateOrder.ymd;
@override
DatePickerDateTimeOrder get datePickerDateTimeOrder => DatePickerDateTimeOrder.date_time_dayPeriod;
@override
String get anteMeridiemAbbreviation => 'AM';
@override
String get postMeridiemAbbreviation => 'PM';
@override
String get alertDialogLabel => '提示信息';
@override
String timerPickerHour(int hour) => hour.toString();
@override
String timerPickerMinute(int minute) => minute.toString();
@override
String timerPickerSecond(int second) => second.toString();
@override
String timerPickerHourLabel(int hour) => '时';
@override
String timerPickerMinuteLabel(int minute) => '分';
@override
String timerPickerSecondLabel(int second) => '秒';
@override
String get cutButtonLabel => '剪切';
@override
String get copyButtonLabel => '复制';
@override
String get pasteButtonLabel => '粘贴';
@override
String get selectAllButtonLabel => '全选';
/// Creates an object that provides US English resource values for the
/// cupertino library widgets.
///
/// The [locale] parameter is ignored.
///
/// This method is typically used to create a [LocalizationsDelegate].
static Future<CupertinoLocalizations> load(Locale locale) {
return SynchronousFuture<CupertinoLocalizations>(ChineseCupertinoLocalizations(locale));
}
/// A [LocalizationsDelegate] that uses [DefaultCupertinoLocalizations.load]
/// to create an instance of this class.
static const LocalizationsDelegate<CupertinoLocalizations> delegate = _CupertinoLocalizationsDelegate();
}
\ No newline at end of file
import 'package:kefu_workbench/views/user_edit/index.dart';
import 'core_flutter.dart';
import 'provider/global.dart';
import 'views/admin_detail/index.dart';
import 'views/admin_edit/index.dart';
import 'views/admins/index.dart';
import 'views/auth/index.dart';
import 'views/chat/index.dart';
import 'views/chat_record/index.dart';
import 'views/edit_password/index.dart';
import 'views/edit_profile/index.dart';
import 'views/home/index.dart';
import 'views/knowledge/index.dart';
import 'views/knowledge_detail/index.dart';
import 'views/knowledge_edit/index.dart';
import 'views/platform_edit/index.dart';
import 'views/robot_detail/index.dart';
import 'views/robot_edit/index.dart';
import 'views/robots/index.dart';
import 'views/shortcut_edit/index.dart';
import 'views/shortcuts/index.dart';
import 'views/statistical/index.dart';
import 'views/system/index.dart';
import 'views/user_detail/index.dart';
import 'views/users/index.dart';
import 'views/workorder/index.dart';
import 'views/workorder_detail/index.dart';
import 'views/workorder_setting/index.dart';
class Routers {
static Widget buildPage(String path, {Object arguments}) {
GlobalProvide globalProvide = GlobalProvide.getInstance();
bool isLogin = globalProvide.isLogin;
if(!isLogin) return LoginPage(arguments: arguments);
globalProvide.setCurrentRoutePath(path.replaceAll("/", ""));
switch (path) {
case "/login":
return LoginPage(arguments: arguments);
break;
case "/home":
return HomePage(arguments: arguments);
break;
case "/statistical":
return StatisticalPage(arguments: arguments);
break;
case "/chat":
return ChatPage(arguments: arguments);
break;
case "/knowledge":
return KnowledgePage(arguments: arguments);
case "/edit_profile":
return EditProfilePage(arguments: arguments);
case "/edit_password":
return EditPasswordPage(arguments: arguments);
case "/knowledge_detail":
return KnowledgeDetailPage(arguments: arguments);
case "/knowledge_add":
case "/knowledge_edit":
return KnowledgeEditPage(arguments: arguments);
break;
case "/robots":
return RobotsPage(arguments: arguments);
break;
case "/robot_add":
case "/robot_edit":
return RobotEditPage(arguments: arguments);
break;
case "/robot_detail":
return RobotDetailPage(arguments: arguments);
break;
case "/users":
return UsersPage(arguments: arguments);
break;
case "/user_edit":
return UserEditPage(arguments: arguments);
case "/user_detail":
return UserDetailPage(arguments: arguments);
break;
case "/admins":
return AdminsPage(arguments: arguments);
break;
case "/admin_detail":
return AdminDetailPage(arguments: arguments);
case "/admin_edit":
case "/admin_add":
return AdminEditPage(arguments: arguments);
break;
case "/chat_record":
return ChatReCordPage(arguments: arguments);
break;
case "/shortcuts":
return ShortcutsPage(arguments: arguments);
break;
case "/shortcut_edit":
case "/shortcut_add":
return ShortcutEditPage(arguments: arguments);
break;
case "/system":
return SystemPage(arguments: arguments);
break;
case "/platform_edit":
case "/platform_add":
return PlatformEditPage(arguments: arguments);
break;
case "/workorder":
return WorkOrderPage(arguments: arguments);
break;
case "/workorder/detail":
return WorkOrderDetailPage(arguments: arguments);
break;
case "/workorder/setting":
return WorkOrderSettingPage(arguments: arguments);
break;
default:
return Scaffold(
body: Center(
child: Text("not fund page"),
),
);
}
}
}
import 'package:dio/dio.dart';
import 'api.dart';
import 'base_service.dart';
class AdminService extends BaseServices {
static AdminService instance;
static AdminService getInstance() {
if (instance != null) {
return instance;
} else {
instance = AdminService();
}
return instance;
}
// 获取单个客服信息
Future<Response> getItem({int id}) async {
try {
Response response = await http.get(API_ADMIN + id.toString());
return response;
} on DioError catch (e) {
return error(e, API_REGISTER);
}
}
// 删除
Future<Response> delete({int id}) async {
try {
Response response = await http.delete(API_ADMIN + id.toString());
return response;
} on DioError catch (e) {
return error(e, API_USER);
}
}
// 获取个人信息
Future<Response> getMe() async {
try {
Response response = await http.get(API_GET_ME);
return response;
} on DioError catch (e) {
return error(e, API_REGISTER);
}
}
// 添加
Future<Response> add(data) async {
try {
Response response = await http.post(API_ADMIN, data: data);
return response;
} on DioError catch (e) {
return error(e, API_ADMIN);
}
}
// 更新当前服务谁
Future<Response> updateCurrentServiceUser({int accountId}) async {
try {
Response response = await http.get(API_UPDATE_CURRENR_USER + accountId.toString());
return response;
} on DioError catch (e) {
return error(e, API_UPDATE_CURRENR_USER);
}
}
// 更新登录状态
Future<Response> updateUserOnlineStatus({int status}) async {
try {
Response response = await http.put(API_UPDATE_ONLINE_STATUS + status.toString());
return response;
} on DioError catch (e) {
return error(e, API_REGISTER);
}
}
// 获取客服列表
Future<Response> getAdmins({int pageOn = 1, int pageSize = 20,int online = 3,String keyword = ""}) async {
try {
Response response = await http.post(API_GET_ADMINS, data: {
"page_on": pageOn,
"page_size": pageSize,
"online": online,
"keyword": keyword
});
return response;
} on DioError catch (e) {
return error(e, API_GET_ADMINS);
}
}
// 更新客服信息
Future<Response> saveAdminInfo(Map data) async {
try {
Response response = await http.put(API_ADMIN, data: data);
return response;
} on DioError catch (e) {
return error(e, API_ADMIN);
}
}
// 修改客服密码
Future<Response> updatePassword(Map data) async {
try {
Response response = await http.put(API_UPDATE_ADMIN_PASSWORD, data: data);
return response;
} on DioError catch (e) {
return error(e, API_UPDATE_ADMIN_PASSWORD);
}
}
}
/// 七牛文件上传
const String API_QINIU_UPLOAD_FILE = "https://upload.qiniup.com";
/// 用户登录
const String API_LOGIN = "/auth/login";
/// 退出登录
const String API_LOGOUT = "/auth/logout";
/// 工作台记录
const String API_CONTACT = "/contact/";
/// 获取会话列表
const String API_CONTACTS = "/contact/list";
/// 获取快捷语
const String API_GET_SHORTCUT = "/shortcut/list";
/// 快捷语
const String API_SHORTCUT = "/shortcut/";
/// 获取个人信息
const String API_GET_ME = "/admin/me";
/// 更新客服上线状态
const String API_UPDATE_ONLINE_STATUS = "/admin/online/";
/// 更新当前服务谁
const String API_UPDATE_CURRENR_USER = "/admin/current/user/";
/// 获取客服列表
const String API_GET_ADMINS = "/admin/list";
/// 转接用户
const String API_TRANSFER_USER = "/message/transfer";
/// 注册IM账号
const String API_REGISTER = "/public/register";
/// 上报最后活动时间
const String API_ACTIVITY = "/public/activity/";
/// 获取机器人 /platform
const String API_GET_ROBOT = "/public/robot/1";
/// 机器人
const String API_ROBOT = "/robot/";
/// 获取机器人列表
const String API_ROBOTS = "/robot/list/";
/// 获取未读消息
const String API_GET_READ = "/public/read/";
/// 获取历史消息记录
const String API_GET_MESSAGE = "/message/list/";
/// 获取历史消息记录
const String API_GET_MESSAGE_HOSTORY = "/message/history/";
/// 删除单条消息
const String API_REMOVE_MESSAGE = "/message/remove";
/// 清除未读消息
const String API_CLEAN_READ = "/public/clean_read";
/// 清除未读消息
const String API_WINDOW_CHANGE = "/public/window";
/// 获取配置信息
const String API_CONFIGS = "/public/configs";
//// 一分钟上报一次我的活动
const String API_RUN_LAST_ACTiIVITY = "/public/activity/";
/// 内置文件上传api
const String API_UPLOAD_FILE = "/public/upload";
/// 获取平台信息
const String API_GET_PLATFORM = "/platform/list";
/// 用户接口
const String API_USER = "/user/";
/// 用户接口
const String API_USERS = "/user/list/";
/// 客服接口
const String API_ADMIN = "/admin/";
/// 修改客服密码
const String API_UPDATE_ADMIN_PASSWORD = "/admin/password/";
/// 获取知识库列表
const String API_GET_KNOWLEDGE = "/knowledge/list/";
/// 知识库操作
const String API_KNOWLEDGE = "/knowledge/";
/// 服务记录
const String API_SERVICES_STATISTICAL = "/services_statistical/list/";
/// 系统信息
const String API_SYSTEM = "/system/";
/// 更新工单配置打开或者关闭
const String API_SYSTEM_WORKORDER = "/system/workorder/";
/// 获取上传配置
const String API_UPLOADS_CONFIG = "/uploads/config/";
/// 获取公司信息
const String API_COMPANY_INFO = "/public/company/";
/// 公司信息接口
const String API_COMPANY = "/company/";
/// 七牛接口
const String API_QINIU = "/qiniu/";
/// 渠道平台
const String API_PLATFORM = "/platform/";
/// 获取各渠道服务统计量
const String API_STATISTICAL = "/home/statistical/";
/// 获取独立用户访问量
const String API_TODAY_STATISTICAL = "/home/flow_statistical/";
/// 获取工单列表
const String API_WORK_ORDERS= "/workorder/list/";
/// 获取单个工单
const String API_WORK_ORDER= "/public/workorder/";
/// 获取单个工单的conments
const String API_WORK_ORDER_COMMENTS= "/public/workorder/comments/";
/// 回复工单
const String API_REPLY_WORK_ORDER= "/public/workorder/reply/";
/// 关闭工单
const String API_CLOSE_WORK_ORDER= "/workorder/close/";
/// 删除工单
const String API_DELETE_WORK_ORDER= "/public/workorder/";
/// 获取工单类型数据
const String API_WORK_ORDER_TYPES= "/workorder/types/";
/// 获取工单系统counts
const String API_WORK_ORDER_COUNTS= "/workorder/counts/";
/// 获取工单类型
const String API_WORK_ORDER_TYPE= "/workorder/type/";
import 'package:dio/dio.dart';
import 'api.dart';
import 'base_service.dart';
class AuthService extends BaseServices {
static AuthService instance;
static AuthService getInstance() {
if (instance != null) {
return instance;
} else {
instance = AuthService();
}
return instance;
}
// 登录
Future<Response> login({String username, String password}) async {
try {
Response response = await http
.post(API_LOGIN, data: {"username": username, "password": password, "auth_type": 2});
return response;
} on DioError catch (e) {
return error(e, API_LOGIN);
}
}
// 退出登录
Future<Response> logout({String username, String password}) async {
try {
Response response = await http
.get(API_LOGOUT);
return response;
} on DioError catch (e) {
return error(e, API_LOGOUT);
}
}
}
import 'package:dio/dio.dart';
import 'package:kefu_workbench/provider/global.dart';
import 'package:shared_preferences/shared_preferences.dart';
import '../configs.dart';
import '../core_flutter.dart';
class BaseServices{
// dio 实例
Dio get http => getDioInstance();
// 去除空字段
Map<String, dynamic> removeNull(Map<String, dynamic> json){
json.removeWhere((key, value) => value == null);
return json;
}
// 全局服务器错误返回信息
Response error(DioError e, String url){
printf("$url =====服务器错误返回信息=====$e====${e.response.data}");
if(e.response.data != null && e.response.data['code']?.toString() == "401"){
GlobalProvide.getInstance().applicationLogout();
if(GlobalProvide.getInstance()?.rooContext != null){
Navigator.pushNamedAndRemoveUntil(GlobalProvide.getInstance()?.rooContext, "/login", ModalRoute.withName('/'), arguments: {"isAnimate": false});
}
}
return Response(data: e.response.data);
}
Dio getDioInstance(){
final dio = Dio();
dio.options.baseUrl = Configs.HOST;
dio.options.connectTimeout = 60000;
dio.options.receiveTimeout = 60000;
dio.interceptors.add(InterceptorsWrapper(
onRequest:(RequestOptions options) async{
SharedPreferences prefs = await SharedPreferences.getInstance();
final String authorization = prefs.getString('Authorization');
debugPrint('调用了API=${options.uri}');
debugPrint('request body=${options.data}');
debugPrint('Authorization=$authorization');
if(authorization != null){
options.headers['Authorization'] = authorization;
}
// 判断网络是否可用
if(!await checkNetWork()){
return dio.resolve(Response(data: {"code": 503, "msg": '您的网络异常,请检查您的网络!', "data": null}));
}
return options;
},
onResponse:(Response response) async{
SharedPreferences prefs = await SharedPreferences.getInstance();
// 如果有authorization 换掉本地的
if(response.headers['authorization'] != null){
prefs.setString('Authorization', response.headers['authorization'][0]);
}
return response; // continue
},
onError: (DioError e) {
return e;
}
));
return dio;
}
}
import 'package:dio/dio.dart';
import 'api.dart';
import 'base_service.dart';
class ContactService extends BaseServices {
static ContactService instance;
static ContactService getInstance() {
if (instance != null) {
return instance;
} else {
instance = ContactService();
}
return instance;
}
// 获取聊天列表
Future<Response> getContacts() async {
try {
Response response = await http.get(API_CONTACTS);
return response;
} on DioError catch (e) {
return error(e, API_CONTACTS);
}
}
// 删除单个
Future<Response> removeSingle(int cid) async {
try {
Response response = await http.delete(API_CONTACT + cid.toString());
return response;
} on DioError catch (e) {
return error(e, API_CONTACT + cid.toString());
}
}
}
import 'package:dio/dio.dart';
import 'package:kefu_workbench/core_flutter.dart';
import 'api.dart';
import 'base_service.dart';
class KnowledgeService extends BaseServices {
static KnowledgeService instance;
static KnowledgeService getInstance() {
if (instance != null) {
return instance;
} else {
instance = KnowledgeService();
}
return instance;
}
// 查询
Future<Response> getItem({int id}) async {
try {
Response response = await http.get(API_KNOWLEDGE + id.toString());
return response;
} on DioError catch (e) {
return error(e, API_KNOWLEDGE);
}
}
// 删除
Future<Response> delete({int id}) async {
try {
Response response = await http.delete(API_KNOWLEDGE + id.toString());
return response;
} on DioError catch (e) {
return error(e, API_KNOWLEDGE);
}
}
// 更新
Future<Response> update({Map data}) async {
try {
Response response = await http.put(API_KNOWLEDGE, data: data);
return response;
} on DioError catch (e) {
return error(e, API_KNOWLEDGE);
}
}
// 添加
Future<Response> add({Map data}) async {
try {
Response response = await http.post(API_KNOWLEDGE, data: data);
return response;
} on DioError catch (e) {
return error(e, API_KNOWLEDGE);
}
}
// 获取数据
Future<Response> getList({int pageOn = 1, int pageSize = 20, int platform = 1, String keyword = ""}) async {
try {
Response response = await http.post(API_GET_KNOWLEDGE, data: {"page_on": pageOn, "page_size": pageSize, "platform": platform, "keyword": keyword});
return response;
} on DioError catch (e) {
return error(e, API_GET_KNOWLEDGE);
}
}
}
import 'package:dio/dio.dart';
import 'api.dart';
import 'base_service.dart';
class MessageService extends BaseServices {
static MessageService instance;
static MessageService getInstance() {
if (instance != null) {
return instance;
} else {
instance = MessageService();
}
return instance;
}
// 获取服务器消息列表
Future<Response> getMessageRecord(
{int timestamp, int pageSize = 15, int account, int service, bool isHistory = false}) async {
try {
Response response = await http.post(isHistory ? API_GET_MESSAGE_HOSTORY : API_GET_MESSAGE, data: {
"timestamp": timestamp,
"page_size": pageSize,
"service": service,
"account": account
});
return response;
} on DioError catch (e) {
return error(e, API_GET_MESSAGE);
}
}
// 转接用户
Future<Response> transformerUser({int toAccount, int userAccount}) async {
try {
Response response = await http.post(API_TRANSFER_USER, data: {
"to_account": toAccount,
"user_account": userAccount
});
return response;
} on DioError catch (e) {
return error(e, API_TRANSFER_USER);
}
}
// 删除单条消息
Future<Response> removeMeessge(
{int toAccount, int fromAccount, int key}) async {
try {
Response response = await http.post(API_REMOVE_MESSAGE, data: {
"to_account": toAccount,
"from_account": fromAccount,
"key": key
});
return response;
} on DioError catch (e) {
return error(e, API_REMOVE_MESSAGE);
}
}
}
import 'package:dio/dio.dart';
import 'api.dart';
import 'base_service.dart';
class PlatformService extends BaseServices {
static PlatformService instance;
static PlatformService getInstance() {
if (instance != null) {
return instance;
} else {
instance = PlatformService();
}
return instance;
}
// 获取平台列表
Future<Response> getPlatforms() async {
try {
Response response = await http.get(API_GET_PLATFORM);
return response;
} on DioError catch (e) {
return error(e, API_GET_PLATFORM);
}
}
// 更新信息
Future<Response> update(Map data) async {
try {
Response response = await http.put(API_PLATFORM, data: data);
return response;
} on DioError catch (e) {
return error(e, API_PLATFORM);
}
}
// 添加信息
Future<Response> add(Map data) async {
try {
Response response = await http.post(API_PLATFORM, data: data);
return response;
} on DioError catch (e) {
return error(e, API_PLATFORM);
}
}
// 添加信息
Future<Response> delete(int id) async {
try {
Response response = await http.delete(API_PLATFORM + id.toString());
return response;
} on DioError catch (e) {
return error(e, API_PLATFORM);
}
}
// 获取单个信息
Future<Response> getItem(int id) async {
try {
Response response = await http.get(API_PLATFORM + id.toString());
return response;
} on DioError catch (e) {
return error(e, API_PLATFORM);
}
}
}
import 'package:dio/dio.dart';
import 'api.dart';
import 'base_service.dart';
class PublicService extends BaseServices {
static PublicService instance;
static PublicService getInstance() {
if (instance != null) {
return instance;
} else {
instance = PublicService();
}
return instance;
}
// 注册IM
Future<Response> registerImAccount({int accountId}) async {
try {
Response response = await http
.post(API_REGISTER, data: {"type": 1, "account_id": accountId});
return response;
} on DioError catch (e) {
return error(e, API_REGISTER);
}
}
// 获取配置信息
Future<Response> getConfigs() async {
try {
Response response = await http.get(API_CONFIGS);
return response;
} on DioError catch (e) {
return error(e, API_CONFIGS);
}
}
// 一分钟上报一次我的活动
Future<Response> upImLastActivity() async {
try {
Response response = await http.get(API_RUN_LAST_ACTiIVITY);
return response;
} on DioError catch (e) {
return error(e, API_RUN_LAST_ACTiIVITY);
}
}
// 获取服务记录
Future<Response> getServicesStatistical({int pageOn = 1, int pageSize = 20, int cid, String date, bool isDeWeighting = false,bool isReception = false}) async {
try {
Response response = await http
.post(API_SERVICES_STATISTICAL, data: {
"page_on": pageOn,
"page_size": pageSize,
"cid": cid,
"is_reception": isReception,
"date": date,
"is_de_weighting": isDeWeighting
});
return response;
} on DioError catch (e) {
return error(e, API_SERVICES_STATISTICAL);
}
}
// 获取各渠道服务统计量
Future<Response> getCountStatistical({String dateStart, String dateEnd}) async {
try {
Response response = await http
.post(API_STATISTICAL, data: {
"date_start": dateStart,
"date_end": dateEnd
});
return response;
} on DioError catch (e) {
return error(e, API_STATISTICAL);
}
}
// 获取独立用户访问量
Future<Response> getIpStatistical({String dateStart, String dateEnd}) async {
try {
Response response = await http
.post(API_TODAY_STATISTICAL, data: {
"date_start": dateStart,
"date_end": dateEnd
});
return response;
} on DioError catch (e) {
return error(e, API_TODAY_STATISTICAL);
}
}
}
import 'package:dio/dio.dart';
import 'api.dart';
import 'base_service.dart';
class RobotService extends BaseServices {
static RobotService instance;
static RobotService getInstance() {
if (instance != null) {
return instance;
} else {
instance = RobotService();
}
return instance;
}
// 查询
Future<Response> getItem({int id}) async {
try {
Response response = await http.get(API_ROBOT + id.toString());
return response;
} on DioError catch (e) {
return error(e, API_ROBOT);
}
}
// 删除
Future<Response> delete({int id}) async {
try {
Response response = await http.delete(API_ROBOT + id.toString());
return response;
} on DioError catch (e) {
return error(e, API_ROBOT);
}
}
// 更新
Future<Response> update({Map data}) async {
try {
Response response = await http.put(API_ROBOT, data: data);
return response;
} on DioError catch (e) {
return error(e, API_ROBOT);
}
}
// 添加
Future<Response> add({Map data}) async {
try {
Response response = await http.post(API_ROBOT, data: data);
return response;
} on DioError catch (e) {
return error(e, API_ROBOT);
}
}
// 获取数据
Future<Response> getList() async {
try {
Response response = await http.get(API_ROBOTS);
return response;
} on DioError catch (e) {
return error(e, API_ROBOTS);
}
}
// 获取一个在线机器人
Future<Response> getOnlineRobot() async {
try {
Response response = await http.get(API_GET_ROBOT);
return response;
} on DioError catch (e) {
return error(e, API_GET_ROBOT);
}
}
}
export 'message_service.dart';
export 'auth_service.dart';
export 'admin_service.dart';
export 'public_service.dart';
export 'contact_service.dart';
export 'shortcut_service.dart';
export 'platform_service.dart';
export 'user_service.dart';
export 'knowledge_service.dart';
export 'robot_service.dart';
\ No newline at end of file
import 'package:dio/dio.dart';
import 'api.dart';
import 'base_service.dart';
class ShortcutService extends BaseServices {
static ShortcutService instance;
static ShortcutService getInstance() {
if (instance != null) {
return instance;
} else {
instance = ShortcutService();
}
return instance;
}
// 获取快捷语列表
Future<Response> getShortcuts() async {
try {
Response response = await http.get(API_GET_SHORTCUT);
return response;
} on DioError catch (e) {
return error(e, API_GET_SHORTCUT);
}
}
// 添加
Future<Response> add(Map data) async {
try {
Response response = await http.post(API_SHORTCUT, data: data);
return response;
} on DioError catch (e) {
return error(e, API_SHORTCUT);
}
}
// 修改
Future<Response> update(Map data) async {
try {
Response response = await http.put(API_SHORTCUT, data: data);
return response;
} on DioError catch (e) {
return error(e, API_SHORTCUT);
}
}
// 删除
Future<Response> delete(int id) async {
try {
Response response = await http.delete(API_SHORTCUT+id.toString());
return response;
} on DioError catch (e) {
return error(e, API_SHORTCUT);
}
}
}
import 'package:dio/dio.dart';
import 'api.dart';
import 'base_service.dart';
class SystemService extends BaseServices {
static SystemService instance;
static SystemService getInstance() {
if (instance != null) {
return instance;
} else {
instance = SystemService();
}
return instance;
}
// 获取系统信息
Future<Response> getSystemInfo() async {
try {
Response response = await http.get(API_SYSTEM);
return response;
} on DioError catch (e) {
return error(e, API_SYSTEM);
}
}
// 更新工单配置打开或者关闭
Future<Response> toggleOpenWorkorder(int openWorkorder) async {
try {
Response response = await http.put(API_SYSTEM_WORKORDER, data: {"open_workorder": openWorkorder});
return response;
} on DioError catch (e) {
return error(e, API_SYSTEM);
}
}
// 获取上传配置信息
Future<Response> getUploadsConfig() async {
try {
Response response = await http.get(API_UPLOADS_CONFIG);
return response;
} on DioError catch (e) {
return error(e, API_UPLOADS_CONFIG);
}
}
// 保存系统信息
Future<Response> saveSystemInfo(Map data) async {
try {
Response response = await http.put(API_SYSTEM, data: data);
return response;
} on DioError catch (e) {
return error(e, API_SYSTEM);
}
}
// 获取公司信息
Future<Response> getCompanyInfo() async {
try {
Response response = await http.get(API_COMPANY_INFO);
return response;
} on DioError catch (e) {
return error(e, API_COMPANY_INFO);
}
}
// 保存系统信息
Future<Response> saveCompanyInfo(Map data) async {
try {
Response response = await http.put(API_COMPANY, data: data);
return response;
} on DioError catch (e) {
return error(e, API_COMPANY);
}
}
// 获取七牛配置信息
Future<Response> getQiniuInfo() async {
try {
Response response = await http.get(API_QINIU);
return response;
} on DioError catch (e) {
return error(e, API_QINIU);
}
}
// 保存七牛配置信息
Future<Response> saveQiniuInfo(Map data) async {
try {
Response response = await http.put(API_QINIU, data: data);
return response;
} on DioError catch (e) {
return error(e, API_QINIU);
}
}
}
import 'package:dio/dio.dart';
import 'api.dart';
import 'base_service.dart';
class UserService extends BaseServices {
static UserService instance;
static UserService getInstance() {
if (instance != null) {
return instance;
} else {
instance = UserService();
}
return instance;
}
// 查询
Future<Response> getItem({int id}) async {
try {
Response response = await http.get(API_USER + id.toString());
return response;
} on DioError catch (e) {
return error(e, API_USER);
}
}
// 删除
Future<Response> delete({int id}) async {
try {
Response response = await http.delete(API_USER + id.toString());
return response;
} on DioError catch (e) {
return error(e, API_USER);
}
}
// 添加
Future<Response> add({Map data}) async {
try {
Response response = await http.post(API_USER, data: data);
return response;
} on DioError catch (e) {
return error(e, API_USER);
}
}
// 获取数据
Future<Response> getList({int pageOn = 1, int pageSize = 20, String keyword, int platform = 1, String dateStart, String dateEnd}) async {
try {
Response response = await http.post(API_USERS, data: {
"page_on": pageOn,
"page_size": pageSize,
"keyword": keyword,
"platform": platform,
"date_start": dateStart,
"date_end": dateEnd
});
return response;
} on DioError catch (e) {
return error(e, API_ROBOTS);
}
}
// 保存用户信息
Future<Response> update(Map data) async {
try {
Response response = await http.put(API_USER, data: data);
return response;
} on DioError catch (e) {
return error(e, API_USER);
}
}
}
import 'package:dio/dio.dart';
import 'api.dart';
import 'base_service.dart';
class WorkOrderService extends BaseServices {
static WorkOrderService instance;
static WorkOrderService getInstance() {
if (instance != null) {
return instance;
} else {
instance = WorkOrderService();
}
return instance;
}
// 查询
Future<Response> getItem({int id}) async {
try {
Response response = await http.get(API_USER + id.toString());
return response;
} on DioError catch (e) {
return error(e, API_USER);
}
}
// 删除
Future<Response> delete({int id}) async {
try {
Response response = await http.delete(API_USER + id.toString());
return response;
} on DioError catch (e) {
return error(e, API_USER);
}
}
// 获取工单列表
Future<Response> getWorkOrders(requestBody) async {
try {
Response response = await http.post(API_WORK_ORDERS, data: requestBody);
return response;
} on DioError catch (e) {
return error(e, API_ROBOTS);
}
}
// 获取单个工单数据
Future<Response> getWorkOrder(int id) async {
try {
Response response = await http.get(API_WORK_ORDER + id.toString());
return response;
} on DioError catch (e) {
return error(e, API_ROBOTS);
}
}
// 获取单个工单数据
Future<Response> getWorkOrderComments(int id) async {
try {
Response response = await http.get(API_WORK_ORDER_COMMENTS + id.toString());
return response;
} on DioError catch (e) {
return error(e, API_ROBOTS);
}
}
// 回复工单
Future<Response> reply(requestBody) async {
try {
Response response = await http.post(API_REPLY_WORK_ORDER, data: requestBody);
return response;
} on DioError catch (e) {
return error(e, API_ROBOTS);
}
}
// 关闭工单
Future<Response> close(requestBody) async {
try {
Response response = await http.post(API_CLOSE_WORK_ORDER, data: requestBody);
return response;
} on DioError catch (e) {
return error(e, API_ROBOTS);
}
}
// 删除工单
Future<Response> delWorkOrder(int wid) async {
try {
Response response = await http.delete(API_DELETE_WORK_ORDER + wid.toString());
return response;
} on DioError catch (e) {
return error(e, API_ROBOTS);
}
}
// 获取类型数据
Future<Response> getWorkorderTypes() async {
try {
Response response = await http.get(API_WORK_ORDER_TYPES);
return response;
} on DioError catch (e) {
return error(e, API_ROBOTS);
}
}
// 获取工单系统counts
Future<Response> getWorkOrderCounts() async {
try {
Response response = await http.get(API_WORK_ORDER_COUNTS);
return response;
} on DioError catch (e) {
return error(e, API_ROBOTS);
}
}
// 添加工单分类
Future<Response> addType(requestBody) async {
try {
Response response = await http.post(API_WORK_ORDER_TYPE, data: requestBody);
return response;
} on DioError catch (e) {
return error(e, API_ROBOTS);
}
}
// 更新工单分类
Future<Response> updateType(requestBody) async {
try {
Response response = await http.put(API_WORK_ORDER_TYPE, data: requestBody);
return response;
} on DioError catch (e) {
return error(e, API_ROBOTS);
}
}
// 删除工单分类
Future<Response> delype(int id) async {
try {
Response response = await http.delete(API_WORK_ORDER_TYPE + id.toString());
return response;
} on DioError catch (e) {
return error(e, API_ROBOTS);
}
}
}
import 'package:connectivity/connectivity.dart';
final Connectivity connectivity = Connectivity();
Future<bool> checkNetWork() async{
var result = await connectivity.checkConnectivity();
if(result == ConnectivityResult.none){
return false;
}else{
return true;
}
}
\ No newline at end of file
class DateTimeUtil {
static String dateTimeNowIso() => DateTime.now().toIso8601String();
static int dateTimeNowMilli() => DateTime.now().millisecondsSinceEpoch;
}
\ No newline at end of file
export 'topx.dart';
export 'ux.dart';
export 'check_network.dart';
export 'utils.dart';
export 'datetime_util.dart';
export 'print_util.dart';
export 'permission.dart';
export 'upload_image.dart';
export 'local_notifications.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:kefu_workbench/core_flutter.dart';
class LocalNotifications{
static LocalNotifications instance;
static LocalNotifications getInstance() {
if (instance != null) {
return instance;
} else {
instance = LocalNotifications();
}
return instance;
}
FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin;
LocalNotifications(){
flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
var initializationSettingsAndroid =
new AndroidInitializationSettings('app_icon');
var initializationSettingsIOS = IOSInitializationSettings(
onDidReceiveLocalNotification: onDidReceiveLocalNotification);
var initializationSettings = InitializationSettings(
initializationSettingsAndroid, initializationSettingsIOS);
flutterLocalNotificationsPlugin.initialize(initializationSettings,
onSelectNotification: onSelectNotification);
}
Future<dynamic> onDidReceiveLocalNotification(int id, String title, String body, String payload) async{
await flutterLocalNotificationsPlugin.cancelAll();
return true;
}
Future<dynamic> onSelectNotification(String payload) async{
await flutterLocalNotificationsPlugin.cancelAll();
return true;
}
void showNotifications({String title, String body,String payload, String channelId = "0", String channelName = "channelName", String channelDescription = "channelDescription"}) async{
var androidPlatformChannelSpecifics = AndroidNotificationDetails(
channelId, channelName, channelDescription,
importance: Importance.Max, priority: Priority.High, ticker: 'ticker');
var iOSPlatformChannelSpecifics = IOSNotificationDetails();
var platformChannelSpecifics = NotificationDetails(androidPlatformChannelSpecifics, iOSPlatformChannelSpecifics);
await flutterLocalNotificationsPlugin.show(int.parse(channelId), title, body, platformChannelSpecifics,payload: payload);
}
}
\ No newline at end of file
// 检测系统权限
import 'package:kefu_workbench/core_flutter.dart';
import 'package:permission_handler/permission_handler.dart';
export 'package:permission_handler/permission_handler.dart';
Future<bool> checkPermission(context, {PermissionGroup permissionGroupType}) async {
try {
PermissionStatus permission;
// 相机
if (permissionGroupType == PermissionGroup.camera){
await PermissionHandler().requestPermissions([PermissionGroup.camera]);
permission =
await PermissionHandler().checkPermissionStatus(PermissionGroup.camera);
}
// 位置
if (permissionGroupType == PermissionGroup.locationWhenInUse){
await PermissionHandler().requestPermissions([PermissionGroup.locationWhenInUse]);
permission = await PermissionHandler().checkPermissionStatus(PermissionGroup.locationWhenInUse);
}
// 相册
if (permissionGroupType == PermissionGroup.photos) {
await PermissionHandler().requestPermissions([PermissionGroup.photos]);
permission =
await PermissionHandler().checkPermissionStatus(PermissionGroup.photos);
}
// 文件读写
if (permissionGroupType == PermissionGroup.storage) {
await PermissionHandler().requestPermissions([PermissionGroup.storage]);
permission = await PermissionHandler().checkPermissionStatus(
PermissionGroup.storage);
}
// 通知
if (permissionGroupType == PermissionGroup.notification) {
await PermissionHandler().requestPermissions([PermissionGroup.notification]);
permission = await PermissionHandler().checkPermissionStatus(
PermissionGroup.notification);
}
if (permission != PermissionStatus.granted) {
// 提示内容
String permissionName;
if (permissionGroupType == PermissionGroup.camera)
permissionName = "相机权限";
if (permissionGroupType == PermissionGroup.photos)
permissionName = "相册权限";
if (permissionGroupType == PermissionGroup.notification)
permissionName = "通知权限";
if (permissionGroupType == PermissionGroup.storage)
permissionName = "文件读写权限";
if (permissionGroupType == PermissionGroup.locationWhenInUse)
permissionName = "位置定位权限,APP无法帮您识别您的车牌归属地";
UX.alert(
context, content: "您还没有授权使用$permissionName,现在去设置?",
onConfirm: () async {
// 打开设置
await PermissionHandler().openAppSettings();
});
return false;
}else{
return true;
}
}catch(e){
return false;
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'datetime_util.dart';
/// 通用打印方法,Release 不会打印,超长字符串分段打印
void printf(dynamic message) {
if (message != null) {
var content = message.toString();
if (content == '') {
debugPrint('${DateTimeUtil.dateTimeNowIso()} ');
return;
}
while (content.length > 800) {
debugPrint(
'${DateTimeUtil.dateTimeNowIso()} ${content.substring(0, 800)}');
content = content.substring(800, content.length);
}
if (content != '')
debugPrint('${DateTimeUtil.dateTimeNowIso()} $content');
}
}
import 'package:flutter/material.dart';
/// 根据设计稿的设备尺寸适配size
class ToPx {
static ToPx _instance;
ToPx({
this.width = 750,
this.height = 1334,
this.allowFontScaling = false,
});
int width;
int height;
bool allowFontScaling;
static MediaQueryData _mediaQueryData;
static double _screenWidth;
static double _screenHeight;
static double _pixelRatio;
static double _statusBarHeight;
static double _bottomBarHeight;
static double _textScaleFactor;
static ToPx get instance => getInstance();
static ToPx getInstance() {
if (_instance == null) {
_instance = ToPx();
}
return _instance;
}
void init(BuildContext context) {
MediaQueryData mediaQuery = MediaQuery.of(context);
_mediaQueryData = mediaQuery;
_pixelRatio = mediaQuery.devicePixelRatio;
_screenWidth = mediaQuery.size.width;
_screenHeight = mediaQuery.size.height;
_statusBarHeight = mediaQuery.padding.top;
_bottomBarHeight = _mediaQueryData.padding.bottom;
_textScaleFactor = mediaQuery.textScaleFactor;
}
static MediaQueryData get mediaQueryData => _mediaQueryData;
static double get textScaleFactory => _textScaleFactor;
static double get pixelRatio => _pixelRatio;
static double get screenWidthDp => _screenWidth;
static double get screenHeightDp => _screenHeight;
static double get screenWidth => _screenWidth * _pixelRatio;
static double get screenHeight => _screenHeight * _pixelRatio;
static double get statusBarHeight => _statusBarHeight * _pixelRatio;
static double get bottomBarHeight => _bottomBarHeight * _pixelRatio;
get scaleWidth => _screenWidth / instance.width;
get scaleHeight => _screenHeight / instance.height;
static double size(int width) => width * instance.scaleWidth;
}
import 'package:file_picker/file_picker.dart';
import 'package:image_picker/image_picker.dart';
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/provider/global.dart';
import 'package:kefu_workbench/utils//permission.dart';
/// 选择图片上传
Future<T> uploadImage<T>(BuildContext context, {double maxWidth = 2000}) async{
int selectIndex = await UX.selectImageSheet(context);
ImageSource imageSource = selectIndex == 0 ? ImageSource.camera : selectIndex == 1 ? ImageSource.gallery : null;
if(imageSource == null){
return null;
}
if(imageSource == ImageSource.camera && !await checkPermission(context, permissionGroupType: PermissionGroup.camera)){
UX.showToast("未授权使用相机!");
return null;
}
if(Platform.isAndroid && imageSource == ImageSource.gallery && !await checkPermission(context, permissionGroupType: PermissionGroup.storage)){
UX.showToast("未授权使用相册!");
return null;
}
if(Platform.isIOS && imageSource == ImageSource.gallery && !await checkPermission(context, permissionGroupType: PermissionGroup.photos)){
UX.showToast("未授权使用相册!");
return null;
}
File _file;
if(imageSource == null){
_file = await FilePicker.getFile();
}else{
_file = await ImagePicker.pickImage(source: imageSource, maxWidth: maxWidth);
}
if(_file == null){
return null;
}
if(T == String){
String imgUser = await GlobalProvide.getInstance().uploadFile(_file);
if(imgUser != null && imgUser.isNotEmpty){
return (imgUser as T);
}else{
UX.showToast("上传失败了,请重新尝试");
return null;
}
}else{
return (_file as T);
}
}
\ No newline at end of file
import 'package:dio/dio.dart';
import 'package:kefu_workbench/core_flutter.dart';
import 'package:path_provider/path_provider.dart';
typedef DownloadProgress(int sent,int total); // 下载进度
class Utils{
// 日期轉中文表示
static String toChineseDate(date){
if(date == null) return "";
if(date is String && date.isEmpty) return "";
int dateTimeStamp = DateTime.tryParse(date)?.millisecondsSinceEpoch;
int minute = 1000 * 60;
int hour = minute * 60;
int day = hour * 24;
int month = day * 30;
int now = DateTime.now().millisecondsSinceEpoch;
int diffValue = now - dateTimeStamp;
var monthC =diffValue/month ~/ 1;
var weekC =diffValue/(7*day) ~/ 1;
var dayC =diffValue/day ~/ 1;
var hourC =diffValue/hour ~/ 1;
var minC =diffValue/minute ~/ 1;
String result;
if(monthC >= 12){
result= date ;
}else if(monthC>=1){
result="$monthC个月前";
}
else if(weekC>=1){
result="$weekC周前";
}
else if(dayC>=1){
result="$dayC天前";
}
else if(hourC>=1){
result="$hourC小时前";
}
else if(minC>=1){
result="$minC分钟前";
}else
result="刚刚";
return result;
}
/// 是否是当天.
static bool isToday(int milliseconds, {bool isUtc = false}) {
if (milliseconds == null || milliseconds == 0) return false;
DateTime date = DateTime.fromMillisecondsSinceEpoch(milliseconds, isUtc: isUtc);
DateTime fixDate = isUtc ? DateTime.now().toUtc() : DateTime.now().toLocal();
return date.year == fixDate.year && date.month == fixDate.month && date.day == fixDate.day;
}
/// 是否是昨天.
static bool isYesterday(int milliseconds, {bool isUtc = false}) {
if (milliseconds == null || milliseconds == 0) return false;
DateTime date = DateTime.fromMillisecondsSinceEpoch(milliseconds, isUtc: isUtc);
DateTime _fixDate = isUtc ? DateTime.now().toUtc() : DateTime.now().toLocal();
DateTime fixDate = DateTime.fromMillisecondsSinceEpoch(_fixDate.millisecondsSinceEpoch-86400000, isUtc: isUtc);
return date.year == fixDate.year && date.month == fixDate.month && date.day == fixDate.day;
}
/// 是否是前天.
static bool isBeforeYesterday(int milliseconds, {bool isUtc = false}) {
if (milliseconds == null || milliseconds == 0) return false;
DateTime date = DateTime.fromMillisecondsSinceEpoch(milliseconds, isUtc: isUtc);
DateTime _fixDate = isUtc ? DateTime.now().toUtc() : DateTime.now().toLocal();
DateTime fixDate = DateTime.fromMillisecondsSinceEpoch(_fixDate.millisecondsSinceEpoch-86400000*2, isUtc: isUtc);
return date.year == fixDate.year && date.month == fixDate.month && date.day == fixDate.day;
}
// 日期格式化
static String formatDate(int timeStamp, {bool isformatFull = false}){
if(timeStamp.toString().length <= 10){
timeStamp = int.parse(timeStamp.toString() + '000');
}
if(timeStamp == null) return "";
DateTime date = DateTime.fromMillisecondsSinceEpoch(timeStamp);
if(isformatFull){
return "${date.year}-${date.month < 10 ? "0" + date.month.toString() : date.month}-${date.day < 10 ? "0" + date.day.toString() + "日" : date.day} ${date.hour}:${date.minute < 10 ? "0" + date.minute.toString() : date.minute}";
}
return "${date.year}/${date.month}/${date.day}";
}
// 日期格式化1
static String epocFormat(int timeStamp){
if(timeStamp.toString().length <= 10){
timeStamp = int.parse(timeStamp.toString() + '000');
}
if(timeStamp == null) return "";
String result;
DateTime date = DateTime.fromMillisecondsSinceEpoch(timeStamp);
String d = "";
if(Utils.isToday(timeStamp)){
if(date.hour>1 && date.hour < 6){
d = "凌晨";
}else if(date.hour>6 && date.hour< 12){
d = "上午";
}else if(date.hour>12 && date.hour < 18){
d = "下午";
}else if(date.hour>18 && date.hour < 24){
d = "晚上";
}
result= "$d ${date.hour}:${date.minute < 10 ? "0" + date.minute.toString() : date.minute}";
}else if(Utils.isYesterday(timeStamp)){
d = "昨天";
result= "$d ${date.hour}:${date.minute < 10 ? "0" + date.minute.toString() : date.minute}";
}else if(Utils.isBeforeYesterday(timeStamp)){
d = "前天";
result= "$d ${date.hour}:${date.minute < 10 ? "0" + date.minute.toString() : date.minute}";
}else{
result= "${date.year}${date.month < 10 ? "0" + date.month.toString() : date.month}${date.day < 10 ? "0" + date.day.toString() + "日" : date.day} ${date.hour}:${date.minute < 10 ? "0" + date.minute.toString() : date.minute}";
}
return result;
}
// 下载文件
static Future<String> downloadFile(String url, {DownloadProgress showDownloadProgress}) async {
try {
Response response = await Dio().get(
url,
onReceiveProgress: showDownloadProgress,
options: Options(
responseType: ResponseType.bytes,
followRedirects: false,
),
);
Directory tempDir = await getTemporaryDirectory();
String tempPath = tempDir.path;
String savePath = tempPath + '/'+ url.substring(url.lastIndexOf('/')+1, url.length);
File file = File(savePath);
var raf = file.openSync(mode: FileMode.write);
raf.writeFromSync(response.data);
await raf.close();
return savePath;
} catch (e) {
return null;
}
}
// 保存网络图片到相册
// static Future<bool> savedGallery(String url) async{
// String path = await Utils.downloadFile(url);
// if(path != null){
// ByteData bytes = await rootBundle.load(path);
// final filePath = await ImageSaver.toFile(
// fileData: Uint8List.view(bytes.buffer)
// );
// if(filePath != null){
// return true;
// }else{
// return false;
// }
// }else{
// return false;
// }
// }
}
import 'package:fluttertoast/fluttertoast.dart';
import 'package:image_picker/image_picker.dart';
import 'package:kefu_workbench/core_flutter.dart';
enum ToastPosition {
top,
bottom,
center
}
class UX {
// 显示loading
static Timer _timerLoading;
static void showLoading(BuildContext context, {String content = '请稍等...', Function onTimeOut}) {
_timerLoading?.cancel();
Navigator.of(context).push(PageRouteBuilder(
opaque: false,
pageBuilder: (_, __, ___) {
return MiniLoading(context: context, content: content);
}));
_timerLoading = Timer.periodic(Duration(milliseconds: 60000), (_){
_timerLoading?.cancel();
hideLoading(context);
if(onTimeOut != null) onTimeOut();
});
}
// 隐藏loading
static void hideLoading(BuildContext context) {
_timerLoading?.cancel();
Navigator.pop(context);
}
// 显示toast
static void showToast(String content, {ToastPosition position = ToastPosition.center}){
Fluttertoast.showToast(
msg: content,
toastLength: Toast.LENGTH_SHORT,
gravity: position == ToastPosition.center ? ToastGravity.CENTER : position == ToastPosition.top ? ToastGravity.TOP : ToastGravity.BOTTOM,
timeInSecForIos: 2,
backgroundColor: Color.fromRGBO(0, 0, 0, 0.8),
fontSize: ToPx.size(28)
);
}
// 撤销toast
static void cancelToast(){
Fluttertoast.cancel();
}
/// 普通询问弹窗
/// * [title] 标题
/// * [content] 内容
/// * [cancelText] 撤销文字 默认 = 取消
/// * [confirmText] 确定文字 默认 = 确定
static Future<bool> alert(context,{
String title = "温馨提示!",
dynamic content,
String cancelText = '取消',
String confirmText = '确定',
bool isConfirmPop = true,
VoidCallback onCancel,
VoidCallback onConfirm,
}) {
return showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
ThemeData themeData = Theme.of(context);
return CupertinoAlertDialog(
title: title.isEmpty ? null : Padding(
padding: EdgeInsets.only(bottom: ToPx.size(20)),
child: Text(title),
),
content: content is Widget ? content : Text('$content', style: TextStyle(height: 1.5),),
actions: <Widget>[
CupertinoDialogAction(
child: Text(cancelText, style: themeData.textTheme.title.copyWith(
color: themeData.errorColor
)),
isDestructiveAction: true,
onPressed: () {
Navigator.pop(context);
if (onCancel != null){
onCancel();
}
},
),
CupertinoDialogAction(
child: Text(
confirmText,
),
isDefaultAction: true,
onPressed: () {
if(isConfirmPop) Navigator.pop(context);
if (onConfirm != null) onConfirm();
},
),
],
);
});
}
// 图片预览
static void photoPreview(context,{List<String> images}){
Navigator.push(context, PageRouteBuilder(
barrierDismissible: true,
opaque: false,
pageBuilder: (context, _, __){
return PhotoPreview(images: images, context: context,);
}
));
}
// 图片选择 sheet
static Future<int> selectImageSheet(context) async {
ThemeData themeData = Theme.of(context);
var imageSource = await showCupertinoModalPopup(
context: context,
builder: (BuildContext context){
return CupertinoActionSheet(
actions: <Widget>[
CupertinoActionSheetAction(
child: Text('拍照', style: themeData.textTheme.body2),
onPressed: () {
Navigator.pop(context, 0); // ImageSource.camera
},
),
CupertinoActionSheetAction(
child: Text('打开相册', style: themeData.textTheme.body2),
onPressed: () {
Navigator.pop(context, 1); // ImageSource.gallery
},
),
CupertinoActionSheetAction(
child: Text('选择文件', style: themeData.textTheme.body2),
onPressed: () {
Navigator.pop(context, 2); // ImageSource.gallery
},
),
],
cancelButton: SizedBox(
child: CupertinoActionSheetAction(
isDestructiveAction: true,
child: Text('取消', style: themeData.textTheme.body2),
onPressed: () {
Navigator.pop(context, null);
},
),
)
);
}
);
return imageSource;
}
// 时间选择器 IOS 风格
static Future<String> selectDatePicker(context, {String oldDate, int maximumYear, DateTime maximumDate}) async{
DateTime initDate = oldDate != null ? DateTime.tryParse(oldDate) : DateTime.now();
var date = await Navigator.of(context).push(
PageRouteBuilder(
opaque: false,
barrierColor: Color.fromRGBO(0, 0, 0, 0.5),
barrierDismissible: true,
barrierLabel: 'route',
maintainState: false,
pageBuilder: (BuildContext ctx, Animation<double> _, Animation<double> __){
return DatePicker(
context: context,
maximumDate: maximumDate,
maximumYear: maximumYear != null ? maximumYear : DateTime.now().year,
initDate: initDate != null ? initDate : DateTime.now()
);
},
transitionDuration: Duration(milliseconds: 300),
transitionsBuilder: (BuildContext context,
Animation<double> animation,
Animation<double> secondaryAnimation,
Widget child,
) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(0.0, 1.0),
end: Offset.zero,
).animate(animation),
child: child,
);
}
)
);
if (date == null) return null;
final String month = date.month < 10 ? '0' + date.month.toString() : date.month.toString();
final String day = date.day < 10 ? '0' + date.day.toString() : date.day.toString();
final String selectedDate = date.year.toString() + '-' + month + '-' + day;
return selectedDate;
}
}
import 'package:dio/dio.dart';
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/provider/admin.dart';
import 'package:kefu_workbench/provider/global.dart';
class AdminDetailPage extends StatefulWidget {
final Map<dynamic, dynamic> arguments;
AdminDetailPage({this.arguments});
@override
_AdminDetailPageState createState() => _AdminDetailPageState();
}
class _AdminDetailPageState extends State<AdminDetailPage> {
AdminModel admin;
@override
void initState() {
super.initState();
admin = widget.arguments['admin'];
}
/// edit
void _goEdit(BuildContext context) async{
Navigator.pushNamed(context, "/admin_edit",arguments: {
"admin": admin
}).then((isSuccess) async{
if(isSuccess == true){
await AdminProvide.getInstance().getUser(admin.id);
admin = AdminProvide.getInstance().getItem(admin.id);
setState(() {});
}
});
}
Color lineColor(int online){
return online == 1 ? Colors.green[400] : online == 0 ? Colors.grey : online == 2 ? Colors.amber : Colors.grey;
}
/// delete
void _delete(BuildContext context){
UX.alert(
context,
content: Text("是否删除该客服吗!"),
onConfirm: () async{
Response response = await AdminService.getInstance().delete(id: admin.id);
if (response.data["code"] == 200) {
UX.showToast("删除成功");
AdminProvide.getInstance().deleteItem(admin.id);
Navigator.pop(context);
} else {
UX.showToast("${response.data["message"]}");
}
}
);
}
@override
Widget build(context) {
admin = AdminProvide.getInstance().getItem(admin.id);
return PageContext(builder: (context){
ThemeData themeData = Theme.of(context);
Widget _lineItem({
Widget label = const Text(""),
Widget icon = const Text(""),
Widget content = const Text(""),
TextStyle style,
Widget subChild = const SizedBox(),
CrossAxisAlignment contextCrossAxisAlignment = CrossAxisAlignment.start
}){
return Container(
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: themeData.dividerColor))
),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(20), vertical: ToPx.size(40)),
child: DefaultTextStyle(
style: style,
child: Row(
children: <Widget>[
icon,
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
crossAxisAlignment: contextCrossAxisAlignment,
children: <Widget>[
label,
Expanded(
child: content,
)
],
),
subChild
],),
)
],
)
)
);
}
return Scaffold(
backgroundColor: themeData.primaryColorLight,
appBar: customAppBar(
title: Text(
"${admin.nickname}",
style: themeData.textTheme.display1,
),
actions: [
Offstage(
offstage: GlobalProvide.getInstance().serviceUser.root != 1,
child: Button(
height: ToPx.size(90),
useIosStyle: true,
color: Colors.transparent,
width: ToPx.size(150),
child: Text("编辑"),
onPressed: () => _goEdit(context)
))
],
),
body: ListView(
children: <Widget>[
_lineItem(
icon: Avatar(
size: ToPx.size(100),
imgUrl: admin.avatar == null || admin.avatar.isEmpty ?
"http://qiniu.cmp520.com/avatar_default.png" : admin.avatar
),
content:Row(
children: <Widget>[
Text(" ${admin.nickname} "),
Text(
admin.online == 0 ? " 离线" :
admin.online == 1 ? " 在线" :
admin.online == 2 ? " 离开" : "未知",
style: themeData.textTheme.caption.copyWith(
color: lineColor(admin.online)
),),
],
),
style: themeData.textTheme.title,
contextCrossAxisAlignment: CrossAxisAlignment.center,
subChild: Row(
children: <Widget>[
Text(" 角色:", style: themeData.textTheme.caption),
Text("${admin.root ==1 ? "超级管理" : "客服专员"}", style: themeData.textTheme.caption),
],
)
),
_lineItem(
label: Text("客服ID:"),
content: Text(admin.id.toString()),
contextCrossAxisAlignment: CrossAxisAlignment.center,
style: themeData.textTheme.body1,
),
_lineItem(
label: Text("客服账号:"),
content: Text(admin.username),
contextCrossAxisAlignment: CrossAxisAlignment.center,
style: themeData.textTheme.body1,
),
_lineItem(
label: Text("联系电话:"),
content: Text(admin.phone ?? "未设置电话"),
contextCrossAxisAlignment: CrossAxisAlignment.center,
style: themeData.textTheme.body1,
),
_lineItem(
label: Text("自动回复语:"),
content: Text(admin.autoReply),
contextCrossAxisAlignment: CrossAxisAlignment.center,
style: themeData.textTheme.body1,
),
_lineItem(
label: Text("最后活动时间:"),
content: Text(Utils.epocFormat(admin.lastActivity)),
contextCrossAxisAlignment: CrossAxisAlignment.center,
style: themeData.textTheme.body1,
),
_lineItem(
label: Text("注册时间:"),
content: Text(Utils.formatDate(admin.createAt)),
contextCrossAxisAlignment: CrossAxisAlignment.center,
style: themeData.textTheme.body1,
),
Offstage(
offstage: GlobalProvide.getInstance().serviceUser.root != 1 || admin.root == 1,
child: Button(
margin: EdgeInsets.symmetric(horizontal: ToPx.size(40), vertical: ToPx.size(50)),
child: Text("删除"),
withAlpha: 200,
color: Colors.redAccent,
onPressed: () => _delete(context),
),
)
],
)
);
});
}
}
import 'package:dio/dio.dart';
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/provider/global.dart';
class AdminEditPage extends StatefulWidget {
final Map<dynamic, dynamic> arguments;
AdminEditPage({this.arguments});
@override
_AdminEditPagePageState createState() => _AdminEditPagePageState();
}
class _AdminEditPagePageState extends State<AdminEditPage> {
AdminModel admin;
String avatar = "";
bool isEdit = false;
TextEditingController nicknameCtr;
TextEditingController usernameCtr;
TextEditingController phoneCtr;
TextEditingController autoReplyCtr;
TextEditingController passwordCtr;
TextEditingController passwordYesCtr;
/// 选择图片上传
void _pickerImage() async{
String imgUser = await uploadImage<String>(context, maxWidth: 300.0);
if(imgUser == null) return;
avatar = imgUser;
setState(() {});
}
/// save
void _saveAdmin() async{
String nickname = nicknameCtr.value.text.trim();
String username = usernameCtr.value.text.trim();
AdminModel user = AdminModel(
nickname: nickname,
phone: phoneCtr.value.text.trim(),
autoReply: autoReplyCtr.value.text.trim(),
id: admin?.id ?? null,
avatar: avatar,
password: null,
username: username
);
Map useMap = user.toJson();
/// 判断昵称不能为空
if(nickname.isEmpty || nickname == ""){
UX.showToast("昵称不能为空");
return;
}
FocusScope.of(context).requestFocus(FocusNode());
UX.showLoading(context, content: "保存中...");
Response response;
if(isEdit){
useMap.removeWhere((key, value) => value == null);
response = await AdminService.getInstance().saveAdminInfo(useMap);
}else{
String password = passwordCtr.value.text.trim();
String passwordYes = passwordCtr.value.text.trim();
if(password.isEmpty || password == ""){
UX.showToast("请输入密码");
return;
}
if(passwordYes.isEmpty || passwordYes == ""){
UX.showToast("请再次输入密码");
return;
}
if(passwordYes != password){
UX.showToast("两次密码不一致");
return;
}
useMap["password"] = password;
useMap.removeWhere((key, value) => value == null);
printf("useMapuseMapuseMap===$useMap");
response = await AdminService.getInstance().add(useMap);
}
UX.hideLoading(context);
if(response.statusCode == 200){
UX.showToast("保存成功");
Navigator.pop(context, true);
GlobalProvide.getInstance().getMe();
}else{
UX.showToast(response.data["message"]);
}
}
@override
void initState() {
super.initState();
if(mounted && widget.arguments != null){
isEdit = true;
admin = (widget.arguments['admin'] as AdminModel);
usernameCtr = TextEditingController(text: admin.username);
nicknameCtr = TextEditingController(text: admin.nickname);
phoneCtr = TextEditingController(text: admin.phone);
autoReplyCtr = TextEditingController(text: admin.autoReply);
avatar = admin.avatar;
}else{
nicknameCtr = TextEditingController();
usernameCtr = TextEditingController();
phoneCtr = TextEditingController();
autoReplyCtr = TextEditingController();
passwordCtr = TextEditingController();
passwordYesCtr = TextEditingController();
}
}
@override
void dispose() {
nicknameCtr?.dispose();
autoReplyCtr?.dispose();
usernameCtr?.dispose();
passwordCtr?.dispose();
phoneCtr?.dispose();
passwordYesCtr?.dispose();
super.dispose();
}
@override
Widget build(_) {
return PageContext(builder: (context){
ThemeData themeData = Theme.of(context);
Widget _fromInput({
String label,
TextEditingController controller,
String placeholder,
bool enabled = true,
bool autofocus = false,
}){
return Container(
height: ToPx.size(90),
decoration: BoxDecoration(
color: Colors.white,
border: Border(bottom: BorderSide(color: themeData.dividerColor,width: ToPx.size(2)))
),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(40)),
child: Row(
children: <Widget>[
Text("$label", style: themeData.textTheme.title,),
Expanded(
child: Input(
enabled: enabled,
autofocus: autofocus,
border: Border.all(style: BorderStyle.none, color: Colors.transparent),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(10)),
placeholder: "$placeholder",
showClear: true,
controller: controller,
),
)
],
),
);
}
return Scaffold(
appBar: customAppBar(
title: Text(
isEdit ? "编辑客服" : "添加客服",
style: themeData.textTheme.display1,
)),
body: ListView(
children: <Widget>[
SizedBox(
height: ToPx.size(40),
),
Container(
width: double.infinity,
color: Colors.white,
padding: EdgeInsets.symmetric(vertical: ToPx.size(30)),
child: Stack(
children: <Widget>[
Center(
child: Avatar(
size: ToPx.size(150),
imgUrl: avatar.isEmpty ? "http://qiniu.cmp520.com/avatar_default.png" : avatar,
onPressed: _pickerImage
)
),
Center(
child: Padding(
padding: EdgeInsets.only(top: ToPx.size(120), left: ToPx.size(80)),
child: Icon(Icons.camera_alt, color:themeData.primaryColor.withAlpha(150), size: ToPx.size(35),),
)
)
],
),
),
_fromInput(
label: "客服账号:",
placeholder: "请输入客服账号",
autofocus: !isEdit,
enabled: !isEdit,
controller: usernameCtr
),
_fromInput(
label: "客服昵称:",
placeholder: "请输入昵称",
autofocus: isEdit,
controller: nicknameCtr
),
Offstage(
offstage: isEdit,
child: _fromInput(
label: "登录密码:",
placeholder: "请输入密码",
autofocus: true,
controller: passwordCtr
)),
Offstage(
offstage: isEdit,
child: _fromInput(
label: "确认密码:",
placeholder: "请再次输入确认密码",
autofocus: true,
controller: passwordYesCtr
)),
Offstage(
offstage: !isEdit,
child: _fromInput(
label: "联系方式:",
placeholder: "请输入联系方式",
controller: phoneCtr
)),
Offstage(
offstage: !isEdit,
child: Container(
height: ToPx.size(250),
decoration: BoxDecoration(
color: Colors.white,
border: Border(bottom: BorderSide(color: themeData.dividerColor,width: ToPx.size(2)))
),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(40), vertical: ToPx.size(10)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("自动回复语:", style: themeData.textTheme.title,),
Expanded(
child: Input(
border: Border.all(style: BorderStyle.none, color: Colors.transparent),
placeholder: "请输入自动回复语",
controller: autoReplyCtr,
textAlign: TextAlign.start,
minLines: 5,
maxLength: 100,
placeholderAlignment: Alignment.topLeft,
textInputAction: TextInputAction.newline,
maxLines: 5,
),
)
],
),
),),
Button(
margin: EdgeInsets.symmetric(horizontal: ToPx.size(40), vertical: ToPx.size(80)),
onPressed: _saveAdmin,
withAlpha: 200,
child: Text("保存"),
)
],
)
);
});
}
}
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/provider/admin.dart';
import 'package:kefu_workbench/provider/global.dart';
import 'package:provider/provider.dart';
class AdminsPage extends StatelessWidget {
final Map<dynamic, dynamic> arguments;
AdminsPage({this.arguments});
@override
Widget build(_) {
return ChangeNotifierProvider<AdminProvide>(
create: (_) => AdminProvide.getInstance(),
child: Consumer<AdminProvide>(builder: (context, adminState, _){
return PageContext(builder: (context){
ThemeData themeData = Theme.of(context);
Color lineColor(int online){
return online == 1 ? Colors.green[400] : online == 0 ? Colors.grey : online == 2 ? Colors.amber : Colors.grey;
}
return Scaffold(
backgroundColor: themeData.primaryColorLight,
appBar: customAppBar(
title: Text(
"客服列表(${adminState.usersTotal}人)",
style: themeData.textTheme.display1,
),
actions: [
Offstage(
offstage: GlobalProvide.getInstance()?.serviceUser?.root != 1,
child: Button(
height: ToPx.size(90),
useIosStyle: true,
color: Colors.transparent,
width: ToPx.size(150),
child: Text("新增"),
onPressed: () => adminState.goAdd(context)
)),
],
),
body: adminState.isLoading && adminState.admins.length == 0 ? Center(
child: loadingIcon(size: ToPx.size(50)),
):
RefreshIndicator(
color: themeData.primaryColorLight,
backgroundColor: themeData.primaryColor,
onRefresh: adminState.onRefresh,
child: CustomScrollView(
scrollDirection: Axis.vertical,
physics: AlwaysScrollableScrollPhysics(),
controller: adminState.scrollController,
slivers: <Widget>[
SliverToBoxAdapter(
child: Column(children: <Widget>[
Container(
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: themeData.dividerColor, width: ToPx.size(2)))
),
child: Row(
children: <Widget>[
Expanded(
child: Input(
onEditingComplete: adminState.onSearch,
textInputAction: TextInputAction.done,
controller: adminState.searchTextEditingController,
border: Border.all(style: BorderStyle.none, color: Colors.transparent),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(20)),
placeholder: "请输入关键词查找客服~",
onChanged: (value){
if(value.trim().isEmpty){
adminState.isLoadEnd = false;
adminState.pageOn = 0;
adminState.keyword = "";
adminState.getAdmins();
}
},
),
),
IconButton(
icon: Icon(CupertinoIcons.search, color: Colors.grey,),
onPressed: adminState.onSearch,
)
],
),
)
],),
),
SliverToBoxAdapter(
child: Offstage(
offstage: adminState.admins.length > 0 || adminState.isLoading,
child: Padding(
padding: EdgeInsets.only(top: ToPx.size(50)),
child: Text("暂无数据~", style: themeData.textTheme.body1, textAlign: TextAlign.center,),
),
),
),
SliverList(
delegate: SliverChildBuilderDelegate((context, index){
AdminModel admin = adminState.admins[index];
return Column(
children: <Widget>[
ListTile(
onTap: () => Navigator.pushNamed(context, "/admin_detail",arguments: {
"admin": admin
}),
subtitle: Row(
children: <Widget>[
Text("最后在线时间:", style: themeData.textTheme.caption),
Text("${Utils.epocFormat(admin.lastActivity)}")
],
),
trailing: Text(
admin.online == 0 ? " 离线" :
admin.online == 1 ? " 在线" :
admin.online == 2 ? " 离开" : "未知",
style: themeData.textTheme.caption.copyWith(
fontSize: ToPx.size(22),
color: lineColor(admin.online)
),),
leading: Avatar(
size: ToPx.size(100),
imgUrl: admin.avatar == null || admin.avatar.isEmpty ?
"http://qiniu.cmp520.com/avatar_default.png" : admin.avatar
),
title: Text("${admin.nickname}", style: themeData.textTheme.title, maxLines: 2, overflow: TextOverflow.ellipsis,),
),
Divider(height: 1.0,)
],
);
},
childCount: adminState.admins.length
),
),
SliverToBoxAdapter(
child: Offstage(
child: Center(
child: SizedBox(
height: ToPx.size(150),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
loadingIcon(),
Text(' 内容加载中...',
style: themeData.textTheme.caption)
],
),
),
),
offstage: !adminState.isLoading || adminState.isLoadEnd
)
),
SliverToBoxAdapter(
child: Offstage(
child: Padding(
padding: EdgeInsets.symmetric(vertical: ToPx.size(40)),
child: Center(
child: Text(
'没有更多了', style: themeData.textTheme.caption)
),),
offstage: !adminState.isLoadEnd || adminState.admins.length == 0
)
),
],
)
),
);
});
},),
);
}
}
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/provider/global.dart';
import 'package:kefu_workbench/provider/login.dart';
import 'package:provider/provider.dart';
class LoginPage extends StatefulWidget {
final Map<dynamic, dynamic> arguments;
LoginPage({this.arguments});
@override
_LoginPageState createState() => _LoginPageState();
}
class _LoginPageState extends State<LoginPage> {
LoginProvide loginProvide;
@override
void initState() {
super.initState();
if (mounted) {
loginProvide = LoginProvide();
var prefs = GlobalProvide.getInstance().prefs;
String account = prefs.getString("account");
String host = prefs.getString("host");
loginProvide.hostCtr = TextEditingController(text: host);
loginProvide.accountCtr = TextEditingController(text: account);
String password = prefs.getString("password");
if (password != null) {
loginProvide.setIsSavePassword(true);
}
loginProvide.passwordCtr = TextEditingController(text: password ?? "");
setState(() {});
}
}
@override
void dispose() {
loginProvide?.dispose();
super.dispose();
}
@override
Widget build(_) {
return ChangeNotifierProvider<LoginProvide>.value(
value: loginProvide,
child: Builder(
builder: (context) {
return PageContext(builder: (context) {
GlobalProvide.getInstance().setRooContext(context);
ThemeData themeData = Theme.of(context);
LoginProvide loginState = Provider.of<LoginProvide>(context);
return Scaffold(
backgroundColor: themeData.primaryColor,
appBar: customAppBar(
isShowLeading: false,
leading: "",
title: Text(
"用户登录",
style: themeData.textTheme.display1,
)),
body: CustomScrollView(
slivers: <Widget>[
SliverPadding(
padding: EdgeInsets.symmetric(horizontal: ToPx.size(60)),
sliver: SliverToBoxAdapter(
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.only(top: ToPx.size(100)),
child: Icon(
IconData(0xe674, fontFamily: 'IconFont'),
color: Colors.white,
size: ToPx.size(150),
),
),
Container(
margin: EdgeInsets.only(top: ToPx.size(100)),
padding: EdgeInsets.symmetric(
horizontal: ToPx.size(20)),
height: ToPx.size(90),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(
Radius.circular(ToPx.size(10)))),
child: Row(
children: <Widget>[
Icon(
Icons.link,
color: Colors.grey[400],
size: ToPx.size(35),
),
Expanded(
child: Input(
key: Key("hostCtr"),
controller: loginState.hostCtr,
padding:
EdgeInsets.only(left: ToPx.size(20)),
border: Border.all(
width: 0.0,
color: Colors.transparent),
placeholder: "请输服务器地址~",
),
)
],
),
),
Container(
margin: EdgeInsets.only(top: ToPx.size(20)),
padding: EdgeInsets.symmetric(
horizontal: ToPx.size(20)),
height: ToPx.size(90),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(
Radius.circular(ToPx.size(10)))),
child: Row(
children: <Widget>[
Icon(
IconData(0xe60d, fontFamily: 'IconFont'),
color: Colors.grey[400],
size: ToPx.size(35),
),
Expanded(
child: Input(
key: Key("accountCtr"),
controller: loginState.accountCtr,
padding:
EdgeInsets.only(left: ToPx.size(20)),
border: Border.all(
width: 0.0,
color: Colors.transparent),
placeholder: "请输入客服账号~",
),
)
],
),
),
Container(
margin: EdgeInsets.only(top: ToPx.size(20)),
padding: EdgeInsets.symmetric(
horizontal: ToPx.size(20)),
height: ToPx.size(90),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(
Radius.circular(ToPx.size(10)))),
child: Row(
children: <Widget>[
Icon(
IconData(0xe62a, fontFamily: 'IconFont'),
color: Colors.grey[400],
size: ToPx.size(35),
),
Expanded(
child: Input(
key: Key("passwordCtr"),
obscureText: true,
controller: loginState.passwordCtr,
padding:
EdgeInsets.only(left: ToPx.size(20)),
border: Border.all(
width: 0.0,
color: Colors.transparent),
placeholder: "请输入密码~",
),
)
],
),
),
Padding(
padding:
EdgeInsets.symmetric(vertical: ToPx.size(10)),
child: Row(
children: <Widget>[
Text(
"保存登录密码",
style: themeData.textTheme.caption,
),
Platform.isAndroid
? Switch(
value: loginState.isSavePassword,
onChanged: (bool isSwitch) {
loginState
.setIsSavePassword(isSwitch);
},
activeColor: Colors.black)
: Transform.scale(
scale: .7,
child: CupertinoSwitch(
value: loginState.isSavePassword,
onChanged: (bool isSwitch) {
loginState.setIsSavePassword(
isSwitch);
},
activeColor: Colors.black)),
],
),
),
Button(
withAlpha: 230,
margin: EdgeInsets.only(top: ToPx.size(80)),
height: ToPx.size(90),
color: Colors.black,
onPressed: () => loginState.login(context),
child: Text("登录"),
)
],
),
),
)
],
),
);
});
},
));
}
}
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/provider/chat.dart';
import 'package:kefu_workbench/provider/global.dart';
import 'package:provider/provider.dart';
import 'widget/bottom_bar.dart';
import 'widget/emoji_panel.dart';
import 'widget/end_drawer.dart';
import 'widget/knowledge_message.dart';
import 'widget/photo_message.dart';
import 'widget/shortcut_panel.dart';
import 'widget/system_message.dart';
import 'widget/text_message.dart';
import 'widget/transfer_panel.dart';
class ChatPage extends StatelessWidget {
final Map<dynamic, dynamic> arguments;
ChatPage({this.arguments});
void openDrawer(context) {
Scaffold.of(context).openEndDrawer();
}
@override
Widget build(_) {
bool isReadOnly = arguments['isReadOnly'] ?? false;
int accountId = arguments['accountId'];
int serviceId = arguments['serviceId'];
int userId = arguments['userId'];
String title = arguments['title'];
ChatProvide chatProvide = ChatProvide.getInstance(isReadOnly: isReadOnly, accountId: accountId, serviceId: serviceId, userId: userId);
return ChangeNotifierProvider<ChatProvide>(
create: (_) => chatProvide,
child: Consumer<ChatProvide>(
builder: (context, chatState , _){
return PageContext(builder: (context){
ThemeData themeData = Theme.of(context);
return Consumer<GlobalProvide>(
builder: (context, globalState, _){
List<ImMessageModel> messagesRecords = globalState.currentUserMessagesRecords(chatState.accountId);
return Scaffold(
appBar: customAppBar(
title: Text(
title ?? (globalState.isPong ? "对方正在输入..." : "${globalState.currentContact?.nickname}"),
style: themeData.textTheme.display1,
),
actions: [
Offstage(
offstage:chatState.isReadOnly || globalState.currentContact == null || globalState.currentContact?.isSessionEnd == 1,
child: Button(
useIosStyle: true,
width: ToPx.size(110),
color: Colors.transparent,
onPressed: () => chatState.onShowEndMessageAlert(context),
child: Text("结束会话", style: themeData.textTheme.caption.copyWith(
color: Colors.amber
),),
),
),
Offstage(
offstage: chatState.isReadOnly,
child: Builder(
builder: (ctx) {
return IconButton(
icon: Icon(Icons.person_pin),
onPressed: ()=>openDrawer(ctx),
);
})
)
]
),
body: Column(children: <Widget>[
Expanded(
child: GestureDetector(
onPanDown: (_) {
chatState.onToggleShortcutPanel(false);
chatState.onHideEmoJiPanel();
chatState.onToggleTransferPanel(false);
FocusScope.of(context).requestFocus(FocusNode());
},
child:
chatState.isChatFullLoading ?
Center(
child: loadingIcon(size: ToPx.size(50)),
) :
CustomScrollView(
controller: chatState.scrollController,
reverse: true,
slivers: <Widget>[
SliverPadding(
padding: EdgeInsets.symmetric(
horizontal: 10.0, vertical: 20.0),
sliver: SliverList(
delegate: SliverChildBuilderDelegate((ctx, i) {
int index = messagesRecords.length - i - 1;
ImMessageModel _msg = messagesRecords[index];
/// 判断是否需要显示时间
if (i == messagesRecords.length - 1 || (_msg.timestamp - 120) > messagesRecords[index - 1].timestamp) {
_msg.isShowDate = true;
}
switch (_msg.bizType) {
case "text":
case "welcome":
return TextMessage(
message: _msg,
isSelf: _msg.fromAccount != (accountId ?? globalState.currentContact.fromAccount),
onCancel: () => chatState.onCancelMessage(_msg),
onOperation: () => chatState.onOperation(context, _msg),
);
case "photo":
return PhotoMessage(
message: _msg,
isSelf:
_msg.fromAccount == globalState.serviceUser.id,
onCancel: () => chatState.onCancelMessage(_msg),
onOperation: () => chatState.onOperation(context, _msg),
);
case "end":
case "transfer":
case "cancel":
case "timeout":
case "system":
return SystemMessage(
message: _msg,
isSelf: _msg.fromAccount == globalState.serviceUser.id,
);
case "knowledge":
return KnowledgeMessage(
message: _msg,
onOperation: () => chatState.onOperation(context, _msg),
);
default:
return SizedBox();
}
}, childCount: messagesRecords.length)),
),
SliverToBoxAdapter(
child: Offstage(
offstage: !globalState.isLoadingMorRecord,
child: Padding(
padding: EdgeInsets.symmetric(vertical: 10.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Platform.isAndroid
? SizedBox(
width: 10.0,
height: 10.0,
child: CircularProgressIndicator(
strokeWidth: 2.0,
))
: CupertinoActivityIndicator(),
Text(
" 加载更多",
style: TextStyle(color: Colors.black38),
)
],
),
),
),
),
],
),
),
),
chatState.isReadOnly ?
SizedBox() :
Container(
color: Colors.white,
child: SafeArea(
top: false,
child: Column(
children: <Widget>[
Offstage(
offstage: globalState.advanceText.isEmpty || globalState.advanceText == "",
child: Container(
width: double.infinity,
color: Colors.grey.withAlpha(80),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(20), vertical: ToPx.size(20)),
child: Text("客户输入:${globalState.advanceText}...", style: themeData.textTheme.caption,),
),
),
BottomBar(
editingController: chatState.editingController,
focusNode: chatState.focusNode,
onSubmit: chatState.onSubmit,
isShowEmoJiPanel: chatState.isShowEmoJiPanel,
isShowShortcutPanel: chatState.isShowShortcutPanel,
onShowEmoJiPanel: chatState.onShowEmoJiPanel,
onHideEmoJiPanel: chatState.onHideEmoJiPanel,
onInputChanged: chatState.onInputChanged,
onPickrImage: () => chatState.onPickImage(context),
enabled: globalState.currentContact.isSessionEnd == 0,
onToggleShortcutPanel: () => chatState.onToggleShortcutPanel(!chatState.isShowShortcutPanel),
onToggleTransferPanel: () => chatState.onToggleTransferPanel(!chatState.isShowTransferPanel),
),
EmoJiPanel(
isShow: chatState.isShowEmoJiPanel,
onSelected: (value){
chatState.editingController.text = chatState.editingController.value.text + value;
chatState.editingController.selection =
TextSelection.collapsed(offset: chatState.editingController.value.text.length);
},
),
ShortcutPanel(
onSelected: chatState.onSelectedShortcut,
listData: globalState.shortcuts,
isShow: chatState.isShowShortcutPanel,
),
TransferPanel(
onSelected: chatState.onSelectedSeviceUser,
listData: chatState.serviceOnlineUsers,
isShow: chatState.isShowTransferPanel,
),
],
),
),
)
],),
endDrawer: globalState.currentContact == null || chatState.isReadOnly ? null : Drawer(
child: ChatEndDrawer(),
),
);
});
});
},
));
}
}
import 'package:kefu_workbench/core_flutter.dart';
class BottomBar extends StatelessWidget{
BottomBar({
this.isShowEmoJiPanel = false,
this.onHideEmoJiPanel,
this.onShowEmoJiPanel,
this.onPickrImage,
this.focusNode,
this.editingController,
this.onSubmit,
this.onInputChanged,
this.enabled = true,
this.onToggleTransferPanel,
this.onToggleShortcutPanel,
this.isShowShortcutPanel,
});
final bool enabled;
final bool isShowEmoJiPanel;
final bool isShowShortcutPanel;
final VoidCallback onHideEmoJiPanel;
final VoidCallback onShowEmoJiPanel;
final VoidCallback onToggleTransferPanel;
final VoidCallback onToggleShortcutPanel;
final VoidCallback onPickrImage;
final FocusNode focusNode;
final TextEditingController editingController;
final VoidCallback onSubmit;
final ValueChanged<String> onInputChanged;
void _onSubmit(BuildContext context) {
onSubmit();
if(isShowEmoJiPanel) return;
if(isShowShortcutPanel) return;
FocusScope.of(context).requestFocus(focusNode);
}
@override
Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context);
return Container(
width: double.infinity,
padding: EdgeInsets.symmetric(horizontal: ToPx.size(20), vertical:ToPx.size(10),),
decoration: BoxDecoration(
color: Colors.white,
border: Border(
top: BorderSide(color: Colors.grey.withAlpha(60), width: .5),
bottom: BorderSide(
color: Colors.grey.withAlpha(isShowEmoJiPanel ? 60 : 0),
width: .5),
)),
constraints: BoxConstraints(
minHeight: 80.0,
),
child: Column(
children: <Widget>[
SizedBox(
child: Row(
children: <Widget>[
GestureDetector(
child: Padding(
padding: EdgeInsets.all(3.0),
child: Icon(
Icons.insert_emoticon,
color: Colors.black26,
size: ToPx.size(60),
),
),
onTap: isShowEmoJiPanel
? onHideEmoJiPanel
: onShowEmoJiPanel),
GestureDetector(
child: Container(
padding: EdgeInsets.all(3.0),
child: Icon(
Icons.image,
color: Colors.black26,
size: ToPx.size(60),
),
),
onTap: onPickrImage,
),
GestureDetector(
child: Container(
padding: EdgeInsets.all(3.0),
child: Icon(
Icons.assignment,
color: Colors.black26,
size: ToPx.size(58),
),
),
onTap: onToggleShortcutPanel,
),
GestureDetector(
child: Container(
padding: EdgeInsets.all(3.0),
child: Icon(
Icons.swap_horizontal_circle,
color: Colors.black26,
size: ToPx.size(58),
),
),
onTap: onToggleTransferPanel,
),
],
),
),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
child: Container(
constraints: BoxConstraints(minHeight: ToPx.size(80)),
padding: EdgeInsets.symmetric(horizontal:ToPx.size(10)),
child: Input(
placeholder: enabled ? "请输入内容~" : "当前会话已结束~",
focusNode: focusNode,
controller: editingController,
minLines: 1,
enabled: enabled,
maxLines: 5,
maxLength: 200,
border: Border.all(style: BorderStyle.none, width: 0.0),
onEditingComplete: () => _onSubmit(context),
textInputAction: Platform.isIOS
? TextInputAction.send
: TextInputAction.newline,
onChanged: (String value) {
onInputChanged(value);
},
))),
Offstage(
offstage: (Platform.isIOS && !isShowEmoJiPanel) && (Platform.isIOS && !isShowShortcutPanel),
child: Center(
child: SizedBox(
width: 60.0,
child: Button(
width: ToPx.size(100),
height: ToPx.size(60),
color: Theme.of(context).primaryColor,
onPressed: () => _onSubmit(context),
child: Text(
"发送",
style: themeData.textTheme.title.copyWith(
color: themeData.primaryColorLight
)
),
),
),
),
)
],
)
],
),
);
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:kefu_workbench/core_flutter.dart';
class DateWidget extends StatelessWidget {
DateWidget({this.date});
final int date;
@override
Widget build(BuildContext context) {
return Container(
height: ToPx.size(50),
margin: EdgeInsets.symmetric(vertical: ToPx.size(20)),
constraints: BoxConstraints(
minWidth: 200.0,
),
alignment: Alignment.center,
child: Text(
"${Utils.epocFormat(date)}",
style: TextStyle(color: Colors.black38),
),
);
}
}
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/resources/emoji.dart';
typedef EmoJiPanelCallback(String emoji);
class EmoJiPanel extends StatelessWidget {
EmoJiPanel({this.isShow = false, this.onSelected});
final bool isShow;
final EmoJiPanelCallback onSelected;
@override
Widget build(BuildContext context) {
return Offstage(
offstage: !isShow,
child: Container(
width: double.infinity,
height: ToPx.size(500),
color: Colors.white,
child: GridView.builder(
itemCount: emojis.length,
padding: EdgeInsets.all(6.0),
gridDelegate:
SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 11),
itemBuilder: (ctx, index) {
return InkWell(
onTap: () => onSelected(emojis[index]),
child: Text(
"${emojis[index]}",
style: TextStyle(fontSize: Platform.isAndroid ? 25 : 30),
),
);
}),
),
);
}
}
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/provider/global.dart';
import 'package:provider/provider.dart';
class ChatEndDrawer extends StatelessWidget {
@override
Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context);
GlobalProvide globalState = Provider.of<GlobalProvide>(context);
ContactModel currentContact = globalState.currentContact;
Color lineColor = currentContact?.online == 1 ? Colors.green[400] : Colors.grey;
Widget _listTile({
String title,
String context
}){
return Container(
padding: EdgeInsets.only(left: ToPx.size(40), right: ToPx.size(20), top: ToPx.size(20), bottom: ToPx.size(20)),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("$title", style: themeData.textTheme.title.copyWith(
color: Colors.white
),),
Expanded(
child: Text("$context", style: themeData.textTheme.title.copyWith(
color: Colors.white
),),
)
],
),
);
}
Widget _userInfo(){
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
width: double.infinity,
padding: EdgeInsets.only(top: ToPx.size(30), bottom: ToPx.size(30)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Avatar(
size: ToPx.size(130),
imgUrl: currentContact == null || currentContact.avatar.isEmpty ?
"http://qiniu.cmp520.com/avatar_default.png" : currentContact.avatar,
),
Padding(
padding: EdgeInsets.only(top: ToPx.size(20)),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text("${currentContact?.nickname} ", style: themeData.textTheme.display1,),
Container(
width: ToPx.size(10),
height: ToPx.size(10),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: lineColor
),
),
Text(
currentContact?.online == 0 ? " 离线" : " 在线",
style: themeData.textTheme.caption.copyWith(
fontSize: ToPx.size(22),
color: lineColor
),
),
],
)
)
],
)
),
Divider(color: Colors.black.withAlpha(50)),
Expanded(
child: Column(
children: <Widget>[
_listTile(
title: " 用户 ID:",
context: "${currentContact?.fromAccount}"
),
_listTile(
title: "所在地区:",
context: "${currentContact.address.isEmpty ? "未知的位置信息" : currentContact.address}"
),
_listTile(
title: "联系方式:",
context: "${currentContact.phone.isEmpty ? "暂无联系方式" : currentContact.phone}"
),
_listTile(
title: "所在平台:",
context: "${globalState.getPlatformTitle(currentContact.platform)}"
),
_listTile(
title: "创建时间:",
context: "${Utils.epocFormat(currentContact?.createAt)}"
),
_listTile(
title: "备注信息:",
context: "${currentContact.remarks.isEmpty ? "暂无备注信息" : currentContact?.remarks}"
),
Expanded(
child: SizedBox(
width: double.infinity,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Button(
margin: EdgeInsets.only(bottom: ToPx.size(50)),
color: themeData.primaryColorDark,
width: ToPx.size(220),
height: ToPx.size(60),
onPressed: () => Navigator.popAndPushNamed(context, "/user_edit", arguments: {
"user": UserModel.fromJson(currentContact.toJson())
}),
child: Text(
"编辑用户信息",
style: themeData.textTheme.title
.copyWith(color: themeData.primaryColorLight),
),
)
],
),
),
)
],
),
)
]
);
}
return Container(
color: themeData.primaryColor,
padding: EdgeInsets.only(top: ToPx.size(30)),
child: DefaultTabController(
length: 1,
child: Column(
children: <Widget>[
SizedBox(
height: ToPx.size(70),
child: TabBar(
labelColor: Colors.amber,
isScrollable: true,
labelStyle: themeData.textTheme.caption.copyWith(color: themeData.primaryColor, fontWeight: FontWeight.w500),
unselectedLabelColor: themeData.accentColor,
indicatorWeight: ToPx.size(3),
tabs: <Widget>[
Tab(
child: Text("用户信息"),
),
],
),),
Divider(height: 0.0,color: Colors.black.withAlpha(50)),
Expanded(
child: TabBarView(
physics: BouncingScrollPhysics(),
children: <Widget>[
_userInfo(),
],),
)
],
)
),
);
}
}
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:kefu_workbench/core_flutter.dart';
import 'date_widget.dart';
typedef SendKnowledgeMessage(KnowledgeModel message);
class KnowledgeMessage extends StatelessWidget {
KnowledgeMessage({this.message, this.onOperation});
final ImMessageModel message;
final VoidCallback onOperation;
List<KnowledgeModel> get knowledgeModelList =>
((json.decode(message.payload) as List)
.map((i) => KnowledgeModel.fromJson(i))
.toList());
@override
Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context);
Widget msgWidget() {
return Container(
margin: EdgeInsets.only(bottom: ToPx.size(30)),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Padding(
padding: EdgeInsets.only(right: ToPx.size(15)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
margin: EdgeInsets.only(top: ToPx.size(8)),
width: ToPx.size(550),
padding:
EdgeInsets.symmetric(horizontal: ToPx.size(20), vertical: ToPx.size(10)),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
offset: Offset(0.0, 3.0),
color: Colors.black26.withAlpha(5),
blurRadius: 4.0,
),
BoxShadow(
offset: Offset(0.0, 3.0),
color: Colors.black26.withAlpha(5),
blurRadius: 4.0,
),
],
borderRadius: BorderRadius.all(Radius.circular(5))),
child: GestureDetector(
onLongPress: onOperation,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("以下是您关心的相关问题?", style: themeData.textTheme.title),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children:
knowledgeModelList.map((KnowledgeModel item) {
return DefaultTextStyle(
style: themeData.textTheme.body1,
child: Padding(
padding:
EdgeInsets.symmetric(vertical: 2.0),
child: Row(
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.only(top: 3.0),
child: Text(
" • ",
style: TextStyle(
fontWeight: FontWeight.w600),
),
),
Expanded(
child: Text("${item.title}"),
)
],
),
));
}).toList(),
),
],
),
),
)
],
),
),
Avatar(
imgUrl: message.avatar,
),
],
),
);
}
return Column(
children: <Widget>[
Offstage(
offstage: !message.isShowDate,
child: DateWidget(
date: message.timestamp,
),
),
msgWidget()
],
);
}
}
import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_advanced_networkimage/zoomable.dart';
import 'package:kefu_workbench/core_flutter.dart';
import 'date_widget.dart';
class PhotoMessage extends StatelessWidget {
PhotoMessage({this.message, this.onCancel, this.onOperation, this.isSelf});
final ImMessageModel message;
final VoidCallback onCancel;
final VoidCallback onOperation;
final bool isSelf;
@override
Widget build(BuildContext context) {
Widget _avatar(bool show) {
return Offstage(
offstage: !show,
child: Avatar(
imgUrl: message.avatar,
));
}
Widget _loading() {
return SizedBox(
width: 10.0,
height: 10.0,
child: Platform.isAndroid
? CircularProgressIndicator(
strokeWidth: 2.0,
)
: CupertinoActivityIndicator(
radius: 8.0,
),
);
}
Widget _state() {
return Offstage(
offstage: !isSelf,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Offstage(
offstage: !message.isShowCancel,
child: GestureDetector(
onTap: onCancel,
child: Text(" 撤回 ", style: TextStyle(color: Colors.blue)),
),
),
Offstage(
offstage:
message.uploadProgress == 0 || message.uploadProgress == 100,
child: Row(
children: <Widget>[
_loading(),
Text(
" ${message.uploadProgress}% ",
style: TextStyle(color: Colors.black26),
),
],
),
)
],
),
);
}
Widget msgWidget() {
return Container(
margin: EdgeInsets.only(bottom: ToPx.size(30)),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment:
isSelf ? MainAxisAlignment.end : MainAxisAlignment.start,
children: <Widget>[
_avatar(!isSelf),
Padding(
padding: EdgeInsets.only(
left: isSelf ? 0 : ToPx.size(15), right: isSelf ? ToPx.size(15) : 0),
child: Column(
crossAxisAlignment:
isSelf ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: <Widget>[
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
_state(),
Hero(
tag: message.payload,
child: GestureDetector(
onLongPress: onOperation,
onTap: () {
showDialog(
context: context,
builder: (context) {
return Center(
child: ZoomableWidget(
onTap: () => Navigator.pop(context),
maxScale: 2.0,
minScale: 0.5,
child: CachedNetworkImage(
width: MediaQuery.of(context)
.size
.width /
1,
height: MediaQuery.of(context)
.size
.height /
1,
src: message.payload,
bgColor: Colors.transparent,
fit: BoxFit.fitWidth),
));
});
},
child: Container(
margin: EdgeInsets.only(top: 3.0),
constraints: BoxConstraints(
maxWidth: ToPx.size(400),
),
child: ClipRRect(
borderRadius:
BorderRadius.all(Radius.circular(5.0)),
child: CachedNetworkImage(
width: ToPx.size(300),
bgColor: Colors.white,
src: message.payload,
fit: BoxFit.fitWidth)),
))),
],
)
],
),
),
_avatar(isSelf),
],
),
);
}
return Column(
children: <Widget>[
Offstage(
offstage: !message.isShowDate,
child: DateWidget(
date: message.timestamp,
),
),
msgWidget()
],
);
}
}
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/provider/global.dart';
import 'package:provider/provider.dart';
class PopupMenu extends StatelessWidget{
@override
Widget build(BuildContext context) {
return Consumer<GlobalProvide>(
builder: (context, globalState, _){
ThemeData themeData = Theme.of(context);
GlobalProvide globalState = Provider.of<GlobalProvide>(context);
return PopupMenuButton<int>(
onSelected: (int status) =>
globalState?.setOnline(online: status),
child: Align(
child: Container(
width: ToPx.size(80),
height: ToPx.size(40),
child: Icon(Icons.more_vert)
),
),
itemBuilder: (BuildContext context) {
List<PopupMenuEntry<int>> menus = [
PopupMenuItem<int>(
value: 0,
child: Text(
'转接客服',
style: themeData.textTheme.title.copyWith(
color: themeData.primaryColorLight
),
),
),
PopupMenuItem<int>(
value: 1,
child: Text(
'结束会话',
style: themeData.textTheme.title.copyWith(
color: Colors.amber),
),
)
];
return menus;
});
}
);
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/utils//index.dart';
class ShortcutPanel extends StatelessWidget{
ShortcutPanel({
this.isShow,
this.listData,
this.onSelected
});
final bool isShow;
final List<ShortcutModel> listData;
final ValueChanged<String> onSelected;
@override
Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context);
return Offstage(
offstage: !isShow,
child: Container(
width: double.infinity,
height: ToPx.size(500),
decoration: BoxDecoration(
border: Border(top: BorderSide(width: 1.0, color: themeData.dividerColor))
),
child:
listData.length == 0 ?
Center(
child: Text("没您还没有设置相关快捷语!", style: themeData.textTheme.body1),
) :
ListView.builder(
itemCount: listData.length,
itemBuilder: (context, index){
return Column(
children: <Widget>[
Button(
alignment: Alignment.centerLeft,
radius: 0.0,
height: ToPx.size(135),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(20)),
color: Colors.white,
withAlpha: 50,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("${listData[index].title}", style: themeData.textTheme.body1, maxLines: 1, overflow: TextOverflow.ellipsis,),
Text("${listData[index].content}", style: themeData.textTheme.caption, maxLines: 1, overflow: TextOverflow.ellipsis),
],
),
onPressed: () => onSelected(listData[index].content),
),
Divider(height: 1.0,)
],
);
}
),
),
);
}
}
\ No newline at end of file
import 'package:flutter/material.dart';
import 'package:kefu_workbench/models/index.dart';
import 'package:kefu_workbench/utils//index.dart';
class SystemMessage extends StatelessWidget {
SystemMessage({this.message, this.isSelf});
final ImMessageModel message;
final bool isSelf;
@override
Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context);
return Padding(
padding: EdgeInsets.only(bottom: ToPx.size(30)),
child: Container(
height: ToPx.size(45),
width: double.infinity,
padding: EdgeInsets.symmetric(horizontal: ToPx.size(20)),
alignment: Alignment.center,
child: DefaultTextStyle(
style: themeData.textTheme.caption,
child: Builder(builder: (_) {
switch (message.bizType) {
case "cancel":
return Text(isSelf ? "您撤回了一条消息" : "对方撤回了一条消息");
case "end":
return Text(isSelf ? "你结束了会话" : "对方结束了会话");
case "timeout":
case "system":
case "transfer":
return Text('${message.payload}');
default:
return SizedBox();
}
})),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:kefu_workbench/core_flutter.dart';
import 'date_widget.dart';
class TextMessage extends StatelessWidget {
TextMessage({this.message, this.onCancel, this.onOperation, this.isSelf});
final ImMessageModel message;
final VoidCallback onCancel;
final VoidCallback onOperation;
final bool isSelf;
@override
Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context);
Widget _avatar(bool show) {
return Offstage(
offstage: !show,
child: Avatar(
imgUrl: message.avatar,
));
}
Widget _cancel() {
return Offstage(
offstage: !message.isShowCancel,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
GestureDetector(
onTap: onCancel,
child:
Text(" 撤回 ", style: TextStyle(color: themeData.primaryColor)),
),
],
),
);
}
Widget msgWidget() {
return Container(
margin: EdgeInsets.only(bottom: ToPx.size(30)),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment:
isSelf ? MainAxisAlignment.end : MainAxisAlignment.start,
children: <Widget>[
_avatar(!isSelf),
Padding(
padding: EdgeInsets.only(
left: isSelf ? 0 : ToPx.size(15), right: isSelf ? ToPx.size(15) : 0),
child: Column(
crossAxisAlignment:
isSelf ? CrossAxisAlignment.end : CrossAxisAlignment.start,
children: <Widget>[
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: <Widget>[
_cancel(),
GestureDetector(
onLongPress: onOperation,
child: Container(
margin: EdgeInsets.only(top: ToPx.size(8)),
constraints: BoxConstraints(maxWidth: ToPx.size(550),),
padding: EdgeInsets.symmetric(
horizontal: ToPx.size(20), vertical: ToPx.size(12)),
decoration: BoxDecoration(
color: isSelf
? themeData.primaryColor
: Colors.white,
boxShadow: [
BoxShadow(
offset: Offset(0.0, 3.0),
color: Colors.black26.withAlpha(5),
blurRadius: 4.0,
),
BoxShadow(
offset: Offset(0.0, 3.0),
color: Colors.black26.withAlpha(5),
blurRadius: 4.0,
),
],
borderRadius:
BorderRadius.all(Radius.circular(3.0))),
child: Text("${message.payload}",
style: TextStyle(
fontSize: ToPx.size(28),
color: isSelf
? Colors.white
: Colors.black87.withAlpha(180))),
),
)
],
),
],
),
),
_avatar(isSelf),
],
),
);
}
return Column(
children: <Widget>[
Offstage(
offstage: !message.isShowDate,
child: DateWidget(
date: message.timestamp,
),
),
msgWidget()
],
);
}
}
import 'package:flutter/material.dart';
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/utils//index.dart';
class TransferPanel extends StatelessWidget{
TransferPanel({
this.isShow,
this.listData,
this.onSelected
});
final bool isShow;
final List<AdminModel> listData;
final ValueChanged<AdminModel> onSelected;
@override
Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context);
return Offstage(
offstage: !isShow,
child: Container(
width: double.infinity,
height: ToPx.size(500),
decoration: BoxDecoration(
border: Border(top: BorderSide(width: 1.0, color: themeData.dividerColor))
),
child:
listData.length == 0 ?
Center(
child: Text("当前没有其他客服在线!", style: themeData.textTheme.body1),
) :
ListView.builder(
itemCount: listData.length,
itemBuilder: (context, index){
AdminModel user = listData[index];
return Column(
children: <Widget>[
Container(
alignment: Alignment.centerLeft,
height: ToPx.size(90),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(20)),
color: Colors.white,
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Expanded(
child: Row(
children: <Widget>[
Avatar(
size: ToPx.size(60),
imgUrl: user.avatar ??
"http://qiniu.cmp520.com/avatar_default.png",
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(" ${user.nickname}", style: themeData.textTheme.body1),
Text(" 当前在线", style: themeData.textTheme.caption.copyWith(
color: Colors.green[400],
fontSize: ToPx.size(20)
)),
],
)
],
),
),
Button(
width: ToPx.size(90),
height: ToPx.size(50),
withAlpha: 200,
child: Text("转接", style: themeData.textTheme.body1.copyWith(
color: Colors.amber,
fontSize: ToPx.size(20)
),),
onPressed: () => onSelected(listData[index]),
)
],
),
),
Divider(height: 1.0,)
],
);
}
),
),
);
}
}
\ No newline at end of file
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/provider/chat_record.dart';
import 'package:kefu_workbench/provider/global.dart';
import 'package:provider/provider.dart';
import 'widget/popup_button.dart';
class ChatReCordPage extends StatelessWidget {
final Map<dynamic, dynamic> arguments;
ChatReCordPage({this.arguments});
@override
Widget build(_) {
return ChangeNotifierProvider<ChatReCordProvide>(
create: (_) => ChatReCordProvide.getInstance(),
child: Consumer<ChatReCordProvide>(builder: (context, chatReCordState, _){
return PageContext(builder: (context){
ThemeData themeData = Theme.of(context);
return Scaffold(
backgroundColor: themeData.primaryColorLight,
appBar: customAppBar(
title: Text(
"服务记录",
style: themeData.textTheme.display1,
),
actions: [
PopupButton()
],
),
body: chatReCordState.isLoading && chatReCordState.admins.length == 0 ? Center(
child: loadingIcon(size: ToPx.size(50)),
):
RefreshIndicator(
color: themeData.primaryColorLight,
backgroundColor: themeData.primaryColor,
onRefresh: chatReCordState.onRefresh,
child: CustomScrollView(
scrollDirection: Axis.vertical,
physics: AlwaysScrollableScrollPhysics(),
controller: chatReCordState.scrollController,
slivers: <Widget>[
SliverToBoxAdapter(
child: Padding(
padding: EdgeInsets.all(ToPx.size(20)),
child: Column(
children: <Widget>[
GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => chatReCordState.onSelectDate(context),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("选择时间:", style: themeData.textTheme.title,),
Icon(Icons.date_range, size: ToPx.size(40), color: themeData.textTheme.body1.color),
Text("${chatReCordState.date ?? '今天'} ", style: themeData.textTheme.title,),
]
)
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Row(
children: <Widget>[
Text("去重", style: themeData.textTheme.title,),
Platform.isAndroid
? Switch(
value: chatReCordState.isDeWeighting,
onChanged: (bool isSwitch) => chatReCordState.setDeWeighting(isSwitch),
activeColor: Colors.black)
: Transform.scale(
scale: .7,
child: CupertinoSwitch(
value: chatReCordState.isDeWeighting,
onChanged: (bool isSwitch) => chatReCordState.setDeWeighting(isSwitch),
activeColor: Colors.black))
],
),
Row(
children: <Widget>[
Text("仅显示未接待", style: themeData.textTheme.title,),
Platform.isAndroid
? Switch(
value: chatReCordState.isReception,
onChanged: (bool isSwitch) => chatReCordState.setReception(isSwitch),
activeColor: Colors.black)
: Transform.scale(
scale: .7,
child: CupertinoSwitch(
value: chatReCordState.isReception,
onChanged: (bool isSwitch) => chatReCordState.setReception(isSwitch),
activeColor: Colors.black))
],
),
],)
],)
)
),
SliverToBoxAdapter(
child: Divider(height: 1.0,),
),
SliverToBoxAdapter(
child: Offstage(
offstage: chatReCordState.total == 0,
child: Padding(
padding: EdgeInsets.symmetric(vertical: ToPx.size(5)),
child: Text("${chatReCordState?.selectedAdmin?.nickname} 当天总服务人次(${chatReCordState?.total}人)", style: themeData.textTheme.caption, textAlign: TextAlign.center,)
)
),
),
SliverToBoxAdapter(
child: Offstage(
offstage: chatReCordState.servicesStatisticals.length > 0 || chatReCordState.isLoading,
child: Padding(
padding: EdgeInsets.only(top: ToPx.size(50)),
child: Text("暂无数据~", style: themeData.textTheme.body1, textAlign: TextAlign.center,),
),
),
),
SliverList(
delegate: SliverChildBuilderDelegate((context, index){
ServicesStatisticalModel _servicesStatistical = chatReCordState.servicesStatisticals[index];
return Column(
children: <Widget>[
ListTile(
onTap: () => Navigator.pushNamed(context, "/chat",arguments: {
"isReadOnly": true,
"accountId": int.parse(_servicesStatistical.userAccount),
"serviceId": chatReCordState.selectedAdmin.id,
"title": "${chatReCordState.getAdminNickName(int.parse(_servicesStatistical.serviceAccount))}${_servicesStatistical.nickname} 的聊天记录",
}),
subtitle: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Row(
children: <Widget>[
Text("平台:", style: themeData.textTheme.caption),
Text("${GlobalProvide.getInstance().getPlatformTitle(int.parse(_servicesStatistical.platform))}")
],
),
Row(
children: <Widget>[
Text("${_servicesStatistical.isReception == "1" ? "已接待" : "未接待"}", style: themeData.textTheme.caption.copyWith(
color: _servicesStatistical.isReception == "1" ? Colors.green : Colors.red
)),
],
)
],
),
title: Row(
children: <Widget>[
Expanded(child: Text("${_servicesStatistical.nickname}", style: themeData.textTheme.title, maxLines: 2, overflow: TextOverflow.ellipsis,),),
Text("${Utils.epocFormat(int.parse(_servicesStatistical.createAt))}",
style: themeData.textTheme.caption.copyWith(
fontSize: ToPx.size(22),
),)
],
)
),
Divider(height: 1.0,)
],
);
},
childCount: chatReCordState.servicesStatisticals.length
),
),
SliverToBoxAdapter(
child: Offstage(
child: Center(
child: SizedBox(
height: ToPx.size(150),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
loadingIcon(),
Text(' 内容加载中...',
style: themeData.textTheme.caption)
],
),
),
),
offstage: !chatReCordState.isLoading || chatReCordState.isLoadEnd
)
),
SliverToBoxAdapter(
child: Offstage(
child: Padding(
padding: EdgeInsets.symmetric(vertical: ToPx.size(40)),
child: Center(
child: Text(
'没有更多了', style: themeData.textTheme.caption)
),),
offstage: !chatReCordState.isLoadEnd || chatReCordState.servicesStatisticals.length == 0
)
),
],
)
),
);
});
},),
);
}
}
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/provider/chat_record.dart';
import 'package:provider/provider.dart';
class PopupButton extends StatelessWidget{
@override
Widget build(BuildContext context) {
return Consumer<ChatReCordProvide>(
builder: (context, chatReCordState, _){
ThemeData themeData = Theme.of(context);
return PopupMenuButton<AdminModel>(
onSelected: (AdminModel admin) => chatReCordState.onSelected(admin),
child: Align(
alignment: Alignment.centerRight,
child: Container(
alignment: Alignment.centerRight,
width: ToPx.size(250),
child: Text("客服:${chatReCordState?.selectedAdmin?.nickname ?? '选择客服'} ",
style: themeData.textTheme.caption.copyWith(
color: Colors.amber,
fontSize: ToPx.size(26)
)))
),
itemBuilder: (BuildContext context) => chatReCordState.admins.map((i) => PopupMenuItem<AdminModel>(
value: i,
child: Center(
child: Text(
'${i.nickname}',
style: themeData.textTheme.title.copyWith(
color: chatReCordState.selectedAdmin?.id == i.id ? Colors.amber : themeData.primaryColorLight
),
)
),
)).toList());
}
);
}
}
\ No newline at end of file
import 'package:dio/dio.dart';
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/provider/global.dart';
class EditPasswordPage extends StatefulWidget {
final Map<dynamic, dynamic> arguments;
EditPasswordPage({this.arguments});
@override
EditPasswordPageState createState() => EditPasswordPageState();
}
class EditPasswordPageState extends State<EditPasswordPage> {
AdminModel serviceUser = GlobalProvide.getInstance().serviceUser;
TextEditingController oldPasswordCtr;
TextEditingController newPasswordCtr;
TextEditingController enterPasswordCtr;
/// save
void _save() async{
String oldPassword = oldPasswordCtr.value.text.trim();
String newPassword = newPasswordCtr.value.text.trim();
String enterPassword = enterPasswordCtr.value.text.trim();
/// 判断
if(oldPassword.isEmpty || oldPassword == ""){
UX.showToast("旧密码不能为空");
return;
}
if(newPassword.isEmpty || newPassword == ""){
UX.showToast("新密码不能为空");
return;
}
if(enterPassword.isEmpty || enterPassword == ""){
UX.showToast("请再次输入新密码");
return;
}
if(enterPassword != newPassword){
UX.showToast("两次密码不一致");
return;
}
FocusScope.of(context).requestFocus(FocusNode());
UX.showLoading(context, content: "保存中...");
Response response = await AdminService.getInstance().updatePassword({
"old_password": oldPassword,
"new_password": newPassword,
"enter_password": enterPassword
});
UX.hideLoading(context);
if(response.statusCode == 200){
UX.showToast("密码修改成功");
Navigator.pop(context);
GlobalProvide.getInstance().getMe();
}else{
UX.showToast(response.data["message"]);
}
}
@override
void initState() {
super.initState();
if(mounted && serviceUser!= null){
oldPasswordCtr = TextEditingController();
newPasswordCtr = TextEditingController();
enterPasswordCtr = TextEditingController();
}
}
@override
void dispose() {
oldPasswordCtr?.dispose();
newPasswordCtr?.dispose();
enterPasswordCtr?.dispose();
super.dispose();
}
@override
Widget build(_) {
return PageContext(builder: (context){
ThemeData themeData = Theme.of(context);
Widget _fromInput({
String label,
TextEditingController controller,
String placeholder,
bool enabled = true,
bool obscureText = false,
bool autofocus = false,
}){
return Container(
height: ToPx.size(90),
decoration: BoxDecoration(
color: Colors.white,
border: Border(bottom: BorderSide(color: themeData.dividerColor,width: ToPx.size(2)))
),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(40)),
child: Row(
children: <Widget>[
Text("$label", style: themeData.textTheme.title,),
Expanded(
child: Input(
enabled: enabled,
obscureText: obscureText,
border: Border.all(style: BorderStyle.none, color: Colors.transparent),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(10)),
placeholder: "$placeholder",
showClear: true,
autofocus: autofocus,
controller: controller,
),
)
],
),
);
}
return Scaffold(
appBar: customAppBar(
title: Text(
"修改密码",
style: themeData.textTheme.display1,
)),
body: ListView(
children: <Widget>[
SizedBox(
height: ToPx.size(40),
),
_fromInput(
label: "旧密码:",
placeholder: "请输入旧密码",
controller: oldPasswordCtr,
autofocus: true
),
_fromInput(
label: "新密码:",
placeholder: "请输入新密码",
obscureText: true,
controller: newPasswordCtr
),
_fromInput(
label: "确认密码:",
obscureText: true,
placeholder: "请再次输入新密码",
controller: enterPasswordCtr
),
Button(
margin: EdgeInsets.symmetric(horizontal: ToPx.size(40), vertical: ToPx.size(80)),
onPressed: _save,
withAlpha: 200,
child: Text("保存"),
)
],
)
);
});
}
}
import 'package:dio/dio.dart';
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/provider/global.dart';
class EditProfilePage extends StatefulWidget {
final Map<dynamic, dynamic> arguments;
EditProfilePage({this.arguments});
@override
EditProfilePageState createState() => EditProfilePageState();
}
class EditProfilePageState extends State<EditProfilePage> {
AdminModel serviceUser = GlobalProvide.getInstance().serviceUser;
TextEditingController nicknameCtr;
TextEditingController phoneCtr;
TextEditingController autoReplyCtr;
TextEditingController usernameCtr;
/// 选择图片上传
void _pickerImage() async{
String imgUser = await uploadImage<String>(context, maxWidth: 300.0);
if(imgUser == null) return;
serviceUser.avatar = imgUser;
setState(() {});
}
/// save
void saveUser() async{
String nickname = nicknameCtr.value.text.trim();
AdminModel user = AdminModel(
nickname: nickname,
phone: phoneCtr.value.text.trim(),
autoReply: autoReplyCtr.value.text.trim(),
id: serviceUser.id,
avatar: serviceUser.avatar,
username: usernameCtr.value.text.trim(),
);
Map useMap = user.toJson();
useMap.removeWhere((key, value) => value == null);
/// 判断昵称不能为空
if(nickname.isEmpty || nickname == ""){
UX.showToast("昵称不能为空");
return;
}
FocusScope.of(context).requestFocus(FocusNode());
UX.showLoading(context, content: "保存中...");
Response response = await AdminService.getInstance().saveAdminInfo(useMap);
UX.hideLoading(context);
if(response.statusCode == 200){
UX.showToast("保存成功");
Navigator.pop(context);
GlobalProvide.getInstance().getMe();
}else{
UX.showToast(response.data["message"]);
}
}
@override
void initState() {
super.initState();
if(mounted && serviceUser!= null){
usernameCtr = TextEditingController(text: serviceUser.username);
nicknameCtr = TextEditingController(text: serviceUser.nickname);
phoneCtr = TextEditingController(text: serviceUser.phone);
autoReplyCtr = TextEditingController(text: serviceUser.autoReply);
}
}
@override
void dispose() {
nicknameCtr?.dispose();
autoReplyCtr?.dispose();
phoneCtr?.dispose();
usernameCtr?.dispose();
super.dispose();
}
@override
Widget build(_) {
return PageContext(builder: (context){
ThemeData themeData = Theme.of(context);
Widget _fromInput({
String label,
TextEditingController controller,
String placeholder,
bool enabled = true,
bool autofocus = false,
}){
return Container(
height: ToPx.size(90),
decoration: BoxDecoration(
color: Colors.white,
border: Border(bottom: BorderSide(color: themeData.dividerColor,width: ToPx.size(2)))
),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(40)),
child: Row(
children: <Widget>[
Text("$label", style: themeData.textTheme.title,),
Expanded(
child: Input(
enabled: enabled,
autofocus: autofocus,
border: Border.all(style: BorderStyle.none, color: Colors.transparent),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(10)),
placeholder: "$placeholder",
showClear: true,
controller: controller,
),
)
],
),
);
}
return Scaffold(
appBar: customAppBar(
actions: [
Button(
height: ToPx.size(90),
useIosStyle: true,
color: Colors.transparent,
width: ToPx.size(150),
child: Text("修改密码"),
onPressed: () => Navigator.pushNamed(context, "/edit_password")
),
],
title: Text(
"编辑个人资料",
style: themeData.textTheme.display1,
)),
body: ListView(
children: <Widget>[
SizedBox(
height: ToPx.size(40),
),
Container(
width: double.infinity,
color: Colors.white,
padding: EdgeInsets.symmetric(vertical: ToPx.size(30)),
child: Stack(
children: <Widget>[
Center(
child: Avatar(
size: ToPx.size(150),
imgUrl: serviceUser.avatar.isEmpty ? "http://qiniu.cmp520.com/avatar_default.png" : serviceUser?.avatar,
onPressed: _pickerImage
)
),
Center(
child: Padding(
padding: EdgeInsets.only(top: ToPx.size(120), left: ToPx.size(80)),
child: Icon(Icons.camera_alt, color:themeData.primaryColor.withAlpha(150), size: ToPx.size(35),),
)
)
],
),
),
_fromInput(
label: " 账号:",
placeholder: "请输入账号",
controller: usernameCtr,
enabled: false
),
_fromInput(
label: "用户昵称:",
placeholder: "请输入昵称",
autofocus: true,
controller: nicknameCtr
),
_fromInput(
label: "联系方式:",
placeholder: "请输入联系方式",
controller: phoneCtr
),
Container(
height: ToPx.size(250),
decoration: BoxDecoration(
color: Colors.white,
border: Border(bottom: BorderSide(color: themeData.dividerColor,width: ToPx.size(2)))
),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(40), vertical: ToPx.size(10)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("自动回复语:", style: themeData.textTheme.title,),
Expanded(
child: Input(
border: Border.all(style: BorderStyle.none, color: Colors.transparent),
placeholder: "请输入自动回复语",
controller: autoReplyCtr,
textAlign: TextAlign.start,
minLines: 5,
maxLength: 100,
placeholderAlignment: Alignment.topLeft,
textInputAction: TextInputAction.newline,
maxLines: 5,
),
)
],
),
),
Button(
margin: EdgeInsets.symmetric(horizontal: ToPx.size(40), vertical: ToPx.size(80)),
onPressed: saveUser,
withAlpha: 200,
child: Text("保存"),
)
],
)
);
});
}
}
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/provider/global.dart';
import 'package:kefu_workbench/provider/home.dart';
import 'package:provider/provider.dart';
import 'widget/contact_widget.dart';
import 'widget/drawer_menu.dart';
import 'widget/popup_menu_button.dart';
// 点击两次返回退出
int lastExitTime = 0;
Future<bool> onBackPressed() async {
int nowExitTime = DateTime.now().millisecondsSinceEpoch;
if (nowExitTime - lastExitTime > 2000) {
lastExitTime = nowExitTime;
UX.showToast('再按一次退出程序');
return await Future.value(false);
}
return await Future.value(true);
}
class HomePage extends StatelessWidget {
final Map<dynamic, dynamic> arguments;
HomePage({this.arguments});
void openDrawer(context) {
Scaffold.of(context).openDrawer();
}
@override
Widget build(ctx) {
return Consumer<HomeProvide>(builder: (context, homeState, ___){
return PageContext(builder: (context) {
GlobalProvide.getInstance().setRooContext(context);
ThemeData themeData = Theme.of(context);
GlobalProvide globalState = Provider.of<GlobalProvide>(context);
return WillPopScope(
onWillPop: onBackPressed,
child: Scaffold(
backgroundColor: Colors.white,
appBar: customAppBar(
leading: Builder(
builder: (context) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () => openDrawer(context),
child: Stack(
children: <Widget>[
Positioned(
left: ToPx.size(-20),
top: ToPx.size(25),
child: Icon(
Icons.menu,
size: ToPx.size(45),
color: Colors.white.withAlpha(100),
),
),
Consumer<GlobalProvide>(
builder: (context, globalState, _){
return Align(
alignment: Alignment.centerRight,
child: Avatar(
size: ToPx.size(70),
imgUrl: "${globalState?.serviceUser?.avatar ?? 'http://qiniu.cmp520.com/avatar_default.png'}",
));
},
),
],
),
);
},
),
actions: [
LinePopupMenuButton()
],
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
"工作台 ",
style: themeData.textTheme.display1,
),
Offstage(
offstage: homeState.contactReadCount == 0,
child: Align(
alignment: Alignment.topRight,
child: Container(
margin: EdgeInsets.only(top: ToPx.size(5)),
child: Center(
child: Text(
"${homeState.contactReadCount}",
style: themeData.textTheme.caption.copyWith(
color: Colors.white,
fontSize: ToPx.size(20)),
),
),
width: ToPx.size(35),
height: ToPx.size(35),
decoration: BoxDecoration(
color: Colors.red, shape: BoxShape.circle),
),
),
)
],
)),
body:
globalState.isContactShowLoading
? Center(
child: loadingIcon(size: ToPx.size(50)),
)
: Column(
children: <Widget>[
Offstage(
offstage: globalState.newWorkHandleCounts == 0,
child: GestureDetector(
onTap: () => Navigator.pushNamed(context, "/workorder"),
child: Padding(
padding: EdgeInsets.symmetric(vertical: ToPx.size(15)),
child: Text("有${globalState.newWorkHandleCounts}条工单急需处理,点击去处理~", style: themeData.textTheme.body1.copyWith(
color: Colors.red
),),
),
),
),
Expanded(
child: RefreshIndicator(
color: themeData.primaryColorLight,
backgroundColor: themeData.primaryColor,
onRefresh: () => homeState.onRefresh(),
child: CustomScrollView(
scrollDirection: Axis.vertical,
physics: AlwaysScrollableScrollPhysics(),
slivers: <Widget>[
Consumer<GlobalProvide>(
builder: (context, globalState, _){
return SliverToBoxAdapter(
child: Offstage(
offstage: globalState.contacts.length > 0 || globalState.isContactShowLoading,
child: SizedBox(
height: ToPx.size(200),
child: Center(
child: Text(
"暂无聊天记录~",
style: themeData.textTheme.body1,
),
),
),
),
);
},
),
Consumer<GlobalProvide>(
builder: (context, globalState, _){
return SliverList(delegate: SliverChildBuilderDelegate((context, index) {
return ContactWidget(globalState.contacts[index]);
}, childCount: globalState.contacts.length));
},
),
],
)),
)
],
),
drawer: Drawer(
child: DrawerMenu(),
),
),
);
});
});
}
}
import 'package:flutter/gestures.dart';
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/provider/global.dart';
import 'package:kefu_workbench/provider/home.dart';
import 'package:provider/provider.dart';
class ContactWidget extends StatelessWidget{
ContactWidget(this.contact);
final ContactModel contact;
@override
Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context);
HomeProvide homeState = Provider.of<HomeProvide>(context);
GlobalProvide globalProvide = Provider.of<GlobalProvide>(context);
return Dismissible(
dragStartBehavior: DragStartBehavior.down,
direction: DismissDirection.endToStart,
confirmDismiss: (DismissDirection direction) async {
if (direction.index != 2) return false;
globalProvide.removeSingleContact(contact.cid);
return true;
},
secondaryBackground: Container(
color: Colors.red.withAlpha(200),
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
SizedBox(
child: Icon(
Icons.delete,
color: Colors.white,
),
width: ToPx.size(150),
),
],
),
),
background: Container(
color: Colors.white,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ListTile(
onTap: () => homeState.selectContact(contact),
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Text(
"${contact.nickname}",
style: themeData.textTheme.title,
),
Text(
"${Utils.epocFormat(contact?.contactCreateAt)}",
style: themeData.textTheme.caption,
),
],
),
leading: SizedBox(
width: ToPx.size(100),
child: Stack(
children: <Widget>[
Center(
child:
contact.avatar.isEmpty ?
Container(
width: ToPx.size(90),
height: ToPx.size(90),
alignment: Alignment.center,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Color(0xffc0c4cb)
),
child: Text("访", style: themeData.textTheme.display1),
) :
Avatar(
size: ToPx.size(90),
imgUrl:
"${contact.avatar.isEmpty ? 'http://qiniu.cmp520.com/avatar_default.png' : contact.avatar}",
),
),
Offstage(
offstage: contact.read == 0,
child: Align(
alignment: Alignment.topRight,
child: Container(
margin: EdgeInsets.only(top: ToPx.size(5)),
child: Center(
child: Text(
"${contact.read}",
style: themeData.textTheme.caption
.copyWith(
color: Colors.white,
fontSize: ToPx.size(20)),
),
),
width: ToPx.size(35),
height: ToPx.size(35),
decoration: BoxDecoration(
color: Colors.red,
shape: BoxShape.circle),
),
),
),
Align(
alignment: Alignment.bottomRight,
child: Container(
margin: EdgeInsets.only(bottom: ToPx.size(15), right: ToPx.size(13)),
width: ToPx.size(16),
height: ToPx.size(16),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: contact.online == 0 ? Colors.grey : Colors.green[400]
),
),
)
],
),
),
subtitle: Text(
contact.lastMessageType == "text"
? contact.lastMessage
: contact.lastMessageType == "photo"
? "图片"
: contact.lastMessageType == "video"
? "视频"
: contact.lastMessageType == "end"
? "会话结束"
: contact.lastMessageType == "timeout"
? "会话超时,结束对话"
: contact.lastMessageType ==
"transfer"
? "客服转接..."
: contact.lastMessageType ==
"system"
? "系统提示..."
: contact.lastMessageType ==
"cancel"
? "撤回了消息"
: "未知消息内容~",
style: themeData.textTheme.caption,
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
Divider(
height: 1.0,
)
],
),
key: GlobalKey(),
);
}
}
\ No newline at end of file
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/provider/global.dart';
import 'package:kefu_workbench/provider/home.dart';
import 'package:provider/provider.dart';
class DrawerMenu extends StatelessWidget {
@override
Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context);
HomeProvide homeState = Provider.of<HomeProvide>(context);
GlobalProvide globalState = Provider.of<GlobalProvide>(context);
Color lineColor = globalState?.serviceUser?.online == 1
? Colors.green[400]
: globalState?.serviceUser?.online == 0
? Colors.grey
: globalState?.serviceUser?.online == 2
? Colors.amber
: Colors.grey;
Widget _listTile(
{IconData icon,
String title,
VoidCallback onTap,
bool selected = false,
Color selectedColor = Colors.white}) {
return Button(
onPressed: onTap,
withAlpha: 200,
padding: EdgeInsets.symmetric(horizontal: ToPx.size(40)),
height: ToPx.size(100),
color: themeData.primaryColor,
radius: 0.0,
child: Row(
children: <Widget>[
Expanded(
child: Row(
children: <Widget>[
Icon(icon,
color: selected
? Colors.amber
: themeData.primaryColorLight),
Padding(
padding: EdgeInsets.only(left: ToPx.size(50)),
child: Text(
title,
style: themeData.textTheme.title.copyWith(
color: selected
? Colors.amber
: themeData.primaryColorLight,
fontWeight: FontWeight.w500),
),
)
],
),
),
Icon(Icons.chevron_right,
color: selected
? Colors.amber
: themeData.primaryColorLight.withAlpha(30))
],
),
);
}
return Container(
color: themeData.primaryColor,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
color: themeData.primaryColorDark,
width: double.infinity,
height: ToPx.size(260),
padding: EdgeInsets.only(
top: ToPx.size(80), left: ToPx.size(20), right: ToPx.size(20)),
child: Stack(
children: <Widget>[
Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Avatar(
size: ToPx.size(100),
imgUrl: globalState.serviceUser == null ||
globalState.serviceUser.avatar.isEmpty
? "http://qiniu.cmp520.com/avatar_default.png"
: globalState.serviceUser?.avatar,
),
Padding(
padding: EdgeInsets.only(top: ToPx.size(20)),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
"${globalState.serviceUser?.nickname ?? '未知昵称'} ",
style: themeData.textTheme.title
.copyWith(color: themeData.primaryColorLight),
),
Container(
width: ToPx.size(10),
height: ToPx.size(10),
decoration: BoxDecoration(
shape: BoxShape.circle, color: lineColor),
),
Text(
globalState?.serviceUser?.online == 0
? " 离线"
: globalState?.serviceUser?.online == 1
? " 在线"
: globalState?.serviceUser?.online == 2
? " 离开"
: "未知",
style: themeData.textTheme.caption.copyWith(
fontSize: ToPx.size(22), color: lineColor),
),
],
),
),
],
),
),
Align(
alignment: Alignment.bottomRight,
child: Button(
useIosStyle: true,
width: ToPx.size(120),
onPressed: () =>
Navigator.popAndPushNamed(context, "/edit_profile"),
color: Colors.transparent,
child: Text(
"编辑资料",
style: themeData.textTheme.caption,
),
))
],
),
),
Divider(
height: 1.0,
color: Colors.black12,
),
Expanded(
child: ListView(
padding: EdgeInsets.zero,
children: <Widget>[
Column(
children: <Widget>[
_listTile(
icon: Icons.home,
title: "工作台",
selected: true,
onTap: () => Navigator.pop(context)),
_listTile(
icon: Icons.library_books,
title: "工单管理",
onTap: () =>
Navigator.popAndPushNamed(context, "/workorder")),
_listTile(
icon: Icons.insert_chart,
title: "流量统计",
onTap: () =>
Navigator.popAndPushNamed(context, "/statistical")),
_listTile(
icon: Icons.library_books,
title: "知识库",
onTap: () =>
Navigator.popAndPushNamed(context, "/knowledge")),
_listTile(
icon: Icons.android,
title: "机器人",
onTap: () =>
Navigator.popAndPushNamed(context, "/robots")),
_listTile(
icon: Icons.supervised_user_circle,
title: "客服管理",
onTap: () =>
Navigator.popAndPushNamed(context, "/admins")),
_listTile(
icon: Icons.supervisor_account,
title: "用户管理",
onTap: () =>
Navigator.popAndPushNamed(context, "/users")),
_listTile(
icon: Icons.featured_play_list,
title: "服务记录",
onTap: () =>
Navigator.popAndPushNamed(context, "/chat_record")),
_listTile(
icon: Icons.assignment,
title: "快捷语设置",
onTap: () =>
Navigator.popAndPushNamed(context, "/shortcuts")),
Offstage(
offstage: globalState?.serviceUser?.root != 1,
child: _listTile(
icon: Icons.settings,
title: "系统设置",
onTap: () =>
Navigator.popAndPushNamed(context, "/system")),
),
],
),
SizedBox(
width: double.infinity,
height: 100.0,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Button(
margin: EdgeInsets.only(bottom: ToPx.size(50)),
color: themeData.primaryColorDark,
width: ToPx.size(180),
height: ToPx.size(60),
onPressed: () => homeState.logout(context),
child: Text(
"退出登录",
style: themeData.textTheme.title
.copyWith(color: themeData.primaryColorLight),
),
)
],
),
)
],
),
),
],
),
);
}
}
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/provider/global.dart';
import 'package:provider/provider.dart';
class LinePopupMenuButton extends StatelessWidget{
@override
Widget build(BuildContext context) {
return Consumer<GlobalProvide>(
builder: (context, globalState, _){
ThemeData themeData = Theme.of(context);
GlobalProvide globalState = Provider.of<GlobalProvide>(context);
Color lineColor = globalState?.serviceUser?.online == 1 ? Colors.green[400] :
globalState?.serviceUser?.online == 0 ? Colors.grey :
globalState?.serviceUser?.online == 2 ? Colors.amber : Colors.grey;
return PopupMenuButton<int>(
onSelected: (int status) =>
globalState?.setOnline(online: status),
child: Align(
child: Container(
width: ToPx.size(80),
height: ToPx.size(40),
child: Row(
children: <Widget>[
Container(
width: ToPx.size(10),
height: ToPx.size(10),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: lineColor
),
),
Text(
globalState?.serviceUser?.online == 0 ? " 离线" :
globalState?.serviceUser?.online == 1 ? " 在线" :
globalState?.serviceUser?.online == 2 ? " 离开" : "未知",
style: themeData.textTheme.caption.copyWith(
fontSize: ToPx.size(22),
color: lineColor
),
),
],
)),
),
itemBuilder: (BuildContext context) {
List<PopupMenuEntry<int>> menus = [];
PopupMenuItem<int> online = PopupMenuItem<int>(
value: 1,
child: Text(
'我要上线',
style: themeData.textTheme.title.copyWith(
color: themeData.primaryColorLight),
),
);
PopupMenuItem<int> offline = PopupMenuItem<int>(
value: 0,
child: Text(
'我要下线',
style: themeData.textTheme.title.copyWith(
color: themeData.primaryColorLight),
),
);
PopupMenuItem<int> leave = PopupMenuItem<int>(
value: 2,
child: Text(
'我要离开',
style: themeData.textTheme.title.copyWith(
color: themeData.primaryColorLight),
),
);
if (globalState?.serviceUser?.online != 1) {
menus.add(online);
}
if (globalState?.serviceUser?.online != 0) {
menus.add(offline);
}
if (globalState?.serviceUser?.online != 2) {
menus.add(leave);
}
return menus;
});
}
);
}
}
\ No newline at end of file
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/provider/global.dart';
import 'package:kefu_workbench/provider/knowledge.dart';
import 'package:provider/provider.dart';
class KnowledgePage extends StatelessWidget{
final Map<dynamic, dynamic> arguments;
KnowledgePage({this.arguments});
@override
Widget build(_) {
return ChangeNotifierProvider<KnowledgeProvide>(
create: (_) => KnowledgeProvide.getInstance(),
child: Consumer<KnowledgeProvide>(builder: (context, knowledgeProvide, _){
return PageContext(builder: (context){
ThemeData themeData = Theme.of(context);
return Scaffold(
backgroundColor: themeData.primaryColorLight,
appBar: customAppBar(
title: Text(
"知识库列表",
style: themeData.textTheme.display1,
),
actions: [
Button(
height: ToPx.size(90),
useIosStyle: true,
color: Colors.transparent,
width: ToPx.size(150),
child: Text("新增"),
onPressed: () => knowledgeProvide.goAdd(context)
),
],
),
body:
knowledgeProvide.isLoading && knowledgeProvide.knowledges.length == 0 ? Center(
child: loadingIcon(size: ToPx.size(50)),
):
RefreshIndicator(
color: themeData.primaryColorLight,
backgroundColor: themeData.primaryColor,
onRefresh: knowledgeProvide.onRefresh,
child: CustomScrollView(
scrollDirection: Axis.vertical,
physics: AlwaysScrollableScrollPhysics(),
controller: knowledgeProvide.scrollController,
slivers: <Widget>[
SliverToBoxAdapter(
child: Offstage(
offstage: knowledgeProvide.knowledges.length > 0 || knowledgeProvide.isLoading,
child: Padding(
padding: EdgeInsets.only(top: ToPx.size(50)),
child: Text("暂无数据~", style: themeData.textTheme.body1, textAlign: TextAlign.center,),
),
),
),
SliverToBoxAdapter(
child: Column(children: <Widget>[
Container(
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: themeData.dividerColor, width: ToPx.size(2)))
),
child: Row(
children: <Widget>[
Expanded(
child: Input(
onEditingComplete: (){
knowledgeProvide.pageOn = 0;
knowledgeProvide.isLoadEnd = false;
knowledgeProvide.getKnowledges();
FocusScope.of(context).requestFocus(FocusNode());
},
textInputAction: TextInputAction.search,
border: Border.all(style: BorderStyle.none, color: Colors.transparent),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(20)),
placeholder: "请输入关键词查找~",
onChanged: (value) async{
knowledgeProvide.keyword = value;
await Future.delayed(Duration(milliseconds: 500));
if(value.trim().isEmpty){
knowledgeProvide.isLoadEnd = false;
knowledgeProvide.pageOn = 0;
knowledgeProvide.keyword = "";
knowledgeProvide.getKnowledges();
}
},
),
),
IconButton(
icon: Icon(CupertinoIcons.search, color: Colors.grey),
onPressed: () => knowledgeProvide.getKnowledges(),
)
],
),
)
],),
),
SliverList(
delegate: SliverChildBuilderDelegate((context, index){
KnowledgeModel knowledge = knowledgeProvide.knowledges[index];
return Column(
children: <Widget>[
ListTile(
onTap: () => Navigator.pushNamed(context, "/knowledge_detail",arguments: {
"knowledge": knowledge
}),
subtitle: Row(
children: <Widget>[
Expanded(child: Text("${knowledge.content}", style: themeData.textTheme.caption, maxLines: 1, overflow: TextOverflow.ellipsis,)),
Text(" [${GlobalProvide.getInstance().getPlatformTitle(knowledge.platform)}] ", style: TextStyle(
color: Colors.green,
fontSize: ToPx.size(24)
),),
],
),
trailing: Text("${Utils.formatDate(knowledge.createAt)}", style: themeData.textTheme.caption),
title: Row(children: <Widget>[
Text("${index+1}、", style: themeData.textTheme.title),
Expanded(
child: Text("${knowledge.title}", style: themeData.textTheme.title, maxLines: 2, overflow: TextOverflow.ellipsis,),
)
],),
),
Divider(height: 1.0,)
],
);
},
childCount: knowledgeProvide.knowledges.length
),
),
SliverToBoxAdapter(
child: Offstage(
child: Center(
child: SizedBox(
height: ToPx.size(150),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
loadingIcon(),
Text(' 内容加载中...',
style: themeData.textTheme.caption)
],
),
),
),
offstage: !knowledgeProvide.isLoading || knowledgeProvide.isLoadEnd
)
),
SliverToBoxAdapter(
child: Offstage(
child: Padding(
padding: EdgeInsets.symmetric(vertical: ToPx.size(40)),
child: Center(
child: Text(
'没有更多了', style: themeData.textTheme.caption)
),),
offstage: !knowledgeProvide.isLoadEnd
)
),
],
)
),
);
});
},),
);
}
}
import 'package:dio/dio.dart';
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/provider/global.dart';
import 'package:kefu_workbench/provider/knowledge.dart';
class KnowledgeDetailPage extends StatefulWidget {
final Map<dynamic, dynamic> arguments;
KnowledgeDetailPage({this.arguments});
@override
_KnowledgeDetailPageState createState() => _KnowledgeDetailPageState();
}
class _KnowledgeDetailPageState extends State<KnowledgeDetailPage> {
KnowledgeModel knowledge;
@override
void initState() {
super.initState();
knowledge = widget.arguments['knowledge'];
}
/// edit
void _goEdit(BuildContext context) async{
Navigator.pushNamed(context, "/knowledge_edit",arguments: {
"knowledge": knowledge
}).then((isSuccess) async{
if(isSuccess == true){
knowledge = KnowledgeProvide.getInstance().getItem(knowledge.id);
setState(() {});
}
});
}
/// delete
void _delete(BuildContext context){
UX.alert(
context,
content: Text("是否删除该知识库!"),
onConfirm: () async{
Response response = await KnowledgeService.getInstance().delete(id: knowledge.id);
if (response.data["code"] == 200) {
UX.showToast("删除成功");
KnowledgeProvide.getInstance().deleteItem(knowledge.id);
Navigator.pop(context);
} else {
UX.showToast("${response.data["message"]}");
}
}
);
}
@override
Widget build(context) {
knowledge = KnowledgeProvide.getInstance().getItem(knowledge.id);
return PageContext(builder: (context){
ThemeData themeData = Theme.of(context);
Widget _lineItem({
String label,
String content,
TextStyle style,
Widget subChild = const SizedBox()
}){
return Container(
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: themeData.dividerColor))
),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(20), vertical: ToPx.size(40)),
child: DefaultTextStyle(
style: style,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("$label"),
Expanded(
child: Text("$content", textAlign: TextAlign.left,),
)
],
),
subChild
],)
)
);
}
return Scaffold(
backgroundColor: themeData.primaryColorLight,
appBar: customAppBar(
title: Text(
"${knowledge.title}",
style: themeData.textTheme.display1,
),
actions: [
Button(
height: ToPx.size(90),
useIosStyle: true,
color: Colors.transparent,
width: ToPx.size(150),
child: Text("编辑"),
onPressed: () => _goEdit(context)
),
],
),
body: ListView(
children: <Widget>[
_lineItem(
label: "主标题:",
content: "${knowledge.title}",
style: themeData.textTheme.title,
subChild: DefaultTextStyle(
style: themeData.textTheme.caption,
child: Padding(
padding: EdgeInsets.only(top: ToPx.size(30)),
child: Row(
children: <Widget>[
Text(
"展示平台:${GlobalProvide.getInstance().getPlatformTitle(knowledge.platform)} ",
),
Text("创建时间:${Utils.formatDate(knowledge.createAt)}")
],
),
),
)
),
_lineItem(
label: "副标题:",
content: "${knowledge.subTitle.replaceAll("|", "、")}",
style: themeData.textTheme.body1,
),
_lineItem(
label: "内容:",
content: "${knowledge.content}",
style: themeData.textTheme.body1,
),
Button(
margin: EdgeInsets.symmetric(horizontal: ToPx.size(40), vertical: ToPx.size(50)),
child: Text("删除"),
withAlpha: 200,
color: Colors.redAccent,
onPressed: () => _delete(context),
)
],
)
);
});
}
}
import 'package:dio/dio.dart';
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/provider/global.dart';
import 'package:kefu_workbench/provider/knowledge.dart';
class KnowledgeEditPage extends StatefulWidget {
final Map<dynamic, dynamic> arguments;
KnowledgeEditPage({this.arguments});
@override
_KnowledgeEditPageState createState() => _KnowledgeEditPageState(
knowledge: arguments != null ? arguments['knowledge'] : null
);
}
class _KnowledgeEditPageState extends State<KnowledgeEditPage> {
final KnowledgeModel knowledge;
bool isEdit = false;
TextEditingController titleCtr;
TextEditingController subTitleCtr;
TextEditingController contentCtr;
_KnowledgeEditPageState({this.knowledge});
List<PlatformModel> platforms = GlobalProvide.getInstance().platforms;
String selectPlatform = "全平台";
@override
void initState() {
super.initState();
if(mounted){
if(knowledge != null){
isEdit = true;
titleCtr = TextEditingController(text: knowledge.title);
subTitleCtr = TextEditingController(text: knowledge.subTitle.replaceAll("|", "\n"));
contentCtr = TextEditingController(text: knowledge.content);
selectPlatform = GlobalProvide.getInstance().getPlatformTitle(knowledge.platform);
}else{
titleCtr = TextEditingController();
subTitleCtr = TextEditingController();
contentCtr = TextEditingController();
}
}
}
@override
void dispose() {
titleCtr?.dispose();
subTitleCtr?.dispose();
contentCtr?.dispose();
super.dispose();
}
/// 保存
void _save(BuildContext context) async{
String title = titleCtr.value.text.trim();
String subTitle = subTitleCtr.value.text.trim();
String content = contentCtr.value.text.trim();
if(title.isEmpty || title == ""){
UX.showToast("主标题不能为空");
return;
}
if(subTitle.isEmpty || subTitle == ""){
UX.showToast("副标题不能为空");
return;
}
if(content.isEmpty || content == ""){
UX.showToast("内容不能为空");
return;
}
if(selectPlatform.isEmpty || selectPlatform == ""){
UX.showToast("平台不能为空");
return;
}
FocusScope.of(context).requestFocus(FocusNode());
UX.showLoading(context, content: "保存中...");
GlobalProvide globalProvide = GlobalProvide.getInstance();
Map data = {
"uid": globalProvide.serviceUser.id,
"platform": globalProvide.getPlatformId(selectPlatform),
"title": title,
"id": knowledge?.id ?? null,
"sub_title": subTitle.replaceAll("\n", "|"),
"content": content,
};
Response response;
if(isEdit){
response = await KnowledgeService.getInstance().update(data: data);
}else{
response = await KnowledgeService.getInstance().add(data: data);
}
UX.hideLoading(context);
if(response.statusCode == 200){
UX.showToast("保存成功");
if(isEdit){
await KnowledgeProvide.getInstance().getKnowledge(knowledge.id);
}
Navigator.pop(context, true);
}else{
UX.showToast(response.data["message"]);
}
}
@override
Widget build(_) {
return PageContext(builder: (context){
ThemeData themeData = Theme.of(context);
Widget _lineItem({
String label,
String placeholder,
TextStyle style,
int minLines = 1,
int maxLines = 1,
bool autofocus = false,
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
TextEditingController controller,
Widget subChild = const SizedBox()
}){
return Container(
padding: EdgeInsets.symmetric(horizontal: ToPx.size(20),vertical: ToPx.size(10)),
child: DefaultTextStyle(
style: style,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
crossAxisAlignment: crossAxisAlignment,
children: <Widget>[
Text("$label"),
Expanded(
child: Input(
minLines: minLines,
maxLines: maxLines,
autofocus: autofocus,
bgColor: Colors.black.withAlpha(8),
borderRadius: BorderRadius.all(Radius.circular(3)),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(10)),
border: Border.all(style: BorderStyle.none, color: Colors.transparent),
height: ToPx.size(90),
contentPadding: EdgeInsets.symmetric(vertical: ToPx.size(15)),
textInputAction: maxLines > 1 ? TextInputAction.newline : null,
placeholder: placeholder,
controller: controller,
)
),
],
),
subChild,
Divider(height: ToPx.size(30),),
],)
)
);
}
return Scaffold(
backgroundColor: themeData.primaryColorLight,
appBar: customAppBar(
title: Text(
"${isEdit ? "编辑知识库" : "添加知识库"}",
style: themeData.textTheme.display1,
),
),
body: ListView(
children: <Widget>[
SizedBox(height: ToPx.size(20),),
_lineItem(
label: "主标题:",
placeholder: "请输入主标题",
style: themeData.textTheme.title,
controller: titleCtr,
autofocus: true
),
_lineItem(
minLines: 1,
maxLines: 8,
label: "副标题:",
placeholder: "请输入副标题(每行一条)",
style: themeData.textTheme.title,
controller: subTitleCtr,
crossAxisAlignment: CrossAxisAlignment.start,
subChild: Padding(
padding: EdgeInsets.symmetric(vertical: ToPx.size(8)),
child: Text("注意:副标题如有多条请换行处理", style: themeData.textTheme.caption.copyWith(
color: Colors.amber
),),
)
),
_lineItem(
minLines: 1,
maxLines: 40,
label: " 内容:",
placeholder: "请输入内容",
style: themeData.textTheme.title,
controller: contentCtr,
crossAxisAlignment: CrossAxisAlignment.start
),
DropdownButtonHideUnderline(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: ToPx.size(30)),
child: InputDecorator(
decoration: InputDecoration(
labelText: '所属平台',
hintText: '请选择所属平台',
labelStyle: themeData.textTheme.title.copyWith(
color: themeData.primaryColor,
fontSize: ToPx.size(38)
),
contentPadding: EdgeInsets.zero,
),
child: DropdownButton<String>(
value: selectPlatform,
onChanged: (String newValue) {
setState(() {
selectPlatform = newValue;
});
},
items: platforms.map<DropdownMenuItem<String>>((PlatformModel platform) {
return DropdownMenuItem<String>(
value: platform.title,
child: Text(platform.title, style: themeData.textTheme.title,),
);
}).toList(),
),
),
)
),
Button(
margin: EdgeInsets.symmetric(horizontal: ToPx.size(40), vertical: ToPx.size(200)),
child: Text("保存"),
withAlpha: 200,
onPressed: () => _save(context),
)
],
)
);
});
}
}
import 'package:kefu_workbench/core_flutter.dart';
class TemplatePage extends StatelessWidget {
final Map<dynamic, dynamic> arguments;
TemplatePage({this.arguments});
@override
Widget build(_) {
return PageContext(builder: (context){
ThemeData themeData = Theme.of(context);
return Scaffold(
appBar: customAppBar(
isShowLeading: false,
title: Text(
"TemplatePage title",
style: themeData.textTheme.display1,
)),
body: Text("TemplatePage"),
);
});
}
}
import 'package:dio/dio.dart';
import 'package:kefu_workbench/core_flutter.dart';
class PlatformEditPage extends StatefulWidget {
final Map<dynamic, dynamic> arguments;
PlatformEditPage({this.arguments});
@override
_PlatformEditPageState createState() => _PlatformEditPageState();
}
class _PlatformEditPageState extends State<PlatformEditPage> {
PlatformModel platform;
bool isEdit = false;
bool isSystem = false;
TextEditingController titleCtr;
TextEditingController aliasCtr;
/// save
void _save() async{
String title = titleCtr.value.text.trim();
String alias = aliasCtr.value.text.trim();
UX.showLoading(context, content: "保存中...");
Response response;
if(isEdit){
platform.title = title;
platform.alias = alias;
response = await PlatformService.getInstance().update(platform.toJson());
}else{
response = await PlatformService.getInstance().add({
"title": title,
"alias": alias
});
}
UX.hideLoading(context);
if(response.statusCode == 200){
UX.showToast("保存成功");
Navigator.pop(context, true);
}else{
UX.showToast(response.data["message"]);
}
}
/// _delete
void _delete(BuildContext context){
UX.alert(
context,
content: Text("是否删除该平台吗!"),
onConfirm: () async{
Response response = await PlatformService.getInstance().delete(platform.id);
if (response.data["code"] == 200) {
UX.showToast("删除成功");
Navigator.pop(context, true);
} else {
UX.showToast("${response.data["message"]}");
}
}
);
}
@override
void initState() {
super.initState();
if(mounted && widget.arguments != null){
isEdit = true;
platform = (widget.arguments['platform'] as PlatformModel);
isSystem = platform.system == 1;
titleCtr = TextEditingController(text: platform.title);
aliasCtr = TextEditingController(text: platform.alias);
}else{
titleCtr = TextEditingController();
aliasCtr = TextEditingController();
}
}
@override
void dispose() {
aliasCtr?.dispose();
titleCtr?.dispose();
super.dispose();
}
@override
Widget build(_) {
return PageContext(builder: (context){
ThemeData themeData = Theme.of(context);
return Scaffold(
appBar: customAppBar(
title: Text(
isEdit ? "编辑平台" : "添加平台",
style: themeData.textTheme.display1,
)),
body: ListView(
children: <Widget>[
SizedBox(
height: ToPx.size(20),
),
Container(
height: ToPx.size(90),
decoration: BoxDecoration(
color: Colors.white,
border: Border(bottom: BorderSide(color: themeData.dividerColor,width: ToPx.size(2)))
),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(40)),
child: Row(
children: <Widget>[
Text("平台名称:", style: themeData.textTheme.title,),
Expanded(
child: Input(
autofocus: !isSystem,
enabled: !isSystem,
border: Border.all(style: BorderStyle.none, color: Colors.transparent),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(10)),
placeholder: "请输入平台名称",
showClear: true,
controller: titleCtr,
),
)
],
),
),
Container(
height: ToPx.size(90),
decoration: BoxDecoration(
color: Colors.white,
border: Border(bottom: BorderSide(color: themeData.dividerColor,width: ToPx.size(2)))
),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(40)),
child: Row(
children: <Widget>[
Text("平台别名:", style: themeData.textTheme.title,),
Expanded(
child: Input(
enabled: !isSystem,
border: Border.all(style: BorderStyle.none, color: Colors.transparent),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(10)),
placeholder: "请输入平台别名",
showClear: true,
controller: aliasCtr,
),
)
],
),
),
Offstage(
offstage: !isSystem,
child: Padding(
padding: EdgeInsets.symmetric(vertical: ToPx.size(15)),
child: Text("系统内置,无法编辑或删除", style: themeData.textTheme.body2, textAlign: TextAlign.center,),
)
),
Offstage(
offstage: isSystem,
child: Button(
margin: EdgeInsets.symmetric(horizontal: ToPx.size(40), vertical: ToPx.size(isEdit ? 25 : 50)),
onPressed: _save,
withAlpha: 200,
child: Text("保存"),
),
),
Offstage(
offstage: !isEdit || isSystem,
child: Button(
color: Colors.red,
margin: EdgeInsets.symmetric(horizontal: ToPx.size(40)),
onPressed: () => _delete(context),
withAlpha: 200,
child: Text("删除"),
),
)
],
)
);
});
}
}
import 'package:dio/dio.dart';
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/provider/global.dart';
import 'package:kefu_workbench/provider/robot.dart';
class RobotDetailPage extends StatefulWidget {
final Map<dynamic, dynamic> arguments;
RobotDetailPage({this.arguments});
@override
_RotobDetailPageState createState() => _RotobDetailPageState();
}
class _RotobDetailPageState extends State<RobotDetailPage> {
RobotModel robot;
@override
void initState() {
super.initState();
robot = widget.arguments['robot'];
}
/// edit
void _goEdit(BuildContext context) async{
Navigator.pushNamed(context, "/robot_edit",arguments: {
"robot": robot
}).then((isSuccess) async{
if(isSuccess == true){
robot = RobotProvide.getInstance().getItem(robot.id);
setState(() {});
}
});
}
/// delete
void _delete(BuildContext context){
UX.alert(
context,
content: Text("是否删除该机器人吗!"),
onConfirm: () async{
Response response = await RobotService.getInstance().delete(id: robot.id);
if (response.data["code"] == 200) {
UX.showToast("删除成功");
RobotProvide.getInstance().deleteItem(robot.id);
Navigator.pop(context);
} else {
UX.showToast("${response.data["message"]}");
}
}
);
}
@override
Widget build(context) {
robot = RobotProvide.getInstance().getItem(robot.id);
return PageContext(builder: (context){
ThemeData themeData = Theme.of(context);
Widget _lineItem({
Widget label = const Text(""),
Widget icon = const Text(""),
String content,
TextStyle style,
Widget subChild = const SizedBox(),
CrossAxisAlignment contextCrossAxisAlignment = CrossAxisAlignment.start
}){
return Container(
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: themeData.dividerColor))
),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(20), vertical: ToPx.size(40)),
child: DefaultTextStyle(
style: style,
child: Row(
children: <Widget>[
icon,
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
crossAxisAlignment: contextCrossAxisAlignment,
children: <Widget>[
label,
Expanded(
child: Text("$content", textAlign: TextAlign.left,),
)
],
),
subChild
],),
)
],
)
)
);
}
return Scaffold(
backgroundColor: themeData.primaryColorLight,
appBar: customAppBar(
title: Text(
"${robot.nickname}",
style: themeData.textTheme.display1,
),
actions: [
Offstage(
offstage: GlobalProvide.getInstance()?.serviceUser?.root != 1,
child: Button(
height: ToPx.size(90),
useIosStyle: true,
color: Colors.transparent,
width: ToPx.size(150),
child: Text("编辑"),
onPressed: () => _goEdit(context)
)),
],
),
body: ListView(
children: <Widget>[
_lineItem(
icon: Avatar(
size: ToPx.size(100),
imgUrl: robot.avatar == null || robot.avatar.isEmpty ?
"http://qiniu.cmp520.com/avatar_default.png" : robot.avatar
),
content: " ${robot.nickname}",
style: themeData.textTheme.title,
contextCrossAxisAlignment: CrossAxisAlignment.center,
subChild: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Row(
children: <Widget>[
Text(" 服务平台:", style: themeData.textTheme.caption),
Text("${GlobalProvide.getInstance().getPlatformTitle(robot.platform)}", style: themeData.textTheme.caption),
],
),
RichText(
text: TextSpan(
style: themeData.textTheme.caption,
children: [
TextSpan(text: "状态:"),
TextSpan(text: robot.isRun == 1 ? "运行中" : "暂停中", style: themeData.textTheme.caption.copyWith(
color: robot.isRun == 1 ? Colors.green : Colors.amber
)),
]
))
],
)
),
_lineItem(
label: Text("欢迎语:"),
content: robot.welcome,
style: themeData.textTheme.body1,
),
_lineItem(
label: Text("无匹配:"),
content: robot.understand,
style: themeData.textTheme.body1,
),
_lineItem(
label: Text("超时语:"),
content: robot.timeoutText,
style: themeData.textTheme.body1,
),
_lineItem(
label: Text("无人工:"),
content: robot.noServices,
style: themeData.textTheme.body1,
),
_lineItem(
label: Text("时间长:"),
content: robot.loogTimeWaitText,
style: themeData.textTheme.body1,
),
_lineItem(
label: Text("检索词:"),
content: robot.keyword.replaceAll("|", "、"),
style: themeData.textTheme.body1,
),
_lineItem(
label: Text("转人工:"),
content: robot.artificial.replaceAll("|", "、"),
style: themeData.textTheme.body1,
),
Offstage(
offstage: robot.system == 1,
child: Button(
margin: EdgeInsets.symmetric(horizontal: ToPx.size(40), vertical: ToPx.size(50)),
child: Text("删除"),
withAlpha: 200,
color: Colors.redAccent,
onPressed: () => _delete(context),
),
)
],
)
);
});
}
}
import 'package:dio/dio.dart';
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/provider/global.dart';
import 'package:kefu_workbench/provider/robot.dart';
class RobotEditPage extends StatefulWidget {
final Map<dynamic, dynamic> arguments;
RobotEditPage({this.arguments});
@override
_RobotEditPageState createState() => _RobotEditPageState(
robot: arguments != null ? arguments['robot'] : null
);
}
class _RobotEditPageState extends State<RobotEditPage> {
final RobotModel robot;
bool isEdit = false;
TextEditingController nicknameCtr;
TextEditingController welcomeCtr;
TextEditingController understandCtr;
TextEditingController timeoutTextCtr;
TextEditingController noServicesCtr;
TextEditingController loogTimeWaitTextCtr;
TextEditingController keywordCtr;
TextEditingController artificialCtr;
_RobotEditPageState({this.robot});
List<PlatformModel> platforms = GlobalProvide.getInstance().platforms;
String selectPlatform = "全平台";
String avatar = "";
bool isSwitch = true;
@override
void initState() {
super.initState();
if(mounted){
if(robot != null){
isEdit = true;
nicknameCtr = TextEditingController(text: robot.nickname);
welcomeCtr = TextEditingController(text: robot.welcome);
understandCtr = TextEditingController(text: robot.understand);
timeoutTextCtr = TextEditingController(text: robot.timeoutText);
noServicesCtr = TextEditingController(text: robot.noServices);
loogTimeWaitTextCtr = TextEditingController(text: robot.loogTimeWaitText);
keywordCtr = TextEditingController(text: robot.keyword.replaceAll("|", "\n"));
artificialCtr = TextEditingController(text: robot.artificial.replaceAll("|", "\n"));
selectPlatform = GlobalProvide.getInstance().getPlatformTitle(robot.platform);
isSwitch = robot.isRun == 1;
avatar = robot.avatar;
}else{
nicknameCtr = TextEditingController();
welcomeCtr = TextEditingController();
understandCtr = TextEditingController();
noServicesCtr = TextEditingController();
timeoutTextCtr = TextEditingController();
loogTimeWaitTextCtr = TextEditingController();
keywordCtr = TextEditingController();
artificialCtr = TextEditingController();
}
}
}
@override
void dispose() {
nicknameCtr?.dispose();
welcomeCtr?.dispose();
understandCtr?.dispose();
noServicesCtr?.dispose();
timeoutTextCtr?.dispose();
loogTimeWaitTextCtr?.dispose();
keywordCtr?.dispose();
artificialCtr?.dispose();
super.dispose();
}
/// 选择图片上传
void _pickerImage() async{
String imgUser = await uploadImage<String>(context, maxWidth: 300.0);
if(imgUser == null) return;
avatar = imgUser;
setState(() {});
}
/// 保存
void _save(BuildContext context) async{
String nickname = nicknameCtr.value.text.trim();
String welcome = welcomeCtr.value.text.trim();
String understand = understandCtr.value.text.trim();
String noServices = noServicesCtr.value.text.trim();
String timeoutText = timeoutTextCtr.value.text.trim();
String loogTimeWaitText = loogTimeWaitTextCtr.value.text.trim();
String keyword = keywordCtr.value.text.trim();
String artificial = artificialCtr.value.text.trim();
FocusScope.of(context).requestFocus(FocusNode());
UX.showLoading(context, content: "保存中...");
GlobalProvide globalProvide = GlobalProvide.getInstance();
Map data = {
"id": robot != null ? robot.id : null,
"nickname": nickname,
"avatar": avatar,
"welcome": welcome,
"understand": understand,
"artificial": artificial.replaceAll("\n", "|"),
"keyword": keyword.replaceAll("\n", "|"),
"timeout_text": timeoutText,
"no_services": noServices,
"loog_time_wait_text": loogTimeWaitText,
"platform": globalProvide.getPlatformId(selectPlatform),
"switch": isSwitch ? 1 : 0
};
Response response;
if(isEdit){
response = await RobotService.getInstance().update(data: data);
}else{
response = await RobotService.getInstance().add(data: data);
}
UX.hideLoading(context);
if(response.statusCode == 200){
UX.showToast("保存成功");
if(isEdit){
await RobotProvide.getInstance().getRobot(robot.id);
}
Navigator.pop(context, true);
}else{
UX.showToast(response.data["message"]);
}
}
@override
Widget build(_) {
return PageContext(builder: (context){
ThemeData themeData = Theme.of(context);
Widget _tip(String content,{Color color}){
return Padding(
padding: EdgeInsets.symmetric(vertical: ToPx.size(8)),
child: Text(" $content", style: themeData.textTheme.caption.copyWith(
color: color == null ? themeData.disabledColor : color
),),
);
}
Widget _lineItem({
String label,
String placeholder,
TextStyle style,
int minLines = 1,
int maxLines = 1,
bool autofocus = false,
CrossAxisAlignment crossAxisAlignment = CrossAxisAlignment.center,
TextEditingController controller,
Widget subChild = const SizedBox()
}){
return Container(
padding: EdgeInsets.symmetric(horizontal: ToPx.size(20),vertical: ToPx.size(10)),
child: DefaultTextStyle(
style: style,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
crossAxisAlignment: crossAxisAlignment,
children: <Widget>[
Text("$label"),
Expanded(
child: Input(
minLines: minLines,
maxLines: maxLines,
autofocus: autofocus,
bgColor: Colors.black.withAlpha(8),
borderRadius: BorderRadius.all(Radius.circular(3)),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(10)),
border: Border.all(style: BorderStyle.none, color: Colors.transparent),
height: ToPx.size(90),
contentPadding: EdgeInsets.symmetric(vertical: ToPx.size(15)),
textInputAction: maxLines > 1 ? TextInputAction.newline : null,
placeholder: placeholder,
controller: controller,
)
),
],
),
subChild,
Divider(height: ToPx.size(30),),
],)
)
);
}
return Scaffold(
backgroundColor: themeData.primaryColorLight,
appBar: customAppBar(
title: Text(
"${isEdit ? "编辑机器人" : "添加机器人"}",
style: themeData.textTheme.display1,
),
),
body: ListView(
children: <Widget>[
SizedBox(height: ToPx.size(20),),
Container(
width: double.infinity,
color: Colors.white,
height: ToPx.size(250),
padding: EdgeInsets.symmetric(vertical: ToPx.size(30)),
child: Stack(
children: <Widget>[
Center(
child: Avatar(
size: ToPx.size(150),
imgUrl: avatar.isNotEmpty ? avatar : "http://qiniu.cmp520.com/avatar_default.png",
onPressed: _pickerImage
)
),
Center(
child: Padding(
padding: EdgeInsets.only(top: ToPx.size(120), left: ToPx.size(80)),
child: Icon(Icons.camera_alt, color:themeData.primaryColor.withAlpha(150), size: ToPx.size(35),),
)
)
],
),
),
_lineItem(
label: " 昵称:",
placeholder: "请输入机器人昵称",
style: themeData.textTheme.title,
controller: nicknameCtr,
autofocus: true
),
_lineItem(
minLines: 1,
maxLines: 10,
label: "欢迎语:",
placeholder: "请输入机器人欢迎语",
style: themeData.textTheme.title,
controller: welcomeCtr,
crossAxisAlignment: CrossAxisAlignment.start,
subChild: _tip("机器人自动回复的欢迎语句")
),
_lineItem(
minLines: 1,
maxLines: 10,
label: "无匹配:",
placeholder: "请输入无匹配知识库语",
style: themeData.textTheme.title,
controller: understandCtr,
crossAxisAlignment: CrossAxisAlignment.start,
subChild: _tip("机器人无法匹配知识库,回复该语句")
),
_lineItem(
minLines: 1,
maxLines: 10,
label: "超时语:",
placeholder: "请输入超时结束提示",
style: themeData.textTheme.title,
controller: timeoutTextCtr,
crossAxisAlignment: CrossAxisAlignment.start,
subChild: _tip("用户超时结束后,回复语")
),
_lineItem(
minLines: 1,
maxLines: 10,
label: "无人工:",
placeholder: "请输入无人工在线提示(每行一个)",
style: themeData.textTheme.title,
controller: noServicesCtr,
crossAxisAlignment: CrossAxisAlignment.start,
subChild: _tip("无客服在线提示")
),
_lineItem(
minLines: 1,
maxLines: 10,
label: "时间长:",
placeholder: "请输入长时间等待提示",
style: themeData.textTheme.title,
controller: loogTimeWaitTextCtr,
crossAxisAlignment: CrossAxisAlignment.start,
subChild: _tip("客服超过一定的时间没有回复用户提示语")
),
_lineItem(
minLines: 1,
maxLines: 10,
label: "检索词:",
placeholder: "请输入检索知识库热词(每行一个)",
style: themeData.textTheme.title,
controller: keywordCtr,
crossAxisAlignment: CrossAxisAlignment.start,
subChild: _tip("检索知识库词库,多个请换行处理", color: Colors.amber)
),
_lineItem(
minLines: 1,
maxLines: 10,
label: "转人工:",
placeholder: "请输入转人工关键词(每行一个)",
style: themeData.textTheme.title,
controller: artificialCtr,
crossAxisAlignment: CrossAxisAlignment.start,
subChild: _tip("接入人工关键词,多个请换行处理", color: Colors.amber)
),
Offstage(
offstage: isEdit && robot.system == 1,
child: DropdownButtonHideUnderline(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: ToPx.size(30)),
child: InputDecorator(
decoration: InputDecoration(
labelText: '所属平台',
hintText: '请选择所属平台',
labelStyle: themeData.textTheme.title.copyWith(
color: themeData.primaryColor,
fontSize: ToPx.size(38)
),
contentPadding: EdgeInsets.zero,
),
child: DropdownButton<String>(
value: selectPlatform,
onChanged: (String newValue) {
setState(() {
selectPlatform = newValue;
});
},
items: platforms.map<DropdownMenuItem<String>>((PlatformModel platform) {
return DropdownMenuItem<String>(
value: platform.title,
child: Text(platform.title, style: themeData.textTheme.title,),
);
}).toList(),
),
),
)
)
),
Offstage(
offstage: isEdit && robot.system == 1,
child: Container(
padding: EdgeInsets.symmetric(horizontal: ToPx.size(20),vertical: ToPx.size(10)),
child: Row(
children: <Widget>[
Text("运行状态:", style: themeData.textTheme.title,),
Platform.isAndroid ?
Switch(value: isSwitch, onChanged: (bool _isSwitch){
isSwitch = _isSwitch;
}, activeColor: themeData.primaryColor) :
Transform.scale(scale: .7, child: CupertinoSwitch(value: isSwitch, onChanged: (bool _isSwitch){
isSwitch = _isSwitch;
}, activeColor: themeData.primaryColor))
],
),
),
),
Button(
margin: EdgeInsets.symmetric(horizontal: ToPx.size(40), vertical: ToPx.size(200)),
child: Text("保存"),
withAlpha: 200,
onPressed: () => _save(context),
)
],
)
);
});
}
}
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/provider/global.dart';
import 'package:kefu_workbench/provider/robot.dart';
import 'package:provider/provider.dart';
class RobotsPage extends StatelessWidget {
final Map<dynamic, dynamic> arguments;
RobotsPage({this.arguments});
@override
Widget build(_) {
return ChangeNotifierProvider<RobotProvide>(
create: (_) => RobotProvide.getInstance(),
child: Consumer<RobotProvide>(builder: (context, robotState, _){
return PageContext(builder: (context){
ThemeData themeData = Theme.of(context);
return Scaffold(
backgroundColor: themeData.primaryColorLight,
appBar: customAppBar(
title: Text(
"机器人列表",
style: themeData.textTheme.display1,
),
actions: [
Offstage(
offstage: GlobalProvide.getInstance()?.serviceUser?.root != 1,
child: Button(
height: ToPx.size(90),
useIosStyle: true,
color: Colors.transparent,
width: ToPx.size(150),
child: Text("新增"),
onPressed: () => robotState.goAdd(context)
),
),
],
),
body:
robotState.isLoading && robotState.robots.length == 0 ? Center(
child: loadingIcon(size: ToPx.size(50)),
):
RefreshIndicator(
color: themeData.primaryColorLight,
backgroundColor: themeData.primaryColor,
onRefresh: robotState.onRefresh,
child: CustomScrollView(
scrollDirection: Axis.vertical,
physics: AlwaysScrollableScrollPhysics(),
slivers: <Widget>[
SliverToBoxAdapter(
child: Offstage(
offstage: robotState.robots.length > 0 || robotState.isLoading,
child: Padding(
padding: EdgeInsets.only(top: ToPx.size(50)),
child: Text("暂无数据~", style: themeData.textTheme.body1, textAlign: TextAlign.center,),
),
),
),
SliverList(
delegate: SliverChildBuilderDelegate((context, index){
RobotModel robot = robotState.robots[index];
return Column(
children: <Widget>[
ListTile(
onTap: () => Navigator.pushNamed(context, "/robot_detail",arguments: {
"robot": robot
}),
subtitle: Row(
children: <Widget>[
Text("服务平台:", style: themeData.textTheme.caption),
Text("${GlobalProvide.getInstance().getPlatformTitle(robot.platform)}", style: themeData.textTheme.caption),
],
),
trailing: RichText(
text: TextSpan(
style: themeData.textTheme.caption,
children: [
TextSpan(text: "状态:"),
TextSpan(text: robot.isRun == 1 ? "运行中" : "暂停中", style: themeData.textTheme.caption.copyWith(
color: robot.isRun == 1 ? Colors.green : Colors.amber
)),
]
),
),
leading: Avatar(
size: ToPx.size(100),
imgUrl: robot.avatar == null || robot.avatar.isEmpty ?
"http://qiniu.cmp520.com/avatar_default.png" : robot.avatar
),
title: Text("${robot.nickname}", style: themeData.textTheme.title, maxLines: 2, overflow: TextOverflow.ellipsis,),
),
Divider(height: 1.0,)
],
);
},
childCount: robotState.robots.length
),
),
],
)
),
);
});
},),
);
}
}
import 'package:dio/dio.dart';
import 'package:kefu_workbench/core_flutter.dart';
class ShortcutEditPage extends StatefulWidget {
final Map<dynamic, dynamic> arguments;
ShortcutEditPage({this.arguments});
@override
_ShortcutEditPageState createState() => _ShortcutEditPageState();
}
class _ShortcutEditPageState extends State<ShortcutEditPage> {
ShortcutModel shortcut;
bool isEdit = false;
TextEditingController titleCtr;
TextEditingController contentCtr;
/// save
void _save() async{
String title = titleCtr.value.text.trim();
String content = contentCtr.value.text.trim();
/// 判断title不能为空
if(title.isEmpty || title == ""){
UX.showToast("标题不能为空");
return;
}
/// 判断content不能为空
if(content.isEmpty || content == ""){
UX.showToast("内容不能为空");
return;
}
FocusScope.of(context).requestFocus(FocusNode());
UX.showLoading(context, content: "保存中...");
Response response;
if(isEdit){
response = await ShortcutService.getInstance().update({
"id": shortcut.id,
"title": title,
"content": content
});
}else{
response = await ShortcutService.getInstance().add({
"title": title,
"content": content
});
}
UX.hideLoading(context);
if(response.statusCode == 200){
UX.showToast("保存成功");
Navigator.pop(context, true);
}else{
UX.showToast(response.data["message"]);
}
}
/// _delete
void _delete(BuildContext context){
UX.alert(
context,
content: Text("是否删除该条快捷语吗!"),
onConfirm: () async{
Response response = await ShortcutService.getInstance().delete(shortcut.id);
if (response.data["code"] == 200) {
UX.showToast("删除成功");
Navigator.pop(context, true);
} else {
UX.showToast("${response.data["message"]}");
}
}
);
}
@override
void initState() {
super.initState();
if(mounted && widget.arguments != null){
isEdit = true;
shortcut = (widget.arguments['shortcut'] as ShortcutModel);
titleCtr = TextEditingController(text: shortcut.title);
contentCtr = TextEditingController(text: shortcut.content);
}else{
titleCtr = TextEditingController();
contentCtr = TextEditingController();
}
}
@override
void dispose() {
contentCtr?.dispose();
titleCtr?.dispose();
super.dispose();
}
@override
Widget build(_) {
return PageContext(builder: (context){
ThemeData themeData = Theme.of(context);
return Scaffold(
appBar: customAppBar(
title: Text(
isEdit ? "编辑快捷语" : "添加快捷语",
style: themeData.textTheme.display1,
)),
body: ListView(
children: <Widget>[
SizedBox(
height: ToPx.size(20),
),
Container(
height: ToPx.size(90),
decoration: BoxDecoration(
color: Colors.white,
border: Border(bottom: BorderSide(color: themeData.dividerColor,width: ToPx.size(2)))
),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(40)),
child: Row(
children: <Widget>[
Text("标题:", style: themeData.textTheme.title,),
Expanded(
child: Input(
autofocus: true,
border: Border.all(style: BorderStyle.none, color: Colors.transparent),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(10)),
placeholder: "请输入标题",
showClear: true,
controller: titleCtr,
),
)
],
),
),
Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border(bottom: BorderSide(color: themeData.dividerColor,width: ToPx.size(2)))
),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(40), vertical: ToPx.size(10)),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("内容:", style: themeData.textTheme.title,),
Expanded(
child: Input(
minLines: 10,
maxLines: 10,
textInputAction: TextInputAction.newline,
border: Border.all(style: BorderStyle.none, color: Colors.transparent),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(10)),
placeholder: "请输入内容",
controller: contentCtr,
),
)
],
),
),
Button(
margin: EdgeInsets.symmetric(horizontal: ToPx.size(40), vertical: ToPx.size(isEdit ? 25 : 50)),
onPressed: _save,
withAlpha: 200,
child: Text("保存"),
),
Offstage(
offstage: !isEdit,
child: Button(
color: Colors.red,
margin: EdgeInsets.symmetric(horizontal: ToPx.size(40)),
onPressed: () => _delete(context),
withAlpha: 200,
child: Text("删除"),
),
)
],
)
);
});
}
}
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/provider/shortcut.dart';
import 'package:provider/provider.dart';
class ShortcutsPage extends StatelessWidget {
final Map<dynamic, dynamic> arguments;
ShortcutsPage({this.arguments});
@override
Widget build(_) {
return ChangeNotifierProvider<ShortcutProvide>(
create: (_) => ShortcutProvide.getInstance(),
child: Consumer<ShortcutProvide>(builder: (context, shortcutState, _){
return PageContext(builder: (context){
ThemeData themeData = Theme.of(context);
return Scaffold(
backgroundColor: themeData.primaryColorLight,
appBar: customAppBar(
title: Text(
"快捷语列表(${shortcutState.shortcuts.length})",
style: themeData.textTheme.display1,
),
actions: [
Button(
height: ToPx.size(90),
useIosStyle: true,
color: Colors.transparent,
width: ToPx.size(150),
child: Text("新增"),
onPressed: () => shortcutState.goAdd(context)
),
],
),
body: shortcutState.isLoading && shortcutState.shortcuts.length == 0 ? Center(
child: loadingIcon(size: ToPx.size(50)),
):
RefreshIndicator(
color: themeData.primaryColorLight,
backgroundColor: themeData.primaryColor,
onRefresh: shortcutState.onRefresh,
child: CustomScrollView(
scrollDirection: Axis.vertical,
physics: AlwaysScrollableScrollPhysics(),
controller: shortcutState.scrollController,
slivers: <Widget>[
SliverToBoxAdapter(
child: Offstage(
offstage: shortcutState.shortcuts.length > 0 || shortcutState.isLoading,
child: Padding(
padding: EdgeInsets.only(top: ToPx.size(50)),
child: Text("暂无数据~", style: themeData.textTheme.body1, textAlign: TextAlign.center,),
),
),
),
SliverList(
delegate: SliverChildBuilderDelegate((context, index){
ShortcutModel shortcut = shortcutState.shortcuts[index];
return Column(
children: <Widget>[
ListTile(
onTap: () => shortcutState.goEdit(context, shortcut),
subtitle: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children:[
Text("${shortcut.content}", style: themeData.textTheme.caption),
Text("添加时间:${Utils.epocFormat(shortcut.createAt)}",
style: themeData.textTheme.caption.copyWith(
color: themeData.textTheme.caption.color.withAlpha(150),
fontSize: ToPx.size(24),
),)
]
),
title: Text("${index + 1}${shortcut.title}", style: themeData.textTheme.title, maxLines: 2, overflow: TextOverflow.ellipsis,),
),
Divider(height: 1.0,)
],
);
},
childCount: shortcutState.shortcuts.length
),
),
SliverToBoxAdapter(
child: Offstage(
child: Center(
child: SizedBox(
height: ToPx.size(150),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
loadingIcon(),
Text(' 内容加载中...',
style: themeData.textTheme.caption)
],
),
),
),
offstage: !shortcutState.isLoading
)
),
SliverToBoxAdapter(
child: Offstage(
child: Padding(
padding: EdgeInsets.symmetric(vertical: ToPx.size(40)),
child: Center(
child: Text(
'没有更多了', style: themeData.textTheme.caption)
),),
offstage: shortcutState.shortcuts.length == 0 || shortcutState.isLoading
)
),
],
)
),
);
});
},),
);
}
}
import 'package:kefu_workbench/core_flutter.dart';
import 'widget/flow.dart';
import 'widget/services.dart';
class StatisticalPage extends StatefulWidget {
final Map<dynamic, dynamic> arguments;
StatisticalPage({this.arguments});
@override
State<StatefulWidget> createState() {
return _StatisticalPage();
}
}
class _StatisticalPage extends State<StatisticalPage> with SingleTickerProviderStateMixin{
TabController tabController;
@override
void initState() {
super.initState();
tabController = TabController(vsync: this, length: 2);
}
@override
void dispose() {
tabController?.dispose();
super.dispose();
}
@override
Widget build(_) {
return PageContext(builder: (context){
ThemeData themeData = Theme.of(context);
return Scaffold(
appBar: customAppBar(
title: Text(
"统计",
style: themeData.textTheme.display1,
),
),
backgroundColor: Colors.white,
body: Column(
children: <Widget>[
SizedBox(
height: ToPx.size(80),
child: TabBar(
labelColor: themeData.primaryColor,
labelStyle: themeData.textTheme.body1.copyWith(color: themeData.primaryColor, fontWeight: FontWeight.w500),
unselectedLabelColor: themeData.accentColor,
indicatorWeight: ToPx.size(3),
controller: tabController,
tabs: <Widget>[
Tab(
child: Text("用户访问量"),
),
Tab(
child: Text("服务量统计"),
),
],
),),
Divider(height: 0.0,),
Expanded(
child: TabBarView(
controller: tabController,
physics: BouncingScrollPhysics(),
children: <Widget>[
FlowView(),
ServiceCountView(),
],),
)
],
),
);
});
}
}
import 'package:dio/dio.dart';
import 'package:kefu_workbench/core_flutter.dart';
class FlowView extends StatefulWidget{
@override
_FlowViewState createState() => _FlowViewState();
}
class _FlowViewState extends State<FlowView> with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
List<FlowModel> flows = [];
String dateStart;
String dateEnd;
PublicService publicService = PublicService.getInstance();
bool isLoading = true;
/// 获取信息
Future<void> _getIpStatistical() async{
await Future.delayed(Duration(milliseconds: 500));
Response response = await publicService.getIpStatistical(dateStart: dateStart, dateEnd: dateEnd);
if (response.data["code"] == 200) {
flows = (response.data['data'] as List).map((i) => FlowModel.fromJson(i)).toList();
} else {
UX.showToast("${response.data["message"]}");
}
isLoading = false;
setState(() {});
}
// 选择开始时间
void _onSelectStartDate(BuildContext context) async{
var _date = await UX.selectDatePicker(context, oldDate: dateStart);
if (_date == null || _date == dateStart) return;
if(DateTime.parse(_date).millisecondsSinceEpoch > DateTime.parse(dateEnd).millisecondsSinceEpoch){
UX.showToast("开始时间不能大于结束时间");
return;
}
dateStart = _date;
setState(() {});
_getIpStatistical();
}
// 选择结束时间
void _onSelectEndDate(BuildContext context) async{
var _date = await UX.selectDatePicker(context, oldDate: dateEnd);
if (_date == null || _date == dateEnd) return;
if(DateTime.parse(_date).millisecondsSinceEpoch < DateTime.parse(dateStart).millisecondsSinceEpoch){
UX.showToast("结束时间不能小于开始时间");
return;
}
dateEnd = _date;
setState(() {});
_getIpStatistical();
}
// 刷新
Future<bool> _onRefresh() async{
await _getIpStatistical();
UX.showToast("刷新成功", position: ToastPosition.top);
return true;
}
@override
void initState() {
super.initState();
if(mounted){
DateTime _dateTime = DateTime.now();
String _date = "${_dateTime.year}-${_dateTime.month < 10 ? '0'+_dateTime.month.toString() : _dateTime.month}-${_dateTime.day < 10 ? '0'+_dateTime.day.toString() : _dateTime.day}";
dateStart = _date;
dateEnd = _date;
_getIpStatistical();
}
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context);
if(isLoading){
return Center(
child: loadingIcon(size: ToPx.size(50)),
);
}
return Column(
children: <Widget>[
SizedBox(
height: ToPx.size(20),
),
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
GestureDetector(
onTap: () => _onSelectStartDate(context),
behavior: HitTestBehavior.opaque,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.date_range, color: themeData.textTheme.body1.color, size: ToPx.size(30),),
Text(" $dateStart", style: themeData.textTheme.body1,)
],
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: ToPx.size(50)),
child: Text("至", style: themeData.textTheme.body1,),
),
GestureDetector(
onTap: () => _onSelectEndDate(context),
behavior: HitTestBehavior.opaque,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.date_range, color: themeData.textTheme.body1.color, size: ToPx.size(30),),
Text(" $dateEnd", style: themeData.textTheme.body1,)
],
),
),
],
),
),
Divider(),
Expanded(
child: RefreshIndicator(
color: themeData.primaryColorLight,
backgroundColor: themeData.primaryColor,
onRefresh: () => _onRefresh(),
child: ListView.builder(
itemBuilder: (context, index){
FlowModel flow = flows[index];
return Column(children: <Widget>[
ListTile(
title: Row(children: <Widget>[
Text("${index+1}、", style: themeData.textTheme.title,),
Text("${flow.title}", style: themeData.textTheme.title,)
],),
trailing: Text("${flow.count}人次", style: themeData.textTheme.title.copyWith(
color: Colors.amber
),),
),
Divider(height: 0.0)
],);
},
itemCount: flows.length,
)
))
],
);
}
}
\ No newline at end of file
import 'package:dio/dio.dart';
import 'package:kefu_workbench/core_flutter.dart';
class ServiceCountView extends StatefulWidget{
@override
_ServiceCountViewState createState() => _ServiceCountViewState();
}
class _ServiceCountViewState extends State<ServiceCountView> with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
List<StatisticalCountModel> statisticalCountItems = [];
List<ServicesCountModel> servicesCountItems = [];
PageController pageController;
String dateStart;
String dateEnd;
PublicService publicService = PublicService.getInstance();
bool isLoading = true;
int pageIndex = 0;
int get servicesCounts{
int _count = 0;
for(var i = 0; i<servicesCountItems.length; i++){
_count += int.parse(servicesCountItems[i].count);
}
return _count;
}
/// 获取信息
Future<void> _getCountStatistical() async{
await Future.delayed(Duration(milliseconds: 500));
Response response = await publicService.getCountStatistical(dateStart: dateStart, dateEnd: dateEnd);
if (response.data["code"] == 200) {
servicesCountItems = (response.data['data']['members'] as List).map((i) => ServicesCountModel.fromJson(i)).toList();
statisticalCountItems = (response.data['data']['statistical'] as List).map((i) => StatisticalCountModel.fromJson(i)).toList();
printf(servicesCountItems.toList());
printf(statisticalCountItems.toList());
} else {
UX.showToast("${response.data["message"]}");
}
isLoading = false;
setState(() {});
}
// 选择开始时间
void _onSelectStartDate(BuildContext context) async{
var _date = await UX.selectDatePicker(context, oldDate: dateStart);
if (_date == null || _date == dateStart) return;
if(DateTime.parse(_date).millisecondsSinceEpoch > DateTime.parse(dateEnd).millisecondsSinceEpoch){
UX.showToast("开始时间不能大于结束时间");
return;
}
dateStart = _date;
setState(() {});
_getCountStatistical();
}
// 选择结束时间
void _onSelectEndDate(BuildContext context) async{
var _date = await UX.selectDatePicker(context, oldDate: dateEnd);
if (_date == null || _date == dateEnd) return;
if(DateTime.parse(_date).millisecondsSinceEpoch < DateTime.parse(dateStart).millisecondsSinceEpoch){
UX.showToast("结束时间不能小于开始时间");
return;
}
dateEnd = _date;
setState(() {});
_getCountStatistical();
}
// 刷新
Future<bool> _onRefresh() async{
await _getCountStatistical();
UX.showToast("刷新成功", position: ToastPosition.top);
return true;
}
@override
void initState() {
super.initState();
if(mounted){
DateTime _dateStartTime = DateTime.fromMillisecondsSinceEpoch(DateTime.now().millisecondsSinceEpoch - (86400000 * 6));
dateStart = "${_dateStartTime.year}-${_dateStartTime.month < 10 ? '0'+_dateStartTime.month.toString() : _dateStartTime.month}-${_dateStartTime.day < 10 ? '0'+_dateStartTime.day.toString() : _dateStartTime.day}";;
DateTime _dateEndTime = DateTime.now();
dateEnd = "${_dateEndTime.year}-${_dateEndTime.month < 10 ? '0'+_dateEndTime.month.toString() : _dateEndTime.month}-${_dateEndTime.day < 10 ? '0'+_dateEndTime.day.toString() : _dateEndTime.day}";;
_getCountStatistical();
pageController = PageController(initialPage: pageIndex);
}
}
@override
void dispose() {
pageController?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context);
if(isLoading){
return Center(
child: loadingIcon(size: ToPx.size(50)),
);
}
return Column(
children: <Widget>[
SizedBox(
height: ToPx.size(20),
),
Container(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
GestureDetector(
onTap: () => _onSelectStartDate(context),
behavior: HitTestBehavior.opaque,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.date_range, color: themeData.textTheme.body1.color, size: ToPx.size(30),),
Text(" $dateStart", style: themeData.textTheme.body1,)
],
),
),
Padding(
padding: EdgeInsets.symmetric(horizontal: ToPx.size(50)),
child: Text("至", style: themeData.textTheme.body1,),
),
GestureDetector(
onTap: () => _onSelectEndDate(context),
behavior: HitTestBehavior.opaque,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.date_range, color: themeData.textTheme.body1.color, size: ToPx.size(30),),
Text(" $dateEnd", style: themeData.textTheme.body1,)
],
),
),
],
),
),
Divider(),
Expanded(
child: PageView(
controller: pageController,
scrollDirection: Axis.vertical,
onPageChanged: (int index){
pageIndex = index;
setState(() {});
},
children: <Widget>[
Container(
child: Column(
children: <Widget>[
Text("各渠道服务量统计(${servicesCounts.toString() + '人次'})"),
Expanded(
child: DefaultTabController(
length: statisticalCountItems.length,
child: Column(children: <Widget>[
SizedBox(
height: ToPx.size(80),
child: TabBar(
labelColor: themeData.primaryColor,
isScrollable: true,
labelStyle: themeData.textTheme.body1.copyWith(color: themeData.primaryColor, fontWeight: FontWeight.w500),
unselectedLabelColor: themeData.accentColor,
indicatorWeight: ToPx.size(3),
tabs: statisticalCountItems.map((i) => Tab(
child: Text("${i.date}"),
)).toList()
),),
Divider(height: 0.0,),
Expanded(
child: TabBarView(
physics: BouncingScrollPhysics(),
children: statisticalCountItems.map((i){
return RefreshIndicator(
color: themeData.primaryColorLight,
backgroundColor: themeData.primaryColor,
onRefresh: () => _onRefresh(),
child: ListView.builder(
itemBuilder: (context, index){
StatisticalItems statisticalItem = i.statisticalItems[index];
return Column(children: <Widget>[
ListTile(
title: Row(children: <Widget>[
Text("${index+1}、", style: themeData.textTheme.title,),
Text("${statisticalItem.title}", style: themeData.textTheme.title,)
],),
trailing: Text("${statisticalItem.count}人", style: themeData.textTheme.title.copyWith(
color: Colors.green
),),
),
Divider(height: 0.0)
],);
},
itemCount: i.statisticalItems.length,
)
);
}).toList()),
)
],),
),
),
Offstage(
offstage: pageIndex == 1,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: (){
pageController.animateToPage(1, duration: Duration(milliseconds: 500), curve: Curves.ease);
},
child: Icon(Icons.keyboard_arrow_up, size: ToPx.size(100),color: Colors.grey,),
),
)
],
),
),
Container(
child: Column(
children: <Widget>[
Offstage(
offstage: pageIndex == 0,
child: GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: (){
pageController.animateToPage(0, duration: Duration(milliseconds: 500), curve: Curves.ease);
},
child: Icon(Icons.keyboard_arrow_down, size: ToPx.size(80),color: Colors.grey,),
),
),
Text("客服接入量统计(${servicesCounts.toString() + '人次'})"),
Divider(),
Expanded(
child: RefreshIndicator(
color: themeData.primaryColorLight,
backgroundColor: themeData.primaryColor,
onRefresh: () => _onRefresh(),
child: ListView.builder(
itemBuilder: (context, index){
ServicesCountModel servicesCountItem = servicesCountItems[index];
return Column(children: <Widget>[
ListTile(
title: Row(children: <Widget>[
Text("${index+1}、", style: themeData.textTheme.title,),
Text("${servicesCountItem.nickname}", style: themeData.textTheme.title,)
],),
trailing: Text("服务${servicesCountItem.count}人", style: themeData.textTheme.title.copyWith(
color: Colors.green
),),
),
Divider(height: 0.0)
],);
},
itemCount: servicesCountItems.length,
)
))
],
),
),
],
)
)
],
);
}
}
\ No newline at end of file
import 'package:kefu_workbench/core_flutter.dart';
import 'widget/base.dart';
import 'widget/company.dart';
import 'widget/platform.dart';
import 'widget/qiniu.dart';
class SystemPage extends StatefulWidget {
final Map<dynamic, dynamic> arguments;
SystemPage({this.arguments});
@override
State<StatefulWidget> createState() {
return _SystemPageState();
}
}
class _SystemPageState extends State<SystemPage> with SingleTickerProviderStateMixin{
TabController tabController;
@override
void initState() {
super.initState();
tabController = TabController(vsync: this, length: 4);
tabController.addListener((){
FocusScope.of(context).requestFocus(FocusNode());
});
}
@override
void dispose() {
tabController?.dispose();
super.dispose();
}
@override
Widget build(_) {
return PageContext(builder: (context){
ThemeData themeData = Theme.of(context);
return Scaffold(
backgroundColor: Colors.white,
appBar: customAppBar(
title: Text(
"系统设置",
style: themeData.textTheme.display1,
)),
body: Column(
children: <Widget>[
SizedBox(
height: ToPx.size(80),
child: TabBar(
labelColor: themeData.primaryColor,
isScrollable: true,
labelStyle: themeData.textTheme.body1.copyWith(color: themeData.primaryColor, fontWeight: FontWeight.w500),
unselectedLabelColor: themeData.accentColor,
indicatorWeight: ToPx.size(3),
controller: tabController,
tabs: <Widget>[
Tab(
child: Text("基本设置"),
),
Tab(
child: Text("公司信息"),
),
Tab(
child: Text("七牛云存储配置"),
),
Tab(
child: Text("客户端平台"),
),
],
),),
Divider(height: 0.0,),
Expanded(
child: TabBarView(
controller: tabController,
physics: BouncingScrollPhysics(),
children: <Widget>[
BaseSettingView(),
CompamySettingView(),
QiniuSettingView(),
PlatformSettingView(),
],),
)
],
),
);
});
}
}
import 'package:dio/dio.dart';
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/provider/global.dart';
import 'package:kefu_workbench/services/system_service.dart';
class BaseSettingView extends StatefulWidget{
@override
_BaseSettingViewState createState() => _BaseSettingViewState();
}
class _BaseSettingViewState extends State<BaseSettingView> with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
SystemService systemService = SystemService.getInstance();
TextEditingController systemNameCtr;
TextEditingController copyrightCtr;
String selectUploadTitle;
String logo;
bool isLoading = true;
List<UploadsConfigModel> uploadsConfig = [];
SystemInfoModel systemInfo;
/// 获取系统信息
void _getSystemInfo() async{
await Future.delayed(Duration(milliseconds: 500));
Response response = await systemService.getSystemInfo();
if (response.data["code"] == 200) {
systemInfo = SystemInfoModel.fromJson(response.data["data"]);
systemNameCtr = TextEditingController(text: systemInfo.title);
copyrightCtr = TextEditingController(text: systemInfo.copyRight);
logo = systemInfo.logo;
_getUploadsConfig();
setState(() {});
} else {
UX.showToast("${response.data["message"]}");
}
}
/// 选择图片上传
void _pickerImage() async{
String imgUser = await uploadImage<String>(context, maxWidth: 300.0);
if(imgUser == null) return;
logo = imgUser;
setState(() {});
}
/// 获取上传配置信息
void _getUploadsConfig() async{
Response response = await systemService.getUploadsConfig();
if (response.data["code"] == 200) {
uploadsConfig = (response.data["data"] as List).map((i){
UploadsConfigModel uploadsConfigModel = UploadsConfigModel.fromJson(i);
if(uploadsConfigModel.id == systemInfo.uploadMode){
selectUploadTitle = uploadsConfigModel.name;
}
return uploadsConfigModel;
}).toList();
setState(() {});
} else {
UX.showToast("${response.data["message"]}");
}
setState(() {
isLoading = false;
});
}
/// 保存
void _save() async{
systemInfo.title = systemNameCtr.value.text.trim();
systemInfo.copyRight = copyrightCtr.value.text.trim();
systemInfo.logo = logo;
for (UploadsConfigModel item in uploadsConfig) {
if(item.name == selectUploadTitle){
systemInfo.uploadMode = item.id;
break;
}
}
UX.showLoading(context, content: "保存中...");
Response response = await systemService.saveSystemInfo(systemInfo.toJson());
UX.hideLoading(context);
if (response.data["code"] == 200) {
UX.showToast("保存成功");
GlobalProvide.getInstance().getConfigs();
} else {
UX.showToast("${response.data["message"]}");
}
}
@override
void initState() {
super.initState();
if(mounted){
_getSystemInfo();
}
}
@override
void dispose() {
systemNameCtr?.dispose();
copyrightCtr?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context);
Widget _fromInput({
String label,
TextEditingController controller,
String placeholder,
bool enabled = true,
bool autofocus = false,
}){
return Container(
height: ToPx.size(90),
decoration: BoxDecoration(
color: Colors.white,
border: Border(bottom: BorderSide(color: themeData.dividerColor,width: ToPx.size(2)))
),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(40)),
child: Row(
children: <Widget>[
Text("$label", style: themeData.textTheme.title,),
Expanded(
child: Input(
enabled: enabled,
autofocus: autofocus,
border: Border.all(style: BorderStyle.none, color: Colors.transparent),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(10)),
placeholder: "$placeholder",
showClear: true,
controller: controller,
),
)
],
),
);
}
if(isLoading){
return Center(
child: loadingIcon(size: ToPx.size(50)),
);
}
return ListView(
children: <Widget>[
SizedBox(
height: ToPx.size(20),
),
Container(
width: double.infinity,
color: Colors.grey.withAlpha(20),
padding: EdgeInsets.all(ToPx.size(30)),
child: GestureDetector(
onTap: _pickerImage,
behavior: HitTestBehavior.opaque,
child: Stack(
children: <Widget>[
logo == null || logo.isEmpty ?
Center(
child: Padding(
padding: EdgeInsets.symmetric(vertical: ToPx.size(50)),
child: Text("点击上传LOGO", style: themeData.textTheme.caption,),
),
) :
Center(
child: CachedNetworkImage(
bgColor: Colors.transparent,
src: logo
)),
Align(
alignment: Alignment.bottomRight,
child: Icon(Icons.camera_alt, color:themeData.primaryColor.withAlpha(50), size: ToPx.size(35),)
)
],
)
)
),
_fromInput(
label: "系统名称:",
placeholder: "请输入系统名称",
controller: systemNameCtr
),
_fromInput(
label: "版权信息:",
placeholder: "请输入版权信息",
controller: copyrightCtr
),
DropdownButtonHideUnderline(
child: Padding(
padding: EdgeInsets.symmetric(horizontal: ToPx.size(30), vertical: ToPx.size(10)),
child: InputDecorator(
decoration: InputDecoration(
labelText: '选择资源存储空间服务商:',
hintText: '请选择所属平台',
labelStyle: themeData.textTheme.title.copyWith(
color: themeData.primaryColor,
fontSize: ToPx.size(36)
),
contentPadding: EdgeInsets.zero,
),
child: DropdownButton<String>(
value: selectUploadTitle,
onChanged: (String newValue) {
setState(() {
selectUploadTitle = newValue;
});
},
items: uploadsConfig.map<DropdownMenuItem<String>>((UploadsConfigModel item) {
return DropdownMenuItem<String>(
value: item.name,
child: Text(item.name, style: themeData.textTheme.title,),
);
}).toList(),
),
),
)
),
Button(
margin: EdgeInsets.symmetric(horizontal: ToPx.size(40), vertical: ToPx.size(80)),
onPressed: _save,
withAlpha: 200,
child: Text("保存"),
)
],
);
}
}
\ No newline at end of file
import 'package:dio/dio.dart';
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/services/system_service.dart';
class CompamySettingView extends StatefulWidget{
@override
_CompamySettingViewState createState() => _CompamySettingViewState();
}
class _CompamySettingViewState extends State<CompamySettingView> with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
SystemService systemService = SystemService.getInstance();
CompanyModel companyInfo;
TextEditingController companyNameCtr;
TextEditingController serviceTimeCtr;
TextEditingController emailCtr;
TextEditingController telCtr;
TextEditingController addrCtr;
bool isLoading = true;
String logo;
/// 获取系统信息
void _getCompanyInfo() async{
await Future.delayed(Duration(milliseconds: 500));
Response response = await systemService.getCompanyInfo();
if (response.data["code"] == 200) {
companyInfo = CompanyModel.fromJson(response.data["data"]);
companyNameCtr = TextEditingController(text: companyInfo.title);
serviceTimeCtr = TextEditingController(text: companyInfo.service);
emailCtr = TextEditingController(text: companyInfo.email);
telCtr = TextEditingController(text: companyInfo.tel);
addrCtr = TextEditingController(text: companyInfo.address);
logo = companyInfo.logo;
printf(companyInfo.toJson());
} else {
UX.showToast("${response.data["message"]}");
}
isLoading = false;
setState(() {});
}
/// 选择图片上传
void _pickerImage() async{
String imgUser = await uploadImage<String>(context, maxWidth: 300.0);
if(imgUser == null) return;
logo = imgUser;
setState(() {});
}
/// 保存
void _save() async{
companyInfo.title = companyNameCtr.value.text.trim();
companyInfo.service = serviceTimeCtr.value.text.trim();
companyInfo.email = emailCtr.value.text.trim();
companyInfo.tel = telCtr.value.text.trim();
companyInfo.address = addrCtr.value.text.trim();
companyInfo.logo = logo;
UX.showLoading(context, content: "保存中...");
Response response = await systemService.saveCompanyInfo(companyInfo.toJson());
UX.hideLoading(context);
if (response.data["code"] == 200) {
UX.showToast("保存成功");
} else {
UX.showToast("${response.data["message"]}");
}
}
@override
void initState() {
super.initState();
if(mounted){
_getCompanyInfo();
}
}
@override
void dispose() {
companyNameCtr?.dispose();
serviceTimeCtr?.dispose();
emailCtr?.dispose();
telCtr?.dispose();
addrCtr?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context);
Widget _fromInput({
String label,
TextEditingController controller,
String placeholder,
bool enabled = true,
bool autofocus = false,
}){
return Container(
height: ToPx.size(90),
decoration: BoxDecoration(
color: Colors.white,
border: Border(bottom: BorderSide(color: themeData.dividerColor,width: ToPx.size(2)))
),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(40)),
child: Row(
children: <Widget>[
Text("$label", style: themeData.textTheme.title,),
Expanded(
child: Input(
enabled: enabled,
autofocus: autofocus,
border: Border.all(style: BorderStyle.none, color: Colors.transparent),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(10)),
placeholder: "$placeholder",
showClear: true,
controller: controller,
),
)
],
),
);
}
if(isLoading){
return Center(
child: loadingIcon(size: ToPx.size(50)),
);
}
return ListView(
children: <Widget>[
SizedBox(
height: ToPx.size(20),
),
Container(
width: double.infinity,
color: Colors.grey.withAlpha(20),
padding: EdgeInsets.all(ToPx.size(30)),
child: GestureDetector(
onTap: _pickerImage,
behavior: HitTestBehavior.opaque,
child: Stack(
children: <Widget>[
logo == null || logo.isEmpty ?
Center(
child: Padding(
padding: EdgeInsets.symmetric(vertical: ToPx.size(50)),
child: Text("点击上传LOGO", style: themeData.textTheme.caption,),
),
) :
Center(
child: CachedNetworkImage(
bgColor: Colors.transparent,
src: logo
)),
Align(
alignment: Alignment.bottomRight,
child: Icon(Icons.camera_alt, color:themeData.primaryColor.withAlpha(50), size: ToPx.size(35),)
)
],
)
)
),
_fromInput(
label: "公司名称:",
placeholder: "请输入公司名称",
controller: companyNameCtr
),
_fromInput(
label: "服务时间:",
placeholder: "请输入服务时间",
controller: serviceTimeCtr
),
_fromInput(
label: "公司邮箱:",
placeholder: "请输入公司邮箱",
controller: emailCtr
),
_fromInput(
label: "公司电话:",
placeholder: "请输入公司电话",
controller: telCtr
),
Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border(bottom: BorderSide(color: themeData.dividerColor,width: ToPx.size(2)))
),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(40), vertical: ToPx.size(10)),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("公司地址:", style: themeData.textTheme.title,),
Expanded(
child: Input(
minLines: 5,
maxLines: 5,
textInputAction: TextInputAction.newline,
border: Border.all(style: BorderStyle.none, color: Colors.transparent),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(10)),
placeholder: "请输入公司地址",
controller: addrCtr,
),
)
],
),
),
Button(
margin: EdgeInsets.symmetric(horizontal: ToPx.size(40), vertical: ToPx.size(80)),
onPressed: _save,
withAlpha: 200,
child: Text("保存"),
)
],
);
}
}
\ No newline at end of file
import 'package:dio/dio.dart';
import 'package:kefu_workbench/core_flutter.dart';
class PlatformSettingView extends StatefulWidget{
@override
_PlatformSettingViewState createState() => _PlatformSettingViewState();
}
class _PlatformSettingViewState extends State<PlatformSettingView> with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
PlatformService platformService = PlatformService.getInstance();
List<PlatformModel> platforms = [];
bool isLoading = true;
/// 获取平台信息
Future<void> _getPlatforms() async{
await Future.delayed(Duration(milliseconds: 500));
Response response = await platformService.getPlatforms();
if (response.data["code"] == 200) {
platforms = (response.data["data"] as List).map((i) =>PlatformModel.fromJson(i)).toList();
} else {
UX.showToast("${response.data["message"]}");
}
isLoading = false;
setState(() {});
}
/// 刷新
Future<bool> _onRefresh() async{
await _getPlatforms();
UX.showToast("刷新成功", position: ToastPosition.top);
return true;
}
/// 去添加
void _goAdd(BuildContext context) async{
Navigator.pushNamed(context, "/platform_add").then((isSuccess){
if(isSuccess == true){
_getPlatforms();
}
});
}
/// 去编辑
void _goEdit(BuildContext context, PlatformModel platform){
Navigator.pushNamed(context, "/platform_edit", arguments: {
"platform": platform
}).then((isSuccess){
if(isSuccess == true){
_getPlatforms();
}
});
}
@override
void initState() {
super.initState();
if(mounted){
_getPlatforms();
}
}
@override
Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context);
if(isLoading){
return Center(
child: loadingIcon(size: ToPx.size(50)),
);
}
return RefreshIndicator(
color: themeData.primaryColorLight,
backgroundColor: themeData.primaryColor,
onRefresh: () => _onRefresh(),
child: CustomScrollView(
slivers: <Widget>[
SliverPadding(
padding: EdgeInsets.symmetric(vertical: ToPx.size(20)),
sliver: SliverToBoxAdapter(child: Text("通过该配置,对接的平台,机器人,知识库匹配等", style: themeData.textTheme.caption, textAlign: TextAlign.center,),),
),
SliverList(
delegate: SliverChildBuilderDelegate((context, index){
PlatformModel platform = platforms[index];
return Column(
children: <Widget>[
ListTile(
onTap: () => _goEdit(context, platform),
subtitle: Text("别名:${platform.alias}", style: themeData.textTheme.caption),
trailing: Text(platform.system == 1 ?"系统内置" : "自定义", style: themeData.textTheme.body1.copyWith(
color: platform.system == 1 ? Colors.amber : themeData.textTheme.body1.color
),),
title: Text("${index + 1}${platform.title}", style: themeData.textTheme.title, maxLines: 2, overflow: TextOverflow.ellipsis,),
),
Divider(height: 1.0,)
],
);
},
childCount: platforms.length
),
),
SliverToBoxAdapter(
child: Button(
withAlpha: 200,
margin: EdgeInsets.symmetric(horizontal: ToPx.size(40), vertical: ToPx.size(80)),
onPressed: () => _goAdd(context),
child: Text("添加"),
)
)
],
)
);
}
}
\ No newline at end of file
import 'package:dio/dio.dart';
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/services/system_service.dart';
class QiniuSettingView extends StatefulWidget{
@override
_QiniuSettingViewState createState() => _QiniuSettingViewState();
}
class _QiniuSettingViewState extends State<QiniuSettingView> with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => true;
SystemService systemService = SystemService.getInstance();
QiniuModel qiniuInfo;
TextEditingController bucketCtr;
TextEditingController accessKeyCtr;
TextEditingController secretKeyCtr;
TextEditingController hostCtr;
bool isLoading = true;
/// 获取系统信息
void _getQiniuInfo() async{
await Future.delayed(Duration(milliseconds: 500));
Response response = await systemService.getQiniuInfo();
if (response.data["code"] == 200) {
qiniuInfo = QiniuModel.fromJson(response.data["data"]);
bucketCtr = TextEditingController(text: qiniuInfo.bucket);
accessKeyCtr = TextEditingController(text: qiniuInfo.accessKey);
secretKeyCtr = TextEditingController(text: qiniuInfo.secretKey);
hostCtr = TextEditingController(text: qiniuInfo.host);
printf(qiniuInfo.toJson());
} else {
UX.showToast("${response.data["message"]}");
}
isLoading = false;
setState(() {});
}
/// 保存
void _save() async{
qiniuInfo.bucket = bucketCtr.value.text.trim();
qiniuInfo.accessKey = accessKeyCtr.value.text.trim();
qiniuInfo.secretKey = secretKeyCtr.value.text.trim();
qiniuInfo.host = hostCtr.value.text.trim();
UX.showLoading(context, content: "保存中...");
Response response = await systemService.saveQiniuInfo(qiniuInfo.toJson());
UX.hideLoading(context);
if (response.data["code"] == 200) {
UX.showToast("保存成功");
} else {
UX.showToast("${response.data["message"]}");
}
}
@override
void initState() {
super.initState();
if(mounted){
_getQiniuInfo();
}
}
@override
void dispose() {
bucketCtr?.dispose();
accessKeyCtr?.dispose();
secretKeyCtr?.dispose();
hostCtr?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context);
Widget _fromInput({
String label,
TextEditingController controller,
String placeholder,
bool enabled = true,
bool autofocus = false,
}){
return Container(
height: ToPx.size(90),
decoration: BoxDecoration(
color: Colors.white,
border: Border(bottom: BorderSide(color: themeData.dividerColor,width: ToPx.size(2)))
),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(40)),
child: Row(
children: <Widget>[
Text("$label", style: themeData.textTheme.title,),
Expanded(
child: Input(
enabled: enabled,
autofocus: autofocus,
border: Border.all(style: BorderStyle.none, color: Colors.transparent),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(10)),
placeholder: "$placeholder",
showClear: true,
controller: controller,
),
)
],
),
);
}
if(isLoading){
return Center(
child: loadingIcon(size: ToPx.size(50)),
);
}
return ListView(
children: <Widget>[
SizedBox(
height: ToPx.size(20),
),
_fromInput(
label: "Bucket:",
placeholder: "请输入Bucket",
controller: bucketCtr
),
_fromInput(
label: "accessKey:",
placeholder: "请输入accessKey",
controller: accessKeyCtr
),
_fromInput(
label: "secretKey:",
placeholder: "请输入secretKey",
controller: secretKeyCtr
),
_fromInput(
label: "Host:",
placeholder: "请输入Host",
controller: hostCtr
),
SizedBox(
height: ToPx.size(20),
),
Text("请不要随意修改该选项,可能会导致客户端上传不了文件或图片", style: themeData.textTheme.caption, textAlign: TextAlign.center,),
Button(
margin: EdgeInsets.symmetric(horizontal: ToPx.size(40), vertical: ToPx.size(80)),
onPressed: _save,
withAlpha: 200,
child: Text("保存"),
)
],
);
}
}
\ No newline at end of file
import 'package:dio/dio.dart';
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/provider/global.dart';
import 'package:kefu_workbench/provider/user.dart';
class UserDetailPage extends StatefulWidget {
final Map<dynamic, dynamic> arguments;
UserDetailPage({this.arguments});
@override
_UserDetailPageState createState() => _UserDetailPageState();
}
class _UserDetailPageState extends State<UserDetailPage> {
UserModel user;
@override
void initState() {
super.initState();
user = widget.arguments['user'];
}
/// edit
void _goEdit(BuildContext context) async{
Navigator.pushNamed(context, "/user_edit",arguments: {
"user": user
}).then((isSuccess) async{
if(isSuccess == true){
await UserProvide.getInstance().getUser(user.id);
user = UserProvide.getInstance().getItem(user.id);
setState(() {});
}
});
}
/// delete
void _delete(BuildContext context){
UX.alert(
context,
content: Text("是否删除该用户吗!"),
onConfirm: () async{
Response response = await UserService.getInstance().delete(id: user.id);
if (response.data["code"] == 200) {
UX.showToast("删除成功");
UserProvide.getInstance().deleteItem(user.id);
Navigator.pop(context);
} else {
UX.showToast("${response.data["message"]}");
}
}
);
}
@override
Widget build(context) {
user = UserProvide.getInstance().getItem(user.id);
return PageContext(builder: (context){
ThemeData themeData = Theme.of(context);
Widget _lineItem({
Widget label = const Text(""),
Widget icon = const Text(""),
String content,
TextStyle style,
Widget subChild = const SizedBox(),
CrossAxisAlignment contextCrossAxisAlignment = CrossAxisAlignment.start
}){
return Container(
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: themeData.dividerColor))
),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(20), vertical: ToPx.size(40)),
child: DefaultTextStyle(
style: style,
child: Row(
children: <Widget>[
icon,
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Row(
crossAxisAlignment: contextCrossAxisAlignment,
children: <Widget>[
label,
Expanded(
child: Text("$content", textAlign: TextAlign.left,),
)
],
),
subChild
],),
)
],
)
)
);
}
return Scaffold(
backgroundColor: themeData.primaryColorLight,
appBar: customAppBar(
title: Text(
"${user.nickname}",
style: themeData.textTheme.display1,
),
actions: [
Button(
height: ToPx.size(90),
useIosStyle: true,
color: Colors.transparent,
width: ToPx.size(150),
child: Text("编辑"),
onPressed: () => _goEdit(context)
),
],
),
body: ListView(
children: <Widget>[
_lineItem(
icon: Avatar(
size: ToPx.size(100),
imgUrl: user.avatar == null || user.avatar.isEmpty ?
"http://qiniu.cmp520.com/avatar_default.png" : user.avatar
),
content: " ${user.nickname}",
style: themeData.textTheme.title,
contextCrossAxisAlignment: CrossAxisAlignment.center,
subChild: Row(
children: <Widget>[
Text(" 所在平台:", style: themeData.textTheme.caption),
Text("${GlobalProvide.getInstance().getPlatformTitle(user.platform)}", style: themeData.textTheme.caption),
],
)
),
_lineItem(
label: Text("在线状态:"),
content: user.online ==1 ? "在线" : "离线",
contextCrossAxisAlignment: CrossAxisAlignment.center,
style: themeData.textTheme.body1,
),
_lineItem(
label: Text("业务平台ID:"),
content: user.uid.toString(),
contextCrossAxisAlignment: CrossAxisAlignment.center,
style: themeData.textTheme.body1,
),
_lineItem(
label: Text("所在地区:"),
content: user.address.isNotEmpty ? user.address : "未知地区",
contextCrossAxisAlignment: CrossAxisAlignment.center,
style: themeData.textTheme.body1,
),
_lineItem(
label: Text("联系方式:"),
content: user.phone.isNotEmpty ? user.phone : "未知联系方式",
contextCrossAxisAlignment: CrossAxisAlignment.center,
style: themeData.textTheme.body1,
),
_lineItem(
label: Text("备注信息:"),
content: user.remarks.isNotEmpty ? user.remarks : "未知设置备注",
contextCrossAxisAlignment: CrossAxisAlignment.center,
style: themeData.textTheme.body1,
),
_lineItem(
label: Text("注册时间:"),
content: Utils.formatDate(user.createAt),
contextCrossAxisAlignment: CrossAxisAlignment.center,
style: themeData.textTheme.body1,
),
Button(
margin: EdgeInsets.symmetric(horizontal: ToPx.size(40), vertical: ToPx.size(50)),
child: Text("删除"),
withAlpha: 200,
color: Colors.redAccent,
onPressed: () => _delete(context),
)
],
)
);
});
}
}
import 'package:dio/dio.dart';
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/provider/global.dart';
class UserEditPage extends StatefulWidget {
final Map<dynamic, dynamic> arguments;
UserEditPage({this.arguments});
@override
_UserEditPagePageState createState() => _UserEditPagePageState(arguments['user']);
}
class _UserEditPagePageState extends State<UserEditPage> {
final UserModel user;
_UserEditPagePageState(this.user);
TextEditingController nicknameCtr;
TextEditingController addrCtr;
TextEditingController phoneCtr;
TextEditingController remarksCtr;
TextEditingController idCtr;
/// save
void saveUser() async{
String nickname = nicknameCtr.value.text.trim();
UserModel user = UserModel(
nickname: nickname,
address: addrCtr.value.text.trim(),
phone: phoneCtr.value.text.trim(),
remarks: remarksCtr.value.text.trim(),
id: int.parse(idCtr.value.text),
);
Map useMap = user.toJson();
useMap.removeWhere((key, value) => value == null);
/// 判断昵称不能为空
if(nickname.isEmpty || nickname == ""){
UX.showToast("用户昵称不能为空");
return;
}
FocusScope.of(context).requestFocus(FocusNode());
UX.showLoading(context, content: "保存中...");
Response response = await UserService.getInstance().update(useMap);
UX.hideLoading(context);
if(response.statusCode == 200){
UX.showToast("保存成功");
Navigator.pop(context, true);
GlobalProvide.getInstance().getContacts();
}else{
UX.showToast(response.data["message"]);
}
}
@override
void initState() {
super.initState();
if(mounted && user!= null){
nicknameCtr = TextEditingController(text: user.nickname);
addrCtr = TextEditingController(text: user.address);
phoneCtr = TextEditingController(text: user.phone);
remarksCtr = TextEditingController(text: user.remarks);
idCtr = TextEditingController(text: user.id.toString());
}
}
@override
void dispose() {
nicknameCtr?.dispose();
addrCtr?.dispose();
phoneCtr?.dispose();
idCtr?.dispose();
remarksCtr?.dispose();
super.dispose();
}
@override
Widget build(_) {
return PageContext(builder: (context){
ThemeData themeData = Theme.of(context);
Widget _fromInput({
String label,
TextEditingController controller,
String placeholder,
bool enabled = true,
}){
return Container(
height: ToPx.size(90),
decoration: BoxDecoration(
color: Colors.white,
border: Border(bottom: BorderSide(color: themeData.dividerColor,width: ToPx.size(2)))
),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(40)),
child: Row(
children: <Widget>[
Text("$label", style: themeData.textTheme.title,),
Expanded(
child: Input(
enabled: enabled,
border: Border.all(style: BorderStyle.none, color: Colors.transparent),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(10)),
placeholder: "$placeholder",
showClear: true,
controller: controller,
),
)
],
),
);
}
return Scaffold(
appBar: customAppBar(
title: Text(
"编辑用户信息",
style: themeData.textTheme.display1,
)),
body: ListView(
children: <Widget>[
SizedBox(
height: ToPx.size(40),
),
_fromInput(
label: " 用户 ID:",
placeholder: "请输入用户昵称",
controller: idCtr,
enabled: false
),
_fromInput(
label: "用户昵称:",
placeholder: "请输入用户昵称",
controller: nicknameCtr
),
_fromInput(
label: "所在地区:",
placeholder: "请输入用户所在地区",
controller: addrCtr
),
_fromInput(
label: "联系方式:",
placeholder: "请输入用户联系方式",
controller: phoneCtr
),
Container(
height: ToPx.size(250),
decoration: BoxDecoration(
color: Colors.white,
border: Border(bottom: BorderSide(color: themeData.dividerColor,width: ToPx.size(2)))
),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(40), vertical: ToPx.size(10)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("备注信息:", style: themeData.textTheme.title,),
Expanded(
child: Input(
border: Border.all(style: BorderStyle.none, color: Colors.transparent),
placeholder: "请输入用户备注信息",
controller: remarksCtr,
textAlign: TextAlign.start,
minLines: 5,
maxLength: 100,
placeholderAlignment: Alignment.topLeft,
textInputAction: TextInputAction.newline,
maxLines: 5,
),
)
],
),
),
Button(
margin: EdgeInsets.symmetric(horizontal: ToPx.size(40), vertical: ToPx.size(80)),
onPressed: saveUser,
withAlpha: 200,
child: Text("保存"),
)
],
)
);
});
}
}
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/provider/global.dart';
import 'package:kefu_workbench/provider/user.dart';
import 'package:provider/provider.dart';
class UsersPage extends StatelessWidget {
final Map<dynamic, dynamic> arguments;
UsersPage({this.arguments});
@override
Widget build(_) {
return ChangeNotifierProvider<UserProvide>(
create: (_) => UserProvide.getInstance(),
child: Consumer<UserProvide>(builder: (context, userState, _){
return PageContext(builder: (context){
ThemeData themeData = Theme.of(context);
return Scaffold(
backgroundColor: themeData.primaryColorLight,
appBar: customAppBar(
title: Text(
"用户列表(${userState.usersTotal}人)",
style: themeData.textTheme.display1,
),
),
body:
userState.isLoading && userState.users.length == 0 ? Center(
child: loadingIcon(size: ToPx.size(50)),
):
RefreshIndicator(
color: themeData.primaryColorLight,
backgroundColor: themeData.primaryColor,
onRefresh: userState.onRefresh,
child: CustomScrollView(
controller: userState.scrollController,
slivers: <Widget>[
SliverToBoxAdapter(
child: Column(children: <Widget>[
Container(
decoration: BoxDecoration(
border: Border(bottom: BorderSide(color: themeData.dividerColor, width: ToPx.size(2)))
),
child: Row(
children: <Widget>[
Expanded(
child: Input(
onEditingComplete: userState.onSearch,
textInputAction: TextInputAction.search,
controller: userState.searchTextEditingController,
border: Border.all(style: BorderStyle.none, color: Colors.transparent),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(20)),
placeholder: "请输入关键词查找用户~",
onChanged: (value){
if(value.trim().isEmpty){
userState.isLoadEnd = false;
userState.pageOn = 0;
userState.keyword = "";
userState.getUsers();
}
},
),
),
IconButton(
icon: Icon(CupertinoIcons.search, color: Colors.grey),
onPressed: userState.onSearch,
)
],
),
)
],),
),
SliverToBoxAdapter(
child: Offstage(
offstage: userState.users.length > 0 || userState.isLoading,
child: Padding(
padding: EdgeInsets.only(top: ToPx.size(50)),
child: Text("暂无数据~", style: themeData.textTheme.body1, textAlign: TextAlign.center,),
),
),
),
SliverList(
delegate: SliverChildBuilderDelegate((context, index){
UserModel user = userState.users[index];
return Column(
children: <Widget>[
ListTile(
onTap: () => Navigator.pushNamed(context, "/user_detail",arguments: {
"user": user
}),
subtitle: Row(
children: <Widget>[
Text("所在平台:", style: themeData.textTheme.caption),
Expanded(child: Text("${GlobalProvide.getInstance().getPlatformTitle(user.platform)}", style: themeData.textTheme.caption, overflow: TextOverflow.ellipsis,))
],
),
trailing: Text("${Utils.formatDate(user.createAt)}"),
leading: Avatar(
size: ToPx.size(100),
imgUrl: user.avatar == null || user.avatar.isEmpty ?
"http://qiniu.cmp520.com/avatar_default.png" : user.avatar
),
title: Text("${user.nickname}", style: themeData.textTheme.title, maxLines: 2, overflow: TextOverflow.ellipsis,),
),
Divider(height: 1.0,)
],
);
},
childCount: userState.users.length
),
),
SliverToBoxAdapter(
child: Offstage(
child: Center(
child: SizedBox(
height: ToPx.size(150),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
loadingIcon(),
Text(' 内容加载中...',
style: themeData.textTheme.caption)
],
),
),
),
offstage: !userState.isLoading || userState.isLoadEnd
)
),
SliverToBoxAdapter(
child: Offstage(
child: Padding(
padding: EdgeInsets.symmetric(vertical: ToPx.size(40)),
child: Center(
child: Text(
'没有更多了', style: themeData.textTheme.caption)
),),
offstage: !userState.isLoadEnd || userState.users.length == 0
)
),
],
)
),
);
});
},),
);
}
}
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/provider/global.dart';
import 'package:kefu_workbench/provider/workorder.dart';
import 'package:provider/provider.dart';
class WorkOrderPage extends StatefulWidget {
final Map<dynamic, dynamic> arguments;
WorkOrderPage({this.arguments});
@override
State<StatefulWidget> createState() {
return _WorkOrderPageState();
}
}
class _WorkOrderPageState extends State<WorkOrderPage>
with SingleTickerProviderStateMixin {
TabController tabController;
GlobalProvide globalProvide = GlobalProvide.getInstance();
@override
void initState() {
super.initState();
_init();
}
void _init() async{
if(globalProvide.workOrderTypes.length == 0){
await globalProvide.getWorkorderTypes();
WorkOrderProvide.getInstance().getWorkOrders();
}
tabController = TabController(
vsync: this, length: globalProvide.workOrderTypes.length);
tabController.addListener(() {
WorkOrderProvide.getInstance()
.changeTabControllerIndex(tabController.index);
WorkOrderProvide.getInstance().getWorkOrders();
});
}
@override
void dispose() {
tabController?.dispose();
super.dispose();
}
@override
Widget build(_) {
return ChangeNotifierProvider<WorkOrderProvide>(
create: (_) => WorkOrderProvide.getInstance(),
child:
Consumer<WorkOrderProvide>(builder: (context, workorderState, ___) {
return PageContext(builder: (context) {
ThemeData themeData = Theme.of(context);
Widget _loading() {
return SizedBox(
width: ToPx.size(30),
height: ToPx.size(30),
child: Platform.isAndroid
? CircularProgressIndicator(
strokeWidth: 2.0,
)
: CupertinoActivityIndicator(
radius: 8.0,
),
);
}
List<Widget> _tabView() {
List<Widget> _widgets = [];
for (var i = 0; i < globalProvide.workOrderTypes.length; i++) {
int tid = globalProvide.workOrderTypes[i].id;
var workOrders = workorderState.getWorkOrder(tid);
_widgets.add(RefreshIndicator(
color: themeData.primaryColorLight,
backgroundColor: themeData.primaryColor,
onRefresh: () async{
workorderState.isLoadEnd = false;
await WorkOrderProvide.getInstance().getWorkOrders();
await globalProvide.getWorkorderTypes();
return true;
},
child: CustomScrollView(
physics: AlwaysScrollableScrollPhysics(),
slivers: <Widget>[
SliverList(
delegate: SliverChildBuilderDelegate((ctx, index) {
var workOrder = workOrders[index];
return Column(
children: <Widget>[
ListTile(
onTap: (){
Navigator.pushNamed(
context, "/workorder/detail",
arguments: {"workOrder": workOrder}).then((_){
WorkOrderProvide.getInstance().getWorkOrders();
});
},
title: Padding(
padding: EdgeInsets.symmetric(
vertical: ToPx.size(10)),
child: Row(
children: <Widget>[
Padding(
padding:EdgeInsets.only(right: ToPx.size(20)),
child: Icon(
Icons.library_books,
color: themeData.textTheme.title.color.withAlpha(180),
size: ToPx.size(50),
),
),
Expanded(
child: Column(
crossAxisAlignment:CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.only(bottom: ToPx.size(10)),
child: Row(
children: <Widget>[
Expanded(child: Text("${workOrder.title}",
style:themeData.textTheme.title,
overflow: TextOverflow.ellipsis,
)),
workorderState.tabControllerIndex == globalProvide.workOrderTypes.length - 1
? Text("已删除", style: themeData.textTheme.caption.copyWith(color:Colors.red))
: Text(
workOrder.status == 2 ? "已回复" :
workOrder.status == 3 ? "已结束" :
workOrder.status == 0 ? "待处理" :
workOrder.status == 1 ? "待回复" : "未知状态",
style: themeData.textTheme.caption.copyWith(
color: workOrder.status == 2 ? Color(0XFF8bc34a) :
workOrder.status == 3 ? Color(0XFFcccCCC) :
workOrder.status == 0 ? Color(0XFFFF9800) :
workOrder.status == 1 ? Color(0XFFFF9800) : Colors.amber),
)
],
),
),
Row(
mainAxisAlignment:MainAxisAlignment.spaceBetween,
children: <Widget>[
Text("时间:${Utils.formatDate(workOrder.createAt, isformatFull: true)}", style: themeData.textTheme.caption),
Text("最后处理:${workOrder.aNickname.isEmpty ? '---' : workOrder.aNickname}", style: themeData.textTheme.caption),
],
)
],
)),
],
)),
),
Divider(height: 1.0)
],
);
}, childCount: workOrders.length)),
SliverToBoxAdapter(
child: Offstage(
offstage: workorderState.isLoading || workOrders.length > 0,
child: Padding(
padding: EdgeInsets.symmetric(vertical: ToPx.size(30)),
child: Column(
children: <Widget>[
Icon(
Icons.library_books,
color: themeData.textTheme.title.color.withAlpha(150),
size: ToPx.size(60),
),
Text("暂无相关数据~",style: themeData.textTheme.body1),
],
),
),
),
),
SliverToBoxAdapter(
child: Offstage(
offstage: !workorderState.isLoading,
child: Padding(
padding:
EdgeInsets.symmetric(vertical: ToPx.size(20)),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
_loading(),
Text(" 加载中...",style: themeData.textTheme.body1),
],
),
),
),
),
],
)));
}
return _widgets;
}
return Scaffold(
backgroundColor: Colors.white,
appBar: customAppBar(
title: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
"工单管理 ",
style: themeData.textTheme.display1,
),
Offstage(
offstage: globalProvide.newWorkHandleCounts == 0,
child: Align(
alignment: Alignment.topRight,
child: Container(
margin: EdgeInsets.only(top: ToPx.size(5)),
child: Center(
child: Text(
"${globalProvide.newWorkHandleCounts}",
style: themeData.textTheme.caption.copyWith(
color: Colors.white,
fontSize: ToPx.size(20)),
),
),
width: ToPx.size(35),
height: ToPx.size(35),
decoration: BoxDecoration(
color: Colors.red, shape: BoxShape.circle),
),
),
)
],
),
actions: [
Button(
height: ToPx.size(90),
useIosStyle: true,
color: Colors.transparent,
width: ToPx.size(150),
child: Text("设置"),
onPressed: () => Navigator.pushNamed(context, "/workorder/setting")),
]),
body: globalProvide.workOrderTypes.length == 0
? Center(child: _loading())
: Column(
children: <Widget>[
SizedBox(
height: ToPx.size(80),
child: TabBar(
isScrollable: true,
controller: tabController,
labelColor: themeData.primaryColor,
labelStyle: themeData.textTheme.body1.copyWith(
color: themeData.primaryColor,
fontWeight: FontWeight.w500),
unselectedLabelColor: themeData.accentColor,
indicatorWeight: ToPx.size(3),
tabs: globalProvide.workOrderTypes
.map((i) => Tab(
child: Text("${i.title}(${i.count})"),
))
.toList()),
),
Divider(
height: 0.0,
),
Expanded(
child: NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification notification) {
double progress = notification.metrics.pixels /
notification.metrics.maxScrollExtent;
if (progress != null &&
progress >= 1.05 &&
progress < 2.0 &&
!workorderState.isLoading &&
!workorderState.isLoadEnd) {
var pageOn = workorderState.workOrderRequest['page_on'] + 1;
WorkOrderProvide.getInstance().getWorkOrders(pageOn: pageOn);
return true;
}
return true;
},
child: TabBarView(
controller: tabController,
physics: BouncingScrollPhysics(),
children: _tabView().toList()),
)),
],
),
);
});
}));
}
}
import 'package:flutter/services.dart';
import 'package:flutter_advanced_networkimage/zoomable.dart';
import 'package:flutter_html/flutter_html.dart';
import 'package:flutter_html/image_properties.dart';
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/models/work_order.dart';
import 'package:kefu_workbench/models/work_order_comment.dart';
import 'package:kefu_workbench/provider/global.dart';
import 'package:kefu_workbench/provider/workorder_detail.dart';
import 'package:provider/provider.dart';
class WorkOrderDetailPage extends StatefulWidget {
final Map<dynamic, dynamic> arguments;
WorkOrderDetailPage({this.arguments});
@override
State<StatefulWidget> createState() {
return _WorkOrderDetailPageState();
}
}
class _WorkOrderDetailPageState extends State<WorkOrderDetailPage> {
GlobalProvide globalProvide = GlobalProvide.getInstance();
WorkOrderDetailProvide workOrderDetailProvide = WorkOrderDetailProvide.getInstance();
WorkOrderModel _workOrder;
String _getTypeTitle(int tid) {
return globalProvide.workOrderTypes.where((item) => item.id == tid).first.title;
}
@override
void initState() {
super.initState();
_workOrder = widget.arguments['workOrder'];
workOrderDetailProvide.getWorkOrder(_workOrder.id);
workOrderDetailProvide.getWorkOrderComments(_workOrder.id);
setState(() {});
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(_) {
return ChangeNotifierProvider<WorkOrderDetailProvide>(
create: (_) => workOrderDetailProvide,
child: Consumer<WorkOrderDetailProvide>(
builder: (context, workorderDetailState, ___) {
return PageContext(builder: (context) {
ThemeData themeData = Theme.of(context);
var workOrder = workorderDetailState.workOrder ?? _workOrder;
Widget _html(String data) {
bool isShowimage = data.contains(">下载附件</a>");
return Html(
data: "$data",
showImages: !isShowimage,
imageProperties: ImageProperties(height: ToPx.size(250)),
onLinkTap: (String link) {
Clipboard.setData(ClipboardData(text: link));
UX.alert(context, content: "附件下载链接已复制到粘贴板");
},
onImageTap: (String img) {
if (img == null || img.isEmpty) return;
showDialog(
context: context,
builder: (context) {
return Center(
child: ZoomableWidget(
onTap: () => Navigator.pop(context),
maxScale: 2.0,
minScale: 0.5,
child: CachedNetworkImage(
width: MediaQuery.of(context).size.width / 1,
height: MediaQuery.of(context).size.height / 1,
src: img,
bgColor: Colors.transparent,
fit: BoxFit.fitWidth),
));
});
},
);
}
Widget _lineBox(
{String label, dynamic content, Color contentColor}) {
return Container(
padding: EdgeInsets.symmetric(horizontal: ToPx.size(20), vertical: ToPx.size(10)),
child: DefaultTextStyle(
style: themeData.textTheme.title,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text("$label"),
Expanded(
child: content is String
? Text(
"$content",
style: TextStyle(color: contentColor),
)
: content is Widget ? content : Text("")),
],
)),
);
}
Widget _conment(WorkOrderCommentModel comment) {
return Container(
padding: EdgeInsets.only(bottom: ToPx.size(20)),
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(horizontal: ToPx.size(20)),
child: Divider(
height: 0.5,
),
),
SizedBox(
height: ToPx.size(20),
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.symmetric(
horizontal: ToPx.size(20), vertical: ToPx.size(10)),
child: comment.aid == 0
? Avatar(
imgUrl: comment.uAvatar.isNotEmpty
? comment.uAvatar
: "http://qiniu.cmp520.com/avatar_degault_3.png",
)
: Avatar(
imgUrl: comment.aAvatar.isNotEmpty
? comment.aAvatar
: "http://qiniu.cmp520.com/avatar_degault_3.png",
)),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
comment.aid == 0
? "${comment.uNickname}"
: "${comment.aNickname}",
style: themeData.textTheme.body1),
Padding(
padding: EdgeInsets.only(bottom: ToPx.size(10)),
child: _html(comment.content)),
Text(
"${Utils.formatDate(comment.createAt, isformatFull: true)}",
style: themeData.textTheme.caption
),
],
),
)
],
)
],
),
);
}
Widget _inputForm() {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
width: double.infinity,
decoration: BoxDecoration(
color: Colors.white,
border: Border(
top: BorderSide(
color: themeData.dividerColor, width: ToPx.size(10)))),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(20)),
child: workorderDetailState.isUploadFail
? Text("上传失败了~",
style: themeData.textTheme.caption)
: workorderDetailState.uploadProgress > 0 && workorderDetailState.uploadProgress < 100
? Text(
"文件上传中${workorderDetailState.uploadProgress}%...",
style: themeData.textTheme.caption.copyWith(
color: Colors.grey
)
)
: Offstage(
offstage: workorderDetailState.html.isEmpty,
child: Row(
children: <Widget>[
Icon(
Icons.attach_file,
size: 15.0,
color: Colors.grey,
),
Text(
"你已成功添加附件,重新上传可替换~",
style: themeData.textTheme.caption.copyWith(
color: Color(0XFF8bc34a)
))
],
),
)),
Container(
height: ToPx.size(150),
padding: EdgeInsets.symmetric(horizontal: ToPx.size(15)),
decoration: BoxDecoration(
color: Colors.white,
border: Border(
top: BorderSide(
color: themeData.dividerColor, width: ToPx.size(1)))),
child: Column(
children: <Widget>[
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Expanded(
child: TextField(
cursorColor: themeData.primaryColor,
controller: workorderDetailState.replyInputCtr,
decoration: InputDecoration(
hintText: "请输入内容~",
border: InputBorder.none,
hintStyle: TextStyle(
fontSize: ToPx.size(28),
color: Colors.grey.withAlpha(150),
),
counterStyle: TextStyle(
color: Colors.grey.withAlpha(200)),
contentPadding:
EdgeInsets.symmetric(vertical: 3.0),
counterText: ""),
style: TextStyle(
color: Colors.black87.withAlpha(180),
fontSize: 15.0,
),
minLines: 1,
maxLines: 3,
),
),
SizedBox(
height: ToPx.size(143),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
IconButton(
icon: Icon(
Icons.cloud_upload,
size: ToPx.size(60),
color: Colors.black87.withAlpha(50),
),
onPressed: () => workorderDetailState.onPickFile(context),
),
SizedBox(
width: ToPx.size(25),
),
SizedBox(
width: ToPx.size(145),
height: ToPx.size(65),
child: RaisedButton(
color: themeData.primaryColor,
onPressed: workorderDetailState.reply,
child: Text(
"提交",
style: TextStyle(color: Colors.white),
)),
)
],
),
)
],
)
],
),
)
],
);
}
Widget _loading() {
return Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SizedBox(
width: ToPx.size(30),
height: ToPx.size(30),
child: Platform.isAndroid
? CircularProgressIndicator(
strokeWidth: 2.0,
)
: CupertinoActivityIndicator(
radius: 15.0,
)),
Text("\r\n加载中...", style: themeData.textTheme.caption,)
]
),
);
}
return Scaffold(
backgroundColor: Colors.white,
appBar: customAppBar(
title: Text(
"工单详情",
style: themeData.textTheme.display1,
),
actions: [
Offstage(
offstage: workOrder.status == 3 || workOrder.delete == 1,
child: Button(
height: ToPx.size(90),
useIosStyle: true,
color: Colors.transparent,
width: ToPx.size(150),
child: Text("关闭工单"),
onPressed: ()=>workorderDetailState.closeWorkOrder(context)),
),
Offstage(
offstage: workOrder.status != 3 || workOrder.delete == 1,
child: Button(
height: ToPx.size(90),
useIosStyle: true,
color: Colors.transparent,
width: ToPx.size(150),
child: Text("删除工单"),
onPressed: ()=>workorderDetailState.deleteWorkOrder(context)),
)
]),
body: workorderDetailState.isLoading ?
_loading() :
Column(
children: <Widget>[
Expanded(
child: RefreshIndicator(
color: themeData.primaryColorLight,
backgroundColor: themeData.primaryColor,
onRefresh: () async{
await workOrderDetailProvide.getWorkOrder(_workOrder.id, isLoading: false);
await workOrderDetailProvide.getWorkOrderComments(_workOrder.id);
return true;
},
child: CustomScrollView(
physics: AlwaysScrollableScrollPhysics(),
controller: workorderDetailState.customScrollView,
slivers: <Widget>[
SliverToBoxAdapter(
child: Column(
children: <Widget>[
SizedBox(height: 10.0),
_lineBox(
label: "用户:",
content: "${workOrder.uNickname}"),
_lineBox(
label: "标题:",
content: "${workOrder.title}"),
_lineBox(
label: "内容:",
content: _html("${workOrder.content}")),
_lineBox(
label: "电话:",
content: "${workOrder.phone}"),
_lineBox(
label: "邮箱:",
content: "${workOrder.email.isEmpty ? '未提供邮箱' : workOrder.email}"),
_lineBox(
label: "时间:",
content: "${Utils.formatDate(workOrder.createAt, isformatFull: true)}"),
_lineBox(
label: "类型:",
content:"${_getTypeTitle(workOrder.tid)}"),
_lineBox(
label: "状态:",
content: Text(
workOrder.status == 2 ? "已回复" :
workOrder.status == 3 ? "已结束" :
workOrder.status == 0 ? "待处理" :
workOrder.status == 1 ? "待回复" : "未知状态",
style: TextStyle(
color: workOrder.status == 2 ? Color(0XFF8bc34a) :
workOrder.status == 3 ? Color(0XFFcccCCC) :
workOrder.status == 0 ? Color(0XFFFF9800) :
workOrder.status == 1 ? Color(0XFFFF9800) : Colors.amber),
)),
],
),
),
workorderDetailState.workOrderComments.length == 0
? SliverToBoxAdapter(
child: Column(
children: <Widget>[
Divider(),
Text(
"暂无回复内容~",
style: TextStyle(
fontSize: 14.0,
color: Colors.grey),
),
SizedBox(height: 50.0)
],
),
)
: SliverList(
delegate: SliverChildBuilderDelegate(
(ctx, index) {
var comment = workorderDetailState.workOrderComments[index];
return _conment(comment);
}, childCount: workorderDetailState.workOrderComments.length)),
SliverToBoxAdapter(
child: Offstage(
offstage: workOrder.status != 3,
child: Column(
children: <Widget>[
Divider(),
Text(
"工单已结束~",
style: TextStyle(
fontSize: 14.0,
color: Colors.grey),
),
SizedBox(height: 50.0)
],
),
),
),
],
))),
Offstage(
offstage: workOrder.status == 3,
child: _inputForm(),
)
],
));
});
}));
}
}
import 'package:kefu_workbench/core_flutter.dart';
import 'package:kefu_workbench/models/work_order_type.dart';
import 'package:kefu_workbench/provider/global.dart';
import 'package:kefu_workbench/provider/workorde_setting.dart';
import 'package:provider/provider.dart';
class WorkOrderSettingPage extends StatefulWidget {
final Map<dynamic, dynamic> arguments;
WorkOrderSettingPage({this.arguments});
@override
State<StatefulWidget> createState() {
return _WorkOrderSettingPageState();
}
}
class _WorkOrderSettingPageState extends State<WorkOrderSettingPage> {
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(_) {
return ChangeNotifierProvider<WorkOrderSettingProvide>(
create: (_) => WorkOrderSettingProvide.getInstance(),
child: Consumer<WorkOrderSettingProvide>(
builder: (context, workorderSettingState, ___) {
return PageContext(builder: (context) {
ThemeData themeData = Theme.of(context);
return Scaffold(
backgroundColor: Colors.white,
appBar: customAppBar(
title: Text(
"工单设置",
style: themeData.textTheme.display1,
),
actions: [
Button(
height: ToPx.size(90),
useIosStyle: true,
color: Colors.transparent,
width: ToPx.size(150),
child: Text("添加分类"),
onPressed: () => workorderSettingState.createWorkOrder(context),)
]),
body: Column(
children: <Widget>[
SizedBox(
height: ToPx.size(80),
child: Stack(
children: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Platform.isAndroid
? Switch(
value: workorderSettingState.isOpenWorkorder,
onChanged: (bool isSwitch) => workorderSettingState.setOpenWorkorder(context, isSwitch),
activeColor: Color(0xFF8bc34a))
: Transform.scale(
scale: .7,
child: CupertinoSwitch(
value: workorderSettingState.isOpenWorkorder,
onChanged: (bool isSwitch) => workorderSettingState.setOpenWorkorder(context, isSwitch),
activeColor: Color(0xFF8bc34a))),
Text(workorderSettingState.isOpenWorkorder ? "工单功能启用中" : "工单功能关闭中", style: themeData.textTheme.title.copyWith(
color: workorderSettingState.isOpenWorkorder ? Color(0xFF8bc34a) : Color(0xFFcccccc)
),),
],
),
GestureDetector(
behavior: HitTestBehavior.opaque,
onHorizontalDragUpdate: (_) => workorderSettingState.setOpenWorkorder(context, !workorderSettingState.isOpenWorkorder),
onTap: () => workorderSettingState.setOpenWorkorder(context, !workorderSettingState.isOpenWorkorder),
)
],
),
),
Text("工单关闭后客户端无法发起工单", style: themeData.textTheme.caption, textAlign: TextAlign.center,),
Divider(),
Expanded(
child: RefreshIndicator(
color: themeData.primaryColorLight,
backgroundColor: themeData.primaryColor,
onRefresh: () async{
await GlobalProvide.getInstance().getWorkorderTypes();
return true;
},
child: CustomScrollView(
physics: AlwaysScrollableScrollPhysics(),
slivers: <Widget>[
SliverToBoxAdapter(
child: Column(children: <Widget>[
Text("工单分类管理"),
Divider(),
],),
),
SliverList(
delegate: SliverChildBuilderDelegate((ctx, index){
WorkOrderTypeModel workOrderType = workorderSettingState.getWorkorderTypes[index];
return SizedBox(
child: Column(
children: <Widget>[
ListTile(
onTap: () => workorderSettingState.operating(context, workOrderType),
title: Row(
children: <Widget>[
Icon(Icons.library_books, size: ToPx.size(40),color: themeData.textTheme.title.color),
Text(" ${workOrderType.title}", style: themeData.textTheme.title,),
],
)
),
Divider(height: ToPx.size(1),)
],
));
}, childCount: workorderSettingState.getWorkorderTypes.length),
),
],
))),
],
));
});
}));
}
}
import '../core_flutter.dart';
class Avatar extends StatelessWidget{
Avatar({this.size, this.onPressed, this.imgUrl});
final double size;
final VoidCallback onPressed;
final String imgUrl;
@override
Widget build(BuildContext context) {
if(imgUrl == null) return Container();
return Button(
useIosStyle: true,
width: size == null ? ToPx.size(60) : size,
height: size == null ? ToPx.size(60) : size,
color: Colors.transparent,
disabledColor: Colors.transparent,
onPressed: onPressed,
child: ClipOval(
child: SizedBox(
child: CachedNetworkImage(
fit: BoxFit.cover,
src: imgUrl,
),
),
),
);
}
}
\ No newline at end of file
import '../core_flutter.dart';
/// border 不能设置单边
class Button extends StatelessWidget {
Button(
{this.child,
this.onPressed,
this.minSize,
this.color,
this.radius = 3.0,
this.width,
this.height,
this.pressedOpacity = .2,
this.border,
this.padding,
this.useIosStyle = false,
this.alignment = Alignment.center,
this.withAlpha = 35,
this.disabledColor,
this.margin});
final double width;
final double height;
final bool useIosStyle;
final Widget child;
final double minSize;
final Border border;
final double pressedOpacity;
final VoidCallback onPressed;
final Color color;
final EdgeInsetsGeometry padding;
final EdgeInsetsGeometry margin;
final double radius;
final Alignment alignment;
final int withAlpha;
final Color disabledColor;
@override
Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context);
Widget _buttonCenter() {
double height = this.height == null ? ToPx.size(80) : this.height;
double width =
this.width == null ? MediaQuery.of(context).size.width : this.width;
return DefaultTextStyle(
style:
TextStyle(fontSize: ToPx.size(28), fontWeight: FontWeight.w600),
child: Container(
height: height,
width: width,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(radius)),
border: border == null
? Border.all(
width: ToPx.size(1),
color: border == null ? Colors.transparent : color)
: border,
),
alignment: alignment,
child: child,
));
}
return Container(
height: height == null ? ToPx.size(80) : height,
width: width == null ? MediaQuery.of(context).size.width : width,
margin: margin,
child: Stack(
children: <Widget>[
Platform.isIOS == true || useIosStyle == true
? CupertinoButton(
disabledColor: disabledColor == null
? themeData.disabledColor
: disabledColor,
color: color == null ? themeData.buttonColor : color,
pressedOpacity: pressedOpacity,
padding: padding == null ? EdgeInsets.zero : padding,
minSize: minSize == null ? ToPx.size(80) : minSize,
borderRadius: BorderRadius.all(Radius.circular(radius)),
child: _buttonCenter(),
onPressed: onPressed)
: RaisedButton(
elevation: 0.0,
disabledColor: disabledColor == null
? themeData.disabledColor
: disabledColor,
animationDuration: Duration(milliseconds: 50),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(radius)),
),
highlightColor: color == null
? themeData.primaryColor.withAlpha(withAlpha)
: color?.withAlpha(withAlpha),
highlightElevation: 0.0,
color: color == null ? themeData.primaryColor : color,
padding: padding == null ? EdgeInsets.zero : padding,
onPressed: onPressed,
child: _buttonCenter(),
)
],
),
);
}
}
import 'package:flutter_advanced_networkimage/provider.dart';
import 'package:flutter_advanced_networkimage/transition.dart';
import '../core_flutter.dart';
class CachedNetworkImage extends StatelessWidget{
CachedNetworkImage({@required this.src, this.width = 100.0, this.height = 100.0, this.fit = BoxFit.contain, this.bgColor, this.radius, this.color});
final String src;
final double width;
final double height;
final BoxFit fit;
final Color bgColor;
final Color color;
final BorderRadius radius;
// 是否是本地资源
bool get isLocal => src != null && src != "" && !src.contains(RegExp(r'^(http://|https://)'));
@override
Widget build(BuildContext context) {
// if(src == null || src.isEmpty) return Container(width: width,height: height,color: bgColor == null ? Color.fromRGBO(0, 0, 0, 0.1) : bgColor,);
try {
if(src == null || src.isEmpty) throw 'no image!';
return ClipRRect(
borderRadius: radius != null ? radius : BorderRadius.all(
Radius.circular(ToPx.size(3))),
child: Container(
width: width,
height: height,
color: bgColor == null ? Color.fromRGBO(0, 0, 0, 0.05) : bgColor,
child: src == null ? SizedBox() :
SizedBox(
width: width,
height: height,
child: isLocal ?
Image.file(
File(src),
width: width,
height: height,
fit: fit,
) :
TransitionToImage(
color: color,
image: AdvancedNetworkImage(
src,
useDiskCache: true,
cacheRule: CacheRule(maxAge: const Duration(days: 30)),
),
loadingWidgetBuilder: (_, double progress, __) =>
Center(
child: loadingIcon()
),
fit: fit,
placeholder: Icon(
Icons.photo, color: Colors.grey.withAlpha(100),),
width: width,
height: height,
enableRefresh: true,
)
)
),
);
}catch(_){
return Container(
color: Colors.grey.withAlpha(10),
child: Center(
child: Text("图片已损坏\r\n或已删除", style: TextStyle(fontSize: ToPx.size(36), color: Colors.grey), textAlign: TextAlign.center,),
),
);
}
}
}
import '../core_flutter.dart';
PreferredSize customAppBar({
Widget title,
leading,
Color backgroundColor,
double elevation,
List<Widget> actions,
PreferredSizeWidget bottom,
bool isShowLeading = true,
Widget flexibleSpace,
Brightness brightness,
double size
}){
return PreferredSize(
preferredSize: Size(double.infinity, size ?? ToPx.size(90)),
child: AppBar(
automaticallyImplyLeading: isShowLeading,
backgroundColor: backgroundColor ?? ThemeProvide.getInstance().getCurrentTheme().primaryColor,
elevation: elevation,
centerTitle: true,
title: title,
actions: actions,
brightness: brightness,
leading: isShowLeading ? leading == null ? CustomBackButton() : leading : null,
bottom: bottom,
flexibleSpace: flexibleSpace,
)
);
}
class CustomBackButton extends StatelessWidget{
CustomBackButton({this.color = Colors.white});
final Color color;
@override
Widget build(BuildContext context) {
return Button(
width: ToPx.size(60),
useIosStyle: true,
color: Colors.transparent,
onPressed: () => Navigator.pop(context),
child: Icon(IconData(0xe600, fontFamily: 'IconFont'),size: ToPx.size(30), color: color,),
);
}
}
\ No newline at end of file
import '../core_flutter.dart';
class DatePicker extends StatefulWidget {
DatePicker({@required this.context, this.initDate, this.maximumDate, this.maximumYear});
final DateTime initDate;
final DateTime maximumDate;
final int maximumYear;
final BuildContext context;
@override
State<StatefulWidget> createState() {
return _DatePickerState();
}
}
class _DatePickerState extends State<DatePicker> {
DateTime selected;
@override
void initState() {
super.initState();
if (mounted) selected = widget.initDate;
}
// 日期选择
Widget _buildBottomPicker(Widget picker) {
ThemeData themeData = Theme.of(widget.context);
return Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
alignment: Alignment.bottomCenter,
child: Container(
height: ToPx.size(850),
color: CupertinoColors.white,
child: Column(
children: <Widget>[
Container(
height: ToPx.size(120),
decoration: BoxDecoration(
color: Color(0xffffffff),
border: Border(
bottom: BorderSide(color: themeData.dividerColor))),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Button(
color: Colors.transparent,
useIosStyle: true,
onPressed: () => Navigator.of(context).pop(null),
width: ToPx.size(150),
height: ToPx.size(120),
child: Text('取消',style: themeData.textTheme.body1),
),
Text(''),
Button(
color: Colors.transparent,
useIosStyle: true,
onPressed: () => Navigator.of(context).pop(selected),
width: ToPx.size(150),
height: ToPx.size(120),
child: Text('确定', style: themeData.textTheme.body1.copyWith(fontWeight: FontWeight.w600)),
),
],
)),
Expanded(
child: DefaultTextStyle(
style: themeData.textTheme.body1.copyWith(fontSize: ToPx.size(50)),
child: GestureDetector(
onTap: () {},
child: SafeArea(
top: false,
child: picker,
),
),
),
)
],
),
),
);
}
@override
Widget build(BuildContext context) {
return _buildBottomPicker(
CupertinoDatePicker(
mode: CupertinoDatePickerMode.date,
initialDateTime: widget.initDate,
maximumDate: widget.maximumDate,
maximumYear: widget.maximumYear,
onDateTimeChanged: (DateTime newDate) {
setState(() {
selected = newDate;
});
},
),
);
}
}
export 'avatar.dart';
export 'button.dart';
export 'cached_network_image.dart';
export 'custom_app_bar.dart';
export 'loading_icon.dart';
export 'mini_loading.dart';
export 'photo_preview.dart';
export 'refresh_widget.dart';
export 'date_picker.dart';
export 'input.dart';
export 'sliver_refresh_control.dart';
import 'dart:io';
import '../core_flutter.dart';
typedef BoolCallBack = Function(bool b);
class Input extends StatefulWidget{
Input({
Key key,
this.maxLength,
this.keyboardType = TextInputType.text,
this.onChanged,
this.placeholder,
this.obscureText = false,
this.width,
this.height,
this.bgColor = Colors.transparent,
this.border = const Border(bottom: BorderSide(width: 1.0, color: Color(0xfff3f3f3))),
this.textAlign = TextAlign.left,
this.maxLines = 1,
this.minLines = 1,
this.controller,
this.focusNode,
this.borderRadius,
this.padding = EdgeInsets.zero,
this.autofocus = false,
this.textCapitalization = TextCapitalization.none,
this.onEditingComplete,
this.textInputAction,
this.showClear = false,
this.maxHeight = double.infinity,
this.onClear,
this.isToUpperCase = false,
this.onFocus,
this.enabled = true,
this.onClick,
this.counterText = "",
this.textMaxLength,
this.style,
this.placeholderAlignment,
this.contentPadding
}) : super(key : key);
final TextStyle style;
final BoolCallBack onFocus;
final int maxLength;
final int textMaxLength;
final bool showClear;
final TextInputType keyboardType;
final ValueChanged<String> onChanged;
final String placeholder;
final bool obscureText;
final double width;
final double height;
final Color bgColor;
final BoxBorder border;
final TextAlign textAlign;
final int maxLines;
final int minLines;
final TextEditingController controller;
final FocusNode focusNode;
final BorderRadius borderRadius;
final EdgeInsetsGeometry padding;
final EdgeInsetsGeometry contentPadding;
final bool autofocus;
final TextCapitalization textCapitalization;
final VoidCallback onEditingComplete;
final VoidCallback onClear;
final TextInputAction textInputAction;
final bool isToUpperCase;
final double maxHeight;
final bool enabled;
final VoidCallback onClick;
final String counterText;
final AlignmentGeometry placeholderAlignment;
@override
State<StatefulWidget> createState() {
return _InputState();
}
}
class _InputState extends State<Input>{
TextEditingController controller;
FocusNode focusNode;
bool isShowClear = false;
double maxHeight = double.infinity;
double minHeight = ToPx.size(90);
bool isInputEmpty = false;
bool isShowVisibility = false;
bool obscureText = false;
@override
void initState() {
super.initState();
if(mounted){
if(widget.height != null) minHeight = widget.height;
if(widget.maxHeight != null && widget.maxHeight > minHeight) maxHeight = widget.maxHeight;
obscureText = widget.obscureText;
if(widget.obscureText) isShowVisibility = true;
setState(() {});
// 判断外部是否给控制器
if(widget.controller == null){
controller = TextEditingController();
}else{
controller = widget.controller;
}
// 监听输入框判断是否显示清除按钮
controller.addListener(() async{
// 清除按钮
if(controller.value.text.length == 0){
isShowClear = false;
isInputEmpty = false;
}else{
if(widget.showClear) isShowClear = true;
isInputEmpty = true;
}
// 判断长度截取掉
if(widget.maxLength != null && controller.value.text.length > widget.maxLength && Platform.isAndroid){
controller.text = controller.value.text.substring(0, widget.maxLength);
controller.selection = TextSelection.collapsed(offset: widget.maxLength);
}
setState(() {});
});
// 判断外部是否给focusNode
if(widget.focusNode == null){
focusNode = FocusNode();
}else{
focusNode = widget.focusNode;
}
focusNode.addListener((){
// 判断长度截取掉
if(widget.maxLength != null && controller.value.text.length > widget.maxLength && Platform.isIOS){
controller.text = controller.value.text.substring(0, widget.maxLength);
controller.selection = TextSelection.collapsed(offset: widget.maxLength);
}
if(widget.onFocus != null) widget.onFocus(focusNode.hasFocus);
if(!focusNode.hasFocus && widget.isToUpperCase){
controller.text = controller.value.text.toUpperCase();
}
controller.text = controller.value.text.trim();
});
if(controller.value.text.length > 0){
isInputEmpty = true;
setState(() {});
}
}
}
@override
void dispose() {
if(widget.controller == null){
controller?.dispose();
}
if(widget.focusNode == null){
focusNode?.dispose();
}
super.dispose();
}
@override
Widget build(BuildContext context) {
ThemeData themeData = Theme.of(context);
return Theme(
data: Theme.of(context).copyWith(
splashColor: Colors.transparent,
),
child: GestureDetector(
onTap: widget.onClick,
child: SizedBox(
width: widget.width == null ? MediaQuery.of(context).size.width : widget.width,
child: Stack(
children: <Widget>[
Align(
alignment: Alignment.centerLeft,
child: Container(
width: widget.width == null ? MediaQuery.of(context).size.width : widget.width,
padding: widget.padding,
constraints: BoxConstraints(
minHeight: minHeight,
maxHeight: maxHeight
),
decoration: BoxDecoration(
color: widget.bgColor,
border: widget.border,
borderRadius: widget.borderRadius
),
alignment: Alignment.centerLeft,
child: Stack(
children: <Widget>[
Offstage(
offstage: isInputEmpty,
child: SizedBox(
height: minHeight,
child: Align(
alignment: widget.placeholderAlignment != null ? widget.placeholderAlignment :widget.minLines > 1 ? Alignment.topLeft : widget.textAlign == TextAlign.center ?
Alignment.center : widget.textAlign == TextAlign.right || widget.textAlign == TextAlign.end ?
Alignment.centerRight : Alignment.centerLeft,
child: Text(widget.placeholder, style: themeData.textTheme.caption.copyWith(
color: themeData.disabledColor
),),
),
),
),
Align(
alignment: Alignment.centerLeft,
child: TextField(
scrollPhysics: Platform.isAndroid ? BouncingScrollPhysics() : null,
controller: controller,
focusNode: focusNode,
enabled: widget.enabled,
textCapitalization: widget.textCapitalization,
onEditingComplete: widget.onEditingComplete,
textInputAction: widget.textInputAction,
decoration: InputDecoration(
contentPadding: widget.contentPadding != null ? widget.contentPadding : EdgeInsets.all(0.0),
counterText: widget.counterText,
hintStyle: themeData.textTheme.body1.copyWith(
fontSize: widget.style != null ? widget.style.fontSize : themeData.textTheme.body1.fontSize,
color: themeData.disabledColor
),
hintText: "",
border: InputBorder.none,
),
cursorColor: themeData.primaryColor,
cursorRadius: Radius.circular(ToPx.size(3)),
cursorWidth: ToPx.size(5),
style: widget.style ?? themeData.textTheme.body1,
obscureText: obscureText,
keyboardType: widget.keyboardType,
maxLines: widget.maxLines,
minLines: widget.minLines,
maxLength: widget.textMaxLength,
onChanged: widget.onChanged,
textAlign: widget.textAlign,
autofocus: widget.autofocus,
keyboardAppearance: Brightness.light,
),
)
],
),
),
),
Align(
alignment: Alignment.centerRight,
child: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Offstage(
offstage: !isShowVisibility,
child: Button(
onPressed: (){
setState(() {
obscureText = !obscureText;
});
},
alignment: Alignment.centerRight,
useIosStyle: true,
disabledColor: Colors.transparent,
color: Colors.transparent,
width: ToPx.size(45),
child: Icon(obscureText ? Icons.visibility : Icons.visibility_off, size: ToPx.size(40), color: Color(0xffcccccc),),
),
),
Align(
alignment: Alignment.centerRight,
child: Offstage(
offstage: !widget.showClear,
child: Offstage(
offstage: !isShowClear,
child: Button(
onPressed: (){
controller.clear();
if(widget.onClear != null) widget.onClear();
},
alignment: Alignment.centerRight,
useIosStyle: true,
disabledColor: Colors.transparent,
color: Colors.transparent,
width: ToPx.size(45),
child: Icon(Icons.cancel, size: ToPx.size(35), color: Color(0xffcccccc),),
),
),
)
),
]))
],
),
),
),
);
}
}
\ No newline at end of file
import '../core_flutter.dart';
Widget loadingIcon({double size}){
return SizedBox(
width: size ?? ToPx.size(25),
height: size ?? ToPx.size(25),
child: Platform.isAndroid ?
CircularProgressIndicator(
strokeWidth: 2.0,
):
CupertinoActivityIndicator(radius: ToPx.size(25),)
);
}
\ No newline at end of file
import '../core_flutter.dart';
class MiniLoading extends StatelessWidget{
MiniLoading({this.context, this.content = 'content'});
final BuildContext context;
final String content;
@override
Widget build(BuildContext _) {
ThemeData themeData = Theme.of(context);
return WillPopScope(
child: Material(
color: Colors.transparent,
child: Theme(data: themeData.copyWith(accentColor: themeData.primaryColor), child: Center(
child: Container(
width: double.infinity,
height: double.infinity,
color: Color.fromRGBO(0, 0, 0, 0.3),
child: Center(
child: Container(
width: ToPx.size(250),
height: ToPx.size(250),
padding: EdgeInsets.symmetric(
horizontal: ToPx.size(20), vertical: ToPx.size(30)),
decoration: BoxDecoration(
color: Color.fromRGBO(0, 0, 0, .7),
borderRadius:
BorderRadius.all(Radius.circular(5.0))),
child: Column(
children: <Widget>[
Padding(
padding: EdgeInsets.only(
bottom: ToPx.size(40), top: ToPx.size(20)),
child: Platform.isIOS == true
? CupertinoActivityIndicator(
radius: ToPx.size(30),
)
: SizedBox(
width: ToPx.size(45),
height: ToPx.size(45),
child: CircularProgressIndicator(
backgroundColor: Colors.white,
strokeWidth: 2.0,
),
)),
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
content,
style: TextStyle(
color: Color(0xffffffff),
fontSize: ToPx.size(32)),
textAlign: TextAlign.center,
),
],
),
)
],
)),
),
)
)),
),
onWillPop: () async {
return false;
});
}
}
\ No newline at end of file
import 'package:photo_view/photo_view.dart';
import 'package:photo_view/photo_view_gallery.dart';
import '../core_flutter.dart';
class PhotoPreview extends StatefulWidget{
PhotoPreview({this.images, this.initIndex = 0, @required this.context});
final List<String> images;
final int initIndex;
final BuildContext context;
@override
State<StatefulWidget> createState() {
return PhotoPreviewState();
}
}
class PhotoPreviewState extends State<PhotoPreview>{
int tabIndex = 0;
PageController pageController;
// 是否是本地资源
bool get isLocal => widget.images[tabIndex] != null && !widget.images[tabIndex].contains(RegExp(r'^(http://|https://)'));
Widget pagination({bool on = false}){
return Container(
margin: EdgeInsets.symmetric(horizontal: ToPx.size(10)),
width: ToPx.size(18),
height: ToPx.size(18),
decoration: BoxDecoration(
color: on ? Color.fromRGBO(255, 255, 255, .9) : Color.fromRGBO(255, 255, 255, .5),
shape: BoxShape.circle
),
);
}
@override
void initState(){
super.initState();
tabIndex = widget.initIndex;
}
@override
void dispose() {
pageController.dispose();
super.dispose();
}
@override
Widget build(_) {
ThemeData themeData = Theme.of(widget.context);
tabIndex = widget.initIndex;
pageController = PageController(initialPage: tabIndex);
List<Widget> paginationWidgets = [];
for(var i =0; i< widget.images.length; i++){
paginationWidgets.add(pagination(on: i == tabIndex));
}
List<PhotoViewGalleryPageOptions> imageWidgets = [];
for(var i =0; i< widget.images.length; i++){
imageWidgets.add(PhotoViewGalleryPageOptions.customChild(
heroAttributes: PhotoViewHeroAttributes(tag: widget.images[tabIndex]),
onTapUp: (ctx, _, __){
Navigator.pop(context);
},
minScale: 0.5,
maxScale: 5.0,
child: AspectRatio(
aspectRatio: MediaQuery.of(context).size.aspectRatio,
child: CachedNetworkImage(
bgColor: Color.fromRGBO(0, 0, 0, 0.0),
src: widget.images[i],
),
),
childSize: Size.fromRadius(100.0)
)
);
}
return Material(
color: Colors.transparent,
child: Theme(
data: themeData,
child: Stack(
children: <Widget>[
PhotoViewGallery(
pageController: pageController,
onPageChanged: (index) => setState(() => tabIndex = index),
pageOptions: imageWidgets.toList(),
backgroundDecoration: BoxDecoration(color: Color(0xff000000)),
),
Align(
alignment: Alignment.bottomCenter,
child: Container(
width: double.infinity,
alignment: Alignment.center,
height: ToPx.size(100),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: paginationWidgets.toList()
),
)
),
Align(
alignment: Alignment.bottomRight,
child: Offstage(
offstage: isLocal,
child: Button(
color: Colors.transparent,
useIosStyle: true,
onPressed: () async{
// bool isComplete = await Utils.savedGallery(widget.images[tabIndex]);
// if(isComplete){
// UX.showToast("已保存到相册");
// }else{
// UX.showToast("保存失败");
// }
},
width:ToPx.size(200),
height: ToPx.size(100),
alignment: Alignment.center,
child: Icon(Icons.file_download, color: Colors.white.withAlpha(100),size: ToPx.size(80),)
),
)
),
],
)
),
);
}
}
import 'dart:math';
import '../core_flutter.dart';
/*
* 重新定义刷新样式
*/
class RefreshWidget extends StatelessWidget{
RefreshWidget(
this.refreshState,
this.pulledExtent,
this.refreshTriggerPullDistance,
this.refreshIndicatorExtent,
{
this.bgSrc,
this.bgColor = const Color(0xffffffff),
this.dragText = "下拉刷新",
this.doneText = "刷新完成",
this.refreshText = "刷新中...",
this.isNoRefresh = false,
this.iconColor = Colors.grey,
this.textColor = const Color(0xff666666),
this.noRefreshWidget = const SizedBox()
}
);
final RefreshIndicatorMode refreshState;
final double pulledExtent;
final double refreshTriggerPullDistance;
final double refreshIndicatorExtent;
final Color bgColor;
final String dragText;
final String doneText;
final String refreshText;
final String bgSrc;
final bool isNoRefresh;
final Color iconColor;
final Color textColor;
final Widget noRefreshWidget;
@override
Widget build(BuildContext context) {
if(isNoRefresh) return Container(
width: double.infinity,
height: double.infinity,
color: bgColor,
child: noRefreshWidget
);
const Curve opacityCurve = Interval(0.4, 0.8, curve: Curves.fastOutSlowIn);
return Container(
width: double.infinity,
height: double.infinity,
padding: EdgeInsets.symmetric(vertical: ToPx.size(10)),
decoration: BoxDecoration(
color: bgColor,
),
child: Opacity(
opacity: opacityCurve.transform(min(pulledExtent / refreshIndicatorExtent, 1.0)),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
refreshState == RefreshIndicatorMode.done ?
Icon(Icons.done,color: iconColor ,size: ToPx.size(30),) :
refreshState == RefreshIndicatorMode.drag ?
Icon(Icons.arrow_downward,color:iconColor,size: ToPx.size(30),) :
loadingIcon(),
Text(
refreshState == RefreshIndicatorMode.done
? " $doneText" : refreshState == RefreshIndicatorMode.drag ? " $dragText" : " $refreshText",
style: TextStyle(fontSize: ToPx.size(26), color: textColor),
)
],
)
),
);
}
}
import '../core_flutter.dart';
CupertinoSliverRefreshControl sliverRefreshControl({
VoidCallback onRefresh,
Color bgColor = const Color(0xffffffff),
bool isNoRefresh = false,
Color iconColor = Colors.grey,
Color textColor = const Color(0xff666666),
Widget noRefreshWidget
}){
return CupertinoSliverRefreshControl(
builder: (context, refreshState, pulledExtent, refreshTriggerPullDistance, refreshIndicatorExtent){
return RefreshWidget(
refreshState,
pulledExtent,
refreshTriggerPullDistance,
refreshIndicatorExtent,
bgColor: bgColor,
isNoRefresh: isNoRefresh,
iconColor: iconColor,
textColor: textColor,
noRefreshWidget: noRefreshWidget
);
},
onRefresh: onRefresh
);
}
\ No newline at end of file
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
archive:
dependency: transitive
description:
name: archive
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.11"
args:
dependency: transitive
description:
name: args
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.5.2"
async:
dependency: transitive
description:
name: async
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.0"
boolean_selector:
dependency: transitive
description:
name: boolean_selector
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.5"
charcode:
dependency: transitive
description:
name: charcode
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.2"
collection:
dependency: transitive
description:
name: collection
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.14.11"
connectivity:
dependency: "direct main"
description:
name: connectivity
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.4.8+2"
connectivity_macos:
dependency: transitive
description:
name: connectivity_macos
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.1.0+2"
connectivity_platform_interface:
dependency: transitive
description:
name: connectivity_platform_interface
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.3"
convert:
dependency: transitive
description:
name: convert
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.1"
crypto:
dependency: transitive
description:
name: crypto
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.3"
csslib:
dependency: transitive
description:
name: csslib
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.16.1"
cupertino_icons:
dependency: "direct main"
description:
name: cupertino_icons
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.1.3"
dio:
dependency: "direct main"
description:
name: dio
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.9"
file_picker:
dependency: "direct main"
description:
name: file_picker
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.6.3+1"
flutter:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_advanced_networkimage:
dependency: "direct main"
description:
name: flutter_advanced_networkimage
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.6.4"
flutter_html:
dependency: "direct main"
description:
name: flutter_html
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.11.1"
flutter_local_notifications:
dependency: "direct main"
description:
name: flutter_local_notifications
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.4.1"
flutter_local_notifications_platform_interface:
dependency: transitive
description:
name: flutter_local_notifications_platform_interface
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.1"
flutter_localizations:
dependency: "direct main"
description: flutter
source: sdk
version: "0.0.0"
flutter_mimc:
dependency: "direct main"
description:
name: flutter_mimc
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.2"
flutter_plugin_android_lifecycle:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.6"
flutter_svg:
dependency: transitive
description:
name: flutter_svg
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.17.3+1"
flutter_test:
dependency: "direct dev"
description: flutter
source: sdk
version: "0.0.0"
flutter_web_plugins:
dependency: transitive
description: flutter
source: sdk
version: "0.0.0"
fluttertoast:
dependency: "direct main"
description:
path: "."
ref: HEAD
resolved-ref: "8b7d690a0123f1c3c9866edbc0acfab937f56a23"
url: "https://github.com/chenxianqi/FlutterToast.git"
source: git
version: "3.1.0"
html:
dependency: transitive
description:
name: html
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.14.0+3"
http:
dependency: transitive
description:
name: http
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.12.0+4"
http_parser:
dependency: transitive
description:
name: http_parser
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.1.4"
image:
dependency: transitive
description:
name: image
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.4"
image_picker:
dependency: "direct main"
description:
name: image_picker
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.6.5"
intl:
dependency: transitive
description:
name: intl
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.16.0"
matcher:
dependency: transitive
description:
name: matcher
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.12.6"
meta:
dependency: transitive
description:
name: meta
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.8"
nested:
dependency: transitive
description:
name: nested
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.0.4"
path:
dependency: transitive
description:
name: path
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.6.4"
path_drawing:
dependency: transitive
description:
name: path_drawing
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.4.1"
path_parsing:
dependency: transitive
description:
name: path_parsing
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.1.4"
path_provider:
dependency: "direct main"
description:
name: path_provider
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.6.5"
path_provider_macos:
dependency: transitive
description:
name: path_provider_macos
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.0.4"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.1"
pedantic:
dependency: transitive
description:
name: pedantic
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.8.0+1"
permission_handler:
dependency: "direct main"
description:
name: permission_handler
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.0.0"
petitparser:
dependency: transitive
description:
name: petitparser
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.4.0"
photo_view:
dependency: "direct main"
description:
name: photo_view
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.9.2"
platform:
dependency: transitive
description:
name: platform
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.2.1"
plugin_platform_interface:
dependency: transitive
description:
name: plugin_platform_interface
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.2"
provider:
dependency: "direct main"
description:
name: provider
url: "https://pub.flutter-io.cn"
source: hosted
version: "4.0.5"
quiver:
dependency: transitive
description:
name: quiver
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.5"
shared_preferences:
dependency: "direct main"
description:
name: shared_preferences
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.5.6+3"
shared_preferences_macos:
dependency: transitive
description:
name: shared_preferences_macos
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.0.1+6"
shared_preferences_platform_interface:
dependency: transitive
description:
name: shared_preferences_platform_interface
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.3"
shared_preferences_web:
dependency: transitive
description:
name: shared_preferences_web
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.1.2+4"
sky_engine:
dependency: transitive
description: flutter
source: sdk
version: "0.0.99"
source_span:
dependency: transitive
description:
name: source_span
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.5.5"
stack_trace:
dependency: transitive
description:
name: stack_trace
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.9.3"
stream_channel:
dependency: transitive
description:
name: stream_channel
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.0"
string_scanner:
dependency: transitive
description:
name: string_scanner
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.5"
term_glyph:
dependency: transitive
description:
name: term_glyph
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.0"
test_api:
dependency: transitive
description:
name: test_api
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.11"
typed_data:
dependency: transitive
description:
name: typed_data
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.6"
vector_math:
dependency: transitive
description:
name: vector_math
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.8"
xml:
dependency: transitive
description:
name: xml
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.5.0"
sdks:
dart: ">=2.6.0 <3.0.0"
flutter: ">=1.12.13+hotfix.5 <2.0.0"
name: kefu_workbench
description: A new Flutter application.
version: 2.0.0
environment:
sdk: ">=2.1.0 <3.0.0"
dependencies:
flutter:
sdk: flutter
flutter_localizations:
sdk: flutter
cupertino_icons: ^0.1.2
permission_handler: '4.0.0'
dio: ^3.0.8
path_provider: ^1.5.1
shared_preferences: ^0.5.6
image_picker: ^0.6.2+3
flutter_advanced_networkimage: ^0.6.2
photo_view: ^0.9.0
provider: ^4.0.1
flutter_html: ^0.11.1
flutter_mimc: ^1.0.2
file_picker: ^1.6.3+1
connectivity: ^0.4.6+2
flutter_local_notifications: ^1.1.5+1
fluttertoast:
git: 'https://github.com/chenxianqi/FlutterToast.git'
dev_dependencies:
flutter_test:
sdk: flutter
flutter:
uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
fonts:
- family: IconFont
fonts:
- asset: assets/fonts/iconfont/iconfont.ttf
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility that Flutter provides. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:kefu_workbench/app.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(createApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment