haveyou преди 1 година
родител
ревизия
2062a6c853
променени са 26 файла, в които са добавени 2143 реда и са изтрити 227 реда
  1. 69 0
      src/components/CommonPage/CitySelect.vue
  2. 2 1
      src/components/CommonPage/CommonTable.vue
  3. 7 8
      src/components/CommonPage/SimpleForm.vue
  4. 56 10
      src/components/CommonPage/SimpleFormModal.vue
  5. 22 1
      src/components/CommonPage/SimpleTable.vue
  6. 26 2
      src/styles/index.scss
  7. 44 0
      src/views/data/books/components/books-change-log.vue
  8. 131 88
      src/views/data/books/components/books-edit.vue
  9. 142 0
      src/views/data/books/components/books-import.vue
  10. 31 47
      src/views/data/books/components/books-search.vue
  11. 67 63
      src/views/data/books/index.vue
  12. 1 1
      src/views/data/universities/components/universities-edit.vue
  13. 1 6
      src/views/data/universities/index.vue
  14. 56 0
      src/views/recycleLogistics/abnormalSetting/index.vue
  15. 168 0
      src/views/recycleLogistics/abnormalSign/index.vue
  16. 58 0
      src/views/recycleLogistics/arrivalSign/components/arrivalSign-search.vue
  17. 168 0
      src/views/recycleLogistics/arrivalSign/index.vue
  18. 284 0
      src/views/recycleLogistics/components/order-page.vue
  19. 83 0
      src/views/recycleLogistics/components/order-search.vue
  20. 66 0
      src/views/recycleLogistics/expressCheck/index.vue
  21. 58 0
      src/views/recycleLogistics/transferSign/components/page-search.vue
  22. 66 0
      src/views/recycleLogistics/transferSign/index.vue
  23. 55 0
      src/views/recycleLogistics/warehouse/components/area-setting.vue
  24. 160 0
      src/views/recycleLogistics/warehouse/components/warehouse-edit.vue
  25. 58 0
      src/views/recycleLogistics/warehouse/components/warehouse-search.vue
  26. 264 0
      src/views/recycleLogistics/warehouse/index.vue

+ 69 - 0
src/components/CommonPage/CitySelect.vue

@@ -0,0 +1,69 @@
+<template>
+  <regions-select
+    v-model="city"
+    :cascaderProps="cascaderProps"
+    :placeholder="placeholder"
+    :options="[]"
+    @change="handleCityChange"
+  />
+</template>
+
+<script setup>
+  import { ref } from 'vue';
+  import RegionsSelect from '@/components/RegionsSelect/index.vue';
+  import request from '@/utils/request';
+
+  const emit = defineEmits(['update:modelValue']);
+  const props = defineProps({
+    modelValue: {
+      type: Array,
+      default() {
+        return [];
+      }
+    },
+    placeholder: {
+      type: String,
+      default: '请选择省市区'
+    },
+    number: {
+      type: Number,
+      default: 2
+    }
+  });
+  const handleCityChange = (value) => {
+    console.log(value);
+    emit('update:modelValue', value);
+  };
+
+  const city = ref([]);
+  watch(
+    () => props.modelValue,
+    (val) => {
+      city.value = val;
+    },
+    { deep: true, immediate: true }
+  );
+
+  let cascaderProps = {
+    lazy: true,
+    lazyLoad: async (node, resolve) => {
+      const { level } = node;
+      console.log(node, 'dsadsa');
+      let { data } = await getProviceList(node.data.value);
+      let nodes = data.map((item) => {
+        return {
+          value: item.id,
+          label: item.district,
+          leaf: level >= props.number
+        };
+      });
+      console.log(nodes);
+      resolve(nodes);
+    }
+  };
+
+  async function getProviceList(id = 1) {
+    let { data } = await request.get(`/baseinfo/districtInfo/findInfo/${id}`);
+    return data;
+  }
+</script>

+ 2 - 1
src/components/CommonPage/CommonTable.vue

@@ -62,7 +62,8 @@
 
   /** 表格数据源 */
   const datasource = ({ pages, where, orders }) => {
-    return queryPage({ ...where, ...orders, ...pages });
+    let initKeys = props.pageConfig.params || {};
+    return queryPage({ ...where, ...orders, ...pages, ...initKeys });
   };
 
   /** 搜索 */

+ 7 - 8
src/components/CommonPage/SimpleForm.vue

@@ -1,15 +1,16 @@
 <!-- 编辑弹窗 -->
 <template>
   <pro-form
+    class="simple-form"
     v-bind="$attrs"
     ref="proFormRef"
     :model="form"
     :items="items"
-    :grid="{ span: gird }"
+    :grid="{ span: grid }"
     @updateValue="setFieldValue"
   >
-    <template v-for="(val, key) in slotArray" v-slot:[key]="{ row }">
-      <slot :name="key" :row="row"></slot>
+    <template v-for="(val, key) in slotArray" v-slot:[key]="{ item, model, updateValue }">
+      <slot :name="key" :item="item" :model="model" :updateValue="updateValue"></slot>
     </template>
   </pro-form>
 </template>
@@ -19,6 +20,7 @@
   import { useFormData } from '@/utils/use-form-data';
   import ProForm from '@/components/ProForm/index.vue';
   const slotArray = useSlots();
