Selaa lähdekoodia

设备二维码

zhengjinyi 11 kuukautta sitten
vanhempi
commit
3b3bcdcd2f

+ 224 - 188
src/api/product.js

@@ -1,188 +1,224 @@
-import request from '@/utils/request'
-
-// 获取商品列表
-export function getProdList(params) {
-  return request({
-    url: '/auth/product/list',
-    method: 'get',
-    params
-  })
-}
-
-// 添加设备
-export function saveProduct(data) {
-  return request({
-    url: '/auth/product/save',
-    method: 'post',
-    data
-  })
-}
-
-// 关联设备
-export function saveProductRelation(data) {
-  return request({
-    url: '/auth/product/save/relation',
-    method: 'post',
-    data
-  })
-}
-
-// 根据id获取商品信息
-export function getProductById(params) {
-  return request({
-    url: '/auth/product/form/data',
-    method: 'get',
-    params
-  })
-}
-
-// 修改商品状态
-export function setProductStatus(data) {
-  return request({
-    url: '/auth/product/update/status',
-    method: 'post',
-    data
-  })
-}
-
-// 删除商品
-export function removeProduct(data) {
-  return request({
-    url: '/auth/product/delete',
-    method: 'post',
-    data
-  })
-}
-
-// 审核商品 /auth/product/audit
-export function auditProduct(data) {
-  return request({
-    url: '/auth/product/audit',
-    method: 'post',
-    data
-  })
-}
-
-// 审核商品 /auth/product/audit
-export function auditProductCheck(id) {
-  return request({
-    url: `/auth/product/check/${id}`,
-    method: 'post'
-  })
-}
-
-// 添加设备分类
-export function createProductCate(data) {
-  return request({
-    url: '/auth/product/type/save',
-    method: 'post',
-    data
-  })
-}
-
-// 设备分类数据回显
-export function fetchAuthProductFormData(params) {
-  return request({
-    url: '/auth/product/type/form/data',
-    method: 'get',
-    params
-  })
-}
-
-// 获取设备分类列表
-export function fetchProductCateList(params) {
-  return request({
-    url: '/auth/product/type/list',
-    method: 'get',
-    params
-  })
-}
-
-// 删除设备分类
-export function removeProductCate(data) {
-  return request({
-    url: '/auth/product/type/delete',
-    method: 'post',
-    data
-  })
-}
-
-// 修改设备分类状态
-export function updateProductCateStatus(data) {
-  return request({
-    url: '/auth/product/type/update/status',
-    method: 'post',
-    data
-  })
-}
-
-// 设备选择列表
-export function fetchProductSelectList(params) {
-  return request({
-    url: '/auth/product/type/select',
-    method: 'get',
-    params
-  })
-}
-
-// 设备分类审核
-export function auditProductCate(data) {
-  return request({
-    url: '/auth/product/type/audit',
-    method: 'post',
-    data
-  })
-}
-
-// 设备分类审核
-export function auditProductCateCheck(id) {
-  return request({
-    url: `/auth/product/type/check/${id}`,
-    method: 'post'
-  })
-}
-
-// 设备sn码列表
-export function fetchProductSnList(params) {
-  return request({
-    url: '/auth/product/sn/list',
-    method: 'get',
-    params
-  })
-}
-
-// 通过sn码获取设备信息
-export function fetchDetialBySnCode(params) {
-  return request({
-    url: '/auth/product/info',
-    method: 'get',
-    params
-  })
-}
-
-// 根据类型获取设备sn码
-export function fetchProductSnListType(params) {
-  return request({
-    url: '/auth/product/sn/listSn',
-    method: 'get',
-    params
-  })
-}
-
-// 获取设备关联机构列表
-export function fetchDeviceAssClubList(data) {
-  return request({
-    url: '/auth/product/relation/list',
-    method: 'post',
-    data
-  })
-}
-
-// 获取机构设备sn列表
-export function fetchClubDeviceSnList(params) {
-  return request({
-    url: '/auth/product/sn/list',
-    method: 'get',
-    params
-  })
-}
+import request from '@/utils/request'
+
+// 获取商品列表
+export function getProdList(params) {
+  return request({
+    url: '/auth/product/list',
+    method: 'get',
+    params
+  })
+}
+
+// 添加设备
+export function saveProduct(data) {
+  return request({
+    url: '/auth/product/save',
+    method: 'post',
+    data
+  })
+}
+
+// 关联设备
+export function saveProductRelation(data) {
+  return request({
+    url: '/auth/product/save/relation',
+    method: 'post',
+    data
+  })
+}
+
+// 根据id获取商品信息
+export function getProductById(params) {
+  return request({
+    url: '/auth/product/form/data',
+    method: 'get',
+    params
+  })
+}
+
+// 修改商品状态
+export function setProductStatus(data) {
+  return request({
+    url: '/auth/product/update/status',
+    method: 'post',
+    data
+  })
+}
+
+// 删除商品
+export function removeProduct(data) {
+  return request({
+    url: '/auth/product/delete',
+    method: 'post',
+    data
+  })
+}
+
+// 审核商品 /auth/product/audit
+export function auditProduct(data) {
+  return request({
+    url: '/auth/product/audit',
+    method: 'post',
+    data
+  })
+}
+
+// 审核商品 /auth/product/audit
+export function auditProductCheck(id) {
+  return request({
+    url: `/auth/product/check/${id}`,
+    method: 'post'
+  })
+}
+
+// 添加设备分类
+export function createProductCate(data) {
+  return request({
+    url: '/auth/product/type/save',
+    method: 'post',
+    data
+  })
+}
+
+// 设备分类数据回显
+export function fetchAuthProductFormData(params) {
+  return request({
+    url: '/auth/product/type/form/data',
+    method: 'get',
+    params
+  })
+}
+
+// 获取设备分类列表
+export function fetchProductCateList(params) {
+  return request({
+    url: '/auth/product/type/list',
+    method: 'get',
+    params
+  })
+}
+
+// 删除设备分类
+export function removeProductCate(data) {
+  return request({
+    url: '/auth/product/type/delete',
+    method: 'post',
+    data
+  })
+}
+
+// 修改设备分类状态
+export function updateProductCateStatus(data) {
+  return request({
+    url: '/auth/product/type/update/status',
+    method: 'post',
+    data
+  })
+}
+
+// 设备选择列表
+export function fetchProductSelectList(params) {
+  return request({
+    url: '/auth/product/type/select',
+    method: 'get',
+    params
+  })
+}
+
+// 设备分类审核
+export function auditProductCate(data) {
+  return request({
+    url: '/auth/product/type/audit',
+    method: 'post',
+    data
+  })
+}
+
+// 设备分类审核
+export function auditProductCateCheck(id) {
+  return request({
+    url: `/auth/product/type/check/${id}`,
+    method: 'post'
+  })
+}
+
+// 设备sn码列表
+export function fetchProductSnList(params) {
+  return request({
+    url: '/auth/product/sn/list',
+    method: 'get',
+    params
+  })
+}
+
+// 通过sn码获取设备信息
+export function fetchDetialBySnCode(params) {
+  return request({
+    url: '/auth/product/info',
+    method: 'get',
+    params
+  })
+}
+
+// 根据类型获取设备sn码
+export function fetchProductSnListType(params) {
+  return request({
+    url: '/auth/product/sn/listSn',
+    method: 'get',
+    params
+  })
+}
+
+// 获取设备关联机构列表
+export function fetchDeviceAssClubList(data) {
+  return request({
+    url: '/auth/product/relation/list',
+    method: 'post',
+    data
+  })
+}
+
+// 获取机构设备sn列表
+export function fetchClubDeviceSnList(params) {
+  return request({
+    url: '/auth/product/sn/list',
+    method: 'get',
+    params
+  })
+}
+
+// 获取设备二维码列表
+export function fetchDeviceQrcodeList(params) {
+  return request({
+    url: '/auth/qrcode/list',
+    method: 'get',
+    params
+  })
+}
+
+// 获取二维码关联机构列表
+export function fetchQrcodeDeviceConcatList(params) {
+  return request({
+    url: '/auth/concat/list',
+    method: 'get',
+    params
+  })
+}
+
+// 批量生成设备二维码
+export function fetchDeviceQrcodeMade(data) {
+  return request({
+    url: '/auth/qrcode/made',
+    method: 'post',
+    data
+  })
+}
+
+// 设备二维码关联机构
+export function authQrcodeConcat(data) {
+  return request({
+    url: '/auth/qrcode/concat',
+    method: 'post',
+    data
+  })
+}

