Bläddra i källkod

Merge branch 'develop' into feature/1.5.3-smartLife-20241008

# Conflicts:
#	languages/en.js
#	languages/es.js
#	languages/ru-RU.js
#	languages/zh-CN.js
vothin 9 månader sedan
förälder
incheckning
5e5a530204

+ 21 - 8
languages/en.js

@@ -1389,15 +1389,19 @@ module.exports = {
     SATISFACTION: 'Satisfaction survey'
   },
   entraceguardUser: {
-    named: 'User Name',
+    named: 'User name',
     idNo: 'ID number',
-    ic: 'IC Card No',
-    phone: 'cell-phone number',
-    password: 'Access code',
-    forbidden: 'No Entry',
-    refreshUser: 'Refresh Users',
-    yes: 'YES',
-    nop: 'NO'
+    ic: 'IC card number',
+    phone: 'Phone number',
+    password: 'Access password',
+    forbidden: 'Forbidden access',
+    refreshUser: 'Refresh available users',
+    yes: 'Yes',
+    nop: 'No',
+    expire: 'Expiration date',
+    face: 'Face recognition photo',
+    chooseImage: 'Choose image',
+    syncToDevice: 'Synchronize to device',
   },
   boardTitle: {
     add: 'Add information board screen',
@@ -1698,5 +1702,14 @@ module.exports = {
     MINUTE60: '60 minutes',
     MINUTE90: '90 minutes',
     MINUTE120: '120 minutes'
+  },
+  wu20240928: {
+    editImage: 'Edit image',
+    preview: 'Preview',
+    reload: 'Reload',
+    comfirm: 'Confirm',
+    cancel: 'Cancel',
+    incorrectFormat: 'Not an image format file',
+    fileSizeLimit: 'Please upload an image less than {size}M'
   }
 }

+ 19 - 6
languages/es.js

@@ -1392,12 +1392,16 @@ module.exports = {
     named: 'Nombre de usuario',
     idNo: 'Número de identificación',
     ic: 'Número de tarjeta IC',
-    phone: 'Número de teléfono móvil',
-    password: 'Código de acceso',
-    forbidden: 'Entrada no autorizada',
-    refreshUser: 'Actualizar usuarios',
-    yes: 'Sí.',
-    nop: 'No.'
+    phone: 'Número de teléfono',
+    password: 'Contraseña de acceso',
+    forbidden: 'Acceso prohibido',
+    refreshUser: 'Actualizar usuarios disponibles',
+    yes: 'Sí',
+    nop: 'No',
+    expire: 'Fecha de vencimiento',
+    face: 'Foto de reconocimiento facial',
+    chooseImage: 'Elegir imagen',
+    syncToDevice: 'Sincronizar con el dispositivo'
   },
   boardTitle: {
     add: 'Añadir panel de visualización de información',
@@ -1698,5 +1702,14 @@ module.exports = {
     MINUTE60: '60 minutos',
     MINUTE90: '90 minutos',
     MINUTE120: '120 minutos',
+  },
+  wu20240928: {
+    editImage: 'Editar imagen',
+    preview: 'Vista previa',
+    reload: 'Recargar',
+    comfirm: 'Confirmar',
+    cancel: 'Cancelar',
+    incorrectFormat: 'No es un archivo de formato de imagen',
+    fileSizeLimit: 'Por favor, suba una imagen de menos de {size}M'
   }
 }

+ 18 - 5
languages/ru-RU.js

