就不買捷達!10萬內這些車型性價比高得飛起_網頁設計公司

※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

台中景泰電動車行只是一個單純的理由,將來台灣的環境,出門可以自由放心的深呼吸,讓空氣回歸自然的乾淨,減少污染,留給我們下一代有好品質無空污的優質環境

帝豪GL的前臉設計非常具有辨識度,除了家族式的回紋格柵以外,整體性更強的設計風格讓帝豪GL看上去更加的和諧漂亮,側面小溜背的低風阻設計營造出了一種運動感,也讓整車看起來更加的修長,氣質更加穩重。帝豪GL使用的是兩款發動機,分別是1。

福睿斯可以說是目前在十萬價格區間裏面“非日系”用戶選擇最多的家用三廂車之一。類似於蒙迪歐的家族式大嘴前臉和中規中矩的造型讓很多首次購車的人群為他傾心,在十萬人民幣左右的價格區間裏面真的無車可選了么?其實不然。

雪佛蘭科沃茲

指導價格:7.99-10.99

或許已經有人知道我要說科沃茲,的確,科沃茲是雪佛蘭旗下的一款中國專供車型,但是同樣採用了雪佛蘭當下最新的家族式設計理念,外觀非常具有運動感和攻擊性,符合當下年輕人崇尚個性的選擇。

內飾層面與雪佛蘭科魯茲的相似度也是極高,一貫的設計語言彰顯出一定的檔次感,在使用層面上說,人機工程學的考慮也是非常細緻,使用便利性不錯,也讓科沃茲的與駕駛者之間的溝通感保持在一個較高的水平。

科沃茲的定位稍低於現款的全新科魯茲,但是作為一台家用車來說,科沃茲儘管使用的是扭力梁懸挂,但支撐性和底盤對於路面的顛簸過濾都做得很不錯,行駛中的底盤整合性給人有一種厚實的信心。1.5L的動力總成與英朗身上的一樣,不算突出,但是家用車來說也非常實用。

帝豪GL

指導價格:7.88-11.38萬

帝豪GL的出現可以說一定程度上樹立了緊湊型家用車的標杆,

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!

以設計的實用美學觀點,規劃出舒適、美觀的視覺畫面,有效提昇使用者的心理期待,營造出輕鬆、愉悅的網站瀏覽體驗。

或許會像如今的哈弗H6一樣,帝豪GL或許會在轎車領域成為十萬級別緊湊型家用車皆樹其為首席競爭對手的三廂轎車。

帝豪GL的前臉設計非常具有辨識度,除了家族式的回紋格柵以外,整體性更強的設計風格讓帝豪GL看上去更加的和諧漂亮,側面小溜背的低風阻設計營造出了一種運動感,也讓整車看起來更加的修長,氣質更加穩重。

帝豪GL使用的是兩款發動機,分別是1.3T和1.8L兩種排量,在這個小排量渦輪增壓發動機大行其道的時代背景下,吉利的1.3T發動機在動力輸出表現性和日常行駛當中的平順性還是有着挺深的造詣,1.3T的車型可以放心購買。

海馬福美來

指導價格:7.68-9.28萬(2017款)

福美來的知名度在國內還是非常高的車型,新款福美來上市先期只推出1.6L自吸版本的車型,新款售價在7.68萬-9.28萬元之間。

新款福美來的前臉設計感進行了革新,將原來的直瀑式格柵換成與車標線條更為整體和諧的橫置貫穿式格柵,從格柵處來說確實讓福美來看上去更精神。

福美來的內飾增強了科技感,更加與時代同步的設計讓注重時尚潮流的購車人群在用車時候增加了可玩性,只是將空調系統融入觸控屏的操控方式是否真的是便利性配置,個人存保留意見,畢竟空調的調節經過不止一次的實測物理的按鍵操作方式會更便捷。

全文總結:在十萬元左右的緊湊型家用車市場,自主品牌的車型成熟度已經非常高,如果是用車訴求僅僅只在家用層面,以代步通勤和平穩駕駛為主的話,目前自主品牌的性價比並不輸給大量合資,在滿大街的福睿斯作為街車的背景下,以上三款較新的車型不妨也可以作為各位近期有購車需求的潛在買家考慮的對象。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網站的第一印象網頁設計,決定了客戶是否繼續瀏覽的意願。台北網動廣告製作的RWD網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上它。

我看行!中國品牌的SUV已經開始趕超韓系車了!_台北網頁設計

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

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

不過這個在自主SUV就不存在這樣的問題了,由於相對較低成本優勢,使得自主SUV很多實用的配置都是標配,近期推出的車型更是開始配備全景攝像頭與ACC自適應巡航系統了。不相上下的動力系統不過在度過前期艱難的時期之後,一些實力雄厚的自主品牌投入了不少的研發資金,如今經過了多年的積累,可以看到最近比較熱門的SUV車型都開始推出自家研發的發動機。

前言

相信最近這幾年裡,自主品牌做車提升大家都是有目共睹的,而其中最具代表的就是目前非常紅火的SUV車型了,在SUV火了之後,自主品牌為了更好地適應市場的變化,更加詳細地了解消費者的需求,這段期間推出的SUV車型無論是顏值還是整體實用性都非常符合國人的要求。因此就不難看到每個月里汽車銷量榜單中,自主SUV佔據了非常大的市場份額。

而在之前的一段時間里,韓繫緊湊SUV都是憑藉著較高的顏值與超過的性價比佔據着SUV領域不少的市場份額,但事到如今韓系SUV已經漸顯頹勢,之前幾個月在前十的銷量榜單中都可以經常看到韓系SUV的身影,如今就剩下途勝了。究其原因是最近的自主SUV的品質能經得起市場的考驗。

提起韓系車,消費者對其印象都是性價比與顏值非常好,但如今韓系這個優勢在自主品牌在開始意識到汽車的外觀造型是消費者對其具有良好觀感的時候已經漸漸失去了,可以看到的是最近推出的熱門自主SUV車型都具有同樣的特點就是高顏值,先用漂亮的外觀去吸引消費者。

作為緊湊級SUV車型,韓系車畢竟是合資品牌,無論怎麼變化也不會把自己的身段放低,因此相比於自主SUV來說,韓系的定價仍然偏高。至於前些年消費者對自主品牌的車型不太注重的是其整體可靠性不算太好,而韓系SUV則具有超高的顏值與豐富的配置,

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

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

與德系日系SUV相比,韓系SUV整體性價比更高一些,不過如今自主SUV逐漸提升的品質使得消費者對其信心更足。

韓系SUV對於其他合資品牌的車型來說,前期不僅僅憑藉著較低的起步價吸引了不少資金不太充裕的消費者,而且還有超豐富的車內配置去吸引消費者,很多時候都是一些越級的配置使得很多人乍一看感覺非常有吸引力,但其實大多數還是高配車型才會配備。不過這個在自主SUV就不存在這樣的問題了,由於相對較低成本優勢,使得自主SUV很多實用的配置都是標配,近期推出的車型更是開始配備全景攝像頭與ACC自適應巡航系統了。

不過在度過前期艱難的時期之後,一些實力雄厚的自主品牌投入了不少的研發資金,如今經過了多年的積累,可以看到最近比較熱門的SUV車型都開始推出自家研發的發動機。而且就發動機的技術水平來說已經達到主流合資發動機的水準了,動力輸出也沒有太大的差距。

