haveyou пре 1 година
родитељ
комит
f298684c99

+ 36 - 0
src/components/CommonPage/CommonStatistics.vue

@@ -0,0 +1,36 @@
+<template>
+  <div class="flex flex-col items-center">
+    <ele-count-up
+      :end-val="value"
+      type="success"
+      :style="valueStyle"
+      :options="options"
+      :precision="2"
+    />
+    <ele-text>{{ title }}</ele-text>
+  </div>
+</template>
+
+<script setup>
+  const props = defineProps({
+    title: {
+      type: String,
+      default: 'Hello World'
+    },
+    value: {
+      type: [String, Number],
+      default: 0
+    },
+    valueStyle: {
+      type: Object,
+      default: () => ({ fontSize: '28px', color: '#67C23A' })
+    },
+    options: {
+      type: Object,
+      default: () => ({
+        duration: 1,
+        useEasing: true
+      })
+    }
+  });
+</script>

+ 63 - 0
src/views/recycle/booklistStat/components/book-recycle-rate.vue

@@ -0,0 +1,63 @@
+<template>
+  <v-chart
+    class="flex-1"
+    ref="saleChartRef"
+    style="height: 420px"
+    :option="options"
+  />
+</template>
+<script setup>
+  import VChart from 'vue-echarts';
+  import { use } from 'echarts/core';
+  import { CanvasRenderer } from 'echarts/renderers';
+  import { PieChart } from 'echarts/charts';
+  import {
+    GridComponent,
+    TooltipComponent,
+    LegendComponent,
+    TitleComponent
+  } from 'echarts/components';
+
+  // 按需加载echarts
+  use([
+    CanvasRenderer,
+    PieChart,
+    GridComponent,
+    TooltipComponent,
+    LegendComponent,
+    TitleComponent
+  ]);
+
+  const options = reactive({
+    title: {
+      text: '多本回收占比',
+      textStyle: {
+        color: '#363636',
+        fontSize: 14,
+        fontWeight: 'normal'
+      }
+    },
+    legend: {
+      right: 'right',
+      orient: 'vertical',
+      top: 'center'
+    },
+    series: [
+      {
+        name: '多本回收占比',
+        type: 'pie',
+        radius: [50, 150],
+        center: ['50%', '50%'],
+        roseType: 'area',
+        itemStyle: {
+          borderRadius: 8
+        },
+        data: [
+          { value: 40, name: '中等' },
+          { value: 38, name: '良好' },
+          { value: 32, name: '极差' },
+        ]
+      }
+    ]
+  });
+</script>

+ 64 - 0
src/views/recycle/booklistStat/components/recycle-discount-rate.vue

@@ -0,0 +1,64 @@
+<template>
+  <v-chart
+    class="flex-1"
+    ref="saleChartRef"
+    style="height: 420px"
+    :option="options"
+  />
+</template>
+<script setup>
+  import VChart from 'vue-echarts';
+  import { use } from 'echarts/core';
+  import { CanvasRenderer } from 'echarts/renderers';
+  import { PieChart } from 'echarts/charts';
+  import {
+    GridComponent,
+    TooltipComponent,
+    LegendComponent,
+    TitleComponent
+  } from 'echarts/components';
+
+  // 按需加载echarts
+  use([
+    CanvasRenderer,
+    PieChart,
+    GridComponent,
+    TooltipComponent,
+    LegendComponent,
+    TitleComponent
+  ]);
+
+  const options = reactive({
+    title: {
+      text: '回收折扣占比',
+      textStyle: {
+        color: '#363636',
+        fontSize: 14,
+        fontWeight: 'normal'
+      }
+    },
+    legend: {
+      right: 'right',
+      orient: 'vertical',
+      top: 'center'
+    },
+    series: [
+      {
+        name: '回收折扣占比',
+        type: 'pie',
+        radius: [50, 150],
+        center: ['50%', '50%'],
+        roseType: 'area',
+        itemStyle: {
+          borderRadius: 8
+        },
+        data: [
+          { value: 40, name: '≥2折' },
+          { value: 38, name: '2<至≥1' },
+          { value: 32, name: '1<至≥0.5' },
+          { value: 30, name: '≤0.5' }
+        ]
+      }
+    ]
+  });
+</script>

+ 41 - 0
src/views/recycle/booklistStat/index.vue

@@ -0,0 +1,41 @@
+<template>
+  <ele-page flex-table>
+    <ele-card flex-table>
+      <div class="flex justify-center">
+        <div class="flex gap-6">
+          <div class="common-section">
+            <CommonStatistics title="开放回收种类(种)" :value="565621" />
+          </div>
+          <div class="common-section">
+            <CommonStatistics
+              title="单本回收均价(元)"
+              :value="2.4"
+              :options="{ decimalPlaces: 2 }"
+            />
+          </div>
+          <div class="common-section">
+            <CommonStatistics title="回收本数(本)" :value="454445" />
+          </div>
+          <div class="common-section">
+            <CommonStatistics title="无数据图书(本)" :value="5621" />
+          </div>
+        </div>
+      </div>
+
+      <div class="flex mt-12 gap-8">
+        <div class="flex-1">
+            <bookRecycleRate />
+        </div>
+        <div class="flex-1">
+          <recycleDiscountRate />
+        </div>
+      </div>
+    </ele-card>
+  </ele-page>
+</template>
+
+<script setup>
+  import CommonStatistics from '@/components/CommonPage/CommonStatistics.vue';
+  import recycleDiscountRate from '@/views/recycle/booklistStat/components/recycle-discount-rate.vue';
+  import bookRecycleRate from '@/views/recycle/booklistStat/components/book-recycle-rate.vue';
+</script>

+ 107 - 0
src/views/recycle/fallbackLog/index.vue

