我所理解MVVM模式

前言

其實(shí)關(guān)于MVVM,筆者早就想談?wù)勛约旱南敕ㄟ罕笥褌兘涣鲗W(xué)習(xí)。但是由于這段時(shí)間公司任務(wù)緊理盆,加班多痘煤,而抽不出時(shí)間來。這樣一來離上一篇MVP模式已經(jīng)有兩個(gè)月了猿规。

起源

MVVM 最早于 2005 年被微軟的 WPF 和 Silverlight 的架構(gòu)師 John Gossman 提出衷快,并不斷完善。微軟為了MVVM姨俩,可謂是費(fèi)勁心力烦磁。為平臺(tái)整合了大量基礎(chǔ)設(shè)施和高級(jí)特性,如XAML哼勇、Blend、Bingding System呕乎、AttachBehavior积担、DependencyProperty等等,這些都大大的簡(jiǎn)化了MVVM的開發(fā)猬仁,使MVVM模式在微軟平臺(tái)得到了廣泛應(yīng)用帝璧。

MVVM在解決什么問題?

當(dāng)面試官問你這個(gè)問題的時(shí)候湿刽,千萬別只說MVVM是在給Controller瘦身的烁,這樣你一眼就被看穿對(duì)MVVM一知半解。任何一個(gè)模式的出現(xiàn)诈闺,一定為了解決軟件工程中某個(gè)特定的痛點(diǎn)渴庆,要么是為了提高開發(fā)效率,縮短軟件開發(fā)周期雅镊;要么是為了提高軟件的穩(wěn)定性襟雷、可擴(kuò)展性;要么是為了提高軟件的運(yùn)行性能等等仁烹,“瘦身”不過是被捎帶上的結(jié)果耸弄。那MVVM在解決什么問題呢?或者說MVVM為什么而出現(xiàn)呢卓缰?

MVVM是為了讓界面設(shè)計(jì)師專注于界面元素和交互计呈,從而能夠設(shè)計(jì)出讓用戶欣喜若狂的產(chǎn)品砰诵;讓開發(fā)者專注于邏輯,完全脫離UI捌显,從而能夠保證程序的穩(wěn)定性茁彭、可擴(kuò)展性、和高性能苇瓣。由于界面設(shè)計(jì)和業(yè)務(wù)完全分離尉间,使得更換界面變得簡(jiǎn)單,調(diào)整業(yè)務(wù)邏輯也不會(huì)對(duì)界面產(chǎn)生嚴(yán)重的影響击罪;另一方面哲嘲,也使得我們可以單獨(dú)對(duì)業(yè)務(wù)進(jìn)行單元測(cè)試。

模式解析

