傳小米電動車已進入量產 售價新台幣 20 萬有找

小米在智慧型手機及資訊領域攻城掠地後,顯然創辦人雷軍並未因此滿足,打從更早之前就一直有傳聞小米想要打造車子,而且還是高技術門檻的電動車,如今又有進一步消息指稱,小米電動車已經進入量產階段。   身為 Tesla 在中國市場的第一批車主,雷軍也曾多次親自拜訪 Tesla 的執行長 Elon Musk,交流許多關於電動車的未來、以及對智慧車輛的看法,近期中國網路上盛傳,內部代號「米斯拉」的小米牌電動車已經開始量產,甚至連其代工合作夥伴為比亞迪等資訊都被揭露。   消息也指出,米斯拉除了會是台電動車外,還會搭載小米自家的 MIUI 系統,將擁有豐富的智慧聯網功能;更驚人的是,這樣的一台高科技智慧電動車,售價僅要 39,999 人民幣,折合新台幣不過 20 萬有找,若此消息為真,小米將會在車界掀起另一波破壞巨浪。    

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

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

※台北網頁設計公司全省服務真心推薦

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

新北清潔公司,居家、辦公、裝潢細清專業服務

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

油價大跌 陸新能源汽車銷售目標恐難達成

國際原油每桶已跌破 55 美元,這對大陸正在推動新能源汽車恐怕將踢到鐵板,尤其是明年銷售 50 萬輛的目標很難達陣。大陸正致力推動的新能源政策,也將出現減速的阻力,包括太陽能等替代能源產業,都在這次國際油價重跌後,面臨無以為繼的結果。   《南方周末》報導,國際油價下跌,新能源汽車首當其衝,其中以中國電動車龍頭企業比亞迪受傷最重,原本就賣不動的電動車,在油價直直落後,不少消費者根本對電動車無感。   據悉,工信部副部長蘇波除到比亞迪考察新能源汽車發展和推廣情況外,更召集相關部會舉行節能與新能源汽車產業發展部際聯席會議聯絡員會議,除了發改委、科技部、財政部等 18 個部際聯席會議成員單位,還邀請國管局、國土資源部參加,規模空前,顯見中國政府也預見新能源汽車受油價影響,政府訂定的銷售目標恐難以達陣。  

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

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

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

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

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

※幫你省時又省力,新北清潔一流服務好口碑

※回頭車貨運收費標準

南韓推廣電動車 目標 2020 年上路 20 萬輛

南韓政府致力推廣價格更低、能源效率更高也更環保的下一代電動車,預計在 2020 年之前,把電動車的使用量提高到 20 萬輛。   南韓環境部 19 日宣布一套涵蓋範圍甚廣的措施,目標是加速電動車的商業化。具體措施包括延長實施稅務優惠、投資新的技術、訂定公家機關購車配額、研擬計畫擴增充電站數目,以及其他獎勵購買電動車的措施。   依照這項計畫,南韓政府訂定目標,希望在南韓上路的電動車總數能從目前的 800 輛提高到 2015 年的 3,000 輛,到 2020 年再提高到 20 萬輛。

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

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

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

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

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

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

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

北京明年起新能源車補貼下降 純電動車補27.4萬

北京市財政局公佈了《北京市示範應用新能源小客車財政補助資金管理細則》,明確了今後3年的相關財政補助具體標準。在2015年至2017年12月31日期間,北京市消費者購買純電動小客車最多可獲補助5.4萬元人民幣(約新臺幣27.4萬),購買燃料電池小客車的補助最高為18萬元(約新臺幣91萬),較今年標準均有下降。   此外,汽車生產企業享受中央和本市財政補助總額最高不超過車輛銷售價格的60%。如補助總額高於車輛銷售價格的60%,按車輛銷售價格60%扣除中央補助後計算本市財政補助額。對於實行公務用車編制管理的單位購買新能源小客車,不享受本市財政補助。

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

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

※台北網頁設計公司全省服務真心推薦

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

新北清潔公司,居家、辦公、裝潢細清專業服務

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

Day12-微信小程序實戰-交友小程序-優化“附近的人”頁面與serach組件的布局和樣式以及搜索歷史記錄和本地緩存*內附代碼)

回顧/:我們已經實現了显示附近的人的功能了,可以多個人看到附近的人頁面了

但是還是要進行優化有幾個問題:1、我們用戶選擇了其他的自定義頭像之後,在首頁可以看到頭像的變化,但是在附近的人中頭像會變成報錯的樣式:如:

 

 

 也就是500了,也就是找不到這個圖片了,解決方法:看開發文檔-》雲開發

https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/storage/api.html

其中有一個 “換取臨時鏈接”的功能(通過這個方法可以臨時的拿到一個圖片的路徑了),然後這個路徑就可以對應到我們的iconpath中了,有直接看demo

wx.cloud.getTempFileURL({
  fileList: ['cloud://xxx.png'],
  success: res => {
    // fileList 是一個有如下結構的對象數組
    // [{
    //    fileID: 'cloud://xxx.png', // 文件 ID
    //    tempFileURL: '', // 臨時文件網絡鏈接
    //    maxAge: 120 * 60 * 1000, // 有效期
    // }]
    console.log(res.fileList)
  },
  fail: console.error
})