+ 239 - 0
src/components/QrcodeDeviceUnid/index.vue

@@ -0,0 +1,239 @@
+<template>
+  <div class="code-container">
+    <div class="qrcode">
+      <div class="title">{{ productInfo.productName }}</div>
+      <div class="content">
+        <img :src="imgUrl" alt="">
+      </div>
+      <div class="btn down-btn" @click="handleDown">下载二维码</div>
+      <div class="btn close-btn" @click="handleClose">关闭</div>
+    </div>
+    <canvas id="canvas" style="display: none" />
+    <div v-if="isVisible" class="mask" @click="handleClose" />
+    <a id="downloadLink" href="#" style="display: none" />
+  </div>
+</template>
+
+<script>
+import QRCode from 'qrcode'
+import downImage from '@/assets/img/qrcode-bg-club-down.png'
+import { mapGetters } from 'vuex'
+export default {
+  name: 'QrcodeDeviceUnid',
+  props: {
+    productInfo: {
+      type: Object,
+      default: () => {}
+    },
+    isVisible: {
+      type: Boolean,
+      default: true
+    }
+  },
+  data() {
+    return {
+      imgUrl: '',
+      wwwServer: process.env.VUE_APP_BASE_SERVER,
+      wwwHost: process.env.VUE_APP_WWW_HOST,
+      qrcodePath: ''
+    }
+  },
+  computed: {
+    ...mapGetters(['authUserId', 'prefix'])
+  },
+  created() {
+    this.initQrcode()
+  },
+  methods: {
+    // 关闭二维码
+    handleClose() {
+      this.$emit('close')
+    },
+    // 下载二维码
+    handleDown() {
+      this.createDownFile((downCanvas) => {
+        // 构造url
+        var url = downCanvas.toDataURL('image/jpg')
+        // 构造a标签并模拟点击
+        var downloadLink = document.getElementById('downloadLink')
+        downloadLink.setAttribute('href', url)
+        downloadLink.setAttribute('download', '二维码.jpg')
+        downloadLink.click()
+      })
+    },
+    // 初始化二维码
+    async initQrcode() {
+      // if (this.authUserId === 12) {
+      this.qrcodePath = `${this.wwwHost}/${this.productInfo.authUserId}/${this.prefix}/approve/device/detail?id=${this.productInfo?.qrCodeId}`
+      // } else {
+      // this.qrcodePath = `${this.wwwServer}/product/auth/product-${this.productInfo?.productId}.html`
+      // }
+      console.log(this.qrcodePath)
+      // 二维码配置
+      const options = {
+        width: 192,
+        height: 192,
+        margin: 1
+      }
+      try {
+        this.imgUrl = await QRCode.toDataURL(this.qrcodePath, options)
+      } catch (err) {
+        console.error(err)
+      }
+    },
+    // 生成下载的文件
+    async createDownFile(callback) {
+      // this.qrcodePath = `${this.wwwHost}/${this.productInfo.authUserId}/${this.prefix}/approve/device/detail?id=${this.productInfo?.productId}`
+      // if (this.authUserId === 12) {
+      this.qrcodePath = `${this.wwwHost}/${this.productInfo.authUserId}/${this.prefix}/approve/device/detail?id=${this.productInfo?.qrCodeId}`
+      // } else {
+      // this.qrcodePath = `${this.wwwServer}/product/auth/product-${this.productInfo?.productId}.html`
+      // }
+      const strHeader = this.productInfo.productName
+      const strFooter1 = ''
+      const strFooter2 = ''
+      // 生成二维码参数信息
+      const options = {
+        width: 720,
+        height: 720,
+        margin: 1
+      }
+      // 生成二维码dataURL
+      const downDataURL = await QRCode.toDataURL(this.qrcodePath, options)
+
+      // 设置下载二维码
+      const downCanvas = document.getElementById('canvas')
+      const downImg = new Image()
+      const downBgImg = new Image()
+      downImg.src = downDataURL
+      downBgImg.src = downImage
+      downBgImg.onload = function() {
+        // 重新绘制画布
+        const w = this.width
+        const h = this.height
+        downCanvas.width = w
+        downCanvas.height = h
+        const ctx = downCanvas.getContext('2d')
+        // 设置画布背景
+        ctx.fillStyle = '#ffffff'
+        ctx.fillRect(0, 0, downCanvas.width, downCanvas.height)
+        // 设置文字样式
+        ctx.fillStyle = '#ffffff'
+        ctx.font = 'bold 52px MicrosoftYaHei'
+        ctx.textAlign = 'center'
+        // 绘制背景
+        ctx.drawImage(this, 0, 0)
+        // 绘制二维码
+        ctx.drawImage(downImg, 185, 372)
+        // 绘制顶部文字描述
+        const chr = strHeader.split('')
+        let temp = ''
+        const row = []
+        for (let a = 0; a < chr.length; a++) {
+          if (ctx.measureText(temp).width >= w - 290) {
+            row.push(temp)
+            temp = ''
+          }
+          temp += chr[a]
+        }
+        row.push(temp)
+        if (row.length === 1) {
+          ctx.fillText(row[0], w / 2, 122)
+        } else {
+          for (var b = 0; b < row.length; b++) {
+            ctx.fillText(row[b], w / 2, 160 - (row.length - b - 1) * 65)
+          }
+        }
+        // 绘制底部文字
+        ctx.fillStyle = '#42aaff'
+        ctx.font = 'bold 33px MicrosoftYaHei'
+        ctx.textAlign = 'center'
+        ctx.fillText(strFooter1 + strFooter2, w / 2, 238)
+        // 绘制完成后的回调
+        callback(downCanvas)
+      }
+    },
+    hanldeSNcode(code) {
+      const start = code.slice(0, 2)
+      const end = code.slice(code.length - 5, code.length - 1)
+      return start + '*'.repeat(6) + end
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.code-container {
+  position: relative;
+  z-index: 999999;
+}
+.mask {
+  position: fixed;
+  top: 0;
+  left: 0;
+  z-index: 999998;
+  width: 100vw;
+  height: 100vh;
+  background: rgba(0, 0, 0, 0.5);
+}
+.qrcode {
+  z-index: 999999;
+  position: fixed;
+  left: 50%;
+  top: 20%;
+  transform: translateX(-50%);
+  width: 300px;
+  height: 400px;
+  background: url(../../assets/img/qrcode-bg-club-show.png) no-repeat center;
+  .content {
+    width: 192px;
+    height: 192px;
+    position: absolute;
+    left: 54px;
+    bottom: 85px;
+  }
+  .down-btn {
+    left: 0;
+  }
+  .close-btn {
+    right: 0;
+  }
+  .title {
+    position: absolute;
+    top: 32px;
+    left: 50%;
+    transform: translateX(-50%);
+    width: 256px;
+    text-align: center;
+    font-size: 16px;
+    color: #fff;
+    font-weight: bold;
+  }
+  .sncode {
+    position: absolute;
+    top: 78px;
+    left: 50%;
+    transform: translateX(-50%);
+    text-align: center;
+    width: 192px;
+    color: #0e9ef0;
+    font-size: 14px;
+    span {
+      font-weight: bold;
+    }
+  }
+}
+.btn {
+  position: absolute;
+  bottom: -60px;
+  width: 108px;
+  height: 32px;
+  background-image: linear-gradient(-90deg, #50c0ff 0%, #0e90dc 100%);
+  border-radius: 4px;
+  text-align: center;
+  line-height: 32px;
+  color: #fff;
+  font-size: 14px;
+  cursor: pointer;
+}
+</style>

+ 7 - 0
src/router/module/normal/club.js

@@ -71,6 +71,13 @@ const clubRoutes = [
         component: () => import('@/views/normal/club/cate/edit'),
         name: 'ClubDeviceCateEdit',
         meta: { title: '设备编辑', noCache: true, activeMenu: '/club/device-cate' }
+      },
+      // 设备二维码
+      {
+        path: 'device-qrcode',
+        component: () => import('@/views/normal/club/qrcode/index'),
+        name: 'ClubDeviceQrcode',
+        meta: { title: '设备二维码', noCache: true, activeMenu: '/club/device-qrcode' }
       }
     ]
   }

+ 145 - 0
src/views/components/ClubDeviceListSelector/index.vue

@@ -0,0 +1,145 @@
+<template>
+  <el-dialog
+    title="二维码关联"
+    :visible.sync="visible"
+    width="900px"
+    :close-on-click-modal="false"
+    :show-close="false"
+  >
+    <div class="filter-container">
+      <div class="filter-control">
+        <span>机构名称:</span>
+        <el-input
+          v-model="listQuery.authParty"
+          placeholder="机构名称"
+          clearable
+          style="width: 160px"
+          @keyup.enter.native="getList"
+          @clear="getList"
+        />
+      </div>
+      <div class="filter-control">
+        <el-button type="primary" @click="getList"> 查询 </el-button>
+      </div>
+    </div>
+    <el-table
+      ref="table"
+      v-loading="isLoading"
+      :data="list"
+      height="400px"
+      border
+      @select="handleSelect"
+    >
+      <el-table-column type="selection" width="55" />
+      <el-table-column label="机构名称" prop="authParty" align="center" />
+      <el-table-column label="机构状态" prop="auditStatus" align="center">
+        <template slot-scope="{ row }">
+          <el-tag v-if="row.auditStatus === 1" type="success" size="small">已上线</el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="创建时间" class-name="status-col" align="center">
+        <template slot-scope="{ row }">
+          <span>{{ row.createTime | formatTime }}</span>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 页码 -->
+    <pagination
+      :total="total"
+      :page-sizes="[10, 20]"
+      :page-size="100"
+      :page.sync="listQuery.pageNum"
+      :limit.sync="listQuery.pageSize"
+      @pagination="fetchQrcodeDeviceConcatList"
+    />
+    <div slot="footer">
+      <el-button @click="handleCanle"> 取 消 </el-button>
+      <el-button type="primary" :disabled="disabled" @click="handleConfirm"> 确 定 </el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import { fetchQrcodeDeviceConcatList } from '@/api/product'
+import { mapGetters } from 'vuex'
+export default {
+  name: 'ClubDeviceListSelector',
+  filters: {
+
+  },
+  props: {
+    productTypeId: {
+      type: Number,
+      default: 0
+    },
+    id: {
+      type: Number,
+      default: 0
+    }
+  },
+  data() {
+    return {
+      visible: true,
+      listQuery: {
+        id: 0,
+        authParty: '', // 设备类型Id
+        authUserId: 0,
+        pageNum: 1, // 页码
+        pageSize: 10, // 页面数据数
+        productTypeId: '' // 设备类型Id
+      },
+      list: [],
+      total: 0,
+      clubsRadio: null,
+      isLoading: true
+    }
+  },
+  computed: {
+    ...mapGetters(['authUserId']),
+    disabled() {
+      return this.clubsRadio === null
+    }
+  },
+  created() {
+    this.listQuery.id = this.id
+    this.listQuery.productTypeId = this.productTypeId
+    this.listQuery.authUserId = this.authUserId
+    this.getList()
+  },
+  methods: {
+    // 获取机构列表
+    async getList() {
+      this.list = []
+      this.listQuery.pageNum = 1
+      this.fetchQrcodeDeviceConcatList()
+    },
+    // 获取机构列表
+    async fetchQrcodeDeviceConcatList() {
+      try {
+        const res = await fetchQrcodeDeviceConcatList(this.listQuery)
+        this.list = res.data.list
+        this.total = res.data.total
+        this.isLoading = false
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 选择机构
+    handleSelect(selection, row) {
+      this.$refs.table.clearSelection()
+      this.$refs.table.toggleRowSelection(row)
+      this.clubsRadio = row.authId
+      console.log('clubsRadio', this.clubsRadio)
+    },
+    // 确认选择机构
+    handleConfirm() {
+      this.$emit('confirm', this.clubsRadio)
+    },
+    handleCanle() {
+      // 取消弹窗
+      this.$emit('cancel')
+    }
+  }
+}
+</script>
+<style lang="scss" scoped></style>

+ 247 - 242
src/views/normal/club/cate/index.vue

@@ -1,242 +1,247 @@
-<template>
-  <div class="app-container">
-    <div class="filter-container">
-      <div class="filter-control">
-        <span>设备名称:</span>
-        <el-input v-model="listQuery.name" placeholder="设备名称" @keyup.enter.native="handleFilter" />
-      </div>
-      <div class="filter-control">
-        <span>审核状态:</span>
-        <el-select v-model="listQuery.auditStatus" placeholder="审核状态" clearable @change="getList">
-          <el-option label="全部" value="" />
-          <el-option label="待审核" :value="2" />
-          <el-option label="审核通过" :value="1" />
-          <el-option label="审核未通过" :value="0" />
-        </el-select>
-      </div>
-      <div class="filter-control">
-        <span>上线状态:</span>
-        <el-select v-model="listQuery.status" placeholder="上线状态" clearable @change="getList">
-          <el-option label="全部" value="" />
-          <el-option label="已上线" :value="1" />
-          <el-option label="待上线" :value="2" />
-          <el-option label="未上线" :value="0" />
-        </el-select>
-      </div>
-      <div class="filter-control">
-        <permission-button type="primary" @click="handleFilter">查询</permission-button>
-        <permission-button type="primary" @click="handleAdd">添加</permission-button>
-      </div>
-    </div>
-    <!-- 表格区域 -->
-    <el-table
-      :key="tableKey"
-      :data="list"
-      border
-      fit
-      highlight-current-row
-      style="width: 100%"
-      header-row-class-name="tableHeader"
-    >
-      <el-table-column label="序号" :index="indexMethod" type="index" align="center" width="80" />
-      <el-table-column label="设备名称" align="center" prop="name" />
-
-      <el-table-column label="审核状态" width="220px" align="center">
-        <template slot-scope="{ row }">
-          <audit-status :status="row.auditStatus" :reason="row.invalidReason" />
-        </template>
-      </el-table-column>
-
-      <el-table-column label="上线状态" width="140px" align="center">
-        <template slot-scope="{ row }">
-          <!-- 只有审核通过了才能操作上下线 auditStatus :审核状态 -->
-          <template v-if="row.auditStatus === 1">
-            <template v-if="row.status === 0">
-              <span style="margin-right: 10px" class="status danger">已下线</span>
-              <permission-button type="primary" size="mini" @click="handleChangeStatus(row)">上线</permission-button>
-            </template>
-            <template v-else>
-              <span style="margin-right: 10px" class="status success">已上线</span>
-              <permission-button type="info" size="mini" @click="handleChangeStatus(row)">下线</permission-button>
-            </template>
-          </template>
-          <template v-else>
-            <!-- <el-tag type="warning">待上线</el-tag> -->
-            <span style="margin-right: 10px" class="status warning">待上线</span>
-          </template>
-        </template>
-      </el-table-column>
-      <el-table-column label="创建时间" class-name="status-col" width="160px" align="center">
-        <template slot-scope="{ row }">
-          <span>{{ row.createTime | formatTime }}</span>
-        </template>
-      </el-table-column>
-      <el-table-column label="创建人" align="center" prop="createBy" width="160" />
-      <!-- <el-table-column v-if="false" label="创建人" width="180px" align="center" prop="createBy" /> -->
-      <el-table-column label="操作" align="center" width="240px" class-name="small-padding fixed-width">
-        <template slot-scope="{ row }">
-          <permission-button type="primary" size="mini" @click="handleEdit(row)"> 编辑 </permission-button>
-          <permission-button type="danger" size="mini" @click="handleRemove(row)"> 删除 </permission-button>
-        </template>
-      </el-table-column>
-    </el-table>
-
-    <!-- 页码 -->
-    <pagination :total="total" :page.sync="listQuery.pageNum" :limit.sync="listQuery.pageSize" @pagination="getList" />
-  </div>
-</template>
-
-<script>
-import { mapGetters } from 'vuex'
-import { fetchProductCateList, removeProductCate, updateProductCateStatus } from '@/api/product'
-
-const resetFormData = () => ({
-  productTypeId: '',
-  authUserId: '',
-  name: '',
-  image: '',
-  createBy: ''
-})
-
-export default {
-  name: 'ComplexTable',
-  data() {
-    return {
-      tableKey: 0,
-      list: null,
-      total: 0,
-      listQuery: {
-        authUserId: '',
-        auditStatus: '',
-        listType: 1,
-        name: '',
-        pageNum: 1,
-        pageSize: 10,
-        status: ''
-      },
-      productInfo: {},
-      // 添加分类表单
-      formData: resetFormData(),
-      rules: {
-        name: [{ required: true, message: '请输入设备名称', trigger: ['blur'] }],
-        image: [{ required: true, message: '请上传设备图片', trigger: ['change'] }]
-      },
-      productImageList: []
-    }
-  },
-  computed: {
-    ...mapGetters(['authUserId'])
-  },
-  created() {
-    this.getList()
-  },
-  activated() {
-    this.getList()
-  },
-
-  methods: {
-    // 获取列表信息
-    getList() {
-      this.fetchProductList()
-    },
-    // 过滤列表
-    handleFilter() {
-      this.formData.pageNum = 1
-      this.list = []
-      this.fetchProductList()
-    },
-    // 改变启用状态
-    async handleChangeStatus(row) {
-      let status = Boolean(row.status)
-      const tip = status ? '下架' : '上架'
-      let confirmType = ''
-      try {
-        confirmType = await this.$confirm(`确定${tip}该设备管理吗?`, '提示', {
-          confirmButtonText: '确定',
-          cancelButtonText: '取消',
-          type: 'warning'
-        })
-        status = !status
-      } catch (error) {
-        console.log(error)
-      }
-
-      if (confirmType !== 'confirm') {
-        return
-      }
-      try {
-        await updateProductCateStatus({
-          productTypeId: row.productTypeId,
-          status: Number(status)
-        })
-        this.$message.success('修改设备状态成功')
-        this.fetchProductList()
-      } catch (error) {
-        console.log(error)
-      }
-    },
-    // 获取设备分类列表
-    async fetchProductList() {
-      this.listQuery.authUserId = this.authUserId
-      try {
-        const res = await fetchProductCateList(this.listQuery)
-        this.total = res.data.total
-        this.list = res.data.list
-      } catch (error) {
-        console.log(error)
-      }
-    },
-    // 添加分类
-    handleAdd() {
-      this.$router.push(`device-cate-edit?type=add`)
-    },
-    // 编辑分类
-    handleEdit(row) {
-      this.$router.push(`device-cate-edit?id=${row.productTypeId}&type=edit`)
-    },
-    // 删除
-    async handleRemove(row) {
-      let confirmType = ''
-      try {
-        confirmType = await this.$confirm('确认删除改商品分类吗?该操作不可逆!', '提示', {
-          confirmButtonText: '确定',
-          cancelButtonText: '取消',
-          type: 'warning'
-        })
-      } catch (error) {
-        console.log(error)
-      }
-
-      if (confirmType !== 'confirm') return
-
-      try {
-        await removeProductCate({
-          productTypeId: row.productTypeId
-        })
-        this.$message.success('删除设备分类成功')
-        this.fetchProductList()
-      } catch (error) {
-        console.log(error)
-      }
-    },
-
-    // 表格索引
-    indexMethod(index) {
-      return index + this.listQuery.pageSize * (this.listQuery.pageNum - 1) + 1
-    }
-  }
-}
-</script>
-
-<style lang="scss" scoped>
-.app-container {
-  ::v-deep {
-    .el-dialog__body {
-      padding-bottom: 0;
-    }
-  }
-  .pd-10 {
-    padding-top: 10px;
-  }
-}
-</style>
+<template>
+  <div class="app-container">
+    <div class="filter-container">
+      <div class="filter-control">
+        <span>设备名称:</span>
+        <el-input v-model="listQuery.name" placeholder="设备名称" @keyup.enter.native="handleFilter" />
+      </div>
+      <div class="filter-control">
+        <span>审核状态:</span>
+        <el-select v-model="listQuery.auditStatus" placeholder="审核状态" clearable @change="getList">
+          <el-option label="全部" value="" />
+          <el-option label="待审核" :value="2" />
+          <el-option label="审核通过" :value="1" />
+          <el-option label="审核未通过" :value="0" />
+        </el-select>
+      </div>
+      <div class="filter-control">
+        <span>上线状态:</span>
+        <el-select v-model="listQuery.status" placeholder="上线状态" clearable @change="getList">
+          <el-option label="全部" value="" />
+          <el-option label="已上线" :value="1" />
+          <el-option label="待上线" :value="2" />
+          <el-option label="未上线" :value="0" />
+        </el-select>
+      </div>
+      <div class="filter-control">
+        <permission-button type="primary" @click="handleFilter">查询</permission-button>
+        <permission-button type="primary" @click="handleAdd">添加</permission-button>
+      </div>
+    </div>
+    <!-- 表格区域 -->
+    <el-table
+      :key="tableKey"
+      :data="list"
+      border
+      fit
+      highlight-current-row
+      style="width: 100%"
+      header-row-class-name="tableHeader"
+    >
+      <el-table-column label="序号" :index="indexMethod" type="index" align="center" width="80" />
+      <el-table-column label="设备名称" align="center" prop="name" />
+
+      <el-table-column label="审核状态" width="220px" align="center">
+        <template slot-scope="{ row }">
+          <audit-status :status="row.auditStatus" :reason="row.invalidReason" />
+        </template>
+      </el-table-column>
+
+      <el-table-column label="上线状态" width="140px" align="center">
+        <template slot-scope="{ row }">
+          <!-- 只有审核通过了才能操作上下线 auditStatus :审核状态 -->
+          <template v-if="row.auditStatus === 1">
+            <template v-if="row.status === 0">
+              <span style="margin-right: 10px" class="status danger">已下线</span>
+              <permission-button type="primary" size="mini" @click="handleChangeStatus(row)">上线</permission-button>
+            </template>
+            <template v-else>
+              <span style="margin-right: 10px" class="status success">已上线</span>
+              <permission-button type="info" size="mini" @click="handleChangeStatus(row)">下线</permission-button>
+            </template>
+          </template>
+          <template v-else>
+            <!-- <el-tag type="warning">待上线</el-tag> -->
+            <span style="margin-right: 10px" class="status warning">待上线</span>
+          </template>
+        </template>
+      </el-table-column>
+      <el-table-column label="创建时间" class-name="status-col" width="160px" align="center">
+        <template slot-scope="{ row }">
+          <span>{{ row.createTime | formatTime }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="创建人" align="center" prop="createBy" width="160" />
+      <!-- <el-table-column v-if="false" label="创建人" width="180px" align="center" prop="createBy" /> -->
+      <el-table-column label="操作" align="center" width="240px" class-name="small-padding fixed-width">
+        <template slot-scope="{ row }">
+          <permission-button type="primary" size="mini" @click="handleQrcode(row)"> 设备二维码 </permission-button>
+          <permission-button type="primary" size="mini" @click="handleEdit(row)"> 编辑 </permission-button>
+          <permission-button type="danger" size="mini" @click="handleRemove(row)"> 删除 </permission-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 页码 -->
+    <pagination :total="total" :page.sync="listQuery.pageNum" :limit.sync="listQuery.pageSize" @pagination="getList" />
+  </div>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+import { fetchProductCateList, removeProductCate, updateProductCateStatus } from '@/api/product'
+
+const resetFormData = () => ({
+  productTypeId: '',
+  authUserId: '',
+  name: '',
+  image: '',
+  createBy: ''
+})
+
+export default {
+  name: 'ComplexTable',
+  data() {
+    return {
+      tableKey: 0,
+      list: null,
+      total: 0,
+      listQuery: {
+        authUserId: '',
+        auditStatus: '',
+        listType: 1,
+        name: '',
+        pageNum: 1,
+        pageSize: 10,
+        status: ''
+      },
+      productInfo: {},
+      // 添加分类表单
+      formData: resetFormData(),
+      rules: {
+        name: [{ required: true, message: '请输入设备名称', trigger: ['blur'] }],
+        image: [{ required: true, message: '请上传设备图片', trigger: ['change'] }]
+      },
+      productImageList: []
+    }
+  },
+  computed: {
+    ...mapGetters(['authUserId'])
+  },
+  created() {
+    this.getList()
+  },
+  activated() {
+    this.getList()
+  },
+
+  methods: {
+    // 获取列表信息
+    getList() {
+      this.fetchProductList()
+    },
+    // 过滤列表
+    handleFilter() {
+      this.formData.pageNum = 1
+      this.list = []
+      this.fetchProductList()
+    },
+    // 改变启用状态
+    async handleChangeStatus(row) {
+      let status = Boolean(row.status)
+      const tip = status ? '下架' : '上架'
+      let confirmType = ''
+      try {
+        confirmType = await this.$confirm(`确定${tip}该设备管理吗?`, '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        })
+        status = !status
+      } catch (error) {
+        console.log(error)
+      }
+
+      if (confirmType !== 'confirm') {
+        return
+      }
+      try {
+        await updateProductCateStatus({
+          productTypeId: row.productTypeId,
+          status: Number(status)
+        })
+        this.$message.success('修改设备状态成功')
+        this.fetchProductList()
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 获取设备分类列表
+    async fetchProductList() {
+      this.listQuery.authUserId = this.authUserId
+      try {
+        const res = await fetchProductCateList(this.listQuery)
+        this.total = res.data.total
+        this.list = res.data.list
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 添加分类
+    handleAdd() {
+      this.$router.push(`device-cate-edit?type=add`)
+    },
+    // 编辑分类
+    handleEdit(row) {
+      this.$router.push(`device-cate-edit?id=${row.productTypeId}&type=edit`)
+    },
+    // 设备二维码
+    handleQrcode(row) {
+      this.$router.push(`device-qrcode?id=${row.productTypeId}&name=${row.name}`)
+    },
+    // 删除
+    async handleRemove(row) {
+      let confirmType = ''
+      try {
+        confirmType = await this.$confirm('确认删除改商品分类吗?该操作不可逆!', '提示', {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        })
+      } catch (error) {
+        console.log(error)
+      }
+
+      if (confirmType !== 'confirm') return
+
+      try {
+        await removeProductCate({
+          productTypeId: row.productTypeId
+        })
+        this.$message.success('删除设备分类成功')
+        this.fetchProductList()
+      } catch (error) {
+        console.log(error)
+      }
+    },
+
+    // 表格索引
+    indexMethod(index) {
+      return index + this.listQuery.pageSize * (this.listQuery.pageNum - 1) + 1
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.app-container {
+  ::v-deep {
+    .el-dialog__body {
+      padding-bottom: 0;
+    }
+  }
+  .pd-10 {
+    padding-top: 10px;
+  }
+}
+</style>

+ 182 - 0
src/views/normal/club/qrcode/bind.vue

@@ -0,0 +1,182 @@
+<template>
+  <div class="app-container">
+    <el-form ref="form" label-width="110px" :model="formData" :rules="rules">
+      <el-form-item label="认证方式:">关联已认证设备</el-form-item>
+      <el-form-item label="SN码列表:" prop="snList">
+        <el-checkbox-group v-model="formData.snList" />
+        <el-button size="mini" type="primary" @click="onChooseCode">选择SN码</el-button>
+      </el-form-item>
+      <el-form-item>
+        <sncode-list class="snCode-list" :selection="false" :control="true" :list="selectedCodeList">
+          <template #control="{ row }">
+            <el-button size="mini" type="danger" @click="onSelectCodeRemove(row)">删除</el-button>
+          </template>
+        </sncode-list>
+      </el-form-item>
+    </el-form>
+
+    <!-- 表单提交 返回 -->
+    <div class="control-box">
+      <el-button type="primary" @click="submit">保存</el-button>
+      <el-button type="warning" @click="navigateBack">返回</el-button>
+    </div>
+
+    <!-- 选择sn码 -->
+    <el-dialog title="选择关联设备" :visible.sync="dialogCodeListVisible" width="70%" :show-close="false">
+      <sncode-list
+        :selection="true"
+        :control="false"
+        :list="unselectedCodeList"
+        height="380"
+        @selected="onCodeSelected"
+      />
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" size="mini" @click="onCodeCancel">取 消</el-button>
+        <el-button type="primary" size="mini" @click="onCodeConfirm">确 定</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { SncodeList } from '@/views/components'
+import { fetchClubDeviceSnList, saveProductRelation } from '@/api/product'
+import { getStorage } from '@/utils/storage'
+import { uniqueArr } from '@/utils'
+export default {
+  components: {
+    SncodeList
+  },
+  data() {
+    return {
+      dialogCodeListVisible: false,
+      formData: {
+        authType: 2,
+        authId: '',
+        snList: []
+      },
+      rules: {
+        snList: [{ type: 'array', required: true, message: 'SN码列表不能为空', trigger: ['change'] }]
+      },
+      listQuery: {
+        authId: '',
+        downStatus: 0,
+        productName: '',
+        snCode: '',
+        authParty: ''
+      },
+      codeList: [], // sn码列表
+      preSelectedCodeList: [] // table中已选中的sn码列表
+    }
+  },
+  computed: {
+    // 选中的sn码列表
+    selectedCodeList() {
+      return this.codeList.filter((item) => this.formData.snList.includes(item.snCode))
+    },
+    // 剩余未选中的sn码列表
+    unselectedCodeList() {
+      return this.codeList.filter((item) => !this.formData.snList.includes(item.snCode))
+    }
+  },
+  watch: {
+    selectedCodeListAll(nval) {
+      this.formData.snList = nval
+    }
+  },
+  created() {
+    // 获取当前设备所属机构的id
+    this.formData.authId = getStorage('device-setting-authId')
+    this.listQuery.authId = this.formData.authId
+    this.fetchSnCodeList()
+  },
+  methods: {
+    // 选择sn码
+    onChooseCode() {
+      this.dialogCodeListVisible = true
+    },
+    // 获取全部sn码列表
+    async fetchSnCodeList() {
+      try {
+        // 获取全部sn码列表
+        this.listQuery.downStatus = 0
+        const res1 = await fetchClubDeviceSnList(this.listQuery)
+        res1.data = res1.data.map((item) => {
+          item.downStatus = 0
+          return item
+        })
+        // 获取与该机构有关联sn码列表
+        this.listQuery.downStatus = 1
+        const res2 = await fetchClubDeviceSnList(this.listQuery)
+        res2.data = res2.data.map((item) => {
+          item.downStatus = 0
+          return item
+        })
+        this.codeList = [...res1.data, ...res2.data]
+        console.log('🚀 ~ file: bind.vue:108 ~ fetchSnCodeList ~ this.codeList:', this.codeList)
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 保存
+    async onSave() {
+      try {
+        await saveProductRelation(this.formData)
+        this.$message.success('关联已认证设备成功')
+        this.$store.dispatch('tagsView/delView', this.$route)
+        this.$router.push(`/club/device-list?id=${this.formData.authId}`)
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 提交表单
+    async submit() {
+      try {
+        await this.$refs.form.validate()
+        this.onSave()
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 选中sn码
+    onCodeSelected(list) {
+      this.preSelectedCodeList = list
+    },
+    // 取消选择sn码
+    onCodeCancel() {
+      this.preSelectedCodeList = []
+      this.dialogCodeListVisible = false
+    },
+    // 确认选择sn码
+    onCodeConfirm() {
+      const sncodeList = this.preSelectedCodeList.map((item) => item.snCode)
+      const snList = [...this.formData.snList, ...sncodeList]
+      this.formData.snList = uniqueArr(snList)
+      this.dialogCodeListVisible = false
+    },
+    // 已选中sn码删除
+    onSelectCodeRemove(row) {
+      this.formData.snList = this.formData.snList.filter((code) => code !== row.snCode)
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.app-container {
+  width: 80%;
+}
+.filter-container {
+  background: #eee;
+  padding: 6px 10px;
+
+  .filter-control {
+    margin-bottom: 0;
+  }
+}
+.snCode-cate {
+  display: flex;
+  align-items: center;
+  margin-bottom: 16px;
+}
+</style>

+ 435 - 0
src/views/normal/club/qrcode/edit.vue

@@ -0,0 +1,435 @@
+<template>
+  <div class="device-edit page-form-container">
+    <el-form ref="form" label-width="100px" :model="formData" :rules="rules">
+      <el-tabs v-model="activeName">
+        <!-- 基础资料 -->
+        <el-tab-pane label="基础资料" name="first">
+          <el-form-item label="设备名称:" prop="productTypeId">
+            <el-select v-model="formData.productTypeId" style="width: 100%" clearable>
+              <el-option
+                v-for="item in productCateList"
+                :key="item.productTypeId"
+                :label="item.name"
+                :value="item.productTypeId"
+              />
+            </el-select>
+          </el-form-item>
+          <el-form-item label="设备SN码:" prop="snCode">
+            <el-input v-model="formData.snCode" placeholder="设备SN码" />
+          </el-form-item>
+          <el-form-item label="购买渠道:" prop="purchaseWay">
+            <el-input v-model="formData.purchaseWay" placeholder="购买渠道" />
+          </el-form-item>
+          <el-form-item label="发票:">
+            <upload-image
+              tip="建议图片分辨率:242px*242px"
+              :limit="1"
+              :image-list="invoiceImageList"
+              :before-upload="beforeInvoiceImageUpload"
+              @success="uploadInvoiceImageSuccess"
+              @remove="handleInvoiceImageRemove"
+            />
+            <el-input v-show="false" v-model="formData.invoiceImage" />
+          </el-form-item>
+          <el-form-item label="认证方式:">
+            <span>{{ authTypeName }}</span>
+          </el-form-item>
+        </el-tab-pane>
+        <!-- 授权牌资料 -->
+        <el-tab-pane label="授权牌资料" name="second">
+          <el-form-item label="授权牌:" prop="certificateImageType">
+            <el-radio-group v-model="formData.certificateImageType" size="mini">
+              <el-radio :label="0">暂不需要</el-radio>
+              <el-radio :label="1">模板库生成</el-radio>
+              <el-radio :label="2">自定义上传</el-radio>
+            </el-radio-group>
+            <template v-if="formData.certificateImageType === 1">
+              <div v-if="authTempFlag">
+                <preview-image v-if="formData.certificateImage" :src="formData.certificateImage" />
+              </div>
+              <div v-else>无</div>
+            </template>
+            <template v-if="formData.certificateImageType === 2">
+              <upload-image
+                tip="建议尺寸:150px * 112px"
+                :image-list="certificateImageList"
+                :before-upload="beforeCertificateImageUpload"
+                @success="uploadCertificateImageSuccess"
+                @remove="handleCertificateImageRemove"
+              />
+            </template>
+          </el-form-item>
+          <el-form-item v-if="authTempFlag && formData.certificateImageType === 1" label="授权牌logo:" prop="authImageLogo">
+            <div class="form-label-tip">授权牌logo(提示:授权牌logo与机构名称组合)</div>
+            <div style="margin-top: 8px">
+              <upload-image
+                :tip="authImageLogoUploadTip"
+                :image-list="authImageLogoList"
+                :before-upload="beforeAuthImageLogoUpload"
+                @success="uploadAuthImageLogoSuccess"
+                @remove="handleAuthImageLogoRemove"
+              />
+              <el-input v-show="false" v-model="formData.authImageLogo" />
+            </div>
+          </el-form-item>
+          <el-form-item v-if="formData.certificateImageType === 1" label="认证日期:" prop="authDate">
+            <el-date-picker v-model="formData.authDate" type="date" placeholder="选择日期" style="width: 100%" />
+          </el-form-item>
+
+        </el-tab-pane>
+      </el-tabs>
+
+      <!-- 表单提交 返回 -->
+      <div class="control-box">
+        <el-button type="primary" @click="submit">保存</el-button>
+        <el-button type="warning" @click="navigateBack">返回</el-button>
+      </div>
+    </el-form>
+  </div>
+</template>
+
+<script>
+import UploadImage from '@/components/UploadImage'
+import { fetchProductSelectList, getProductById, saveProduct } from '@/api/product'
+import { isSnCode } from '@/utils/validate'
+import { getStorage } from '@/utils/storage'
+import { authTempUsed } from '@/api/system'
+import { formatDate } from '@/utils'
+export default {
+  components: { UploadImage },
+  data() {
+    const valideSNcode = (rules, value, callback) => {
+      if (!isSnCode(value)) {
+        return callback(new Error('只能是字母+数字组合'))
+      }
+      callback()
+    }
+    return {
+      activeName: 'first',
+      editType: 'add',
+      formData: {
+        authId: '', //	授权id
+        relationId: '',
+        authType: 1, // 认证类型
+        authUserId: '',
+        certificateImage: '', //	授权牌照
+        // 	设备参数
+        productId: '', //	授权设备id
+        snCode: '', //	设备SN码
+        certificateImageType: 0,
+        productTypeId: '',
+        purchaseWay: '', // 购买渠道
+        invoiceImage: '', // 发票
+        authDate: '',
+        authImageLogo: ''
+      },
+      productCateList: [],
+      certificateImageList: [],
+      invoiceImageList: [],
+      rules: {
+        certificateImage: [{ required: true, message: '授权牌照不能为空', trigger: 'change' }],
+        certificateImageType: [{ required: true, message: '授权牌照不能为空', trigger: 'change' }],
+        snCode: [{ required: true, message: 'SN码不能为空' }, { validator: valideSNcode }],
+        productTypeId: [{ required: true, message: '设备名称不能为空', trigger: 'change' }],
+        // purchaseWay: [{ required: true, message: '购买渠道不能为空', trigger: 'change' }],
+        invoiceImage: [{ required: true, message: '发票不能为空', trigger: 'change' }],
+        authDate: [{ required: true, message: '认证日期不能为空', trigger: 'change' }],
+        authImageLogo: [{ required: true, message: '授权牌logo不能为空', trigger: 'change' }]
+      },
+      authTempFlag: true,
+      // 验证
+      validatorFields: {
+        authImageLogoWidth: 100,
+        authImageLogoHeight: 100
+      },
+      authImageLogoList: [],
+      initProductData: null
+    }
+  },
+  computed: {
+    authTypeName() {
+      return this.formData.authType === 1 ? '新设备认证' : '关联已认证设备'
+    },
+    // 授权牌logo上传提示
+    authImageLogoUploadTip() {
+      return `限制尺寸:${this.validatorFields.authImageLogoWidth}px *${this.validatorFields.authImageLogoHeight}px`
+    }
+  },
+  watch: {
+    'formData.certificateImageType': {
+      handler(val) {
+        if (val === 1) {
+          this.fetchAuthTempUsed()
+        } else if (val === 0) {
+          this.formData.certificateImage = ''
+          this.formData.authDate = ''
+          this.formData.authImageLogo = ''
+        } else if (val === 2) {
+          this.formData.authDate = ''
+          this.formData.authImageLogo = ''
+          this.fetchAuthTempUsed()
+        }
+        if (this.$route.query.isAdd === 'true') {
+          this.formData.authDate = ''
+          this.formData.authImageLogo = ''
+          this.formData.certificateImage = ''
+          this.certificateImageList = []
+          this.fetchAuthTempUsed()
+        }
+      },
+      deep: true
+    }
+  },
+  created() {
+    this.editType = this.$route.query.type || 'add'
+    this.formData.relationId = this.$route.query.relationId
+    this.formData.productId = this.$route.query.id
+    // 获取当前设备所属机构的id
+    this.formData.authId = getStorage('device-setting-authId')
+    this.fetchProductCateList()
+    if (this.editType === 'edit') {
+      this.fetchProductDetail()
+    } else {
+      this.fetchAuthTempUsed()
+    }
+  },
+  methods: {
+    // 提交
+    async submit() {
+      try {
+        await this.$refs.form.validate()
+        this.onSave()
+      } catch (error) {
+        console.log(error)
+      }
+    },
+
+    // 保存
+    async onSave() {
+      try {
+        this.formData.certificateImage = this.formData.certificateImageType === 0 ? '' : this.formData.certificateImage
+        this.formData.authDate = this.formData.authDate ? formatDate(this.formData.authDate, 'yyyy.MM.DD') : ''
+        console.log('授权牌数据', this.formData)
+        await saveProduct(this.formData)
+        const h = this.$createElement
+        const tip = this.editType === 'add' ? '添加' : '修改'
+        this.$notify.success({
+          title: tip + '设备',
+          message: h('i', { style: 'color: #333' }, `已${tip}设备`),
+          duration: 2000
+        })
+        this.$store.dispatch('tagsView/delView', this.$route)
+        this.$router.push(`/club/device-list?id=${this.formData.authId}`)
+      } catch (error) {
+        console.log(error)
+      }
+    },
+
+    // 获取设备信息
+    async fetchProductDetail() {
+      try {
+        const { productId, relationId } = this.formData
+        const res = await getProductById({ productId, relationId })
+        this.initFormData(res.data)
+      } catch (error) {
+        console.log(error)
+      }
+    },
+
+    // 初始化表单数据
+    initFormData(data) {
+      this.initProductData = data
+      for (const key in this.formData) {
+        if (Object.hasOwnProperty.call(data, key)) {
+          if (key === 'productTypeId') {
+            this.formData[key] = parseInt(data[key])
+          } else {
+            this.formData[key] = data[key]
+          }
+        }
+        this.formData.authImageLogo = data.authImageLogo
+        if (data.authImageLogo) {
+          this.authImageLogoList = [{ name: '授权牌logo', url: data.authImageLogo }]
+        }
+      }
+      if (data.invoiceImage) {
+        this.invoiceImageList = [{ name: '发票', url: data.invoiceImage }]
+      }
+      if (data.certificateImage) {
+        this.certificateImageList = [{ name: '授权牌图片', url: data.certificateImage }]
+      }
+    },
+
+    // 获取设备分类列表
+    async fetchProductCateList() {
+      try {
+        const res = await fetchProductSelectList({ authUserId: this.authUserId })
+        this.productCateList = res.data
+      } catch (error) {
+        console.log(error)
+      }
+    },
+
+    // 获取当前机构可用授权牌模板
+    async fetchAuthTempUsed() {
+      if (this.initProductData && this.editType === 'edit' && this.initProductData.certificateImageType === 0) {
+        try {
+          const res = await authTempUsed({
+            authUserId: this.authUserId,
+            authFlag: 1,
+            status: 1
+          })
+          this.authTempFlag = !!res.data.templateImage
+          this.formData.certificateImage = res.data.templateImage
+          const [width, height] = res.data.logoSize.split(',')
+          this.validatorFields.authImageLogoWidth = width
+          this.validatorFields.authImageLogoHeight = height
+        } catch (error) {
+          console.log(error)
+        }
+      } else if (this.initProductData && this.editType === 'edit' && this.initProductData.certificateImageType === 1) {
+        this.certificateImageList = []
+        this.formData.authCode = this.initProductData.authCode
+        this.formData.authDate = this.initProductData.authDate
+        this.formData.authImageLogo = this.initProductData.authImageLogo
+        this.formData.certificateImage = this.initProductData.certificateImage
+        if (this.initProductData.authImageLogo) {
+          this.authImageLogoList = [{ name: '授权牌logo', url: this.initProductData.authImageLogo }]
+        }
+        try {
+          const res = await authTempUsed({
+            authUserId: this.authUserId,
+            authFlag: 1,
+            status: 1
+          })
+          this.authTempFlag = !!res.data.templateImage
+          console.log(this.authTempFlag)
+          if (this.$route.query.isAdd === 'true') {
+            this.formData.certificateImage = res.data.templateImage
+          }
+          const [width, height] = res.data.logoSize.split(',')
+          this.validatorFields.authImageLogoWidth = width
+          this.validatorFields.authImageLogoHeight = height
+        } catch (error) {
+          console.log(error)
+        }
+      } else if (this.initProductData && this.editType === 'edit' && this.initProductData.certificateImageType === 2) {
+        this.formData.certificateImage = this.initProductData.certificateImage
+        if (this.initProductData.authImage) {
+          this.certificateImageList = [{ name: '自定义授权牌', url: this.initProductData.authImage }]
+        }
+        try {
+          const res = await authTempUsed({
+            authUserId: this.authUserId,
+            authFlag: 1,
+            status: 1
+          })
+          this.authTempFlag = !!res.data.templateImage
+          console.log(this.authTempFlag)
+          this.formData.certificateImage = res.data.templateImage
+          const [width, height] = res.data.logoSize.split(',')
+          this.validatorFields.authImageLogoWidth = width
+          this.validatorFields.authImageLogoHeight = height
+        } catch (error) {
+          console.log(error)
+        }
+      } else {
+        try {
+          const res = await authTempUsed({
+            authUserId: this.authUserId,
+            authFlag: 1,
+            status: 1
+          })
+          this.authTempFlag = !!res.data.templateImage
+          console.log(this.authTempFlag)
+          if (this.$route.query.isAdd === 'true') {
+            this.formData.certificateImage = res.data.templateImage
+          }
+          const [width, height] = res.data.logoSize.split(',')
+          this.validatorFields.authImageLogoWidth = width
+          this.validatorFields.authImageLogoHeight = height
+        } catch (error) {
+          console.log(error)
+        }
+      }
+    },
+
+    // 授权牌照上传
+    beforeCertificateImageUpload(file) {
+      const flag = file.size / 1024 / 1024 < 1
+      if (!flag) {
+        this.$message.error('上传授权牌图片大小不能超过 1MB!')
+      }
+      return flag
+    },
+    uploadCertificateImageSuccess({ response, file, fileList }) {
+      this.certificateImageList = fileList
+      this.formData.certificateImage = response.data
+    },
+    handleCertificateImageRemove({ file, fileList }) {
+      this.certificateImageList = fileList
+      this.formData.certificateImage = ''
+    },
+
+    // 发票上传
+    beforeInvoiceImageUpload(file) {
+      const flag = file.size / 1024 / 1024 < 1
+      if (!flag) {
+        this.$message.error('上传授权牌图片大小不能超过 1MB!')
+      }
+      return flag
+    },
+    uploadInvoiceImageSuccess({ response, file, fileList }) {
+      this.invoiceImageList = fileList
+      this.formData.invoiceImage = response.data
+    },
+    handleInvoiceImageRemove({ file, fileList }) {
+      this.invoiceImageList = fileList
+      this.formData.invoiceImage = ''
+    },
+    // 授权牌logo上传
+    beforeAuthImageLogoUpload(file) {
+      return new Promise((resolve, reject) => {
+        if (file.size > 5 * 1024 * 1024) {
+          this.$message.error('授权牌logo图片大小不能超过 5MB!')
+          reject('图片大小超出最大限制')
+        }
+        const image = new Image()
+        image.src = URL.createObjectURL(file)
+        image.onload = (e) => {
+          const { naturalWidth, naturalHeight } = e.path ? e.path[0] : e.target
+          const { authImageLogoWidth: width, authImageLogoHeight: height } = this.validatorFields
+          if (naturalWidth > width || naturalHeight > height) {
+            this.$message.error('图片尺寸校验未通过')
+            reject('图片尺寸校验未通过')
+          } else {
+            resolve('图片尺寸校验通过')
+          }
+        }
+      })
+    },
+    uploadAuthImageLogoSuccess({ response, file, fileList }) {
+      this.authImageLogoList = fileList
+      this.formData.authImageLogo = response.data
+    },
+    handleAuthImageLogoRemove({ file, fileList }) {
+      this.authImageLogoList = fileList
+      this.formData.authImageLogo = ''
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped>
+.el-tab-pane {
+  margin-top: 24px;
+}
+::v-deep .el-tabs__item {
+  font-size: 20px;
+  font-weight: bold;
+  color: #82848a;
+}
+::v-deep .el-tabs__item.is-active {
+  color: #1890ff;
+}
+</style>

+ 331 - 0
src/views/normal/club/qrcode/index.vue

@@ -0,0 +1,331 @@
+<template>
+  <div class="app-container">
+    <div class="filter-container">
+      <div class="filter-control">
+        <span>设备名称:{{ deviceName }}</span>
+      </div>
+      <div class="filter-control">
+        <span>激活状态:</span>
+        <el-select v-model="listQuery.unionStatus" placeholder="激活状态" clearable @change="handleFilter">
+          <el-option label="全部" value="" />
+          <el-option label="已激活" :value="1" />
+          <el-option label="未激活" :value="0" />
+        </el-select>
+      </div>
+      <div class="filter-control">
+        <permission-button type="primary" @click="handleFilter">查询</permission-button>
+        <permission-button type="primary" @click="dialogFormVisible = true">批量生成设备二维码</permission-button>
+      </div>
+    </div>
+    <!-- 表格区域 -->
+    <el-table
+      :key="tableKey"
+      :data="list"
+      border
+      fit
+      highlight-current-row
+      style="width: 100%"
+      header-row-class-name="tableHeader"
+    >
+      <el-table-column label="序号" :index="indexMethod" type="index" align="center" width="80" />
+      <el-table-column label="激活状态" align="center">
+        <template slot-scope="{ row }">
+          <!--  关联机构后激活 unionStatus :激活状态 -->
+          <template v-if="row.unionStatus === 1">
+            <el-tag type="success">已激活</el-tag>
+          </template>
+          <template v-else>
+            <el-tag type="info">未激活</el-tag>
+          </template>
+        </template>
+      </el-table-column>
+      <el-table-column label="创建时间" class-name="status-col" align="center">
+        <template slot-scope="{ row }">
+          <span>{{ row.addtime | formatTime }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="关联机构名称" class-name="status-col" align="center">
+        <template slot-scope="{ row }">
+          <span v-if="row.authParty">{{ row.authParty }}</span>
+          <span v-else>- -</span>
+        </template>
+      </el-table-column>
+      <!-- <el-table-column v-if="false" label="创建人" width="180px" align="center" prop="createBy" /> -->
+      <el-table-column label="操作" align="center" width="340px" class-name="small-padding fixed-width">
+        <template slot-scope="{ row }">
+          <permission-button type="primary" size="mini" @click="handleShowQRcode(row)">
+            二维码
+          </permission-button>
+          <permission-button v-if="row.unionStatus === 0" type="success" size="mini" @click="handleConcatClub(row)">
+            关联机构
+          </permission-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 页码 -->
+    <pagination :total="total" :page.sync="listQuery.pageNum" :limit.sync="listQuery.pageSize" @pagination="fetchDeviceQrcodeList" />
+
+    <!-- 二维码 -->
+    <transition name="fade">
+      <qrcode-device-unid v-if="showQRcode" :product-info="productInfo" @close="showQRcode = false" />
+    </transition>
+    <!-- 关联机构 -->
+    <club-list-selector
+      v-if="dialogClubVisible"
+      :id="qrCodepConcatParams.id"
+      ref="clubListSelector"
+      :product-type-id="listQuery.productTypeId"
+      @confirm="handleClubConfirm"
+      @cancel="handleCancel"
+    />
+    <!--<el-dialog title="其他关联机构" :visible.sync="dialogClubVisible" width="30%">
+      <associated-club-list
+        class="associated-club-list"
+        :selection="false"
+        :control="true"
+        :show-filter="false"
+        height="300"
+        :list="assClubList"
+      >
+        <template #control="{ row }">
+          <el-button size="mini" type="primary" @click="onToClubDetail(row)">查看</el-button>
+        </template>
+      </associated-club-list>
+    </el-dialog>-->
+    <!-- 生成二维码弹窗 -->
+    <el-dialog title="批量生成二维码" :visible.sync="dialogFormVisible" width="350px">
+      <el-form
+        ref="dataForm"
+        :model="qrCodepParams"
+        label-position="right"
+      >
+        <el-form-item label="二维码数量:" prop="num">
+          <el-input v-model="qrCodepParams.num" style="width: 80px" />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="dialogFormVisible = false">取消</el-button>
+        <el-button
+          type="primary"
+          @click="handleMadeQrcode"
+        >确定</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { fetchDeviceQrcodeMade, fetchDeviceQrcodeList, fetchDeviceAssClubList, authQrcodeConcat, removeProduct } from '@/api/product'
+import ClubListSelector from '@/views/components/ClubDeviceListSelector'
+import QrcodeDeviceUnid from '@/components/QrcodeDeviceUnid'
+import { mapGetters } from 'vuex'
+export default {
+  name: 'ComplexTable',
+  components: { QrcodeDeviceUnid, ClubListSelector },
+  data() {
+    return {
+      deviceName: this.$route.query.name,
+      authParty: '',
+      tableKey: 0,
+      list: null,
+      total: 0,
+      listQuery: {
+        productTypeId: '',
+        authUserId: '',
+        unionStatus: '',
+        pageNum: 1,
+        pageSize: 10
+      },
+      showQRcode: false,
+      productInfo: {},
+      // 审核未通过
+      auditFailedList: [],
+      auditNoticeFlag: true,
+      dialogClubVisible: false,
+      assClubList: [],
+      dialogFormVisible: false,
+      qrCodepParams: {
+        productTypeId: '',
+        authUserId: '',
+        num: 10
+      },
+      qrCodepConcatParams: {
+        id: '',
+        authId: ''
+      }
+    }
+  },
+  computed: {
+    ...mapGetters(['authUserId'])
+  },
+  created() {
+    this.initPage()
+  },
+
+  activated() {
+    this.initPage()
+  },
+
+  methods: {
+    initPage() {
+      this.listQuery.productTypeId = this.qrCodepParams.productTypeId = this.$route.query.id * 1
+      this.listQuery.authUserId = this.qrCodepParams.authUserId = this.authUserId
+      this.getList()
+    },
+    // 批量生成设备二维码
+    handleMadeQrcode() {
+      this.fetchDeviceQrcodeMade()
+    },
+    // 调用生成二维码
+    async fetchDeviceQrcodeMade() {
+      try {
+        await fetchDeviceQrcodeMade(this.qrCodepParams)
+        this.$message({
+          message: '操作成功',
+          duration: 500,
+          type: 'success'
+        })
+        this.dialogFormVisible = false
+        this.getList()
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 确认选择机构
+    handleClubConfirm(data) {
+      console.log('data', data)
+      this.qrCodepConcatParams.authId = data
+      this.authQrcodeConcat()
+    },
+    // 关联
+    async authQrcodeConcat() {
+      try {
+        await authQrcodeConcat(this.qrCodepConcatParams)
+        this.$message.success('关联成功,二维码已激活~')
+        this.dialogClubVisible = false
+        this.getList()
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    handleConcatClub(row) {
+      this.qrCodepConcatParams.id = row.id
+      this.dialogClubVisible = true
+    },
+    //  取消关联
+    handleCancel() {
+      this.dialogClubVisible = false
+    },
+    // 获取设备关联机构列表
+    async fetchDeviceAssClubList(row) {
+      try {
+        const res = await fetchDeviceAssClubList({ snCode: row.snCode })
+        this.assClubList = res.data
+        this.dialogClubVisible = true
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 获取列表信息
+    getList() {
+      this.listQuery.pageNum = 1
+      this.list = []
+      this.fetchDeviceQrcodeList()
+    },
+    async fetchDeviceQrcodeList() {
+      try {
+        this.isLoading = true
+        const res = await fetchDeviceQrcodeList(this.listQuery)
+        this.list = res.data.list
+        this.total = res.data.total
+        this.isLoading = false
+      } catch (error) {
+        console.log(error)
+      }
+    },
+    // 过滤列表
+    handleFilter() {
+      this.listQuery.pageNum = 1
+      this.getList()
+    },
+    // 改变启用状态
+    handleChangeStatus(item) {
+      if (this.userIdentity === 2 || this.proxyInfo !== null) {
+        const { status, productId } = item
+        const newStatus = status === 0 ? 1 : 0
+        authQrcodeConcat({ status: newStatus, productId })
+          .then((res) => {
+            // this.$message.success(res.data)
+            this.$message({
+              message: '操作成功',
+              duration: 500,
+              type: 'success'
+            })
+          })
+          .catch((error) => {
+            console.log(error)
+          })
+          .finally(() => {
+            this.getList()
+          })
+      }
+    },
+    // 删除商品
+    async handleRemoveProduct(item) {
+      const text = await this.$confirm(
+        '确定删除该设备认证吗?删除后,其他机构已关联此设备认证的也将一并删除,请慎重操作。',
+        '提示',
+        {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning'
+        }
+      ).catch(() => {
+        this.$message.info('已取消操作')
+      })
+      if (text !== 'confirm') return
+
+      removeProduct({ productId: item.productId })
+        .then((res) => {
+          const h = this.$createElement
+          this.$notify.success({
+            title: '删除商品',
+            message: h('i', { style: 'color: #333' }, `已删除商品:"${item.productName}"`),
+            duration: 2000
+          })
+        })
+        .catch((error) => {
+          console.log(error)
+        })
+        .finally(() => {
+          this.getList()
+        })
+    },
+    // 显示二维码
+    handleShowQRcode(item) {
+      this.productInfo = item
+      this.showQRcode = true
+    },
+    // 获取审核未通过条数
+    // Audit failed 审核未通过
+    checkAuditFailedList(data) {
+      this.auditFailedList = data.filter((item) => item.auditStatus === 0)
+      if (this.auditFailedList.length > 0 && this.auditNoticeFlag) {
+        this.$notify.info({
+          title: '消息通知',
+          dangerouslyUseHTMLString: true,
+          message: `共有<b style="color:red">${this.auditFailedList.length}</b>件商品未能通过审核,请查看原因并及时修改!`,
+          duration: 3000
+        })
+        this.auditNoticeFlag = false
+      }
+    },
+    indexMethod(index) {
+      return index + this.listQuery.pageSize * (this.listQuery.pageNum - 1) + 1
+    }
+  }
+}
+</script>
+
+<style lang="scss" scoped></style>