雖然目前很多自主SUV車型已經漸漸成為消費者的首要選擇的車型,這個我們可以在每個月的銷量榜單可以證明我們國內消費者對自主SUV的認可度越來越高了。雖然在多方面都感覺已經超出了韓系SUV不少,但韓系車的崛起不是沒有原因的,憑藉著多年的賽事積累,他們的調校技術仍然對自主SUV更勝一籌。而且調校經驗一直都是合資品牌藏起來的不願意透露的,我們只能慢慢的去積累調校經驗。因此目前一些熱門的自主SUV在行駛當中動力的匹配仍有很大的提高水平。

就目前的來說,可以說自主SUV已經登上主流消費者的首選,但是由於中國龐大的消費市場使得一旦某一個領域火了之後,自主品牌都是一窩蜂地上馬各種項目,因此目前市場上仍存在大量參差不起的產品。雖然一些自主品牌已經意識到只有品質才能取勝,投入大量的研發資金去研發出行,但是一些資金不太雄厚的廠商則仍然採用逆向研發,一些模仿的產品仍然大行其道,這樣下去只會影響消費者對自主品牌的整體形象,這對於目前正在上升態勢的自主SUV是一個很大的傷害。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

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

擁有後台管理系統的網站,將擁有強大的資料管理與更新功能,幫助您隨時新增網站的內容並節省網站開發的成本。

現在存好錢 下一年必買這些重磅SUV!_網頁設計公司

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

透過資料庫的網站架設建置,建立公司的形象或購物系統,並提供最人性化的使用介面,讓使用者能即時接收到相關的資訊

新發現依然有着變態的越野能力,最大涉水深度為900mm,還有5種地形選擇系統,足以應對各種複雜路面。發動機為2。0T、3。0T 柴油發動機和3。0T汽油發動機,變速箱為采埃孚的8AT。全新發現的定位發生了較大的變化,外觀更加清秀,內飾更加精緻,但是唯一不變的是越野性和實用的性。

SUV一直是國內的熱銷車型,為了滿足大家的喜好,小編就給大家找到幾款關注度比較高的,將會在下一年引進國內的熱門SUV,看看哪一個是你的菜。

奧迪Q5

Q5準確的來說應該是全新奧迪Q5,因為現款在售的Q5已經8歲了,算是比較老了,早就該換代了。全新Q5前段時間的巴黎車展首發,同時中國作為Q5最重要的市場,對Q5的銷量有着重大影響,所以新車型很快就會在中國進行投產。

外觀設計上全新Q5和現款Q5的變化不太大,畢竟現在都流行套娃的家族式設計,況且現款Q5的外觀設計也比較成功。所以新Q5還是繼承了現款Q5的經典外觀設計,只是全新的前進氣格柵和LED前大燈組使得新款的Q5看起來更加年輕,因為新Q7早在之前已經發布了,同時兄弟倆都是採用了奧迪最新的設計語言,所以Q5看起來更像是縮小版的Q7。

新Q5車身尺寸4660/1890/1660mm,軸距為2820mm,長度和軸距相比現款車型有所增加。因為使用了最新的MLB Evo平台,所以自身減重達到90kg,但是車身的剛度卻有了明顯的提升。內飾設計和新A4風格相同。乘坐空間和現款Q5差不多,表現一般。Q5將搭載2.0TFSI、2.0TDI及3.0TDI三款不同排量的發動機,匹配6擋手動或7速S tronic雙離合或8速自動變速箱。不過2.0T車型將會成為銷售主力。

售價方面估計會和現款Q5持平,競爭對手會是即將國產的寶馬X3,奔馳GLC,沃爾沃全新XC60等。

路虎(進口)-第五代發現

路虎發現系列一直硬派越野SUV,屬於肌肉猛男類型的,但是全新的發現變化很大,

※想知道最厲害的網頁設計公司嚨底家"!

RWD(響應式網頁設計)是透過瀏覽器的解析度來判斷要給使用者看到的樣貌

看起來甚至有點秀氣了。因為全新的發現採用了全新的設計語言,主要是為了向家庭用戶這些消費者靠攏。攬勝系列的定位是高端奢華行政系列,發現系列的定位一直都是硬派、實用、越野,但是全新的發現看起來更加秀氣,更像是一台加大的發現神行,遭到了很多人的吐槽。

新車的車身尺寸為4970/2073/1846mm,軸距為2923mm。由於新車外觀設計更加平滑,使得風阻係數僅為0.33,同時新平台的使用也使得新車比老款車減重22%。這將會極大的降低油耗。新發現依然有着變態的越野能力,最大涉水深度為900mm,還有5種地形選擇系統,足以應對各種複雜路面。發動機為2.0T、3.0T 柴油發動機和3.0T汽油發動機,變速箱為采埃孚的8AT。

全新發現的定位發生了較大的變化,外觀更加清秀,內飾更加精緻,但是唯一不變的是越野性和實用的性。競爭對手有XC90、X5等,售價將會和發現四價格接近。

全新標緻5008

5008曾經是標緻的一款7座MpV車型,但是隨着國內SUV的大賣,標緻將它變成了全新7座SUV車型,並且在下一年會落戶東風標緻進行國產。新車使用的平台為EMp2,使用該平台的還有3008、雪鐵龍C4 pICASSO等。

5008定位中型SUV。也是使用了最新的家族式設計語言,造型前衛,不過5008的長度只有4640mm,軸距為2840mm,所以作為7座SUV來說第三排空間表現會很局促。與漢蘭達、銳界相比還是很吃虧的。但是可以和斯柯達Kodiaq進行競爭。

國內5008預計將搭載1.6T汽油發動機,至於是否會提供1.8T發動機暫時還不清楚。變速箱將會為6速手動或6速自動變速箱。入門級價格估計會在20萬以為。

梅賽德斯-AMG GLC 43 Coupe

此車型作為GLC Coupe車型的性能版,主要競爭對手將會是寶馬X4 M40i。將會在2017年以進口的方式引入國內銷售。

新車和普通版GLC Coupe相比,擁有着更加張揚的外觀包圍套件,發動機為3.0L雙渦輪增壓V6發動機,最大功率367馬力,最大扭矩520牛·米,匹配9速自動變速箱,配備四驅系統,0-100km/h加速時間為4.9秒。

這款車適合追求速度與激情的消費者,個性十足,不過價格也會很動人,估計會在80萬元左右。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

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

當全世界的人們隨著網路時代而改變向上時您還停留在『網站美醜不重要』的舊有思維嗎?機會是留給努力改變現況的人們,別再浪費一分一秒可以接觸商機的寶貴時間!

免費音樂素材庫 UseMySound,各種免授權商用隨你下載_租車

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

日本、大陸,發現這些先進的國家已經早就讓電動車優先上路,而且先進國家空氣品質相當好,電動車節能減碳可以減少空污

網路的發達促進自媒體時代的到來,個人拍攝影片、錄製 Podcast 的門檻降低,人人都能是內容創作者,精心拍攝的影像畫面加上動聽的音樂可以從視覺與聽覺兩方面引人入勝,可是音樂素材的收集對一般大眾來說實在不容易啊!這回要為大家推薦一個免費音樂素材庫 UseMySound,好多音樂素材隨你用。

免費音樂素材庫 UseMySound,各種免授權商用隨你下載