@@ -1391,13 +1391,17 @@ module.exports = {
   entraceguardUser: {
     named: 'Имя пользователя',
     idNo: 'Номер удостоверения личности',
-    ic: 'Номер карты IC',
+    ic: 'Номер IC-карты',
     phone: 'Номер телефона',
     password: 'Пароль доступа',
-    forbidden: 'Запрет на передвижение',
-    refreshUser: 'Actualizar usuarios',
-    yes: 'Да.',
-    nop: 'Нет'
+    forbidden: 'Запретить доступ',
+    refreshUser: 'Обновить доступных пользователей',
+    yes: 'Да',
+    nop: 'Нет',
+    expire: 'Дата истечения срока действия',
+    face: 'Фото для распознавания лица',
+    chooseImage: 'Выбрать изображение',
+    syncToDevice: 'Синхронизировать с устройством'
   },
   boardTitle: {
     add: 'Добавить информационный экран',
@@ -1697,5 +1701,14 @@ module.exports = {
     MINUTE60: '60 минут',
     MINUTE90: '90 минут',
     MINUTE120: '120 минут'
+  },
+  wu20240928: {
+    editImage: 'Редактировать изображение',
+    preview: 'Предварительный просмотр',
+    reload: 'Перезагрузить',
+    comfirm: 'Подтвердить',
+    cancel: 'Отменить',
+    incorrectFormat: 'Файл не в формате изображения',
+    fileSizeLimit: 'Пожалуйста, загрузите изображение меньше {size}M'
   }
 }

+ 14 - 1
languages/zh-CN.js

@@ -1397,7 +1397,11 @@ module.exports = {
     forbidden: '禁止通行',
     refreshUser: '刷新可用用户',
     yes: '是',
-    nop: '否'
+    nop: '否',
+    expire: '截止有效时间',
+    face: '人脸识别照片',
+    chooseImage: '选择图片',
+    syncToDevice: '同步到设备'
   },
   boardTitle: {
     add: '添加看板屏幕',
@@ -1701,5 +1705,14 @@ module.exports = {
     MINUTE60: '60分钟',
     MINUTE90: '90分钟',
     MINUTE120: '120分钟'
+  },
+  wu20240928: {
+    editImage: '编辑图片',
+    preview: '预览',
+    reload: '重新上传',
+    comfirm: '确定',
+    cancel: '取消',
+    incorrectFormat: '不是图片格式文件',
+    fileSizeLimit: '请上传小于{size}M的图片'
   }
 }

+ 2 - 0
package.json

@@ -59,6 +59,7 @@
     "jsonlint": "1.6.3",
     "jszip": "3.2.1",
     "jwt-decode": "^3.1.2",
+    "less-loader": "^12.2.0",
     "moment": "^2.29.4",
     "moment-timezone": "^0.5.43",
     "nanoid": "^4.0.2",
@@ -79,6 +80,7 @@
     "vue-baidu-map": "^0.21.22",
     "vue-class-component": "^7.2.6",
     "vue-count-to": "^1.0.13",
+    "vue-cropper": "^0.5.2",
     "vue-draggable-resizable": "^2.3.0",
     "vue-i18n": "^8.26.1",
     "vue-json-viewer": "^2.2.22",

+ 8 - 0
src/api/ncs_entrace_guard_user.js

@@ -26,3 +26,11 @@ export function refreshUser(part_id) {
     method: 'post'
   })
 }
+
+export function syncUserToDevice(part_id) {
+  return request({
+    url: `/entrace-guard-user/sync-to-device/${part_id}`,
+    method: 'POST',
+    baseURL:domain.DeviceUrl
+  })
+}

+ 15 - 0
src/api/upload.js

@@ -12,3 +12,18 @@ export function upload(file) {
     ]
   })
 }
+
+export function uploadSenceFile(file, scene) {
+  console.log(file)
+  return request({
+    url: `ncs/upload/uploadFile?scene=${scene}`,
+    method: 'POST',
+    data: file,
+    headers: { 'Content-Type': 'multipart/form-data' },
+    transformRequest: [
+      function() {
+        return file
+      }
+    ]
+  })
+}

+ 87 - 87
src/components/FileManager/locales/zhCN.js

