Browse Source

add 骑手添加附件

tea 3 months ago
parent
commit
c2bfcf05f1

+ 17 - 0
kxmall-admin-ui/src/api/rider/attachment.js

@@ -0,0 +1,17 @@
+import request from '@/utils/request'
+
+// 根据骑手ID查询附件列表
+export function getAttachmentsByRiderId(riderId) {
+  return request({
+    url: '/rider/attachment/list/' + riderId,
+    method: 'get'
+  })
+}
+
+// 删除单个附件
+export function deleteAttachment(attachmentId) {
+  return request({
+    url: '/rider/attachment/' + attachmentId,
+    method: 'delete'
+  })
+}

+ 8 - 0
kxmall-admin-ui/src/api/rider/rider.js

@@ -102,3 +102,11 @@ export function getRiderQrcodeImage(data) {
   })
 }
 
+// 重置骑手密码
+export function resetRiderPassword(riderId) {
+  return request({
+    url: '/rider/rider/resetPassword/' + riderId,
+    method: 'post'
+  })
+}
+

+ 287 - 0
kxmall-admin-ui/src/components/AttachmentUpload/index.vue

@@ -0,0 +1,287 @@
+<template>
+  <div class="attachment-upload">
+    <el-upload
+      :action="uploadUrl"
+      :headers="headers"
+      :data="uploadData"
+      :show-file-list="false"
+      :on-success="handleUploadSuccess"
+      :on-error="handleUploadError"
+      :before-upload="beforeUpload"
+      :accept="accept"
+      :multiple="multiple"
+      :disabled="disabled"
+      :loading="uploading"
+    >
+      <el-button :type="buttonType" :size="buttonSize" :icon="buttonIcon" :disabled="disabled">
+        {{ buttonText }}
+      </el-button>
+    </el-upload>
+    
+    <div v-if="fileList.length > 0" class="file-list">
+      <div v-for="(file, index) in fileList" :key="index" class="file-item">
+        <div class="file-preview">
+          <img v-if="isImageFile(file.url)" :src="file.url" class="file-image" />
+          <div v-else class="file-icon">
+            <i class="el-icon-document"></i>
+          </div>
+        </div>
+        <div class="file-info">
+          <div class="file-name">{{ file.name }}</div>
+          <div class="file-size">{{ formatFileSize(file.size) }}</div>
+        </div>
+        <div class="file-actions">
+          <el-button type="text" size="mini" @click="previewFile(file)">预览</el-button>
+          <el-button type="text" size="mini" @click="downloadFile(file)">下载</el-button>
+          <el-button type="text" size="mini" @click="removeFile(index)" style="color: #f56c6c;">删除</el-button>
+        </div>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: 'AttachmentUpload',
+  props: {
+    // 上传地址
+    uploadUrl: {
+      type: String,
+      default: '/common/upload'
+    },
+    // 请求头
+    headers: {
+      type: Object,
+      default: () => ({})
+    },
+    // 上传时附带的额外参数
+    uploadData: {
+      type: Object,
+      default: () => ({})
+    },
+    // 接受上传的文件类型
+    accept: {
+      type: String,
+      default: '*'
+    },
+    // 是否支持多选文件
+    multiple: {
+      type: Boolean,
+      default: true
+    },
+    // 是否禁用
+    disabled: {
+      type: Boolean,
+      default: false
+    },
+    // 按钮类型
+    buttonType: {
+      type: String,
+      default: 'primary'
+    },
+    // 按钮大小
+    buttonSize: {
+      type: String,
+      default: 'mini'
+    },
+    // 按钮图标
+    buttonIcon: {
+      type: String,
+      default: 'el-icon-upload'
+    },
+    // 按钮文字
+    buttonText: {
+      type: String,
+      default: '上传文件'
+    },
+    // 文件列表
+    value: {
+      type: Array,
+      default: () => []
+    }
+  },
+  data() {
+    return {
+      uploading: false,
+      fileList: []
+    }
+  },
+  watch: {
+    value: {
+      handler(newVal) {
+        this.fileList = [...newVal];
+      },
+      immediate: true
+    }
+  },
+  methods: {
+    // 上传前校验
+    beforeUpload(file) {
+      // 文件大小限制 (10MB)
+      const isLt10M = file.size / 1024 / 1024 < 10;
+      if (!isLt10M) {
+        this.$message.error('上传文件大小不能超过 10MB!');
+        return false;
+      }
+      
+      this.uploading = true;
+      return true;
+    },
+    
+    // 上传成功
+    handleUploadSuccess(response, file) {
+      this.uploading = false;
+      
+      if (response.code === 200) {
+        const fileInfo = {
+          name: file.name,
+          url: response.data.url,
+          size: file.size,
+          type: file.type
+        };
+        
+        this.fileList.push(fileInfo);
+        this.$emit('input', this.fileList);
+        this.$emit('success', fileInfo);
+        
+        this.$message.success('上传成功');
+      } else {
+        this.$message.error(response.msg || '上传失败');
+      }
+    },
+    
+    // 上传失败
+    handleUploadError(err, file) {
+      this.uploading = false;
+      this.$message.error('上传失败');
+      this.$emit('error', err, file);
+    },
+    
+    // 预览文件
+    previewFile(file) {
+      if (this.isImageFile(file.url)) {
+        // 图片预览
+        this.$alert(`<img src="${file.url}" style="max-width: 100%;" />`, '文件预览', {
+          dangerouslyUseHTMLString: true,
+          showConfirmButton: false,
+          customClass: 'preview-dialog'
+        });
+      } else {
+        // 其他文件类型,提供下载链接
+        window.open(file.url, '_blank');
+      }
+    },
+    
+    // 下载文件
+    downloadFile(file) {
+      const link = document.createElement('a');
+      link.href = file.url;
+      link.download = file.name;
+      document.body.appendChild(link);
+      link.click();
+      document.body.removeChild(link);
+    },
+    
+    // 删除文件
+    removeFile(index) {
+      this.fileList.splice(index, 1);
+      this.$emit('input', this.fileList);
+      this.$emit('remove', index);
+    },
+    
+    // 判断是否为图片文件
+    isImageFile(url) {
+      if (!url) return false;
+      const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'];
+      return imageExtensions.some(ext => url.toLowerCase().includes(ext));
+    },
+    
+    // 格式化文件大小
+    formatFileSize(bytes) {
+      if (bytes === 0) return '0 B';
+      const k = 1024;
+      const sizes = ['B', 'KB', 'MB', 'GB'];
+      const i = Math.floor(Math.log(bytes) / Math.log(k));
+      return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
+    }
+  }
+}
+</script>
+
+<style scoped>
+.attachment-upload {
+  width: 100%;
+}
+
+.file-list {
+  margin-top: 15px;
+}
+
+.file-item {
+  display: flex;
+  align-items: center;
+  margin-bottom: 10px;
+  padding: 10px;
+  border: 1px solid #e4e7ed;
+  border-radius: 4px;
+  background-color: #fafafa;
+}
+
+.file-preview {
+  margin-right: 15px;
+}
+
+.file-image {
+  width: 60px;
+  height: 60px;
+  object-fit: cover;
+  border-radius: 4px;
+}
+
+.file-icon {
+  width: 60px;
+  height: 60px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background-color: #f5f7fa;
+  border-radius: 4px;
+  color: #909399;
+}
+
+.file-icon i {
+  font-size: 24px;
+}
+
+.file-info {
+  flex: 1;
+  margin-right: 15px;
+}
+
+.file-name {
+  font-size: 14px;
+  color: #303133;
+  margin-bottom: 5px;
+  word-break: break-all;
+}
+
+.file-size {
+  font-size: 12px;
+  color: #909399;
+}
+
+.file-actions {
+  display: flex;
+  gap: 5px;
+}
+
+/* 预览对话框样式 */
+:deep(.preview-dialog) {
+  width: 80%;
+  max-width: 800px;
+}
+
+:deep(.preview-dialog .el-message-box__content) {
+  text-align: center;
+}
+</style>

