feat: sale_quotation

This commit is contained in:
louis 2024-04-12 09:56:00 +08:00
parent 59e3d00a00
commit 8a4e1791b7
47 changed files with 1137 additions and 435 deletions

1
assets/icons/export.svg Normal file
View File

@ -0,0 +1 @@
<?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"><svg t="1712886588712" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4546" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><path d="M945.493333 839.543467l70.0416 77.141333a13.653333 13.653333 0 0 1 0 18.295467l-70.0416 77.141333a10.376533 10.376533 0 0 1-12.424533 3.003733 13.653333 13.653333 0 0 1-7.645867-12.151466v-154.282667a13.653333 13.653333 0 0 1 7.645867-12.151467 10.376533 10.376533 0 0 1 12.424533 3.003734z m0 0" fill="#0b988f" p-id="4547"></path><path d="M914.773333 133.9392H539.170133V35.498667a26.487467 26.487467 0 0 0-35.2256-27.306667L27.306667 134.485333a30.856533 30.856533 0 0 0-18.158934 30.037334v619.3152A30.856533 30.856533 0 0 0 27.306667 813.8752l477.047466 125.610667a26.487467 26.487467 0 0 0 35.2256-27.306667v-91.7504H914.773333a19.797333 19.797333 0 0 0 19.797334-19.797333V153.736533A19.797333 19.797333 0 0 0 914.773333 133.9392zM400.5888 616.311467l-57.070933 2.730666-69.632-102.673066L204.8 619.042133l-57.070933-2.730666L245.76 474.7264l-98.440533-145.408 55.296 2.594133 71.2704 102.4 71.2704-102.4 55.296-2.594133-98.440534 145.408zM641.706667 709.973333a10.376533 10.376533 0 0 1-10.376534 10.376534h-79.189333a10.376533 10.376533 0 0 1-10.376533-10.376534v-63.351466a10.24 10.24 0 0 1 10.376533-10.24h78.6432a10.376533 10.376533 0 0 1 10.376533 10.24z m0-136.533333a10.376533 10.376533 0 0 1-10.376534 10.376533h-79.189333a10.376533 10.376533 0 0 1-10.376533-10.376533v-62.122667a10.24 10.24 0 0 1 10.376533-10.24h78.6432a10.376533 10.376533 0 0 1 10.376533 10.24z m0-135.304533a10.376533 10.376533 0 0 1-10.376534 10.376533h-79.189333a10.376533 10.376533 0 0 1-10.376533-10.376533v-62.122667a10.24 10.24 0 0 1 10.376533-10.24h78.6432a10.376533 10.376533 0 0 1 10.376533 10.24z m0-135.304534a10.376533 10.376533 0 0 1-10.376534 10.376534h-79.189333a10.376533 10.376533 0 0 1-10.376533-10.376534v-62.122666a10.24 10.24 0 0 1 10.376533-10.24h78.6432a10.376533 10.376533 0 0 1 10.376533 10.24zM826.9824 709.973333a10.376533 10.376533 0 0 1-10.376533 10.376534h-103.082667a10.376533 10.376533 0 0 1-10.376533-10.376534v-63.351466a10.24 10.24 0 0 1 10.376533-10.24h103.2192a10.24 10.24 0 0 1 10.376533 10.24z m0-135.304533a10.376533 10.376533 0 0 1-10.376533 10.376533h-103.082667a10.376533 10.376533 0 0 1-10.376533-11.605333v-62.122667a10.24 10.24 0 0 1 10.376533-10.24h103.2192a10.24 10.24 0 0 1 10.376533 10.24z m0-135.304533a10.376533 10.376533 0 0 1-10.376533 10.376533h-103.082667a10.376533 10.376533 0 0 1-10.376533-10.376533v-63.351467a10.24 10.24 0 0 1 10.376533-10.24h103.2192a10.24 10.24 0 0 1 10.376533 10.24z m0-135.304534a10.376533 10.376533 0 0 1-10.376533 10.376534h-103.082667a10.376533 10.376533 0 0 1-10.376533-10.376534v-63.351466a10.24 10.24 0 0 1 10.376533-10.24h103.2192a10.24 10.24 0 0 1 10.376533 10.24z" fill="#0b988f" p-id="4548"></path><path d="M716.3904 883.5072l226.235733 0 0 80.554667-226.235733 0 0-80.554667Z" fill="#0b988f" p-id="4549"></path></svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

View File

@ -0,0 +1 @@
<?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"><svg t="1712818988776" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="38399" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><path d="M512 0c282.763636 0 512 229.236364 512 512s-229.236364 512-512 512S0 794.763636 0 512 229.236364 0 512 0zM426.077091 227.84c-20.293818 16.290909-40.634182 40.680727-52.829091 81.361455L352.907636 390.516364H234.961455a8.750545 8.750545 0 0 0-8.09891 8.145454v65.070546c0 4.049455 4.049455 8.098909 8.09891 8.098909h97.605818l-60.974546 243.991272c-12.194909 48.779636-44.730182 40.680727-44.730182 40.680728H186.181818V837.818182h40.680727c32.535273 0 65.024-4.049455 81.31491-24.389818 20.340364-20.340364 32.535273-56.925091 40.634181-97.605819l61.021091-243.991272h93.556364a8.750545 8.750545 0 0 0 8.098909-8.098909V398.661818a8.750545 8.750545 0 0 0-8.145455-8.145454H430.173091l20.340364-77.265455c4.049455-12.194909 24.389818-32.535273 36.584727-40.680727 44.683636-32.488727 105.704727-12.194909 146.385454-4.049455V199.400727c-40.680727-8.145455-130.141091-36.584727-207.406545 28.439273z m222.533818 162.676364h-105.704727a8.750545 8.750545 0 0 0-8.145455 8.145454v65.070546c0 4.049455 4.049455 8.098909 8.145455 8.098909h65.070545c4.049455 0 8.098909 4.096 12.194909 8.145454l24.389819 52.875637v12.194909l-73.216 85.364363c-4.049455 0-8.098909 4.096-12.19491 4.096H502.225455a8.750545 8.750545 0 0 0-8.145455 8.098909v65.070546c0 4.096 4.096 8.145455 8.145455 8.145454h65.070545c4.049455 0 12.194909-4.049455 12.194909-8.145454l93.556364-105.704727c4.049455-4.049455 8.098909-4.049455 8.098909 0l52.875636 105.704727c0 4.096 8.098909 8.145455 12.194909 8.145454h65.070546a8.750545 8.750545 0 0 0 8.098909-8.145454v-65.070546a8.750545 8.750545 0 0 0-8.098909-8.098909h-24.389818c-4.096 0-8.145455-4.096-12.241455-8.145454l-40.634182-81.314909v-12.194909l48.779637-52.875637c4.096-4.049455 8.145455-8.145455 12.194909-8.145454h56.925091a8.750545 8.750545 0 0 0 8.145454-8.098909V398.661818a8.750545 8.750545 0 0 0-8.145454-8.145454h-65.024c-4.096 0-12.241455 4.049455-12.241455 8.145454l-69.12 73.169455c-4.049455 4.096-8.098909 4.096-8.098909 0l-36.631273-73.169455c-4.049455-4.096-8.098909-8.145455-12.194909-8.145454z" fill="#d4237a" p-id="38400"></path></svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

View File