@@ -1,90 +1,90 @@
-import uppyLocalezhCN from '@uppy/locales/lib/zh_CN.js';
+import uppyLocalezhCN from '@uppy/locales/lib/zh_CN.js'
 
 export default {
-  "Language": "语言",
-  "Create": "创建",
-  "Close": "关闭",
-  "Cancel": "取消",
-  "Save": "保存",
-  "Edit": "编辑",
-  "Crop": "裁切",
-  "New Folder": "新文件夹",
-  "New File": "新文件",
-  "Rename": "重命名",
-  "Delete": "删除",
-  "Upload": "上传",
-  "Download": "下载",
-  "Archive": "压缩",
-  "Unarchive": "解压缩",
-  "Open": "打开",
-  "Open containing folder": "打开对应的文件夹",
-  "Refresh": "刷新",
-  "Preview": "预览",
-  "Toggle Full Screen": "切换到全屏",
-  "Change View": "切换视图",
-  "Storage" : "存储",
-  "Go up a directory": "上一级目录",
-  "Search anything..": "搜索..",
-  "Name": "名称",
-  "Size": "大小",
-  "Date": "日期",
-  "Filepath": "文件路径",
-  "About": "关于",
-  "Folder Name": "文件夹名称",
-  "File Name": "文件名称",
-  "Move files": "移动文件",
-  "Yes, Move!": "确定,移动!",
-  "Delete files": "删除文件",
-  "Yes, Delete!": "确定,删除!",
-  "Upload Files" : "上传文件",
-  "No files selected!": "未选择文件!",
-  "Select Files": "选择文件",
-  "Archive the files": "压缩文件",
-  "Unarchive the files": "解压缩文件",
-  "The archive will be unarchived at": "此压缩文件将解压到",
-  "Archive name. (.zip file will be created)": "压缩包名称。(将创建 .zip 文件)",
-  "Vuefinder is a file manager component for vue 3.": "Vuefinder 是 Vue 3 的一个文件管理组件。",
-  "Create a new folder": "创建一个新文件夹",
-  "Create a new file": "创建一个新文件",
-  "Are you sure you want to delete these files?": "您确定要删除这些文件吗?",
-  "This action cannot be undone.": "此操作不能撤销。",
-  "Search results for" : "搜索结果为",
-  "{0} item(s) selected.": "{0} 个文件 已选择。",
-  "{0} is renamed." : "{0} 已重命名。",
-  "This is a readonly storage." : "这是只读存储。",
-  "{0} is created." : "{0} 已创建。",
-  "Files moved." : "文件已移动。",
-  "Files deleted." : "文件已删除。",
-  "The file unarchived." : "文件已解压。",
-  "The file(s) archived." : "文件已压缩。",
-  "Updated." : "已更新。",
-  "No search result found." : "未找到搜索结果。",
-  "Are you sure you want to move these files?" : "您确定要移动这些文件吗?",
-  "File Size": "文件大小",
-  "Last Modified": "文件修改时间",
-  "Drag&Drop: on": "拖拽: 开",
-  "Drag&Drop: off": "拖拽: 关",
-  "Select Folders": "选择文件夹",
-  "Clear all": "清除全部",
-  "Clear only successful": "清除已成功上传的",
-  "Drag and drop the files/folders to here or click here.": "拖拽或点击此处上传文件/文件夹。",
-  "Release to drop these files.": "放开后添加这些文件。",
-  "Canceled": "已取消",
-  "Done": "已完成",
-  "Network Error, Unable establish connection to the server or interrupted.": "网络错误,无法连接到服务器或连接被意外中断。",
-  "Pending upload": "待上传",
-  "Please select file to upload first." : "请先选择要上传的文件。",
-  "About {0}": "关于 {0}",
-  "Settings": "设置",
-  "Use Metric Units": "使用公制单位",
-  "Saved.": "已保存。",
-  "Reset Settings": "重置设置",
-  "Download doesn\'t work? You can try right-click \"Download\" button, select \"Save link as...\".": "下载不管用?您可以尝试右键点击“下载”按钮,选择“链接另存为...”。",
-  "Theme": "主题",
-  "Dark": "深色",
-  "Light": "浅色",
-  "System": "系统",
-  "Target Directory": "目标目录",
-  "uppy": uppyLocalezhCN,
-  'Select':'选择'
+  'Language': '语言',
+  'Create': '创建',
+  'Close': '关闭',
+  'Cancel': '取消',
+  'Save': '保存',
+  'Edit': '编辑',
+  'Crop': '裁切',
+  'New Folder': '新文件夹',
+  'New File': '新文件',
+  'Rename': '重命名',
+  'Delete': '删除',
+  'Upload': '上传',
+  'Download': '下载',
+  'Archive': '压缩',
+  'Unarchive': '解压缩',
+  'Open': '打开',
+  'Open containing folder': '打开对应的文件夹',
+  'Refresh': '刷新',
+  'Preview': '预览',
+  'Toggle Full Screen': '切换到全屏',
+  'Change View': '切换视图',
+  'Storage': '存储',
+  'Go up a directory': '上一级目录',
+  'Search anything..': '搜索..',
+  'Name': '名称',
+  'Size': '大小',
+  'Date': '日期',
+  'Filepath': '文件路径',
+  'About': '关于',
+  'Folder Name': '文件夹名称',
+  'File Name': '文件名称',
+  'Move files': '移动文件',
+  'Yes, Move!': '确定,移动!',
+  'Delete files': '删除文件',
+  'Yes, Delete!': '确定,删除!',
+  'Upload Files': '上传文件',
+  'No files selected!': '未选择文件!',
+  'Select Files': '选择文件',
+  'Archive the files': '压缩文件',
+  'Unarchive the files': '解压缩文件',
+  'The archive will be unarchived at': '此压缩文件将解压到',
+  'Archive name. (.zip file will be created)': '压缩包名称。(将创建 .zip 文件)',
+  'Vuefinder is a file manager component for vue 3.': 'Vuefinder 是 Vue 3 的一个文件管理组件。',
+  'Create a new folder': '创建一个新文件夹',
+  'Create a new file': '创建一个新文件',
+  'Are you sure you want to delete these files?': '您确定要删除这些文件吗?',
+  'This action cannot be undone.': '此操作不能撤销。',
+  'Search results for': '搜索结果为',
+  '{0} item(s) selected.': '{0} 个文件 已选择。',
+  '{0} is renamed.': '{0} 已重命名。',
+  'This is a readonly storage.': '这是只读存储。',
+  '{0} is created.': '{0} 已创建。',
+  'Files moved.': '文件已移动。',
+  'Files deleted.': '文件已删除。',
+  'The file unarchived.': '文件已解压。',
+  'The file(s) archived.': '文件已压缩。',
+  'Updated.': '已更新。',
+  'No search result found.': '未找到搜索结果。',
+  'Are you sure you want to move these files?': '您确定要移动这些文件吗?',
+  'File Size': '文件大小',
+  'Last Modified': '文件修改时间',
+  'Drag&Drop: on': '拖拽: 开',
+  'Drag&Drop: off': '拖拽: 关',
+  'Select Folders': '选择文件夹',
+  'Clear all': '清除全部',
+  'Clear only successful': '清除已成功上传的',
+  'Drag and drop the files/folders to here or click here.': '拖拽或点击此处上传文件/文件夹。',
+  'Release to drop these files.': '放开后添加这些文件。',
+  'Canceled': '已取消',
+  'Done': '已完成',
+  'Network Error, Unable establish connection to the server or interrupted.': '网络错误,无法连接到服务器或连接被意外中断。',
+  'Pending upload': '待上传',
+  'Please select file to upload first.': '请先选择要上传的文件。',
+  'About {0}': '关于 {0}',
+  'Settings': '设置',
+  'Use Metric Units': '使用公制单位',
+  'Saved.': '已保存。',
+  'Reset Settings': '重置设置',
+  "Download doesn\'t work? You can try right-click \"Download\" button, select \"Save link as...\".": '下载不管用?您可以尝试右键点击“下载”按钮,选择“链接另存为...”。',
+  'Theme': '主题',
+  'Dark': '深色',
+  'Light': '浅色',
+  'System': '系统',
+  'Target Directory': '目标目录',
+  'uppy': uppyLocalezhCN,
+  'Select': '选择'
 }