Model層:數(shù)據(jù)服務(wù)層媳禁,跟其他類MVC模式一樣眠副,不管最終對(duì)接的是數(shù)據(jù)庫還是網(wǎng)絡(luò)API或是其它,都是在負(fù)責(zé)數(shù)據(jù)的存儲(chǔ)竣稽,并提供訪問數(shù)據(jù)的接口囱怕,以支持?jǐn)?shù)據(jù)的增刪改查的基本操作。
View層:界面層毫别,但大家注意娃弓,這里的View層相對(duì)于MVP模式中的View來說,指代的范圍更狹小岛宦,原則上它不包含任何界面邏輯台丛,在基本組件庫滿足需求的情況下,View層的設(shè)計(jì)和制作完全不需要程序員的參與砾肺,所有工作都由界面設(shè)計(jì)師完成挽霉,這也是MVVM的一個(gè)核心的思想。對(duì)于MVC系列的其他模式变汪,由于界面設(shè)計(jì)和邏輯開發(fā)可以獨(dú)立的同時(shí)進(jìn)行侠坎,第一個(gè)好處是開發(fā)周期縮短,程序員再不需要等UI將設(shè)計(jì)圖拿給你才開始寫上層的功能代碼裙盾;第二個(gè)好處是界面設(shè)計(jì)師和程序員都可以在更專注的干好自己份內(nèi)的事实胸。
ViewModel層:ViewModel翻譯過來—視圖的模型,很恰當(dāng)番官。ViewModel就是完全反應(yīng)View的狀態(tài)和行為童芹,是View的內(nèi)在抽象。John Gossman 在他的博文中說什么鲤拿?他說ViewModel包含ViewState假褪、ValueConverter、Commands近顷、DataBindings生音,所以說ViewModel是一個(gè)抽象的View一點(diǎn)都沒錯(cuò)宁否。我在網(wǎng)上看到很多朋友錯(cuò)誤的理解,大家切記ViewModel不是數(shù)據(jù)模型的封裝缀遍,不是數(shù)據(jù)模型的封裝慕匠,不是數(shù)據(jù)模型的封裝,重說三域醇!從ViewModel的外在屬性來看台谊,ViewModel和Model層的數(shù)據(jù)模型半毛錢關(guān)系都沒有,它不過是使用了數(shù)據(jù)模型所攜帶的數(shù)據(jù)而已譬挚。另外锅铅,在MVVM模式的開發(fā)設(shè)計(jì)中,是重View和ViewModel减宣,而輕Model的盐须。當(dāng)然說輕Model,不是說你Model層就可以隨心所欲的設(shè)計(jì)漆腌,而是強(qiáng)調(diào)設(shè)計(jì)師的中心在界面上贼邓,程序員的重心在ViewModel上,最后將這精心設(shè)計(jì)的兩層binding起來闷尿,就可以保證咱們項(xiàng)目的高大上~塑径。好了,再解釋一下上面提到的幾個(gè)關(guān)鍵詞:

  • ViewState 指數(shù)據(jù)的數(shù)據(jù)狀態(tài)和顯示狀態(tài)填具。數(shù)據(jù)狀態(tài)就是在視圖生命周期中展示的數(shù)據(jù)的值及其變化晓勇;顯示狀態(tài)就是視圖在生命周期中顯示成什么樣的。
  • ValueConverter 用于格式化數(shù)據(jù)的灌旧,比如需求是將時(shí)間顯示為昨天今天明天,但是模型中是時(shí)間戳绰筛,我們就需要ValueConverter來對(duì)時(shí)間進(jìn)行格式化枢泰。
  • Commands 包含了視圖的所有業(yè)務(wù)行為,比如登陸操作铝噩,就對(duì)應(yīng)一個(gè)登陸的command衡蚂,它將于登陸按鈕的點(diǎn)擊事件綁定起來,當(dāng)點(diǎn)擊事件發(fā)生骏庸,command內(nèi)封裝的登陸業(yè)務(wù)就會(huì)自動(dòng)觸發(fā)毛甲。
  • DataBindings 不用解釋,肯定是指View和ViewModel的綁定了具被。一般的View中數(shù)據(jù)容器如textview玻募,跟ViewModel的ViewState中的數(shù)據(jù)字段綁定起來;View的展示屬性跟ViewModel的ViewState中的展示狀態(tài)綁定起來一姿;View的事件跟ViewModel中定義的命令綁定起來七咧。

Binder層:之所以要將Binder單獨(dú)拿出來說跃惫,是因?yàn)橐獙?shí)現(xiàn)一個(gè)穩(wěn)定的高效的,應(yīng)用廣泛的綁定機(jī)制實(shí)際是相當(dāng)復(fù)雜的艾栋,牽涉到很多問題爆存。正如微軟做的一樣,將Binder作為MVVM開發(fā)的基礎(chǔ)組件內(nèi)置在了平臺(tái)中蝗砾,讓開發(fā)者解放出來做更有意義的事情先较。
Controller層:MVVM模式雖然名稱里沒有“C”的字樣,但是并不代表沒有Controller悼粮,正式Controller將View和ViewModel關(guān)聯(lián)起來闲勺,當(dāng)然用的是Binder層提供的綁定機(jī)制。這里提一句:在iOS上矮锈,controller也可能負(fù)責(zé)界面的生命周期霉翔、View的組織等工作,但工作量顯然輕多了苞笨,這就是我們常常說的瘦身的作用债朵。

下面是一張簡(jiǎn)單的結(jié)構(gòu)圖


MVVM模式結(jié)構(gòu)

上圖中,View和ViewModel之間用的是虛線的箭頭相連瀑凝,這表明View和ViewModel沒有直接的引用關(guān)系序芦,他們各自對(duì)于另一方都是透明的。View和ViewModel是在Controller的控制下通過Binding機(jī)制綁定在一起粤咪,從而協(xié)同工作的谚中。

talk is cheap

接下來是一個(gè)MVVM的demo,實(shí)現(xiàn)的是跟上一篇MVP一樣的功能寥枝。由于代碼太多宪塔,這里只能展示一部分,完整demo大家可以進(jìn)到這里囊拜,歡迎star和fork某筐。

  1. 登陸界面View層