@ -0,0 +1 @@
<?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"><svg t="1712818660922" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="10922" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><path d="M512 872.838095c-24.380952 0-46.32381-4.87619-68.266667-17.066666l-365.714285-190.171429-51.2 26.819048c-34.133333 17.066667-34.133333 68.266667 0 85.333333l451.047619 236.495238c9.752381 4.87619 21.942857 7.314286 34.133333 7.314286s24.380952-2.438095 34.133333-7.314286l451.047619-236.495238c34.133333-17.066667 34.133333-68.266667 0-85.333333l-51.2-26.819048-365.714285 190.171429c-21.942857 12.190476-43.885714 17.066667-68.266667 17.066666z" fill="#8F65FF" p-id="10923"></path><path d="M997.180952 468.114286l-51.2-26.819048-134.095238 70.704762-80.457143 41.447619-151.161904 80.457143c-21.942857 9.752381-43.885714 17.066667-68.266667 17.066667s-46.32381-4.87619-68.266667-17.066667L292.571429 553.447619 212.114286 512l-134.095238-70.704762-51.2 26.819048c-34.133333 17.066667-34.133333 68.266667 0 85.333333l51.2 26.819048 78.019047 41.447619 319.390476 168.228571c9.752381 4.87619 21.942857 7.314286 34.133334 7.314286s24.380952-2.438095 34.133333-7.314286l319.390476-168.228571 78.019048-41.447619 51.2-26.819048c39.009524-17.066667 39.009524-65.828571 4.87619-85.333333z" fill="#C7B2FF" p-id="10924"></path><path d="M512 799.695238c-12.190476 0-24.380952-2.438095-34.133333-7.314286L158.47619 624.152381l-78.019047 41.447619 365.714286 190.171429c21.942857 9.752381 43.885714 17.066667 68.266666 17.066666s46.32381-4.87619 68.266667-17.066666l365.714286-190.171429-78.019048-41.447619-319.390476 168.228571c-14.628571 4.87619-26.819048 7.314286-39.009524 7.314286z" fill="#FFFFFF" p-id="10925"></path><path d="M997.180952 246.247619L546.133333 9.752381c-4.87619-2.438095-9.752381-4.87619-17.066666-7.314286-4.87619-2.438095-12.190476-2.438095-17.066667-2.438095s-12.190476 0-17.066667 2.438095-12.190476 2.438095-17.066666 7.314286L26.819048 246.247619c-34.133333 17.066667-34.133333 68.266667 0 85.333333l51.2 26.819048 78.019047 41.447619 134.095238 70.704762 78.019048 41.447619 107.27619 56.07619c9.752381 4.87619 21.942857 7.314286 34.133334 7.314286s24.380952-2.438095 34.133333-7.314286l107.276191-56.07619 78.019047-41.447619 134.095238-70.704762 78.019048-41.447619 51.2-26.819048c39.009524-17.066667 39.009524-68.266667 4.87619-85.333333z" fill="#8F65FF" p-id="10926"></path><path d="M529.066667 4.87619c-12.190476-2.438095-24.380952-2.438095-34.133334 0 4.87619-2.438095 12.190476-2.438095 17.066667-2.438095 4.87619 0 12.190476 0 17.066667 2.438095z" fill="#FFFFFF" p-id="10927"></path></svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -0,0 +1 @@
<?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"><svg t="1712818467557" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5527" xmlns:xlink="http://www.w3.org/1999/xlink" width="48" height="48"><path d="M952.32 122.88v573.44H579.584c-26.624-22.528-63.488-30.72-98.304-22.528l-55.296 12.288c-16.384-36.864-53.248-59.392-94.208-53.248l-98.304 14.336V122.88h718.848z" fill="#4DA9FF" p-id="5528"></path><path d="M968.704 81.92h-757.76c-10.24 0-20.48 10.24-20.48 20.48v552.96c-20.48 6.144-40.96 14.336-59.392 26.624l-77.824 47.104c-18.432 10.24-24.576 32.768-14.336 53.248L122.88 921.6c6.144 12.288 20.48 20.48 34.816 20.48h104.448c40.96 0 81.92-12.288 118.784-32.768l210.944-126.976c16.384-10.24 22.528-28.672 18.432-45.056h358.4c10.24 0 20.48-10.24 20.48-20.48V102.4c0-10.24-8.192-20.48-20.48-20.48zM360.448 872.448c-28.672 18.432-63.488 26.624-96.256 26.624H159.744l-81.92-137.216L153.6 716.8c12.288-8.192 24.576-14.336 38.912-18.432s26.624-8.192 40.96-10.24h4.096l102.4-14.336c20.48-2.048 38.912 8.192 49.152 24.576 0 0 0 2.048 2.048 2.048L272.384 737.28l-30.72 10.24c-10.24 4.096-16.384 16.384-12.288 26.624 4.096 8.192 10.24 14.336 20.48 14.336 2.048 0 4.096 0 6.144-2.048l145.408-49.152 20.48-6.144c2.048 0 4.096-2.048 6.144-2.048l63.488-14.336c26.624-6.144 55.296 4.096 73.728 24.576 2.048 2.048 4.096 6.144 6.144 10.24l-210.944 122.88zM948.224 696.32h-368.64c-26.624-22.528-63.488-30.72-98.304-22.528l-55.296 12.288c-16.384-36.864-53.248-59.392-94.208-53.248l-98.304 14.336V122.88h716.8l-2.048 573.44z" fill="#1C2754" p-id="5529"></path><path d="M354.304 184.32h471.04c12.288 0 20.48 8.192 20.48 20.48v61.44c0 12.288-8.192 20.48-20.48 20.48h-471.04c-12.288 0-20.48-8.192-20.48-20.48v-61.44c0-10.24 10.24-20.48 20.48-20.48z" fill="#334489" p-id="5530"></path><path d="M354.304 368.64h61.44c12.288 0 20.48 8.192 20.48 20.48s-8.192 20.48-20.48 20.48h-61.44c-12.288 0-20.48-8.192-20.48-20.48 0-10.24 10.24-20.48 20.48-20.48zM559.104 368.64h61.44c12.288 0 20.48 8.192 20.48 20.48s-8.192 20.48-20.48 20.48h-61.44c-12.288 0-20.48-8.192-20.48-20.48 0-10.24 10.24-20.48 20.48-20.48zM354.304 471.04h61.44c12.288 0 20.48 8.192 20.48 20.48s-8.192 20.48-20.48 20.48h-61.44c-12.288 0-20.48-8.192-20.48-20.48 0-10.24 10.24-20.48 20.48-20.48zM559.104 471.04h61.44c12.288 0 20.48 8.192 20.48 20.48s-8.192 20.48-20.48 20.48h-61.44c-12.288 0-20.48-8.192-20.48-20.48 0-10.24 10.24-20.48 20.48-20.48zM354.304 573.44h61.44c12.288 0 20.48 8.192 20.48 20.48s-8.192 20.48-20.48 20.48h-61.44c-12.288 0-20.48-8.192-20.48-20.48 0-10.24 10.24-20.48 20.48-20.48zM559.104 573.44h61.44c12.288 0 20.48 8.192 20.48 20.48s-8.192 20.48-20.48 20.48h-61.44c-12.288 0-20.48-8.192-20.48-20.48 0-10.24 10.24-20.48 20.48-20.48zM763.904 368.64h61.44c12.288 0 20.48 8.192 20.48 20.48s-8.192 20.48-20.48 20.48h-61.44c-12.288 0-20.48-8.192-20.48-20.48 0-10.24 10.24-20.48 20.48-20.48zM763.904 471.04h61.44c12.288 0 20.48 8.192 20.48 20.48s-8.192 20.48-20.48 20.48h-61.44c-12.288 0-20.48-8.192-20.48-20.48 0-10.24 10.24-20.48 20.48-20.48zM763.904 573.44h61.44c12.288 0 20.48 8.192 20.48 20.48s-8.192 20.48-20.48 20.48h-61.44c-12.288 0-20.48-8.192-20.48-20.48 0-10.24 10.24-20.48 20.48-20.48z" fill="#DBECFF" p-id="5531"></path><path d="M573.44 751.616l-215.04 122.88c-28.672 18.432-63.488 26.624-96.256 26.624H157.696l-81.92-137.216L153.6 716.8c12.288-8.192 24.576-14.336 38.912-18.432s26.624-8.192 40.96-10.24h4.096l102.4-14.336c20.48-2.048 43.008 8.192 51.2 26.624L272.384 737.28l-30.72 10.24c-10.24 4.096-16.384 16.384-12.288 26.624 4.096 8.192 10.24 14.336 20.48 14.336 2.048 0 4.096 0 6.144-2.048l145.408-49.152 20.48-6.144c2.048 0 4.096-2.048 6.144-2.048l63.488-14.336c26.624-6.144 55.296 4.096 73.728 24.576 0 2.048 8.192 8.192 8.192 12.288z" fill="#FFD2BF" p-id="5532"></path></svg>

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -15,6 +15,33 @@ class Api {
);
}
///
static Future<Response<PaginationData>> deleteSaleQuotationTemplate(int id) {
return DioService.dio
.delete<PaginationData>('${Urls.saleQuotationTemplate}/$id');
}
///
static Future<Response<PaginationData>> getSaleQuotationTemplate(
{Map? params}) {
return DioService.dio.get<PaginationData>(Urls.saleQuotationTemplate,
queryParameters: {'page': 1, 'pageSize': 99, ...(params ?? {})});
}
///
static Future<Response<PaginationData>> getSaleQuotationComponents(
{Map? params}) {
return DioService.dio.get<PaginationData>(Urls.saleQuotationComponent,
queryParameters: {'page': 1, 'pageSize': 99, ...(params ?? {})});
}
///
static Future<Response<PaginationData>> getSaleQuotationGroups(
{Map? params}) {
return DioService.dio.get<PaginationData>(Urls.saleQuotationGroup,
queryParameters: {'page': 1, 'pageSize': 99, ...(params ?? {})});
}
///
static Future<Response> getResources() {
return DioService.dio.get(Urls.accountResources);
@ -61,48 +88,59 @@ class Api {
queryParameters: {'page': 1, 'pageSize': 10, ...params});
}
//
///
static Future<Response<PaginationData>> getProducts(Map params) {
return DioService.dio.get<PaginationData>(Urls.products,
queryParameters: {'page': 1, 'pageSize': 10, ...params});
}
//
///
static Future<Response<PaginationData>> getInventoryInout(Map params) {
return DioService.dio.get<PaginationData>(Urls.inventoryInout,
queryParameters: {'page': 1, 'pageSize': 10, ...(params)});
}
//
///
static Future<Response> getInventoryInOutInfo(int id) {
return DioService.dio.get(
'${Urls.inventoryInout}/$id',
);
}
//
///
static Future<Response> createInventoryInout(Map params) {
return DioService.dio.post(Urls.inventoryInout, data: params);
}
//
///
static Future<Response> createSaleQuotationTemplate(Map params) {
return DioService.dio.post(Urls.saleQuotationTemplate, data: params);
}
///
static Future<Response> updateSaleQuotationTemplate(int id, Map params) {
return DioService.dio
.put('${Urls.saleQuotationTemplate}/$id', data: params);
}
///
static Future<Response> updateInventoryInout(int id, Map params) {
return DioService.dio.put('${Urls.inventoryInout}/$id', data: params);
}
//
///
static Future<Response<PaginationData>> getInventory(Map params) {
return DioService.dio.get<PaginationData>(Urls.inventory,
queryParameters: {'page': 1, 'pageSize': 10, ...(params)});
}
//
///
static Future<Response<PaginationData>> getUsers(Map params) {
return DioService.dio.get<PaginationData>(Urls.sysUser,
queryParameters: {'page': 1, 'pageSize': 10, ...(params)});
}
// ()
/// ()
static Future<Response> getDictTypeAll(Map params) {
return DioService.dio.post(Urls.getDictType, data: params);
}

View File

