Commit 3520b32d9322ad39bf68a357d89f277bf659445c

Authored by weixin_45529955
1 parent e6531640

营销活动新

@@ -20,19 +20,19 @@ if (flutterRoot == null) { @@ -20,19 +20,19 @@ if (flutterRoot == null) {
20 20
21 21
22 22
23 -def flutterVersionCode = "710" 23 +def flutterVersionCode = "720"
24 if (flutterVersionCode == null) { 24 if (flutterVersionCode == null) {
25 - flutterVersionCode = '710' 25 + flutterVersionCode = '720'
26 26
27 27
28 28
29 } 29 }
30 30
31 31
32 -def flutterVersionName = "7.1.0" 32 +def flutterVersionName = "7.2.0"
33 33
34 if (flutterVersionName == null) { 34 if (flutterVersionName == null) {
35 - flutterVersionName = '7.1.0' 35 + flutterVersionName = '7.2.0'
36 36
37 } 37 }
38 38
@@ -83,8 +83,8 @@ android { @@ -83,8 +83,8 @@ android {
83 83
84 ndk { 84 ndk {
85 //选择要添加的对应 cpu 类型的 .so 库。 85 //选择要添加的对应 cpu 类型的 .so 库。
86 - abiFilters 'arm64-v8a', 'x86_64', 'mips64' //魅族64位,不支持32位  
87 - // abiFilters 'armeabi', 'armeabi-v7a', 'arm64-v8a', 'x86_64','x86', 'mips', 'mips64' 86 + // abiFilters 'arm64-v8a', 'x86_64', 'mips64' //魅族64位,不支持32位
  87 + abiFilters 'armeabi', 'armeabi-v7a', 'arm64-v8a', 'x86_64','x86', 'mips', 'mips64'
88 } 88 }
89 89
90 lintOptions { 90 lintOptions {
@@ -77,6 +77,10 @@ class HttpUrl { @@ -77,6 +77,10 @@ class HttpUrl {
77 "?service=App.Reserve.NewGetReserveList"; //商家预订本列表【2019-11-26】 77 "?service=App.Reserve.NewGetReserveList"; //商家预订本列表【2019-11-26】
78 static const String create_customer_activity = 78 static const String create_customer_activity =
79 "?service=App.Activity.CreateCustomerActivity"; //客户经理创建活动 79 "?service=App.Activity.CreateCustomerActivity"; //客户经理创建活动
  80 + static const String import_activity_phone_coupon =
  81 + "?service=App.Activity.ImportActivityPhoneCoupon"; //创建手机号营销活动并发放权益
  82 + static const String send_phone_marketing_sms =
  83 + "?service=App.Activity.SendPhoneMarketingSms"; //发送手机号营销活动短信
80 static const String get_customer_activity = 84 static const String get_customer_activity =
81 "?service=App.Activity.NewCustomerActivityList"; //获取所创建活动的列表 85 "?service=App.Activity.NewCustomerActivityList"; //获取所创建活动的列表
82 86
  1 +import 'dart:convert';
  2 +
  3 +import 'package:flutter/material.dart';
  4 +import 'package:one_key/constant/state.dart';
  5 +import 'package:one_key/models/coupon_model.dart';
  6 +import 'package:one_key/models/store_customer_home_model.dart'
  7 + as storeCustomerHomeModel;
  8 +import 'package:one_key/provider/customer_making_provider.dart';
  9 +import 'package:one_key/provider/customer_provider.dart';
  10 +import 'package:one_key/provider/user_model.dart';
  11 +import 'package:one_key/res/colors.dart';
  12 +import 'package:one_key/route/navigator_util.dart';
  13 +import 'package:one_key/utils/dialog_util.dart';
  14 +import 'package:one_key/utils/utils.dart';
  15 +import 'package:one_key/widgets/system_app_bar.dart';
  16 +import 'package:provider/provider.dart';
  17 +
  18 +import '../../../utils/screen_util.dart';
  19 +
  20 +class CreateMarketingPlanPage extends StatefulWidget {
  21 + final bool isSeller;
  22 + final String? uid;
  23 + final List<storeCustomerHomeModel.SellerUser>? sellerUser;
  24 +
  25 + const CreateMarketingPlanPage({
  26 + Key? key,
  27 + required this.isSeller,
  28 + this.uid,
  29 + this.sellerUser,
  30 + }) : super(key: key);
  31 +
  32 + @override
  33 + State<CreateMarketingPlanPage> createState() =>
  34 + _CreateMarketingPlanPageState();
  35 +}
  36 +
  37 +class _CreateMarketingPlanPageState extends State<CreateMarketingPlanPage> {
  38 + late CustomerProvider customerProvider;
  39 + late CustomerMakingProvider customerMakingProvider;
  40 +
  41 + final TextEditingController _greetingController =
  42 + TextEditingController(text: '好久不见!甚是想念,为了表达我们的思念之情');
  43 + final TextEditingController _amountController = TextEditingController();
  44 +
  45 + List<String> couponNames = [];
  46 + int? _selectedCouponIndex;
  47 + String? _selectedCouponName;
  48 +
  49 + /// 0:发放消费金;1:赠送优惠券
  50 + int _giftType = 0;
  51 +
  52 + String gid = '';
  53 +
  54 + @override
  55 + void initState() {
  56 + super.initState();
  57 + _greetingController.addListener(_onFormChanged);
  58 + _amountController.addListener(_onFormChanged);
  59 +
  60 + WidgetsBinding.instance.addPostFrameCallback((_) {
  61 + if (!mounted) return;
  62 + _computeGid();
  63 + _loadCoupons();
  64 + });
  65 + }
  66 +
  67 + @override
  68 + void dispose() {
  69 + _greetingController.removeListener(_onFormChanged);
  70 + _amountController.removeListener(_onFormChanged);
  71 + _greetingController.dispose();
  72 + _amountController.dispose();
  73 + super.dispose();
  74 + customerMakingProvider.clearData();
  75 + }
  76 +
  77 + void _onFormChanged() {
  78 + if (mounted) {
  79 + setState(() {});
  80 + }
  81 + }
  82 +
  83 + void _computeGid() {
  84 + gid = '';
  85 + if (widget.isSeller) {
  86 + for (final item in customerMakingProvider.selectCustomerSeller) {
  87 + gid += '${item.gid},';
  88 + }
  89 + } else {
  90 + for (final item in customerMakingProvider.selectCustomer) {
  91 + gid += '${item.id},';
  92 + }
  93 + }
  94 + if (gid.endsWith(',')) {
  95 + gid = gid.substring(0, gid.length - 1);
  96 + }
  97 + }
  98 +
  99 + Future<void> _loadCoupons() async {
  100 + final userModel = Provider.of<UserModel>(context, listen: false);
  101 + final uid = widget.isSeller ? userModel.user!.openid : widget.uid;
  102 + final params = <String, String?>{
  103 + 'uid': uid,
  104 + 'status': '1',
  105 + };
  106 +
  107 + await customerProvider.getCustomerSelectActivityNew(context, params,
  108 + isShowLoading: false);
  109 +
  110 + if (!mounted) return;
  111 + if (customerProvider.couponListSelect == null ||
  112 + customerProvider.couponListSelect!.isEmpty) {
  113 + return;
  114 + }
  115 +
  116 + couponNames = customerProvider.couponListSelect!
  117 + .map((e) => e.aname)
  118 + .whereType<String>()
  119 + .toList();
  120 + _selectedCouponIndex = 0;
  121 + _selectedCouponName = couponNames.isNotEmpty ? couponNames.first : null;
  122 + setState(() {});
  123 + }
  124 +
  125 + String get _labels {
  126 + final labels = customerMakingProvider.labels;
  127 + if (labels == null || labels.trim().isEmpty) {
  128 + return '';
  129 + }
  130 + return '[$labels] ';
  131 + }
  132 +
  133 + int get _selectedCount => widget.isSeller
  134 + ? customerMakingProvider.selectCustomerSeller.length
  135 + : customerMakingProvider.selectCustomer.length;
  136 +
  137 + String get _selectedSummary => '已选 $_labels${_selectedCount}位顾客';
  138 +
  139 + String get _giftText {
  140 + if (_giftType == 0) {
  141 + final amount = _amountController.text.trim();
  142 + return amount.isEmpty ? '赠送您消费金' : '赠送您$amount消费金';
  143 + }
  144 + return '赠送您${_selectedCouponName ?? '优惠券'}';
  145 + }
  146 +
  147 + String _buildPreview() {
  148 + final userModel = Provider.of<UserModel>(context, listen: false);
  149 + final storeName = userModel.user?.sellername ?? '';
  150 + final greeting = _greetingController.text.trim();
  151 +
  152 + if (_giftType == 1 && _selectedCouponIndex != null) {
  153 + final coupon = customerProvider.couponListSelect![_selectedCouponIndex!];
  154 + final requirement = coupon.desc?.trim();
  155 + final validity = _buildCouponValidity(coupon);
  156 + final couponContent = <String>[
  157 + _giftText,
  158 + if (requirement != null && requirement.isNotEmpty) requirement,
  159 + '凭手机号核销',
  160 + if (validity.isNotEmpty) validity,
  161 + ].join(',');
  162 + return '--$storeName--\n嘿,老朋友! $greeting, $couponContent。 '
  163 + '欢迎提前预订,拒收请回复R';
  164 + }
  165 +
  166 + return '--$storeName--\n嘿,老朋友! $greeting, '
  167 + '$_giftText,到店凭手机号核销。 欢迎提前预订,拒收请回复R';
  168 + }
  169 +
  170 + String _buildCouponValidity(CouponSunModel coupon) {
  171 + String? validForever;
  172 + String? stime;
  173 + String? etime;
  174 + if (coupon.expirationDate != null) {
  175 + validForever = coupon.expirationDate!.validForever;
  176 + stime = coupon.expirationDate!.stime;
  177 + etime = coupon.expirationDate!.etime;
  178 + } else if (coupon.aexpiration != null) {
  179 + validForever = coupon.aexpiration!.validForever;
  180 + stime = coupon.aexpiration!.stime;
  181 + etime = coupon.aexpiration!.etime;
  182 + }
  183 + if (validForever == '1') {
  184 + return '';
  185 + }
  186 +
  187 + final startDate = _formatCouponTimestamp(stime);
  188 + final endDate = _formatCouponTimestamp(etime);
  189 + if (startDate.isNotEmpty && endDate.isNotEmpty) {
  190 + return '有效期自$startDate到$endDate';
  191 + }
  192 + if (startDate.isNotEmpty) {
  193 + return '有效期自$startDate起';
  194 + }
  195 + if (endDate.isNotEmpty) {
  196 + return '有效期至$endDate';
  197 + }
  198 + return '';
  199 + }
  200 +
  201 + String _formatCouponTimestamp(String? timestamp) {
  202 + if (timestamp == null || timestamp.isEmpty) {
  203 + return '';
  204 + }
  205 + final seconds = int.tryParse(timestamp);
  206 + if (seconds == null || seconds <= 0) {
  207 + return '';
  208 + }
  209 + final date = DateTime.fromMillisecondsSinceEpoch(seconds * 1000);
  210 + final month = date.month.toString().padLeft(2, '0');
  211 + final day = date.day.toString().padLeft(2, '0');
  212 + return '${date.year}-$month-$day';
  213 + }
  214 +
  215 + Future<void> _viewCustomers() async {
  216 + FocusScope.of(context).requestFocus(FocusNode());
  217 + if (widget.isSeller) {
  218 + await NavigatorUtil.goCustomerListPage(context,
  219 + FromPage.FromPageMakeRecord, '', {'object': widget.sellerUser});
  220 +
  221 + if (!mounted) return;
  222 + _computeGid();
  223 + setState(() {});
  224 + } else {
  225 + await NavigatorUtil.goMyCustomerListPage(
  226 + context,
  227 + FromPage.FromPageMakeRecord,
  228 + widget.uid,
  229 + labels: customerMakingProvider.labels ?? '',
  230 + );
  231 +
  232 + if (!mounted) return;
  233 + _computeGid();
  234 + setState(() {});
  235 + }
  236 + }
  237 +
  238 + void _selectCoupon() {
  239 + FocusScope.of(context).requestFocus(FocusNode());
  240 + if (couponNames.isEmpty) {
  241 + Utils.showToast('暂无优惠券,请创建新优惠');
  242 + return;
  243 + }
  244 +
  245 + DialogUtil.showSelectCusmanDialog(context, couponNames, (value) {
  246 + if (value != null && value is List && value.isNotEmpty) {
  247 + setState(() {
  248 + _selectedCouponIndex = value.first as int;
  249 + _selectedCouponName = couponNames[_selectedCouponIndex!];
  250 + });
  251 + }
  252 + }, selecteds: _selectedCouponIndex == null ? [] : [_selectedCouponIndex!]);
  253 + }
  254 +
  255 + Future<void> _confirm() async {
  256 + FocusScope.of(context).requestFocus(FocusNode());
  257 + if (_selectedCount == 0) {
  258 + Utils.showToast('请至少选择1名顾客');
  259 + return;
  260 + }
  261 + if (_giftType == 0 && _amountController.text.trim().isEmpty) {
  262 + Utils.showToast('请输入发放消费金金额');
  263 + return;
  264 + }
  265 + if (_giftType == 1 && _selectedCouponIndex == null) {
  266 + Utils.showToast('请选择优惠券');
  267 + return;
  268 + }
  269 +
  270 + final userModel = Provider.of<UserModel>(context, listen: false);
  271 + final greeting = _greetingController.text.trim();
  272 + if (greeting.isEmpty) {
  273 + Utils.showToast('请输入问候语');
  274 + return;
  275 + }
  276 +
  277 + final phones = <String>[];
  278 + if (widget.isSeller) {
  279 + for (final item in customerMakingProvider.selectCustomerSeller) {
  280 + final phone = item.phone?.trim();
  281 + if (phone != null && phone.isNotEmpty) {
  282 + phones.add(phone);
  283 + }
  284 + }
  285 + } else {
  286 + for (final item in customerMakingProvider.selectCustomer) {
  287 + final phone = item.phone?.trim();
  288 + if (phone != null && phone.isNotEmpty) {
  289 + phones.add(phone);
  290 + }
  291 + }
  292 + }
  293 + if (phones.isEmpty) {
  294 + Utils.showToast('所选顾客暂无手机号');
  295 + return;
  296 + }
  297 +
  298 + final labels = customerMakingProvider.labels ?? '';
  299 + final targetGroupNames = labels
  300 + .split(',')
  301 + .map((e) => e.trim())
  302 + .where((e) => e.isNotEmpty)
  303 + .toList();
  304 +
  305 + final params = <String, dynamic>{
  306 + 'sellerid': userModel.user!.sellerid,
  307 + 'phones': json.encode(phones.toSet().toList()),
  308 + 'benefit_type': _giftType == 0 ? 1 : 2,
  309 + 'greeting': greeting,
  310 + };
  311 + if (targetGroupNames.isNotEmpty) {
  312 + params['target_group_names'] = json.encode(targetGroupNames);
  313 + }
  314 +
  315 + final uidValue = widget.isSeller ? userModel.user!.openid : widget.uid;
  316 + final uidInt = int.tryParse(uidValue ?? '');
  317 + if (uidInt != null && uidInt > 0) {
  318 + params['uid'] = uidInt;
  319 + } else if (uidValue != null && uidValue.isNotEmpty) {
  320 + params['uid'] = uidValue;
  321 + }
  322 +
  323 + if (_giftType == 0) {
  324 + params['consume_amount'] =
  325 + double.tryParse(_amountController.text.trim()) ?? 0;
  326 + } else if (_selectedCouponIndex != null) {
  327 + params['aid'] = int.tryParse(
  328 + customerProvider.couponListSelect![_selectedCouponIndex!].id ??
  329 + '') ??
  330 + 0;
  331 + }
  332 +
  333 + final importResult = await customerProvider
  334 + .importActivityPhoneCoupon(context, params, showLoading: true);
  335 + if (!mounted) return;
  336 + if (importResult == null) return;
  337 +
  338 + final marketingId = _extractMarketingId(importResult);
  339 + if (marketingId == null || marketingId <= 0) {
  340 + Utils.showToast('创建营销活动失败');
  341 + return;
  342 + }
  343 +
  344 + final success = await customerProvider.sendPhoneMarketingSms(
  345 + context,
  346 + <String, dynamic>{
  347 + 'sellerid': userModel.user!.sellerid,
  348 + 'marketing_id': marketingId,
  349 + },
  350 + showLoading: true);
  351 + if (!mounted) return;
  352 + if (!success) return;
  353 +
  354 + Utils.showToast('执行成功');
  355 + if (Navigator.canPop(context)) {
  356 + Navigator.pop(context);
  357 + }
  358 + }
  359 +
  360 + int? _extractMarketingId(dynamic data) {
  361 + if (data is Map) {
  362 + for (final key in ['marketing_id', 'id']) {
  363 + if (data.containsKey(key)) {
  364 + final value = data[key];
  365 + final parsed = _extractMarketingId(value);
  366 + if (parsed != null) return parsed;
  367 + }
  368 + }
  369 + for (final value in data.values) {
  370 + final parsed = _extractMarketingId(value);
  371 + if (parsed != null) return parsed;
  372 + }
  373 + } else if (data is List) {
  374 + for (final value in data) {
  375 + final parsed = _extractMarketingId(value);
  376 + if (parsed != null) return parsed;
  377 + }
  378 + } else if (data is num) {
  379 + return data.toInt();
  380 + } else if (data is String) {
  381 + return int.tryParse(data);
  382 + }
  383 + return null;
  384 + }
  385 +
  386 + @override
  387 + Widget build(BuildContext context) {
  388 + customerProvider = Provider.of<CustomerProvider>(context, listen: true);
  389 + customerMakingProvider =
  390 + Provider.of<CustomerMakingProvider>(context, listen: true);
  391 +
  392 + return Scaffold(
  393 + backgroundColor: MyColours.color_ffffff,
  394 + appBar: SystemAppBar(
  395 + '创建营销活动',
  396 + ),
  397 + body: SingleChildScrollView(
  398 + child: Column(
  399 + crossAxisAlignment: CrossAxisAlignment.stretch,
  400 + children: [
  401 + _buildSelectedBar(),
  402 + _buildSectionRow('赠送优惠选择'),
  403 + _buildGiftMoneyRow(),
  404 + _buildGiftCouponRow(),
  405 + _buildSectionRow('问候语(点击可以编写)'),
  406 + _buildGreetingInput(),
  407 + _buildSectionRow('短信内容预览'),
  408 + _buildPreviewBox(),
  409 + SizedBox(height: w(3)),
  410 + _buildBottomButton(),
  411 + ],
  412 + ),
  413 + ),
  414 + );
  415 + }
  416 +
  417 + Widget _buildSelectedBar() {
  418 + return Container(
  419 + color: MyColours.app_main,
  420 + padding: EdgeInsets.symmetric(horizontal: w(16), vertical: w(16)),
  421 + child: Row(
  422 + children: [
  423 + Expanded(
  424 + child: Text(
  425 + _selectedSummary,
  426 + maxLines: 1,
  427 + overflow: TextOverflow.ellipsis,
  428 + style: TextStyle(
  429 + color: MyColours.color_ffffff,
  430 + fontSize: font(14),
  431 + ),
  432 + ),
  433 + ),
  434 + InkWell(
  435 + onTap: _viewCustomers,
  436 + child: Padding(
  437 + padding: EdgeInsets.only(left: w(16)),
  438 + child: Text(
  439 + '查看',
  440 + style: TextStyle(
  441 + color: MyColours.color_ffffff,
  442 + fontSize: font(14),
  443 + decoration: TextDecoration.underline,
  444 + ),
  445 + ),
  446 + ),
  447 + ),
  448 + ],
  449 + ),
  450 + );
  451 + }
  452 +
  453 + Widget _buildSectionRow(String title) {
  454 + return Container(
  455 + height: w(48),
  456 + padding: EdgeInsets.symmetric(horizontal: w(16)),
  457 + alignment: Alignment.centerLeft,
  458 + decoration: BoxDecoration(
  459 + color: MyColours.color_ffffff,
  460 + border: Border(
  461 + bottom: BorderSide(
  462 + color: MyColours.color_E4E4E4,
  463 + width: w(1),
  464 + ),
  465 + ),
  466 + ),
  467 + child: Text(
  468 + title,
  469 + style: TextStyle(
  470 + color: MyColours.color_27262B,
  471 + fontSize: font(16),
  472 + fontWeight: FontWeight.w400,
  473 + ),
  474 + ),
  475 + );
  476 + }
  477 +
  478 + Widget _buildGiftMoneyRow() {
  479 + return _buildGiftOptionRow(
  480 + type: 0,
  481 + title: '发放消费金',
  482 + trailing: Row(
  483 + mainAxisSize: MainAxisSize.min,
  484 + children: [
  485 + _buildAmountInput(),
  486 + SizedBox(width: w(6)),
  487 + Text(
  488 + '元',
  489 + style: TextStyle(
  490 + color: MyColours.color_27262B,
  491 + fontSize: font(14),
  492 + ),
  493 + ),
  494 + ],
  495 + ),
  496 + alignTrailingRight: false,
  497 + );
  498 + }
  499 +
  500 + Widget _buildGiftCouponRow() {
  501 + return _buildGiftOptionRow(
  502 + type: 1,
  503 + title: '赠送优惠券',
  504 + trailing: _buildCouponSelect(),
  505 + );
  506 + }
  507 +
  508 + Widget _buildGiftOptionRow({
  509 + required int type,
  510 + required String title,
  511 + required Widget trailing,
  512 + bool alignTrailingRight = true,
  513 + }) {
  514 + final selected = _giftType == type;
  515 + return InkWell(
  516 + onTap: () {
  517 + setState(() {
  518 + _giftType = type;
  519 + });
  520 + },
  521 + child: Container(
  522 + height: w(40),
  523 + padding: EdgeInsets.only(left: w(16), right: w(16)),
  524 + decoration: BoxDecoration(
  525 + color: MyColours.color_ffffff,
  526 + border: Border(
  527 + bottom: BorderSide(
  528 + color: MyColours.color_E4E4E4,
  529 + width: w(1),
  530 + ),
  531 + ),
  532 + ),
  533 + child: Row(
  534 + children: [
  535 + Image.asset(
  536 + Utils.getImgPath(selected ? 'f_select' : 'f_select_not'),
  537 + width: w(18),
  538 + height: w(18),
  539 + ),
  540 + SizedBox(width: w(8)),
  541 + Text(
  542 + title,
  543 + style: TextStyle(
  544 + color: MyColours.color_27262B,
  545 + fontSize: font(14),
  546 + ),
  547 + ),
  548 + if (alignTrailingRight) const Spacer(),
  549 + if (!alignTrailingRight) SizedBox(width: w(10)),
  550 + trailing,
  551 + ],
  552 + ),
  553 + ),
  554 + );
  555 + }
  556 +
  557 + Widget _buildAmountInput() {
  558 + return Container(
  559 + width: w(150),
  560 + height: w(30),
  561 + padding: EdgeInsets.symmetric(horizontal: w(8)),
  562 + alignment: Alignment.center,
  563 + decoration: BoxDecoration(
  564 + color: MyColours.color_ffffff,
  565 + border: Border.all(
  566 + color: MyColours.color_B7B7B7,
  567 + width: w(1),
  568 + ),
  569 + ),
  570 + child: TextField(
  571 + controller: _amountController,
  572 + keyboardType: const TextInputType.numberWithOptions(decimal: true),
  573 + maxLines: 1,
  574 + textAlignVertical: TextAlignVertical.center,
  575 + style: TextStyle(
  576 + color: MyColours.color_27262B,
  577 + fontSize: font(10),
  578 + ),
  579 + decoration: InputDecoration(
  580 + isCollapsed: true,
  581 + border: InputBorder.none,
  582 + hintText: '请输入数字',
  583 + hintStyle: TextStyle(
  584 + color: MyColours.color_999999,
  585 + fontSize: font(10),
  586 + ),
  587 + ),
  588 + ),
  589 + );
  590 + }
  591 +
  592 + Widget _buildCouponSelect() {
  593 + return InkWell(
  594 + onTap: _selectCoupon,
  595 + child: Container(
  596 + height: w(30),
  597 + padding: EdgeInsets.symmetric(horizontal: w(10), vertical: w(4)),
  598 + decoration: BoxDecoration(
  599 + color: MyColours.color_E4E4E4,
  600 + borderRadius: BorderRadius.circular(r(5)),
  601 + ),
  602 + child: Row(
  603 + mainAxisSize: MainAxisSize.min,
  604 + children: [
  605 + Text(
  606 + _selectedCouponName ?? '请选择优惠券',
  607 + style: TextStyle(
  608 + color: MyColours.app_main,
  609 + fontSize: font(12),
  610 + ),
  611 + ),
  612 + SizedBox(width: w(6)),
  613 + Image.asset(
  614 + Utils.getImgPath('down_c'),
  615 + width: w(22),
  616 + height: w(22),
  617 + ),
  618 + ],
  619 + ),
  620 + ),
  621 + );
  622 + }
  623 +
  624 + Widget _buildGreetingInput() {
  625 + return Container(
  626 + height: w(116),
  627 + margin: EdgeInsets.symmetric(horizontal: w(16), vertical: w(3)),
  628 + padding: EdgeInsets.all(w(10)),
  629 + decoration: BoxDecoration(
  630 + color: MyColours.color_ffffff,
  631 + borderRadius: BorderRadius.circular(r(8)),
  632 + border: Border.all(
  633 + color: MyColours.color_B7B7B7,
  634 + width: w(1),
  635 + ),
  636 + ),
  637 + child: TextField(
  638 + controller: _greetingController,
  639 + keyboardType: TextInputType.multiline,
  640 + maxLines: 4,
  641 + minLines: 4,
  642 + textAlignVertical: TextAlignVertical.top,
  643 + style: TextStyle(
  644 + color: MyColours.color_27262B,
  645 + fontSize: font(14),
  646 + ),
  647 + decoration: InputDecoration(
  648 + border: InputBorder.none,
  649 + isCollapsed: true,
  650 + hintText: '请输入问候语',
  651 + hintStyle: TextStyle(
  652 + color: MyColours.color_999999,
  653 + fontSize: font(14),
  654 + ),
  655 + ),
  656 + ),
  657 + );
  658 + }
  659 +
  660 + Widget _buildPreviewBox() {
  661 + return Container(
  662 + height: w(220),
  663 + margin: EdgeInsets.symmetric(horizontal: w(16), vertical: w(3)),
  664 + padding: EdgeInsets.all(w(10)),
  665 + decoration: BoxDecoration(
  666 + color: MyColours.color_ffffff,
  667 + borderRadius: BorderRadius.circular(r(8)),
  668 + border: Border.all(
  669 + color: MyColours.color_B7B7B7,
  670 + width: w(1),
  671 + ),
  672 + ),
  673 + child: Text(
  674 + _buildPreview(),
  675 + maxLines: 9,
  676 + overflow: TextOverflow.ellipsis,
  677 + style: TextStyle(
  678 + color: MyColours.color_999999,
  679 + fontSize: font(14),
  680 + height: 1.4,
  681 + ),
  682 + ),
  683 + );
  684 + }
  685 +
  686 + Widget _buildBottomButton() {
  687 + return Container(
  688 + padding: EdgeInsets.only(
  689 + left: w(45),
  690 + right: w(45),
  691 + top: w(12),
  692 + bottom: w(26),
  693 + ),
  694 + child: SizedBox(
  695 + width: double.infinity,
  696 + height: w(42),
  697 + child: ElevatedButton(
  698 + onPressed: _confirm,
  699 + style: ElevatedButton.styleFrom(
  700 + backgroundColor: MyColours.app_main,
  701 + foregroundColor: MyColours.color_ffffff,
  702 + elevation: 0,
  703 + shape: RoundedRectangleBorder(
  704 + borderRadius: BorderRadius.circular(r(5)),
  705 + ),
  706 + ),
  707 + child: Text(
  708 + '确认执行',
  709 + style: TextStyle(
  710 + color: MyColours.color_ffffff,
  711 + fontSize: font(16),
  712 + fontWeight: FontWeight.w500,
  713 + ),
  714 + ),
  715 + ),
  716 + ),
  717 + );
  718 + }
  719 +}
@@ -463,7 +463,8 @@ class _MyCustomerListPageState extends State<MyCustomerListPage> @@ -463,7 +463,8 @@ class _MyCustomerListPageState extends State<MyCustomerListPage>
463 customerMakingProvider.selectCustomer = 463 customerMakingProvider.selectCustomer =
464 customerProvider!.selectCustomer; 464 customerProvider!.selectCustomer;
465 customerMakingProvider.fromPage = widget.fromPage; 465 customerMakingProvider.fromPage = widget.fromPage;
466 - NavigatorUtil.goMyMakeMarkingPage(context, widget.uid); 466 + NavigatorUtil.goCreateMarketingPlanPage(context,
  467 + isSeller: false, uid: widget.uid);
467 } else { 468 } else {
468 customerMakingProvider.labels = customerProvider!.labels; 469 customerMakingProvider.labels = customerProvider!.labels;
469 customerMakingProvider.selectCustomer = 470 customerMakingProvider.selectCustomer =
@@ -526,9 +526,9 @@ class _CustomerListPageState extends State<CustomerListPage> @@ -526,9 +526,9 @@ class _CustomerListPageState extends State<CustomerListPage>
526 customerMakingProvider.selectCustomerSeller = 526 customerMakingProvider.selectCustomerSeller =
527 customerProvider!.selectCustomerSeller; 527 customerProvider!.selectCustomerSeller;
528 customerMakingProvider.fromPage = widget.fromPage; 528 customerMakingProvider.fromPage = widget.fromPage;
529 - NavigatorUtil.goMakeMarkingPage(  
530 - context, {'object': widget.sellerUser},  
531 - fromPage: FromPage.FromPageCustomerList); 529 + NavigatorUtil.goCreateMarketingPlanPage(context,
  530 + isSeller: true,
  531 + sellerUserObject: {'object': widget.sellerUser});
532 } else { 532 } else {
533 customerMakingProvider.labels = customerProvider!.labels; 533 customerMakingProvider.labels = customerProvider!.labels;
534 customerMakingProvider.selectCustomerSeller = 534 customerMakingProvider.selectCustomerSeller =
@@ -588,13 +588,10 @@ class CustomerProvider extends ChangeNotifier { @@ -588,13 +588,10 @@ class CustomerProvider extends ChangeNotifier {
588 CouponModel.fromJson(baseResp.responseData as Map<String, dynamic>) 588 CouponModel.fromJson(baseResp.responseData as Map<String, dynamic>)
589 .data! 589 .data!
590 .cast<CouponSunModel>(); 590 .cast<CouponSunModel>();
591 - // 屏蔽type==2 的券 我的优惠券 591 + // 只保留免费优惠券(免费代金券 type == 3)
592 List<CouponSunModel> temp = []; 592 List<CouponSunModel> temp = [];
593 for (int i = 0; i < couponListSelect!.length; i++) { 593 for (int i = 0; i < couponListSelect!.length; i++) {
594 - if (couponListSelect![i].isType == '2' ||  
595 - (couponListSelect![i].isType == '1' &&  
596 - couponListSelect![i].isExamine != '1')) {  
597 - } else { 594 + if (couponListSelect![i].isType == '3') {
598 temp.add(couponListSelect![i]); 595 temp.add(couponListSelect![i]);
599 } 596 }
600 } 597 }
@@ -1265,6 +1262,74 @@ class CustomerProvider extends ChangeNotifier { @@ -1265,6 +1262,74 @@ class CustomerProvider extends ChangeNotifier {
1265 } 1262 }
1266 } 1263 }
1267 1264
  1265 + ///创建手机号营销活动并发放权益(消费金/优惠券)
  1266 + Future<Map<String, dynamic>?> importActivityPhoneCoupon(
  1267 + BuildContext context, Map<String, dynamic> params,
  1268 + {bool showLoading = false}) async {
  1269 + try {
  1270 + BaseResp<dynamic> baseResp = await DioUtil().request<dynamic>(
  1271 + context, Method.post, HttpUrl.import_activity_phone_coupon,
  1272 + data: params, showLoading: showLoading);
  1273 + if (baseResp != null && baseResp.code == Constant.status_success) {
  1274 + final data = _normalizeActivityData(baseResp.data);
  1275 + if (data != null) {
  1276 + return data;
  1277 + }
  1278 + return _normalizeActivityData(baseResp.responseData?['data']);
  1279 + } else {
  1280 + if (baseResp != null) {
  1281 + Utils.showToast(baseResp.msg ?? "");
  1282 + }
  1283 + }
  1284 + } catch (error) {
  1285 + print(error.toString());
  1286 + }
  1287 + return null;
  1288 + }
  1289 +
  1290 + ///确认执行:发送手机号营销活动短信
  1291 + Future<bool> sendPhoneMarketingSms(
  1292 + BuildContext context, Map<String, dynamic> params,
  1293 + {bool showLoading = false}) async {
  1294 + try {
  1295 + BaseResp<dynamic> baseResp = await DioUtil().request<dynamic>(
  1296 + context, Method.post, HttpUrl.send_phone_marketing_sms,
  1297 + data: params, showLoading: showLoading);
  1298 + if (baseResp != null && baseResp.code == Constant.status_success) {
  1299 + return true;
  1300 + } else {
  1301 + if (baseResp != null) {
  1302 + Utils.showToast(baseResp.msg ?? "");
  1303 + }
  1304 + }
  1305 + } catch (error) {
  1306 + print(error.toString());
  1307 + }
  1308 + return false;
  1309 + }
  1310 +
  1311 + Map<String, dynamic>? _normalizeActivityData(dynamic data) {
  1312 + if (data is Map<String, dynamic>) {
  1313 + return data;
  1314 + }
  1315 + if (data is Map) {
  1316 + return Map<String, dynamic>.from(data);
  1317 + }
  1318 + if (data is List) {
  1319 + if (data.isEmpty) {
  1320 + return {};
  1321 + }
  1322 + final first = data.first;
  1323 + if (first is Map<String, dynamic>) {
  1324 + return first;
  1325 + }
  1326 + if (first is Map) {
  1327 + return Map<String, dynamic>.from(first);
  1328 + }
  1329 + }
  1330 + return null;
  1331 + }
  1332 +
1268 ///催促未完成任务经理发消息 1333 ///催促未完成任务经理发消息
1269 Future<bool> sendMarketingMsgByMid( 1334 Future<bool> sendMarketingMsgByMid(
1270 BuildContext context, Map<String, String?> params, 1335 BuildContext context, Map<String, String?> params,
@@ -958,6 +958,20 @@ class NavigatorUtil { @@ -958,6 +958,20 @@ class NavigatorUtil {
958 replace: replace); 958 replace: replace);
959 } 959 }
960 960
  961 + /// 创建营销计划
  962 + static Future<dynamic> goCreateMarketingPlanPage(
  963 + BuildContext context, {
  964 + required bool isSeller,
  965 + String? uid,
  966 + Map? sellerUserObject,
  967 + replace = false,
  968 + }) {
  969 + return _navigateTo(
  970 + context,
  971 + "${Routes.create_marketing_plan_page}?isSeller=${isSeller}&uid=${uid ?? ''}&sellerUserObject=${FluroConvertUtils.object2string(sellerUserObject ?? {'object': []})}",
  972 + replace: replace);
  973 + }
  974 +
961 /// 我的创建营销 975 /// 我的创建营销
962 static Future<dynamic> goMyMakeMarkingPage(BuildContext context, String? uid, 976 static Future<dynamic> goMyMakeMarkingPage(BuildContext context, String? uid,
963 {replace = false, 977 {replace = false,
@@ -69,6 +69,7 @@ import 'package:one_key/pages/my/consume/return_money_page.dart'; @@ -69,6 +69,7 @@ import 'package:one_key/pages/my/consume/return_money_page.dart';
69 import 'package:one_key/pages/my/customer_type/custom_label_page.dart'; 69 import 'package:one_key/pages/my/customer_type/custom_label_page.dart';
70 import 'package:one_key/pages/my/customer_type/customer_home_page.dart'; 70 import 'package:one_key/pages/my/customer_type/customer_home_page.dart';
71 import 'package:one_key/pages/my/customer_type/customer_statistics_page.dart'; 71 import 'package:one_key/pages/my/customer_type/customer_statistics_page.dart';
  72 +import 'package:one_key/pages/my/customer_type/create_marketing_plan_page.dart';
72 73
73 import 'package:one_key/pages/my/customer_type/my_customer/my_customer_detail_edit_page.dart'; 74 import 'package:one_key/pages/my/customer_type/my_customer/my_customer_detail_edit_page.dart';
74 import 'package:one_key/pages/my/customer_type/my_customer/my_customer_detail_page.dart'; 75 import 'package:one_key/pages/my/customer_type/my_customer/my_customer_detail_page.dart';
@@ -1764,6 +1765,29 @@ var makeMarkingPageHandle = new Handler( @@ -1764,6 +1765,29 @@ var makeMarkingPageHandle = new Handler(
1764 child: MakeMarkingPage(fromPage: fromPage, sellerUser: sellerUser)); 1765 child: MakeMarkingPage(fromPage: fromPage, sellerUser: sellerUser));
1765 }); 1766 });
1766 1767
  1768 +///创建营销计划
  1769 +var createMarketingPlanPageHandle = new Handler(
  1770 + handlerFunc: (BuildContext? context, Map<dynamic, List<Object>> params) {
  1771 + bool isSeller =
  1772 + FluroConvertUtils.string2bool(params["isSeller"]!.first as String);
  1773 + String uid = params["uid"]!.first as String;
  1774 + Map object =
  1775 + FluroConvertUtils.string2map(params["sellerUserObject"]!.first as String)!;
  1776 + List temp = object['object'] ?? [];
  1777 + List<storeCustomerHomeModel.SellerUser> sellerUser = [];
  1778 +
  1779 + for (var item in temp) {
  1780 + sellerUser.add(storeCustomerHomeModel.SellerUser.fromJson(item));
  1781 + }
  1782 +
  1783 + return ChangeNotifierProvider<CustomerProvider>(
  1784 + create: (_) => CustomerProvider(),
  1785 + child: CreateMarketingPlanPage(
  1786 + isSeller: isSeller,
  1787 + uid: uid.isEmpty ? null : uid,
  1788 + sellerUser: sellerUser));
  1789 +});
  1790 +
1767 ///我的创建营销 1791 ///我的创建营销
1768 var myMakeMarkingPageHandle = new Handler( 1792 var myMakeMarkingPageHandle = new Handler(
1769 handlerFunc: (BuildContext? context, Map<dynamic, List<Object>> params) { 1793 handlerFunc: (BuildContext? context, Map<dynamic, List<Object>> params) {
@@ -122,6 +122,7 @@ class Routes { @@ -122,6 +122,7 @@ class Routes {
122 static String my_marketing_records_detail_page = 122 static String my_marketing_records_detail_page =
123 "/my_marketing_records_detail_page"; //全店营销记录详情 123 "/my_marketing_records_detail_page"; //全店营销记录详情
124 static String my_make_marketing_page = "/my_make_marketing_page"; //创建营销 124 static String my_make_marketing_page = "/my_make_marketing_page"; //创建营销
  125 + static String create_marketing_plan_page = "/create_marketing_plan_page"; //创建营销计划
125 static String my_customer_detail_page = "/my_customer_detail_page"; //客户详情 126 static String my_customer_detail_page = "/my_customer_detail_page"; //客户详情
126 static String my_customer_detail_page_sim = 127 static String my_customer_detail_page_sim =
127 "/my_customer_detail_page_sim"; //客户详情 128 "/my_customer_detail_page_sim"; //客户详情
@@ -605,6 +606,8 @@ class Routes { @@ -605,6 +606,8 @@ class Routes {
605 router.define(my_marketing_records_detail_page, 606 router.define(my_marketing_records_detail_page,
606 handler: myMarketingRecordsDetailPageHandle); 607 handler: myMarketingRecordsDetailPageHandle);
607 router.define(my_make_marketing_page, handler: myMakeMarkingPageHandle); 608 router.define(my_make_marketing_page, handler: myMakeMarkingPageHandle);
  609 + router.define(create_marketing_plan_page,
  610 + handler: createMarketingPlanPageHandle);
608 router.define(my_customer_detail_page, handler: myCustomerDetailPageHandle); 611 router.define(my_customer_detail_page, handler: myCustomerDetailPageHandle);
609 router.define(my_customer_detail_page_sim, 612 router.define(my_customer_detail_page_sim,
610 handler: myCustomerDetailPageSimHandle); 613 handler: myCustomerDetailPageSimHandle);
@@ -14,6 +14,8 @@ class SystemAppBar extends StatelessWidget implements PreferredSizeWidget { @@ -14,6 +14,8 @@ class SystemAppBar extends StatelessWidget implements PreferredSizeWidget {
14 final Color bgColor; 14 final Color bgColor;
15 final bool needWhite; 15 final bool needWhite;
16 final Color titleColor; 16 final Color titleColor;
  17 + final double backIconWidth;
  18 + final double backIconHeight;
17 19
18 const SystemAppBar(this.title, 20 const SystemAppBar(this.title,
19 {Key? key, 21 {Key? key,
@@ -24,7 +26,9 @@ class SystemAppBar extends StatelessWidget implements PreferredSizeWidget { @@ -24,7 +26,9 @@ class SystemAppBar extends StatelessWidget implements PreferredSizeWidget {
24 this.bgColor = MyColours.color_ffffff, 26 this.bgColor = MyColours.color_ffffff,
25 this.needWhite = false, 27 this.needWhite = false,
26 this.leftTitle = '', 28 this.leftTitle = '',
27 - this.titleColor = MyColours.color_353535}) 29 + this.titleColor = MyColours.color_353535,
  30 + this.backIconWidth = 20,
  31 + this.backIconHeight = 31})
28 : super(key: key); 32 : super(key: key);
29 33
30 @override 34 @override
@@ -58,8 +62,8 @@ class SystemAppBar extends StatelessWidget implements PreferredSizeWidget { @@ -58,8 +62,8 @@ class SystemAppBar extends StatelessWidget implements PreferredSizeWidget {
58 needWhite 62 needWhite
59 ? "assets/icon_white.png" 63 ? "assets/icon_white.png"
60 : "assets/icon_back.png", 64 : "assets/icon_back.png",
61 - width: ScreenUtil().setWidth(20),  
62 - height: ScreenUtil().setWidth(31), 65 + width: ScreenUtil().setWidth(backIconWidth),
  66 + height: ScreenUtil().setWidth(backIconHeight),
63 ), 67 ),
64 leftTitle != '' 68 leftTitle != ''
65 ? Padding( 69 ? Padding(