소스 검색

feat(priceWorkbench): 新增价格工作台相关功能模块

新增任务待处理、任务列表、价格审核、库存周转率、任务设置等模块
添加搜索组件、分配弹窗、详情弹窗、日志弹窗等功能组件
实现批量导入、规则生成、历史数据查看等业务功能
ylong 3 일 전
부모
커밋
3f64420bec

+ 113 - 0
src/views/priceWorkbench/priceAudit/components/page-search.vue

@@ -0,0 +1,113 @@
+<!-- 搜索表单 -->
+<template>
+    <ele-card :body-style="{ paddingBottom: '8px' }">
+        <ProSearch
+            :items="formItems"
+            ref="searchRef"
+            @search="search"
+            :initKeys="initKeys"
+        />
+    </ele-card>
+</template>
+
+<script setup>
+    import { reactive, ref, defineEmits, getCurrentInstance } 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: 'input', label: 'ISBN', prop: 'isbn' },
+        { type: 'input', label: '作者', prop: 'author' },
+        { type: 'input', label: '出版社', prop: 'publisher' },
+        {
+            type: 'daterange',
+            label: '出版时间',
+            prop: 'pubDate',
+            keys: ['pubDateStart', 'pubDateEnd'],
+            props: {
+                format: 'YYYY-MM-DD',
+                valueFormat: 'YYYY-MM-DD',
+                onChange: (val) => {
+                    searchRef.value?.setData({
+                        pubDateStart: val && val.length > 0 ? val[0] : '',
+                        pubDateEnd: val && val.length > 0 ? val[1] : ''
+                    });
+                }
+            }
+        },
+        {
+            type: 'inputNumberRange',
+            label: '定价',
+            prop: 'price',
+            keys: ['minPrice', 'maxPrice'],
+            props: {
+                label: '定价',
+                onChange: (val) => {
+                    searchRef.value?.setData({
+                        minPrice: val.min,
+                        maxPrice: val.max
+                    });
+                }
+            }
+        },
+        { type: 'input', label: '处理人', prop: 'handler' },
+        {
+            type: 'dictSelect',
+            label: '图书类型',
+            prop: 'bookTag',
+            props: {
+                code: 'book_tag'
+            }
+        },
+        {
+            type: 'inputNumberRange',
+            label: '回收折扣',
+            prop: 'discount',
+            keys: ['minDiscount', 'maxDiscount'],
+            props: {
+                minAttrs: { min: 0 },
+                maxAttrs: { max: 10 },
+                label: '回收折扣',
+                onChange: (val) => {
+                    searchRef.value?.setData({
+                        minDiscount: val.min,
+                        maxDiscount: val.max
+                    });
+                }
+            }
+        }
+    ]);
+
+    const initKeys = reactive({
+        bookName: '',
+        isbn: '',
+        author: '',
+        publisher: '',
+        pubDateStart: '',
+        pubDateEnd: '',
+        minPrice: void 0,
+        maxPrice: void 0,
+        handler: '',
+        bookTag: '',
+        minDiscount: void 0,
+        maxDiscount: void 0
+    });
+
+    const searchRef = ref(null);
+    /** 搜索 */
+    const search = (data) => {
+        let params = JSON.parse(JSON.stringify(data));
+        delete params.price;
+        delete params.discount;
+        delete params.pubDate;
+        emit('search', params);
+    };
+    
+    defineExpose({
+        reset: () => searchRef.value?.reset(),
+        search: () => searchRef.value?.search()
+    });
+</script>

+ 201 - 0
src/views/priceWorkbench/priceAudit/index.vue

