diff --git a/assets/icons/contract.svg b/assets/icons/contract.svg
new file mode 100644
index 0000000..acf3785
--- /dev/null
+++ b/assets/icons/contract.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/icons/hr.svg b/assets/icons/hr.svg
new file mode 100644
index 0000000..c4a3fb4
--- /dev/null
+++ b/assets/icons/hr.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/icons/inventory.svg b/assets/icons/inventory.svg
new file mode 100644
index 0000000..f2f9b89
--- /dev/null
+++ b/assets/icons/inventory.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/icons/product.svg b/assets/icons/product.svg
new file mode 100644
index 0000000..33595d8
--- /dev/null
+++ b/assets/icons/product.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/icons/report.svg b/assets/icons/report.svg
new file mode 100644
index 0000000..6c88a12
--- /dev/null
+++ b/assets/icons/report.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/icons/sale_quotation.svg b/assets/icons/sale_quotation.svg
new file mode 100644
index 0000000..4d6ab77
--- /dev/null
+++ b/assets/icons/sale_quotation.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/icons/task_manage.svg b/assets/icons/task_manage.svg
new file mode 100644
index 0000000..8ca2cb2
--- /dev/null
+++ b/assets/icons/task_manage.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/icons/vehicle.svg b/assets/icons/vehicle.svg
new file mode 100644
index 0000000..326d7f0
--- /dev/null
+++ b/assets/icons/vehicle.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/lib/app_theme.dart b/lib/app_theme.dart
index 9ea8308..859d638 100644
--- a/lib/app_theme.dart
+++ b/lib/app_theme.dart
@@ -93,7 +93,7 @@ final theme = ThemeData(
)),
appBarTheme: AppBarTheme(
centerTitle: true,
- iconTheme: const IconThemeData(color: Colors.black),
+ iconTheme: const IconThemeData(color: Colors.white),
backgroundColor: AppTheme.primaryColor,
titleTextStyle: TextStyle(
color: Colors.white,
diff --git a/lib/config.dart b/lib/config.dart
index 02b0257..64a31e5 100644
--- a/lib/config.dart
+++ b/lib/config.dart
@@ -5,10 +5,10 @@ class GloablConfig {
// static const BASE_URL = "http://10.0.2.2:8001/api/";
// static const OSS_URL = "http://10.0.2.2:8001";
- static const BASE_URL = "http://144.123.43.138:3001/api/";
- static const OSS_URL = "http://144.123.43.138:3001";
- // static const BASE_URL = "http://192.168.60.220:8001/api/";
- // static const OSS_URL = "http://192.168.60.220:8001";
+ // static const BASE_URL = "http://144.123.43.138:3001/api/";
+ // static const OSS_URL = "http://144.123.43.138:3001";
+ static const BASE_URL = "http://192.168.60.220:8001/api/";
+ static const OSS_URL = "http://192.168.60.220:8001";
static const DOMAIN_NAME = "山矿通";
static const DEBUG = true;
static const PRIVACY_POLICY = 'http://h5.heeru.xyz/privacyPolicy.html';
diff --git a/lib/constants/router.dart b/lib/constants/router.dart
index 94fc7fb..cba8d47 100644
--- a/lib/constants/router.dart
+++ b/lib/constants/router.dart
@@ -1,6 +1,7 @@
import 'package:get/get.dart';
import 'package:sk_base_mobile/screens/inventory/inventory.dart';
import 'package:sk_base_mobile/screens/login/login.dart';
+import 'package:sk_base_mobile/screens/sale_quotation/sale_quotation.dart';
import '../screens/landing/landing.dart';
import '../screens/mine/useinfo/userinfo.dart';
@@ -10,10 +11,13 @@ class RouteConfig {
static const String login = '/login';
static const String userinfo = '/userinfo';
static const String inventory = '/inventory';
+ static const String saleQuotation = '/sale_quotation';
+
static final List getPages = [
GetPage(name: login, page: () => LoginScreen()),
GetPage(name: home, page: () => LandingPage()),
GetPage(name: userinfo, page: () => UserInfoPage()),
GetPage(name: inventory, page: () => const InventoryPage()),
+ GetPage(name: saleQuotation, page: () => const SaleQuotationPage())
];
}
diff --git a/lib/models/workbench.model.dart b/lib/models/workbench.model.dart
new file mode 100644
index 0000000..4af6efa
--- /dev/null
+++ b/lib/models/workbench.model.dart
@@ -0,0 +1,7 @@
+class WorkBenchModel {
+ final String title;
+ final String icon;
+ final String route;
+ WorkBenchModel(
+ {required this.title, required this.route, required this.icon});
+}
diff --git a/lib/screens/inventory_inout/components/custom_app_bar.dart b/lib/screens/inventory_inout/components/custom_app_bar.dart
index 68cb19b..e7bce3b 100644
--- a/lib/screens/inventory_inout/components/custom_app_bar.dart
+++ b/lib/screens/inventory_inout/components/custom_app_bar.dart
@@ -6,7 +6,7 @@ import 'package:sk_base_mobile/constants/constants.dart';
import 'package:sk_base_mobile/screens/inventory_inout/inventory_inout_controller.dart';
import 'package:sk_base_mobile/store/auth.store.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
-import 'package:sk_base_mobile/widgets/core/zt_base_date_picker.dart';
+import 'package:sk_base_mobile/widgets/core/sk_base_date_picker.dart';
class CustomAppBar extends StatelessWidget {
CustomAppBar({super.key});
@@ -59,7 +59,7 @@ class CustomAppBar extends StatelessWidget {
showCupertinoModalPopup(
context: Get.overlayContext!,
builder: (BuildContext context) {
- return ZtBaseDatePicker(
+ return SkBaseDatePicker(
initialDate: controller.endTime.value,
endDate: controller.endTime.value,
onDateTimeChanged: (date) {
diff --git a/lib/screens/inventory_inout/inventory_inout.dart b/lib/screens/inventory_inout/inventory_inout.dart
index 1178b9f..0e937d0 100644
--- a/lib/screens/inventory_inout/inventory_inout.dart
+++ b/lib/screens/inventory_inout/inventory_inout.dart
@@ -14,6 +14,10 @@ class InvenotryInoutPage extends StatelessWidget {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
+ // 工具栏的高度
+ SizedBox(
+ height: MediaQuery.of(context).padding.top,
+ ),
UperBody(),
const Expanded(
child: TaskPageBody(),
diff --git a/lib/screens/landing/landing.dart b/lib/screens/landing/landing.dart
index 1fee2f9..3c34c65 100644
--- a/lib/screens/landing/landing.dart
+++ b/lib/screens/landing/landing.dart
@@ -16,21 +16,19 @@ class LandingPage extends StatelessWidget {
return Material(
child: Stack(children: [
const BackColors(),
- SafeArea(
- bottom: false,
- child: Scaffold(
- floatingActionButtonLocation:
- FloatingActionButtonLocation.centerDocked,
- // floatingActionButton: [0].indexWhere(
- // (item) => item == controller.currentIndex.value) >
- // -1
- // ? FloatingCreateButton()
- // : null,
- floatingActionButton: FloatingCreateButton(),
- bottomNavigationBar: BottomNavBar(),
- backgroundColor: Colors.transparent,
- body: Obx(() => controller.pages[controller.currentIndex.value]),
- ))
+ Scaffold(
+ floatingActionButtonLocation:
+ FloatingActionButtonLocation.centerDocked,
+ // floatingActionButton: [0].indexWhere(
+ // (item) => item == controller.currentIndex.value) >
+ // -1
+ // ? FloatingCreateButton()
+ // : null,
+ floatingActionButton: FloatingCreateButton(),
+ bottomNavigationBar: BottomNavBar(),
+ backgroundColor: Colors.transparent,
+ body: Obx(() => controller.pages[controller.currentIndex.value]),
+ )
]),
);
}
diff --git a/lib/screens/new_inventory_inout/components/inventory_search.dart b/lib/screens/new_inventory_inout/components/inventory_search.dart
index 462f5b7..fe4662c 100644
--- a/lib/screens/new_inventory_inout/components/inventory_search.dart
+++ b/lib/screens/new_inventory_inout/components/inventory_search.dart
@@ -124,7 +124,7 @@ class InventorySearch extends StatelessWidget {
// return Container(
// width: ScreenAdaper.width(200),
// constraints: BoxConstraints(minWidth: ScreenAdaper.width(200)),
- // child: ZtSearchSelect(
+ // child: SkSearchSelect(
// isRequired: true,
// contentPadding:
// EdgeInsets.symmetric(horizontal: ScreenAdaper.width(15)),
diff --git a/lib/screens/new_inventory_inout/new_inventory_inout.dart b/lib/screens/new_inventory_inout/new_inventory_inout.dart
index b7404bb..2bc3a80 100644
--- a/lib/screens/new_inventory_inout/new_inventory_inout.dart
+++ b/lib/screens/new_inventory_inout/new_inventory_inout.dart
@@ -13,10 +13,10 @@ import 'package:sk_base_mobile/screens/new_inventory_inout/components/inventory_
import 'package:sk_base_mobile/screens/new_inventory_inout/components/product_search.dart';
import 'package:sk_base_mobile/store/dict.store.dart';
import 'package:sk_base_mobile/util/util.dart';
-import 'package:sk_base_mobile/widgets/core/zt_number_input.dart';
-import 'package:sk_base_mobile/widgets/core/zt_search_select.dart';
-import 'package:sk_base_mobile/widgets/core/zk_date_picker.dart';
-import 'package:sk_base_mobile/widgets/core/zt_text_input.dart';
+import 'package:sk_base_mobile/widgets/core/sk_number_input.dart';
+import 'package:sk_base_mobile/widgets/core/sk_search_select.dart';
+import 'package:sk_base_mobile/widgets/core/sk_date_picker.dart';
+import 'package:sk_base_mobile/widgets/core/sk_text_input.dart';
import 'package:sk_base_mobile/widgets/gradient_button.dart';
import 'package:sk_base_mobile/screens/new_inventory_inout/new_inventory_inout_controller.dart';
@@ -180,7 +180,7 @@ class NewInventoryInout extends StatelessWidget {
/// 项目
Widget buildProjectPicker() {
- return ZtSearchSelect(
+ return SkSearchSelect(
isRequired: true,
textController: controller.projectTextController,
labelText: '项目',
@@ -278,7 +278,7 @@ class NewInventoryInout extends StatelessWidget {
/// 时间
Widget buildDatePicker() {
- return ZtDatePicker(
+ return SkDatePicker(
textController: controller.dateTextController,
onClear: () {
controller.payload.remove('time');
@@ -294,7 +294,7 @@ class NewInventoryInout extends StatelessWidget {
/// 数量
Widget buildQuantity() {
- return ZtNumberInput(
+ return SkNumberInput(
textController: controller.quantityTextController,
labelText: '数量',
onChanged: (value) {
@@ -306,7 +306,7 @@ class NewInventoryInout extends StatelessWidget {
/// 单价
Widget buildUnitPrice() {
- return ZtNumberInput(
+ return SkNumberInput(
textController: controller.unitPriceTextController,
labelText: '单价',
onChanged: (value) {
@@ -317,7 +317,7 @@ class NewInventoryInout extends StatelessWidget {
/// 金额
Widget buildAmount() {
- return ZtNumberInput(
+ return SkNumberInput(
textController: controller.amountTextController,
labelText: '金额',
onChanged: (value) {
@@ -375,7 +375,7 @@ class NewInventoryInout extends StatelessWidget {
/// 备注
Widget buildRemark() {
- return ZtTextInput(
+ return SkTextInput(
textController: controller.remarkTextController,
labelText: '备注',
isTextArea: true,
diff --git a/lib/screens/sale_quotation/components/data_sources.dart b/lib/screens/sale_quotation/components/data_sources.dart
new file mode 100644
index 0000000..ab529c2
--- /dev/null
+++ b/lib/screens/sale_quotation/components/data_sources.dart
@@ -0,0 +1,692 @@
+// ignore_for_file: avoid_print
+
+import 'package:flutter/material.dart';
+import 'package:intl/intl.dart';
+import 'package:data_table_2/data_table_2.dart';
+
+// Copyright 2019 The Flutter team. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// The file was extracted from GitHub: https://github.com/flutter/gallery
+// Changes and modifications by Maxim Saplin, 2021
+
+/// Keeps track of selected rows, feed the data into DesertsDataSource
+class RestorableDessertSelections extends RestorableProperty> {
+ Set _dessertSelections = {};
+
+ /// Returns whether or not a dessert row is selected by index.
+ bool isSelected(int index) => _dessertSelections.contains(index);
+
+ /// Takes a list of [Dessert]s and saves the row indices of selected rows
+ /// into a [Set].
+ void setDessertSelections(List desserts) {
+ final updatedSet = {};
+ for (var i = 0; i < desserts.length; i += 1) {
+ var dessert = desserts[i];
+ if (dessert.selected) {
+ updatedSet.add(i);
+ }
+ }
+ _dessertSelections = updatedSet;
+ notifyListeners();
+ }
+
+ @override
+ Set createDefaultValue() => _dessertSelections;
+
+ @override
+ Set fromPrimitives(Object? data) {
+ final selectedItemIndices = data as List;
+ _dessertSelections = {
+ ...selectedItemIndices.map((dynamic id) => id as int),
+ };
+ return _dessertSelections;
+ }
+
+ @override
+ void initWithValue(Set value) {
+ _dessertSelections = value;
+ }
+
+ @override
+ Object toPrimitives() => _dessertSelections.toList();
+}
+
+int _idCounter = 0;
+
+/// Domain model entity
+class Dessert {
+ Dessert(
+ this.name,
+ this.calories,
+ this.fat,
+ this.carbs,
+ this.protein,
+ this.sodium,
+ this.calcium,
+ this.iron,
+ );
+
+ final int id = _idCounter++;
+
+ final String name;
+ final int calories;
+ final double fat;
+ final int carbs;
+ final double protein;
+ final int sodium;
+ final int calcium;
+ final int iron;
+ bool selected = false;
+}
+
+/// Data source implementing standard Flutter's DataTableSource abstract class
+/// which is part of DataTable and PaginatedDataTable synchronous data fecthin API.
+/// This class uses static collection of deserts as a data store, projects it into
+/// DataRows, keeps track of selected items, provides sprting capability
+class DessertDataSource extends DataTableSource {
+ DessertDataSource.empty(this.context) {
+ desserts = [];
+ }
+
+ DessertDataSource(this.context,
+ [sortedByCalories = false,
+ this.hasRowTaps = false,
+ this.hasRowHeightOverrides = false,
+ this.hasZebraStripes = false]) {
+ desserts = _desserts;
+ if (sortedByCalories) {
+ sort((d) => d.calories, true);
+ }
+ }
+
+ final BuildContext context;
+ late List desserts;
+ // Add row tap handlers and show snackbar
+ bool hasRowTaps = false;
+ // Override height values for certain rows
+ bool hasRowHeightOverrides = false;
+ // Color each Row by index's parity
+ bool hasZebraStripes = false;
+
+ void sort(Comparable Function(Dessert d) getField, bool ascending) {
+ desserts.sort((a, b) {
+ final aValue = getField(a);
+ final bValue = getField(b);
+ return ascending
+ ? Comparable.compare(aValue, bValue)
+ : Comparable.compare(bValue, aValue);
+ });
+ notifyListeners();
+ }
+
+ void updateSelectedDesserts(RestorableDessertSelections selectedRows) {
+ _selectedCount = 0;
+ for (var i = 0; i < desserts.length; i += 1) {
+ var dessert = desserts[i];
+ if (selectedRows.isSelected(i)) {
+ dessert.selected = true;
+ _selectedCount += 1;
+ } else {
+ dessert.selected = false;
+ }
+ }
+ notifyListeners();
+ }
+
+ @override
+ DataRow2 getRow(int index, [Color? color]) {
+ final format = NumberFormat.decimalPercentPattern(
+ locale: 'en',
+ decimalDigits: 0,
+ );
+ assert(index >= 0);
+ if (index >= desserts.length) throw 'index > _desserts.length';
+ final dessert = desserts[index];
+ return DataRow2.byIndex(
+ index: index,
+ selected: dessert.selected,
+ color: color != null
+ ? MaterialStateProperty.all(color)
+ : (hasZebraStripes && index.isEven
+ ? MaterialStateProperty.all(Theme.of(context).highlightColor)
+ : null),
+ onSelectChanged: (value) {
+ if (dessert.selected != value) {
+ _selectedCount += value! ? 1 : -1;
+ assert(_selectedCount >= 0);
+ dessert.selected = value;
+ notifyListeners();
+ }
+ },
+ onTap: hasRowTaps
+ ? () => _showSnackbar(context, 'Tapped on row ${dessert.name}')
+ : null,
+ onDoubleTap: hasRowTaps
+ ? () => _showSnackbar(context, 'Double Tapped on row ${dessert.name}')
+ : null,
+ onLongPress: hasRowTaps
+ ? () => _showSnackbar(context, 'Long pressed on row ${dessert.name}')
+ : null,
+ onSecondaryTap: hasRowTaps
+ ? () => _showSnackbar(context, 'Right clicked on row ${dessert.name}')
+ : null,
+ onSecondaryTapDown: hasRowTaps
+ ? (d) =>
+ _showSnackbar(context, 'Right button down on row ${dessert.name}')
+ : null,
+ specificRowHeight:
+ hasRowHeightOverrides && dessert.fat >= 25 ? 100 : null,
+ cells: [
+ DataCell(Text(dessert.name)),
+ DataCell(Text('${dessert.calories}'),
+ onTap: () => _showSnackbar(context,
+ 'Tapped on a cell with "${dessert.calories}"', Colors.red)),
+ DataCell(Text(dessert.fat.toStringAsFixed(1))),
+ DataCell(Text('${dessert.carbs}')),
+ DataCell(Text(dessert.protein.toStringAsFixed(1))),
+ DataCell(Text('${dessert.sodium}')),
+ DataCell(Text(format.format(dessert.calcium / 100))),
+ DataCell(Text(format.format(dessert.iron / 100))),
+ ],
+ );
+ }
+
+ @override
+ int get rowCount => desserts.length;
+
+ @override
+ bool get isRowCountApproximate => false;
+
+ @override
+ int get selectedRowCount => _selectedCount;
+
+ void selectAll(bool? checked) {
+ for (final dessert in desserts) {
+ dessert.selected = checked ?? false;
+ }
+ _selectedCount = (checked ?? false) ? desserts.length : 0;
+ notifyListeners();
+ }
+}
+
+/// Async datasource for AsynPaginatedDataTabke2 example. Based on AsyncDataTableSource which
+/// is an extension to Flutter's DataTableSource and aimed at solving
+/// saync data fetching scenarious by paginated table (such as using Web API)
+class DessertDataSourceAsync extends AsyncDataTableSource {
+ DessertDataSourceAsync() {
+ print('DessertDataSourceAsync created');
+ }
+
+ DessertDataSourceAsync.empty() {
+ _empty = true;
+ print('DessertDataSourceAsync.empty created');
+ }
+
+ DessertDataSourceAsync.error() {
+ _errorCounter = 0;
+ print('DessertDataSourceAsync.error created');
+ }
+
+ bool _empty = false;
+ int? _errorCounter;
+
+ RangeValues? _caloriesFilter;
+
+ RangeValues? get caloriesFilter => _caloriesFilter;
+ set caloriesFilter(RangeValues? calories) {
+ _caloriesFilter = calories;
+ refreshDatasource();
+ }
+
+ final DesertsFakeWebService _repo = DesertsFakeWebService();
+
+ String _sortColumn = "name";
+ bool _sortAscending = true;
+
+ void sort(String columnName, bool ascending) {
+ _sortColumn = columnName;
+ _sortAscending = ascending;
+ refreshDatasource();
+ }
+
+ Future getTotalRecords() {
+ return Future.delayed(
+ const Duration(milliseconds: 0), () => _empty ? 0 : _dessertsX3.length);
+ }
+
+ @override
+ Future getRows(int startIndex, int count) async {
+ print('getRows($startIndex, $count)');
+ if (_errorCounter != null) {
+ _errorCounter = _errorCounter! + 1;
+
+ if (_errorCounter! % 2 == 1) {
+ await Future.delayed(const Duration(milliseconds: 1000));
+ throw 'Error #${((_errorCounter! - 1) / 2).round() + 1} has occured';
+ }
+ }
+
+ final format = NumberFormat.decimalPercentPattern(
+ locale: 'en',
+ decimalDigits: 0,
+ );
+ assert(startIndex >= 0);
+
+ // List returned will be empty is there're fewer items than startingAt
+ var x = _empty
+ ? await Future.delayed(const Duration(milliseconds: 2000),
+ () => DesertsFakeWebServiceResponse(0, []))
+ : await _repo.getData(
+ startIndex, count, _caloriesFilter, _sortColumn, _sortAscending);
+
+ var r = AsyncRowsResponse(
+ x.totalRecords,
+ x.data.map((dessert) {
+ return DataRow(
+ key: ValueKey(dessert.id),
+ //selected: dessert.selected,
+ onSelectChanged: (value) {
+ if (value != null) {
+ setRowSelection(ValueKey(dessert.id), value);
+ }
+ },
+ cells: [
+ DataCell(Text(dessert.name)),
+ DataCell(Text('${dessert.calories}')),
+ DataCell(Text(dessert.fat.toStringAsFixed(1))),
+ DataCell(Text('${dessert.carbs}')),
+ DataCell(Text(dessert.protein.toStringAsFixed(1))),
+ DataCell(Text('${dessert.sodium}')),
+ DataCell(Text(format.format(dessert.calcium / 100))),
+ DataCell(Text(format.format(dessert.iron / 100))),
+ ],
+ );
+ }).toList());
+
+ return r;
+ }
+}
+
+class DesertsFakeWebServiceResponse {
+ DesertsFakeWebServiceResponse(this.totalRecords, this.data);
+
+ /// THe total ammount of records on the server, e.g. 100
+ final int totalRecords;
+
+ /// One page, e.g. 10 reocrds
+ final List data;
+}
+
+class DesertsFakeWebService {
+ int Function(Dessert, Dessert)? _getComparisonFunction(
+ String column, bool ascending) {
+ var coef = ascending ? 1 : -1;
+ switch (column) {
+ case 'name':
+ return (Dessert d1, Dessert d2) => coef * d1.name.compareTo(d2.name);
+ case 'calories':
+ return (Dessert d1, Dessert d2) => coef * (d1.calories - d2.calories);
+ case 'fat':
+ return (Dessert d1, Dessert d2) => coef * (d1.fat - d2.fat).round();
+ case 'carbs':
+ return (Dessert d1, Dessert d2) => coef * (d1.carbs - d2.carbs);
+ case 'protein':
+ return (Dessert d1, Dessert d2) =>
+ coef * (d1.protein - d2.protein).round();
+ case 'sodium':
+ return (Dessert d1, Dessert d2) => coef * (d1.sodium - d2.sodium);
+ case 'calcium':
+ return (Dessert d1, Dessert d2) => coef * (d1.calcium - d2.calcium);
+ case 'iron':
+ return (Dessert d1, Dessert d2) => coef * (d1.iron - d2.iron);
+ }
+
+ return null;
+ }
+
+ Future getData(int startingAt, int count,
+ RangeValues? caloriesFilter, String sortedBy, bool sortedAsc) async {
+ return Future.delayed(
+ Duration(
+ milliseconds: startingAt == 0
+ ? 2650
+ : startingAt < 20
+ ? 2000
+ : 400), () {
+ var result = _dessertsX3;
+
+ if (caloriesFilter != null) {
+ result = result
+ .where((e) =>
+ e.calories >= caloriesFilter.start &&
+ e.calories <= caloriesFilter.end)
+ .toList();
+ }
+
+ result.sort(_getComparisonFunction(sortedBy, sortedAsc));
+ return DesertsFakeWebServiceResponse(
+ result.length, result.skip(startingAt).take(count).toList());
+ });
+ }
+}
+
+int _selectedCount = 0;
+
+List _desserts = [
+ Dessert(
+ 'Frozen Yogurt',
+ 159,
+ 6.0,
+ 24,
+ 4.0,
+ 87,
+ 14,
+ 1,
+ ),
+ Dessert(
+ 'Ice Cream Sandwich',
+ 237,
+ 9.0,
+ 37,
+ 4.3,
+ 129,
+ 8,
+ 1,
+ ),
+ Dessert(
+ 'Eclair',
+ 262,
+ 16.0,
+ 24,
+ 6.0,
+ 337,
+ 6,
+ 7,
+ ),
+ Dessert(
+ 'Cupcake',
+ 305,
+ 3.7,
+ 67,
+ 4.3,
+ 413,
+ 3,
+ 8,
+ ),
+ Dessert(
+ 'Gingerbread',
+ 356,
+ 16.0,
+ 49,
+ 3.9,
+ 327,
+ 7,
+ 16,
+ ),
+ Dessert(
+ 'Jelly Bean',
+ 375,
+ 0.0,
+ 94,
+ 0.0,
+ 50,
+ 0,
+ 0,
+ ),
+ Dessert(
+ 'Lollipop',
+ 392,
+ 0.2,
+ 98,
+ 0.0,
+ 38,
+ 0,
+ 2,
+ ),
+ Dessert(
+ 'Honeycomb',
+ 408,
+ 3.2,
+ 87,
+ 6.5,
+ 562,
+ 0,
+ 45,
+ ),
+ Dessert(
+ 'Donut',
+ 452,
+ 25.0,
+ 51,
+ 4.9,
+ 326,
+ 2,
+ 22,
+ ),
+ Dessert(
+ 'Apple Pie',
+ 518,
+ 26.0,
+ 65,
+ 7.0,
+ 54,
+ 12,
+ 6,
+ ),
+ Dessert(
+ 'Frozen Yougurt with sugar',
+ 168,
+ 6.0,
+ 26,
+ 4.0,
+ 87,
+ 14,
+ 1,
+ ),
+ Dessert(
+ 'Ice Cream Sandwich with sugar',
+ 246,
+ 9.0,
+ 39,
+ 4.3,
+ 129,
+ 8,
+ 1,
+ ),
+ Dessert(
+ 'Eclair with sugar',
+ 271,
+ 16.0,
+ 26,
+ 6.0,
+ 337,
+ 6,
+ 7,
+ ),
+ Dessert(
+ 'Cupcake with sugar',
+ 314,
+ 3.7,
+ 69,
+ 4.3,
+ 413,
+ 3,
+ 8,
+ ),
+ Dessert(
+ 'Gingerbread with sugar',
+ 345,
+ 16.0,
+ 51,
+ 3.9,
+ 327,
+ 7,
+ 16,
+ ),
+ Dessert(
+ 'Jelly Bean with sugar',
+ 364,
+ 0.0,
+ 96,
+ 0.0,
+ 50,
+ 0,
+ 0,
+ ),
+ Dessert(
+ 'Lollipop with sugar',
+ 401,
+ 0.2,
+ 100,
+ 0.0,
+ 38,
+ 0,
+ 2,
+ ),
+ Dessert(
+ 'Honeycomd with sugar',
+ 417,
+ 3.2,
+ 89,
+ 6.5,
+ 562,
+ 0,
+ 45,
+ ),
+ Dessert(
+ 'Donut with sugar',
+ 461,
+ 25.0,
+ 53,
+ 4.9,
+ 326,
+ 2,
+ 22,
+ ),
+ Dessert(
+ 'Apple pie with sugar',
+ 527,
+ 26.0,
+ 67,
+ 7.0,
+ 54,
+ 12,
+ 6,
+ ),
+ Dessert(
+ 'Forzen yougurt with honey',
+ 223,
+ 6.0,
+ 36,
+ 4.0,
+ 87,
+ 14,
+ 1,
+ ),
+ Dessert(
+ 'Ice Cream Sandwich with honey',
+ 301,
+ 9.0,
+ 49,
+ 4.3,
+ 129,
+ 8,
+ 1,
+ ),
+ Dessert(
+ 'Eclair with honey',
+ 326,
+ 16.0,
+ 36,
+ 6.0,
+ 337,
+ 6,
+ 7,
+ ),
+ Dessert(
+ 'Cupcake with honey',
+ 369,
+ 3.7,
+ 79,
+ 4.3,
+ 413,
+ 3,
+ 8,
+ ),
+ Dessert(
+ 'Gignerbread with hone',
+ 420,
+ 16.0,
+ 61,
+ 3.9,
+ 327,
+ 7,
+ 16,
+ ),
+ Dessert(
+ 'Jelly Bean with honey',
+ 439,
+ 0.0,
+ 106,
+ 0.0,
+ 50,
+ 0,
+ 0,
+ ),
+ Dessert(
+ 'Lollipop with honey',
+ 456,
+ 0.2,
+ 110,
+ 0.0,
+ 38,
+ 0,
+ 2,
+ ),
+ Dessert(
+ 'Honeycomd with honey',
+ 472,
+ 3.2,
+ 99,
+ 6.5,
+ 562,
+ 0,
+ 45,
+ ),
+ Dessert(
+ 'Donut with honey',
+ 516,
+ 25.0,
+ 63,
+ 4.9,
+ 326,
+ 2,
+ 22,
+ ),
+ Dessert(
+ 'Apple pie with honey',
+ 582,
+ 26.0,
+ 77,
+ 7.0,
+ 54,
+ 12,
+ 6,
+ ),
+];
+
+List _dessertsX3 = _desserts.toList()
+ ..addAll(_desserts.map((i) => Dessert('${i.name} x2', i.calories, i.fat,
+ i.carbs, i.protein, i.sodium, i.calcium, i.iron)))
+ ..addAll(_desserts.map((i) => Dessert('${i.name} x3', i.calories, i.fat,
+ i.carbs, i.protein, i.sodium, i.calcium, i.iron)));
+
+_showSnackbar(BuildContext context, String text, [Color? color]) {
+ ScaffoldMessenger.of(context).showSnackBar(SnackBar(
+ backgroundColor: color,
+ duration: const Duration(seconds: 1),
+ content: Text(text),
+ ));
+}
diff --git a/lib/screens/sale_quotation/components/data_table.dart b/lib/screens/sale_quotation/components/data_table.dart
new file mode 100644
index 0000000..288367e
--- /dev/null
+++ b/lib/screens/sale_quotation/components/data_table.dart
@@ -0,0 +1,755 @@
+import 'package:data_table_2/data_table_2.dart';
+import 'package:sk_base_mobile/app_theme.dart';
+import 'package:sk_base_mobile/util/screen_adaper_util.dart';
+import 'package:sk_base_mobile/widgets/core/sk_text_input.dart';
+import './helper.dart';
+import 'package:flutter/material.dart';
+import 'package:intl/intl.dart';
+
+import './data_sources.dart';
+import './nav_helper.dart';
+
+// Copyright 2019 The Flutter team. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// The file was extracted from GitHub: https://github.com/flutter/gallery
+// Changes and modifications by Maxim Saplin, 2021
+
+class DataTable2FixedNMDemo extends StatefulWidget {
+ const DataTable2FixedNMDemo({super.key});
+
+ @override
+ DataTable2FixedNMDemoState createState() => DataTable2FixedNMDemoState();
+}
+
+class DataTable2FixedNMDemoState extends State {
+ bool _sortAscending = true;
+ int? _sortColumnIndex;
+ late DessertDataSource _dessertsDataSource;
+ late DessertDataSourceAsync _asyncDessertsDataSource;
+ bool _initialized = false;
+ final ScrollController _controller = ScrollController();
+ String selectedTableType = dflt;
+
+ int _fixedRows = 1;
+ int _fixedCols = 1;
+ int _dataItems = 30;
+
+ DataRow _getRow(int index, [Color? color]) {
+ final format = NumberFormat.decimalPercentPattern(
+ locale: 'zh',
+ decimalDigits: 0,
+ );
+ assert(index >= 0);
+ if (index >= _desserts.length) throw 'index > _desserts.length';
+ final dessert = _desserts[index];
+ return DataRow2.byIndex(
+ index: index,
+ // selected: dessert.selected,
+ color: color != null ? MaterialStateProperty.all(color) : null,
+ // onSelectChanged: (value) {
+ // if (dessert.selected != value) {
+ // dessert.selected = value!;
+ // setState(() {});
+ // }
+ // },
+ cells: [
+ DataCell(SkTextInput(textController: TextEditingController())),
+ DataCell(Text('${dessert.calories}')),
+ DataCell(Text(dessert.fat.toStringAsFixed(1))),
+ DataCell(Text('${dessert.carbs}')),
+ DataCell(Text(dessert.protein.toStringAsFixed(1))),
+ DataCell(Text('${dessert.sodium}')),
+ DataCell(Text(format.format(dessert.calcium / 100))),
+ DataCell(Text(format.format(dessert.iron / 100))),
+ ],
+ );
+ }
+
+ void selectAll(bool? checked) {
+ for (final dessert in _desserts) {
+ dessert.selected = checked ?? false;
+ }
+ }
+
+ @override
+ void didChangeDependencies() {
+ super.didChangeDependencies();
+ if (!_initialized) {
+ _dessertsDataSource = DessertDataSource(context);
+ _asyncDessertsDataSource = DessertDataSourceAsync();
+ _initialized = true;
+ _dessertsDataSource.addListener(() {
+ setState(() {});
+ });
+ }
+ }
+
+ void _sort(
+ Comparable Function(Dessert d) getField,
+ int columnIndex,
+ bool ascending,
+ ) {
+ _dessertsDataSource.sort(getField, ascending);
+ setState(() {
+ _sortColumnIndex = columnIndex;
+ _sortAscending = ascending;
+ });
+ }
+
+ Widget getTableFromSelectedType() {
+ switch (getCurrentRouteOption(context)) {
+ case paginatedFixedRowsCols:
+ {
+ return getPaginatedDataTable();
+ }
+ case asyncPaginatedFixedRowsCols:
+ {
+ return getAsyncPaginatedDataTable();
+ }
+ default:
+ {
+ return getDataTable();
+ }
+ }
+ }
+
+ @override
+ void dispose() {
+ _dessertsDataSource.dispose();
+ _controller.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Padding(
+ padding: const EdgeInsets.all(16),
+ child: Column(children: [
+ // _getParamsWidget(context),
+ Flexible(
+ fit: FlexFit.tight,
+ child: Theme(
+ data: ThemeData(dividerColor: Colors.grey[400]),
+ child: getTableFromSelectedType()))
+ ]));
+ }
+
+ DataTable2 getDataTable() {
+ return DataTable2(
+ scrollController: _controller,
+ columnSpacing: 0,
+ bottomMargin: 20,
+ border: TableBorder.all(width: 1.0, color: AppTheme.dividerColor),
+ // headingRowColor: MaterialStateProperty.resolveWith(
+ // (states) => _fixedRows > 0 ? Colors.grey[200] : Colors.transparent),
+ // headingRowDecoration: BoxDecoration(
+ // gradient: LinearGradient(
+ // colors: [
+ // Colors.grey[400]!,
+ // Colors.grey[200]!,
+ // ],
+ // begin: Alignment.topCenter,
+ // end: Alignment.bottomCenter,
+ // ),
+ // ),
+ fixedColumnsColor: Colors.grey[100],
+ // fixedCornerColor: Colors.grey[400],
+ minWidth: 1000,
+ fixedTopRows: _fixedRows,
+ fixedLeftColumns: _fixedCols,
+ // sortColumnIndex: _sortColumnIndex,
+ // sortAscending: _sortAscending,
+ // onSelectAll: (val) => setState(() => selectAll(val)),
+ columns: [
+ DataColumn2(
+ label: const Text('过渡'),
+ onSort: (columnIndex, ascending) =>
+ _sort((d) => d.name, columnIndex, ascending),
+ ),
+ DataColumn2(
+ label: const Text('名称'),
+ size: ColumnSize.S,
+ numeric: true,
+ onSort: (columnIndex, ascending) =>
+ _sort((d) => d.calories, columnIndex, ascending),
+ ),
+ DataColumn2(
+ label: const Text('规格、型号及说明'),
+ size: ColumnSize.S,
+ numeric: true,
+ onSort: (columnIndex, ascending) =>
+ _sort((d) => d.fat, columnIndex, ascending),
+ ),
+ DataColumn2(
+ label: const Text('单位'),
+ size: ColumnSize.S,
+ numeric: true,
+ onSort: (columnIndex, ascending) =>
+ _sort((d) => d.carbs, columnIndex, ascending),
+ ),
+ DataColumn2(
+ label: const Text('数量'),
+ size: ColumnSize.S,
+ numeric: true,
+ onSort: (columnIndex, ascending) =>
+ _sort((d) => d.protein, columnIndex, ascending),
+ ),
+ DataColumn2(
+ label: const Text('备注'),
+ size: ColumnSize.S,
+ numeric: true,
+ onSort: (columnIndex, ascending) =>
+ _sort((d) => d.sodium, columnIndex, ascending),
+ ),
+ DataColumn2(
+ label: const Text('单价'),
+ size: ColumnSize.S,
+ numeric: true,
+ onSort: (columnIndex, ascending) =>
+ _sort((d) => d.calcium, columnIndex, ascending),
+ ),
+ DataColumn2(
+ label: const Text('总价'),
+ size: ColumnSize.S,
+ numeric: true,
+ onSort: (columnIndex, ascending) =>
+ _sort((d) => d.iron, columnIndex, ascending),
+ ),
+ ],
+ rows: List.generate(
+ _dataItems, (index) => _getRow(index, Colors.transparent)));
+ }
+
+ Widget getPaginatedDataTable() {
+ return PaginatedDataTable2(
+ scrollController: _controller,
+ columnSpacing: 0,
+ horizontalMargin: 12,
+ border: TableBorder.all(width: 1.0, color: Colors.grey),
+ headingRowColor: MaterialStateProperty.resolveWith(
+ (states) => _fixedRows > 0 ? Colors.grey[200] : Colors.transparent),
+ fixedColumnsColor: Colors.grey[300],
+ fixedCornerColor: Colors.grey[400],
+ minWidth: 1000,
+ fixedTopRows: _fixedRows,
+ fixedLeftColumns: _fixedCols,
+ sortColumnIndex: _sortColumnIndex,
+ sortAscending: _sortAscending,
+ onSelectAll: (val) => setState(() => selectAll(val)),
+ columns: [
+ DataColumn2(
+ label: const Text('Desert'),
+ size: ColumnSize.S,
+ onSort: (columnIndex, ascending) =>
+ _sort((d) => d.name, columnIndex, ascending),
+ ),
+ DataColumn2(
+ label: const Text('Calories'),
+ size: ColumnSize.S,
+ numeric: true,
+ onSort: (columnIndex, ascending) =>
+ _sort((d) => d.calories, columnIndex, ascending),
+ ),
+ DataColumn2(
+ label: const Text('Fat (gm)'),
+ size: ColumnSize.S,
+ numeric: true,
+ onSort: (columnIndex, ascending) =>
+ _sort((d) => d.fat, columnIndex, ascending),
+ ),
+ DataColumn2(
+ label: const Text('Carbs (gm)'),
+ size: ColumnSize.S,
+ numeric: true,
+ onSort: (columnIndex, ascending) =>
+ _sort((d) => d.carbs, columnIndex, ascending),
+ ),
+ DataColumn2(
+ label: const Text('Protein (gm)'),
+ size: ColumnSize.S,
+ numeric: true,
+ onSort: (columnIndex, ascending) =>
+ _sort((d) => d.protein, columnIndex, ascending),
+ ),
+ DataColumn2(
+ label: const Text('Sodium (mg)'),
+ size: ColumnSize.S,
+ numeric: true,
+ onSort: (columnIndex, ascending) =>
+ _sort((d) => d.sodium, columnIndex, ascending),
+ ),
+ DataColumn2(
+ label: const Text('Calcium (%)'),
+ size: ColumnSize.S,
+ numeric: true,
+ onSort: (columnIndex, ascending) =>
+ _sort((d) => d.calcium, columnIndex, ascending),
+ ),
+ DataColumn2(
+ label: const Text('Iron (%)'),
+ size: ColumnSize.S,
+ numeric: true,
+ onSort: (columnIndex, ascending) =>
+ _sort((d) => d.iron, columnIndex, ascending),
+ ),
+ ],
+ source: _dessertsDataSource);
+ }
+
+ Widget getAsyncPaginatedDataTable() {
+ return AsyncPaginatedDataTable2(
+ scrollController: _controller,
+ columnSpacing: 0,
+ horizontalMargin: 12,
+ border: TableBorder.all(width: 1.0, color: Colors.grey),
+ headingRowColor: MaterialStateProperty.resolveWith(
+ (states) => _fixedRows > 0 ? Colors.grey[200] : Colors.transparent),
+ fixedColumnsColor: Colors.grey[300],
+ fixedCornerColor: Colors.grey[400],
+ minWidth: 1000,
+ fixedTopRows: _fixedRows,
+ fixedLeftColumns: _fixedCols,
+ sortColumnIndex: _sortColumnIndex,
+ sortAscending: _sortAscending,
+ onSelectAll: (val) => setState(() => selectAll(val)),
+ columns: [
+ DataColumn2(
+ label: const Text('Desert'),
+ size: ColumnSize.S,
+ onSort: (columnIndex, ascending) =>
+ _sort((d) => d.name, columnIndex, ascending),
+ ),
+ DataColumn2(
+ label: const Text('Calories'),
+ size: ColumnSize.S,
+ numeric: true,
+ onSort: (columnIndex, ascending) =>
+ _sort((d) => d.calories, columnIndex, ascending),
+ ),
+ DataColumn2(
+ label: const Text('Fat (gm)'),
+ size: ColumnSize.S,
+ numeric: true,
+ onSort: (columnIndex, ascending) =>
+ _sort((d) => d.fat, columnIndex, ascending),
+ ),
+ DataColumn2(
+ label: const Text('Carbs (gm)'),
+ size: ColumnSize.S,
+ numeric: true,
+ onSort: (columnIndex, ascending) =>
+ _sort((d) => d.carbs, columnIndex, ascending),
+ ),
+ DataColumn2(
+ label: const Text('Protein (gm)'),
+ size: ColumnSize.S,
+ numeric: true,
+ onSort: (columnIndex, ascending) =>
+ _sort((d) => d.protein, columnIndex, ascending),
+ ),
+ DataColumn2(
+ label: const Text('Sodium (mg)'),
+ size: ColumnSize.S,
+ numeric: true,
+ onSort: (columnIndex, ascending) =>
+ _sort((d) => d.sodium, columnIndex, ascending),
+ ),
+ DataColumn2(
+ label: const Text('Calcium (%)'),
+ size: ColumnSize.S,
+ numeric: true,
+ onSort: (columnIndex, ascending) =>
+ _sort((d) => d.calcium, columnIndex, ascending),
+ ),
+ DataColumn2(
+ label: const Text('Iron (%)'),
+ size: ColumnSize.S,
+ numeric: true,
+ onSort: (columnIndex, ascending) =>
+ _sort((d) => d.iron, columnIndex, ascending),
+ ),
+ ],
+ source: _asyncDessertsDataSource);
+ }
+
+ Theme _getParamsWidget(BuildContext context) {
+ const double col1 = 130;
+ const double col2 = 210;
+ return Theme(
+ data: blackSlider(context),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ SizedBox(
+ height: 36,
+ child: Row(
+ mainAxisSize: MainAxisSize.max,
+ children: [
+ SizedBox(
+ width: col1, child: Text('Fixed rows ($_fixedRows)')),
+ SizedBox(
+ width: col2,
+ child: Slider(
+ value: _fixedRows.toDouble(),
+ min: 0,
+ max: 10,
+ divisions: 10,
+ onChanged: (val) {
+ setState(() {
+ _fixedRows = val.toInt();
+ });
+ }))
+ ],
+ )),
+ SizedBox(
+ height: 36,
+ child: Row(
+ mainAxisSize: MainAxisSize.max,
+ children: [
+ SizedBox(
+ width: col1,
+ child: Text('Fixed columns ($_fixedCols)')),
+ SizedBox(
+ width: col2,
+ child: Slider(
+ value: _fixedCols.toDouble(),
+ min: 0,
+ max: 10,
+ divisions: 10,
+ onChanged: (val) {
+ setState(() {
+ _fixedCols = val.toInt();
+ });
+ }))
+ ],
+ )),
+ SizedBox(
+ height: 36,
+ child: Row(
+ mainAxisSize: MainAxisSize.max,
+ children: [
+ SizedBox(
+ width: col1, child: Text('Data items ($_dataItems)')),
+ SizedBox(
+ width: col2,
+ child: Slider(
+ value: _dataItems.toDouble(),
+ min: 0,
+ max: 30,
+ divisions: 10,
+ onChanged: (val) {
+ setState(() {
+ _dataItems = val.toInt();
+ });
+ }))
+ ],
+ ))
+ ],
+ ));
+ }
+}
+
+List _desserts = [
+ Dessert(
+ 'Frozen Yogurt',
+ 159,
+ 6.0,
+ 24,
+ 4.0,
+ 87,
+ 14,
+ 1,
+ ),
+ Dessert(
+ 'Ice Cream Sandwich',
+ 237,
+ 9.0,
+ 37,
+ 4.3,
+ 129,
+ 8,
+ 1,
+ ),
+ Dessert(
+ 'Eclair',
+ 262,
+ 16.0,
+ 24,
+ 6.0,
+ 337,
+ 6,
+ 7,
+ ),
+ Dessert(
+ 'Cupcake',
+ 305,
+ 3.7,
+ 67,
+ 4.3,
+ 413,
+ 3,
+ 8,
+ ),
+ Dessert(
+ 'Gingerbread',
+ 356,
+ 16.0,
+ 49,
+ 3.9,
+ 327,
+ 7,
+ 16,
+ ),
+ Dessert(
+ 'Jelly Bean',
+ 375,
+ 0.0,
+ 94,
+ 0.0,
+ 50,
+ 0,
+ 0,
+ ),
+ Dessert(
+ 'Lollipop',
+ 392,
+ 0.2,
+ 98,
+ 0.0,
+ 38,
+ 0,
+ 2,
+ ),
+ Dessert(
+ 'Honeycomb',
+ 408,
+ 3.2,
+ 87,
+ 6.5,
+ 562,
+ 0,
+ 45,
+ ),
+ Dessert(
+ 'Donut',
+ 452,
+ 25.0,
+ 51,
+ 4.9,
+ 326,
+ 2,
+ 22,
+ ),
+ Dessert(
+ 'Apple Pie',
+ 518,
+ 26.0,
+ 65,
+ 7.0,
+ 54,
+ 12,
+ 6,
+ ),
+ Dessert(
+ 'Frozen Yougurt with sugar',
+ 168,
+ 6.0,
+ 26,
+ 4.0,
+ 87,
+ 14,
+ 1,
+ ),
+ Dessert(
+ 'Ice Cream Sandwich with sugar',
+ 246,
+ 9.0,
+ 39,
+ 4.3,
+ 129,
+ 8,
+ 1,
+ ),
+ Dessert(
+ 'Eclair with sugar',
+ 271,
+ 16.0,
+ 26,
+ 6.0,
+ 337,
+ 6,
+ 7,
+ ),
+ Dessert(
+ 'Cupcake with sugar',
+ 314,
+ 3.7,
+ 69,
+ 4.3,
+ 413,
+ 3,
+ 8,
+ ),
+ Dessert(
+ 'Gingerbread with sugar',
+ 345,
+ 16.0,
+ 51,
+ 3.9,
+ 327,
+ 7,
+ 16,
+ ),
+ Dessert(
+ 'Jelly Bean with sugar',
+ 364,
+ 0.0,
+ 96,
+ 0.0,
+ 50,
+ 0,
+ 0,
+ ),
+ Dessert(
+ 'Lollipop with sugar',
+ 401,
+ 0.2,
+ 100,
+ 0.0,
+ 38,
+ 0,
+ 2,
+ ),
+ Dessert(
+ 'Honeycomd with sugar',
+ 417,
+ 3.2,
+ 89,
+ 6.5,
+ 562,
+ 0,
+ 45,
+ ),
+ Dessert(
+ 'Donut with sugar',
+ 461,
+ 25.0,
+ 53,
+ 4.9,
+ 326,
+ 2,
+ 22,
+ ),
+ Dessert(
+ 'Apple pie with sugar',
+ 527,
+ 26.0,
+ 67,
+ 7.0,
+ 54,
+ 12,
+ 6,
+ ),
+ Dessert(
+ 'Forzen yougurt with honey',
+ 223,
+ 6.0,
+ 36,
+ 4.0,
+ 87,
+ 14,
+ 1,
+ ),
+ Dessert(
+ 'Ice Cream Sandwich with honey',
+ 301,
+ 9.0,
+ 49,
+ 4.3,
+ 129,
+ 8,
+ 1,
+ ),
+ Dessert(
+ 'Eclair with honey',
+ 326,
+ 16.0,
+ 36,
+ 6.0,
+ 337,
+ 6,
+ 7,
+ ),
+ Dessert(
+ 'Cupcake with honey',
+ 369,
+ 3.7,
+ 79,
+ 4.3,
+ 413,
+ 3,
+ 8,
+ ),
+ Dessert(
+ 'Gignerbread with hone',
+ 420,
+ 16.0,
+ 61,
+ 3.9,
+ 327,
+ 7,
+ 16,
+ ),
+ Dessert(
+ 'Jelly Bean with honey',
+ 439,
+ 0.0,
+ 106,
+ 0.0,
+ 50,
+ 0,
+ 0,
+ ),
+ Dessert(
+ 'Lollipop with honey',
+ 456,
+ 0.2,
+ 110,
+ 0.0,
+ 38,
+ 0,
+ 2,
+ ),
+ Dessert(
+ 'Honeycomd with honey',
+ 472,
+ 3.2,
+ 99,
+ 6.5,
+ 562,
+ 0,
+ 45,
+ ),
+ Dessert(
+ 'Donut with honey',
+ 516,
+ 25.0,
+ 63,
+ 4.9,
+ 326,
+ 2,
+ 22,
+ ),
+ Dessert(
+ 'Apple pie with honey',
+ 582,
+ 26.0,
+ 77,
+ 7.0,
+ 54,
+ 12,
+ 6,
+ ),
+];
diff --git a/lib/screens/sale_quotation/components/helper.dart b/lib/screens/sale_quotation/components/helper.dart
new file mode 100644
index 0000000..c0af457
--- /dev/null
+++ b/lib/screens/sale_quotation/components/helper.dart
@@ -0,0 +1,176 @@
+import 'package:flutter/material.dart';
+import 'dart:math';
+
+ThemeData blackSlider(BuildContext context) {
+ return Theme.of(context).copyWith(
+ sliderTheme: SliderThemeData(
+ rangeThumbShape:
+ const RectRangeSliderThumbShape(enabledThumbRadius: 8),
+ thumbShape: const RectSliderThumbShape(enabledThumbRadius: 8),
+ thumbColor: Colors.grey[800],
+ activeTrackColor: Colors.grey[700],
+ inactiveTrackColor: Colors.grey[400],
+ activeTickMarkColor: Colors.white,
+ inactiveTickMarkColor: Colors.white));
+}
+
+class RectRangeSliderThumbShape extends RangeSliderThumbShape {
+ const RectRangeSliderThumbShape({
+ this.enabledThumbRadius = 10.0,
+ this.disabledThumbRadius,
+ this.elevation = 1.0,
+ this.pressedElevation = 6.0,
+ });
+
+ final double enabledThumbRadius;
+
+ final double? disabledThumbRadius;
+ double get _disabledThumbRadius => disabledThumbRadius ?? enabledThumbRadius;
+
+ final double elevation;
+
+ final double pressedElevation;
+
+ @override
+ Size getPreferredSize(bool isEnabled, bool isDiscrete) {
+ return Size.fromRadius(
+ isEnabled == true ? enabledThumbRadius : _disabledThumbRadius);
+ }
+
+ @override
+ void paint(
+ PaintingContext context,
+ Offset center, {
+ required Animation activationAnimation,
+ required Animation enableAnimation,
+ bool isDiscrete = false,
+ bool isEnabled = false,
+ bool? isOnTop,
+ required SliderThemeData sliderTheme,
+ TextDirection? textDirection,
+ Thumb? thumb,
+ bool? isPressed,
+ }) {
+ assert(sliderTheme.showValueIndicator != null);
+ assert(sliderTheme.overlappingShapeStrokeColor != null);
+
+ final Canvas canvas = context.canvas;
+ final Tween radiusTween = Tween(
+ begin: _disabledThumbRadius,
+ end: enabledThumbRadius,
+ );
+ final ColorTween colorTween = ColorTween(
+ begin: sliderTheme.disabledThumbColor,
+ end: sliderTheme.thumbColor,
+ );
+ final double radius = radiusTween.evaluate(enableAnimation);
+ final Tween elevationTween = Tween(
+ begin: elevation,
+ end: pressedElevation,
+ );
+
+ if (isOnTop ?? false) {
+ final Paint strokePaint = Paint()
+ ..color = sliderTheme.overlappingShapeStrokeColor!
+ ..strokeWidth = 1.0
+ ..style = PaintingStyle.stroke;
+ canvas.drawRect(
+ Rect.fromCenter(
+ center: center, width: 2 * radius, height: 2 * radius),
+ strokePaint);
+ }
+
+ final Color color = colorTween.evaluate(enableAnimation)!;
+
+ final double evaluatedElevation =
+ isPressed! ? elevationTween.evaluate(activationAnimation) : elevation;
+ final Path shadowPath = Path()
+ ..addArc(
+ Rect.fromCenter(
+ center: center, width: 2 * radius, height: 2 * radius),
+ 0,
+ pi * 2);
+ canvas.drawShadow(shadowPath, Colors.black, evaluatedElevation, true);
+
+ canvas.drawRect(
+ Rect.fromCenter(center: center, width: 2 * radius, height: 2 * radius),
+ Paint()..color = color,
+ );
+ }
+}
+
+class RectSliderThumbShape extends SliderComponentShape {
+ const RectSliderThumbShape({
+ this.enabledThumbRadius = 10.0,
+ this.disabledThumbRadius,
+ this.elevation = 1.0,
+ this.pressedElevation = 6.0,
+ });
+
+ final double enabledThumbRadius;
+
+ final double? disabledThumbRadius;
+ double get _disabledThumbRadius => disabledThumbRadius ?? enabledThumbRadius;
+
+ final double elevation;
+
+ final double pressedElevation;
+
+ @override
+ Size getPreferredSize(bool isEnabled, bool isDiscrete) {
+ return Size.fromRadius(
+ isEnabled == true ? enabledThumbRadius : _disabledThumbRadius);
+ }
+
+ @override
+ void paint(
+ PaintingContext context,
+ Offset center, {
+ required Animation activationAnimation,
+ required Animation enableAnimation,
+ required bool isDiscrete,
+ required TextPainter labelPainter,
+ required RenderBox parentBox,
+ required SliderThemeData sliderTheme,
+ required TextDirection textDirection,
+ required double value,
+ required double textScaleFactor,
+ required Size sizeWithOverflow,
+ }) {
+ assert(sliderTheme.disabledThumbColor != null);
+ assert(sliderTheme.thumbColor != null);
+
+ final Canvas canvas = context.canvas;
+ final Tween radiusTween = Tween(
+ begin: _disabledThumbRadius,
+ end: enabledThumbRadius,
+ );
+ final ColorTween colorTween = ColorTween(
+ begin: sliderTheme.disabledThumbColor,
+ end: sliderTheme.thumbColor,
+ );
+
+ final Color color = colorTween.evaluate(enableAnimation)!;
+ final double radius = radiusTween.evaluate(enableAnimation);
+
+ final Tween elevationTween = Tween(
+ begin: elevation,
+ end: pressedElevation,
+ );
+
+ final double evaluatedElevation =
+ elevationTween.evaluate(activationAnimation);
+ final Path path = Path()
+ ..addArc(
+ Rect.fromCenter(
+ center: center, width: 2 * radius, height: 2 * radius),
+ 0,
+ pi * 2);
+ canvas.drawShadow(path, Colors.black, evaluatedElevation, true);
+
+ canvas.drawRect(
+ Rect.fromCenter(center: center, width: 2 * radius, height: 2 * radius),
+ Paint()..color = color,
+ );
+ }
+}
diff --git a/lib/screens/sale_quotation/components/nav_helper.dart b/lib/screens/sale_quotation/components/nav_helper.dart
new file mode 100644
index 0000000..8eb5d90
--- /dev/null
+++ b/lib/screens/sale_quotation/components/nav_helper.dart
@@ -0,0 +1,71 @@
+import 'package:flutter/material.dart';
+
+/// Route options are used to configure certain features of
+/// the given example
+String getCurrentRouteOption(BuildContext context) {
+ var isEmpty = ModalRoute.of(context) != null &&
+ ModalRoute.of(context)!.settings.arguments != null &&
+ ModalRoute.of(context)!.settings.arguments is String
+ ? ModalRoute.of(context)!.settings.arguments as String
+ : '';
+
+ return isEmpty;
+}
+
+// Route options
+const dflt = 'Default';
+const noData = 'No data';
+const autoRows = 'Auto rows';
+const showBordersWithZebraStripes = 'Borders with Zebra';
+const custPager = 'Custom pager';
+const defaultSorting = 'Default sorting';
+const selectAllPage = 'Select all at page';
+const rowTaps = 'Row Taps';
+const rowHeightOverrides = 'Row height overrides';
+const fixedColumnWidth = 'Fixed column width';
+const dataTable2 = 'DataTable2';
+const paginatedFixedRowsCols = 'PaginatedDataTable2';
+const asyncPaginatedFixedRowsCols = 'AsyncPaginatedDataTable2';
+const custArrows = 'Custom sort arrows';
+const asyncErrors =
+ "Errors/Retries"; // Async sample that emulates network error and allow retrying load operation
+const goToLast =
+ "Start at last page"; // Used by async example, navigates to the very last page upon opening the screen
+const rounded = 'Rounded style';
+
+/// Configurations available to given example routes
+const Map> routeOptions = {
+ '/datatable2': [
+ dflt,
+ noData,
+ showBordersWithZebraStripes,
+ fixedColumnWidth,
+ rowTaps,
+ rowHeightOverrides,
+ custArrows,
+ rounded
+ ],
+ '/paginated2': [dflt, noData, autoRows, custPager, defaultSorting],
+ '/datatable2fixedmn': [
+ dataTable2,
+ paginatedFixedRowsCols,
+ asyncPaginatedFixedRowsCols
+ ],
+ '/asyncpaginated2': [
+ dflt,
+ noData,
+ selectAllPage,
+ autoRows,
+ asyncErrors,
+ goToLast,
+ custPager
+ ],
+};
+
+List? getOptionsForRoute(String route) {
+ if (!routeOptions.containsKey(route)) {
+ return null;
+ }
+
+ return routeOptions[route];
+}
diff --git a/lib/screens/sale_quotation/sale_quotation.dart b/lib/screens/sale_quotation/sale_quotation.dart
new file mode 100644
index 0000000..12387a3
--- /dev/null
+++ b/lib/screens/sale_quotation/sale_quotation.dart
@@ -0,0 +1,50 @@
+import 'package:flutter/material.dart';
+import 'package:flutter_sticky_header/flutter_sticky_header.dart';
+import 'package:sk_base_mobile/screens/sale_quotation/components/data_table.dart';
+import 'package:sk_base_mobile/util/screen_adaper_util.dart';
+import 'package:sk_base_mobile/widgets/sk_appbar.dart';
+
+class SaleQuotationPage extends StatelessWidget {
+ const SaleQuotationPage({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: const SkAppbar(title: '报价计算'),
+ body: buildBody(),
+ );
+ }
+
+ Widget buildBody() {
+ return DataTable2FixedNMDemo();
+ // return SingleChildScrollView(
+ // child: Column(
+ // children: [Text('11')],
+ // ),
+ // );
+ // return SliverStickyHeader.builder(
+ // builder: (context, state) => Container(
+ // height: 60.0,
+ // color: (state.isPinned ? Colors.pink : Colors.lightBlue)
+ // .withOpacity(1.0 - state.scrollPercentage),
+ // padding: EdgeInsets.symmetric(horizontal: 16.0),
+ // alignment: Alignment.centerLeft,
+ // child: Text(
+ // 'Header #1',
+ // style: const TextStyle(color: Colors.white),
+ // ),
+ // ),
+ // sliver: SliverList(
+ // delegate: SliverChildBuilderDelegate(
+ // (context, i) => ListTile(
+ // leading: CircleAvatar(
+ // child: Text('0'),
+ // ),
+ // title: Text('List tile #$i'),
+ // ),
+ // childCount: 4,
+ // ),
+ // ),
+ // );
+ }
+}
diff --git a/lib/screens/workbench/workbench.dart b/lib/screens/workbench/workbench.dart
index 9d78eb6..e47def3 100644
--- a/lib/screens/workbench/workbench.dart
+++ b/lib/screens/workbench/workbench.dart
@@ -1,118 +1,150 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
+import 'package:flutter_staggered_grid_view/flutter_staggered_grid_view.dart';
+import 'package:flutter_svg/svg.dart';
import 'package:get/get.dart';
-import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/constants/router.dart';
+import 'package:sk_base_mobile/screens/workbench/workbench_controller.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
import 'package:sk_base_mobile/util/snack_bar.util.dart';
-class WorkBenchModel {
- final String title;
- final String route;
- WorkBenchModel({required this.title, required this.route});
-}
-
class WorkBenchPage extends StatelessWidget {
WorkBenchPage({super.key});
- final List works = [
- WorkBenchModel(title: '库存', route: '/inventory'),
- WorkBenchModel(title: '产品', route: '/product'),
- WorkBenchModel(title: '合同', route: '/contract'),
- WorkBenchModel(title: '人事', route: '/personnel'),
- WorkBenchModel(title: '公车', route: '/finance'),
- WorkBenchModel(title: '任务', route: '/task_manage'),
- WorkBenchModel(title: '报表', route: '/report'),
- ];
+ final controller = Get.put(WorkBenchController());
@override
Widget build(BuildContext context) {
return Scaffold(
- appBar: AppBar(
- leading: const SizedBox(),
- title: const Text(
- '工作台',
- ),
- ),
- body: SingleChildScrollView(
- child: Column(children: [
- Container(
- padding: EdgeInsets.all(ScreenAdaper.width(20)),
- child: GridView.builder(
- shrinkWrap: true,
- physics: const NeverScrollableScrollPhysics(),
- itemCount: works.length,
- gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
- crossAxisCount: Get.width > 800 ? 5 : 3,
- crossAxisSpacing: ScreenAdaper.width(20),
- mainAxisSpacing: ScreenAdaper.height(20),
- childAspectRatio: 1.0),
- itemBuilder: (BuildContext context, int index) {
- return buildCard(index);
- },
+ appBar: AppBar(
+ leading: const SizedBox(),
+ title: const Text(
+ '工作台',
),
- )
- ])),
+ ),
+ body: buildList()
+ // Container(
+ // padding: EdgeInsets.all(ScreenAdaper.width(20)),
+ // child: GridView.builder(
+ // shrinkWrap: true,
+ // physics: const NeverScrollableScrollPhysics(),
+ // itemCount: works.length,
+ // gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
+ // crossAxisCount: Get.width > 800 ? 5 : 3,
+ // crossAxisSpacing: ScreenAdaper.width(20),
+ // mainAxisSpacing: ScreenAdaper.height(20),
+ // childAspectRatio: 1.0),
+ // itemBuilder: (BuildContext context, int index) {
+ // return buildCard(index);
+ // },
+ // ),
+ // )
+
+ );
+ }
+
+ Widget buildList() {
+ return MasonryGridView.count(
+ padding: EdgeInsets.only(top: ScreenAdaper.width(20)),
+ crossAxisCount: Get.width > 800 ? 6 : 4,
+ itemCount: controller.menus.length,
+ itemBuilder: (context, index) {
+ return buildItem(
+ index,
+ );
+ },
);
}
- Widget buildCard(int index) {
+ Widget buildItem(int index) {
return InkWell(
- onTap: () {
- final route = RouteConfig.getPages
- .map((e) => e.name)
- .firstWhereOrNull((name) => name == works[index].route);
- if (route != null) {
- Get.toNamed(works[index].route);
- } else {
- SnackBarUtil().info('功能待开发。');
- }
- },
- child: Container(
- clipBehavior: Clip.antiAlias,
- decoration: BoxDecoration(
- borderRadius: BorderRadius.circular(10),
- gradient: const LinearGradient(
- begin: Alignment.centerLeft,
- end: Alignment.centerRight,
- colors: [AppTheme.primaryColorLight, AppTheme.primaryColor]),
- boxShadow: [
- BoxShadow(
- color: AppTheme.black.withOpacity(0.4),
- offset: const Offset(0, 0),
- blurRadius: 1,
- spreadRadius: 1)
- ],
+ onTap: () {
+ final route = RouteConfig.getPages
+ .map((e) => e.name)
+ .firstWhereOrNull((name) => name == controller.menus[index].route);
+ if (route != null) {
+ Get.toNamed(controller.menus[index].route);
+ } else {
+ SnackBarUtil().info('功能待开发。');
+ }
+ },
+ child: Container(
+ alignment: Alignment.center,
+ padding: EdgeInsets.only(
+ top: ScreenAdaper.height(20), bottom: ScreenAdaper.height(20)),
+ child: Column(
+ children: [
+ SvgPicture.asset(
+ 'assets/icons/${controller.menus[index].icon}',
+ width: ScreenAdaper.width(100),
+ height: ScreenAdaper.width(100),
),
- child: Stack(
- children: [
- // Image.asset(works[index].icon),
- Center(
- child: Stack(
- alignment: Alignment.center,
- children: [
- Text(
- works[index].title,
- style: TextStyle(
- letterSpacing: ScreenAdaper.width(10),
- fontSize: ScreenAdaper.height(40),
- fontWeight: FontWeight.bold,
- foreground: Paint()
- ..style = PaintingStyle.stroke
- ..strokeWidth = 5
- ..color = Colors.black),
- ),
- Text(
- works[index].title,
- style: TextStyle(
- letterSpacing: ScreenAdaper.width(10),
- color: Colors.white,
- fontSize: ScreenAdaper.height(40),
- fontWeight: FontWeight.bold),
- ),
- ],
- ),
- )
- ],
- )));
+ SizedBox(
+ height: ScreenAdaper.height(10),
+ ),
+ Text(controller.menus[index].title)
+ ],
+ ),
+ ),
+ );
}
+ // Widget buildCard(int index) {
+ // return InkWell(
+ // onTap: () {
+ // final route = RouteConfig.getPages
+ // .map((e) => e.name)
+ // .firstWhereOrNull((name) => name == works[index].route);
+ // if (route != null) {
+ // Get.toNamed(works[index].route);
+ // } else {
+ // SnackBarUtil().info('功能待开发。');
+ // }
+ // },
+ // child: Container(
+ // clipBehavior: Clip.antiAlias,
+ // decoration: BoxDecoration(
+ // borderRadius: BorderRadius.circular(10),
+ // gradient: const LinearGradient(
+ // begin: Alignment.centerLeft,
+ // end: Alignment.centerRight,
+ // colors: [AppTheme.primaryColorLight, AppTheme.primaryColor]),
+ // boxShadow: [
+ // BoxShadow(
+ // color: AppTheme.black.withOpacity(0.4),
+ // offset: const Offset(0, 0),
+ // blurRadius: 1,
+ // spreadRadius: 1)
+ // ],
+ // ),
+ // child: Stack(
+ // children: [
+ // // Image.asset(works[index].icon),
+ // Center(
+ // child: Stack(
+ // alignment: Alignment.center,
+ // children: [
+ // Text(
+ // works[index].title,
+ // style: TextStyle(
+ // letterSpacing: ScreenAdaper.width(10),
+ // fontSize: ScreenAdaper.height(40),
+ // fontWeight: FontWeight.bold,
+ // foreground: Paint()
+ // ..style = PaintingStyle.stroke
+ // ..strokeWidth = 5
+ // ..color = Colors.black),
+ // ),
+ // Text(
+ // works[index].title,
+ // style: TextStyle(
+ // letterSpacing: ScreenAdaper.width(10),
+ // color: Colors.white,
+ // fontSize: ScreenAdaper.height(40),
+ // fontWeight: FontWeight.bold),
+ // ),
+ // ],
+ // ),
+ // )
+ // ],
+ // )));
+ // }
}
diff --git a/lib/screens/workbench/workbench_controller.dart b/lib/screens/workbench/workbench_controller.dart
index 530ea08..fbc8537 100644
--- a/lib/screens/workbench/workbench_controller.dart
+++ b/lib/screens/workbench/workbench_controller.dart
@@ -1,9 +1,20 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
+import 'package:sk_base_mobile/models/workbench.model.dart';
class WorkBenchController extends GetxController {
late final AnimationController animationController;
-
+ final List menus = [
+ WorkBenchModel(title: '库存', route: '/inventory', icon: 'inventory.svg'),
+ WorkBenchModel(title: '产品', route: '/product', icon: 'product.svg'),
+ WorkBenchModel(title: '合同', route: '/contract', icon: 'contract.svg'),
+ WorkBenchModel(title: '人事', route: '/hr', icon: 'hr.svg'),
+ WorkBenchModel(title: '公车', route: '/vehicle', icon: 'vehicle.svg'),
+ WorkBenchModel(title: '任务', route: '/task_manage', icon: 'task_manage.svg'),
+ WorkBenchModel(title: '报表', route: '/report', icon: 'report.svg'),
+ WorkBenchModel(
+ title: '报价计算', route: '/sale_quotation', icon: 'sale_quotation.svg'),
+ ];
@override
void onClose() {
animationController.dispose();
diff --git a/lib/util/modal.util.dart b/lib/util/modal.util.dart
index a547900..7660244 100644
--- a/lib/util/modal.util.dart
+++ b/lib/util/modal.util.dart
@@ -2,7 +2,7 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
-import 'package:sk_base_mobile/widgets/core/zt_bottomsheet_picker.dart';
+import 'package:sk_base_mobile/widgets/core/sk_bottomsheet_picker.dart';
class ModalUtil {
static Future showWarningDialog(
@@ -55,7 +55,7 @@ class ModalUtil {
showCupertinoModalPopup(
context: Get.overlayContext!,
builder: (BuildContext context) {
- return ZtBottomSheetPicker(
+ return SkBottomSheetPicker(
title: title,
onChanged: onChanged,
firstLevel: firstLevel,
diff --git a/lib/widgets/core/zt_base_date_picker.dart b/lib/widgets/core/sk_base_date_picker.dart
similarity index 98%
rename from lib/widgets/core/zt_base_date_picker.dart
rename to lib/widgets/core/sk_base_date_picker.dart
index 8eecbff..d15def2 100644
--- a/lib/widgets/core/zt_base_date_picker.dart
+++ b/lib/widgets/core/sk_base_date_picker.dart
@@ -5,14 +5,14 @@ import 'package:sk_base_mobile/util/logger_util.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
// ignore: must_be_immutable
-class ZtBaseDatePicker extends StatelessWidget {
+class SkBaseDatePicker extends StatelessWidget {
final Function(String)? onDateTimeChanged;
late final FixedExtentScrollController yearController;
late final FixedExtentScrollController monthController;
late final FixedExtentScrollController dayController;
DateTime? initialDate = DateTime.now();
DateTime? endDate = DateTime.now();
- ZtBaseDatePicker(
+ SkBaseDatePicker(
{super.key, this.onDateTimeChanged, this.initialDate, this.endDate});
@override
diff --git a/lib/widgets/core/zt_bottomsheet_picker.dart b/lib/widgets/core/sk_bottomsheet_picker.dart
similarity index 98%
rename from lib/widgets/core/zt_bottomsheet_picker.dart
rename to lib/widgets/core/sk_bottomsheet_picker.dart
index 7309cb0..60c4443 100644
--- a/lib/widgets/core/zt_bottomsheet_picker.dart
+++ b/lib/widgets/core/sk_bottomsheet_picker.dart
@@ -4,7 +4,7 @@ import 'package:get/get.dart';
import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
-class ZtBottomSheetPicker extends StatelessWidget {
+class SkBottomSheetPicker extends StatelessWidget {
final Function? onChanged;
final FixedExtentScrollController firstLevelControlller =
FixedExtentScrollController();
@@ -18,7 +18,7 @@ class ZtBottomSheetPicker extends StatelessWidget {
final double? itemHeight;
final double? popupHeight;
final String title;
- ZtBottomSheetPicker(
+ SkBottomSheetPicker(
{super.key,
this.title = '请选择',
required this.onChanged,
diff --git a/lib/widgets/core/zk_date_picker.dart b/lib/widgets/core/sk_date_picker.dart
similarity index 97%
rename from lib/widgets/core/zk_date_picker.dart
rename to lib/widgets/core/sk_date_picker.dart
index 47d823a..c03a55a 100644
--- a/lib/widgets/core/zk_date_picker.dart
+++ b/lib/widgets/core/sk_date_picker.dart
@@ -2,14 +2,14 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
-class ZtDatePicker extends StatelessWidget {
+class SkDatePicker extends StatelessWidget {
final TextEditingController textController;
final VoidCallback? onClear;
final Function? onDateSelected;
final bool isRequired;
final String labelText;
- const ZtDatePicker({
+ const SkDatePicker({
super.key,
this.onClear,
this.onDateSelected,
diff --git a/lib/widgets/core/zt_number_input.dart b/lib/widgets/core/sk_number_input.dart
similarity index 96%
rename from lib/widgets/core/zt_number_input.dart
rename to lib/widgets/core/sk_number_input.dart
index 1d87d1c..1781362 100644
--- a/lib/widgets/core/zt_number_input.dart
+++ b/lib/widgets/core/sk_number_input.dart
@@ -3,7 +3,7 @@ import 'package:flutter/services.dart';
import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
-class ZtNumberInput extends StatelessWidget {
+class SkNumberInput extends StatelessWidget {
final TextEditingController textController;
final VoidCallback? onTap;
final Function(String)? onChanged;
@@ -11,7 +11,7 @@ class ZtNumberInput extends StatelessWidget {
final bool isRequired;
final String labelText;
final String? hint;
- const ZtNumberInput(
+ const SkNumberInput(
{super.key,
required this.textController,
this.onTap,
diff --git a/lib/widgets/core/zt_search_select.dart b/lib/widgets/core/sk_search_select.dart
similarity index 98%
rename from lib/widgets/core/zt_search_select.dart
rename to lib/widgets/core/sk_search_select.dart
index f381e6f..e0bf0d8 100644
--- a/lib/widgets/core/zt_search_select.dart
+++ b/lib/widgets/core/sk_search_select.dart
@@ -3,7 +3,7 @@ import 'package:flutter_typeahead/flutter_typeahead.dart';
import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
-class ZtSearchSelect extends StatelessWidget {
+class SkSearchSelect extends StatelessWidget {
final TextEditingController textController;
final Function? suggestionsCallback;
final void Function(T)? onSelected;
@@ -13,7 +13,7 @@ class ZtSearchSelect extends StatelessWidget {
final EdgeInsetsGeometry? contentPadding;
final bool isRequired;
final Widget Function(BuildContext, T) itemBuilder;
- const ZtSearchSelect(
+ const SkSearchSelect(
{super.key,
required this.textController,
required this.itemBuilder,
diff --git a/lib/widgets/core/zt_text_input.dart b/lib/widgets/core/sk_text_input.dart
similarity index 96%
rename from lib/widgets/core/zt_text_input.dart
rename to lib/widgets/core/sk_text_input.dart
index b544de0..86e78e4 100644
--- a/lib/widgets/core/zt_text_input.dart
+++ b/lib/widgets/core/sk_text_input.dart
@@ -2,14 +2,14 @@ import 'package:flutter/material.dart';
import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
-class ZtTextInput extends StatelessWidget {
+class SkTextInput extends StatelessWidget {
final TextEditingController textController;
final VoidCallback? onTap;
final bool isRequired;
final String labelText;
final String? hint;
final bool isTextArea;
- const ZtTextInput({
+ const SkTextInput({
super.key,
required this.textController,
this.onTap,
diff --git a/lib/widgets/sk_appbar.dart b/lib/widgets/sk_appbar.dart
new file mode 100644
index 0000000..235224c
--- /dev/null
+++ b/lib/widgets/sk_appbar.dart
@@ -0,0 +1,14 @@
+import 'package:flutter/material.dart';
+
+class SkAppbar extends StatelessWidget implements PreferredSizeWidget {
+ final String title;
+ const SkAppbar({super.key, required this.title});
+
+ @override
+ Widget build(BuildContext context) {
+ return AppBar(title: Text(title));
+ }
+
+ @override
+ Size get preferredSize => Size.fromHeight(kToolbarHeight);
+}
diff --git a/pubspec.lock b/pubspec.lock
index 60a6d95..80834f4 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -161,6 +161,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.6"
+ data_table_2:
+ dependency: "direct main"
+ description:
+ name: data_table_2
+ sha256: e403de6d9a58dddf27700114b614ea8ea5aa8442d7fbdfbe8b3d11b0512e7a49
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.5.12"
date_format:
dependency: "direct main"
description:
@@ -366,6 +374,22 @@ packages:
url: "https://pub.dev"
source: hosted
version: "0.7.0"
+ flutter_sticky_header:
+ dependency: "direct main"
+ description:
+ name: flutter_sticky_header
+ sha256: "017f398fbb45a589e01491861ca20eb6570a763fd9f3888165a978e11248c709"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.6.5"
+ flutter_svg:
+ dependency: "direct main"
+ description:
+ name: flutter_svg
+ sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2"
+ url: "https://pub.dev"
+ source: hosted
+ version: "2.0.10+1"
flutter_test:
dependency: "direct dev"
description: flutter
@@ -592,6 +616,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.8.3"
+ path_parsing:
+ dependency: transitive
+ description:
+ name: path_parsing
+ sha256: e3e67b1629e6f7e8100b367d3db6ba6af4b1f0bb80f64db18ef1fbabd2fa9ccf
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.0.1"
path_provider:
dependency: "direct main"
description:
@@ -957,6 +989,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "4.3.3"
+ value_layout_builder:
+ dependency: transitive
+ description:
+ name: value_layout_builder
+ sha256: "98202ec1807e94ac72725b7f0d15027afde513c55c69ff3f41bcfccb950831bc"
+ url: "https://pub.dev"
+ source: hosted
+ version: "0.3.1"
+ vector_graphics:
+ dependency: transitive
+ description:
+ name: vector_graphics
+ sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.11+1"
+ vector_graphics_codec:
+ dependency: transitive
+ description:
+ name: vector_graphics_codec
+ sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.11+1"
+ vector_graphics_compiler:
+ dependency: transitive
+ description:
+ name: vector_graphics_compiler
+ sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81"
+ url: "https://pub.dev"
+ source: hosted
+ version: "1.1.11+1"
vector_math:
dependency: transitive
description:
diff --git a/pubspec.yaml b/pubspec.yaml
index 06196df..416fa25 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -61,6 +61,9 @@ dependencies:
image_gallery_saver: ^2.0.3
flutter_staggered_grid_view: ^0.7.0
flutter_native_splash: ^2.3.11
+ flutter_svg: ^2.0.10+1
+ flutter_sticky_header: ^0.6.5
+ data_table_2: ^2.5.12
dev_dependencies:
flutter_test:
sdk: flutter