沒有這玩意,黃金右腳也剎不住車?

)簡單來說就是發動機為剎車助力泵提供能量來幫你踩剎車。所以當發動機熄火后剎車助力泵也就跟着被掏空了,你的剎車踏板和剎車之間就是硬連接。雖然如此,也不是意味着發動機熄火了就沒有制動了,只是剎車系統沒有助力,踩起來比較重,但因為剎車助力泵的結構,發動機熄火以後裏面還有一定真空,所以一般還可以提供3次左右的剎車助力,再之後由於發動機熄火沒有繼續為助力泵產生真空條件,也就沒有剎車助力了。

前两天網上流傳着一個視頻,一個司機發生碰撞事故從車上跳下來用自身的力量來讓車停下來。這種用腳剎車的方式以往我們只能在自行車上見到,后經採訪是肇事司機駕駛生疏導致,但是這種情況我們在日常生活中可能還真會遇到。

雖然現代汽車上的电子設備越來越多,但還是有不少部件是机械結構,比如說剎車部分。如果以前老車開得多的老司機就會知道,當車輛行駛過程如果發動機意外熄火了,除了方向會變重之外剎車還會變硬。如果沒有經驗的新手碰到這種情況還真有可能下車用腳剎。

為什麼呢?首先汽車的剎車踏板不是直接作用到汽車剎車上,因為如果踏板和剎車之間是硬連接的話長下坡的時候你就算扯着方向盤站起來也不一定能把車停下來,這個時候就需要

剎車助力泵。

剎車助力泵內部有一个中部裝有推桿的膜片(或活塞),將腔體隔成兩部份,一部份與大氣相通,另一部份通過管道與發動機進氣管相連。

它是利用發動機工作時吸入空氣這一原理,造成助力器的一側真空,相對於另一側正常空氣壓力的壓力差,利用這壓力差來加強制動推力。 (但是由於後來的缸內直噴發動機越來越多,而且廠商開始逐漸在混合動力車和純電動車領域發力,很多車就會配備電動助力泵。)

簡單來說就是發動機為剎車助力泵提供能量來幫你踩剎車。所以當發動機熄火后剎車助力泵也就跟着被掏空了,你的剎車踏板和剎車之間就是硬連接。

雖然如此,也不是意味着發動機熄火了就沒有制動了,只是剎車系統沒有助力,踩起來比較重,但因為剎車助力泵的結構,發動機熄火以後裏面還有一定真空,所以一般還可以提供3次左右的剎車助力,再之後由於發動機熄火沒有繼續為助力泵產生真空條件,也就沒有剎車助力了。

就像抽水馬桶一樣,如果停水之前馬桶還有水,那麼就還能沖最後一次。下次還想沖水就得等來水以後將水箱灌滿才能繼續沖水。真空助力剎車泵也是同理。

所以有真空助力器的車載發動機熄火以後還能保持10-30分鐘的剎車助力。熄火后15分鐘再踩踩是否仍然輕鬆,自己可以試着踩一下。

最後就是手動擋千萬不要空擋滑行,更不要熄火滑行,因為這實在是一件很危險的事。如果不小心遇到這種情況逐個降低擋位,車速降低時再停車。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

印尼人每年吃掉百萬隻狗 動保人士籲嚴格取締

摘錄自2020年09月06日中央社報導

印尼近年來保護狗的意識成長,但每年仍有逾百萬隻狗被屠殺來吃,跨省運輸狗的違法行為氾濫,今年勢難達成脫離狂犬病疫區目標。動保人士呼籲完全禁食狗肉,加強執法,杜絕狗肉交易。

印尼2013年紀念9月28日世界狂犬病日時,訂下今年要自狂犬病疫區除名的目標。但到今年初,全國34個省和特區的25省仍有狂犬病疫情。

由「雅加達動物救援網」(Jakarta Animal Aid Network,JAAN)等動物保護團體組成的「印尼無狗肉」聯盟(Dog Meat Free Indonesia,DMFI)兩年多前調查顯示,亞洲每年有約3000萬隻狗遭屠殺食用,印尼至少有上百萬隻。

JAAN共同創辦人佛蘭肯(Karin Franken)4日接受中央社記者訪問時說,調查發現印尼狗肉交易的規模如此之大,「我們感到很驚訝」,各地市場的大小規模不同,但「基本上幾乎每個地方都有」。她舉例,特別是在蘇拉威西(Sulawesi)、蘇門答臘(Sumatra)及中爪哇省的梭羅市(Solo)。

印尼農業部約一年半前頒布指示,不再承認吃狗肉合法,也禁止任何機關發出狗肉食用的相關證明。

生物多樣性
國際新聞
印尼
狗肉

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

環團抗議氣候變遷 打亂英國多家報紙派送作業

摘錄自2020年09月05日中央社報導

英國氣候變遷抗議人士今早封鎖兩家報紙印刷廠,打亂了「泰晤士報」(The Times)、「每日電訊報」(Daily Telegraph)及「太陽報」(The Sun)等報的派送作業。

法新社報導,環保團體「反抗滅絕」(Extinction Rebellion)表示,矛頭對準這兩家印報廠是「為了揭露這些媒體未能準確報導氣候和生態危機」。

數十名示威者昨夜抵達現場,以車輛及其他障礙物阻擋廠外道路,警方連夜逮捕了63人。

「反抗滅絕」發表聲明說,他們希望擾亂新聞集團(News Corp.)旗下的這些報紙,以及「每日郵報」(Daily Mail)及「倫敦標準晚報」(London Evening Standard)等右翼報紙。

擁有及經營這些印刷廠的公司Newsprinters表示,印報作業已轉移到其他地點,並為「送報時間延誤」而對訂戶表達歉意。

「泰晤士報」也對無法買到這份報紙的讀者致歉,同時推文表示,報社「正努力將報紙儘快送到零售商手上」。

氣候變遷
國際新聞
英國
反抗滅絕
報紙

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

一款什麼樣的轎車才是符合當下潮流的選擇?

CADS3帶行人識別感應制動保護系統,對於前方靜止和移動的事物都有着靈敏的感知能力,預防車輛外界碰撞盡在分寸之間。ACC全速智能自適應巡航控制系統,最高可在180KM/H的工況下,實時監視前方路況信息,隨時保持安全車距,讓出行更加放心。

試想一下

你是一個職場精英

工作與家庭的特性

你需要購置一台轎車

你的選擇會是什麼?

你又會想你的車有着怎麼樣的優點?

高顏值的汽車外觀?還是洶湧澎湃的動力輸出?

正所謂情人眼裡出西施,一台車在不同的人眼裡,有着不一樣的顏值標準。一台車的顏值高低,向來是一個見仁見智無法量化的標準。

而汽車的油耗是用車成本的第一筆也是最大的一筆支出,相信每一個車主都不忍心看到在加油站時那冷酷的加油機上不斷跳躍上升的数字,它每跳動一下,都是我們錢包里紅通通的人民幣。

所以你需要的,是一款智能的汽車

正在用手機看文章的你,一定知道現在是一個智能化的信息時代,隨着科技的發展,智能化設備在生活中的普及程度越來越高,作為從一個代步工具逐漸成為我們生活中的夥伴、家庭中重要一員的汽車來說,智能化、信息化的進化方式也成了當下提升汽車品質、豐富人們生活的流行趨勢;

2017款新蒙迪歐順應信息時代的潮流智行而來,致力成為國內領先的科技網聯智能高品質B級車。

智行:輔助駕駛,行車安全盡在掌控之中

行車安全是每一個汽車駕駛員在駕車時都需要第一位考慮的重要事項,2017款新蒙迪歐,植入智能的大腦,對於行車安全具備更主動的思考能力。

2017款新蒙迪歐採用全新設計的豪華內飾,並在座椅中植入按摩功能,在路途中讓駕乘人員時刻感受到舒適與一台B級車應該有的高檔感。並輔以全方位安全配置,極大程度上保證了車輛與人身安全。

LKA車道保持輔助系統,全程監測兩側車道標記,全天候的提醒並輔助車輛在車道上無意識的誤操作,穩定行車軌跡,將意外發生的可能性降至最低。

CADS3帶行人識別感應制動保護系統,對於前方靜止和移動的事物都有着靈敏的感知能力,預防車輛外界碰撞盡在分寸之間。

ACC全速智能自適應巡航控制系統,最高可在180KM/H的工況下,實時監視前方路況信息,隨時保持安全車距,讓出行更加放心。

此外,2017款新蒙迪歐還配備主動泊車系統與全車10氣囊環繞式安全防護、氣囊式安全帶真正做到便利輔助、安全出行。

智擎:從心出發,引領智慧動力

作為一台汽車運轉的核心,2017款新蒙迪歐所搭載的全新升級1.5T和2.0T渦輪增壓發動機,採用先進的雙渦流渦輪增壓技術,有效緩解普通渦輪增壓車型擁有的渦輪遲滯,最大扭矩平台爆發時間較早,在提升動力的同時,配合上出色的發動機啟停功能,更出色有效的控制了燃油經濟性,並且平穩的運轉特性擁有着非常優秀的隔音效果,在提升動力輸出的同時,並不會對車內人員造成過分的噪音影響。

與這台發動機配合的是一台6速手自一體變速箱,採用先進的E-shifter电子旋鈕式換擋器,操作更加靈活編輯,視覺上也更具科技感與檔次感。