@@ -0,0 +1,201 @@
+<template>
+    <ele-page flex-table>
+        <page-search @search="reload" />
+
+        <common-table ref="pageRef" :pageConfig="pageConfig" :columns="columns" :tools="false" :datasource="datasource">
+            <template #toolbar>
+                <el-radio-group @change="handleStatusChange" v-model="searchType" class="audit-tabs">
+                    <el-radio-button label="全部" value="0" />
+                    <el-radio-button label="待审核" value="1" />
+                    <el-radio-button label="已完成" value="2" />
+                </el-radio-group>
+            </template>
+
+            <template #cover="{ row }">
+                <el-image style="width: 100px; height: 100px; border-radius: 4px" fit="cover" :src="row.cover"
+                    :preview-src-list="[row.cover]" :initial-index="0" preview-teleported />
+            </template>
+
+            <template #info="{ row }">
+                <div class="flex flex-col text-sm">
+                    <div class="mb-1">
+                        <span class="font-bold">书名: </span>{{ row.bookName }}
+                    </div>
+                    <div class="mb-1">
+                        <span class="font-bold">作者: </span>{{ row.author }}
+                    </div>
+                    <div class="mb-1">
+                        <span class="font-bold">ISBN: </span>{{ row.isbn }}
+                    </div>
+                    <div class="mb-1">
+                        <span class="font-bold">出版社: </span>{{ row.publisher }}
+                    </div>
+                    <div class="mb-1">
+                        <span class="font-bold">出版时间: </span>{{ row.pubDate }}
+                    </div>
+                    <div class="mb-1">
+                        <span class="font-bold">所属类型: </span>{{ row.type }}
+                    </div>
+                    <div class="mb-1">
+                        <span class="font-bold">回收状态: </span>{{ row.recycleStatus }}
+                    </div>
+                    <div class="mt-2 border-t pt-2">
+                        <span class="mr-4">定价: {{ row.price }}</span>
+                        <span class="mr-4">回收折扣: {{ row.recycleDiscount }}折 <span
+                                class="text-red-500 text-xs">建议0.5-0.6折</span></span>
+                        <span>回收价格: {{ row.recyclePrice }}</span>
+                    </div>
+                    <div class="mb-1">
+                        <span class="text-green-500 text-xs">谢程娟: 2025-06-15</span>
+                    </div>
+                    <div class="mb-1">
+                        <span class="mr-4">销售价格: {{ row.salesPrice }}</span>
+                        <span class="text-green-500 text-xs">谢程娟: 2025-06-15</span>
+                    </div>
+                    <div class="mb-1">
+                        <span>(已回收数量: {{ row.recycledCount }} 当前库存: {{ row.currentStock }})</span>
+                    </div>
+                </div>
+            </template>
+
+            <template #newPrice="{ row }">
+                <div class="flex flex-col items-center">
+                    <div class="flex items-center text-xl font-bold">
+                        {{ row.newPrice }}
+                        <el-icon class="ml-2 cursor-pointer text-red-500">
+                            <Edit />
+                        </el-icon>
+                    </div>
+                    <el-button size="small" type="info" plain class="mt-1">填入原售价</el-button>
+                </div>
+            </template>
+
+            <template #action="{ row }">
+                <div class="flex flex-col items-center gap-2">
+                    <el-button color="#951d1d" size="small" @click="handleViewUrl(row, 'kw')">查看孔网</el-button>
+                    <el-button color="#f37607" size="small" @click="handleViewUrl(row, 'tb')">查看淘宝</el-button>
+                    <el-button color="#4095e5" size="small">设置独立参数</el-button>
+                    <el-button type="success" size="small">同意</el-button>
+                </div>
+            </template>
+        </common-table>
+    </ele-page>
+</template>
+
+<script setup>
+import { ref, reactive } from 'vue';
+import { Edit } from '@element-plus/icons-vue';
+import CommonTable from '@/components/CommonPage/CommonTable.vue';
+import PageSearch from './components/page-search.vue';
+
+defineOptions({ name: 'PriceAudit' });
+
+// Component Refs
+const pageRef = ref(null);
+const searchType = ref('0');
+
+const columns = ref([
+    { type: 'selection', width: 50, align: 'center', fixed: 'left' },
+    { label: '图片', prop: 'cover', width: 120, slot: 'cover' },
+    { label: '信息', prop: 'info', minWidth: 400, slot: 'info' },
+    { label: '总回收量', prop: 'totalRecycled', width: 110, sortable: true, align: 'center' },
+    { label: '状态', prop: 'status', width: 100, align: 'center' },
+    { label: '分配时间', prop: 'assignTime', width: 160, align: 'center' },
+    { label: '当前库存', prop: 'currentStock', width: 110, sortable: true, align: 'center' },
+    { label: '当前售价', prop: 'salesPrice', width: 100, align: 'center' },
+    { label: '处理人', prop: 'handler', width: 100, align: 'center' },
+    { label: '新售价', prop: 'newPrice', width: 150, slot: 'newPrice', align: 'center' },
+    { label: '操作', prop: 'action', width: 120, slot: 'action', fixed: 'right', align: 'center' }
+]);
+
+const pageConfig = reactive({
+    fileName: '价格审核',
+    cacheKey: 'priceAuditTable',
+    params: {
+        searchType: '0'
+    },
+    rowKey: 'id'
+});
+
+// Mock datasource
+const datasource = ({ page, limit, where, orders }) => {
+    return Promise.resolve({
+        code: 0,
+        msg: 'success',
+        count: 10,
+        data: Array.from({ length: 10 }).map((_, i) => ({
+            id: i + 1,
+            cover: 'https://img1.baidu.com/it/u=2356551695,3062334057&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=707',
+            bookName: '马克思主义基本原理',
+            author: '编写组',
+            isbn: '9787056025415',
+            publisher: '高等教育出版社',
+            pubDate: '2018-06',
+            type: '教材库',
+            recycleStatus: '正在回收',
+            price: 25,
+            recycleDiscount: 0.2,
+            recyclePrice: 0.5,
+            salesPrice: 6.5,
+            recycledCount: 200,
+            currentStock: 75 + i,
+            totalRecycled: 200,
+            status: i % 2 === 0 ? '待审核' : '已审核',
+            assignTime: '2024-06-15 15:06:11',
+            handler: 'zzz',
+            newPrice: 10.55
+        }))
+    });
+};
+
+function reload(where) {
+    pageRef.value?.reload(where);
+}
+
+function handleStatusChange(value) {
+    pageRef.value?.reload({ searchType: value });
+}
+
+//查看淘宝和孔网
+function handleViewUrl(row, type) {
+    let url = '';
+    if (type == 'tb') {
+        url = `https://s.taobao.com/search?q=${row.isbn}`;
+    } else if (type == 'kw') {
+        url = `https://search.kongfz.com/product_result/?key=${row.isbn}&status=0&_stpmt=eyJzZWFyY2hfdHlwZSI6ImFjdGl2ZSJ9`;
+    }
+    window.open(url, '_blank');
+}
+</script>
+
+<style scoped>
+/* 仿照截图中的Tabs样式 */
+:deep(.audit-tabs) .el-radio-button__inner {
+    width: 200px;
+    /* 增加宽度 */
+    border-radius: 0;
+    border: none;
+    border-bottom: 2px solid transparent;
+    background: transparent;
+    box-shadow: none;
+    color: #666;
+}
+
+:deep(.audit-tabs) .el-radio-button__original-radio:checked+.el-radio-button__inner {
+    background-color: #409eff;
+    /* 蓝色背景 */
+    color: white;
+    border-radius: 0;
+    /* 保持方形或自定义圆角 */
+    box-shadow: none;
+}
+
+/* 整体背景可能需要调整,这里仅做基本样式覆盖 */
+.audit-tabs {
+    display: flex;
+    background-color: #fff;
+    border-bottom: 1px solid #eee;
+    margin-bottom: 10px;
+    width: 100%;
+}
+</style>

+ 98 - 0
src/views/priceWorkbench/stockTurnoverRate/components/history-dialog.vue

@@ -0,0 +1,98 @@
+<template>
+    <ele-modal
+        :width="800"
+        title="历史库存周转率"
+        v-model="visible"
+        :body-style="{ padding: '20px' }"
+    >
+        <div style="height: 400px">
+            <v-chart ref="chartRef" :option="option" autoresize />
+        </div>
+    </ele-modal>
+</template>
+
+<script setup>
+    import { ref, reactive, defineExpose } from 'vue';
+    import { useEcharts } from '@/utils/use-echarts';
+    import VChart from 'vue-echarts';
+    import { use } from 'echarts/core';
+    import { CanvasRenderer } from 'echarts/renderers';
+    import { LineChart } from 'echarts/charts';
+    import {
+        GridComponent,
+        TooltipComponent,
+        LegendComponent,
+        TitleComponent
+    } from 'echarts/components';
+
+    use([
+        CanvasRenderer,
+        LineChart,
+        GridComponent,
+        TooltipComponent,
+        LegendComponent,
+        TitleComponent
+    ]);
+
+    const visible = defineModel({ type: Boolean });
+    const chartRef = ref(null);
+
+    useEcharts([chartRef]);
+
+    const option = reactive({
+        tooltip: {
+            trigger: 'axis'
+        },
+        legend: {
+            data: ['历史周转率', '平均库存', '剩余可卖天数', '销量'],
+            bottom: 0
+        },
+        grid: {
+            left: '3%',
+            right: '4%',
+            bottom: '10%',
+            containLabel: true
+        },
+        xAxis: {
+            type: 'category',
+            boundaryGap: false,
+            data: ['09-07', '09-08', '09-10', '09-11', '09-12', '09-13', '09-14']
+        },
+        yAxis: {
+            type: 'value'
+        },
+        series: [
+            {
+                name: '历史周转率',
+                type: 'line',
+                smooth: true,
+                data: [100, 132, 101, 134, 90, 230, 150]
+            },
+            {
+                name: '平均库存',
+                type: 'line',
+                smooth: true,
+                data: [150, 182, 191, 234, 100, 160, 110]
+            },
+            {
+                name: '剩余可卖天数',
+                type: 'line',
+                smooth: true,
+                data: [100, 100, 120, 140, 140, 190, 180]
+            },
+            {
+                name: '销量',
+                type: 'line',
+                smooth: true,
+                data: [200, 130, 145, 145, 230, 105, 180]
+            }
+        ]
+    });
+
+    const setData = (row) => {
+        // Here you would fetch real data based on row.isbn or similar
+        console.log('Viewing history for:', row);
+    };
+
+    defineExpose({ setData });
+</script>

