|
|
@@ -1,235 +1,313 @@
|
|
|
<template>
|
|
|
- <div class="product-manage admin-page">
|
|
|
- <el-card>
|
|
|
- <div slot="header" class="admin-page-header">
|
|
|
- <span class="admin-page-title">商品管理</span>
|
|
|
- <el-button type="success" size="mini" icon="el-icon-plus" @click="$router.push('/product-edit')">新增商品</el-button>
|
|
|
- <el-dropdown trigger="hover" @command="handleBatchCmd">
|
|
|
- <el-button size="mini" type="primary">
|
|
|
- 批量<i class="el-icon-arrow-down el-icon--right"></i>
|
|
|
+ <div class="product-manage">
|
|
|
+ <el-card class="pm-card" shadow="never">
|
|
|
+ <!-- 页面标题区:唯一主操作「新增商品」,其余降为次按钮 -->
|
|
|
+ <div class="pm-header">
|
|
|
+ <div class="pm-header__left">
|
|
|
+ <h1 class="pm-title">商品管理</h1>
|
|
|
+ <span class="pm-subtitle">共 {{ pagination.total }} 个商品</span>
|
|
|
+ </div>
|
|
|
+ <div class="pm-header__right">
|
|
|
+ <el-dropdown trigger="click" @command="handleBatchCmd">
|
|
|
+ <el-button size="small">
|
|
|
+ 批量操作<i class="el-icon-arrow-down el-icon--right"></i>
|
|
|
+ </el-button>
|
|
|
+ <el-dropdown-menu slot="dropdown">
|
|
|
+ <el-dropdown-item command="batchOnShelf" icon="el-icon-upload2">批量上架</el-dropdown-item>
|
|
|
+ <el-dropdown-item command="batchOffShelf" icon="el-icon-download">批量下架</el-dropdown-item>
|
|
|
+ </el-dropdown-menu>
|
|
|
+ </el-dropdown>
|
|
|
+ <el-button size="small" type="primary" icon="el-icon-plus" @click="$router.push('/product-edit')">
|
|
|
+ 新增商品
|
|
|
</el-button>
|
|
|
- <el-dropdown-menu slot="dropdown">
|
|
|
- <el-dropdown-item command="batchOnShelf">批量上架</el-dropdown-item>
|
|
|
- <el-dropdown-item command="batchOffShelf">批量下架</el-dropdown-item>
|
|
|
- </el-dropdown-menu>
|
|
|
- </el-dropdown>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
|
|
|
- <div class="filter-bar" style="margin-bottom:12px;">
|
|
|
- <el-input
|
|
|
- v-model="filters.keyword"
|
|
|
- placeholder="搜索商品名称"
|
|
|
- prefix-icon="el-icon-search"
|
|
|
- clearable
|
|
|
- style="width:200px;"
|
|
|
- @clear="loadList"
|
|
|
- @keyup.enter.native="loadList"
|
|
|
- />
|
|
|
- <el-select v-model="filters.status" placeholder="商品状态" @change="loadList" clearable style="width:130px;">
|
|
|
- <el-option label="全部状态" value="" />
|
|
|
- <el-option label="待审核" value="pending" />
|
|
|
- <el-option label="待上架" value="approved" />
|
|
|
- <el-option label="已上架" value="on_shelf" />
|
|
|
- <el-option label="已下架" value="off_shelf" />
|
|
|
- <el-option label="已拒绝" value="rejected" />
|
|
|
- </el-select>
|
|
|
- <el-select v-model="filters.productType" placeholder="商品类型" @change="loadList" clearable style="width:140px;">
|
|
|
- <el-option label="全部类型" value="" />
|
|
|
- <el-option label="实物商品" value="physical" />
|
|
|
- <el-option label="虚拟商品" value="virtual" />
|
|
|
- <el-option label="优惠券" value="coupon" />
|
|
|
- <el-option label="测评商品" value="assessment" />
|
|
|
- </el-select>
|
|
|
- <el-button type="primary" size="mini" @click="loadList">查询</el-button>
|
|
|
- <el-button size="mini" @click="resetFilters">重置</el-button>
|
|
|
+ <!-- 筛选区:标签 + 控件结构,操作按钮居右 -->
|
|
|
+ <div class="pm-filter">
|
|
|
+ <div class="pm-filter__fields">
|
|
|
+ <div class="pm-field">
|
|
|
+ <label class="pm-field__label">关键词</label>
|
|
|
+ <el-input
|
|
|
+ v-model="filters.keyword"
|
|
|
+ placeholder="搜索商品名称"
|
|
|
+ prefix-icon="el-icon-search"
|
|
|
+ size="small"
|
|
|
+ clearable
|
|
|
+ class="pm-field__control pm-field__control--search"
|
|
|
+ @clear="loadList"
|
|
|
+ @keyup.enter.native="loadList"
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div class="pm-field">
|
|
|
+ <label class="pm-field__label">商品状态</label>
|
|
|
+ <el-select v-model="filters.status" placeholder="全部状态" size="small" clearable class="pm-field__control" @change="loadList">
|
|
|
+ <el-option label="全部状态" value="" />
|
|
|
+ <el-option label="待审核" value="pending" />
|
|
|
+ <el-option label="待上架" value="approved" />
|
|
|
+ <el-option label="已上架" value="on_shelf" />
|
|
|
+ <el-option label="已下架" value="off_shelf" />
|
|
|
+ <el-option label="已拒绝" value="rejected" />
|
|
|
+ </el-select>
|
|
|
+ </div>
|
|
|
+ <div class="pm-field">
|
|
|
+ <label class="pm-field__label">商品类型</label>
|
|
|
+ <el-select v-model="filters.productType" placeholder="全部类型" size="small" clearable class="pm-field__control" @change="loadList">
|
|
|
+ <el-option label="全部类型" value="" />
|
|
|
+ <el-option label="实物商品" value="physical" />
|
|
|
+ <el-option label="虚拟商品" value="virtual" />
|
|
|
+ <el-option label="优惠券" value="coupon" />
|
|
|
+ <el-option label="测评商品" value="assessment" />
|
|
|
+ </el-select>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="pm-filter__actions">
|
|
|
+ <el-button size="small" @click="resetFilters">重置</el-button>
|
|
|
+ <el-button size="small" type="primary" @click="loadList">查询</el-button>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
|
|
|
- <div class="table-scroll-wrap-sm">
|
|
|
- <el-table :max-height="tableHeight" :data="list" v-loading="loading" border stripe @selection-change="handleSelectionChange">
|
|
|
- <el-table-column type="selection" width="60" />
|
|
|
- <el-table-column prop="id" label="ID" width="70" />
|
|
|
- <el-table-column label="商品图片" width="80">
|
|
|
- <template slot-scope="{ row }">
|
|
|
- <img
|
|
|
- v-if="row.coverImage"
|
|
|
- :src="row.coverImage"
|
|
|
- class="product-thumb"
|
|
|
- @click="previewImage(row.coverImage)"
|
|
|
- />
|
|
|
- <span v-else style="color:#999">-</span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column prop="name" label="商品名称" min-width="150" show-overflow-tooltip>
|
|
|
- <template slot="header">
|
|
|
- <span class="sort-header" @click.stop="onSortClick('name', $event)" @dblclick.stop="onSortToggle('name')">商品名称<span v-if="sortIndex('name') !== null" class="sort-index" :class="sortClass('name') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('name') }}</span>{{ sortIcon('name') }}</span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
-
|
|
|
- <el-table-column label="类型" width="90">
|
|
|
- <template slot-scope="{ row }">
|
|
|
- {{ typeLabel(row.productType) }}
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
-
|
|
|
- <el-table-column label="售价(元)" width="90">
|
|
|
- <template slot-scope="{ row }">
|
|
|
- {{ formatPriceWithSymbol(row.price || 0) }}
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column prop="stock" label="库存" width="80">
|
|
|
- <template slot-scope="{ row }">
|
|
|
- <span :class="{ 'stock-warning': row.stock !== null && row.stock < 10 }">
|
|
|
- {{ row.stock }}
|
|
|
- </span>
|
|
|
- </template>
|
|
|
- <template slot="header">
|
|
|
- <span class="sort-header" @click.stop="onSortClick('stock', $event)" @dblclick.stop="onSortToggle('stock')">库存<span v-if="sortIndex('stock') !== null" class="sort-index" :class="sortClass('stock') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('stock') }}</span>{{ sortIcon('stock') }}</span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column prop="salesCount" label="销量" width="70">
|
|
|
- <template slot="header">
|
|
|
- <span class="sort-header" @click.stop="onSortClick('salesCount', $event)" @dblclick.stop="onSortToggle('salesCount')">销量<span v-if="sortIndex('salesCount') !== null" class="sort-index" :class="sortClass('salesCount') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('salesCount') }}</span>{{ sortIcon('salesCount') }}</span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
-
|
|
|
- <el-table-column label="供应商" width="120" show-overflow-tooltip>
|
|
|
- <template slot-scope="{ row }">
|
|
|
- {{ row.vendorName || '-' }}
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
-
|
|
|
- <el-table-column label="状态" width="90">
|
|
|
- <template slot-scope="{ row }">
|
|
|
- <el-tag :type="statusType(row.status)" size="mini">{{ statusLabel(row.status) }}</el-tag>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column prop="sortOrder" label="排序" width="80">
|
|
|
- <template slot-scope="{ row }">
|
|
|
- {{ row.sortOrder != null ? row.sortOrder : '-' }}
|
|
|
- </template>
|
|
|
- <template slot="header">
|
|
|
- <span class="sort-header" @click.stop="onSortClick('sortOrder', $event)" @dblclick.stop="onSortToggle('sortOrder')">排序<span v-if="sortIndex('sortOrder') !== null" class="sort-index" :class="sortClass('sortOrder') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('sortOrder') }}</span>{{ sortIcon('sortOrder') }}</span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="最低可见级别" width="140">
|
|
|
- <template slot-scope="{ row }">
|
|
|
- <span v-if="row.minVisibleLevel">{{ levelLabel(row.minVisibleLevel) }}</span>
|
|
|
- <span v-else style="color:#999">全部</span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column prop="createdAt" label="创建时间" width="160">
|
|
|
- <template slot-scope="{ row }">
|
|
|
- {{ formatTime(row.createdAt) }}
|
|
|
- </template>
|
|
|
- <template slot="header">
|
|
|
- <span class="sort-header" @click.stop="onSortClick('createdAt', $event)" @dblclick.stop="onSortToggle('createdAt')">创建时间<span v-if="sortIndex('createdAt') !== null" class="sort-index" :class="sortClass('createdAt') === 'sort-asc' ? 'sort-asc-badge' : 'sort-desc-badge'">{{ sortIndex('createdAt') }}</span>{{ sortIcon('createdAt') }}</span>
|
|
|
-
|
|
|
- </template></el-table-column>
|
|
|
- <el-table-column label="操作" width="280" fixed="right">
|
|
|
- <template slot-scope="{ row }">
|
|
|
- <el-button size="mini" type="primary" @click="$router.push('/product-edit?id=' + row.id)">编辑</el-button>
|
|
|
- <el-button size="mini" type="info" @click="showInventoryDialog(row)">库存</el-button>
|
|
|
- <el-dropdown trigger="hover" @command="(cmd) => handleActionCmd(row, cmd)">
|
|
|
- <el-button size="mini">
|
|
|
- 更多<i class="el-icon-arrow-down el-icon--right"></i>
|
|
|
- </el-button>
|
|
|
- <el-dropdown-menu slot="dropdown">
|
|
|
- <el-dropdown-item command="copy" icon="el-icon-document-copy">复制</el-dropdown-item>
|
|
|
- <el-dropdown-item v-if="row.status === 'pending'" command="approve" icon="el-icon-circle-check">通过</el-dropdown-item>
|
|
|
- <el-dropdown-item v-if="row.status === 'pending'" command="reject" icon="el-icon-circle-close">拒绝</el-dropdown-item>
|
|
|
- <el-dropdown-item v-if="row.status === 'on_shelf'" command="offShelf" icon="el-icon-download">下架</el-dropdown-item>
|
|
|
- <el-dropdown-item v-if="row.status === 'off_shelf'" command="onShelf" icon="el-icon-upload2">上架</el-dropdown-item>
|
|
|
- </el-dropdown-menu>
|
|
|
- </el-dropdown>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
+ <!-- 批量选择提示条:仅在选择后出现,避免常态占位 -->
|
|
|
+ <div v-if="selectedIds.length" class="pm-batch-bar">
|
|
|
+ <span class="pm-batch-bar__text">已选 <b>{{ selectedIds.length }}</b> 个商品</span>
|
|
|
+ <div class="pm-batch-bar__actions">
|
|
|
+ <el-button type="text" size="small" @click="handleBatchShelve(true)">批量上架</el-button>
|
|
|
+ <el-divider direction="vertical"></el-divider>
|
|
|
+ <el-button type="text" size="small" @click="handleBatchShelve(false)">批量下架</el-button>
|
|
|
+ <el-divider direction="vertical"></el-divider>
|
|
|
+ <el-button type="text" size="small" class="pm-text-muted" @click="clearSelection">清空选择</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 表格区:数值右对齐等宽、状态标准化标签、操作列降为文本按钮 -->
|
|
|
+ <div class="pm-table-wrap">
|
|
|
+ <el-table
|
|
|
+ ref="table"
|
|
|
+ :max-height="tableHeight"
|
|
|
+ :data="list"
|
|
|
+ v-loading="loading"
|
|
|
+ border
|
|
|
+ size="small"
|
|
|
+ @selection-change="handleSelectionChange"
|
|
|
+ >
|
|
|
+ <el-table-column type="selection" width="48" align="center" />
|
|
|
+ <el-table-column prop="id" label="ID" width="72" align="right" class-name="pm-num" />
|
|
|
+ <el-table-column label="图片" width="72" align="center">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <img
|
|
|
+ v-if="row.coverImage"
|
|
|
+ :src="row.coverImage"
|
|
|
+ class="pm-thumb"
|
|
|
+ @click="previewImage(row.coverImage)"
|
|
|
+ />
|
|
|
+ <span v-else class="pm-placeholder">—</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="name" label="商品名称" min-width="180" show-overflow-tooltip>
|
|
|
+ <template slot="header">
|
|
|
+ <span
|
|
|
+ class="pm-sort-header"
|
|
|
+ title="单击切换排序,双击取消"
|
|
|
+ @click.stop="onSortClick('name', $event)"
|
|
|
+ @dblclick.stop="onSortToggle('name')"
|
|
|
+ >
|
|
|
+ 商品名称<span v-if="sortIndex('name') !== null" class="pm-sort-index">{{ sortIndex('name') }}</span>{{ sortIcon('name') }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="类型" width="96">
|
|
|
+ <template slot-scope="{ row }">{{ typeLabel(row.productType) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="售价(元)" width="112" align="right" header-align="right" class-name="pm-num">
|
|
|
+ <template slot-scope="{ row }">{{ formatPriceWithSymbol(row.price || 0) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="stock" label="库存" width="88" align="right" header-align="right" class-name="pm-num">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <span :class="{ 'pm-stock-warning': row.stock !== null && row.stock < 10 }">{{ row.stock }}</span>
|
|
|
+ </template>
|
|
|
+ <template slot="header">
|
|
|
+ <span
|
|
|
+ class="pm-sort-header"
|
|
|
+ title="单击切换排序,双击取消"
|
|
|
+ @click.stop="onSortClick('stock', $event)"
|
|
|
+ @dblclick.stop="onSortToggle('stock')"
|
|
|
+ >
|
|
|
+ 库存<span v-if="sortIndex('stock') !== null" class="pm-sort-index">{{ sortIndex('stock') }}</span>{{ sortIcon('stock') }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="salesCount" label="销量" width="88" align="right" header-align="right" class-name="pm-num">
|
|
|
+ <template slot="header">
|
|
|
+ <span
|
|
|
+ class="pm-sort-header"
|
|
|
+ title="单击切换排序,双击取消"
|
|
|
+ @click.stop="onSortClick('salesCount', $event)"
|
|
|
+ @dblclick.stop="onSortToggle('salesCount')"
|
|
|
+ >
|
|
|
+ 销量<span v-if="sortIndex('salesCount') !== null" class="pm-sort-index">{{ sortIndex('salesCount') }}</span>{{ sortIcon('salesCount') }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="供应商" width="140" show-overflow-tooltip>
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <span :class="{ 'pm-placeholder': !row.vendorName }">{{ row.vendorName || '—' }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="状态" width="96">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <span class="pm-status" :class="'pm-status--' + row.status">
|
|
|
+ <i class="pm-status__dot"></i>{{ statusLabel(row.status) }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="sortOrder" label="排序" width="88" align="right" header-align="right" class-name="pm-num">
|
|
|
+ <template slot-scope="{ row }">{{ row.sortOrder != null ? row.sortOrder : '—' }}</template>
|
|
|
+ <template slot="header">
|
|
|
+ <span
|
|
|
+ class="pm-sort-header"
|
|
|
+ title="单击切换排序,双击取消"
|
|
|
+ @click.stop="onSortClick('sortOrder', $event)"
|
|
|
+ @dblclick.stop="onSortToggle('sortOrder')"
|
|
|
+ >
|
|
|
+ 排序<span v-if="sortIndex('sortOrder') !== null" class="pm-sort-index">{{ sortIndex('sortOrder') }}</span>{{ sortIcon('sortOrder') }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="最低可见级别" width="128">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <span :class="{ 'pm-placeholder': !row.minVisibleLevel }">{{ levelLabel(row.minVisibleLevel) }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="createdAt" label="创建时间" width="160">
|
|
|
+ <template slot-scope="{ row }">{{ formatTime(row.createdAt) }}</template>
|
|
|
+ <template slot="header">
|
|
|
+ <span
|
|
|
+ class="pm-sort-header"
|
|
|
+ title="单击切换排序,双击取消"
|
|
|
+ @click.stop="onSortClick('createdAt', $event)"
|
|
|
+ @dblclick.stop="onSortToggle('createdAt')"
|
|
|
+ >
|
|
|
+ 创建时间<span v-if="sortIndex('createdAt') !== null" class="pm-sort-index">{{ sortIndex('createdAt') }}</span>{{ sortIcon('createdAt') }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" width="176" fixed="right" align="left">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <el-button type="text" size="small" @click="$router.push('/product-edit?id=' + row.id)">编辑</el-button>
|
|
|
+ <el-button type="text" size="small" @click="showInventoryDialog(row)">库存</el-button>
|
|
|
+ <el-dropdown trigger="click" @command="(cmd) => handleActionCmd(row, cmd)">
|
|
|
+ <el-button type="text" size="small">
|
|
|
+ 更多<i class="el-icon-arrow-down el-icon--right"></i>
|
|
|
+ </el-button>
|
|
|
+ <el-dropdown-menu slot="dropdown">
|
|
|
+ <el-dropdown-item command="copy" icon="el-icon-document-copy">复制</el-dropdown-item>
|
|
|
+ <el-dropdown-item v-if="row.status === 'pending'" command="approve" icon="el-icon-circle-check">通过</el-dropdown-item>
|
|
|
+ <el-dropdown-item v-if="row.status === 'pending'" command="reject" icon="el-icon-circle-close" divided>拒绝</el-dropdown-item>
|
|
|
+ <el-dropdown-item v-if="row.status === 'on_shelf'" command="offShelf" icon="el-icon-download">下架</el-dropdown-item>
|
|
|
+ <el-dropdown-item v-if="row.status === 'off_shelf'" command="onShelf" icon="el-icon-upload2">上架</el-dropdown-item>
|
|
|
+ </el-dropdown-menu>
|
|
|
+ </el-dropdown>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
</el-table>
|
|
|
- </div>
|
|
|
-
|
|
|
- <el-pagination
|
|
|
- @size-change="handleSizeChange"
|
|
|
- @current-change="handlePageChange"
|
|
|
- :current-page="pagination.page"
|
|
|
- :page-sizes="[10, 20, 50]"
|
|
|
- :page-size="pagination.size"
|
|
|
- :total="pagination.total"
|
|
|
- layout="total, sizes, prev, pager, next, jumper"
|
|
|
- class="pagination-wrap"
|
|
|
- />
|
|
|
-
|
|
|
- <el-dialog title="拒绝原因" :visible.sync="rejectDialogVisible" width="420px">
|
|
|
- <el-input v-model="rejectReason" type="textarea" :rows="3" placeholder="请输入拒绝原因" />
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 分页:右侧对齐,总数前置 -->
|
|
|
+ <div class="pm-pagination">
|
|
|
+ <el-pagination
|
|
|
+ @size-change="handleSizeChange"
|
|
|
+ @current-change="handlePageChange"
|
|
|
+ :current-page="pagination.page"
|
|
|
+ :page-sizes="[10, 20, 50]"
|
|
|
+ :page-size="pagination.size"
|
|
|
+ :total="pagination.total"
|
|
|
+ layout="total, sizes, prev, pager, next, jumper"
|
|
|
+ background
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+
|
|
|
+ <!-- 拒绝原因弹窗 -->
|
|
|
+ <el-dialog title="拒绝原因" :visible.sync="rejectDialogVisible" width="460px" class="pm-dialog">
|
|
|
+ <el-input v-model="rejectReason" type="textarea" :rows="3" maxlength="200" show-word-limit placeholder="请输入拒绝原因(将同步给商品提交方)" />
|
|
|
<span slot="footer">
|
|
|
- <el-button @click="rejectDialogVisible = false">取消</el-button>
|
|
|
- <el-button type="danger" @click="confirmReject">确认拒绝</el-button>
|
|
|
+ <el-button size="small" @click="rejectDialogVisible = false">取消</el-button>
|
|
|
+ <el-button size="small" type="danger" @click="confirmReject">确认拒绝</el-button>
|
|
|
</span>
|
|
|
</el-dialog>
|
|
|
|
|
|
- <el-dialog title="商品详情" :visible.sync="detailDialogVisible" width="600px">
|
|
|
- <el-descriptions :column="2" border v-if="detail">
|
|
|
+ <!-- 商品详情弹窗 -->
|
|
|
+ <el-dialog title="商品详情" :visible.sync="detailDialogVisible" width="720px" class="pm-dialog">
|
|
|
+ <el-descriptions :column="2" border size="small" v-if="detail">
|
|
|
<el-descriptions-item label="商品名称">{{ detail.name }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="商品类型">{{ typeLabel(detail.productType) }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="售价">{{ formatPriceWithSymbol(detail.price || 0) }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="库存">{{ detail.stock }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="销量">{{ detail.salesCount || 0 }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="供应商">{{ detail.vendorName || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="供应商">{{ detail.vendorName || '—' }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="状态">
|
|
|
- <el-tag :type="statusType(detail.status)" size="mini">{{ statusLabel(detail.status) }}</el-tag>
|
|
|
- </el-descriptions-item>
|
|
|
- <el-descriptions-item label="最低可见级别">
|
|
|
- {{ detail.minVisibleLevel ? levelLabel(detail.minVisibleLevel) : '全部' }}
|
|
|
+ <span class="pm-status" :class="'pm-status--' + detail.status">
|
|
|
+ <i class="pm-status__dot"></i>{{ statusLabel(detail.status) }}
|
|
|
+ </span>
|
|
|
</el-descriptions-item>
|
|
|
- <el-descriptions-item label="拒绝原因" v-if="detail.rejectReason">{{ detail.rejectReason }}</el-descriptions-item>
|
|
|
- <el-descriptions-item label="商品描述" :span="2">{{ detail.description || '-' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="最低可见级别">{{ detail.minVisibleLevel ? levelLabel(detail.minVisibleLevel) : '所有用户' }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="拒绝原因" v-if="detail.rejectReason" :span="2">{{ detail.rejectReason }}</el-descriptions-item>
|
|
|
+ <el-descriptions-item label="商品描述" :span="2">{{ detail.description || '—' }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="创建时间" :span="2">{{ formatTime(detail.createdAt) }}</el-descriptions-item>
|
|
|
</el-descriptions>
|
|
|
- <div v-else style="text-align:center;color:#999;">加载中...</div>
|
|
|
+ <div v-else class="pm-empty">加载中…</div>
|
|
|
</el-dialog>
|
|
|
- <!-- 库存弹窗 -->
|
|
|
- <el-dialog title="库存管理" :visible.sync="inventoryDialogVisible" width="800px">
|
|
|
+
|
|
|
+ <!-- 库存弹窗 -->
|
|
|
+ <el-dialog title="库存管理" :visible.sync="inventoryDialogVisible" width="860px" class="pm-dialog">
|
|
|
<div v-loading="inventoryLoading">
|
|
|
- <el-descriptions :column="2" border v-if="inventoryData">
|
|
|
+ <el-descriptions :column="3" border size="small" v-if="inventoryData">
|
|
|
<el-descriptions-item label="商品名称">{{ inventoryData.productName }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="当前库存">{{ inventoryData.stock }}</el-descriptions-item>
|
|
|
<el-descriptions-item label="预警下限">{{ inventoryData.minStockAlert != null ? inventoryData.minStockAlert : 10 }}</el-descriptions-item>
|
|
|
</el-descriptions>
|
|
|
|
|
|
- <h4 style="margin:16px 0 8px;">SKU 库存</h4>
|
|
|
- <el-table :data="inventoryData.skus" border stripe v-if="inventoryData && inventoryData.skus && inventoryData.skus.length">
|
|
|
- <el-table-column prop="id" label="SKU ID" width="70" />
|
|
|
- <el-table-column prop="specs" label="规格" min-width="150" />
|
|
|
- <el-table-column prop="stock" label="库存" width="70" />
|
|
|
- <el-table-column prop="minStockAlert" label="预警下限" width="70" />
|
|
|
- </el-table>
|
|
|
- <div v-else-if="inventoryData" style="color:#999;padding:8px;">无SKU数据</div>
|
|
|
+ <div class="pm-section">
|
|
|
+ <div class="pm-section__title">SKU 库存</div>
|
|
|
+ <el-table :data="inventoryData && inventoryData.skus || []" border size="small" empty-text="暂无 SKU 数据">
|
|
|
+ <el-table-column prop="id" label="SKU ID" width="88" align="right" class-name="pm-num" />
|
|
|
+ <el-table-column prop="specs" label="规格" min-width="180" show-overflow-tooltip />
|
|
|
+ <el-table-column prop="stock" label="库存" width="88" align="right" class-name="pm-num" />
|
|
|
+ <el-table-column prop="minStockAlert" label="预警下限" width="96" align="right" class-name="pm-num" />
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
|
|
|
- <h4 style="margin:16px 0 8px;">最近 10 条流水</h4>
|
|
|
- <el-table :data="inventoryData.recentTransactions" border stripe v-if="inventoryData && inventoryData.recentTransactions && inventoryData.recentTransactions.length">
|
|
|
- <el-table-column label="类型" width="80">
|
|
|
- <template slot-scope="{ row }">
|
|
|
- <el-tag :type="row.type === 'inbound' ? 'success' : row.type === 'outbound' ? 'danger' : 'warning'" size="mini">
|
|
|
- {{ {'inbound':'入库','outbound':'出库','adjustment':'调账','counting':'盘点','refund':'退款入库'}[row.type] || row.type }}
|
|
|
- </el-tag>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="数量" width="70">
|
|
|
- <template slot-scope="{ row }">
|
|
|
- <span :style="{ color: row.direction === 1 ? '#67c23a' : '#f56c6c' }">{{ row.direction === 1 ? '+' : '-' }}{{ row.quantity }}</span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column prop="beforeStock" label="变动前" width="70" />
|
|
|
- <el-table-column prop="afterStock" label="变动后" width="70" />
|
|
|
- <el-table-column prop="remark" label="备注" min-width="150" />
|
|
|
- <el-table-column prop="createdAt" label="时间" width="160" />
|
|
|
- </el-table>
|
|
|
- <div v-else style="color:#999;padding:8px;">暂无流水记录</div>
|
|
|
+ <div class="pm-section">
|
|
|
+ <div class="pm-section__title">最近 10 条流水</div>
|
|
|
+ <el-table :data="inventoryData && inventoryData.recentTransactions || []" border size="small" empty-text="暂无流水记录">
|
|
|
+ <el-table-column label="类型" width="96">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <span class="pm-status" :class="'pm-status--' + (row.type === 'inbound' || row.type === 'refund' ? 'on_shelf' : row.type === 'outbound' ? 'rejected' : 'pending')">
|
|
|
+ <i class="pm-status__dot"></i>{{ txTypeLabel(row.type) }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="数量" width="96" align="right" class-name="pm-num">
|
|
|
+ <template slot-scope="{ row }">
|
|
|
+ <span :class="row.direction === 1 ? 'pm-qty-in' : 'pm-qty-out'">
|
|
|
+ {{ row.direction === 1 ? '+' : '−' }}{{ row.quantity }}
|
|
|
+ </span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column prop="beforeStock" label="变动前" width="88" align="right" class-name="pm-num" />
|
|
|
+ <el-table-column prop="afterStock" label="变动后" width="88" align="right" class-name="pm-num" />
|
|
|
+ <el-table-column prop="remark" label="备注" min-width="160" show-overflow-tooltip />
|
|
|
+ <el-table-column label="时间" width="160">
|
|
|
+ <template slot-scope="{ row }">{{ formatTime(row.createdAt) }}</template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
<span slot="footer">
|
|
|
- <el-button @click="inventoryDialogVisible = false">关闭</el-button>
|
|
|
- <el-button type="primary" @click="$router.push('/inventory')">进入库存管理</el-button>
|
|
|
+ <el-button size="small" @click="inventoryDialogVisible = false">关闭</el-button>
|
|
|
+ <el-button size="small" type="primary" @click="$router.push('/inventory')">进入库存管理</el-button>
|
|
|
</span>
|
|
|
</el-dialog>
|
|
|
- </el-card>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
@@ -240,15 +318,11 @@ import { getProductList, reviewProduct, shelveProduct, batchShelveProduct, copyP
|
|
|
export default {
|
|
|
mixins: [SortMixin],
|
|
|
name: 'ProductManage',
|
|
|
- computed: {
|
|
|
- tableHeight() {
|
|
|
- return window.innerHeight - 300
|
|
|
- }
|
|
|
- },
|
|
|
data() {
|
|
|
return {
|
|
|
list: [],
|
|
|
loading: false,
|
|
|
+ tableHeight: 480,
|
|
|
filters: {
|
|
|
keyword: '',
|
|
|
status: '',
|
|
|
@@ -269,13 +343,23 @@ export default {
|
|
|
inventoryDialogVisible: false,
|
|
|
inventoryLoading: false,
|
|
|
inventoryData: null,
|
|
|
- selectedIds: [] // 批量操作选中的商品ID集合
|
|
|
+ selectedIds: [] // 批量操作选中的商品ID集合
|
|
|
}
|
|
|
},
|
|
|
mounted() {
|
|
|
+ this.calcTableHeight()
|
|
|
+ window.addEventListener('resize', this.calcTableHeight)
|
|
|
this.loadList()
|
|
|
},
|
|
|
+ beforeDestroy() {
|
|
|
+ window.removeEventListener('resize', this.calcTableHeight)
|
|
|
+ },
|
|
|
methods: {
|
|
|
+ calcTableHeight() {
|
|
|
+ // 页面留白 24*2 + 卡片内边距 24*2 + 头部 32 + 筛选区 56 + 分页 56 ≈ 基准 300
|
|
|
+ const offset = this.selectedIds.length ? 336 : 300
|
|
|
+ this.tableHeight = Math.max(320, window.innerHeight - offset)
|
|
|
+ },
|
|
|
async loadList() {
|
|
|
this.loading = true
|
|
|
try {
|
|
|
@@ -306,16 +390,6 @@ export default {
|
|
|
this.pagination.page = page
|
|
|
this.loadList()
|
|
|
},
|
|
|
- statusType(status) {
|
|
|
- const map = {
|
|
|
- pending: 'warning',
|
|
|
- approved: 'success',
|
|
|
- on_shelf: 'success',
|
|
|
- off_shelf: 'info',
|
|
|
- rejected: 'danger'
|
|
|
- }
|
|
|
- return map[status] || 'info'
|
|
|
- },
|
|
|
statusLabel(status) {
|
|
|
const map = {
|
|
|
pending: '待审核',
|
|
|
@@ -326,22 +400,27 @@ export default {
|
|
|
}
|
|
|
return map[status] || status
|
|
|
},
|
|
|
+ txTypeLabel(type) {
|
|
|
+ const map = { inbound: '入库', outbound: '出库', adjustment: '调账', counting: '盘点', refund: '退款入库' }
|
|
|
+ return map[type] || type
|
|
|
+ },
|
|
|
typeLabel(type) {
|
|
|
const map = { physical: '实物商品', virtual: '虚拟商品', coupon: '优惠券', assessment: '测评商品' }
|
|
|
return map[type] || type
|
|
|
},
|
|
|
levelLabel(code) {
|
|
|
- if (!code) return '全部'
|
|
|
- const map = { FREE: '免费版', FAMILY: '家庭版', PROVIDER: '服务商版', PREMIUM: '高级版', LIFETIME: '终身版' }
|
|
|
+ if (!code) return '所有用户'
|
|
|
+ const map = { FREE: '免费版', BASIC: '体验版', FAMILY: '家庭版', PROVIDER: '服务商版', PREMIUM: '高级版', LIFETIME: '终身版' }
|
|
|
return map[code] || code
|
|
|
},
|
|
|
formatTime(t) {
|
|
|
- if (!t) return '-'
|
|
|
+ if (!t) return '—'
|
|
|
return t.replace ? t.replace('T', ' ').substring(0, 19) : t
|
|
|
},
|
|
|
formatPriceWithSymbol(price) {
|
|
|
- if (price === 0) return '免费'
|
|
|
- return '¥' + (price / 100).toFixed(2)
|
|
|
+ if (!price) return '免费'
|
|
|
+ const v = (price / 100).toFixed(2)
|
|
|
+ return '¥' + v.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
|
|
|
},
|
|
|
async handleReview(row, action) {
|
|
|
try {
|
|
|
@@ -435,7 +514,7 @@ export default {
|
|
|
await this.$confirm('确认' + (shelve ? '批量上架' : '批量下架') + '选中的 ' + this.selectedIds.length + ' 个商品?', '提示')
|
|
|
await batchShelveProduct(this.selectedIds, shelve)
|
|
|
this.$message.success(shelve ? '批量上架成功' : '批量下架成功')
|
|
|
- this.selectedIds = []
|
|
|
+ this.clearSelection()
|
|
|
this.loadList()
|
|
|
} catch (e) {
|
|
|
if (e !== 'cancel') this.$message.error('操作失败')
|
|
|
@@ -443,42 +522,290 @@ export default {
|
|
|
},
|
|
|
handleSelectionChange(rows) {
|
|
|
this.selectedIds = rows.map(r => r.id)
|
|
|
+ this.calcTableHeight()
|
|
|
+ },
|
|
|
+ clearSelection() {
|
|
|
+ this.selectedIds = []
|
|
|
+ if (this.$refs.table) this.$refs.table.clearSelection()
|
|
|
+ this.calcTableHeight()
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
</script>
|
|
|
|
|
|
<style scoped>
|
|
|
+/* ==========================================================
|
|
|
+ 商品管理页 · 企业级视觉规范
|
|
|
+ 间距 8 基准 / 主色仅 1 处主操作 / 状态色仅承载语义
|
|
|
+ ========================================================== */
|
|
|
.product-manage {
|
|
|
- padding: 20px;
|
|
|
+ padding: 24px;
|
|
|
+}
|
|
|
+
|
|
|
+.pm-card {
|
|
|
+ border: 1px solid #f0f0f0;
|
|
|
+ border-radius: 8px;
|
|
|
+ box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.03);
|
|
|
+}
|
|
|
+
|
|
|
+.pm-card /deep/ .el-card__body {
|
|
|
+ padding: 24px;
|
|
|
+}
|
|
|
+
|
|
|
+/* ---------- 页面标题区 ---------- */
|
|
|
+.pm-header {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ margin-bottom: 24px;
|
|
|
+}
|
|
|
+
|
|
|
+.pm-header__left {
|
|
|
+ display: flex;
|
|
|
+ align-items: baseline;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.pm-title {
|
|
|
+ margin: 0;
|
|
|
+ font-size: 20px;
|
|
|
+ line-height: 1.4;
|
|
|
+ font-weight: 600;
|
|
|
+ color: rgba(0, 0, 0, 0.88);
|
|
|
+}
|
|
|
+
|
|
|
+.pm-subtitle {
|
|
|
+ font-size: 12px;
|
|
|
+ color: rgba(0, 0, 0, 0.45);
|
|
|
+}
|
|
|
+
|
|
|
+.pm-header__right {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+/* ---------- 筛选区:白底 + 下分割线,不用阴影 ---------- */
|
|
|
+.pm-filter {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-end;
|
|
|
+ justify-content: space-between;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 16px;
|
|
|
+ padding-bottom: 16px;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ border-bottom: 1px solid #f0f0f0;
|
|
|
+}
|
|
|
+
|
|
|
+.pm-filter__fields {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.pm-field {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.pm-field__label {
|
|
|
+ font-size: 14px;
|
|
|
+ color: rgba(0, 0, 0, 0.65);
|
|
|
+ white-space: nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+.pm-field__control {
|
|
|
+ width: 150px;
|
|
|
+}
|
|
|
+
|
|
|
+.pm-field__control--search {
|
|
|
+ width: 220px;
|
|
|
+}
|
|
|
+
|
|
|
+.pm-filter__actions {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+/* ---------- 批量选择提示条 ---------- */
|
|
|
+.pm-batch-bar {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+ height: 40px;
|
|
|
+ padding: 0 16px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ background: #e6f4ff;
|
|
|
+ border: 1px solid #91caff;
|
|
|
+ border-radius: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.pm-batch-bar__text {
|
|
|
+ font-size: 14px;
|
|
|
+ color: rgba(0, 0, 0, 0.65);
|
|
|
+}
|
|
|
+
|
|
|
+.pm-batch-bar__text b {
|
|
|
+ color: #0958d9;
|
|
|
+ font-weight: 600;
|
|
|
+}
|
|
|
+
|
|
|
+.pm-batch-bar__actions {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+}
|
|
|
+
|
|
|
+/* ---------- 表格 ---------- */
|
|
|
+.pm-table-wrap /deep/ .el-table {
|
|
|
+ font-size: 14px;
|
|
|
+ border-radius: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.pm-table-wrap /deep/ .el-table th {
|
|
|
+ height: 46px;
|
|
|
+ padding: 0;
|
|
|
+ background: #fafafa;
|
|
|
+ color: rgba(0, 0, 0, 0.88);
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.pm-table-wrap /deep/ .el-table td {
|
|
|
+ padding: 12px 0;
|
|
|
+}
|
|
|
+
|
|
|
+/* 数值列:右对齐 + 等宽数字 */
|
|
|
+.pm-table-wrap /deep/ .pm-num {
|
|
|
+ font-variant-numeric: tabular-nums;
|
|
|
+ font-feature-settings: 'tnum';
|
|
|
}
|
|
|
-.product-thumb {
|
|
|
- width: 50px;
|
|
|
- height: 50px;
|
|
|
+
|
|
|
+.pm-thumb {
|
|
|
+ width: 40px;
|
|
|
+ height: 40px;
|
|
|
object-fit: cover;
|
|
|
border-radius: 4px;
|
|
|
+ border: 1px solid #f0f0f0;
|
|
|
cursor: pointer;
|
|
|
+ display: block;
|
|
|
}
|
|
|
-.stock-warning {
|
|
|
- color: #f56c6c;
|
|
|
- font-weight: bold;
|
|
|
+
|
|
|
+.pm-placeholder {
|
|
|
+ color: rgba(0, 0, 0, 0.25);
|
|
|
}
|
|
|
-.header-search {
|
|
|
- width: 180px;
|
|
|
+
|
|
|
+.pm-stock-warning {
|
|
|
+ color: #cf1322;
|
|
|
+ font-weight: 600;
|
|
|
}
|
|
|
|
|
|
-.sort-header {
|
|
|
+/* 排序表头 */
|
|
|
+.pm-sort-header {
|
|
|
cursor: pointer;
|
|
|
user-select: none;
|
|
|
+ font-size: 14px;
|
|
|
font-weight: 500;
|
|
|
- font-size: 12px;
|
|
|
}
|
|
|
-.sort-header:hover {
|
|
|
- color: #409eff;
|
|
|
+
|
|
|
+.pm-sort-header:hover {
|
|
|
+ color: #1677ff;
|
|
|
}
|
|
|
-.sort-tag {
|
|
|
- margin-left: 8px;
|
|
|
- cursor: pointer;
|
|
|
+
|
|
|
+.pm-sort-index {
|
|
|
+ display: inline-block;
|
|
|
+ min-width: 14px;
|
|
|
+ height: 14px;
|
|
|
+ line-height: 14px;
|
|
|
+ margin-left: 4px;
|
|
|
+ padding: 0 3px;
|
|
|
+ font-size: 11px;
|
|
|
+ font-weight: 400;
|
|
|
+ text-align: center;
|
|
|
+ color: #1677ff;
|
|
|
+ background: #e6f4ff;
|
|
|
+ border-radius: 2px;
|
|
|
+}
|
|
|
+
|
|
|
+/* ---------- 标准化状态标签(浅底 + 深字 + 同色点) ---------- */
|
|
|
+.pm-status {
|
|
|
+ display: inline-flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ padding: 2px 8px;
|
|
|
+ font-size: 12px;
|
|
|
+ line-height: 20px;
|
|
|
+ border-radius: 2px;
|
|
|
white-space: nowrap;
|
|
|
}
|
|
|
+
|
|
|
+.pm-status__dot {
|
|
|
+ width: 6px;
|
|
|
+ height: 6px;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: currentColor;
|
|
|
+}
|
|
|
+
|
|
|
+.pm-status--pending { background: #fffbe6; color: #d48806; }
|
|
|
+.pm-status--approved { background: #e6f4ff; color: #0958d9; }
|
|
|
+.pm-status--on_shelf { background: #f6ffed; color: #389e0d; }
|
|
|
+.pm-status--off_shelf { background: #f5f5f5; color: rgba(0, 0, 0, 0.65); }
|
|
|
+.pm-status--rejected { background: #fff1f0; color: #cf1322; }
|
|
|
+
|
|
|
+/* ---------- 分页 ---------- */
|
|
|
+.pm-pagination {
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ padding-top: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+/* ---------- 弹窗内区块 ---------- */
|
|
|
+.pm-dialog /deep/ .el-dialog__header {
|
|
|
+ padding: 16px 24px;
|
|
|
+ border-bottom: 1px solid #f0f0f0;
|
|
|
+}
|
|
|
+
|
|
|
+.pm-dialog /deep/ .el-dialog__title {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: rgba(0, 0, 0, 0.88);
|
|
|
+}
|
|
|
+
|
|
|
+.pm-dialog /deep/ .el-dialog__body {
|
|
|
+ padding: 24px;
|
|
|
+}
|
|
|
+
|
|
|
+.pm-dialog /deep/ .el-dialog__footer {
|
|
|
+ padding: 12px 24px;
|
|
|
+ border-top: 1px solid #f0f0f0;
|
|
|
+}
|
|
|
+
|
|
|
+.pm-section {
|
|
|
+ margin-top: 24px;
|
|
|
+}
|
|
|
+
|
|
|
+.pm-section__title {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: rgba(0, 0, 0, 0.88);
|
|
|
+ padding-left: 8px;
|
|
|
+ border-left: 3px solid #1677ff;
|
|
|
+ margin-bottom: 12px;
|
|
|
+ line-height: 1.2;
|
|
|
+}
|
|
|
+
|
|
|
+.pm-qty-in { color: #389e0d; font-variant-numeric: tabular-nums; }
|
|
|
+.pm-qty-out { color: #cf1322; font-variant-numeric: tabular-nums; }
|
|
|
+
|
|
|
+.pm-empty {
|
|
|
+ padding: 32px 0;
|
|
|
+ text-align: center;
|
|
|
+ color: rgba(0, 0, 0, 0.45);
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.pm-text-muted {
|
|
|
+ color: rgba(0, 0, 0, 0.45);
|
|
|
+}
|
|
|
</style>
|