+ 292 - 0
src/components/ReCropperPreview/index.vue

@@ -0,0 +1,292 @@
+<template>
+  <el-dialog
+      :title="this.$t('wu20240928.editImage')"
+      class="cropper-dialog"
+      :close-on-click-modal="false"
+      :visible="dialogVisible"
+      center
+      @close="close"
+  >
+    <div class="cropper-wrap">
+      <div
+          class="cropper-box"
+          :style="cropperStyle"
+      >
+        <vue-cropper
+            ref="cropper"
+            :img="option.img"
+            :output-size="option.size"
+            :output-type="option.outputType"
+            :info="option.info"
+            :full="option.full"
+            :canScale="option.canScale"
+            :can-move="option.canMove"
+            :can-move-box="option.canMoveBox"
+            :fixed="option.fixed"
+            :fixed-box="option.fixedBox"
+            :original="option.original"
+            :auto-crop="option.autoCrop"
+            :auto-crop-width="option.autoCropWidth"
+            :auto-crop-height="option.autoCropHeight"
+            :center-box="option.centerBox"
+            :high="option.high"
+            :info-true="option.infoTrue"
+            :max-img-size="option.maxImageSize"
+            :enlarge="option.enlarge"
+            :mode="option.mode"
+            :maxImgSize="option.maxImgSize"
+            @realTime="realTime"
+        />
+      </div>
+      <div class="preview-box">
+        <div class="preview-title">
+          <span>{{this.$t('wu20240928.preview')}}</span>
+          <span @click="upload" class="preveiw-upload">{{this.$t('wu20240928.reload')}}</span>
+        </div>
+        <input
+            ref="upload"
+            type="file"
+            style="position:absolute; clip:rect(0 0 0 0);"
+            accept="image/png, image/jpeg, image/jpg"
+            @change="uploadImg"
+        >
+        <div
+
+            class="preview-img"
+
+        >
+<!--          <div :class="preview.div" :style="previewStyle">-->
+<!--            <img-->
+<!--                :style="preview.img"-->
+<!--                :src="preview.url"-->
+<!--            />-->
+<!--          </div>-->
+
+          <div class="show-preview" :style="{'width': preview.w + 'px', 'height': preview.h + 'px',  'overflow': 'hidden', 'margin': '5px'}">
+            <div :style="preview.div">
+              <img :src="preview.url" :style="preview.img"/>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div slot="footer" class="dialog-footer">
+      <el-button type="success" icon="el-icon-refresh-right" @click="rotateRight" ></el-button>
+      <el-button type="success" icon="el-icon-refresh-left" @click="rotateLeft"></el-button>
+      <el-button @click="close">{{this.$t('wu20240928.cancel')}}</el-button>
+      <el-button type="primary" @click="finish" :loading="loading">{{this.$t('wu20240928.comfirm')}}</el-button>
+    </div>
+  </el-dialog>
+</template>
+
+<script>
+import {VueCropper} from "vue-cropper";  // 引入vue-cropper
+
+export default {
+  name: 'ReCropperPreview',
+  components: {
+    VueCropper
+  },
+  data () {
+    return {
+      // 裁剪组件的基础配置option
+      option: {
+        img: '', // 裁剪图片的地址
+        outputSize: 1, // 裁剪生成图片的质量
+        outputType: 'jpg', // 裁剪生成图片的格式
+        full: true, // 是否输出原图比例的截图
+        info: true, // 图片大小信息
+        canScale: true, // 图片是否允许滚轮缩放
+        autoCrop: true, // 是否默认生成截图框
+        autoCropWidth: 200, // 默认生成截图框宽度
+        autoCropHeight: 150, // 默认生成截图框高度
+        canMove: true, // 上传图片是否可以移动
+        fixedBox: false, // 固定截图框大小 不允许改变
+        fixed: false, // 是否开启截图框宽高固定比例
+        canMoveBox: true, // 截图框能否拖动
+        original: false, // 上传图片按照原始比例渲染
+        centerBox: false, // 截图框是否被限制在图片里面
+        height: true,
+        infoTrue: false, // true 为展示真实输出图片宽高 false 展示看到的截图框宽高
+        enlarge: 1, // 图片根据截图框输出比例倍数
+        mode: 'container', // 图片默认渲染方式
+        maxImgSize: 375
+      },
+      // 防止重复提交
+      loading: false,
+      preview: {},
+      previewStyle: {}
+    }
+  },
+  props: {
+    dialogVisible: {
+      type: Boolean,
+      default: false
+    },
+    cropperOption: {
+      type: Object,
+      default: () => {}
+    },
+    cropperStyle: {
+      type: Object,
+      default: () => {}
+    },
+    fileSize: {
+      type: Number,
+      default: 2
+    },
+    // 裁剪预览图片缩放比例
+    zoom: {
+      type: Number,
+      default: 1
+    }
+  },
+  watch: {
+    cropperOption: {
+      handler (value) {
+        this.option = Object.assign(this.option, value)
+      },
+      immediate: true,
+      deep: true
+    }
+  },
+  methods: {
+    upload () { // 点击上传
+      this.$refs.upload.value = null
+      this.$refs.upload.click()
+    },
+    uploadImg (e) { // 上传图片
+      let file = e.target.files[0]
+      if (!/\.(jpg|jpeg|png|JPG|PNG)$/.test(e.target.value)) {
+        this.$message.error(`${file.name}${this.$t('wu20240928.incorrectFormat')}`)
+        return false
+      }
+      if (file.size > 1024 * 1024 * this.fileSize) { // 图片不大于2M
+        this.$message.error(`${this.$t('wu20240928.fileSize',this.fileSize)}`)
+        return false
+      }
+      let reader = new FileReader()
+      // 转化为blob
+      reader.readAsArrayBuffer(file)
+      reader.onload = e => {
+        let data
+        if (typeof e.target.result === 'object') {
+          // 把Array Buffer转化为blob 如果是base64不需要
+          data = window.URL.createObjectURL(new Blob([e.target.result]))
+        } else {
+          data = e.target.result
+        }
+        this.$set(this.option, 'img', data)
+      }
+    },
+    realTime (preview) { // 实时预览
+      this.preview = preview
+      console.log('preview',preview)
+      this.previewStyle = {
+        width: preview.w+'px',
+        height: preview.h + 'px',
+        overflow: 'hidden',
+        margin: '0',
+        zoom: this.zoom
+      }
+    },
+    // 将base64转换为文件
+    dataURLtoFile (dataurl, filename) {
+      let arr = dataurl.split(',')
+      let mime = arr[0].match(/:(.*?);/)[1]
+      let bstr = atob(arr[1])
+      let len = bstr.length
+      let u8arr = new Uint8Array(len)
+      while (len--) {
+        u8arr[len] = bstr.charCodeAt(len)
+      }
+      return new File([u8arr], filename, { type: mime })
+    },
+    // 将base64转换为png文件图片
+    finish () {
+      this.$refs.cropper.getCropData(data => {
+        let file = this.dataURLtoFile(data, 'images.png')
+        this.$emit('uploadCropper', file, data)
+      })
+    },
+    close () {
+      this.$emit('close')
+    },
+    rotateLeft() {
+      this.$refs.cropper.rotateLeft()
+    },
+    rotateRight() {
+      this.$refs.cropper.rotateRight()
+    },
+  }
+}
+</script>
+<style  scoped>
+.cropper-dialog .el-dialog {
+  width: max-content;
+}
+
+.cropper-dialog .el-dialog__body {
+  padding: 20px;
+}
+
+
+
+.cropper-wrap {
+  display: flex;
+}
+
+.cropper-wrap .cropper-box {
+  margin-right: 20px;
+  width: 375px;
+  height: 176px;
+}
+.cropper-wrap .preview-box{
+ flex: 1;
+}
+.cropper-wrap .preview-box .preview-title {
+  display: flex;
+  min-width: 100px;
+  justify-content: space-between;
+  align-items: center;
+  height: 32px;
+  color: rgba(30,35,48,1);
+}
+
+.cropper-wrap .preview-box .preview-title .preveiw-upload {
+  color: #0067ED;
+  cursor: pointer;
+}
+
+.cropper-wrap .preview-box .preview-img {
+  border-radius: 2px;
+}
+
+.fun-btn {
+  margin-top: 16px;
+}
+
+.fun-btn i {
+  margin-right: 16px;
+  font-size: 18px;
+  color: #8c8c8c;
+  cursor: pointer;
+}
+
+.fun-btn i:hover {
+  color: #0067ED;
+}
+
+.avatar-right-previews {
+  width: 100%;
+  height: 100%;
+  overflow: hidden;
+}
+
+.fun-btn .reUpload {
+  margin-right: 16px;
+}
+.show-preview img{
+  max-width: none;
+}
+</style>

