nes-code">
+    "610327": "陇县",
3474
+    "610328": "千阳县",
3475
+    "610329": "麟游县",
3476
+    "610330": "凤县",
3477
+    "610331": "太白县"
3478
+  },
3479
+  "610400": {
3480
+    "610402": "秦都区",
3481
+    "610403": "杨陵区",
3482
+    "610404": "渭城区",
3483
+    "610422": "三原县",
3484
+    "610423": "泾阳县",
3485
+    "610424": "乾县",
3486
+    "610425": "礼泉县",
3487
+    "610426": "永寿县",
3488
+    "610427": "彬县",
3489
+    "610428": "长武县",
3490
+    "610429": "旬邑县",
3491
+    "610430": "淳化县",
3492
+    "610431": "武功县",
3493
+    "610481": "兴平市"
3494
+  },
3495
+  "610500": {
3496
+    "610502": "临渭区",
3497
+    "610503": "华州区",
3498
+    "610522": "潼关县",
3499
+    "610523": "大荔县",
3500
+    "610524": "合阳县",
3501
+    "610525": "澄城县",
3502
+    "610526": "蒲城县",
3503
+    "610527": "白水县",
3504
+    "610528": "富平县",
3505
+    "610581": "韩城市",
3506
+    "610582": "华阴市"
3507
+  },
3508
+  "610600": {
3509
+    "610602": "宝塔区",
3510
+    "610603": "安塞区",
3511
+    "610621": "延长县",
3512
+    "610622": "延川县",
3513
+    "610623": "子长县",
3514
+    "610625": "志丹县",
3515
+    "610626": "吴起县",
3516
+    "610627": "甘泉县",
3517
+    "610628": "富县",
3518
+    "610629": "洛川县",
3519
+    "610630": "宜川县",
3520
+    "610631": "黄龙县",
3521
+    "610632": "黄陵县"
3522
+  },
3523
+  "610700": {
3524
+    "610702": "汉台区",
3525
+    "610721": "南郑县",
3526
+    "610722": "城固县",
3527
+    "610723": "洋县",
3528
+    "610724": "西乡县",
3529
+    "610725": "勉县",
3530
+    "610726": "宁强县",
3531
+    "610727": "略阳县",
3532
+    "610728": "镇巴县",
3533
+    "610729": "留坝县",
3534
+    "610730": "佛坪县"
3535
+  },
3536
+  "610800": {
3537
+    "610802": "榆阳区",
3538
+    "610803": "横山区",
3539
+    "610821": "神木县",
3540
+    "610822": "府谷县",
3541
+    "610824": "靖边县",
3542
+    "610825": "定边县",
3543
+    "610826": "绥德县",
3544
+    "610827": "米脂县",
3545
+    "610828": "佳县",
3546
+    "610829": "吴堡县",
3547
+    "610830": "清涧县",
3548
+    "610831": "子洲县"
3549
+  },
3550
+  "610900": {
3551
+    "610902": "汉滨区",
3552
+    "610921": "汉阴县",
3553
+    "610922": "石泉县",
3554
+    "610923": "宁陕县",
3555
+    "610924": "紫阳县",
3556
+    "610925": "岚皋县",
3557
+    "610926": "平利县",
3558
+    "610927": "镇坪县",
3559
+    "610928": "旬阳县",
3560
+    "610929": "白河县"
3561
+  },
3562
+  "611000": {
3563
+    "611002": "商州区",
3564
+    "611021": "洛南县",
3565
+    "611022": "丹凤县",
3566
+    "611023": "商南县",
3567
+    "611024": "山阳县",
3568
+    "611025": "镇安县",
3569
+    "611026": "柞水县"
3570
+  },
3571
+  "620000": {
3572
+    "620100": "兰州市",
3573
+    "620200": "嘉峪关市",
3574
+    "620300": "金昌市",
3575
+    "620400": "白银市",
3576
+    "620500": "天水市",
3577
+    "620600": "武威市",
3578
+    "620700": "张掖市",
3579
+    "620800": "平凉市",
3580
+    "620900": "酒泉市",
3581
+    "621000": "庆阳市",
3582
+    "621100": "定西市",
3583
+    "621200": "陇南市",
3584
+    "622900": "临夏回族自治州",
3585
+    "623000": "甘南藏族自治州"
3586
+  },
3587
+  "620100": {
3588
+    "620102": "城关区",
3589
+    "620103": "七里河区",
3590
+    "620104": "西固区",
3591
+    "620105": "安宁区",
3592
+    "620111": "红古区",
3593
+    "620121": "永登县",
3594
+    "620122": "皋兰县",
3595
+    "620123": "榆中县"
3596
+  },
3597
+  "620300": {
3598
+    "620302": "金川区",
3599
+    "620321": "永昌县"
3600
+  },
3601
+  "620400": {
3602
+    "620402": "白银区",
3603
+    "620403": "平川区",
3604
+    "620421": "靖远县",
3605
+    "620422": "会宁县",
3606
+    "620423": "景泰县"
3607
+  },
3608
+  "620500": {
3609
+    "620502": "秦州区",
3610
+    "620503": "麦积区",
3611
+    "620521": "清水县",
3612
+    "620522": "秦安县",
3613
+    "620523": "甘谷县",
3614
+    "620524": "武山县",
3615
+    "620525": "张家川回族自治县"
3616
+  },
3617
+  "620600": {
3618
+    "620602": "凉州区",
3619
+    "620621": "民勤县",
3620
+    "620622": "古浪县",
3621
+    "620623": "天祝藏族自治县"
3622
+  },
3623
+  "620700": {
3624
+    "620702": "甘州区",
3625
+    "620721": "肃南裕固族自治县",
3626
+    "620722": "民乐县",
3627
+    "620723": "临泽县",
3628
+    "620724": "高台县",
3629
+    "620725": "山丹县"
3630
+  },
3631
+  "620800": {
3632
+    "620802": "崆峒区",
3633
+    "620821": "泾川县",
3634
+    "620822": "灵台县",
3635
+    "620823": "崇信县",
3636
+    "620824": "华亭县",
3637
+    "620825": "庄浪县",
3638
+    "620826": "静宁县"
3639
+  },
3640
+  "620900": {
3641
+    "620902": "肃州区",
3642
+    "620921": "金塔县",
3643
+    "620922": "瓜州县",
3644
+    "620923": "肃北蒙古族自治县",
3645
+    "620924": "阿克塞哈萨克族自治县",
3646
+    "620981": "玉门市",
3647
+    "620982": "敦煌市"
3648
+  },
3649
+  "621000": {
3650
+    "621002": "西峰区",
3651
+    "621021": "庆城县",
3652
+    "621022": "环县",
3653
+    "621023": "华池县",
3654
+    "621024": "合水县",
3655
+    "621025": "正宁县",
3656
+    "621026": "宁县",
3657
+    "621027": "镇原县"
3658
+  },
3659
+  "621100": {
3660
+    "621102": "安定区",
3661
+    "621121": "通渭县",
3662
+    "621122": "陇西县",
3663
+    "621123": "渭源县",
3664
+    "621124": "临洮县",
3665
+    "621125": "漳县",
3666
+    "621126": "岷县"
3667
+  },
3668
+  "621200": {
3669
+    "621202": "武都区",
3670
+    "621221": "成县",
3671
+    "621222": "文县",
3672
+    "621223": "宕昌县",
3673
+    "621224": "康县",
3674
+    "621225": "西和县",
3675
+    "621226": "礼县",
3676
+    "621227": "徽县",
3677
+    "621228": "两当县"
3678
+  },
3679
+  "622900": {
3680
+    "622901": "临夏市",
3681
+    "622921": "临夏县",
3682
+    "622922": "康乐县",
3683
+    "622923": "永靖县",
3684
+    "622924": "广河县",
3685
+    "622925": "和政县",
3686
+    "622926": "东乡族自治县",
3687
+    "622927": "积石山保安族东乡族撒拉族自治县"
3688
+  },
3689
+  "623000": {
3690
+    "623001": "合作市",
3691
+    "623021": "临潭县",
3692
+    "623022": "卓尼县",
3693
+    "623023": "舟曲县",
3694
+    "623024": "迭部县",
3695
+    "623025": "玛曲县",
3696
+    "623026": "碌曲县",
3697
+    "623027": "夏河县"
3698
+  },
3699
+  "630000": {
3700
+    "630100": "西宁市",
3701
+    "630200": "海东市",
3702
+    "632200": "海北藏族自治州",
3703
+    "632300": "黄南藏族自治州",
3704
+    "632500": "海南藏族自治州",
3705
+    "632600": "果洛藏族自治州",
3706
+    "632700": "玉树藏族自治州",
3707
+    "632800": "海西蒙古族藏族自治州"
3708
+  },
3709
+  "630100": {
3710
+    "630102": "城东区",
3711
+    "630103": "城中区",
3712
+    "630104": "城西区",
3713
+    "630105": "城北区",
3714
+    "630121": "大通回族土族自治县",
3715
+    "630122": "湟中县",
3716
+    "630123": "湟源县"
3717
+  },
3718
+  "630200": {
3719
+    "630202": "乐都区",
3720
+    "630203": "平安区",
3721
+    "630222": "民和回族土族自治县",
3722
+    "630223": "互助土族自治县",
3723
+    "630224": "化隆回族自治县",
3724
+    "630225": "循化撒拉族自治县"
3725
+  },
3726
+  "632200": {
3727
+    "632221": "门源回族自治县",
3728
+    "632222": "祁连县",
3729
+    "632223": "海晏县",
3730
+    "632224": "刚察县"
3731
+  },
3732
+  "632300": {
3733
+    "632321": "同仁县",
3734
+    "632322": "尖扎县",
3735
+    "632323": "泽库县",
3736
+    "632324": "河南蒙古族自治县"
3737
+  },
3738
+  "632500": {
3739
+    "632521": "共和县",
3740
+    "632522": "同德县",
3741
+    "632523": "贵德县",
3742
+    "632524": "兴海县",
3743
+    "632525": "贵南县"
3744
+  },
3745
+  "632600": {
3746
+    "632621": "玛沁县",
3747
+    "632622": "班玛县",
3748
+    "632623": "甘德县",
3749
+    "632624": "达日县",
3750
+    "632625": "久治县",
3751
+    "632626": "玛多县"
3752
+  },
3753
+  "632700": {
3754
+    "632701": "玉树市",
3755
+    "632722": "杂多县",
3756
+    "632723": "称多县",
3757
+    "632724": "治多县",
3758
+    "632725": "囊谦县",
3759
+    "632726": "曲麻莱县"
3760
+  },
3761
+  "632800": {
3762
+    "632801": "格尔木市",
3763
+    "632802": "德令哈市",
3764
+    "632821": "乌兰县",
3765
+    "632822": "都兰县",
3766
+    "632823": "天峻县"
3767
+  },
3768
+  "640000": {
3769
+    "640100": "银川市",
3770
+    "640200": "石嘴山市",
3771
+    "640300": "吴忠市",
3772
+    "640400": "固原市",
3773
+    "640500": "中卫市"
3774
+  },
3775
+  "640100": {
3776
+    "640104": "兴庆区",
3777
+    "640105": "西夏区",
3778
+    "640106": "金凤区",
3779
+    "640121": "永宁县",
3780
+    "640122": "贺兰县",
3781
+    "640181": "灵武市"
3782
+  },
3783
+  "640200": {
3784
+    "640202": "大武口区",
3785
+    "640205": "惠农区",
3786
+    "640221": "平罗县"
3787
+  },
3788
+  "640300": {
3789
+    "640302": "利通区",
3790
+    "640303": "红寺堡区",
3791
+    "640323": "盐池县",
3792
+    "640324": "同心县",
3793
+    "640381": "青铜峡市"
3794
+  },
3795
+  "640400": {
3796
+    "640402": "原州区",
3797
+    "640422": "西吉县",
3798
+    "640423": "隆德县",
3799
+    "640424": "泾源县",
3800
+    "640425": "彭阳县"
3801
+  },
3802
+  "640500": {
3803
+    "640502": "沙坡头区",
3804
+    "640521": "中宁县",
3805
+    "640522": "海原县"
3806
+  },
3807
+  "650000": {
3808
+    "650100": "乌鲁木齐市",
3809
+    "650200": "克拉玛依市",
3810
+    "650400": "吐鲁番市",
3811
+    "650500": "哈密市",
3812
+    "652300": "昌吉回族自治州",
3813
+    "652700": "博尔塔拉蒙古自治州",
3814
+    "652800": "巴音郭楞蒙古自治州",
3815
+    "652900": "阿克苏地区",
3816
+    "653000": "克孜勒苏柯尔克孜自治州",
3817
+    "653100": "喀什地区",
3818
+    "653200": "和田地区",
3819
+    "654000": "伊犁哈萨克自治州",
3820
+    "654200": "塔城地区",
3821
+    "654300": "阿勒泰地区",
3822
+    "659001": "石河子市",
3823
+    "659002": "阿拉尔市",
3824
+    "659003": "图木舒克市",
3825
+    "659004": "五家渠市",
3826
+    "659006": "铁门关市"
3827
+  },
3828
+  "650100": {
3829
+    "650102": "天山区",
3830
+    "650103": "沙依巴克区",
3831
+    "650104": "新市区",
3832
+    "650105": "水磨沟区",
3833
+    "650106": "头屯河区",
3834
+    "650107": "达坂城区",
3835
+    "650109": "米东区",
3836
+    "650121": "乌鲁木齐县"
3837
+  },
3838
+  "650200": {
3839
+    "650202": "独山子区",
3840
+    "650203": "克拉玛依区",
3841
+    "650204": "白碱滩区",
3842
+    "650205": "乌尔禾区"
3843
+  },
3844
+  "650400": {
3845
+    "650402": "高昌区",
3846
+    "650421": "鄯善县",
3847
+    "650422": "托克逊县"
3848
+  },
3849
+  "650500": {
3850
+    "650502": "伊州区",
3851
+    "650521": "巴里坤哈萨克自治县",
3852
+    "650522": "伊吾县"
3853
+  },
3854
+  "652300": {
3855
+    "652301": "昌吉市",
3856
+    "652302": "阜康市",
3857
+    "652323": "呼图壁县",
3858
+    "652324": "玛纳斯县",
3859
+    "652325": "奇台县",
3860
+    "652327": "吉木萨尔县",
3861
+    "652328": "木垒哈萨克自治县"
3862
+  },
3863
+  "652700": {
3864
+    "652701": "博乐市",
3865
+    "652702": "阿拉山口市",
3866
+    "652722": "精河县",
3867
+    "652723": "温泉县"
3868
+  },
3869
+  "652800": {
3870
+    "652801": "库尔勒市",
3871
+    "652822": "轮台县",
3872
+    "652823": "尉犁县",
3873
+    "652824": "若羌县",
3874
+    "652825": "且末县",
3875
+    "652826": "焉耆回族自治县",
3876
+    "652827": "和静县",
3877
+    "652828": "和硕县",
3878
+    "652829": "博湖县"
3879
+  },
3880
+  "652900": {
3881
+    "652901": "阿克苏市",
3882
+    "652922": "温宿县",
3883
+    "652923": "库车县",
3884
+    "652924": "沙雅县",
3885
+    "652925": "新和县",
3886
+    "652926": "拜城县",
3887
+    "652927": "乌什县",
3888
+    "652928": "阿瓦提县",
3889
+    "652929": "柯坪县"
3890
+  },
3891
+  "653000": {
3892
+    "653001": "阿图什市",
3893
+    "653022": "阿克陶县",
3894
+    "653023": "阿合奇县",
3895
+    "653024": "乌恰县"
3896
+  },
3897
+  "653100": {
3898
+    "653101": "喀什市",
3899
+    "653121": "疏附县",
3900
+    "653122": "疏勒县",
3901
+    "653123": "英吉沙县",
3902
+    "653124": "泽普县",
3903
+    "653125": "莎车县",
3904
+    "653126": "叶城县",
3905
+    "653127": "麦盖提县",
3906
+    "653128": "岳普湖县",
3907
+    "653129": "伽师县",
3908
+    "653130": "巴楚县",
3909
+    "653131": "塔什库尔干塔吉克自治县"
3910
+  },
3911
+  "653200": {
3912
+    "653201": "和田市",
3913
+    "653221": "和田县",
3914
+    "653222": "墨玉县",
3915
+    "653223": "皮山县",
3916
+    "653224": "洛浦县",
3917
+    "653225": "策勒县",
3918
+    "653226": "于田县",
3919
+    "653227": "民丰县"
3920
+  },
3921
+  "654000": {
3922
+    "654002": "伊宁市",
3923
+    "654003": "奎屯市",
3924
+    "654004": "霍尔果斯市",
3925
+    "654021": "伊宁县",
3926
+    "654022": "察布查尔锡伯自治县",
3927
+    "654023": "霍城县",
3928
+    "654024": "巩留县",
3929
+    "654025": "新源县",
3930
+    "654026": "昭苏县",
3931
+    "654027": "特克斯县",
3932
+    "654028": "尼勒克县"
3933
+  },
3934
+  "654200": {
3935
+    "654201": "塔城市",
3936
+    "654202": "乌苏市",
3937
+    "654221": "额敏县",
3938
+    "654223": "沙湾县",
3939
+    "654224": "托里县",
3940
+    "654225": "裕民县",
3941
+    "654226": "和布克赛尔蒙古自治县"
3942
+  },
3943
+  "654300": {
3944
+    "654301": "阿勒泰市",
3945
+    "654321": "布尔津县",
3946
+    "654322": "富蕴县",
3947
+    "654323": "福海县",
3948
+    "654324": "哈巴河县",
3949
+    "654325": "青河县",
3950
+    "654326": "吉木乃县"
3951
+  },
3952
+  "810000": {
3953
+    "810001": "中西區",
3954
+    "810002": "灣仔區",
3955
+    "810003": "東區",
3956
+    "810004": "南區",
3957
+    "810005": "油尖旺區",
3958
+    "810006": "深水埗區",
3959
+    "810007": "九龍城區",
3960
+    "810008": "黃大仙區",
3961
+    "810009": "觀塘區",
3962
+    "810010": "荃灣區",
3963
+    "810011": "屯門區",
3964
+    "810012": "元朗區",
3965
+    "810013": "北區",
3966
+    "810014": "大埔區",
3967
+    "810015": "西貢區",
3968
+    "810016": "沙田區",
3969
+    "810017": "葵青區",
3970
+    "810018": "離島區"
3971
+  },
3972
+  "820000": {
3973
+    "820001": "花地瑪堂區",
3974
+    "820002": "花王堂區",
3975
+    "820003": "望德堂區",
3976
+    "820004": "大堂區",
3977
+    "820005": "風順堂區",
3978
+    "820006": "嘉模堂區",
3979
+    "820007": "路氹填海區",
3980
+    "820008": "聖方濟各堂區"
3981
+  }
3982
+}

+ 7 - 0
data/tests.py

@@ -0,0 +1,7 @@
1
+# -*- coding: utf-8 -*-
2
+from __future__ import unicode_literals
3
+
4
+from django.test import TestCase
5
+
6
+
7
+# Create your tests here.

+ 7 - 0
data/views.py

@@ -0,0 +1,7 @@
1
+# -*- coding: utf-8 -*-
2
+from __future__ import unicode_literals
3
+
4
+from django.shortcuts import render
5
+
6
+
7
+# Create your views here.

+ 0 - 0
docs/COMMANDS.md


+ 2 - 0
docs/CRONTAB.md

@@ -0,0 +1,2 @@
1
+# 定时任务
2
+# m h  dom mon dow   command

+ 3 - 0
isort.sh

@@ -0,0 +1,3 @@
1
+#!/bin/bash
2
+
3
+isort -rc -sp . .

+ 0 - 0
jdjos/__init__.py


+ 26 - 0
jdjos/basemodels.py

@@ -0,0 +1,26 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+from django.db import models
4
+from django.utils.translation import ugettext_lazy as _
5
+
6
+
7
+class BaseModelMixin(models.Model):
8
+    status = models.BooleanField(_(u'status'), default=True, help_text=_(u'状态'), db_index=True)
9
+    created_at = models.DateTimeField(_(u'created_at'), auto_now_add=True, editable=True, help_text=_(u'创建时间'))
10
+    updated_at = models.DateTimeField(_(u'updated_at'), auto_now=True, editable=True, help_text=_(u'更新时间'))
11
+
12
+    class Meta:
13
+        abstract = True
14
+
15
+
16
+class SexChoicesMixin(models.Model):
17
+    MALE = 1
18
+    FEMALE = 0
19
+
20
+    SEX_TYPE = (
21
+        (MALE, u'男'),
22
+        (FEMALE, u'女'),
23
+    )
24
+
25
+    class Meta:
26
+        abstract = True

+ 93 - 0
jdjos/decorators.py

@@ -0,0 +1,93 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+from functools import wraps
4
+
5
+from django.conf import settings
6
+from django.shortcuts import redirect
7
+from furl import furl
8
+from pywe_oauth import get_oauth_redirect_url
9
+from pywe_sign import check_signature
10
+
11
+from utils.error.errno_utils import SignatureStatusCode
12
+from utils.error.response_utils import response
13
+from utils.redis.connect import r
14
+
15
+
16
+def check_user_cookie(func=None, key=settings.COOKIE_USER_CHECK_KEY):
17
+    def decorator(func):
18
+        @wraps(func)
19
+        def returned_wrapper(request, *args, **kwargs):
20
+            user_id = request.get_signed_cookie(key, default='', salt=settings.COOKIE_SALT)
21
+            if not user_id:
22
+                return redirect(get_oauth_redirect_url(settings.WECHAT_OAUTH2_REDIRECT_URI, 'snsapi_userinfo', request.get_full_path()))
23
+            return func(request, *args, **kwargs)
24
+        return returned_wrapper
25
+
26
+    if not func:
27
+        def foo(func):
28
+            return decorator(func)
29
+        return foo
30
+
31
+    return decorator(func)
32
+
33
+
34
+def check_token(func=None, entry=None):
35
+    def decorator(func):
36
+        @wraps(func)
37
+        def returned_wrapper(request, *args, **kwargs):
38
+            if not settings.DEBUG and request.wechat:
39
+                vtoken = request.GET.get('vtoken', '') or request.POST.get('vtoken', '')
40
+                token_check_key = request.GET.get(settings.TOKEN_CHECK_KEY, '') or request.POST.get(settings.TOKEN_CHECK_KEY, '')
41
+                if not r.token_exists(token_check_key, vtoken):
42
+                    # 3rd OAuth
43
+                    # return redirect(settings.WECHAT_OAUTH2_REDIRECT_URL)
44
+                    # Current OAuth
45
+                    redirect_url = furl(entry or settings.WECHAT_OAUTH2_REDIRECT_ENTRY).add({}).url
46
+                    return redirect(get_oauth_redirect_url(settings.WECHAT_OAUTH2_REDIRECT_URI, 'snsapi_userinfo', redirect_url))
47
+            return func(request, *args, **kwargs)
48
+        return returned_wrapper
49
+
50
+    if not func:
51
+        def foo(func):
52
+            return decorator(func)
53
+        return foo
54
+
55
+    return decorator(func)
56
+
57
+
58
+def check_sign(func=None, method='POST'):
59
+    def decorator(func):
60
+        @wraps(func)
61
+        def returned_wrapper(request, *args, **kwargs):
62
+            if not settings.DEBUG and not check_signature(getattr(request, method).dict(), settings.PARAMS_SIGN_KEY):
63
+                return response(SignatureStatusCode.SIGNATURE_ERROR)
64
+            return func(request, *args, **kwargs)
65
+        return returned_wrapper
66
+
67
+    if not func:
68
+        def foo(func):
69
+            return decorator(func)
70
+        return foo
71
+
72
+    return decorator(func)
73
+
74
+
75
+def check_cookie(func=None, entry=None):
76
+    def decorator(func):
77
+        @wraps(func)
78
+        def returned_wrapper(request, *args, **kwargs):
79
+            if not settings.DEBUG and not request.COOKIES.get('user_id'):
80
+                # 3rd OAuth
81
+                # return redirect(settings.WECHAT_OAUTH2_REDIRECT_URL)
82
+                # Current OAuth
83
+                redirect_url = furl(entry or settings.WECHAT_OAUTH2_REDIRECT_ENTRY).add({}).url
84
+                return redirect(get_oauth_redirect_url(settings.WECHAT_OAUTH2_REDIRECT_URI, 'snsapi_userinfo', redirect_url))
85
+            return func(request, *args, **kwargs)
86
+        return returned_wrapper
87
+
88
+    if not func:
89
+        def foo(func):
90
+            return decorator(func)
91
+        return foo
92
+
93
+    return decorator(func)

+ 35 - 0
jdjos/deploy.bak/jdjos.ini

@@ -0,0 +1,35 @@
1
+# Refer: https://uwsgi-docs.readthedocs.io/en/latest/tutorials/Django_and_nginx.html
2
+# jdjos_uwsgi.ini file
3
+[uwsgi]
4
+
5
+# Django-related settings
6
+# the base directory (full path)
7
+chdir           = /home/diors/work/jdjos
8
+# Django's wsgi file
9
+module          = jdjos.wsgi
10
+# the virtualenv (full path)
11
+# home            = /path/to/virtualenv
12
+
13
+# process-related settings
14
+# master
15
+master          = true
16
+# maximum number of worker processes
17
+processes       = 10
18
+# the socket (use the full path to be safe
19
+socket          = /home/diors/work/jdjos/jdjos/deploy/jdjos.sock
20
+# ... with appropriate permissions - may be needed
21
+chmod-socket    = 777
22
+# clear environment on exit
23
+vacuum          = true
24
+
25
+# connect() to unix:///home/xxx/xxx.sock failed (11: Resource temporarily unavailable) while connecting to upstream
26
+# Exec sysctl.sh first
27
+# #!/bin/bash
28
+# sudo sysctl -w net.core.somaxconn=4096
29
+# sudo sysctl -w net.core.netdev_max_backlog=50000
30
+reload-mercy    = 64
31
+max-requests    = 8192
32
+listen          = 4096
33
+
34
+# recv() failed (104: Connection reset by peer) while reading response header from upstream
35
+buffer-size     = 65536

+ 91 - 0
jdjos/deploy.bak/jdjos_nginx.conf

@@ -0,0 +1,91 @@
1
+# jdjos_nginx.conf
2
+
3
+# the upstream component nginx needs to connect to
4
+upstream jdjos {
5
+    # server unix:///home/diors/work/jdjos/jdjos/deploy/jdjos.sock; # for a file socket
6
+    server 127.0.0.1:8888; # for a web port socket (we'll use this first)
7
+}
8
+
9
+# configuration of the server
10
+server {
11
+    # the port your site will be served on
12
+    listen      80;
13
+    # the domain name it will serve for
14
+    server_name .a.com; # substitute your machine's IP address or FQDN
15
+    charset     utf-8;
16
+
17
+    # access_log /var/log/nginx/jdjos_access.log;
18
+    # error_log /var/log/nginx/jdjos_error.log;
19
+
20
+    # max upload size
21
+    client_max_body_size 75M;   # adjust to taste
22
+
23
+    # JS接口安全域名 & 业务域名 验证
24
+    location /xxx.txt {
25
+        alias /home/diors/work/jdjos/docs/we/xxx.txt;
26
+    }
27
+
28
+    # Django media
29
+    location /media  {
30
+        alias /home/diors/work/jdjos/media;  # your Django project's media files - amend as required
31
+    }
32
+
33
+    location /static {
34
+        alias /home/diors/work/jdjos/collect_static; # your Django project's static files - amend as required
35
+    }
36
+
37
+    # Finally, send all non-media requests to the Django server.
38
+    location / {
39
+        # uwsgi_pass  jdjos;
40
+        proxy_pass  http://jdjos;
41
+        include     /home/diors/work/jdjos/jdjos/deploy/uwsgi_params; # the uwsgi_params file you installed
42
+    }
43
+}
44
+
45
+# configuration of the server
46
+server {
47
+    # the port your site will be served on
48
+    listen      443;
49
+    # the domain name it will serve for
50
+    server_name .a.com; # substitute your machine's IP address or FQDN
51
+    charset     utf-8;
52
+
53
+    # access_log /var/log/nginx/jdjos_ssl_access.log;
54
+    # error_log /var/log/nginx/jdjos_ssl_error.log;
55
+
56
+    ssl on;
57
+    ssl_certificate   cert/214180103970874.pem;
58
+    ssl_certificate_key  cert/214180103970874.key;
59
+    ssl_session_timeout 5m;
60
+    # worker process * exited on signal 11
61
+    # 同一台服务器部署多个 SSL 转发,Nginx 默认是开启 Session 缓存的,导致冲突,验证不通过,连接直接退出
62
+    # 解决方案:禁用 Ningx SSL Session 的缓存
63
+    ssl_session_cache none;
64
+    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
65
+    ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
66
+    ssl_prefer_server_ciphers on;
67
+
68
+    # max upload size
69
+    client_max_body_size 75M;   # adjust to taste
70
+
71
+    # JS接口安全域名 & 业务域名 验证
72
+    location /xxx.txt {
73
+        alias /home/diors/work/jdjos/docs/we/xxx.txt;
74
+    }
75
+
76
+    # Django media
77
+    location /media  {
78
+        alias /home/diors/work/jdjos/media;  # your Django project's media files - amend as required
79
+    }
80
+
81
+    location /static {
82
+        alias /home/diors/work/jdjos/collect_static; # your Django project's static files - amend as required
83
+    }
84
+
85
+    # Finally, send all non-media requests to the Django server.
86
+    location / {
87
+        # uwsgi_pass  jdjos;
88
+        proxy_pass  http://jdjos;
89
+        include     /home/diors/work/jdjos/jdjos/deploy/uwsgi_params; # the uwsgi_params file you installed
90
+    }
91
+}

+ 12 - 0
jdjos/deploy.bak/jdjos_supervisor.ini

@@ -0,0 +1,12 @@
1
+[program:jdjos]
2
+command=/home/diors/env/bin/uwsgi --ini /home/diors/work/jdjos/jdjos/deploy/jdjos.ini
3
+autostart=true
4
+autorestart=true
5
+startretries=3
6
+exitcodes=0,1,2
7
+stopsignal=KILL
8
+stopasgroup=true
9
+killasgroup=true
10
+stdout_logfile=/var/log/supervisor_jdjos_access.log
11
+stderr_logfile=/var/log/supervisor_jdjos_error.log
12
+user=diors

+ 12 - 0
jdjos/deploy.bak/supervisor_commands/pollqueue.ini

@@ -0,0 +1,12 @@
1
+[program:pollqueue]
2
+command=/home/diors/env/bin/python /home/diors/work/jdjos/manage.py poll_queue
3
+autostart=true
4
+autorestart=true
5
+startretries=3
6
+exitcodes=0,1,2
7
+stopsignal=KILL
8
+stopasgroup=true
9
+killasgroup=true
10
+stdout_logfile=/var/log/supervisor_pollqueue_access.log
11
+stderr_logfile=/var/log/supervisor_pollqueue_error.log
12
+user=diors

+ 12 - 0
jdjos/deploy.bak/supervisor_commands/rlistlog.ini

@@ -0,0 +1,12 @@
1
+[program:rlistlog]
2
+command=/home/diors/env/bin/python /home/diors/work/jdjos/manage.py rlistlog --key=django:logit:jdjos --filename=/tmp/jdjos.logit.log
3
+autostart=true
4
+autorestart=true
5
+startretries=3
6
+exitcodes=0,1,2
7
+stopsignal=KILL
8
+stopasgroup=true
9
+killasgroup=true
10
+stdout_logfile=/var/log/supervisor_rlistlog_access.log
11
+stderr_logfile=/var/log/supervisor_rlistlog_error.log
12
+user=diors

+ 15 - 0
jdjos/deploy.bak/uwsgi_params

@@ -0,0 +1,15 @@
1
+uwsgi_param	QUERY_STRING		$query_string;
2
+uwsgi_param	REQUEST_METHOD		$request_method;
3
+uwsgi_param	CONTENT_TYPE		$content_type;
4
+uwsgi_param	CONTENT_LENGTH		$content_length;
5
+
6
+uwsgi_param	REQUEST_URI		$request_uri;
7
+uwsgi_param	PATH_INFO		$document_uri;
8
+uwsgi_param	DOCUMENT_ROOT		$document_root;
9
+uwsgi_param	SERVER_PROTOCOL		$server_protocol;
10
+uwsgi_param	UWSGI_SCHEME		$scheme;
11
+
12
+uwsgi_param	REMOTE_ADDR		$remote_addr;
13
+uwsgi_param	REMOTE_PORT		$remote_port;
14
+uwsgi_param	SERVER_PORT		$server_port;
15
+uwsgi_param	SERVER_NAME		$server_name;