@@ -0,0 +1,107 @@
+<template>
+  <ele-page flex-table>
+    <book-search @search="reload"></book-search>
+
+    <common-table
+      ref="pageRef"
+      :pageConfig="pageConfig"
+      :columns="columns"
+      :tools="false"
+    >
+      <template #cover="{ row }">
+        <el-image
+          style="width: 90px; height: 120px; border-radius: 4px"
+          fit="cover"
+          :src="row.cover"
+        />
+      </template>
+      <template #baseInfo="{ row }">
+        <div class="flex justify-start items-center">
+          <div style="flex: 2">
+            <book-info :row="row"></book-info>
+          </div>
+          <div style="flex: 1.5; margin-left: 15px">
+            <book-other-info :row="row"></book-other-info>
+          </div>
+        </div>
+      </template>
+
+      <template #action="{ row }">
+        <el-button
+          type="primary"
+          link
+          v-permission="'recycle:fallbackLog:updateBook'"
+          @click="handleUpdateBook(row)"
+        >
+          [编辑]
+        </el-button>
+        <el-button
+          type="success"
+          link
+          v-permission="'recycle:fallbackLog:viewPic'"
+          @click="handleViewPic(row)"
+        >
+          [查看图片]
+        </el-button>
+      </template>
+    </common-table>
+
+    <books-edit ref="editRef"></books-edit>
+  </ele-page>
+</template>
+
+<script setup>
+  import { ref, reactive } from 'vue';
+  import CommonTable from '@/components/CommonPage/CommonTable.vue';
+  import booksEdit from '@/views/data/books/components/books-edit.vue';
+  import bookSearch from '@/views/recycle/components/book-search.vue';
+  import bookInfo from '@/views/recycle/components/book-info.vue';
+  import bookOtherInfo from '@/views/recycle/components/book-other-info.vue';
+  import { useDictData } from '@/utils/use-dict-data';
+
+  defineOptions({ name: 'fallbackLog' });
+  const [useStatusDicts] = useDictData(['use_status']);
+
+  /** 表格列配置 */
+  const columns = ref([
+    {
+      type: 'selection',
+      columnKey: 'selection',
+      width: 50,
+      align: 'center',
+      fixed: 'left'
+    },
+    { label: '图片', prop: 'cover', width: 120, slot: 'cover' },
+    { label: '信息', prop: 'baseInfo', slot: 'baseInfo' },
+    { label: '回收折扣', prop: 'discount', width: 140 },
+    {
+      columnKey: 'action',
+      label: '操作',
+      width: 170,
+      slot: 'action',
+      fixed: 'right'
+    }
+  ]);
+
+  /** 页面组件实例 */
+  const pageRef = ref(null);
+
+  const pageConfig = reactive({
+    pageUrl: '/book/bookInfo/list',
+    fileName: '图书反馈记录',
+    cacheKey: 'fallbackLogTable'
+  });
+
+  //刷新表格
+  function reload(where) {
+    pageRef.value?.reload(where);
+  }
+
+  //编辑
+  const editRef = ref(null);
+  function handleUpdateBook(row) {
+    editRef.value?.handleOpen(row);
+  }
+  //查看图片
+  function handleViewPic(row) {}
+</script>

+ 251 - 0
src/views/recycle/inventoryAlert/index.vue