想要找音效配在影片或鋪墊在 Podcast 背景中,讓內容更精彩,可是想要找到好音樂又不是那麼容易,實在叫人愁白了頭。UseMySound  免費音樂素材庫裡面所提供的各種音效包含背景音樂,種類相當繁多,正面輕鬆、動感搖擺,各種曲風應有盡有,最棒的是全都提供 CC0 授權,不管要用在影片、遊戲、廣告、Podcast 或是任何地方都能 100% 免費商用。
【前往 UseMySound 網站,點這裡】

有別於很多網站都會要求註冊登入,UseMySound 完全不需要上面這些程序就能直接試聽與下載,非常方便。每個音樂後面都會標註曲風,想要試聽音效只需點擊音效名稱前的播放圖示就可直接聆聽,喜歡想要下載則點選曲風後面的下載按鈕即可。該網站所下載的音效皆提供 MP3 格式,讓你在做後製合成時可直接使用。

當然,這個網站雖然是免費,但如果你覺得創作者的作品很棒,也可以點選後面的咖啡杯圖樣,以實際的金錢來贊助創作者,每杯咖啡 3 美元,以此為單位自己增加。另外,倘若你也想要讓自己創作的音效能夠供其他人免費下載使用,還可以將自己點選網頁右上角的「Support Project」來與網站方聯繫,將自己的音樂捐贈出來,造福它人。

※超省錢租車方案

商務出差、學生出遊、旅遊渡假、臨時用車!GO 神州租賃有限公司!合法經營、合法連鎖、合法租賃小客車!

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

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

有別於一般網頁架設公司,除了模組化的架站軟體,我們的營業主軸還包含:資料庫程式開發、網站建置、網頁設計、電子商務專案開發、系統整合、APP設計建置、專業網路行銷。

IKEA 直接賣「家」了,來看看這既環保又有型的 Tiny Home 小屋拖車_包裝設計

南投搬家公司費用需注意的眉眉角角,別等搬了再說!

上新台中搬家公司提供您一套專業有效率且人性化的辦公室搬遷、公司行號搬家及工廠遷廠的搬家服務

演而優則… 誒不是。總之,IKEA 居家產品賣著賣著,現在竟然也做起了「移動的家」露營拖車。重點是看起來還是非常地有瑞典風而且細節也是滿滿呢!繼續閱讀 IKEA 直接賣「家」了,來看看這既環保又有型的 Tiny Home 小屋拖車 報導內文。

▲圖片來源:IKEA

IKEA 直接賣「家」了,來看看這既環保又有型的 Tiny Home 小屋拖車

這個方正如貨櫃屋但又以很舒服外在色調環繞的 Tiny Home 小屋拖車。是 IKEA 與 Vox Creative 合作打造的微屋 / 小房子 / Tiny Home 計畫。希望透過這個可永續、可負擔、低浪費且有型,小小僅約 5 坪多(187 平方英尺)的空間,證明任何人都可以無拘無束地實現更永續的生活。

▲圖片來源:IKEA

先前我們也有介紹過類似概念的「家」– 是個美到讓人會想要反問,這房子怎麼會有駕駛座的校車改造計畫。這次 IKEA 的 Tiny Home Project,則是以露營拖車的形式實現的小屋計畫,並且也確認成真,將可用美金 4.75 萬也就是約 135 萬台幣擁有。

雖說對於 IKEA 而言,小屋應該主要還是可以展示他們的居家產品的用途為主(還有個網站可以做虛擬「看房」)。但這由 Escape Vista Boho XL 拖車屋為基礎所訂製而成的 Tiny Home,還具備有包括太陽能板、可堆肥廁所系統等功能細節,並可依需提供包括熱水的功能(這部分的能源則由車輛提供)。

▲圖片來源:IKEA

而根據 NBC 的報導,這個以電力運作的小屋是以零碳排與零污染為概念所建成,包括內在建材與家具也都有考量到永續環保或回收材質,但也同時十分注重如桌面採可折疊收納的功能性與美觀 — 這就是 IKEA 展現實力的地方吧。

雖說可能得要仰賴其他能源的狀態下,很顯然要正常在這個小屋生活,基本上很難達到真正的零污染。但至少整個建造的低浪費與環保方向還是很值得鼓勵,也讓人看到了環保不見得就得要犧牲美觀與設計的可能。

本篇圖片 / 引用來源

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

窩窩觸角包含自媒體、自有平台及其他國家營銷業務等,多角化經營並具有國際觀的永續理念。

延伸閱讀:

支援 AirPods Max 磁吸休眠的三方皮革收納盒來了,連充電器也能完整裝入

電動車時代來臨?《消費者報告》調查逾 7 成消費者考慮購入電動車

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

網動廣告出品的網頁設計,採用精簡與質感的CSS語法,提升企業的專業形象與簡約舒適的瀏覽體驗,讓瀏覽者第一眼就愛上她。

外媒票選 2020 年最討人厭手機設計,湊數用鏡頭勇奪第一_台中搬家

台中搬家遵守搬運三大原則,讓您的家具不再被破壞!

台中搬家公司推薦超過30年經驗,首選台中大展搬家

2021 年的開始也是 2020 年的結束,過去一年的智慧型手機上出現眾多讓人驚豔的設計,也有許多讓消費者覺得無言的設計,從中當然也看到許多新趨勢的崛起,其中當然也有大家其實並不想看見的設計。國外媒體發起了一項開放式票選,最不受消費者青睞的前三名在 2021 年應該會有越來越多手機具備。

外媒票選 2020 年最討人厭手機設計,湊數用鏡頭勇奪第一

在 GSMArena 票選結果公布後,無用的相機模組成為現在智慧型手機中最受讀者討厭的設計。各家廠商比拚鏡頭數量的潮流其實並不是在 2020 年才開始有,但是像 ToF、語焉不詳的 AI 感測器,甚至微距鏡頭等,其實對一般消費者來說很少會用到,而且也不會讓手機看起來更好或有加分作用,這似乎與當初原廠手機設計的初衷有所出入。

最討人厭的第二名則是省略了幾乎所有隨機附贈的配件。Apple 一直是各家手機業者的風向球,在今年推出 iPhone 12 系列時,Apple 將包裝盒內的配件最少化,這點也勢必會引起各家業者的效仿,目前第一個跟隨者就是甫發表的小米 11,雖說它還提供了包含充電設備的加量不加價套裝,但在 2021 年應該會有更多廠商跟進。

而從網友發表的評論意見中,瀏海與打洞螢幕依然不是太討喜的設計,只是螢幕下前鏡頭的普及還有很長一段時間。且還有許多人對小型手機趕到不滿意,除了小型經濟實惠的機型,小型旗艦也不受寵。相比於小尺寸手機來說,更惱人的就是因為手機體型變小而跟著縮小的電池,在手機廠商陷入了快充速度競爭中,雖說 100W 充電能夠成為媒體競相報導的頭條,但對大多數用戶來說,更希望有不需要經常充電的手機。

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

上面這些不被喜歡的設計卻很有可能是 2021 年變成常態,下面與大家分享一下整體的投票結果,對你來說最不喜歡的設計會是什麼呢?

◎資料來源:GSMArena

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

台中搬家公司費用怎麼算?

擁有20年純熟搬遷經驗,提供免費估價且流程透明更是5星評價的搬家公司

販售僅兩週! 蘋果iPhone 12成為全球5G手機銷量冠軍_台中搬家公司

※推薦台中搬家公司優質服務,可到府估價

台中搬鋼琴,台中金庫搬運,中部廢棄物處理,南投縣搬家公司,好幫手搬家,西屯區搬家

