Kaynağa Gözat

feat(飞码)活动列表 授权手机号

xing.li 3 yıl önce
ebeveyn
işleme
993225640f

+ 5 - 0
.idea/.gitignore

@@ -0,0 +1,5 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
+# 基于编辑器的 HTTP 客户端请求
+/httpRequests/

+ 8 - 0
.idea/modules.xml

@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="ProjectModuleManager">
+    <modules>
+      <module fileurl="file://$PROJECT_DIR$/.idea/share_activity.iml" filepath="$PROJECT_DIR$/.idea/share_activity.iml" />
+    </modules>
+  </component>
+</project>

+ 12 - 0
.idea/share_activity.iml

@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="WEB_MODULE" version="4">
+  <component name="NewModuleRootManager">
+    <content url="file://$MODULE_DIR$">
+      <excludeFolder url="file://$MODULE_DIR$/temp" />
+      <excludeFolder url="file://$MODULE_DIR$/.tmp" />
+      <excludeFolder url="file://$MODULE_DIR$/tmp" />
+    </content>
+    <orderEntry type="inheritedJdk" />
+    <orderEntry type="sourceFolder" forTests="false" />
+  </component>
+</module>

+ 6 - 0
.idea/vcs.xml

@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project version="4">
+  <component name="VcsDirectoryMappings">
+    <mapping directory="$PROJECT_DIR$" vcs="Git" />
+  </component>
+</project>

+ 89 - 0
api/activity.js

@@ -0,0 +1,89 @@
+import request from '../utils/request.js'
+
+class activity extends request {
+
+
+  static async getActivityList(nextPage,pageSize,status) {
+    let params = {
+      nextPage:nextPage,
+      pageSize:pageSize,
+      condition:{
+        statusList:[status],
+        source:"FREE_MINI_APP",
+      }
+    };
+    const res = await this.postRequest(`${this.BASE_URL}open/activity/get-list`, params)
+
+    return res.data
+  }
+  
+  static async getSessionKeyFromApi(code) {
+    let params = {
+      appCode:getApp().globalData.appCode,
+      sessionCode:code
+    };
+   
+    const res = await this.postRequest(`${this.BASE_URL}open/wxapp/wx-login`, params)
+    return res.data
+  }
+
+  /**
+   * 解锁手机号
+   * @param {*} orderSn 
+   */
+  static async getAuthMobile(params) {
+   
+    const res = await this.postRequest(`${this.BASE_URL}open/wxapp/auth-mobile`, params)
+    return res.data
+  }
+
+  /**
+   * 保存用户信息
+   * @param {*} openId 
+   * @param {*} mobile 
+   */
+  static async saveUser(openId,mobile) {
+    let params = {
+      openid:openId,
+      mobile:mobile,
+      source:'FREE_MINI_APP'
+    };
+
+    const res = await this.postRequest(`${this.BASE_URL}open/activity/save-user`, params)
+    return res.data
+   
+  }
+
+  /**
+   * 设置手机缓存
+   * @param {*} mobile 
+   */
+  static setMobileCache(mobile) {
+    let data = {
+      mobile:mobile,
+    }
+    wx.setStorageSync('userInfo',data);
+  }
+
+  static getMobileCache() {
+    let userInfo = wx.getStorageSync('userInfo');
+    if(userInfo) {
+      return userInfo.mobile;
+    }
+    return '';
+  }
+
+  static getSessionKey() {
+    return wx.getStorageSync('loginInfo').sessionKey;
+  }
+
+  /**
+   * 获取用户openid
+   */
+  static getOpenId() {
+    return wx.getStorageSync('loginInfo').openId;
+  }
+
+}
+
+export default activity

+ 79 - 5
app.js

@@ -1,19 +1,93 @@
 // app.js