+ 128 - 0
src/views/priceWorkbench/stockTurnoverRate/components/import-dialog.vue

@@ -0,0 +1,128 @@
+<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
+                @click="
+                    downloadOssLink(
+                        'https://shuhi.oss-cn-qingdao.aliyuncs.com/default/search_template.xlsx',
+                        '导入模板'
+                    )
+                "
+                link
+                type="primary"
+                >下载导入模板</el-button
+            >
+        </div>
+
+        <div class="flex flex-col mb-2">
+            <div class="text-sm text-yellow-500">使用说明:</div>
+            <div class="text-sm">1.支持通过导入查询,导出数据结果</div>
+            <div class="text-sm">2.文件ISBN不存在或者错误,会自动过滤掉</div>
+            <div class="text-sm mb-2">3.导入文件第一行需与模版完全一致</div>
+        </div>
+        <div v-loading="loading" class="user-import-upload">
+            <el-upload
+                v-model:file-list="fileList"
+                ref="uploadRef"
+                action=""
+                accept=".xls,.xlsx"
+                :before-upload="doUpload"
+                :auto-upload="false"
+                :limit="1"
+                :on-exceed="handleExceed"
+            >
+                <el-button type="primary" class="mr-2">选择文件</el-button>
+                <ele-text type="placeholder">只能上传 xls、xlsx 文件</ele-text>
+            </el-upload>
+        </div>
+
+        <template #footer>
+            <el-button @click="visible = false">关闭</el-button>
+            <el-button type="primary" @click="handleSumbit" :loading="loading"
+                >导入</el-button
+            >
+        </template>
+    </ele-modal>
+</template>
+
+<script setup>
+    import { ref } from 'vue';
+    import { genFileId, ElMessage } from 'element-plus';
+    import { downloadOssLink } from '@/utils/common';
+    // import request from '@/utils/request'; // Mocking 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);
+    }
+
+    //导入图书 (Mock)
+    async function importBooks(file) {
+        // const formData = new FormData();
+        // formData.append('file', file);
+        // const res = await request.post('/book/bookInfo/exportByFile', formData);
+        return new Promise((resolve) => {
+            setTimeout(() => {
+                resolve({ code: 200, msg: '导入成功' });
+            }, 1000);
+        });
+    }
+
+    /** 提交 */
+    const handleSumbit = () => {
+        if (fileList.value.length === 0) {
+            ElMessage.error('请选择文件');
+            return;
+        }
+        loading.value = true;
+        let file = fileList.value[0];
+        importBooks(file.raw)
+            .then((res) => {
+                loading.value = false;
+                ElMessage.success(res.msg);
+                visible.value = false;
+                emit('done');
+            })
+            .catch((e) => {
+                loading.value = false;
+                ElMessage.error('导入失败');
+            });
+    };
+
+    /** 上传 */
+    const doUpload = (file) => {
+        if (
+            ![
+                'application/vnd.ms-excel',
+                'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
+            ].includes(file.type)
+        ) {
+            ElMessage.error('只能选择 excel 文件');
+            return false;
+        }
+        if (file.size / 1024 / 1024 > 10) {
+            ElMessage.error('大小不能超过 10MB');
+            return false;
+        }
+        return false;
+    };
+</script>

+ 90 - 0
src/views/priceWorkbench/stockTurnoverRate/components/log-dialog.vue

@@ -0,0 +1,90 @@
+<template>
+    <ele-modal
+        :width="800"
+        title="操作记录"
+        v-model="visible"
+        :body-style="{ padding: '20px' }"
+    >
+        <el-table :data="tableData" border style="width: 100%">
+            <el-table-column prop="importDate" label="导入日期" width="180" />
+            <el-table-column prop="operator" label="操作员" width="120" />
+            <el-table-column prop="calcPeriod" label="计算周期" width="120" />
+            <el-table-column prop="turnoverRate" label="库存周转率" width="120" />
+            <el-table-column prop="remainingDays" label="剩余可卖天数" width="120" />
+            <el-table-column prop="sales" label="销量" />
+        </el-table>
+        <div class="mt-4 flex justify-end">
+             <el-pagination
+                background
+                layout="total, sizes, prev, pager, next, jumper"
+                :total="total"
+                v-model:current-page="currentPage"
+                v-model:page-size="pageSize"
+                :page-sizes="[10, 20, 50, 100]"
+            />
+        </div>
+        <template #footer>
+            <el-button type="primary" @click="visible = false">确定</el-button>
+            <el-button @click="visible = false">取消</el-button>
+        </template>
+    </ele-modal>
+</template>
+
+<script setup>
+    import { ref, defineExpose } from 'vue';
+
+    const visible = defineModel({ type: Boolean });
+    const currentPage = ref(1);
+    const pageSize = ref(20);
+    const total = ref(100);
+
+    const tableData = ref([
+        {
+            importDate: '2025-06-15 15:00:00',
+            operator: 'xxx',
+            calcPeriod: '30天',
+            turnoverRate: '0.5',
+            remainingDays: '100',
+            sales: '200'
+        },
+         {
+            importDate: '2025-06-15 15:00:00',
+            operator: 'aaa',
+            calcPeriod: '30天',
+            turnoverRate: '0.5',
+            remainingDays: '100',
+            sales: '200'
+        },
+         {
+            importDate: '2025-06-15 15:00:00',
+            operator: 'ddd',
+            calcPeriod: '30天',
+            turnoverRate: '0.5',
+            remainingDays: '100',
+            sales: '200'
+        },
+         {
+            importDate: '2025-06-15 15:00:00',
+            operator: 'eee',
+            calcPeriod: '30天',
+            turnoverRate: '0.5',
+            remainingDays: '100',
+            sales: '200'
+        },
+         {
+            importDate: '2025-06-15 15:00:00',
+            operator: 'eee',
+            calcPeriod: '30天',
+            turnoverRate: '0.5',
+            remainingDays: '100',
+            sales: '200'
+        }
+    ]);
+
+    const handleOpen = (row) => {
+        visible.value = true;
+        // Fetch data for row
+    };
+
+    defineExpose({ handleOpen });
+</script>

+ 204 - 0
src/views/priceWorkbench/stockTurnoverRate/components/page-search.vue