蘋果今年新款iPhone 12系列手機,分別在今年10月、11月上市。而由於其中每款手機都支援5G頻段,這也讓iPhone 12系列手機問世之後,隨即攻佔5G手機銷售寶座。現在就有市場調查機構資料顯示,iPhone 12 系列手機在上市兩周後,就成為全球最熱銷的5G手機。

根據《Counterpoint Research》最新一份調查報告顯示,iPhone 12與iPhone 12 Pro在10月上市之後,僅靠兩周的銷量就拿下該月份的銷售冠軍,銷售量分別是16%與8%,而這兩支手機的銷量,也占據了該月份5G手機全球銷量的四分之一。

或許有人說這是新機優勢,剛上市本來就會賣得比較好。但該報告中也把iPhone 12與iPhone 12 Pro的銷量拿去與2020年1至10月的5G手機銷量做比較,在整體銷售排行榜中,iPhone 12單憑10月的兩周銷量,就拿下該排行榜的第7名,由此可見iPhone 12超誇張的銷售結果。

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

報告中分析,iPhone 12之所以會有這麼強勁的銷售成果,主要是因為消費者對5G開始好奇、需求開始提升。而且全球各大電信業者都針對iPhone 12推出促銷活動,而透過促銷活動所販售的iPhone 12 與 iPhone 12 Pro 就占當月美國銷量的33%以上,其中也包含了許多電信商推出的零元電信配套方案。

而且相較於部分Android品牌的5G手機,發展上有一些地域性的限制,iPhone歷來都在全球140多個國家販售,市場覆蓋層面比起任何一家Android手機廠商還要更廣,因此可以在初期取得極佳的銷售成績。

您也許會喜歡:

【推爆】終身$0月租 打電話只要1元/分

立達合法徵信社-讓您安心的選擇

台中搬家公司教你幾個打包小技巧,輕鬆整理裝箱!

還在煩惱搬家費用要多少哪?台中大展搬家線上試算搬家費用,從此不再擔心「物品怎麼計費」、「多少車才能裝完」

運用惰性刪除和定時刪除實現可過期的localStorage緩存_網頁設計

網頁設計最專業,超強功能平台可客製化

窩窩以「數位行銷」「品牌經營」「網站與應用程式」「印刷品設計」等四大主軸,為每一位客戶客製建立行銷脈絡及洞燭市場先機。

localStorage簡介

使用localStorage可以在瀏覽器中存儲鍵值對的數據。經常被和localStorage一併提及的是sessionStorage,它們都可以在當瀏覽器中存儲鍵值對的數據。但是它們之間的區別是:存儲在localStorage的數據可以長期保留;而當頁面會話結束(也就是當頁面被關閉)時,存儲在sessionStorage的數據會被清除。

另外需要注意的是,localStorage中的鍵值對總是以字符串的形式存儲,並且只能訪問當前域名下的數據,不能跨域名訪問。

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

localStorage方法

可以通過setItem方法增加了一個鍵值對數據,比如:

localStorage.setItem('name', 'OneMore');

如果該鍵已經存在,那麼該鍵對應的值將被覆蓋。還可以使用getItem方法讀取對應鍵的值數據,比如:

var name = localStorage.getItem('name');

可以使用removeItem方法移除對應的鍵,比如:

localStorage.removeItem('name');

也可以使用clear方法移除當前域名下所有的鍵值對數據,比如:

localStorage.clear();

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

可過期的localStorage緩存

正如上面所提到的,localStorage只能用於長久保存整個網站的數據,保存的數據沒有過期時間,直到手動去刪除。所以要實現可過期的localStorage緩存的中重點就是:如何清理過期的緩存?

惰性刪除

惰性刪除是指,某個鍵值過期后,該鍵值不會被馬上刪除,而是等到下次被使用的時候,才會被檢查到過期,此時才能得到刪除。我們先來簡單實現一下:

※推薦評價好的iphone維修中心

擁有專業的維修技術團隊,同時聘請資深iphone手機維修專家,現場說明手機問題,快速修理,沒修好不收錢

var lsc = (function (self) {
    var prefix = 'one_more_lsc_'
    /**
     * 增加一個鍵值對數據
     * @param key 鍵
     * @param val 值
     * @param expires 過期時間,單位為秒
     */
    self.set = function (key, val, expires) {
        key = prefix + key;
        val = JSON.stringify({'val': val, 'expires': new Date().getTime() + expires * 1000});
        localStorage.setItem(key, val);
    };
    /**
     * 讀取對應鍵的值數據
     * @param key 鍵
     * @returns {null|*} 對應鍵的值
     */
    self.get = function (key) {
        key = prefix + key;
        var val = localStorage.getItem(key);
        if (!val) {
            return null;
        }
        val = JSON.parse(val);
        if (val.expires < new Date().getTime()) {
            localStorage.removeItem(key);
            return null;
        }
        return val.val;
    };
    return self;
}(lsc || {}));

上述代碼通過惰性刪除已經實現了可過期的localStorage緩存,但是也有比較明顯的缺點:如果一個key一直沒有被用到,即使它已經過期了也永遠存放在localStorage。為了彌補這樣缺點,我們引入另一種清理過期緩存的策略。

歡迎關注微信公眾號:萬貓學社,每周一分享Java技術乾貨。

定時刪除

定時刪除是指,每隔一段時間執行一次刪除操作,並通過限制刪除操作執行的次數和頻率,來減少刪除操作對CPU的長期佔用。另一方面定時刪除也有效的減少了因惰性刪除帶來的對localStorage空間的浪費。

每隔一秒執行一次定時刪除,操作如下:

  1. 隨機測試20個設置了過期時間的key。
  2. 刪除所有發現的已過期的key。
  3. 若刪除的key超過5個則重複步驟1,直至重複500次。

具體實現如下:

var lsc = (function (self) {
    var prefix = 'one_more_lsc_'
    var list = [];
    //初始化list
    self.init = function () {
        var keys = Object.keys(localStorage);
        var reg = new RegExp('^' + prefix);
        var temp = [];
        //遍歷所有localStorage中的所有key
        for (var i = 0; i < keys.length; i++) {
        	//找出可過期緩存的key
            if (reg.test(keys[i])) {
                temp.push(keys[i]);
            }
        }
        list = temp;
    };
    self.init();
    self.check = function () {
        if (!list || list.length == 0) {
            return;
        }
        var checkCount = 0;
        while (checkCount < 500) {
            var expireCount = 0;
            //隨機測試20個設置了過期時間的key
            for (var i = 0; i < 20; i++) {
                if (list.length == 0) {
                    break;
                }
                var index = Math.floor(Math.random() * list.length);
                var key = list[index];
                var val = localStorage.getItem(list[index]);
                //從list中刪除被惰性刪除的key
                if (!val) {
                    list.splice(index, 1);
                    expireCount++;
                    continue;
                }
                val = JSON.parse(val);
                //刪除所有發現的已過期的key
                if (val.expires < new Date().getTime()) {
                    list.splice(index, 1);
                    localStorage.removeItem(key);
                    expireCount++;
                }
            }
            //若刪除的key不超過5個則跳出循環
            if (expireCount <= 5 || list.length == 0) {
                break;
            }
            checkCount++;
        }
    }
    //每隔一秒執行一次定時刪除
    window.setInterval(self.check, 1000);
    return self;
}(lsc || {}));

完整源碼及使用示例