+ 116 - 4
src/views/entrace-guard/users.vue

@@ -24,6 +24,8 @@
                 <div class="toolbar-btns">
                     <el-button type="primary" size="mini" @click="refreshUser">{{ this.$t('entraceguardUser.refreshUser') }}
                     </el-button>
+                  <el-button type="primary" size="mini" @click="syncToDevice()">{{ this.$t('entraceguardUser.syncToDevice') }}
+                  </el-button>
                 </div>
             </div>
             <el-pagination
@@ -91,6 +93,40 @@
                     </el-col>
                 </el-row>
 
+              <el-row>
+                <el-col :span="12">
+                  <el-form-item :label="this.$t('entraceguardUser.expire')" prop="effect_time">
+                    <el-date-picker
+                        v-model="formmodel.effect_time"
+                        type="datetime"
+                        format="yyyy-MM-dd HH:mm:ss"
+                        value-format="yyyy-MM-dd HH:mm:ss"
+                        :placeholder="this.$t('entraceguardUser.expire')">
+                    </el-date-picker>
+<!--                    <el-input v-model="formmodel.effect_time" clearable-->
+<!--                              :placeholder="this.$t('entraceguardUser.effect_time')" :maxlength="20"/>-->
+                  </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                  <el-form-item :label="this.$t('entraceguardUser.face')">
+                    <el-upload
+                        class="upload-banner"
+                        action=""
+                        :auto-upload="true"
+                        :on-change="handleCrop"
+                        :before-upload="beforeAvatarUpload"
+                        :show-file-list="false"
+                    >
+<!--                      <el-image v-if="cropperData.iconUrl" :src="cropperData.iconUrl" fit="contain"></el-image>-->
+                      <el-image style="max-width: 200px" v-if="formmodel.face_template" :src="formmodel.face_template" fit="contain"></el-image>
+                      <div v-else class="upload-box">
+                        <el-button type="primary" class="select-btn">{{this.$t('entraceguardUser.chooseImage')}}}</el-button>
+                      </div>
+                    </el-upload>
+                  </el-form-item>
+                </el-col>
+              </el-row>
+
                 <el-form-item>
                     <el-button type="primary" class="save" @click="handlerSubmit('editForm')">{{ this.$t('action.save')
                         }}