我們剛剛換了頭像的測試號,可以看到在數據庫中

 

 

 

 正常的試https這樣的,但是我們修改了之後,它的路徑變成了我們設置的默認的,cloud開始的了

所以我們就可以直接在near.js裏面用for來判斷每個字段符不符合條件即可了,一旦找到了這個cloud開頭的路徑的話,也就是if裏面進行的東西

我們就要換取臨時的路徑即可了,如果else的話,我們還是和之前一樣的,直接push進去即可了

if裏面的話直接copy文檔裏面的demo即可了

我們通過

console.log(res.fileList) 打印出來的東西試一個數組: 

 

 裏面的那個tempFileURL就是一個臨時的路徑了

 getNearUsers(){
    db.collection('users').where({
      location: _.geoNear({
        geometry: db.Geo.Point(this.data.longitude, this.data.latitude),
        minDistance: 0,
        maxDistance: 5000
        //這1000和5000的單位是米
      }),
      islocation : true
    }).field({
      longitude : true,
      latitude : true ,
      userPhoto : true
    }).get().then((res)=>{
      console.log(res.data);
      let data = res.data;
      let result = [];
      if(data.length){

        for(let i=0;i<data.length;i++){
          if(data[i].userPhoto.includes('cloud://')){
            wx.cloud.getTempFileURL({
              fileList: [data[i].userPhoto ],
              success: res => {
                // console.log(res.fileList[0].tempFileURL)
                result.push({
                  // 然後就是把我們獲取到的臨時路徑直接賦值給iconpath即可了
                  iconPath: res.fileList[0].tempFileURL,
                  id: data[i]._id,
                  latitude: data[i].latitude,
                  longitude: data[i].longitude,
                  width: 30,
                  height: 30
                });
                
              }
            })
          }
          else{
            result.push({
              iconPath: data[i].userPhoto,
              id: data[i]._id,
              latitude: data[i].latitude,
              longitude: data[i].longitude,
              width: 30,
              height: 30
            });
          }
        
        }
        this.setData({
          markers : result
        });
      }
    });
  }

如果只是這個代碼的話,會發現我們測試賬號的如何信息都無法渲染出來,這個是因為js是異步操作的,我們要在if之後立馬就進行 setdata操作即可了

如何在全部for結束之後也再次的進行setdata操作即可了,完整代碼就是

getNearUsers(){
    db.collection('users').where({
      location: _.geoNear({
        geometry: db.Geo.Point(this.data.longitude, this.data.latitude),
        minDistance: 0,
        maxDistance: 5000
        //這1000和5000的單位是米
      }),
      islocation : true
    }).field({
      longitude : true,
      latitude : true ,
      userPhoto : true
    }).get().then((res)=>{
      console.log(res.data);
      let data = res.data;
      let result = [];
      if(data.length){

        for(let i=0;i<data.length;i++){
          if(data[i].userPhoto.includes('cloud://')){
            wx.cloud.getTempFileURL({
              fileList: [data[i].userPhoto ],
              success: res => {
                // console.log(res.fileList[0].tempFileURL)
                result.push({
                  // 然後就是把我們獲取到的臨時路徑直接賦值給iconpath即可了
                  iconPath: res.fileList[0].tempFileURL,
                  id: data[i]._id,
                  latitude: data[i].latitude,
                  longitude: data[i].longitude,
                  width: 30,
                  height: 30
                });
                this.setData({
                  markers: result
                });
              }
            })
          }
          else{
            result.push({
              iconPath: data[i].userPhoto,
              id: data[i]._id,
              latitude: data[i].latitude,
              longitude: data[i].longitude,
              width: 30,
              height: 30
            });
          }
        
        }
        this.setData({
          markers : result
        });
      }
    });
  }

 

 

 得到的效果就是,可以看到另外一個用戶剛剛它換的頭像了