完整源碼及使用示例已上傳到我的GitHub(https://github.com/heihaozi/LocalStorageCache)上,感謝各位小夥伴的Star和Fork。

總結

一種策略可能會有自己的缺點,為了規避相應的缺點,我們可以合理運用多種策略,揚長避短就得到接近完美的解決方案。

微信公眾號:萬貓學社

微信掃描二維碼

獲得更多Java技術乾貨

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

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

網動是一群專業、熱情、向前行的工作團隊,我們擁有靈活的組織與溝通的能力,能傾聽客戶聲音,激發創意的火花,呈現完美的作品

基於redis實現分佈式鎖_貨運

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

搬家價格與搬家費用透明合理,不亂收費。本公司提供下列三種搬家計費方案,由資深專業組長到府估價,替客戶量身規劃選擇最經濟節省的計費方式

目錄

  • 原理剖析
  • 實現
    • 編寫註解
    • 攔截器攔截
    • 上述提及工具
      • RedisLock
      • StockKeyGenerator
    • 問題分析
      • 業務處理時間>上鎖過期時間

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

網動結合了許多網際網路業界的菁英共同研發簡單易操作的架站工具,及時性的更新,為客戶創造出更多的網路商機。

系統的不斷擴大,分佈式鎖是最基本的保障。與單機的多線程不一樣的是,分佈式跨多個機器。線程的共享變量無法跨機器。

為了保證一個在高併發存場景下只能被同一個線程操作,java併發處理提供ReentrantLock或Synchronized進行互斥控制。但是這僅僅對單機環境有效。我們實現分佈式鎖大概通過三種方式。

  • redis實現分佈式鎖
  • 數據庫實現分佈式鎖
  • zk實現分佈式鎖
    今天我們介紹通過redis實現分佈式鎖。實際上這三種和java對比看屬於一類。都是屬於程序外部鎖。

原理剖析

  • 上述三種分佈式鎖都是通過各自為依據對各個請求進行上鎖,解鎖從而控制放行還是拒絕。redis鎖是基於其提供的setnx命令。
  • setnx當且僅當key不存在。若給定key已經存在,則setnx不做任何動作。setnx是一個原子性操作。
  • 和數據庫分佈式相比,因為redis內存輕量。所以redis分佈式鎖性能更好

實現

  • 原理很簡單。結合springboot項目我們實現一套通過註解形式對接口進行庫存上鎖案例進行理解

編寫註解

  • 我們編寫註解。方便我們在接口上添加註解提供攔截信息

/**
 * @author 張新華
 * @version V1.0
 * @Package com.ay.framework.order.redis.product
 * @date 2020年03月26日, 0026 10:29
 * @Copyright © 2020 安元科技有限公司
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface StockLock {

    /**
     * @author zxhtom
     * @Description 鎖key的前綴
     * @Date 15:25 2020年03月25日, 0025
     * @Param []
     * @return java.lang.String
     */
    String prefix() default "";
    /**
     * @author zxhtom
     * @Description key的分隔符
     * @Date 15:27 2020年03月25日, 0025
     * @Param []
     * @return java.lang.String
     */
    String delimiter() default ":";
}


/**
 * @author 張新華
 * @version V1.0
 * @Package com.ay.framework.order.redis.product
 * @date 2020年03月26日, 0026 11:09
 * @Copyright © 2020 安元科技有限公司
 */