@@ -0,0 +1,251 @@
+<template>
+  <ele-page flex-table>
+    <book-search @search="reload"></book-search>
+
+    <common-table
+      ref="pageRef"
+      :pageConfig="pageConfig"
+      :columns="columns"
+      :tools="false"
+    >
+      <template #toolbar>
+        <el-radio-group @change="handleStatusChange" v-model="useStatus">
+          <el-radio-button label="库存收满预警" value="0" />
+          <el-radio-button label="库存已收满" value="1" />
+        </el-radio-group>
+
+        <span class="ml-8"></span>
+        <el-button
+          type="success"
+          plain
+          v-permission="'recycle:inventoryAlert:batchAddBooklist'"
+          @click="handleOptBooklist()"
+        >
+          加入回收书单
+        </el-button>
+        <el-button
+          type="warning"
+          plain
+          v-permission="'recycle:inventoryAlert:batchRemoveBlacklist'"
+          @click="handleOptBlacklist()"
+        >
+          移除黑名单
+        </el-button>
+        <el-button
+          type="danger"
+          plain
+          v-permission="'recycle:inventoryAlert:batchAddBlacklist'"
+          @click="handleOptBlacklist()"
+        >
+          加黑名单
+        </el-button>
+
+        <el-button
+          type="warning"
+          plain
+          v-permission="'recycle:inventoryAlert:batchPauseRecycle'"
+          @click="handleOptRecycle()"
+        >
+          暂停回收
+        </el-button>
+
+        <el-button
+          type="success"
+          plain
+          v-permission="'recycle:inventoryAlert:batchAddSocial'"
+          @click="handleOptType(1)"
+        >
+          加社科库
+        </el-button>
+        <el-button
+          color="#192bbe"
+          plain
+          v-permission="'recycle:inventoryAlert:batchAddTeach'"
+          @click="handleOptType(2)"
+        >
+          加教材库
+        </el-button>
+        <el-button
+          type="success"
+          plain
+          v-permission="'recycle:inventoryAlert:exprot'"
+          @click="handleExportExcel()"
+        >
+          导出Excel
+        </el-button>
+      </template>
+
+      <template #cover="{ row }">
+        <el-image
+          style="width: 90px; height: 120px; border-radius: 4px"
+          fit="cover"
+          :src="row.cover"
+        />
+      </template>
+      <template #baseInfo="{ row }">
+        <div class="flex justify-start items-center">
+          <div style="flex: 2">
+            <book-info :row="row"></book-info>
+          </div>
+          <div style="flex: 1.5; margin-left: 15px">
+            <book-other-info :row="row"></book-other-info>
+          </div>
+        </div>
+      </template>
+
+      <template #action="{ row }">
+        <el-button
+          type="danger"
+          link
+          v-permission="'recycle:inventoryAlert:delete'"
+          @click="handleDelete(row)"
+        >
+          [删除]
+        </el-button>
+        <el-button
+          type="danger"
+          link
+          v-permission="'recycle:inventoryAlert:deal'"
+          @click="handleDelete(row)"
+        >
+          [去处理]
+        </el-button>
+        <el-button
+          type="warning"
+          link
+          v-permission="'recycle:inventoryAlert:dealLog'"
+          @click="handleDelete(row)"
+        >
+          [处理日志]
+        </el-button>
+        <el-button
+          type="success"
+          link
+          v-permission="'recycle:inventoryAlert:remind'"
+          @click="handleModifyType(row)"
+        >
+          [一个月后提醒]
+        </el-button>
+      </template>
+    </common-table>
+  </ele-page>
+</template>
+
+<script setup>
+  import { ref, reactive } from 'vue';
+  import CommonTable from '@/components/CommonPage/CommonTable.vue';
+  import bookSearch from '@/views/recycle/components/book-search.vue';
+  import bookInfo from '@/views/recycle/components/book-info.vue';
+  import bookOtherInfo from '@/views/recycle/components/book-other-info.vue';
+  import { useDictData } from '@/utils/use-dict-data';
+
+  defineOptions({ name: 'inventoryAlert' });
+  const [useStatusDicts, bookTagDicts] = useDictData([
+    'use_status',
+    'book_tag_ku'
+  ]);
+
+  const useStatus = ref('0');
+  function handleStatusChange(value) {
+    pageRef.value.reload({ useStatus: value });
+  }
+
+  /** 表格列配置 */
+  const columns = ref([
+    {
+      type: 'selection',
+      columnKey: 'selection',
+      width: 50,
+      align: 'center',
+      fixed: 'left'
+    },
+    {
+      label: '图片',
+      prop: 'cover',
+      width: 120,
+      slot: 'cover'
+    },
+    {
+      label: '信息',
+      prop: 'baseInfo',
+      width: 540,
+      slot: 'baseInfo'
+    },
+    { label: '回收折扣', prop: 'discount' },
+    { label: '预估金额', prop: 'price' },
+    { label: '最大回收量', prop: 'maxRecycledAmount' },
+    { label: '当前剩余回收量', prop: 'remainingRecycledAmount' },
+    { label: '预警次数', prop: 'alertNum' },
+    { label: '销量', prop: 'salesVolume' },
+    {
+      columnKey: 'action',
+      label: '操作',
+      width: 120,
+      slot: 'action',
+      fixed: 'right'
+    }
+  ]);
+
+  /** 页面组件实例 */
+  const pageRef = ref(null);
+
+  const pageConfig = reactive({
+    pageUrl: '/book/bookInfo/list',
+    exportUrl: '/book/bookInfo/export',
+    fileName: '图书库存预警',
+    cacheKey: 'inventoryAlertTable'
+  });
+
+  //刷新表格
+  function reload(where) {
+    pageRef.value?.reload(where);
+  }
+
+  //导出excel
+  function handleExportExcel() {
+    pageRef.value?.exportData('图书库存预警');
+  }
+
+  //回收书单操作
+  function handleOptBooklist(row) {
+    let selections = row ? [row] : pageRef.value?.getSelections();
+    let ids = selections.map((item) => item.id).join(',');
+    let url = `/book/bookInfo/removeById/${ids}`;
+    let title = row ? '确认移除回收书单?' : '确认加入回收书单?';
+    pageRef.value?.operatBatch({ title, method: 'post', url, row });
+  }
+  //黑名单操作
+  function handleOptBlacklist(row) {
+    let selections = row ? [row] : pageRef.value?.getSelections();
+    let ids = selections.map((item) => item.id).join(',');
+    let url = `/book/bookInfo/removeBlacklist/${ids}`;
+    let title = row ? '确认移除黑名单?' : '确认加入黑名单?';
+    pageRef.value?.operatBatch({ title, method: 'post', url, row });
+  }
+  //回收操作
+  function handleOptRecycle(row) {
+    let selections = row ? [row] : pageRef.value?.getSelections();
+    let ids = selections.map((item) => item.id).join(',');
+    let url = `/book/bookInfo/recycle/${ids}`;
+    let title = '确认回收?';
+    pageRef.value?.operatBatch({ title, method: 'post', url, row });
+  }
+  //类型操作
+  function handleOptType(type) {
+    let selections = pageRef.value?.getSelections();
+    let ids = selections.map((item) => item.id).join(',');
+    let url = `/book/bookInfo/changeType/${ids}`;
+    let title = type == 1 ? '确认加社科库?' : '确认加教材库?';
+    pageRef.value?.operatBatch({ title, method: 'post', url, row });
+  }
+
+  //删除
+  function handleDelete(row) {
+    pageRef.value?.messageBoxConfirm({
+      message: '确定删除吗?',
+      fetch: () => request.get('/common/exportrecord/deleteRecord?id=' + row.id)
+    });
+  }
+  //修改类型
+  function handleModifyType(row) {}
+</script>

+ 39 - 0
src/views/recycle/pending/components/page-search.vue

@@ -0,0 +1,39 @@
+<!-- 搜索表单 -->
+<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: 'ISBN', prop: 'isbn' },
+    { type: 'select', label: '状态', prop: 'author' },
+    { type: 'input', label: '处理人', prop: 'publisher' },
+    { type: 'select', label: '处理标记', prop: 'author' }
+  ]);
+
+  const initKeys = reactive({
+    bookName: '',
+    isbn: '',
+    author: ''
+  });
+
+  const searchRef = ref(null);
+  /** 搜索 */
+  const search = (data) => {
+    let params = JSON.parse(JSON.stringify(data));
+    emit('search', params);
+  };
+</script>

+ 140 - 0
src/views/recycle/pending/index.vue