此外除了汽油版車型以外,2017款新蒙迪歐引入了HEV混合動力版本車型,阿特金森循環的發動機配合上電動機一同聯動,並且加上ECVT變速箱的輔助,使得2017款新蒙迪歐混合動力版本有着業內領先的燃油經濟性、行車途中出色的車內靜謐性;並且由於有着先進的軟件控制,使得2017款新蒙迪歐混動在動力和油耗間做到更完美的平衡,不失高效傳動的特性更兼顧環境保護能力,智慧動力,與時俱進。

智聯:智能人機,出行便利僅在彈指之間

現如今的生活離不開智能,我們每天使用的电子設備和移動終端都向智能化和便捷化發展,

作為一台與時俱進的科技網聯B級座駕,2017款新蒙迪歐使用車載互聯模塊+Fordpass技術,在2017款新蒙迪歐與智能手機之間架設起一座互聯的橋樑,便於車主對2017款新蒙迪歐進行遠程開閉汽車鎖、遠程啟動、汽車定位、更可以方便車主隨時查看汽車狀態。

2017款新蒙迪歐所使用的SYNC3智能聲控多媒體系統,可以非常便利地讓駕駛者解放雙手而在駕車過程中延續社交的習慣,與Siri無縫鏈接,讓語音指令得以迅速的響應,高精度的電容屏幕可以精確地識別連寫字體,讓輸入不再成為車載多媒體的弊端。

作為時代的寵兒,2017款新蒙迪歐,一款科技網聯座駕順應而生,它在人、車、與智能信息終端間架起了一條無縫對接的橋樑,一款真正智能的汽車,才是真正智能、安全、高效出行的行業典範。

科技是人類進步的階梯,智能是品質生活的主題,2017款新蒙迪歐,智行、智擎、智聯的“三智”科技網聯B級車,在車內配置和使用檔次上提供給人們便利的同時,更將車輛安全和人身安全提升到行業領先級別,一款安全、智能的高科技網聯智能座駕,方為當下潮流品質之選。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

※教你寫出一流的銷售文案?

※超省錢租車方案

明明買了奔馳寶馬卻被人嘲笑 我到底做錯了什麼

雖然面對他們的嘲笑我表示不解,但是車還是要買,聽說買了奔馳,妹子就會自動上車。於是我又買了輛最新款的SUV。結果還是被他們嘲笑。後來我又換了寶馬。路虎。但是結果都是一樣,我覺得不要買那麼好的車了,太容易買到假貨,車要那麼好的品牌幹嘛,能夠遮風擋雨代步就夠了,所以下定決心買一輛吉利熊貓,結果買回去他們卻說,我買車的這段時間,他們的臉上都長肌肉了。

眼看年關將至,為了過年回家的時候能在親戚朋友面前,展示一下自己一年的成績,決定去提輛車。一直都有聽朋友說本田的超跑不錯,還可以爆VTEC,所以決定買輛GK5。

不知怎麼買回來就被朋友取笑,難道在他們眼中Type R才是本田的超跑嗎?後來想了想。

於是我決定換個選擇。雖然超跑很刺激,但是過年車多路滑,安全也很重要,聽說奧迪的quattro四驅很厲害,所以決定去買輛奧迪的SUV。

結果買回來又被他們取笑,難道現在的奧迪是quattro嗎?

雖然面對他們的嘲笑我表示不解,但是車還是要買,聽說買了奔馳,妹子就會自動上車。於是我又買了輛最新款的SUV。

結果還是被他們嘲笑。

後來我又換了寶馬。

路虎。

但是結果都是一樣,我覺得不要買那麼好的車了,太容易買到假貨,車要那麼好的品牌幹嘛,能夠遮風擋雨代步就夠了,所以下定決心買一輛吉利熊貓,結果買回去他們卻說,我買車的這段時間,他們的臉上都長肌肉了。

我的人生觀彷彿在這一刻崩塌。

其實並不是笑話這些車是假車,段子聽聽就完了,畢竟他們除了換了個Logo之外知道自己是老年代步車,並沒有說自己是汽車,不想其他品牌。

本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

C#9.0 終於來了,帶你一起解讀 nint 和 Pattern matching 兩大新特性玩法

一:背景

1. 講故事

上一篇跟大家聊到了Target-typed newLambda discard parameters,看博客園和公號里的閱讀量都達到了新高,甚是欣慰,不管大家對新特性是多頭還是空頭,起碼還是對它抱有一種極為關注的態度,所以我的這個系列還得跟,那就繼續開擼吧,今天繼續帶來兩個新特性,更多新特性列表,請大家關注:新特性預覽

二:新特性研究

1. Native ints

從字面上看貌似是什麼原生類型ints,有點莫名其妙,還是看一看Issues上舉得例子吧:


Summary: nint i = 1; and nuint i2 = 2;

Shipped in preview in 16.7p1.

有點意思,還是第一次看到有nint這麼個東西,應該就是C#9新增的關鍵詞,好奇心爆棚,快來實操一下。


   static void Main(string[] args)
   {
        nint i = 10;
        Console.WriteLine($"i={i}");
   }

從圖中看,可以原樣輸出,然後用ILSpy查查底層IL代碼,發現連IL代碼都不用看。如下圖:

從圖中看原來 nint 就是 IntPtr 結構體哈,如果你玩過 C# 到 C++ 之間的互操作,我相信你會對Ptr再熟悉不過了,從這個 nint 上看,你不覺得C#團隊對指針操作是前所未有的重視嗎? 前有指針類型IntPtr,後有內存段處理集合Span,到現在直接提供關鍵詞支持,就是盡最大努力讓你在類型安全的前提下使用指針。