@Target({ElementType.PARAMETER , ElementType.METHOD , ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface StockParam {
    /**
    * @author zxhtom
    * @Description 組成key
    * @Date 11:11 2020年03月26日, 0026
    * @Param []
    * @return java.lang.String[]
    */
    String[] names() default {""};
}

攔截器攔截

  • redis分佈式鎖實現的關鍵就是攔截器的編寫。上面的註解只是為了實現攔截的一個輔助。

@Around("execution(public * *(..)) && @annotation(com.ay.framework.order.redis.product.StockLock)")

  • 通過springboot的Around進行針對StockLock註解的攔截。通過攔截我們可以獲取到攔截的方法、參數、及需要的鎖的參數。
  • 我們獲取到需要鎖的名稱這裏叫做【a】之後通過redis的原子性操作對該key進行遞減操作。
  • 為了方便我們在削減庫存的時候可以對庫存進行更新操作。我們在遞減庫存前還需要藉助於另一把鎖。 這一把鎖我們叫做【a_key】
  • 換句話說我們接口想訪問就必須獲取【a】鎖,拿到【a】鎖需要減少庫存。減少庫存之前需要獲取【a_key】鎖。
  • 拿到鎖之後處理完邏輯之後我們需要釋放對應鎖。

RedisAtomicLong entityIdCounter = new RedisAtomicLong(lockKey, redisTemplate.getConnectionFactory());
    if (redisTemplate.hasKey(CoreConstants.UPDATEPRODUCTREDISLOCKKEY + lockKey)) {
        //表示lockKey的庫存信息有變動。此時無法進行交易
        throw new BusinessException("庫存變動。暫無法交易");
    }
    Long increment = entityIdCounter.decrementAndGet();
    if (increment >= 0) {
        try {
            Object proceed = pjp.proceed();
        } catch (Throwable throwable) {
            //所佔資源需要釋放回資源池
            while (!redisLock.tryGetLock(CoreConstants.UPDATEPRODUCTREDISLOCKKEY + lockKey, "")) {

            }
            //表示lockKey的庫存信息有變動。此時無法進行交易
            long l = entityIdCounter.incrementAndGet();
            if (l < 1) {
                redisTemplate.opsForValue().set(lockKey,1);
            }
            redisLock.unLock(CoreConstants.UPDATEPRODUCTREDISLOCKKEY + lockKey);
            throwable.printStackTrace();
        }
    } else {
        redisTemplate.opsForValue().set(lockKey,0);
        throw new BusinessException("庫存不足!無法操作");
    }

  • 因為我們上鎖就需要釋放鎖。但是程序在中途處理業務是發生異常導致沒有走到釋放鎖的步驟。這個時候就導致我們的分佈式鎖一直被鎖。俗稱【死鎖】。為了避免這種場景的發生。我們常常在上鎖的時候給一個有效期。有效期已過自動釋放鎖。這個特性恰好和redis的過期策略不摩爾和。

上述提及工具

RedisLock


public Boolean tryGetLock(String key , String value) {
    return tryGetLock(key, value, -1, TimeUnit.DAYS);
}
public Boolean tryGetLock(String key , String value, Integer expire) {
    return tryGetLock(key, value, expire, TimeUnit.SECONDS);
}
public Boolean tryGetLock(String key , String value, Integer expire , TimeUnit timeUnit) {
    ValueOperations operations = redisTemplate.opsForValue();
    if (operations.setIfAbsent(key, value)) {
        //說明 redis沒有該key , 換言之 加鎖成功  設置過期時間防止死鎖
        if (expire > 0) {
            redisTemplate.expire(key, expire, timeUnit);
        }
        return true;
    }
    return false;
}

public Boolean unLock(String key) {
    return redisTemplate.delete(key);
}

StockKeyGenerator


@Component()
@Primary
public class StockKeyGenerator implements CacheKeyGenerator {
    @Override
    public String getLockKey(ProceedingJoinPoint pjp) {
        //獲取方法簽名
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        Method method = signature.getMethod();
        //獲取方法cacheLock註解
        StockLock stockLock = method.getAnnotation(StockLock.class);
        //獲取方法參數
        Object[] args = pjp.getArgs();
        Parameter[] parameters = method.getParameters();
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < parameters.length; i++) {
            StockParam stockParam = parameters[i].getAnnotation(StockParam.class);
            Object arg = args[i];
            if (arg instanceof Map) {
                Map<String, Object> temArgMap = (Map<String, Object>) arg;
                String[] names = stockParam.names();
                for (String name : names) {
                    if (builder.length() > 0) {
                        builder.append(stockLock.delimiter());
                    }
                    builder.append(temArgMap.get(name));
                }
            }

        }
        return builder.toString();
    }
}

問題分析

  • 上面分析了一個死鎖的場景,理論上出了死鎖我們redis分佈鎖很好的解決了分佈式問題。但是還是會出現問題。下面列舉寫小編遇到的問題。

業務處理時間>上鎖過期時間

  • a線程獲取到鎖,開始進行業務處理需要8S,
  • 在8S內,鎖的有效期是5S,在鎖過期后也就是第6S , b線程進入開始獲取鎖這個時候b是可以獲取到新鎖的。這個時候就是有問題的。
  • 假設b線程業務處理只需要3S , 但是因為a線程釋放了鎖,所以在第8S的時候雖然b線程沒有釋放鎖,b的鎖也沒有過期但是這時候也沒有了鎖。從而導致C線程也可以進入

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

※回頭車貨運收費標準

宇安交通關係企業,自成立迄今,即秉持著「以誠待人」、「以實處事」的企業信念

關於vue的多頁面標籤功能,對於嵌套router-view緩存的最終無奈解決方法_網頁設計公司

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

節能減碳愛地球是景泰電動車的理念,是創立景泰電動車行的初衷,滿意態度更是服務客戶的最高品質,我們的成長來自於你的推薦。

最近寫我自己的後台開發框架,要弄一個多頁面標籤功能,之前有試過vue-element-admin的多頁面,以為很完美,就按它的思路重新寫了一個,但發現還是有問題的。

vue-element-admin它用的是在keep-alive組件上使用include屬性,綁定$store.state.tagsView.cachedViews,當點擊菜單時,往$store.state.tagsView.cachedViews添加頁面的name值,在標籤卡上點擊關閉后就從$store.state.tagsView.cachedViews裏面把緩存的name值刪除掉,這樣聽似乎沒什麼問題。但它無法很好的支持無限級別的子菜單的緩存。

目前vue-element-admin官方預覽地址的菜單結構大多是一級菜單分類,下面是二級子菜單。如下圖所示,它只能緩存二級子菜單,三級子菜單它緩存不了。為什麼會出現這個情況呢。因為嵌套router-view的問題。

 

 

 

按vue-element-admin的路由結構,它的一級菜單,其實對應的是一個layout組件,layout裏面有個router-view(稱它為一級router-view)它有用keep-alive包裹着,用來放二級菜單對應的頁面,所以對於二級菜單來說,它都是用同一個router-view。如果我需要創建三級菜單的話,那就需要在二級菜單目錄里創建一個包含router-view(稱它為二級router-view)的index.vue文件,用來放三級菜單對應的頁面,那麼你就會發現這個三級菜單的頁面怎麼也緩存不了。

 

因為只有一級router-view被keep-alive包裹起着緩存作用,下面的router-view它不緩存。當然我們也可以在二級的router-view也包一個keep-alive,也用include屬性,但你會發現也用不了,因為還要匹配name值,就是說二級router-view的文件也得寫上name值,寫上name值后你發現還是用不了,因為include數組裡面沒有這個二級router-view的name值,所以你還得在tabsView里的addView裏面做手腳,把路由所匹配到的所有路由的name值都添加到cachedViews里,然後還要在關閉時再進行處理。天啊。我想想都頭痛,理論是應該是可以實現的,但會增加了很多前端代碼量。

 

請注意!下面的方法也是有Bug的,請重點看下面的BUT開始部分

還好keep-alive還有另一個屬性exclude,我馬上就有思路了,而且非常簡潔,默認全部頁面進行緩存,所有的router-view都包一層keep-alive,只有在點擊標籤卡上的關閉按鈕時,往$store.state.sys.excludeViews添加關閉頁面的name值,下次打開后再從excludeViews裏面把頁面的name值刪除掉就行了,非常地簡單易懂,不過最底層的頁面,仍然需要寫上跟路由定義時完全匹配的name值。這一步我仍然想不到有什麼辦法可以省略掉。

為方便代碼,我寫了一個組件aliveRouterView組件,併合局註冊,這個組件用來代替router-view組件,如下面代碼所示,$store.state.sys.config.PAGE_TABS這個值是是否開戶多頁面標籤功能參數

<template>
  <keep-alive :exclude="exclude">
    <router-view />
  </keep-alive>
</template>
<script>
export default {
  computed: {
    exclude() {
      if (this.$store.state.sys.config.PAGE_TABS) {
        return this.$store.state.sys.excludeViews;
      } else {
        return /.*/;
      }
    }
  }
};
</script>

 

多頁面標籤組件viewTabs.vue,如下面代碼所示

<template>
  <div class="__common-layout-tabView">
    <el-scrollbar>
      <div class="__tabs">
        <div
          class="__tab-item"
          :class="{ '__is-active':item.name==$route.name }"
          v-for="item in viewRouters"
          :key="item.path"
          @click="onClick(item)"
        >
          {{item.meta.title}}
          <span
            class="el-icon-close"
            @click.stop="onClose(item)"
            :style="viewRouters.length<=1?'width:0;':''"
          ></span>
        </div>
      </div>
    </el-scrollbar>
  </div>
</template>
<script>
export default {
  data() {
    return {
      viewRouters: []
    };
  },
  watch: {
    $route: {
      handler(v) {
        if (!this.viewRouters.some(item => item.name == v.name)) {
          this.viewRouters.push(v);
        }
      },
      immediate: true
    }
  },
  methods: {
    onClick(data) {
      if (this.$route.fullPath != data.fullPath) {
        this.$router.push(data.fullPath);
      }
    },
    onClose(data) {
      let index = this.viewRouters.indexOf(data);
      if (index >= 0) {
        this.viewRouters.splice(index, 1);
        if (data.name == this.$route.name) {
          this.$router.push(this.viewRouters[index < 1 ? 0 : index - 1].path);
        }
        this.$store.dispatch("excludeView", data.name);
      }
    }
  }
};
</script>
<style lang="scss">
.__common-layout-tabView {
  $c-tab-border-color: #dcdfe6;
  position: relative;
  &::before {
    content: "";
    border-bottom: 1px solid $c-tab-border-color;
    position: absolute;
    left: 0;
    right: 0;
    bottom: 2px;
    height: 100%;
  }
  .__tabs {
    display: flex;
    .__tab-item {
      white-space: nowrap;
      padding: 8px 6px 8px 18px;
      font-size: 12px;
      border: 1px solid $c-tab-border-color;
      border-left: none;
      border-bottom: 0px;
      line-height: 14px;
      cursor: pointer;
      transition: color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1),
        padding 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
      &:first-child {
        border-left: 1px solid $c-tab-border-color;
        border-top-left-radius: 2px;
        margin-left: 10px;
      }
      &:last-child {
        border-top-right-radius: 2px;
        margin-right: 10px;
      }
      &:not(.__is-active):hover {
        color: #409eff;
        .el-icon-close {
          width: 12px;
          margin-right: 0px;
        }
      }
      &.__is-active {
        padding-right: 12px;
        border-bottom: 1px solid #fff;
        color: #409eff;
        .el-icon-close {
          width: 12px;
          margin-right: 0px;
          margin-left: 2px;
        }
      }
      .el-icon-close {
        width: 0px;
        height: 12px;
        overflow: hidden;
        border-radius: 50%;
        font-size: 12px;
        margin-right: 12px;
        transform-origin: 100% 50%;
        transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
        vertical-align: text-top;
        &:hover {
          background-color: #c0c4cc;
          color: #fff;
        }
      }
    }
  }
}
</style>

 

