tremble 5 năm trước cách đây
mục cha
commit
f40d683901

+ 1 - 1
config/index.js

@@ -12,7 +12,7 @@ module.exports = {
     assetsPublicPath: '/',
     proxyTable: {
       '/api': {    //将www.exaple.com印射为/apis
-        target: 'https://pro.4dkankan.com',  // 接口域名
+        target: 'https://www.4dkankan.com',  // 接口域名
         secure: false,  // 如果是https接口,需要配置这个参数
         changeOrigin: true,  //是否跨域
         pathRewrite: {

+ 1 - 0
package.json

@@ -17,6 +17,7 @@
     "axios": "^0.19.0",
     "echarts": "^4.2.1",
     "element-ui": "^2.12.0",
+    "js-base64": "^2.5.2",
     "vue": "^2.5.2",
     "vue-router": "^3.0.1"
   },

+ 14 - 1
src/page/device/index.vue

@@ -20,6 +20,8 @@
               </el-table-column>
               <el-table-column prop="goodsName" label="设备类型">
               </el-table-column>
+              <el-table-column prop="snCode" label="sn码">
+              </el-table-column>
               <el-table-column width="300" label="激活时间">
                 <template slot-scope="scope">
                   <div>{{scope.row.activatedTime}}</div>
@@ -91,6 +93,9 @@
             <el-form-item label="物理地址" prop="address">
               <el-input v-model="ruleForm.address"></el-input>
             </el-form-item>
+            <el-form-item label="sn码" prop="snCode">
+              <el-input v-model="ruleForm.snCode"></el-input>
+            </el-form-item>
             <el-form-item label="初始点数" @keyup.native="_checkPointInput" prop="initPoint">
               <el-input v-model="ruleForm.initPoint"></el-input>
             </el-form-item>
@@ -161,6 +166,7 @@ export default {
         initPoint: '',
         type: [],
         own: [],
+        snCode: '',
         orderNum: ''
       },
       rules: {
@@ -172,6 +178,10 @@ export default {
           { required: true, message: '请输入物理地址', trigger: 'blur' }
           // { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
         ],
+        snCode: [
+          { required: true, message: '请输入SN码', trigger: 'blur' }
+          // { min: 3, max: 5, message: '长度在 3 到 5 个字符', trigger: 'blur' }
+        ],
         initPoint: [
           { required: true, message: '请输入初始点数', trigger: 'blur' }
           // { type: "number", message: '必须为数字值', trigger: 'blur' }
@@ -333,9 +343,10 @@ export default {
         initPoint: balance,
         own,
         type,
+        snCode,
         orderNum: orderSn
       } = this.ruleForm
-      if (wifiName === '' || childName === '' || balance === '' || own === '' || type === '') {
+      if (wifiName === '' || childName === '' || snCode === '' || balance === '' || own === '' || type === '') {
         return
       }
 
@@ -344,6 +355,7 @@ export default {
         childName,
         wifiName,
         orderSn,
+        snCode,
         balance: Number(balance),
         type: Number(type),
         own
@@ -358,6 +370,7 @@ export default {
         this.ruleForm.wifi = ''
         this.ruleForm.own = ''
         this.ruleForm.orderNum = ''
+        this.ruleForm.snCode = ''
         this.ruleForm.type = ''
         this.ruleForm.initPoint = ''
 

+ 543 - 0
src/page/invoice/index.vue

@@ -0,0 +1,543 @@
+<template>
+  <div id="order-check">
+    <el-dialog
+      title="查看图片"
+      :visible.sync="dialogVisible"
+      width="45%"
+      center
+      >
+      <div style="text-align:center;">
+        <img style="width:100%" :src="invoiceImg" alt="">
+      </div>
+      <span slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="dialogVisible = false">关 闭</el-button>
+      </span>
+    </el-dialog>
+    <div class="order-check-body" v-loading.fullscreen.lock="fullscreenLoading">
+      <div class="order-management-body">
+        <div class="order-management-inner">
+          <span>发票类型:</span>
+          <el-select @change="selectChange" v-model="search_value" placeholder="请选择">
+            <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value">
+            </el-option>
+          </el-select>
+          <div class="base-info">
+            <span>订单号:</span>
+            <el-input ref="searchOrderNumber" v-model="orderSn" value="" placeholder="订单号"></el-input>
+            <span style="margin-left:20px;">关键字:</span>
+            <el-input ref="searchPhone" v-model="key_input" value="" placeholder="抬头/电话/邮箱"></el-input>
+            <el-button type="primary" @click="_searchOrderData(1)" color='red'>筛选</el-button>
+          </div>
+        </div>
+        <div class="order-check_bottom">
+          <div class="order-management-table">
+            <div class="order-check_tab">
+              <ul>
+                <li v-for="(item,index) in tabs" :key="index" :class="{'order-check_tab_li_active':item.idx==tabIndex}" @click="clickTabItem(item.idx)">{{item.name}}<span v-if="item.idx != 1" style="margin:0 10px;color: #999;font-weight: normal;">/</span></li>
+              </ul>
+              <div style="float: right;vertical-align: middle;">
+                <!-- <el-select v-model="selectValue" placeholder="请选择">
+                  <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value">
+                  </el-option>
+                </el-select> -->
+                <el-button type="primary" @click="_exportExcelForOrder" color='red'>导出</el-button>
+              </div>
+            </div>
+            <el-table ref="order_table" @expand-change="_getOrderDetail" class='e-table' :data="orders" style="width: 100%">
+              <el-table-column type="index">
+              </el-table-column>
+              <el-table-column v-for="(item,i) in tableHeader" :key="i" :prop="item.name" :label="item.label">
+                <template slot-scope="scope">
+                  <span>{{scope.row[item.name]||'-'}}</span>
+                </template>
+              </el-table-column>
+              <el-table-column type="expand" prop="operation" label="操作" width="150">
+                <template slot-scope="scope">
+                  <div class="order_check_row">
+                    <div class="product_info">
+                      <div class="product_info_title">
+                        商品信息
+                      </div>
+                      <el-table @expand-change="_getOrderDetail" class='e-table' :data="scope.row.orderDetail.orderItems||[]" style="width: 100%">
+                        <el-table-column v-for="(item,i) in itemHeader" :key="i" :prop="item.name" :label="item.label">
+                          <template slot-scope="scope">
+                            <span>{{scope.row[item.name]||'-'}}</span>
+                          </template>
+                        </el-table-column>
+                      </el-table>
+                      <div class="total">总价:{{scope.row.money}}</div>
+                    </div>
+                    <div class="order_info">
+                      <div class="order_info_title">
+                        下单信息
+                      </div>
+                      <div class="order_info_body">
+                        <div class="order_info_body_info">
+                          <div class="order_info_body_info_item">
+                            <span class="order_info_body_info_item_title">顾客姓名</span>
+                            <span class="order_info_body_info_item_content">{{scope.row.shipName||'-'}}</span>
+                          </div>
+                          <div class="order_info_body_info_item">
+                            <span class="order_info_body_info_item_title">电话</span>
+                            <span class="order_info_body_info_item_content">{{scope.row.shipMobile||'-'}}</span>
+                          </div>
+                          <div class="order_info_body_info_item">
+                            <span class="order_info_body_info_item_title">收货地址</span>
+                            <span class="order_info_body_info_item_content">{{scope.row.shipAddress||'-'}}</span>
+                          </div>
+
+                          <template v-if="scope.row.type === 2">
+                            <div class="order_info_body_info_item">
+                              <span class="order_info_body_info_item_title">电子邮件:</span>
+                              <span class="order_info_body_info_item_content">{{scope.row.emailAddress||'-'}}</span>
+                            </div>
+
+                            <div class="order_info_body_info_item">
+                              <span class="order_info_body_info_item_title">电子发票:</span>
+                              <span class="order_info_body_info_item_content" style="position:relative;">
+                                <div style="display:inline-block;">
+                                    <el-upload
+                                      class="upload-demo"
+                                      :headers="{token:token}"
+                                      :on-change="handleChange"
+                                      :file-list="fileList"
+                                      :on-success="upload_success"
+                                      :on-error="upload_fail"
+                                      :action="`https://test.4dkankan.com/api/manager/invoice/uploadEInvoice`"
+                                      :limit="1"
+                                      >
+                                      <el-button size="small" type="primary">{{scope.row.einvoice?'更改发票':'上传发票'}}</el-button>
+                                    </el-upload>
+                                </div>
+
+                                <span style="color:#1fe4dc;margin-left:10px; cursor:pointer;" @click="lookInvoice">查看电子发票</span>
+                              </span>
+                            </div>
+                          </template>
+
+                          <template v-if="scope.row.type === 3">
+                           <div class="order_info_body_info_item">
+                              <span class="order_info_body_info_item_title">快递单号:</span>
+                              <span class="order_info_body_info_item_content">
+                                <el-input
+                                  v-model="scope.row.expressNumber" maxlength="15" placeholder="请输入15位顺丰快单号"
+                                  type="text">
+                                </el-input>
+                              </span>
+                            </div>
+                          </template>
+                          <div class="order_info_body_info_item">
+                            <span class="order_info_body_info_item_title">开票备注:</span>
+                            <span class="order_info_body_info_item_content">
+                              <div style="width:220px;display:inline-block;" >
+                                <el-input type="textarea" v-model="scope.row.remarks"></el-input>
+                              </div>
+                            </span>
+                          </div>
+
+                          <div class="order_info_body_info_item">
+                            <span class="order_info_body_info_item_title"></span>
+                            <span class="order_info_body_info_item_content">
+                              <el-button type="primary" @click="saveRow(scope.row)">{{scope.row.type === 2?'发送':'保存'}}</el-button>
+                              <el-button @click="hideRow(scope.row)">取消</el-button>
+                            </span>
+                          </div>
+                        </div>
+                      </div>
+                    </div>
+                  </div>
+                  <!-- <div v-if="">暂无数据</div> -->
+                </template>
+              </el-table-column>
+            </el-table>
+          </div>
+          <div class="order-management-pagination">
+            <el-pagination  @current-change="handleCurrentChange" :current-page.sync="currentPage" :page-size="10" layout="total, prev, pager, next, jumper" :total="total">
+            </el-pagination>
+          </div>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+const tableHeader = [{
+  name: 'orderSn',
+  label: '订单编号'
+}, {
+  name: 'typeStr',
+  label: '发票类型'
+}, {
+  name: 'money',
+  label: '开票金额'
+}, {
+  name: 'title',
+  label: '发票抬头'
+}, {
+  name: 'code',
+  label: '18位税号'
+}, {
+  name: 'organizedAddress',
+  label: '注册地址'
+}, {
+  name: 'registerPhone',
+  label: '注册电话'
+}, {
+  name: 'bankAccount',
+  label: '银行账号'
+}, {
+  name: 'shipAddress',
+  label: '收件地址'
+}, {
+  name: 'emailAddress',
+  label: '电子邮箱'
+}, {
+  name: 'shipMobile',
+  label: '手机号码'
+}]
+
+const itemHeader = [
+  {
+    name: 'goodsName',
+    label: '商品名称'
+  },
+  {
+    name: 'goodsCount',
+    label: '数量'
+  },
+  {
+    name: 'goodsPrice',
+    label: '价格'
+  },
+  {
+    name: 'abroad',
+    label: '币种'
+  }
+]
+
+const TypeArr = {
+  2: '增值税普通发票',
+  3: '增值税专用发票'
+}
+
+export default {
+
+  name: 'order-check',
+
+  data () {
+    return {
+      dialogVisible: false,
+      fileVal: '',
+      tableHeader,
+      itemHeader,
+      statusMap: {
+        unprocessed: '未处理',
+        processed: '已确认',
+        completed: '已完成',
+        invalid: '已取消'
+      },
+      payMap: {
+        '0': '微信',
+        '1': '支付宝',
+        '2': 'paypal',
+        '3': '其他',
+        '4': '货到付款'
+      },
+      paymentStatusMap: {
+        unpaid: '未付款',
+        paid: '已付款',
+        cancel: '已取消',
+        partPayment: '部分支付',
+        partRefund: '部分退款',
+        refunded: '全额退款'
+      },
+      tabs: [
+        { name: '未开票', idx: 0 },
+        { name: '已开票', idx: 1 }
+      ],
+      expandedArr: [],
+      orders: [],
+      currentPage: 1,
+      key_input: '',
+      orderSn: '',
+      fullscreenLoading: false,
+      product: {
+        'name': '',
+        'packageName': '',
+        'count': '',
+        'amount': '',
+        'url': ''
+      },
+      receive: {
+        'name': '',
+        'phone': '',
+        'address': '',
+        'invoice': '',
+        'expressNum': ''
+      },
+      total: 0,
+      options: [{
+        value: '',
+        label: '全部'
+      }, {
+        value: 2,
+        label: '增值税普通发票'
+      }, {
+        value: 3,
+        label: '增值税专用发票'
+      }],
+      selectValue: '',
+      // expressNum_input: "",
+      searchDate: [],
+      searchOrderNumber: '',
+      searchPhone: '',
+      searchExpressNum: '',
+      hasClickSearch: false,
+      tabIndex: 0,
+      search_value: '',
+      invoiceImg: '',
+      token: sessionStorage.token,
+      fileList: []
+    }
+  },
+  watch: {
+
+  },
+  methods: {
+    handleChange (file, fileList) {
+      // console.log('file', file)
+      // console.log('fileList', fileList)
+
+      if (fileList.length > 1) {
+        this.fileList = fileList.slice(-1)
+      }
+    },
+    upload_success (data) {
+      if (data.code === 0) {
+        this.invoiceImg = data.msg
+      }
+    },
+    lookInvoice () {
+      if (this.invoiceImg) {
+        window.open(this.invoiceImg, '_blank')
+      }
+    },
+    upload_fail (data) {
+      this.$notify.error({
+        title: '上传失败',
+        message: data.message
+      })
+    },
+    selectChange (e) {
+      this.search_value = e
+    },
+    async saveRow (row) {
+      let {type, id, remarks, expressNumber} = row
+      let params = {
+        invoiceId: id,
+        remarks,
+        filePath: this.invoiceImg,
+        expressNumber,
+        expressCompany: '顺丰'
+      }
+      var pattern = /^[\u4E00-\u9FA5]{1,5}$/
+
+      let url = type === 2 ? '/manager/invoice/sendEInvoice' : '/manager/invoice/sendExpress'
+
+      if (!this.invoiceImg && type === 2) {
+        return this.$alert('请上传电子发票', '提示', {
+          confirmButtonText: '确定',
+          callback: action => {
+          }
+        })
+      }
+
+      if (!expressNumber && type === 3) {
+        return this.$alert('请输入快递单号', '提示', {
+          confirmButtonText: '确定',
+          callback: action => {
+          }
+        })
+      } else if (pattern.test(expressNumber) && type === 3) {
+        return this.$alert('快递单号不能为中文', '提示', {
+          confirmButtonText: '确定',
+          callback: action => {
+
+          }
+        })
+      }
+
+      let data = await this.$http.post(url, params)
+      if (data.code === 0) {
+        this.$message({
+          message: '保存成功!',
+          type: 'success'
+        })
+        this.hideRow(row)
+      } else {
+        this.$alert('保存失败', '提示', {
+          confirmButtonText: '确定',
+          callback: action => {}
+        })
+      }
+    },
+    hideRow (row) {
+      this.$refs.order_table.toggleRowExpansion(row, false)
+    },
+    handleCurrentChange (val) {
+      let page = val
+      if (this.total > 0 && !this.hasClickSearch) {
+        this._searchOrderData(page)
+      } else {
+        this._searchOrderData(page)
+      }
+    },
+    clickTabItem (idx) {
+      this.tabIndex = idx
+      this.hasClickSearch = false
+      this.total = 0
+      this.currentPage = 1
+      this.status = this.tabIndex === -1 ? null : this.tabIndex
+
+      if (!this.searchDate || this.expressNum || !this.userName || !this.orderNum) {
+        this._searchOrderData()
+      }
+    },
+
+    async _searchOrderData (page) {
+      this.hasClickSearch = true
+
+      // console.log(searchDate,searchPhone,searchExpressNum,searchOrderNumber)
+      this.fullscreenLoading = true
+
+      let data = (await this.$http.post('/manager/invoice/invoiceList', {
+        pageNum: page,
+        pageSize: 10,
+        type: this.search_value,
+        finish: this.tabIndex,
+        orderSn: this.orderSn,
+        searchKey: this.key_input
+      })).data
+
+      this.fullscreenLoading = false
+      let temp = data.list
+      for (var i = 0; i < temp.length; i++) {
+        // temp[i]['expressNum_input'] = "";
+        temp[i].items = []
+        temp[i]['typeStr'] = TypeArr[temp[i]['type']]
+        temp[i]['orderSn'] = temp[i]['orderDetail'] ? temp[i]['orderDetail']['orderSn'] : ''
+      }
+      this.orders = temp
+      this.currentPage = page
+      this.total = data.total
+    },
+    _exportExcelForOrder () {
+      let date1, date2, userName, expressNum, orderNum
+      this.date1 = this.searchDate ? this.searchDate[0] : null
+      this.date2 = this.searchDate ? this.searchDate[1] : null
+      date1 = this.date1 ? (this.date1 + ' 00:00:00') : null
+      date2 = this.date2 ? (this.date2 + ' 23:59:59') : null
+
+      this.userName = userName = this.searchPhone || null
+      this.expressNum = expressNum = this.searchExpressNum || null
+      this.orderNum = orderNum = this.searchOrderNumber || null
+
+      let data = {
+        'type': this.status,
+        'startDate': date1,
+        'endDate': date2,
+        'orderSn': orderNum,
+        'phoneNum': userName,
+        'expressNum': expressNum
+      }
+      this.fullscreenLoading = true
+
+      this.$confirm('确定当前标签下的订单记录?', '导出订单', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(async () => {
+        let exec = await this.$http({
+          methods: 'get',
+          params: data,
+          url: '/manager/order/export',
+          responseType: 'arraybuffer'
+        })
+        try {
+          let blob = new Blob([exec], {type: 'application/vnd.ms-excel'})
+          let url = URL.createObjectURL(blob)
+          location.href = url
+        } catch (e) {
+          console.error(e)
+        }
+        this.fullscreenLoading = false
+      }).catch(() => {
+        this.$message({
+          type: 'info',
+          message: '已取消导出'
+        })
+        this.fullscreenLoading = false
+      })
+    },
+    async _getOrderDetail (row) {
+      console.log('row---------', row)
+      this.invoiceImg = row.einvoice
+      // this.fullscreenLoading = true
+      // let data = (await this.$http.post('/manager/order/detail', {orderId: row.id})).data
+      // let temp = this.orders.find(item => item.id === row.id)
+
+      // temp.items = data.orderItems.map(item => {
+      //   return {
+      //     ...item,
+      //     product: {
+      //       url: item.pic,
+      //       name: item.goodsName,
+      //       packageName: item.description,
+      //       count: item.goodsCount,
+      //       amount: item.goodsPrice
+      //     },
+      //     receive: {
+      //       name: data.shipName,
+      //       phone: data.shipMobile,
+      //       address: data.shipAddress,
+      //       invoice: data.invoice,
+      //       expressNum: item.expressNum,
+      //       expressName: item.expressName
+      //     }
+      //   }
+      // })
+      // console.log(temp)
+      // this.fullscreenLoading = false
+    }
+  },
+  mounted () {
+    // console.log(window.location.origin)
+    // window.open(window.location.origin+'/main')
+    this._searchOrderData(1)
+  }
+}
+</script>
+
+<style scoped>
+@import url('./style.css');
+.el-icon-s-flag {
+  cursor: pointer;
+}
+
+</style>
+
+<style type="text/css">
+.el-table__expand-icon--expanded {
+  transform: rotate(0) !important;
+}
+.el-table__expand-icon>i {
+  display: none !important;
+}
+
+.el-table__expand-icon:after {
+  content: "\8BE6\7EC6";
+  color: #09e1c0;
+  cursor: pointer;
+}
+</style>

+ 180 - 0
src/page/invoice/style.css

@@ -0,0 +1,180 @@
+.order-check-body {
+  width: 100%;
+  float: left;
+  /*  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+  -moz-box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+  -webkit-box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);*/
+  /*border: 1px solid #ebeef5;*/
+}
+.total{
+  text-align: right;
+  font-size: 18px;
+  margin-top: 10px;
+  font-weight: bold;
+}
+.order_check_row {
+  display: flex;
+  width: 100%;
+}
+
+.product_info {
+  width: 50%;
+  flex-direction: column;
+  display: flex;
+  padding: 20px;
+  background: #fff;
+}
+
+.order_info {
+  width: 50%;
+  padding: 20px;
+  margin-left: 20px;
+  background: #fff;
+}
+
+.product_info_title {
+  text-align: left;
+  font-weight: 700;
+}
+
+.product_info_body {
+  margin-left: 30px;
+  display: flex;
+  margin-top: 20px;
+}
+
+.product_info_body_img {
+  width: 110px;
+  height: 110px;
+  border: 1px solid #ddd;
+}
+
+.product_img {
+  margin: 5px auto;
+  width: 100px;
+  height: 100px;
+}
+
+.product_info_body_info {
+  margin-left: 50px;
+}
+
+.product_info_body_info_item {
+  line-height: 27px;
+  text-align: left;
+}
+
+.product_info_body_info_item_title {
+  width: 100px;
+  display: inline-block;
+  margin-right: 20px;
+  font-weight: 700;
+}
+
+.order_info_title {
+  text-align: left;
+  font-weight: 700;
+  margin-bottom: 20px;
+}
+
+.order_info_body {
+  margin-left: 30px;
+  text-align: left;
+}
+
+.order_info_body_info_item {
+  line-height: 23px;
+  padding: 10px 0;
+  border-bottom: 1px solid #f5f5f5;
+  text-align: left;
+}
+
+.order_info_body_info_item_title {
+  width: 100px;
+  display: inline-block;
+  margin-right: 20px;
+  font-weight: 700;
+  vertical-align: top;
+}
+
+.fix-order_info_body_info_item_content {
+  width: 70%;
+  display: inline-block;
+}
+
+.order-management-body {
+  width: 100%;
+  margin: 30px 0 20px 0;
+  float: left;
+}
+
+.order-management-inner {
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+  -moz-box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+  -webkit-box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+  /*border: 1px solid #ebeef5;*/
+  padding: 30px 20px;
+  background: #fff;
+  border-radius: 5px;
+}
+
+.base-info {
+  margin: 20px 0;
+}
+
+.order-check_bottom {
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+  -moz-box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+  -webkit-box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+  /*border: 1px solid #ebeef5;*/
+  margin-top: 30px;
+  background: #fff;
+  border-radius: 5px;
+  padding: 20px 0;
+}
+
+.order-check_tab {
+  margin: 0 0 20px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.order-check_tab ul {
+  display: inline-block;
+}
+
+.order-check_tab li {
+  display: inline-block;
+  cursor: pointer;
+  color: #999;
+  line-height: 1;
+}
+
+.order-check_tab_li_active {
+  color: #000 !important;
+  font-weight: bold;
+}
+
+.order-management-table {
+  margin: 0 20px;
+}
+
+.order-management-pagination {
+  padding: 20px 20px 0;
+  position: relative;
+  text-align: right;
+}
+
+.el-input {
+  width: 220px;
+}
+
+.upload-btn{
+  opacity: 0;
+  position: absolute;
+  top: 0;
+  width: 80px;
+  height: 30px;
+  left: 0;
+}

+ 3 - 2
src/page/layout/slide.vue

@@ -49,7 +49,8 @@ export default {
       //   ],
       //   top: 98
       // },
-      { text: '设备管理', link: {name: 'user-list'}, top: 126 },
+      { text: '设备管理', link: {name: 'device'}, top: 126 },
+      { text: '发票管理', link: {name: 'invoice'}, top: 126 },
       {
         text: '更多设置',
         link: {name: 'home'},
@@ -63,7 +64,7 @@ export default {
       { text: '经销商申请', link: {name: 'distributor-list'}, top: 98 },
       { text: '留言管理', link: {name: 'leaving'}, top: 182 },
       { text: '版本管理', link: {name: 'edition'}, top: 182 },
-      { text: '数据统计', link: {name: 'Statistics'}, top: 210 },
+      { text: '数据统计', link: {name: 'Statistics'}, top: 210 }
     //   { text: '数据下载', link: {name: 'Down'}, top: 210 }
       // ,
       // {

+ 3 - 1
src/page/login/index.vue

@@ -41,6 +41,8 @@
   </div>
 </template>
 <script>
+import { reg, encodeStr } from '@/util'
+import { Base64 } from 'js-base64'
 
 export default {
   name: 'login',
@@ -103,7 +105,7 @@ export default {
     async login () {
       let param = {
         [this.param.user]: this.ruleForm2.username,
-        [this.param.pw]: this.ruleForm2.pass
+        [this.param.pw]: encodeStr(Base64.encode(this.ruleForm2.pass))
       }
 
       let data = await this.$http.post('/sso/manager/login', param)

+ 12 - 14
src/page/order/index.vue

@@ -54,7 +54,7 @@
               </el-table-column>
               <el-table-column type="expand" prop="operation" label="操作" width="150">
                 <template slot-scope="scope">
-                  
+
                   <div class="order_check_row">
                     <div class="product_info">
                       <div class="product_info_title">
@@ -100,7 +100,7 @@
                           </div>
                           <div class="order_info_body_info_item">
                             <span class="order_info_body_info_item_title">收货地址</span>
-                            <span class="order_info_body_info_item_content">{{scope.row.items[0].receive.address}}</span>
+                            <span class="order_info_body_info_item_content">{{scope.row.shipAreaPath}}{{scope.row.items[0].receive.address}}</span>
                           </div>
                           <div class="order_info_body_info_item">
                             <span class="order_info_body_info_item_title">发票</span>
@@ -110,7 +110,7 @@
                             <span class="order_info_body_info_item_title">快递类别</span>
                             <span class="order_info_body_info_item_content">
                               <el-input
-                                v-model="scope.row.items[0].receive.expressName" maxlength="15" placeholder="请输入快递类别" 
+                                v-model="scope.row.items[0].receive.expressName" maxlength="15" placeholder="请输入快递类别"
                                 type="text" v-if="scope.row.items[0].shippingStatus != 'shipped'" disabled>
                               </el-input>
                               {{scope.row.items[0].shippingStatus == 'shipped'?scope.row.items[0].receive.expressName:""}}
@@ -120,7 +120,7 @@
                             <span class="order_info_body_info_item_title">快递单号</span>
                             <span class="order_info_body_info_item_content">
                               <el-input
-                                v-model="scope.row.items[0].receive.expressNum" maxlength="15" placeholder="请输入15位顺丰快单号" 
+                                v-model="scope.row.items[0].receive.expressNum" maxlength="15" placeholder="请输入15位顺丰快单号"
                                 type="text" v-if="scope.row.items[0].shippingStatus != 'shipped'">
                               </el-input>
                               {{scope.row.items[0].shippingStatus == 'shipped'?scope.row.items[0].receive.expressNum:""}}
@@ -271,14 +271,14 @@ export default {
           confirmButtonText: '确定',
           callback: action => {
           }
-        });
+        })
       } else if (pattern.test(expressNum)) {
         return this.$alert('快递单号不能为中文', '提示', {
           confirmButtonText: '确定',
           callback: action => {
 
           }
-        });
+        })
       }
 
       let data = await this.$http.post('/manager/order/confirmDelivery', {
@@ -394,12 +394,12 @@ export default {
       let status = this.status
 
       let data = {
-        "type": this.status,
-        "startDate": date1,
-        "endDate": date2,
-        "orderSn": orderNum,
-        "phoneNum": userName,
-        "expressNum": expressNum,
+        'type': this.status,
+        'startDate': date1,
+        'endDate': date2,
+        'orderSn': orderNum,
+        'phoneNum': userName,
+        'expressNum': expressNum
       }
       this.fullscreenLoading = true
 
@@ -484,8 +484,6 @@ export default {
   display: none !important;
 }
 
-
-
 .el-table__expand-icon:after {
   content: "\8BE6\7EC6";
   color: #09e1c0;

+ 298 - 168
src/page/scene/index.vue

@@ -10,6 +10,13 @@
             placeholder="关键词"
           ></el-input>
           <el-button type="primary" @click="currentPage=1&&_getSceneData()" color="red">搜索</el-button>
+          <span style="margin-left:10px;">SN码:</span>
+          <el-input
+            @keyup.enter.native="getSceneBySN"
+            v-model="snKey"
+            placeholder="输入SN码下载场景数据"
+          ></el-input>
+          <el-button type="primary" @click="getSceneBySN" color="red">下载场景数据</el-button>
         </div>
       </div>
       <!-- 全部 -->
@@ -94,7 +101,7 @@
                 <el-button
                   v-if="scope.row.sceneScheme>4"
                   type="text"
-                  @click="_downloadScene(scope.row.num);"
+                  @click="_showDownloadSelect(scope.row.num);"
                   class="download_btn"
                 >下载</el-button>
                 <el-button type="text" @click="_deleScene(scope.row.id)" class="delete_btn">删除</el-button>
@@ -280,6 +287,27 @@
 
     <el-dialog
       width="500px"
+      title="场景下载"
+      :visible.sync="downloadDialogVisible"
+    >
+      <el-form :model="downloadOptions">
+        <el-form-item label="是否下载tiles">
+          <el-switch
+            v-model="downloadOptions.isTiles"
+            active-color="#13ce66"
+            inactive-color="#ff4949">
+          </el-switch>
+        </el-form-item>
+      </el-form>
+
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="downloadDialogVisible = false">取 消</el-button>
+        <el-button type="primary" @click="_downloadSingleScene">确 定</el-button>
+      </span>
+    </el-dialog>
+
+    <el-dialog
+      width="500px"
       title="下载场景"
       :visible.sync="download.showSta"
       :before-close="_handleClose"
@@ -302,291 +330,393 @@
 </template>
 <script>
 const _sceneTypeName = {
-  0: "其他",
-  1: "文博",
-  2: "地产",
-  3: "电商",
-  4: "餐饮",
-  5: "家居"
-};
+  0: '其他',
+  1: '文博',
+  2: '地产',
+  3: '电商',
+  4: '餐饮',
+  5: '家居'
+}
+
+// const serverName = 'https://test.4dkankan.com'
+// const serverName = 'http://192.168.0.208:8887'
+const serverName = 'https://www.4dage.com'
+
 export default {
-  data() {
+  data () {
     return {
-      getRowKeys(row) {
-        return row.number;
+      getRowKeys (row) {
+        return row.number
       },
+
       download: {
         showSta: false,
-        downloadSta: "正在拉取数据",
-        downloadDataName: "场景数据.zip",
+        downloadSta: '正在拉取数据',
+        downloadDataName: '场景数据.zip',
         percent: 0,
         timer: 0
       },
       downloadDialogVisible: false,
-      progressColor: "#09e1c0",
+      progressColor: '#09e1c0',
       tabs: [
-        { name: "全部", idx: 0 },
-        { name: "展示中", idx: 1 },
-        { name: "已隐藏", idx: -2 }
+        { name: '全部', idx: 0 },
+        { name: '展示中', idx: 1 },
+        { name: '已隐藏', idx: -2 }
       ],
       expands: [],
       expandedArr: [],
       scenes: [],
       currentPage: 1,
-      key_input: "",
+      key_input: '',
       fullscreenLoading: false,
       total: 0,
       // expressNum_input: "",
       searchDate: [],
-      searchKey: "",
-      searchOrderNumber: "",
-      searchPhone: "",
-      searchExpressNum: "",
+      searchKey: '',
+      snKey: '',
+      searchOrderNumber: '',
+      searchPhone: '',
+      searchExpressNum: '',
       hasClickSearch: false,
       tabIndex: 0,
       pageSize: 10,
-      value2: true
-    };
+      value2: true,
+      downloadOptions: {}
+    }
   },
   watch: {
-    currentPage() {
-      this._getSceneData();
+    currentPage () {
+      this._getSceneData()
     },
-    tabIndex() {
-      this._getSceneData();
+    tabIndex () {
+      this._getSceneData()
     }
   },
   methods: {
-    async changTypeHandle(item) {
-      this.fullscreenLoading = true;
-      await this.$http.post("/manager/scene/updateSceneType", {
+    async changTypeHandle (item) {
+      this.fullscreenLoading = true
+      await this.$http.post('/manager/scene/updateSceneType', {
         sceneType: item.sceneType,
         sceneId: item.id
-      });
-
-      this._getSceneData();
-      this.fullscreenLoading = false;
+      })
 
+      this._getSceneData()
+      this.fullscreenLoading = false
     },
-    handleCurrentChange(val) {
-      let page = val;
+
+    handleCurrentChange (val) {
+      let page = val
       // console.log(`当前页: ${val}`)
       if (this.total > 0 && !this.hasClickSearch) {
-        this._getSceneData(page);
+        this._getSceneData(page)
       } else {
-        this._searchOrderByPage(page);
+        this._searchOrderByPage(page)
       }
     },
-    clickTabItem(idx) {
+    clickTabItem (idx) {
       // console.log(idx)
-      this.tabIndex = idx;
-      this.total = 0;
-      this.hasClickSearch = false;
-      this.currentPage = 0;
-      this.$refs.searchKey.currentValue = this.key_input = "";
-      this._getSceneData(1);
+      this.tabIndex = idx
+      this.total = 0
+      this.hasClickSearch = false
+      this.currentPage = 0
+      this.$refs.searchKey.currentValue = this.key_input = ''
+      this._getSceneData(1)
+    },
+
+    async getSceneBySN () {
+      if (!this.snKey) {
+        return
+      }
+      this.fullscreenLoading = true
+
+      let res = await this.$http.post('/manager/scene/findSceneProBySnCode', {
+        type: '',
+        snCode: this.snKey
+      })
+      this.fullscreenLoading = false
+      let arr = []
+      if (res.code === 0) {
+        arr = res.data.map((item) => item.num)
+      }
+      if (arr.length <= 0) {
+        return this.$message({
+          type: 'info',
+          message: '查询不到该SN码场景数据。'
+        })
+      }
+      this.$confirm(`此操作将下载该相机下的${arr.length}个场景, 是否继续?`, '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      })
+        .then(() => {
+          this._downloadScene(this.snKey, arr, this.downloadOptions.isTiles)
+        })
+        .catch(() => {
+          this.$message({
+            type: 'info',
+            message: '已取消'
+          })
+          this.fullscreenLoading = false
+        })
     },
-    async _getSceneData() {
-      this.fullscreenLoading = true;
-      let status = this.tabIndex === 0 ? null : this.tabIndex;
 
-      let res = await this.$http.post("/manager/scene/list", {
+    async _getSceneData () {
+      this.fullscreenLoading = true
+      let status = this.tabIndex === 0 ? null : this.tabIndex
+
+      let res = await this.$http.post('/manager/scene/list', {
         type: status,
         searchKey: this.searchKey,
         pageNum: this.currentPage,
         pageSize: this.pageSize
-      });
+      })
 
-      this.fullscreenLoading = false;
+      this.fullscreenLoading = false
 
       if (res.code === 0) {
-        let temp = res.data.list;
+        let temp = res.data.list
         for (var i = 0; i < temp.length; i++) {
-          temp[i].sceneType = _sceneTypeName[temp[i].sceneType];
-          temp[i].scenekey = temp[i].scenekey ? "私密" : "公开";
-          temp[i]["userName"] = temp[i]["userName"]
-            ? temp[i]["userName"]
-            : "未绑定";
-          let gpsStr = temp[i].gps || "";
+          temp[i].sceneType = _sceneTypeName[temp[i].sceneType]
+          temp[i].scenekey = temp[i].scenekey ? '私密' : '公开'
+          temp[i]['userName'] = temp[i]['userName']
+            ? temp[i]['userName']
+            : '未绑定'
+          let gpsStr = temp[i].gps || ''
 
           if (gpsStr instanceof Object) {
             JSON.parse(gpsStr, (k, v) => {
-              if (k && k === "latitude") {
-                temp[i].latitude = this.formatDegree(v);
-              } else if (k && k === "longitude") {
-                temp[i].longitude = this.formatDegree(v);
+              if (k && k === 'latitude') {
+                temp[i].latitude = this.formatDegree(v)
+              } else if (k && k === 'longitude') {
+                temp[i].longitude = this.formatDegree(v)
               }
-            });
+            })
           }
 
           if (temp[i].status === 1) {
-            temp[i].statusBoo = true;
+            temp[i].statusBoo = true
           } else if (temp[i].status === -2) {
-            temp[i].statusBoo = false;
+            temp[i].statusBoo = false
           } else {
-            temp[i].statusBoo = "";
+            temp[i].statusBoo = ''
           }
         }
-        this.scenes = temp;
-        this.total = res.data.total ? res.data.total : this.total;
+        this.scenes = temp
+        this.total = res.data.total ? res.data.total : this.total
       }
     },
 
-    formatDegree(value) {
-      value = Math.abs(value);
-      var v1 = Math.floor(value); // 度
-      var v2 = Math.floor((value - v1) * 60); // 分
-      var v3 = Math.round(((value - v1) * 3600) % 60); // 秒
-      return v1 + "°" + v2 + "'" + v3 + '"';
+    formatDegree (value) {
+      value = Math.abs(value)
+      var v1 = Math.floor(value) // 度
+      var v2 = Math.floor((value - v1) * 60) // 分
+      var v3 = Math.round(((value - v1) * 3600) % 60) // 秒
+      return v1 + '°' + v2 + "'" + v3 + '"'
     },
-    async _updateSceneStatus(num, status) {
-      let tempStatus;
+    async _updateSceneStatus (num, status) {
+      let tempStatus
       if (status === 1) {
-        tempStatus = -2;
+        tempStatus = -2
       } else if (status === -2) {
-        tempStatus = 1;
+        tempStatus = 1
       } else {
         this.$notify.error({
-          title: "错误",
-          message: "切换失败"
-        });
-        return;
+          title: '错误',
+          message: '切换失败'
+        })
+        return
       }
-      this.fullscreenLoading = true;
-      await this.$http.post("/manager/scene/updateStatus", {
+      this.fullscreenLoading = true
+      await this.$http.post('/manager/scene/updateStatus', {
         type: tempStatus,
         sceneId: num
-      });
+      })
+
+      this._getSceneData()
+      this.fullscreenLoading = false
+    },
 
-      this._getSceneData();
-      this.fullscreenLoading = false;
+    _showDownloadSelect (num) {
+      this.downloadDialogVisible = true
+      this.downloadOptions = {
+        activeNum: num,
+        isTiles: false
+      }
+    },
+
+    _downloadSingleScene () {
+      let {activeNum: num, isTiles} = this.downloadOptions
+      this._downloadScene('', [num], isTiles)
     },
 
-    _downloadScene(sceneCode) {
-      this.fullscreenLoading = true;
-      let promise = this.$http.get(
-        `/scene/getInfo?num=${sceneCode}&t=${new Date().getTime()}`
-      ); // 请求sceneData.json数据
-      promise.then(resp => {
-        this.fullscreenLoading = false;
-        resp.data.sceneScheme = 1; // 禁止本地端放大缩小
+    _downloadScene (SNCode = '', sceneArr, isTiles = true) {
+      this.fullscreenLoading = true
+
+      // let {isTiles} = this.downloadOptions
+
+      // SNCode = '0223B29DFw'
+
+      console.log(this.downloadOptions)
+      let arr = sceneArr
+
+      // arr = ['1ggqA2lb1', 'RQpb8C4qz', '1IgPTqRUB', '3dFKNeveg']
+      let urlArr = arr.map(item => {
+        return this.$http.get(`/scene/getInfo?num=${item}&t=${new Date().getTime()}`)
+      })
+
+      let allPromise = this.$http.all(urlArr)
+      let errmsg = ''
+      allPromise.then(res => {
+        let temp = []
+        res.forEach(ele => {
+          if (ele && ele.code === 0) {
+            ele.data.sceneScheme = 1 // 禁止本地端放大缩小
+            temp.push({
+              sceneCode: ele.data.num,
+              sceneInfo: JSON.stringify(ele)
+            })
+          } else {
+            errmsg = ele.msg
+          }
+        })
+
+        console.log('------temp----', temp)
+        if (temp.length <= 0) {
+          this.downloadDialogVisible = false
+          this.fullscreenLoading = false
+          return this.$message({
+            type: 'info',
+            message: '下载失败,' + errmsg
+          })
+        }
+
+        let snCode = SNCode ? ('SN_' + SNCode) : temp[0].sceneCode
+
+        console.log('-snCode---', snCode)
+        this.downloadDialogVisible = false
+        this.fullscreenLoading = false
         this.$http
-          .post("https://test.4dkankan.com/downloadData/", {
-            sceneCode: sceneCode,
-            sceneInfo: JSON.stringify(resp)
+          .post(`${serverName}/downloadData/`, {
+            sceneCodeArr: temp,
+            isTiles: isTiles,
+            snCode
+            // sceneCode: sceneCode,
+            // isTiles,
+            // sceneInfo: JSON.stringify(resp)
           })
           .then(resp => {
             // 将请求发送至服务器后再轮询
-            if (resp["sta"] === 1003) {
+            if (resp['sta'] === 1003) {
               // 文件已存在
-              this._browserDownload(resp["data"]["url"]); // 调用浏览器下载文件
+              this._browserDownload(resp['data']['url']) // 调用浏览器下载文件
             } else {
-              this.download.showSta = true;
-              this.download.downloadDataName = `${sceneCode}.zip`;
-              if (resp["sta"] === 1000 || resp["sta"] === 1002) {
-                this._downloadHandler(resp);
+              this.download.showSta = true
+              this.download.downloadDataName = `${snCode}.zip`
+              if (resp['sta'] === 1000 || resp['sta'] === 1002) {
+                this._downloadHandler(resp)
               }
-              if (resp["sta"] === 1001) {
-                this._compressHandler(resp);
+              if (resp['sta'] === 1001) {
+                this._compressHandler(resp)
               }
               this.download.timer = setInterval(() => {
                 this.$http
                   .get(
-                    `https://test.4dkankan.com/downloadData/process?sceneCode=${sceneCode}`
+                    `${serverName}/downloadData/process?snCode=${snCode}`
                   )
                   .then(resp => {
-                    console.log(resp);
-                    if (resp["sta"] === 1000) {
-                      this._downloadHandler(resp);
+                    console.log(resp)
+                    if (resp['sta'] === 1000) {
+                      this._downloadHandler(resp)
                     }
-                    if (resp["sta"] === 1001) {
-                      this._compressHandler(resp);
+                    if (resp['sta'] === 1001) {
+                      this._compressHandler(resp)
                     }
-                  });
-              }, 1000);
+                  })
+              }, 1000)
             }
-          });
-      });
+          })
+      })
     },
 
-    _handleClose() {
-      this.$confirm("取消下载?", "提示")
+    _handleClose () {
+      this.$confirm('取消下载?', '提示')
         .then(() => {
-          this.download.showSta = false;
-          clearInterval(this.download.timer);
+          this.download.showSta = false
+          clearInterval(this.download.timer)
         })
-        .catch(_ => {});
+        .catch(_ => {})
     },
 
-    _downloadHandler(resp) {
-      this.download.downloadSta = "正在拉取数据";
-      this.download.percent = parseInt(resp["data"]["percent"]);
-      let percent = parseInt(resp["data"]["percent"]);
-      this.download.percent = percent;
+    _downloadHandler (resp) {
+      this.download.downloadSta = '正在拉取数据'
+      this.download.percent = parseInt(resp['data']['percent'])
+      let percent = parseInt(resp['data']['percent'])
+      this.download.percent = percent
     },
 
-    _compressHandler(resp) {
-      this.download.downloadSta = "正在压缩数据";
-      let percent = parseInt(resp["data"]["percent"]);
-      this.download.percent = percent;
+    _compressHandler (resp) {
+      this.download.downloadSta = '正在压缩数据'
+      let percent = parseInt(resp['data']['percent'])
+      this.download.percent = percent
       if (percent === 100) {
-        this.download.showSta = false;
-        clearInterval(this.download.timer);
-        this._browserDownload(resp["data"]["url"]);
+        this.download.showSta = false
+        clearInterval(this.download.timer)
+        this._browserDownload(resp['data']['url'])
       }
     },
 
-    _browserDownload(url) {
-      let a = document.createElement("a");
-      let urlArr = url.split("/");
-      let fileName = urlArr[urlArr.length - 1];
-      a.href = url;
-      a.download = fileName;
-      a.style.display = "none";
-      document.body.appendChild(a);
-      a.click();
-      document.body.removeChild(a);
+    _browserDownload (url) {
+      let a = document.createElement('a')
+      let urlArr = url.split('/')
+      let fileName = urlArr[urlArr.length - 1]
+      a.href = url
+      a.download = fileName
+      a.style.display = 'none'
+      document.body.appendChild(a)
+      a.click()
+      document.body.removeChild(a)
     },
 
-    _deleScene(num) {
-      this.$confirm("此操作将删除该场景, 是否继续?", "提示", {
-        confirmButtonText: "确定",
-        cancelButtonText: "取消",
-        type: "warning"
+    _deleScene (num) {
+      this.$confirm('此操作将删除该场景, 是否继续?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
       })
         .then(async () => {
-          this.fullscreenLoading = true;
-          let res = await this.$http.post("/manager/scene/delete", {
+          this.fullscreenLoading = true
+          let res = await this.$http.post('/manager/scene/delete', {
             sceneId: num
-          });
+          })
           if (res.code === 0) {
             this.$message({
-              type: "success",
-              message: "删除成功!"
-            });
-            this._getSceneData();
+              type: 'success',
+              message: '删除成功!'
+            })
+            this._getSceneData()
           } else {
             this.$message({
-              type: "error",
+              type: 'error',
               message: res.msg
-            });
-            this._getSceneData();
+            })
+            this._getSceneData()
           }
-          this.fullscreenLoading = false;
+          this.fullscreenLoading = false
         })
         .catch(() => {
           this.$message({
-            type: "info",
-            message: "已取消删除"
-          });
-          this.fullscreenLoading = false;
-        });
+            type: 'info',
+            message: '已取消删除'
+          })
+          this.fullscreenLoading = false
+        })
     }
   },
-  created() {
-    this._getSceneData();
+  created () {
+    this._getSceneData()
   }
-};
+}
 </script>
 <style lang="css" scoped>
 @import "./style.css";

+ 7 - 1
src/router/index.js

@@ -37,7 +37,13 @@ export default new Router({
           path: '/device',
           name: 'device',
           component: require('@/page/device').default,
-          meta: {text: '反馈消息'}
+          meta: {text: '设备管理'}
+        },
+        {
+          path: '/invoice',
+          name: 'invoice',
+          component: require('@/page/invoice').default,
+          meta: {text: '发票管理'}
         },
         {
           path: '/scene',

+ 1 - 1
src/util/http.js

@@ -1,7 +1,7 @@
 import axios from 'axios'
 
 // 配置请求域名
-// axios.defaults.baseURL = 'https://pro.4dkankan.com/api'
+// axios.defaults.baseURL = 'https://www.4dkankan.com/api'
 // axios.defaults.baseURL = 'https://test.4dkankan.com/api'
 // axios.defaults.baseURL = '/apid'
 axios.defaults.baseURL = '/api'

+ 80 - 0
src/util/index.js

@@ -0,0 +1,80 @@
+function randomWord (randomFlag, min, max) {
+  let str = ''
+  let range = min
+  let arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
+  // 随机产生
+  if (randomFlag) {
+    range = Math.round(Math.random() * (max - min)) + min
+  }
+  for (var i = 0; i < range; i++) {
+    let pos = Math.round(Math.random() * (arr.length - 1))
+    str += arr[pos]
+  }
+  return str
+}
+module.exports = {
+  getPosition: function (dom, parent) {
+    var ret = {
+      x: 0,
+      y: 0
+    }
+    while (dom && dom !== parent && dom !== document.documentElement) {
+      ret.x += dom.offsetLeft
+      ret.y += dom.offsetTop
+      dom = dom.offsetParent
+    }
+    return ret
+  },
+  reg: {
+    idCard: /^\d{6}(18|19|20)?\d{2}(0[1-9]|1[012])(0[1-9]|[12]\d|3[01])\d{3}(\d|[xX])$/,
+    phone: /^1(?:3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\d|9\d)\d{8}$/,
+    email: /^[A-Za-z\d]+([_.-][A-Za-z\d]+)*@([A-Za-z\d]+[-.])+[A-Za-z\d]{2,4}$/,
+    guhua: /^(\(\d{3,4}\)|\d{3,4}-|\s)?\d{7,14}$/
+  },
+  isEmptyObject: function (obj) {
+    for (var key in obj) {
+      return false
+    };
+    return true
+  },
+  isWide: function () {
+    return Math.round(devicePixelRatio * 100) === 100 ? window.innerWidth > 1600 : true
+  },
+  isWorkPhone: function (phone) {
+    let phones = [
+      /^1(?:3\d|4[4-9]|5[0-35-9]|6[67]|7[013-8]|8\d|9\d)\d{8}$/
+    ]
+    for (let i = 0; i < phones.length; i++) {
+      if (phones[i].test(phone)) {
+        return true
+      }
+    }
+    return false
+  },
+  formatDate: function (time) {
+    var date = new Date(time)
+    var year = date.getFullYear()
+    var month = date.getMonth() + 1
+    var day = date.getDate()
+    // var hour = date.getHours()
+    // var minute = date.getMinutes()
+    // var second = date.getSeconds()
+    //  return year+"-"+month+"-"+date+" "+hour+":"+minute+":"+second;
+    return year + '-' + (String(month).length > 1 ? month : '0' + month) + '-' +
+      (String(day).length > 1 ? day : '0' + day)
+      // + ' ' + (String(hour).length > 1 ? hour : '0' + hour) + ':' + (String(minute).length > 1 ? minute : '0' + minute) +
+      //  ':' + (String(second).length > 1 ? second : '0' + second)
+  },
+
+  encodeStr: function (str) {
+    const NUM = 2
+    const front = randomWord(false, 8)
+    const middle = randomWord(false, 8)
+    const end = randomWord(false, 8)
+
+    let str1 = str.substring(0, NUM)
+    let str2 = str.substring(NUM)
+
+    return front + str2 + middle + str1 + end
+  }
+}