@@ -0,0 +1,140 @@
+<template>
+  <ele-page flex-table>
+    <page-search @search="reload"></page-search>
+
+    <common-table
+      ref="pageRef"
+      :pageConfig="pageConfig"
+      :columns="columns"
+      :tools="false"
+    >
+      <template #toolbar>
+        <el-button
+          type="success"
+          plain
+          v-permission="'recycle:pending:oneDetection'"
+          @click="handleOptBooklist()"
+        >
+          一键检测
+        </el-button>
+        <el-button
+          type="success"
+          plain
+          v-permission="'recycle:pending:exprot'"
+          @click="handleExportExcel()"
+        >
+          导出Excel
+        </el-button>
+        <el-button
+          type="warning"
+          plain
+          v-permission="'recycle:pending:clear'"
+          @click="handleOptBlacklist()"
+        >
+          清空
+        </el-button>
+      </template>
+
+      <template #action="{ row }">
+        <el-button
+          type="primary"
+          link
+          v-permission="'recycle:pending:delete'"
+          @click="handleDelete(row)"
+        >
+          [删除]
+        </el-button>
+        <el-button
+          type="danger"
+          link
+          v-permission="'recycle:pending:deal'"
+          @click="handleDelete(row)"
+        >
+          [去处理]
+        </el-button>
+        <el-button
+          type="success"
+          link
+          v-permission="'recycle:pending:detection'"
+          @click="handleDelete(row)"
+        >
+          [检测]
+        </el-button>
+        <el-button
+          type="warning"
+          link
+          v-permission="'recycle:pending:dealLog'"
+          @click="handleModifyType(row)"
+        >
+          [处理日志]
+        </el-button>
+      </template>
+    </common-table>
+  </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 { useDictData } from '@/utils/use-dict-data';
+
+  defineOptions({ name: 'pending' });
+  const [useStatusDicts, bookTagDicts] = useDictData([
+    'use_status',
+    'book_tag_ku'
+  ]);
+
+  const useStatus = ref('0');
+  function handleStatusChange(value) {
+    pageRef.value.reload({ useStatus: value });
+  }
+
+  /** 表格列配置 */
+  const columns = ref([
+    { label: 'ID', prop: 'id', width: 120 },
+    { label: 'ISBN', prop: 'isbn' },
+    { label: '处理标记', prop: 'discount' },
+    { label: '创建时间', prop: 'price' },
+    { label: '是否检测', prop: 'maxRecycledAmount' },
+    { label: '处理人', prop: 'remainingRecycledAmount' },
+    { label: '状态', prop: 'alertNum' },
+    {
+      columnKey: 'action',
+      label: '操作',
+      width: 200,
+      slot: 'action',
+      fixed: 'right'
+    }
+  ]);
+
+  /** 页面组件实例 */
+  const pageRef = ref(null);
+
+  const pageConfig = reactive({
+    pageUrl: '/book/bookInfo/list',
+    exportUrl: '/book/bookInfo/export',
+    fileName: '待处理ISBN',
+    cacheKey: 'pendingTable'
+  });
+
+  //刷新表格
+  function reload(where) {
+    pageRef.value?.reload(where);
+  }
+
+  //导出excel
+  function handleExportExcel() {
+    pageRef.value?.exportData('待处理ISBN');
+  }
+
+  //删除
+  function handleDelete(row) {
+    pageRef.value?.messageBoxConfirm({
+      message: '确定删除吗?',
+      fetch: () => request.get('/common/exportrecord/deleteRecord?id=' + row.id)
+    });
+  }
+  //修改类型
+  function handleModifyType(row) {}
+</script>

+ 251 - 0
src/views/recycle/priceAlert/index.vue