貼上我的sys的store文件,後面我發現,我把頁面name添加到excludeViews后,在下一幀中再從excludeViews中把name刪除后,這樣也能有效果。如下面excludeView所示。這樣就更加簡潔。我只需在關閉標籤卡時處理一下就行了。

const sys = {
    state: {
        permissionRouters: [],//權限路由表
        permissionMenus: [],//權限菜單列表
        config: null, //系統配置        
        excludeViews: [] //用於多頁面選項卡
    },
    getters: {

    },
    mutations: {
        SET_PERMISSION_ROUTERS(state, routers) {
            state.permissionRouters = routers;
        },
        SET_PERMISSION_MENUS(state, menus) {
            state.permissionMenus = menus;
        },
        SET_CONFIG(state, config) {
            state.config = config;
        },
        ADD_EXCLUDE_VIEW(state, viewName) {
            state.excludeViews.push(viewName);
        },
        DEL_EXCLUDE_VIEW(state, viewName) {
            let index = state.excludeViews.indexOf(viewName);
            if (index >= 0) {
                state.excludeViews.splice(index, 1);
            }
        }
    },
    actions: {
        //排除頁面
        excludeView({ state, commit, dispatch }, viewName) {
            if (!state.excludeViews.includes(viewName)) {
                commit("ADD_EXCLUDE_VIEW", viewName);
                Promise.resolve().then(() => {
                    commit("DEL_EXCLUDE_VIEW", viewName);
                })
            }
        }
    }
}
export default sys

 

效果如下圖所示,記得一點,就是得在你的頁面上填寫name值,需要跟定義路由時完全一致

 

BUT!!當我截完上面的動圖后,我就發現了問題了,而且是一個無法解決的問題,按我上面的方法,如果我點一下首頁,再點回原來的用戶管理,再關閉用戶管理,再打開用戶管理,你會發現緩存一直都在。

這是為什麼呢?究根詰底還是這個嵌套router-view的問題,不同的router-view的緩存是獨立的,首頁頁面是緩存在一級router-view下面,而用戶管理頁面是緩存在二級router-view下面,當我關閉用戶管理頁面后,只是往excludeViews添加了用戶管理頁面的name(sys.anme),所以只會刪除二級router-view下面name值為sys.user的頁面,二級router-view的name值為sys,它還緩存在一級router-view,所以導致用戶管理一直緩存着。

當然我也想過在關閉頁面時,把頁面父級的所有router-view的name值都添加到excludeViews裏面,這樣的話,也會出現問題,就是當我關閉用戶管理頁面后,同樣在name值為sys的二級router-view下面的頁面緩存都刪除掉了。

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

透過選單樣式的調整、圖片的縮放比例、文字的放大及段落的排版對應來給使用者最佳的瀏覽體驗,所以不用擔心有手機版網站兩個後台的問題,而視覺效果也是透過我們前端設計師優秀的空間比例設計,不會因為畫面變大變小而影響到整體視覺的美感。

當我測試了一晚上,我發現這真的是無解的,中間我也試過網上說的暴力刪除cache方法(方法介紹),也是因為這個嵌套router-view的問題導致失敗。

其實網上有人提出的解決方法是把框架改成只有一個一級router-view,一開始我覺得這是個下策,後面發現這也是唯一的方法了。

無奈,我確實不想扔棄這個多頁面標籤功能。那就改吧,其實改起來也不複雜,就是將菜單跟路由數組分為兩成數組,各自獨立。路由全部同級,均在layout布局組件的children裏面。

只使用一級router-view後面,這個多頁面標籤功能就非常好解決了,用include或exclude都可以,沒有什麼問題,但這兩種方法都得在頁面上寫name值,我是一個懶惰的程序員,總是寫這種跟業務無關係的name值顯得特別多餘。幸運的是,我之前在網上有找到一種暴力刪除緩存的方法,經過我的測試后,發現只有一個小問題(下面會提到),其它方面幾乎完美,而且跟include、exclude相比,還能完美支持同個頁面可以根據不同參數同時緩存的功能。(在vue-element-admin裏面也有說到include是沒法支持這種功能的,如下圖)

 

思想是這樣的,在store里創建一個openedPageRouters(已打開的頁面路由數組),我watch路由的變化,當打開一個新頁面時,往openedPageRouters裏面添加頁面路由,當我關閉頁面標籤時,到openedPageRouters裏面刪除對應的頁面路由,而上面提到的暴力刪除緩存,是在頁面的beforeRouterLeave事件中進行刪除中,所以我註冊一個全局mixin的beforeRouterLeave事件,檢測離開的頁面如果不存在於openedPageRouters數組裡面,那就進行緩存刪除。

思路很完美,當然裏面還有一個小問題,就是刪除不是當前激活的頁面,怎麼處理,因為beforeRouterLeave必須在要刪除頁面的生命周期才能觸發的,這個我用了點小手段,我先跳轉到要刪除的頁面,然後往openedPageRouters里刪除這個頁面路由,然後再跳回原來的頁面,這樣就能讓它觸發beforeRouterLeave了。哈哈,不過這個會導致一個小問題,就是地址欄的閃動一下,也就是上面提到的小問題。

下面是我的pageTabs.vue多頁面標籤組件的代碼

<template>
  <div class="__common-layout-pageTabs">
    <el-scrollbar>
      <div class="__tabs">
        <div
          class="__tab-item"
          v-for="item in $store.state.sys.openedPageRouters"
          :class="{ '__is-active': item.meta.canMultipleOpen?item.fullPath==$route.fullPath:item.path==$route.path }"
          :key="item.fullPath"
          @click="onClick(item)"
        >
          {{item.meta.title}}
          <span
            class="el-icon-close"
            @click.stop="onClose(item)"
            :style="$store.state.sys.openedPageRouters.length<=1?'width:0;':''"
          ></span>
        </div>
      </div>
    </el-scrollbar>
  </div>