+ 5 - 0
jdjos/django_file_callback_settings.py

@@ -0,0 +1,5 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+
4
+def DJANGO_FILE_UPLOAD_CALLBACK_FUNC(request, file_path=None, file_url=None):
5
+    """ DJANGO FILE UPLOAD Callback Func """

+ 81 - 0
jdjos/django_we_callback_settings.py

@@ -0,0 +1,81 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+
4
+def DJANGO_WE_CFG_FUNC(request, state=None):
5
+    """ WeChat CFG Callback Func """
6
+
7
+
8
+def DJANGO_WE_QUOTE_STATE_FUNC(request, state):
9
+    """ WeChat Quote Callback Func """
10
+    from utils.redis.connect import r
11
+    return r.quote(state, short_uuid=True)
12
+
13
+
14
+def DJANGO_WE_UNQUOTE_STATE_FUNC(request, state):
15
+    """ WeChat UnQuote Callback Func """
16
+    from utils.redis.connect import r
17
+    # If Not Buf, When Wechat Multi Request, Unquote Will Get None
18
+    # Then Once Should ReOAuth, Will Raise Error
19
+    return r.unquote(state, buf=True) or state
20
+
21
+
22
+def DJANGO_WE_BASE_FUNC(code, state, access_info=None):
23
+    """ WeChat Base Redirect Callback Func """
24
+
25
+
26
+def DJANGO_WE_BASE_COOKIE_FUNC(code, state, access_info=None):
27
+    """ WeChat Base Set Cookie Redirect Callback Func """
28
+
29
+
30
+def DJANGO_WE_USERINFO_FUNC(code, state, access_info=None, userinfo=None):
31
+    """ WeChat Userinfo Redirect Callback Func """
32
+    from django.conf import settings
33
+    from utils.redis.connect import r
34
+    from utils.user.userinfo_save import userinfo_save
35
+
36
+    # Save profile or something else
37
+    user = userinfo_save(userinfo)
38
+
39
+    token_check_key = getattr(user, settings.TOKEN_CHECK_KEY)
40
+
41
+    return {
42
+        settings.TOKEN_CHECK_KEY: token_check_key,
43
+        'vtoken': r.token(token_check_key, ex=True, time=r.REDIS_EXPIRED_ONE_DAY, buf=False, short_uuid=True),
44
+    }
45
+
46
+
47
+def DJANGO_WE_USERINFO_COOKIE_FUNC(code, state, access_info=None, userinfo=None):
48
+    """ WeChat Userinfo Set Cookie Redirect Callback Func """
49
+    from django.conf import settings
50
+    from utils.user.userinfo_save import userinfo_save
51
+
52
+    # Save profile or something else
53
+    user = userinfo_save(userinfo)
54
+
55
+    token_check_key = getattr(user, settings.TOKEN_CHECK_KEY)
56
+
57
+    return {}, settings.TOKEN_CHECK_KEY, token_check_key
58
+
59
+
60
+def DJANGO_WE_SHARE_FUNC(request, state=None):
61
+    """ WeChat Share Callback Func """
62
+    # from django.conf import settings
63
+    # return settings.WECHAT_OAUTH2_REDIRECT_URL
64
+
65
+
66
+def DJANGO_WE_MESSAGE_CALLBACK_FUNC(request, xmldict, decrypted):
67
+    """ WeChat Message Callback Func """
68
+
69
+
70
+def DJANGO_WE_COMPONENT_AUTH_FUNC(request, xmldict, decrypted):
71
+    """ WeChat COMPONENT Auth Func """
72
+
73
+
74
+def DJANGO_WE_COMPONENT_CALLBACK_FUNC(request, appid, xmldict, decrypted):
75
+    """ WeChat COMPONENT Callback Func """
76
+
77
+
78
+def DJANGO_WE_REDIS_OBJ_FUNC(request):
79
+    """ WeChat Redis Object Callback Func """
80
+    from utils.redis.connect import r
81
+    return r

+ 16 - 0
jdjos/func_settings.py

@@ -0,0 +1,16 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+import redis_extensions as redis
4
+
5
+
6
+def redis_conf(conf):
7
+    return {
8
+        'host': conf.get('HOST', 'localhost'),
9
+        'port': conf.get('PORT', 6379),
10
+        'password': '{0}:{1}'.format(conf.get('USER', ''), conf.get('PASSWORD', '')) if conf.get('USER') else '',
11
+        'db': conf.get('db', 0),
12
+    }
13
+
14
+
15
+def redis_connect(conf):
16
+    return redis.StrictRedisExtensions(connection_pool=redis.ConnectionPool(**redis_conf(conf)))

+ 17 - 0
jdjos/local_settings_bak.py

@@ -0,0 +1,17 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+# DEBUG = False
4
+
5
+ALLOWED_HOSTS = ['127.0.0.1', 'localhost', 'jdjos']
6
+
7
+# DOMAIN
8
+DOMAIN = 'http://a.com'
9
+
10
+# 邮件设置
11
+# 只有当 DEBUG = False 的时候,才会邮件发送报错信息
12
+SERVER_EMAIL = 'error.notify@exmail.com'
13
+EMAIL_HOST_USER = 'error.notify@exmail.com'
14
+EMAIL_HOST_PASSWORD = '<^_^>pwd<^_^>'
15
+DEFAULT_FROM_EMAIL = 'error.notify <error.notify@exmail.com>'
16
+ADMINS = [('Zhang San', 'san.zhang@exmail.com'), ('Li Si', 'si.li@exmail.com')]
17
+EMAIL_SUBJECT_PREFIX = u'[Templet] '

+ 27 - 0
jdjos/local_settings_dev_bak.py

@@ -0,0 +1,27 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+import os
4
+
5
+
6
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
7
+PROJ_DIR = os.path.abspath(os.path.dirname(os.path.abspath(__file__)))
8
+
9
+TEMPLATES = [
10
+    {
11
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
12
+        'DIRS': [os.path.join(BASE_DIR, 'templates')],
13
+        # 'APP_DIRS': True,
14
+        'OPTIONS': {
15
+            'context_processors': [
16
+                'django.template.context_processors.debug',
17
+                'django.template.context_processors.request',
18
+                'django.contrib.auth.context_processors.auth',
19
+                'django.contrib.messages.context_processors.messages',
20
+            ],
21
+            'loaders': [
22
+                'django.template.loaders.filesystem.Loader',
23
+                'django.template.loaders.app_directories.Loader',
24
+            ],
25
+        },
26
+    },
27
+]

+ 396 - 0
jdjos/settings.py

@@ -0,0 +1,396 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+"""
4
+Django settings for jdjos project.
5
+
6
+Generated by 'django-admin startproject' using Django 1.11.3.
7
+
8
+For more information on this file, see
9
+https://docs.djangoproject.com/en/1.11/topics/settings/
10
+
11
+For the full list of settings and their values, see
12
+https://docs.djangoproject.com/en/1.11/ref/settings/
13
+"""
14
+
15
+import os
16
+
17
+# try:
18
+#     from func_settings import redis_connect
19
+#     REDIS_CACHE = redis_connect(REDIS.get('default', {}))
20
+# except ImportError:
21
+#     REDIS_CACHE = None
22
+from django_redis_connector import connector
23
+
24
+
25
+# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
26
+BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
27
+PROJ_DIR = os.path.abspath(os.path.dirname(os.path.abspath(__file__)))
28
+
29
+
30
+# Quick-start development settings - unsuitable for production
31
+# See https://docs.djangoproject.com/en/1.11/howto/deployment/checklist/
32
+
33
+# SECURITY WARNING: keep the secret key used in production secret!
34
+SECRET_KEY = '0=hpv21&am(7(k5ab!^zjvvl=ntj)^i@7)87t47uzumt_5rq$+'
35
+
36
+# SECURITY WARNING: don't run with debug turned on in production!
37
+DEBUG = True
38
+
39
+ALLOWED_HOSTS = []
40
+
41
+
42
+# Application definition
43
+
44
+INSTALLED_APPS = [
45
+    'django.contrib.admin',
46
+    'django.contrib.auth',
47
+    'django.contrib.contenttypes',
48
+    'django.contrib.sessions',
49
+    'django.contrib.messages',
50
+    'django.contrib.staticfiles',
51
+    # 'django_file_upload',
52
+    # 'django_short_url',
53
+    'django_uniapi',
54
+    'django_admin',
55
+    'django_we',
56
+    'commands',
57
+    'api',
58
+]
59
+
60
+MIDDLEWARE = [
61
+    'django.middleware.security.SecurityMiddleware',
62
+    'django.contrib.sessions.middleware.SessionMiddleware',
63
+    'django.middleware.common.CommonMiddleware',
64
+    # 'django.middleware.csrf.CsrfViewMiddleware',
65
+    'django.contrib.auth.middleware.AuthenticationMiddleware',
66
+    'django.contrib.messages.middleware.MessageMiddleware',
67
+    'django.middleware.clickjacking.XFrameOptionsMiddleware',
68
+    'detect.middleware.UserAgentDetectionMiddleware',
69
+]
70
+
71
+ROOT_URLCONF = 'jdjos.urls'
72
+
73
+TEMPLATES = [
74
+    {
75
+        'BACKEND': 'django.template.backends.django.DjangoTemplates',
76
+        'DIRS': [os.path.join(BASE_DIR, 'templates')],
77
+        # 'APP_DIRS': True,
78
+        'OPTIONS': {
79
+            'context_processors': [
80
+                'django.template.context_processors.debug',
81
+                'django.template.context_processors.request',
82
+                'django.contrib.auth.context_processors.auth',
83
+                'django.contrib.messages.context_processors.messages',
84
+            ],
85
+            'loaders': [
86
+                ('django.template.loaders.cached.Loader', [
87
+                    'django.template.loaders.filesystem.Loader',
88
+                    'django.template.loaders.app_directories.Loader',
89
+                ]),
90
+            ],
91
+        },
92
+    },
93
+]
94
+
95
+WSGI_APPLICATION = 'jdjos.wsgi.application'
96
+
97
+
98
+# Database9
99
+# https://docs.djangoproject.com/en/1.11/ref/settings/#databases
100
+
101
+DATABASES = {
102
+    # Create Database
103
+    # CREATE DATABASE jdjos DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
104
+    'default': {
105
+        'ENGINE': 'django.db.backends.mysql',
106
+        'NAME': 'jdjos',
107
+        'USER': 'root',
108
+        'PASSWORD': '',
109
+        'HOST': '127.0.0.1',
110
+        'PORT': 3306,
111
+        'CONN_MAX_AGE': 600,
112
+        'OPTIONS': {
113
+            # Utf8mb4 for Emoji
114
+            #
115
+            # Nickname
116
+            #
117
+            # account.WechatInfo ==> nickname
118
+            #   ALTER TABLE account_wechatinfo MODIFY COLUMN nickname VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
119
+            'charset': 'utf8mb4',
120
+        },
121
+    }
122
+}
123
+
124
+
125
+# Password validation
126
+# https://docs.djangoproject.com/en/1.11/ref/settings/#auth-password-validators
127
+
128
+AUTH_PASSWORD_VALIDATORS = [
129
+    {
130
+        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
131
+    },
132
+    {
133
+        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
134
+    },
135
+    {
136
+        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
137
+    },
138
+    {
139
+        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
140
+    },
141
+]
142
+
143
+
144
+# Internationalization
145
+# https://docs.djangoproject.com/en/1.11/topics/i18n/
146
+
147
+LANGUAGE_CODE = 'zh-Hans'
148
+
149
+TIME_ZONE = 'Asia/Shanghai'
150
+
151
+USE_I18N = True
152
+
153
+USE_L10N = True
154
+
155
+USE_TZ = True
156
+
157
+
158
+# Static files (CSS, JavaScript, Images)
159
+# https://docs.djangoproject.com/en/1.11/howto/static-files/
160
+
161
+STATICFILES_DIRS = (
162
+    os.path.join(PROJ_DIR, 'static').replace('\\', '/'),
163
+)
164
+
165
+STATIC_ROOT = os.path.join(BASE_DIR, 'collect_static').replace('\\', '/')
166
+
167
+STATIC_URL = '/static/'
168
+
169
+STATICFILES_FINDERS = (
170
+    'django.contrib.staticfiles.finders.FileSystemFinder',
171
+    'django.contrib.staticfiles.finders.AppDirectoriesFinder',
172
+    # 'django.contrib.staticfiles.finders.DefaultStorageFinder',
173
+)
174
+
175
+MEDIA_ROOT = os.path.join(BASE_DIR, 'media').replace('\\', '/')
176
+
177
+MEDIA_URL = '/media/'
178
+
179
+# File 设置
180
+FILE_UPLOAD_MAX_MEMORY_SIZE = 5242880  # InMemoryUploadedFile 文件最大值,设置为 5 MB
181
+FILE_UPLOAD_PERMISSIONS = 0o644  # TemporaryUploadedFile 文件权限设置
182
+
183
+# DOMAIN
184
+DOMAIN = 'http://a.com'
185
+
186
+# Redis 设置
187
+REDIS = {
188
+    'default': {
189
+        'HOST': '127.0.0.1',
190
+        'PORT': 6379,
191
+        'USER': '',
192
+        'PASSWORD': '',
193
+        'db': 0,
194
+    }
195
+}
196
+
197
+# 微信设置
198
+WECHAT = {
199
+    'JSAPI': {
200
+        'trade_type': 'JSAPI',  # JSAPI-网页支付、Native-原生支付、APP-APP支付、MICROPAY-刷卡支付
201
+        'token': '5201314',
202
+        'appID': '',
203
+        'appsecret': '',
204
+        'encodingaeskey': '',
205
+        'mchID': '',
206
+        'apiKey': '',
207
+        'mch_cert': '',
208
+        'mch_key': '',
209
+        'redpack': {
210
+
211
+        }
212
+    },
213
+}
214
+
215
+WECHAT_DEFAULT_CFG = 'JSAPI'
216
+
217
+# 微信唯一标识
218
+# Choices: 'unionid' or 'openid'
219
+#
220
+# models.py
221
+#   'unique_identifier': self.unionid if settings.WECHAT_UNIQUE_IDENTIFICATION == 'unionid' else self.openid,
222
+# views.py
223
+#   unique_identifier = request.POST.get(settings.WECHAT_UNIQUE_IDENTIFICATION, '')
224
+#   profile = Profile.objects.get(**{settings.WECHAT_UNIQUE_IDENTIFICATION: unique_identifier})
225
+#
226
+# If not bind to OpenPlat, change `WECHAT_UNIQUE_IDENTIFICATION` as `openid`
227
+WECHAT_UNIQUE_IDENTIFICATION = 'unionid'
228
+
229
+# Token 错误重授权设置
230
+TOKEN_CHECK_KEY = ''
231
+# TOKEN_CHECK_KEY = 'user_id'
232
+WECHAT_OAUTH2_REDIRECT_ENTRY = ''
233
+WECHAT_OAUTH2_REDIRECT_URL = ''
234
+
235
+# Cookie 设置
236
+DJANGO_WE_COOKIE_MAX_AGE = COOKIE_MAX_AGE = 31536000  # 单位:秒,1年:365 * 24 * 60 * 60 = 31536000
237
+DJANGO_WE_COOKIE_SALT = COOKIE_SALT = 'djwe'  # Salt for ``set_signed_cookie``
238
+# Cookie 校验设置
239
+COOKIE_USER_CHECK_KEY = ''
240
+# COOKIE_USER_CHECK_KEY = 'user_id'
241
+
242
+# 邮件设置
243
+# https://docs.djangoproject.com/en/1.11/howto/error-reporting/#email-reports
244
+# When DEBUG is False, Django will email the users listed in the ADMINS setting
245
+# whenever your code raises an unhandled exception and results in an internal server error (HTTP status code 500).
246
+# 只有当 DEBUG = False 的时候,才会邮件发送报错信息
247
+# Email address that error messages come from.
248
+SERVER_EMAIL = 'error.notify@exmail.com'
249
+# The email backend to use. For possible shortcuts see django.core.mail.
250
+# The default is to use the SMTP backend.
251
+# Third-party backends can be specified by providing a Python path
252
+# to a module that defines an EmailBackend class.
253
+EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
254
+# Host for sending email.
255
+EMAIL_HOST = 'smtp.exmail.qq.com'
256
+# Port for sending email.
257
+EMAIL_PORT = 25
258
+# Optional SMTP authentication information for EMAIL_HOST.
259
+EMAIL_HOST_USER = 'error.notify@exmail.com'
260
+EMAIL_HOST_PASSWORD = '<^_^>pwd<^_^>'
261
+EMAIL_USE_TLS = False
262
+EMAIL_USE_SSL = False
263
+EMAIL_SSL_CERTFILE = None
264
+EMAIL_SSL_KEYFILE = None
265
+EMAIL_TIMEOUT = None
266
+# Default email address to use for various automated correspondence from
267
+# the site managers.
268
+DEFAULT_FROM_EMAIL = 'error.notify <error.notify@exmail.com>'
269
+# People who get code error notifications.
270
+# In the format [('Full Name', 'email@example.com'), ('Full Name', 'anotheremail@example.com')]
271
+ADMINS = [('Zhang San', 'san.zhang@exmail.com'), ('Li Si', 'si.li@exmail.com')]
272
+# Not-necessarily-technical managers of the site. They get broken link
273
+# notifications and other various emails.
274
+MANAGERS = ADMINS
275
+# Subject-line prefix for email messages send with django.core.mail.mail_admins
276
+# or ...mail_managers.  Make sure to include the trailing space.
277
+EMAIL_SUBJECT_PREFIX = u'[Templet] '
278
+
279
+# Django-Admin Settings
280
+DJANGO_ADMIN_DISABLE_DELETE_SELECTED = False
281
+
282
+# Django-FILE-UPLOAD Settings
283
+DJANGO_FILE_UPLOAD_USE_YM = True
284
+DJANGO_FILE_UPLOAD_USE_DT = True
285
+
286
+# Django-Logit Settings
287
+DJANGO_LOGIT_ENABLED = True
288
+DJANGO_LOGIT_BODY_FLAG = False
289
+DJANGO_LOGIT_RES_FLAG = False
290
+
291
+# Django-Onerror Settings
292
+DJANGO_ONERROR_ACCEPT_REPORT = True
293
+DJANGO_ONERROR_ADMIN_SITE_REGISTER = True
294
+
295
+# Django-Short-URL Settings
296
+# Redirect url when short url not exists
297
+DJANGO_SHORT_URL_REDIRECT_URL = ''
298
+
299
+# Django-We Settings
300
+DJANGO_WE_QUOTE_OR_NOT = True
301
+# Enable Cookie or not
302
+# DJANGO_WE_BASE_REDIRECT_SET_COOKIE = False
303
+# DJANGO_WE_USERINFO_REDIRECT_SET_COOKIE = True
304
+# Cookie Config
305
+DJANGO_WE_COOKIE_MAX_AGE = COOKIE_MAX_AGE
306
+DJANGO_WE_COOKIE_SALT = COOKIE_SALT
307
+
308
+# 开发调试相关配置
309
+if DEBUG:
310
+    try:
311
+        from local_settings_dev import *
312
+    except ImportError:
313
+        pass
314
+
315
+try:
316
+    from local_settings import *
317
+except ImportError:
318
+    pass
319
+
320
+try:
321
+    from django_file_callback_settings import *
322
+except ImportError:
323
+    pass
324
+
325
+try:
326
+    from django_we_callback_settings import *
327
+except ImportError:
328
+    pass
329
+
330
+# 依赖 local_settings 中的配置
331
+# 微信授权设置
332
+# WECHAT_OAUTH2_REDIRECT_URI = '{0}/we/oauth2?scope={{0}}&redirect_url={{1}}'.format(DOMAIN)
333
+# Shorten URL
334
+# ``o`` is short for oauth2
335
+# ``r`` is short for redirect_url
336
+WECHAT_OAUTH2_REDIRECT_URI = '{0}/we/o?scope={{0}}&r={{1}}'.format(DOMAIN)
337
+WECHAT_OAUTH2_USERINFO_REDIRECT_URI = '{0}/we/o?r={{0}}'.format(DOMAIN)  # Scope default snsapi_userinfo
338
+WECHAT_BASE_REDIRECT_URI = '{0}/we/base_redirect'.format(DOMAIN)
339
+WECHAT_USERINFO_REDIRECT_URI = '{0}/we/userinfo_redirect'.format(DOMAIN)
340
+WECHAT_DIRECT_BASE_REDIRECT_URI = '{0}/we/direct_base_redirect'.format(DOMAIN)
341
+WECHAT_DIRECT_USERINFO_REDIRECT_URI = '{0}/we/direct_userinfo_redirect'.format(DOMAIN)
342
+
343
+JDJOS_OAUTH_AUTHORIZE = 'https://oauth.jd.com/oauth/authorize?response_type=code&client_id={client_id}&redirect_uri={redirect_uri}&state={state}'
344
+JDJOS_OAUTH_TOKEN = 'https://oauth.jd.com/oauth/token?grant_type=authorization_code&client_id={client_id}&redirect_uri={redirect_uri}&code={code}&state={state}&client_secret={client_secret}'
345
+JDJOS_REDIRECT_URI = '{0}/jos/oauth'.format(DOMAIN)
346
+
347
+# Redis 连接
348
+WECHAT_REDIS_OBJ = REDIS_CACHE = connector(REDIS.get('default', {}))
349
+
350
+# LOGGER 设置
351
+# python manage.py rlistlog --key=django:logit:jdjos --filename=/tmp/jdjos.logit.log
352
+LOGGING = {
353
+    'version': 1,
354
+    'disable_existing_loggers': False,
355
+    'formatters': {
356
+        'verbose': {
357
+            'format': '%(levelname)s %(asctime)s %(module)s %(process)d %(thread)d %(message)s'
358
+        },
359
+        'simple': {
360
+            'format': '%(levelname)s %(message)s'
361
+        },
362
+    },
363
+    'handlers': {
364
+        'logit': {
365
+            'level': 'DEBUG',
366
+            'class': 'rlog.RedisListHandler',
367
+            'redis_client': REDIS_CACHE,
368
+            'key': 'django:logit:jdjos',
369
+            'formatter': 'verbose',
370
+        },
371
+        'console': {
372
+            'level': 'DEBUG',
373
+            'class': 'logging.StreamHandler',
374
+            'formatter': 'verbose'
375
+        },
376
+    },
377
+    'loggers': {
378
+        'logit': {
379
+            'handlers': ['logit'],
380
+            'level': 'DEBUG',
381
+            'propagate': True,
382
+        },
383
+        'console': {
384
+            'handlers': ['console'],
385
+            'level': 'DEBUG',
386
+            'propagate': True,
387
+        },
388
+    },
389
+}
390
+
391
+# MAX_BIGINT
392
+# Why Not ``sys.maxint``
393
+# n bit platform sys.maxint = 2 ** (n - 1) - 1
394
+# 64 bit 9223372036854775807, 32 bit 2147483647
395
+from django.db.models import BigIntegerField  # isort:skip
396
+MAX_BIGINT = BigIntegerField.MAX_BIGINT

+ 68 - 0
jdjos/static/jdjos/css/common.css

@@ -0,0 +1,68 @@
1
+.text-center {
2
+    text-align: center;
3
+}
4
+
5
+/*
6
+  文本溢出省略,元素需要设置定宽度
7
+  */
8
+/* 单行 */
9
+.text_ellipsis {
10
+    overflow: hidden;
11
+    white-space: nowrap;
12
+    text-overflow: ellipsis;
13
+}
14
+/* 多行 */
15
+.a {
16
+    overflow: hidden;
17
+    text-overflow: ellipsis;
18
+    display: -webkit-box;
19
+    -webkit-line-clamp: 2;
20
+    -webkit-box-orient: vertical;
21
+}
22
+
23
+/*
24
+  浮动 & 清除浮动
25
+ */
26
+.fl {
27
+    float: left;
28
+}
29
+
30
+.fr {
31
+    float: right;
32
+}
33
+
34
+.clearfix {
35
+    zoom: 1;
36
+}
37
+
38
+.clearfix:after {
39
+    content: "\200B";
40
+    display: block;
41
+    height: 0;
42
+    clear: both;
43
+}
44
+
45
+.clearfix:after {content:"."; display:block; height:0; visibility:hidden; clear:both; }
46
+.clearfix { *zoom:1; }
47
+
48
+/*
49
+  解决安卓微信点击图片预览问题
50
+  这个会让 img 标签的点击事件失效,如果想要点击图片就要给上面再写一层
51
+  需要长按识别二维码的时候,则再单独给该图片添加
52
+  ```css
53
+  pointer-events: auto;
54
+  ```
55
+  来恢复对应的点击事件
56
+  */
57
+img {
58
+    pointer-events: none;
59
+}
60
+
61
+.clickable {
62
+    pointer-events: auto;
63
+}
64
+
65
+/* Input 自动填充后,移除 Chrome 默认黄色 */
66
+input:-webkit-autofill {
67
+    box-shadow: 0 0 0 1000px white inset !important;
68
+}

+ 574 - 0
jdjos/static/jdjos/js/jswe.js