@@ -0,0 +1,204 @@
+<!-- 搜索表单 -->
+<template>
+    <ele-card :body-style="{ paddingBottom: '8px' }">
+        <ProSearch
+            :items="formItems"
+            ref="searchRef"
+            @search="search"
+            :initKeys="initKeys"
+        />
+    </ele-card>
+</template>
+
+<script setup>
+    import { reactive, ref, defineEmits, getCurrentInstance } 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: 'input', label: 'ISBN', prop: 'isbn' },
+        {
+            type: 'dictSelect',
+            label: '图书类型',
+            prop: 'bookTag',
+            props: {
+                code: 'book_tag'
+            }
+        },
+        {
+            type: 'inputNumberRange',
+            label: '库存周转率',
+            prop: 'turnoverRate',
+            keys: ['minTurnoverRate', 'maxTurnoverRate'],
+            props: {
+                label: '库存周转率',
+                onChange: (val) => {
+                    searchRef.value?.setData({
+                        minTurnoverRate: val.min,
+                        maxTurnoverRate: val.max
+                    });
+                }
+            }
+        },
+        {
+            type: 'select',
+            label: '回收状态',
+            prop: 'recycleStatus',
+            options: [
+                { label: '正在回收', value: 1 },
+                { label: '暂停回收', value: 2 },
+                { label: '未回收', value: 0 }
+            ]
+        },
+        {
+            type: 'inputNumberRange',
+            label: '销量',
+            prop: 'sales',
+            keys: ['minSales', 'maxSales'],
+            props: {
+                label: '销量',
+                onChange: (val) => {
+                    searchRef.value?.setData({
+                        minSales: val.min,
+                        maxSales: val.max
+                    });
+                }
+            }
+        },
+        {
+            type: 'select',
+            label: '计算周期',
+            prop: 'calcPeriod',
+            options: [
+                { label: '7天', value: 7 },
+                { label: '15天', value: 15 },
+                { label: '30天', value: 30 }
+            ]
+        },
+        {
+            type: 'inputNumberRange',
+            label: '回收折扣',
+            prop: 'discount',
+            keys: ['minDiscount', 'maxDiscount'],
+            props: {
+                label: '回收折扣',
+                onChange: (val) => {
+                    searchRef.value?.setData({
+                        minDiscount: val.min,
+                        maxDiscount: val.max
+                    });
+                }
+            }
+        },
+        {
+            type: 'inputNumberRange',
+            label: '剩余可卖天数',
+            prop: 'remainingDays',
+            keys: ['minRemainingDays', 'maxRemainingDays'],
+            props: {
+                label: '剩余可卖天数',
+                onChange: (val) => {
+                    searchRef.value?.setData({
+                        minRemainingDays: val.min,
+                        maxRemainingDays: val.max
+                    });
+                }
+            }
+        },
+        {
+            type: 'inputNumberRange',
+            label: '库存',
+            prop: 'stock',
+            keys: ['minStock', 'maxStock'],
+            props: {
+                label: '库存',
+                onChange: (val) => {
+                    searchRef.value?.setData({
+                        minStock: val.min,
+                        maxStock: val.max
+                    });
+                }
+            }
+        },
+        {
+            type: 'select',
+            label: '单个订单多本回收',
+            prop: 'multiRecycle',
+            options: [
+                { label: '是', value: 1 },
+                { label: '否', value: 0 }
+            ]
+        },
+        {
+            type: 'daterange',
+            label: '价格变动',
+            prop: 'priceChangeTime',
+            keys: ['priceChangeStartTime', 'priceChangeEndTime'],
+            props: {
+                format: 'YYYY-MM-DD',
+                valueFormat: 'YYYY-MM-DD',
+                onChange: (val) => {
+                    searchRef.value?.setData({
+                        priceChangeStartTime: val && val.length > 0 ? val[0] : '',
+                        priceChangeEndTime: val && val.length > 0 ? val[1] : ''
+                    });
+                }
+            }
+        },
+        {
+            type: 'inputNumberRange',
+            label: '价格',
+            prop: 'price',
+            keys: ['minPrice', 'maxPrice'],
+            props: {
+                label: '价格',
+                onChange: (val) => {
+                    searchRef.value?.setData({
+                        minPrice: val.min,
+                        maxPrice: val.max
+                    });
+                }
+            }
+        }
+    ]);
+
+    const initKeys = reactive({
+        bookName: '',
+        isbn: '',
+        bookTag: '',
+        minTurnoverRate: void 0,
+        maxTurnoverRate: void 0,
+        recycleStatus: '',
+        minSales: void 0,
+        maxSales: void 0,
+        calcPeriod: '',
+        minDiscount: void 0,
+        maxDiscount: void 0,
+        minRemainingDays: void 0,
+        maxRemainingDays: void 0,
+        minStock: void 0,
+        maxStock: void 0,
+        multiRecycle: '',
+        priceChangeStartTime: '',
+        priceChangeEndTime: '',
+        minPrice: void 0,
+        maxPrice: void 0
+    });
+
+    const searchRef = ref(null);
+    /** 搜索 */
+    const search = (data) => {
+        let params = JSON.parse(JSON.stringify(data));
+        delete params.turnoverRate;
+        delete params.sales;
+        delete params.discount;
+        delete params.remainingDays;
+        delete params.stock;
+        delete params.priceChangeTime;
+        delete params.price;
+        emit('search', params);
+    };
+</script>

+ 32 - 0
src/views/priceWorkbench/stockTurnoverRate/components/rule-dialog.vue

@@ -0,0 +1,32 @@
+<template>
+    <ele-modal
+        :width="400"
+        title="生成任务规则"
+        v-model="visible"
+        :body-style="{ padding: '20px' }"
+    >
+        <div class="mb-4">
+            <div class="mb-2 text-base font-bold">1、60天没有更新价格</div>
+            <div class="mb-4 text-base font-bold">2、回收折扣0.5-0.6</div>
+            <el-checkbox v-model="priority" label="是否将其他规则任务中的数据优先处理" />
+        </div>
+        <template #footer>
+            <el-button @click="visible = false">取消</el-button>
+            <el-button type="success" @click="handleGenerate">生成规则</el-button>
+        </template>
+    </ele-modal>
+</template>
+
+<script setup>
+    import { ref } from 'vue';
+    import { ElMessage } from 'element-plus';
+
+    const visible = defineModel({ type: Boolean });
+    const priority = ref(true);
+
+    const handleGenerate = () => {
+        // Mock generation
+        ElMessage.success('规则生成成功');
+        visible.value = false;
+    };
+</script>

+ 188 - 0
src/views/priceWorkbench/stockTurnoverRate/index.vue