</template>
<script>
export default {
  watch: {
    $route: {
      handler(v) {
        this.$store.dispatch("openPage", v);
      },
      immediate: true
    }
  },
  methods: {
    //點擊頁面標籤卡時
    onClick(data) {
      if (this.$route.fullPath != data.fullPath) {
        this.$router.push(data.fullPath);
      }
    },
    //關閉頁面標籤時
    onClose(route) {
      if (route.fullPath == this.$route.fullPath) {
        let index = this.$store.state.sys.openedPageRouters.indexOf(route);
        this.$store.dispatch("closePage", route);
        //刪除頁面后,跳轉到上一頁面
        this.$router.push(
          this.$store.state.sys.openedPageRouters[index < 1 ? 0 : index - 1]
            .path
        );
      } else {
        let lastPath = this.$route.fullPath;
        //先跳轉到要刪除的頁面,再刪除頁面路由,再跳轉回來原來的頁面
        this.$router.replace(route).then(() => {          
          this.$store.dispatch("closePage", route);
          this.$router.replace(lastPath);
        });
      }
    }
  }
};
</script>
<style lang="scss">
.__common-layout-pageTabs {
  $c-tab-border-color: #dcdfe6;
  position: relative;
  &::before {
    content: "";
    border-bottom: 1px solid $c-tab-border-color;
    position: absolute;
    left: 0;
    right: 0;
    bottom: 2px;
    height: 100%;
  }
  .__tabs {
    display: flex;
    .__tab-item {
      white-space: nowrap;
      padding: 8px 6px 8px 18px;
      font-size: 12px;
      border: 1px solid $c-tab-border-color;
      border-left: none;
      border-bottom: 0px;
      line-height: 14px;
      cursor: pointer;
      transition: color 0.3s cubic-bezier(0.645, 0.045, 0.355, 1),
        padding 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
      &:first-child {
        border-left: 1px solid $c-tab-border-color;
        border-top-left-radius: 2px;
        margin-left: 10px;
      }
      &:last-child {
        border-top-right-radius: 2px;
        margin-right: 10px;
      }
      &:not(.__is-active):hover {
        color: #409eff;
        .el-icon-close {
          width: 12px;
          margin-right: 0px;
        }
      }
      &.__is-active {
        padding-right: 12px;
        border-bottom: 1px solid #fff;
        color: #409eff;
        .el-icon-close {
          width: 12px;
          margin-right: 0px;
          margin-left: 2px;
        }
      }
      .el-icon-close {
        width: 0px;
        height: 12px;
        overflow: hidden;
        border-radius: 50%;
        font-size: 12px;
        margin-right: 12px;
        transform-origin: 100% 50%;
        transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
        vertical-align: text-top;
        &:hover {
          background-color: #c0c4cc;
          color: #fff;
        }
      }
    }
  }
}
</style>

 

以下是store代碼

const sys = {
    state: {
        menus: [],//
        permissionRouters: [],//權限路由表
        permissionMenus: [],//權限菜單列表
        config: null, //系統配置        
        openedPageRouters: [] //已打開原頁面路由
    },
    getters: {

    },
    mutations: {
        SET_PERMISSION_ROUTERS(state, routers) {
            state.permissionRouters = routers;
        },
        SET_PERMISSION_MENUS(state, menus) {
            state.permissionMenus = menus;
        },
        SET_MENUS(state, menus) {
            state.menus = menus;
        },
        SET_CONFIG(state, config) {
            state.config = config;
        },
        //添加頁面路由        
        ADD_PAGE_ROUTER(state, route) {
            state.openedPageRouters.push(route);
        },
        //刪除頁面路由
        DEL_PAGE_ROUTER(state, route) {
            let index = state.openedPageRouters.indexOf(route);
            if (index >= 0) {
                state.openedPageRouters.splice(index, 1);
            }
        },
        //替換頁面路由
        REPLACE_PAGE_ROUTER(state, route) {
            for (let key in state.openedPageRouters) {
                if (state.openedPageRouters[key].path == route.path) {
                    state.openedPageRouters.splice(key, 1, route)
                    break;
                }
            }
        }
    },
    actions: {
        //打開頁面
        openPage({ state, commit }, route) {
            let isExist = state.openedPageRouters.some(
                item => item.fullPath == route.fullPath
            );
            if (!isExist) {
                //判斷頁面是否支持不同參數多開頁面功能,如果不支持且已存在path值一樣的頁面路由,那就替換它
                if (route.meta.canMultipleOpen || !state.openedPageRouters.some(
                    item => item.path == route.path
                )) {
                    commit("ADD_PAGE_ROUTER", route);
                } else {
                    commit("REPLACE_PAGE_ROUTER", route);
                }
            }
        },
        //關閉頁面
        closePage({ state, commit }, route) {
            commit("DEL_PAGE_ROUTER", route);
        }        
    }
}
export default sys

 

以下是暴力刪除頁面緩存的代碼,我寫成了一個全局的mixin

import Vue from 'vue'
Vue.mixin({
  beforeRouteLeave(to, from, next) {
    //限制只有在我寫的那個父類里才可能會用這個緩存刪除功能
    if (!this.$parent || this.$parent.$el.className != "el-main __common-layout-main" || !this.$store.state.sys.config.PAGE_TABS) {
      next();
      return;
    }
    let isExist = this.$store.state.sys.openedPageRouters.some(item => item.fullPath == from.fullPath)
    if (!isExist) {
      let tag = this.$vnode.tag;
      let cache = this.$vnode.parent.componentInstance.cache;
      let keys = this.$vnode.parent.componentInstance.keys;
      let key;
      for (let k in cache) {
        if (cache[k].tag == tag) {
          key = k;
          break;
        }
      }
      if (key) {
        if (cache[key] != null) {
          delete cache[key];
          let index = keys.indexOf(key);
          if (index > -1) {
            keys.splice(index, 1);
          }
        }
      }
    }
    next();
  }
})

 

 然後router-view這樣使用,根據我的配置$store.state.sys.config.PAGE_TABS(是否啟用多頁面標籤)進行判斷 ,對了,我相信有不少人肯定會想到,路由不嵌套了,沒有matched數組了,怎麼弄麵包屑,可以看我下面代碼的處理,$store.state.sys.permissionMenus這個數組是我從後台傳過來的,是一個根據當前用戶的權限獲取到的所有有權限訪問的菜單數組,都是一級數組,沒有嵌套關係,我的菜單數組跟路由都是根據這個permissionMenus進行構建的。而我的麵包屑數組就是從這個數組遞歸出來的。

<template>
  <el-main class="__common-layout-main">
    <page-tabs class="c-mg-t-10p" v-if="$store.state.sys.config.PAGE_TABS" />
    <div class="c-pd-20p">
      <el-breadcrumb separator="/">
        <el-breadcrumb-item v-for="m in breadcrumbItems" :key="m.id">{{m.name}}</el-breadcrumb-item>
      </el-breadcrumb>
      <div class="c-h-15p"></div>
      <keep-alive v-if="$store.state.sys.config.PAGE_TABS">
        <router-view :key="$route.fullPath" />
      </keep-alive>
      <router-view v-else />
    </div>
  </el-main>
</template>
<script>
import pageTabs from "./pageTabs";
export default {
  components: { pageTabs },
  data() {
    return {
      viewNames: ["role"]
    };
  },
  computed: {
    breadcrumbItems() {
      let items = [];
      let buildItems = id => {
        let b = this.$store.state.sys.permissionMenus.find(
          item => item.id == id
        );
        if (b) {
          items.unshift(b);
          if (b.parentId) {
            buildItems(b.parentId);
          }
        }
      };
      buildItems(this.$route.meta.id);
      return items;
    }
  }
};
</script>
<style lang="scss">
$c-tab-border-color: #dcdfe6;
.__common-layout-main.el-main {
  padding: 0px;
  overflow: unset;
  .el-breadcrumb {
    font-size: 12px;
  }
}
</style>

 

演示一個最終效果,哎,弄了我整整两天時間,不過我改成不嵌套路由后,發現代碼量也少了很多,也是因禍得福啊。這更符合我的Less框架的理念了。哈哈哈!

對了,我之前有說到個小問題,大家可以仔細看一下,下圖的地址欄,當我關閉非當前激活的頁面標籤時,你會發現地址欄會閃現一下。好吧,下面這個動圖還不太明顯。

大家可以到我的LessAdmin框架預覽地址測試下,不要亂改菜單數據哦,會導致打不開的

http://test.caijt.com:9001

用戶:superadmin

密碼:admin

 

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

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

搬家費用:依消費者運送距離、搬運樓層、有無電梯、步行距離、特殊地形、超重物品等計價因素後,評估每車次單