+  console.log(slotArray,'daa');
 
   const proFormRef = ref(null);
   const props = defineProps({
@@ -26,7 +28,7 @@
       type: Array,
       default: () => []
     },
-    gird: {
+    grid: {
       type: Number,
       default: 24
     }
@@ -37,7 +39,7 @@
     (values) => {
       initKeys = values.map((item) => item.prop);
     },
-    { deep: true,immediate: true }
+    { deep: true, immediate: true }
   );
 
   const [form, resetFields, assignFields, setFieldValue] = useFormData();
@@ -63,14 +65,11 @@
   };
 
   function setData(data) {
-    console.log('setData', initKeys)
-
     Object.keys(data).forEach((key) => {
       if (initKeys.includes(key)) {
         setFieldValue(key, data[key]);
       }
     });
-    console.log(data, form, 'setData');
   }
 
   defineExpose({

+ 56 - 10
src/components/CommonPage/SimpleFormModal.vue

@@ -1,21 +1,35 @@
 <!-- 编辑弹窗 -->
 <template>
-  <ele-modal form :width="width" v-model="visible" :title="title">
+  <ele-modal
+    form
+    :width="width"
+    v-model="visible"
+    :title="title"
+    position="center"
+    :body-style="{ overflow: 'auto', maxHeight: '84vh' }"
+  >
     <SimpleForm
       :items="items"
       :labelWidth="labelWidth"
       ref="formRef"
       v-bind="formProps"
-    ></SimpleForm>
+      :disabled="type === 'detail'"
+    >
+    <template v-for="(val, key) in slotArray" v-slot:[key]="{ item, model, updateValue }">
+      <slot :name="key" :item="item" :model="model" :updateValue="updateValue"></slot>
+    </template>
+    </SimpleForm>
     <template #footer>
       <el-button @click="handleCancel">关闭</el-button>
-      <el-button type="primary" @click="handleSumbit">确定</el-button>
+      <el-button type="primary" @click="handleSumbit" v-if="type !== 'detail'"
+        >确定</el-button
+      >
     </template>
   </ele-modal>
 </template>
 
 <script setup>
-  import { ref, reactive, nextTick, getCurrentInstance } from 'vue';
+  import { ref, reactive, nextTick, getCurrentInstance, useSlots } from 'vue';
   import { Flag, ChatDotSquare } from '@element-plus/icons-vue';
   import orderTimeline from '@/views/recycleOrder/components/order-timeline.vue';
   import SimpleForm from '@/components/CommonPage/SimpleForm.vue';
@@ -23,13 +37,21 @@
   import { EleMessage } from 'ele-admin-plus/es';
   let { proxy } = getCurrentInstance();
 
+  const slotArray = useSlots();
+
   const props = defineProps({
     items: { type: Array, default: () => [] },
     title: { type: String, default: '编辑' },
+    type: { type: String },
     width: { type: String, default: '520px' },
     labelWidth: { type: String, default: '90px' },
     formProps: { type: Object, default: () => ({}) },
-    baseUrl: { type: Object, default: () => ({}) }
+    baseUrl: { type: Object, default: () => ({}) },
+    formatData: {
+      type: Function,
+      default: (data) => data
+    }, //格式化提交数据
+    fallbackData: { type: Function, default: (data) => data } //回填数据
   });
   const emit = defineEmits(['success']);
   /** 弹窗是否打开 */
@@ -42,25 +64,49 @@
 
   const form = ref({});
   /** 弹窗打开事件 */
-  const handleOpen = (data) => {
+  const handleOpen = async (data) => {
     visible.value = true;
     nextTick(() => {
       formRef.value?.resetForm();
-      form.value = data;
-      formRef.value?.setData(data);
+
+      if (!props.baseUrl.detail) {
+        form.value = data;
+        formRef.value?.setData(data);
+      } else {
+        getDetail(data.id).then((res) => {
+          form.value = res.data;
+          let data = props.fallbackData(res.data);
+          formRef.value?.setData(data);
+        });
+      }
+    });
+  };
+
+  //获取详情数据
+  const getDetail = (id) => {
+    if (!props.baseUrl.detail) return;
+    let url = `${props.baseUrl.detail}/${id}`;
+    return new Promise((resolve, reject) => {
+      proxy.$http.get(url).then((res) => {
+        if (res.data.code !== 200) return EleMessage.error(res.data.msg);
+        resolve(res.data || {});
+      });
     });
   };
 
   const formRef = ref();
   const handleSumbit = () => {
+    console.log(props.formatData, '格式化数据');
+
     formRef.value?.submitForm().then((data) => {
       data.id = form.value?.id;
       let url = data.id ? props.baseUrl.update : props.baseUrl.add;
-      proxy.$http.post(url, data).then((res) => {
+      let format = props.formatData(data);
+      proxy.$http.post(url, format).then((res) => {
         if (res.data.code !== 200) return;
         visible.value = false;
         emit('success', data);
-        EleMessage.success(form.id ? '编辑成功' : '新增成功');
+        EleMessage.success(format.id ? '编辑成功' : '新增成功');
       });
     });
   };

+ 22 - 1
src/components/CommonPage/SimpleTable.vue

@@ -18,6 +18,7 @@
 
 <script setup>
   import { ref, reactive, nextTick, useSlots } from 'vue';
+  import request from '@/utils/request';
   import { pagePosts } from '@/api/system/post';
 
   const slotArray = useSlots();
@@ -31,13 +32,33 @@
     rowId: {
       type: String,
       default: 'id'
+    },
+    pageUrl: {
+      type: String
+    },
+    otherParams: {
+      // 额外参数
+      type: Object,
+      default() {
+        return {};
+      }
     }
   });
 
   const tableRef = ref(null);
+
+  async function queryPage(params) {
+    if (!props.pageUrl) return Promise.reject(new Error('请配置pageUrl'));
+    const res = await request.get(props.pageUrl, { params });
+    if (res.data.code === 200) {
+      return res.data;
+    }
+    return Promise.reject(new Error(res.data.msg));
+  }
+
   /** 表格数据源 */
   const datasource = ({ pages, where, orders }) => {
-    return pagePosts({ ...where, ...orders, ...pages });
+    return queryPage({ ...where, ...orders, ...pages, ...props.otherParams });
   };
 
   /** 搜索 */

+ 26 - 2
src/styles/index.scss

@@ -124,6 +124,13 @@ body {
   }
 }
 
+//表单下编辑器样式
+.simple-form {
+  .tox-tinymce {
+    width: 100% !important;
+  }
+}
+
 //table操作按钮
 .ele-pro-table {
   .el-table .cell.el-tooltip {
@@ -141,6 +148,22 @@ body {
   .el-text {
     align-self: flex-start;
   }
+  //toolbar的状态选择
+  .ele-toolbar-body {
+    .el-radio-group {
+      position: relative;
+      top: -3px;
+    }
+
+    .el-radio-button {
+      min-width: 120px;
+    }
+
+    .el-radio-button__inner {
+      width: 100%;
+      padding: 9px 15px;
+    }
+  }
 }
 
 //搜索况下时间选择框的样式
@@ -151,12 +174,13 @@ body {
 
     .el-form-item__label {
       position: absolute;
-      top: -16px;
+      top: -10px;
       right: 12px;
       font-size: 14px;
       display: inline-block;
       z-index: 2;
-      height: 22px;
+      height: 18px;
+      line-height: 18px;
       padding: 0;
       width: fit-content !important;
       background-color: #ffffff;

+ 44 - 0
src/views/data/books/components/books-change-log.vue

@@ -0,0 +1,44 @@
+<!-- 编辑弹窗 -->
+<template>
+  <ele-modal form :width="800" v-model="visible" title="变动记录">
+    <SimpleTable style="width: 100%" :columns="columns" pageUrl="/book/bookInfoLog/list" :otherParams="otherParams"></SimpleTable>
+
+    <template #footer>
+      <el-button @click="handleCancel">关闭</el-button>
+    </template>
+  </ele-modal>
+</template>
+
+<script setup>
+  import { ref, reactive, nextTick } from 'vue';
+  import { Flag, ChatDotSquare } from '@element-plus/icons-vue';
+  import SimpleTable from '@/components/CommonPage/SimpleTable.vue';
+
+  /** 弹窗是否打开 */
+  const visible = defineModel({ type: Boolean });
+
+  /** 关闭弹窗 */
+  const handleCancel = () => {
+    visible.value = false;
+  };
+
+  /** 弹窗打开事件 */
+  const otherParams = ref({ });
+  const handleOpen = (row) => {
+    visible.value = true;
+    otherParams.value.id = row.id;
+    nextTick(() => console.log('打开'));
+  };
+
+  // 表格数据
+  const columns = reactive([
+    { label: '变动属性', prop: 'createBy', width: 100 },
+    { label: '变动值', prop: 'logDescription' },
+    { label: '操作员', prop: 'logDescription' },
+    { label: '变动时间', prop: 'createTime', width: 180 }
+  ]);
+
+  defineExpose({
+    handleOpen
+  });
+</script>

+ 131 - 88
src/views/data/books/components/books-edit.vue

@@ -1,124 +1,167 @@
 <!-- 搜索表单 -->
 <template>
-  <ele-card :body-style="{ paddingBottom: '8px' }">
-    <ProSearch :columns="columns" v-model:form="form" ref="searchRef">
-      <el-col :span="6" style="min-width: 160px">
-        <el-button style="width: 80px" type="primary" plain @click="search"
-          >查询</el-button
-        >
-        <el-button style="width: 80px" type="info" @click="reset"
-          >重置</el-button
-        >
-      </el-col>
-    </ProSearch>
-  </ele-card>
+  <simple-form-modal
+    :title="title"
+    :items="formItems"
+    ref="editRef"
+    :baseUrl="baseUrl"
+    width="1080px"
+    :formProps="{ grid: 8 }"
+    :formatData="formatData"
+    @success="(data) => emit('success', data)"
+  ></simple-form-modal>
 </template>
 
 <script setup>
-  import { reactive, ref, defineEmits } from 'vue';
+  import { reactive, ref, defineEmits, getCurrentInstance } from 'vue';
   import { useFormData } from '@/utils/use-form-data';
-  import ProSearch from '@/components/CommonPage/ProSearch.vue';
+  import SimpleFormModal from '@/components/CommonPage/SimpleFormModal.vue';
+  const { proxy } = getCurrentInstance();
 
-  const emit = defineEmits(['search']);
-  const columns = reactive([
-    { tag: 'el-input', label: '发件人名称', prop: 'senderName', span: 4 },
-    { tag: 'el-input', label: '发件人电话', prop: 'senderPhone', span: 4 },
+  //获取省市
+  const provinceList = ref([]);
+  const cityList = ref([]);
+  const title = ref('新增图书');
+  const emit = defineEmits(['success']);
+
+  const formItems = reactive([
+    { type: 'input', label: '书名', prop: 'bookName', required: true },
+    { type: 'input', label: 'ISBN', prop: 'isbn', required: true },
+    { type: 'imageUpload', label: '封面', prop: 'cover', props: { limit: 1 } },
+    { type: 'input', label: '作者', prop: 'author', required: true },
+    { type: 'input', label: '定价', prop: 'price', required: true },
+    { type: 'input', label: '出版社', prop: 'publish', required: true },
     {
-      tag: 'el-input',
-      label: '发件人地址(仅查询7天内数据)',
-      prop: 'senderAddress',
-      span: 5
+      type: 'date',
+      label: '出版时间',
+      prop: 'pubDate',
+      required: true,
+      props: {
+        format: 'YYYY-MM-DD',
+        valueFormat: 'YYYY-MM-DD'
+      }
     },
-    { tag: 'el-input', label: '用户名', prop: 'userName', span: 4 },
     {
-      tag: 'el-input',
-      label: '搜索备注关键字',
-      prop: 'searchRemarks',
-      span: 4
+      type: 'dictSelect',
+      label: '分类标签',
+      prop: 'bookTag',
+      props: { code: 'book_tag' }
     },
+    { type: 'input', label: '中图分类号', prop: 'chineCateNo' },
+    { type: 'input', label: '丛书提名', prop: 'bookPut' },
+    { type: 'input', label: '其他提名', prop: 'otherPut' },
+    { type: 'input', label: '主题', prop: 'bookTopic' },
+    { type: 'input', label: '语种', prop: 'lang' },
+    { type: 'input', label: '装帧', prop: 'bookFormat' },
+    { type: 'input', label: '开本尺寸', prop: 'bookFormat' },
     {
-      tag: 'el-select',
-      label: '收货仓库',
-      prop: 'receivingWarehouse',
-      span: 3
+      type: 'dictSelect',
+      label: '是否套装',
+      prop: 'suit',
+      props: { code: 'is_common_yes' }
     },
-    { tag: 'el-select', label: '全部订单', prop: 'allOrders', span: 3 },
+    { type: 'input', label: '版本', prop: 'bookVersion' },
+    { type: 'input', label: '总页数', prop: 'pageTotal' },
     {
-      tag: 'el-select',
-      label: '物流公司',
-      prop: 'logisticsCompany',
-      span: 3
+      type: 'dictSelect',
+      label: '人工核实',
+      prop: 'perCheck',
+      props: { code: 'is_common_yes' }
     },
-    { tag: 'el-select', label: '全部方式', prop: 'allMethods', span: 3 },
+    { type: 'input', label: '出版地', prop: 'putAddress' },
+    { type: 'input', label: '读者对象', prop: 'readers' },
+    { type: 'input', label: '印刷厂', prop: 'printHouse' },
     {
-      tag: 'el-date-picker',
-      label: '建单时间(开始时间)',
-      prop: 'orderStartTime',
-      span: 3,
-      tagAttrs: {
+      type: 'date',
+      label: '印刷时间',
+      prop: 'printTime',
+      props: {
+        format: 'YYYY-MM-DD',
         valueFormat: 'YYYY-MM-DD'
       }
     },
+    { type: 'input', label: '印刷数量', prop: 'printNum' },
     {
-      tag: 'el-date-picker',
-      label: '建单时间(结束时间)',
-      prop: 'orderEndTime',
-      span: 4,
-      tagAttrs: {
-        valueFormat: 'YYYY-MM-DD'
-      }
+      type: 'textarea',
+      label: '图书短评',
+      prop: 'shortReview',
+      colProps: { span: 24 }
     },
     {
-      tag: 'el-date-picker',
-      label: '建单时间(开始时间)',
-      prop: 'orderStartTime',
-      span: 4,
-      tagAttrs: {
-        valueFormat: 'YYYY-MM-DD'
-      }
+      type: 'editor',
+      label: '书嗨推荐',
+      prop: 'shuhiRecommend',
+      colProps: { span: 24 }
     },
     {
-      tag: 'el-date-picker',
-      label: '建单时间(结束时间)',
-      prop: 'orderEndTime',
-      span: 4,
-      tagAttrs: {
-        valueFormat: 'YYYY-MM-DD'
-      }
+      type: 'editor',
+      label: '书嗨摘要',
+      prop: 'shuhiBlurb',
+      colProps: { span: 24 }
     },
     {
-      tag: 'el-input',
-      label: '订单编号(多个以中文逗号、空格或换行分割)',
-      prop: 'orderNumber',
-      span: 6
+      type: 'editor',
+      label: '内容简介',
+      prop: 'contentBlurb',
+      colProps: { span: 24 }
     },
     {
-      tag: 'el-input',
-      label: '物流单号(多个以中文逗号、空格或换行分割)',
-      prop: 'logisticsNumber',
-      span: 6
+      type: 'editor',
+      label: '作者简介',
+      prop: 'anthorBlurb',
+      colProps: { span: 24 }
+    },
+    {
+      type: 'editor',
+      label: '图书目录',
+      prop: 'catalog',
+      colProps: { span: 24 }
     }
   ]);
+  //默认值
+  const baseUrl = reactive({
+    add: '/book/bookInfo/save',
+    update: '/book/bookInfo/update',
+    detail: '/book/bookInfo/getDetail'
+  });
+  const formData = ref({ perCheck: '1', suit: '0', bookTag: '1' });
+  const editRef = ref(null);
 
-  const initKeys = {};
-  for (let i = 0; i < columns.length; i++) {
-    initKeys[columns[i].prop] = '';
+  function handleOpen(data = {}) {
+    title.value = data && data.id ? '编辑图书' : '新增图书';
+    formData.value = Object.assign(formData.value, data || {});
+    editRef.value?.handleOpen(formData.value);
   }
 
-  /** 表单数据 */
-  const [form, resetFields] = useFormData({
-    ...initKeys
-  });
+  //格式化数据
+  function formatData(data) {
+    let values = JSON.parse(JSON.stringify(data));
+    let bookBlurb = {
+      bookId: data.id,
+      isbn: data.isbn,
+      shuhiRecommend: data.shuhiRecommend,
+      shuhiBlurb: data.shuhiBlurb,
+      contentBlurb: data.contentBlurb,
+      anthorBlurb: data.anthorBlurb,
+      catalog: data.catalog
+    };
+    values.bookBlurb = bookBlurb;
+    return values;
+  }
+
+  //回填数据
+  function fallbackData(data) {
+    let values = JSON.parse(JSON.stringify(data));
+    if (data.bookBlurb) {
+      values.shuhiRecommend = data.bookBlurb.shuhiRecommend;
+      values.shuhiBlurb = data.bookBlurb.shuhiBlurb;
+      values.contentBlurb = data.bookBlurb.contentBlurb;
+      values.anthorBlurb = data.bookBlurb.anthorBlurb;
+      values.catalog = data.bookBlurb.catalog;
+    }
 
-  const searchRef = ref(null);
-  /** 搜索 */
-  const search = () => {
-    emit('search', { ...form });
-  };
+    return values;
+  }
 
-  /** 重置 */
-  const reset = () => {
-    resetFields();
-    search();
-  };
+  defineExpose({ handleOpen });
 </script>

+ 142 - 0
src/views/data/books/components/books-import.vue

@@ -0,0 +1,142 @@
+<template>
+  <ele-modal
+    :width="460"
+    title="图书导入"
+    :body-style="{ paddingTop: '8px' }"
+    v-model="visible"
+  >
+    <div class="flex mb-2">
+      <div class="text-sm">请先下载'图书基础数据导入模板:</div>
+      <el-button link type="primary" @click="handleDownload"
+        >下载商品信息导入模板</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.导入文件属性值不存在或者超长,会自动过滤掉</div>
+      <div class="text-sm">3.导入文件第一行需与模版完全一致</div>
+      <div class="text-sm">4.相同编码覆盖导入</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 { downloadTemplate } from '@/api/system/user';
+  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/import', formData);
+    if (res.data.code === 200) {
+      return res.data.msg;
+    }
+    return Promise.reject(new Error(res.data.msg));
+  }
+
+  /** 提交 */
+  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
+        });
+
+        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;
+  };
+
+  /** 下载模板 */
+  const handleDownload = () => {
+    const loading = EleMessage.loading({
+      message: '请求中..',
+      plain: true
+    });
+    downloadTemplate()
+      .then(() => {
+        loading.close();
+      })
+      .catch((e) => {
+        loading.close();
+        EleMessage.error(e.message);
+      });
+  };
+</script>

+ 31 - 47
src/views/data/books/components/books-search.vue

@@ -1,69 +1,53 @@
 <!-- 搜索表单 -->
 <template>
   <ele-card :body-style="{ paddingBottom: '8px' }">
-    <ProSearch :columns="columns" v-model:form="form" ref="searchRef">
-      <el-col :span="6" style="min-width: 160px">
-        <el-button style="width: 80px" type="primary" plain @click="search"
-          >查询</el-button
-        >
-        <el-button style="width: 80px" type="info" @click="reset"
-          >重置</el-button
-        >
-      </el-col>
-    </ProSearch>
+    <ProSearch
+      :items="formItems"
+      ref="searchRef"
+      @search="search"
+      :initKeys="initKeys"
+    ></ProSearch>
   </ele-card>
 </template>
 
 <script setup>
   import { reactive, ref, defineEmits } from 'vue';
-  import { useFormData } from '@/utils/use-form-data';
-  import ProSearch from '@/components/CommonPage/ProSearch.vue';
+  import ProSearch from '@/components/CommonPage/ProSearch2.vue';
 
+  let { proxy } = getCurrentInstance();
   const emit = defineEmits(['search']);
-  const columns = reactive([
-    { tag: 'el-input', label: '书名', prop: 'bookName', span: 4 },
-    { tag: 'el-input', label: '条码', prop: 'code', span: 4 },
-    {
-      tag: 'el-input',
-      label: '作者',
-      prop: 'senderAddress',
-      span: 4
-    },
-    { tag: 'el-input', label: '出版社', prop: 'userName', span: 4 },
+
+  const formItems = reactive([
+    { type: 'input', label: '书名', prop: 'bookName' },
+    { type: 'input', label: '条码', prop: 'isbn' },
+    { type: 'input', label: '作者', prop: 'author' },
+    { type: 'input', label: '出版社', prop: 'publish' },
     {
-      tag: 'el-select',
+        type: 'dictSelect',
       label: '人工核实',
-      prop: 'receivingWarehouse',
-      span: 3
+      prop: 'perCheck',
+      props: { code: 'is_common_yes' }
     },
-    { tag: 'el-select', label: '是否套装', prop: 'allOrders', span: 3 },
     {
-      tag: 'el-select',
-      label: '商品标记',
-      prop: 'logisticsCompany',
-      span: 3
-    }
+      type: 'dictSelect',
+      label: '是否套装',
+      prop: 'suit',
+      props: { code: 'is_common_yes' }
+    },
   ]);
 
-  const initKeys = {};
-  for (let i = 0; i < columns.length; i++) {
-    initKeys[columns[i].prop] = '';
-  }
-
-  /** 表单数据 */
-  const [form, resetFields] = useFormData({
-    ...initKeys
+  const initKeys = reactive({
+    schoolName: '',
+    provinceId: '',
+    cityId: '',
+    schoolLevel: '',
+    departmentName: '',
+    shcoolTag: ''
   });
 
   const searchRef = ref(null);
   /** 搜索 */
-  const search = () => {
-    emit('search', { ...form });
-  };
-
-  /** 重置 */
-  const reset = () => {
-    resetFields();
-    search();
+  const search = (data) => {
+    emit('search', { ...data });
   };
 </script>

+ 67 - 63
src/views/data/books/index.vue

@@ -1,7 +1,7 @@
 <template>
   <ele-page flex-table>
     <books-search @search="reload"></books-search>
-    
+
     <common-table ref="pageRef" :pageConfig="pageConfig" :columns="columns">
       <template #toolbar>
         <el-button
@@ -9,7 +9,7 @@
           plain
           :icon="PlusOutlined"
           v-permission="'data:books:add'"
-          @click="handleBatchDelete"
+          @click="handleUpdate()"
         >
           新增图书
         </el-button>
@@ -18,7 +18,7 @@
           plain
           :icon="DeleteOutlined"
           v-permission="'data:books:batchDelete'"
-          @click="handleBatchDelete"
+          @click="handleBatchDelete()"
         >
           批量删除
         </el-button>
@@ -26,7 +26,8 @@
           type="primary"
           plain
           v-permission="'data:books:import'"
-          @click="handleBatchDelete"
+          @click="handleImportExcel"
+          :icon="UploadOutlined"
         >
           导入EXCEL
         </el-button>
@@ -34,37 +35,37 @@
           type="success"
           plain
           v-permission="'data:books:export'"
-          @click="handleBatchDelete"
+          @click="handleExportExcel"
           :icon="DownloadOutlined"
         >
           导出图书明细
         </el-button>
       </template>
-      <template #picture="{ row }">
+      <template #cover="{ row }">
         <el-image
           style="width: 80px; height: 100px"
           fit="cover"
-          src="https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg"
+          :src="row.cover"
         />
       </template>
 
       <template #baseInfo="{ row }">
         <div class="base-info flex flex-col items-start">
           <div class="common-text">
-            <el-text>书     名:</el-text>
-            <el-text type="success">行政法与行政诉讼法(第六版)</el-text>
+            <el-text>书 名:</el-text>
+            <el-text type="success">{{ row.bookName || '-' }}</el-text>
           </div>
           <div class="common-text">
-            <el-text>条    码:</el-text>
-            <el-text>9787301257975</el-text>
+            <el-text>条 码:</el-text>
+            <el-text>{{ row.isbn || '-' }}</el-text>
           </div>
           <div class="common-text">
-            <el-text>作    者:</el-text>
-            <el-text>姜明安</el-text>
+            <el-text>作 者:</el-text>
+            <el-text>{{ row.author || '-' }} </el-text>
           </div>
           <div class="common-text">
             <el-text>出版社:</el-text>
-            <el-text>北京大学出版社</el-text>
+            <el-text>{{ row.publish || '-' }}</el-text>
           </div>
         </div>
       </template>
@@ -75,7 +76,7 @@
             type="primary"
             link
             v-permission="'data:books:changeLogs'"
-            @click="handleApplyForOrderRestore(row)"
+            @click="handleChangeLogs(row)"
           >
             变动记录
           </el-button>
@@ -83,30 +84,34 @@
             type="primary"
             link
             v-permission="'data:books:update'"
-            @click="toOrderDetail(row)"
+            @click="handleUpdate(row)"
           >
             编辑
           </el-button>
         </div>
       </template>
     </common-table>
+    <books-edit ref="editRef" @success="reload()"></books-edit>
+    <books-import ref="importRef" v-model="showImport"></books-import>
+    <books-change-log ref="changeLogRef"></books-change-log>
   </ele-page>
 </template>
 
 <script setup>
   import { ref, reactive } from 'vue';
-  import { ElMessageBox } from 'element-plus/es';
-  import { EleMessage } from 'ele-admin-plus/es';
   import {
     PlusOutlined,
     DeleteOutlined,
-    DownloadOutlined
+    DownloadOutlined,
+    UploadOutlined
   } from '@/components/icons';
+  import { dayjs } from 'element-plus';
   import CommonTable from '@/components/CommonPage/CommonTable.vue';
   import booksEdit from '@/views/data/books/components/books-edit.vue';
   import booksSearch from '@/views/data/books/components/books-search.vue';
+  import booksImport from '@/views/data/books/components/books-import.vue'
+  import booksChangeLog from '@/views/data/books/components/books-change-log.vue'
   import { useDictData } from '@/utils/use-dict-data';
-  import { useRouter } from 'vue-router';
 
   defineOptions({ name: 'recycleOrderCancelled' });
 
@@ -121,8 +126,8 @@
     },
     {
       label: '图片',
-      prop: 'picture',
-      slot: 'picture',
+      prop: 'cover',
+      slot: 'cover',
       align: 'center',
       minWidth: 100
     },
@@ -136,10 +141,16 @@
     {
       label: '定价',
       prop: 'price',
-      formatter: (row) => '¥30',
+      formatter: (row) => (row.price ? '¥ ' + row.price : '-'),
       align: 'center'
     },
-    { label: '出版时间', prop: 'createTime', align: 'center', minWidth: 120 },
+    {
+      label: '出版时间',
+      prop: 'pubDate',
+      align: 'center',
+      minWidth: 120,
+      formatter: (row) => dayjs(row.pubDate).format('YYYY-MM-DD')
+    },
     {
       label: '分类标签',
       prop: 'tag',
@@ -161,63 +172,56 @@
     }
   ]);
 
-  let router = useRouter();
   /** 页面组件实例 */
   const pageRef = ref(null);
 
   const pageConfig = reactive({
-    pageUrl: '',
-    exportUrl: '',
+    pageUrl: '/book/bookInfo/list',
+    exportUrl: '/book/bookInfo/export',
     fileName: '图书基础数据',
     cacheKey: 'books-base-data'
   });
-  
+
+  //导出excel
+  function handleExportExcel() {
+    pageRef.value?.exportData('图书基础数据');
+  }
+
+  //导入
+  const showImport = ref(false);
+  function handleImportExcel() {
+    showImport.value = true;
+  }
+
   //刷新表格
-  function reload() {
-    pageRef.value?.reload();
+  function reload(where) {
+    pageRef.value?.reload(where);
   }
 
   //批量删除
-  function handleBatchDelete() {
+  function handleBatchDelete(row) {
+    let selections = row ? [row] : pageRef.value?.getSelections();
+    let ids = selections.map((item) => item.id).join(',');
+    console.log(ids,selections)
+    let url = `/book/bookInfo/removeById/${ids}`;
     pageRef.value?.operatBatch({
       title: '确认删除?',
-      url: '/recycleOrder/batchAudit'
+      method: 'post',
+      url,
+      row
     });
   }
 
-  //订单详情
-  function toOrderDetail(row) {
-    router.push({ path: '/recycleOrder/detail', query: { id: row.postId } });
-  }
-
-  //订单日志
-  const orderLogRef = ref(null);
-  function openOrderLog(row) {
-    orderLogRef.value?.handleOpen(row);
+  //新增编辑图书
+  const editRef = ref(null);
+  function handleUpdate(row) {
+    editRef.value?.handleOpen(row);
   }
 
-  //用户绑定标签
-  const userTagRef = ref(null);
-  function openEditUserTag(row) {
-    userTagRef.value?.handleOpen(row);
+  //变动记录
+  const changeLogRef = ref(null);
+  function handleChangeLogs(row) {
+    changeLogRef.value?.handleOpen(row);
   }
 
-  function messageBoxConfirm({ message, url, row }) {
-    ElMessageBox.confirm(message, '提示', {
-      confirmButtonText: '确定',
-      cancelButtonText: '关闭',
-      type: 'warning'
-    }).then(() => {
-      console.log(row, 'row');
-    });
-  }
-
-  //申请恢复订单
-  function handleApplyForOrderRestore(row) {
-    messageBoxConfirm({
-      message: '确认申请恢复订单?',
-      url: `/recycleOrder/applyForOrderRestore/${row.postId}`,
-      row
-    });
-  }
 </script>

+ 1 - 1
src/views/data/universities/components/universities-edit.vue

@@ -95,7 +95,7 @@
         cityList.value = res.data.data;
       });
     }
-    editRef.value?.handleOpen(data);
+    editRef.value?.handleOpen(formData.value);
   }
 
   function getProviceList(id = 1) {

+ 1 - 6
src/views/data/universities/index.vue

@@ -94,7 +94,7 @@
       </template>
     </common-table>
 
-    <universities-edit ref="editRef" @success="reload"></universities-edit>
+    <universities-edit ref="editRef" @success="reload()"></universities-edit>
   </ele-page>
 </template>
 
@@ -120,11 +120,6 @@
     'school_tag'
   ]);
 
-  console.log(
-    schoolLevelDicts.value,
-    schoolTagDicts,
-    'schoolLevelDicts, schoolTagDicts'
-  );
   /** 表格列配置 */
   const columns = ref([
     {

+ 56 - 0
src/views/recycleLogistics/abnormalSetting/index.vue

@@ -0,0 +1,56 @@
+<template>
+  <ele-page flex-table>
+    <ele-card flex-table header="异常签收设置">
+      <el-form
+        ref="formRef"
+        style="max-width: 520px"
+        :model="form"
+        label-width="160px"
+      >
+        <el-form-item label="每人每日异常签收限量" prop="abnormalNum1">
+          <el-input v-model="form.abnormalNum1">
+            <template #append>次</template>
+          </el-input>
+        </el-form-item>
+        <el-form-item label="每日异常签收限量" prop="abnormalNum2">
+          <el-input v-model="form.abnormalNum2">
+            <template #append>次</template>
+          </el-input>
+        </el-form-item>
+        <el-form-item label="预留手机号" prop="mobile">
+          <el-input v-model="form.mobile" />
+        </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>

+ 168 - 0
src/views/recycleLogistics/abnormalSign/index.vue

@@ -0,0 +1,168 @@
+<template>
+  <order-page ref="pageRef" :pageConfig="pageConfig">
+    <template #toolbar>
+      <el-radio-group @change="handleStatusChange" v-model="useStatus">
+        <el-radio-button label="待处理订单" value="1" />
+        <el-radio-button label="历史异常签收" value="2" />
+      </el-radio-group>
+    </template>
+
+    <template #action="{ row }">
+      <div>
+        <el-button
+          type="primary"
+          link
+          v-permission="'recycleLogistics:abnormalSign:agree'"
+          @click="toOrderDetail(row)"
+        >
+          [同意]
+        </el-button>
+        <el-button
+          type="danger"
+          link
+          v-permission="'recycleLogistics:abnormalSign:reject'"
+          @click="toOrderDetail(row)"
+        >
+          [驳回]
+        </el-button>
+        <el-button
+          type="success"
+          link
+          v-permission="'recycleLogistics:abnormalSign:detail'"
+          @click="toOrderDetail(row)"
+        >
+          [订单详情]
+        </el-button>
+        <el-button
+          type="warning"
+          link
+          v-permission="'recycleLogistics:abnormalSign:log'"
+          @click="openOrderLog(row)"
+        >
+          [订单日志]
+        </el-button>
+        <el-button
+          type="danger"
+          link
+          v-permission="'recycleLogistics:abnormalSign:cancel'"
+          @click="cancelOrder(row)"
+        >
+          [取消订单]
+        </el-button>
+        <el-button
+          type="primary"
+          link
+          v-permission="'recycleLogistics:abnormalSign:fallback'"
+          @click="fallbackOrder(row)"
+        >
+          [回退状态]
+        </el-button>
+        <el-button
+          type="success"
+          link
+          v-permission="'recycleLogistics:abnormalSign:receive'"
+          @click="handleReceive(row)"
+        >
+          [物流签收]
+        </el-button>
+        <el-button
+          type="warning"
+          link
+          v-permission="'recycleLogistics:abnormalSign:userTag'"
+          @click="openEditUserTag(row)"
+        >
+          [用户标签]
+        </el-button>
+        <el-button
+          type="danger"
+          link
+          v-permission="'recycleLogistics:abnormalSign:interception'"
+          @click="applyForInterception(row)"
+        >
+          [申请拦截退回]
+        </el-button>
+      </div>
+    </template>
+
+    <order-log ref="orderLogRef" />
+    <userBindTag ref="userTagRef" />
+  </order-page>
+</template>
+
+<script setup>
+  import { ref, reactive } from 'vue';
+  import { ElMessageBox } from 'element-plus/es';
+  import { EleMessage } from 'ele-admin-plus/es';
+  import { DownloadOutlined } from '@/components/icons';
+  import OrderPage from '@/views/recycleLogistics/components/order-page.vue';
+  import { useDictData } from '@/utils/use-dict-data';
+  import { useRouter } from 'vue-router';
+
+  //订单日志
+  import orderLog from '@/views/recycleOrder/components/order-log.vue';
+  //用户标签
+  import userBindTag from '@/views/recycleOrder/components/user-bind-tag.vue';
+
+  defineOptions({ name: 'abnormalSign' });
+
+  let router = useRouter();
+  /** 页面组件实例 */
+  const pageRef = ref(null);
+
+  const useStatus = ref('1');
+  function handleStatusChange() {
+    pageRef.value.reload({ useStatus: useStatus.value });
+  }
+
+  const pageConfig = reactive({
+    pageUrl: '',
+    exportUrl: '',
+    fileName: '路由异常签收',
+    cacheKey: 'abnormalSignTable',
+    params: { status: '1' }
+  });
+
+  //订单详情
+  function toOrderDetail(row) {
+    router.push({ path: '/recycleOrder/detail', query: { id: row.postId } });
+  }
+
+  //订单日志
+  const orderLogRef = ref(null);
+  function openOrderLog(row) {
+    orderLogRef.value?.handleOpen(row);
+  }
+
+  //用户绑定标签
+  const userTagRef = ref(null);
+  function openEditUserTag(row) {
+    userTagRef.value?.handleOpen(row);
+  }
+
+  function messageBoxConfirm({ message, url, row }) {
+    ElMessageBox.confirm(message, '提示', {
+      confirmButtonText: '确定',
+      cancelButtonText: '关闭',
+      type: 'warning'
+    }).then(() => {
+      console.log(row, 'row');
+    });
+  }
+
+  //取消订单
+  function cancelOrder(row) {
+    messageBoxConfirm({ message: '确认取消?', url: '', row });
+  }
+  //回退状态
+  function fallbackOrder(row) {
+    messageBoxConfirm({ message: '确认回退状态?', url: '', row });
+  }
+  //物流签收
+  function handleReceive(row) {
+    messageBoxConfirm({ message: '确认签收?', url: '', row });
+  }
+  //申请拦截退回
+  function applyForInterception(row) {
+    messageBoxConfirm({ message: '确认申请拦截退回?', url: '', row });
+  }
+</script>

+ 58 - 0
src/views/recycleLogistics/arrivalSign/components/arrivalSign-search.vue

@@ -0,0 +1,58 @@
+<!-- 搜索表单 -->
+<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: 'contactsName' },
+    { type: 'input', label: '物流单号', prop: 'phoneNum' },
+    { type: 'input', label: '物流CODE', prop: 'code' },
+    {
+      type: 'date',
+      label: '签收时间(开始事件)',
+      prop: 'beginTime',
+      props: {
+        format: 'YYYY-MM-DD',
+        valueFormat: 'YYYY-MM-DD'
+      }
+    },
+    {
+      type: 'date',
+      label: '签收时间(结束事件)',
+      prop: 'endTime',
+      props: {
+        format: 'YYYY-MM-DD',
+        valueFormat: 'YYYY-MM-DD'
+      }
+    }
+  ]);
+
+  const initKeys = reactive({
+    schoolName: '',
+    provinceId: '',
+    cityId: '',
+    schoolLevel: '',
+    departmentName: '',
+    shcoolTag: ''
+  });
+
+  const searchRef = ref(null);
+  /** 搜索 */
+  const search = (data) => {
+    emit('search', { ...data });
+  };
+</script>

+ 168 - 0
src/views/recycleLogistics/arrivalSign/index.vue

@@ -0,0 +1,168 @@
+<template>
+  <ele-page flex-table>
+    <arrivalSign-search @search="reload"></arrivalSign-search>
+
+    <common-table ref="pageRef" :pageConfig="pageConfig" :columns="columns">
+      <template #toolbar>
+        <el-radio-group @change="handleStatusChange" v-model="useStatus">
+          <el-radio-button label="全部到仓签收" value="1" />
+          <el-radio-button label="重量问题包裹" value="2" />
+        </el-radio-group>
+
+        <el-button
+          type="success"
+          plain
+          v-permission="'recycleLogistics:arrivalSign:export'"
+          @click="handleExportExcel"
+          :icon="DownloadOutlined"
+          style="margin-left: 40px"
+        >
+          导出EXCEL
+        </el-button>
+      </template>
+
+      <template #action="{ row }">
+        <div>
+          <el-button
+            type="success"
+            link
+            v-permission="'recycleLogistics:arrivalSign:viewImage'"
+            @click="handleUpdate(row)"
+          >
+            [查看图片]
+          </el-button>
+        </div>
+      </template>
+    </common-table>
+  </ele-page>
+</template>
+
+<script setup>
+  import { ref, reactive } from 'vue';
+  import { ElMessageBox } from 'element-plus/es';
+  import { EleMessage } from 'ele-admin-plus/es';
+  import {
+    PlusOutlined,
+    DeleteOutlined,
+    DownloadOutlined
+  } from '@/components/icons';
+  import CommonTable from '@/components/CommonPage/CommonTable.vue';
+  import arrivalSignSearch from '@/views/recycleLogistics/arrivalSign/components/arrivalSign-search.vue';
+  import { useDictData } from '@/utils/use-dict-data';
+  import { useRouter } from 'vue-router';
+  import request from '@/utils/request';
+
+  defineOptions({ name: 'arrivalSign' });
+  const [useStatusDicts] = useDictData(['use_status']);
+
+  const useStatus = ref('1');
+  function handleStatusChange() {
+    pageRef.value.reload({ useStatus: useStatus.value });
+  }
+
+  /** 表格列配置 */
+  const columns = ref([
+    {
+      type: 'selection',
+      columnKey: 'selection',
+      width: 50,
+      align: 'center',
+      fixed: 'left'
+    },
+    {
+      label: '签收者',
+      prop: 'godownName',
+      align: 'center',
+    },
+    { label: '物流单号', prop: 'contactsName', align: 'center' },
+    { label: '物流CODE', prop: 'phoneNum', align: 'center'},
+    { label: '包裹号', prop: 'addressDetail', align: 'center' },
+    { label: '重量', prop: 'addressDetail', align: 'center' },
+    { label: '重量单位', prop: 'addressDetail', align: 'center' },
+    { label: '仓库', prop: 'addressDetail', align: 'center' },
+    { label: '签收时间', prop: 'createTime', align: 'center',width: 170 },
+    {
+      columnKey: 'action',
+      label: '操作',
+      width: 160,
+      align: 'center',
+      slot: 'action'
+    }
+  ]);
+
+  let router = useRouter();
+  /** 页面组件实例 */
+  const pageRef = ref(null);
+
+  const pageConfig = reactive({
+    pageUrl: '/system/info/pagelist',
+    exportUrl: '/system/info/export',
+    fileName: '仓库管理',
+    cacheKey: 'arrivalSignTable',
+    params: { useStatus: 1 }
+  });
+
+  //刷新表格
+  function reload(where) {
+    pageRef.value?.reload(where);
+  }
+
+  //修改状态
+  const statusSwitchChange = (value, row, key) => {
+    let data = JSON.parse(JSON.stringify(row));
+    let message = value == 0 ? '确认关闭?' : '确认开启?';
+    ElMessageBox.confirm(message, '提示', {
+      confirmButtonText: '确定',
+      cancelButtonText: '关闭',
+      type: 'warning'
+    })
+      .then(() => {
+        data[key] = value;
+        request.post('/system/info/update', data).then((res) => {
+          if (res.data.code === 200) {
+            EleMessage.success('操作成功');
+            reload();
+          } else {
+            EleMessage.error(res.data.msg);
+          }
+        });
+      })
+      .catch(() => {
+        row[key] = value == 1 ? 0 : 1;
+      });
+  };
+
+  //批量删除
+  function handleBatchDelete(row) {
+    let selections = row ? [row] : pageRef.value?.getSelections();
+    let ids = selections.map((item) => item.id).join(',');
+    let url = `/system/info/delete/${ids}`;
+    pageRef.value?.operatBatch({
+      title: '确认删除?',
+      method: 'delete',
+      url,
+      row
+    });
+  }
+  //导出excel
+  function handleExportExcel() {
+    pageRef.value?.exportData('到仓签收');
+  }
+
+  //禁用/启用
+  function handleChangeStatus(row) {
+    let message = row.useStatus == 1 ? '确认禁用?' : '确认启用?';
+    let data = JSON.parse(JSON.stringify(row));
+    data.useStatus = row.useStatus == 1 ? 0 : 1;
+    pageRef.value?.messageBoxConfirm({
+      message,
+      fetch: () => request.post('/system/info/update', data)
+    });
+  }
+
+  //编辑页面
+  const editRef = ref(null);
+  function handleUpdate(row) {
+    editRef.value?.handleOpen(row);
+  }
+</script>

+ 284 - 0
src/views/recycleLogistics/components/order-page.vue

@@ -0,0 +1,284 @@
+<template>
+  <ele-page flex-table>
+    <!-- 搜索表单 -->
+    <order-search @search="reload" />
+    <ele-card :body-style="{ paddingTop: '8px' }" flex-table>
+      <!-- 表格 -->
+      <ele-pro-table
+        ref="tableRef"
+        row-key="postId"
+        :columns="columns"
+        :datasource="datasource"
+        :show-overflow-tooltip="true"
+        v-model:selections="selections"
+        highlight-current-row
+        :export-config="{ fileName: pageConfig.fileName }"
+        :cache-key="pageConfig.cacheKey"
+      >
+        <template #toolbar>
+          <slot name="toolbar"></slot>
+        </template>
+
+        <template #status="{ row }">
+          <dict-data
+            code="sys_normal_disable"
+            type="tag"
+            :model-value="row.status"
+          />
+        </template>
+        <template #orderNumber="{ row }">
+          <order-number :row="row"></order-number>
+        </template>
+        <template #customer="{ row }">
+          <order-customer :row="row"></order-customer>
+        </template>
+        <template #amount="{ row }">
+          <order-amount :row="row"></order-amount>
+        </template>
+        <template #time="{ row }">
+          <order-time :row="row"></order-time>
+        </template>
+        <template #sender="{ row }">
+          <div class="common-text">
+            <el-text>推送人:</el-text>
+            <el-text>周武泰</el-text>
+          </div>
+          <div class="common-text">
+            <el-text>推送时间:</el-text>
+            <el-text>2024-11-21 10:24:56 </el-text>
+          </div>
+        </template>
+        <template #dealer="{ row }">
+          <div class="common-text">
+            <el-text>处理人:</el-text>
+            <el-text>周武泰</el-text>
+          </div>
+          <div class="common-text">
+            <el-text>处理时间:</el-text>
+            <el-text>2024-11-21 10:24:56 </el-text>
+          </div>
+        </template>
+        <template #remarks="{ row }">
+          <el-popover trigger="hover" width="240px">
+            <template #reference>
+              <el-button
+                :icon="Flag"
+                link
+                style="font-size: 20px"
+                @click="handleRemarks(row)"
+              >
+              </el-button>
+            </template>
+            <orderTimeline
+              :records="activities"
+              title="备注历史记录"
+            ></orderTimeline>
+          </el-popover>
+        </template>
+
+        <template #action="{ row }">
+          <slot name="action" :row="row"></slot>
+        </template>
+      </ele-pro-table>
+    </ele-card>
+
+    <slot></slot>
+    <orderRemarks ref="remarksRef" />
+  </ele-page>
+</template>
+
+<script setup>
+  import { ref, getCurrentInstance } from 'vue';
+  import { ElMessageBox } from 'element-plus/es';
+  import { EleMessage } from 'ele-admin-plus/es';
+  import { DownloadOutlined } from '@/components/icons';
+  import { Flag, ChatDotSquare } from '@element-plus/icons-vue';
+  import OrderSearch from './order-search.vue';
+  import OrderNumber from '@/views/recycleOrder/components/order-number.vue';
+  import OrderCustomer from '@/views/recycleOrder/components/order-customer.vue';
+  import OrderAmount from '@/views/recycleOrder/components/order-amount.vue';
+  import OrderTime from '@/views/recycleOrder/components/order-time.vue';
+  import { useDictData } from '@/utils/use-dict-data';
+  import { download, toFormData, checkDownloadRes } from '@/utils/common';
+  import orderRemarks from '@/views/recycleOrder/components/order-remarks.vue';
+  import orderTimeline from '@/views/recycleOrder/components/order-timeline.vue';
+
+  let props = defineProps({
+    pageConfig: {
+      type: Object,
+      default: () => ({
+        cacheKey: 'recycleOrderTable',
+        fileName: '回收订单查询'
+      })
+    },
+    pageUrl: { type: String, default: '/system/post/list' },
+    exportUrl: { type: String, default: '/system/post/export' }
+  });
+  let { proxy } = getCurrentInstance();
+  /** 字典数据 */
+  const [statusDicts] = useDictData(['sys_normal_disable']);
+
+  /** 表格实例 */
+  const tableRef = ref(null);
+
+  /** 表格列配置 */
+  const columns = ref([
+    {
+      type: 'selection',
+      columnKey: 'selection',
+      width: 50,
+      align: 'center',
+      fixed: 'left'
+    },
+    { label: '单号', prop: 'orderNumber', slot: 'orderNumber', minWidth: 180 },
+    { label: '客户', prop: 'customer', slot: 'customer', minWidth: 360 },
+    { label: '信息', prop: 'amount', slot: 'amount', minWidth: 160 },
+    {
+      label: '状态',
+      prop: 'status',
+      slot: 'status',
+      formatter: (row) =>
+        statusDicts.value.find((d) => d.dictValue == row.status)?.dictLabel
+    },
+    { label: '推送人', prop: 'sender', slot: 'sender', minWidth: 170 },
+    { label: '处理人', prop: 'dealer', slot: 'dealer', minWidth: 170 },
+    {
+      label: '处理结果',
+      prop: 'status',
+      slot: 'status',
+      formatter: (row) =>
+        statusDicts.value.find((d) => d.dictValue == row.status)?.dictLabel
+    },
+    { label: '时间', prop: 'time', slot: 'time', minWidth: 200 },
+    { label: '处理备注', prop: 'remarks', slot: 'remarks' },
+    {
+      columnKey: 'action',
+      label: '操作',
+      width: 180,
+      align: 'center',
+      slot: 'action',
+      fixed: 'right',
+    }
+  ]);
+
+  /** 表格选中数据 */
+  const selections = ref([]);
+
+  /** 当前编辑数据 */
+  const current = ref(null);
+
+  /** 是否显示编辑弹窗 */
+  const showEdit = ref(false);
+
+  async function queryPage(params) {
+    const res = await proxy.$http.get(props.pageUrl, { params });
+    if (res.data.code === 200) {
+      return res.data;
+    }
+    return Promise.reject(new Error(res.data.msg));
+  }
+
+  /** 表格数据源 */
+  const datasource = ({ pages, where, orders }) => {
+    return queryPage({ ...where, ...orders, ...pages });
+  };
+
+  /** 搜索 */
+  const reload = (where) => {
+    tableRef.value?.reload?.({ page: 1, where });
+  };
+
+  /** 批量操作 */
+  const operatBatch = ({ row, url, title }) => {
+    const rows = row == null ? selections.value : [row];
+    if (!rows.length) {
+      EleMessage.error('请至少选择一条数据');
+      return;
+    }
+    title = title || '是否确认当前操作?';
+    ElMessageBox.confirm(title, '提示', {
+      type: 'warning',
+      draggable: true
+    })
+      .then(() => {
+        const loading = EleMessage.loading({
+          message: '请求中..',
+          plain: true
+        });
+
+        proxy.$http
+          .delete(url)
+          .then(() => {
+            loading.close();
+            EleMessage.success('操作成功');
+            reload();
+          })
+          .catch((e) => {
+            loading.close();
+            EleMessage.error(e.message);
+          });
+      })
+      .catch(() => {});
+  };
+
+  /// 导出数据
+  async function exportPage(params, name) {
+    const res = await proxy.$http({
+      url: props.exportUrl,
+      method: 'POST',
+      data: toFormData(params),
+      responseType: 'blob'
+    });
+    await checkDownloadRes(res);
+    download(
+      res.data,
+      name ? `${name}_${Date.now()}.xlsx` : `post_${Date.now()}.xlsx`
+    );
+  }
+
+  /** 导出数据 */
+  const exportData = (name) => {
+    const loading = EleMessage.loading({
+      message: '请求中..',
+      plain: true
+    });
+    tableRef.value?.fetch?.(({ where, orders }) => {
+      exportPage({ ...where, ...orders }, name)
+        .then(() => {
+          loading.close();
+        })
+        .catch((e) => {
+          loading.close();
+          EleMessage.error(e.message);
+        });
+    });
+  };
+
+  //修改备注
+  const remarksRef = ref(null);
+  function handleRemarks(row) {
+    remarksRef.value?.handleOpen(row);
+  }
+  const activities = [
+    {
+      content: 'Event start',
+      timestamp: '2018-04-15',
+      color: '#0bbd87',
+      icon: ChatDotSquare
+    },
+    {
+      content: 'Approved',
+      timestamp: '2018-04-13',
+      color: '#0bbd87',
+      icon: ChatDotSquare
+    },
+    {
+      content: 'Success',
+      timestamp: '2018-04-11',
+      color: '#0bbd87',
+      icon: ChatDotSquare
+    }
+  ];
+
+  defineExpose({ reload, exportData, operatBatch });
+</script>

+ 83 - 0
src/views/recycleLogistics/components/order-search.vue

@@ -0,0 +1,83 @@
+<!-- 搜索表单 -->
+<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: 'bookName' },
+    {
+      type: 'date',
+      label: '推送时间(开始事件)',
+      prop: 'beginTime',
+      props: {
+        format: 'YYYY-MM-DD',
+        valueFormat: 'YYYY-MM-DD'
+      }
+    },
+    {
+      type: 'date',
+      label: '推送时间(结束事件)',
+      prop: 'endTime',
+      props: {
+        format: 'YYYY-MM-DD',
+        valueFormat: 'YYYY-MM-DD'
+      }
+    },
+    { type: 'input', label: '处理人', prop: 'isbn' },
+    {
+      type: 'date',
+      label: '建单时间(开始事件)',
+      prop: 'beginTime',
+      props: {
+        format: 'YYYY-MM-DD',
+        valueFormat: 'YYYY-MM-DD'
+      }
+    },
+    {
+      type: 'date',
+      label: '建单时间(结束事件)',
+      prop: 'endTime',
+      props: {
+        format: 'YYYY-MM-DD',
+        valueFormat: 'YYYY-MM-DD'
+      }
+    },
+    { type: 'input', label: '订单编号', prop: 'author' },
+    { type: 'input', label: '物流编号', prop: 'publish' },
+    {
+      type: 'dictSelect',
+      label: '处理结果',
+      prop: 'perCheck',
+      props: { code: 'is_common_yes' }
+    },
+  ]);
+
+  const initKeys = reactive({
+    schoolName: '',
+    provinceId: '',
+    cityId: '',
+    schoolLevel: '',
+    departmentName: '',
+    shcoolTag: ''
+  });
+
+  const searchRef = ref(null);
+  /** 搜索 */
+  const search = (data) => {
+    emit('search', { ...data });
+  };
+</script>

+ 66 - 0
src/views/recycleLogistics/expressCheck/index.vue

@@ -0,0 +1,66 @@
+<template>
+  <ele-page flex-table>
+    <page-search @search="reload"></page-search>
+
+    <common-table ref="pageRef" :pageConfig="pageConfig" :columns="columns">
+      <template #toolbar>
+        <el-button
+          type="success"
+          plain
+          v-permission="'recycleLogistics:expressCheck:export'"
+          @click="handleExportExcel"
+          :icon="DownloadOutlined"
+        >
+          导出EXCEL
+        </el-button>
+      </template>
+    </common-table>
+  </ele-page>
+</template>
+
+<script setup>
+  import { ref, reactive } from 'vue';
+  import { ElMessageBox } from 'element-plus/es';
+  import { EleMessage } from 'ele-admin-plus/es';
+  import { DownloadOutlined } from '@/components/icons';
+  import CommonTable from '@/components/CommonPage/CommonTable.vue';
+  import pageSearch from '../transferSign/components/page-search.vue';
+  import { useDictData } from '@/utils/use-dict-data';
+  import request from '@/utils/request';
+
+  defineOptions({ name: 'expressCheck' });
+
+  /** 表格列配置 */
+  const columns = ref([
+    {
+      label: '签收者',
+      prop: 'godownName',
+      align: 'center'
+    },
+    { label: '物流单号', prop: 'contactsName', align: 'center' },
+    { label: '物流CODE', prop: 'phoneNum', align: 'center' },
+    { label: '包裹号', prop: 'addressDetail', align: 'center' },
+    { label: '仓库', prop: 'addressDetail', align: 'center' },
+    { label: '签收时间', prop: 'createTime', align: 'center' }
+  ]);
+
+  /** 页面组件实例 */
+  const pageRef = ref(null);
+
+  const pageConfig = reactive({
+    pageUrl: '/system/info/pagelist',
+    exportUrl: '/system/info/export',
+    fileName: '快递验收',
+    cacheKey: 'expressCheckTable',
+  });
+
+  //刷新表格
+  function reload(where) {
+    pageRef.value?.reload(where);
+  }
+
+  //导出excel
+  function handleExportExcel() {
+    pageRef.value?.exportData('快递验收');
+  }
+</script>

+ 58 - 0
src/views/recycleLogistics/transferSign/components/page-search.vue

@@ -0,0 +1,58 @@
+<!-- 搜索表单 -->
+<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: 'contactsName' },
+    { type: 'input', label: '物流单号', prop: 'phoneNum' },
+    { type: 'input', label: '物流CODE', prop: 'code' },
+    {
+      type: 'date',
+      label: '签收时间(开始事件)',
+      prop: 'beginTime',
+      props: {
+        format: 'YYYY-MM-DD',
+        valueFormat: 'YYYY-MM-DD'
+      }
+    },
+    {
+      type: 'date',
+      label: '签收时间(结束事件)',
+      prop: 'endTime',
+      props: {
+        format: 'YYYY-MM-DD',
+        valueFormat: 'YYYY-MM-DD'
+      }
+    }
+  ]);
+
+  const initKeys = reactive({
+    schoolName: '',
+    provinceId: '',
+    cityId: '',
+    schoolLevel: '',
+    departmentName: '',
+    shcoolTag: ''
+  });
+
+  const searchRef = ref(null);
+  /** 搜索 */
+  const search = (data) => {
+    emit('search', { ...data });
+  };
+</script>