@@ -100,24 +136,36 @@
 
         </el-dialog>
         <!-- 用户信息 -->
-
+      <reCropperPreview
+          :dialogVisible="showCropper"
+          :cropper-option="cropperOption"
+          :file-size="1"
+          :cropper-style="cropperStyle"
+          @close="showCropper=false"
+          @uploadCropper="uploadImg"
+      />
     </div>
 </template>
 
 <script>
+
     import {AG_GRID_LOCALE_CN} from '@/utils/AgGridVueLocaleCn'
     import ButtonCellRender from '@/components/AgGridCellRender/ButtonCellRender'
     import ButtonCellRenderList from '@/components/AgGridCellRender/ButtonCellRenderList'
     import ListFilter from '@/components/AgGridCustomFilter/ListFilter'
     import RadioFilter from '@/components/AgGridCustomFilter/RadioFilter'
     import * as API_Customer from '@/api/ncs_entrace_guard_user'
-    import {unix2Date, unixToDate} from '@/utils/Foundation'
 
+    import reCropperPreview from "@/components/ReCropperPreview/index.vue";
+    import * as API_Upload from "@/api/upload";
+    import {syncUserToDevice} from "@/api/ncs_entrace_guard_user";
+    const serverUrl = domain.serverUrl
     export default {
         name: "users",
-        components: {ButtonCellRenderList, ButtonCellRender, ListFilter, RadioFilter},
+        components: {ButtonCellRenderList, ButtonCellRender, ListFilter, RadioFilter,reCropperPreview},
         data() {
             return {
+
                 /** ag-grid参数 **/
                 pageData: {}, // 翻页数据
                 columnDefs: null,
@@ -142,7 +190,18 @@
                     {key: this.$t('entraceguardUser.yes'), value: true, color: 'green'},
                     {key: this.$t('entraceguardUser.nop'), value: false, color: 'red'}
                 ],
-                rules: {}
+                rules: {},
+              showCropper:false,
+              cropperOption: {
+                img: '',
+                autoCropWidth: 375,
+                autoCropHeight: 176
+              },
+              cropperStyle: {
+                width: '390px',
+                height: '290px'
+              },
+              cropperData: {}
             }
         },
         computed: {
@@ -276,6 +335,14 @@
         },
 
         methods: {
+          syncToDevice(){
+            syncUserToDevice(this.$store.getters.partId).then(res => {
+              this.getList()
+            }).catch(err => {
+              this.$message.error(err.message)
+            })
+          },
+
             windowResize() {
                 this.$set(this, 'mainAreaHeight', Number(document.documentElement.clientHeight) - 84)
             },
@@ -397,7 +464,52 @@
                         return false
                     }
                 })