@@ -0,0 +1,188 @@
+<template>
+    <ele-page flex-table>
+        <page-search @search="reload" />
+
+        <common-table
+            ref="pageRef"
+            :pageConfig="pageConfig"
+            :columns="columns"
+            :tools="false"
+            :datasource="datasource"
+        >
+            <template #toolbar>
+                <el-button type="warning" @click="importVisible = true">
+                    <el-icon class="mr-1"><Upload /></el-icon>
+                    批量导入
+                </el-button>
+                <el-button type="danger" plain @click="ruleVisible = true">
+                    <el-icon class="mr-1"><DocumentAdd /></el-icon>
+                    生成规则
+                </el-button>
+            </template>
+
+            <template #cover="{ row }">
+                <el-image
+                    style="width: 80px; height: 110px; border-radius: 4px"
+                    fit="cover"
+                    :src="row.cover"
+                    :preview-src-list="[row.cover]"
+                    :initial-index="0"
+                    preview-teleported
+                />
+            </template>
+
+            <template #info="{ row }">
+                <div class="flex flex-col text-sm">
+                    <div class="mb-1">
+                        <span class="font-bold">书名: </span>{{ row.bookName }}
+                    </div>
+                    <div class="mb-1">
+                        <span class="font-bold">作者: </span>{{ row.author }}
+                    </div>
+                    <div class="mb-1">
+                        <span class="font-bold">ISBN: </span>{{ row.isbn }}
+                    </div>
+                    <div class="mb-1">
+                        <span class="font-bold">出版社: </span>{{ row.publisher }}
+                    </div>
+                    <div class="mb-1">
+                        <span class="font-bold">出版时间: </span>{{ row.pubDate }}
+                    </div>
+                    <div class="mb-1">
+                        <span class="font-bold">所属类型: </span>{{ row.type }}
+                    </div>
+                    <div class="mb-1">
+                        <span class="font-bold">回收状态: </span>{{ row.recycleStatus }}
+                    </div>
+                    <div class="mt-2 border-t pt-2">
+                        <span class="mr-4">定价: {{ row.price }}</span>
+                        <span class="mr-4">回收折扣: {{ row.recycleDiscount }}折 <span class="text-red-500 text-xs">建议0.5-0.6折</span></span>
+                        <span>回收价格: {{ row.recyclePrice }}</span>
+                    </div>
+                    <div class="mb-1">
+                        <span class="mr-4">销售价格: {{ row.salesPrice }} <el-icon class="cursor-pointer text-blue-500"><Edit /></el-icon></span>
+                        <span class="text-green-500 text-xs">谢程娟: 2025-06-15</span>
+                    </div>
+                     <div class="mb-1">
+                        <span>(已回收数量: {{ row.recycledCount }} 当前库存: {{ row.currentStock }})</span>
+                    </div>
+                </div>
+            </template>
+
+            <template #turnoverRate="{ row }">
+                <div class="flex items-center">
+                    <el-tag :type="row.turnoverType === '年' ? 'primary' : 'danger'" effect="plain" class="mr-1">
+                        {{ row.turnoverType }}
+                    </el-tag>
+                    <span>{{ row.turnoverRate }}</span>
+                    <el-icon class="ml-2 cursor-pointer text-blue-500 text-lg" @click="handleHistory(row)">
+                        <DataLine />
+                    </el-icon>
+                </div>
+            </template>
+
+            <template #action="{ row }">
+                 <div class="flex flex-col items-start gap-1">
+                    <el-button link type="primary" @click="handleHistory(row)">[历史周转率]</el-button>
+                    <el-button link type="primary" @click="handleLog(row)">[操作记录]</el-button>
+                </div>
+            </template>
+        </common-table>
+
+        <import-dialog v-model="importVisible" @done="reload" />
+        <rule-dialog v-model="ruleVisible" />
+        <history-dialog v-model="historyVisible" ref="historyRef" />
+        <log-dialog v-model="logVisible" ref="logRef" />
+    </ele-page>
+</template>
+
+<script setup>
+    import { ref, reactive } from 'vue';
+    import { Upload, DocumentAdd, Edit, DataLine } from '@element-plus/icons-vue';
+    import CommonTable from '@/components/CommonPage/CommonTable.vue';
+    import PageSearch from './components/page-search.vue';
+    import ImportDialog from './components/import-dialog.vue';
+    import RuleDialog from './components/rule-dialog.vue';
+    import HistoryDialog from './components/history-dialog.vue';
+    import LogDialog from './components/log-dialog.vue';
+
+    defineOptions({ name: 'StockTurnoverRate' });
+
+    // Component Refs
+    const pageRef = ref(null);
+    const historyRef = ref(null);
+    const logRef = ref(null);
+
+    // Dialog Visibilities
+    const importVisible = ref(false);
+    const ruleVisible = ref(false);
+    const historyVisible = ref(false);
+    const logVisible = ref(false);
+
+    const columns = ref([
+        { type: 'selection', width: 50, align: 'center', fixed: 'left' },
+        { label: '图片', prop: 'cover', width: 100, slot: 'cover' },
+        { label: '信息', prop: 'info', minWidth: 400, slot: 'info' },
+        { label: '库存周转率', prop: 'turnoverRate', width: 150, slot: 'turnoverRate', sortable: true },
+        { label: '剩余可卖天数', prop: 'remainingDays', width: 120, sortable: true },
+        { label: '销量', prop: 'sales', width: 100, sortable: true },
+        { label: '期初库存', prop: 'initialStock', width: 100, sortable: true },
+        { label: '期末库存', prop: 'endingStock', width: 100, sortable: true },
+        { label: '平均库存', prop: 'averageStock', width: 100, sortable: true },
+        { label: '操作', prop: 'action', width: 120, slot: 'action', fixed: 'right' }
+    ]);
+
+    const pageConfig = reactive({
+        fileName: '库存周转率',
+        cacheKey: 'stockTurnoverTable',
+        params: {},
+        rowKey: 'id'
+    });
+    
+    // Mock datasource
+    const datasource = ({ page, limit, where, orders }) => {
+        return Promise.resolve({
+            code: 0,
+            msg: 'success',
+            count: 10,
+            data: Array.from({ length: 10 }).map((_, i) => ({
+                id: i + 1,
+                cover: 'https://img1.baidu.com/it/u=2356551695,3062334057&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=707',
+                bookName: '马克思主义基本原理',
+                author: '编写组',
+                isbn: '9787056025415',
+                publisher: '高等教育出版社',
+                pubDate: '2018-06',
+                type: '教材库',
+                recycleStatus: '正在回收',
+                price: 25,
+                recycleDiscount: 0.2,
+                recyclePrice: 0.5,
+                salesPrice: 6.5,
+                recycledCount: 200,
+                currentStock: 75,
+                turnoverRate: 200,
+                turnoverType: i % 2 === 0 ? '年' : '半',
+                remainingDays: 55,
+                sales: 200,
+                initialStock: 200,
+                endingStock: 200,
+                averageStock: 50
+            }))
+        });
+    };
+
+    function reload(where) {
+        pageRef.value?.reload(where);
+    }
+
+    function handleHistory(row) {
+        historyVisible.value = true;
+        historyRef.value?.setData(row);
+    }
+
+    function handleLog(row) {
+        logVisible.value = true;
+        logRef.value?.handleOpen(row);
+    }
+</script>