@interface LoginView : UIView
@property (weak, nonatomic) IBOutlet UITextField *accountField;
@property (weak, nonatomic) IBOutlet UITextField *pwdField;
@property (weak, nonatomic) IBOutlet UIButton *loginBtn;
@property (strong,nonatomic) VLoadingProperty * logging;
@property (strong,nonatomic) VAlertProperty * logErr;
@property (strong,nonatomic) VNavProperty * toMain;
@property (strong,nonatomic) VEditBehavior * editEnabled;

- (id)initWithFrame:(CGRect)frame controller:(UIViewController *)vc;
@end
@implementation LoginView

///MARK: 初始化
- (id)initWithFrame:(CGRect)frame controller:(UIViewController *)vc {
    if ([self initWithFrame:frame]) {
        self.vc = vc;
        NSArray * arr=[[NSBundle mainBundle] loadNibNamed:@"LoginView" owner:self options:nil];
        UIView * view = arr.firstObject;
        if (view) {
            [self addSubview:view];
            view.frame=self.bounds;
            
            //初始化屬性和行為
            [self logging];
            [self logErr];
            [self toMain];
            [self editEnabled];
        }
    }
    return self;
}

///MARK: 屬性
- (VLoadingProperty *)logging {
    if (!_logging) {
        _logging= [VLoadingProperty new];
        _logging.superView=self;
    }
    return _logging;
}

- (VAlertProperty *)logErr {
    if (!_logErr) {
        _logErr = [VAlertProperty new];
        _logErr.vc = self.vc;
    }
    return _logErr;
}

- (VNavProperty *)toMain {
    if (!_toMain) {
        _toMain = [VNavProperty new];
        _toMain.nav = self.vc.navigationController;
    }
    return _toMain;
}

///MARK: 行為
- (VEditBehavior *)editEnabled {
    if (!_editEnabled) {
        _editEnabled = [[VEditBehavior alloc] initWithView:self];
    }
    return _editEnabled;
}

@end
  1. 登陸界面ViewModel層
@interface LoginVM : NSObject<IViewModel>

@property(assign,nonatomic)BOOL logging;//正在登陸

@property(strong,nonatomic)MainViewController *main;//登陸成功后有效

@property(strong,nonatomic)NSString * logErr;//登陸失敗錯(cuò)誤

@property(strong,nonatomic)NSString * account;//輸入賬號(hào)

@property(strong,nonatomic)NSString * password;//輸入密碼

@property(strong,nonatomic)VMCommand * login;//登陸操作

- (void)start;

@end

大家會(huì)發(fā)現(xiàn)LoginView 中除了自身初始化和屬性初始化沒有任何的界面邏輯,而這一部分工作在WPF中則是用XAML來做冠跷;同時(shí)南誊,LoginViewModel中除了start方法也沒有直接定義任何的其他業(yè)務(wù)方法,跟LoginView中屬性幾乎是一一對(duì)應(yīng)蜜托。接下來我們看另外一個(gè)界面
3抄囚、我的朋友界面View層

@interface FriendListView : UITableView

- (id)initWithController:(UIViewController *)viewController;

@property(strong,nonatomic)VDataListProperty * datalist;

@property(strong,nonatomic)VAlertProperty * rmError;

@property(strong,nonatomic)VConfirmProperty * confirm;

@end
@implementation FriendListView

///MARK: 初始化
- (id)initWithController:(UIViewController *)viewController {
    if (self=[self initWithFrame:CGRectZero style:UITableViewStylePlain]) {
        _viewController=viewController;
    }
    return self;
}

///MARK: 屬性

- (VDataListProperty *)datalist {
    if (!_datalist) {
        _datalist = [VDataListProperty new];
        _datalist.tableView = self;
        _datalist.cellNib = @"FriendViewCell";
        _datalist.cellHeight = 80;
        _datalist.cellSelectionStyle = UITableViewCellSelectionStyleNone;
        _datalist.cellEditStyle = UITableViewCellEditingStyleDelete;
        _datalist.select = [VSelectBehavior new];
        _datalist.edit = [VSelectBehavior new];
    }
    return _datalist;
}

- (VAlertProperty *)rmError {
    if (!_rmError) {
        _rmError = [VAlertProperty new];
        _rmError.vc = self.viewController;
        _rmError.title = @"刪除錯(cuò)誤";
    }
    return _rmError;
}

- (VConfirmProperty *)confirm {
    if (!_confirm) {
        _confirm = [VConfirmProperty new];
        _confirm.vc = self.viewController;
        _confirm.title = @"再次確認(rèn)";
    }
    return _confirm;
}

