feat: export excel open it in third app

This commit is contained in:
louis 2024-04-12 15:17:29 +08:00
parent 8a4e1791b7
commit 2cce68fe0c
17 changed files with 267 additions and 121 deletions

View File

@ -4,7 +4,7 @@
# This file should be version controlled and should not be manually edited.
version:
revision: "300451adae589accbece3490f4396f10bdf15e6e"
revision: "41456452f29d64e8deb623a3c927524bcf9f111b"
channel: "stable"
project_type: app
@ -13,26 +13,26 @@ project_type: app
migration:
platforms:
- platform: root
create_revision: 300451adae589accbece3490f4396f10bdf15e6e
base_revision: 300451adae589accbece3490f4396f10bdf15e6e
create_revision: 41456452f29d64e8deb623a3c927524bcf9f111b
base_revision: 41456452f29d64e8deb623a3c927524bcf9f111b
- platform: android
create_revision: 300451adae589accbece3490f4396f10bdf15e6e
base_revision: 300451adae589accbece3490f4396f10bdf15e6e
create_revision: 41456452f29d64e8deb623a3c927524bcf9f111b
base_revision: 41456452f29d64e8deb623a3c927524bcf9f111b
- platform: ios
create_revision: 300451adae589accbece3490f4396f10bdf15e6e
base_revision: 300451adae589accbece3490f4396f10bdf15e6e
create_revision: 41456452f29d64e8deb623a3c927524bcf9f111b
base_revision: 41456452f29d64e8deb623a3c927524bcf9f111b
- platform: linux
create_revision: 300451adae589accbece3490f4396f10bdf15e6e
base_revision: 300451adae589accbece3490f4396f10bdf15e6e
create_revision: 41456452f29d64e8deb623a3c927524bcf9f111b
base_revision: 41456452f29d64e8deb623a3c927524bcf9f111b
- platform: macos
create_revision: 300451adae589accbece3490f4396f10bdf15e6e
base_revision: 300451adae589accbece3490f4396f10bdf15e6e
create_revision: 41456452f29d64e8deb623a3c927524bcf9f111b
base_revision: 41456452f29d64e8deb623a3c927524bcf9f111b
- platform: web
create_revision: 300451adae589accbece3490f4396f10bdf15e6e
base_revision: 300451adae589accbece3490f4396f10bdf15e6e
create_revision: 41456452f29d64e8deb623a3c927524bcf9f111b
base_revision: 41456452f29d64e8deb623a3c927524bcf9f111b
- platform: windows
create_revision: 300451adae589accbece3490f4396f10bdf15e6e
base_revision: 300451adae589accbece3490f4396f10bdf15e6e
create_revision: 41456452f29d64e8deb623a3c927524bcf9f111b
base_revision: 41456452f29d64e8deb623a3c927524bcf9f111b
# User provided section

View File