+ 80 - 0
src/views/priceWorkbench/taskList/components/assign-dialog.vue

@@ -0,0 +1,80 @@
+<template>
+    <ele-modal v-model="visible" title="分配任务" width="500px">
+        <el-form ref="formRef" :model="form" :rules="rules" label-width="120px">
+            <el-form-item label="生成数量" prop="quantity">
+                <el-input v-model="form.quantity" disabled />
+            </el-form-item>
+            <el-form-item label="指派人" prop="assignee">
+                <template #label>
+                    <span>
+                        指派人
+                        <el-tooltip content="数据 角色的人 都可以选择" placement="top">
+                            <el-icon><QuestionFilled /></el-icon>
+                        </el-tooltip>
+                    </span>
+                </template>
+                <el-select v-model="form.assignee" placeholder="请选择指派人" style="width: 100%">
+                    <el-option label="张三" value="zhangsan" />
+                    <el-option label="李四" value="lisi" />
+                    <el-option label="王五" value="wangwu" />
+                </el-select>
+            </el-form-item>
+            <el-form-item label="计划完成时间" prop="scheduledTime">
+                <el-date-picker
+                    v-model="form.scheduledTime"
+                    type="date"
+                    placeholder="选择日期"
+                    value-format="YYYY-MM-DD"
+                    style="width: 100%"
+                />
+            </el-form-item>
+        </el-form>
+        <template #footer>
+            <el-button @click="visible = false">取消</el-button>
+            <el-button type="primary" @click="handleSubmit">确定</el-button>
+        </template>
+    </ele-modal>
+</template>
+
+<script setup>
+import { ref, reactive } from 'vue';
+import { QuestionFilled } from '@element-plus/icons-vue';
+import request from '@/utils/request';
+import { ElMessage } from 'element-plus';
+
+const emit = defineEmits(['success']);
+const visible = ref(false);
+const formRef = ref();
+const form = reactive({
+    id: null,
+    quantity: 0,
+    assignee: '',
+    scheduledTime: ''
+});
+
+const rules = {
+    assignee: [{ required: true, message: '请选择指派人', trigger: 'change' }],
+    scheduledTime: [{ required: true, message: '请选择计划完成时间', trigger: 'change' }]
+};
+
+const handleOpen = (row) => {
+    form.id = row.id;
+    form.quantity = row.quantity || 2000; // Mock data or from row
+    form.assignee = '';
+    form.scheduledTime = '';
+    visible.value = true;
+};
+
+const handleSubmit = async () => {
+    await formRef.value.validate();
+    // TODO: Replace with actual API endpoint
+    // const res = await request.post('/priceWorkbench/task/assign', form);
+    // if (res.data.code === 200) {
+        ElMessage.success('分配成功');
+        visible.value = false;
+        emit('success');
+    // }
+};
+
+defineExpose({ handleOpen });
+</script>

+ 81 - 0
src/views/priceWorkbench/taskList/components/details-dialog.vue

@@ -0,0 +1,81 @@
+<template>
+    <ele-modal v-model="visible" title="查看详情" width="1000px">
+        <el-table :data="tableData" border stripe>
+            <el-table-column prop="subTaskId" label="任务ID" width="100" />
+            <el-table-column prop="handler" label="处理人" />
+            <el-table-column prop="quantity" label="任务数量" />
+            <el-table-column prop="completedQuantity" label="已完成数量" />
+            <el-table-column prop="progress" label="完成进度" />
+            <el-table-column prop="scheduledTime" label="计划完成时间" width="120" />
+            <el-table-column prop="completionTime" label="完成时间" width="160" />
+            <el-table-column label="操作" width="100" fixed="right">
+                <template #default="{ row }">
+                    <el-popconfirm 
+                        title="确定要撤销任务吗?撤销后无法重启!"
+                        @confirm="handleRevoke(row)"
+                    >
+                        <template #reference>
+                            <el-button link type="primary">
+                                [撤销任务]
+                                <el-icon class="el-icon--right"><Lightning /></el-icon>
+                            </el-button>
+                        </template>
+                    </el-popconfirm>
+                </template>
+            </el-table-column>
+        </el-table>
+        <template #footer>
+            <el-button type="primary" @click="visible = false">关闭</el-button>
+        </template>
+    </ele-modal>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import { Lightning } from '@element-plus/icons-vue';
+import { ElMessage } from 'element-plus';
+
+const visible = ref(false);
+const tableData = ref([]);
+
+const handleOpen = (row) => {
+    // Mock data based on row
+    tableData.value = [
+        {
+            subTaskId: '565632',
+            handler: 'zzz',
+            quantity: 500,
+            completedQuantity: 300,
+            progress: '60%',
+            scheduledTime: '2025-06-15',
+            completionTime: ''
+        },
+        {
+            subTaskId: '565632',
+            handler: 'zzz',
+            quantity: 500,
+            completedQuantity: 300,
+            progress: '60%',
+            scheduledTime: '2025-06-15',
+            completionTime: ''
+        },
+        {
+            subTaskId: '565632',
+            handler: 'zzz',
+            quantity: 500,
+            completedQuantity: 500,
+            progress: '100%',
+            scheduledTime: '2025-06-15',
+            completionTime: '2025-06-15 15:00:00'
+        }
+    ];
+    visible.value = true;
+};
+
+const handleRevoke = (row) => {
+    // Logic to revoke sub-task
+    ElMessage.success('撤销成功');
+};
+
+defineExpose({ handleOpen });
+</script>

+ 34 - 0
src/views/priceWorkbench/taskList/components/log-dialog.vue

@@ -0,0 +1,34 @@
+<template>
+    <ele-modal v-model="visible" title="操作日志" width="800px">
+        <el-table :data="tableData" border stripe>
+            <el-table-column prop="operator" label="操作员" width="120" />
+            <el-table-column prop="operationTime" label="操作时间" width="180" />
+            <el-table-column prop="description" label="描述" />
+        </el-table>
+        <template #footer>
+            <el-button type="primary" @click="visible = false">确定</el-button>
+            <el-button @click="visible = false">取消</el-button>
+        </template>
+    </ele-modal>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+
+const visible = ref(false);
+const tableData = ref([]);
+
+const handleOpen = (row) => {
+    // Mock data
+    tableData.value = [
+        { operator: 'zzz', operationTime: '2025-06-15 15:00:00', description: '生成任务' },
+        { operator: 'aaa', operationTime: '2025-06-15 15:00:00', description: '分配任务给aaa、bbb、ccc,计划完成时间2026-01-10' },
+        { operator: 'ddd', operationTime: '2025-06-15 15:00:00', description: '完成子任务' },
+        { operator: 'aaa', operationTime: '2025-06-15 15:00:00', description: '完成子任务' },
+        { operator: 'eee', operationTime: '2025-06-15 15:00:00', description: '完成子任务' }
+    ];
+    visible.value = true;
+};
+
+defineExpose({ handleOpen });
+</script>