@end

4、我的朋友界面VM層

@interface FriendVM : NSObject<IViewModel>

@property(strong,nonatomic)UIImage * logo;

@property(strong,nonatomic)NSString * name;

@property(strong,nonatomic)NSString * signture;

- (id)initWithFriend:(Friend *)friend;

- (void)start;

@end
@interface FriendsVM : NSObject<IViewModel>

@property(strong,nonatomic)NSArray<FriendVM *> * friends;

@property(strong,nonatomic)VMCommand * rm;

@property(strong,nonatomic)NSString * confirm;

@property(strong,nonatomic)VMCommand * rm_hard;

@property(strong,nonatomic)NSString * rmError;

- (void)start;

@end

5橄务、Controller中綁定View與ViewModel并啟動(dòng)的代碼
登陸

dispatch_once(&onceToken, ^{
        [[V2MBinder shared] registerMappings:@{
                                               @"accountField.text":@"account",
                                               @"pwdField.text":@"password",
                                               @"loginBtn.touch":@"login",
                                               @"logging":@"logging",
                                               @"logErr":@"logErr",
                                               @"toMain":@"main"
                                               } betweenView:LoginView.class andVM:LoginVM.class];
    });
    [[V2MBinder shared]
     bindView:self.loginView
     withVM:self.vm];
    [self.vm start];

我的朋友

[[V2MBinder shared] registerMappings:@{
                                           @"headImgView.image":@"logo",
                                           @"nameLabel.text":@"name",
                                           @"signatureLabel.text":@"signture"
                                           } betweenView:FriendViewCell.class andVM:FriendVM.class];
    [[V2MBinder shared] registerMappings:@{
                                           @"headView.image":@"logo",
                                           @"nameLabel.text":@"name"
                                           } betweenView:FriendViewItem.class andVM:FriendVM.class];
    [[V2MBinder shared] registerMappings:@{
                                           @"datalist":@"friends",
                                           @"rmError":@"rmError",
                                           @"confirm":@"confirm",
                                           @"datalist.edit":@"rm",
                                           @"confirm.sure":@"rm_hard"
                                           } betweenView:FriendListView.class andVM:FriendsVM.class];
    [[V2MBinder shared] registerMappings:@{
                                           @"datalist":@"friends",
                                           @"rmError":@"rmError",
                                           @"confirm":@"confirm",
                                           @"datalist.select":@"rm",
                                           @"confirm.sure":@"rm_hard"
                                           } betweenView:FriendGridView.class andVM:FriendsVM.class];
    [[V2MBinder shared] bindView:self.listView withVM:self.vm];
    [self.vm start];

另外說明一下幔托,demo中并沒有實(shí)現(xiàn)VM和Model之間的雙向綁定,由于這不是討論的重點(diǎn)蜂挪,也由于實(shí)現(xiàn)起來太多麻煩柑司,就沒有做這個(gè)功能迫肖,大家看demo的時(shí)候注意一下就行了。

總結(jié)

說了這么多攒驰,希望大家已經(jīng)對(duì)MVVM有一定認(rèn)識(shí)了蟆湖,如果還是是懂非懂,請(qǐng)一定要看一看這里的demo玻粪。結(jié)合代碼來看會(huì)更容易理解和記憶的隅津。下面再對(duì)MVVM模式做一下總結(jié):

  1. MVVM模式有利于界面設(shè)計(jì)和程序開發(fā)進(jìn)行更加明確的分工,提高產(chǎn)品質(zhì)量和開發(fā)效率劲室。
  2. MVVM模式由于業(yè)務(wù)邏輯和界面邏輯都完全脫離了UI伦仍,所以有利于進(jìn)行單元測(cè)試。
  3. MVVM模式中由于View層與ViewModel層完全解耦很洋,所以都具有很高的可復(fù)用性和擴(kuò)展性充蓝。
  4. MVVM模式中由于ViewModel被定義為View層的抽象,所以通過保存ViewModel喉磁,可以很容易的對(duì)View層進(jìn)行狀態(tài)恢復(fù)谓苟。
  5. MVVM模式中雙向綁定機(jī)制會(huì)對(duì)性能和代碼調(diào)試又一定的影響。
  6. MVVM模式實(shí)現(xiàn)起來比較復(fù)雜协怒,在沒有基礎(chǔ)開發(fā)平臺(tái)的支持的情況下涝焙,開發(fā)效率不容易提高,所以目前MVVM不適應(yīng)于iOS孕暇,andriod等開發(fā)平臺(tái)仑撞。相對(duì)來說,MVP模式更適用于上述兩個(gè)平臺(tái)妖滔。