@ -31,7 +31,6 @@ android {
namespace "com.sdkj.skt"
compileSdkVersion flutter.compileSdkVersion
ndkVersion flutter.ndkVersion
// compileSdkVersion 34
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
@ -77,4 +76,6 @@ flutter {
source '../..'
}
dependencies {}
dependencies {
}

View File

@ -1,4 +1,5 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application
android:label="山矿通"
android:name="${applicationName}"
@ -30,6 +31,17 @@
<meta-data
android:name="flutterEmbedding"
android:value="2" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileProvider"
android:exported="false"
android:grantUriPermissions="true"
tools:replace="android:authorities">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/filepaths"
tools:replace="android:resource" />
</provider>
</application>
<uses-permission
android:name="android.permission.READ_PHONE_STATE" />

View File

@ -0,0 +1,8 @@
<paths>
<external-path name="external-path" path="." />
<external-cache-path name="external-cache-path" path="." />
<external-files-path name="external-files-path" path="." />
<files-path name="files_path" path="." />
<cache-path name="cache-path" path="." />
<root-path name="root" path="." />
</paths>

View File

@ -6,6 +6,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.0' // Change this to a version that supports compileSdkVersion 34
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
@ -24,7 +25,16 @@ subprojects {
subprojects {
project.evaluationDependsOn(':app')
}
subprojects {
project.configurations.all {
resolutionStrategy.eachDependency { details ->
if (details.requested.group == 'com.android.support'
&& !details.requested.name.contains('multidex') ) {
details.useVersion "27.1.1"
}
}
}
}
tasks.register("clean", Delete) {
delete rootProject.buildDir
}

View File

@ -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';

View File

@ -22,4 +22,5 @@ class Urls {
'sale_quotation/sale_quotation_component';
static String saleQuotationTemplate =
'sale_quotation/sale_quotation_template';
static String saleQuotation = 'sale_quotation/sale_quotation';
}

View File

@ -1,10 +1,8 @@
import 'dart:ui';
class WorkBenchModel {
final String title;
final String icon;
final String? route;
final VoidCallback? onTap;
final Function()? onTap;
WorkBenchModel(
{required this.title, required this.icon, this.route, this.onTap});
}

View File

@ -1,5 +1,6 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import 'package:flutter_svg/svg.dart';
import 'package:get/get.dart';
import 'package:sk_base_mobile/app_theme.dart';
@ -17,13 +18,12 @@ import 'package:sk_base_mobile/widgets/core/sk_appbar.dart';
class SaleQuotationEndDrawer extends StatelessWidget {
final controller = Get.find<SaleQuotationController>();
SaleQuotationEndDrawer({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: const SkAppbar(title: '', hideLeading: true),
body: buildBody(),
);
appBar: const SkAppbar(title: '', hideLeading: true), body: buildBody()
// Only render the body if '_isRendered' is true
);
}
Widget buildBody() {
@ -33,7 +33,7 @@ class SaleQuotationEndDrawer extends StatelessWidget {
Expanded(
child: buildTemplatePicker(),
),
buildAction()
// buildAction()
],
);
}
@ -59,8 +59,8 @@ class SaleQuotationEndDrawer extends StatelessWidget {
child: SkFlatButton(
onPressed: () async {
if (controller.templateName.value != '默认') {
await RouterUtil.back();
await controller.saveToDatabase();
await RouterUtil.back();
} else {
templateNameDialog();
}
@ -117,15 +117,7 @@ class SaleQuotationEndDrawer extends StatelessWidget {
Widget buildItem(int index) {
return SkInk(
onTap: () {
// final route = RouteConfig.getPages
// .map((e) => e.name)
// .firstWhereOrNull((name) => name == controller.menus[index].route);
// if (route != null) {
// } else {
// SnackBarUtil().info('您没有权限,请联系管理员。后期会隐藏没有权限的功能');
// }
},
onTap: controller.menus[index].onTap,
child: Container(
alignment: Alignment.center,
padding: EdgeInsets.only(
@ -182,63 +174,65 @@ class SaleQuotationEndDrawer extends StatelessWidget {
}
Widget buildTemplateItem(SaleQuotationTemplateModel element, int index) {
return SkInk(
onTap: () async {
await Future.delayed(const Duration(milliseconds: 150));
controller.changeTemplate(element);
await RouterUtil.back();
},
margin: EdgeInsets.symmetric(
horizontal: ScreenAdaper.width(10),
vertical: ScreenAdaper.height(10)),
border: Border.all(color: AppTheme.dividerColor),
borderRadius: BorderRadius.circular(ScreenAdaper.sp(15)),
child: Container(
padding: EdgeInsets.symmetric(
vertical: ScreenAdaper.height(20),
horizontal: ScreenAdaper.width(20)),
child: Row(children: [
Expanded(
child: Text(
'${element.name}',
style: TextStyle(fontSize: ScreenAdaper.height(25)),
)),
SkInk(
onTap: () {
templateNameDialog(title: element.name);
},
child: Icon(
Icons.edit,
color: Colors.grey[600],
size: ScreenAdaper.height(40),
),
return Obx(() => SkInk(
color: controller.templateId.value == element.id
? AppTheme.primaryColorLight
: Colors.transparent,
onTap: () async {
controller.changeTemplate(element);
await RouterUtil.back();
},
margin: EdgeInsets.symmetric(
horizontal: ScreenAdaper.width(10),
vertical: ScreenAdaper.height(10)),
border: Border.all(color: AppTheme.dividerColor),
borderRadius: BorderRadius.circular(ScreenAdaper.sp(15)),
child: Container(
padding: EdgeInsets.symmetric(
vertical: ScreenAdaper.height(20),
horizontal: ScreenAdaper.width(20)),
child: Row(children: [
Expanded(
child: Text(
'${element.name}',
style: TextStyle(fontSize: ScreenAdaper.height(25)),
)),
SkInk(
onTap: () {
templateNameDialog(title: element.name);
},
child: Icon(
Icons.edit,
color: Colors.grey[600],
size: ScreenAdaper.height(40),
),
),
SizedBox(
width: ScreenAdaper.width(10),
),
SkInk(
color: Colors.transparent,
onTap: () {
ModalUtil.alert(
title: '删除模板',
content: Text(
'确定删除模板${element.name}吗?',
style: TextStyle(fontSize: ScreenAdaper.height(30)),
),
onConfirm: () async {
await controller.deleteTemplate(element.id!);
controller.templates.removeAt(index);
});
},
child: Icon(
Icons.delete,
color: Colors.grey[600],
size: ScreenAdaper.height(40),
),
),
]),
),
SizedBox(
width: ScreenAdaper.width(10),
),
SkInk(
color: Colors.transparent,
onTap: () {
ModalUtil.alert(
title: '删除模板',
content: Text(
'确定删除模板${element.name}吗?',
style: TextStyle(fontSize: ScreenAdaper.height(30)),
),
onConfirm: () async {
await controller.deleteTemplate(element.id!);
controller.templates.removeAt(index);
});
},
child: Icon(
Icons.delete,
color: Colors.grey[600],
size: ScreenAdaper.height(40),
),
),
]),
),
);
));
}
void templateNameDialog({String? title = ''}) {

View File

@ -1,10 +1,17 @@
import 'dart:convert';
import 'dart:io';
import 'package:decimal/decimal.dart';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:math_expressions/math_expressions.dart';
import 'package:open_filex/open_filex.dart';
import 'package:path_provider/path_provider.dart';
import 'package:sk_base_mobile/apis/api.dart';
import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/config.dart';
import 'package:sk_base_mobile/constants/constants.dart';
import 'package:sk_base_mobile/models/base_search_more_controller.dart';
import 'package:sk_base_mobile/models/sale_quotaion_component.model.dart';
import 'package:sk_base_mobile/models/sale_quotaion_group.model.dart';
@ -12,12 +19,16 @@ import 'package:sk_base_mobile/models/sale_quotation.model.dart';
import 'package:sk_base_mobile/models/sale_quotation_template.model.dart';
import 'package:sk_base_mobile/models/workbench.model.dart';
import 'package:sk_base_mobile/screens/sale_quotation/components/sale_quotation_group_search.dart';
import 'package:sk_base_mobile/services/dio.service.dart';
import 'package:sk_base_mobile/services/storage.service.dart';
import 'package:sk_base_mobile/util/logger_util.dart';
import 'package:sk_base_mobile/util/snack_bar.util.dart';
import 'package:sk_base_mobile/widgets/core/sk_flat_button.dart';
import 'package:sk_base_mobile/widgets/form_item/sk_multi_search_more.dart';
import 'package:sk_base_mobile/util/modal.util.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
import 'package:pinyin/pinyin.dart';
import 'package:sk_base_mobile/widgets/loading_indicator.dart';
class SaleQuotationController extends GetxController {
static SaleQuotationController get to => Get.find();
@ -29,22 +40,114 @@ class SaleQuotationController extends GetxController {
RxBool isFormulaEditing = false.obs;
RxString formula = '成本 * 1.3 / 0.864'.obs;
RxList<SaleQuotationTemplateModel> templates = RxList([]);
final List<WorkBenchModel> menus = [
WorkBenchModel(title: '导出明细', icon: 'export.svg'),
// WorkBenchModel(title: '模板', icon: 'sale_quotation_template.svg'),
WorkBenchModel(title: '配件管理', icon: 'product.svg'),
WorkBenchModel(title: '分组管理', icon: 'sale_quotation_group.svg'),
WorkBenchModel(title: '计算公式', icon: 'sale_quotation_formula.svg'),
];
final List<WorkBenchModel> menus = [];
RxString templateName = '默认'.obs;
int? templateId;
RxnInt templateId = RxnInt(null);
final downloadProgress = RxDouble(0.0);
@override
void onReady() {
init();
super.onReady();
}
Future export() async {
try {
final dir = await getDownloadsDirectory();
if (dir != null) {
String storagePath = dir.path;
File file = File('$storagePath/$templateName.xls');
if (!file.existsSync()) {
file.createSync();
}
CancelToken token = CancelToken();
ModalUtil.alert(
barrierDismissible: false,
contentPadding: EdgeInsets.zero,
showActions: false,
content:
//
Obx(() => Column(
mainAxisAlignment: MainAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
Stack(
children: [
SizedBox(
height: ScreenAdaper.height(60),
child: LinearProgressIndicator(
value: downloadProgress.value,
),
),
Positioned(
left: 0,
right: 0,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'${downloadProgress.value * 100}%',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: ScreenAdaper.height(30)),
),
const LoadingIndicator(
color: AppTheme.nearlyBlack)
],
)),
],
),
SkFlatButton(
onPressed: () {
token.cancel('已取消下载');
Get.back();
},
textColor: AppTheme.nearlyBlack,
color: AppTheme.nearlyWhite,
buttonText: '取消',
)
],
)));
try {
await DioService.dio.download(
'${Urls.saleQuotation}/export/${templateId.value}', file.path,
cancelToken: token,
onReceiveProgress: onReceiveProgress,
options: Options(
responseType: ResponseType.bytes,
followRedirects: false,
));
Get.back();
await OpenFilex.open(file.path);
} catch (e) {
Get.back();
SnackBarUtil().error((e as dynamic).error ?? '暂时无法下载,请稍后重试或联系管理员');
LoggerUtil().error(e);
}
}
} catch (e) {
SnackBarUtil().error('暂时无法下载,请稍后重试或联系管理员');
}
}
///
void onReceiveProgress(num received, num total) {
LoggerUtil().info(received);
if (total != -1) {
downloadProgress.value =
Decimal.parse((received / total).toStringAsFixed(2))
.toDouble()
.toPrecision(2);
}
}
Future<void> init() async {
menus.addAll([
WorkBenchModel(title: '导出明细', icon: 'export.svg', onTap: export),
// WorkBenchModel(title: '模板', icon: 'sale_quotation_template.svg'),
WorkBenchModel(title: '配件管理', icon: 'product.svg'),
WorkBenchModel(title: '分组管理', icon: 'sale_quotation_group.svg'),
WorkBenchModel(title: '计算公式', icon: 'sale_quotation_formula.svg'),
]);
String? salesQuotation = StorageService.to.getString('salesQuotation');
if (salesQuotation != null) {
SaleQuotationTemplateModel editTemplate =
@ -60,7 +163,7 @@ class SaleQuotationController extends GetxController {
formula.value = editTemplate.template.formula;
totalPrice.value = editTemplate.template.totalPrice?.toDouble() ?? 0.0;
totalCost.value = editTemplate.template.totalCost?.toDouble() ?? 0.0;
templateId = editTemplate.id;
templateId.value = editTemplate.id;
}
///
@ -153,15 +256,15 @@ class SaleQuotationController extends GetxController {
'formula': formula.value
}
};
if (templateId != null) {
data['id'] = templateId;
if (templateId.value != null) {
data['id'] = templateId.value;
}
await StorageService.to.setString('salesQuotation', jsonEncode(data));
}
///
Future<bool> saveToDatabase() async {
if (templateId == null) {
if (templateId.value == null) {
await Api.createSaleQuotationTemplate({
'name': templateName.value,
'template': {
@ -172,7 +275,7 @@ class SaleQuotationController extends GetxController {
}
});
} else {
await Api.updateSaleQuotationTemplate(templateId!, {
await Api.updateSaleQuotationTemplate(templateId.value!, {
'name': templateName.value,
'template': {
'data': groups.toJson(),
@ -188,8 +291,8 @@ class SaleQuotationController extends GetxController {
}
///
Future<void> deleteTemplate(int templateId) async {
await Api.deleteSaleQuotationTemplate(templateId);
Future<void> deleteTemplate(int deleteId) async {
await Api.deleteSaleQuotationTemplate(deleteId);
SnackBarUtil().success('已删除');
}
@ -321,7 +424,7 @@ class SaleQuotationController extends GetxController {
totalPrice.value = 0.0;
formula.value = '成本 * 1.3 / 0.864';
templateName.value = '默认';
templateId = null;
templateId.value = null;
saveToLocal();
}
}

View File

@ -234,6 +234,7 @@ class SaleQuotationPage extends StatelessWidget {
top: ScreenAdaper.height(10),
bottom: ScreenAdaper.height(10)),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
GestureDetector(
onTap: () {

View File

@ -14,7 +14,7 @@ class DioService extends get_package.GetxService {
static DioService get to => get_package.Get.find();
static Dio get dio => _dio;
static late Dio _dio;
List<String> whiteList = [Urls.login];
List<String> whiteList = [Urls.login, '${Urls.saleQuotation}/export'];
BaseOptions dioBaseOptions = BaseOptions(
connectTimeout: const Duration(seconds: GloablConfig.DIO_TIMEOUT),
baseUrl: GloablConfig.BASE_URL,
@ -129,7 +129,9 @@ class DioService extends get_package.GetxService {
void onResponse(Response response, ResponseInterceptorHandler handler) async {
/* LoggerUtil().info('[Service-dio] ${response.data}'); */
if (whiteList.contains(response.requestOptions.path)) {
if (whiteList.firstWhereOrNull(
(item) => response.requestOptions.path.contains(item)) !=
null) {
handler.next(response);
return;
}

View File

@ -5,6 +5,7 @@ import 'package:flutter/widgets.dart';
import 'package:get/get.dart';
import 'package:sk_base_mobile/apis/api.dart';
import 'package:sk_base_mobile/store/dict.store.dart';
import 'package:sk_base_mobile/store/resource.store.dart';
import 'package:sk_base_mobile/util/logger_util.dart';
import 'package:sk_base_mobile/widgets/tap_to_dismiss_keyboard.dart';
import 'package:sk_base_mobile/models/auth.dart';
@ -135,7 +136,8 @@ class AuthStore extends GetxService {
Future<void> getCommonInfo() async {
await AppInfoService.to.getAppConfig();
await Future.wait([
DictService.to.getDictTypes()
DictService.to.getDictTypes(),
ResourceService.to.getResources()
///
// Get.putAsync<BlockStore>(() => BlockStore().init()),

View File

@ -11,11 +11,13 @@ class SkFlatButton extends StatelessWidget {
final String buttonText;
final Icon? icon;
final Color? color;
final Color? textColor;
final BorderRadiusGeometry? borderRadius;
const SkFlatButton(
{super.key,
this.buttonText = TextEnum.createInventoryInOutBtnText,
this.onPressed,
this.textColor,
this.icon,
this.color,
this.borderRadius,
@ -45,7 +47,8 @@ class SkFlatButton extends StatelessWidget {
: Text(
buttonText,
style: TextStyle(
color: AppTheme.nearlyWhite,
color: textColor ??
Theme.of(context).colorScheme.onPrimary,
fontWeight: FontWeight.bold,
fontSize: ScreenAdaper.height(25),
),

View File

@ -6,8 +6,9 @@ import 'package:sk_base_mobile/util/screen_adaper_util.dart';
class LoadingIndicator extends StatelessWidget {
final bool animating;
final bool common;
final Color? color;
const LoadingIndicator(
{super.key, this.animating = true, this.common = false});
{super.key, this.animating = true, this.common = false, this.color});
@override
Widget build(BuildContext context) {
@ -18,7 +19,7 @@ class LoadingIndicator extends StatelessWidget {
padding: EdgeInsets.all(ScreenAdaper.height(10)),
child: CupertinoActivityIndicator(
animating: animating,
color: AppTheme.primaryColor,
color: color ?? AppTheme.primaryColor,
radius: ScreenAdaper.sp(25),
),
);

View File

@ -351,10 +351,10 @@ packages:
dependency: transitive
description:
name: flutter_plugin_android_lifecycle
sha256: b068ffc46f82a55844acfa4fdbb61fad72fa2aef0905548419d97f0f95c456da
sha256: "592dc01a18961a51c24ae5d963b724b2b7fa4a95c100fe8eb6ca8a5a4732cadf"
url: "https://pub.dev"
source: hosted
version: "2.0.17"
version: "2.0.18"
flutter_screenutil:
dependency: "direct main"
description:
@ -621,6 +621,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.0.0"
open_filex:
dependency: "direct main"
description:
name: open_filex
sha256: "74e2280754cf8161e860746c3181db2c996d6c1909c7057b738ede4a469816b8"
url: "https://pub.dev"
source: hosted
version: "4.4.0"
package_info:
dependency: "direct main"
description:

View File

@ -72,6 +72,8 @@ dependencies:
sk_datetime_picker:
git:
url: https://gitee.com/lu-zixun/sk-date-time-picker.git
open_filex: ^4.4.0
# sk_datetime_picker:
# path: ./lib/widgets/common/datetime_picker
dev_dependencies: