数据未从 Flutter 应用更新到 Google 表格

问题描述 投票:0回答:1

我使用 Google Sheets 作为数据源,并在 Google Sheets 中使用 Google Apps 脚本设置了 Google Sheets API。使用Postman测试时,数据成功进入Google Sheets。但是,运行 Flutter 应用程序时,数据不会记录在 Google Sheets 中,并且会显示一条提及“XMLHTTPRequest”的错误。

应用程序脚本中的代码:

// Function to handle GET requests`your text`
function doGet(e) {
  return ContentService.createTextOutput('GET request received. Please use POST method to send data.')
                       .setMimeType(ContentService.MimeType.TEXT);
}
var sheet = SpreadsheetApp.openByUrl('https://docs.google.com/spreadsheets.......').getSheetByName('Sheet1');
 
// Function to handle POST requests
function doPost(e) {
  function doPost(e) {
  var response = {status: 'success', message: 'Data received'};
  return ContentService.createTextOutput(JSON.stringify(response))
                       .setMimeType(ContentService.MimeType.JSON)
                       .setHeader('Access-Control-Allow-Origin', '*')
                       .setHeader('Access-Control-Allow-Methods', 'POST')
                       .setHeader('Access-Control-Allow-Headers', 'Content-Type');
}
 
  if (!e || !e.postData || !e.postData.contents) {
    return ContentService.createTextOutput(JSON.stringify({status: 'error', message: 'Invalid POST request'}))
                         .setMimeType(ContentService.MimeType.JSON);
  }
 
  try {
    // Parse the JSON data from the POST request
    var jsonData = JSON.parse(e.postData.contents);
 
    // Extract data (assuming the JSON object has the correct keys)
    var branch = jsonData.branch || '';
    var shop = jsonData.shop || '';
    var deliveryMode = jsonData.deliveryMode || '';
    var creditDays = jsonData.creditDays || '';
    var discountPercentage = jsonData.discountPercentage || '';
    var remarks = jsonData.remarks || '';
    var items = jsonData.items || [];
    var totalQuantity = jsonData.totalQuantity || '';
    var subtotal = jsonData.subtotal || '';
    var discountAmount = jsonData.discountAmount || '';
    var netAmount = jsonData.netAmount || '';

 
    items.forEach(function(item) {
      var itemName = item.itemName || '';
      var quantity = item.quantity || '';
      var rate = item.rate || '';
      var total = item.total || '';
 
      // Append data to the sheet
      sheet.appendRow([branch, shop, deliveryMode, creditDays, discountPercentage, remarks, itemName, quantity, rate, total,totalQuantity,subtotal,discountAmount,netAmount]);
    });
 
    // Return a success response
    return ContentService.createTextOutput(JSON.stringify({status: 'success'}))
                         .setMimeType(ContentService.MimeType.JSON);
  } catch (error) {
    return ContentService.createTextOutput(JSON.stringify({status: 'error', message: error.toString()}))
                         .setMimeType(ContentService.MimeType.JSON);
  }
}

可视化代码中main.dart中的代码:

import 'package:flutter/material.dart';
import 'dart:convert';
import 'package:http/http.dart' as http;

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'SOB',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        primaryColor: Colors.blue,
        colorScheme: ColorScheme.fromSwatch().copyWith(secondary: Colors.orange),
        fontFamily: 'Roboto',
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home Page'),
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => SalesOrderForm()),
            );
          },
          child: Text('Go to Sales Order Form'),
        ),
      ),
    );
  }
}

class SalesOrderForm extends StatefulWidget {
  @override
  _SalesOrderFormState createState() => _SalesOrderFormState();
}

class _SalesOrderFormState extends State<SalesOrderForm> {
  String? _selectedBranch;
  String? _selectedShop;
  String? _selectedDeliveryMode;
  List<String> _branches = ['Branch A', 'Branch B', 'Branch C'];
  List<String> _shops = ['Shop 1', 'Shop 2', 'Shop 3'];
  List<String> _deliveryModes = ['Standard', 'Express', 'Next Day'];
  final TextEditingController _creditDaysController = TextEditingController();
  final TextEditingController _discountPercentageController = TextEditingController();
  final TextEditingController _remarksController = TextEditingController();
  List<Item> _items = [];
  final _formKey = GlobalKey<FormState>();

  double get subtotal {
    return _items.fold(0.0, (sum, item) => sum + item.total);
  }

  double get discountAmount {
    double discountPercentage = double.tryParse(_discountPercentageController.text) ?? 0.0;
    return subtotal * (discountPercentage / 100);
  }

  double get netAmount {
    return subtotal - discountAmount;
  }