+            },
+          // 自定义上传方法
+          uploadImg (file, data) {
+            let fileFormData = new FormData()
+            fileFormData.append('file', file)
+            console.log(file,data)
+            // 移除上传组件带来的bug
+            // document.getElementsByTagName('body')[0].removeAttribute('style')
+            this.cropperData.iconUrl = data
+            this.showCropper = false
+            // api.uploadFile(fileFormData, this).then(res => {
+            //   this.cropperData.iconUrl = res
+            //   this.showCropper = false
+            //   this.$message({
+            //     message: '操作成功',
+            //     type: 'success'
+            //   })
+            // })
+            API_Upload.uploadSenceFile(fileFormData,'avatar').then(res => {
+              this.formmodel.face_template = `${serverUrl}/${res}`
+            })
+          },
+          handleCrop (file) {
+            // 点击弹出剪裁框
+            this.$nextTick(() => {
+              if (this.canCropper) {
+                this.cropperOption.img = window.URL.createObjectURL(file.raw)
+                this.showCropper = this.canCropper
+              }
+            })
+          },
+          beforeAvatarUpload (file) {
+            // 上传前校验
+            const isJPG = file.type === 'image/jpeg' || file.type === 'image/jpg' || file.type === 'image/png'
+            const isLt2M = file.size / 1024 / 1024 < 2
+
+            if (!isJPG) {
+              this.$message.error(`${this.$t('wu20240928.incorrectFormat')}`)
+            }
+            if (!isLt2M) {
+              this.$message.error(`${this.$t('wu20240928.fileSizeLimit',{size:2})}`)
             }
+            // 校验通过后显示裁剪框
+            this.canCropper = isJPG && isLt2M
+            return false
+          }
         }
     }
 </script>