+ 66 - 0
src/views/recycleLogistics/transferSign/index.vue

@@ -0,0 +1,66 @@
+<template>
+  <ele-page flex-table>
+    <page-search @search="reload"></page-search>
+
+    <common-table ref="pageRef" :pageConfig="pageConfig" :columns="columns">
+      <template #toolbar>
+        <el-button
+          type="success"
+          plain
+          v-permission="'recycleLogistics:transferSign:export'"
+          @click="handleExportExcel"
+          :icon="DownloadOutlined"
+        >
+          导出EXCEL
+        </el-button>
+      </template>
+    </common-table>
+  </ele-page>
+</template>
+
+<script setup>
+  import { ref, reactive } from 'vue';
+  import { ElMessageBox } from 'element-plus/es';
+  import { EleMessage } from 'ele-admin-plus/es';
+  import { DownloadOutlined } from '@/components/icons';
+  import CommonTable from '@/components/CommonPage/CommonTable.vue';
+  import pageSearch from './components/page-search.vue';
+  import { useDictData } from '@/utils/use-dict-data';
+  import request from '@/utils/request';
+
+  defineOptions({ name: 'transferSign' });
+
+  /** 表格列配置 */
+  const columns = ref([
+    {
+      label: '签收者',
+      prop: 'godownName',
+      align: 'center'
+    },
+    { label: '物流单号', prop: 'contactsName', align: 'center' },
+    { label: '物流CODE', prop: 'phoneNum', align: 'center' },
+    { label: '包裹号', prop: 'addressDetail', align: 'center' },
+    { label: '仓库', prop: 'addressDetail', align: 'center' },
+    { label: '签收时间', prop: 'createTime', align: 'center' }
+  ]);
+
+  /** 页面组件实例 */
+  const pageRef = ref(null);
+
+  const pageConfig = reactive({
+    pageUrl: '/system/info/pagelist',
+    exportUrl: '/system/info/export',
+    fileName: '中转签收',
+    cacheKey: 'transferSignTable',
+  });
+
+  //刷新表格
+  function reload(where) {
+    pageRef.value?.reload(where);
+  }
+
+  //导出excel
+  function handleExportExcel() {
+    pageRef.value?.exportData('中转签收');
+  }
+</script>