+
+import Activity from './api/activity';
 App({
   onLaunch() {
     // 展示本地存储能力
-    const logs = wx.getStorageSync('logs') || []
-    logs.unshift(Date.now())
-    wx.setStorageSync('logs', logs)
+    // const logs = wx.getStorageSync('logs') || []
+    // logs.unshift(Date.now())
+    // wx.setStorageSync('logs', logs)
 
     // 登录
+    var openId = wx.getStorageSync('loginInfo').openId
+    if(!openId){
+      this.login();
+    }
+   
+  },
+  checkInvalid(param={},callBack='') {
+    let _self = this;
+    wx.checkSession({
+      success () {
+        //session_key 未过期,并且在本生命周期一直有效
+        if(typeof(callBack)==='function'){
+          callBack(param);
+        }
+      },
+      fail () {
+        _self.login(function(){
+           // session_key 已经失效,需要重新执行登录流程
+          if(typeof(callBack)==='function'){
+            callBack(param);
+          }
+        });
+       
+      }
+    })
+  },
+   // 登录
+   login() {
     wx.login({
       success: res => {
-        // 发送 res.code 到后台换取 openId, sessionKey, unionId
+        let code = res.code;
+        let result = Activity.getSessionKeyFromApi(code);
+        result.then(res=>{
+          wx.setStorageSync('loginInfo',{
+            openId:res.openId,
+            sessionKey:res.sessionKey
+          });
+        });
+        // if(typeof(call)=='function') {
+        //   call();
+        // }
       }
     })
   },
+  /**
+   * 接口请求手机号
+   * @param {*} encryptedData
+   * @param {*} iv
+   * @param call
+   */
+  async doDecodePhone(encryptedData,iv,call='') {
+    let _self = this;
+    //验证登录是否失效
+    _self.checkInvalid({encryptedData:encryptedData,iv:iv,call:call},function(param){
+      var params = {
+        encryptedData: param.encryptedData,
+        iv: param.iv,
+        sessionKey:Activity.getSessionKey(),
+      };
+      try {
+        let res = Activity.getAuthMobile(params);   
+        res.then(userInfo=>{
+          let mobile = userInfo.phoneNumber;
+          // @todo从缓存里面取openId
+          let openId = Activity.getOpenId();
+          //保存用户信息
+          Activity.saveUser(openId,mobile);
+          Activity.setMobileCache(mobile);
+          if(param.call && typeof(param.call)=='function') {
+            param.call();
+          }
+        })
+      }catch(err) {
+        console.log(err);
+      }
+    });
+  },
   globalData: {
-    userInfo: null
+    userInfo: null,
+    appCode:'HSAY_SHARE_COUPON',
   }
 })

+ 3 - 0
app.json