@@ -0,0 +1,251 @@
+<template>
+  <ele-page flex-table>
+    <book-search @search="reload"></book-search>
+
+    <common-table
+      ref="pageRef"
+      :pageConfig="pageConfig"
+      :columns="columns"
+      :tools="false"
+    >
+      <template #toolbar>
+        <el-radio-group @change="handleStatusChange" v-model="useStatus">
+          <el-radio-button label="库存收满预警" value="0" />
+          <el-radio-button label="库存已收满" value="1" />
+        </el-radio-group>
+
+        <span class="ml-8"></span>
+        <el-button
+          type="success"
+          plain
+          v-permission="'recycle:priceAlert:batchAddBooklist'"
+          @click="handleOptBooklist()"
+        >
+          加入回收书单
+        </el-button>
+        <el-button
+          type="warning"
+          plain
+          v-permission="'recycle:priceAlert:batchRemoveBlacklist'"
+          @click="handleOptBlacklist()"
+        >
+          移除黑名单
+        </el-button>
+        <el-button
+          type="danger"
+          plain
+          v-permission="'recycle:priceAlert:batchAddBlacklist'"
+          @click="handleOptBlacklist()"
+        >
+          加黑名单
+        </el-button>
+
+        <el-button
+          type="warning"
+          plain
+          v-permission="'recycle:priceAlert:batchPauseRecycle'"
+          @click="handleOptRecycle()"
+        >
+          暂停回收
+        </el-button>
+
+        <el-button
+          type="success"
+          plain
+          v-permission="'recycle:priceAlert:batchAddSocial'"
+          @click="handleOptType(1)"
+        >
+          加社科库
+        </el-button>
+        <el-button
+          color="#192bbe"
+          plain
+          v-permission="'recycle:priceAlert:batchAddTeach'"
+          @click="handleOptType(2)"
+        >
+          加教材库
+        </el-button>
+        <el-button
+          type="success"
+          plain
+          v-permission="'recycle:priceAlert:exprot'"
+          @click="handleExportExcel()"
+        >
+          导出Excel
+        </el-button>
+      </template>
+
+      <template #cover="{ row }">
+        <el-image
+          style="width: 90px; height: 120px; border-radius: 4px"
+          fit="cover"
+          :src="row.cover"
+        />
+      </template>
+      <template #baseInfo="{ row }">
+        <div class="flex justify-start items-center">
+          <div style="flex: 2">
+            <book-info :row="row"></book-info>
+          </div>
+          <div style="flex: 1.5; margin-left: 15px">
+            <book-other-info :row="row"></book-other-info>
+          </div>
+        </div>
+      </template>
+
+      <template #action="{ row }">
+        <el-button
+          type="danger"
+          link
+          v-permission="'recycle:priceAlert:delete'"
+          @click="handleDelete(row)"
+        >
+          [删除]
+        </el-button>
+        <el-button
+          type="danger"
+          link
+          v-permission="'recycle:priceAlert:deal'"
+          @click="handleDelete(row)"
+        >
+          [去处理]
+        </el-button>
+        <el-button
+          type="warning"
+          link
+          v-permission="'recycle:priceAlert:dealLog'"
+          @click="handleDelete(row)"
+        >
+          [处理日志]
+        </el-button>
+        <el-button
+          type="success"
+          link
+          v-permission="'recycle:priceAlert:remind'"
+          @click="handleModifyType(row)"
+        >
+          [一个月后提醒]
+        </el-button>
+      </template>
+    </common-table>
+  </ele-page>
+</template>
+
+<script setup>
+  import { ref, reactive } from 'vue';
+  import CommonTable from '@/components/CommonPage/CommonTable.vue';
+  import bookSearch from '@/views/recycle/components/book-search.vue';
+  import bookInfo from '@/views/recycle/components/book-info.vue';
+  import bookOtherInfo from '@/views/recycle/components/book-other-info.vue';
+  import { useDictData } from '@/utils/use-dict-data';
+
+  defineOptions({ name: 'priceAlert' });
+  const [useStatusDicts, bookTagDicts] = useDictData([
+    'use_status',
+    'book_tag_ku'
+  ]);
+
+  const useStatus = ref('0');
+  function handleStatusChange(value) {
+    pageRef.value.reload({ useStatus: value });
+  }
+
+  /** 表格列配置 */
+  const columns = ref([
+    {
+      type: 'selection',
+      columnKey: 'selection',
+      width: 50,
+      align: 'center',
+      fixed: 'left'
+    },
+    {
+      label: '图片',
+      prop: 'cover',
+      width: 120,
+      slot: 'cover'
+    },
+    {
+      label: '信息',
+      prop: 'baseInfo',
+      width: 540,
+      slot: 'baseInfo'
+    },
+    { label: '回收折扣', prop: 'discount' },
+    { label: '预估金额', prop: 'price' },
+    { label: '最大回收量', prop: 'maxRecycledAmount' },
+    { label: '当前剩余回收量', prop: 'remainingRecycledAmount' },
+    { label: '预警次数', prop: 'alertNum' },
+    { label: '销量', prop: 'salesVolume' },
+    {
+      columnKey: 'action',
+      label: '操作',
+      width: 120,
+      slot: 'action',
+      fixed: 'right'
+    }
+  ]);
+
+  /** 页面组件实例 */
+  const pageRef = ref(null);
+
+  const pageConfig = reactive({
+    pageUrl: '/book/bookInfo/list',
+    exportUrl: '/book/bookInfo/export',
+    fileName: '回收价格预警',
+    cacheKey: 'priceAlertTable'
+  });
+
+  //刷新表格
+  function reload(where) {
+    pageRef.value?.reload(where);
+  }
+
+  //导出excel
+  function handleExportExcel() {
+    pageRef.value?.exportData('回收价格预警');
+  }
+
+  //回收书单操作
+  function handleOptBooklist(row) {
+    let selections = row ? [row] : pageRef.value?.getSelections();
+    let ids = selections.map((item) => item.id).join(',');
+    let url = `/book/bookInfo/removeById/${ids}`;
+    let title = row ? '确认移除回收书单?' : '确认加入回收书单?';
+    pageRef.value?.operatBatch({ title, method: 'post', url, row });
+  }
+  //黑名单操作
+  function handleOptBlacklist(row) {
+    let selections = row ? [row] : pageRef.value?.getSelections();
+    let ids = selections.map((item) => item.id).join(',');
+    let url = `/book/bookInfo/removeBlacklist/${ids}`;
+    let title = row ? '确认移除黑名单?' : '确认加入黑名单?';
+    pageRef.value?.operatBatch({ title, method: 'post', url, row });
+  }
+  //回收操作
+  function handleOptRecycle(row) {
+    let selections = row ? [row] : pageRef.value?.getSelections();
+    let ids = selections.map((item) => item.id).join(',');
+    let url = `/book/bookInfo/recycle/${ids}`;
+    let title = '确认回收?';
+    pageRef.value?.operatBatch({ title, method: 'post', url, row });
+  }
+  //类型操作
+  function handleOptType(type) {
+    let selections = pageRef.value?.getSelections();
+    let ids = selections.map((item) => item.id).join(',');
+    let url = `/book/bookInfo/changeType/${ids}`;
+    let title = type == 1 ? '确认加社科库?' : '确认加教材库?';
+    pageRef.value?.operatBatch({ title, method: 'post', url, row });
+  }
+
+  //删除
+  function handleDelete(row) {
+    pageRef.value?.messageBoxConfirm({
+      message: '确定删除吗?',
+      fetch: () => request.get('/common/exportrecord/deleteRecord?id=' + row.id)
+    });
+  }
+  //修改类型
+  function handleModifyType(row) {}
+</script>

+ 116 - 0
src/views/recycle/processed/index.vue

@@ -0,0 +1,116 @@
+<template>
+  <ele-page flex-table>
+    <page-search @search="reload"></page-search>
+
+    <common-table
+      ref="pageRef"
+      :pageConfig="pageConfig"
+      :columns="columns"
+      :tools="false"
+    >
+      <template #toolbar>
+        <el-button
+          type="success"
+          plain
+          v-permission="'recycle:processed:oneDetection'"
+          @click="handleOptBooklist()"
+        >
+          一键检测
+        </el-button>
+        <el-button
+          type="success"
+          plain
+          v-permission="'recycle:processed:exprot'"
+          @click="handleExportExcel()"
+        >
+          导出Excel
+        </el-button>
+      </template>
+
+      <template #action="{ row }">
+        <el-button
+          type="primary"
+          link
+          v-permission="'recycle:processed:delete'"
+          @click="handleDelete(row)"
+        >
+          [删除]
+        </el-button>
+        <el-button
+          type="warning"
+          link
+          v-permission="'recycle:processed:dealLog'"
+          @click="handleModifyType(row)"
+        >
+          [处理日志]
+        </el-button>
+      </template>
+    </common-table>
+  </ele-page>
+</template>
+
+<script setup>
+  import { ref, reactive } from 'vue';
+  import CommonTable from '@/components/CommonPage/CommonTable.vue';
+  import pageSearch from '@/views/recycle/pending/components/page-search.vue';
+  import { useDictData } from '@/utils/use-dict-data';
+
+  defineOptions({ name: 'processed' });
+  const [useStatusDicts, bookTagDicts] = useDictData([
+    'use_status',
+    'book_tag_ku'
+  ]);
+
+  const useStatus = ref('0');
+  function handleStatusChange(value) {
+    pageRef.value.reload({ useStatus: value });
+  }
+
+  /** 表格列配置 */
+  const columns = ref([
+    { label: 'ID', prop: 'id', width: 120 },
+    { label: 'ISBN', prop: 'isbn' },
+    { label: '处理标记', prop: 'discount' },
+    { label: '创建时间', prop: 'price' },
+    { label: '是否检测', prop: 'maxRecycledAmount' },
+    { label: '处理人', prop: 'remainingRecycledAmount' },
+    { label: '状态', prop: 'alertNum' },
+    {
+      columnKey: 'action',
+      label: '操作',
+      width: 160,
+      slot: 'action',
+      fixed: 'right'
+    }
+  ]);
+
+  /** 页面组件实例 */
+  const pageRef = ref(null);
+
+  const pageConfig = reactive({
+    pageUrl: '/book/bookInfo/list',
+    exportUrl: '/book/bookInfo/export',
+    fileName: '已处理ISBN',
+    cacheKey: 'processedTable'
+  });
+
+  //刷新表格
+  function reload(where) {
+    pageRef.value?.reload(where);
+  }
+
+  //导出excel
+  function handleExportExcel() {
+    pageRef.value?.exportData('已处理ISBN');
+  }
+
+  //删除
+  function handleDelete(row) {
+    pageRef.value?.messageBoxConfirm({
+      message: '确定删除吗?',
+      fetch: () => request.get('/common/exportrecord/deleteRecord?id=' + row.id)
+    });
+  }
+  //修改类型
+  function handleModifyType(row) {}
+</script>