關(guān)于MVVM模式就告一段落了隧哮,如果有什么疑問或發(fā)現(xiàn)什么錯(cuò)誤,歡迎在下方留言進(jìn)行討論和指正座舍,感謝大家的支持沮翔。

參考資料

https://blogs.msdn.microsoft.com/johngossman/2005/10/08/introduction-to-modelviewviewmodel-pattern-for-building-wpf-apps/
https://blogs.msdn.microsoft.com/johngossman/2005/10/09/100-modelviewviewmodels-of-mt-fuji/
https://blogs.msdn.microsoft.com/johngossman/2006/02/26/model-view-viewmodel-pattern-example/
https://blogs.msdn.microsoft.com/johngossman/2006/03/07/collectionview/#comment-1303
https://blogs.msdn.microsoft.com/johngossman/2006/03/04/advantages-and-disadvantages-of-m-v-vm/
https://blogs.msdn.microsoft.com/johngossman/2006/04/13/uml-diagram-of-model-view-viewmodel-pattern/

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市簸州,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌歧譬,老刑警劉巖岸浑,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異瑰步,居然都是意外死亡矢洲,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門缩焦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來读虏,“玉大人责静,你說我怎么就攤上這事「乔牛” “怎么了灾螃?”我有些...
    開封第一講書人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)揩徊。 經(jīng)常有香客問我腰鬼,道長(zhǎng),這世上最難降的妖魔是什么塑荒? 我笑而不...
    開封第一講書人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任熄赡,我火速辦了婚禮,結(jié)果婚禮上齿税,老公的妹妹穿的比我還像新娘彼硫。我一直安慰自己,他們只是感情好凌箕,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開白布拧篮。 她就那樣靜靜地躺著,像睡著了一般陌知。 火紅的嫁衣襯著肌膚如雪他托。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,730評(píng)論 1 289
  • 那天仆葡,我揣著相機(jī)與錄音赏参,去河邊找鬼。 笑死沿盅,一個(gè)胖子當(dāng)著我的面吹牛把篓,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播腰涧,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼韧掩,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了窖铡?” 一聲冷哼從身側(cè)響起疗锐,我...
    開封第一講書人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎费彼,沒想到半個(gè)月后滑臊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡箍铲,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年雇卷,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡关划,死狀恐怖小染,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情贮折,我是刑警寧澤裤翩,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站脱货,受9級(jí)特大地震影響岛都,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜振峻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一臼疫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧扣孟,春花似錦烫堤、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至利诺,卻和暖如春富蓄,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背慢逾。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來泰國(guó)打工立倍, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人侣滩。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓口注,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親君珠。 傳聞我的和親對(duì)象是個(gè)殘疾皇子寝志,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 談?wù)?MVX 中的 Model 談?wù)?MVX 中的 View 談?wù)?MVX 中的 Controller 淺談 MV...
    Draveness閱讀 14,839評(píng)論 0 60
  • 前言: 本文主要是對(duì)常見設(shè)計(jì)模式的一些分析材部,以及講述在Android項(xiàng)目中實(shí)現(xiàn)Mvvm模式的兩種方式。通過Data...
    Yagami3zZ閱讀 45,089評(píng)論 29 117
  • 前言 談起MVC唯竹,MVP和MVVM這三個(gè)最耳熟能詳?shù)腁ndroid框架乐导,相信大家對(duì)它們都不陌生,但在實(shí)際的情況下摩窃,...
    ghroost閱讀 3,869評(píng)論 0 40
  • 姓名:徐萍 常州新日催化劑有限公司 六項(xiàng)精進(jìn)422班:【反省二組】 【日精進(jìn)打卡第14天】 【知~學(xué)習(xí)】 讀書半小...
    蘋果_4735閱讀 112評(píng)論 0 0
  • 下班和團(tuán)隊(duì)談了個(gè)業(yè)務(wù)兽叮。然后開始做開業(yè)前準(zhǔn)備,有點(diǎn)小激動(dòng)呢猾愿!家人們也都來體驗(yàn)館幫忙鹦聪,感動(dòng)! 舔臉:這是汪汪隊(duì)中毒太深...
    Joyce_kexin閱讀 130評(píng)論 0 1