+ 224 - 13
kxmall-admin-ui/src/views/rider/rider/index.vue

@@ -103,8 +103,6 @@
         </template>
       </el-table-column>
 
-
-
       <el-table-column label="操作" align="center" width="300">
         <template slot-scope="scope">
           <el-button v-show="scope.row.state === 1" type="text" size="mini" style="color: #F56C6C;" @click="onDisable(scope.row)">禁用</el-button>
@@ -112,8 +110,7 @@
           <el-button v-show="scope.row.state === 1 && scope.row.workState === 1" type="text" size="mini" style="color: #409EFF;" @click="onRest(scope.row)">休息</el-button>
           <el-button v-show="scope.row.state === 1 && scope.row.workState === 0" type="text" size="mini" style="color: #409EFF;" @click="onWork(scope.row)">开工</el-button>
           <el-button size="mini" type="text" style="color: #409EFF;" @click="handleUpdate(scope.row)" v-hasPermi="['rider:rider:edit']">修改</el-button>
-          <el-button size="mini" type="text" style="color: #F56C6C;" @click="handleDelete(scope.row)" v-hasPermi="['rider:rider:remove']">删除</el-button>
-          <el-button size="mini" type="text" style="color: #67C23A;" @click="showBankInfo(scope.row)">银行信息</el-button>
+          <el-button size="mini" type="text" style="color: #E6A23C;" @click="onResetPassword(scope.row)">重置密码</el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -159,7 +156,7 @@
             </el-time-select>
             <el-time-select
                 v-model="form.deliveryEnd"