  int get totalQuantity {
    return _items.fold(0, (sum, item) => sum + item.quantity);
  }

  @override
  void dispose() {
    _creditDaysController.dispose();
    _discountPercentageController.dispose();
    _remarksController.dispose();
    super.dispose();
  }

  void _updateUI() {
    setState(() {});
  }

  Future<void> _submitForm() async {
    if (_formKey.currentState!.validate()) {
      try {
        final response = await http.post(
          Uri.parse('https://script.google.com/macros/s/...../exec'),
          headers: <String, String>{
            'Content-Type': 'application/json; charset=UTF-8',
          },
          body: jsonEncode(<String, dynamic>{
            'branch': _selectedBranch,
            'shop': _selectedShop,
            'deliveryMode': _selectedDeliveryMode,
            'creditDays': _creditDaysController.text,
            'discountPercentage': _discountPercentageController.text,
            'remarks': _remarksController.text,
            'items': _items.map((item) => item.toMap()).toList(),
            'totalQuantity': totalQuantity.toString(),
            'subtotal': subtotal.toString(),
            'discountAmount': discountAmount.toString(),
            'netAmount': netAmount.toString(),
          }),
        );

        if (response.statusCode == 200) {
          final result = json.decode(response.body);
          if (result['status'] == 'success') {
            ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Data updated successfully')));
          } else {
            ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Failed to update data: ${result['message']}')));
          }
        } else {
          ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Failed to update data')));
        }
      } catch (e) {
        ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Error: ${e.toString()}')));
      }
    }
  }