@@ -1,6 +1,9 @@
 {
   "pages":[
+    
     "pages/activityList/activityList",
+    "pages/activityInfo/activityInfo",
+    "pages/myCoupons/myCoupons",
     "pages/index/index",
     "pages/logs/logs"
     

+ 6 - 0
app.wxss

@@ -21,3 +21,9 @@
   justify-content: left;
   align-items: left;
 }
+
+.over-ellipsis{
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}

BIN
images/logo.png


BIN
images/xjq.png


BIN
images/zkq.png


+ 66 - 0
pages/activityInfo/activityInfo.js

@@ -0,0 +1,66 @@
+// pages/activityInfo/activityInfo.js
+Page({
+
+  /**
+   * 页面的初始数据
+   */
+  data: {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面加载
+   */
+  onLoad: function (options) {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面初次渲染完成
+   */
+  onReady: function () {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面显示
+   */
+  onShow: function () {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面隐藏
+   */
+  onHide: function () {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面卸载
+   */
+  onUnload: function () {
+
+  },
+
+  /**
+   * 页面相关事件处理函数--监听用户下拉动作
+   */
+  onPullDownRefresh: function () {
+
+  },
+
+  /**
+   * 页面上拉触底事件的处理函数
+   */
+  onReachBottom: function () {
+
+  },
+
+  /**
+   * 用户点击右上角分享
+   */
+  onShareAppMessage: function () {
+
+  }
+})

+ 10 - 0
pages/activityInfo/activityInfo.json

@@ -0,0 +1,10 @@
+{
+  "usingComponents": {},
+  "navigationBarBackgroundColor": "#fff",
+  "navigationBarTextStyle": "black",
+  "navigationBarTitleText": "活动详情",
+  "backgroundColor": "#fff",
+  "backgroundTextStyle": "light"
+
+ 
+}

+ 18 - 0
pages/activityInfo/activityInfo.wxml

@@ -0,0 +1,18 @@
+<!-- <view class="contain flex-row"> -->
+<view class="info flex-column">
+  <view class="top flex-row">
+    <image src="/images/logo.png" class="top-image"></image>
+  </view>
+  <view class="bottom flex-column">
+  <view class="title over-ellipsi">长阳路外卖店</view>
+  <view class="coupon-info over-ellipsi">含 折扣券(3)| 现金券(1)</view>
+  <image class="qrcode-img" src="/images/logo.png"> </image>
+  <view class="qrcode-title over-ellipsi">扫二维码 获得卡券礼包</view>
+  <view class="bottom-line"></view>
+  <view class="flex-row bottom-date">
+  <view>有效期</view>
+  <view>2021.09.07-2021.12.15</view>
+  </view>
+  </view>
+</view>
+<!-- </view> -->

+ 91 - 0
pages/activityInfo/activityInfo.wxss

@@ -0,0 +1,91 @@
+Page {
+  width: 100%;
+  height: 100%;
+  background-color: #BF2637;
+  box-sizing: border-box;
+  padding-top: 102px;
+  padding-left: 26px;
+}
+.info{
+  width: 327px;
+  height: 428px;
+  /* margin-top: 102px; */
+  align-items: center;
+  /* margin-left: 26px; */
+}
+.top{
+  height: 46px;
+  width: 327px;
+  background: #EEEEEE;
+  border-radius: 5px 5px 10px 10px;
+}
+.contain{
+  width: 100%;
+  height: 100%;
+  justify-content: center!important;
+}
+
+.top-image{
+  width: 142px;
+  height: 25px;
+  margin-left: 19px;
+}
+
+.bottom{
+  width: 327px;
+  height: 382px;
+  background: #FFFFFF;
+  border-radius: 10px 10px 5px 5px;
+  box-sizing: border-box;
+  padding:31px  13px;
+  align-items: center;
+}
+.title{
+  width: 300px;
+  height: 24px;
+  font-size: 24px;
+  font-weight: bold;
+  color: #444444;
+  text-align: center;
+  line-height: 24px;
+}
+.coupon-info{
+  width: 300px;
+  height: 18px;
+  font-size: 16px;
+  font-weight: 400;
+  color: #444444;
+  text-align: center;
+  line-height: 18px;
+  margin-top: 15px;
+}
+.qrcode-img{
+  width: 146px;
+  height: 146px;
+  margin-top: 28px;
+}
+.qrcode-title{
+  width: 200px;
+  height: 14px;
+  font-size: 14px;
+  font-weight: 400;
+  color: #444444;
+  line-height: 14px;
+  text-align: center;
+  margin-top: 16px;
+}
+.bottom-line{
+  border-bottom: 1px solid #ABABAB;
+  width: 300px;
+  margin-top: 24px;
+}
+.bottom-date{
+  margin-top: 24px;
+  justify-content: space-between;
+  width: 300px;
+  font-size: 14px;
+  line-height: 14px;
+  height: 14px;
+  font-weight: 400;
+  color: #444444;
+}

+ 61 - 3
pages/activityList/activityList.js

@@ -1,3 +1,5 @@
+const { default: activity } = require("../../api/activity");
+
 // pages/activityList/activityList.js
 Page({
 
@@ -5,14 +7,20 @@ Page({
    * 页面的初始数据
    */
   data: {
+    userMobile:'',
+     //火热
+     list: [], //卡券列表
+     page:1,
+     pageNum:10,
+     count:0
 
   },
 
   /**
    * 生命周期函数--监听页面加载
    */
-  onLoad: function (options) {
-
+  onLoad: function () {
+    this.getList()
   },
 
   /**
@@ -55,6 +63,18 @@ Page({
    */
   onReachBottom: function () {
 
+    var totalPage = Math.ceil(this.data.count/this.data.pageNum)
+    console.log('到底了')
+    var page = this.data.page
+    page++;
+    this.setData({
+      page:page
+    })
+    if(page<=totalPage){
+      console.log(123)
+      this.getList();
+    }
+
   },
 
   /**
@@ -62,5 +82,43 @@ Page({
    */
   onShareAppMessage: function () {
 
-  }
+  },
+  goToShare(){
+    wx.navigateTo({
+      url: '/pages/activityInfo/activityInfo',
+    })
+  },
+  getPhoneNumber(e) {
+    let _self = this;
+    var encryptedData = e.detail.encryptedData;
+    var iv = e.detail.iv;
+    if (!encryptedData || encryptedData.length == 0 || !iv || iv.length == 0) {
+      return;
+    }
+    //获取手机号
+    getApp().doDecodePhone(encryptedData, iv, function () {
+      let userMobile = activity.getMobileCache();
+      if (userMobile.length !== 0) {
+        _self.setData({
+          userMobile: userMobile,
+        })
+      }
+    
+    });
+  },
+
+  getList: async function() {
+    var result = await activity.getActivityList(this.data.page,this.data.pageNum,2);
+    if(this.data.page != 1){
+      var nowResult = this.data.list.concat(result.list)
+    }else{
+      var nowResult = result.list
+    }
+    
+    this.setData({
+      list:nowResult,
+      count:result.count,
+      page:result.page
+    })
+  },
 })

+ 1 - 1
pages/activityList/activityList.json

@@ -2,7 +2,7 @@
   "usingComponents": {},
   "navigationBarBackgroundColor": "#fff",
   "navigationBarTextStyle": "black",
-  "navigationBarTitleText": "领取活动",
+  "navigationBarTitleText": "活动列表",
   "backgroundColor": "#fff",
   "backgroundTextStyle": "light"
  

+ 8 - 7
pages/activityList/activityList.wxml

@@ -1,9 +1,9 @@
 <view class="list">
-  <view class="activity flex-column">
+  <view class="activity flex-column" data-id="{{item.id}}" wx:for="{{list}}" wx:if="{{list.length>0}}" wx:key="unique">
     <view class="top flex-row">
-      <image class="top-left" src="https://dy.shpr.top/recruit/home_page.png"></image>
+      <image class="top-left" src="{{item.fullCoverImg}}"></image>
       <view class="top-right flex-column">
-        <view class="top-right-top">长阳路外卖店券</view>
+        <view class="top-right-top">{{item.name}}</view>
         <view class="top-right-bottom flex-row">
           <view class="top-right-bottom-view  flex-row">
             <image class="trbv-img" src="/images/xjq.png"></image>
@@ -21,11 +21,12 @@
       <view class="middle-line"></view>
     </view>
     <view class="bottom flex-row">
-      <view class="bottom-left">2021.09.07-2022.12.15</view>
-      <view class="bottom-right">立即分享</view>
+      <view class="bottom-left">{{item.beginTimestamp}}至{{item.endTimestamp}}</view>
+      <view class="bottom-right" bindtap="goToShare" wx:if="{{userMobile.length>0}}">立即分享</view>
+      <button class="bottom-right" open-type="getPhoneNumber" bindgetphonenumber="getPhoneNumber" wx:else>立即分享</button>
     </view>
   </view>
-  <view class="activity flex-column">
+  <!-- <view class="activity flex-column">
     <view class="top flex-row">
       <image class="top-left" src="https://dy.shpr.top/recruit/home_page.png"></image>
       <view class="top-right flex-column">
@@ -50,5 +51,5 @@
       <view class="bottom-left">2021.09.07-2022.12.15</view>
       <view class="bottom-right">立即分享</view>
     </view>
-  </view>
+  </view> -->
 </view>

+ 9 - 5
pages/activityList/activityList.wxss

@@ -95,16 +95,17 @@ Page {
   justify-content: space-between!important;
 }
 .bottom-left{
-  width: 150px;
+  width: 200px;
 height: 20px;
 line-height: 20px;
-font-size: 10px;
+font-size: 9.5px;
 font-weight: 400;
 color: #BF2637;
-margin-left: 34px;
+margin-left: 22px;
 }
+
 .bottom-right{
-  width: 80px;
+  width: 80px!important;
   height: 20px;
   background-color: #BF2637;
   color: #fff;
@@ -112,5 +113,8 @@ margin-left: 34px;
   text-align: center;
   line-height: 20px;
   border-radius: 10px;
-  margin-right: 25px;
+  margin-right: 25px!important;
+  padding: 0 0;
+
 }
+

+ 1 - 1
pages/index/index.js

@@ -41,7 +41,7 @@ Page({
     // 不推荐使用getUserInfo获取用户信息,预计自2021年4月13日起,getUserInfo将不再弹出弹窗,并直接返回匿名的用户个人信息
     console.log(e)
     this.setData({
-      userInfo: e.detail.userInfo,
+      userInfo: e.detail.userInfo, 
       hasUserInfo: true
     })
   }

+ 66 - 0
pages/myCoupons/myCoupons.js

@@ -0,0 +1,66 @@
+// pages/myCoupons/myCoupons.js
+Page({
+
+  /**
+   * 页面的初始数据
+   */
+  data: {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面加载
+   */
+  onLoad: function (options) {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面初次渲染完成
+   */
+  onReady: function () {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面显示
+   */
+  onShow: function () {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面隐藏
+   */
+  onHide: function () {
+
+  },
+
+  /**
+   * 生命周期函数--监听页面卸载
+   */
+  onUnload: function () {
+
+  },
+
+  /**
+   * 页面相关事件处理函数--监听用户下拉动作
+   */
+  onPullDownRefresh: function () {
+
+  },
+
+  /**
+   * 页面上拉触底事件的处理函数
+   */
+  onReachBottom: function () {
+
+  },
+
+  /**
+   * 用户点击右上角分享
+   */
+  onShareAppMessage: function () {
+
+  }
+})

+ 8 - 0
pages/myCoupons/myCoupons.json

@@ -0,0 +1,8 @@
+{
+  "usingComponents": {},
+  "navigationBarBackgroundColor": "#fff",
+  "navigationBarTextStyle": "black",
+  "navigationBarTitleText": "我的卡券",
+  "backgroundColor": "#fff",
+  "backgroundTextStyle": "light"
+}

+ 1 - 0
pages/myCoupons/myCoupons.wxml

@@ -0,0 +1 @@
+

+ 1 - 0
pages/myCoupons/myCoupons.wxss

@@ -0,0 +1 @@
+/* pages/myCoupons/myCoupons.wxss */

+ 139 - 0
utils/request.js

@@ -0,0 +1,139 @@
+import util from './util.js'
+class request {
+
+    //本地的
+     static BASE_URL = 'http://www.lx.com:81/'
+//   pre环境的
+//   static BASE_URL = 'https://oapi.shpr.top/'
+//   正式的
+//   static BASE_URL = 'https://vapi.hsayi.com/'
+//   @todo需要修改正式的域名
+
+  static HEAD = {
+      "Content-Type": "application/json",
+      "SIGN":'',
+      "NONCE":'',
+      "TIMESTAMP":'',
+  }
+  static constructor() {
+
+  }
+
+  /**
+   * 设置统一的异常处理
+   */
+  static setErrorHandler(handler) {
+      this._errorHandler = handler
+  }
+
+  /**
+   * GET类型的网络请求
+   */
+  static getRequest(url, data ) {
+     let  headerSign = this.getSignHead();
+      return this.requestAll(url, data, headerSign, 'GET')
+  }
+
+  /**
+   * POST类型的网络请求
+   */
+  static postRequest(url, data) {
+      let  headerSign = this.getSignHead();
+      return this.requestAll(url, data, headerSign, 'POST')
+  }
+
+  static upload(url, name, path, ortherData, header = this.HEAD){
+      return new Promise((resolve, reject) => {
+          wx.uploadFile({
+              url: url,
+              header: header,
+              filePath: path,
+              name: name,
+              formData: ortherData,
+              success: (res => {
+                  res.data = JSON.parse(res.data);
+                  if (res.data.code === 200) {
+                      //200: 服务端业务处理正常结束
+                      resolve(res.data)
+                  } else {
+                        wx.showToast({
+                            title: res.data.msg,
+                        })
+                      //其它错误,提示用户错误信息
+                      if (this._errorHandler != null) {
+                          //如果有统一的异常处理,就先调用统一异常处理函数对异常进行处理
+                          this._errorHandler(res)
+                      }
+                      reject(res)
+                  }
+              }),
+              fail: (res => {
+                  if (this._errorHandler != null) {
+                      this._errorHandler(res)
+                  }
+                  wx.showToast({
+                    title: '网络异常请,稍后再试~',
+                  })
+                  reject(res)
+              })
+          })
+      })
+  }
+  static getSignHead(){
+    let timestamp = parseInt(Date.now()/1000);
+    let nonce = Math.floor(Math.random() * 1000).toString()
+    let signStr = timestamp+nonce+""+"U6Watb875eCiX4Lq";
+    let sign =  util.sha1(signStr).toString();
+    
+    let signHeadInfo = {
+        "SIGN":sign,
+        "NONCE":nonce,
+        "TIMESTAMP":timestamp,
+        "Content-Type": "application/json"
+    }
+    return signHeadInfo;
+  }
+  /**
+   * 网络请求
+   */
+  static requestAll(url, data, header, method) {
+     
+      wx.showLoading()
+      return new Promise((resolve, reject) => {
+          wx.request({
+              url: url,
+              data: data,
+              header: header,
+              method: method,
+              success: (res => {
+                  wx.hideLoading()
+                  if (res?.data.code === 200) {
+                      //200: 服务端业务处理正常结束
+                      resolve(res?.data)
+                  } else {
+                    wx.showToast({
+                        title: res?.data?.msg,
+                    })
+                      //其它错误,提示用户错误信息
+                      if (this._errorHandler != null) {
+                          //如果有统一的异常处理,就先调用统一异常处理函数对异常进行处理
+                          this._errorHandler(res)
+                      }
+                      reject(res)
+                  }
+              }),
+              fail: (res => {
+                  if (this._errorHandler != null) {
+                      this._errorHandler(res)
+                  }
+                  wx.showToast({
+                    title: '网络异常请稍后',
+                  })
+                  reject(res)
+              })
+          })
+      })
+  }
+}
+
+export default request

+ 52 - 1
utils/util.js

@@ -14,6 +14,57 @@ const formatNumber = n => {
   return n[1] ? n : `0${n}`
 }
 
+function encodeUTF8(s) {
+  var i, r = [], c, x;
+  for (i = 0; i < s.length; i++)
+    if ((c = s.charCodeAt(i)) < 0x80) r.push(c);
+    else if (c < 0x800) r.push(0xC0 + (c >> 6 & 0x1F), 0x80 + (c & 0x3F));
+    else {
+      if ((x = c ^ 0xD800) >> 10 == 0) //对四字节UTF-16转换为Unicode
+        c = (x << 10) + (s.charCodeAt(++i) ^ 0xDC00) + 0x10000,
+          r.push(0xF0 + (c >> 18 & 0x7), 0x80 + (c >> 12 & 0x3F));
+      else r.push(0xE0 + (c >> 12 & 0xF));
+      r.push(0x80 + (c >> 6 & 0x3F), 0x80 + (c & 0x3F));
+    };
+  return r;
+}
+
+// 字符串加密成 hex 字符串
+function sha1(s) {
+  var data = new Uint8Array(encodeUTF8(s))
+  var i, j, t;
+  var l = ((data.length + 8) >>> 6 << 4) + 16, s = new Uint8Array(l << 2);
+  s.set(new Uint8Array(data.buffer)), s = new Uint32Array(s.buffer);
+  for (t = new DataView(s.buffer), i = 0; i < l; i++)s[i] = t.getUint32(i << 2);
+  s[data.length >> 2] |= 0x80 << (24 - (data.length & 3) * 8);
+  s[l - 1] = data.length << 3;
+  var w = [], f = [
+    function () { return m[1] & m[2] | ~m[1] & m[3]; },
+    function () { return m[1] ^ m[2] ^ m[3]; },
+    function () { return m[1] & m[2] | m[1] & m[3] | m[2] & m[3]; },
+    function () { return m[1] ^ m[2] ^ m[3]; }
+  ], rol = function (n, c) { return n << c | n >>> (32 - c); },
+    k = [1518500249, 1859775393, -1894007588, -899497514],
+    m = [1732584193, -271733879, null, null, -1009589776];
+  m[2] = ~m[0], m[3] = ~m[1];
+  for (i = 0; i < s.length; i += 16) {
+    var o = m.slice(0);
+    for (j = 0; j < 80; j++)
+      w[j] = j < 16 ? s[i + j] : rol(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16], 1),
+        t = rol(m[0], 5) + f[j / 20 | 0]() + m[4] + w[j] + k[j / 20 | 0] | 0,
+        m[1] = rol(m[1], 30), m.pop(), m.unshift(t);
+    for (j = 0; j < 5; j++)m[j] = m[j] + o[j] | 0;
+  };
+  t = new DataView(new Uint32Array(m).buffer);
+  for (var i = 0; i < 5; i++)m[i] = t.getUint32(i << 2);
+
+  var hex = Array.prototype.map.call(new Uint8Array(new Uint32Array(m).buffer), function (e) {
+    return (e < 16 ? "0" : "") + e.toString(16);
+  }).join("");
+  return hex;
+}
+
 module.exports = {
-  formatTime
+  formatTime,
+  sha1
 }