@@ -0,0 +1,574 @@
1
+!(function(e, t) {
2
+    var config = {
3
+        wxconfig: 'http://api.tt4it.com/wx/jsapi_signature',
4
+        callback: 'callback'
5
+    }, wxData = {
6
+        debug: false,
7
+        imgUrl: '',
8
+        link: '',
9
+        desc: '',
10
+        title: '',
11
+        timeLine: ''
12
+    }, wxConfig = {
13
+        hide: false,
14
+        baseFlag: false,
15
+        baseHide: false,
16
+        close: false,
17
+        hideMenuItems: [],
18
+        showMenuItems: []
19
+    }, jsApiList = [
20
+        'checkJsApi',
21
+        'onMenuShareTimeline',
22
+        'onMenuShareAppMessage',
23
+        'onMenuShareQQ',
24
+        'onMenuShareWeibo',
25
+        'onMenuShareQZone',
26
+        'hideMenuItems',
27
+        'showMenuItems',
28
+        'hideAllNonBaseMenuItem',
29
+        'showAllNonBaseMenuItem',
30
+        'translateVoice',
31
+        'startRecord',
32
+        'stopRecord',
33
+        'onRecordEnd',
34
+        'playVoice',
35
+        'pauseVoice',
36
+        'stopVoice',
37
+        'uploadVoice',
38
+        'downloadVoice',
39
+        'chooseImage',
40
+        'previewImage',
41
+        'uploadImage',
42
+        'downloadImage',
43
+        'getLocalImgData',
44
+        'getNetworkType',
45
+        'openLocation',
46
+        'getLocation',
47
+        'hideOptionMenu',
48
+        'showOptionMenu',
49
+        'closeWindow',
50
+        'scanQRCode',
51
+        'chooseWXPay',
52
+        'openEnterpriseRedPacket',
53
+        'openProductSpecificView',
54
+        'addCard',
55
+        'chooseCard',
56
+        'openCard'
57
+    ], wxApiFun
58
+
59
+    function isEmpty(obj) {
60
+        if (obj == null) return true
61
+        if (obj.length > 0) return false
62
+        if (obj.length === 0) return true
63
+        for (var key in obj) {
64
+            if (Object.prototype.hasOwnProperty.call(obj, key)) return false
65
+        }
66
+        return true
67
+    }
68
+
69
+    function isNotEmpty(obj) {
70
+        return !isEmpty(obj)
71
+    }
72
+
73
+    function isOpenOnPC() {  // 判断当前网页是否在 PC 浏览器中打开
74
+        var ua = navigator.userAgent
75
+        return /windows nt/i.test(ua) || /macintosh/i.test(ua) || /linux x86_64/i.test(ua)
76
+    }
77
+
78
+    function isOpenInWeixin() {  // 判断当前网页是否在微信内置浏览器中打开
79
+        return /micromessenger/i.test(navigator.userAgent)
80
+    }
81
+
82
+    function getWeixinVersion() {
83
+        var ua = navigator.userAgent,
84
+            mt = ua.match(/micromessenger\/([\d.]+)/i)
85
+        return (mt ? mt[1] : '')
86
+    }
87
+
88
+    // This function checks whether Wechat is the appointed version or not
89
+    // Cmp: http://jsperf.com/regexp-test-vs-indexof-ignore-upper-and-lower
90
+    function isWeixinVersion(version) {
91
+        // return new RegExp('micromessenger/' + version , 'i').test(navigator.userAgent)
92
+        return navigator.userAgent.toLowerCase().indexOf('micromessenger/' + version) != -1
93
+    }
94
+
95
+    function hideOptionMenu() {
96
+        wxConfig.hide = true
97
+        fixedWxData()
98
+    }
99
+
100
+    function showOptionMenu() {
101
+        wxConfig.hide = false
102
+        fixedWxData()
103
+    }
104
+
105
+    function hideMenuItems(items) {
106
+        wxConfig.hideMenuItems = items
107
+        fixedWxData()
108
+    }
109
+
110
+    function showMenuItems(items) {
111
+        wxConfig.showMenuItems = items
112
+        fixedWxData()
113
+    }
114
+
115
+    function hideAllNonBaseMenuItem() {
116
+        wxConfig.baseFlag = true
117
+        wxConfig.baseHide = true
118
+        fixedWxData()
119
+    }
120
+
121
+    function showAllNonBaseMenuItem() {
122
+        wxConfig.baseFlag = true
123
+        wxConfig.baseHide = false
124
+        fixedWxData()
125
+    }
126
+
127
+    function closeWindow() {
128
+        wxConfig.close = true
129
+        fixedWxData()
130
+    }
131
+
132
+    function wxReady(data) {
133
+        data = typeof data === 'object' ? data : JSON.parse(data)
134
+        wx.config({
135
+            debug: wxData.debug,
136
+            appId: data.appId,
137
+            timestamp: data.timestamp,
138
+            nonceStr: data.nonceStr,
139
+            signature: data.signature,
140
+            jsApiList: jsApiList
141
+        })
142
+
143
+        var callbacks = {
144
+            trigger: function (res) {
145
+                // alert('用户点击发送给朋友')
146
+                if (JSWE.wxTrigger) {JSWE.wxTrigger(res)}
147
+            },
148
+            success: function (res) {
149
+                // alert('已分享')
150
+                if (JSWE.wxSuccess) {JSWE.wxSuccess(res)}
151
+            },
152
+            cancel: function (res) {
153
+                // alert('已取消')
154
+                if (JSWE.wxCancel) {JSWE.wxCancel(res)}
155
+            },
156
+            fail: function (res) {
157
+                // alert(JSON.stringify(res))
158
+                if (JSWE.wxFail) {JSWE.wxFail(res)}
159
+            }
160
+        }, shareInfo = function(flag) {
161
+            var _share = {
162
+                title: flag ? wxData.title : (wxData.timeLine || wxData.desc),
163
+                link: wxData.link,
164
+                imgUrl: wxData.imgUrl,
165
+                trigger: callbacks.trigger,
166
+                success: callbacks.success,
167
+                cancel: callbacks.cancel,
168
+                fail: callbacks.fail
169
+            }
170
+            if (flag) _share.desc = wxData.desc
171
+            return _share
172
+        }, wxShareApi = function() {
173
+            // 2. 分享接口
174
+            // 2.1 监听“分享给朋友”,按钮点击、自定义分享内容及分享结果接口
175
+            wx.onMenuShareAppMessage(shareInfo(1))
176
+            // 2.2 监听“分享到朋友圈”按钮点击、自定义分享内容及分享结果接口
177
+            wx.onMenuShareTimeline(shareInfo(0))
178
+            // 2.3 监听“分享到QQ”按钮点击、自定义分享内容及分享结果接口
179
+            wx.onMenuShareQQ(shareInfo(1))
180
+            // 2.4 监听“分享到微博”按钮点击、自定义分享内容及分享结果接口
181
+            wx.onMenuShareWeibo(shareInfo(1))
182
+            // 2.5 监听“分享到QQ空间”按钮点击、自定义分享内容及分享结果接口
183
+            wx.onMenuShareQZone(shareInfo(1))
184
+        }, wxMenuApi = function () {
185
+            // 8. 界面操作接口
186
+            // 8.1 隐藏右上角菜单
187
+            // 8.2 显示右上角菜单
188
+            if (wxConfig.hide) {wx.hideOptionMenu()} else {wx.showOptionMenu()}
189
+            // 8.3 批量隐藏菜单项
190
+            if (isNotEmpty(wxConfig.hideMenuItems)) {
191
+                wx.hideMenuItems({
192
+                    menuList: wxConfig.hideMenuItems,
193
+                    success: function (res) {
194
+                        if (JSWE.wxHideMenuItemsSuccess) {JSWE.wxHideMenuItemsSuccess(res)}
195
+                    },
196
+                    fail: function (res) {
197
+                        if (JSWE.wxHideMenuItemsFail) {JSWE.wxHideMenuItemsFail(res)}
198
+                    }
199
+                })
200
+            }
201
+            // 8.4 批量显示菜单项
202
+            if (isNotEmpty(wxConfig.showMenuItems)) {
203
+                wx.showMenuItems({
204
+                    menuList: wxConfig.showMenuItems,
205
+                    success: function (res) {
206
+                        if (JSWE.wxShowMenuItemsSuccess) {JSWE.wxShowMenuItemsSuccess(res)}
207
+                    },
208
+                    fail: function (res) {
209
+                        if (JSWE.wxShowMenuItemsFail) {JSWE.wxShowMenuItemsFail(res)}
210
+                    }
211
+                })
212
+            }
213
+            // 8.5 隐藏所有非基本菜单项
214
+            // 8.6 显示所有被隐藏的非基本菜单项
215
+            if (wxConfig.baseFlag) {
216
+                if (wxConfig.baseHide) {wx.hideAllNonBaseMenuItem()} else {wx.showAllNonBaseMenuItem()}
217
+            }
218
+            // 8.7 关闭当前窗口
219
+            if (wxConfig.close) {wx.closeWindow()}
220
+        }, wxVoiceApi = function() {
221
+            // 4.3 监听录音自动停止
222
+            wx.onVoiceRecordEnd({
223
+                complete: function (res) {
224
+                    voice.localId = res.localId
225
+                    if (JSWE.wxVoiceRecordEnd) {JSWE.wxVoiceRecordEnd(res)}
226
+                }
227
+            })
228
+            // 4.7 监听录音播放停止
229
+            wx.onVoicePlayEnd({
230
+                complete: function (res) {
231
+                    if (JSWE.wxVoicePlayEnd) {JSWE.wxVoicePlayEnd(res)}
232
+                }
233
+            })
234
+        }, wxApi = function () {
235
+            wxShareApi()
236
+            wxMenuApi()
237
+            wxVoiceApi()
238
+        }
239
+
240
+        wx.ready(wxApi)
241
+
242
+        return wxApiFun = wxApi
243
+    }
244
+
245
+    if (isOpenInWeixin() || isOpenOnPC()) {
246
+        if ('undefined' !== typeof JSWE_CONF_UPDATE) JSWE_CONF_UPDATE(config)
247
+        $.ajax({
248
+            url: config.wxconfig,
249
+            type: 'get',
250
+            dataType: 'jsonp',
251
+            jsonpCallback: config.callback,
252
+            data: {
253
+                url: window.location.href.split('#')[0]
254
+            },
255
+            success: wxReady
256
+        })
257
+    }
258
+
259
+    function initWxData(data, flag) {
260
+        for(var d in data) {if (d in wxData) wxData[d] = data[d]}
261
+        if (flag) fixedWxData()
262
+    }
263
+
264
+    function changeWxData(key, value, flag) {
265
+        if (key in falDwxDataata) {wxData[key] = value}
266
+        if (flag) fixedWxData()
267
+    }
268
+
269
+    function fixedWxData() {
270
+        if ('undefined' !== typeof wxApiFun) wxApiFun()
271
+    }
272
+
273
+    // 3 智能接口
274
+    var voice = {
275
+        localId: '',
276
+        serverId: ''
277
+    }
278
+    // 3.1 识别音频并返回识别结果
279
+    function translateVoice() {
280
+        if (voice.localId == '') {
281
+            if (JSWE.wxTranslateVoiceEmpty) {JSWE.wxTranslateVoiceEmpty()}
282
+            return
283
+        }
284
+        wx.translateVoice({
285
+            localId: voice.localId,
286
+            complete: function (res) {
287
+                if (JSWE.wxTranslateVoiceComplete) {JSWE.wxTranslateVoiceComplete(res)}
288
+            }
289
+        })
290
+    }
291
+
292
+    // 4 音频接口
293
+    // 4.1 开始录音
294
+    function startRecord() {
295
+        wx.startRecord({
296
+            cancel: function () {
297
+                if (JSWE.wxStartRecordCancel) {JSWE.wxStartRecordCancel(res)}
298
+            }
299
+        })
300
+    }
301
+
302
+    // 4.2 停止录音
303
+    function stopRecord() {
304
+        wx.stopRecord({
305
+          success: function (res) {
306
+              voice.localId = res.localId
307
+              if (JSWE.wxStopRecordSuccess) {JSWE.wxStopRecordSuccess(res)}
308
+          },
309
+          fail: function (res) {
310
+              if (JSWE.wxStopRecordFail) {JSWE.wxStopRecordFail(res)}
311
+          }
312
+        })
313
+    }
314
+
315
+    // 4.4 播放音频
316
+    function playVoice() {
317
+        if (voice.localId == '') {
318
+            if (JSWE.wxPlayVoiceEmpty) {JSWE.wxPlayVoiceEmpty()}
319
+            return
320
+        }
321
+        wx.playVoice({
322
+            localId: voice.localId
323
+        })
324
+    }
325
+
326
+    // 4.5 暂停播放音频
327
+    function pauseVoice() {
328
+        if (voice.localId == '') {
329
+            if (JSWE.wxPauseVoiceEmpty) {JSWE.wxPauseVoiceEmpty()}
330
+            return
331
+        }
332
+        wx.pauseVoice({
333
+            localId: voice.localId
334
+        })
335
+    }
336
+
337
+    // 4.6 停止播放音频
338
+    function stopVoice() {
339
+        if (voice.localId == '') {
340
+            if (JSWE.wxStopVoiceEmpty) {JSWE.wxStopVoiceEmpty()}
341
+            return
342
+        }
343
+        wx.stopVoice({
344
+            localId: voice.localId
345
+        })
346
+    }
347
+
348
+    // 4.8 上传语音
349
+    function uploadVoice() {
350
+        var localId = voice.localId
351
+        if (localId == '') {
352
+            if (JSWE.wxUploadVoiceEmpty) {JSWE.wxUploadVoiceEmpty()}
353
+            return
354
+        }
355
+        wx.uploadVoice({
356
+            localId: localId,
357
+            success: function (res) {
358
+                voice.serverId = res.serverId
359
+                if (JSWE.wxUploadVoiceSuccess) {JSWE.wxUploadVoiceSuccess(res, localId)}
360
+            }
361
+        })
362
+    }
363
+
364
+    // 4.9 下载语音
365
+    function downloadVoice() {
366
+        var serverId = voice.serverId
367
+        if (serverId == '') {
368
+            if (JSWE.wxDownloadVoiceEmpty) {JSWE.wxDownloadVoiceEmpty()}
369
+            return
370
+        }
371
+        wx.downloadVoice({
372
+            serverId: serverId,
373
+            success: function (res) {
374
+                voice.localId = res.localId
375
+                if (JSWE.wxDownloadVoiceSuccess) {JSWE.wxDownloadVoiceSuccess(res, serverId)}
376
+            }
377
+        })
378
+    }
379
+
380
+    // 5 图片接口
381
+    var images = {
382
+        localIds: [],
383
+        serverIds: []
384
+    }
385
+    // 5.1 拍照、本地选图
386
+    function chooseImage(choose_params) {
387
+        if ('undefined' === typeof choose_params) choose_params = {}
388
+        wx.chooseImage({
389
+            count: choose_params.count || 9, // 默认9
390
+            sizeType: choose_params.sizeType || ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
391
+            sourceType: choose_params.sourceType || ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
392
+            success: function (res) {
393
+                images.localIds = res.localIds // 返回选定照片的本地ID列表,localId可以作为img标签的src属性显示图片
394
+                // 判断是否直接上传
395
+                if (choose_params.directUpload) {setTimeout(uploadImages({localIds: images.localIds, isShowProgressTips: choose_params.isShowProgressTips || 1}), 100)}
396
+                // 拍照、本地选图成功后的回调函数
397
+                if (JSWE.wxChooseImageSuccess) {JSWE.wxChooseImageSuccess(res, choose_params.extras || {})}
398
+            }
399
+        })
400
+    }
401
+
402
+    // 5.2 图片预览
403
+    function previewImage(preview_params) {
404
+        wx.previewImage({
405
+            current: preview_params.current, // 当前显示图片的链接,不填则默认为 urls 的第一张
406
+            urls: preview_params.urls // 需要预览的图片链接列表
407
+        })
408
+    }
409
+
410
+    // 5.3 上传图片
411
+    function uploadImage(upload_params) {
412
+        // 上传图片为异步处理,重复上传同一图片,返回的serverId也是不同的
413
+        var localId = upload_params.localId
414
+        wx.uploadImage({
415
+            localId: localId, // 需要上传的图片的本地ID,由chooseImage接口获得
416
+            isShowProgressTips: upload_params.isShowProgressTips || 1, // 默认为1,显示进度提示
417
+            success: function (res) {
418
+                images.serverIds.push(res.serverId) // 返回图片的服务器端ID
419
+                // 上传图片成功后的回调函数
420
+                if (JSWE.wxUploadImageSuccess) {JSWE.wxUploadImageSuccess(res, localId)}
421
+            }
422
+        })
423
+    }
424
+
425
+    function uploadImages(upload_params) {
426
+        var localIds = upload_params.localIds, isShowProgressTips = upload_params.isShowProgressTips || 1
427
+        images.serverIds = []
428
+        for (var idx in localIds) {uploadImage({localId: localIds[idx], isShowProgressTips: isShowProgressTips})}
429
+    }
430
+
431
+    // 5.4 下载图片
432
+    function downloadImage(download_params) {
433
+        var serverId = download_params.serverId
434
+        wx.downloadImage({
435
+            serverId: serverId, // 需要下载的图片的服务器端ID,由uploadImage接口获得
436
+            isShowProgressTips: download_params.isShowProgressTips || 1, // 默认为1,显示进度提示
437
+            success: function (res) {
438
+                images.localId.push(res.localId)
439
+                if (JSWE.wxDownloadImageSuccess) {JSWE.wxDownloadImageSuccess(res, serverId)}
440
+            }
441
+        })
442
+    }
443
+
444
+    function downloadImages(download_params) {
445
+        var serverIds = download_params.serverIds, isShowProgressTips = download_params.isShowProgressTips || 1
446
+        images.localIds = []
447
+        for (var idx in serverIds) {downloadImage({serverId: serverIds[idx], isShowProgressTips: isShowProgressTips})}
448
+    }
449
+
450
+    function getLocalImgData(localId) {
451
+        wx.getLocalImgData({
452
+            localId: localId, // 图片的localID
453
+            success: function (res) {
454
+                // var localData = res.localData; // localData是图片的base64数据,可以用img标签显示
455
+                if (JSWE.wxGetLocalImgDataSuccess) {JSWE.wxGetLocalImgDataSuccess(res)}
456
+            }
457
+        })
458
+    }
459
+
460
+    // 9 微信原生接口
461
+    // 9.1.1 扫描二维码并返回结果
462
+    // 9.1.2 扫描二维码并返回结果
463
+    function scanQRCode(scan_params) {
464
+        if ('undefined' === typeof scan_params) scan_params = {}
465
+        wx.scanQRCode({
466
+            needResult: scan_params.needResult || 0,  // 默认为0,0扫描结果由微信处理,1直接返回扫描结果
467
+            scanType: scan_params.scanType || ['qrCode', 'barCode'],  // 可以指定扫二维码还是一维码,默认二者都有
468
+            success: function (res) {  // 当 needResult 为 1 时,扫码返回的结果
469
+                if (JSWE.wxScanQRCodeSuccess) {JSWE.wxScanQRCodeSuccess(res)}
470
+            }
471
+        })
472
+    }
473
+
474
+    // QRCode & BarCode is different
475
+    function parseScanQRCodeResultStr(resultStr) {
476
+        var strs = resultStr.split(',')
477
+        return strs[strs.length - 1]
478
+    }
479
+
480
+    // 10 微信支付接口
481
+    // 10.1 发起一个支付请求
482
+    function chooseWXPay(wxpay_params) {
483
+        wx.chooseWXPay({
484
+            timestamp: wxpay_params.timeStamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
485
+            nonceStr: wxpay_params.nonceStr, // 支付签名随机串,不长于 32 位
486
+            package: wxpay_params.package, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=***)
487
+            signType: wxpay_params.signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
488
+            paySign: wxpay_params.paySign, // 支付签名
489
+            success: function (res) {
490
+                // 支付成功后的回调函数
491
+                if (JSWE.wxPaySuccess) {JSWE.wxPaySuccess(res)}
492
+            }
493
+        })
494
+    }
495
+
496
+    // xx 微信原生企业红包接口
497
+    // xx.1 发起一个发送原生企业红包请求
498
+    function openEnterpriseRedPacket(wxredpack_params) {
499
+        wx.openEnterpriseRedPacket({
500
+            timeStamp: wxredpack_params.timeStamp, // 红包签名时间戳,注意原生企业红包接口timeStamp字段名需大写其中的S字符,而支付接口timeStamp字段名无需大写其中的S字符。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
501
+            nonceStr: wxredpack_params.nonceStr, // 红包签名随机串,不长于 32 位
502
+            package: encodeURIComponent(wxredpack_params.package), // 发放红包接口返回的prepay_id参数值,提交格式如:prepay_id=***)
503
+            signType: wxredpack_params.signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
504
+            paySign: wxredpack_params.paySign, // 红包签名
505
+            success: function (res) {
506
+                // 发送原生企业红包成功后的回调函数
507
+                if (JSWE.wxEnterpriseRedPacketSuccess) {JSWE.wxEnterpriseRedPacketSuccess(res)}
508
+            }
509
+        })
510
+    }
511
+
512
+    var v = {
513
+        version: '1.0.5',
514
+
515
+        // Basic Vars
516
+        config: config,
517
+        wxData: wxData,
518
+        jsApiList: jsApiList,
519
+
520
+        isEmpty: isEmpty,
521
+        isNotEmpty: isNotEmpty,
522
+
523
+        // Weixin Function
524
+        isOpenInWeixin: isOpenInWeixin,
525
+        getWeixinVersion: getWeixinVersion,
526
+        isWeixinVersion: isWeixinVersion,
527
+
528
+        // Menu Function
529
+        hideOptionMenu: hideOptionMenu,
530
+        showOptionMenu: showOptionMenu,
531
+        hideMenuItems: hideMenuItems,
532
+        showMenuItems: showMenuItems,
533
+        hideAllNonBaseMenuItem: hideAllNonBaseMenuItem,
534
+        showAllNonBaseMenuItem: showAllNonBaseMenuItem,
535
+        closeWindow: closeWindow,
536
+
537
+        // Share Function
538
+        initWxData: initWxData,
539
+        changeWxData: changeWxData,
540
+        fixedWxData: fixedWxData,
541
+
542
+        // Voice Function
543
+        voice: voice,
544
+        translateVoice: translateVoice,
545
+        startRecord: startRecord,
546
+        stopRecord: stopRecord,
547
+        playVoice: playVoice,
548
+        pauseVoice: pauseVoice,
549
+        stopVoice: stopVoice,
550
+        uploadVoice: uploadVoice,
551
+        downloadVoice: downloadVoice,
552
+
553
+        // Image Function
554
+        images: images,
555
+        chooseImage: chooseImage,
556
+        previewImage: previewImage,
557
+        uploadImage: uploadImage,
558
+        uploadImages: uploadImages,
559
+        downloadImage: downloadImage,
560
+        downloadImages: downloadImages,
561
+        getLocalImgData: getLocalImgData,
562
+
563
+        // Scan Function
564
+        scanQRCode: scanQRCode,
565
+        parseScanQRCodeResultStr: parseScanQRCodeResultStr,
566
+
567
+        // Pay Function
568
+        chooseWXPay: chooseWXPay,
569
+
570
+        // EnterpriseRedPacket Function
571
+        openEnterpriseRedPacket: openEnterpriseRedPacket
572
+    }
573
+    e.JSWE = e.V = v
574
+})(window)

+ 52 - 0
jdjos/urls.py

@@ -0,0 +1,52 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+"""jdjos URL Configuration
4
+
5
+The `urlpatterns` list routes URLs to views. For more information please see:
6
+    https://docs.djangoproject.com/en/1.11/topics/http/urls/
7
+Examples:
8
+Function views
9
+    1. Add an import:  from my_app import views
10
+    2. Add a URL to urlpatterns:  url(r'^$', views.home, name='home')
11
+Class-based views
12
+    1. Add an import:  from other_app.views import Home
13
+    2. Add a URL to urlpatterns:  url(r'^$', Home.as_view(), name='home')
14
+Including another URLconf
15
+    1. Import the include() function: from django.conf.urls import url, include
16
+    2. Add a URL to urlpatterns:  url(r'^blog/', include('blog.urls'))
17
+"""
18
+from django.conf import settings
19
+from django.conf.urls import include, url
20
+from django.conf.urls.static import static
21
+from django.contrib import admin
22
+
23
+
24
+urlpatterns = [
25
+    url(r'^admin/', admin.site.urls),
26
+]
27
+
28
+urlpatterns += [
29
+    url(r'^api/', include('api.urls', namespace='api')),
30
+    url(r'^uniapi/', include('django_uniapi.urls', namespace='uniapi')),
31
+]
32
+
33
+urlpatterns += [
34
+    # url(r'^s/', include('django_short_url.urls', namespace='django_short_url')),
35
+]
36
+
37
+urlpatterns += [
38
+    url(r'^w/', include('django_we.urls', namespace='shortwechat')),
39
+    url(r'^we/', include('django_we.urls', namespace='wechat')),
40
+]
41
+
42
+urlpatterns += [
43
+    # url(r'^p/', include('page.urls', namespace='shortpage')),
44
+    # url(r'^page/', include('page.urls', namespace='page')),
45
+]
46
+
47
+urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
48
+urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
49
+
50
+# AdminSite
51
+admin.site.site_title = ''
52
+admin.site.site_header = 'My administration'

+ 17 - 0
jdjos/wsgi.py

@@ -0,0 +1,17 @@
1
+"""
2
+WSGI config for jdjos project.
3
+
4
+It exposes the WSGI callable as a module-level variable named ``application``.
5
+
6
+For more information on this file, see
7
+https://docs.djangoproject.com/en/1.11/howto/deployment/wsgi/
8
+"""
9
+
10
+import os
11
+
12
+from django.core.wsgi import get_wsgi_application
13
+
14
+
15
+os.environ.setdefault("DJANGO_SETTINGS_MODULE", "jdjos.settings")
16
+
17
+application = get_wsgi_application()

+ 23 - 0
manage.py

@@ -0,0 +1,23 @@
1
+#!/usr/bin/env python
2
+import os
3
+import sys
4
+
5
+
6
+if __name__ == "__main__":
7
+    os.environ.setdefault("DJANGO_SETTINGS_MODULE", "jdjos.settings")
8
+    try:
9
+        from django.core.management import execute_from_command_line
10
+    except ImportError:
11
+        # The above import may fail for some other reason. Ensure that the
12
+        # issue is really that Django is missing to avoid masking other
13
+        # exceptions on Python 2.
14
+        try:
15
+            import django
16
+        except ImportError:
17
+            raise ImportError(
18
+                "Couldn't import Django. Are you sure it's installed and "
19
+                "available on your PYTHONPATH environment variable? Did you "
20
+                "forget to activate a virtual environment?"
21
+            )
22
+        raise
23
+    execute_from_command_line(sys.argv)

+ 10 - 0
pep8.sh

@@ -0,0 +1,10 @@
1
+#!/bin/bash
2
+
3
+# Ignoring autogenerated files
4
+#  -- Migration directories
5
+# Ignoring error codes
6
+#  -- E128 continuation line under-indented for visual indent
7
+#  -- E402 module level import not at top of file
8
+#  -- E501 line too long
9
+
10
+pycodestyle --exclude=build,migrations,.tox --ignore=E128,E402,E501 .

+ 7 - 0
requirements.txt

@@ -0,0 +1,7 @@
1
+StatusCode==1.0.0
2
+furl==2.0.0
3
+jsonfield==2.0.2
4
+mysqlclient==1.3.13
5
+-r requirements_dj.txt
6
+-r requirements_pywe.txt
7
+-r requirements_redis.txt

+ 3 - 0
requirements_deploy.txt

@@ -0,0 +1,3 @@
1
+ipdb==0.11
2
+ipython==5.6.0
3
+uwsgi==2.0.17

+ 2 - 0
requirements_dev.txt

@@ -0,0 +1,2 @@
1
+isort==4.3.9
2
+pycodestyle==2.4.0

+ 15 - 0
requirements_dj.txt

@@ -0,0 +1,15 @@
1
+Django==1.11.16
2
+django-admin==1.3.2
3
+django-detect==1.0.8
4
+django-file==1.0.3
5
+django-json-render==1.0.2
6
+django-json-response==1.1.5
7
+django-logit==1.1.3
8
+django-models-ext==1.1.8
9
+django-redis-connector==1.0.1
10
+django-response==1.1.1
11
+django-rlog==1.0.7
12
+django-short-url==1.1.3
13
+django-six==1.0.4
14
+django-uniapi==1.0.5
15
+django-we==1.4.2

+ 2 - 0
requirements_pywe.txt

@@ -0,0 +1,2 @@
1
+pywe-oauth==1.0.7
2
+pywe-pay==1.0.12

+ 3 - 0
requirements_redis.txt

@@ -0,0 +1,3 @@
1
+hiredis==1.0.0
2
+redis==2.10.6
3
+redis-extensions==1.2.5

+ 4 - 0
sysctl.sh

@@ -0,0 +1,4 @@
1
+#!/bin/bash
2
+
3
+sudo sysctl -w net.core.somaxconn=4096
4
+sudo sysctl -w net.core.netdev_max_backlog=50000

+ 0 - 0
utils/__init__.py


+ 0 - 0
utils/error/__init__.py


+ 77 - 0
utils/error/errno_utils.py

@@ -0,0 +1,77 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+from StatusCode import BaseStatusCode, StatusCodeField
4
+
5
+
6
+class ParamStatusCode(BaseStatusCode):
7
+    """ 4000xx 参数相关错误码 """
8
+    PARAM_NOT_FOUND = StatusCodeField(400000, 'Param Not Found', description=u'参数不存在')
9
+
10
+
11
+class ProfileStatusCode(BaseStatusCode):
12
+    """ 4001xx 用户相关错误码 """
13
+    PROFILE_NOT_FOUND = StatusCodeField(400101, 'Profile Not Found', description=u'用户不存在')
14
+
15
+
16
+class PhoneStatusCode(BaseStatusCode):
17
+    """ 4002xx 手机相关错误码 """
18
+    INVALID_PHONE = StatusCodeField(400200, 'Invalid Phone', description=u'非法手机号')
19
+    PHONE_NOT_FOUND = StatusCodeField(400201, 'Phone Not Found', description=u'手机号不存在')
20
+    PHONE_ALREADY_EXISTS = StatusCodeField(400202, 'Phone Already Exists', description=u'手机号已存在')
21
+
22
+
23
+class OrderStatusCode(BaseStatusCode):
24
+    """ 4040xx 订单/支付相关错误码 """
25
+    UNIFIED_ORDER_FAIL = StatusCodeField(404000, 'Unified Order Fail', description=u'统一下单失败')
26
+    ORDER_NOT_FOUND = StatusCodeField(404001, 'Order Not Found', description=u'订单不存在')
27
+    # 订单支付状态
28
+    ORDER_NOT_PAY = StatusCodeField(404011, 'Order Not Pay', description=u'订单未支付')
29
+    ORDER_PAYING = StatusCodeField(404012, 'Order Paying', description=u'订单支付中')
30
+    ORDER_PAY_FAIL = StatusCodeField(404013, 'Order Pay Fail', description=u'微信支付失败')
31
+    # 通知校验状态
32
+    SIGN_CHECK_FAIL = StatusCodeField(404090, 'Sign Check Fail', description=u'签名校验失败')
33
+    FEE_CHECK_FAIL = StatusCodeField(404091, 'FEE Check Fail', description=u'金额校验失败')
34
+
35
+
36
+class PayStatusCode(BaseStatusCode):
37
+    """ 4041xx 支付相关错误码 """
38
+
39
+
40
+class WithdrawStatusCode(BaseStatusCode):
41
+    """ 4042xx 提现相关错误码 """
42
+    BALANCE_INSUFFICIENT = StatusCodeField(404200, 'Balance Insufficient', description=u'提现金额不足')
43
+
44
+
45
+class TokenStatusCode(BaseStatusCode):
46
+    """ 4090xx 票据相关错误码 """
47
+    TOKEN_NOT_FOUND = StatusCodeField(409001, 'Token Not Found', description=u'票据不存在')
48
+
49
+
50
+class SignatureStatusCode(BaseStatusCode):
51
+    """ 4091xx 签名校验错误 """
52
+    SIGNATURE_ERROR = StatusCodeField(409101, 'Signature Error', description=u'签名错误')
53
+
54
+
55
+class GVCodeStatusCode(BaseStatusCode):
56
+    """ 4092xx 图形验证码相关错误码 """
57
+    GRAPHIC_VCODE_ERROR = StatusCodeField(409201, 'Graphic VCode Error', description=u'图形验证码错误')
58
+
59
+
60
+class SVCodeStatusCode(BaseStatusCode):
61
+    """ 4093xx 短信验证码相关错误码 """
62
+    SMS_QUOTA_LIMIT = StatusCodeField(409300, 'SMS Quota Limit', description=u'短信次数超限')
63
+    SMS_VCODE_ERROR = StatusCodeField(409301, 'SMS VCode Error', description=u'验证码错误,请稍后重试')
64
+    SMS_VCODE_HAS_SEND = StatusCodeField(409302, 'SMS VCode Has Send', description=u'验证码已发送,请勿重复获取')
65
+
66
+
67
+class InsufficientStatusCode(BaseStatusCode):
68
+    """ 4095xx 不足相关错误码 """
69
+    BALANCE_INSUFFICIENT = StatusCodeField(409501, 'Balance Insufficient', description=u'余额不足')
70
+    INTEGRAL_INSUFFICIENT = StatusCodeField(409502, 'Integral Insufficient', description=u'积分不足')
71
+
72
+
73
+class PermissionStatusCode(BaseStatusCode):
74
+    """ 4099xx 权限相关错误码 """
75
+    PERMISSION_DENIED = StatusCodeField(409900, 'Permission Denied', description=u'权限不足')
76
+    UPLOAD_PERMISSION_DENIED = StatusCodeField(409910, 'Upload Permission Denied', description=u'上传权限不足')
77
+    UPDATE_PERMISSION_DENIED = StatusCodeField(409930, 'Update Permission Denied', description=u'更新权限不足')

+ 24 - 0
utils/error/response_utils.py

@@ -0,0 +1,24 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+from json_response import JsonpResponse, JsonResponse
4
+from StatusCode import StatusCodeField
5
+
6
+
7
+def response_data(status_code=200, message=None, description=None, data={}, **kwargs):
8
+    return dict({
9
+        'status': status_code,
10
+        'message': message,
11
+        'description': description,
12
+        'data': data,
13
+    }, **kwargs)
14
+
15
+
16
+def response(status_code=200, message=None, description=None, data={}, msg_args=[], msg_kwargs={}, desc_args=[], desc_kwargs={}, request=None, callback=None, **kwargs):
17
+    # Final Message and Description
18
+    message, description = (message or status_code.message, description or status_code.description) if isinstance(status_code, StatusCodeField) else (message, description)
19
+    # Final Response Data
20
+    resp_data = response_data(status_code, (message or '').format(*msg_args, **msg_kwargs), (description or '').format(*desc_args, **desc_kwargs), data, **kwargs)
21
+    # Assign Callback
22
+    callback = callback or (request and request.GET.get('callback'))
23
+    # Call JsonResponse or JsonpResponse
24
+    return JsonpResponse(callback, resp_data, safe=False) if callback else JsonResponse(resp_data, safe=False)

+ 0 - 0
utils/redis/__init__.py


+ 6 - 0
utils/redis/connect.py

@@ -0,0 +1,6 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+from django.conf import settings
4
+
5
+
6
+r = settings.REDIS_CACHE

+ 1 - 0
utils/redis/rkeys.py

@@ -0,0 +1 @@
1
+# -*- coding: utf-8 -*-

+ 0 - 0
utils/user/__init__.py


+ 18 - 0
utils/user/userinfo_save.py

@@ -0,0 +1,18 @@
1
+# -*- coding: utf-8 -*-
2
+
3
+
4
+def userinfo_save(userinfo):
5
+    """ Save profile or something else """
6
+    # from account.models import UserInfo
7
+    # from django.conf import settings
8
+    #
9
+    # unique_identifier = userinfo.get(settings.WECHAT_UNIQUE_IDENTIFICATION, '')
10
+    #
11
+    # user, created = UserInfo.objects.select_for_update().get_or_create(**{settings.WECHAT_UNIQUE_IDENTIFICATION: unique_identifier})
12
+    # user.unionid = userinfo.get('unionid', '')
13
+    # user.openid = userinfo.get('openid', '')
14
+    # user.nickname = userinfo.get('nickname', '')
15
+    # user.avatar = userinfo.get('headimgurl', '')
16
+    # user.save()
17
+    #
18
+    # return user

add Toast · 37a8899d90 - Gogs: Go Git Service

add Toast

FFIB 7 jaren geleden
bovenliggende
commit
37a8899d90
43 gewijzigde bestanden met toevoegingen van 776 en 776 verwijderingen
  1. 0 4
      PaiAi/Paiai.xcodeproj/project.pbxproj
  2. BIN
      PaiAi/Paiai.xcodeproj/project.xcworkspace/xcuserdata/FFIB.xcuserdatad/UserInterfaceState.xcuserstate
  3. 12 0
      PaiAi/Paiai.xcodeproj/xcuserdata/FFIB.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist
  4. 1 1
      PaiAi/Paiai/Assets.xcassets/module/home/BTN-camera.imageset/Contents.json
  5. 1 1
      PaiAi/Paiai/Assets.xcassets/module/home/BTN-delete.imageset/Contents.json
  6. 0 0
      PaiAi/Paiai/Assets.xcassets/module/photoDetail/icon-success.imageset/Contents.json
  7. 0 0
      PaiAi/Paiai/Assets.xcassets/module/photoDetail/icon-success.imageset/提示弹窗-勾.png
  8. 21 0
      PaiAi/Paiai/Assets.xcassets/navigation/navigation-background-black.imageset/Contents.json
  9. BIN
      PaiAi/Paiai/Assets.xcassets/navigation/navigation-background-black.imageset/未命名设计.png
  10. 11 7
      PaiAi/PaiaiDataKit/DataLayer/Model/GroupItem.swift
  11. 7 2
      PaiAi/PaiaiDataKit/DataLayer/Model/PhotoCommentItem.swift
  12. 1 1
      PaiAi/PaiaiDataKit/DataLayer/Repositories/Remote/GroupRemoteAPI.swift
  13. 15 12
      PaiAi/PaiaiDataKit/PresentLayer/Group/GroupDetail/GroupDetailViewModel.swift
  14. 2 0
      PaiAi/PaiaiDataKit/PresentLayer/Group/GroupDetail/GroupMemberViewModel.swift
  15. 9 0
      PaiAi/PaiaiDataKit/PresentLayer/Group/GroupViewModel.swift
  16. 4 2
      PaiAi/PaiaiDataKit/PresentLayer/Home/CreateGroupViewModel.swift
  17. 16 5
      PaiAi/PaiaiDataKit/PresentLayer/Home/ScanQRViewModel.swift
  18. 4 2
      PaiAi/PaiaiDataKit/PresentLayer/Message/MessageListViewModel.swift
  19. 0 24
      PaiAi/PaiaiDataKit/PresentLayer/Mine/MineFeedbackViewModel.swift
  20. 2 1
      PaiAi/PaiaiDataKit/PresentLayer/Mine/MineGroupViewModel.swift
  21. 51 10
      PaiAi/PaiaiDataKit/Resuable/Toast/Toast.swift
  22. 1 1
      PaiAi/PaiaiDataKit/Resuable/Toast/ToastOption.swift
  23. 80 66
      PaiAi/PaiaiDataKit/Resuable/Toast/ToastView.swift
  24. 7 0
      PaiAi/PaiaiUIKit/Reusable/Extension/UIKit/UIImageExt.swift
  25. 0 5
      PaiAi/PaiaiUIKit/Reusable/Extension/UIKit/UIViewController+Navigation.swift
  26. 1 1
      PaiAi/PaiaiUIKit/Reusable/UIKit/QR/QRCodeScanDelegate.swift
  27. 14 16
      PaiAi/PaiaiUIKit/Reusable/UIKit/QR/QRCodeScanView.swift
  28. 23 1
      PaiAi/Paiai_iOS/App/Group/Group.storyboard
  29. 155 248
      PaiAi/Paiai_iOS/App/Group/GroupDetail/GroupDetail.storyboard
  30. 3 3
      PaiAi/Paiai_iOS/App/Group/GroupDetail/GroupDetailCoordinator.swift
  31. 4 4
      PaiAi/Paiai_iOS/App/Group/GroupDetail/GroupDetailViewController.swift
  32. 6 6
      PaiAi/Paiai_iOS/App/Group/GroupDetail/GroupMemberCell.swift
  33. 15 3
      PaiAi/Paiai_iOS/App/Group/GroupViewController.swift
  34. 14 2
      PaiAi/Paiai_iOS/App/Home/HomeCoordinator.swift
  35. 4 2
      PaiAi/Paiai_iOS/App/Home/HomeViewController.swift
  36. 75 66
      PaiAi/Paiai_iOS/App/Home/Main.storyboard
  37. 23 86
      PaiAi/Paiai_iOS/App/Home/ScanQRViewController.swift
  38. 173 173
      PaiAi/Paiai_iOS/App/Message/Message.storyboard
  39. 1 1
      PaiAi/Paiai_iOS/App/Mine/GroupCell.swift
  40. 1 1
      PaiAi/Paiai_iOS/App/Mine/MineFeedbackViewController.swift
  41. 5 5
      PaiAi/Paiai_iOS/App/PhotoDetail/PhotoDetailCommentCell.swift
  42. 1 1
      PaiAi/Paiai_iOS/App/PhotoDetail/PhotoDetailCoordinator.swift
  43. 13 13
      PaiAi/Paiai_iOS/App/PhotoDetail/PhotoPreviewViewController.swift