-                :picker-options="{ start: '00:00', step: '00:15', end: '24:00', minTime: form.deliveryStart }"
+                :picker-options="{ start: '00:00', step: '00:15', end: '24:00'}"
                 placeholder="结束时间">
             </el-time-select>
           </div>
@@ -174,10 +171,63 @@
           </el-select>
         </el-form-item>
 
+        <el-form-item label="骑手状态" prop="state">
+          <el-radio-group v-model="form.state">
+            <el-radio :label="1">正常</el-radio>
+            <el-radio :label="0">禁用</el-radio>
+          </el-radio-group>
+        </el-form-item>
+
+        <el-form-item label="开工状态" prop="workState">
+          <el-radio-group v-model="form.workState">
+            <el-radio :label="1">开工中</el-radio>
+            <el-radio :label="0">休息中</el-radio>
+          </el-radio-group>
+        </el-form-item>
+
         <el-form-item label="消息推送订阅">
           <img style="width: 162px;height: 162px;" :src="qrcode" alt="">
         </el-form-item>
 
+        <el-form-item label="骑手附件">
+          <div class="attachment-container">
+            <div class="attachment-list">
+              <div v-for="(attachment, index) in form.attachments" :key="index" class="attachment-item">
+                <div class="attachment-preview">
+                  <img v-if="isImageFile(attachment.attachmentUrl)" :src="attachment.attachmentUrl" class="attachment-image" />
+                  <div v-else class="attachment-file">
+                    <i class="el-icon-document"></i>
+                    <span>{{ getFileName(attachment.attachmentUrl) }}</span>
+                  </div>
+                </div>
+                <div class="attachment-info">
+                  <el-select v-model="attachment.authAttachmentType" placeholder="证件类型" size="mini">
+                    <el-option label="身份证正面" :value="1"></el-option>
+                    <el-option label="身份证反面" :value="2"></el-option>
+                    <el-option label="驾驶证" :value="3"></el-option>
+                    <el-option label="行驶证" :value="4"></el-option>
+                    <el-option label="其他证件" :value="5"></el-option>
+                  </el-select>
+                  <el-upload
+                    :action="uploadUrl"
+                    :headers="uploadHeaders"
+                    :show-file-list="false"
+                    :before-upload="beforeRowUpload"
+                    :on-success="(res, file) => onRowUploadSuccess(res, file, index)"
+                    :on-error="onRowUploadError"
+                    style="margin-left: 10px;"
+                  >
+                    <el-button size="mini" type="primary">上传</el-button>
+                  </el-upload>
+                  <el-button type="danger" size="mini" @click="removeAttachment(index)" style="margin-left: 10px;">删除</el-button>
+                </div>
+              </div>
+            </div>
+
+            <el-button type="primary" size="mini" @click="addAttachment" style="margin-top: 10px;">添加附件</el-button>
+          </div>
+        </el-form-item>
+
       </el-form>
       <div slot="footer" class="dialog-footer">
         <el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
