haveyou 1 год назад
Родитель
Сommit
f5f4071abf

+ 3 - 2
src/components/CommonUpload/index.vue

@@ -43,7 +43,7 @@
     // 是否显示文件列表
     showFileList: {
       type: Boolean,
-      default: true
+      default: false
     },
     /** 文件大小限制, 单位MB */
     fileLimit: {
@@ -105,7 +105,7 @@
     locale: Object
   });
 
-  const emit = defineEmits(['itemClick', 'preview']);
+  const emit = defineEmits(['itemClick', 'preview', 'success']);
 
   /** 已上传数据 */
   const images = defineModel({
@@ -166,6 +166,7 @@
         item.progress = 100;
         item.status = 'done';
         item.url = res.url;
+        emit('success', res);
       })
       .catch((e) => {
         item.status = 'exception';

+ 10 - 0
src/utils/common.js

@@ -1,4 +1,14 @@
 import { removeToken } from '@/utils/token-util';
+import axios from 'axios';
+
+export async function downloadOssLink(url, name) {
+  const res = await axios.get(url, {
+    responseType: 'blob' // 设置响应类型为blob,以便处理二进制数据
+  });
+  console.log(res, '12321321');
+  await checkDownloadRes(res);
+  download(res.data, `${name}_${Date.now()}.xlsx`);
+}
 
 /**
  * 退出登录

+ 1 - 1
src/utils/request.js

@@ -66,7 +66,7 @@ service.interceptors.response.use(
     let bool =
       res.data?.code !== 200 &&
       res.config.method !== 'get' &&
-      res.config.responseType !== 'blob';
+      res.config.responseType !== 'blob' && res.config.showTips !== false;
     if (bool) {
       EleMessage.error(res.data?.msg);
     }

+ 3 - 4
src/views/data/books/components/book-export-log.vue

@@ -27,7 +27,7 @@
         <el-button
           type="primary"
           link
-          :disabled="row.exportStatus != 3"
+          :disabled="row.exportStatus != 3 || row.cleanStatus == 1"
           @click="handleDownload(row)"
           class="ml-4"
           >[下载]</el-button
@@ -78,7 +78,6 @@
   /** 弹窗打开事件 */
   const handleOpen = () => {
     visible.value = true;
-    nextTick(() => console.log('打开'));
   };
 
   let statusKeys = ['', '处理中', '失败', '成功'];
@@ -88,7 +87,6 @@
     { label: '申请时间', prop: 'createTime', align: 'center', width: 180 },
     { label: '条数', prop: 'dataCount', align: 'center', width: 90 },
     { label: '操作员', prop: 'createName', align: 'center' },
-    { label: '导出类型', prop: 'exportName', align: 'center' },
     {
       label: '处理状态',
       prop: 'exportStatus',
@@ -115,7 +113,8 @@
   const pageConfig = reactive({
     pageUrl: '/common/exportrecord/pagelist',
     fileName: '操作记录',
-    cacheKey: 'data-operation-log'
+    cacheKey: 'data-operation-log',
+    params: { logType: 1 }
   });
 
   async function handleDownload(row) {

+ 126 - 0
src/views/data/books/components/book-import-log.vue

@@ -0,0 +1,126 @@
+<!-- 编辑弹窗 -->
+<template>
+  <ele-modal
+    form
+    :width="1160"
+    v-model="visible"
+    title="导出记录"
+    @open="handleOpen"
+  >
+    <common-table
+      ref="pageRef"
+      :pageConfig="pageConfig"
+      :columns="columns"
+      :tools="false"
+    >
+      <template #toolbar>
+        <ProDatePicker
+          start-placeholder="导入时间(开始时间)"
+          end-placeholder="导入时间(结束时间)"
+          v-model="searchData"
+        />
+        <el-button type="primary" @click="reload" class="ml-4">查询</el-button>
+        <el-button type="info" @click="handleReset">重置</el-button>
+      </template>
+
+      <template #action="{ row }">
+        <el-button
+          type="primary"
+          link
+          :disabled="row.cleanStatus === 1"
+          @click="handleDownload(row)"
+          class="ml-4"
+          >[下载源文件]</el-button
+        >
+      </template>
+    </common-table>
+    <template #footer>
+      <el-button @click="handleCancel">关闭</el-button>
+    </template>
+  </ele-modal>
+</template>
+
+<script setup>
+  import { ref, reactive, nextTick } from 'vue';
+  import request from '@/utils/request';
+  import CommonTable from '@/components/CommonPage/CommonTable.vue';
+  import ProDatePicker from '@/components/CommonPage/ProDatePicker.vue';
+  import { download, toFormData, checkDownloadRes } from '@/utils/common';
+
+  const searchData = ref({ createTimeStart: '', createTimeEnd: '' });
+
+  const pageRef = ref(null);
+  function reload() {
+    console.log('reload', searchData.value);
+    pageRef.value?.reload(searchData.value);
+  }
+  function handleReset() {
+    searchData.value.createTimeStart = '';
+    searchData.value.createTimeEnd = '';
+    reload();
+  }
+
+  /** 弹窗是否打开 */
+  const visible = defineModel({ type: Boolean });
+
+  /** 关闭弹窗 */
+  const handleCancel = () => {
+    visible.value = false;
+  };
+
+  /** 弹窗打开事件 */
+  const handleOpen = () => {
+    visible.value = true;
+  };
+
+  let statusKeys = ['', '处理中', '失败', '成功'];
+  // cleanStatus 文件清理状态 0未清理 1已清理
+  /** 表格列配置 */
+  const columns = ref([
+    { label: '导入时间', prop: 'createTime', align: 'center', width: 180 },
+    { label: '条数', prop: 'dataCount', align: 'center', width: 90 },
+    { label: '操作员', prop: 'createName', align: 'center' },
+    {
+      label: '处理状态',
+      prop: 'exportStatus',
+      align: 'center',
+      formatter: (row) => statusKeys[row.exportStatus],
+      width: 90
+    },
+    {
+      label: '上次下载时间',
+      prop: 'downloadTime',
+      align: 'center',
+      width: 180
+    },
+    { label: '下载次数', prop: 'downloadNum', align: 'center', width: 90 },
+    {
+      columnKey: 'action',
+      label: '操作',
+      width: 150,
+      align: 'center',
+      slot: 'action'
+    }
+  ]);
+
+  const pageConfig = reactive({
+    pageUrl: '/common/exportrecord/pagelist',
+    fileName: '操作记录',
+    cacheKey: 'data-operation-log',
+    params: { logType: 2 }
+  });
+
+  async function handleDownload(row) {
+    const res = await request({
+      url: '/common/exportrecord/downLoadFile?id=' + row.id,
+      method: 'get',
+      responseType: 'blob'
+    });
+    await checkDownloadRes(res);
+    download(res.data, row.fileName);
+  }
+
+  defineExpose({
+    handleOpen
+  });
+</script>

+ 126 - 0
src/views/data/books/components/books-export-isbn.vue

@@ -0,0 +1,126 @@
+<template>
+  <ele-modal
+    :width="460"
+    title="根据ISBN导出"
+    :body-style="{ paddingTop: '8px' }"
+    v-model="visible"
+  >
+    <div class="flex mb-2">
+      <div class="text-sm">请先下载'图书基础数据导出模板':</div>
+      <el-button
+        @click="
+          downloadOssLink(
+            'https://shuhi.oss-cn-qingdao.aliyuncs.com/default/search_template.xlsx',
+            '图书基础数据导出模板'
+          )
+        "
+        link
+        type="primary"
+        >下载图书基础数据导出模板</el-button
+      >
+    </div>
+
+    <div class="flex flex-col mb-2">
+      <div class="text-sm text-yellow-500">使用说明:</div>
+      <div class="text-sm">1.支持通过导入查询,导出数据结果</div>
+      <div class="text-sm">2.文件ISBN不存在或者错误,会自动过滤掉</div>
+      <div class="text-sm mb-2">3.导入文件第一行需与模版完全一致</div>
+    </div>
+    <div v-loading="loading" class="user-import-upload">
+      <el-upload
+        v-model:file-list="fileList"
+        ref="uploadRef"
+        action=""
+        accept=".xls,.xlsx"
+        :before-upload="doUpload"
+        :auto-upload="false"
+        :limit="1"
+        :on-exceed="handleExceed"
+      >
+        <el-button type="primary" class="mr-2">选择文件</el-button>
+        <ele-text type="placeholder">只能上传 xls、xlsx 文件</ele-text>
+      </el-upload>
+    </div>
+
+    <template #footer>
+      <el-button @click="visible = false">关闭</el-button>
+      <el-button type="primary" @click="handleSumbit" :loading="loading"
+        >导入</el-button
+      >
+    </template>
+  </ele-modal>
+</template>
+
+<script setup>
+  import { ref, h } from 'vue';
+  import { genFileId } from 'element-plus';
+  import { ElMessageBox } from 'element-plus/es';
+  import { EleMessage } from 'ele-admin-plus/es';
+  import { CloudUploadOutlined } from '@/components/icons';
+  import { downloadOssLink } from '@/utils/common';
+  import request from '@/utils/request';
+
+  const emit = defineEmits(['done']);
+
+  /** 弹窗是否打开 */
+  const visible = defineModel({ type: Boolean });
+
+  /** 导入请求状态 */
+  const loading = ref(false);
+
+  const uploadRef = ref(null);
+  const fileList = ref([]);
+
+  function handleExceed(files) {
+    uploadRef.value?.clearFiles();
+    const file = files[0];
+    file.uid = genFileId();
+    uploadRef.value?.handleStart(file);
+  }
+
+  //导入图书
+  async function importBooks(file) {
+    const formData = new FormData();
+    formData.append('file', file);
+
+    const res = await request.post('/book/bookInfo/exportByFile', formData);
+    if (res.data.code === 200) {
+      return res.data;
+    }
+    return Promise.reject(new Error(res.data.msg));
+  }
+
+  /** 提交 */
+  const handleSumbit = () => {
+    loading.value = true;
+    let file = fileList.value[0];
+    importBooks(file.raw)
+      .then((res) => {
+        loading.value = false;
+        ElMessage.success(res.data || res.msg);
+        visible.value = false;
+        emit('done');
+      })
+      .catch((e) => {
+        loading.value = false;
+      });
+  };
+
+  /** 上传 */
+  const doUpload = (file) => {
+    if (
+      ![
+        'application/vnd.ms-excel',
+        'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
+      ].includes(file.type)
+    ) {
+      EleMessage.error('只能选择 excel 文件');
+      return false;
+    }
+    if (file.size / 1024 / 1024 > 10) {
+      EleMessage.error('大小不能超过 10MB');
+      return false;
+    }
+    return false;
+  };
+</script>

+ 23 - 31
src/views/data/books/components/books-import.vue

@@ -6,9 +6,17 @@
     v-model="visible"
   >
     <div class="flex mb-2">
-      <div class="text-sm">请先下载'图书基础数据导入模板:</div>
-      <el-button link type="primary" @click="handleDownload"
-        >下载商品信息导入模板</el-button
+      <div class="text-sm">请先下载'图书基础数据导入模板':</div>
+      <el-button
+        @click="
+          downloadOssLink(
+            'https://shuhi.oss-cn-qingdao.aliyuncs.com/default/book_info_export_template.xlsx',
+            '图书基础数据导入模板'
+          )
+        "
+        link
+        type="primary"
+        >下载图书基础数据导入模板</el-button
       >
     </div>
 
@@ -16,8 +24,12 @@
       <div class="text-sm text-yellow-500">使用说明:</div>
       <div class="text-sm">1.支持通过导入来批量修改,会忽略空白内容</div>
       <div class="text-sm">2.导入文件属性值不存在或者超长,会自动过滤掉</div>
-      <div class="text-sm">3.导入文件第一行需与模版完全一致</div>
-      <div class="text-sm">4.相同编码覆盖导入</div>
+      <div class="text-sm mb-2">3.导入文件第一行需与模版完全一致</div>
+
+      <el-radio-group v-model="coverImport">
+        <el-radio :value="1">相同ISBN覆盖导入</el-radio>
+        <el-radio :value="0">相同ISBN忽略导入</el-radio>
+      </el-radio-group>
     </div>
     <div v-loading="loading" class="user-import-upload">
       <el-upload
@@ -50,7 +62,7 @@
   import { ElMessageBox } from 'element-plus/es';
   import { EleMessage } from 'ele-admin-plus/es';
   import { CloudUploadOutlined } from '@/components/icons';
-  import { downloadTemplate } from '@/api/system/user';
+  import { downloadOssLink } from '@/utils/common';
   import request from '@/utils/request';
 
   const emit = defineEmits(['done']);
@@ -60,6 +72,8 @@
 
   /** 导入请求状态 */
   const loading = ref(false);
+  /** 是否覆盖 */
+  const coverImport = ref(1);
 
   const uploadRef = ref(null);
   const fileList = ref([]);
@@ -75,6 +89,8 @@
   async function importBooks(file) {
     const formData = new FormData();
     formData.append('file', file);
+    formData.append('coverImport', coverImport.value);
+
     const res = await request.post('/book/bookInfo/import', formData);
     if (res.data.code === 200) {
       return res.data.msg;
@@ -86,18 +102,10 @@
   const handleSumbit = () => {
     loading.value = true;
     let file = fileList.value[0];
-    console.log(file);
     importBooks(file.raw)
       .then((msg) => {
         loading.value = false;
-        ElMessageBox({
-          type: 'success',
-          title: '导入结果',
-          message: h('div', { innerHTML: msg }),
-          customStyle: { maxWidth: '442px' },
-          draggable: true
-        });
-
+        ElMessage.success(msg);
         visible.value = false;
         emit('done');
       })
@@ -123,20 +131,4 @@
     }
     return false;
   };
-
-  /** 下载模板 */
-  const handleDownload = () => {
-    const loading = EleMessage.loading({
-      message: '请求中..',
-      plain: true
-    });
-    downloadTemplate()
-      .then(() => {
-        loading.close();
-      })
-      .catch((e) => {
-        loading.close();
-        EleMessage.error(e.message);
-      });
-  };
 </script>

+ 74 - 16
src/views/data/books/components/upload-image.vue

@@ -13,9 +13,9 @@
               >共有{{ form.images.length }}条</el-text
             >
 
-            <el-radio-group v-model="radio">
+            <el-radio-group v-model="form.coverImport">
               <el-radio :value="1">相同文件名覆盖</el-radio>
-              <el-radio :value="2">相同文件名忽略</el-radio>
+              <el-radio :value="0">相同文件名忽略</el-radio>
             </el-radio-group>
           </div>
           <div>
@@ -30,11 +30,11 @@
             class="upload-image"
             ref="uploadRef"
             v-model:file-list="form.images"
-            action="https://run.mocky.io/v3/9d059bf9-4660-45f2-925d-ce80ad6c4d15"
             list-type="picture-card"
             :auto-upload="false"
             :show-file-list="false"
             multiple
+            accept="image/*"
           >
             <div class="flex flex-col items-center">
               <el-icon :size="24"><Plus /></el-icon>
@@ -51,6 +51,12 @@
               :src="item.url"
               fit="cover"
             />
+            <div
+              class="mask-error flex items-center justify-center"
+              v-if="item.error"
+            >
+              <el-text type="danger" size="small">{{ item.error }}</el-text>
+            </div>
           </div>
         </el-checkbox-group>
       </el-form-item>
@@ -67,11 +73,11 @@
 
   const form = reactive({
     images: [],
-    radio: 1
+    coverImport: 1
   });
+  const emit = defineEmits(['done']);
   /** 弹窗是否打开 */
   const visible = defineModel({ type: Boolean });
-  const radio = ref(1);
   /** 关闭弹窗 */
   const handleCancel = () => {
     visible.value = false;
@@ -106,18 +112,60 @@
     checkList.value = checkAll.value ? form.images.map((item) => item.uid) : [];
   }
 
-  function handleSubmit() {
-    const formData = new FormData();
-    form.images.forEach((file) => {
-      formData.append('files', file.raw); //文件
-    });
+  // 提交
+  async function handleSubmit() {
+    const { results, failedImages } = await processUploadQueue([
+      ...form.images
+    ]);
+    form.images = failedImages;
+    ElMessage.success(
+      `上传成功${results.length}张,失败${failedImages.length}张`
+    );
+    emit('done', results);
+  }
 
-    request.post('/common/uploads', formData).then((res) => {
-      if (res.data.code === 200) {
-        visible.value = false;
-        ElMessage.success('上传成功');
-      }
-    });
+  //上传队列
+  async function processUploadQueue(images, maxUploadNum = 6) {
+    const queue = [];
+    const results = [];
+    const failedImages = [];
+
+    // 创建一个函数来添加新的上传任务到队列中
+    function enqueue(image) {
+      const formData = new FormData();
+      formData.append('file', image.raw); //文件
+      formData.append('coverImport', form.coverImport);
+
+      let uploadImage = request
+        .post('/book/bookInfo/importCover', formData, { showTips: false })
+        .then((res) => {
+          if (res.data.code == 200) {
+            results.push(res);
+          } else {
+            image.status = 'fail';
+            image.error = res.data.msg;
+            failedImages.push(image);
+          }
+        })
+        .finally(() => {
+          // 当一个上传任务完成时,从队列中移除它,并尝试添加新的任务
+          queue.shift();
+          if (images.length > 0 && queue.length < maxUploadNum) {
+            enqueue(images.shift());
+          }
+        });
+      queue.push(uploadImage);
+    }
+
+    // 初始化队列
+    for (let i = 0; i < Math.min(images.length, maxUploadNum); i++) {
+      enqueue(images.shift());
+    }
+
+    // 等待所有上传任务完成
+    await Promise.all(queue);
+
+    return { results, failedImages };
   }
 
   defineExpose({
@@ -137,5 +185,15 @@
       top: -8px;
       left: 0;
     }
+
+    .mask-error {
+      width: 100%;
+      height: 24px;
+      background-color: rgba(255, 0, 0, 0.1);
+      position: absolute;
+      overflow: hidden;
+      bottom: 0;
+      left: 0;
+    }
   }
 </style>

+ 30 - 12
src/views/data/books/index.vue

@@ -13,15 +13,6 @@
         >
           新增图书
         </el-button>
-        <el-button
-          type="danger"
-          plain
-          :icon="DeleteOutlined"
-          v-permission="'data:books:batchDelete'"
-          @click="handleBatchDelete()"
-        >
-          批量删除
-        </el-button>
         <el-button
           type="primary"
           plain
@@ -49,6 +40,15 @@
         >
           上传图片
         </el-button>
+        <el-button
+          type="success"
+          plain
+          v-permission="'data:books:exportByIsbn'"
+          @click="handleExportByIsbn"
+          :icon="DownloadOutlined"
+        >
+          根据ISBN导出
+        </el-button>
         <el-button
           type="info"
           plain
@@ -102,10 +102,16 @@
       </template>
     </common-table>
     <books-edit ref="editRef" @success="reload()"></books-edit>
-    <books-import ref="importRef" v-model="showImport"></books-import>
+    <books-import
+      ref="importRef"
+      v-model="showImport"
+      @done="reload()"
+    ></books-import>
     <books-change-log ref="changeLogRef"></books-change-log>
-    <upload-image ref="uploadImageRef"></upload-image>
+    <upload-image ref="uploadImageRef" @done="reload()"></upload-image>
     <book-export-log ref="exportLogRef"></book-export-log>
+    <books-export-isbn ref="exportIsbnRef" v-model="showExportByIsbn"></books-export-isbn>
+    <book-import-log ref="importLogRef"></book-import-log>
   </ele-page>
 </template>
 
@@ -128,6 +134,8 @@
   import bookBaseInfo from '@/views/data/books/components/book-base-info.vue';
   import uploadImage from '@/views/data/books/components/upload-image.vue';
   import bookExportLog from '@/views/data/books/components/book-export-log.vue';
+  import bookImportLog from '@/views/data/books/components/book-import-log.vue'
+  import booksExportIsbn from '@/views/data/books/components/books-export-isbn.vue'
   import { useDictData } from '@/utils/use-dict-data';
 
   defineOptions({ name: 'recycleOrderCancelled' });
@@ -236,5 +244,15 @@
     exportLogRef.value?.handleOpen();
   }
   //导入记录
-  function handleImportLog() {}
+  const importLogRef = ref(null);
+  function handleImportLog() {
+    importLogRef.value?.handleOpen();
+  }
+
+  //根据isbn导出
+  const showExportByIsbn = ref(false);
+  function handleExportByIsbn() {
+    showExportByIsbn.value = true;
+  }
+
 </script>

+ 138 - 57
src/views/data/defaultImage/index.vue

@@ -4,96 +4,177 @@
       <el-button
         type="primary"
         style="width: 200px"
-        @click="handleSubmit"
+        @click="handleSetDefault"
         v-permission="'data:defaultImage:set'"
         >设置默认图片</el-button
       >
 
-      <div class="flex flex-wrap mt-4">
-        <image-upload ref="imagesUploadRef" v-model="form.images" :tools="false" multiple />
-      </div>
+      <el-checkbox-group
+        style="height: calc(100vh - 270px)"
+        v-model="checkList"
+        class="flex flex-wrap items-start content-start overflow-auto"
+        @change="handlecheckboxChange"
+      >
+        <el-upload
+          ref="uploadRef"
+          v-model:file-list="form.images"
+          list-type="picture-card"
+          :show-file-list="false"
+          multiple
+          accept="image/*"
+          :http-request="uploadFile"
+          style="margin-right: 14px; margin-top: 12px"
+        >
+          <div class="flex flex-col items-center">
+            <el-icon :size="24"><Plus /></el-icon>
+            <el-text type="info" style="margin-top: 20px">上传图片</el-text>
+          </div>
+        </el-upload>
+        <div class="pos-relative default-image-item" v-for="item in imageList">
+          <el-checkbox :value="item.id" class="checkbox-item"></el-checkbox>
+          <el-image
+            style="width: 146px; height: 146px; border-radius: 6px"
+            :src="item.imgUrl"
+            fit="cover"
+          />
+
+          <div
+            class="mask-error flex items-center justify-center"
+            v-if="item.defaultUse == 1"
+          >
+            <el-text type="primary" size="small">默认图片</el-text>
+          </div>
+        </div>
+      </el-checkbox-group>
+
+      <ele-pagination
+        v-model:current-page="pageParams.pageNum"
+        v-model:page-size="pageParams.pageSize"
+        :total="total"
+        layout="total,prev, pager, next, sizes, jumper"
+        @update:currentPage="handleCurrentChange"
+        @update:pageSize="handlePageSizeChange"
+      />
     </ele-card>
   </ele-page>
 </template>
 
 <script setup>
   import { ref, reactive, nextTick } from 'vue';
+  import CommonUpload from '@/components/CommonUpload/index.vue';
   import ImageUpload from '@/components/ImageUpload/index.vue';
   import { Plus } from '@element-plus/icons-vue';
   import request from '@/utils/request';
-  import { ElMessage } from 'element-plus';
+  import { ElMessage, ElMessageBox } from 'element-plus';
 
+  const imageList = ref([]);
+  const checkList = ref([]);
+  const total = ref(0);
   const form = reactive({
-    images: [],
-    radio: 1
+    images: []
   });
-  /** 弹窗是否打开 */
-  const visible = defineModel({ type: Boolean });
-  const radio = ref(1);
-  /** 关闭弹窗 */
-  const handleCancel = () => {
-    visible.value = false;
+  const pageParams = ref({ pageNum: 1, pageSize: 50 });
+  const getImageList = (data = {}) => {
+    request
+      .get('/baseinfo/img/pagelist', {
+        params: { ...pageParams.value, ...data }
+      })
+      .then((res) => {
+        imageList.value = res.data.rows;
+        total.value = res.data.total || 0;
+        checkList.value = imageList.value
+          .filter((item) => item.defaultUse == 1)
+          .map((item) => item.id);
+      });
   };
+  getImageList();
 
-  //选择的文件
-  const checkList = ref([]);
-
-  /** 弹窗打开事件 */
-  const handleOpen = () => {
-    visible.value = true;
-    nextTick(() => {
-      checkAll.value = false;
-      checkList.value = [];
-      form.images = [];
-    });
+  //页码变化
+  const handleCurrentChange = (val) => {
+    pageParams.value.pageNum = val;
+    getImageList();
+  };
+  //每页显示多少条
+  const handlePageSizeChange = (val) => {
+    pageParams.value.pageSize = val;
+    getImageList();
   };
 
-  const uploadRef = ref(null);
-  //批量删除
-  function handleBatchDelete() {
-    let checkFiles = form.images.filter((item) =>
-      checkList.value.includes(item.uid)
-    );
-    checkFiles.forEach((file) => {
-      uploadRef.value?.handleRemove(file);
-    });
-  }
-
-  const checkAll = ref(false);
-  function handleCheckAllChange() {
-    checkList.value = checkAll.value ? form.images.map((item) => item.uid) : [];
+  //执行图片上传并新增到列表
+  function uploadFile(file) {
+    let formData = new FormData();
+    formData.append('file', file.file);
+    request
+      .post('/common/upload', formData, { showTips: false })
+      .then((res) => {
+        if (res.data.code == 200) {
+          handleAddImage(res.data);
+        }
+      });
   }
-
-  function handleSubmit() {
-    const formData = new FormData();
-    form.images.forEach((file) => {
-      formData.append('files', file.raw); //文件
-    });
-
-    request.post('/common/uploads', formData).then((res) => {
-      if (res.data.code === 200) {
-        visible.value = false;
-        ElMessage.success('上传成功');
+  //添加图片
+  const handleAddImage = (res) => {
+    request.post('/baseinfo/img/add', { imgUrl: res.url }).then((res) => {
+      if (res.data.code == 200) {
+        getImageList();
       }
     });
-  }
+  };
 
-  defineExpose({
-    handleOpen
-  });
+  //选中图片
+  const handlecheckboxChange = (value) => {
+    let last = value[value.length - 1];
+    checkList.value = [last];
+  };
+
+  const handleSetDefault = () => {
+    if (checkList.value.length == 0) {
+      ElMessage({ type: 'warning', message: '请选择图片' });
+      return;
+    }
+    ElMessageBox.confirm('是否将该图片设置默认图片?', '提示').then(() => {
+      request
+        .post('/baseinfo/img/setDefault?id=' + checkList.value[0])
+        .then((res) => {
+          if (res.data.code == 200) {
+            ElMessage({ type: 'success', message: '设置成功' });
+            getImageList();
+          }
+        });
+    });
+  };
 </script>
 
-<style lang="scss">
-  .image-item {
-    margin-right: 10px;
-    margin-bottom: 10px;
+<style lang="scss" scoped>
+  .default-image-item {
+    margin-right: 14px;
+    margin-top: 14px;
+    margin-bottom: 0;
     position: relative;
     border-radius: 6px;
     border: 1px solid #e4e7ed;
+    &:hover {
+      .checkbox-item {
+        opacity: 1;
+      }
+    }
     .checkbox-item {
       position: absolute;
       top: -8px;
       left: 0;
+      opacity: 0;
+      &.is-checked {
+        opacity: 1;
+      }
+    }
+    .mask-error {
+      width: 100%;
+      height: 26px;
+      background-color: rgba(0, 0, 0, 0.1);
+      position: absolute;
+      overflow: hidden;
+      bottom: 0;
+      left: 0;
     }
   }
 </style>

+ 68 - 0
src/views/riskControl/restrictConfig/index.vue

@@ -0,0 +1,68 @@
+<template>
+  <ele-page flex-table>
+    <ele-card flex-table header="访问限制规则">
+      <el-form
+        ref="formRef"
+        style="max-width: 520px"
+        :model="form"
+        label-width="200px"
+      >
+        <el-form-item label="每人每日最多扫描" prop="abnormalNum1" required>
+          <el-input v-model="form.abnormalNum1">
+            <template #append>次</template>
+          </el-input>
+        </el-form-item>
+        <el-form-item label="每人每月最多可卖" prop="abnormalNum2" required>
+          <el-input v-model="form.abnormalNum2">
+            <template #append>单</template>
+          </el-input>
+        </el-form-item>
+        <el-form-item label="每人每日最多可卖" prop="abnormalNum2" required>
+          <el-input v-model="form.abnormalNum2">
+            <template #append>单</template>
+          </el-input>
+        </el-form-item>
+        <el-form-item label="未提交订单每人最多可扫描" prop="abnormalNum2" required>
+          <el-input v-model="form.abnormalNum2">
+            <template #append>次</template>
+          </el-input>
+        </el-form-item>
+        <el-form-item label="单个订单删除本数" prop="abnormalNum2" required>
+          <el-input v-model="form.abnormalNum2">
+            <template #append>本</template>
+          </el-input>
+        </el-form-item>
+        <el-form-item>
+          <el-button
+            style="min-width: 100px"
+            type="primary"
+            @click="submitForm(formRef)"
+            v-permission="'recycleLogistics:abnormalSetting:update'"
+          >
+            确认修改
+          </el-button>
+        </el-form-item>
+      </el-form>
+    </ele-card>
+  </ele-page>
+</template>
+
+<script setup>
+  const formRef = ref(null);
+  const form = reactive({
+    abnormalNum1: '',
+    abnormalNum2: '',
+    mobile: ''
+  });
+
+  const submitForm = async (formEl) => {
+    if (!formEl) return;
+    await formEl.validate((valid, fields) => {
+      if (valid) {
+        console.log('submit!');
+      } else {
+        console.log('error submit!', fields);
+      }
+    });
+  };
+</script>

+ 35 - 0
src/views/riskControl/restrictLog/components/page-search.vue

@@ -0,0 +1,35 @@
+<!-- 搜索表单 -->
+<template>
+  <ele-card :body-style="{ paddingBottom: '8px' }">
+    <ProSearch
+      :items="formItems"
+      ref="searchRef"
+      @search="search"
+      :initKeys="initKeys"
+    ></ProSearch>
+  </ele-card>
+</template>
+
+<script setup>
+  import { reactive, ref, defineEmits } from 'vue';
+  import ProSearch from '@/components/CommonPage/ProSearch2.vue';
+
+  let { proxy } = getCurrentInstance();
+  const emit = defineEmits(['search']);
+
+  const formItems = reactive([
+    { type: 'input', label: '用户名', prop: 'userName' },
+    { type: 'select', label: '限制类型', prop: 'restrictType' }
+  ]);
+
+  const initKeys = reactive({
+    userName: '',
+    restrictType: ''
+  });
+
+  const searchRef = ref(null);
+  /** 搜索 */
+  const search = (data) => {
+    emit('search', { ...data });
+  };
+</script>

+ 59 - 0
src/views/riskControl/restrictLog/components/restrict-history.vue

@@ -0,0 +1,59 @@
+<!-- 编辑弹窗 -->
+<template>
+  <ele-modal
+    :width="700"
+    v-model="visible"
+    title="限制历史"
+    @open="handleOpen"
+    :body-style="{ padding: '0' }"
+  >
+    <common-table
+      ref="pageRef"
+      :pageConfig="pageConfig"
+      :columns="columns"
+      :tools="false"
+    >
+    </common-table>
+    <template #footer>
+      <el-button @click="handleCancel">关闭</el-button>
+    </template>
+  </ele-modal>
+</template>
+
+<script setup>
+  import { ref, reactive, nextTick } from 'vue';
+  import request from '@/utils/request';
+  import CommonTable from '@/components/CommonPage/CommonTable.vue';
+
+  const pageRef = ref(null);
+  /** 弹窗是否打开 */
+  const visible = defineModel({ type: Boolean });
+
+  /** 关闭弹窗 */
+  const handleCancel = () => {
+    visible.value = false;
+  };
+
+  /** 弹窗打开事件 */
+  const handleOpen = () => {
+    visible.value = true;
+  };
+
+  /** 表格列配置 */
+  const columns = ref([
+    { label: '限制时间', prop: 'createTime', align: 'center', width: 180 },
+    { label: '限制原因', prop: 'dataCount', align: 'center' },
+    { label: '操作员', prop: 'createName', align: 'center', width: 90 }
+  ]);
+
+  const pageConfig = reactive({
+    pageUrl: '/common/exportrecord/pagelist',
+    fileName: '限制历史',
+    cacheKey: 'restrict-history',
+    params: { logType: 1 }
+  });
+
+  defineExpose({
+    handleOpen
+  });
+</script>

+ 72 - 0
src/views/riskControl/restrictLog/index.vue

@@ -0,0 +1,72 @@
+<template>
+  <ele-page flex-table>
+    <page-search @search="reload"></page-search>
+
+    <common-table
+      ref="pageRef"
+      :pageConfig="pageConfig"
+      :columns="columns"
+      :tools="false"
+    >
+      <template #action="{ row }">
+        <div>
+          <el-button
+            type="danger"
+            link
+            v-permission="'riskControl:restrictLog:detail'"
+            @click="handleView(row)"
+          >
+            [查看]
+          </el-button>
+        </div>
+      </template>
+    </common-table>
+    <restrict-history ref="restrictLogRef" />
+  </ele-page>
+</template>
+
+<script setup>
+  import { ref, reactive } from 'vue';
+  import CommonTable from '@/components/CommonPage/CommonTable.vue';
+  import pageSearch from './components/page-search.vue';
+  import restrictHistory from '@/views/riskControl/restrictLog/components/restrict-history.vue'
+  import { useDictData } from '@/utils/use-dict-data';
+  import request from '@/utils/request';
+
+  defineOptions({ name: 'restrictLog' });
+
+  /** 表格列配置 */
+  const columns = ref([
+    { label: '用户UID', prop: 'uid', align: 'center' },
+    { label: '限制类型', prop: 'restrictType', align: 'center' },
+    { label: '限制时间', prop: 'createTime', align: 'center' },
+    { label: '限制ip', prop: 'ip', align: 'center' },
+    {
+      columnKey: 'action',
+      label: '操作',
+      width: 160,
+      align: 'center',
+      slot: 'action'
+    }
+  ]);
+
+  /** 页面组件实例 */
+  const pageRef = ref(null);
+
+  const pageConfig = reactive({
+    pageUrl: '/system/post/list',
+    fileName: '访问限制日志',
+    cacheKey: 'restrictLogTable'
+  });
+
+  //刷新表格
+  function reload(where) {
+    pageRef.value?.reload(where);
+  }
+
+  //编辑页面
+  const restrictLogRef = ref(null);
+  function handleView(row) {
+    restrictLogRef.value?.handleOpen(row);
+  }
+</script>