+ 0 - 4
PaiAi/Paiai.xcodeproj/project.pbxproj

@@ -171,7 +171,6 @@
171 171
 		053E127521F5A72000A64893 /* Error.swift in Sources */ = {isa = PBXBuildFile; fileRef = 053E127421F5A72000A64893 /* Error.swift */; };
172 172
 		053E127821F5B6E400A64893 /* AlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 053E127721F5B6E400A64893 /* AlertController.swift */; };
173 173
 		0543E7F421CB911200A42807 /* UserInfoViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0543E7F321CB911200A42807 /* UserInfoViewModel.swift */; };
174
-		0543E80721D0CDFA00A42807 /* MineFeedbackViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0543E80621D0CDFA00A42807 /* MineFeedbackViewModel.swift */; };
175 174
 		0543E80B21D1DF4000A42807 /* GroupMemberItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0543E80A21D1DF4000A42807 /* GroupMemberItem.swift */; };
176 175
 		0543E80D21D1E2EA00A42807 /* PhotoGroupDetailRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0543E80C21D1E2EA00A42807 /* PhotoGroupDetailRepository.swift */; };
177 176
 		0543E80F21D1FD1100A42807 /* GroupDetailItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0543E80E21D1FD1100A42807 /* GroupDetailItem.swift */; };
@@ -413,7 +412,6 @@
413 412
 		0543274721C68C1900C6388D /* DateExt.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DateExt.swift; sourceTree = "<group>"; };
414 413
 		0543276621C68C3300C6388D /* UIImageView+Kingfisher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIImageView+Kingfisher.swift"; sourceTree = "<group>"; };
415 414
 		0543E7F321CB911200A42807 /* UserInfoViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserInfoViewModel.swift; sourceTree = "<group>"; };
416
-		0543E80621D0CDFA00A42807 /* MineFeedbackViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MineFeedbackViewModel.swift; sourceTree = "<group>"; };
417 415
 		0543E80A21D1DF4000A42807 /* GroupMemberItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupMemberItem.swift; sourceTree = "<group>"; };
418 416
 		0543E80C21D1E2EA00A42807 /* PhotoGroupDetailRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoGroupDetailRepository.swift; sourceTree = "<group>"; };
419 417
 		0543E80E21D1FD1100A42807 /* GroupDetailItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GroupDetailItem.swift; sourceTree = "<group>"; };
@@ -948,7 +946,6 @@
948 946
 			children = (
949 947
 				42CF50451EC0526C00D23E9F /* MineGroupViewModel.swift */,
950 948
 				42CF50471EC052B200D23E9F /* MineOrderViewModel.swift */,
951
-				0543E80621D0CDFA00A42807 /* MineFeedbackViewModel.swift */,
952 949
 			);
953 950
 			path = Mine;
954 951
 			sourceTree = "<group>";
@@ -1710,7 +1707,6 @@
1710 1707
 				057CA9CE21DDAE8100FB7D03 /* GroupDetailRepository.swift in Sources */,
1711 1708
 				0543E80B21D1DF4000A42807 /* GroupMemberItem.swift in Sources */,
1712 1709
 				0513103121CA1B67004EF1BE /* MessageRepository.swift in Sources */,
1713
-				0543E80721D0CDFA00A42807 /* MineFeedbackViewModel.swift in Sources */,
1714 1710
 				0513103321CA1B67004EF1BE /* UserInfoRepository.swift in Sources */,
1715 1711
 				0513103521CA1B67004EF1BE /* RecentGroupInfo.swift in Sources */,
1716 1712
 				0513103921CA1B67004EF1BE /* HomeViewModel.swift in Sources */,

BIN
PaiAi/Paiai.xcodeproj/project.xcworkspace/xcuserdata/FFIB.xcuserdatad/UserInterfaceState.xcuserstate


+ 12 - 0
PaiAi/Paiai.xcodeproj/xcuserdata/FFIB.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist

@@ -2,4 +2,16 @@
2 2
 <Bucket
3 3
    type = "1"
4 4
    version = "2.0">
5
+   <Breakpoints>
6
+      <BreakpointProxy
7
+         BreakpointExtensionID = "Xcode.Breakpoint.ExceptionBreakpoint">
8
+         <BreakpointContent
9
+            shouldBeEnabled = "Yes"
10
+            ignoreCount = "0"
11
+            continueAfterRunningActions = "No"
12
+            scope = "0"
13
+            stopOnStyle = "0">
14
+         </BreakpointContent>
15
+      </BreakpointProxy>
16
+   </Breakpoints>
5 17
 </Bucket>

+ 1 - 1
PaiAi/Paiai/Assets.xcassets/module/home/BTN-camera.imageset/Contents.json

@@ -2,6 +2,7 @@
2 2
   "images" : [
3 3
     {
4 4
       "idiom" : "universal",
5
+      "filename" : "BTN-拍照.png",
5 6
       "scale" : "1x"
6 7
     },
7 8
     {
@@ -10,7 +11,6 @@
10 11
     },
11 12
     {
12 13
       "idiom" : "universal",
13
-      "filename" : "BTN-拍照.png",
14 14
       "scale" : "3x"
15 15
     }
16 16
   ],

+ 1 - 1
PaiAi/Paiai/Assets.xcassets/module/home/BTN-delete.imageset/Contents.json

@@ -2,6 +2,7 @@
2 2
   "images" : [
3 3
     {
4 4
       "idiom" : "universal",
5
+      "filename" : "delete.png",
5 6
       "scale" : "1x"
6 7
     },
7 8
     {
@@ -10,7 +11,6 @@
10 11
     },
11 12
     {
12 13
       "idiom" : "universal",
13
-      "filename" : "delete.png",
14 14
       "scale" : "3x"
15 15
     }
16 16
   ],

PaiAi/Paiai/Assets.xcassets/module/photoDetail/提示弹窗-勾.imageset/Contents.json → PaiAi/Paiai/Assets.xcassets/module/photoDetail/icon-success.imageset/Contents.json


PaiAi/Paiai/Assets.xcassets/module/photoDetail/提示弹窗-勾.imageset/提示弹窗-勾.png → PaiAi/Paiai/Assets.xcassets/module/photoDetail/icon-success.imageset/提示弹窗-勾.png


+ 21 - 0
PaiAi/Paiai/Assets.xcassets/navigation/navigation-background-black.imageset/Contents.json

@@ -0,0 +1,21 @@
1
+{
2
+  "images" : [
3
+    {
4
+      "idiom" : "universal",
5
+      "filename" : "未命名设计.png",
6
+      "scale" : "1x"
7
+    },
8
+    {
9
+      "idiom" : "universal",
10
+      "scale" : "2x"
11
+    },
12
+    {
13
+      "idiom" : "universal",
14
+      "scale" : "3x"
15
+    }
16
+  ],
17
+  "info" : {
18
+    "version" : 1,
19
+    "author" : "xcode"
20
+  }
21
+}

BIN
PaiAi/Paiai/Assets.xcassets/navigation/navigation-background-black.imageset/未命名设计.png


+ 11 - 7
PaiAi/PaiaiDataKit/DataLayer/Model/GroupItem.swift

@@ -10,7 +10,6 @@ import Foundation
10 10
 import ObjectMapper
11 11
 import RxDataSources
12 12
 
