|
|
@@ -0,0 +1,386 @@
|
|
|
+<template>
|
|
|
+ <ele-page flex-table :bodyStyle="{ padding: '0 20px' }">
|
|
|
+ <goods-search ref="searchRef" @search="reload" />
|
|
|
+
|
|
|
+ <div class="px-4 py-2 bg-white">
|
|
|
+ <el-tabs v-model="activeTab" @tab-change="handleTabChange" :head-style="{marginBottom: '0'}">
|
|
|
+ <el-tab-pane label="全部" name="all" />
|
|
|
+ <el-tab-pane label="出售中" name="on_sale" />
|
|
|
+ <el-tab-pane label="仓库中" name="in_warehouse" />
|
|
|
+ <el-tab-pane label="销售黑名单" name="blacklist" />
|
|
|
+ <el-tab-pane label="待上架列表" name="pending_listing" />
|
|
|
+ </el-tabs>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <common-table ref="tableRef" :pageConfig="pageConfig" :columns="columns" :tools="false" :datasource="mockDatasource" :bodyStyle="{paddingTop:0}">
|
|
|
+ <template #toolbar>
|
|
|
+ <div class="flex gap-2">
|
|
|
+ <el-button type="primary" plain @click="openNewBookModal">新建图书商品</el-button>
|
|
|
+ <el-button type="primary" plain @click="openNewOtherModal">新建其他商品</el-button>
|
|
|
+ <el-button type="danger" plain @click="openUpdatePriceModal">更新价格</el-button>
|
|
|
+ <el-button type="warning" plain @click="openListDelistModal">上架/下架</el-button>
|
|
|
+ <el-button type="primary" plain v-if="activeTab === 'pending_listing'"
|
|
|
+ @click="handleOneClickListing">一键上架</el-button>
|
|
|
+ <el-button color="#bd3124" plain @click="handleExport">导出</el-button>
|
|
|
+ <el-button type="warning" plain @click="handleOperationLog">操作日志</el-button>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <!-- Columns Slots -->
|
|
|
+ <template #cover="{ row }">
|
|
|
+ <el-image style="width: 80px; height: 80px; border-radius: 4px" fit="cover"
|
|
|
+ :src="row.cover || row.image" :preview-src-list="[row.cover || row.image]" preview-teleported />
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <template #info="{ row }">
|
|
|
+ <goods-info :row="row" />
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <template #price="{ row }">
|
|
|
+ <div class="flex items-center justify-center">
|
|
|
+ <span>¥{{ row.price }}</span>
|
|
|
+ <el-icon class="ml-1 cursor-pointer text-blue-500" @click="handleEditPrice(row)">
|
|
|
+ <EditPen />
|
|
|
+ </el-icon>
|
|
|
+ </div>
|
|
|
+ <div v-if="row.schedule" class="text-xs text-green-500 mt-1">
|
|
|
+ 谢程婧: {{ row.schedule }}
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <template #stock="{ row }">
|
|
|
+ <div class="text-xs text-left">
|
|
|
+ <div>中等: {{ row.stockMedium || 0 }}</div>
|
|
|
+ <div>良好: {{ row.stockGood || 0 }}</div>
|
|
|
+ <div>次品: {{ row.stockDefective || 0 }}</div>
|
|
|
+ <div>合计: {{ row.stockTotal || 0 }}</div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <template #action="{ row }">
|
|
|
+ <div class="flex flex-wrap gap-1 button-group">
|
|
|
+ <!-- Common Actions -->
|
|
|
+ <el-button type="success" size="small" @click="handleModify(row)">修改</el-button>
|
|
|
+
|
|
|
+ <!-- Specific Actions based on Tab/Status -->
|
|
|
+ <template v-if="activeTab === 'pending_listing'">
|
|
|
+ <el-button type="primary" size="small" @click="handleListing(row)">上架</el-button>
|
|
|
+ <el-button color="#e99d42" size="small" @click="handleRecycleLog(row)">回收日志</el-button>
|
|
|
+ <el-button type="primary" size="small" @click="handlePriceLog(row)">售价日志</el-button>
|
|
|
+
|
|
|
+ <el-button color="#f37607" size="small" @click="handleViewTaobao(row)">查看淘宝</el-button>
|
|
|
+ <el-button color="#951d1d" size="small" @click="handleViewKongfz(row)">查看孔网</el-button>
|
|
|
+ <el-button type="primary" size="small"
|
|
|
+ @click="handleToggleRecycleList(row)">移除/加入回收书单</el-button>
|
|
|
+ <el-button type="danger" size="small" @click="handleToggleRecycle(row)">暂停/开启回收</el-button>
|
|
|
+
|
|
|
+ <el-button color="#7728f5" size="small"
|
|
|
+ @click="handleSetIndependentParams(row)">设置独立参数</el-button>
|
|
|
+ <el-button color="#333333" size="small" @click="handleToggleBlacklist(row)">加入/移除黑名单</el-button>
|
|
|
+ </template>
|
|
|
+
|
|
|
+ <template v-else>
|
|
|
+ <el-button type="danger" size="small" @click="handlePriceLog(row)">售价日志</el-button>
|
|
|
+ <el-button type="warning" size="small" @click="handleDelist(row)">下架</el-button>
|
|
|
+ <el-button type="warning" size="small" @click="handleSalesDetail(row)">销售明细</el-button>
|
|
|
+ <el-button type="danger" size="small" @click="handleRecycleDetail(row)">回收明细</el-button>
|
|
|
+ </template>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </common-table>
|
|
|
+
|
|
|
+ <!-- Modals -->
|
|
|
+ <new-book-modal v-model="newBookVisible" @done="reload" />
|
|
|
+
|
|
|
+ <common-import-modal v-model="updatePriceVisible" title="更新价格" template-name="更新价格模板"
|
|
|
+ template-url="https://example.com/price_template.xlsx" upload-url="/goods/price/import"
|
|
|
+ instruction-title="更新规则" :instructions="[
|
|
|
+ '文件ISBN不存在或者错误,会自动过滤掉',
|
|
|
+ '导入文件第一行需与模版完全一致'
|
|
|
+ ]" @done="reload" />
|
|
|
+
|
|
|
+ <common-import-modal v-model="listDelistVisible" title="上架/下架" template-name="上架/下架模板"
|
|
|
+ template-url="https://example.com/list_delist_template.xlsx" upload-url="/goods/status/import"
|
|
|
+ instruction-title="上架/下架规则" :instructions="[
|
|
|
+ '文件ISBN不存在或者错误,会自动过滤掉',
|
|
|
+ '导入文件第一行需与模版完全一致'
|
|
|
+ ]" @done="reload" />
|
|
|
+
|
|
|
+ <edit-price-modal ref="editPriceModalRef" v-model="editPriceVisible" @done="reload" />
|
|
|
+
|
|
|
+ <sales-detail-modal ref="salesDetailModalRef" v-model="salesDetailVisible" />
|
|
|
+
|
|
|
+ <recycle-detail-modal ref="recycleDetailModalRef" v-model="recycleDetailVisible" />
|
|
|
+
|
|
|
+ <operation-log-modal ref="operationLogModalRef" v-model="operationLogVisible" />
|
|
|
+
|
|
|
+ </ele-page>
|
|
|
+</template>
|
|
|
+
|
|
|
+<script setup>
|
|
|
+import { ref, reactive, computed } from 'vue';
|
|
|
+import CommonTable from '@/components/CommonPage/CommonTable.vue';
|
|
|
+import GoodsSearch from './components/goods-search.vue';
|
|
|
+import GoodsInfo from './components/goods-info.vue';
|
|
|
+import NewBookModal from './components/new-book-modal.vue';
|
|
|
+import CommonImportModal from './components/common-import-modal.vue';
|
|
|
+import EditPriceModal from './components/edit-price-modal.vue';
|
|
|
+import SalesDetailModal from './components/sales-detail-modal.vue';
|
|
|
+import RecycleDetailModal from './components/recycle-detail-modal.vue';
|
|
|
+import OperationLogModal from './components/operation-log-modal.vue';
|
|
|
+import { EditPen } from '@element-plus/icons-vue';
|
|
|
+import { EleMessage } from 'ele-admin-plus/es';
|
|
|
+
|
|
|
+defineOptions({ name: 'GoodsList' });
|
|
|
+
|
|
|
+// State
|
|
|
+const activeTab = ref('all');
|
|
|
+const tableRef = ref(null);
|
|
|
+const searchRef = ref(null);
|
|
|
+
|
|
|
+// Modals State
|
|
|
+const newBookVisible = ref(false);
|
|
|
+const updatePriceVisible = ref(false);
|
|
|
+const listDelistVisible = ref(false);
|
|
|
+const editPriceVisible = ref(false);
|
|
|
+const salesDetailVisible = ref(false);
|
|
|
+const recycleDetailVisible = ref(false);
|
|
|
+const operationLogVisible = ref(false);
|
|
|
+
|
|
|
+const editPriceModalRef = ref(null);
|
|
|
+const salesDetailModalRef = ref(null);
|
|
|
+const recycleDetailModalRef = ref(null);
|
|
|
+const operationLogModalRef = ref(null);
|
|
|
+
|
|
|
+// Page Config
|
|
|
+const pageConfig = reactive({
|
|
|
+ pageUrl: '/goods/list', // Mock URL
|
|
|
+ fileName: '商品列表',
|
|
|
+ cacheKey: 'goods-list-data',
|
|
|
+ params: {
|
|
|
+ status: 'all'
|
|
|
+ }
|
|
|
+});
|
|
|
+
|
|
|
+// Mock Datasource
|
|
|
+const mockDatasource = ({ page, limit, where }) => {
|
|
|
+ // Simulate API delay
|
|
|
+ return new Promise((resolve) => {
|
|
|
+ setTimeout(() => {
|
|
|
+ const list = [
|
|
|
+ {
|
|
|
+ id: 1,
|
|
|
+ cover: 'https://img3.doubanio.com/view/subject/s/public/s34049753.jpg',
|
|
|
+ title: 'Vue.js设计与实现',
|
|
|
+ author: '霍春阳',
|
|
|
+ isbn: '9787115583648',
|
|
|
+ publisher: '人民邮电出版社',
|
|
|
+ productType: '图书',
|
|
|
+ price: 89.00,
|
|
|
+ stock: 100,
|
|
|
+ stockMedium: 50,
|
|
|
+ stockGood: 30,
|
|
|
+ stockDefective: 20,
|
|
|
+ stockTotal: 100,
|
|
|
+ salesVolume: 500,
|
|
|
+ listingTime: '2023-01-01',
|
|
|
+ status: 'on_sale'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 2,
|
|
|
+ cover: 'https://img9.doubanio.com/view/subject/s/public/s29653655.jpg',
|
|
|
+ title: '深入浅出Node.js',
|
|
|
+ author: '朴灵',
|
|
|
+ isbn: '9787115323562',
|
|
|
+ publisher: '人民邮电出版社',
|
|
|
+ productType: '图书',
|
|
|
+ price: 69.00,
|
|
|
+ stock: 0,
|
|
|
+ stockMedium: 0,
|
|
|
+ stockGood: 0,
|
|
|
+ stockDefective: 0,
|
|
|
+ stockTotal: 0,
|
|
|
+ salesVolume: 1200,
|
|
|
+ listingTime: '2022-05-20',
|
|
|
+ status: 'in_warehouse'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 3,
|
|
|
+ cover: 'https://img1.doubanio.com/view/subject/s/public/s28359307.jpg',
|
|
|
+ title: 'JavaScript高级程序设计',
|
|
|
+ author: '马特·弗里斯比',
|
|
|
+ isbn: '9787115545381',
|
|
|
+ publisher: '人民邮电出版社',
|
|
|
+ productType: '图书',
|
|
|
+ price: 99.00,
|
|
|
+ stock: 50,
|
|
|
+ stockMedium: 20,
|
|
|
+ stockGood: 20,
|
|
|
+ stockDefective: 10,
|
|
|
+ stockTotal: 50,
|
|
|
+ salesVolume: 300,
|
|
|
+ listingTime: '2023-06-01',
|
|
|
+ status: 'pending_listing'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ id: 4,
|
|
|
+ cover: 'https://img2.doubanio.com/view/subject/s/public/s34049753.jpg',
|
|
|
+ title: 'CSS世界',
|
|
|
+ author: '张鑫旭',
|
|
|
+ isbn: '9787115472199',
|
|
|
+ publisher: '人民邮电出版社',
|
|
|
+ productType: '图书',
|
|
|
+ price: 59.00,
|
|
|
+ stock: 20,
|
|
|
+ stockMedium: 10,
|
|
|
+ stockGood: 5,
|
|
|
+ stockDefective: 5,
|
|
|
+ stockTotal: 20,
|
|
|
+ salesVolume: 150,
|
|
|
+ listingTime: '2021-12-12',
|
|
|
+ status: 'blacklist'
|
|
|
+ }
|
|
|
+ ];
|
|
|
+
|
|
|
+ // Filter by tab status (simulated)
|
|
|
+ let filteredList = list;
|
|
|
+ if (activeTab.value !== 'all') {
|
|
|
+ if (activeTab.value === 'pending_listing') {
|
|
|
+ // For demo purposes, let's make sure we have data for pending_listing
|
|
|
+ // Or just filter by status if it matches
|
|
|
+ filteredList = list.filter(item => item.status === activeTab.value);
|
|
|
+ // If empty for demo, just show item 3
|
|
|
+ if (filteredList.length === 0) filteredList = [list[2]];
|
|
|
+ } else {
|
|
|
+ filteredList = list.filter(item => item.status === activeTab.value);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ resolve({
|
|
|
+ code: 0,
|
|
|
+ msg: 'success',
|
|
|
+ count: list.length,
|
|
|
+ data: filteredList
|
|
|
+ });
|
|
|
+ }, 500);
|
|
|
+ });
|
|
|
+};
|
|
|
+
|
|
|
+// Columns
|
|
|
+const columns = computed(() => [
|
|
|
+ { type: 'selection', width: 50, align: 'center', fixed: 'left' },
|
|
|
+ { label: '商品图', prop: 'cover', width: 100, slot: 'cover', align: 'center' },
|
|
|
+ { label: '商品名称', prop: 'info', minWidth: 200, slot: 'info' },
|
|
|
+ { label: '商品类型', prop: 'productType', width: 100, align: 'center' },
|
|
|
+ { label: '售价', prop: 'price', width: 120, slot: 'price', align: 'center', sortable: 'custom' },
|
|
|
+ { label: '库存', prop: 'stock', width: 150, slot: 'stock', align: 'center', sortable: 'custom' },
|
|
|
+ { label: '销量', prop: 'salesVolume', width: 100, align: 'center', sortable: 'custom' },
|
|
|
+ { label: '上架时间', prop: 'listingTime', width: 160, align: 'center', sortable: 'custom' },
|
|
|
+ { label: '操作', prop: 'action', width: 280, slot: 'action', align: 'center', fixed: 'right' }
|
|
|
+]);
|
|
|
+
|
|
|
+// Methods
|
|
|
+const reload = (params = {}) => {
|
|
|
+ tableRef.value?.reload(params);
|
|
|
+};
|
|
|
+
|
|
|
+const handleTabChange = (name) => {
|
|
|
+ pageConfig.params.status = name;
|
|
|
+ reload();
|
|
|
+};
|
|
|
+
|
|
|
+// Modal Openers
|
|
|
+const openNewBookModal = () => {
|
|
|
+ newBookVisible.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+const openNewOtherModal = () => {
|
|
|
+ EleMessage.info('功能开发中...');
|
|
|
+};
|
|
|
+
|
|
|
+const openUpdatePriceModal = () => {
|
|
|
+ updatePriceVisible.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+const openListDelistModal = () => {
|
|
|
+ listDelistVisible.value = true;
|
|
|
+};
|
|
|
+
|
|
|
+const handleEditPrice = (row) => {
|
|
|
+ editPriceVisible.value = true;
|
|
|
+ editPriceModalRef.value?.open(row);
|
|
|
+};
|
|
|
+
|
|
|
+// Actions
|
|
|
+const handleOneClickListing = () => {
|
|
|
+ EleMessage.success('一键上架指令已发送');
|
|
|
+};
|
|
|
+
|
|
|
+const handleExport = () => {
|
|
|
+ EleMessage.success('开始导出...');
|
|
|
+};
|
|
|
+
|
|
|
+const handleOperationLog = () => {
|
|
|
+ operationLogVisible.value = true;
|
|
|
+ operationLogModalRef.value?.open();
|
|
|
+};
|
|
|
+
|
|
|
+const handleModify = (row) => {
|
|
|
+ EleMessage.info(`修改商品: ${row.title}`);
|
|
|
+};
|
|
|
+
|
|
|
+const handlePriceLog = (row) => {
|
|
|
+ EleMessage.info(`查看售价日志: ${row.title}`);
|
|
|
+};
|
|
|
+
|
|
|
+const handleDelist = (row) => {
|
|
|
+ EleMessage.warning(`下架商品: ${row.title}`);
|
|
|
+};
|
|
|
+
|
|
|
+const handleListing = (row) => {
|
|
|
+ EleMessage.success(`上架商品: ${row.title}`);
|
|
|
+};
|
|
|
+
|
|
|
+const handleSalesDetail = (row) => {
|
|
|
+ salesDetailVisible.value = true;
|
|
|
+ salesDetailModalRef.value?.open(row);
|
|
|
+};
|
|
|
+
|
|
|
+const handleRecycleDetail = (row) => {
|
|
|
+ recycleDetailVisible.value = true;
|
|
|
+ recycleDetailModalRef.value?.open(row);
|
|
|
+};
|
|
|
+
|
|
|
+const handleRecycleLog = (row) => {
|
|
|
+ EleMessage.info(`回收日志: ${row.title}`);
|
|
|
+};
|
|
|
+
|
|
|
+const handleViewTaobao = (row) => {
|
|
|
+ window.open(`https://s.taobao.com/search?q=${row.isbn}`, '_blank');
|
|
|
+};
|
|
|
+
|
|
|
+const handleViewKongfz = (row) => {
|
|
|
+ window.open(`https://search.kongfz.com/product_result/?key=${row.isbn}`, '_blank');
|
|
|
+};
|
|
|
+
|
|
|
+const handleToggleRecycleList = (row) => {
|
|
|
+ EleMessage.info('切换回收书单状态');
|
|
|
+};
|
|
|
+
|
|
|
+const handleToggleRecycle = (row) => {
|
|
|
+ EleMessage.info('切换回收状态');
|
|
|
+};
|
|
|
+
|
|
|
+const handleSetIndependentParams = (row) => {
|
|
|
+ EleMessage.info('设置独立参数');
|
|
|
+};
|
|
|
+
|
|
|
+const handleToggleBlacklist = (row) => {
|
|
|
+ EleMessage.info('切换黑名单状态');
|
|
|
+};
|
|
|
+
|
|
|
+</script>
|
|
|
+
|
|
|
+<style scoped>
|
|
|
+/* Custom styles if needed */
|
|
|
+</style>
|