+ 34 - 0
src/views/priceWorkbench/taskList/components/page-search.vue

@@ -0,0 +1,34 @@
+<template>
+    <ele-card :body-style="{ paddingBottom: '8px' }">
+        <ProSearch :items="formItems" @search="search" />
+    </ele-card>
+</template>
+
+<script setup>
+import { computed, defineEmits } from 'vue';
+import ProSearch from '@/components/CommonPage/ProSearch2.vue';
+
+const emit = defineEmits(['search']);
+
+const formItems = computed(() => {
+    return [
+        { type: 'input', label: '书名', prop: 'bookName' },
+        { type: 'input', label: 'ISBN', prop: 'isbn' },
+        {
+            type: 'select',
+            label: '任务状态',
+            prop: 'status',
+            options: [
+                { label: '待分配', value: 1 },
+                { label: '待作业', value: 2 },
+                { label: '作业中', value: 3 },
+                { label: '已完成', value: 4 },
+                { label: '已撤销', value: 5 }
+            ]
+        },
+        { type: 'input', label: '操作员', prop: 'operator' }
+    ];
+});
+
+const search = (data) => emit('search', data);
+</script>

+ 181 - 0
src/views/priceWorkbench/taskList/index.vue

@@ -0,0 +1,181 @@
+<template>
+    <ele-page flex-table>
+        <!-- Search Bar -->
+        <page-search @search="reload" />
+
+        <!-- Stats Cards -->
+        <div class="stats-container mb-4">
+            <el-row :gutter="20">
+                <el-col :span="6" v-for="(stat, index) in stats" :key="index">
+                    <div class="stat-card">
+                        <div class="stat-value">{{ stat.value }}</div>
+                        <div class="stat-label">
+                            {{ stat.label }}
+                            <el-tooltip v-if="stat.tooltip" :content="stat.tooltip" placement="top">
+                                <el-icon class="ml-1">
+                                    <QuestionFilled />
+                                </el-icon>
+                            </el-tooltip>
+                        </div>
+                    </div>
+                </el-col>
+            </el-row>
+        </div>
+
+        <!-- Main Table -->
+        <common-table ref="pageRef" :pageConfig="pageConfig" :columns="columns">
+            <!-- Custom Columns -->
+            <template #operator="{ row }">
+                <span>
+                    {{ row.operator }}
+                    <el-tooltip content="谁生成的任务" placement="top">
+                        <el-icon class="ml-1">
+                            <QuestionFilled />
+                        </el-icon>
+                    </el-tooltip>
+                </span>
+            </template>
+
+            <template #action="{ row }">
+                <div class="action-buttons">
+                    <el-button link type="primary" @click="handleAssign(row)">
+                        [分配任务]
+                        <el-icon class="el-icon--right">
+                            <Lightning />
+                        </el-icon>
+                    </el-button>
+                    <el-button link type="primary" @click="handleView(row)">
+                        [查看详情]
+                        <el-icon class="el-icon--right">
+                            <Lightning />
+                        </el-icon>
+                    </el-button>
+                    <el-popconfirm title="确定要撤销任务吗?撤销后无法重启!" @confirm="handleRevoke(row)">
+                        <template #reference>
+                            <span class="inline-block">
+                                <el-tooltip content="只有待分配的任务可以撤销" placement="top" :disabled="row.status === '待分配'">
+                                    <el-button link type="primary" :disabled="row.status !== '待分配'">
+                                        [撤销任务]
+                                        <el-icon class="el-icon--right">
+                                            <Lightning />
+                                        </el-icon>
+                                    </el-button>
+                                </el-tooltip>
+                            </span>
+                        </template>
+                    </el-popconfirm>
+                    <el-button link type="primary" @click="handleLog(row)">
+                        [操作日志]
+                        <el-icon class="el-icon--right">
+                            <Lightning />
+                        </el-icon>
+                    </el-button>
+                </div>
+            </template>
+        </common-table>
+
+        <!-- Dialogs -->
+        <assign-dialog ref="assignDialogRef" @success="reload" />
+        <details-dialog ref="detailsDialogRef" />
+        <log-dialog ref="logDialogRef" />
+    </ele-page>
+</template>
+
+<script setup>
+import { ref, reactive } from 'vue';
+import { QuestionFilled, Lightning } from '@element-plus/icons-vue';
+import CommonTable from '@/components/CommonPage/CommonTable.vue';
+import PageSearch from './components/page-search.vue';
+import AssignDialog from './components/assign-dialog.vue';
+import DetailsDialog from './components/details-dialog.vue';
+import LogDialog from './components/log-dialog.vue';
+import { ElMessage } from 'element-plus';
+
+defineOptions({ name: 'TaskList' });
+
+// Stats Data
+const stats = ref([
+    { label: '待分配任务', value: 56666 },
+    { label: '待作业任务', value: 56666 },
+    { label: '总任务进度', value: '60%', tooltip: '已完成的任务不计算在内' }
+]);
+
+// Table Config
+const pageConfig = reactive({
+    pageUrl: '/priceWorkbench/task/list', // Replace with actual API
+    rowKey: 'id',
+    mock: true // Enable mock if no backend
+});
+
+const columns = ref([
+    { label: '任务编号', prop: 'taskNo', minWidth: 100 },
+    { label: '任务数量', prop: 'quantity', minWidth: 100 },
+    { label: '任务状态', prop: 'status', minWidth: 100 },
+    { label: '任务描述', prop: 'description', minWidth: 200, showOverflowTooltip: true },
+    { label: '任务生成时间', prop: 'createTime', minWidth: 160 },
+    { label: '操作员', prop: 'operator', slot: 'operator', minWidth: 100 },
+    { label: '操作', slot: 'action', fixed: 'right', width: 340 }
+]);
+
+// Refs
+const pageRef = ref(null);
+const assignDialogRef = ref(null);
+const detailsDialogRef = ref(null);
+const logDialogRef = ref(null);
+
+// Actions
+const reload = (params) => pageRef.value?.reload(params);
+
+const handleAssign = (row) => {
+    assignDialogRef.value?.handleOpen(row);
+};
+
+const handleView = (row) => {
+    detailsDialogRef.value?.handleOpen(row);
+};
+
+const handleRevoke = (row) => {
+    // API call to revoke
+    ElMessage.success('撤销成功');
+    reload();
+};
+
+const handleLog = (row) => {
+    logDialogRef.value?.handleOpen(row);
+};
+</script>
+
+<style lang="scss" scoped>
+.stats-container {
+    .stat-card {
+        background-color: #f5f5f5;
+        padding: 20px;
+        text-align: center;
+        border-radius: 4px;
+
+        .stat-value {
+            font-size: 24px;
+            font-weight: bold;
+            margin-bottom: 8px;
+            color: #333;
+        }
+
+        .stat-label {
+            color: #666;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+        }
+    }
+}
+
+.action-buttons {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 8px;
+
+    .el-button {
+        margin-left: 0 !important;
+    }
+}
+</style>

+ 0 - 0
src/views/priceWorkbench/taskPending/index.vue


+ 212 - 0
src/views/priceWorkbench/taskSettings/index.vue

@@ -0,0 +1,212 @@
+<template>
+    <ele-page>
+        <div class="task-settings-container">
+            <el-row :gutter="20">
+                <el-col :span="8" v-for="rule in rules" :key="rule.id">
+                    <div class="rule-card">
+                        <div class="card-header">
+                            <span class="rule-title">{{ rule.title }}</span>
+                            <el-icon class="delete-icon" @click="handleDelete(rule)">
+                                <Delete />
+                            </el-icon>
+                        </div>
+
+                        <div class="card-content">
+                            <div class="rule-desc-section">
+                                <div class="label">规则描述:</div>
+                                <div class="desc-list">
+                                    <div v-for="(desc, index) in rule.description" :key="index" class="desc-item">
+                                        {{ index + 1 }}、{{ desc }}
+                                    </div>
+                                </div>
+                            </div>
+
+                            <div class="stats-text">
+                                {{ rule.stats }}
+                            </div>
+
+                            <div class="time-info">
+                                <div>规则新建时间:{{ rule.createTime }}</div>
+                                <div>规则更新时间:{{ rule.updateTime }}</div>
+                            </div>
+
+                            <div class="action-area">
+                                <el-button type="success" v-if="rule.hasTask" plain class="action-btn">更新任务</el-button>
+                                <el-button v-else type="success" plain class="action-btn">一键生成任务</el-button>
+                            </div>
+                        </div>
+                    </div>
+                </el-col>
+            </el-row>
+        </div>
+    </ele-page>
+</template>
+
+<script setup>
+import { ref } from 'vue';
+import { Delete } from '@element-plus/icons-vue';
+import { ElMessageBox, ElMessage } from 'element-plus';
+
+defineOptions({ name: 'TaskSettings' });
+
+const rules = ref([
+    {
+        id: 1,
+        title: '规则一',
+        description: ['60天没有更新过的售价', '销售价格大于4', '教材'],
+        stats: '约564555条 (5条新加入, 10条正在处理)',
+        createTime: '2025-06-15 15:00:00',
+        updateTime: '2025-06-15 15:00:00',
+        hasTask: true,
+    },
+    {
+        id: 2,
+        title: '规则二',
+        description: ['库存周转率0.01', '库存大于10'],
+        stats: '约564555条 (5条新加入, 10条正在处理)',
+        createTime: '2025-06-15 15:00:00',
+        updateTime: '2025-06-15 15:00:00',
+        hasTask: false,
+    },
+    {
+        id: 3,
+        title: '规则三',
+        description: ['回收折扣大于等于0.8', '多本回收'],
+        stats: '约564555条 (5条新加入, 10条正在处理)',
+        createTime: '2025-06-15 15:00:00',
+        updateTime: '2025-06-15 15:00:00',
+        hasTask: false,
+    },
+]);
+
+const handleDelete = (rule) => {
+    ElMessageBox.confirm(
+        '规则删除后,该规则下的任务将被释放,确定要删除吗?',
+        '温馨提示!',
+        {
+            confirmButtonText: '删除',
+            cancelButtonText: '取消',
+            type: 'warning',
+            confirmButtonClass: 'el-button--danger',
+        }
+    )
+        .then(() => {
+            // TODO: Call API to delete rule
+            rules.value = rules.value.filter(item => item.id !== rule.id);
+            ElMessage({
+                type: 'success',
+                message: '删除成功',
+            });
+        })
+        .catch(() => {
+            // Cancelled
+        });
+};
+</script>
+
+<style lang="scss" scoped>
+.task-settings-container {
+    padding: 20px;
+}
+
+.rule-card {
+    background-color: #f5f5f5;
+    border-radius: 4px;
+    padding: 20px;
+    position: relative;
+    height: 100%;
+    min-height: 350px;
+    display: flex;
+    flex-direction: column;
+
+    .card-header {
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+        margin-bottom: 20px;
+
+        .rule-title {
+            font-size: 16px;
+            font-weight: bold;
+            color: #333;
+        }
+
+        .delete-icon {
+            cursor: pointer;
+            color: #909399;
+            font-size: 18px;
+
+            &:hover {
+                color: #f56c6c;
+            }
+        }
+    }
+
+    .card-content {
+        flex: 1;
+        display: flex;
+        flex-direction: column;
+
+        .rule-desc-section {
+            margin-bottom: 20px;
+
+            .label {
+                font-weight: bold;
+                margin-bottom: 8px;
+                color: #333;
+            }
+
+            .desc-list {
+                .desc-item {
+                    color: #606266;
+                    line-height: 1.8;
+                    font-size: 14px;
+                }
+            }
+        }
+
+        .stats-text {
+            color: #67c23a;
+            margin-bottom: 15px;
+            font-size: 14px;
+        }
+
+        .time-info {
+            color: #333;
+            font-size: 14px;
+            line-height: 1.6;
+            margin-bottom: 30px;
+        }
+
+        .action-area {
+            margin-top: auto;
+            text-align: center;
+
+            .action-btn {
+                width: 140px;
+                background-color: #e1f3d8;
+                border-color: #e1f3d8;
+                color: #67c23a;
+
+                &:hover {
+                    background-color: #c2e7b0;
+                    border-color: #c2e7b0;
+                }
+            }
+        }
+    }
+}
+</style>
+
+<style lang="scss">
+.task-tooltip {
+    max-width: 300px;
+
+    .tooltip-content {
+        p {
+            margin: 5px 0;
+            line-height: 1.5;
+        }
+    }
+}
+</style>

+ 1 - 1
src/views/statistic/recycleOrder/index.vue

@@ -47,7 +47,7 @@
         { label: '审核金额', prop: 'auditMoney', align: 'center' },
         { label: '审核订单预估金额', prop: 'auditTotalMoney', align: 'center' },
         { label: '审核平均单本价', prop: 'auditAverageMoney', align: 'center' },
-        { label: '库存周转率', prop: 'stockWeekTurnoverRate', align: 'center' },
+        { label: '库存周转率', prop: 'stockTurnoverRate', align: 'center' },
         { label: '平均库存', prop: 'averageStock', align: 'center' },
     ]);