@ -4,6 +4,7 @@ import 'package:sk_base_mobile/util/screen_adaper_util.dart';
class AppTheme {
AppTheme._();
static const Color primaryColor = Color(0xFFC89607);
static const Color onPrimaryColor = Color(0xFFFEFEFE);
static const Color primaryColorLight = Color.fromARGB(255, 255, 206, 70);
static const Color primaryColorDark = Color.fromARGB(255, 163, 120, 0);
static const Color primaryTextColorWithBg = Color(0x00000000);
@ -27,7 +28,7 @@ class AppTheme {
static const Color dividerColor = Color.fromARGB(255, 224, 224, 224);
static const Color appbarBgColor = AppTheme.primaryColor;
static const Color scaffoldBackgroundColor = Color(0XFFe9f0fd);
static const Color inputFillColor = Color(0xfffefefe);
static const Color inputFillColor = Color(0xFFf5f8ff);
}
final theme = ThemeData(
@ -63,7 +64,7 @@ final theme = ThemeData(
),
dialogBackgroundColor: AppTheme.nearlyWhite,
colorScheme: ColorScheme.fromSeed(
onPrimary: AppTheme.nearlyWhite,
onPrimary: AppTheme.onPrimaryColor,
seedColor: AppTheme.primaryColor,
primary: AppTheme.primaryColor),
datePickerTheme: DatePickerThemeData(

View File

@ -2,11 +2,11 @@
// Global config
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://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://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 = "山矿通";

View File

@ -17,4 +17,9 @@ class Urls {
static String accountResources = 'account/menus';
static String depts = 'system/depts';
static String roles = 'system/roles';
static String saleQuotationGroup = 'sale_quotation/sale_quotation_group';
static String saleQuotationComponent =
'sale_quotation/sale_quotation_component';
static String saleQuotationTemplate =
'sale_quotation/sale_quotation_template';
}

View File

@ -1,19 +1,38 @@
import 'package:flutter/material.dart';
import 'package:get/get_rx/src/rx_types/rx_types.dart';
import 'package:get/get_state_manager/src/simple/get_controllers.dart';
import 'package:pull_to_refresh/pull_to_refresh.dart';
import 'package:sk_base_mobile/models/base_search_more.model.dart';
mixin BaseSearchMoreController<T extends BaseSearchMoreModel> {
class BaseSearchMoreController<T extends BaseSearchMoreModel>
extends GetxController {
RxList<T> list = RxList([]);
RxString searchKey = ''.obs;
final searchBarTextConroller = TextEditingController();
RefreshController refreshController = RefreshController(initialRefresh: true);
RefreshController refreshController =
RefreshController(initialRefresh: false);
int page = 1;
int limit = 15;
int total = 0;
RxList<int> selectedIndex = RxList([]);
final loading = false.obs;
getData({bool isRefresh = false}) {}
@override
onReady() {
super.onReady();
initData();
}
initData() async {
loading.value = true;
try {
await getData(isRefresh: true);
} finally {
loading.value = false;
}
}
Future<void> onRefresh() async {
await getData(isRefresh: true).then((_) {
refreshController.refreshCompleted(resetFooterState: true);

View File

@ -0,0 +1,75 @@
class SaleQuotationComponentModel {
SaleQuotationComponentModel({
required this.id,
required this.createdAt,
required this.updatedAt,
required this.name,
required this.componentSpecification,
required this.unitId,
required this.unitPrice,
required this.remark,
required this.isDelete,
required this.unit,
});
final int? id;
final DateTime? createdAt;
final DateTime? updatedAt;
final String? name;
final String? componentSpecification;
final int? unitId;
final double? unitPrice;
final String? remark;
final int? isDelete;
final Unit? unit;
factory SaleQuotationComponentModel.fromJson(Map<String, dynamic> json) {
return SaleQuotationComponentModel(
id: json["id"],
createdAt: DateTime.tryParse(json["createdAt"] ?? ""),
updatedAt: DateTime.tryParse(json["updatedAt"] ?? ""),
name: json["name"],
componentSpecification: json["componentSpecification"],
unitId: json["unitId"],
unitPrice: double.parse(json["unitPrice"]),
remark: json["remark"],
isDelete: json["isDelete"],
unit: json["unit"] == null ? null : Unit.fromJson(json["unit"]),
);
}
Map<String, dynamic> toJson() => {
"id": id,
"createdAt": createdAt?.toIso8601String(),
"updatedAt": updatedAt?.toIso8601String(),
"name": name,
"componentSpecification": componentSpecification,
"unitId": unitId,
"unitPrice": unitPrice,
"remark": remark,
"isDelete": isDelete,
"unit": unit?.toJson(),
};
}
class Unit {
Unit({
required this.id,
required this.label,
});
final int? id;
final String? label;
factory Unit.fromJson(Map<String, dynamic> json) {
return Unit(
id: json["id"],
label: json["label"],
);
}
Map<String, dynamic> toJson() => {
"id": id,
"label": label,
};
}

View File

@ -0,0 +1,33 @@
class SaleQuotationGroupModel {
SaleQuotationGroupModel({
required this.id,
required this.createdAt,
required this.updatedAt,
required this.name,
required this.isDelete,
});
final int? id;
final DateTime? createdAt;
final DateTime? updatedAt;
final String? name;
final int? isDelete;
factory SaleQuotationGroupModel.fromJson(Map<String, dynamic> json) {
return SaleQuotationGroupModel(
id: json["id"],
createdAt: DateTime.tryParse(json["createdAt"] ?? ""),
updatedAt: DateTime.tryParse(json["updatedAt"] ?? ""),
name: json["name"],
isDelete: json["isDelete"],
);
}
Map<String, dynamic> toJson() => {
"id": id,
"createdAt": createdAt?.toIso8601String(),
"updatedAt": updatedAt?.toIso8601String(),
"name": name,
"isDelete": isDelete,
};
}

View File

@ -26,9 +26,9 @@ class SaleQuotationItemModel extends BaseSearchMoreModel {
final String? unit;
final String? remark;
//
int cost;
num cost;
int quantity;
int amount;
num amount;
SaleQuotationItemModel(
{required this.name,

View File

@ -0,0 +1,64 @@
import 'package:sk_base_mobile/models/sale_quotation.model.dart';
class SaleQuotationTemplateModel {
SaleQuotationTemplateModel({
this.id,
this.name,
required this.template,
this.isDelete,
});
final int? id;
final String? name;
final Template template;
final int? isDelete;
factory SaleQuotationTemplateModel.fromJson(Map<String, dynamic> json) {
return SaleQuotationTemplateModel(
id: json["id"],
name: json["name"],
template: Template.fromJson(json["template"]),
isDelete: json["isDelete"],
);
}
Map<String, dynamic> toJson() => {
"id": id,
"name": name,
"template": template.toJson(),
"isDelete": isDelete,
};
}
class Template {
Template({
required this.data,
required this.formula,
required this.totalCost,
required this.totalPrice,
});
List<SaleQuotationModel> data;
String formula;
num? totalCost;
num? totalPrice;
factory Template.fromJson(Map<String, dynamic> json) {
return Template(
data: json["data"] == null
? []
: List<SaleQuotationModel>.from(
json["data"]!.map((x) => SaleQuotationModel.fromJson(x))),
formula: json["formula"],
totalCost: json["totalCost"],
totalPrice: json["totalPrice"],
);
}
Map<String, dynamic> toJson() => {
"data": data.map((x) => x.toJson()).toList(),
"formula": formula,
"totalCost": totalCost,
"totalPrice": totalPrice,
};
}

View File

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

View File

@ -1,9 +1,15 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sk_base_mobile/constants/constants.dart';
import 'package:sk_base_mobile/store/resource.store.dart';
import 'package:sk_base_mobile/util/snack_bar.util.dart';
class RouterUtil {
static List<String> hasPermissionRoute = [
RouteConfig.hrManage,
RouteConfig.inventory,
RouteConfig.saleQuotation,
];
static Future<T?> toNamed<T>(String routeName, {arguments}) async {
//
if (Get.context != null) {
@ -12,12 +18,21 @@ class RouterUtil {
bool isExsited = ResourceService.to.resources
.firstWhereOrNull((element) => element.path == routeName) !=
null;
if (!isExsited) {
bool hasPermission = isExsited || !hasPermissionRoute.contains(routeName);
if (!hasPermission) {
SnackBarUtil().info('您没有权限,请联系管理员分配权限,后期将隐藏无权限的菜单');
return null;
}
return await Get.toNamed<T?>(routeName, arguments: arguments);
return Get.toNamed<T?>(routeName, arguments: arguments);
}
///
static Future<void> back<T>({T? result}) async {
//
if (Get.context != null) {
FocusScope.of(Get.context!).requestFocus(FocusNode());
}
Get.key.currentState?.pop(result);
}
}

View File

@ -9,6 +9,7 @@ import 'package:sk_base_mobile/constants/bg_color.dart';
import 'package:sk_base_mobile/constants/enum.dart';
import 'package:sk_base_mobile/models/role.model.dart';
import 'package:sk_base_mobile/models/user_info.model.dart';
import 'package:sk_base_mobile/router/router.util.dart';
import 'package:sk_base_mobile/screens/hr_manage/components/dept_picker.dart';
import 'package:sk_base_mobile/util/loading_util.dart';
import 'package:sk_base_mobile/util/logger_util.dart';
@ -323,12 +324,13 @@ class EditUserInfoController extends GetxController {
}
}
await Api.updateUserInfo(userInfo.value!.id!, data);
final resUser = await Api.getUserInfo(userInfo.value!.id!);
await Api.updateUserInfo(userInfo.value.id!, data);
await LoadingUtil.to.dismiss();
final resUser = await Api.getUserInfo(userInfo.value.id!);
if (resUser.data != null) {
userInfo.value = UserInfoModel.fromJson(resUser.data);
}
Get.back(result: userInfo.value);
await RouterUtil.back<UserInfoModel>(result: userInfo.value);
SnackBarUtil().success(
'保存成功',
);

View File

@ -150,6 +150,7 @@ class HrManagePage extends StatelessWidget {
Widget buildUserCard(int index) {
return SkInk(
color: AppTheme.nearlyWhite,
onTap: () {
RouterUtil.toNamed(RouteConfig.employeeDetail,
arguments: controller.list[index]);

View File

@ -5,6 +5,7 @@ import 'package:sk_base_mobile/apis/api.dart';
import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/constants/enum.dart';
import 'package:sk_base_mobile/db_helper/db_help.dart';
import 'package:sk_base_mobile/router/router.util.dart';
import 'package:sk_base_mobile/screens/inventory_inout/components/inventory_inout_info.dart';
import 'package:sk_base_mobile/screens/new_inventory_inout/new_inventory_inout.dart';
import 'package:sk_base_mobile/util/date.util.dart';
@ -76,7 +77,7 @@ class InventoryInoutController extends GetxController {
),
child: ElevatedButton(
onPressed: () {
Get.back();
RouterUtil.back();
showInventoryInoutCreateDialog(
InventoryInOrOutEnum.In);
},
@ -145,7 +146,7 @@ class InventoryInoutController extends GetxController {
),
child: ElevatedButton(
onPressed: () {
Get.back();
RouterUtil.back();
showInventoryInoutCreateDialog(
InventoryInOrOutEnum.Out);
},

View File

@ -8,6 +8,7 @@ import 'package:sk_base_mobile/constants/dict_enum.dart';
import 'package:sk_base_mobile/models/index.dart';
import 'package:sk_base_mobile/models/inventory.model.dart';
import 'package:sk_base_mobile/models/user_info.model.dart';
import 'package:sk_base_mobile/router/router.util.dart';
import 'package:sk_base_mobile/screens/new_inventory_inout/components/agent_search.dart';
import 'package:sk_base_mobile/screens/new_inventory_inout/components/inventory_search.dart';
import 'package:sk_base_mobile/screens/new_inventory_inout/components/product_search.dart';
@ -242,7 +243,7 @@ class NewInventoryInout extends StatelessWidget {
if (inOrOut == InventoryInOrOutEnum.In) {
ModalUtil.showGeneralDialog(content: ProductSearch(
onProductSelected: (ProductModel product) {
Get.back();
RouterUtil.back();
String productName =
'${product.productNumber} ${product.name!}';
controller.payload['productId'] = product.id;
@ -263,7 +264,7 @@ class NewInventoryInout extends StatelessWidget {
return (itemData.quantity ?? 0) > 0;
},
onInventorySelected: (InventoryModel inventory) {
Get.back();
RouterUtil.back();
controller.payload['inventoryId'] = inventory.id;
String productName =
'${inventory.product!.name!}(¥${double.parse('${inventory.unitPrice}')})(${inventory.product?.company?.name})';
@ -364,7 +365,7 @@ class NewInventoryInout extends StatelessWidget {
ModalUtil.showGeneralDialog(
content: AgentSearch(
onSelected: (UserInfoModel userInfo) {
Get.back();
RouterUtil.back();
controller.payload['agent'] = userInfo.nickname;
controller.agentTextController.text = userInfo.nickname!;
},

View File

@ -8,6 +8,7 @@ import 'package:sk_base_mobile/apis/api.dart';
import 'package:sk_base_mobile/constants/enum.dart';
import 'package:sk_base_mobile/db_helper/db_help.dart';
import 'package:sk_base_mobile/models/index.dart';
import 'package:sk_base_mobile/router/router.util.dart';
import 'package:sk_base_mobile/screens/inventory_inout/inventory_inout_controller.dart';
import 'package:sk_base_mobile/util/date.util.dart';
import 'package:sk_base_mobile/util/loading_util.dart';
@ -190,7 +191,7 @@ class NewInventoryInoutController extends GetxController {
await Api.updateInventoryInout(
response.data, {'fileIds': uploadRes.map((e) => e!.id).toList()});
}
Get.back(result: true);
await RouterUtil.back<bool>(result: true);
SnackBarUtil().success(
'提交成功',
);
@ -238,7 +239,7 @@ class NewInventoryInoutController extends GetxController {
// }
// }
// // uploadImgFilePath(pickedFile.path);
// Get.back();
// await RouterUtil.back();
// } catch (e) {
// SnackBarUtil().error('上传失败,请重试');
// } finally {
@ -354,7 +355,7 @@ class NewInventoryInoutController extends GetxController {
// inventoryInoutlist[dif.inDays].add(value);
// Timer(const Duration(seconds: 1), () {
// loading.value = false;
// Get.back();
// await RouterUtil.back();
// SnackBarUtil().success(
// 'Successful',
// message: 'Task is created',

View File

@ -1,16 +1,27 @@
import 'package:collection/collection.dart';
import 'package:flutter/material.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/models/sale_quotation_template.model.dart';
import 'package:sk_base_mobile/router/router.util.dart';
import 'package:sk_base_mobile/screens/sale_quotation/sale_quotation.controller.dart';
import 'package:sk_base_mobile/util/modal.util.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
import 'package:sk_base_mobile/widgets/gradient_button.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/core/sk_ink.dart';
import 'package:sk_base_mobile/widgets/form_item/sk_text_input.dart';
import 'package:sk_base_mobile/widgets/core/sk_appbar.dart';
class SaleQuotationEndDrawer extends StatelessWidget {
const SaleQuotationEndDrawer({super.key});
final controller = Get.find<SaleQuotationController>();
SaleQuotationEndDrawer({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: SkAppbar(title: '工具栏', hideLeading: true),
appBar: const SkAppbar(title: '', hideLeading: true),
body: buildBody(),
);
}
@ -18,24 +29,280 @@ class SaleQuotationEndDrawer extends StatelessWidget {
Widget buildBody() {
return Column(
children: [
ListTile(
title: Text('选择报价模板'),
onTap: () {},
buildToolbar(),
Expanded(
child: buildTemplatePicker(),
),
Divider(
color: AppTheme.dividerColor,
height: 1,
),
Spacer(),
GradientButton(
buildAction()
],
);
}
Widget buildAction() {
return Row(
children: [
Expanded(
child: SkFlatButton(
color: AppTheme.dangerColor,
onPressed: () {
controller.clearWorkbench();
RouterUtil.back();
},
icon: Icon(
Icons.delete,
color: AppTheme.nearlyWhite,
size: ScreenAdaper.height(40),
),
buttonText: '清空工作区',
)),
Expanded(
child: SkFlatButton(
onPressed: () async {
if (controller.templateName.value != '默认') {
await RouterUtil.back();
await controller.saveToDatabase();
} else {
templateNameDialog();
}
// final isSuccessed = await controller.saveToDatabase();
// if (isSuccessed) await RouterUtil.back();
},
icon: Icon(
Icons.save,
color: AppTheme.nearlyWhite,
size: ScreenAdaper.height(40),
),
buttonText: '保存为模板',
)
buttonText: '保存模板',
))
],
);
}
Widget buildToolbar() {
return Container(
decoration: BoxDecoration(
color: AppTheme.nearlyWhite,
borderRadius: BorderRadius.circular(ScreenAdaper.sp(20))),
margin: EdgeInsets.symmetric(
horizontal: ScreenAdaper.height(20),
vertical: ScreenAdaper.height(20)),
child: Column(
children: [
buildGroupHeader('工具栏'),
Row(
children: controller.menus
.mapIndexed(
(index, item) => Expanded(child: buildItem(index)))
.toList())
],
),
);
}
Widget buildGroupHeader(String text) {
return Container(
padding: EdgeInsets.only(
right: ScreenAdaper.width(20),
left: ScreenAdaper.width(20),
top: ScreenAdaper.height(20)),
alignment: Alignment.centerLeft,
child: Text(
text,
style: TextStyle(
fontSize: ScreenAdaper.height(25), color: Colors.grey[600]),
),
);
}
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('您没有权限,请联系管理员。后期会隐藏没有权限的功能');
// }
},
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(75),
height: ScreenAdaper.width(75),
),
SizedBox(
height: ScreenAdaper.height(10),
),
Text(
controller.menus[index].title,
style: TextStyle(
fontSize: ScreenAdaper.height(22),
),
)
],
),
),
);
}
Widget buildTemplatePicker() {
return Obx(() => Container(
decoration: BoxDecoration(
color: AppTheme.nearlyWhite,
borderRadius: BorderRadius.circular(ScreenAdaper.sp(20))),
margin: EdgeInsets.only(
left: ScreenAdaper.height(20),
right: ScreenAdaper.height(20),
bottom: ScreenAdaper.height(20)),
child: Column(
children: [
buildGroupHeader('请选择模板'),
SizedBox(
height: ScreenAdaper.height(10),
),
Expanded(
child: ListView(
padding:
EdgeInsets.symmetric(horizontal: ScreenAdaper.height(20)),
children: controller.templates
.mapIndexed((int index, element) =>
buildTemplateItem(element, index))
.toList(),
))
],
),
));
}
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),
),
),
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 = ''}) {
final textController = TextEditingController(text: title);
Get.dialog(AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(ScreenAdaper.sp(20)),
),
contentPadding: EdgeInsets.only(
top: ScreenAdaper.height(10),
right: ScreenAdaper.height(20),
left: ScreenAdaper.height(20)),
content: Column(mainAxisSize: MainAxisSize.min, children: [
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
SkInk(
onTap: () {
RouterUtil.back();
},
child: const Icon(Icons.close))
],
),
SkTextInput(
height: ScreenAdaper.height(100),
textController: textController,
customLabel: true,
labelText: '模板名称',
),
Row(
children: [
Expanded(
child: TextButton(
onPressed: () {
RouterUtil.back();
},
child: const Text(
'取消',
style: TextStyle(color: AppTheme.nearlyBlack),
),
),
),
Expanded(
child: TextButton(
onPressed: () async {
if (textController.text.isEmpty) {
SnackBarUtil().error('模板名不能为空');
return;
}
controller.templateName.value = textController.text;
await RouterUtil.back();
await controller.saveToLocal();
await controller.saveToDatabase();
await controller.getTemplates();
},
child: const Text('确定'),
))
],
)
]),
));
}
}
// class SaleQuotationDrawerController extends GetxController{
// }

View File

@ -111,34 +111,10 @@ class SaleQuotationGroupSearch extends StatelessWidget {
vertical: ScreenAdaper.height(10)),
child: Row(
children: [
// ClipRRect(
// borderRadius: BorderRadius.circular(ScreenAdaper.sp(15)),
// child: FadeInCacheImage(
// url: controller.list[index].avatar ?? '',
// width: ScreenAdaper.width(60),
// height: ScreenAdaper.width(60),
// ),
// ),
// SizedBox(
// width: ScreenAdaper.width(20),
// ),
Text(
'${controller.list[index].name}',
controller.list[index].name,
style: TextStyle(fontSize: ScreenAdaper.height(25)),
),
// if (controller.list[index].dept != null) ...[
// SizedBox(
// width: ScreenAdaper.width(20),
// ),
// Container(
// padding: EdgeInsets.symmetric(
// horizontal: ScreenAdaper.width(10),
// vertical: ScreenAdaper.height(5)),
// decoration: BoxDecoration(
// color: Colors.grey[300],
// borderRadius: BorderRadius.circular(ScreenAdaper.sp(10))),
// child: Text('${controller.list[index].dept?.name}'),
// )
],
),
),

View File

@ -4,10 +4,16 @@ import 'package:decimal/decimal.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:math_expressions/math_expressions.dart';
import 'package:sk_base_mobile/apis/api.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';
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/storage.service.dart';
import 'package:sk_base_mobile/util/snack_bar.util.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';
@ -17,39 +23,21 @@ class SaleQuotationController extends GetxController {
static SaleQuotationController get to => Get.find();
final RxList editingcell = RxList([null, null, null]);
final GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
RxList<SaleQuotationItemModel> products = RxList([
SaleQuotationItemModel(
name: '矿用本安型支架控制器', unit: '', spec: 'ZDYZ-Z', cost: 4700),
SaleQuotationItemModel(name: '矿用本安型电磁阀驱动器', cost: 1200),
SaleQuotationItemModel(name: '矿用隔爆兼本安型电源', cost: 5700),
SaleQuotationItemModel(name: '矿用本安型隔离耦合器', cost: 1200),
SaleQuotationItemModel(name: '钢丝编织橡胶护套连接器', cost: 600),
SaleQuotationItemModel(name: '钢丝编织橡胶护套连接器', remark: '控制器-控制器', cost: 400),
SaleQuotationItemModel(name: '钢丝编织橡胶护套连接器', remark: '控制器-驱动器', cost: 500),
SaleQuotationItemModel(
name: '钢丝编织橡胶护套连接器', remark: '控制器-隔离耦合器', cost: 2000),
SaleQuotationItemModel(name: '矿用本安型支架控制器', cost: 4700),
SaleQuotationItemModel(name: '矿用本安型电磁阀驱动器', cost: 1200),
SaleQuotationItemModel(name: '矿用隔爆兼本安型电源', cost: 5700),
SaleQuotationItemModel(
name: '电液换向阀(10功能10接口)', remark: '中间过渡架主阀组', cost: 13200),
SaleQuotationItemModel(
name: '电液换向阀(20功能20接口)', remark: '端头架主阀组', cost: 26500),
SaleQuotationItemModel(
name: '自动反冲洗过滤装置', remark: '流量:900L/min,过滤精度25μm', cost: 2000),
SaleQuotationItemModel(name: '全自动反冲洗过滤器电缆', remark: '控制器-自动反冲洗'),
SaleQuotationItemModel(name: '矿用本安型位移传感器'),
SaleQuotationItemModel(name: '矿用本安型压力传感器'),
SaleQuotationItemModel(name: '矿用本安型红外发射器'),
SaleQuotationItemModel(name: '矿用本安型LED信号灯'),
SaleQuotationItemModel(name: '倾角传感器'),
SaleQuotationItemModel(name: '各类安装附件'),
]);
RxList<SaleQuotationModel> groups = RxList<SaleQuotationModel>([]);
RxInt totalCost = RxInt(0);
RxDouble totalCost = RxDouble(0.0);
RxDouble totalPrice = RxDouble(0.0);
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'),
];
RxString templateName = '默认'.obs;
int? templateId;
@override
void onReady() {
init();
@ -59,18 +47,43 @@ class SaleQuotationController extends GetxController {
Future<void> init() async {
String? salesQuotation = StorageService.to.getString('salesQuotation');
if (salesQuotation != null) {
groups.assignAll((jsonDecode(salesQuotation) as List)
.map((e) => SaleQuotationModel.fromJson(e))
.toList());
calculateTotal();
SaleQuotationTemplateModel editTemplate =
SaleQuotationTemplateModel.fromJson(jsonDecode(salesQuotation));
parseTemplateModel(editTemplate);
}
await getTemplates();
}
void parseTemplateModel(SaleQuotationTemplateModel editTemplate) {
groups.assignAll(editTemplate.template.data);
templateName.value = editTemplate.name ?? '';
formula.value = editTemplate.template.formula;
totalPrice.value = editTemplate.template.totalPrice?.toDouble() ?? 0.0;
totalCost.value = editTemplate.template.totalCost?.toDouble() ?? 0.0;
templateId = editTemplate.id;
}
///
void changeTemplate(SaleQuotationTemplateModel templateModel) {
parseTemplateModel(templateModel);
saveToLocal();
}
///
Future<void> getTemplates() async {
final res = await Api.getSaleQuotationTemplate();
List<SaleQuotationTemplateModel> newList = res.data!.items
.map((e) => SaleQuotationTemplateModel.fromJson(e))
.toList();
templates.assignAll(newList);
}
///
void calculateTotal({String? newFormula}) {
//groups中所有items中的amout总和
totalCost.value = groups.fold<int>(0, (previousValue, element) {
totalCost.value = groups.fold<double>(0, (previousValue, element) {
return previousValue +
element.items.fold<int>(0, (previousValue, element) {
element.items.fold<double>(0, (previousValue, element) {
return previousValue + element.amount;
});
});
@ -100,39 +113,87 @@ class SaleQuotationController extends GetxController {
//
if (changedField != 'amount') {
Decimal result = cost * quantity;
data.amount = result != Decimal.zero ? result.toBigInt().toInt() : 0;
data.amount = result != Decimal.zero ? result.toDouble() : 0;
}
if (changedField == 'amount' && quantity != Decimal.zero) {
Decimal result =
(amount / quantity).toDecimal(scaleOnInfinitePrecision: 10);
data.cost = result != Decimal.zero ? result.toBigInt().toInt() : 0;
data.cost = result != Decimal.zero ? result.toDouble() : 0.0;
} else if (changedField != 'amount') {
Decimal result = (cost * quantity);
data.amount = result != Decimal.zero ? result.toBigInt().toInt() : 0;
data.amount = result != Decimal.zero ? result.toDouble() : 0;
}
return data;
}
void saveChanges(int groupIndex, int rowIndex, SaleQuotationItemModel data,
String changedField) {
///
void afterRowChanges(int groupIndex, int rowIndex,
SaleQuotationItemModel data, String changedField) {
data = calculateRow(data, changedField);
groups[groupIndex].items[rowIndex] = data;
calculateTotal();
stopEditing();
}
///
void stopEditing() {
editingcell.assignAll([null, null, null]);
save();
saveToLocal();
}
Future<void> save() async {
await StorageService.to
.setString('salesQuotation', jsonEncode(groups.toJson()));
// SnackBarUtil().success('已保存到本地');
///
Future<void> saveToLocal() async {
Map<String, dynamic> data = {
'name': templateName.value,
'template': {
'data': groups.map((e) => e.toJson()).toList(),
'totalCost': totalCost.value,
'totalPrice': totalPrice.value,
'formula': formula.value
}
};
if (templateId != null) {
data['id'] = templateId;
}
await StorageService.to.setString('salesQuotation', jsonEncode(data));
}
///
Future<bool> saveToDatabase() async {
if (templateId == null) {
await Api.createSaleQuotationTemplate({
'name': templateName.value,
'template': {
'data': groups.toJson(),
'totalCost': totalCost.value,
'totalPrice': totalPrice.value,
'formula': formula.value
}
});
} else {
await Api.updateSaleQuotationTemplate(templateId!, {
'name': templateName.value,
'template': {
'data': groups.toJson(),
'totalCost': totalCost.value,
'totalPrice': totalPrice.value,
'formula': formula.value
}
});
}
await getTemplates();
await SnackBarUtil().success('已保存');
return true;
}
///
Future<void> deleteTemplate(int templateId) async {
await Api.deleteSaleQuotationTemplate(templateId);
SnackBarUtil().success('已删除');
}
///
void addGroup() async {
final controller = Get.put(GroupSearchMoreController());
//
@ -145,7 +206,7 @@ class SaleQuotationController extends GetxController {
groups.addAll(controller.list.where((element) {
return indexes.contains(controller.list.indexOf(element));
}));
save();
saveToLocal();
},
leadingBuilder: (index) {
return Container(
@ -168,15 +229,16 @@ class SaleQuotationController extends GetxController {
calculateTotal();
}
///
void removeGroup(int index) {
groups.removeAt(index);
calculateTotal();
save();
saveToLocal();
}
void addItems(int groupIndex) async {
final controller = Get.put(ItemSearchMoreController());
//
//
ModalUtil.showGeneralDialog(
content: SkMutilSearchMore<SaleQuotationItemModel>(
controller: controller,
@ -184,10 +246,12 @@ class SaleQuotationController extends GetxController {
enablePullDown: true,
isDialog: true,
onOk: (List<int> indexes) {
groups[groupIndex].items.addAll(products.where((element) {
return indexes.contains(products.indexOf(element));
groups[groupIndex]
.items
.addAll(controller.list.where((element) {
return indexes.contains(controller.list.indexOf(element));
}));
save();
saveToLocal();
},
leadingBuilder: (index) {
return Container(
@ -250,22 +314,41 @@ class SaleQuotationController extends GetxController {
groups[groupIndex].items.removeAt(rowIndex);
calculateTotal();
}
void clearWorkbench() {
groups.value = [];
totalCost.value = 0.0;
totalPrice.value = 0.0;
formula.value = '成本 * 1.3 / 0.864';
templateName.value = '默认';
templateId = null;
saveToLocal();
}
}
class GroupSearchMoreController extends GetxController
with BaseSearchMoreController<SaleQuotationModel> {
class GroupSearchMoreController
extends BaseSearchMoreController<SaleQuotationModel> {
@override
Future<List<SaleQuotationModel>> getData({bool isRefresh = false}) async {
List<SaleQuotationModel> newList = [
SaleQuotationModel(name: '中间过渡架电控部分', items: RxList([])),
SaleQuotationModel(name: '端头架电控部分', items: RxList([])),
SaleQuotationModel(name: '主阀部分', items: RxList([])),
SaleQuotationModel(name: '自动反冲洗过滤器部分', items: RxList([])),
SaleQuotationModel(name: '位移测量部分', items: RxList([])),
SaleQuotationModel(name: '压力检测部分', items: RxList([])),
SaleQuotationModel(name: '煤机定位部分', items: RxList([])),
SaleQuotationModel(name: '姿态检测部分', items: RxList([])),
];
await Future.delayed(const Duration(seconds: 1));
final res = await Api.getSaleQuotationGroups();
List<SaleQuotationGroupModel> groups = res.data!.items
.map((e) => SaleQuotationGroupModel.fromJson(e))
.toList();
List<SaleQuotationModel> newList = groups
.map((e) => SaleQuotationModel(name: e.name!, items: RxList([])))
.toList();
// List<SaleQuotationModel> newList = [
// SaleQuotationModel(name: '中间过渡架电控部分', items: RxList([])),
// SaleQuotationModel(name: '端头架电控部分', items: RxList([])),
// SaleQuotationModel(name: '主阀部分', items: RxList([])),
// SaleQuotationModel(name: '自动反冲洗过滤器部分', items: RxList([])),
// SaleQuotationModel(name: '位移测量部分', items: RxList([])),
// SaleQuotationModel(name: '压力检测部分', items: RxList([])),
// SaleQuotationModel(name: '煤机定位部分', items: RxList([])),
// SaleQuotationModel(name: '姿态检测部分', items: RxList([])),
// ];
list.assignAll(newList
.where((element) => PinyinHelper.getPinyin(element.name, separator: '')
.contains(searchKey.value))
@ -274,42 +357,58 @@ class GroupSearchMoreController extends GetxController
}
}
class ItemSearchMoreController extends GetxController
with BaseSearchMoreController<SaleQuotationItemModel> {
class ItemSearchMoreController
extends BaseSearchMoreController<SaleQuotationItemModel> {
@override
Future<List<SaleQuotationItemModel>> getData({bool isRefresh = false}) async {
List<SaleQuotationItemModel> newList = [
SaleQuotationItemModel(
name: '矿用本安型支架控制器', unit: '', spec: 'ZDYZ-Z', cost: 4700),
SaleQuotationItemModel(name: '矿用本安型电磁阀驱动器', cost: 1200),
SaleQuotationItemModel(name: '矿用隔爆兼本安型电源', cost: 5700),
SaleQuotationItemModel(name: '矿用本安型隔离耦合器', cost: 1200),
SaleQuotationItemModel(name: '钢丝编织橡胶护套连接器', cost: 600),
SaleQuotationItemModel(name: '钢丝编织橡胶护套连接器', remark: '控制器-控制器', cost: 400),
SaleQuotationItemModel(name: '钢丝编织橡胶护套连接器', remark: '控制器-驱动器', cost: 500),
SaleQuotationItemModel(
name: '钢丝编织橡胶护套连接器', remark: '控制器-隔离耦合器', cost: 2000),
SaleQuotationItemModel(name: '矿用本安型支架控制器', cost: 4700),
SaleQuotationItemModel(name: '矿用本安型电磁阀驱动器', cost: 1200),
SaleQuotationItemModel(name: '矿用隔爆兼本安型电源', cost: 5700),
SaleQuotationItemModel(
name: '电液换向阀(10功能10接口)', remark: '中间过渡架主阀组', cost: 13200),
SaleQuotationItemModel(
name: '电液换向阀(20功能20接口)', remark: '端头架主阀组', cost: 26500),
SaleQuotationItemModel(
name: '自动反冲洗过滤装置', remark: '流量:900L/min,过滤精度25μm', cost: 2000),
SaleQuotationItemModel(name: '全自动反冲洗过滤器电缆', remark: '控制器-自动反冲洗'),
SaleQuotationItemModel(name: '矿用本安型位移传感器'),
SaleQuotationItemModel(name: '矿用本安型压力传感器'),
SaleQuotationItemModel(name: '矿用本安型红外发射器'),
SaleQuotationItemModel(name: '矿用本安型LED信号灯'),
SaleQuotationItemModel(name: '倾角传感器'),
SaleQuotationItemModel(name: '各类安装附件'),
];
list.assignAll(newList
.where((element) => PinyinHelper.getPinyin(element.name, separator: '')
.contains(searchKey.value))
.toList());
return newList;
try {
final res = await Api.getSaleQuotationComponents();
List<SaleQuotationComponentModel> componets = res.data!.items
.map((e) => SaleQuotationComponentModel.fromJson(e))
.toList();
List<SaleQuotationItemModel> newList = componets
.map((e) => SaleQuotationItemModel(
name: e.name!,
cost: e.unitPrice ?? 0.0,
unit: e.unit?.label ?? '',
remark: e.remark))
.toList();
// List<SaleQuotationItemModel> newList = [
// SaleQuotationItemModel(
// name: '矿用本安型支架控制器', unit: '', spec: 'ZDYZ-Z', cost: 4700),
// SaleQuotationItemModel(name: '矿用本安型电磁阀驱动器', cost: 1200),
// SaleQuotationItemModel(name: '矿用隔爆兼本安型电源', cost: 5700),
// SaleQuotationItemModel(name: '矿用本安型隔离耦合器', cost: 1200),
// SaleQuotationItemModel(name: '钢丝编织橡胶护套连接器', cost: 600),
// SaleQuotationItemModel(name: '钢丝编织橡胶护套连接器', remark: '控制器-控制器', cost: 400),
// SaleQuotationItemModel(name: '钢丝编织橡胶护套连接器', remark: '控制器-驱动器', cost: 500),
// SaleQuotationItemModel(
// name: '钢丝编织橡胶护套连接器', remark: '控制器-隔离耦合器', cost: 2000),
// SaleQuotationItemModel(name: '矿用本安型支架控制器', cost: 4700),
// SaleQuotationItemModel(name: '矿用本安型电磁阀驱动器', cost: 1200),
// SaleQuotationItemModel(name: '矿用隔爆兼本安型电源', cost: 5700),
// SaleQuotationItemModel(
// name: '电液换向阀(10功能10接口)', remark: '中间过渡架主阀组', cost: 13200),
// SaleQuotationItemModel(
// name: '电液换向阀(20功能20接口)', remark: '端头架主阀组', cost: 26500),
// SaleQuotationItemModel(
// name: '自动反冲洗过滤装置', remark: '流量:900L/min,过滤精度25μm', cost: 2000),
// SaleQuotationItemModel(name: '全自动反冲洗过滤器电缆', remark: '控制器-自动反冲洗'),
// SaleQuotationItemModel(name: '矿用本安型位移传感器'),
// SaleQuotationItemModel(name: '矿用本安型压力传感器'),
// SaleQuotationItemModel(name: '矿用本安型红外发射器'),
// SaleQuotationItemModel(name: '矿用本安型LED信号灯'),
// SaleQuotationItemModel(name: '倾角传感器'),
// SaleQuotationItemModel(name: '各类安装附件'),
// ];
list.assignAll(newList
.where((element) =>
PinyinHelper.getPinyin(element.name, separator: '')
.contains(searchKey.value))
.toList());
return newList;
} catch (e) {
return [];
}
}
}

View File

@ -6,12 +6,11 @@ import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/models/sale_quotation.model.dart';
import 'package:sk_base_mobile/screens/sale_quotation/components/sale_quotation_drawer.dart';
import 'package:sk_base_mobile/screens/sale_quotation/sale_quotation.controller.dart';
import 'package:sk_base_mobile/util/common.util.dart';
import 'package:sk_base_mobile/util/modal.util.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
import 'package:sk_base_mobile/util/snack_bar.util.dart';
import 'package:sk_base_mobile/widgets/form_item/sk_number_input.dart';
import 'package:sk_base_mobile/widgets/form_item/sk_text_input.dart';
import 'package:sk_base_mobile/widgets/empty.dart';
import 'package:sk_base_mobile/widgets/core/sk_appbar.dart';
import 'package:flutter_sticky_header/flutter_sticky_header.dart';
import 'package:math_expressions/math_expressions.dart';
@ -22,93 +21,75 @@ class SaleQuotationPage extends StatelessWidget {
final quantityWidth = 140.0;
final unitPriceWidth = 140.0;
final amountWidth = 140.0;
final headerTitleStyle = TextStyle(
final TextStyle? headerTitleStyle = TextStyle(
fontSize: ScreenAdaper.height(30),
fontWeight: FontWeight.w600,
color: AppTheme.nearlyBlack);
final headerBgcolor = const Color.fromARGB(255, 238, 238, 238);
color: Theme.of(Get.context!).colorScheme.onPrimary);
final headerBgcolor = AppTheme.appbarBgColor;
@override
Widget build(BuildContext context) {
return Scaffold(
key: controller.scaffoldKey,
endDrawer: const Drawer(
//
child: SaleQuotationEndDrawer(),
),
appBar: SkAppbar(
title: '报价计算',
action: [
IconButton(
onPressed: () {
controller.scaffoldKey.currentState?.openEndDrawer();
},
icon: const Icon(Icons.more_horiz_outlined, color: AppTheme.white),
return Obx(() => Scaffold(
key: controller.scaffoldKey,
backgroundColor: AppTheme.nearlyWhite,
endDrawer: Drawer(
//
child: SaleQuotationEndDrawer(),
),
Container(
padding: EdgeInsets.symmetric(
vertical: ScreenAdaper.height(10),
horizontal: ScreenAdaper.width(20)),
child: Row(mainAxisAlignment: MainAxisAlignment.end, children: [
GestureDetector(
onTap: () {
controller.addGroup();
appBar: SkAppbar(
title: '报价计算-${controller.templateName}',
action: [
IconButton(
onPressed: () {
controller.scaffoldKey.currentState?.openEndDrawer();
},
child: Icon(
Icons.add,
size: ScreenAdaper.height(40),
),
)
]),
),
],
),
body: SafeArea(
bottom: ScreenAdaper.isLandspace() ? false : true,
child: Stack(children: [
Column(
children: [
builderHeader(),
Expanded(
child: Obx(() => controller.groups.isEmpty
? Empty(text: '请先添加分组')
: CustomScrollView(
slivers: controller.groups
.mapIndexed<Widget>(
(index, e) => buildBody(index))
.toList(),
))),
//
SizedBox(
height: ScreenAdaper.height(100),
child: buildTotalCostRow(),
icon: const Icon(Icons.more_horiz_outlined,
color: AppTheme.white),
),
SizedBox(
height: ScreenAdaper.height(100),
child: buildTotalSalesPriceRow(),
)
],
),
// Positioned(
// bottom: ScreenAdaper.height(220),
// right: ScreenAdaper.height(10),
// child: IconButton(
// padding: EdgeInsets.all(ScreenAdaper.height(20)),
// style: ButtonStyle(
// backgroundColor: MaterialStateProperty.all<Color>(
// AppTheme.primaryColorLight)),
// onPressed: () {
// controller.addGroup();
// },
// icon: Icon(
// Icons.add,
// size: ScreenAdaper.height(40),
// ),
// ))
]),
),
);
body: SafeArea(
bottom: ScreenAdaper.isLandspace() ? false : true,
child: Stack(children: [
Column(
children: [
builderHeader(),
Expanded(
child: Obx(() => controller.groups.isEmpty
? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'请右上角选择模板',
style: TextStyle(
fontSize: ScreenAdaper.height(30)),
),
Text('或者左上角加号添加分组',
style: TextStyle(
fontSize: ScreenAdaper.height(30)))
],
)
: CustomScrollView(
slivers: controller.groups
.mapIndexed<Widget>(
(index, e) => buildBody(index))
.toList(),
))),
//
SizedBox(
height: ScreenAdaper.height(100),
child: buildTotalCostRow(),
),
SizedBox(
height: ScreenAdaper.height(100),
child: buildTotalSalesPriceRow(),
)
],
),
]),
),
));
}
Widget buildTotalSalesPriceRow() {
@ -254,6 +235,19 @@ class SaleQuotationPage extends StatelessWidget {
bottom: ScreenAdaper.height(10)),
child: Row(
children: [
GestureDetector(
onTap: () {
controller.addGroup();
},
child: Icon(
Icons.add_circle_outline_outlined,
size: ScreenAdaper.height(40),
color: Theme.of(Get.context!).colorScheme.onPrimary,
),
),
SizedBox(
width: ScreenAdaper.width(5),
),
Text(
'名称',
style: headerTitleStyle,
@ -315,8 +309,13 @@ class SaleQuotationPage extends StatelessWidget {
color: AppTheme.nearlyBlack,
fontWeight: FontWeight.w600);
return Container(
decoration: BoxDecoration(
color: const Color.fromARGB(255, 235, 235, 235)
.withOpacity(1.0 - state.scrollPercentage),
border: const Border(
top: BorderSide(width: 1, color: AppTheme.dividerColor),
bottom: BorderSide(width: 1, color: AppTheme.dividerColor))),
height: ScreenAdaper.height(80),
color: headerBgcolor.withOpacity(1.0 - state.scrollPercentage),
alignment: Alignment.centerLeft,
child: InkWell(
onTap: () {
@ -469,7 +468,9 @@ class SaleQuotationPage extends StatelessWidget {
),
Expanded(
child: Text(
'${controller.groups[groupIndex].items[rowIndex].remark ?? ''}${controller.groups[groupIndex].items[rowIndex].remark ?? ''}${controller.products[rowIndex].remark ?? ''}',
controller.groups[groupIndex].items[rowIndex]
.remark ??
'',
overflow: TextOverflow.ellipsis,
style: subTextStyle,
),
@ -496,18 +497,19 @@ class SaleQuotationPage extends StatelessWidget {
value: controller.groups[groupIndex].items[rowIndex].quantity,
func: (value) {
//
bool isValid = value > 0;
if (!isValid) {
SnackBarUtil().error('数量必须>0,若想删除产品0请左滑');
}
return isValid;
// bool isValid = (value ?? 0) > 0;
// if (!isValid) {
// SnackBarUtil().error('数量必须>0,若想删除产品0请左滑');
// }
return true;
}),
buildEditCell<int>(
buildEditCell<double>(
Container(
alignment: Alignment.center,
width: ScreenAdaper.width(unitPriceWidth),
child: Text(
'${controller.groups[groupIndex].items[rowIndex].cost}',
CommonUtil.toNumberWithout0(
controller.groups[groupIndex].items[rowIndex].cost),
style: TextStyle(fontSize: ScreenAdaper.height(25)),
),
),
@ -516,12 +518,13 @@ class SaleQuotationPage extends StatelessWidget {
field: 'cost',
inputWidth: ScreenAdaper.width(unitPriceWidth),
value: controller.groups[groupIndex].items[rowIndex].cost),
buildEditCell<int>(
buildEditCell<double>(
Container(
alignment: Alignment.center,
width: ScreenAdaper.width(amountWidth),
child: Text(
'${controller.groups[groupIndex].items[rowIndex].amount}',
CommonUtil.toNumberWithout0(
controller.groups[groupIndex].items[rowIndex].amount),
style: TextStyle(fontSize: ScreenAdaper.height(25)),
),
),
@ -558,22 +561,24 @@ class SaleQuotationPage extends StatelessWidget {
onFieldSubmitted: (value) {
final editingData =
controller.groups[groupIndex].items[rowIndex].toJson();
editingData[field] = value as T;
controller.saveChanges(groupIndex, rowIndex,
editingData[field] = value;
controller.afterRowChanges(groupIndex, rowIndex,
SaleQuotationItemModel.fromJson(editingData), field);
},
onTapOutside: (dynamic value) {
value = value ?? 0;
final editingData =
controller.groups[groupIndex].items[rowIndex].toJson();
editingData[field] = value as T;
controller.saveChanges(groupIndex, rowIndex,
controller.afterRowChanges(groupIndex, rowIndex,
SaleQuotationItemModel.fromJson(editingData), field);
},
contentPadding: EdgeInsets.symmetric(
horizontal: ScreenAdaper.width(20),
vertical: ScreenAdaper.height(10)),
textController:
TextEditingController(text: '${value == 0 ? '' : value}')),
textController: TextEditingController(
text:
value == 0 ? '' : CommonUtil.toNumberWithout0(value))),
)
: InkWell(
onTap: () {

View File

@ -43,7 +43,7 @@ class WorkBenchPage extends StatelessWidget {
.map((e) => e.name)
.firstWhereOrNull((name) => name == controller.menus[index].route);
if (route != null) {
RouterUtil.toNamed(controller.menus[index].route);
RouterUtil.toNamed(controller.menus[index].route!);
} else {
SnackBarUtil().info('您没有权限,请联系管理员。后期会隐藏没有权限的功能');
}

View File

@ -11,6 +11,7 @@ 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/models/app_config.dart';
import 'package:sk_base_mobile/router/router.util.dart';
import 'package:sk_base_mobile/services/service.dart';
import 'package:sk_base_mobile/util/device.util.dart';
import 'package:sk_base_mobile/util/logger_util.dart';
@ -125,6 +126,7 @@ class AppInfoService extends GetxService {
ModalUtil.alert(
// title: '有新版本',
builder: (_) => UpgradeConfirm(
version: newVersion,
onConfirm: () {
upgradeApp(newVersion);
},
@ -156,8 +158,7 @@ class AppInfoService extends GetxService {
try {
///
downloadProgress.value = 0;
Get.back();
await RouterUtil.back();
ModalUtil.alert(
barrierDismissible: false,
contentPadding: EdgeInsets.symmetric(vertical: 0),
@ -167,7 +168,7 @@ class AppInfoService extends GetxService {
Obx(
() => Stack(
children: [
Container(
SizedBox(
height: ScreenAdaper.height(40),
child: LinearProgressIndicator(
value: downloadProgress.value,
@ -191,7 +192,7 @@ class AppInfoService extends GetxService {
responseType: ResponseType.bytes,
followRedirects: false,
));
Get.back();
await RouterUtil.back();
InstallPlugin.install(
file.path,
).then((result) {}).catchError((error) {});

View File

@ -135,7 +135,7 @@ class DioService extends get_package.GetxService {
}
if (response.data != null && response.data is Map) {
if (response.data['code'] == 200) {
if (GloablConfig.DEBUG) LoggerUtil().info(response.data['data']);
// if (GloablConfig.DEBUG) LoggerUtil().info(response.data['data']);
response.data = response.data['data'];
//
if (response.data != null &&

View File

@ -36,6 +36,10 @@ class CommonUtil {
}
return result;
}
static String toNumberWithout0(num num) {
return num.toStringAsFixed(num.truncateToDouble() == num ? 0 : 2);
}
}
class TreeNode<T> {

View File

@ -33,12 +33,12 @@ class ModalUtil {
titlePadding: EdgeInsets.zero,
contentPadding: contentPadding ??
EdgeInsets.symmetric(
vertical: ScreenAdaper.height(10),
),
vertical: ScreenAdaper.height(15),
horizontal: ScreenAdaper.width(25)),
title: title != null
? Container(
padding: EdgeInsets.symmetric(
vertical: ScreenAdaper.height(20),
vertical: ScreenAdaper.height(10),
),
decoration: const BoxDecoration(
border: Border(
@ -59,7 +59,7 @@ class ModalUtil {
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.w500,
fontSize: ScreenAdaper.height(30),
fontSize: ScreenAdaper.height(25),
color: AppTheme.black,
),
)

View File

@ -1,6 +1,7 @@
import 'package:flutter/cupertino.dart';
import 'package:get/get.dart';
import 'package:image_picker/image_picker.dart';
import 'package:sk_base_mobile/router/router.util.dart';
import 'package:sk_base_mobile/util/media_util.dart';
import 'package:sk_base_mobile/app_theme.dart';
@ -13,7 +14,7 @@ class PhotoPickerUtil {
title: Text(title),
cancelButton: CupertinoActionSheetAction(
onPressed: () {
Get.back();
RouterUtil.back();
},
child: const Text(
'取消',
@ -22,7 +23,7 @@ class PhotoPickerUtil {
actions: ['拍照', '相册']
.map((item) => CupertinoActionSheetAction(
onPressed: () async {
Get.back();
await RouterUtil.back();
XFile? pickedFile = item == '拍照'
? await MediaUtil().getImageFromCamera()
: await MediaUtil().getImageFromGallery();

View File

@ -23,7 +23,7 @@ class SnackBarUtil {
}
}
if (Get.isSnackbarOpen) {
await Get.closeCurrentSnackbar();
Get.back();
}
Get.rawSnackbar(
snackPosition: SnackPosition.TOP,
@ -46,7 +46,7 @@ class SnackBarUtil {
Future<void> success(String title, {String? message}) async {
if (checkIsSnackBarInit()) {
if (Get.isSnackbarOpen) {
await Get.closeCurrentSnackbar();
Get.back();
}
Get.rawSnackbar(
message: title,

View File

@ -1,6 +1,6 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sk_base_mobile/router/router.util.dart';
import 'package:sk_base_mobile/util/logger_util.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
@ -32,7 +32,7 @@ class SkBaseDatePicker extends StatelessWidget {
children: [
TextButton(
onPressed: () {
Get.back();
RouterUtil.back();
},
child: Text(
'取消',
@ -41,7 +41,7 @@ class SkBaseDatePicker extends StatelessWidget {
const Spacer(),
TextButton(
onPressed: () {
Get.back();
RouterUtil.back();
final year = 2000 + yearController.selectedItem;
String month = '${monthController.selectedItem + 1}';
if (int.parse(month) < 10) {

View File

@ -1,7 +1,7 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/router/router.util.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
class SkBottomSheetPicker extends StatelessWidget {
@ -39,7 +39,7 @@ class SkBottomSheetPicker extends StatelessWidget {
children: [
TextButton(
onPressed: () {
Get.back();
RouterUtil.back();
},
child: Text(
'取消',
@ -57,7 +57,7 @@ class SkBottomSheetPicker extends StatelessWidget {
)),
TextButton(
onPressed: () {
Get.back();
RouterUtil.back();
onChanged!(
firstLevelControlller.selectedItem,
(secondLevel ?? []).isNotEmpty

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/router/router.util.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
import 'package:sk_base_mobile/widgets/core/sk_ink.dart';
import 'package:sk_base_mobile/widgets/loading_indicator.dart';
@ -65,7 +66,7 @@ class SkCascadePicker<T, Z> extends StatelessWidget {
children: [
SkInk(
onTap: () {
Get.back();
RouterUtil.back();
},
child: Text(
'取消',
@ -74,7 +75,7 @@ class SkCascadePicker<T, Z> extends StatelessWidget {
const Spacer(),
SkInk(
onTap: () {
Get.back();
RouterUtil.back();
if (onConfirm != null) {
onConfirm!(cascadeController.selectedTabs
.where((e) =>
@ -98,7 +99,7 @@ class SkCascadePicker<T, Z> extends StatelessWidget {
// GradientButton(
// buttonText: '确定',
// onPressed: () {
// Get.back();
// await RouterUtil.back();
// if (onConfirm != null) {
// onConfirm!(cascadeController.selectedTabs
// .where((e) =>

View File

@ -1,6 +1,6 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/router/router.util.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
class SkDialogHeader extends StatelessWidget {
@ -39,11 +39,11 @@ class SkDialogHeader extends StatelessWidget {
),
),
trailing ??
Container(
SizedBox(
width: ScreenAdaper.width(100),
child: IconButton(
onPressed: () {
Get.back();
RouterUtil.back();
},
icon: Icon(
Icons.close,

View File

@ -0,0 +1,56 @@
import 'package:flutter/material.dart';
import 'package:loading_animation_widget/loading_animation_widget.dart';
import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/constants/constants.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
import 'package:sk_base_mobile/widgets/core/sk_ink.dart';
class SkFlatButton extends StatelessWidget {
final VoidCallback? onPressed;
final bool isLoading;
final String buttonText;
final Icon? icon;
final Color? color;
final BorderRadiusGeometry? borderRadius;
const SkFlatButton(
{super.key,
this.buttonText = TextEnum.createInventoryInOutBtnText,
this.onPressed,
this.icon,
this.color,
this.borderRadius,
this.isLoading = false});
@override
Widget build(BuildContext context) {
return SkInk(
onTap: onPressed ?? () {},
color: color ?? AppTheme.primaryColor,
child: SizedBox(
height: ScreenAdaper.height(80),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (icon != null) ...[
icon!,
SizedBox(
width: ScreenAdaper.width(10),
)
],
isLoading
? LoadingAnimationWidget.fourRotatingDots(
color: AppTheme.nearlyWhite,
size: ScreenAdaper.height(40),
)
: Text(
buttonText,
style: TextStyle(
color: AppTheme.nearlyWhite,
fontWeight: FontWeight.bold,
fontSize: ScreenAdaper.height(25),
),
)
])),
);
}
}

View File

@ -29,11 +29,11 @@ class SkInk extends StatelessWidget {
child: ClipRRect(
borderRadius: borderRadius ?? BorderRadius.zero,
child: Material(
color: color ?? Colors.transparent,
child: Ink(
padding: padding,
decoration: BoxDecoration(
border: border,
color: color ?? AppTheme.nearlyWhite,
gradient: gradient,
borderRadius: borderRadius,
),

View File

@ -1,27 +1,34 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
class Empty extends StatelessWidget {
final String? text;
const Empty({super.key, this.text});
final Widget? icon;
final void Function()? onTap;
const Empty({super.key, this.text, this.icon, this.onTap});
@override
Widget build(BuildContext context) {
return Center(
child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Center(
child: Icon(Icons.error_outline,
size: ScreenAdaper.height(40), color: Colors.red)),
SizedBox(
width: ScreenAdaper.width(10),
),
Text(
text ?? '',
textAlign: TextAlign.center,
style: TextStyle(fontSize: ScreenAdaper.height(35)),
),
]),
return GestureDetector(
onTap: onTap,
child: Center(
child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [
Center(
child: icon ??
Icon(Icons.error_outline,
size: ScreenAdaper.height(40),
color: AppTheme.dangerColor)),
SizedBox(
width: ScreenAdaper.width(10),
),
Text(
text ?? '',
textAlign: TextAlign.center,
style: TextStyle(fontSize: ScreenAdaper.height(35)),
),
]),
),
);
}
}

View File

@ -5,10 +5,12 @@ import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/constants/bg_color.dart';
import 'package:sk_base_mobile/models/base_search_more.model.dart';
import 'package:sk_base_mobile/models/base_search_more_controller.dart';
import 'package:sk_base_mobile/router/router.util.dart';
import 'package:sk_base_mobile/util/debouncer.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
import 'package:sk_base_mobile/widgets/empty.dart';
import 'package:sk_base_mobile/widgets/gradient_button.dart';
import 'package:sk_base_mobile/widgets/loading_indicator.dart';
class SkMutilSearchMore<T extends BaseSearchMoreModel> extends StatelessWidget {
final Function(List<int>)? onOk;
@ -18,7 +20,6 @@ class SkMutilSearchMore<T extends BaseSearchMoreModel> extends StatelessWidget {
final bool enablePullUp;
final bool enablePullDown;
final bool isDialog;
final Function(int)? leadingBuilder;
SkMutilSearchMore(
{super.key,
@ -33,42 +34,45 @@ class SkMutilSearchMore<T extends BaseSearchMoreModel> extends StatelessWidget {
TextStyle(fontSize: ScreenAdaper.height(20), fontWeight: FontWeight.w600);
@override
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.symmetric(
horizontal: ScreenAdaper.width(20),
vertical: ScreenAdaper.height(20)),
child: Column(
children: [
buildDialogHeader(),
SizedBox(
height: ScreenAdaper.height(defaultPadding) / 2,
),
buildSearchBar(),
SizedBox(
height: ScreenAdaper.height(defaultPadding) / 2,
),
Expanded(child: buildList()),
SizedBox(
height: ScreenAdaper.height(defaultPadding) / 2,
),
GradientButton(
onPressed: () {
if (onOk != null) {
onOk!(controller.selectedIndex);
}
if (isDialog) {
Get.back();
}
controller.selectedIndex.clear();
},
buttonText: '确定',
)
],
));
return Column(
children: [
Expanded(
child: Container(
padding: EdgeInsets.symmetric(
horizontal: ScreenAdaper.width(20),
vertical: ScreenAdaper.height(20)),
child: Column(
children: [
buildDialogHeader(),
SizedBox(
height: ScreenAdaper.height(defaultPadding) / 2,
),
buildSearchBar(),
SizedBox(
height: ScreenAdaper.height(defaultPadding) / 2,
),
Expanded(child: buildList()),
],
)),
),
GradientButton(
onPressed: () {
if (onOk != null) {
onOk!(controller.selectedIndex);
}
if (isDialog) {
RouterUtil.back();
}
controller.selectedIndex.clear();
},
buttonText: '确定',
)
],
);
}
Widget buildDialogHeader() {
return Container(
return SizedBox(
height: ScreenAdaper.height(60),
child: Stack(
children: [
@ -84,7 +88,7 @@ class SkMutilSearchMore<T extends BaseSearchMoreModel> extends StatelessWidget {
right: 0,
child: GestureDetector(
onTap: () {
Get.back();
RouterUtil.back();
},
child: Icon(
Icons.close,
@ -135,23 +139,25 @@ class SkMutilSearchMore<T extends BaseSearchMoreModel> extends StatelessWidget {
}
Widget buildList() {
return Obx(() => SmartRefresher(
enablePullDown: enablePullDown,
enablePullUp: enablePullUp,
controller: controller.refreshController,
onLoading: controller.onLoading,
onRefresh: controller.onRefresh,
child: controller.list.isEmpty
? const Center(
child: Empty(text: '暂无数据'),
)
: ListView.separated(
separatorBuilder: (context, index) => const Divider(
color: AppTheme.dividerColor,
height: 1,
),
itemCount: controller.list.length,
itemBuilder: (_, index) => buildItem(index))));
return Obx(() => controller.loading.value
? const LoadingIndicator(common: true)
: SmartRefresher(
enablePullDown: enablePullDown,
enablePullUp: enablePullUp,
controller: controller.refreshController,
onLoading: controller.onLoading,
onRefresh: controller.onRefresh,
child: controller.list.isEmpty
? const Center(
child: Empty(text: '暂无数据'),
)
: ListView.separated(
separatorBuilder: (context, index) => const Divider(
color: AppTheme.dividerColor,
height: 1,
),
itemCount: controller.list.length,
itemBuilder: (_, index) => buildItem(index))));
}
Widget buildItem(int index) {

View File

@ -7,7 +7,7 @@ class SkNumberInput<T> extends StatefulWidget {
final TextEditingController textController;
final Function(FocusNode)? onTap;
final Function(String)? onChanged;
final Function(dynamic)? onTapOutside;
final void Function(dynamic)? onTapOutside;
final Function(dynamic)? validator;
final bool isDense;
@ -16,7 +16,7 @@ class SkNumberInput<T> extends StatefulWidget {
final bool isRequired;
final String labelText;
final String? hint;
final ValueChanged<int>? onFieldSubmitted;
final ValueChanged<T>? onFieldSubmitted;
const SkNumberInput(
{super.key,
required this.textController,
@ -76,13 +76,13 @@ class _SkNumberInputState<T> extends State<SkNumberInput> {
},
onTapOutside: (event) {
if (widget.onTapOutside != null) {
dynamic value;
dynamic value = widget.textController.text;
if (T == double) {
value = double.tryParse(widget.textController.text);
value = double.tryParse(widget.textController.text) ?? 0.0;
widget.onTapOutside!(value as T);
} else {
value = int.tryParse(widget.textController.text);
value = int.tryParse(widget.textController.text) ?? 0;
}
if (widget.validator != null && !widget.validator!(value)) {
return;

View File

@ -4,6 +4,7 @@ import 'package:pull_to_refresh/pull_to_refresh.dart';
import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/constants/bg_color.dart';
import 'package:sk_base_mobile/models/base_search_more_controller.dart';
import 'package:sk_base_mobile/router/router.util.dart';
import 'package:sk_base_mobile/util/debouncer.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
import 'package:sk_base_mobile/widgets/empty.dart';
@ -52,7 +53,7 @@ class SkSingleSearchMore<T> extends StatelessWidget {
}
Widget buildDialogHeader() {
return Container(
return SizedBox(
height: ScreenAdaper.height(60),
child: Stack(
children: [
@ -68,7 +69,7 @@ class SkSingleSearchMore<T> extends StatelessWidget {
right: 0,
child: GestureDetector(
onTap: () {
Get.back();
RouterUtil.back();
},
child: Icon(
Icons.close,

View File

@ -22,7 +22,7 @@ class SkTextInput extends SkBaseFieldWidget {
final InputBorder? border;
final FloatingLabelBehavior? floatingLabelBehavior;
final TextInputType? keyboardType;
final double? height;
SkTextInput(
{super.key,
super.customLabel = false,
@ -40,6 +40,7 @@ class SkTextInput extends SkBaseFieldWidget {
this.suffixIcon,
this.onChanged,
this.border,
this.height,
this.floatingLabelBehavior = FloatingLabelBehavior.always,
this.isTextArea = false,
this.contentPadding,
@ -48,69 +49,76 @@ class SkTextInput extends SkBaseFieldWidget {
@override
Widget build(BuildContext context) {
Widget field = TextFormField(
focusNode: focusNode,
controller: textController,
onChanged: (String value) {
if (onChanged != null) {
onChanged!(value);
}
},
onTapOutside: (event) {
if (onTapOutside != null) {
onTapOutside!(textController.text);
FocusScope.of(context).unfocus();
}
},
maxLines: isTextArea ? 2 : 1, //
onTap: () {
if (onTap != null) {
onTap!(focusNode);
}
},
keyboardType: keyboardType,
onFieldSubmitted: onFieldSubmitted,
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: validator,
Widget field = SizedBox(
height: height,
child: TextFormField(
focusNode: focusNode,
controller: textController,
onChanged: (String value) {
if (onChanged != null) {
onChanged!(value);
}
},
onTapOutside: (event) {
if (onTapOutside != null) {
onTapOutside!(textController.text);
FocusScope.of(context).unfocus();
}
},
maxLines: isTextArea ? 2 : 1, //
onTap: () {
if (onTap != null) {
onTap!(focusNode);
}
},
keyboardType: keyboardType,
onFieldSubmitted: onFieldSubmitted,
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: validator,
decoration: InputDecoration(
prefixIcon: prefix,
suffixIcon: suffixIcon,
fillColor: fillColor,
filled: true,
errorStyle: const TextStyle(fontSize: 0, height: 0.01),
contentPadding: contentPadding,
isDense: isDense,
border: border,
floatingLabelBehavior: floatingLabelBehavior,
label: labelText != null && !customLabel
? Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
if (isRequired)
decoration: InputDecoration(
prefixIcon: prefix,
suffixIcon: suffixIcon,
fillColor: fillColor,
filled: true,
errorStyle: const TextStyle(fontSize: 0, height: 0.01),
contentPadding: contentPadding,
isDense: isDense,
border: border,
floatingLabelBehavior: floatingLabelBehavior,
label: labelText != null && !customLabel
? Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisSize: MainAxisSize.min,
children: [
if (isRequired)
Text(
"*",
style: TextStyle(
color: Colors.red,
fontSize: ScreenAdaper.height(30)),
),
Text(
"*",
style: TextStyle(
color: Colors.red,
fontSize: ScreenAdaper.height(30)),
labelText!,
style: TextStyle(fontSize: ScreenAdaper.height(30)),
),
Text(
labelText!,
style: TextStyle(fontSize: ScreenAdaper.height(30)),
),
])
: null,
hintText: hint ?? '请输入',
])
: null,
hintText: hint ?? '请输入',
),
),
);
return SkFormItem(
controller: baseFieldController,
customLabel: customLabel,
labelText: labelText,
isRequired: isRequired,
child: field,
);
if (customLabel) {
return SkFormItem(
controller: baseFieldController,
customLabel: customLabel,
labelText: labelText,
isRequired: isRequired,
child: field,
);
} else {
return field;
}
}
}

View File

@ -10,7 +10,7 @@ class GradientButton extends StatelessWidget {
final bool isLoading;
final String buttonText;
final Icon? icon;
final BorderRadiusGeometry? borderRadius;
final BorderRadius? borderRadius;
const GradientButton(
{super.key,
this.buttonText = TextEnum.createInventoryInOutBtnText,
@ -21,6 +21,7 @@ class GradientButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return SkInk(
borderRadius: borderRadius,
onTap: onPressed ?? () {},
gradient: const LinearGradient(
colors: [AppTheme.primaryColorLight, AppTheme.primaryColor]),

View File

@ -1,10 +1,10 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:photo_view/photo_view.dart';
import 'package:photo_view/photo_view_gallery.dart';
import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/config.dart';
import 'package:sk_base_mobile/models/file.model.dart';
import 'package:sk_base_mobile/router/router.util.dart';
import 'package:sk_base_mobile/util/loading_util.dart';
import 'package:sk_base_mobile/util/media_util.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
@ -96,7 +96,7 @@ class _ImagePreivewState extends State<ImagePreivew> {
final FileModel item = widget.galleryItems[index];
return PhotoViewGalleryPageOptions(
onTapUp: (_, tapUpDetails, value) {
Get.back();
RouterUtil.back();
},
imageProvider: Image.network('${GloablConfig.OSS_URL}${item.path}').image,
initialScale: PhotoViewComputedScale.contained,

View File

@ -80,7 +80,7 @@ class MyAvatarController extends GetxController {
// }
// }
// uploadImgFilePath(pickedFile.path);
// Get.back();
// await RouterUtil.back();
} catch (e) {
SnackBarUtil().error('Update avatar failed.');
} finally {

View File

@ -1,13 +1,19 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:sk_base_mobile/app_theme.dart';
import 'package:sk_base_mobile/router/router.util.dart';
import 'package:sk_base_mobile/util/screen_adaper_util.dart';
import 'package:sk_base_mobile/widgets/gradient_button.dart';
class UpgradeConfirm extends StatelessWidget {
final void Function()? onConfirm;
final bool forceUpgrade;
const UpgradeConfirm({super.key, this.onConfirm, this.forceUpgrade = true});
final String version;
const UpgradeConfirm(
{super.key,
this.onConfirm,
this.forceUpgrade = true,
required this.version});
@override
Widget build(BuildContext context) {
@ -33,7 +39,7 @@ class UpgradeConfirm extends StatelessWidget {
child: Column(
children: [
Text(
'有新的版本更新啦',
'有新的版本v_${version}更新啦',
style: TextStyle(
decoration: TextDecoration.none,
fontSize: ScreenAdaper.height(35),
@ -83,7 +89,7 @@ class UpgradeConfirm extends StatelessWidget {
child: //
IconButton(
onPressed: () {
Get.back();
RouterUtil.back();
},
icon: const Icon(Icons.close)),
),

View File

@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.devz
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1
version: 1.0.2+1
environment:
sdk: '>=3.2.6 <4.0.0'