+ 55 - 0
src/views/recycleLogistics/warehouse/components/area-setting.vue

@@ -0,0 +1,55 @@
+<!-- 编辑弹窗 -->
+<template>
+  <ele-modal
+    form
+    :width="1080"
+    v-model="visible"
+    title="仓库区域设置"
+    @open="handleOpen"
+  >
+    <template #footer>
+      <el-button @click="handleCancel">关闭</el-button>
+      <el-button type="primary" @click="handleSubmit">确定</el-button>
+    </template>
+  </ele-modal>
+</template>
+
+<script setup>
+  import { ref, reactive, nextTick } from 'vue';
+  import request from '@/utils/request';
+
+  /** 弹窗是否打开 */
+  const visible = defineModel({ type: Boolean });
+
+  /** 关闭弹窗 */
+  const handleCancel = () => {
+    visible.value = false;
+  };
+
+  /** 弹窗打开事件 */
+  const handleOpen = (row) => {
+    visible.value = true;
+    nextTick(() => {
+        getAreaInfo();
+    });
+  };
+
+  /** 提交 */
+  const handleSubmit = () => {
+    visible.value = false;
+  };
+
+  //获取区域基础数据信息
+  const getAreaInfo = () => {
+    request.get('/system/area/list').then((res) => {
+      console.log(res, '区域基础数据信息');
+    });
+    request.get('/system/area/pagelist').then((res) => {
+      console.log(res, '区域基础数据信息');
+    });
+  };
+
+  defineExpose({
+    handleOpen
+  });
+</script>

