使用 php 构建您的初创公司系列的一部分。在本系列中,我将使用我的Meeting Planner 应用程序作为现实生活中的示例,指导您从概念到现实启动一家初创公司 。在此过程中的每一步,我都会将 Meeting Planner 代码作为开源示例发布,您可以从中学习。我还将解决与初创公司相关的业务问题。
如何进行主要功能更新
这些天来,我经常致力于为Meeting Planner添加小的增量改进。基础工作很好,我正在尝试根据我的愿景和人们的反馈逐步改进应用程序。有时,我的愿景是做出更大的改变,而现在代码库已经增长了这么多,这可能会更难。
在今天的教程中,我将讨论考虑对现有代码库进行更大更改的方法。具体来说,我将向您介绍增加会议参与者协作头脑风暴和决定活动的能力的影响,即我们在开会时应该做什么。
如果您还没有,请尝试 安排会议,现在您还可以安排活动。它将帮助您在学习本教程时理解。
在我们开始之前,请记得在下面分享您的意见和反馈。我监控他们,你也可以在 Twitter @lookahead_io 上联系我。如果您想为未来的教程推荐新功能或主题,我特别感兴趣。
提醒一下,Meeting Planner 的所有代码都是用 PHP 的 Yii2 F ram ework 编写的。如果您想了解更多关于 Yii2 的信息,请查看我们的并行系列 Programming With Yii2。
活动计划功能
基本上,Meeting Planner 和 Simple Planner 旨在让日程安排尽可能简单。您提出了几个时间和地点,并与另外一个人分享,让他们权衡选择。然后,您共同决定,会议策划者会跟踪日历条目、提醒和事后进行调整的简单方法。
例如,这是一个安排小组的视频:
我想将针对时间和地点提供的调度支持扩展到活动的概念。例如,在计划与您的朋友见面时,您实际上是在问,我们应该去看电影、跳舞还是滑雪板。
换句话说,我想为如下所示的时间创建一个面板,但用于活动:
MVC 架构和我的代码命名方案在会议时间和地点之间非常相似,因此构建活动表面上看起来很简单。然而,这样做的决定产生了广泛的影响。
确定更改范围
添加一个大特性时,重要的是要考虑代码需要更改的位置以及应用程序中可能受到影响的所有位置,以考虑影响。
面向客户的影响
从设计方面,我考虑了活动将如何影响面向客户的服务:
它将改变组织会议以允许一种新的类型,即活动驱动的事件。计划页面上将有一个附加面板。
活动面板需要设计成允许人们从默认设置中进行选择或自定义和添加自己的设置,例如野外滑雪而不只是滑雪,“去看星球大战侠盗一号”而不是“去看电影”。
电子邮件邀请需要包含列出活动选项的空间。
日历活动将希望将所选活动与聚会主题相结合。
组织者可能想在没有选择地点的情况下将一些活动创意发送给朋友或团体,所以我需要允许这样做。目前,系统不允许您发送邀请,直到至少有一个建议的时间和地点。
如果有人要求对会议进行更改,则必须将对更改请求的支持扩展到支持活动。
这些是大部分基础知识。现在,让我们考虑一下代码。
代码影响
源代码分支
通常,在 GitHub 中分支您自己的代码会很有帮助,这样您就可以在稳定的生产级代码库之外使用新功能。这允许您在进行重大更改时返回和修复错误或进行较小的增量更改。GitHub 的人在某种程度上对团队来说更严格:
只有一条规则: master 分支中的任何东西都是可部署的。
因为只有我一个人,而且我很擅长管理我的代码库,所以我对这条规则更加自由放任。
但是,当您准备好进行测试时,分支代码对于查看代码更改也很有用。我将在今天的教程结束时分享一个演示。
复制公共代码
现在将有两种类型的会议:仅基于日期和时间的会议以及基于活动、日期和时间的会议。所以会议模式需要适应。
对于时间和地点,有专门的模型 MeetingTime 和 MeetingPlace 以及参与者的偏好模型,称为 MeetingTimeChoices 和 MeetingPlaceChoices。您可以在使用 PHP 构建您的初创公司:调度可用性和选择中阅读有关构建它的更多信息 。
因此,添加活动基本上需要复制这些活动,创建 MeetingActivity 和 MeetingActivityChoices 及其伴随的控制器、模型、视图、javascript和ajax以及数据库迁移。
帐户和会议设置
此外,对于参与者是否可以建议和选择最终活动,组织者具有各种帐户设置和每个会议设置的偏好。
电子邮件模板
添加活动也影响了邀请和已完成会议的电子邮件模板。
事件历史和日志
由于记录了对会议的每次更改,因此还需要记录每个活动选项和更改。
其他杂项领域
应更改 .ics 日历文件以包含活动。最终,api需要更新,甚至管理仪表板的统计信息也需要更新。
虽然看起来很简单,但添加活动实际上需要大量新代码和测试。
编码亮点
虽然在一个教程中有太多的新代码需要介绍,但让我们从上面的一些概念中回顾一下突出的方面。
数据库迁移
首先,我创建了数据库迁移。之前我谈到了复制具有共同特征方面的代码。这是 MeetingActivity 迁移与旧的 MeetingTime 表迁移的示例:
<?php use yii\db\Schema; use yii\db\Migration; class m161202_020757_create_meeting_activity_table extends Migration { public function up() { $tableOptions = null; if ($this->db->driverName === 'mysql') { $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB'; } $this->createTable('{{%meeting_activity}}', [ 'id' => Schema::TYPE_PK, 'meeting_id' => Schema::TYPE_INTEGER.' NOT NULL', 'activity' => Schema::TYPE_STRING.' NOT NULL', 'suggested_by' => Schema::TYPE_BIGINT.' NOT NULL', 'status' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0', 'availability' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0', 'created_at' => Schema::TYPE_INTEGER . ' NOT NULL', 'updated_at' => Schema::TYPE_INTEGER . ' NOT NULL', ], $tableOptions); $this->addForeignKey('fk_meeting_activity_meeting', '{{%meeting_activity}}', 'meeting_id', '{{%meeting}}', 'id', 'CASCADE', 'CASCADE'); $this->addForeignKey('fk_activity_suggested_by', '{{%meeting_activity}}', 'suggested_by', '{{%user}}', 'id', 'CASCADE', 'CASCADE'); } public function down() { $this->dropForeignKey('fk_activity_suggested_by', '{{%meeting_activity}}'); $this->dropForeignKey('fk_meeting_activity_meeting', '{{%meeting_activity}}'); $this->dropTable('{{%meeting_activity}}'); } }
这是 MeetingTime 的迁移,您可以看到相似之处:
<?php use yii\db\Schema; use yii\db\Migration; class m141025_215833_create_meeting_time_table extends Migration { public function up() { $tableOptions = null; if ($this->db->driverName === 'mysql') { $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB'; } $this->createTable('{{%meeting_time}}', [ 'id' => Schema::TYPE_PK, 'meeting_id' => Schema::TYPE_INTEGER.' NOT NULL', 'start' => Schema::TYPE_INTEGER.' NOT NULL', 'suggested_by' => Schema::TYPE_BIGINT.' NOT NULL', 'status' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0', 'created_at' => Schema::TYPE_INTEGER . ' NOT NULL', 'updated_at' => Schema::TYPE_INTEGER . ' NOT NULL', ], $tableOptions); $this->addForeignKey('fk_meeting_time_meeting', '{{%meeting_time}}', 'meeting_id', '{{%meeting}}', 'id', 'CASCADE', 'CASCADE'); $this->addForeignKey('fk_participant_suggested_by', '{{%meeting_time}}', 'suggested_by', '{{%user}}', 'id', 'CASCADE', 'CASCADE'); } public function down() { $this->dropForeignKey('fk_participant_suggested_by', '{{%meeting_time}}'); $this->dropForeignKey('fk_meeting_time_meeting', '{{%meeting_time}}'); $this->dropTable('{{%meeting_time}}'); } }
最终,我需要五个用于以下新表:
m161202_020757_create_meeting_activity_table
m161202_021355_create_meeting_activity_choice_table,用于存储每个会议参与者对每个活动的可用性首选项
m161202_024352_extend_meeting_setting_table_for_activities 用于添加或选择活动的特定会议设置
m161202_024403_extend_user_setting_table_for_activities 用于帐户的默认设置
m161203_010030_extend_meeting_table_for_activities 并用于添加 is_activity 以表示有或没有活动的会议的属性
$ ./yii migrate/up Yii Migration Tool (based on Yii v2.0.10) Total 5 new migrations to be applied: m161202_020757_create_meeting_activity_table m161202_021355_create_meeting_activity_choice_table m161202_024352_extend_meeting_setting_table_for_activities m161202_024403_extend_user_setting_table_for_activities m161203_010030_extend_meeting_table_for_activities Apply the above migrations? (yes|no) [no]:yes *** applying m161202_020757_create_meeting_activity_table > create table {{%meeting_activity}} ... done (time: 0.032s) > add foreign key fk_meeting_activity_meeting: {{%meeting_activity}} (meeting_id) references {{%meeting}} (id) ... done (time: 0.023s) > add foreign key fk_activity_suggested_by: {{%meeting_activity}} (suggested_by) references {{%user}} (id) ... done (time: 0.006s) *** applied m161202_020757_create_meeting_activity_table (time: 0.071s) *** applying m161202_021355_create_meeting_activity_choice_table > create table {{%meeting_activity_choice}} ... done (time: 0.002s) > add foreign key fk_mac_meeting_activity: {{%meeting_activity_choice}} (meeting_activity_id) references {{%meeting_activity}} (id) ... done (time: 0.006s) > add foreign key fk_Mac_user_id: {{%meeting_activity_choice}} (user_id) references {{%user}} (id) ... done (time: 0.004s) *** applied m161202_021355_create_meeting_activity_choice_table (time: 0.016s) *** applying m161202_024352_extend_meeting_setting_table_for_activities > add column participant_add_activity smallint NOT NULL DEFAULT 0 to table {{%meeting_setting}} ... done (time: 0.013s) > add column participant_choose_activity smallint NOT NULL DEFAULT 0 to table {{%meeting_setting}} ... done (time: 0.019s) *** applied m161202_024352_extend_meeting_setting_table_for_activities (time: 0.034s) *** applying m161202_024403_extend_user_setting_table_for_activities > add column participant_add_activity smallint NOT NULL to table {{%user_setting}} ... done (time: 0.024s) > add column participant_choose_activity smallint NOT NULL to table {{%user_setting}} ... done (time: 0.027s) *** applied m161202_024403_extend_user_setting_table_for_activities (time: 0.055s) *** applying m161203_010030_extend_meeting_table_for_activities > add column is_activity smallint NOT NULL to table {{%meeting}} ... done (time: 0.019s) *** applied m161203_010030_extend_meeting_table_for_activities (time: 0.022s) 5 migrations were applied. Migrated up successfully.
为活动构建 MVC 框架
我使用 Yii 的 Gii 脚手架功能来创建模型、控制器和初始视图。我在本系列的前面介绍了迁移和 Gii。
JavaScript 和jquery变化
还对所使用的 javaScript 和 jQuery 进行了大量添加,特别是现在与会议计划元素的交互是使用 Ajax 完成的,而无需刷新页面。
例如,这里是查看是否选择了会议时间的代码循环:
// respond to change in meeting_time $(document).on("click", '[id^=btn_mt_]', function(event) { current_id = $(this).attr('id'); $(this).addClass("btn-primary"); $(this).removeClass("btn-default"); $('[id^=btn_mt_]').each(function(index) { if ($(this).attr('id')!=current_id) { $(this).addClass("btn-default"); $(this).removeClass("btn-primary"); } }); $.ajax({ url: $('#url_prefix').val()+'/meeting-time/choose', data: {id: $('#meeting_id').val(), 'val': current_id}, success: function(data) { displayNotifier('choosetime'); refreshSend(); refreshFinalize(); return true; } }); });
通过使用通用的命名方案,为活动编写代码可以复制如下:
// respond to change in meeting_activity $(document).on("click", '[id^=btn_ma_]', function(event) { current_id = $(this).attr('id'); $(this).addClass("btn-primary"); $(this).removeClass("btn-default"); $('[id^=btn_ma_]').each(function(index) { if ($(this).attr('id')!=current_id) { $(this).addClass("btn-default"); $(this).removeClass("btn-primary"); } }); $.ajax({ url: $('#url_prefix').val()+'/meeting-activity/choose', data: {id: $('#meeting_id').val(), 'val': current_id}, success: function(data) { displayNotifier('chooseactivity'); refreshSend(); refreshFinalize(); return true; } }); });
其他功能,例如向用户显示响应的功能,只需要针对活动进行扩展:
function displayNotifier(mode) { if (notifierOkay) { if (mode == 'time') { $('#notifierTime').show(); } else if (mode == 'place') { $('#notifierPlace').show(); } else if (mode == 'chooseplace') { $('#notifierChoosePlace').show(); } else if (mode == 'choosetime') { $('#notifierChooseTime').show(); } else if (mode == 'activity') { $('#notifierPlace').show(); } else if (mode == 'chooseactivity') { $('#notifierChooseActivity').show(); } else { alert("We\'ll automatically notify the organizer when you're done making changes."); } notifierOkay=false; } }
即使具有与现有功能一样反映质量的功能,新代码的添加也是广泛的。例如,这里有更多的 JavaScript。此代码涵盖了计划页面上会议时间的更多交互式 ajax 功能:
function showActivity() { if ($('#addActivity').hasClass( "hidden")) { $('#addActivity').removeClass("hidden"); $('.activity-form').removeClass("hidden"); } else { $('#addActivity').addClass("hidden"); $('.activity-form').addClass("hidden"); } }; function cancelActivity() { $('#addActivity').addClass("hidden"); $('.activity-form').addClass("hidden"); } function addActivity(id) { activity = $('#meeting_activity').val(); // ajax submit subject and message $.ajax({ url: $('#url_prefix').val()+'/meeting-activity/add', data: { id: id, activity: encodeURIComponent(activity), }, success: function(data) { $('#meeting_activity').val(''); loadActivityChoices(id); insertActivity(id); displayAlert('activityMessage','activityMsg1'); return true; } }); $('#addActivity').addClass('hidden'); } function insertActivity(id) { $.ajax({ url: $('#url_prefix').val()+'/meeting-activity/insertactivity', data: { id: id, }, type: 'GET', success: function(data) { $("#meeting-activity-list").html(data).removeClass('hidden'); $("input[name='meeting-activity-choice']").map(function(){ //$(this).bootstrapSwitch(); $(this).bootstrapSwitch('onText','<i class="glyphicon glyphicon-thumbs-up"></i> yes'); $(this).bootstrapSwitch('offText','<i class="glyphicon glyphicon-thumbs-down"></i> no'); $(this).bootstrapSwitch('onColor','success'); $(this).bootstrapSwitch('offColor','danger'); $(this).bootstrapSwitch('handleWidth',50); $(this).bootstrapSwitch('labelWidth',1); $(this).bootstrapSwitch('size','small'); }); }, }); refreshSend(); refreshFinalize(); } function getActivities(id) { $.ajax({ url: $('#url_prefix').val()+'/meeting-activity/getactivity', data: { id: id, }, type: 'GET', success: function(data) { $('#meeting-activity-list').html(data); }, }); }
框架添加
当然,必须添加模型、控制器和视图。以下是 MeetingActivity.php 模型的摘录,其中列出了用户可以快速键入以使用的许多默认活动:
<?php namespace frontend\models; use Yii; use yii\db\ActiveRecord; use common\components\MiscHelpers; use frontend\models\Participant; /** * This is the model class for table "{{%meeting_activity}}". * * @property integer $id * @property integer $meeting_id * @property string $activity * @property integer $availability * @property integer $suggested_by * @property integer $status * @property integer $created_at * @property integer $updated_at * * @property User $suggestedBy * @property Meeting $meeting * @property MeetingActivityChoice[] $meetingActivityChoices */ class MeetingActivity extends \yii\db\ActiveRecord { const STATUS_SUGGESTED =0; const STATUS_SELECTED =10; // the chosen date time const STATUS_REMOVED =20; const MEETING_LIMIT = 7; public $url_prefix; /** * @inheritdoc */ public static function tableName() { return '{{%meeting_activity}}'; } public function behaviors() { return [ /*[ 'class' => SluggableBehavior::className(), 'attribute' => 'name', 'immutable' => true, 'ensureUnique'=>true, ],*/ 'timestamp' => [ 'class' => 'yii\behaviors\TimestampBehavior', 'attributes' => [ ActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'], ActiveRecord::EVENT_BEFORE_UPDATE => ['updated_at'], ], ], ]; } /** * @inheritdoc */ public function rules() { return [ [['meeting_id', 'activity', 'suggested_by',], 'required'], [['meeting_id', 'suggested_by', 'status', 'created_at', 'updated_at'], 'integer'], [['activity'], 'string', 'max' => 255], [['suggested_by'], 'exist', 'skipOnError' => true, 'targetClass' => \common\models\User::className(), 'targetAttribute' => ['suggested_by' => 'id']], [['meeting_id'], 'exist', 'skipOnError' => true, 'targetClass' => Meeting::className(), 'targetAttribute' => ['meeting_id' => 'id']], ]; } public static function defaultActivityList() { $activities = [ Yii::t('frontend','Bachelor party'), Yii::t('frontend','Birthday party'), Yii::t('frontend','Breakfast'), Yii::t('frontend','Brunch'), Yii::t('frontend','Coffee, Tea, Juice Bar, et al.'), Yii::t('frontend','Concert'), Yii::t('frontend','Counseling'), Yii::t('frontend','Cycling'), Yii::t('frontend','Dessert'), Yii::t('frontend','Dinner'), Yii::t('frontend','Dog walking'), Yii::t('frontend','Drinks'), Yii::t('frontend','Dancing'), Yii::t('frontend','Bar'), Yii::t('frontend','Movies'), Yii::t('frontend','Happy hour'), Yii::t('frontend','Hiking'), Yii::t('frontend','Lunch'), Yii::t('frontend','Meditation'), Yii::t('frontend','Netflix and chill'), Yii::t('frontend','Party'), Yii::t('frontend','Protest'), Yii::t('frontend','Theater'), Yii::t('frontend','Play board games'), Yii::t('frontend','Play scrabble'), Yii::t('frontend','Play video games'), Yii::t('frontend','Running'), Yii::t('frontend','Shopping'), Yii::t('frontend','Skiing'), Yii::t('frontend','Snowboarding'), Yii::t('frontend','Snowshoeing'), Yii::t('frontend','Stand up comedy'), Yii::t('frontend','Walking'), Yii::t('frontend','Watch movies'), Yii::t('frontend','Watch sports'), Yii::t('frontend','Volunteer'), Yii::t('frontend','Yoga'), ]; return $activities; }
这是 /frontend/views/activity/_form.php 的摘录,其中TypeaheadBasic 小部件 使用上述内容defaultActivityList():
<?php $activities=MeetingActivity::defaultActivityList(); echo $form->field($model, 'activity')->label(Yii::t('frontend','Suggest an activity'))->widget(TypeaheadBasic::classname(), [ 'data' => $activities, 'options' => ['placeholder' => Yii::t('frontend','enter your suggestions'), 'id'=>'meeting_activity', //'class'=>'input-large form-control' ], 'pluginOptions' => ['highlight'=>true], ]); ?>
但是除了常见的框架需求之外,还有许多代码更改。这是 Meeting.php 模型canSend(),该函数确定是否允许用户发送会议邀请。它确定会议是否满足发送的最低要求,例如有时间和活动或时间和地点。
下面,您可以看到如何为活动添加新部分:
public function canSend($sender_id) { // check if an invite can be sent // req: a participant, at least one place, at least one time $cntPlaces = 0; foreach($this->meetingPlaces as $mp) { if ($mp->status!=MeetingPlace::STATUS_REMOVED) { $cntPlaces+=1; } } $cntTimes = 0; foreach($this->meetingTimes as $mt) { if ($mt->status!=MeetingTime::STATUS_REMOVED) { $cntTimes+=1; } } $cntActivities =0; // for either type of meeting if ($this->is_activity==Meeting::IS_ACTIVITY) { foreach($this->meetingActivities as $ma) { if ($ma->status!=MeetingActivity::STATUS_REMOVED) { $cntActivities+=1; } } } if ($this->owner_id == $sender_id && count($this->participants)>0 && ($cntPlaces>0 || $this->isVirtual() || ($this->is_activity == Meeting::IS_ACTIVITY && $cntActivities>0)) && $cntTimes>0 && ($this->is_activity == Meeting::NOT_ACTIVITY || ($this->is_activity == Meeting::IS_ACTIVITY && $cntActivities>0)) ) { $this->isReadyToSend = true; } else { $this->isReadyToSend = false; } return $this->isReadyToSend; }
电子邮件模板
更新电子邮件布局需要对设计以及如何最好地展示会议邀请和确认活动中的活动进行一点思考。以下是更新后的电子邮件邀请示例:
本质上,如果会议有活动,那么邀请会在时间和地点上方包含一排宽行,再次复制大量现有的时间和地点代码:
<?php if ($is_activity==Meeting::IS_ACTIVITY) {?> <tr> <td style="color:#777; font-family:Helvetica, Arial, sans-serif; font-size:14px; line-height:21px; text-align:center; border-collapse:collapse; padding:8px 20px; width:280px" align="center" width="280"> <table cellspacing="0" cellpadding="0" width="100%" style="border-collapse:separate"> <tr> <td style="color:#777; font-family:Helvetica, Arial, sans-serif; font-size:14px; line-height:21px; text-align:center; border-collapse:collapse; background-color:#fff; border:1px solid #ccc; border-radius:5px; padding:12px 15px 15px; width:498px" align="center" bgcolor="#ffffff" width="498"> <table cellpadding="0" cellspacing="0" width="100%" style="border-collapse:collapse"> <tr> <td style="color:#777; font-family:Helvetica, Arial, sans-serif; font-size:14px; line-height:21px; text-align:left; border-collapse:collapse" align="left"> <span style="color:#4d4d4d; font-size:18px; font-weight:700; line-height:1.3; padding:5px 0">Possible Activities</span><br> <?php foreach($activities as $activity) { ?> <?php echo $activity->activity; ?><br /> <?php } ?> <br /> <?php if ($meetingSettings->participant_add_activity) { ?> <?php echo HTML::a(Yii::t('frontend','suggest an activity'),$links['addactivity']); ?><br /> <?php } ?> </td> </tr> </table> </td> </tr> </table> </td> </tr> <?php } ?>
反思变化
最终,活动功能需要一个巨大的新代码分支。这是拉取请求:
$ cd /var/www/mp && git pull origin master remote: Counting objects: 183, done. remote: Compressing objects: 100% (183/183), done. remote: Total 183 (delta 115), reused 0 (delta 0), pack-reused 0 Receiving objects: 100% (183/183), 111.48 KiB | 0 bytes/s, done. Resolving deltas: 100% (115/115), done. From github.com:newscloud/mp * branch master -> fetch_HEAD 923b514..cd16262 master -> origin/master Updating 923b514..cd16262 Fast-forward common/components/MiscHelpers.php | 8 +-- common/mail/finalize-html.php | 28 ++++++++- common/mail/invitation-html.php | 39 ++++++++++++- composer.lock | 228 +++++++++++++++++++++++++++++++++++++----------------------------------- console/migrations/m161202_020757_create_meeting_activity_table.php | 35 +++++++++++ console/migrations/m161202_021355_create_meeting_activity_choice_table.php | 34 +++++++++++ console/migrations/m161202_024352_extend_meeting_setting_table_for_activities.php | 22 +++++++ console/migrations/m161202_024403_extend_user_setting_table_for_activities.php | 24 ++++++++ console/migrations/m161203_010030_extend_meeting_table_for_activities.php | 22 +++++++ frontend/assets/AppAsset.php | 2 +- frontend/assets/HomeAsset.php | 2 +- frontend/assets/MapAsset.php | 2 +- frontend/assets/MeetingAsset.php | 2 +- frontend/config/main.php | 1 + frontend/controllers/DaemonController.php | 2 +- frontend/controllers/MeetingActivityChoiceController.php | 70 ++++++++++++++++++++++ frontend/controllers/MeetingActivityController.php | 261 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ frontend/controllers/MeetingController.php | 94 ++++++++++++++++++++++++++++-- frontend/controllers/MeetingTimeController.php | 4 +- frontend/models/Fix.php | 9 +-- frontend/models/Meeting.php | 125 ++++++++++++++++++++++++++++++++++------ frontend/models/MeetingActivity.php | 260 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ frontend/models/MeetingActivityChoice.php | 158 ++++++++++++++++++++++++++++++++++++++++++++++++++ frontend/models/MeetingLog.php | 43 +++++++++++++- frontend/models/MeetingPlaceChoice.php | 3 +- frontend/models/MeetingTimeChoice.php | 3 +- frontend/models/Participant.php | 2 + frontend/views/meeting-activity/_choices.php | 42 ++++++++++++++ frontend/views/meeting-activity/_form.php | 46 +++++++++++++++ frontend/views/meeting-activity/_list.php | 78 +++++++++++++++++++++++++ frontend/views/meeting-activity/_panel.php | 81 ++++++++++++++++++++++++++ frontend/views/meeting-activity/_search.php | 39 +++++++++++++ frontend/views/meeting-activity/_thread.php | 15 +++++ frontend/views/meeting-activity/create.php | 21 +++++++ frontend/views/meeting-activity/index.php | 42 ++++++++++++++ frontend/views/meeting-activity/update.php | 23 ++++++++ frontend/views/meeting-setting/_form.php | 2 + frontend/views/meeting-time/_panel.php | 2 +- frontend/views/meeting-time/view.php | 4 +- frontend/views/meeting/_command_bar_planning.php | 11 +++- frontend/views/meeting/_grid.php | 9 ++- frontend/views/meeting/_panel_what.php | 1 + frontend/views/meeting/view.php | 22 +++++-- frontend/views/meeting/view_confirmed.php | 19 ++++++ frontend/views/meeting/viewactivity.php | 40 +++++++++++++ frontend/views/participant/_panel.php | 4 +- frontend/views/user-setting/_form.php | 7 ++- frontend/web/css/site.css | 9 ++- frontend/web/js/meeting.js | 284 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------- 49 files changed, 2027 insertions(+), 257 deletions(-) create mode 100644 console/migrations/m161202_020757_create_meeting_activity_table.php create mode 100644 console/migrations/m161202_021355_create_meeting_activity_choice_table.php create mode 100644 console/migrations/m161202_024352_extend_meeting_setting_table_for_activities.php create mode 100644 console/migrations/m161202_024403_extend_user_setting_table_for_activities.php create mode 100644 console/migrations/m161203_010030_extend_meeting_table_for_activities.php create mode 100644 frontend/controllers/MeetingActivityChoiceController.php create mode 100644 frontend/controllers/MeetingActivityController.php create mode 100644 frontend/models/MeetingActivity.php create mode 100644 frontend/models/MeetingActivityChoice.php create mode 100644 frontend/views/meeting-activity/_choices.php create mode 100644 frontend/views/meeting-activity/_form.php create mode 100644 frontend/views/meeting-activity/_list.php create mode 100644 frontend/views/meeting-activity/_panel.php create mode 100644 frontend/views/meeting-activity/_search.php create mode 100644 frontend/views/meeting-activity/_thread.php create mode 100644 frontend/views/meeting-activity/create.php create mode 100644 frontend/views/meeting-activity/index.php create mode 100644 frontend/views/meeting-activity/update.php create mode 100644 frontend/views/meeting/viewactivity.php
它是如此之大,以至于我决定制作一个有趣的视频,滚动浏览 GitHub 中的所有更改,并在后台播放适当的音乐......享受:
总体而言,构建活动功能对我思考网站架构以及如何在单人初创公司的代码库上取得快速、稳定的进展很有帮助。使用复制,但首先反映整体范围。
活动功能最终触及的领域比我预期的要多。
提前计划将帮助您避免陷入永无止境的新功能编码陷阱。如果您确实发现自己陷入了一些深沟,这是一场永远不会结束的编码噩梦,请检查您对功能分支的更改,切换回 master,然后进行其他工作。它有助于清理你的头脑。
在这一集中,我确实采取了稍微不同的方法来指导你,我希望它会有所帮助。
- 面向客户的影响
- 代码影响
- 源代码分支
- 复制公共代码
- 帐户和会议设置
- 电子邮件模板
- 事件历史和日志
- 其他杂项领域
- 数据库迁移
- 为活动构建 MVC 框架
- JavaScript 和jquery变化
- 框架添加
- 电子邮件模板