這就讓我想起了前些天寫的一篇互操作的文章,現在就可以用nint進行簡化了,來段代碼給大家看一下。

  • 原來的寫法:

        [DllImport("ConsoleApplication1.dll", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
        extern static IntPtr AddPerson(Person person);

        static void Main(string[] args)
        {
            var person = new Person() { username = "dotnetfly", password = "123456" };
            var ptr = AddPerson(person);
            var str = Marshal.PtrToStringAnsi(ptr);
        }

  • IntPtr -> nint 的寫法

總的來說這個關鍵詞不是最重要的,重要的是C#團隊對指針操作抱有前所未有的重視,這是一個非常积極的信號。

2. Pattern matching improvements

模式匹配這個不算是什麼新特性了,在本次C#9中也是繼續得到了完善,可能有很多朋友對模式匹配不是很熟悉,畢竟是C#7才有的新玩法,後面幾乎每一個新版本都在跟蹤完善,我先科普一下吧。

模式匹配到底解決了什麼問題

大家在編碼的過程中,不可能遇不到 if/else 嵌套 if/else 的這種情況,有時候嵌套甚至達到5,6層之多,特別影響代碼可讀性,我就來YY個例子。

現在各個地方都在發不同面值的消費券,為了實現千人千面,消費券的發放規則如下:

性別 年齡 地區 面值
<20 安徽 2000
<40 上海 4000
剩餘 剩餘 3000
<20 安徽 2500
<60 安徽 1500

如果用傳統的方式,你肯定要用各種花哨的if/else來實現,如下代碼:


        static decimal GetTicket(string sex, int age, string area)
        {
            if (sex == "男")
            {
                if (age < 20 && area == "安徽")
                {
                    return 2000;
                }
                else
                {
                    if (age < 40 && area == "上海")
                    {
                        return 4000;
                    }
                    else
                    {
                        return 3000;
                    }
                }
            }
            else
            {
                if (age < 20 && area == "安徽")
                {
                    return 2500;
                }
                if (age < 60 && area == "安徽")
                {
                    return 1500;
                }
            }

            return 0;
        }

這種代碼可讀性不是一般的差,就像大強子說的那樣:看着都想打人。。。 問題來了,這代碼還有救嗎??? 當然有了,這就需要用Pattern matching 去簡化,畢竟它就是為了這種問題而生的,修改后的代碼如下:


        static decimal GetTicket_Pattern(string sex, int age, string area)
        {
            return (sex, age, area) switch
            {
                ("男", < 20, "安徽") => 2000,
                ("男", < 40, "上海") => 4000,
                ("男", _, _) => 3000,
                ("女", < 20, "安徽") => 2500,
                ("女", < 60, "安徽") => 1500,
                _ => 0
            };
        }

看到這種化簡后的代碼是不是非常驚訝,這就是 Pattern matching 要幫你解決的場景,接下來看看底層的IL代碼是什麼樣子。

從圖中看,這反編譯后的代碼比我手工寫的還要爛,無力吐槽哈,當然 模式匹配 有各種千奇百怪的玩法,絕對讓你瞠目結舌,更多玩法可參考官方文檔:模式匹配

這個特性最重要的是你一定要明白它的客戶群在哪裡?

三: 總結

總的來說,這兩個特性都是比較實用的,尤其是 Pattern matching 化解了你多少不得不這麼寫的爛代碼,頭髮護理就靠它了,快來給它點個贊吧!

好了,先就這樣吧,感謝您的閱讀,希望本篇對你有幫助,謝謝。

如您有更多問題與我互動,掃描下方進來吧~

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

為何說要多用組合少用繼承?如何決定該用組合還是繼承?

在面向對象編程中,有一條非常經典的設計原則,那就是:組合優於繼承,多用組合少用繼承。為什麼不推薦使用繼承?組合相比繼承有哪些優勢?如何判斷該用組合還是繼承?今天,我們就圍繞着這三個問題,來詳細講解一下這條設計原則。

為什麼不推薦使用繼承?

繼承是面向對象的四大特性之一,用來表示類之間的 is-a 關係,可以解決代碼復用的問題。雖然繼承有諸多作用,但繼承層次過深、過複雜,也會影響到代碼的可維護性。所以,對於是否應該在項目中使用繼承,網上有很多爭議。很多人覺得繼承是一種反模式,應該盡量少用,甚至不用。為什麼會有這樣的爭議?我們通過一個例子來解釋一下。

假設我們要設計一個關於鳥的類。我們將“鳥類”這樣一個抽象的事物概念,定義為一個抽象類 AbstractBird。所有更細分的鳥,比如麻雀、鴿子、烏鴉等,都繼承這個抽象類。

我們知道,大部分鳥都會飛,那我們可不可以在 AbstractBird 抽象類中,定義一個 fly() 方法呢?答案是否定的。儘管大部分鳥都會飛,但也有特例,比如鴕鳥就不會飛。鴕鳥繼承具有 fly() 方法的父類,那鴕鳥就具有“飛”這樣的行為,這顯然不符合我們對現實世界中事物的認識。當然,你可能會說,我在鴕鳥這個子類中重寫(override)fly() 方法,讓它拋出 UnSupportedMethodException 異常不就可以了嗎?具體的代碼實現如下所示:

public class AbstractBird {
  //...省略其他屬性和方法...
  public void fly() { //... }
}

public class Ostrich extends AbstractBird { //鴕鳥
  //...省略其他屬性和方法...
  public void fly() {
    throw new UnSupportedMethodException("I can't fly.'");
  }
}

這種設計思路雖然可以解決問題,但不夠優美。因為除了鴕鳥之外,不會飛的鳥還有很多,比如企鵝。對於這些不會飛的鳥來說,我們都需要重寫 fly() 方法,拋出異常。這樣的設計,一方面,徒增了編碼的工作量;另一方面,也違背了我們之後要講的最小知識原則(Least Knowledge Principle,也叫最少知識原則或者迪米特法則),暴露不該暴露的接口給外部,增加了類使用過程中被誤用的概率。

可能又會說,那我們再通過 AbstractBird 類派生出兩個更加細分的抽象類:會飛的鳥類 AbstractFlyableBird 和不會飛的鳥類 AbstractUnFlyableBird,讓麻雀、烏鴉這些會飛的鳥都繼承 AbstractFlyableBird,讓鴕鳥、企鵝這些不會飛的鳥,都繼承 AbstractUnFlyableBird 類,不就可以了嗎?具體的繼承關係如下圖所示:

從圖中我們可以看出,繼承關係變成了三層。不過,整體上來講,目前的繼承關係還比較簡單,層次比較淺,也算是一種可以接受的設計思路。我們再繼續加點難度。在剛剛這個場景中,我們只關注“鳥會不會飛”,但如果我們還關注“鳥會不會叫”,那這個時候,我們又該如何設計類之間的繼承關係呢?

是否會飛?是否會叫?兩個行為搭配起來會產生四種情況:會飛會叫、不會飛會叫、會飛不會叫、不會飛不會叫。如果我們繼續沿用剛才的設計思路,那就需要再定義四個抽象類(AbstractFlyableTweetableBird、AbstractFlyableUnTweetableBird、AbstractUnFlyableTweetableBird、AbstractUnFlyableUnTweetableBird)。

如果我們還需要考慮“是否會下蛋”這樣一個行為,那估計就要組合爆炸了。類的繼承層次會越來越深、繼承關係會越來越複雜。而這種層次很深、很複雜的繼承關係,一方面,會導致代碼的可讀性變差。因為我們要搞清楚某個類具有哪些方法、屬性,必須閱讀父類的代碼、父類的父類的代碼……一直追溯到最頂層父類的代碼。另一方面,這也破壞了類的封裝特性,將父類的實現細節暴露給了子類。子類的實現依賴父類的實現,兩者高度耦合,一旦父類代碼修改,就會影響所有子類的邏輯。

總之,繼承最大的問題就在於:繼承層次過深、繼承關係過於複雜會影響到代碼的可讀性和可維護性。這也是為什麼我們不推薦使用繼承。那剛剛例子中繼承存在的問題,我們又該如何來解決呢?你可以先自己思考一下,再聽我下面的講解。

組合相比繼承有哪些優勢?

實際上,我們可以利用組合(composition)、接口、委託(delegation)三個技術手段,一塊兒來解決剛剛繼承存在的問題。

我們前面講到接口的時候說過,接口表示具有某種行為特性。針對“會飛”這樣一個行為特性,我們可以定義一個 Flyable 接口,只讓會飛的鳥去實現這個接口。對於會叫、會下蛋這些行為特性,我們可以類似地定義 Tweetable 接口、EggLayable 接口。

public interface Flyable {
  void fly();
}
public interface Tweetable {
  void tweet();
}
public interface EggLayable {
  void layEgg();
}
public class Ostrich implements Tweetable, EggLayable {//鴕鳥
  //... 省略其他屬性和方法...
  @Override
  public void tweet() { //... }
  @Override
  public void layEgg() { //... }
}
public class Sparrow impelents Flayable, Tweetable, EggLayable {//麻雀
  //... 省略其他屬性和方法...
  @Override
  public void fly() { //... }
  @Override
  public void tweet() { //... }
  @Override
  public void layEgg() { //... }
}

不過,我們知道,接口只聲明方法,不定義實現。也就是說,每個會下蛋的鳥都要實現一遍 layEgg() 方法,並且實現邏輯是一樣的,這就會導致代碼重複的問題。那這個問題又該如何解決呢?

我們可以針對三個接口再定義三個實現類,它們分別是:實現了 fly() 方法的 FlyAbility 類、實現了 tweet() 方法的 TweetAbility 類、實現了 layEgg() 方法的 EggLayAbility 類。然後,通過組合和委託技術來消除代碼重複。具體的代碼實現如下所示:

public interface Flyable {
  void fly();
}
public class FlyAbility implements Flyable {
  @Override
  public void fly() { //... }
}
//省略Tweetable/TweetAbility/EggLayable/EggLayAbility

public class Ostrich implements Tweetable, EggLayable {//鴕鳥
  private TweetAbility tweetAbility = new TweetAbility(); //組合
  private EggLayAbility eggLayAbility = new EggLayAbility(); //組合
  //... 省略其他屬性和方法...
  @Override
  public void tweet() {
    tweetAbility.tweet(); // 委託
  }
  @Override
  public void layEgg() {
    eggLayAbility.layEgg(); // 委託
  }
}

我們知道繼承主要有三個作用:表示 is-a 關係,支持多態特性,代碼復用。而這三個作用都可以通過其他技術手段來達成。比如 is-a 關係,我們可以通過組合和接口的 has-a 關係來替代;多態特性我們可以利用接口來實現;代碼復用我們可以通過組合和委託來實現。所以,從理論上講,通過組合、接口、委託三個技術手段,我們完全可以替換掉繼承,在項目中不用或者少用繼承關係,特別是一些複雜的繼承關係。

如何判斷該用組合還是繼承?

儘管我們鼓勵多用組合少用繼承,但組合也並不是完美的,繼承也並非一無是處。從上面的例子來看,繼承改寫成組合意味着要做更細粒度的類的拆分。這也就意味着,我們要定義更多的類和接口。類和接口的增多也就或多或少地增加代碼的複雜程度和維護成本。所以,在實際的項目開發中,我們還是要根據具體的情況,來具體選擇該用繼承還是組合。

如果類之間的繼承結構穩定(不會輕易改變),繼承層次比較淺(比如,最多有兩層繼承關係),繼承關係不複雜,我們就可以大膽地使用繼承。反之,系統越不穩定,繼承層次很深,繼承關係複雜,我們就盡量使用組合來替代繼承。

除此之外,還有一些設計模式會固定使用繼承或者組合。比如,裝飾者模式(decorator pattern)、策略模式(strategy pattern)、組合模式(composite pattern)等都使用了組合關係,而模板模式(template pattern)使用了繼承關係。

前面我們講到繼承可以實現代碼復用。利用繼承特性,我們把相同的屬性和方法,抽取出來,定義到父類中。子類復用父類中的屬性和方法,達到代碼復用的目的。但是,有的時候,從業務含義上,A 類和 B 類並不一定具有繼承關係。比如,Crawler 類和 PageAnalyzer 類,它們都用到了 URL 拼接和分割的功能,但並不具有繼承關係(既不是父子關係,也不是兄弟關係)。僅僅為了代碼復用,生硬地抽象出一個父類出來,會影響到代碼的可讀性。如果不熟悉背後設計思路的同事,發現 Crawler 類和 PageAnalyzer 類繼承同一個父類,而父類中定義的卻只是 URL 相關的操作,會覺得這個代碼寫得莫名其妙,理解不了。這個時候,使用組合就更加合理、更加靈活。具體的代碼實現如下所示:

public class Url {
  //...省略屬性和方法
}

public class Crawler {
  private Url url; // 組合
  public Crawler() {
    this.url = new Url();
  }
  //...
}

public class PageAnalyzer {
  private Url url; // 組合
  public PageAnalyzer() {
    this.url = new Url();
  }
  //..
}

還有一些特殊的場景要求我們必須使用繼承。如果你不能改變一個函數的入參類型,而入參又非接口,為了支持多態,只能採用繼承來實現。比如下面這樣一段代碼,其中 FeignClient 是一個外部類,我們沒有權限去修改這部分代碼,但是我們希望能重寫這個類在運行時執行的 encode() 函數。這個時候,我們只能採用繼承來實現了。

public class FeignClient { // feighn client框架代碼
  //...省略其他代碼...
  public void encode(String url) { //... }
}

public void demofunction(FeignClient feignClient) {
  //...
  feignClient.encode(url);
  //...
}

public class CustomizedFeignClient extends FeignClient {
  @Override
  public void encode(String url) { //...重寫encode的實現...}
}

// 調用
FeignClient client = new CustomizedFeignClient();
demofunction(client);

儘管有些人說,要杜絕繼承,100% 用組合代替繼承,但是我的觀點沒那麼極端!之所以“多用組合少用繼承”這個口號喊得這麼響,只是因為,長期以來,我們過度使用繼承。還是那句話,組合併不完美,繼承也不是一無是處。只要我們控制好它們的副作用、發揮它們各自的優勢,在不同的場合下,恰當地選擇使用繼承還是組合,這才是我們所追求的境界。

重點回顧

為什麼不推薦使用繼承?

繼承是面向對象的四大特性之一,用來表示類之間的 is-a 關係,可以解決代碼復用的問題。雖然繼承有諸多作用,但繼承層次過深、過複雜,也會影響到代碼的可維護性。在這種情況下,我們應該盡量少用,甚至不用繼承。

組合相比繼承有哪些優勢?

繼承主要有三個作用:表示 is-a 關係,支持多態特性,代碼復用。而這三個作用都可以通過組合、接口、委託三個技術手段來達成。除此之外,利用組合還能解決層次過深、過複雜的繼承關係影響代碼可維護性的問題。

如何判斷該用組合還是繼承?

儘管我們鼓勵多用組合少用繼承,但組合也並不是完美的,繼承也並非一無是處。在實際的項目開發中,我們還是要根據具體的情況,來選擇該用繼承還是組合。如果類之間的繼承結構穩定,層次比較淺,關係不複雜,我們就可以大膽地使用繼承。反之,我們就盡量使用組合來替代繼承。除此之外,還有一些設計模式、特殊的應用場景,會固定使用繼承或者組合。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※教你寫出一流的銷售文案?

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

※回頭車貨運收費標準

※別再煩惱如何寫文案,掌握八大原則!

※超省錢租車方案

※產品缺大量曝光嗎?你需要的是一流包裝設計!

.net core3.1 abp動態菜單和動態權限(動態菜單實現和動態權限添加) (三)

我們來創建動態菜單吧 

首先,先對動態菜單的概念、操作、流程進行約束:
1.Host和各個Tenant有自己的自定義菜單
2.Host和各個Tenant的權限與自定義菜單相關聯
2.Tenant有一套默認的菜單,規定對應的TenantId=-1,在添加租戶時自動將標準菜單和標準菜單的權限初始化到添加的租戶

一、先實現菜單在數據庫中的增刪改查

第一步:創建表、實體,添加DbContext

我們需要創建一個菜單表,延續Abp的命名方法,表名叫AbpMenus吧(菜單和權限、驗證我們要關聯,所以文件盡量放在Authorization文件夾下)

把創建的實體放在AbpLearn.Core/Authorization下面,新建一個Menus文件夾,再創建Menus實體

    public class AbpMenus : Entity<int>
    {
        public string MenuName { set; get; }
        public string PageName { set; get; }
        public string Name { set; get; }
        public string Url { set; get; }
        public string Icon { set; get; }
        public int ParentId { set; get; }
        public bool IsActive { set; get; }
        public int Orders { set; get; }
        public int? TenantId { set; get; }
    }
如果翻過源碼中實體的定義,可以發現很多實體的繼承,例如:

1.繼承接口 IMayHaveTenant,繼承後生成的sql語句將自動增加TenantId的查詢條件,表中必須包含TenantId列
2.繼承接口 IPassivable,繼承后表中必須包含IsActive列
3.繼承接口 FullAuditedEntity<TPrimaryKey> TPrimaryKey可以是long、int等值類型,必須包含IsDeleted、DeleterUserId、DeletionTime,其中這個接口
還繼承了AuditedEntity<TPrimaryKey>, IFullAudited, IAudited, ICreationAudited, IHasCreationTime, IModificationAudited, IHasModificationTime, IDeletionAudited, IHasDeletionTime, ISoftDelete,這些父類型、接口的定義自己F12就可以看到

 

AbpLearn.EntityFrameworkCore/EntityFrameworkCore/AbpLearnDbContext.cs增加DbSet

public class AbpLearnDbContext : AbpZeroDbContext<Tenant, Role, User, AbpLearnDbContext>
    {
        /* Define a DbSet for each entity of the application */
        
        public AbpLearnDbContext(DbContextOptions<AbpLearnDbContext> options)
            : base(options)
        {
            
        }

        public DbSet<AbpMenus> AbpMenus { set; get; }

    }

再去數據庫中添加AbpMenus表 字段長度請自行調整

DROP TABLE IF EXISTS `AbpMenus`;
CREATE TABLE `AbpMenus` (
`Id` int NOT NULL AUTO_INCREMENT,
`MenuName` varchar(50) DEFAULT NULL,
`PageName` varchar(50) DEFAULT NULL,
`LName` varchar(50) DEFAULT NULL,
`Url` varchar(50) DEFAULT NULL,
`Icon` varchar(20) DEFAULT NULL,
`ParentId` int DEFAULT NULL,
`IsActive` bit(1) NOT NULL DEFAULT b’0′,
`Orders` int DEFAULT NULL,
`TenantId` int DEFAULT NULL,
PRIMARY KEY (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;

第二步:添加Service和Dto

AbpLearn.Application/Authorization下添加Menus文件夾,然後添加IMenusAppService、MenusAppService,然後添加Dto文件夾

第三步:添加控制器和前台頁面、js

Controller文件,MenusController.cs

 

 前台添加Menus及對應的js文件,可以簡單省事的把其他文件夾複製粘貼一份,然後關鍵詞修改下

這些文件太多了,我會把這套代碼上傳到github中,文章最低部會把鏈接掛出來

添加完之後我們就可以生成預覽一下Menus,因為SetNavigation中未將Menus的url加進去,我們自己手打鏈接進入

 

 

此時, 我們的菜單這一塊的crud已經做好了,我們可以看到有一個Host管理員這個部分是什麼意思哪?

我們為了在當前Host中可以控制所有租戶的菜單和權限,將當前Host、標準菜單、租戶做一個select,代碼如下

    public class ChangeModalViewModel
    {
        public int? TenantId { get; set; }

        public string TenancyName { get; set; }

        public int? TenantMenuType { get; set; }


        public List<ComboboxItemDto> TeneacyItems { get; set; }
    }
        public async Task<IActionResult> IndexAsync(int? id = 0)
        {
            var loginTenant = id <= 0 ? null : _tenantManager.GetById((int)id);

            var viewModel = new ChangeModalViewModel
            {
                TenancyName = loginTenant?.TenancyName,
                TenantId = id
            };

            viewModel.TeneacyItems = _tenantManager.Tenants
                .Select(p => new ComboboxItemDto(p.Id.ToString(), p.Name) { IsSelected = viewModel.TenancyName == p.TenancyName })
                .ToList();

            viewModel.TeneacyItems.Add(new ComboboxItemDto("0","Host管理員") { IsSelected = id == 0 });

            viewModel.TeneacyItems.Add(new ComboboxItemDto("-1", "默認菜單") { IsSelected = id == -1 });

            ViewBag.LoginInfo = await _sessionAppService.GetCurrentLoginInformations();

            return View(viewModel);
        }

然後在Index.cshtml中添加或修改

@model ChangeModalViewModel  // 添加


  @await Html.PartialAsync(“~/Views/Menus/Index.AdvancedSearch.cshtml”, Model)  //修改

  

  @await Html.PartialAsync(“~/Views/Menus/_CreateModal.cshtml”,Model.TenantId)  //修改

  

  //添加

  $(“#ChangeTenancyName”).change(function (e) {
     location.href = “/Menus/Index/” + this.options[this.selectedIndex].value;
  });

修改_CreateModal.cshtml

@using Abp.Authorization.Users
@using Abp.MultiTenancy
@using AbpLearn.MultiTenancy
@using AbpLearn.Web.Models.Common.Modals
@model int
@{
    Layout = null;
}
<div class="modal fade" id="MenuCreateModal" tabindex="-1" role="dialog" aria-labelledby="MenuCreateModalLabel" data-backdrop="static">
    <div class="modal-dialog modal-lg" role="document">
        <div class="modal-content">
            @await Html.PartialAsync("~/Views/Shared/Modals/_ModalHeader.cshtml", new ModalHeaderViewModel(L("CreateNewMenu")))
            <form name="systemMenuCreateForm" role="form" class="form-horizontal">
                <div class="modal-body">
                    <div class="form-group row required">
                        <label class="col-md-3 col-form-label">@L("MenuName")</label>
                        <div class="col-md-9">
                            <input type="text" name="MenuName" class="form-control" required minlength="2">
                        </div>
                    </div>
                    <div class="form-group row required">
                        <label class="col-md-3 col-form-label">@L("LName")</label>
                        <div class="col-md-9">
                            <input type="text" name="LName" class="form-control" required>
                        </div>
                    </div>
                    <div class="form-group row required">
                        <label class="col-md-3 col-form-label">@L("Url")</label>
                        <div class="col-md-9">
                            <input type="text" name="Url" class="form-control">
                        </div>
                    </div>
                    <div class="form-group row">
                        <label class="col-md-3 col-form-label">@L("PageName")</label>
                        <div class="col-md-9">
                            <input type="text" name="PageName" class="form-control">
                        </div>
                    </div>
                    <div class="form-group row">
                        <label class="col-md-3 col-form-label">@L("ParentId")</label>
                        <div class="col-md-9">
                            <input type="text" name="ParentId" class="form-control">
                        </div>
                    </div>
                    <div class="form-group row">
                        <label class="col-md-3 col-form-label">@L("Orders")</label>
                        <div class="col-md-9">
                            <input type="text" name="Orders" class="form-control">
                        </div>
                    </div>
                    <div class="form-group row">
                        <label class="col-md-3 col-form-label" for="CreateMenuIsActive">@L("IsActive")</label>
                        <div class="col-md-9">
                            <input id="CreateMenuIsActive" type="checkbox" name="IsActive" value="true" checked />
                        </div>
                    </div>
                </div>
                <input type="hidden" name="TenantId" value="@(Model)" />
                @await Html.PartialAsync("~/Views/Shared/Modals/_ModalFooterWithSaveAndCancel.cshtml")
            </form>
        </div>
    </div>
</div>

View Code

 

修改_EditModal.cshtml

@using AbpLearn.Authorization.Menus.Dto
@using AbpLearn.Web.Models.Common.Modals
@model MenuDto
@{
    Layout = null;
}
@await Html.PartialAsync("~/Views/Shared/Modals/_ModalHeader.cshtml", new ModalHeaderViewModel(L("EditMenu")))
<form name="MenuEditForm" role="form" class="form-horizontal">
    <input type="hidden" name="Id" value="@Model.Id" />
    <div class="modal-body">
        <div class="form-group row required">
            <label class="col-md-3 col-form-label" for="tenancy-name">@L("MenuName")</label>
            <div class="col-md-9">
                <input id="tenancy-name" type="text" class="form-control" name="MenuName" value="@Model.MenuName" required maxlength="64" minlength="2">
            </div>
        </div>
        <div class="form-group row required">
            <label class="col-md-3 col-form-label" for="name">@L("LName")</label>
            <div class="col-md-9">
                <input id="name" type="text" class="form-control" name="LName" value="@Model.LName" required maxlength="128">
            </div>
        </div>
        <div class="form-group row required">
            <label class="col-md-3 col-form-label" for="name">@L("Url")</label>
            <div class="col-md-9">
                <input id="name" type="text" class="form-control" name="Url" value="@Model.Url" required maxlength="128">
            </div>
        </div>

        <div class="form-group row required">
            <label class="col-md-3 col-form-label" for="name">@L("PageName")</label>
            <div class="col-md-9">
                <input id="name" type="text" class="form-control" name="PageName" value="@Model.PageName" required maxlength="128">
            </div>
        </div>
        <div class="form-group row required">
            <label class="col-md-3 col-form-label" for="name">@L("ParentId")</label>
            <div class="col-md-9">
                <input id="name" type="text" class="form-control" name="ParentId" value="@Model.ParentId" required maxlength="128">
            </div>
        </div>
        <div class="form-group row required">
            <label class="col-md-3 col-form-label" for="name">@L("Orders")</label>
            <div class="col-md-9">
                <input id="name" type="text" class="form-control" name="Orders" value="@Model.Orders" required maxlength="128">
            </div>
        </div>
        <div class="form-group row">
            <label class="col-md-3 col-form-label" for="isactive">@L("IsActive")</label>
            <div class="col-md-9">
                <input id="isactive" type="checkbox" name="IsActive" value="true" @(Model.IsActive ? "checked" : "") />
            </div>
        </div>
    </div>
    @await Html.PartialAsync("~/Views/Shared/Modals/_ModalFooterWithSaveAndCancel.cshtml")
</form>

<script src="~/view-resources/Views/Menus/_EditModal.js" asp-append-version="true"></script>

View Code

修改Index.AdvancedSearch.cshtml

@using AbpLearn.Web.Views.Shared.Components.TenantChange
@using Abp.Application.Services.Dto
@model ChangeModalViewModel

    <div class="abp-advanced-search">
        <form id="MenusSearchForm" class="form-horizontal">
            <input type="hidden" name="TenantId" value="@Model.TenantId" />
            </form>
            <div class="form-horizontal">
                <div class="form-group">
                    @Html.DropDownList(
                       "ChangeTenancyNames",
                       Model.TeneacyItems.Select(i => i.ToSelectListItem()),
                       new { @class = "form-control edited", id = "ChangeTenancyName" })
                </div>
            </div>
    </div>

因為在abp裏面加載當前列表調用的是abp.services.app.menus.getAll方法,我們還需要對MenusAppService中的GetAllAsync做一下修改

    [Serializable]
    public class MenusPagedResultRequestDto: PagedResultRequestDto, IPagedAndSortedResultRequest
    {
        public virtual int? TenantId { get; set; }

        public virtual string Sorting { get; set; }

        public virtual bool ShowAll { get; set; }

    }
        #region 查詢全部菜單
        /// <summary>
        /// 查詢全部菜單
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public override async Task<PagedResultDto<MenuDto>> GetAllAsync(MenusPagedResultRequestDto input)
        {
            IQueryable<AbpMenus> query;

            query = CreateFilteredQuery(input).Where(o => o.TenantId == (input.TenantId == 0 ? null : input.TenantId));

            var totalCount = await AsyncQueryableExecuter.CountAsync(query);

            query = ApplySorting(query, input);
            if (!input.ShowAll) query = ApplyPaging(query, input);

            var entities = await AsyncQueryableExecuter.ToListAsync(query);

            return new PagedResultDto<MenuDto>(
                totalCount,
                entities.Select(MapToEntityDto).ToList()
            );
        }

        #endregion

這樣,我們在選中下面中的任意一個Tenant時,將會跳到對應的菜單裏面了

 

 

 

 我們先把Host管理員菜單和默認菜單配置一下

 

 

 

 

 

 

 

 

 

 

二、實現添加租戶時,初始化標準菜單和權限

首先我們找到添加租戶的地方,去TenantAppService裏面去找,可以看到有CreateAsync的重寫

        public override async Task<TenantDto> CreateAsync(CreateTenantDto input)
        {
            CheckCreatePermission();

            // Create tenant
            var tenant = ObjectMapper.Map<Tenant>(input);
            tenant.ConnectionString = input.ConnectionString.IsNullOrEmpty()
                ? null
                : SimpleStringCipher.Instance.Encrypt(input.ConnectionString);

            var defaultEdition = await _editionManager.FindByNameAsync(EditionManager.DefaultEditionName);
            if (defaultEdition != null)
            {
                tenant.EditionId = defaultEdition.Id;
            }

            await _tenantManager.CreateAsync(tenant);
            await CurrentUnitOfWork.SaveChangesAsync(); // To get new tenant's id.

            // Create tenant database
            _abpZeroDbMigrator.CreateOrMigrateForTenant(tenant);

            // We are working entities of new tenant, so changing tenant filter
            using (CurrentUnitOfWork.SetTenantId(tenant.Id))
            {
                // Create static roles for new tenant
                CheckErrors(await _roleManager.CreateStaticRoles(tenant.Id));

                await CurrentUnitOfWork.SaveChangesAsync(); // To get static role ids

                // Grant all permissions to admin role
                var adminRole = _roleManager.Roles.Single(r => r.Name == StaticRoleNames.Tenants.Admin);
                await _roleManager.GrantAllPermissionsAsync(adminRole);

                // Create admin user for the tenant
                var adminUser = User.CreateTenantAdminUser(tenant.Id, input.AdminEmailAddress);
                await _userManager.InitializeOptionsAsync(tenant.Id);
                CheckErrors(await _userManager.CreateAsync(adminUser, User.DefaultPassword));
                await CurrentUnitOfWork.SaveChangesAsync(); // To get admin user's id

                // Assign admin user to role!
                CheckErrors(await _userManager.AddToRoleAsync(adminUser, adminRole.Name));
                await CurrentUnitOfWork.SaveChangesAsync();
            }

            return MapToEntityDto(tenant);
        }

我們需要做的是,在 using (CurrentUnitOfWork.SetTenantId(tenant.Id)) 的內部尾部添加賦予菜單和權限的方法即可

賦予菜單和權限的方法我們分開寫,都放在MenusAppService中,

    public interface IMenusAppService : IAsyncCrudAppService<MenuDto, int, MenusPagedResultRequestDto, CreateMenuDto, MenuDto>
    {
        /// <summary>
        /// 賦予默認菜單
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        Task GiveMenusAsync(EntityDto<int> input);

        /// <summary>
        /// 賦予當前租戶Admin角色菜單權限
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        Task GivePermissionsAsync(EntityDto<int> input);
    }
        #region 賦予默認菜單
        public async Task GiveMenusAsync(EntityDto<int> input)
        {
            if (input.Id > 0)
            {
                var tenant = await _tenantManager.GetByIdAsync(input.Id);

                using (_unitOfWorkManager.Current.SetTenantId(tenant.Id))
                {
                    var query = CreateFilteredQuery(new MenusPagedResultRequestDto()).Where(o => o.TenantId == tenant.Id);

                    var systemMenus = await AsyncQueryableExecuter.ToListAsync(query);

                    if (!systemMenus.Any())
                    {
                        query = CreateFilteredQuery(new MenusPagedResultRequestDto()).Where(o => o.TenantId == -1);

                        var defaultMenus = await AsyncQueryableExecuter.ToListAsync(query);
                        if (defaultMenus.Any())
                        {
                            List<MenusInsert> GetMenusInserts(List<AbpMenus> abpMenus,int parentId = 0)
                            {
                                List<MenusInsert> menusInserts = new List<MenusInsert>();
                                foreach (var entity in abpMenus.Where(o => o.ParentId == parentId))
                                {
                                    var insert = new MenusInsert()
                                    {
                                        LName = entity.LName,
                                        MenuName = entity.MenuName,
                                        PageName = entity.PageName,
                                        Icon = entity.Icon,
                                        Url = entity.Url,
                                        IsActive = entity.IsActive,
                                        Orders = entity.Orders,
                                        ParentId = entity.ParentId,
                                        TenantId = tenant.Id
                                    };
                                    insert.menusInserts = GetMenusInserts(abpMenus, entity.Id);
                                    menusInserts.Add(insert);
                                }
                                return menusInserts;
                            }

                            async Task InsertMenusAsync(List<MenusInsert> inserts,int parentId = 0)
                            {
                                foreach (var insert in inserts)
                                {
                                    var entity = await CreateAsync(new AbpMenus()
                                    {
                                        LName = insert.LName,
                                        MenuName = insert.MenuName,
                                        PageName = insert.PageName,
                                        Icon = insert.Icon,
                                        Url = insert.Url,
                                        IsActive = insert.IsActive,
                                        Orders = insert.Orders,
                                        ParentId = parentId,
                                        TenantId = tenant.Id
                                    });
                                    if (insert.menusInserts.Any())
                                    {
                                        await InsertMenusAsync(insert.menusInserts, entity.Id);
                                    }
                                }
                            }
                            await InsertMenusAsync(GetMenusInserts(defaultMenus));
                            
                        }
                    }
                }
            }

        }
        #endregion


        #region 賦予當前租戶Admin角色菜單權限
        /// <summary>
        /// 賦予當前租戶Admin角色菜單權限
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public async Task GivePermissionsAsync(EntityDto<int> input)
        {
            if (input.Id > 0)
            {
                var tenant = await _tenantManager.GetByIdAsync(input.Id);

                using (_unitOfWorkManager.Current.SetTenantId(tenant.Id))
                {
                    var adminRoles = await _roleRepository.GetAllListAsync(o => o.Name == StaticRoleNames.Tenants.Admin && o.TenantId == tenant.Id);
                    if (adminRoles.FirstOrDefault() != null)
                    {
                        var adminRole = adminRoles.FirstOrDefault();

                        var query = CreateFilteredQuery(new MenusPagedResultRequestDto()).Where(o => o.TenantId == tenant.Id);

                        var systemMenus = await AsyncQueryableExecuter.ToListAsync(query);

                        var permissions = ConvertTenantPermissions(systemMenus);

                        //await _roleManager.ResetAllPermissionsAsync(adminRole.FirstOrDefault()); //重置授權

                        var active_BatchCount = 10;
                        var active_permissions = ConvertTenantPermissions(systemMenus.Where(o => o.IsActive).ToList());
                        for (int i = 0; i < active_permissions.Count(); i += 10)//每次后移5位
                        {
                            //await _roleManager.SetGrantedPermissionsAsync(adminRole.FirstOrDefault().Id, active_permissions.Take(active_BatchCount).Skip(i));
                            foreach (var notActive_permission in active_permissions.Take(active_BatchCount).Skip(i))
                            {
                                await _roleManager.GrantPermissionAsync(adminRole, notActive_permission);
                            }
                            active_BatchCount += 10;//每次從數組中選出N+10位,skip前N位
                        }

                        var notActive_BatchCount = 10;
                        var notActive_permissions = ConvertTenantPermissions(systemMenus.Where(o => !o.IsActive).ToList());
                        for (int i = 0; i < notActive_permissions.Count(); i += 10)//每次后移5位
                        {
                            foreach (var notActive_permission in notActive_permissions.Take(notActive_BatchCount).Skip(i))
                            {
                                await _roleManager.ProhibitPermissionAsync(adminRole, notActive_permission);
                            }
                            notActive_BatchCount += 10;//每次從數組中選出N+10位,skip前N位
                        }
                    }
                    else
                    {
                        throw new AbpDbConcurrencyException("未獲取到當前租戶的Admin角色!");
                    }
                }
            }
            else
            {
                var adminRoles = await _roleRepository.GetAllListAsync(o => o.Name == StaticRoleNames.Tenants.Admin && o.TenantId == null);
                if (adminRoles.FirstOrDefault() != null)
                {
                    var adminRole = adminRoles.FirstOrDefault();

                    var query = CreateFilteredQuery(new MenusPagedResultRequestDto()).Where(o => o.TenantId == null || o.TenantId == 0);

                    var systemMenus = await AsyncQueryableExecuter.ToListAsync(query);

                    //await _roleManager.ResetAllPermissionsAsync(adminRole.FirstOrDefault()); //重置授權

                    var active_BatchCount = 10;
                    var active_permissions = ConvertHostPermissions(systemMenus.Where(o => o.IsActive).ToList());
                    for (int i = 0; i < active_permissions.Count(); i += 10)//每次后移5位
                    {
                        //await _roleManager.SetGrantedPermissionsAsync(adminRole.FirstOrDefault().Id, active_permissions.Take(active_BatchCount).Skip(i));
                        foreach (var notActive_permission in active_permissions.Take(active_BatchCount).Skip(i))
                        {
                            await _roleManager.GrantPermissionAsync(adminRole, notActive_permission);
                        }
                        active_BatchCount += 10;//每次從數組中選出N+10位,skip前N位
                    }

                    var notActive_BatchCount = 10;
                    var notActive_permissions = ConvertHostPermissions(systemMenus.Where(o => !o.IsActive).ToList());
                    for (int i = 0; i < notActive_permissions.Count(); i += 10)//每次后移5位
                    {
                        foreach (var notActive_permission in notActive_permissions.Take(notActive_BatchCount).Skip(i))
                        {
                            await _roleManager.ProhibitPermissionAsync(adminRole, notActive_permission);
                        }
                        notActive_BatchCount += 10;//每次從數組中選出N+10位,skip前N位
                    }
                }
            }
        }

        public IEnumerable<Permission> ConvertTenantPermissions(IReadOnlyList<AbpMenus> systemMenus)
        {
            return systemMenus.Select(o => new Permission(o.PageName, L(o.MenuName), L(o.LName), MultiTenancySides.Tenant));
        }

        public IEnumerable<Permission> ConvertHostPermissions(IReadOnlyList<AbpMenus> systemMenus)
        {
            return systemMenus.Select(o => new Permission(o.PageName, L(o.MenuName), L(o.LName), MultiTenancySides.Host));
        }
        #endregion

TenantAppService.cs中做一下修改

        public override async Task<TenantDto> CreateAsync(CreateTenantDto input)
        {
            CheckCreatePermission();

            // Create tenant
            var tenant = ObjectMapper.Map<Tenant>(input);
            tenant.ConnectionString = input.ConnectionString.IsNullOrEmpty()
                ? null
                : SimpleStringCipher.Instance.Encrypt(input.ConnectionString);

            var defaultEdition = await _editionManager.FindByNameAsync(EditionManager.DefaultEditionName);
            if (defaultEdition != null)
            {
                tenant.EditionId = defaultEdition.Id;
            }

            await _tenantManager.CreateAsync(tenant);
            await CurrentUnitOfWork.SaveChangesAsync(); // To get new tenant's id.

            // Create tenant database
            _abpZeroDbMigrator.CreateOrMigrateForTenant(tenant);

            // We are working entities of new tenant, so changing tenant filter
            using (CurrentUnitOfWork.SetTenantId(tenant.Id))
            {
                // Create static roles for new tenant
                CheckErrors(await _roleManager.CreateStaticRoles(tenant.Id));

                await CurrentUnitOfWork.SaveChangesAsync(); // To get static role ids

                // Grant all permissions to admin role
                var adminRole = _roleManager.Roles.Single(r => r.Name == StaticRoleNames.Tenants.Admin);
                await _roleManager.GrantAllPermissionsAsync(adminRole);

                // Create admin user for the tenant
                var adminUser = User.CreateTenantAdminUser(tenant.Id, input.AdminEmailAddress);
                await _userManager.InitializeOptionsAsync(tenant.Id);
                CheckErrors(await _userManager.CreateAsync(adminUser, User.DefaultPassword));
                await CurrentUnitOfWork.SaveChangesAsync(); // To get admin user's id

                // Assign admin user to role!
                CheckErrors(await _userManager.AddToRoleAsync(adminUser, adminRole.Name));
                await CurrentUnitOfWork.SaveChangesAsync();

                await _menusAppService.GiveMenusAsync(new EntityDto<int>() { Id = tenant.Id });
                await CurrentUnitOfWork.SaveChangesAsync();

                await _menusAppService.GivePermissionsAsync(new EntityDto<int>() { Id = tenant.Id });
                await CurrentUnitOfWork.SaveChangesAsync();
            }

            return MapToEntityDto(tenant);
        }

現在我們添加租戶企業1、企業2

 

 

 

 現在菜單已經同步好了,我們去數據庫看下權限的同步

 

TenantId:

null是Host

1是abp頁面第一次加載時初始化的Default租戶

2是我之前添加的舊的企業1,那個時候方法沒寫好,就把2的刪掉了

3是企業2

4是企業1

由此可以看出,我們添加的菜單對應的PageName已經作為權限添加到權限表了

 

三、實現菜單修改后,權限賦予對應租戶

這一個其實在二里面已經寫好了,前台做一個按鈕,賦予權限,調用一下就好了

例如:

Index.cshtml   //為什麼要加getCurrentLoginInformationsOutput.Tenant == null的判斷?是因為租戶在進入菜單管理的地方,我們不給他們添加、賦予權限的權限

 

 在/wwwroot/view-resources/Views/Menus/Index.js中添加

    $(document).on('click', '#GivePermissions', function (e) {
        var tenantId = $(this).attr('data-tenant-id');

        abp.message.confirm(
            abp.utils.formatString(
                "是否賦予當前租戶管理員賬號所有權限?",
                "系統"
            ),
            null,
            (isConfirmed) => {
                if (isConfirmed) {
                    _menuService
                        .givePermissions({
                            id: tenantId
                        })
                        .done(() => {
                            abp.notify.info("操作成功!");
                            _$menusTable.ajax.reload();
                        });
                }
            }
        );
    });

四、實現菜單的動態加載

在https://www.cnblogs.com/wangpengzong/p/13089690.html中我們找到了菜單生成的地方,在最底部,通過NavigationManager來獲取到Menus,這裏其實有一個初始化方法(Initialize),調用的是AbpLearnNavigationProvider的SetNavigation方法來進行本地化,然後在

NavigationManager的非靜態構造函數中去獲取已經本地化的Menus,但是本地化Menus因為是在初始化時,程序的初始化我們無法獲取到當前的Tenant信息,所以只能將獲取Menus的地方推遲,放在倒數第二個類UserNavigationManager裏面的GetMenuAsync方法中,我們來看下GetMenuAsync
        public async Task<UserMenu> GetMenuAsync(string menuName, UserIdentifier user)
        {
            var menuDefinition = _navigationManager.Menus.GetOrDefault(menuName);
            if (menuDefinition == null)
            {
                throw new AbpException("There is no menu with given name: " + menuName);
            }

            var userMenu = new UserMenu(menuDefinition, _localizationContext);
            await FillUserMenuItems(user, menuDefinition.Items, userMenu.Items);
            return userMenu;
        }

第一句話獲取menuDefinition是關鍵點,我們將menuDefinition修改為從數據庫中獲取,在AbpLearn.Application/Authorization/Menus下添加UserNavigationManager.cs

using Abp; using Abp.Application.Features; using Abp.Application.Navigation; using Abp.Authorization; using Abp.Dependency; using Abp.Localization; using Abp.MultiTenancy; using Abp.Runtime.Session; using AbpLearn.Authorization.Menus.Dto; using AbpLearn.Sessions; using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; namespace AbpLearn.Authorization.Menus { public class UserNavigationManager : IUserNavigationManager, ITransientDependency { public IAbpSession AbpSession { get; set; } private readonly INavigationManager _navigationManager; private readonly ILocalizationContext _localizationContext; private readonly IIocResolver _iocResolver; private readonly IMenusAppService _menuAppService; private readonly ISessionAppService _sessionAppService; public IDictionary<string, MenuDefinition> Menus { get; private set; } public MenuDefinition MainMenu { get { return Menus["MainMenu"]; } } public UserNavigationManager( INavigationManager navigationManager, ILocalizationContext localizationContext, IMenusAppService menuAppService, ISessionAppService sessionAppService, IIocResolver iocResolver) { _navigationManager = navigationManager; _localizationContext = localizationContext; _iocResolver = iocResolver; AbpSession = NullAbpSession.Instance; _menuAppService = menuAppService; _sessionAppService = sessionAppService; } public async Task<UserMenu> GetMenuAsync(string menuName, UserIdentifier user) { var loginInfo = await _sessionAppService.GetCurrentLoginInformations(); Menus = new Dictionary<string, MenuDefinition> { {"MainMenu", new MenuDefinition("MainMenu", new LocalizableString("MainMenu", AbpConsts.LocalizationSourceName))} }; var lists = await _menuAppService.GetAllAsync(new MenusPagedResultRequestDto() { ShowAll = true, TenantId = (loginInfo.Tenant == null ? 0 : loginInfo.Tenant.Id) }); var ParentMenu = lists.Items.Where(k => k.IsActive).ToList().Where(x => x.ParentId == 0).ToList(); if (ParentMenu.Any()) { ParentMenu.ForEach(g => { var menu = new MenuItemDefinition( g.LName, MenuL(g.MenuName), g.Icon, g.Url, false, g.Orders ); BuildSubMenu(menu, g.Id, lists.Items.Where(k => k.IsActive).ToList()); MainMenu.AddItem(menu); }); } var menuDefinition = MainMenu; if (menuDefinition == null) { throw new AbpException("There is no menu with given name: " + menuName); } var userMenu = new UserMenu(); userMenu.Name = menuDefinition.Name; userMenu.DisplayName = menuDefinition.DisplayName.Localize(_localizationContext); userMenu.CustomData = menuDefinition.CustomData; userMenu.Items = new List<UserMenuItem>(); await FillUserMenuItems(user, menuDefinition.Items, userMenu.Items); return userMenu; } public async Task<IReadOnlyList<UserMenu>> GetMenusAsync(UserIdentifier user) { var userMenus = new List<UserMenu>(); foreach (var menu in _navigationManager.Menus.Values) { userMenus.Add(await GetMenuAsync(menu.Name, user)); } return userMenus; } public void BuildSubMenu(MenuItemDefinition menu, int parentId, List<MenuDto> list) { var nList = list.Where(x => x.ParentId == parentId).ToList(); if (nList != null && nList.Count > 0) { nList.ForEach(g => { var subMenu = new MenuItemDefinition( g.PageName, MenuL(g.MenuName), g.Icon, g.Url, false, g.Orders ); menu.AddItem(subMenu); BuildSubMenu(subMenu, g.Id, list); }); } } private static ILocalizableString MenuL(string name) { return new LocalizableString(name, AbpLearnConsts.LocalizationSourceName); } private async Task<int> FillUserMenuItems(UserIdentifier user, IList<MenuItemDefinition> menuItemDefinitions, IList<UserMenuItem> userMenuItems) { //TODO: Can be optimized by re-using FeatureDependencyContext. var addedMenuItemCount = 0; using (var scope = _iocResolver.CreateScope()) { var permissionDependencyContext = scope.Resolve<PermissionDependencyContext>(); permissionDependencyContext.User = user; var featureDependencyContext = scope.Resolve<FeatureDependencyContext>(); featureDependencyContext.TenantId = user == null ? null : user.TenantId; foreach (var menuItemDefinition in menuItemDefinitions) { if (menuItemDefinition.RequiresAuthentication && user == null) { continue; } if (menuItemDefinition.PermissionDependency != null && (user == null || !(await menuItemDefinition.PermissionDependency.IsSatisfiedAsync(permissionDependencyContext)))) { continue; } if (menuItemDefinition.FeatureDependency != null && (AbpSession.MultiTenancySide == MultiTenancySides.Tenant || (user != null && user.TenantId != null)) && !(await menuItemDefinition.FeatureDependency.IsSatisfiedAsync(featureDependencyContext))) { continue; } var userMenuItem = new UserMenuItem(menuItemDefinition, _localizationContext); if (menuItemDefinition.IsLeaf || (await FillUserMenuItems(user, menuItemDefinition.Items, userMenuItem.Items)) > 0) { userMenuItems.Add(userMenuItem); ++addedMenuItemCount; } } } return addedMenuItemCount; } } }

 

然後在Mvc項目的Startup.cs/ConfigureServices下增加

            services.AddScoped<IUserNavigationManager, UserNavigationManager>();

因為在abp中菜單被做做成了模塊,在程序初始化時模塊添加進去,但是我們將菜單修改成了每次讀取數據庫加載,那麼我們就不需要加載這個模塊了

在mvc項目的AbpLearnWebMvcModule.cs註釋下面這句話

            //Configuration.Navigation.Providers.Add<AbpLearnNavigationProvider>();

將AbpLearnNavigationProvider.cs/SetNavigation方法的內容全部註釋掉

預覽一下mvc,用Host登錄一下

 

 用企業1登陸下,登錄切換Host和Tenant,是在登錄界面 Current tenant: 未選 (Change) 點擊Change,在彈框中輸入 E1(因為上面設置的企業1標識是E1),點擊save,頁面刷新后就變為了 Current tenant: E1 (Change) ,輸入賬號密碼登錄

 

 

 

 OK,我們的動態菜單已經完成了

 

添加jstree

 當然,我的菜單使用的是table來显示,你也可以使用tree來,我找到了一個jstree,下面修改一下

MenusAppService.cs

        #region 獲取當前賬戶的菜單樹
        /// <summary>
        /// 獲取當前賬戶的菜單樹
        /// </summary>
        /// <param name="input"></param>
        /// <returns></returns>
        public async Task<string> GetTreeAsync(MenusPagedResultRequestDto input)
        {
            var query = CreateFilteredQuery(new MenusPagedResultRequestDto()).Where(o => o.TenantId == input.TenantId);

            var systemMenus = await AsyncQueryableExecuter.ToListAsync(query);

            var childJObject = new JObject();
            var openJObject = new JObject();
            openJObject.Add("opened", true);
            childJObject.Add("id", 0);
            childJObject.Add("text", "根目錄");
            childJObject.Add("icon", "");
            childJObject.Add("state", openJObject);
            childJObject.Add("children", GetJArray(systemMenus, 0));
            return childJObject.ToString();
        }

        #region 獲取目錄Array
        /// <summary>
        /// 獲取目錄Array
        /// </summary>
        /// <param name="systemMenus"></param>
        /// <param name="parentdId"></param>
        /// <returns></returns>
        private JArray GetJArray(List<AbpMenus> systemMenus, int parentdId)
        {
            JArray jArray = new JArray();
            foreach (var menu in systemMenus.Where(o => o.ParentId == parentdId))
            {
                var jObject = new JObject();
                jObject.Add("id", menu.Id);
                jObject.Add("text", menu.MenuName);
                jObject.Add("icon", menu.Icon);
                //jObject.Add("state", menu.Icon);
                if (systemMenus.Any(o => o.ParentId == menu.Id))
                {
                    jObject.Add("children", GetJArray(systemMenus, menu.Id));
                }
                jArray.Add(jObject);
            }
            return jArray;
        }

        #endregion

        #endregion

 

 前端Index.cshtml  jstree去https://github.com/vakata/jstree/zipball/3.3.8下載,下載后在mvc項目的wwwroot文件夾下添加jstree文件夾,下載文件的src裏面內容全部賦值到jstree文件夾

註釋掉table標籤

添加jstree1

例如:

@section styles
{
    <link href="~/jstree/themes/default/style.css" rel="stylesheet" />
}                     

<div id="jstree1" style="width:100%;"></div> @section scripts { <environment names="Development"> <script src="~/view-resources/Views/Menus/Index.js" asp-append-version="true"></script> </environment> <environment names="Staging,Production"> <script src="~/view-resources/Views/Menus/Index.min.js" asp-append-version="true"></script> </environment> <script type="application/javascript" src="~/jstree/jstree.js"></script> <script type="application/javascript" src="~/jstree/jstree.contextmenu.js"></script> <script type="text/javascript"> $(function () { var _menuService = abp.services.app.menus; l = abp.localization.getSource('A_b_p'); $('#jstree1').jstree({ "core": { "data": function (node, callback) { var filter = $('#MenusSearchForm').serializeFormToObject(true); this, _menuService.getTree(filter).done(function (result) { callback.call(this, JSON.parse(result)); }); }, "themes": { "variant": "large",//加大 "ellipsis": true //文字多時省略 }, "check_callback": true, }, "plugins": ["contextmenu", "wholerow", "themes"],//"checkbox" "contextmenu": { select_node: false, show_at_node: true, "items": { "create": { "label": "新增子菜單", "action": function (obj) { var inst = jQuery.jstree.reference(obj.reference); var clickedNode = inst.get_node(obj.reference); if (parseInt(clickedNode.original.id) >= 0) { $("#ParentId").val(clickedNode.original.id); $("#MenuCreateModal").modal(); } else { abp.notify.info("父節點獲取出錯"); } }, }, "rename": { "label": "修改", "action": function (obj) { var inst = jQuery.jstree.reference(obj.reference); var clickedNode = inst.get_node(obj.reference); if (parseInt(clickedNode.original.id) >= 0) { abp.ajax({ url: abp.appPath + 'Menus/EditModal?menuId=' + clickedNode.original.id, type: 'POST', dataType: 'html', success: function (content) { $("#MenuEditModal").modal(); $('#MenuEditModal div.modal-content').html(content); }, error: function (e) { } }); } else { abp.notify.info("菜單獲取出錯"); } } }, "delete": { "label": "更改菜單狀態", "action": function (obj) { var inst = jQuery.jstree.reference(obj.reference); var clickedNode = inst.get_node(obj.reference); abp.message.confirm( abp.utils.formatString("是否" + (clickedNode.original.state.disabled?"啟用":"禁用") + "當前菜單:" + clickedNode.original.text + "?"), null, (isConfirmed) => { if (isConfirmed) { _menuService .delete({ id: clickedNode.original.id }) .done(() => { abp.notify.info(l('SuccessfullyDeleted')); location.reload(); }); } } ); }, } } } }).on('select_node.jstree', function (event, data) { console.log(data.node); }).on('changed.jstree', function (event, data) { console.log("-----------changed.jstree"); console.log("action:" + data.action); console.log(data.node); }); }); </script> }

 

 預覽一下吧

 

 

github地址

本文github:https://github.com/wangpengzong/AbpLearn

下一篇開始動態權限

 吐槽區域(寫的不好、不對,歡迎吐槽)

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※超省錢租車方案

※別再煩惱如何寫文案,掌握八大原則!

※回頭車貨運收費標準

※教你寫出一流的銷售文案?

※產品缺大量曝光嗎?你需要的是一流包裝設計!

※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

北極最大冰架裂解!格陵蘭冰川110平方公里大冰塊脫離

摘錄自2020年9月14日聯合報報導

英國廣播公司(BBC)報導,衛星畫面顯示,北極剩餘的最大冰架、位於格陵蘭東北部的79N冰川有一大塊脫離,脫離的面積大約有110平方公里,已碎成許多較小的冰塊。

德國埃朗恩 – 紐倫堡大學的極地研究員珍妮.特頓(Jenny Turton)表示:「自1980年以來,此地區的大氣層溫度上升大約攝氏3度。而在2019年到2020年間,這裡夏天的氣溫創新高。」79N冰川大約有80公里長,20公里寬,是格陵蘭東北冰流(Northeast Greenland Ice Stream)漂流的前端,從陸地流到海裡,成為浮冰。

而在79N冰川的前沿,分成兩條冰川,較小的支流往北走,這條支流稱為史帕特冰川(Spalte Glacier),而現在出現裂解的正是這條冰川。史帕特冰川2019年已出現嚴重裂縫,今年夏天的高溫成為致命一擊,現在史帕特冰川已成為冰山的浮冰。

氣候變遷
國際新聞
北極
冰川融化
冰架
全球暖化

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準

10萬買啥車?這5大終極問答能解決99.1%的選車難題!

還有划不划算。其實這裏還需考慮兩個問題:一、貨幣的貶值二、投資的收益第一點很簡單就是你今年花10萬元買的車這10萬元三年後可能就不值10萬元了又比如你貸10萬元買車這10萬元三年後可能也不值10萬元了可是這個必須考慮到貨幣的貶值率而按現在趨勢來說,是一直在貶值的第二點也舉個例子就是如果你有20萬元預算購車可是你只給10萬,貸款10萬再把剩下的10萬去投資(炒個房地產、買個理財產品什麼的)而當你所投資后得到的收益對於你10萬貸款所需的利息那這個貸款就很划算了可是具體購車貸款利率多少。

人生第一次買車

都有哪些情景?

情景一:

打開微信,找個比較懂車的朋友,上來就問

情景二:

找個汽車App或小程序,勾選條件篩出自己的意向車型

情景三:

先逛幾家4S店,看看哪個銷售妹子腿更長?

好吧,其實情況有很多種,可是最有效的方法應該是:打開微信→點擊公眾號→添加公眾號→搜索“車買買”→點擊關注,一系列購車選車用車評車信息任你看(這裏希望老闆可以看到,嘻嘻嘻),好吧,廢話說多了,直接上乾貨。

如果有足夠預算,就選擇進口車吧

要多大有多大、要多長有多長

要多快有多快、要多帥有多帥

然而理想和現實還是有很大差距的

畢竟不是每個人都叫思聰

而且還有一個姓王的爸爸

更多人還是得用自己辛苦賺來的錢

買一輛物有所值或物超所值的車

選擇自主或合資比起進口,性價比會更高一點吧

每次聊到自主和合資品牌

評論區似乎都兩極分化

要不就是以愛國的名義支持國產

要不就是要求更高的品質支持合資

在此虎哥就不參与任何政治言論了

(希望大家能理解)

不過只針對產品本身

虎哥的看法是:都可以買,喜歡就好

由於現在國人的消費水平越來越高

就算買到不好或者不合適自己的車

用一段時間賣掉好像也不是很虧

誰都說不準哪輛車的質量一定就好或壞

國產車也有五菱宏光這樣的神車

合資品牌也有很多斷軸燒機油的案例

你可以根據自己的見解去選

沒有見解,那自主跟合資可以一起考慮

相信每個男人都有個跑車夢吧?

可是跑車對於在座大部分人來說都不現實吧

我認為絕大部分跑車都只是男人們的大玩具

對於有錢又帥又單身的你,買來耍耍還是可以的

當然,今天虎哥並不是推薦你去買跑車上下班

而是告訴你除了轎車和SUV還有很多選擇

比如近期越來越火的MpV車型

裝得比轎車多,用起來比SUV更舒適更方便

比如越野能力和裝載能力都很強的皮卡

關於買轎車還是SUV的問答在網上實在太多

其實對於城市用車來說無非也就那幾個區別

轎車:操控好、加速快、更省油等等

SUV:坐姿高、視野好、裝得多等等

如果當你了解兩者的區別後還不會選

那你就很有必要去看看其它車型了

二手車性價比高是毋庸置疑的

不過二手車最大的問題就是“水太深”

對於不懂行的人來說沒有必要去嘗試

可是如果你有足夠的積累和經驗

肯定能用很低的價錢買到二手好車

(而且不用給中間商賺差價哦)

所以人生第一次買車

更多的是建議買新車

新車不僅可以給你更多的保障

而且還能給你一種超爽的新鮮感

就跟你小時候收到新玩具的感覺一樣

貸款不是都要收利息嗎?還有划不划算?

其實這裏還需考慮兩個問題:

一、貨幣的貶值

二、投資的收益

第一點很簡單

就是你今年花10萬元買的車

這10萬元三年後可能就不值10萬元了

又比如你貸10萬元買車

這10萬元三年後可能也不值10萬元了

可是這個必須考慮到貨幣的貶值率

而按現在趨勢來說,是一直在貶值的

第二點也舉個例子

就是如果你有20萬元預算購車

可是你只給10萬,貸款10萬

再把剩下的10萬去投資

(炒個房地產、買個理財產品什麼的)

而當你所投資后得到的收益

對於你10萬貸款所需的利息

那這個貸款就很划算了

可是具體購車貸款利率多少?

不同主機廠或經銷商所提供的政策不同

具體還需你親自去計算過才知道

而且那些說0%利率的優本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

台北網頁設計公司這麼多該如何選擇?

※智慧手機時代的來臨,RWD網頁設計為架站首選

※評比南投搬家公司費用收費行情懶人包大公開

※回頭車貨運收費標準