+ 160 - 0
src/views/recycleLogistics/warehouse/components/warehouse-edit.vue

@@ -0,0 +1,160 @@
+<!-- 搜索表单 -->
+<template>
+  <simple-form-modal
+    :title="title"
+    :items="formItems"
+    ref="editRef"
+    :baseUrl="baseUrl"
+    labelWidth="120px"
+    width="700px"
+    @success="(data) => emit('success', data)"
+    :type="type"
+  >
+    <template #citySelect="{ item, model, updateValue }">
+      <city-select
+        :placeholder="'请选择' + item.label"
+        v-bind="item.props || {}"
+        :modelValue="model[item.prop]"
+        @update:modelValue="updateValue"
+      >
+      </city-select>
+    </template>
+  </simple-form-modal>
+</template>
+
+<script setup>
+  import { reactive, ref, defineEmits, getCurrentInstance } from 'vue';
+  import { useFormData } from '@/utils/use-form-data';
+  import SimpleFormModal from '@/components/CommonPage/SimpleFormModal.vue';
+  import CitySelect from '@/components/CommonPage/CitySelect.vue';
+  const { proxy } = getCurrentInstance();
+
+  //获取省市
+  const provinceList = ref([]);
+  const cityList = ref([]);
+  const title = ref('新增仓库');
+  const emit = defineEmits(['success']);
+
+  const formItems = computed(() => {
+    return [
+      { type: 'input', label: '仓库名称', prop: 'godownName', required: true },
+      { type: 'input', label: '联系人', prop: 'contactsName', required: true },
+      {
+        type: 'dictRadio',
+        label: '状态',
+        prop: 'useStatus',
+        props: { code: 'use_status' },
+        required: true
+      },
+    //   { type: 'citySelect', label: '省市区', prop: 'contactsPhone' },
+      {
+        type: 'select',
+        label: '省份',
+        prop: 'provinceId',
+        required: true,
+        options: provinceList.value.map((d) => {
+          return { label: d.district, value: d.id };
+        }),
+        props: {
+          filterable: true,
+          onChange: (val) => {
+            getProviceList(val).then((res) => {
+              cityList.value = res.data.data;
+            });
+          }
+        }
+      },
+      {
+        type: 'select',
+        label: '所在市',
+        prop: 'cityId',
+        required: true,
+        options: cityList.value.map((d) => {
+          return { label: d.district, value: d.id };
+        }),
+        props: {
+          filterable: true
+        }
+      },
+      {
+        type: 'input',
+        label: '详细地址',
+        prop: 'addressDetail',
+        required: true
+      },
+      {
+        type: 'input',
+        label: '手机号',
+        prop: 'phoneNum',
+        required: true,
+        itemProps: {
+          rules: [
+            {
+              pattern: /^1[3|4|5|6|7|8|9]\d{9}$/,
+              message: '请输入正确的手机号码'
+            }
+          ]
+        }
+      },
+      {
+        type: 'dictRadio',
+        label: '图书审核提示',
+        prop: 'checkTips',
+        props: { code: 'is_open_close' },
+        required: true
+      },
+      {
+        type: 'dictRadio',
+        label: '绑定审核员',
+        prop: 'bindChecker',
+        props: { code: 'is_open_close' },
+        required: true
+      },
+      {
+        type: 'dictRadio',
+        label: '路由异常签收',
+        prop: 'abnormalSign',
+        props: { code: 'is_open_close' },
+        required: true
+      }
+    ];
+  });
+  //默认值
+  const baseUrl = reactive({
+    add: '/system/info/add',
+    update: '/system/info/update'
+  });
+  const formData = ref({
+    useStatus: '1',
+    abnormalSign: '1',
+    checkTips: '1',
+    bindChecker: '1'
+  });
+
+  const editRef = ref(null);
+  const type = ref('');
+  function handleOpen(data = {}, optType) {
+    type.value = optType;
+    if (optType === 'detail') {
+      title.value = '仓库详情';
+    } else {
+      title.value = data && data.id ? '编辑仓库' : '新增仓库';
+    }
+    formData.value = Object.assign(formData.value, data || {});
+    getProviceList().then((res) => {
+      provinceList.value = res.data.data;
+    });
+    if (data && data.provinceId) {
+      getProviceList(data.provinceId).then((res) => {
+        cityList.value = res.data.data;
+      });
+    }
+    editRef.value?.handleOpen(formData.value);
+  }
+
+  function getProviceList(id = 1) {
+    return proxy.$http.get(`/baseinfo/districtInfo/findInfo/${id}`);
+  }
+
+  defineExpose({ handleOpen });
+</script>