+ 60 - 0
src/views/recycle/scanLog/components/scan-log.vue

@@ -0,0 +1,60 @@
+<!-- 编辑弹窗 -->
+<template>
+  <ele-modal
+    form
+    :width="760"
+    v-model="visible"
+    title="扫描记录详情"
+    @open="handleOpen"
+  >
+    <common-table
+      :body-style="{ padding: '0px' }"
+      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: 'createName', align: 'center', width: 180 },
+    { label: '扫描次数', prop: 'downloadNum', align: 'center', width: 120 },
+    { label: '最后一次扫描时间', prop: 'downloadTime', align: 'center' }
+  ]);
+
+  const pageConfig = reactive({
+    pageUrl: '/common/exportrecord/pagelist',
+    fileName: '扫描记录',
+    cacheKey: 'scanLogList'
+  });
+
+  defineExpose({
+    handleOpen
+  });
+</script>

+ 262 - 0
src/views/recycle/scanLog/index.vue

@@ -0,0 +1,262 @@
+<template>
+  <ele-page flex-table>
+    <book-search @search="reload"></book-search>
+
+    <common-table
+      ref="pageRef"
+      :pageConfig="pageConfig"
+      :columns="columns"
+      :tools="false"
+    >
+      <template #toolbar>
+        <el-radio-group @change="handleStatusChange" v-model="useStatus">
+          <el-radio-button label="已加入回收书单(正在回收)" value="1" />
+          <el-radio-button label="已加入回收书单(暂停回收)" value="2" />
+          <el-radio-button label="未加入回收书单" value="3" />
+          <el-radio-button label="黑名单" value="4" />
+          <el-radio-button label="无数据信息" value="4" />
+        </el-radio-group>
+
+        <span class="ml-8"></span>
+        <el-button
+          type="success"
+          plain
+          v-permission="'recycle:scanLog:batchAddBooklist'"
+          @click="handleOptBooklist()"
+        >
+          加入回收书单
+        </el-button>
+        <el-button
+          type="warning"
+          plain
+          v-permission="'recycle:scanLog:batchRemoveBlacklist'"
+          @click="handleOptBlacklist()"
+        >
+          移除黑名单
+        </el-button>
+        <el-button
+          type="danger"
+          plain
+          v-permission="'recycle:scanLog:batchAddBlacklist'"
+          @click="handleOptBlacklist()"
+        >
+          加黑名单
+        </el-button>
+
+        <el-button
+          type="warning"
+          plain
+          v-permission="'recycle:scanLog:batchPauseRecycle'"
+          @click="handleOptRecycle()"
+        >
+          暂停回收
+        </el-button>
+      </template>
+
+      <template #cover="{ row }">
+        <el-image
+          style="width: 90px; height: 120px; border-radius: 4px"
+          fit="cover"
+          :src="row.cover"
+        />
+      </template>
+      <template #baseInfo="{ row }">
+        <div class="flex justify-start items-center">
+          <div style="flex: 2">
+            <book-info :row="row"></book-info>
+          </div>
+          <div style="flex: 1.5; margin-left: 15px">
+            <book-other-info :row="row"></book-other-info>
+          </div>
+        </div>
+      </template>
+      <template #stock="{ row }">
+        <book-stock :row="row"></book-stock>
+      </template>
+
+      <template #action="{ row }">
+        <el-button
+          type="primary"
+          link
+          v-permission="'recycle:scanLog:viewLog'"
+          @click="handleViewLog(row)"
+        >
+          [查看详情]
+        </el-button>
+        <el-button
+          color="#192bbe"
+          plain
+          link
+          v-permission="'recycle:scanLog:setParams'"
+          @click="handleSetParams(row)"
+        >
+          [设置独立参数]
+        </el-button>
+        <el-button
+          type="warning"
+          link
+          v-permission="'recycle:scanLog:addBlacklist'"
+          @click="handleOptBlacklist(row)"
+        >
+          [加入黑名单]
+        </el-button>
+        <el-button
+          type="success"
+          link
+          v-permission="'recycle:scanLog:removeBlacklist'"
+          @click="handleOptBlacklist(row)"
+        >
+          [移除黑名单]
+        </el-button>
+        <el-button
+          type="danger"
+          link
+          v-permission="'recycle:scanLog:pauseRecycle'"
+          @click="handleOptRecycle(row)"
+        >
+          [暂停回收]
+        </el-button>
+        <el-button
+          type="success"
+          link
+          v-permission="'recycle:scanLog:startRecycle'"
+          @click="handleOptRecycle(row)"
+        >
+          [开启回收]
+        </el-button>
+        <el-button
+          type="success"
+          link
+          v-permission="'recycle:scanLog:removeBooklist'"
+          @click="handleOptBooklist(row)"
+        >
+          [移除回收书单]
+        </el-button>
+        <el-button
+          type="danger"
+          link
+          v-permission="'recycle:scanLog:addBooklist'"
+          @click="handleOptBooklist(row)"
+        >
+          [加入回收书单]
+        </el-button>
+      </template>
+    </common-table>
+
+    <books-edit ref="editRef"></books-edit>
+    <set-params ref="paramsRef"></set-params>
+    <scan-log ref="logRef"></scan-log>
+  </ele-page>
+</template>
+
+<script setup>
+  import { ref, reactive } from 'vue';
+  import CommonTable from '@/components/CommonPage/CommonTable.vue';
+  import booksEdit from '@/views/data/books/components/books-edit.vue';
+  import bookSearch from '@/views/recycle/components/book-search.vue';
+  import bookInfo from '@/views/recycle/components/book-info.vue';
+  import bookOtherInfo from '@/views/recycle/components/book-other-info.vue';
+  import bookStock from '@/views/recycle/components/book-stock.vue';
+  import setParams from '@/views/recycle/components/set-params.vue';
+  import scanLog from './components/scan-log.vue'
+  import { useDictData } from '@/utils/use-dict-data';
+
+  defineOptions({ name: 'scanLoglist' });
+  const [useStatusDicts] = useDictData(['use_status']);
+
+  const useStatus = ref('1');
+  function handleStatusChange(value) {
+    pageRef.value.reload({ useStatus: value });
+  }
+
+  /** 表格列配置 */
+  const columns = ref([
+    {
+      type: 'selection',
+      columnKey: 'selection',
+      width: 50,
+      align: 'center',
+      fixed: 'left'
+    },
+    {
+      label: '图片',
+      prop: 'cover',
+      width: 120,
+      slot: 'cover'
+    },
+    {
+      label: '信息',
+      prop: 'baseInfo',
+      width: 540,
+      slot: 'baseInfo'
+    },
+    { label: '回收折扣', prop: 'discount' },
+    { label: '预估金额', prop: 'amount' },
+    { label: '扫描次数', prop: 'scanNum' },
+    { label: '总回收数量', prop: 'totalRecycledAmount' },
+    { label: '最后一次扫描时间', prop: 'lastTime' },
+    {
+      columnKey: 'action',
+      label: '操作',
+      width: 150,
+      slot: 'action',
+      fixed: 'right'
+    }
+  ]);
+
+  /** 页面组件实例 */
+  const pageRef = ref(null);
+
+  const pageConfig = reactive({
+    pageUrl: '/book/bookInfo/list',
+    fileName: '回收书单管理',
+    cacheKey: 'scanLoglistTable'
+  });
+
+  //刷新表格
+  function reload(where) {
+    pageRef.value?.reload(where);
+  }
+
+  //编辑
+  const editRef = ref(null);
+  function handleUpdateBook(row) {
+    editRef.value?.handleOpen(row);
+  }
+  //设置参数
+  const paramsRef = ref(null);
+  function handleSetParams(row) {
+    paramsRef.value?.handleOpen(row);
+  }
+
+  //回收书单操作
+  function handleOptBooklist(row) {
+    let selections = row ? [row] : pageRef.value?.getSelections();
+    let ids = selections.map((item) => item.id).join(',');
+    let url = `/book/bookInfo/removeById/${ids}`;
+    let title = row ? '确认移除回收书单?' : '确认加入回收书单?';
+    pageRef.value?.operatBatch({ title, method: 'post', url, row });
+  }
+  //黑名单操作
+  function handleOptBlacklist(row) {
+    let selections = row ? [row] : pageRef.value?.getSelections();
+    let ids = selections.map((item) => item.id).join(',');
+    let url = `/book/bookInfo/removeBlacklist/${ids}`;
+    let title = row ? '确认移除黑名单?' : '确认加入黑名单?';
+    pageRef.value?.operatBatch({ title, method: 'post', url, row });
+  }
+  //回收操作
+  function handleOptRecycle(row) {
+    let selections = row ? [row] : pageRef.value?.getSelections();
+    let ids = selections.map((item) => item.id).join(',');
+    let url = `/book/bookInfo/recycle/${ids}`;
+    let title = '确认回收?';
+    pageRef.value?.operatBatch({ title, method: 'post', url, row });
+  }
+
+  //查看详情
+  const logRef = ref(null);
+  function handleViewLog(row) {
+    logRef.value?.handleOpen(row);
+  }
+</script>