  void _addItem() {
    setState(() {
      _items.add(Item());
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Sales Order Form'),
        centerTitle: true,
      ),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: SingleChildScrollView(
          child: Form(
            key: _formKey,
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: <Widget>[
                Text(
                  'Branch Name',
                  style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                ),
                DropdownButtonFormField<String>(
                  decoration: InputDecoration(
                    filled: true,
                    fillColor: Colors.white24,
                    border: OutlineInputBorder(
                      borderRadius: BorderRadius.circular(10.0),
                    ),
                  ),
                  value: _selectedBranch,
                  onChanged: (newValue) {
                    setState(() {
                      _selectedBranch = newValue;
                    });
                  },
                  items: _branches.map((branch) {
                    return DropdownMenuItem<String>(
                      value: branch,
                      child: Text(branch),
                    );
                  }).toList(),
                  validator: (value) => value == null ? 'Please select a branch' : null,
                ),
                SizedBox(height: 20),
                Text(
                  'Shop Name',
                  style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                ),
                DropdownButtonFormField<String>(
                  decoration: InputDecoration(
                    filled: true,
                    fillColor: Colors.white24,
                    border: OutlineInputBorder(
                      borderRadius: BorderRadius.circular(10.0),
                    ),
                  ),
                  value: _selectedShop,
                  onChanged: (newValue) {
                    setState(() {
                      _selectedShop = newValue;
                    });
                  },
                  items: _shops.map((shop) {
                    return DropdownMenuItem<String>(
                      value: shop,
                      child: Text(shop),
                    );
                  }).toList(),
                  validator: (value) => value == null ? 'Please select a shop' : null,
                ),
                SizedBox(height: 20),
                Text(
                  'Delivery Mode',
                  style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                ),
                DropdownButtonFormField<String>(
                  decoration: InputDecoration(
                    filled: true,
                    fillColor: Colors.white24,
                    border: OutlineInputBorder(
                      borderRadius: BorderRadius.circular(10.0),
                    ),
                  ),
                  value: _selectedDeliveryMode,
                  onChanged: (newValue) {
                    setState(() {
                      _selectedDeliveryMode = newValue;
                    });
                  },
                  items: _deliveryModes.map((mode) {
                    return DropdownMenuItem<String>(
                      value: mode,
                      child: Text(mode),
                    );
                  }).toList(),
                  validator: (value) => value == null ? 'Please select a delivery mode' : null,
                ),
                SizedBox(height: 20),
                Text(
                  'Credit Days',
                  style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                ),
                TextFormField(
                  controller: _creditDaysController,
                  decoration: InputDecoration(
                    filled: true,
                    fillColor: Colors.white24,
                    border: OutlineInputBorder(
                      borderRadius: BorderRadius.circular(10.0),
                    ),
                  ),
                  keyboardType: TextInputType.number,
                  validator: (value) {
                    if (value == null || value.isEmpty) {
                      return 'Please enter credit days';
                    }
                    if (int.tryParse(value) == null) {
                      return 'Please enter a valid number';
                    }
                    return null;
                  },
                ),
                SizedBox(height: 20),
                Text(
                  'Discount Percentage',
                  style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                ),
                TextFormField(
                  controller: _discountPercentageController,
                  decoration: InputDecoration(
                    filled: true,
                    fillColor: Colors.white24,
                    border: OutlineInputBorder(
                      borderRadius: BorderRadius.circular(10.0),
                    ),
                  ),
                  keyboardType: TextInputType.number,
                  validator: (value) {
                    if (value == null || value.isEmpty) {
                      return 'Please enter discount percentage';
                    }
                    if (double.tryParse(value) == null) {
                      return 'Please enter a valid number';
                    }
                    return null;
                  },
                ),
                SizedBox(height: 20),
                Text(
                  'Remarks',
                  style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                ),
                TextFormField(
                  controller: _remarksController,
                  decoration: InputDecoration(
                    filled: true,
                    fillColor: Colors.white24,
                    border: OutlineInputBorder(
                      borderRadius: BorderRadius.circular(10.0),
                    ),
                  ),
                  keyboardType: TextInputType.multiline,
                  maxLines: 3,
                ),
                SizedBox(height: 20),
                Text(
                  'Items',
                  style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                ),
                ListView.builder(
                  shrinkWrap: true,
                  itemCount: _items.length,
                  itemBuilder: (context, index) {
                    return ItemWidget(
                      item: _items[index],
                      onDelete: () {
                        setState(() {
                          _items.removeAt(index);
                        });
                      },
                      onUpdate: _updateUI,
                    );
                  },
                ),
                TextButton.icon(
                  onPressed: _addItem,
                  icon: Icon(Icons.add),
                  label: Text('Add Item'),
                ),
                SizedBox(height: 20),
                Text(
                  'Total Quantity: $totalQuantity',
                  style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                ),
                Text(
                  'Subtotal: \$${subtotal.toStringAsFixed(2)}',
                  style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                ),
                Text(
                  'Discount Amount: \$${discountAmount.toStringAsFixed(2)}',
                  style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                ),
                Text(
                  'Net Amount: \$${netAmount.toStringAsFixed(2)}',
                  style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
                ),
                SizedBox(height: 20),
                ElevatedButton(
                  onPressed: _submitForm,
                  child: Text('Submit'),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

class Item {
  String itemName;
  int quantity;
  double rate;

  Item({this.itemName = '', this.quantity = 0, this.rate = 0.0});

  double get total => quantity * rate;

  Map<String, dynamic> toMap() {
    return {
      'itemName': itemName,
      'quantity': quantity,
      'rate': rate,
      'total': total,
    };
  }
}

class ItemWidget extends StatelessWidget {
  final Item item;
  final VoidCallback onDelete;
  final VoidCallback onUpdate;

  const ItemWidget({
    required this.item,
    required this.onDelete,
    required this.onUpdate,
  });

  @override
  Widget build(BuildContext context) {
    final TextEditingController itemNameController = TextEditingController(text: item.itemName);
    final TextEditingController quantityController = TextEditingController(text: item.quantity.toString());
    final TextEditingController rateController = TextEditingController(text: item.rate.toString());

    return Card(
      margin: EdgeInsets.symmetric(vertical: 8.0),
      child: Padding(
        padding: EdgeInsets.all(8.0),
        child: Column(
          children: [
            TextFormField(
              controller: itemNameController,
              decoration: InputDecoration(labelText: 'Item Name'),
              onChanged: (value) {
                item.itemName = value;
                onUpdate();
              },
            ),
            TextFormField(
              controller: quantityController,
              decoration: InputDecoration(labelText: 'Quantity'),
              keyboardType: TextInputType.number,
              onChanged: (value) {
                item.quantity = int.tryParse(value) ?? 0;
                onUpdate();
              },
            ),
            TextFormField(
              controller: rateController,
              decoration: InputDecoration(labelText: 'Rate'),
              keyboardType: TextInputType.number,
              onChanged: (value) {
                item.rate = double.tryParse(value) ?? 0.0;
                onUpdate();
              },
            ),
            SizedBox(height: 8.0),
            Text('Total: \$${item.total.toStringAsFixed(2)}'),
            SizedBox(height: 8.0),
            TextButton.icon(
              onPressed: onDelete,
              icon: Icon(Icons.delete),
              label: Text('Delete'),
              style: TextButton.styleFrom(foregroundColor: Colors.red),
            ),
          ],
        ),
      ),
    );
  }
}

我们正在更新的销售订单表格截图为: googlesheet格式的输出:

javascript flutter google-apps-script
1个回答
0
投票

函数

doPost
也声明了一个名为
doPost
的内部函数,但是这个内部doPost函数没有被调用。

可能还有其他问题,但您应该从解决这个问题开始。

© www.soinside.com 2019 - 2024. All rights reserved.