+ 58 - 0
src/views/recycleLogistics/warehouse/components/warehouse-search.vue

@@ -0,0 +1,58 @@
+<!-- 搜索表单 -->
+<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: 'contactsName' },
+    { type: 'input', label: '联系人电话', prop: 'phoneNum' },
+    {
+      type: 'dictSelect',
+      label: '图书审核提示状态',
+      prop: 'checkTips',
+      props: { code: 'is_open_close' }
+    },
+    {
+      type: 'dictSelect',
+      label: '绑定审核员状态',
+      prop: 'bindChecker',
+      props: { code: 'is_open_close' }
+    },
+    {
+      type: 'dictSelect',
+      label: '异常签收状态',
+      prop: 'abnormalSign',
+      props: { code: 'is_open_close' }
+    },
+    { type: 'input', label: '仓库名称', prop: 'godownName' },
+  ]);
+
+  const initKeys = reactive({
+    schoolName: '',
+    provinceId: '',
+    cityId: '',
+    schoolLevel: '',
+    departmentName: '',
+    shcoolTag: ''
+  });
+
+  const searchRef = ref(null);
+  /** 搜索 */
+  const search = (data) => {
+    emit('search', { ...data });
+  };
+</script>

+ 264 - 0
src/views/recycleLogistics/warehouse/index.vue