+ 14 - 13
src/views/ncs-clerk/components/clerkList.vue

@@ -107,7 +107,7 @@
               <el-radio v-model="addMemberForm.sex" :label="0">{{ this.$t('member.woman') }}</el-radio>
             </el-form-item>
           </el-col>
-          <el-col :span="12">
+          <el-col :span="8">
 
             <el-form-item :label="this.$t('member.face')">
               <el-upload
@@ -122,6 +122,7 @@
               </el-upload>
             </el-form-item>
           </el-col>
+
         </el-row>
 
         <el-row>
@@ -790,18 +791,18 @@ export default {
           image.onload = () => {
             const width = image.width
             const height = image.height
-            if (width > 500 || width < 100) {
-              this.$message.error(this.$t('member.faceError1'))
-              reject()
-            }
-            if (width !== height) {
-              this.$message.error(this.$t('member.faceError2'))
-              reject()
-            }
-            if (height > 500 || height < 100) {
-              this.$message.error(this.$t('member.faceError3'))
-              reject()
-            }
+            // if (width > 500 || width < 100) {
+            //   this.$message.error(this.$t('member.faceError1'))
+            //   reject()
+            // }
+            // if (width !== height) {
+            //   this.$message.error(this.$t('member.faceError2'))
+            //   reject()
+            // }
+            // if (height > 500 || height < 100) {
+            //   this.$message.error(this.$t('member.faceError3'))
+            //   reject()
+            // }
             resolve()
           }
           image.src = event.target.result