13
-
14 13
 public struct GroupItem: JSONCode {
15 14
     public var create_at = ""
16 15
     public var created_at: Date?
@@ -23,6 +22,7 @@ public struct GroupItem: JSONCode {
23 22
     public var group_id = ""
24 23
     public var group_avatar = ""
25 24
     public var group_photo_num = 0
25
+    
26 26
     //tour group info
27 27
     public var gathered_at = ""
28 28
     public var gather_str = ""
@@ -69,13 +69,17 @@ extension GroupItem: Mappable {
69 69
         gather_lat           <-  map["gather_lat"]
70 70
         gather_lon           <-  map["gather_lon"]
71 71
         gather_screenshot    <-  map["gather_screenshot"]
72
-        //        gather_str = gather_at?.getTimeString(format: "HH点MM分")
73
-        guard let value = map["banners"].currentValue as? [String: Any] else {
74
-            return
72
+        if let value = map["banners"].currentValue as? [String: Any] {
73
+            let banner = Map(mappingType: MappingType.fromJSON, JSON: value)
74
+            attentions           <-  banner["attentions"]
75
+            schedules            <-  banner["schedules"]
76
+        }
77
+        
78
+        if let date = created_at {
79
+            let createDateFormatter = DateFormatter()
80
+            createDateFormatter.dateFormat = "yyyy年MM月dd日 HH:mm"
81
+            create_at = createDateFormatter.string(from: date)
75 82
         }
76
-        let banner = Map(mappingType: MappingType.fromJSON, JSON: value)
77
-        attentions           <-  banner["attentions"]
78
-        schedules            <-  banner["schedules"]
79 83
     }
80 84
 }
81 85
 

+ 7 - 2
PaiAi/PaiaiDataKit/DataLayer/Model/PhotoCommentItem.swift

@@ -16,7 +16,8 @@ public struct PhotoCommentItem: JSONCode {
16 16
     public var comment: String = ""
17 17
     public var user_id: String = ""
18 18
     public var nickname: String = ""
19
-    public var create_at: Date?
19
+    public var created_at: Date?
20
+    public var create_at: String = ""
20 21
     
21 22
     init(json: [String: AnyObject]) {
22 23
         self.init(map: Map(mappingType: .fromJSON, JSON: json))
@@ -26,12 +27,16 @@ extension PhotoCommentItem: Mappable {
26 27
     mutating public func mapping(map: Map) {
27 28
         let dateFormatter = DateFormatter()
28 29
         dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZ"
29
-        create_at      <-  (map["created_at"], DateFormatterTransform(dateFormatter:dateFormatter))
30
+        created_at      <-  (map["created_at"], DateFormatterTransform(dateFormatter:dateFormatter))
30 31
         avatar          <-  (map["avatar"])
31 32
         comment         <-  map["comment"]
32 33
         user_id         <-  map["user_id"]
33 34
         nickname        <-  (map["nickname"])
34 35
         comment         <-  map["comment"]
36
+        
37
+        if let date = created_at {
38
+            create_at = date.getTimeInfoFromDate()
39
+        }
35 40
     }
36 41
     
37 42
     public init(map: Map) {

+ 1 - 1
PaiAi/PaiaiDataKit/DataLayer/Repositories/Remote/GroupRemoteAPI.swift

@@ -18,7 +18,7 @@ struct GroupRemoteAPI {
18 18
     
19 19
     private func parse(_ json: JSON) -> NetworkArrayData<GroupItem>? {
20 20
         guard let data  = json["data"] as? [String: AnyObject] else { return nil }
21
-        return NetworkArrayData.init(json: data, dataField: "groups")
21
+        return NetworkArrayData(json: data, dataField: "groups")
22 22
     }
23 23
     
24 24
     private func parseGroup(_ json: JSON) -> GroupItem? {

+ 15 - 12
PaiAi/PaiaiDataKit/PresentLayer/Group/GroupDetail/GroupDetailViewModel.swift

@@ -11,9 +11,9 @@ import RxSwift
11 11
 import RxCocoa
12 12
 
13 13
 public protocol GroupDetailViewModelDelegate: class {
14
-    func navigationToGroupMember(_ item: GroupDetailItem)
15
-    func navigationToGroupNameModification(_ item: GroupDetailItem)
16
-    func navigationToRootViewController()
14
+    func navigateToGroupMember(_ item: GroupDetailItem)
15
+    func navigateToGroupNameModification(_ item: GroupDetailItem)
16
+    func navigateToRootViewController()
17 17
 }
18 18
 
19 19
 public class GroupDetailViewModel {
@@ -68,14 +68,14 @@ public class GroupDetailViewModel {
68 68
                 var v = self.item.value
69 69
                 v.group.group_lock = true
70 70
                 self.item.accept(v)
71
-            }) { (error) in
72
-                
73
-            }.disposed(by: disposeBag)
71
+                Toast.show(message: "群已锁定")
72
+            }).disposed(by: disposeBag)
74 73
         } else {
75 74
             repository.unlock().subscribe(onCompleted: {
76 75
                 var v = self.item.value
77 76
                 v.group.group_lock = false
78
-            }, onError: nil).disposed(by: disposeBag)
77
+                Toast.show(message: "群未锁定")
78
+            }).disposed(by: disposeBag)
79 79
         }
80 80
     }
81 81
     
@@ -87,21 +87,24 @@ public class GroupDetailViewModel {
87 87
     
88 88
     public func quit() {
89 89
         guard item.value.group.admin_id != ShareUserId else {
90
+            Toast.show(message: "群主不能退出群")
90 91
             return
91 92
         }
93
+        Toast.showActivity(message: "正在退出群")
92 94
         repository.quit()
93 95
             .subscribe(onCompleted: {[unowned self] in
94
-                self.delegate?.navigationToRootViewController()
96
+                Toast.show(message: "退出群成功")
97
+                self.delegate?.navigateToRootViewController()
95 98
             }).disposed(by: disposeBag)
96 99
     }
97 100
 }
98 101
 
99 102
 public extension GroupDetailViewModel {
100
-    func navigationToGroupMember() {
101
-        delegate?.navigationToGroupMember(item.value)
103
+    func navigateToGroupMember() {
104
+        delegate?.navigateToGroupMember(item.value)
102 105
     }
103 106
     
104
-    func navigationToGroupNameModification() {
105
-        delegate?.navigationToGroupNameModification(item.value)
107
+    func navigateToGroupNameModification() {
108
+        delegate?.navigateToGroupNameModification(item.value)
106 109
     }
107 110
 }

+ 2 - 0
PaiAi/PaiaiDataKit/PresentLayer/Group/GroupDetail/GroupMemberViewModel.swift

@@ -33,8 +33,10 @@ public class GroupMemberViewModel {
33 33
     }
34 34
     
35 35
     public func removeMember(_ item: GroupMemberItem) {
36
+        Toast.showActivity(message: "正在删除群成员")
36 37
         repository.removeMember(userId: item.user_id)
37 38
             .subscribe(onCompleted: {[unowned self] in
39
+                Toast.showActivity(message: "群成员已删除")
38 40
                 var content = self.items.value
39 41
                 content.removeAll(where: { $0.user_id == item.user_id })
40 42
                 self.items.accept(content)

+ 9 - 0
PaiAi/PaiaiDataKit/PresentLayer/Group/GroupViewModel.swift

@@ -25,6 +25,7 @@ public class GroupViewModel {
25 25
     
26 26
     private var _isLoading = PublishSubject<Void>()
27 27
     private let items = BehaviorRelay<[PhotoItem]>(value: [])
28
+    private let _hasData = BehaviorRelay<Bool>(value: false)
28 29
     
29 30
     public var groupItem: BehaviorRelay<GroupItem>
30 31
     
@@ -43,6 +44,10 @@ public class GroupViewModel {
43 44
     public var isLoading: Observable<Void> {
44 45
         return _isLoading.asObservable()
45 46
     }
47
+    
48
+    public var hasData: Observable<Bool> {
49
+        return _hasData.asObservable()
50
+    }
46 51
 
47 52
     public var contents: Observable<[AnimatableSectionModel<Int, PhotoItem>]> {
48 53
         return items.map({ model in
@@ -64,6 +69,7 @@ public class GroupViewModel {
64 69
                 guard let `self` = self else { return }
65 70
                 self._isLoading.onNext(())
66 71
                 self.items.accept(result)
72
+                self._hasData.accept(result.count > 0)
67 73
                 }, onError: {[weak self] (error) in
68 74
                     guard let `self` = self else { return }
69 75
                     self._isLoading.onNext(())
@@ -71,12 +77,15 @@ public class GroupViewModel {
71 77
     }
72 78
 
73 79
     public func submit(data: Data) {
80
+        Toast.showActivity(message: "正在上传照片")
74 81
         repository.upload(data: data).subscribe(onSuccess: { [weak self] result in
75 82
             guard let `self` = self else { return }
76 83
             self.items.accept(result)
84
+            Toast.show(message: "照片已上传")
77 85
             self._isLoading.onNext(())
78 86
         }) {[weak self] (error) in
79 87
             guard let  `self` = self else { return }
88
+            Toast.show(message: "照片失败")
80 89
             self._isLoading.onNext(())
81 90
         }.disposed(by: disposeBag)
82 91
     }

+ 4 - 2
PaiAi/PaiaiDataKit/PresentLayer/Home/CreateGroupViewModel.swift

@@ -11,7 +11,7 @@ import RxSwift
11 11
 import RxCocoa
12 12
 
13 13
 public protocol CreateGroupViewModelDelegate: class {
14
-    func navigationToGroup(_ item: GroupItem)
14
+    func navigateToGroup(_ item: GroupItem)
15 15
 }
16 16
 
17 17
 public final class CreateGroupViewModel {
@@ -40,9 +40,11 @@ public final class CreateGroupViewModel {
40 40
     }
41 41
     
42 42
     public func createGroup() {
43
+        Toast.showActivity(message: "正在创建群")
43 44
         return repository.create(groupName: name, avatar: "\(mappingIndex)")
44 45
             .subscribe(onSuccess: {[unowned self] item in
45
-                self.delegate?.navigationToGroup(item)
46
+                Toast.show(message: "照片分享群已创建")
47
+                self.delegate?.navigateToGroup(item)
46 48
             }).disposed(by: disposeBag)
47 49
     }
48 50
 }

+ 16 - 5
PaiAi/PaiaiDataKit/PresentLayer/Home/ScanQRViewModel.swift

@@ -7,16 +7,18 @@
7 7
 //
8 8
 
9 9
 import Foundation
10
+import RxSwift
10 11
 
11
-protocol ScanQRViewModelDelegate: class {
12
-    func navigateToGroupPage(item: GroupItem)
12
+public protocol ScanQRViewModelDelegate: class {
13
+    func navigateToGroupFromScanQR(_ item: GroupItem)
13 14
 }
14 15
 
15 16
 public final class ScanQRViewModel {
16 17
     
17
-    weak var delegate: ScanQRViewModelDelegate?
18
+    public weak var delegate: ScanQRViewModelDelegate?
18 19
     
19 20
     var repository: PhotoGroupRepository
21
+    var disposeBag = DisposeBag()
20 22
     
21 23
     public init() {
22 24
         repository = PhotoGroupRepository()
@@ -26,15 +28,24 @@ public final class ScanQRViewModel {
26 28
         let strs = code.components(separatedBy: "/")
27 29
         guard strs.count > 2, let params = strs.last else { return }
28 30
         let flag = strs[strs.count - 2]
31
+        
29 32
         switch flag {
30 33
         case "s":
31 34
             guard let lensman_id = params.components(separatedBy: "=").last,
32 35
                 let session_id = params.components(separatedBy: "?").first else { return }
33 36
             
34
-            repository.join(type: .session, parameter: ["session_id": session_id, "lensman_id": lensman_id])
37
+            repository.join(type: .session,
38
+                            parameter: ["session_id": session_id, "lensman_id": lensman_id])
39
+                .subscribe(onSuccess: { item in
40
+                self.delegate?.navigateToGroupFromScanQR(item)
41
+            }).disposed(by: disposeBag)
35 42
             
36 43
         case "g":
37
-            repository.join(type: .session, parameter: ["group_id": params])
44
+            repository.join(type: .group,
45
+                            parameter: ["group_id": params])
46
+                .subscribe(onSuccess: { item in
47
+                self.delegate?.navigateToGroupFromScanQR(item)
48
+            }).disposed(by: disposeBag)
38 49
         default:
39 50
             return
40 51
         }

+ 4 - 2
PaiAi/PaiaiDataKit/PresentLayer/Message/MessageListViewModel.swift

@@ -92,23 +92,25 @@ public class MessageListViewModel {
92 92
     }
93 93
     
94 94
     public func remove(of index: Int) {
95
+        Toast.showActivity(message: "正在删除这条消息")
95 96
         repository.remove(type, pk: items.value[index].pk)
96 97
             .subscribe(onCompleted: {[weak self] in
97 98
                 guard let `self` = self else { return }
98 99
                 var _items = self.items.value
99 100
                 _items.remove(at: index)
100 101
                 self.items.accept(_items)
101
-                self._showMessage.onNext("删除成功")
102
+                self._showMessage.onNext("消息删除")
102 103
             }).disposed(by: disposeBag)
103 104
     }
104 105
     
105 106
     public func removeAll() {
107
+        Toast.showActivity(message: "正在删除全部消息")
106 108
         repository.removeAll(type)
107 109
             .subscribe(onCompleted: {[weak self] in
108 110
                 guard let `self` = self else { return }
109 111
                 self.items.accept([])
110 112
                 self._isEmpty.accept(false)
111
-                self._showMessage.onNext("删除成功")
113
+                self._showMessage.onNext("消息已清空")
112 114
             }).disposed(by: disposeBag)
113 115
     }
114 116
     

+ 0 - 24
PaiAi/PaiaiDataKit/PresentLayer/Mine/MineFeedbackViewModel.swift

@@ -1,24 +0,0 @@
1
-//
2
-//  MineFeedbackViewModel.swift
3
-//  PaiaiDataKit
4
-//
5
-//  Created by ffib on 2018/12/24.
6
-//  Copyright © 2018 yb. All rights reserved.
7
-//
8
-
9
-import Foundation
10
-
11
-protocol MineFeedbackViewModelDelegate: class {
12
-    func back()
13
-}
14
-
15
-class MineFeedbackViewModel {
16
-    weak var delegate: MineFeedbackViewModelDelegate?
17
-    var api = FeedbackRemoteAPI()
18
-    
19
-    func submit(_ text: String) {
20
-        api.submit(text: text).subscribe(onCompleted: {
21
-            self.delegate?.back()
22
-        }).dispose()
23
-    }
24
-}

+ 2 - 1
PaiAi/PaiaiDataKit/PresentLayer/Mine/MineGroupViewModel.swift

@@ -87,13 +87,14 @@ public class MineGroupViewModel {
87 87
     }
88 88
     
89 89
     public func remove(of index: Int) {
90
+        Toast.showActivity(message: "正在删除此群")
90 91
         repository.remove(groupId: items.value[index].group_id)
91 92
             .subscribe(onCompleted: {[weak self] in
92 93
                 guard let `self` = self else { return }
93 94
                 var _items = self.items.value
94 95
                 _items.remove(at: index)
95 96
                 self.items.accept(_items)
96
-                self._showMessage.onNext("删除成功")
97
+                self._showMessage.onNext("群已删除")
97 98
             }).disposed(by: disposeBag)
98 99
     }
99 100
     

+ 51 - 10
PaiAi/PaiaiDataKit/Resuable/Toast/Toast.swift

@@ -32,11 +32,18 @@ public class Toast {
32 32
         }
33 33
     }
34 34
     
35
+    public enum AnimationDuration {
36
+        case none
37
+        case duration(TimeInterval)
38
+    }
39
+    
35 40
     public struct Config {
36 41
         public init() {}
37 42
         
38 43
         public var toastType: ToastType = .text("")
39 44
         public var presentationStyle: PresentationStyle = .center
45
+        public var duration: AnimationDuration = .duration(1)
46
+        public var option: ToastOption = .default
40 47
         
41 48
         public static var `default`: Config {
42 49
             return Config()
@@ -45,17 +52,37 @@ public class Toast {
45 52
     
46 53
     private var _toastView: ToastView?
47 54
     private var _config: Config = .default
48
-    
49
-    
55
+    private var _task: DispatchWorkItem?
50 56
 }
51 57
 
52 58
 /// MARK: - APIS
53 59
 public extension Toast {
54 60
     func show(config: Config = .default, in view: UIView? = UIApplication.shared.keyWindow) {
55
-        let toastView = ToastView(option: .default, type: config.toastType)
56
-        view?.addSubview(toastView)
57
-        self._toastView = toastView
58
-        showAnimation()
61
+        self._config = config
62
+        if _toastView == nil {
63
+            let toastView = ToastView(option: config.option, type: config.toastType)
64
+            view?.addSubview(toastView)
65
+            _toastView = toastView
66
+            showAnimation()
67
+        } else {
68
+            if case let .duration(interval) = config.duration {
69
+                if let task = _task {
70
+                    let _ = task.wait(timeout: .now() + interval)
71
+                } else {
72
+                    self._task = DispatchWorkItem(block: {
73
+                        self.hideAnimation()
74
+                    })
75
+                    DispatchQueue.main.asyncAfter(deadline: .now() + interval, execute: self._task!)
76
+                }
77
+            } else {
78
+                _task?.cancel()
79
+                _task = nil
80
+            }
81
+            
82
+            _toastView?.option = config.option
83
+            _toastView?.toastType = config.toastType
84
+            _toastView?.switchToast()
85
+        }
59 86
     }
60 87
     
61 88
     func show(message: String, in view: UIView? = UIApplication.shared.keyWindow) {
@@ -79,6 +106,7 @@ public extension Toast {
79 106
     func showActivity(message: String?, in view: UIView? = UIApplication.shared.keyWindow) {
80 107
         var config = Config()
81 108
         config.toastType = .activityIndicator(message)
109
+        config.duration = .none
82 110
         show(config: config, in: view)
83 111
     }
84 112
     
@@ -90,9 +118,16 @@ public extension Toast {
90 118
         guard let _toastView = _toastView else { return }
91 119
         _config.presentationStyle.animator.toastIn(in: _toastView) {[weak self] _ in
92 120
             guard let `self` = self else { return }
93
-            DispatchQueue.main.asyncAfter(deadline: .now() + 1, execute: {
94
-                self.hide()
95
-            })
121
+            switch self._config.duration {
122
+            case .none:
123
+                break
124
+            case let .duration(interval):
125
+                self._task = DispatchWorkItem(block: {
126
+                    self.hideAnimation()
127
+                })
128
+                DispatchQueue.main.asyncAfter(deadline: .now() + interval, execute: self._task!)
129
+                break
130
+            }
96 131
         }
97 132
     }
98 133
     
@@ -100,9 +135,15 @@ public extension Toast {
100 135
         guard let _toastView = _toastView else { return }
101 136
         _config.presentationStyle.animator.toastOut(in: _toastView) {[weak self] (flag) in
102 137
             guard let `self` = self else { return }
103
-            self._toastView?.removeFromSuperview()
138
+            self.clear()
104 139
         }
105 140
     }
141
+    
142
+    private func clear() {
143
+        _toastView?.removeFromSuperview()
144
+        _toastView = nil
145
+        _task = nil
146
+    }
106 147
 }
107 148
 
108 149
 /// MARK: - Static APIS

+ 1 - 1
PaiAi/PaiaiDataKit/Resuable/Toast/ToastOption.swift

@@ -10,7 +10,7 @@ import UIKit
10 10
 
11 11
 public struct ToastOption {
12 12
     var tintColor: UIColor = UIColor.white
13
-    var backgroundColor: UIColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.5)
13
+    var backgroundColor: UIColor = UIColor(red: 0, green: 0, blue: 0, alpha: 1)
14 14
     var font: UIFont = UIFont.systemFont(ofSize: 14)
15 15
     
16 16
     public static var `default`: ToastOption {

+ 80 - 66
PaiAi/PaiaiDataKit/Resuable/Toast/ToastView.swift

@@ -10,9 +10,11 @@ import UIKit
10 10
 
11 11
 class ToastView: UIView {
12 12
     
13
-    fileprivate var option: ToastOption
14
-    fileprivate var toastType: Toast.ToastType
13
+    var option: ToastOption
14
+    var toastType: Toast.ToastType
15 15
     
16
+    fileprivate var stackView = UIStackView(arrangedSubviews: [])
17
+    fileprivate var visualView: UIVisualEffectView
16 18
     fileprivate var label: UILabel?
17 19
     fileprivate var imageView: UIImageView?
18 20
     fileprivate var activityIndicatorView: UIActivityIndicatorView?
@@ -21,31 +23,60 @@ class ToastView: UIView {
21 23
     fileprivate var image: UIImage?
22 24
     fileprivate var isActivity: Bool = false
23 25
     
24
-    fileprivate var topView: UIView?
25
-    fileprivate var bottomView: UIView?
26
-    fileprivate var padding = UIEdgeInsets(top: 12, left: 12, bottom: 12, right: 12)
27
-    
26
+    fileprivate var padding = UIEdgeInsets(top: 24, left: 24, bottom: 24, right: 24)
28 27
     
29 28
     init(option: ToastOption, type: Toast.ToastType) {
30 29
         self.option = option
31 30
         self.toastType = type
31
+        let effect = UIBlurEffect(style: .dark)
32
+        self.visualView = UIVisualEffectView(effect: effect)
32 33
         super.init(frame: CGRect.zero)
33
-        
34 34
         initProperty()
35 35
     }
36 36
     
37
-    @available(*, unavailable, message: "Loading this view from a nib is unsupported")
38
-    public required init?(coder aDecoder: NSCoder) {
39
-        fatalError("Loading this view from a nib is unsupported")
37
+    required init(coder: NSCoder) {
38
+        fatalError("init(coder:) has not been implemented")
40 39
     }
41 40
     
42 41
     private func initProperty() {
43
-        alpha = 0
44 42
         layer.cornerRadius = 5
45
-        backgroundColor = option.backgroundColor
43
+        layer.masksToBounds = true
44
+        
45
+        stackView.axis = .vertical
46
+        stackView.alignment = .center
47
+        stackView.distribution = .equalSpacing
48
+        stackView.spacing = 12
49
+    }
50
+    
51
+    public func switchToast() {
52
+        clear()
53
+        setToast()
54
+        constructStackViewHierarchy()
55
+        activateConstraints()
46 56
     }
47 57
     
48 58
     override func didMoveToWindow() {
59
+        setToast()
60
+        
61
+        addSubview(visualView)
62
+        
63
+        constructStackViewHierarchy()
64
+        activateConstraints()
65
+    }
66
+    
67
+    private func clear() {
68
+        image = nil
69
+        isActivity = false
70
+        text = nil
71
+        let arrangedSubviews = stackView.arrangedSubviews
72
+        arrangedSubviews.forEach {
73
+            stackView.removeArrangedSubview($0)
74
+            $0.removeFromSuperview()
75
+        }
76
+        stackView.removeFromSuperview()
77
+    }
78
+    
79
+    private func setToast() {
49 80
         switch toastType {
50 81
         case let .text(t):
51 82
             text = t
@@ -58,39 +89,53 @@ class ToastView: UIView {
58 89
             text = t
59 90
             isActivity = true
60 91
         }
61
-        
62
-        constructViewHierarchy()
63
-        activateConstraints()
64 92
     }
65 93
 }
66 94
 
67 95
 /// construct view and layout
68 96
 fileprivate extension ToastView {
69 97
     
70
-    func constructViewHierarchy() {
98
+    func constructStackViewHierarchy() {
99
+        addSubview(stackView)
100
+        
71 101
         if image != nil {
72 102
             imageView = makeImageView()
73
-            addSubview(imageView!)
103
+            
104
+            stackView.addArrangedSubview(imageView!)
74 105
         }
75 106
         
76 107
         if isActivity {
77 108
             activityIndicatorView = makeActivityIndicatorView()
78
-            addSubview(activityIndicatorView!)
109
+            stackView.addArrangedSubview(activityIndicatorView!)
79 110
         }
80 111
         
81 112
         if text != nil {
82 113
             label = makeLabel()
83
-            addSubview(label!)
114
+            stackView.addArrangedSubview(label!)
84 115
         }
85 116
     }
86 117
     
118
+    
87 119
     func activateConstraints() {
88 120
         activateConstraintsImage()
89 121
         activateConstraintsIndicator()
90 122
         activateConstraintsText()
123
+        activateConstraintsStackView()
124
+        activateConstraintsVisualView()
91 125
         activateConstraintsContentView()
92 126
     }
93 127
     
128
+    func activateConstraintsStackView() {
129
+        stackView.translatesAutoresizingMaskIntoConstraints = false
130
+        
131
+        NSLayoutConstraint.activate([
132
+            stackView.topAnchor.constraint(equalTo: topAnchor, constant: padding.top),
133
+            stackView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -padding.bottom),
134
+            stackView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: padding.left),
135
+            stackView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -padding.right)
136
+            ])
137
+    }
138
+    
94 139
     func activateConstraintsImage() {
95 140
         guard let imageView = imageView else { return }
96 141
         
@@ -98,25 +143,13 @@ fileprivate extension ToastView {
98 143
         
99 144
         NSLayoutConstraint.activate([
100 145
             imageView.widthAnchor.constraint(equalToConstant: 50),
101
-            imageView.heightAnchor.constraint(equalToConstant: 50),
102
-            imageView.centerXAnchor.constraint(equalTo: centerXAnchor),
103
-            imageView.topAnchor.constraint(equalTo: topAnchor, constant: padding.top)
146
+            imageView.heightAnchor.constraint(equalToConstant: 50)
104 147
             ])
105
-        
106
-        topView = imageView
107 148
     }
108 149
     
109 150
     func activateConstraintsIndicator() {
110 151
         guard let activityIndicatorView = activityIndicatorView else { return }
111 152
         
112
-        activityIndicatorView.translatesAutoresizingMaskIntoConstraints = false
113
-        
114
-        NSLayoutConstraint.activate([
115
-            activityIndicatorView.centerXAnchor.constraint(equalTo: centerXAnchor),
116
-            activityIndicatorView.topAnchor.constraint(equalTo: topAnchor, constant: padding.top)
117
-            ])
118
-        
119
-        topView = activityIndicatorView
120 153
         activityIndicatorView.startAnimating()
121 154
     }
122 155
     
@@ -128,50 +161,30 @@ fileprivate extension ToastView {
128 161
         label.translatesAutoresizingMaskIntoConstraints = false
129 162
         
130 163
         NSLayoutConstraint.activate([
131
-            label.centerXAnchor.constraint(equalTo: centerXAnchor),
132
-            widthAnchor.constraint(lessThanOrEqualToConstant: windowSize.width - 52),
133
-            label.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -padding.bottom),
134
-            label.leadingAnchor.constraint(equalTo: leadingAnchor, constant: padding.left),
135
-            label.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -padding.right),
136
-            label.topAnchor.constraint(equalTo: topView?.bottomAnchor ?? topAnchor, constant: 6),
164
+            widthAnchor.constraint(lessThanOrEqualToConstant: windowSize.width - 52)
137 165
             ])
138
-        
139
-        if (text?.count ?? 0) <= 3 {
140
-            
141
-        } else {
142
-            NSLayoutConstraint.activate([
143
-                ])
144
-        }
145
-        
146
-        if topView != nil {
147
-            NSLayoutConstraint.activate([
148
-                label.widthAnchor.constraint(greaterThanOrEqualTo: heightAnchor)
149
-                ])
150
-        }
151
-        
152
-        bottomView = label
153 166
     }
154 167
     
155 168
     func activateConstraintsContentView() {
156 169
         guard let v = superview else { return }
157
-        translatesAutoresizingMaskIntoConstraints = false
158 170
         
171
+        translatesAutoresizingMaskIntoConstraints = false
172
+
159 173
         NSLayoutConstraint.activate([
160 174
             centerXAnchor.constraint(equalTo: v.centerXAnchor),
161 175
             centerYAnchor.constraint(equalTo: v.centerYAnchor)
162 176
             ])
177
+    }
178
+    
179
+    func activateConstraintsVisualView() {
180
+        visualView.translatesAutoresizingMaskIntoConstraints = false
163 181
         
164
-        if topView == nil {
165
-            NSLayoutConstraint.activate([
166
-                bottomView!.topAnchor.constraint(equalTo: topAnchor, constant: padding.top)
167
-                ])
168
-        } else if bottomView == nil {
169
-            NSLayoutConstraint.activate([
170
-                topView!.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -padding.bottom),
171
-                topView!.leadingAnchor.constraint(equalTo: leadingAnchor, constant: padding.left),
172
-                topView!.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -padding.right)
173
-                ])
174
-        }
182
+        NSLayoutConstraint.activate([
183
+            visualView.topAnchor.constraint(equalTo: topAnchor),
184
+            visualView.leadingAnchor.constraint(equalTo: leadingAnchor),
185
+            visualView.widthAnchor.constraint(equalTo: widthAnchor),
186
+            visualView.heightAnchor.constraint(equalTo: heightAnchor)
187
+            ])
175 188
     }
176 189
 }
177 190
 
@@ -183,6 +196,7 @@ fileprivate extension ToastView {
183 196
         l.font = option.font
184 197
         l.textAlignment = .center
185 198
         l.textColor = option.tintColor
199
+        l.sizeToFit()
186 200
         
187 201
         return l
188 202
     }
@@ -195,7 +209,7 @@ fileprivate extension ToastView {
195 209
     }
196 210
     
197 211
     func makeActivityIndicatorView() -> UIActivityIndicatorView {
198
-        let a = UIActivityIndicatorView(style: .white)
212
+        let a = UIActivityIndicatorView(style: .whiteLarge)
199 213
         
200 214
         return a
201 215
     }

+ 7 - 0
PaiAi/PaiaiUIKit/Reusable/Extension/UIKit/UIImageExt.swift

@@ -40,12 +40,19 @@ public extension UIImage {
40 40
                                                                       resizingMode: .stretch)
41 41
         }
42 42
         
43
+        public static var blackBackground: UIImage? {
44
+            return UIImage(named: "navigation-background-black")?.resizableImage(withCapInsets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0),
45
+                                                                           resizingMode: .stretch)
46
+            
47
+        }
48
+        
43 49
         public static var right: UIImage? {
44 50
             return UIImage(named: "navigation-right")?.resizableImage(withCapInsets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0),
45 51
                                                                            resizingMode: .stretch)
46 52
         }
47 53
         
48 54
         
55
+        
49 56
         public static var verticalPoints: UIImage? {
50 57
             return UIImage(named: "navigation-vertical-points")?.resizableImage(withCapInsets: UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0),
51 58
                                                                            resizingMode: .stretch)

+ 0 - 5
PaiAi/PaiaiUIKit/Reusable/Extension/UIKit/UIViewController+Navigation.swift

@@ -19,11 +19,6 @@ public extension UIViewController {
19 19
         navigationController?.pushViewController(vc, animated: animted)
20 20
     }
21 21
     
22
-    
23
-//    @objc func popViewController(animated: Bool = true) {
24
-//        navigationController?.popViewController(animated: animated)
25
-//    }
26
-    
27 22
     func popToController(_ vc: UIViewController, animated: Bool = true) {
28 23
         navigationController?.popToViewController(vc, animated: animated)
29 24
     }

+ 1 - 1
PaiAi/PaiaiUIKit/Reusable/UIKit/QR/QRCodeScanDelegate.swift

@@ -13,6 +13,6 @@ public protocol QRCodeScanViewDelegate: class {
13 13
     func scanView(_ scanView: QRCodeScanView, didFinshWithError error: NSError?)
14 14
 }
15 15
 
16
-extension QRCodeScanViewDelegate {
16
+public extension QRCodeScanViewDelegate {
17 17
     func scanView(_ scanView: QRCodeScanView, didFinshWithError error: NSError?) {}
18 18
 }

+ 14 - 16
PaiAi/PaiaiUIKit/Reusable/UIKit/QR/QRCodeScanView.swift

@@ -8,6 +8,7 @@
8 8
 
9 9
 import UIKit
10 10
 import AVFoundation
11
+import CoreImage
11 12
 
12 13
 @IBDesignable public class QRCodeScanView: UIView {
13 14
     var qrmaskView: QRCodeMaskView?
@@ -115,19 +116,17 @@ import AVFoundation
115 116
             picker.delegate = self
116 117
             ctl.present(picker, animated: true, completion: nil)
117 118
         } else {
118
-            let alert = UIAlertView(title: "拒绝访问",
119
-                                    message: "请在设置-隐私-相机中允许访问相册",
120
-                                    delegate: nil,
121
-                                    cancelButtonTitle: "确定")
122
-            alert.show()
119
+            let alert = UIAlertController(title: "拒绝访问", message: "请在设置-隐私-相机中允许访问相册", preferredStyle: .alert)
120
+            alert.addAction(UIAlertAction(title: "确定", style: .default, handler: nil))
121
+            ctl.present(alert, animated: true, completion: nil)
123 122
         }
124 123
     }
125 124
 }
126 125
 
127 126
 extension QRCodeScanView: UIImagePickerControllerDelegate, UINavigationControllerDelegate {
128
-    fileprivate func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String: Any]) {
127
+    public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
129 128
         picker.dismiss(animated: true) {
130
-            guard let image = info[UIImagePickerController.InfoKey.originalImage.rawValue] as? UIImage else { return }
129
+            guard let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage else { return }
131 130
             if let res = image.decodeQRCodeInImage() {
132 131
                 self.qrscanner?.stopScan()
133 132
                 self.qrmaskView?.stopAnimation()
@@ -135,17 +134,17 @@ extension QRCodeScanView: UIImagePickerControllerDelegate, UINavigationControlle
135 134
                                         receivedScanResult: QRCodeScanResult(metadataType: AVMetadataObject.ObjectType.qr.rawValue,
136 135
                                                                              result: res))
137 136
             } else {
138
-                let error = NSError.init(domain: "decode qr code error",
139
-                                         code: 101,
140
-                                         userInfo: nil)
137
+                let error = NSError(domain: "decode qr code error",
138
+                                    code: 101,
139
+                                    userInfo: nil)
141 140
                 self.delegate?.scanView(self,
142 141
                                         didFinshWithError: error)
143 142
             }
144 143
             self.indicatorView.stopAnimating()
145 144
         }
146 145
     }
147
-
148
-    fileprivate func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
146
+    
147
+    public func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
149 148
         picker.dismiss(animated: true, completion: nil)
150 149
     }
151 150
 }
@@ -157,10 +156,9 @@ extension UIImage {
157 156
     ///
158 157
     /// - Returns: 二维码信息
159 158
     public func decodeQRCodeInImage() -> String? {
160
-        let context = CIContext()
161
-        let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: context, options: nil)
162
-        guard let image = CIImage(image: self) else { return nil }
163
-        let result = detector?.features(in: image)
159
+        guard let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: nil, options: nil),
160
+            let cgImage1 = self.cgImage else { return nil }
161
+        let result = detector.features(in: CIImage(cgImage: cgImage1), options: [CIDetectorAccuracy: CIDetectorAccuracyHigh])
164 162
         guard let first = (result as? [CIQRCodeFeature])?.first else { return nil }
165 163
         return first.messageString
166 164
     }

+ 23 - 1
PaiAi/Paiai_iOS/App/Group/Group.storyboard

@@ -24,6 +24,23 @@
24 24
                                 <collectionViewLayout key="collectionViewLayout" id="JAB-7S-F8d" customClass="WaterfallFlowLayout" customModule="PaiaiUIKit"/>
25 25
                                 <cells/>
26 26
                             </collectionView>
27
+                            <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalSpacing" alignment="center" spacing="6" translatesAutoresizingMaskIntoConstraints="NO" id="t0e-rE-HhA">
28
+                                <rect key="frame" x="0.0" y="211.5" width="375" height="264"/>
29
+                                <subviews>
30
+                                    <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="iEw-uH-8xG">
31
+                                        <rect key="frame" x="67.5" y="0.0" width="240" height="240"/>
32
+                                        <constraints>
33
+                                            <constraint firstAttribute="width" secondItem="iEw-uH-8xG" secondAttribute="height" multiplier="1:1" id="cJz-bD-POP"/>
34
+                                        </constraints>
35
+                                    </imageView>
36
+                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="邀请朋友扫描二维码加入群" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ODT-Kq-dyd">
37
+                                        <rect key="frame" x="95.5" y="246" width="184" height="18"/>
38
+                                        <fontDescription key="fontDescription" type="system" pointSize="15"/>
39
+                                        <color key="textColor" red="0.20000000000000001" green="0.20000000000000001" blue="0.20000000000000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
40
+                                        <nil key="highlightedColor"/>
41
+                                    </label>
42
+                                </subviews>
43
+                            </stackView>
27 44
                             <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="Ckt-CA-ny6">
28 45
                                 <rect key="frame" x="303" y="597" width="64" height="64"/>
29 46
                                 <constraints>
@@ -41,7 +58,10 @@
41 58
                         <constraints>
42 59
                             <constraint firstItem="jD2-65-6eY" firstAttribute="bottom" secondItem="Ckt-CA-ny6" secondAttribute="bottom" constant="6" id="3sN-1z-kEg"/>
43 60
                             <constraint firstItem="jD2-65-6eY" firstAttribute="trailing" secondItem="SQ2-vg-KI2" secondAttribute="trailing" id="6Mx-Yn-Q9C"/>
61
+                            <constraint firstItem="t0e-rE-HhA" firstAttribute="centerX" secondItem="jD2-65-6eY" secondAttribute="centerX" id="7Ah-BH-xjN"/>
62
+                            <constraint firstItem="t0e-rE-HhA" firstAttribute="centerY" secondItem="jD2-65-6eY" secondAttribute="centerY" id="B7V-VG-zvI"/>
44 63
                             <constraint firstItem="SQ2-vg-KI2" firstAttribute="top" secondItem="jD2-65-6eY" secondAttribute="top" id="SjQ-TS-6Py"/>
64
+                            <constraint firstItem="t0e-rE-HhA" firstAttribute="width" secondItem="3WT-kq-eEc" secondAttribute="width" id="Z9w-hX-2AS"/>
45 65
                             <constraint firstItem="SQ2-vg-KI2" firstAttribute="leading" secondItem="jD2-65-6eY" secondAttribute="leading" id="jE9-N2-trU"/>
46 66
                             <constraint firstItem="jD2-65-6eY" firstAttribute="trailing" secondItem="Ckt-CA-ny6" secondAttribute="trailing" constant="8" id="ory-hZ-HWM"/>
47 67
                             <constraint firstAttribute="bottom" secondItem="SQ2-vg-KI2" secondAttribute="bottom" id="qMe-Om-r14"/>
@@ -51,12 +71,14 @@
51 71
                     <navigationItem key="navigationItem" id="sdS-ms-7Io"/>
52 72
                     <connections>
53 73
                         <outlet property="collectionView" destination="SQ2-vg-KI2" id="QZZ-9l-Lg5"/>
74
+                        <outlet property="emptyView" destination="t0e-rE-HhA" id="Ige-H6-j0I"/>
54 75
                         <outlet property="photographBtn" destination="Ckt-CA-ny6" id="nq9-32-Rhg"/>
76
+                        <outlet property="qrImageView" destination="iEw-uH-8xG" id="smk-33-2OE"/>
55 77
                     </connections>
56 78
                 </viewController>
57 79
                 <placeholder placeholderIdentifier="IBFirstResponder" id="1WI-xu-c0y" sceneMemberID="firstResponder"/>
58 80
             </objects>
59
-            <point key="canvasLocation" x="437.5" y="63.5"/>
81
+            <point key="canvasLocation" x="150" y="50"/>
60 82
         </scene>
61 83
     </scenes>
62 84
     <resources>

+ 155 - 248
PaiAi/Paiai_iOS/App/Group/GroupDetail/GroupDetail.storyboard

@@ -10,95 +10,6 @@
10 10
         <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
11 11
     </dependencies>
12 12
     <scenes>
13
-        <!--ShowGroupQRController-->
14
-        <scene sceneID="XYC-ye-yVT">
15
-            <objects>
16
-                <viewController storyboardIdentifier="ShowGroupQRController" automaticallyAdjustsScrollViewInsets="NO" id="1xf-Gx-gQ6" userLabel="ShowGroupQRController" customClass="ShowGroupQRController" customModule="PaiAi" sceneMemberID="viewController">
17
-                    <view key="view" contentMode="scaleToFill" id="ZBk-gn-qqZ">
18
-                        <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
19
-                        <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
20
-                        <subviews>
21
-                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="AOa-xP-S2e">
22
-                                <rect key="frame" x="47.5" y="132.5" width="280" height="362"/>
23
-                                <subviews>
24
-                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="群名" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="LsX-zm-vaZ">
25
-                                        <rect key="frame" x="123.5" y="80" width="33" height="20"/>
26
-                                        <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
27
-                                        <fontDescription key="fontDescription" type="system" pointSize="16"/>
28
-                                        <color key="textColor" red="0.20000000000000001" green="0.20000000000000001" blue="0.20000000000000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
29
-                                        <nil key="highlightedColor"/>
30
-                                    </label>
31
-                                    <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" translatesAutoresizingMaskIntoConstraints="NO" id="pWX-dr-m4f">
32
-                                        <rect key="frame" x="110" y="16" width="60" height="60"/>
33
-                                        <constraints>
34
-                                            <constraint firstAttribute="width" secondItem="pWX-dr-m4f" secondAttribute="height" multiplier="1:1" id="Y4R-2u-6IF"/>
35
-                                            <constraint firstAttribute="width" constant="60" id="gVZ-d2-0Zl"/>
36
-                                        </constraints>
37
-                                    </imageView>
38
-                                    <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="默认头像" translatesAutoresizingMaskIntoConstraints="NO" id="omt-Jt-Qr4">
39
-                                        <rect key="frame" x="40" y="114" width="200" height="200"/>
40
-                                        <constraints>
41
-                                            <constraint firstAttribute="width" constant="200" id="4Jq-pa-wy8"/>
42
-                                            <constraint firstAttribute="width" secondItem="omt-Jt-Qr4" secondAttribute="height" multiplier="1:1" id="PFq-bE-xho"/>
43
-                                        </constraints>
44
-                                    </imageView>
45
-                                </subviews>
46
-                                <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
47
-                                <constraints>
48
-                                    <constraint firstItem="LsX-zm-vaZ" firstAttribute="centerX" secondItem="AOa-xP-S2e" secondAttribute="centerX" id="6HE-zY-T4a"/>
49
-                                    <constraint firstItem="LsX-zm-vaZ" firstAttribute="top" secondItem="AOa-xP-S2e" secondAttribute="top" constant="80" id="7MB-5e-afJ"/>
50
-                                    <constraint firstAttribute="height" constant="362" id="BqC-e3-veX"/>
51
-                                    <constraint firstItem="pWX-dr-m4f" firstAttribute="top" secondItem="AOa-xP-S2e" secondAttribute="top" constant="16" id="O0H-zl-A5Z"/>
52
-                                    <constraint firstItem="pWX-dr-m4f" firstAttribute="centerX" secondItem="AOa-xP-S2e" secondAttribute="centerX" id="iJd-xI-avR"/>
53
-                                    <constraint firstItem="omt-Jt-Qr4" firstAttribute="centerX" secondItem="AOa-xP-S2e" secondAttribute="centerX" id="iog-Je-bMC"/>
54
-                                    <constraint firstItem="omt-Jt-Qr4" firstAttribute="top" secondItem="AOa-xP-S2e" secondAttribute="top" constant="114" id="onf-UA-eoS"/>
55
-                                    <constraint firstAttribute="width" constant="280" id="smI-a8-6H9"/>
56
-                                </constraints>
57
-                                <userDefinedRuntimeAttributes>
58
-                                    <userDefinedRuntimeAttribute type="number" keyPath="borderWidth">
59
-                                        <real key="value" value="0.0"/>
60
-                                    </userDefinedRuntimeAttribute>
61
-                                    <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
62
-                                        <real key="value" value="0.0"/>
63
-                                    </userDefinedRuntimeAttribute>
64
-                                </userDefinedRuntimeAttributes>
65
-                            </view>
66
-                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="扫描二维码加入群" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3Yp-sn-LQg">
67
-                                <rect key="frame" x="138" y="458.5" width="99" height="15"/>
68
-                                <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
69
-                                <fontDescription key="fontDescription" type="system" pointSize="12"/>
70
-                                <color key="textColor" red="0.59999999999999998" green="0.59999999999999998" blue="0.59999999999999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
71
-                                <nil key="highlightedColor"/>
72
-                            </label>
73
-                        </subviews>
74
-                        <gestureRecognizers/>
75
-                        <constraints>
76
-                            <constraint firstItem="3Yp-sn-LQg" firstAttribute="centerX" secondItem="omt-Jt-Qr4" secondAttribute="centerX" id="5jg-XD-1je"/>
77
-                            <constraint firstItem="AOa-xP-S2e" firstAttribute="centerY" secondItem="ZBk-gn-qqZ" secondAttribute="centerY" constant="-20" id="gqp-We-1FY"/>
78
-                            <constraint firstItem="AOa-xP-S2e" firstAttribute="centerX" secondItem="x9Y-Y7-7ge" secondAttribute="centerX" id="ou7-87-DCL"/>
79
-                            <constraint firstItem="3Yp-sn-LQg" firstAttribute="top" secondItem="omt-Jt-Qr4" secondAttribute="bottom" constant="12" id="vwi-7r-nUU"/>
80
-                        </constraints>
81
-                        <viewLayoutGuide key="safeArea" id="x9Y-Y7-7ge"/>
82
-                        <connections>
83
-                            <outletCollection property="gestureRecognizers" destination="qe1-cB-TmI" appends="YES" id="9kM-Mf-00B"/>
84
-                        </connections>
85
-                    </view>
86
-                    <connections>
87
-                        <outlet property="EWM" destination="omt-Jt-Qr4" id="axb-en-HEB"/>
88
-                        <outlet property="contentView" destination="AOa-xP-S2e" id="DWh-rw-9bu"/>
89
-                        <outlet property="groupImage" destination="pWX-dr-m4f" id="uLJ-jV-jfC"/>
90
-                        <outlet property="groupName" destination="LsX-zm-vaZ" id="Wdr-zQ-QWj"/>
91
-                    </connections>
92
-                </viewController>
93
-                <placeholder placeholderIdentifier="IBFirstResponder" id="Pp3-WT-dSC" userLabel="First Responder" sceneMemberID="firstResponder"/>
94
-                <tapGestureRecognizer id="qe1-cB-TmI">
95
-                    <connections>
96
-                        <action selector="back" destination="1xf-Gx-gQ6" id="rSv-3P-jrb"/>
97
-                    </connections>
98
-                </tapGestureRecognizer>
99
-            </objects>
100
-            <point key="canvasLocation" x="-232" y="2161.6191904047978"/>
101
-        </scene>
102 13
         <!--GroupDetailViewController-->
103 14
         <scene sceneID="Oa4-Yi-HJu">
104 15
             <objects>
@@ -107,207 +18,203 @@
107 18
                         <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
108 19
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
109 20
                         <subviews>
110
-                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="fpL-PF-0xo" userLabel="Group Name">
111
-                                <rect key="frame" x="0.0" y="25" width="375" height="45"/>
21
+                            <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="eF5-Vm-thF">
22
+                                <rect key="frame" x="0.0" y="20" width="375" height="225"/>
112 23
                                 <subviews>
113
-                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="群名称" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="NME-fp-tEb">
114
-                                        <rect key="frame" x="12" y="12.5" width="49" height="20"/>
115
-                                        <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
116
-                                        <fontDescription key="fontDescription" type="system" pointSize="16"/>
117
-                                        <color key="textColor" red="0.20000000000000001" green="0.20000000000000001" blue="0.20000000000000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
118
-                                        <nil key="highlightedColor"/>
119
-                                    </label>
120
-                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="1X6-D5-hkj">
121
-                                        <rect key="frame" x="329" y="22.5" width="0.0" height="0.0"/>
122
-                                        <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
123
-                                        <fontDescription key="fontDescription" type="system" pointSize="16"/>
124
-                                        <color key="textColor" red="0.59999999999999998" green="0.59999999999999998" blue="0.59999999999999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
125
-                                        <nil key="highlightedColor"/>
126
-                                    </label>
127
-                                    <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="list-arrow" translatesAutoresizingMaskIntoConstraints="NO" id="7Hg-xN-Smd">
128
-                                        <rect key="frame" x="339" y="4.5" width="24" height="36"/>
129
-                                    </imageView>
130
-                                </subviews>
131
-                                <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
132
-                                <gestureRecognizers/>
133
-                                <constraints>
134
-                                    <constraint firstItem="7Hg-xN-Smd" firstAttribute="centerY" secondItem="fpL-PF-0xo" secondAttribute="centerY" id="Rjv-h5-Wtq"/>
135
-                                    <constraint firstAttribute="height" constant="45" id="UPg-bK-pto"/>
136
-                                    <constraint firstItem="NME-fp-tEb" firstAttribute="centerY" secondItem="fpL-PF-0xo" secondAttribute="centerY" id="X0F-x5-Iql"/>
137
-                                    <constraint firstItem="NME-fp-tEb" firstAttribute="leading" secondItem="fpL-PF-0xo" secondAttribute="leading" constant="12" id="Znr-w6-kFQ"/>
138
-                                    <constraint firstItem="7Hg-xN-Smd" firstAttribute="leading" secondItem="1X6-D5-hkj" secondAttribute="trailing" constant="10" id="qjJ-vN-hzP"/>
139
-                                    <constraint firstAttribute="trailing" secondItem="7Hg-xN-Smd" secondAttribute="trailing" constant="12" id="tEJ-5a-H97"/>
140
-                                    <constraint firstItem="1X6-D5-hkj" firstAttribute="centerY" secondItem="fpL-PF-0xo" secondAttribute="centerY" id="v6h-VQ-qa8"/>
141
-                                </constraints>
142
-                                <connections>
143
-                                    <outletCollection property="gestureRecognizers" destination="QhO-Be-7C1" appends="YES" id="9lj-Za-c71"/>
144
-                                </connections>
145
-                            </view>
146
-                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="pMy-uJ-dNy" userLabel="Group Member">
147
-                                <rect key="frame" x="0.0" y="70" width="375" height="90"/>
148
-                                <subviews>
149
-                                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="wyR-pX-r7o">
24
+                                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="3DP-6l-tA7" userLabel="Group Name">
150 25
                                         <rect key="frame" x="0.0" y="0.0" width="375" height="45"/>
151 26
                                         <subviews>
152
-                                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="群成员" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="NxF-Pa-Uwm">
153
-                                                <rect key="frame" x="12" y="12.5" width="49" height="20"/>
27
+                                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="群名称" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="EgZ-Rf-o71">
28
+                                                <rect key="frame" x="12" y="13" width="49" height="19.5"/>
154 29
                                                 <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
155 30
                                                 <fontDescription key="fontDescription" type="system" pointSize="16"/>
156 31
                                                 <color key="textColor" red="0.20000000000000001" green="0.20000000000000001" blue="0.20000000000000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
157 32
                                                 <nil key="highlightedColor"/>
158 33
                                             </label>
159
-                                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="10人" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="AxF-rT-r4a">
160
-                                                <rect key="frame" x="299" y="12.5" width="34" height="20"/>
34
+                                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Sv7-52-SPp">
35
+                                                <rect key="frame" x="329" y="22.5" width="0.0" height="0.0"/>
161 36
                                                 <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
162 37
                                                 <fontDescription key="fontDescription" type="system" pointSize="16"/>
163 38
                                                 <color key="textColor" red="0.59999999999999998" green="0.59999999999999998" blue="0.59999999999999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
164 39
                                                 <nil key="highlightedColor"/>
165 40
                                             </label>
166
-                                            <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="list-arrow" translatesAutoresizingMaskIntoConstraints="NO" id="deA-EV-s1P">
41
+                                            <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="list-arrow" translatesAutoresizingMaskIntoConstraints="NO" id="i8n-NV-HOx">
167 42
                                                 <rect key="frame" x="339" y="4.5" width="24" height="36"/>
168 43
                                             </imageView>
169 44
                                         </subviews>
170
-                                        <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
45
+                                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
46
+                                        <gestureRecognizers/>
171 47
                                         <constraints>
172
-                                            <constraint firstAttribute="trailing" secondItem="deA-EV-s1P" secondAttribute="trailing" constant="12" id="99G-fm-NjO"/>
173
-                                            <constraint firstItem="deA-EV-s1P" firstAttribute="leading" secondItem="AxF-rT-r4a" secondAttribute="trailing" constant="6" id="NoO-0x-719"/>
174
-                                            <constraint firstAttribute="height" constant="45" id="OmQ-ue-3uc"/>
175
-                                            <constraint firstItem="NxF-Pa-Uwm" firstAttribute="leading" secondItem="wyR-pX-r7o" secondAttribute="leading" constant="12" id="RxE-Ec-vzI"/>
176
-                                            <constraint firstItem="NxF-Pa-Uwm" firstAttribute="centerY" secondItem="wyR-pX-r7o" secondAttribute="centerY" id="jRi-u6-4du"/>
177
-                                            <constraint firstItem="deA-EV-s1P" firstAttribute="centerY" secondItem="wyR-pX-r7o" secondAttribute="centerY" id="lcj-B2-CSF"/>
178
-                                            <constraint firstItem="AxF-rT-r4a" firstAttribute="centerY" secondItem="wyR-pX-r7o" secondAttribute="centerY" id="s7D-rf-GV6"/>
48
+                                            <constraint firstAttribute="trailing" secondItem="i8n-NV-HOx" secondAttribute="trailing" constant="12" id="0Ii-HJ-nJm"/>
49
+                                            <constraint firstAttribute="height" constant="45" id="20x-CX-Soa"/>
50
+                                            <constraint firstItem="EgZ-Rf-o71" firstAttribute="centerY" secondItem="3DP-6l-tA7" secondAttribute="centerY" id="EXH-C5-I5R"/>
51
+                                            <constraint firstItem="i8n-NV-HOx" firstAttribute="centerY" secondItem="3DP-6l-tA7" secondAttribute="centerY" id="F9Q-ge-Na8"/>
52
+                                            <constraint firstItem="EgZ-Rf-o71" firstAttribute="leading" secondItem="3DP-6l-tA7" secondAttribute="leading" constant="12" id="LCj-o7-LJQ"/>
53
+                                            <constraint firstItem="i8n-NV-HOx" firstAttribute="leading" secondItem="Sv7-52-SPp" secondAttribute="trailing" constant="10" id="Oq2-SO-ogB"/>
54
+                                            <constraint firstItem="Sv7-52-SPp" firstAttribute="centerY" secondItem="3DP-6l-tA7" secondAttribute="centerY" id="hML-m6-vCa"/>
179 55
                                         </constraints>
56
+                                        <connections>
57
+                                            <outletCollection property="gestureRecognizers" destination="QhO-Be-7C1" appends="YES" id="Z5i-Sr-TNc"/>
58
+                                        </connections>
180 59
                                     </view>
181
-                                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="PEf-pV-jyM" customClass="GroupDetailMemeberView" customModule="Paiai_iOS" customModuleProvider="target">
182
-                                        <rect key="frame" x="0.0" y="45" width="375" height="45"/>
183
-                                        <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
60
+                                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="NxF-oL-8bw" userLabel="Group Member">
61
+                                        <rect key="frame" x="0.0" y="45" width="375" height="90"/>
62
+                                        <subviews>
63
+                                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="jUc-1D-3th">
64
+                                                <rect key="frame" x="0.0" y="0.0" width="375" height="45"/>
65
+                                                <subviews>
66
+                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="群成员" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="tpV-44-bE1">
67
+                                                        <rect key="frame" x="12" y="13" width="49" height="19.5"/>
68
+                                                        <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
69
+                                                        <fontDescription key="fontDescription" type="system" pointSize="16"/>
70
+                                                        <color key="textColor" red="0.20000000000000001" green="0.20000000000000001" blue="0.20000000000000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
71
+                                                        <nil key="highlightedColor"/>
72
+                                                    </label>
73
+                                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="10人" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="HZE-FN-DyQ">
74
+                                                        <rect key="frame" x="299.5" y="13" width="33.5" height="19.5"/>
75
+                                                        <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
76
+                                                        <fontDescription key="fontDescription" type="system" pointSize="16"/>
77
+                                                        <color key="textColor" red="0.59999999999999998" green="0.59999999999999998" blue="0.59999999999999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
78
+                                                        <nil key="highlightedColor"/>
79
+                                                    </label>
80
+                                                    <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="list-arrow" translatesAutoresizingMaskIntoConstraints="NO" id="0KU-Hj-GCy">
81
+                                                        <rect key="frame" x="339" y="4.5" width="24" height="36"/>
82
+                                                    </imageView>
83
+                                                </subviews>
84
+                                                <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
85
+                                                <constraints>
86
+                                                    <constraint firstAttribute="trailing" secondItem="0KU-Hj-GCy" secondAttribute="trailing" constant="12" id="8S3-J3-Yyh"/>
87
+                                                    <constraint firstItem="tpV-44-bE1" firstAttribute="centerY" secondItem="jUc-1D-3th" secondAttribute="centerY" id="9WO-P7-gOL"/>
88
+                                                    <constraint firstItem="0KU-Hj-GCy" firstAttribute="centerY" secondItem="jUc-1D-3th" secondAttribute="centerY" id="Bef-yS-WWO"/>
89
+                                                    <constraint firstItem="0KU-Hj-GCy" firstAttribute="leading" secondItem="HZE-FN-DyQ" secondAttribute="trailing" constant="6" id="Ro6-4G-yFP"/>
90
+                                                    <constraint firstAttribute="height" constant="45" id="Rwb-3W-tuU"/>
91
+                                                    <constraint firstItem="tpV-44-bE1" firstAttribute="leading" secondItem="jUc-1D-3th" secondAttribute="leading" constant="12" id="mgZ-37-wOQ"/>
92
+                                                    <constraint firstItem="HZE-FN-DyQ" firstAttribute="centerY" secondItem="jUc-1D-3th" secondAttribute="centerY" id="x7o-Ie-5Wd"/>
93
+                                                </constraints>
94
+                                            </view>
95
+                                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="gAe-sP-K7J" customClass="GroupDetailMemeberView" customModule="Paiai_iOS" customModuleProvider="target">
96
+                                                <rect key="frame" x="0.0" y="45" width="375" height="45"/>
97
+                                                <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
98
+                                                <constraints>
99
+                                                    <constraint firstAttribute="height" constant="45" id="jsl-Zu-k11"/>
100
+                                                </constraints>
101
+                                            </view>
102
+                                        </subviews>
103
+                                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
184 104
                                         <constraints>
185
-                                            <constraint firstAttribute="height" constant="45" id="zVY-I6-46P"/>
105
+                                            <constraint firstItem="gAe-sP-K7J" firstAttribute="top" secondItem="jUc-1D-3th" secondAttribute="bottom" id="5ck-Rv-4zf"/>
106
+                                            <constraint firstAttribute="trailing" secondItem="gAe-sP-K7J" secondAttribute="trailing" id="7bc-CV-rAR"/>
107
+                                            <constraint firstItem="gAe-sP-K7J" firstAttribute="leading" secondItem="NxF-oL-8bw" secondAttribute="leading" id="Cbr-kU-40o"/>
108
+                                            <constraint firstAttribute="height" constant="90" id="TIa-pW-jwd"/>
109
+                                            <constraint firstItem="jUc-1D-3th" firstAttribute="top" secondItem="NxF-oL-8bw" secondAttribute="top" id="bzE-8j-npG"/>
110
+                                            <constraint firstAttribute="trailing" secondItem="jUc-1D-3th" secondAttribute="trailing" id="qdM-oh-oG0"/>
111
+                                            <constraint firstItem="jUc-1D-3th" firstAttribute="leading" secondItem="NxF-oL-8bw" secondAttribute="leading" id="yWi-UY-9VM"/>
186 112
                                         </constraints>
113
+                                        <connections>
114
+                                            <outletCollection property="gestureRecognizers" destination="PAc-yU-TO1" appends="YES" id="Cz9-0C-Lgl"/>
115
+                                        </connections>
187 116
                                     </view>
188
-                                </subviews>
189
-                                <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
190
-                                <constraints>
191
-                                    <constraint firstAttribute="trailing" secondItem="PEf-pV-jyM" secondAttribute="trailing" id="FQu-9X-fNW"/>
192
-                                    <constraint firstAttribute="trailing" secondItem="wyR-pX-r7o" secondAttribute="trailing" id="Rhr-fu-vjC"/>
193
-                                    <constraint firstItem="PEf-pV-jyM" firstAttribute="top" secondItem="wyR-pX-r7o" secondAttribute="bottom" id="T2J-6h-hMy"/>
194
-                                    <constraint firstAttribute="height" constant="90" id="ksr-Ie-zrU"/>
195
-                                    <constraint firstItem="wyR-pX-r7o" firstAttribute="top" secondItem="pMy-uJ-dNy" secondAttribute="top" id="sND-x0-GOF"/>
196
-                                    <constraint firstItem="wyR-pX-r7o" firstAttribute="leading" secondItem="pMy-uJ-dNy" secondAttribute="leading" id="wJT-Tt-evG"/>
197
-                                    <constraint firstItem="PEf-pV-jyM" firstAttribute="leading" secondItem="pMy-uJ-dNy" secondAttribute="leading" id="xfn-6R-s6e"/>
198
-                                </constraints>
199
-                                <connections>
200
-                                    <outletCollection property="gestureRecognizers" destination="PAc-yU-TO1" appends="YES" id="HrQ-Km-0rQ"/>
201
-                                </connections>
202
-                            </view>
203
-                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="rPs-2T-yj8" userLabel="Group QR">
204
-                                <rect key="frame" x="0.0" y="160" width="375" height="45"/>
205
-                                <subviews>
206
-                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="群二维码" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ZfP-6I-3jY">
207
-                                        <rect key="frame" x="12" y="12.5" width="66" height="20"/>
208
-                                        <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
209
-                                        <fontDescription key="fontDescription" type="system" pointSize="16"/>
210
-                                        <color key="textColor" red="0.20000000000000001" green="0.20000000000000001" blue="0.20000000000000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
211
-                                        <nil key="highlightedColor"/>
212
-                                    </label>
213
-                                    <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="list-arrow" translatesAutoresizingMaskIntoConstraints="NO" id="Uws-7i-Qzj">
214
-                                        <rect key="frame" x="339" y="4.5" width="24" height="36"/>
215
-                                    </imageView>
216
-                                    <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="list-QR" translatesAutoresizingMaskIntoConstraints="NO" id="ZNi-wI-pOY">
217
-                                        <rect key="frame" x="305" y="10.5" width="24" height="24"/>
117
+                                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Mdq-nz-jXt" userLabel="Group QR">
118
+                                        <rect key="frame" x="0.0" y="135" width="375" height="45"/>
119
+                                        <subviews>
120
+                                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="群二维码" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="lfa-Ov-NXG">
121
+                                                <rect key="frame" x="12" y="13" width="65.5" height="19.5"/>
122
+                                                <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
123
+                                                <fontDescription key="fontDescription" type="system" pointSize="16"/>
124
+                                                <color key="textColor" red="0.20000000000000001" green="0.20000000000000001" blue="0.20000000000000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
125
+                                                <nil key="highlightedColor"/>
126
+                                            </label>
127
+                                            <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="list-arrow" translatesAutoresizingMaskIntoConstraints="NO" id="IXn-b1-K62">
128
+                                                <rect key="frame" x="339" y="4.5" width="24" height="36"/>
129
+                                            </imageView>
130
+                                            <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="list-QR" translatesAutoresizingMaskIntoConstraints="NO" id="NsS-cG-FY5">
131
+                                                <rect key="frame" x="305" y="10.5" width="24" height="24"/>
132
+                                                <constraints>
133
+                                                    <constraint firstAttribute="width" constant="24" id="YQf-I7-DB5"/>
134
+                                                    <constraint firstAttribute="height" constant="24" id="pu8-YH-Lmv"/>
135
+                                                </constraints>
136
+                                            </imageView>
137
+                                        </subviews>
138
+                                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
218 139
                                         <constraints>
219
-                                            <constraint firstAttribute="width" constant="24" id="DzA-gm-u2E"/>
220
-                                            <constraint firstAttribute="height" constant="24" id="kJ3-8V-wGS"/>
140
+                                            <constraint firstItem="lfa-Ov-NXG" firstAttribute="centerY" secondItem="Mdq-nz-jXt" secondAttribute="centerY" id="CdN-dy-MDB"/>
141
+                                            <constraint firstItem="lfa-Ov-NXG" firstAttribute="leading" secondItem="Mdq-nz-jXt" secondAttribute="leading" constant="12" id="Lf1-UA-hHs"/>
142
+                                            <constraint firstItem="IXn-b1-K62" firstAttribute="centerY" secondItem="Mdq-nz-jXt" secondAttribute="centerY" id="NFS-0b-g1u"/>
143
+                                            <constraint firstAttribute="trailing" secondItem="IXn-b1-K62" secondAttribute="trailing" constant="12" id="NcT-bO-bju"/>
144
+                                            <constraint firstItem="NsS-cG-FY5" firstAttribute="centerY" secondItem="Mdq-nz-jXt" secondAttribute="centerY" id="OpY-xK-6h8"/>
145
+                                            <constraint firstItem="IXn-b1-K62" firstAttribute="leading" secondItem="NsS-cG-FY5" secondAttribute="trailing" constant="10" id="S3s-U9-eAG"/>
146
+                                            <constraint firstAttribute="height" constant="45" id="d1j-ge-Nas"/>
221 147
                                         </constraints>
222
-                                    </imageView>
223
-                                </subviews>
224
-                                <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
225
-                                <constraints>
226
-                                    <constraint firstItem="Uws-7i-Qzj" firstAttribute="centerY" secondItem="rPs-2T-yj8" secondAttribute="centerY" id="2wQ-j8-t3z"/>
227
-                                    <constraint firstAttribute="trailing" secondItem="Uws-7i-Qzj" secondAttribute="trailing" constant="12" id="7qb-cu-8iE"/>
228
-                                    <constraint firstItem="ZfP-6I-3jY" firstAttribute="leading" secondItem="rPs-2T-yj8" secondAttribute="leading" constant="12" id="9xA-HF-Mlj"/>
229
-                                    <constraint firstItem="ZfP-6I-3jY" firstAttribute="centerY" secondItem="rPs-2T-yj8" secondAttribute="centerY" id="GAH-Ls-Ixp"/>
230
-                                    <constraint firstItem="ZNi-wI-pOY" firstAttribute="centerY" secondItem="rPs-2T-yj8" secondAttribute="centerY" id="Mst-vH-6Op"/>
231
-                                    <constraint firstItem="Uws-7i-Qzj" firstAttribute="leading" secondItem="ZNi-wI-pOY" secondAttribute="trailing" constant="10" id="eQT-7f-sku"/>
232
-                                    <constraint firstAttribute="height" constant="45" id="ypH-Mg-3iM"/>
233
-                                </constraints>
234
-                                <connections>
235
-                                    <outletCollection property="gestureRecognizers" destination="CLv-er-MwX" appends="YES" id="6Fw-lQ-Aqq"/>
236
-                                </connections>
237
-                            </view>
238
-                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Pb3-YX-nuZ" userLabel="Group Lock">
239
-                                <rect key="frame" x="0.0" y="205" width="375" height="45"/>
240
-                                <subviews>
241
-                                    <switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="1000" verticalHuggingPriority="750" horizontalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="t9Z-kG-PPs">
242
-                                        <rect key="frame" x="314" y="7" width="51" height="31"/>
243
-                                        <color key="onTintColor" red="0.97647058819999999" green="0.34901960780000002" blue="0.50196078430000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
244 148
                                         <connections>
245
-                                            <action selector="changeSwitch" destination="s9X-RR-Rat" eventType="valueChanged" id="vRG-9q-Y4n"/>
149
+                                            <outletCollection property="gestureRecognizers" destination="CLv-er-MwX" appends="YES" id="YAD-Z4-Zqm"/>
246 150
                                         </connections>
247
-                                    </switch>
248
-                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="1000" verticalHuggingPriority="251" horizontalCompressionResistancePriority="1000" text="群锁定" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="pGa-DR-SDU">
249
-                                        <rect key="frame" x="12" y="12.5" width="49" height="20"/>
250
-                                        <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
251
-                                        <fontDescription key="fontDescription" type="system" pointSize="16"/>
252
-                                        <color key="textColor" red="0.20000000000000001" green="0.20000000000000001" blue="0.20000000000000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
253
-                                        <nil key="highlightedColor"/>
254
-                                    </label>
255
-                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="群未锁定,可以邀请新成员加入" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="niR-rQ-aFl">
256
-                                        <rect key="frame" x="77" y="15.5" width="221" height="17"/>
257
-                                        <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
258
-                                        <fontDescription key="fontDescription" type="system" pointSize="14"/>
259
-                                        <color key="textColor" red="0.66666666666666663" green="0.66666666666666663" blue="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
260
-                                        <nil key="highlightedColor"/>
261
-                                    </label>
151
+                                    </view>
152
+                                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="euC-CX-gvx" userLabel="Group Lock">
153
+                                        <rect key="frame" x="0.0" y="180" width="375" height="45"/>
154
+                                        <subviews>
155
+                                            <switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="1000" verticalHuggingPriority="750" horizontalCompressionResistancePriority="1000" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="BHD-pt-RTg">
156
+                                                <rect key="frame" x="314" y="7" width="51" height="31"/>
157
+                                                <color key="onTintColor" red="0.97647058819999999" green="0.34901960780000002" blue="0.50196078430000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
158
+                                                <connections>
159
+                                                    <action selector="changeSwitch" destination="s9X-RR-Rat" eventType="valueChanged" id="hmQ-IO-RLr"/>
160
+                                                </connections>
161
+                                            </switch>
162
+                                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="1000" verticalHuggingPriority="251" horizontalCompressionResistancePriority="1000" text="群锁定" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="W2B-lN-jKI">
163
+                                                <rect key="frame" x="12" y="13" width="49" height="19.5"/>
164
+                                                <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
165
+                                                <fontDescription key="fontDescription" type="system" pointSize="16"/>
166
+                                                <color key="textColor" red="0.20000000000000001" green="0.20000000000000001" blue="0.20000000000000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
167
+                                                <nil key="highlightedColor"/>
168
+                                            </label>
169
+                                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="群未锁定,可以邀请新成员加入" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="3Yr-Gn-GlA">
170
+                                                <rect key="frame" x="77" y="15.5" width="221" height="17"/>
171
+                                                <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
172
+                                                <fontDescription key="fontDescription" type="system" pointSize="14"/>
173
+                                                <color key="textColor" red="0.66666666669999997" green="0.66666666669999997" blue="0.66666666669999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
174
+                                                <nil key="highlightedColor"/>
175
+                                            </label>
176
+                                        </subviews>
177
+                                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
178
+                                        <constraints>
179
+                                            <constraint firstAttribute="height" constant="45" id="4xj-Zd-riE"/>
180
+                                            <constraint firstItem="BHD-pt-RTg" firstAttribute="leading" secondItem="3Yr-Gn-GlA" secondAttribute="trailing" constant="16" id="M98-BM-2lz"/>
181
+                                            <constraint firstItem="3Yr-Gn-GlA" firstAttribute="bottom" secondItem="W2B-lN-jKI" secondAttribute="bottom" id="MMH-bL-Dek"/>
182
+                                            <constraint firstItem="W2B-lN-jKI" firstAttribute="leading" secondItem="euC-CX-gvx" secondAttribute="leading" constant="12" id="Xwa-8I-bNK"/>
183
+                                            <constraint firstItem="3Yr-Gn-GlA" firstAttribute="leading" secondItem="W2B-lN-jKI" secondAttribute="trailing" constant="16" id="ZVy-6a-GFz"/>
184
+                                            <constraint firstAttribute="trailing" secondItem="BHD-pt-RTg" secondAttribute="trailing" constant="12" id="fUg-Vn-LXT"/>
185
+                                            <constraint firstItem="BHD-pt-RTg" firstAttribute="centerY" secondItem="euC-CX-gvx" secondAttribute="centerY" id="rJa-xe-Kqo"/>
186
+                                            <constraint firstItem="W2B-lN-jKI" firstAttribute="centerY" secondItem="euC-CX-gvx" secondAttribute="centerY" id="srY-5W-aCJ"/>
187
+                                        </constraints>
188
+                                    </view>
262 189
                                 </subviews>
263
-                                <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
264
-                                <constraints>
265
-                                    <constraint firstAttribute="trailing" secondItem="t9Z-kG-PPs" secondAttribute="trailing" constant="12" id="KkU-tC-Egu"/>
266
-                                    <constraint firstAttribute="height" constant="45" id="SJk-Ib-saK"/>
267
-                                    <constraint firstItem="t9Z-kG-PPs" firstAttribute="leading" secondItem="niR-rQ-aFl" secondAttribute="trailing" constant="16" id="fz2-LG-fUc"/>
268
-                                    <constraint firstItem="pGa-DR-SDU" firstAttribute="centerY" secondItem="Pb3-YX-nuZ" secondAttribute="centerY" id="g0e-A6-o5n"/>
269
-                                    <constraint firstItem="pGa-DR-SDU" firstAttribute="leading" secondItem="Pb3-YX-nuZ" secondAttribute="leading" constant="12" id="l6i-0S-OgW"/>
270
-                                    <constraint firstItem="t9Z-kG-PPs" firstAttribute="centerY" secondItem="Pb3-YX-nuZ" secondAttribute="centerY" id="oaN-d4-VQp"/>
271
-                                    <constraint firstItem="niR-rQ-aFl" firstAttribute="leading" secondItem="pGa-DR-SDU" secondAttribute="trailing" constant="16" id="u9L-r3-u2w"/>
272
-                                    <constraint firstItem="niR-rQ-aFl" firstAttribute="bottom" secondItem="pGa-DR-SDU" secondAttribute="bottom" id="yNV-6l-bfB"/>
273
-                                </constraints>
274
-                            </view>
190
+                            </stackView>
275 191
                         </subviews>
276 192
                         <color key="backgroundColor" red="0.94117647058823528" green="0.94117647058823528" blue="0.94117647058823528" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
277 193
                         <constraints>
278
-                            <constraint firstItem="pMy-uJ-dNy" firstAttribute="leading" secondItem="cIo-kp-fSE" secondAttribute="leading" id="DJT-ec-BYi"/>
279
-                            <constraint firstItem="cIo-kp-fSE" firstAttribute="trailing" secondItem="Pb3-YX-nuZ" secondAttribute="trailing" id="GHr-gH-44j"/>
280
-                            <constraint firstItem="rPs-2T-yj8" firstAttribute="top" secondItem="pMy-uJ-dNy" secondAttribute="bottom" id="PC0-el-XOL"/>
281
-                            <constraint firstItem="cIo-kp-fSE" firstAttribute="trailing" secondItem="rPs-2T-yj8" secondAttribute="trailing" id="SBF-Gf-sea"/>
282
-                            <constraint firstItem="cIo-kp-fSE" firstAttribute="trailing" secondItem="fpL-PF-0xo" secondAttribute="trailing" id="dKZ-bN-aud"/>
283
-                            <constraint firstItem="fpL-PF-0xo" firstAttribute="leading" secondItem="cIo-kp-fSE" secondAttribute="leading" id="dvc-Qp-fCK"/>
284
-                            <constraint firstItem="rPs-2T-yj8" firstAttribute="leading" secondItem="cIo-kp-fSE" secondAttribute="leading" id="exJ-yM-58B"/>
285
-                            <constraint firstItem="pMy-uJ-dNy" firstAttribute="top" secondItem="fpL-PF-0xo" secondAttribute="bottom" id="iBX-Qu-4Rh"/>
286
-                            <constraint firstItem="Pb3-YX-nuZ" firstAttribute="leading" secondItem="cIo-kp-fSE" secondAttribute="leading" id="lm3-hT-PcR"/>
287
-                            <constraint firstItem="cIo-kp-fSE" firstAttribute="trailing" secondItem="pMy-uJ-dNy" secondAttribute="trailing" id="mOe-DU-Rdn"/>
288
-                            <constraint firstItem="Pb3-YX-nuZ" firstAttribute="top" secondItem="rPs-2T-yj8" secondAttribute="bottom" id="ow5-8x-3xA"/>
289
-                            <constraint firstItem="fpL-PF-0xo" firstAttribute="top" secondItem="cIo-kp-fSE" secondAttribute="top" constant="5" id="wQP-uP-4dl"/>
194
+                            <constraint firstItem="cIo-kp-fSE" firstAttribute="trailing" secondItem="eF5-Vm-thF" secondAttribute="trailing" id="JNg-sX-8Cu"/>
195
+                            <constraint firstItem="eF5-Vm-thF" firstAttribute="top" secondItem="cIo-kp-fSE" secondAttribute="top" id="d34-oy-pVa"/>
196
+                            <constraint firstItem="eF5-Vm-thF" firstAttribute="leading" secondItem="cIo-kp-fSE" secondAttribute="leading" id="osC-NS-iQM"/>
290 197
                         </constraints>
291 198
                         <viewLayoutGuide key="safeArea" id="cIo-kp-fSE"/>
292 199
                     </view>
293 200
                     <navigationItem key="navigationItem" id="Cjc-V8-n6M"/>
294 201
                     <connections>
295
-                        <outlet property="groupLockSwitch" destination="t9Z-kG-PPs" id="mJz-yT-bcT"/>
296
-                        <outlet property="groupLockTip" destination="niR-rQ-aFl" id="AX6-6k-zux"/>
297
-                        <outlet property="groupMemberCountLabel" destination="AxF-rT-r4a" id="eOC-vz-TTc"/>
298
-                        <outlet property="groupMemeberView" destination="PEf-pV-jyM" id="Cqs-Fq-QzC"/>
299
-                        <outlet property="groupNameLabel" destination="1X6-D5-hkj" id="neB-7y-IOQ"/>
202
+                        <outlet property="groupLockSwitch" destination="BHD-pt-RTg" id="3Ip-yz-qDn"/>
203
+                        <outlet property="groupLockTip" destination="3Yr-Gn-GlA" id="vZj-kd-JUR"/>
204
+                        <outlet property="groupMemberCountLabel" destination="HZE-FN-DyQ" id="A7o-Ij-sbh"/>
205
+                        <outlet property="groupMemeberView" destination="gAe-sP-K7J" id="BgX-En-nx3"/>
206
+                        <outlet property="groupNameLabel" destination="Sv7-52-SPp" id="Q1p-sJ-2da"/>
300 207
                     </connections>
301 208
                 </viewController>
302 209
                 <placeholder placeholderIdentifier="IBFirstResponder" id="3Gk-LZ-t7S" sceneMemberID="firstResponder"/>
303 210
                 <tapGestureRecognizer id="QhO-Be-7C1" userLabel="GroupNameGesture">
304 211
                     <connections>
305
-                        <action selector="navigationToGroupNameModification:" destination="s9X-RR-Rat" id="R9K-8v-b14"/>
212
+                        <action selector="navigateToGroupNameModification:" destination="s9X-RR-Rat" id="R9K-8v-b14"/>
306 213
                     </connections>
307 214
                 </tapGestureRecognizer>
308 215
                 <tapGestureRecognizer id="PAc-yU-TO1" userLabel="GroupMemeberGesture">
309 216
                     <connections>
310
-                        <action selector="navigationToGroupMember:" destination="s9X-RR-Rat" id="V81-eM-vdl"/>
217
+                        <action selector="navigateToGroupMember:" destination="s9X-RR-Rat" id="V81-eM-vdl"/>
311 218
                     </connections>
312 219
                 </tapGestureRecognizer>
313 220
                 <tapGestureRecognizer id="CLv-er-MwX" userLabel="GroupQRGesture">
@@ -377,7 +284,7 @@
377 284
                     </connections>
378 285
                 </tapGestureRecognizer>
379 286
             </objects>
380
-            <point key="canvasLocation" x="665.60000000000002" y="277.06146926536735"/>
287
+            <point key="canvasLocation" x="-106" y="926"/>
381 288
         </scene>
382 289
         <!--GroupMemberViewController-->
383 290
         <scene sceneID="LEa-gB-9eo">
@@ -486,7 +393,7 @@
486 393
                 </viewController>
487 394
                 <placeholder placeholderIdentifier="IBFirstResponder" id="gLf-lq-mC0" sceneMemberID="firstResponder"/>
488 395
             </objects>
489
-            <point key="canvasLocation" x="665.60000000000002" y="1461.7691154422789"/>
396
+            <point key="canvasLocation" x="-106" y="1622"/>
490 397
         </scene>
491 398
     </scenes>
492 399
     <resources>

+ 3 - 3
PaiAi/Paiai_iOS/App/Group/GroupDetail/GroupDetailCoordinator.swift

@@ -22,15 +22,15 @@ class GroupDetailCoordinator: Coordinator {
22 22
 }
23 23
 
24 24
 extension GroupDetailCoordinator: GroupDetailViewModelDelegate {
25
-    func navigationToRootViewController() {
25
+    func navigateToRootViewController() {
26 26
         navigationController.popToRootViewController(animated: true)
27 27
     }
28 28
     
29
-    func navigationToGroupMember(_ item: GroupDetailItem) {
29
+    func navigateToGroupMember(_ item: GroupDetailItem) {
30 30
         navigationController.pushViewController(makeGroupMemberViewController(item))
31 31
     }
32 32
     
33
-    func navigationToGroupNameModification(_ item: GroupDetailItem) {
33
+    func navigateToGroupNameModification(_ item: GroupDetailItem) {
34 34
         navigationController.pushViewController(makeGroupNameModificationViewController(item))
35 35
     }
36 36
 }

+ 4 - 4
PaiAi/Paiai_iOS/App/Group/GroupDetail/GroupDetailViewController.swift

@@ -92,12 +92,12 @@ fileprivate extension GroupDetailViewController {
92 92
 
93 93
 /// storyboard action
94 94
 extension GroupDetailViewController {
95
-    @IBAction func navigationToGroupMember(_ sender: UITapGestureRecognizer) {
96
-        viewModel.navigationToGroupMember()
95
+    @IBAction func navigateToGroupMember(_ sender: UITapGestureRecognizer) {
96
+        viewModel.navigateToGroupMember()
97 97
     }
98 98
     
99
-    @IBAction func navigationToGroupNameModification(_ sender: UITapGestureRecognizer) {
100
-        viewModel.navigationToGroupNameModification()
99
+    @IBAction func navigateToGroupNameModification(_ sender: UITapGestureRecognizer) {
100
+        viewModel.navigateToGroupNameModification()
101 101
     }
102 102
     
103 103
     @IBAction func presentGroupQR(_ sender: UITapGestureRecognizer) {

+ 6 - 6
PaiAi/Paiai_iOS/App/Group/GroupDetail/GroupMemberCell.swift

@@ -28,12 +28,12 @@ final class GroupMemberCell: UITableViewCell {
28 28
     var item = GroupMemberItem(json: [:])
29 29
 
30 30
     // MARK: init interface
31
-    func setInfo(_ model: GroupMemberItem, isAdmin: Bool) {
32
-        item = model
33
-        meberName.text = model.nickname
34
-        uerImage.setImage(model.avatar, placeholder: UIImage.defaultAvatar)
35
-        isManager.isHidden = !model.admin
36
-        deleteConstraint.constant = !isAdmin || model.admin ? -32 : 12
31
+    func setInfo(_ info: GroupMemberItem, isAdmin: Bool) {
32
+        item = info
33
+        meberName.text = info.nickname
34
+        uerImage.setImage(info.avatar, placeholder: UIImage.defaultAvatar)
35
+        isManager.isHidden = !info.admin
36
+        deleteConstraint.constant = !isAdmin || info.admin ? -35 : 12
37 37
     }
38 38
 
39 39
     // MARK: Storyboard  button

+ 15 - 3
PaiAi/Paiai_iOS/App/Group/GroupViewController.swift

@@ -19,7 +19,9 @@ final class GroupViewController: UIViewController {
19 19
     // MARK: Storyboard property
20 20
     @IBOutlet weak var collectionView: UICollectionView!
21 21
     @IBOutlet weak var photographBtn: UIButton!
22
-
22
+    @IBOutlet weak var emptyView: UIStackView!
23
+    @IBOutlet weak var qrImageView: UIImageView!
24
+    
23 25
     // MARK: custom UI property
24 26
     var navigationBarView: UIView = {
25 27
         let view = UIView()
@@ -87,6 +89,9 @@ fileprivate extension GroupViewController {
87 89
     }
88 90
     
89 91
     func binding() {
92
+        bindViewModelToEmptyView()
93
+        bindViewModelToQRImageView()
94
+        
90 95
         bindViewModelToRefreshing()
91 96
         bindCollectionViewDelegate()
92 97
         bindViewModelToCollectionView()
@@ -95,6 +100,14 @@ fileprivate extension GroupViewController {
95 100
         bindViewModelToNavigationBarTitle()
96 101
     }
97 102
     
103
+    func bindViewModelToEmptyView() {
104
+        viewModel.hasData.bind(to: emptyView.rx.isHidden).disposed(by: disposeBag)
105
+    }
106
+    
107
+    func bindViewModelToQRImageView() {
108
+        viewModel.groupItem.map { UIImage(qr: "https:api.pai.ai/g/\($0.group_id)") }.bind(to: qrImageView.rx.image).disposed(by: disposeBag)
109
+    }
110
+    
98 111
     func bindViewModelToRefreshing() {
99 112
         viewModel.isLoading
100 113
             .subscribe(onNext: {[unowned self] flag in
@@ -129,7 +142,6 @@ fileprivate extension GroupViewController {
129 142
                 guard let `self` = self else { return }
130 143
                 self.navigationBarViewImage.image = UIImage(named: avatar)
131 144
         }).disposed(by: disposeBag)
132
-        
133 145
     }
134 146
 }
135 147
 
@@ -168,7 +180,7 @@ extension GroupViewController: NavigationBarInteractiveViewController {
168 180
         let groupItem = viewModel.groupItem.value
169 181
         let alert = AlertViewController(style: .custom(GroupQRView(groupName: groupItem.group_name,
170 182
                                                                    groupAvatar: "Group\(groupItem.group_default_avatar)",
171
-                                                                   groupQR: "https:pai.ai/g/\(groupItem.group_id)"),
183
+                                                                   groupQR: "https:api.pai.ai/g/\(groupItem.group_id)"),
172 184
                                                        AlertAnimator()) )
173 185
         presentController(alert)
174 186
     }

+ 14 - 2
PaiAi/Paiai_iOS/App/Home/HomeCoordinator.swift

@@ -50,7 +50,8 @@ extension HomeCoordinator: HomeViewModelDelegate {
50 50
     
51 51
     func scanQR() {
52 52
         let ctl = UIStoryboard.main.instantiateScanQRViewController()
53
-        homeViewController.pushController(ctl)
53
+        ctl.viewModel.delegate = self
54
+        navigationController.pushViewController(ctl)
54 55
     }
55 56
 }
56 57
 
@@ -73,15 +74,26 @@ extension HomeCoordinator: CreateGroupViewControllerDelegate {
73 74
 }
74 75
 
75 76
 extension HomeCoordinator: CreateGroupViewModelDelegate {
76
-    func navigationToGroup(_ item: GroupItem) {
77
+    func navigateToGroup(_ item: GroupItem) {
77 78
         guard let vc = homeViewController.presentedViewController, vc.isMember(of: CreateGroupConfirmViewController.self) else { return }
78 79
         vc.dismissController()
79 80
         
80 81
         let ctl = UIStoryboard.group.instantiateController(GroupViewController.self)
82
+        ctl.viewModel = GroupViewModel(groupItem: item)
81 83
         let coordinator = GroupCoordinator(ctl,
82 84
                                            navigationController: navigationController)
83 85
         coordinators[.group] = coordinator
86
+        navigationController.pushViewController(ctl)
87
+    }
88
+}
89
+
90
+extension HomeCoordinator: ScanQRViewModelDelegate {
91
+    func navigateToGroupFromScanQR(_ item: GroupItem) {
92
+        let ctl = UIStoryboard.group.instantiateController(GroupViewController.self)
84 93
         ctl.viewModel = GroupViewModel(groupItem: item)
94
+        let coordinator = GroupCoordinator(ctl,
95
+                                           navigationController: navigationController)
96
+        coordinators[.group] = coordinator
85 97
         navigationController.pushViewController(ctl)
86 98
     }
87 99
 }

+ 4 - 2
PaiAi/Paiai_iOS/App/Home/HomeViewController.swift

@@ -16,8 +16,6 @@ final class HomeViewController: UIViewController {
16 16
 
17 17
     @IBOutlet weak var scanBtn: UIButton!
18 18
     @IBOutlet weak var createBtn: UIButton!
19
-    @IBOutlet weak var scanLabel: UILabel!
20
-    @IBOutlet weak var createLabel: UILabel!
21 19
     @IBOutlet weak var collectionView: UICollectionView!
22 20
     // MARK: data property
23 21
     fileprivate let disposeBag = DisposeBag()
@@ -30,6 +28,10 @@ final class HomeViewController: UIViewController {
30 28
         initalize()
31 29
     }
32 30
     
31
+    override func viewDidAppear(_ animated: Bool) {
32
+        super.viewDidAppear(animated)
33
+    }
34
+    
33 35
     func initalize() {
34 36
         collectionView.register(UINib(nibName: "PhotoCell",
35 37
                                       bundle: Bundle(identifier: "com.Paiai-iOS")),

+ 75 - 66
PaiAi/Paiai_iOS/App/Home/Main.storyboard

@@ -18,7 +18,7 @@
18 18
                         <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
19 19
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
20 20
                         <subviews>
21
-                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="7XC-di-knu" customClass="QRCodeScanView" customModule="Paiai_iOS" customModuleProvider="target">
21
+                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="7XC-di-knu" customClass="QRCodeScanView" customModule="PaiaiUIKit">
22 22
                                 <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
23 23
                                 <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
24 24
                             </view>
@@ -31,47 +31,54 @@
31 31
                                 <color key="textColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
32 32
                                 <nil key="highlightedColor"/>
33 33
                             </label>
34
-                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="q8D-p5-28m">
34
+                            <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="q8D-p5-28m" userLabel="Button Group">
35 35
                                 <rect key="frame" x="0.0" y="547" width="375" height="120"/>
36 36
                                 <subviews>
37
-                                    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="rAd-6a-QN5">
38
-                                        <rect key="frame" x="212" y="30" width="126" height="126"/>
39
-                                        <state key="normal" image="BTN-flash"/>
40
-                                        <connections>
41
-                                            <action selector="openLight" destination="xAo-Yl-fd5" eventType="touchUpInside" id="S8Z-U4-bVm"/>
42
-                                        </connections>
43
-                                    </button>
44
-                                    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="WMh-c4-xbf">
45
-                                        <rect key="frame" x="37" y="30" width="126" height="126"/>
46
-                                        <state key="normal" image="BTN-photoAlbum"/>
47
-                                        <connections>
48
-                                            <action selector="chooseFromSYSPhoto" destination="xAo-Yl-fd5" eventType="touchUpInside" id="U7Z-Vq-2r2"/>
49
-                                        </connections>
50
-                                    </button>
51
-                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="相册" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="kS9-OM-EAv">
52
-                                        <rect key="frame" x="86.5" y="166" width="27" height="16"/>
53
-                                        <fontDescription key="fontDescription" type="system" pointSize="13"/>
54
-                                        <color key="textColor" red="0.59999999999999998" green="0.59999999999999998" blue="0.59999999999999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
55
-                                        <nil key="highlightedColor"/>
56
-                                    </label>
57
-                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="开灯" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="3ge-7H-lZn">
58
-                                        <rect key="frame" x="261.5" y="166" width="27" height="16"/>
59
-                                        <fontDescription key="fontDescription" type="system" pointSize="13"/>
60
-                                        <color key="textColor" red="0.59999999999999998" green="0.59999999999999998" blue="0.59999999999999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
61
-                                        <nil key="highlightedColor"/>
62
-                                    </label>
37
+                                    <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalSpacing" alignment="center" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="8lh-CA-1SM">
38
+                                        <rect key="frame" x="37" y="-16" width="126" height="152"/>
39
+                                        <subviews>
40
+                                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="q7U-7O-6Og">
41
+                                                <rect key="frame" x="0.0" y="0.0" width="126" height="126"/>
42
+                                                <state key="normal" image="BTN-photoAlbum"/>
43
+                                                <connections>
44
+                                                    <action selector="openPhotoLibrary:" destination="xAo-Yl-fd5" eventType="touchUpInside" id="z8x-bJ-FA8"/>
45
+                                                </connections>
46
+                                            </button>
47
+                                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="相册" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="QXR-Bg-aZe">
48
+                                                <rect key="frame" x="49.5" y="136" width="27" height="16"/>
49
+                                                <fontDescription key="fontDescription" type="system" pointSize="13"/>
50
+                                                <color key="textColor" red="0.59999999999999998" green="0.59999999999999998" blue="0.59999999999999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
51
+                                                <nil key="highlightedColor"/>
52
+                                            </label>
53
+                                        </subviews>
54
+                                    </stackView>
55
+                                    <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalSpacing" alignment="center" spacing="10" translatesAutoresizingMaskIntoConstraints="NO" id="AkM-en-2b0">
56
+                                        <rect key="frame" x="212" y="-16" width="126" height="152"/>
57
+                                        <subviews>
58
+                                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="yvI-5s-SNa">
59
+                                                <rect key="frame" x="0.0" y="0.0" width="126" height="126"/>
60
+                                                <state key="normal" image="BTN-flash"/>
61
+                                                <connections>
62
+                                                    <action selector="chooseFromSYSPhoto" destination="xAo-Yl-fd5" eventType="touchUpInside" id="5MC-Mh-hVr"/>
63
+                                                    <action selector="openLight" destination="xAo-Yl-fd5" eventType="touchUpInside" id="3CZ-Mz-UhH"/>
64
+                                                </connections>
65
+                                            </button>
66
+                                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="相册" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="q3p-9D-GA5" userLabel="开灯">
67
+                                                <rect key="frame" x="49.5" y="136" width="27" height="16"/>
68
+                                                <fontDescription key="fontDescription" type="system" pointSize="13"/>
69
+                                                <color key="textColor" red="0.59999999999999998" green="0.59999999999999998" blue="0.59999999999999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
70
+                                                <nil key="highlightedColor"/>
71
+                                            </label>
72
+                                        </subviews>
73
+                                    </stackView>
63 74
                                 </subviews>
64 75
                                 <color key="backgroundColor" red="0.0" green="0.0" blue="0.0" alpha="0.61263020833333337" colorSpace="custom" customColorSpace="sRGB"/>
65 76
                                 <constraints>
77
+                                    <constraint firstItem="AkM-en-2b0" firstAttribute="centerX" secondItem="q8D-p5-28m" secondAttribute="centerX" multiplier="2.2:1.5" id="00d-8F-8A8"/>
78
+                                    <constraint firstItem="8lh-CA-1SM" firstAttribute="centerX" secondItem="q8D-p5-28m" secondAttribute="centerX" multiplier="0.8:1.5" id="8Y5-Ty-dQU"/>
66 79
                                     <constraint firstAttribute="height" constant="120" id="C3L-jp-KPg"/>
67
-                                    <constraint firstItem="3ge-7H-lZn" firstAttribute="centerY" secondItem="kS9-OM-EAv" secondAttribute="centerY" id="Eg5-nH-ESw"/>
68
-                                    <constraint firstItem="rAd-6a-QN5" firstAttribute="centerX" secondItem="q8D-p5-28m" secondAttribute="centerX" multiplier="2.2:1.5" id="OvL-8a-H6B"/>
69
-                                    <constraint firstItem="kS9-OM-EAv" firstAttribute="centerX" secondItem="WMh-c4-xbf" secondAttribute="centerX" id="RX1-NM-6ql"/>
70
-                                    <constraint firstItem="3ge-7H-lZn" firstAttribute="centerX" secondItem="rAd-6a-QN5" secondAttribute="centerX" id="SeW-CQ-8PV"/>
71
-                                    <constraint firstItem="rAd-6a-QN5" firstAttribute="top" secondItem="q8D-p5-28m" secondAttribute="top" constant="30" id="lEh-G7-ra7"/>
72
-                                    <constraint firstItem="kS9-OM-EAv" firstAttribute="top" secondItem="WMh-c4-xbf" secondAttribute="bottom" constant="10" id="lc4-9K-GH0"/>
73
-                                    <constraint firstItem="WMh-c4-xbf" firstAttribute="centerX" secondItem="q8D-p5-28m" secondAttribute="centerX" multiplier="0.8:1.5" id="oAa-Vb-s7j"/>
74
-                                    <constraint firstItem="WMh-c4-xbf" firstAttribute="centerY" secondItem="rAd-6a-QN5" secondAttribute="centerY" id="svb-nJ-3Va"/>
80
+                                    <constraint firstItem="8lh-CA-1SM" firstAttribute="centerY" secondItem="q8D-p5-28m" secondAttribute="centerY" id="MEn-bu-Ss6"/>
81
+                                    <constraint firstItem="AkM-en-2b0" firstAttribute="centerY" secondItem="q8D-p5-28m" secondAttribute="centerY" id="ccs-Xt-wa5"/>
75 82
                                 </constraints>
76 83
                             </view>
77 84
                         </subviews>
@@ -80,7 +87,7 @@
80 87
                             <constraint firstItem="7XC-di-knu" firstAttribute="height" secondItem="EBW-ey-zDN" secondAttribute="height" id="1W3-tY-WR7"/>
81 88
                             <constraint firstItem="7XC-di-knu" firstAttribute="centerY" secondItem="EBW-ey-zDN" secondAttribute="centerY" id="4iq-Mx-Dgn"/>
82 89
                             <constraint firstItem="q8D-p5-28m" firstAttribute="leading" secondItem="5ji-s5-pMy" secondAttribute="leading" id="7pa-0E-902"/>
83
-                            <constraint firstItem="5ji-s5-pMy" firstAttribute="bottom" secondItem="q8D-p5-28m" secondAttribute="bottom" id="OlH-gg-PAG"/>
90
+                            <constraint firstAttribute="bottom" secondItem="q8D-p5-28m" secondAttribute="bottom" id="OlH-gg-PAG"/>
84 91
                             <constraint firstItem="7XC-di-knu" firstAttribute="width" secondItem="EBW-ey-zDN" secondAttribute="width" id="Smj-tP-9h1"/>
85 92
                             <constraint firstItem="IUv-gn-PD8" firstAttribute="centerX" secondItem="5ji-s5-pMy" secondAttribute="centerX" id="aJ9-AF-OhC"/>
86 93
                             <constraint firstItem="7XC-di-knu" firstAttribute="centerX" secondItem="5ji-s5-pMy" secondAttribute="centerX" id="c15-dp-2dX"/>
@@ -91,13 +98,12 @@
91 98
                     </view>
92 99
                     <navigationItem key="navigationItem" id="OV6-lQ-3b0"/>
93 100
                     <connections>
94
-                        <outlet property="lightLabel" destination="3ge-7H-lZn" id="gyD-hT-m5I"/>
95 101
                         <outlet property="scanView" destination="7XC-di-knu" id="Nbf-fG-L8b"/>
96 102
                     </connections>
97 103
                 </viewController>
98 104
                 <placeholder placeholderIdentifier="IBFirstResponder" id="Egx-Nh-Avl" userLabel="First Responder" sceneMemberID="firstResponder"/>
99 105
             </objects>
100
-            <point key="canvasLocation" x="2804" y="-128.18590704647679"/>
106
+            <point key="canvasLocation" x="3034" y="-49"/>
101 107
         </scene>
102 108
         <!--Home View Controller-->
103 109
         <scene sceneID="tne-QT-ifu">
@@ -113,34 +119,37 @@
113 119
                                 <collectionViewLayout key="collectionViewLayout" id="0AU-E8-zrg" customClass="WaterfallFlowLayout" customModule="PaiaiUIKit"/>
114 120
                                 <cells/>
115 121
                             </collectionView>
116
-                            <button contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="7sh-KD-Wgc">
117
-                                <rect key="frame" x="303" y="536" width="64" height="64"/>
118
-                                <constraints>
119
-                                    <constraint firstAttribute="width" constant="64" id="6uz-fL-QnX"/>
120
-                                    <constraint firstAttribute="height" constant="64" id="fVK-Un-AnR"/>
121
-                                </constraints>
122
-                                <state key="normal" image="BTN-scanQR"/>
123
-                                <state key="selected" image="BTN-扫码-press"/>
124
-                            </button>
125
-                            <button contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="EqW-Tv-dAo">
126
-                                <rect key="frame" x="303" y="603" width="64" height="64"/>
127
-                                <constraints>
128
-                                    <constraint firstAttribute="height" constant="64" id="6oe-ry-LNq"/>
129
-                                    <constraint firstAttribute="width" constant="64" id="bCz-yX-g9j"/>
130
-                                </constraints>
131
-                                <state key="normal" image="BTN-add"/>
132
-                                <state key="selected" image="BTN-新增-press"/>
133
-                            </button>
122
+                            <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="equalSpacing" spacing="3" translatesAutoresizingMaskIntoConstraints="NO" id="cKo-t0-fYb">
123
+                                <rect key="frame" x="303" y="530" width="64" height="131"/>
124
+                                <subviews>
125
+                                    <button contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="9N2-vw-PuD">
126
+                                        <rect key="frame" x="0.0" y="0.0" width="64" height="64"/>
127
+                                        <constraints>
128
+                                            <constraint firstAttribute="width" constant="64" id="ZvU-38-X8E"/>
129
+                                            <constraint firstAttribute="height" constant="64" id="tmA-ui-pTy"/>
130
+                                        </constraints>
131
+                                        <state key="normal" image="BTN-scanQR"/>
132
+                                        <state key="selected" image="BTN-扫码-press"/>
133
+                                    </button>
134
+                                    <button contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="f1p-NO-R2Q">
135
+                                        <rect key="frame" x="0.0" y="67" width="64" height="64"/>
136
+                                        <constraints>
137
+                                            <constraint firstAttribute="height" constant="64" id="4PT-EP-AZd"/>
138
+                                            <constraint firstAttribute="width" constant="64" id="B4F-Wn-0g0"/>
139
+                                        </constraints>
140
+                                        <state key="normal" image="BTN-add"/>
141
+                                        <state key="selected" image="BTN-新增-press"/>
142
+                                    </button>
143
+                                </subviews>
144
+                            </stackView>
134 145
                         </subviews>
135 146
                         <color key="backgroundColor" red="0.8784313725490196" green="0.8784313725490196" blue="0.8784313725490196" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
136 147
                         <gestureRecognizers/>
137 148
                         <constraints>
138
-                            <constraint firstItem="WrX-F2-609" firstAttribute="trailing" secondItem="EqW-Tv-dAo" secondAttribute="trailing" constant="8" id="8xh-Ra-Ka9"/>
149
+                            <constraint firstItem="WrX-F2-609" firstAttribute="trailing" secondItem="cKo-t0-fYb" secondAttribute="trailing" constant="8" id="PWG-Fo-tUz"/>
139 150
                             <constraint firstItem="jOc-hp-K8U" firstAttribute="leading" secondItem="WrX-F2-609" secondAttribute="leading" id="Qu3-TE-tcJ"/>
140
-                            <constraint firstItem="WrX-F2-609" firstAttribute="bottom" secondItem="EqW-Tv-dAo" secondAttribute="bottom" id="TQF-Gj-zB1"/>
141
-                            <constraint firstItem="7sh-KD-Wgc" firstAttribute="centerX" secondItem="EqW-Tv-dAo" secondAttribute="centerX" id="dbL-Gy-iB4"/>
151
+                            <constraint firstItem="WrX-F2-609" firstAttribute="bottom" secondItem="cKo-t0-fYb" secondAttribute="bottom" constant="6" id="T1b-BO-JPL"/>
142 152
                             <constraint firstItem="WrX-F2-609" firstAttribute="trailing" secondItem="jOc-hp-K8U" secondAttribute="trailing" id="iMD-9d-Ih2"/>
143
-                            <constraint firstItem="EqW-Tv-dAo" firstAttribute="top" secondItem="7sh-KD-Wgc" secondAttribute="bottom" constant="3" id="oUu-Fy-hJV"/>
144 153
                             <constraint firstItem="jOc-hp-K8U" firstAttribute="top" secondItem="WrX-F2-609" secondAttribute="top" id="pOC-6k-2LI"/>
145 154
                             <constraint firstAttribute="bottom" secondItem="jOc-hp-K8U" secondAttribute="bottom" id="xM5-JS-BLE"/>
146 155
                         </constraints>
@@ -150,13 +159,13 @@
150 159
                     <navigationItem key="navigationItem" id="blv-Da-mEq"/>
151 160
                     <connections>
152 161
                         <outlet property="collectionView" destination="jOc-hp-K8U" id="4Hm-wP-XjU"/>
153
-                        <outlet property="createBtn" destination="EqW-Tv-dAo" id="Q3K-6N-47U"/>
154
-                        <outlet property="scanBtn" destination="7sh-KD-Wgc" id="oZT-w4-Ddq"/>
162
+                        <outlet property="createBtn" destination="f1p-NO-R2Q" id="wDa-iP-DeC"/>
163
+                        <outlet property="scanBtn" destination="9N2-vw-PuD" id="tdK-KP-5Vh"/>
155 164
                     </connections>
156 165
                 </viewController>
157 166
                 <placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
158 167
             </objects>
159
-            <point key="canvasLocation" x="1705" y="-152"/>
168
+            <point key="canvasLocation" x="2356" y="-49"/>
160 169
         </scene>
161 170
         <!--CreateGroupViewController-->
162 171
         <scene sceneID="AAe-LB-J2k">
@@ -251,7 +260,7 @@
251 260
                 </viewController>
252 261
                 <placeholder placeholderIdentifier="IBFirstResponder" id="7bb-Fp-aIh" userLabel="First Responder" sceneMemberID="firstResponder"/>
253 262
             </objects>
254
-            <point key="canvasLocation" x="4628" y="80.50974512743629"/>
263
+            <point key="canvasLocation" x="4354" y="-49"/>
255 264
         </scene>
256 265
         <!--CreateGroupConfirmViewController-->
257 266
         <scene sceneID="zhC-tX-7HT">
@@ -369,7 +378,7 @@
369 378
                 </viewController>
370 379
                 <placeholder placeholderIdentifier="IBFirstResponder" id="hw0-Gp-7zQ" userLabel="First Responder" sceneMemberID="firstResponder"/>
371 380
             </objects>
372
-            <point key="canvasLocation" x="3847.1999999999998" y="80.50974512743629"/>
381
+            <point key="canvasLocation" x="3694" y="-49"/>
373 382
         </scene>
374 383
         <!--LoginViewController-->
375 384
         <scene sceneID="ETR-Op-aEq">

+ 23 - 86
PaiAi/Paiai_iOS/App/Home/ScanQRViewController.swift

@@ -15,7 +15,6 @@ final class ScanQRViewController: UIViewController {
15 15
 
16 16
     // MARK: Storyboard property
17 17
     @IBOutlet weak var scanView: QRCodeScanView!
18
-    @IBOutlet weak var lightLabel: UILabel!
19 18
 
20 19
    // MARK: parameter property
21 20
     var viewModel = ScanQRViewModel()
@@ -24,108 +23,46 @@ final class ScanQRViewController: UIViewController {
24 23
     // MARK: view function
25 24
     override func viewDidLoad() {
26 25
         super.viewDidLoad()
27
-//        navigationController?.navigationBar.setBackgroundImage(UIImage.imageWithColor(UIColor.black), for: .default)
28 26
         scanView.delegate = self
27
+        viewModel.join(code: "http://pai.ai/g/SpA5be3")
29 28
     }
30
-
29
+    
30
+    func setNavigationBar() {
31
+        title = "二维码"
32
+        navigationController?.navigationBar.setBackgroundImage(UIImage.Navigation.blackBackground, for: .default)
33
+    }
34
+    
31 35
     override func viewWillAppear(_ animated: Bool) {
32
-        super.viewWillAppear(true)
33
-//        titleWithbackBar = "二维码"
36
+        super.viewWillAppear(animated)
37
+        setNavigationBar()
34 38
     }
35 39
 
36
-    // MARK: Storyboard  button function
37
-    @IBAction func chooseFromSYSPhoto() {
38
-        scanView.openPhotoLibrary(ctl: self)
40
+    override func viewWillDisappear(_ animated: Bool) {
41
+        super.viewWillDisappear(animated)
42
+        navigationController?.navigationBar.setBackgroundImage(UIImage.Navigation.background, for: .default)
39 43
     }
44
+    
45
+}
40 46
 
41
-    @IBAction func openLight() {
42
-        scanView.openLight()
47
+/// storyboard button action
48
+extension ScanQRViewController {
49
+    @IBAction func openPhotoLibrary(_ sender: UIButton) {
50
+        scanView.openPhotoLibrary(ctl: self)
43 51
     }
44 52
     
45
-    
53
+    @IBAction func openLight(_ sender: UIButton) {
54
+        scanView.openLight()
55
+    }
46 56
 }
47 57
 
48 58
 // MARK: read QR message
49 59
 // MARK: custom delegate function
50 60
 extension ScanQRViewController: QRCodeScanViewDelegate {
51
-    func scanView(_ scanView: QRCodeScanView, didFinshWithError error: NSError?) {
52
-        
53
-    }
61
+    func scanView(_ scanView: QRCodeScanView, didFinshWithError error: NSError?) {}
54 62
     
55 63
     func scanView(_ scanner: QRCodeScanView, receivedScanResult: QRCodeScanResult) {
56 64
         viewModel.join(code: receivedScanResult.result)
57 65
     }
58
-
59
-    func addToTourGroup(group_id: String) {
60
-//        if user_phone.isEmpty {
61
-//            let phone = NSString(string: user_phone).integerValue
62
-//            let params = ["admin_id": group_id, "user_id": SharedUserInfo.userId, "authority": authority, "name": user_name, "phone": phone ] as [String: AnyObject]
63
-
64
-//            let request = GroupNetworkRequest(path: .joinTourGroup, parameter: params)
65
-//            NetworkApi.share.post(request: request, handler: { (res) in
66
-//                let ctl = UIStoryboard.main.instantiateController(GroupViewController.self)
67
-//                ctl.isCreate = true
68
-//                ctl.isScan = true
69
-//                ctl.groupModel = res[0]
70
-//                self.navigationController?.pushViewController(ctl, animated: true)
71
-//            })
72
-//        } else {
73
-//            let ctl = UIStoryboard.main.instantiateController(PersonInformationController.self)
74
-//            ctl.admin_id = group_id
75
-//            navigationController?.pushViewController(ctl, animated: true)
76
-//        }
77
-    }
78
-
79
-    func addToGroup(_ code: String) {
80
-//        let strs = code.components(separatedBy: "/")
81
-//        guard strs.count > 2, var group_id = strs.last else {
82
-//            FFToastView.showToast(inView: self.view, withText: "读取失败")
83
-//            return
84
-//        }
85
-//        FFToastView.showLoadingToast(inView: view, blockSuperView: true)
86
-//        let flag = strs[strs.count-2]
87
-//        guard flag == "s" || flag == "g" || flag == "tgu" else {
88
-//            FFToastView.showToast(inView: self.view, withText: "读取失败")
89
-//            return
90
-//        }
91
-//        if flag == "tgu" {
92
-//            addToTourGroup(group_id: group_id)
93
-//            return
94
-//        }
95
-//        let isGroup = flag == "g"
96
-//        let path = isGroup ? Interfaces.joinGroup : Interfaces.joinSession
97
-//        let idName = isGroup ? "group_id" : "session_id"
98
-//        let lensman_id = (group_id.components(separatedBy: "?").last ?? "").components(separatedBy: "=").last ?? ""
99
-//        group_id = group_id.components(separatedBy: "?").first ?? ""
100
-//        let params = ["user_id": SharedUserInfo.userId, idName: group_id, "nickname": SharedUserInfo.name, "lensman_id" : lensman_id]  as [String: AnyObject]
101
-
102
-//        let request = GroupNetworkRequest(path: path, parameter: params)
103
-//        NetworkApi.share.post(request: request, handler: { (res) in
104
-//            let ctl = UIStoryboard.main.instantiateController(GroupViewController.self)
105
-//            ctl.isCreate = true
106
-//            ctl.isScan = true
107
-//            ctl.groupModel = res[0]
108
-//            self.navigationController?.pushViewController(ctl, animated: true)
109
-//            FFToastView.hideLoadingToast()
110
-//        })
111
-    }
112 66
 }
113 67
 
114
-// MARK: imagePick delegate
115
-extension ScanQRViewController: UIImagePickerControllerDelegate {
116
-
117
-    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingImage image: UIImage, editingInfo: [String: AnyObject]?) {
118
-        picker.dismiss(animated: true) {
119
-//            if let sessionid = image.decodeQRCodeInImage() {
120
-//                self.addToGroup(sessionid)
121
-//            } else {
122
-//                FFToastView.showToast(inView: self.view, withText: "未检测到群二维码")
123
-//            }
124
-        }
125
-    }
126
-
127
-    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
128
-        picker.dismissController()
129
-    }
130
-
131
-}
68
+extension ScanQRViewController: NavigationBackViewController {}

+ 173 - 173
PaiAi/Paiai_iOS/App/Message/Message.storyboard

@@ -190,203 +190,203 @@
190 190
                         <rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
191 191
                         <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
192 192
                         <subviews>
193
-                            <view contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" translatesAutoresizingMaskIntoConstraints="NO" id="StI-0Q-fat">
194
-                                <rect key="frame" x="0.0" y="20" width="375" height="647"/>
193
+                            <stackView opaque="NO" contentMode="scaleToFill" axis="vertical" translatesAutoresizingMaskIntoConstraints="NO" id="vQs-oc-uao">
194
+                                <rect key="frame" x="0.0" y="20" width="375" height="192"/>
195 195
                                 <subviews>
196
-                                    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="WTN-Zx-JKR">
196
+                                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="nRF-R0-LcP" userLabel="message system">
197 197
                                         <rect key="frame" x="0.0" y="0.0" width="375" height="64"/>
198
+                                        <subviews>
199
+                                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="W1B-Rc-piS">
200
+                                                <rect key="frame" x="0.0" y="0.0" width="375" height="64"/>
201
+                                                <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
202
+                                                <constraints>
203
+                                                    <constraint firstAttribute="height" constant="64" id="Sh9-mX-758"/>
204
+                                                </constraints>
205
+                                            </button>
206
+                                            <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="message-system" translatesAutoresizingMaskIntoConstraints="NO" id="PdF-av-quF">
207
+                                                <rect key="frame" x="12" y="8" width="48" height="48"/>
208
+                                                <constraints>
209
+                                                    <constraint firstAttribute="width" secondItem="PdF-av-quF" secondAttribute="height" id="X2X-ko-fTx"/>
210
+                                                    <constraint firstAttribute="width" constant="48" id="ozh-Fb-Dwl"/>
211
+                                                </constraints>
212
+                                            </imageView>
213
+                                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="服务提醒" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="7m9-4r-95l">
214
+                                                <rect key="frame" x="70" y="22.5" width="65.5" height="19.5"/>
215
+                                                <fontDescription key="fontDescription" type="system" pointSize="16"/>
216
+                                                <color key="textColor" red="0.20000000000000001" green="0.20000000000000001" blue="0.20000000000000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
217
+                                                <nil key="highlightedColor"/>
218
+                                            </label>
219
+                                            <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="list-arrow" translatesAutoresizingMaskIntoConstraints="NO" id="dyn-zM-jdf">
220
+                                                <rect key="frame" x="341" y="14" width="24" height="36"/>
221
+                                            </imageView>
222
+                                            <view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="veG-mJ-Dsh">
223
+                                                <rect key="frame" x="325" y="28" width="8" height="8"/>
224
+                                                <color key="backgroundColor" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
225
+                                                <constraints>
226
+                                                    <constraint firstAttribute="height" constant="8" id="OSl-kV-dhl"/>
227
+                                                    <constraint firstAttribute="width" constant="8" id="ojQ-4T-AHY"/>
228
+                                                </constraints>
229
+                                                <userDefinedRuntimeAttributes>
230
+                                                    <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
231
+                                                        <real key="value" value="4"/>
232
+                                                    </userDefinedRuntimeAttribute>
233
+                                                </userDefinedRuntimeAttributes>
234
+                                            </view>
235
+                                        </subviews>
198 236
                                         <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
199 237
                                         <constraints>
200
-                                            <constraint firstAttribute="height" constant="64" id="xeq-dk-WF7"/>
201
-                                        </constraints>
202
-                                    </button>
203
-                                    <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="message-system" translatesAutoresizingMaskIntoConstraints="NO" id="PdF-av-quF">
204
-                                        <rect key="frame" x="12" y="8" width="48" height="48"/>
205
-                                        <constraints>
206
-                                            <constraint firstAttribute="width" secondItem="PdF-av-quF" secondAttribute="height" id="X2X-ko-fTx"/>
207
-                                            <constraint firstAttribute="width" constant="48" id="ozh-Fb-Dwl"/>
238
+                                            <constraint firstItem="7m9-4r-95l" firstAttribute="leading" secondItem="PdF-av-quF" secondAttribute="trailing" constant="10" id="3Zc-bV-A1v"/>
239
+                                            <constraint firstItem="7m9-4r-95l" firstAttribute="centerY" secondItem="nRF-R0-LcP" secondAttribute="centerY" id="4Uc-VE-e5u"/>
240
+                                            <constraint firstAttribute="bottom" secondItem="W1B-Rc-piS" secondAttribute="bottom" id="ATc-VY-uV1"/>
241
+                                            <constraint firstItem="dyn-zM-jdf" firstAttribute="centerY" secondItem="nRF-R0-LcP" secondAttribute="centerY" id="AgQ-Nz-FaV"/>
242
+                                            <constraint firstAttribute="trailing" secondItem="dyn-zM-jdf" secondAttribute="trailing" constant="10" id="BuW-nY-yJ8"/>
243
+                                            <constraint firstItem="PdF-av-quF" firstAttribute="leading" secondItem="nRF-R0-LcP" secondAttribute="leading" constant="12" id="CVF-7B-S39"/>
244
+                                            <constraint firstItem="dyn-zM-jdf" firstAttribute="leading" secondItem="veG-mJ-Dsh" secondAttribute="trailing" constant="8" id="GCo-tj-G7p"/>
245
+                                            <constraint firstItem="PdF-av-quF" firstAttribute="centerY" secondItem="nRF-R0-LcP" secondAttribute="centerY" id="KHj-j9-uOp"/>
246
+                                            <constraint firstAttribute="height" constant="64" id="NR8-Hm-Hek"/>
247
+                                            <constraint firstItem="W1B-Rc-piS" firstAttribute="leading" secondItem="nRF-R0-LcP" secondAttribute="leading" id="UxI-MO-gXc"/>
248
+                                            <constraint firstItem="W1B-Rc-piS" firstAttribute="top" secondItem="nRF-R0-LcP" secondAttribute="top" id="Ysp-Kx-3q6"/>
249
+                                            <constraint firstItem="veG-mJ-Dsh" firstAttribute="centerY" secondItem="nRF-R0-LcP" secondAttribute="centerY" id="pwl-I4-zYk"/>
250
+                                            <constraint firstAttribute="trailing" secondItem="W1B-Rc-piS" secondAttribute="trailing" id="z9e-Kz-bqH"/>
208 251
                                         </constraints>
209
-                                    </imageView>
210
-                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="服务提醒" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OGv-b6-6xR">
211
-                                        <rect key="frame" x="70" y="22" width="66" height="20"/>
212
-                                        <fontDescription key="fontDescription" type="system" pointSize="16"/>
213
-                                        <color key="textColor" red="0.20000000000000001" green="0.20000000000000001" blue="0.20000000000000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
214
-                                        <nil key="highlightedColor"/>
215
-                                    </label>
216
-                                    <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="list-arrow" translatesAutoresizingMaskIntoConstraints="NO" id="mre-Tt-F0S">
217
-                                        <rect key="frame" x="341" y="14" width="24" height="36"/>
218
-                                    </imageView>
219
-                                    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="T9q-a4-Ugc">
252
+                                    </view>
253
+                                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="FvO-F8-npa" userLabel="message thumbup">
220 254
                                         <rect key="frame" x="0.0" y="64" width="375" height="64"/>
255
+                                        <subviews>
256
+                                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="cYK-SK-9WL">
257
+                                                <rect key="frame" x="0.0" y="0.0" width="375" height="64"/>
258
+                                                <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
259
+                                                <constraints>
260
+                                                    <constraint firstAttribute="height" constant="64" id="CYE-TV-8Ku"/>
261
+                                                </constraints>
262
+                                            </button>
263
+                                            <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="message-thumpub" translatesAutoresizingMaskIntoConstraints="NO" id="Mdm-bD-HR2">
264
+                                                <rect key="frame" x="12" y="8" width="48" height="48"/>
265
+                                                <constraints>
266
+                                                    <constraint firstAttribute="width" constant="48" id="4Df-nG-nZr"/>
267
+                                                    <constraint firstAttribute="width" secondItem="Mdm-bD-HR2" secondAttribute="height" id="Doc-vC-DdB"/>
268
+                                                </constraints>
269
+                                            </imageView>
270
+                                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="赞" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vFD-Hf-dPV" userLabel="赞">
271
+                                                <rect key="frame" x="70" y="22.5" width="16.5" height="19.5"/>
272
+                                                <fontDescription key="fontDescription" type="system" pointSize="16"/>
273
+                                                <color key="textColor" red="0.20000000000000001" green="0.20000000000000001" blue="0.20000000000000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
274
+                                                <nil key="highlightedColor"/>
275
+                                            </label>
276
+                                            <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="list-arrow" translatesAutoresizingMaskIntoConstraints="NO" id="rWa-ZW-l2r">
277
+                                                <rect key="frame" x="341" y="14" width="24" height="36"/>
278
+                                            </imageView>
279
+                                            <view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="iqd-Ji-zvi">
280
+                                                <rect key="frame" x="325" y="28" width="8" height="8"/>
281
+                                                <color key="backgroundColor" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
282
+                                                <constraints>
283
+                                                    <constraint firstAttribute="height" constant="8" id="MLn-gz-qhL"/>
284
+                                                    <constraint firstAttribute="width" constant="8" id="vUg-zy-oPo"/>
285
+                                                </constraints>
286
+                                                <userDefinedRuntimeAttributes>
287
+                                                    <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
288
+                                                        <real key="value" value="4"/>
289
+                                                    </userDefinedRuntimeAttribute>
290
+                                                </userDefinedRuntimeAttributes>
291
+                                            </view>
292
+                                        </subviews>
221 293
                                         <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
222 294
                                         <constraints>
223
-                                            <constraint firstAttribute="height" constant="64" id="SZg-wA-Ma3"/>
224
-                                        </constraints>
225
-                                    </button>
226
-                                    <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="message-thumpub" translatesAutoresizingMaskIntoConstraints="NO" id="NEm-bF-yno">
227
-                                        <rect key="frame" x="12" y="72" width="48" height="48"/>
228
-                                        <constraints>
229
-                                            <constraint firstAttribute="width" constant="48" id="jD0-aO-eUu"/>
230
-                                            <constraint firstAttribute="width" secondItem="NEm-bF-yno" secondAttribute="height" id="snW-w9-7b3"/>
231
-                                        </constraints>
232
-                                    </imageView>
233
-                                    <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="list-arrow" translatesAutoresizingMaskIntoConstraints="NO" id="8VZ-dR-b6z">
234
-                                        <rect key="frame" x="341" y="78" width="24" height="36"/>
235
-                                    </imageView>
236
-                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="赞" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="J3p-B7-gMi">
237
-                                        <rect key="frame" x="70" y="86" width="17" height="20"/>
238
-                                        <fontDescription key="fontDescription" type="system" pointSize="16"/>
239
-                                        <color key="textColor" red="0.20000000000000001" green="0.20000000000000001" blue="0.20000000000000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
240
-                                        <nil key="highlightedColor"/>
241
-                                    </label>
242
-                                    <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="KVD-Az-tyJ">
243
-                                        <rect key="frame" x="0.0" y="128" width="375" height="64"/>
244
-                                        <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
245
-                                        <constraints>
246
-                                            <constraint firstAttribute="height" constant="64" id="zcX-3o-C4B"/>
247
-                                        </constraints>
248
-                                    </button>
249
-                                    <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="message-comment" translatesAutoresizingMaskIntoConstraints="NO" id="cgb-6k-KQJ">
250
-                                        <rect key="frame" x="12" y="136" width="48" height="48"/>
251
-                                        <constraints>
252
-                                            <constraint firstAttribute="width" secondItem="cgb-6k-KQJ" secondAttribute="height" id="FhM-W8-0eM"/>
253
-                                            <constraint firstAttribute="width" constant="48" id="Kmf-oj-nfI"/>
254
-                                        </constraints>
255
-                                    </imageView>
256
-                                    <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="list-arrow" translatesAutoresizingMaskIntoConstraints="NO" id="hJe-J2-RpF">
257
-                                        <rect key="frame" x="341" y="142" width="24" height="36"/>
258
-                                    </imageView>
259
-                                    <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="评论" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="6fy-0a-WYd">
260
-                                        <rect key="frame" x="70" y="150" width="33" height="20"/>
261
-                                        <fontDescription key="fontDescription" type="system" pointSize="16"/>
262
-                                        <color key="textColor" red="0.20000000000000001" green="0.20000000000000001" blue="0.20000000000000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
263
-                                        <nil key="highlightedColor"/>
264
-                                    </label>
265
-                                    <view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="oLf-Xb-QSv">
266
-                                        <rect key="frame" x="325" y="28" width="8" height="8"/>
267
-                                        <color key="backgroundColor" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
268
-                                        <constraints>
269
-                                            <constraint firstAttribute="height" constant="8" id="8PB-CF-AvI"/>
270
-                                            <constraint firstAttribute="width" constant="8" id="WJH-jN-MFb"/>
271
-                                        </constraints>
272
-                                        <userDefinedRuntimeAttributes>
273
-                                            <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
274
-                                                <real key="value" value="4"/>
275
-                                            </userDefinedRuntimeAttribute>
276
-                                        </userDefinedRuntimeAttributes>
277
-                                    </view>
278
-                                    <view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="R8U-gK-x5D">
279
-                                        <rect key="frame" x="325" y="92" width="8" height="8"/>
280
-                                        <color key="backgroundColor" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
281
-                                        <constraints>
282
-                                            <constraint firstAttribute="height" constant="8" id="pvk-DY-af3"/>
283
-                                            <constraint firstAttribute="width" constant="8" id="y72-go-9Lu"/>
284
-                                        </constraints>
285
-                                        <userDefinedRuntimeAttributes>
286
-                                            <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
287
-                                                <real key="value" value="4"/>
288
-                                            </userDefinedRuntimeAttribute>
289
-                                        </userDefinedRuntimeAttributes>
290
-                                    </view>
291
-                                    <view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Qi9-c7-GCt">
292
-                                        <rect key="frame" x="325" y="156" width="8" height="8"/>
293
-                                        <color key="backgroundColor" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
294
-                                        <constraints>
295
-                                            <constraint firstAttribute="height" constant="8" id="d1V-XC-UJk"/>
296
-                                            <constraint firstAttribute="width" constant="8" id="wcE-da-Ggi"/>
297
-                                        </constraints>
298
-                                        <userDefinedRuntimeAttributes>
299
-                                            <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
300
-                                                <real key="value" value="4"/>
301
-                                            </userDefinedRuntimeAttribute>
302
-                                        </userDefinedRuntimeAttributes>
303
-                                    </view>
304
-                                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="LMK-Y1-Qex">
305
-                                        <rect key="frame" x="70" y="63.5" width="305" height="0.5"/>
306
-                                        <color key="backgroundColor" red="0.90588235289999997" green="0.90588235289999997" blue="0.90588235289999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
307
-                                        <constraints>
308
-                                            <constraint firstAttribute="height" constant="0.5" id="ygP-zr-Y2l"/>
295
+                                            <constraint firstItem="rWa-ZW-l2r" firstAttribute="centerY" secondItem="FvO-F8-npa" secondAttribute="centerY" id="31J-Kc-V4M"/>
296
+                                            <constraint firstItem="cYK-SK-9WL" firstAttribute="top" secondItem="FvO-F8-npa" secondAttribute="top" id="43p-Ol-SCA"/>
297
+                                            <constraint firstItem="vFD-Hf-dPV" firstAttribute="centerY" secondItem="FvO-F8-npa" secondAttribute="centerY" id="FRm-10-cuU"/>
298
+                                            <constraint firstItem="iqd-Ji-zvi" firstAttribute="centerY" secondItem="FvO-F8-npa" secondAttribute="centerY" id="H7y-qI-qAP"/>
299
+                                            <constraint firstItem="Mdm-bD-HR2" firstAttribute="centerY" secondItem="FvO-F8-npa" secondAttribute="centerY" id="TtU-kN-5jV"/>
300
+                                            <constraint firstAttribute="height" constant="64" id="Uhv-Ry-4pQ"/>
301
+                                            <constraint firstItem="rWa-ZW-l2r" firstAttribute="leading" secondItem="iqd-Ji-zvi" secondAttribute="trailing" constant="8" id="bcO-40-lkC"/>
302
+                                            <constraint firstItem="vFD-Hf-dPV" firstAttribute="leading" secondItem="Mdm-bD-HR2" secondAttribute="trailing" constant="10" id="c1Q-w8-1iV"/>
303
+                                            <constraint firstAttribute="trailing" secondItem="rWa-ZW-l2r" secondAttribute="trailing" constant="10" id="f1H-lD-Bhp"/>
304
+                                            <constraint firstItem="cYK-SK-9WL" firstAttribute="leading" secondItem="FvO-F8-npa" secondAttribute="leading" id="hT5-QW-acn"/>
305
+                                            <constraint firstAttribute="bottom" secondItem="cYK-SK-9WL" secondAttribute="bottom" id="il0-Dm-SVq"/>
306
+                                            <constraint firstAttribute="trailing" secondItem="cYK-SK-9WL" secondAttribute="trailing" id="j9y-uz-ARx"/>
307
+                                            <constraint firstItem="Mdm-bD-HR2" firstAttribute="leading" secondItem="FvO-F8-npa" secondAttribute="leading" constant="12" id="vD8-AI-AAE"/>
309 308
                                         </constraints>
310 309
                                     </view>
311
-                                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="gIo-ms-963">
312
-                                        <rect key="frame" x="70" y="127.5" width="305" height="0.5"/>
313
-                                        <color key="backgroundColor" red="0.90588235289999997" green="0.90588235289999997" blue="0.90588235289999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
310
+                                    <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Org-mm-8ZH" userLabel="message comment">
311
+                                        <rect key="frame" x="0.0" y="128" width="375" height="64"/>
312
+                                        <subviews>
313
+                                            <button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="pAl-ew-Vda">
314
+                                                <rect key="frame" x="0.0" y="0.0" width="375" height="64"/>
315
+                                                <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
316
+                                                <constraints>
317
+                                                    <constraint firstAttribute="height" constant="64" id="Dnh-NK-K7H"/>
318
+                                                </constraints>
319
+                                            </button>
320
+                                            <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="message-comment" translatesAutoresizingMaskIntoConstraints="NO" id="0Ga-dC-0cj">
321
+                                                <rect key="frame" x="12" y="8" width="48" height="48"/>
322
+                                                <constraints>
323
+                                                    <constraint firstAttribute="width" constant="48" id="Hcr-cI-PAb"/>
324
+                                                    <constraint firstAttribute="width" secondItem="0Ga-dC-0cj" secondAttribute="height" id="sQe-Iz-UsK"/>
325
+                                                </constraints>
326
+                                            </imageView>
327
+                                            <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="评论" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="OqS-kg-xEo" userLabel="评论">
328
+                                                <rect key="frame" x="70" y="22.5" width="33" height="19.5"/>
329
+                                                <fontDescription key="fontDescription" type="system" pointSize="16"/>
330
+                                                <color key="textColor" red="0.20000000000000001" green="0.20000000000000001" blue="0.20000000000000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
331
+                                                <nil key="highlightedColor"/>
332
+                                            </label>
333
+                                            <imageView userInteractionEnabled="NO" contentMode="scaleToFill" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="list-arrow" translatesAutoresizingMaskIntoConstraints="NO" id="de8-5g-YER">
334
+                                                <rect key="frame" x="341" y="14" width="24" height="36"/>
335
+                                            </imageView>
336
+                                            <view hidden="YES" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="Ywg-9R-Fz9">
337
+                                                <rect key="frame" x="325" y="28" width="8" height="8"/>
338
+                                                <color key="backgroundColor" red="1" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
339
+                                                <constraints>
340
+                                                    <constraint firstAttribute="width" constant="8" id="ZOh-7N-i3b"/>
341
+                                                    <constraint firstAttribute="height" constant="8" id="fXX-l3-MG3"/>
342
+                                                </constraints>
343
+                                                <userDefinedRuntimeAttributes>
344
+                                                    <userDefinedRuntimeAttribute type="number" keyPath="cornerRadius">
345
+                                                        <real key="value" value="4"/>
346
+                                                    </userDefinedRuntimeAttribute>
347
+                                                </userDefinedRuntimeAttributes>
348
+                                            </view>
349
+                                        </subviews>
350
+                                        <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
314 351
                                         <constraints>
315
-                                            <constraint firstAttribute="height" constant="0.5" id="VfL-Rd-QTP"/>
352
+                                            <constraint firstItem="OqS-kg-xEo" firstAttribute="centerY" secondItem="Org-mm-8ZH" secondAttribute="centerY" id="14q-Ka-Vk5"/>
353
+                                            <constraint firstItem="pAl-ew-Vda" firstAttribute="top" secondItem="Org-mm-8ZH" secondAttribute="top" id="2Su-bl-RDw"/>
354
+                                            <constraint firstItem="Ywg-9R-Fz9" firstAttribute="centerY" secondItem="Org-mm-8ZH" secondAttribute="centerY" id="9LY-bT-Hr2"/>
355
+                                            <constraint firstAttribute="trailing" secondItem="de8-5g-YER" secondAttribute="trailing" constant="10" id="Aap-8M-fAT"/>
356
+                                            <constraint firstAttribute="trailing" secondItem="pAl-ew-Vda" secondAttribute="trailing" id="ILI-UR-Vba"/>
357
+                                            <constraint firstItem="OqS-kg-xEo" firstAttribute="leading" secondItem="0Ga-dC-0cj" secondAttribute="trailing" constant="10" id="JTR-w3-YbN"/>
358
+                                            <constraint firstAttribute="bottom" secondItem="pAl-ew-Vda" secondAttribute="bottom" id="Klx-LC-YwX"/>
359
+                                            <constraint firstItem="0Ga-dC-0cj" firstAttribute="centerY" secondItem="Org-mm-8ZH" secondAttribute="centerY" id="NZO-sW-7w8"/>
360
+                                            <constraint firstItem="de8-5g-YER" firstAttribute="centerY" secondItem="Org-mm-8ZH" secondAttribute="centerY" id="fWR-wS-1FO"/>
361
+                                            <constraint firstItem="pAl-ew-Vda" firstAttribute="leading" secondItem="Org-mm-8ZH" secondAttribute="leading" id="hUt-K2-O5X"/>
362
+                                            <constraint firstItem="de8-5g-YER" firstAttribute="leading" secondItem="Ywg-9R-Fz9" secondAttribute="trailing" constant="8" id="lwS-rD-Wv9"/>
363
+                                            <constraint firstItem="0Ga-dC-0cj" firstAttribute="leading" secondItem="Org-mm-8ZH" secondAttribute="leading" constant="12" id="p4H-7I-g5Q"/>
364
+                                            <constraint firstAttribute="height" constant="64" id="qf9-lo-sVB"/>
316 365
                                         </constraints>
317 366
                                     </view>
318 367
                                 </subviews>
319
-                                <color key="backgroundColor" red="0.94117647059999998" green="0.94117647059999998" blue="0.94117647059999998" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
320
-                                <constraints>
321
-                                    <constraint firstItem="PdF-av-quF" firstAttribute="width" secondItem="PdF-av-quF" secondAttribute="height" id="1SJ-Yy-8rF"/>
322
-                                    <constraint firstItem="KVD-Az-tyJ" firstAttribute="leading" secondItem="StI-0Q-fat" secondAttribute="leading" id="1Sn-Ha-d7i"/>
323
-                                    <constraint firstAttribute="trailing" secondItem="KVD-Az-tyJ" secondAttribute="trailing" id="1UZ-ZG-q7u"/>
324
-                                    <constraint firstItem="NEm-bF-yno" firstAttribute="leading" secondItem="T9q-a4-Ugc" secondAttribute="leading" constant="12" id="3CF-yv-4wf"/>
325
-                                    <constraint firstItem="R8U-gK-x5D" firstAttribute="centerY" secondItem="8VZ-dR-b6z" secondAttribute="centerY" id="3Yc-1d-XRV"/>
326
-                                    <constraint firstItem="LMK-Y1-Qex" firstAttribute="leading" secondItem="OGv-b6-6xR" secondAttribute="leading" id="5AB-ql-wr1"/>
327
-                                    <constraint firstItem="NEm-bF-yno" firstAttribute="width" secondItem="NEm-bF-yno" secondAttribute="height" id="5NQ-kl-Eq5"/>
328
-                                    <constraint firstItem="T9q-a4-Ugc" firstAttribute="leading" secondItem="StI-0Q-fat" secondAttribute="leading" id="638-dc-DJj"/>
329
-                                    <constraint firstItem="mre-Tt-F0S" firstAttribute="centerY" secondItem="WTN-Zx-JKR" secondAttribute="centerY" id="7Gh-JK-xlV"/>
330
-                                    <constraint firstItem="hJe-J2-RpF" firstAttribute="leading" secondItem="Qi9-c7-GCt" secondAttribute="trailing" constant="8" id="8cD-ux-8w4"/>
331
-                                    <constraint firstItem="6fy-0a-WYd" firstAttribute="centerY" secondItem="cgb-6k-KQJ" secondAttribute="centerY" id="EAW-Sd-au8"/>
332
-                                    <constraint firstItem="WTN-Zx-JKR" firstAttribute="leading" secondItem="StI-0Q-fat" secondAttribute="leading" id="Ej8-vW-ES1"/>
333
-                                    <constraint firstItem="oLf-Xb-QSv" firstAttribute="centerY" secondItem="mre-Tt-F0S" secondAttribute="centerY" id="Fty-Is-89H"/>
334
-                                    <constraint firstItem="hJe-J2-RpF" firstAttribute="trailing" secondItem="KVD-Az-tyJ" secondAttribute="trailing" constant="-10" id="HLp-UP-zd4"/>
335
-                                    <constraint firstItem="WTN-Zx-JKR" firstAttribute="top" secondItem="StI-0Q-fat" secondAttribute="top" id="Hdq-cw-396"/>
336
-                                    <constraint firstAttribute="trailing" secondItem="gIo-ms-963" secondAttribute="trailing" id="Hfb-BT-Y32"/>
337
-                                    <constraint firstItem="6fy-0a-WYd" firstAttribute="leading" secondItem="cgb-6k-KQJ" secondAttribute="trailing" constant="10" id="IoR-Al-XZl"/>
338
-                                    <constraint firstItem="gIo-ms-963" firstAttribute="bottom" secondItem="T9q-a4-Ugc" secondAttribute="bottom" id="Joq-MV-NEZ"/>
339
-                                    <constraint firstItem="OGv-b6-6xR" firstAttribute="leading" secondItem="PdF-av-quF" secondAttribute="trailing" constant="10" id="K4D-BZ-8ZU"/>
340
-                                    <constraint firstItem="Qi9-c7-GCt" firstAttribute="centerY" secondItem="hJe-J2-RpF" secondAttribute="centerY" id="LUZ-KU-t61"/>
341
-                                    <constraint firstAttribute="trailing" secondItem="LMK-Y1-Qex" secondAttribute="trailing" id="LYk-HD-SVv"/>
342
-                                    <constraint firstItem="OGv-b6-6xR" firstAttribute="centerY" secondItem="PdF-av-quF" secondAttribute="centerY" id="Obx-DC-2Ms"/>
343
-                                    <constraint firstItem="hJe-J2-RpF" firstAttribute="centerY" secondItem="KVD-Az-tyJ" secondAttribute="centerY" id="PGD-Nd-FxL"/>
344
-                                    <constraint firstItem="mre-Tt-F0S" firstAttribute="leading" secondItem="oLf-Xb-QSv" secondAttribute="trailing" constant="8" id="Pgo-iF-JpC"/>
345
-                                    <constraint firstItem="T9q-a4-Ugc" firstAttribute="top" secondItem="WTN-Zx-JKR" secondAttribute="bottom" id="PiP-8o-hlq"/>
346
-                                    <constraint firstItem="J3p-B7-gMi" firstAttribute="centerY" secondItem="NEm-bF-yno" secondAttribute="centerY" id="U2Q-xG-JKb"/>
347
-                                    <constraint firstAttribute="trailing" secondItem="mre-Tt-F0S" secondAttribute="trailing" constant="10" id="Udt-Ur-CYo"/>
348
-                                    <constraint firstItem="cgb-6k-KQJ" firstAttribute="width" secondItem="cgb-6k-KQJ" secondAttribute="height" id="VBI-e4-8KY"/>
349
-                                    <constraint firstItem="NEm-bF-yno" firstAttribute="centerY" secondItem="T9q-a4-Ugc" secondAttribute="centerY" id="VqD-xD-ZFL"/>
350
-                                    <constraint firstItem="NEm-bF-yno" firstAttribute="width" secondItem="NEm-bF-yno" secondAttribute="height" id="WcT-QB-dXH"/>
351
-                                    <constraint firstItem="8VZ-dR-b6z" firstAttribute="leading" secondItem="R8U-gK-x5D" secondAttribute="trailing" constant="8" id="Zm2-DB-9qE"/>
352
-                                    <constraint firstItem="LMK-Y1-Qex" firstAttribute="bottom" secondItem="WTN-Zx-JKR" secondAttribute="bottom" id="hKb-bb-8Re"/>
353
-                                    <constraint firstItem="8VZ-dR-b6z" firstAttribute="trailing" secondItem="T9q-a4-Ugc" secondAttribute="trailing" constant="-10" id="hTL-Jt-Om6"/>
354
-                                    <constraint firstItem="J3p-B7-gMi" firstAttribute="leading" secondItem="NEm-bF-yno" secondAttribute="trailing" constant="10" id="he6-3z-Xta"/>
355
-                                    <constraint firstItem="cgb-6k-KQJ" firstAttribute="leading" secondItem="KVD-Az-tyJ" secondAttribute="leading" constant="12" id="hek-US-nb3"/>
356
-                                    <constraint firstAttribute="trailing" secondItem="WTN-Zx-JKR" secondAttribute="trailing" id="hyd-J3-IAR"/>
357
-                                    <constraint firstItem="cgb-6k-KQJ" firstAttribute="width" secondItem="cgb-6k-KQJ" secondAttribute="height" id="lpl-IR-20q"/>
358
-                                    <constraint firstItem="cgb-6k-KQJ" firstAttribute="width" secondItem="cgb-6k-KQJ" secondAttribute="height" id="oTf-HF-3EN"/>
359
-                                    <constraint firstAttribute="trailing" secondItem="T9q-a4-Ugc" secondAttribute="trailing" id="qPZ-lF-bJJ"/>
360
-                                    <constraint firstItem="gIo-ms-963" firstAttribute="leading" secondItem="J3p-B7-gMi" secondAttribute="leading" id="s1V-MK-hGp"/>
361
-                                    <constraint firstItem="8VZ-dR-b6z" firstAttribute="centerY" secondItem="T9q-a4-Ugc" secondAttribute="centerY" id="vww-WS-1e6"/>
362
-                                    <constraint firstItem="PdF-av-quF" firstAttribute="leading" secondItem="StI-0Q-fat" secondAttribute="leading" constant="12" id="wD2-zK-Qfe"/>
363
-                                    <constraint firstItem="cgb-6k-KQJ" firstAttribute="centerY" secondItem="KVD-Az-tyJ" secondAttribute="centerY" id="wow-yY-0cl"/>
364
-                                    <constraint firstItem="KVD-Az-tyJ" firstAttribute="top" secondItem="T9q-a4-Ugc" secondAttribute="bottom" id="yi0-UM-hgK"/>
365
-                                    <constraint firstItem="PdF-av-quF" firstAttribute="centerY" secondItem="WTN-Zx-JKR" secondAttribute="centerY" id="zfd-Gi-l2q"/>
366
-                                </constraints>
367
-                            </view>
368
+                            </stackView>
368 369
                         </subviews>
369
-                        <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
370
+                        <color key="backgroundColor" red="0.94117647058823528" green="0.94117647058823528" blue="0.94117647058823528" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
370 371
                         <constraints>
371
-                            <constraint firstItem="pk7-RT-o9s" firstAttribute="trailing" secondItem="StI-0Q-fat" secondAttribute="trailing" id="1mB-xr-CQ7"/>
372
-                            <constraint firstAttribute="bottom" secondItem="StI-0Q-fat" secondAttribute="bottom" id="DhK-nf-Lrh"/>
373
-                            <constraint firstItem="StI-0Q-fat" firstAttribute="leading" secondItem="pk7-RT-o9s" secondAttribute="leading" id="jFj-Ls-vJb"/>
374
-                            <constraint firstItem="StI-0Q-fat" firstAttribute="top" secondItem="pk7-RT-o9s" secondAttribute="top" id="xbs-D5-7iw"/>
372
+                            <constraint firstItem="vQs-oc-uao" firstAttribute="leading" secondItem="pk7-RT-o9s" secondAttribute="leading" id="WMt-Hu-L7D"/>
373
+                            <constraint firstItem="pk7-RT-o9s" firstAttribute="trailing" secondItem="vQs-oc-uao" secondAttribute="trailing" id="qWK-p3-7fO"/>
374
+                            <constraint firstItem="vQs-oc-uao" firstAttribute="top" secondItem="pk7-RT-o9s" secondAttribute="top" id="u3B-bZ-94D"/>
375 375
                         </constraints>
376 376
                         <viewLayoutGuide key="safeArea" id="pk7-RT-o9s"/>
377 377
                     </view>
378 378
                     <connections>
379
-                        <outlet property="commentBtn" destination="KVD-Az-tyJ" id="TaX-hY-vn2"/>
380
-                        <outlet property="commentUnreadTip" destination="Qi9-c7-GCt" id="xt8-kU-Ort"/>
381
-                        <outlet property="sysBtn" destination="WTN-Zx-JKR" id="j7o-gs-BXE"/>
382
-                        <outlet property="sysUnreadTip" destination="oLf-Xb-QSv" id="K9s-Ru-LqL"/>
383
-                        <outlet property="thumbupBtn" destination="T9q-a4-Ugc" id="f6h-aq-4F0"/>
384
-                        <outlet property="thumbupUnreadTip" destination="R8U-gK-x5D" id="Lxs-zK-zAs"/>
379
+                        <outlet property="commentBtn" destination="pAl-ew-Vda" id="3S2-SN-tES"/>
380
+                        <outlet property="commentUnreadTip" destination="Ywg-9R-Fz9" id="kdE-V6-bFR"/>
381
+                        <outlet property="sysBtn" destination="W1B-Rc-piS" id="7RM-xD-0Ix"/>
382
+                        <outlet property="sysUnreadTip" destination="veG-mJ-Dsh" id="lsK-b7-GGU"/>
383
+                        <outlet property="thumbupBtn" destination="cYK-SK-9WL" id="SpB-pC-x36"/>
384
+                        <outlet property="thumbupUnreadTip" destination="iqd-Ji-zvi" id="IDb-6y-Pp4"/>
385 385
                     </connections>
386 386
                 </viewController>
387 387
                 <placeholder placeholderIdentifier="IBFirstResponder" id="13z-ah-xbs" userLabel="First Responder" sceneMemberID="firstResponder"/>
388 388
             </objects>
389
-            <point key="canvasLocation" x="53.600000000000001" y="28.335832083958024"/>
389
+            <point key="canvasLocation" x="-52" y="-133"/>
390 390
         </scene>
391 391
     </scenes>
392 392
     <resources>

+ 1 - 1
PaiAi/Paiai_iOS/App/Mine/GroupCell.swift

@@ -22,7 +22,7 @@ final class GroupCell: UITableViewCell {
22 22
     func setInfo(_ info: GroupItem) {
23 23
         groupImageView.setImage(info.group_avatar, placeholder: UIImage(named: "Group\(info.group_default_avatar)"))
24 24
         groupNameLabel.text = info.group_name
25
-//        createTimeLabel.text =  info.created_at
25
+        createTimeLabel.text =  info.create_at
26 26
         photoNumLabel.text = "有\(info.group_photo_num)张照片"
27 27
     }
28 28
 }

+ 1 - 1
PaiAi/Paiai_iOS/App/Mine/MineFeedbackViewController.swift

@@ -32,7 +32,7 @@ final class MineFeedbackViewController: UIViewController {
32 32
 /// storyboard button action
33 33
 extension MineFeedbackViewController {
34 34
     @IBAction func sendFeedBack() {
35
-        Toast.showActivity(message: "正在提交")
35
+        Toast.showActivity(message: "正在提交中")
36 36
         feedbackAPI.submit(text: textView.text).subscribe(onCompleted: {[weak self] in
37 37
             guard let `self` = self else { return }
38 38
             Toast.hide()

+ 5 - 5
PaiAi/Paiai_iOS/App/PhotoDetail/PhotoDetailCommentCell.swift

@@ -28,10 +28,10 @@ class PhotoDetailCommentCell: UITableViewCell {
28 28
     }
29 29
 
30 30
     // MARK: init interface
31
-    func setInfo(_ data: PhotoCommentItem) {
32
-//        headImage.setImageWithNullableURL(data.avatar, placeholderImage: defaultAvatar)
33
-        name.text = data.nickname
34
-        content.text = data.comment
35
-//        time.text = data.create_at.getTimeInfoFromDate()
31
+    func setInfo(_ info: PhotoCommentItem) {
32
+        headImage.setImage(info.avatar, placeholder: UIImage.defaultAvatar)
33
+        name.text = info.nickname
34
+        content.text = info.comment
35
+        time.text = info.create_at
36 36
     }
37 37
 }

+ 1 - 1
PaiAi/Paiai_iOS/App/PhotoDetail/PhotoDetailCoordinator.swift

@@ -52,7 +52,7 @@ extension PhotoDetailCoordinator: PhotoDetailListViewModelDelegate {
52 52
     func didSelected() {
53 53
         let vc = UIStoryboard.photoDetail.instantiateController(PhotoPreviewViewController.self)
54 54
         vc.viewModel = shareListViewModel
55
-        navigationController.pushViewController(vc, animated: true)
55
+        photoDetailViewController.presentController(vc)
56 56
     }
57 57
 }
58 58
 

+ 13 - 13
PaiAi/Paiai_iOS/App/PhotoDetail/PhotoPreviewViewController.swift

@@ -47,21 +47,11 @@ final class PhotoPreviewViewController: UIViewController {
47 47
         navigationController?.setNavigationBarHidden(false, animated: true)
48 48
     }
49 49
     
50
-    @IBAction func download(_ sender: UIButton) {
51
-        guard let cell = collectionView.cellForItem(at: IndexPath(item: viewModel.currIndex, section: 0)) as? ImageCell,
52
-            let image = cell.photoImage.image else {
53
-                //            FFToastView.showToast(inView: view, withText: "未检测到图片")
54
-                return
55
-        }
56
-        
57
-        UIImageWriteToSavedPhotosAlbum(image, self, #selector(image(_:didFinishSavingWithError:contextInfo:)), nil)
58
-    }
59
-    
60 50
     @objc func image(_ image: UIImage?, didFinishSavingWithError error: NSError?, contextInfo info: UnsafeMutableRawPointer) {
61 51
         if error != nil {
62
-            //            FFToastView.showToast(inView: view, withText: "保存图片失败")
52
+            Toast.show(message: "保存照片失败")
63 53
         } else {
64
-            //            FFToastView.showImageToast(inView: view, withText: "已保存图片到相册", withImage: "提示弹窗-勾")
54
+            Toast.show(message: "已保存照片到相册中", image: UIImage(named: "icon-success"))
65 55
         }
66 56
     }
67 57
 }
@@ -105,6 +95,7 @@ extension PhotoPreviewViewController {
105 95
     @IBAction  func back() {
106 96
         navigationController?.popViewController(animated: true)
107 97
     }
98
+    
108 99
     @IBAction func rotateTheImage(_ sender: UIButton) {
109 100
         guard let cell = collectionView.cellForItem(at: IndexPath(item: viewModel.currIndex, section: 0)) as? ImageCell else {
110 101
             return
@@ -127,7 +118,16 @@ extension PhotoPreviewViewController {
127 118
         }
128 119
     }
129 120
     
130
-
121
+    @IBAction func download(_ sender: UIButton) {
122
+        Toast.showActivity(message: "正在保存照片到相册")
123
+        guard let cell = collectionView.cellForItem(at: IndexPath(item: viewModel.currIndex, section: 0)) as? ImageCell,
124
+            let image = cell.photoImage.image else {
125
+                Toast.show(message: "未检测到照片")
126
+                return
127
+        }
128
+        
129
+        UIImageWriteToSavedPhotosAlbum(image, self, #selector(image(_:didFinishSavingWithError:contextInfo:)), nil)
130
+    }
131 131
 }
132 132
 
133 133
 // MARK: UICollectionView delegate