@@ -0,0 +1,264 @@
+<template>
+  <ele-page flex-table>
+    <warehouse-search @search="reload"></warehouse-search>
+
+    <common-table ref="pageRef" :pageConfig="pageConfig" :columns="columns">
+      <template #toolbar>
+        <el-radio-group @change="handleStatusChange" v-model="useStatus">
+          <el-radio-button label="启用" value="1" />
+          <el-radio-button label="禁用" value="2" />
+        </el-radio-group>
+
+        <el-button
+          type="primary"
+          plain
+          :icon="PlusOutlined"
+          v-permission="'recycleLogistics:warehouse:add'"
+          @click="handleUpdate()"
+          style="margin-left: 40px"
+        >
+          新增仓库
+        </el-button>
+      </template>
+
+      <template #checkTips="{ row }">
+        <el-switch
+          v-model="row.checkTips"
+          style="--el-switch-on-color: #13ce66"
+          :active-value="1"
+          :inactive-value="0"
+          @change="statusSwitchChange($event, row, 'checkTips')"
+        />
+        <el-text style="margin-left: 5px">{{
+          row.checkTips == 0 ? '已关闭' : '已开启'
+        }}</el-text>
+      </template>
+      <template #bindChecker="{ row }">
+        <el-switch
+          v-model="row.bindChecker"
+          style="--el-switch-on-color: #13ce66"
+          :active-value="1"
+          :inactive-value="0"
+          @change="statusSwitchChange($event, row, 'bindChecker')"
+        />
+        <el-text style="margin-left: 5px">{{
+          row.bindChecker == 0 ? '已关闭' : '已开启'
+        }}</el-text>
+      </template>
+      <template #abnormalSign="{ row }">
+        <el-switch
+          v-model="row.abnormalSign"
+          style="--el-switch-on-color: #13ce66"
+          :active-value="1"
+          :inactive-value="0"
+          @change="statusSwitchChange($event, row, 'abnormalSign')"
+        />
+        <el-text style="margin-left: 5px">{{
+          row.abnormalSign == 0 ? '已关闭' : '已开启'
+        }}</el-text>
+      </template>
+
+      <template #action="{ row }">
+        <div>
+          <el-button
+            type="primary"
+            link
+            v-permission="'recycleLogistics:warehouse:detail'"
+            @click="handleUpdate(row)"
+          >
+            [详情]
+          </el-button>
+          <el-button
+            :type="row.useStatus == 1 ? 'danger' : 'success'"
+            link
+            v-permission="'recycleLogistics:warehouse:changeStatus'"
+            @click="handleChangeStatus(row)"
+          >
+            {{ row.useStatus == 1 ? '[禁用]' : '[启用]' }}
+          </el-button>
+          <el-button
+            type="warning"
+            link
+            v-permission="'recycleLogistics:warehouse:areaSetting'"
+            @click="handleAreaSetting(row)"
+          >
+            [仓库区域设置]
+          </el-button>
+        </div>
+      </template>
+    </common-table>
+
+    <warehouse-edit ref="editRef" @success="reload()"></warehouse-edit>
+    <area-setting ref="areaSettingRef" @success="reload()"></area-setting>
+  </ele-page>
+</template>
+
+<script setup>
+  import { ref, reactive } from 'vue';
+  import { ElMessageBox } from 'element-plus/es';
+  import { EleMessage } from 'ele-admin-plus/es';
+  import {
+    PlusOutlined,
+    DeleteOutlined,
+    DownloadOutlined
+  } from '@/components/icons';
+  import CommonTable from '@/components/CommonPage/CommonTable.vue';
+  import warehouseEdit from '@/views/recycleLogistics/warehouse/components/warehouse-edit.vue';
+  import warehouseSearch from '@/views/recycleLogistics/warehouse/components/warehouse-search.vue';
+  import areaSetting from '@/views/recycleLogistics/warehouse/components/area-setting.vue'
+  import { useDictData } from '@/utils/use-dict-data';
+  import { useRouter } from 'vue-router';
+  import request from '@/utils/request';
+
+  defineOptions({ name: 'warehouse' });
+  const [useStatusDicts] = useDictData(['use_status']);
+
+  const useStatus = ref('1');
+  function handleStatusChange() {
+    pageRef.value.reload({ useStatus: useStatus.value });
+  }
+
+  /** 表格列配置 */
+  const columns = ref([
+    {
+      type: 'selection',
+      columnKey: 'selection',
+      width: 50,
+      align: 'center',
+      fixed: 'left'
+    },
+    {
+      label: '仓库名称',
+      prop: 'godownName',
+      align: 'center',
+      minWidth: 140
+    },
+    { label: '联系人', prop: 'contactsName', align: 'center', minWidth: 100 },
+    { label: '手机号码', prop: 'phoneNum', align: 'center', minWidth: 120 },
+    {
+      label: '详细地址',
+      prop: 'addressDetail',
+      align: 'center',
+      minWidth: 240
+    },
+    {
+      label: '仓库状态',
+      prop: 'useStatus',
+      align: 'center',
+      minWidth: 80,
+      formatter: (row) =>
+        useStatusDicts.value.find((d) => d.dictValue == row.useStatus)
+          ?.dictLabel
+    },
+    {
+      label: '图书审核提示',
+      prop: 'checkTips',
+      align: 'center',
+      slot: 'checkTips',
+      minWidth: 120
+    },
+    {
+      label: '绑定审核员',
+      prop: 'bindChecker',
+      align: 'center',
+      slot: 'bindChecker',
+      minWidth: 120
+    },
+    {
+      label: '路由异常签收',
+      prop: 'abnormalSign',
+      align: 'center',
+      slot: 'abnormalSign',
+      minWidth: 120
+    },
+    {
+      columnKey: 'action',
+      label: '操作',
+      width: 240,
+      align: 'center',
+      slot: 'action'
+    }
+  ]);
+
+  let router = useRouter();
+  /** 页面组件实例 */
+  const pageRef = ref(null);
+
+  const pageConfig = reactive({
+    pageUrl: '/system/info/pagelist',
+    exportUrl: '/system/info/export',
+    fileName: '仓库管理',
+    cacheKey: 'warehouseTable',
+    params: { useStatus: 1 }
+  });
+
+  //刷新表格
+  function reload(where) {
+    pageRef.value?.reload(where);
+  }
+
+  //修改状态
+  const statusSwitchChange = (value, row, key) => {
+    let data = JSON.parse(JSON.stringify(row));
+    let message = value == 0 ? '确认关闭?' : '确认开启?';
+    ElMessageBox.confirm(message, '提示', {
+      confirmButtonText: '确定',
+      cancelButtonText: '关闭',
+      type: 'warning'
+    })
+      .then(() => {
+        data[key] = value;
+        request.post('/system/info/update', data).then((res) => {
+          if (res.data.code === 200) {
+            EleMessage.success('操作成功');
+            reload();
+          } else {
+            EleMessage.error(res.data.msg);
+          }
+        });
+      })
+      .catch(() => {
+        row[key] = value == 1 ? 0 : 1;
+      });
+  };
+
+  //批量删除
+  function handleBatchDelete(row) {
+    let selections = row ? [row] : pageRef.value?.getSelections();
+    let ids = selections.map((item) => item.id).join(',');
+    let url = `/system/info/delete/${ids}`;
+    pageRef.value?.operatBatch({
+      title: '确认删除?',
+      method: 'delete',
+      url,
+      row
+    });
+  }
+  //导出excel
+  function handleExportExcel() {
+    pageRef.value?.exportData('仓库管理');
+  }
+
+  //禁用/启用
+  function handleChangeStatus(row) {
+    let message = row.useStatus == 1 ? '确认禁用?' : '确认启用?';
+    let data = JSON.parse(JSON.stringify(row));
+    data.useStatus = row.useStatus == 1 ? 0 : 1;
+    pageRef.value?.messageBoxConfirm({
+      message,
+      fetch: () => request.post('/system/info/update', data)
+    });
+  }
+
+  //编辑页面
+  const editRef = ref(null);
+  function handleUpdate(row) {
+    editRef.value?.handleOpen(row);
+  }
+
+  //仓库区域设置
+  const areaSettingRef = ref(null);
+  function handleAreaSetting(row) {
+    areaSettingRef.value?.handleOpen(row);
+  }
+</script>