+ 251 - 0
src/views/recycle/stockFullAlert/index.vue

@@ -0,0 +1,251 @@
+<template>
+  <ele-page flex-table>
+    <book-search @search="reload"></book-search>
+
+    <common-table
+      ref="pageRef"
+      :pageConfig="pageConfig"
+      :columns="columns"
+      :tools="false"
+    >
+      <template #toolbar>
+        <el-radio-group @change="handleStatusChange" v-model="useStatus">
+          <el-radio-button label="库存收满预警" value="0" />
+          <el-radio-button label="库存已收满" value="1" />
+        </el-radio-group>
+
+        <span class="ml-8"></span>
+        <el-button
+          type="success"
+          plain
+          v-permission="'recycle:stockFullAlert:batchAddBooklist'"
+          @click="handleOptBooklist()"
+        >
+          加入回收书单
+        </el-button>
+        <el-button
+          type="warning"
+          plain
+          v-permission="'recycle:stockFullAlert:batchRemoveBlacklist'"
+          @click="handleOptBlacklist()"
+        >
+          移除黑名单
+        </el-button>
+        <el-button
+          type="danger"
+          plain
+          v-permission="'recycle:stockFullAlert:batchAddBlacklist'"
+          @click="handleOptBlacklist()"
+        >
+          加黑名单
+        </el-button>
+
+        <el-button
+          type="warning"
+          plain
+          v-permission="'recycle:stockFullAlert:batchPauseRecycle'"
+          @click="handleOptRecycle()"
+        >
+          暂停回收
+        </el-button>
+
+        <el-button
+          type="success"
+          plain
+          v-permission="'recycle:stockFullAlert:batchAddSocial'"
+          @click="handleOptType(1)"
+        >
+          加社科库
+        </el-button>
+        <el-button
+          color="#192bbe"
+          plain
+          v-permission="'recycle:stockFullAlert:batchAddTeach'"
+          @click="handleOptType(2)"
+        >
+          加教材库
+        </el-button>
+        <el-button
+          type="success"
+          plain
+          v-permission="'recycle:stockFullAlert:exprot'"
+          @click="handleExportExcel()"
+        >
+          导出Excel
+        </el-button>
+      </template>
+
+      <template #cover="{ row }">
+        <el-image
+          style="width: 90px; height: 120px; border-radius: 4px"
+          fit="cover"
+          :src="row.cover"
+        />
+      </template>
+      <template #baseInfo="{ row }">
+        <div class="flex justify-start items-center">
+          <div style="flex: 2">
+            <book-info :row="row"></book-info>
+          </div>
+          <div style="flex: 1.5; margin-left: 15px">
+            <book-other-info :row="row"></book-other-info>
+          </div>
+        </div>
+      </template>
+
+      <template #action="{ row }">
+        <el-button
+          type="danger"
+          link
+          v-permission="'recycle:stockFullAlert:delete'"
+          @click="handleDelete(row)"
+        >
+          [删除]
+        </el-button>
+        <el-button
+          type="danger"
+          link
+          v-permission="'recycle:stockFullAlert:deal'"
+          @click="handleDelete(row)"
+        >
+          [去处理]
+        </el-button>
+        <el-button
+          type="warning"
+          link
+          v-permission="'recycle:stockFullAlert:dealLog'"
+          @click="handleDelete(row)"
+        >
+          [处理日志]
+        </el-button>
+        <el-button
+          type="success"
+          link
+          v-permission="'recycle:stockFullAlert:remind'"
+          @click="handleModifyType(row)"
+        >
+          [一个月后提醒]
+        </el-button>
+      </template>
+    </common-table>
+  </ele-page>
+</template>
+
+<script setup>
+  import { ref, reactive } from 'vue';
+  import CommonTable from '@/components/CommonPage/CommonTable.vue';
+  import bookSearch from '@/views/recycle/components/book-search.vue';
+  import bookInfo from '@/views/recycle/components/book-info.vue';
+  import bookOtherInfo from '@/views/recycle/components/book-other-info.vue';
+  import { useDictData } from '@/utils/use-dict-data';
+
+  defineOptions({ name: 'stockFullAlert' });
+  const [useStatusDicts, bookTagDicts] = useDictData([
+    'use_status',
+    'book_tag_ku'
+  ]);
+
+  const useStatus = ref('0');
+  function handleStatusChange(value) {
+    pageRef.value.reload({ useStatus: value });
+  }
+
+  /** 表格列配置 */
+  const columns = ref([
+    {
+      type: 'selection',
+      columnKey: 'selection',
+      width: 50,
+      align: 'center',
+      fixed: 'left'
+    },
+    {
+      label: '图片',
+      prop: 'cover',
+      width: 120,
+      slot: 'cover'
+    },
+    {
+      label: '信息',
+      prop: 'baseInfo',
+      width: 540,
+      slot: 'baseInfo'
+    },
+    { label: '回收折扣', prop: 'discount' },
+    { label: '预估金额', prop: 'price' },
+    { label: '最大回收量', prop: 'maxRecycledAmount' },
+    { label: '当前剩余回收量', prop: 'remainingRecycledAmount' },
+    { label: '预警次数', prop: 'alertNum' },
+    { label: '销量', prop: 'salesVolume' },
+    {
+      columnKey: 'action',
+      label: '操作',
+      width: 120,
+      slot: 'action',
+      fixed: 'right'
+    }
+  ]);
+
+  /** 页面组件实例 */
+  const pageRef = ref(null);
+
+  const pageConfig = reactive({
+    pageUrl: '/book/bookInfo/list',
+    exportUrl: '/book/bookInfo/export',
+    fileName: '库存收满预警',
+    cacheKey: 'stockFullAlertTable'
+  });
+
+  //刷新表格
+  function reload(where) {
+    pageRef.value?.reload(where);
+  }
+
+  //导出excel
+  function handleExportExcel() {
+    pageRef.value?.exportData('库存收满预警');
+  }
+
+  //回收书单操作
+  function handleOptBooklist(row) {
+    let selections = row ? [row] : pageRef.value?.getSelections();
+    let ids = selections.map((item) => item.id).join(',');
+    let url = `/book/bookInfo/removeById/${ids}`;
+    let title = row ? '确认移除回收书单?' : '确认加入回收书单?';
+    pageRef.value?.operatBatch({ title, method: 'post', url, row });
+  }
+  //黑名单操作
+  function handleOptBlacklist(row) {
+    let selections = row ? [row] : pageRef.value?.getSelections();
+    let ids = selections.map((item) => item.id).join(',');
+    let url = `/book/bookInfo/removeBlacklist/${ids}`;
+    let title = row ? '确认移除黑名单?' : '确认加入黑名单?';
+    pageRef.value?.operatBatch({ title, method: 'post', url, row });
+  }
+  //回收操作
+  function handleOptRecycle(row) {
+    let selections = row ? [row] : pageRef.value?.getSelections();
+    let ids = selections.map((item) => item.id).join(',');
+    let url = `/book/bookInfo/recycle/${ids}`;
+    let title = '确认回收?';
+    pageRef.value?.operatBatch({ title, method: 'post', url, row });
+  }
+  //类型操作
+  function handleOptType(type) {
+    let selections = pageRef.value?.getSelections();
+    let ids = selections.map((item) => item.id).join(',');
+    let url = `/book/bookInfo/changeType/${ids}`;
+    let title = type == 1 ? '确认加社科库?' : '确认加教材库?';
+    pageRef.value?.operatBatch({ title, method: 'post', url, row });
+  }
+
+  //删除
+  function handleDelete(row) {
+    pageRef.value?.messageBoxConfirm({
+      message: '确定删除吗?',
+      fetch: () => request.get('/common/exportrecord/deleteRecord?id=' + row.id)
+    });
+  }
+  //修改类型
+  function handleModifyType(row) {}
+</script>