@@ -214,7 +264,8 @@ import { listRider, getRider, delRider, addRider, updateRider,
   updateStateToNomral,
   updateBusinessStateToRest,
   getRiderQrcodeImage,
-  riderBankInfo} from "@/api/rider/rider";
+  resetRiderPassword} from "@/api/rider/rider";
+import { getAttachmentsByRiderId, deleteAttachment } from "@/api/rider/attachment";
 import {getStorageQrcodeImage, listAllStorage} from "@/api/storage/storage";
 
 const num2TextMap = {
@@ -238,6 +289,8 @@ const text2NumMap = {
 
 export default {
   name: "Rider",
+  components: {
+  },
   filters: {
     stateType(val) {
       if (!val) {
@@ -318,6 +371,11 @@ export default {
         storageId: [
           { required: true, message: "所属仓库不能为空", trigger: "blur" }
         ],
+      },
+      // 上传相关配置
+      uploadUrl: process.env.VUE_APP_BASE_API + '/system/oss/upload',
+      uploadHeaders: {
+        Authorization: 'Bearer ' + this.$store.getters.token
       }
     };
   },
@@ -355,16 +413,16 @@ export default {
       this.form = {
         id: undefined,
         name: undefined,
-        deliveryStart: undefined,
-        deliveryEnd: undefined,
+        deliveryStart: '08:00',
+        deliveryEnd: '22:00',
         phone: undefined,
         avatarUrl: undefined,
         lastLoginIp: undefined,
         gmtLastLogin: undefined,
         loginType: undefined,
         openId: undefined,
-        state: undefined,
-        workState: undefined,
+        state: 1,
+        workState: 0,
         createTime: undefined,
         updateTime: undefined,
         password: undefined,
@@ -372,10 +430,12 @@ export default {
         deliveryRadius: undefined,
         createBy: undefined,
         newPassword: undefined,
-        weekNumberIds:[],
-        updateBy: undefined
+        weekNumberIds: ['周一', '周二', '周三', '周四', '周五', '周六', '周日'],
+        updateBy: undefined,
+        attachments: []
       };
       this.resetForm("form");
+      // 重置行上传无需额外状态
     },
     /** 搜索按钮操作 */
     handleQuery() {
@@ -396,6 +456,10 @@ export default {
     /** 新增按钮操作 */
     handleAdd() {
       this.reset();
+      // 设置默认仓库为第一个
+      if (this.storages && this.storages.length > 0) {
+        this.form.storageId = this.storages[0].id;
+      }
       this.open = true;
       this.title = "添加配送";
     },
@@ -408,8 +472,20 @@ export default {
         this.loading = false;
         this.form = response.data;
         this.form.weekNumberIds = (this.form.weekNumberIds || []).map(item => num2TextMap[item])
+
+        // 加载附件数据
+        if (this.form.attachments) {
+          this.form.attachments = this.form.attachments.map(att => ({
+            ...att,
+            sortNo: att.sortNo || 0
+          }));
+        } else {
+          this.form.attachments = [];
+        }
+
         this.open = true;
         this.title = "修改配送";
+        // 行上传无需额外状态
       });
       const img= (await getRiderQrcodeImage({riderId: id})).data
       this.qrcode = 'data:image/gif;base64,' + img
@@ -487,12 +563,147 @@ export default {
       await updateBusinessWorkState([rider.id])
       rider.workState = 1
     },
+    // 重置密码
+    onResetPassword(row) {
+      this.$modal
+        .confirm('是否确认将该骑手密码重置为默认值?')
+        .then(() => {
+          return resetRiderPassword(row.id)
+        })
+        .then(() => {
+          this.$modal.msgSuccess('重置成功')
+        })
+        .catch(() => {})
+    },
     /** 导出按钮操作 */
     handleExport() {
       this.download('rider/rider/export', {
         ...this.queryParams
-      }, `rider_${new Date().getTime()}.xlsx`)
+      }, `rider_${new Date()}.xlsx`)
+    },
+
+    // 添加附件(仅新增一行,后续点上传再选文件)
+    addAttachment() {
+      this.form.attachments.push({
+        id: null,
+        riderId: null,
+        authAttachmentType: 1,
+        attachmentUrl: ''
+      });
+    },
+
+    // 删除附件
+    removeAttachment(index) {
+      this.form.attachments.splice(index, 1);
+      // 无排序字段
+    },
+
+    // 判断是否为图片文件
+    isImageFile(url) {
+      if (!url) return false;
+      const imageExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp'];
+      return imageExtensions.some(ext => url.toLowerCase().includes(ext));
+    },
+
+    // 获取文件名
+    getFileName(url) {
+      if (!url) return '';
+      return url.substring(url.lastIndexOf('/') + 1);
+    },
+
+    // 行上传前校验
+    beforeRowUpload(file) {
+      const isLt10M = file.size / 1024 / 1024 < 10;
+      if (!isLt10M) {
+        this.$message.error('上传文件大小不能超过 10MB!');
+        return false;
+      }
+      return true;
+    },
+    // 行上传成功
+    onRowUploadSuccess(res, file, index) {
+      if (res && res.code === 200 && res.data && res.data.url) {
+        this.$set(this.form.attachments[index], 'attachmentUrl', res.data.url);
+        this.$message.success('上传成功');
+      } else {
+        this.$message.error(res && res.msg ? res.msg : '上传失败');
+      }
+    },
+    // 行上传失败
+    onRowUploadError() {
+      this.$message.error('上传失败');
     }
   }
 };
 </script>
+
+<style scoped>
+.attachment-container {
+  width: 100%;
+}
+
+.attachment-list {
+  margin-bottom: 10px;
+}
+
+.attachment-item {
+  display: flex;
+  align-items: center;
+  margin-bottom: 15px;
+  padding: 10px;
+  border: 1px solid #e4e7ed;
+  border-radius: 4px;
+  background-color: #fafafa;
+}
+
+.attachment-preview {
+  margin-right: 15px;
+}
+
+.attachment-image {
+  width: 80px;
+  height: 80px;
+  object-fit: cover;
+  border-radius: 4px;
+}
+
+.attachment-file {
+  width: 80px;
+  height: 80px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  background-color: #f5f7fa;
+  border-radius: 4px;
+  color: #909399;
+}
+
+.attachment-file i {
+  font-size: 24px;
+  margin-bottom: 5px;
+}
+
+.attachment-file span {
+  font-size: 12px;
+  text-align: center;
+  word-break: break-all;
+}
+
+.attachment-info {
+  flex: 1;
+  display: flex;
+  align-items: center;
+}
+
+.dateList {
+  display: flex;
+  align-items: center;
+}
+
+.dateList .el-time-select {
+  margin-right: 10px;
+}
+
+/* 移除独立上传区域相关样式,保留行内控件样式 */
+</style>