(後面的優化就是可以點擊這個用戶的頭像之後我們就可以跳轉到它的詳情頁面了

這個功能在實現起來其實頁不複雜的,有一個和markers對應的事件,也就是點擊了這個markers就會觸發這個事件了  

 

通過這個事件其實我們是可以拿到id值得

 

 markertap(ev){
    console.log(ev);
  }

通過在near.js裏面得這個函數,然後我們點擊一下地圖裡面的marker圖片之後,我們得到的值就是:

 

這個markerID其實對應的就是用戶的id值了

  markertap(ev){
    // console.log(ev);
    wx.navigateTo({
      url: '/pages/detail/detail?userId=' + ev.markerId
    })
  }

通過這個代碼其實就可以實現,點擊地圖裡面的圖標的話我們就可以跳轉到這個用戶的詳情頁面去了

3、後面要測試的就是假如測試賬號關閉了共享位置的話

通過測試我們發現,測試號關閉了共享位置的話,在地圖裡面即使是刷新了還是會看到這個用戶的頭像的

 (其實代碼是沒有錯的,把項目關了再重啟之後會看到這個關閉了共享位置的用戶頭像就消失了

(其實還有其他可以優化的,就是可以在地圖的頭像上面加一段語音介紹自己等等的,因為小程序其實也是支持的,或者是可以計算我和你的距離

或者是我去你那邊的話我過去的導航和路線是怎麼樣的

 

二、search組件的布局和樣式

(就是在主頁的上面添加一個查找的框)

1、實現新建一個叫search的組件

 

 創立好了之後,就可以在首頁進行引用了

2、先在index.JSON文件裏面引入這個組件

{
  "usingComponents": {
    "search" : "/components/search/search"
  }
}

3、在主頁裏面和用標籤一樣引用就可以了

可以直接在index.wxml中通過 <search /> 來使用即可了

 

該search組件就被引入了

通過基本的結構wxml

<!--components/search/search.wxml-->
<view class="container">
  <view class="search"> 
    <view class="search-text">
      <text class="iconfont iconsousuo"></text>
      <input type="text" placeholder="搜索喵星人" />
    </view>
    <view class="search-cancel">取消</view>
  </view>
</view>

得到的效果:

 

會發現我們放大鏡圖標沒有显示出來,所以我們要配置一下,讓這個圖標可以穿透出來即可了

也就是之前copyText.js寫過的

  options: {
    styleIsolation: 'apply-shared'
  },

就是為了讓這個圖標可以生效的

 

 

 這樣的話,我們的放大鏡就進來了

之後就可以對search.wxss的樣式進行設計了

 

/* components/search/search.wxss */
.container{position: fixed;left: 0;top: 0;width: 100%;height: 70rpx;z-index: 999;}
.search{ display: flex ; align-items: center;}
.search-text{ display: flex; align-items: center;flex: 1;} 

 

但是發現,圖片和這個組件融合在一起了

 

 這是因為因為是組件的引入的話,就不像在主頁面一樣,可以佔位置的,所以就要到index.wxss設置一下讓index騰出一個空間來放這個搜索框的

通過在

 

 就是直接通過margin來騰出位置即可了

 

 上面其實是在index.wxss中給上面的騰出來100rpx的空間

/* components/search/search.wxss */
.container{position: fixed;left: 0;top: 0;width: 100%;height: 70rpx;z-index: 999;}
.search{ display: flex ; align-items: center; margin:20rpx;}
.search-text{ display: flex; align-items: center;flex: 1;border: 1px #cdcdcd solid;border-radius:10rpx; height: 65rpx} 
.search-text .iconsousuo{margin: 0 10rpx;}
.search-cancel{margin: 0 10rpx;}

得到的效果就是:

 

 但是有一個問題就是:我們在還沒點擊搜索的時候,其實不用显示後面的“取消”按鈕的,這個的話就要通過js邏輯來實現了

定義了一個isfocus來表示光標有沒有显示的(這個取消的按鈕其實是在我們獲取了光標之後才會有的)

通過在取消按鈕加上了一個wx:if判斷之後,得到的效果就是:

 

 並且當我們獲取到了光標之後,這個搜索框會適應整個頁面的高度了

 給contaner加上了  overflow: hidden; 之後得到的效果就是這個搜索框的下邊框“不見了”

 

 這個是因為,我們得container這個大得塊要比我們輸入框得高度要小了,這個時候就可以在wxss裏面通過調節container得height

 

 即可了

因為如果我們點擊了那個輸入框得胡,也就是聚焦了得話,我們得上面得搜索框的大容器显示的樣式是和沒聚焦的時候显示的不同的,所以我們就可以用三目運算符來通過這個isfocus來決定使用哪個容器,也就是說我們可以定義兩個樣式不同的容器了

<view class="{{ isFocus ? 'containerFocus' : 'container' }}">
.containerFocus{position: fixed;left: 0;top: 0;width: 100%;height: 100%;z-index: 999;
background: #ccc}

然後我們自行的吧js文件裏面定義的isFocus變量 定義weighted是true來看看我們獲取光標之後的效果是怎麼樣的:

 

之後我們就要通過邏輯里控制他們的聚焦切換不同的container了,如果是已經點擊聚焦的了話,還有一個就是可以看到我們搜索的歷史記錄,還有列表等等

 

通過:

<view class="search-history">
    <text>歷史記錄</text>
    <text class="iconfont iconshanchu"></text>
  </view>
.search-history{ display: flex;justify-content: space-between;margin:20rpx;}

效果:

 

 然後就是要搞一個搜索池了:

  <view class="search-history-btn">
    <text>小明</text>
    <text>123213</text>
    <text>dsadasd</text>
  </view>
.search-history{ display: flex;justify-content: space-between;margin:20rpx;}
.search-history-btn text{ border: 1px #cdcdcd solid; padding: 10rpx 20rpx;background: white;
border-radius: 20rpx; margin:10rpx;}

效果:(注意上面是給每一個搜索的text進行樣式的定義

上面就吧搜索的關鍵詞的布局搞好了,下面就是要對搜索的列表進行定義了(其實這個搜索的列表和我們好友的列表是很像的,可以直接直接copy 在friendList.wxml裏面的這個結構了

  <navigator wx:for="{{ friendList }}" wx:key="{{ index }}" url="{{ '../detail/detail?userId=' + item._id}}" open-type="navigate">
      <view class="friendList-item">
        <view>
         <image src="{{ item.userPhoto }}" />
         <text> {{ item.nickName }} </text>
        </view>
        <text class="iconfont iconyoujiantou"></text>
      </view>
     </navigator>

然後對  searchList-item 的樣式也是直接拷貝friendList的wxss

.friendList-item{
  /* 這裏可以直接把user.wxss中的樣式複印過來了 */
  height: 120rpx;border-bottom:1px #b4b5b6 dashed;
padding: 10rpx; display: flex;align-items: center;justify-content: space-between;
}
.friendList-item view{display : flex; align-items: center;}
.friendList-item image{width: 100rpx;height: 100rpx;border-radius: 50%;}

 

綜上所述,我們的代碼就是:

CSS

<!--components/search/search.wxml-->
<view class="{{ isFocus ? 'containerFocus' : 'container' }}">
  <view class="search"> 
    <view class="search-text">
      <text class="iconfont iconsousuo"></text>
      <input type="text" placeholder="搜索喵星人" />
    </view>
    <view wx:if="{{ isFocus }}" class="search-cancel">取消</view>
  </view>

  <view class="search-history">
    <text>歷史記錄</text>
    <text class="iconfont iconshanchu"></text>
  </view>
  <view class="search-history-btn">
    <text>小明</text>
    <text>123213</text>
    <text>dsadasd</text>
  </view>

    <navigator url="" open-type="navigate">
      <view class="searchList-item">
        <view>
         <image src="" />
         <text>小喵喵</text>
        </view>
        <text class="iconfont iconyoujiantou"></text>
      </view>
     </navigator>

</view>

html

然後還要在search.js裏面通過

options: { styleIsolation: ‘apply-shared’ } 引入外部樣式 效果圖:(選中搜索框時)

 

(未選中搜索框時

 

 

 三、實現搜索歷史記錄及本地緩存

1、我們先在searc.wxml的輸入框標籤加一個處理點擊這個輸入框的一個點擊事件

bindfocus=”handleFocus”

 還有我們在取消的標籤中,也要加一個點擊事件,點擊了的話就吧isFocus變成是false即可了

 <input type="text" placeholder="搜索喵星人" bindfocus="handleFocus" />


<view wx:if="{{ isFocus }}" class="search-cancel" bindtap="handleCancel">取消</view>
 methods: {
    handleFocus(){
     this.setData({
       isFocus : true
     }); 
    },
    handleCancel(){
      this.setData({
        isFocus: false
      }); 
    }
  }

得到的效果就是:點擊輸入框,就跳轉到輸入,點擊取消,就跳轉到首頁

還有一個小bug就是,因為輸入框的話,會默認只有在一個範圍以內,才可以輸入的,所以我們就可以讓這個輸入框適應整個範圍,可以在

給 search.wxss中添加一個代碼:

.search-text input {flex: 1;}

就讓這個輸入框可以自動的填滿整個的搜索框了

3、之後就是對輸入的東西進行處理了,可以是邊輸入邊搜索,也可以是輸入之後回車了才進行搜索,如果是邊輸入就邊搜索的話,我們可以通過bindinput來進行監聽的,那如果要是按回車的時候搜索怎麼辦呢—這個其實小程序幫我們搞好了

https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/storage/api.html

就可以通過在input中加上 bindconfirm 這個屬性來完成的,我們就定義了一個 handleConfirm 這個方法是只有我們回車了才會進行觸發的

 

 

 在手機端裏面的回車 其實默認的是 “完成”兩個字的(就是點擊這個輸入框的時候,手機就會彈出軟鍵盤了,它的確定按鈕是“搜索”兩個字的,那這個該怎麼樣去修改呢==微信也提供了

 

 默認的是我們的 done 也就是完成

所以就在input標籤中,吧confirm-type 屬性變成是 search 即可了,(這樣的話在手機的軟鍵盤就會显示 搜索 兩個字了)

(下面我們要做的就是 吧這個搜索的 放在歷史裏面管理起來了)

https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/storage/api.html

demo:

wx.setStorage({
  key:"key",
  data:"value"
})

設置的話,就是我們用戶點擊回車 之後,就可以吧這個搜索裏面的 ev.detail.value放到本地存儲裏面即可了

因為這個setStorage的話,我們要讓這個data是一個數組才行的,然後我們先通過

data : [111]看看能不能吧這個111存放到這個數組裡面

 

 可以在下面的調試板中 找到Storage 讓我們查看一下

可以看到,我們隨便輸入一點東西,然後按 回車 之後可以看到

先在search.js的data裏面定義一個 數組

然後我們就可以在wxml中,吧我們的歷史消息text,用一個數組來for出來了

 <view class="search-history-btn">
    <text wx:for="{{ historyList }}" wx:key="{{ index }}">{{ item }}</text>
  </view>

 

然後我們在一開始 聚焦了之後,就立馬從storage裏面吧數組拿出來,用getStorage方法:

https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/storage/api.html

wx.getStorage({
  key: 'key',
  success (res) {
    console.log(res.data)
  }
})

 

使用上面的demo之後,會報錯,這個報錯一般都是因為success的回調的時候要用箭頭函數才行的

   wx.getStorage({
        key: 'searchHistory',
        success:(res)=> {
          this.setData({
            historyList: res.data
          });
        }
      })

修改了之後,我們點擊 聚焦 之後

這個 111 就是我們剛剛寫入到 searchStorage 數組裡面的

 

 (這個有一個小bug,就是,假如我們輸入了兩次相同的搜索,然後存入到歷史記錄再打印出來的話,會有兩個的,我們不應該有兩個相同的歷史記錄的

 但是我們搜索重複詞的話,我們也是显示一次,然後把這個搜索的提升到最前面去),表示最近搜索,並且歷史記錄也要有一個數量的,不能把在一年之間的全部搜索記錄都显示出來的

這個去重的功能:1、實現克隆一份數組

 (unshift的話就是往數組的頭添加東西的,ES6本身就帶有一個set來完成去重功能的)

   handleConfirm(ev){
      // console.log(ev.detail.value);
      let cloneHistoryList = [...this.data.historyList];
      cloneHistoryList.unshift(ev.detail.value);
      wx.setStorage({
        key: "searchHistory",
        data: [...new Set(cloneHistoryList)]
      })
    }

我們的效果就達到了,重複輸入的話,會被提前,=

然後下面我們就要實現 歷史記錄的刪除功能了

就可以直接在這個刪除圖標的wxml中添加一個 bindtap點擊事件  handleDelete 即可了(這個刪除的話,是刪除掉全部的歷史記錄的)

(微信給我們提供的對storage的操作中,remove是操作某一項的,而clear是刪除掉所有的

https://developers.weixin.qq.com/miniprogram/dev/wxcloud/guide/storage/api.html

wx.clearStorage()

直接這樣寫即可了

====**但是這樣可能如果我們後面在storage裏面也定義了其他的東西,這個語句的話會把其他緩存也會清理掉的,所以我們這裏還是使用remove好點的

wx.removeStorage({
  key: 'key',
  success (res) {
    console.log(res)
  }
})

因為我們也是要在這個成功的回到中,把這個歷史數據數組設置為空數組,所以我們就要使用成功返回的箭頭函數才行的

即可實現刪除功能了,

效果就是:

 

 

 之後再次輸入1的時候,

 

 然後就是清空 歷史記錄:

 

 

 

 

下面是這個部分的代碼

//components/search/search.js
Component({
  /**
   * 組件的屬性列表
   */
  options: {
    styleIsolation: 'apply-shared'
  },
  properties: {

  },

  /**
   * 組件的初始數據
   */
  data: {
    isFocus : false,
    historyList : []
  },

  /**
   * 組件的方法列表
   */
  methods: {

    handleFocus(){

      wx.getStorage({
        key: 'searchHistory',
        success:(res)=> {
          this.setData({
            historyList: res.data
          });
        }
      })

     this.setData({
       isFocus : true
     }); 
    },
    handleCancel(){
      this.setData({
        isFocus: false
      }); 
    },
    handleConfirm(ev){
      // console.log(ev.detail.value);
      let cloneHistoryList = [...this.data.historyList];
      cloneHistoryList.unshift(ev.detail.value);
      wx.setStorage({
        key: "searchHistory",
        data: [...new Set(cloneHistoryList)]
      })
    },
    handleHistoryDelete(){
      wx.removeStorage({
        key: 'searchHistory',
        success:(res)=>{
          this.setData({
            historyList : []
          });

        }
      })
    }
  }
})
<!--components/search/search.wxml-->
<view class="{{ isFocus ? 'containerFocus' : 'container' }}">
  <view class="search"> 
    <view class="search-text">
      <text class="iconfont iconsousuo"></text>
      <input type="text" placeholder="搜索喵星人" bindfocus="handleFocus" bindconfirm="handleConfirm" confirm-type="search"/>
    </view>
    <view wx:if="{{ isFocus }}" class="search-cancel" bindtap="handleCancel">取消</view>
  </view>

  <view class="search-history">
    <text>歷史記錄</text>
    <text bindtap="handleHistoryDelete" class="iconfont iconshanchu"></text>
  </view>
  <view class="search-history-btn">
    <text wx:for="{{ historyList }}" wx:key="{{ index }}">{{ item }}</text>
  </view>

    <navigator url="" open-type="navigate">
      <view class="searchList-item">
        <view>
         <image src="" />
         <text>小喵喵</text>
        </view>
        <text class="iconfont iconyoujiantou"></text>
      </view>
     </navigator>

</view>
/* components/search/search.wxss */
.container{position: fixed;left: 0;top: 0;width: 100%;height: 90rpx;z-index: 999;overflow: hidden;}
.containerFocus{position: fixed;left: 0;top: 0;width: 100%;height: 100%;z-index: 999;
background: #ccc}
.search{ display: flex ; align-items: center; margin:20rpx;}
.search-text{ display: flex; align-items: center;flex: 1;border: 1px #cdcdcd solid;border-radius:10rpx; height: 65rpx; background: white;} 
.search-text input {flex: 1;}
.search-text .iconsousuo{margin: 0 10rpx;}
.search-cancel{margin: 0 10rpx;}

.search-history{ display: flex;justify-content: space-between;margin:20rpx;margin-bottom: 30rpx;}
.search-history-btn{ margin-bottom: 30rpx; }
.search-history-btn text{ border: 1px #cdcdcd solid; padding: 10rpx 20rpx;background: white;
border-radius: 20rpx; margin:10rpx;}


.searchList-item{
  /* 這裏可以直接把user.wxss中的樣式複印過來了 */
  height: 120rpx;border-bottom:1px #b4b5b6 dashed;
padding: 10rpx; display: flex;align-items: center;justify-content: space-between;
}
.searchList-item view{display : flex; align-items: center;}
.searchList-item image{width: 100rpx;height: 100rpx;border-radius: 50%;}

 

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

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

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

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

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

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

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

Python元類實戰,通過元類實現數據庫ORM框架

本文始發於個人公眾號:TechFlow,原創不易,求個關注

今天是Python專題的第19篇文章,我們一起來用元類實現一個簡易的ORM數據庫框架。

本文主要是受到了廖雪峰老師Python3入門教程的啟發,不過廖老師的博客有些精簡,一些小白可能看起來比較吃力。我在他的基礎上做了一些補充和註釋,盡量寫得淺顯一些。

ORM框架是什麼

如果是沒有做過後端的小夥伴上來估計會有點蒙,這個ORM框架究竟是什麼?ORM框架是後端工程師常用的一個框架,它的英文全稱是Object Relational Mapping,即對象-關係映射框架。顧名思義就是把關係轉化成對象的框架,關係這個詞我們在哪裡用的最多呢?

顯然應該是數據庫。之前我們在分佈式的文章介紹關係型數據庫和非關係型數據庫的時候就着重介紹過關係的含義。我們常用的MySQL就是經典的關係型數據庫,它存儲的形式是表,但是表承載的數據其實是兩個實體之間的”關係”。比如學生上課這個場景,學生和課程是兩個主體(entity),我們要記錄的是這兩個主體之間的關係,也就是學生上課這件事。

而ORM框架做的事情是將這些關係映射成類,這樣我們可以將這張表當中增刪改查的功能抽象成類當中的方法。這樣我們就可以通過調用類的方式來操作數據庫了,從而達到高度抽象業務邏輯、降低用戶使用難度的目的。

比如Java後端工程師常用的hibernate和ibatis都是用來做這件事情的,明確了框架的功能之後,我們先來設想一下最後的成果。假設我們現在開發出來了這麼一套框架,那麼它用起來的感覺應該是怎樣的?

我們來看下廖老師博客里給的例子:

class User(Model):
    # 定義類的屬性到列的映射:
    id = IntegerField('id')
    name = StringField('username')
    email = StringField('email')
    password = StringField('password')

User類代表了數據庫當中的一張表,它有4個字段:id, name, email和password,我們在定義字段的同時也通過類別指定了它們的類型。這個應該不難理解,上面的這個類等價於我們在數據庫當中執行了這麼一段建表的SQL:

create table if not exists user (
 id int,
    name string,
    email string,
    password string
)

我們定義了表字段之後,接下來要做的就是根據字段創建數據了,其實也就是根據類創建實例。我們希望User類型的實例就對應User表當中的一條記錄,並且我們可以通過調用實例當中的方法,來操作這張表進行增刪改查。

# 創建一個實例:
u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
# 保存到數據庫:
u.save()

那麼,我們怎樣可以實現這樣的功能呢?

功能實現

我們先從簡單的功能開始實現,首先是Field類,Field類表示數據庫表當中一個字段的類型。這裏的邏輯很容易理清楚,我們需要定義多種類型,比如IntegerField和StringField。我們可以對這些field類抽象出一個父類來:

class Field(object):
    def __init__(self, name, column_type):
        self.name = name
        self.column_type = column_type
        
    def __str__(self):
        return '<{}:{}>'.format(self.__class__.__name__, self.name)

__str__方法當中打印出來的兩個字段,分別是類別的名稱和字段的名稱,這段代碼應該不難理解。

接着,我們實現它的兩個子類,分別是IntegerField和StringField:

class StringField(Field):
    def __init__(self, name):
        super(StringField, self).__init__(name, 'varchar(100)')
        
        
class IntegerField(Field):
    def __init__(self, name):
        super(IntegerField, self).__init__(name, 'bigint')

這裏也不難理解,只是一個簡單的繼承應用而已。

接下來就到了最關鍵的部分,也就是Model類的實現。我們先來分析一下我們希望Model這個類擁有的功能,由於它是我們定義出來的每一張表的父類,所以它應該能夠獲取子類當中的字段,並且將它存放在一個容器當中。由於我們需要存儲的是字段名和類型的映射,所以將它存儲在dict當中比較合理。

另外一個功能是我們希望它能夠提供增刪改查的接口,能夠根據子類當中定義的字段自動生成相應的SQL語句去調用數據庫。這個也是ORM框架的意義所在。

第二個功能容易實現,只要第一個功能搞定了,做一下字符串處理即可。但是第一個功能有些麻煩,它也是元類的意義所在。因為父類當中的方法是無法獲取子類中定義的類屬性的,只能通過元類,在構建類的時候可以拿到屬性的信息。

所以我們已經很明確了,我們實現元類的目的就是為了實現這個功能。理清楚了之後,再來寫代碼就不難了。我們先來實現這個元類:

class ModelMetaclass(type):

    def __new__(cls, name, bases, attrs):
        # 創建model類的時候不做任何處理
        if name=='Model':
            return type.__new__(cls, name, bases, attrs)
        # 打印表名的信息
        print('Found model: %s' % name)
        # mappings用來存儲字段的信息
        mappings = dict()
        for k, v in attrs.items():
            # 判斷v的類型,只有是Field的子類才會存儲起來
            if isinstance(v, Field):
                print('Found mapping: %s ==> %s' % (k, v))
                mappings[k] = v
        # 將mappings當中的數據從類屬性當中移除,防止關鍵字衝突
        for k in mappings.keys():
            attrs.pop(k)
        attrs['__mappings__'] = mappings # 保存屬性和列的映射關係
        attrs['__table__'] = name # 假設表名和類名一致
        return type.__new__(cls, name, bases, attrs)

如果你看過之前的文章,對元類已經很熟悉了,那麼這段代碼對你來說應該不難理解。元類搞定了,剩下的Model就更簡單了。按照規範,我們需要實現增刪改查四個函數,但是這裏我們只是為了展示,所以就只實現其中一個作為例子,其他幾個都可以如法炮製。

class Model(dict, metaclass=ModelMetaclass):
    def __init__(self, **kw):
        # 由於Model的基類是dict,所以創造Model的字段會被解析成dict的構造參數
        # 也就是說字段名和字段值的映射會存儲在dict當中
        super(Model, self).__init__(**kw)
        
    def __getattr__(self, key):
        try:
            return self[key]
        except KeyError:
            raise AttributeError(r"'Model' object has no attribute '%s'" % key)

    def __setattr__(self, key, value):
        self[key] = value

    def save(self):
        fields = []
        params = []
        args = []
        for k, v in self.__mappings__.items():
            # fields存儲字段名
            fields.append(v.name)
            # params填充問號
            params.append('?')
            # 獲取字段的值
            args.append(getattr(self, k, None))
        sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(params))
        print('SQL: %s' % sql)
        print('ARGS: %s' % str(args))

Model當中的save方法不難看懂,但是前面的幾個方法看起來有些多餘。但實際上它們也很重要,這裡有一個關鍵信息是Model類的父類是dict,我們在構建Model的時候傳入的參數會被用來初始化一個dict。所以我們創建數據實例的時候數據的名稱和數據值的映射會被存儲在dict當中,所以我們在save方法當中才會從self的attr當中獲取字段的值。並且我們在初始化User的時候,也必須要填寫每個字段的名稱,原因就在這裏。

最後我們來運行一下:

從結果上來看,我們輸出了User這個類的插入SQL以及它的字段的值。只需要鏈接一下數據庫,我們的這個ORM框架就可以真正投入使用了。

總結

在整個ORM框架實現的過程當中,最重要的是我們對Model這個類創建了元類,但是真正應用的地方卻是在Model的子類。實際上在實際創建User類的時候,解釋器會先搜索User內部是否定義了元類,如果沒有,會上一層去往User的父類也就是Model類搜索元類,如果找到了元類,就會使用元類來創建User。相當於元類被隱形地繼承了下來,但是我們在使用子類的時候卻感知不到。

對於框架的使用者來說,也的確不需要了解框架內部的實現機制,只需要明白使用方法,照着使用就行了。雖然元類的實現和理解很複雜,但是使用起來卻很簡單,這也是它的一個顯著特點。

最後,本文的代碼示例源於廖雪峰老師的博客,向廖雪峰老師致敬。想要查看廖老師博客原文的,請點擊查看原文。

如果喜歡本文,可以的話,請點個關注,給我一點鼓勵,也方便獲取更多文章。

本文使用 mdnice 排版

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

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

※台北網頁設計公司全省服務真心推薦

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

新北清潔公司,居家、辦公、裝潢細清專業服務

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

Flutter開發初探

目前跨端開發比較熱門的就是 React NativeFlutter 了,到底該選哪門技術似乎也快成了大前端圈的一個熱門話題。對於web前端來說,基於web生態的 React Native 應該是一個更加順暢而自然的選擇;但 Flutter 讓人動心的地方就是高性能和 跨端UI一致性。而React Native 發展不太明朗和 Flutter 越發成熟的走勢對比促使我從觀望的心態轉為加入 Flutter

這裏主要就是記錄一下學習Flutter的一些感想和看法:

  • 包管理
  • 布局和樣式
  • json
  • 狀態管理

包管理

pubspec.yaml 文件的作用類似於 npmpackage.json ,而yaml格式也比json方便。但是不能用命令行自動安裝包卻讓習慣了npm的我覺得麻煩。因為Flutter 安裝依賴包是這麼一個流程:

  1. 打開pub.dev網站;
  2. 搜索需要的包,得到包的名稱和版本;
  3. 把包名稱和版本填入pubspec.yaml,最後才開始下載包。

我覺得應該直接命令行安裝包,讓它幫我們下載,名稱版本自動寫入pubspec.yaml。如果沒有指定版本就是默認下載最新版本,因為很多時候我們並不想知道版本號,給我個能用的最新的版本號就ok了。

布局和樣式

就和很多人想的一樣,為什麼不使用 jsx 或者 xml 格式進行布局,因為基於代碼的方式看起來太不直觀了,之所以這樣聽說主要是能更方便的和Dart的hot reload特性配合使用,代碼改動能立刻反映布局變化。但我還是期待有適配轉化 DSL 的框架出現。

Flutter一切都是widget,但是連很多屬性都當成widget 這就讓人有些看不明白了,比如 CenterAlignPadding,為什麼不把常用的樣式屬性都加入到布局組件裏面呢?這導致出現了這麼一種情況:嵌套嚴重,一個很簡單的功能需要層層嵌套才能實現,而且樣式也不能方便的復用。目前比較合理的建議就是適當抽取齣子組件減少嵌套。

Json

Dart 作為強類型的語言,一切皆是對象。Dart要方便操作json就得把json轉化為對象,這就意味着每用到一個json,就需要定義一個對應的類,這也是強類型語言的通病了。這絕對讓人很懷念 js/ts 這種對json操作非常自然順暢的弱類型/函數式語言。當然也不是沒有妥協的解決方案,比較方便的就是 json_model,Flutter實戰作者寫的一個工具庫,步驟也簡單:

  1. 在工程根目錄下創建一個名為 “jsons” 的目錄;
  2. 創建或拷貝Json文件到”jsons” 目錄中 ;
  3. 運行 pub run json_model (Dart VM工程)or flutter packages pub run json_model(Flutter中) 命令生成Dart model類,生成的文件默認在”lib/models”目錄下

狀態管理

Flutter 使用initStatesetState方法設置widget狀態,原理類似React。當然這隻是widget內部控制狀態用的,跨組件通信還是需要其他方案的。官方推薦是使用Provider,使用下來中規中矩吧,當然還可以使用大名鼎鼎的 Redux 以及 mbox。不過Redux本身就以過多的樣板代碼而出名,寫React的時候就不喜歡用,hooks 出來后就果斷就放棄Redux了。hooks才是真香啊,Flutter什麼時候才支持類似的函數式狀態管理方案呢?

總結

說了這麼多,本質就是為什麼 Flutter 不向以 React 為代表的 web 生態看齊?更大的原因是Flutter的很多理念和開發模式其實遠遠落後於 React 。這也是為什麼習慣 react/vue 的 web前端 對於Flutter 感覺很彆扭不順手的原因了。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

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

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

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

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

※幫你省時又省力,新北清潔一流服務好口碑

※回頭車貨運收費標準

沖繩世遺首里城大火 主要建築與珍藏品全毀

摘錄自2019年11月1日中央社報導

日本沖繩那霸市登記為世界遺產的首里城31日凌晨發生大火,正殿、南北殿等主要建築全燒毀,珍藏的許多琉球王國時代以來的貴重品也燒毀。

日本放送協會(NHK)報導,位於那霸市的首里城昨天凌晨發生火災,火勢一發不可收拾,在延燒了11個小時之後,首里城的主要建築物正殿、北殿、南殿等全被燒毀。消防單位表示,起火點可能是在正殿,因為猛烈的火勢熱氣太強,就在消防隊員無法靠近滅火之際,風力助長火勢,燃燒面積擴大。

管理首里城的沖繩美島財團表示,被大火全燒毀的正殿等建築物內原本收藏著琉球王國時代傳承下來的畫作、漆器、染織物等。初步研判這些貴重的收藏品已燒毀,沖繩美島財團正在詳細調查這些珍貴收藏品的受災數量。

首里城是琉球王國時代、約500年前興建,1933年被指定為國寶,在二次世界大戰沖繩戰役時曾被美軍炮擊燒毀。1992年正殿修復後,其他建築物陸續修復,西元2000年首里城城址與沖繩縣內其他的城址登記為世界遺產。

曾參與首里城重建的沖繩縣立博物館暨美術館館長田名真之從電視螢幕看到首里城大火情況說:「正因知道重建的艱辛,才覺得這場火災令人無法置信。在沒有資料、沒有木工的情況下,蓋首里城的木材是從台灣運來的,這是集結許多人智慧的建築。(發生大火),真的難過到無言。」

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

【其他文章推薦】

※帶您來了解什麼是 USB CONNECTOR  ?

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

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

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

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

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

印度河恆河平原空污嚴重 研究:居民恐減壽7年

摘錄自2019年10月31日中央社報導

美國芝加哥大學能源政策研究所(Energy Policy Institute)31日發表空氣品質壽命指數(Air Quality Life Index)分析報告,研究團隊針對印度河-恆河平原(Indo-Gangetic Plain)1998年到2016年的空氣品質數據進行分析,發現這裡的空氣污染嚴重程度是印度其他地區的兩倍。

印度河-恆河平原包括比哈省(Bihar)、昌迪加爾(Chandigarh)、德里、哈雅納省(Haryana)、旁遮普省(Punjab)、北方省(Uttar Pradesh)和西孟加拉省(West Bengal)等,印度約有40%的人口居住在這些地區。

研究顯示,在過去18年間,印度河-恆河平原空氣污染程度提升72%。比起生活在污染程度符合世界衛生組織(WHO)指導準則的環境,1998年持續暴露在懸浮微粒污染的當地居民,平均預期壽命減少3.7年;這樣的空污程度持續到2016年,讓人們平均預期壽命更加縮短,減少7.1年。

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

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

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

※台北網頁設計公司全省服務真心推薦

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

新北清潔公司,居家、辦公、裝潢細清專業服務

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

森林大火濃煙飄進雪梨 懸浮微粒濃度達危險等級

摘錄自2019年10月31日中央社報導

澳洲叢林大火的撲鼻濃煙壟罩澳洲最大城雪梨,遮蔽雪梨國家歌劇院(Opera House)、雪梨港灣大橋(Harbour Bridge )等地標,空氣中瀰漫嗆辣味,居民紛紛咳起嗽來,引發健康警告,空氣每百萬分之一的懸浮微粒濃度甚至高於曼谷、雅加達或香港等城市,公衛當局警告有呼吸道疾病的雪梨居民,避免外出活動。 

新南威爾斯省鄉村消防局(RFS)31日表示:「濃煙可能會持續好幾天,在週末期間某個階段才會清除。」 

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

【其他文章推薦】

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

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

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

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

※幫你省時又省力,新北清潔一流服務好口碑

※回頭車貨運收費標準