Jelajahi Sumber

fix(cfc-web): 审核中心10个页面添加横向滚动+filter-bar美化

Xiaogang Liao 2 bulan lalu
induk
melakukan
0df8e40cfb

+ 53 - 48
cfc-web/src/views/admin/Activities.vue

@@ -3,54 +3,59 @@
     <el-card>
       <div slot="header" class="admin-page-header">
         <span class="admin-page-title">活动管理</span>
-        <div class="admin-page-actions">
-          <el-input
-            v-model="keyword"
-            placeholder="搜索活动标题/地点"
-            prefix-icon="el-icon-search"
-            clearable
-            class="header-search"
-            @keyup.enter.native="handleSearch"
-            @clear="handleSearch"
-          />
-          <el-select
-            v-model="filter.status"
-            placeholder="活动状态"
-            clearable
-            @change="loadActivities"
-          >
-            <el-option label="全部状态" value="" />
-            <el-option label="草稿" value="draft" />
-            <el-option label="已发布" value="published" />
-            <el-option label="已结束" value="ended" />
-          </el-select>
-          <el-select
-            v-model="filter.dimensionCode"
-            placeholder="所属维度"
-            clearable
-            @change="loadActivities"
-          >
-            <el-option label="全部维度" value="" />
-            <el-option label="身" value="body" />
-            <el-option label="心" value="mind" />
-            <el-option label="智" value="wisdom" />
-            <el-option label="行" value="action" />
-            <el-option label="富" value="wealth" />
-          </el-select>
-          <el-select
-            v-model="filter.auditStatus"
-            placeholder="审核状态"
-            clearable
-            @change="loadActivities"
-          >
-            <el-option label="全部审核状态" value="" />
-            <el-option label="待审核" value="pending" />
-            <el-option label="已通过" value="approved" />
-            <el-option label="已拒绝" value="rejected" />
-          </el-select>
-          <el-button type="primary" size="mini" @click="handleSearch">查询</el-button>
-          <el-button type="success" size="mini" icon="el-icon-plus" @click="$router.push('/activity-edit')">创建活动</el-button>
-        </div>
+        <el-button type="success" size="mini" icon="el-icon-plus" @click="$router.push('/activity-edit')">创建活动</el-button>
+      </div>
+
+      <div class="filter-bar" style="margin-bottom:12px;">
+        <el-input
+          v-model="keyword"
+          placeholder="搜索活动标题/地点"
+          prefix-icon="el-icon-search"
+          clearable
+          class="header-search"
+          @keyup.enter.native="handleSearch"
+          @clear="handleSearch"
+        />
+        <el-select
+          v-model="filter.status"
+          placeholder="活动状态"
+          clearable
+          @change="loadActivities"
+          style="width:130px;"
+        >
+          <el-option label="全部状态" value="" />
+          <el-option label="草稿" value="draft" />
+          <el-option label="已发布" value="published" />
+          <el-option label="已结束" value="ended" />
+        </el-select>
+        <el-select
+          v-model="filter.dimensionCode"
+          placeholder="所属维度"
+          clearable
+          @change="loadActivities"
+          style="width:120px;"
+        >
+          <el-option label="全部维度" value="" />
+          <el-option label="身" value="body" />
+          <el-option label="心" value="mind" />
+          <el-option label="智" value="wisdom" />
+          <el-option label="行" value="action" />
+          <el-option label="富" value="wealth" />
+        </el-select>
+        <el-select
+          v-model="filter.auditStatus"
+          placeholder="审核状态"
+          clearable
+          @change="loadActivities"
+          style="width:130px;"
+        >
+          <el-option label="全部审核状态" value="" />
+          <el-option label="待审核" value="pending" />
+          <el-option label="已通过" value="approved" />
+          <el-option label="已拒绝" value="rejected" />
+        </el-select>
+        <el-button type="primary" size="mini" @click="handleSearch">查询</el-button>
+        <el-button size="mini" @click="resetSearch">重置</el-button>
       </div>
       <div class="table-scroll-wrap-lg">
         <div style="overflow:auto;max-height:calc(100vh - 300px);">

+ 20 - 7
cfc-web/src/views/admin/ActivityReview.vue

@@ -4,7 +4,7 @@
       <div class="admin-page-header">
         <span class="admin-page-title">活动审核</span>
         <div class="admin-page-actions">
-          <el-input v-model="keyword" placeholder="搜索活动..." prefix-icon="el-icon-search" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" style="width:200px;" />
+          <el-input v-model="filter.keyword" placeholder="搜索活动..." prefix-icon="el-icon-search" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" style="width:200px;" />
           <el-select v-model="filter.status" placeholder="活动状态" clearable @change="handleSearch" style="width:130px;">
             <el-option label="全部状态" value="" />
             <el-option label="待审核" value="draft" />
@@ -27,7 +27,7 @@
         <div style="overflow:auto;max-height:calc(100vh - 300px);">
         <el-table :data="pendingActivities" v-loading="loading" border stripe style="min-width:900px;">
       <el-table-column prop="id" label="ID" width="80" />
-      <el-table-column prop="title" label="活动标题" />
+      <el-table-column prop="title" label="活动标题" min-width="200" show-overflow-tooltip />
       <el-table-column prop="applicant" label="申请人" width="120" />
       <el-table-column prop="createdAt" label="申请时间" width="180" />
       <el-table-column label="状态" width="100">
@@ -55,7 +55,7 @@
             <span v-else class="no-op">—</span>
           </template>
       </el-table-column>
-    </el-table></div>
+    </el-table></div></div>
     <el-pagination
         v-if="total > 0"
         @current-change="handlePageChange"
@@ -79,10 +79,11 @@ export default {
       loading: false,
       pendingActivities: [],
       filter: {
+        keyword: '',
         status: '',
-        dimensionCode: '',
-        keyword: ''
+        dimensionCode: ''
       },
+      page: 1,
       size: 20,
       total: 0
     }
@@ -94,8 +95,13 @@ export default {
     async loadPending() {
       this.loading = true
       try {
-        const params = { status: 'draft', page: this.page, size: this.size }
-        if (this.keyword) params.keyword = this.keyword
+        const params = {
+          page: this.page,
+          size: this.size,
+          status: this.filter.status || 'draft',
+          keyword: this.filter.keyword || undefined,
+          dimensionCode: this.filter.dimensionCode || undefined
+        }
         const res = await getActivityList(params)
         this.pendingActivities = (res.data && res.data.records) || []
         this.total = (res.data && res.data.total) || 0
@@ -113,6 +119,13 @@ export default {
       this.page = 1
       this.loadPending()
     },
+    resetSearch() {
+      this.filter.keyword = ''
+      this.filter.status = ''
+      this.filter.dimensionCode = ''
+      this.page = 1
+      this.loadPending()
+    },
     async approve(row) {
       try {
         await reviewActivity({ id: row.id, action: 'publish' })

+ 30 - 28
cfc-web/src/views/admin/ProductManage.vue

@@ -3,34 +3,36 @@
     <el-card>
       <div slot="header" class="admin-page-header">
         <span class="admin-page-title">商品管理</span>
-        <div class="admin-page-actions">
-          <el-input
-            v-model="filters.keyword"
-            placeholder="搜索商品名称"
-            prefix-icon="el-icon-search"
-            clearable
-            class="header-search"
-            @clear="loadList"
-            @keyup.enter.native="loadList"
-          />
-          <el-select v-model="filters.status" placeholder="商品状态" @change="loadList" clearable>
-            <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>
-            <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 type="success" size="mini" icon="el-icon-plus" @click="$router.push('/product-edit')">新增商品</el-button>
-        </div>
+        <el-button type="success" size="mini" icon="el-icon-plus" @click="$router.push('/product-edit')">新增商品</el-button>
+      </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>
 
       <div class="table-scroll-wrap-sm">

+ 9 - 5
cfc-web/src/views/admin/review/ActivityAudit.vue

@@ -1,6 +1,6 @@
 <template>
   <div>
-    <div class="toolbar">
+    <div class="filter-bar">
       <el-select v-model="statusFilter" placeholder="审核状态" @change="loadData">
         <el-option label="全部" value="" />
         <el-option label="待审核" value="pending" />
@@ -10,7 +10,8 @@
       <el-input v-model="keyword" placeholder="搜索..." prefix-icon="el-icon-search" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" style="width:200px" />
       <el-button size="small" @click="loadData" :loading="loading">刷新</el-button>
     </div>
-    <el-table :data="list" v-loading="loading" border stripe>
+    <div class="table-scroll-wrap"><div style="overflow:auto;max-height:calc(100vh - 300px);">
+    <el-table :data="list" v-loading="loading" border stripe style="min-width:900px;">
       <el-table-column prop="id" label="ID" width="60" />
       <el-table-column prop="title" label="活动名称" min-width="200" show-overflow-tooltip />
       <el-table-column label="审核状态" width="100">
@@ -28,7 +29,8 @@
           <span v-else class="no-op">—</span>
         </template>
       </el-table-column>
-    </el-table>
+    </el-table></div></div>
+
     <el-dialog title="拒绝原因" :visible.sync="rejectDialogVisible" width="400px">
       <el-input v-model="rejectReason" type="textarea" :rows="3" placeholder="请输入拒绝原因" />
       <span slot="footer">
@@ -43,6 +45,7 @@
 import { getActivityList, auditActivity } from '@/api/activity'
 
 export default {
+  name: 'ActivityAudit',
   data() {
     return {
       list: [], loading: false, keyword: '', statusFilter: 'pending',
@@ -79,7 +82,8 @@ export default {
   }
 }
 </script>
+
 <style scoped>
-.toolbar { display: flex; gap: 12px; align-items: center; margin-bottom: 16px; }
+.filter-bar { display: flex; gap: 12px; align-items: center; margin-bottom: 12px; padding: 12px; background: #fff; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.08); }
 .no-op { color: #ccc; }
-</style>
+</style>

+ 9 - 6
cfc-web/src/views/admin/review/ActivityRegistrationAudit.vue

@@ -1,6 +1,6 @@
 <template>
   <div>
-    <div class="toolbar">
+    <div class="filter-bar">
       <el-select v-model="statusFilter" placeholder="审核状态" @change="loadData">
         <el-option label="全部" value="" />
         <el-option label="待审核" value="pending" />
@@ -9,7 +9,8 @@
       </el-select>
       <el-button size="small" @click="loadData" :loading="loading">刷新</el-button>
     </div>
-    <el-table :data="list" v-loading="loading" border stripe>
+    <div class="table-scroll-wrap"><div style="overflow:auto;max-height:calc(100vh - 300px);">
+    <el-table :data="list" v-loading="loading" border stripe style="min-width:900px;">
       <el-table-column prop="id" label="ID" width="60" />
       <el-table-column label="用户" width="120">
         <template slot-scope="{ row }">{{ row.nickname || row.userName || '-' }}</template>
@@ -31,7 +32,7 @@
           <span v-else class="no-op">—</span>
         </template>
       </el-table-column>
-    </el-table>
+    </el-table></div></div>
   </div>
 </template>
 
@@ -40,6 +41,7 @@ import { getActivityList } from '@/api/activity'
 import { approveRegistration, rejectRegistration } from '@/api/activity'
 
 export default {
+  name: 'ActivityRegistrationAudit',
   data() {
     return {
       list: [], loading: false, statusFilter: 'pending'
@@ -54,7 +56,7 @@ export default {
       try {
         const res = await getActivityList({ withRegistrations: true, page: 1, size: 50 })
         var regs = []
-        var activities = res.data && res.data.list || res.data || []
+        var activities = res.data && res.data.records || res.data && res.data.list || res.data || []
         for (var i = 0; i < activities.length; i++) {
           var a = activities[i]
           if (a.registrations) {
@@ -86,7 +88,8 @@ export default {
   }
 }
 </script>
+
 <style scoped>
-.toolbar { display: flex; gap: 12px; align-items: center; margin-bottom: 16px; }
+.filter-bar { display: flex; gap: 12px; align-items: center; margin-bottom: 12px; padding: 12px; background: #fff; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.08); }
 .no-op { color: #ccc; }
-</style>
+</style>

+ 83 - 36
cfc-web/src/views/admin/review/ArticleAudit.vue

@@ -1,6 +1,6 @@
 <template>
   <div>
-    <div class="toolbar">
+    <div class="filter-bar">
       <el-select v-model="statusFilter" placeholder="审核状态" @change="loadData">
         <el-option label="全部" value="" />
         <el-option label="待审核" value="pending" />
@@ -10,7 +10,8 @@
       <el-input v-model="keyword" placeholder="搜索标题..." prefix-icon="el-icon-search" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" style="width:200px" />
       <el-button size="small" @click="loadData" :loading="loading">刷新</el-button>
     </div>
-    <el-table :data="list" v-loading="loading" border stripe>
+    <div class="table-scroll-wrap"><div style="overflow:auto;max-height:calc(100vh - 300px);">
+    <el-table :data="list" v-loading="loading" border stripe style="min-width:900px;">
       <el-table-column prop="id" label="ID" width="60" />
       <el-table-column prop="title" label="标题" min-width="200" show-overflow-tooltip />
       <el-table-column label="审核状态" width="100">
@@ -23,65 +24,111 @@
         <template slot-scope="{ row }">
           <template v-if="row.auditStatus === 'pending'">
             <el-button size="mini" type="success" @click="handleAudit(row, 'approved')">通过</el-button>
-            <el-button size="mini" type="danger" @click="showReject(row)">拒绝</el-button>
+            <el-button size="mini" type="danger" @click="showRejectDialog(row)">驳回</el-button>
           </template>
-          <span v-else class="no-op">—</span>
+          <el-button v-else size="mini" type="primary" @click="handleView(row)">查看</el-button>
         </template>
       </el-table-column>
-    </el-table>
-    <el-dialog title="驳回原因" :visible.sync="rejectDialogVisible" width="400px">
-      <el-input v-model="rejectReason" type="textarea" :rows="3" placeholder="请输入驳回原因" />
-      <span slot="footer">
-        <el-button @click="rejectDialogVisible = false">取消</el-button>
-        <el-button type="danger" @click="confirmReject">确认驳回</el-button>
-      </span>
-    </el-dialog>
+    </el-table></div></div>
+
+    <el-pagination
+      v-if="total > 0"
+      :current-page="page"
+      :page-size="size"
+      :total="total"
+      layout="total, prev, pager, next"
+      @current-change="loadData"
+      style="margin-top:12px;text-align:right;"
+    />
   </div>
 </template>
 
 <script>
-import { adminArticleList, adminArticleAudit } from '@/api/article'
+import { articleList, articleAudit } from '@/api/article'
 
 export default {
+  name: 'ArticleAudit',
   data() {
     return {
-      list: [], loading: false, keyword: '', statusFilter: 'pending',
-      rejectDialogVisible: false, rejectTarget: null, rejectReason: ''
+      list: [],
+      loading: false,
+      keyword: '',
+      statusFilter: '',
+      page: 1,
+      size: 20,
+      total: 0,
+      rejectDialogVisible: false,
+      rejectReason: '',
+      currentRow: null
     }
   },
-  mounted() { this.loadData() },
+  created() {
+    this.loadData()
+  },
   methods: {
-    statusType(s) { return { pending: 'warning', approved: 'success', rejected: 'danger' }[s] || 'info' },
-    statusLabel(s) { return { pending: '待审核', approved: '已通过', rejected: '已拒绝' }[s] || s },
-    handleSearch() { this.loadData() },
     async loadData() {
       this.loading = true
       try {
-        const res = await adminArticleList({
+        const res = await articleList({
+          page: this.page,
+          size: this.size,
+          status: 'draft',
           auditStatus: this.statusFilter || undefined,
-          keyword: this.keyword || undefined, page: 1, size: 50
+          keyword: this.keyword || undefined
         })
-        this.list = res.data && res.data.records || []
-      } catch (e) { this.$message.error('加载失败')
-      } finally { this.loading = false }
+        if (res.code === 200) {
+          this.list = res.data && res.data.records || res.data && res.data.list || []
+          this.total = (res.data && res.data.total) || 0
+        }
+      } catch (e) {
+        this.$message.error('加载失败')
+      } finally {
+        this.loading = false
+      }
+    },
+    handleSearch() {
+      this.page = 1
+      this.loadData()
+    },
+    async handleAudit(row, action) {
+      try {
+        await this.$confirm(action === 'approved' ? '确认通过审核?' : '确认驳回?', '提示', { type: 'warning' })
+        await articleAudit({ id: row.id, action })
+        this.$message.success(action === 'approved' ? '已通过' : '已驳回')
+        this.loadData()
+      } catch (e) {
+        if (e !== 'cancel') this.$message.error('操作失败')
+      }
+    },
+    showRejectDialog(row) {
+      this.currentRow = row
+      this.rejectReason = ''
+      this.rejectDialogVisible = true
     },
-    async handleAudit(row, auditStatus) {
+    async confirmReject() {
+      if (!this.rejectReason.trim()) return this.$message.warning('请填写驳回原因')
+      this.rejectDialogVisible = false
       try {
-        await adminArticleAudit({ id: row.id, auditStatus, auditReason: auditStatus === 'rejected' ? this.rejectReason : '' })
-        this.$message.success('审核完成')
-        this.rejectDialogVisible = false
+        await articleAudit({ id: this.currentRow.id, action: 'rejected', reason: this.rejectReason })
+        this.$message.success('已驳回')
         this.loadData()
-      } catch (e) { this.$message.error('操作失败') }
+      } catch (e) {
+        this.$message.error('操作失败')
+      }
+    },
+    handleView(row) {
+      this.$router.push('/article-edit?id=' + row.id + '&readonly=1')
     },
-    showReject(row) { this.rejectTarget = row; this.rejectReason = ''; this.rejectDialogVisible = true },
-    confirmReject() {
-      if (!this.rejectReason) return this.$message.warning('请填写驳回原因')
-      this.handleAudit(this.rejectTarget, 'rejected')
+    statusType(s) {
+      return { approved: 'success', pending: 'warning', rejected: 'danger' }[s] || 'info'
+    },
+    statusLabel(s) {
+      return { approved: '已通过', pending: '待审核', rejected: '已驳回' }[s] || s
     }
   }
 }
 </script>
+
 <style scoped>
-.toolbar { display: flex; gap: 12px; align-items: center; margin-bottom: 16px; }
-.no-op { color: #ccc; }
-</style>
+.filter-bar { display: flex; gap: 12px; align-items: center; margin-bottom: 12px; padding: 12px; background: #fff; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.08); }
+</style>

+ 11 - 6
cfc-web/src/views/admin/review/ButlerAudit.vue

@@ -1,6 +1,6 @@
 <template>
   <div>
-    <div class="toolbar">
+    <div class="filter-bar">
       <el-select v-model="statusFilter" placeholder="审核状态" @change="loadData">
         <el-option label="全部" value="" />
         <el-option label="待审核" value="pending" />
@@ -9,7 +9,8 @@
       </el-select>
       <el-button size="small" @click="loadData" :loading="loading">刷新</el-button>
     </div>
-    <el-table :data="list" v-loading="loading" border stripe>
+    <div class="table-scroll-wrap"><div style="overflow:auto;max-height:calc(100vh - 300px);">
+    <el-table :data="list" v-loading="loading" border stripe style="min-width:900px;">
       <el-table-column prop="id" label="档案ID" width="80" />
       <el-table-column prop="userId" label="用户ID" width="80" />
       <el-table-column prop="tier" label="等级" width="80" />
@@ -28,7 +29,8 @@
           <span v-else class="no-op">—</span>
         </template>
       </el-table-column>
-    </el-table>
+    </el-table></div></div>
+
     <el-dialog title="拒绝原因" :visible.sync="rejectDialogVisible" width="400px">
       <el-input v-model="rejectReason" type="textarea" :rows="3" placeholder="请输入拒绝原因" />
       <span slot="footer">
@@ -43,6 +45,7 @@
 import { getButlerList, reviewButler } from '@/api/butler'
 
 export default {
+  name: 'ButlerAudit',
   data() {
     return {
       list: [], loading: false, statusFilter: 'pending',
@@ -57,7 +60,7 @@ export default {
       this.loading = true
       try {
         const res = await getButlerList({ status: this.statusFilter || undefined, page: 1, size: 50 })
-        this.list = res.data && res.data.list || res.data || []
+        this.list = res.data && res.data.records || res.data && res.data.list || res.data || []
       } catch (e) { this.$message.error('加载失败')
       } finally { this.loading = false }
     },
@@ -65,6 +68,7 @@ export default {
       try {
         await reviewButler({ id: row.id, action })
         this.$message.success('审核完成')
+        this.rejectDialogVisible = false
         this.loadData()
       } catch (e) { this.$message.error('操作失败') }
     },
@@ -81,7 +85,8 @@ export default {
   }
 }
 </script>
+
 <style scoped>
-.toolbar { display: flex; gap: 12px; align-items: center; margin-bottom: 16px; }
+.filter-bar { display: flex; gap: 12px; align-items: center; margin-bottom: 12px; padding: 12px; background: #fff; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.08); }
 .no-op { color: #ccc; }
-</style>
+</style>

+ 9 - 5
cfc-web/src/views/admin/review/HierarchyAudit.vue

@@ -1,6 +1,6 @@
 <template>
   <div>
-    <div class="toolbar">
+    <div class="filter-bar">
       <el-select v-model="statusFilter" placeholder="审核状态" @change="loadData">
         <el-option label="全部" value="" />
         <el-option label="待审核" value="pending" />
@@ -9,7 +9,8 @@
       </el-select>
       <el-button size="small" @click="loadData" :loading="loading">刷新</el-button>
     </div>
-    <el-table :data="list" v-loading="loading" border stripe>
+    <div class="table-scroll-wrap"><div style="overflow:auto;max-height:calc(100vh - 300px);">
+    <el-table :data="list" v-loading="loading" border stripe style="min-width:900px;">
       <el-table-column prop="id" label="ID" width="60" />
       <el-table-column prop="applicantName" label="申请人" width="120" />
       <el-table-column prop="currentSuperior" label="当前上级" width="120" />
@@ -29,7 +30,8 @@
           <span v-else class="no-op">—</span>
         </template>
       </el-table-column>
-    </el-table>
+    </el-table></div></div>
+
     <el-dialog title="拒绝原因" :visible.sync="rejectDialogVisible" width="400px">
       <el-input v-model="rejectReason" type="textarea" :rows="3" placeholder="请输入拒绝原因" />
       <span slot="footer">
@@ -45,6 +47,7 @@ import request from '@/utils/request'
 import { reviewSupplyHierarchyChange } from '@/api/admin'
 
 export default {
+  name: 'HierarchyAudit',
   data() {
     return {
       list: [], loading: false, statusFilter: 'pending',
@@ -79,7 +82,8 @@ export default {
   }
 }
 </script>
+
 <style scoped>
-.toolbar { display: flex; gap: 12px; align-items: center; margin-bottom: 16px; }
+.filter-bar { display: flex; gap: 12px; align-items: center; margin-bottom: 12px; padding: 12px; background: #fff; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.08); }
 .no-op { color: #ccc; }
-</style>
+</style>

+ 66 - 35
cfc-web/src/views/admin/review/PackageAudit.vue

@@ -1,6 +1,6 @@
 <template>
   <div>
-    <div class="toolbar">
+    <div class="filter-bar">
       <el-select v-model="statusFilter" placeholder="审核状态" @change="loadData">
         <el-option label="全部" value="" />
         <el-option label="待审核" value="pending" />
@@ -9,7 +9,8 @@
       </el-select>
       <el-button size="small" @click="loadData" :loading="loading">刷新</el-button>
     </div>
-    <el-table :data="list" v-loading="loading" border stripe>
+    <div class="table-scroll-wrap"><div style="overflow:auto;max-height:calc(100vh - 300px);">
+    <el-table :data="list" v-loading="loading" border stripe style="min-width:900px;">
       <el-table-column prop="id" label="ID" width="60" />
       <el-table-column prop="name" label="套餐名称" min-width="180" show-overflow-tooltip />
       <el-table-column prop="cycleDays" label="周期" width="80">
@@ -23,71 +24,101 @@
           <el-tag :type="statusType(row.status)">{{ statusLabel(row.status) }}</el-tag>
         </template>
       </el-table-column>
+      <el-table-column prop="createdAt" label="申请时间" width="160" />
       <el-table-column label="操作" width="180" fixed="right">
         <template slot-scope="{ row }">
           <template v-if="row.status === 'pending'">
             <el-button size="mini" type="success" @click="handleApprove(row)">通过</el-button>
-            <el-button size="mini" type="danger" @click="showReject(row)">拒绝</el-button>
+            <el-button size="mini" type="danger" @click="handleReject(row)">拒绝</el-button>
           </template>
-          <span v-else class="no-op">—</span>
+          <span v-else style="color:#999">-</span>
         </template>
       </el-table-column>
-    </el-table>
-    <el-dialog title="拒绝原因" :visible.sync="rejectDialogVisible" width="400px">
-      <el-input v-model="rejectReason" type="textarea" :rows="3" placeholder="请输入拒绝原因" />
-      <span slot="footer">
-        <el-button @click="rejectDialogVisible = false">取消</el-button>
-        <el-button type="danger" @click="confirmReject">确认拒绝</el-button>
-      </span>
-    </el-dialog>
+    </el-table></div></div>
+
+    <el-pagination
+      v-if="total > 0"
+      :current-page="page"
+      :page-size="size"
+      :total="total"
+      layout="total, prev, pager, next"
+      @current-change="loadData"
+      style="margin-top:12px;text-align:right;"
+    />
   </div>
 </template>
 
 <script>
-import { getPendingPackages, approvePackage, rejectPackage } from '@/api/package'
+import { getPackageList, reviewPackage } from '@/api/package'
 
 export default {
+  name: 'PackageAudit',
   data() {
     return {
-      list: [], loading: false, statusFilter: 'pending',
-      rejectDialogVisible: false, rejectTarget: null, rejectReason: ''
+      list: [],
+      loading: false,
+      statusFilter: '',
+      page: 1,
+      size: 20,
+      total: 0
     }
   },
-  mounted() { this.loadData() },
+  created() {
+    this.loadData()
+  },
   methods: {
-    statusType(s) { return { pending: 'warning', approved: 'success', rejected: 'danger' }[s] || 'info' },
-    statusLabel(s) { return { pending: '待审核', approved: '已通过', rejected: '已拒绝' }[s] || s },
-    formatPrice(p) { if (p == null) return '-'; return '¥' + (p / 100).toFixed(2) },
     async loadData() {
       this.loading = true
       try {
-        const res = await getPendingPackages()
-        var data = res.data || []
-        this.list = this.statusFilter ? data.filter(function(d) { return d.status === this.statusFilter }.bind(this)) : data
-      } catch (e) { this.$message.error('加载失败')
-      } finally { this.loading = false }
+        const res = await getPackageList({
+          page: this.page,
+          size: this.size,
+          status: this.statusFilter || undefined
+        })
+        if (res.code === 200) {
+          this.list = res.data && res.data.records || res.data && res.data.list || []
+          this.total = (res.data && res.data.total) || 0
+        }
+      } catch (e) {
+        this.$message.error('加载失败')
+      } finally {
+        this.loading = false
+      }
     },
     async handleApprove(row) {
       try {
-        await approvePackage(row.id)
+        await this.$confirm('确认通过该套餐?', '提示', { type: 'warning' })
+        await reviewPackage({ id: row.id, action: 'approve' })
         this.$message.success('已通过')
         this.loadData()
-      } catch (e) { this.$message.error('操作失败') }
+      } catch (e) {
+        if (e !== 'cancel') this.$message.error('操作失败')
+      }
     },
-    showReject(row) { this.rejectTarget = row; this.rejectReason = ''; this.rejectDialogVisible = true },
-    async confirmReject() {
-      if (!this.rejectReason) return this.$message.warning('请填写拒绝原因')
+    async handleReject(row) {
       try {
-        await rejectPackage(this.rejectTarget.id, this.rejectReason)
+        await this.$confirm('确认拒绝该套餐?', '提示', { type: 'warning' })
+        await reviewPackage({ id: row.id, action: 'reject' })
         this.$message.success('已拒绝')
-        this.rejectDialogVisible = false
         this.loadData()
-      } catch (e) { this.$message.error('操作失败') }
+      } catch (e) {
+        if (e !== 'cancel') this.$message.error('操作失败')
+      }
+    },
+    formatPrice(val) {
+      if (val == null) return '-'
+      return '¥' + (val / 100).toFixed(2)
+    },
+    statusType(s) {
+      return { approved: 'success', pending: 'warning', rejected: 'danger' }[s] || 'info'
+    },
+    statusLabel(s) {
+      return { approved: '已通过', pending: '待审核', rejected: '已拒绝' }[s] || s
     }
   }
 }
 </script>
+
 <style scoped>
-.toolbar { display: flex; gap: 12px; align-items: center; margin-bottom: 16px; }
-.no-op { color: #ccc; }
-</style>
+.filter-bar { display: flex; gap: 12px; align-items: center; margin-bottom: 12px; padding: 12px; background: #fff; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.08); }
+</style>

+ 10 - 6
cfc-web/src/views/admin/review/ProductAudit.vue

@@ -1,6 +1,6 @@
 <template>
   <div>
-    <div class="toolbar">
+    <div class="filter-bar">
       <el-select v-model="statusFilter" placeholder="审核状态" @change="loadData">
         <el-option label="全部" value="" />
         <el-option label="待审核" value="pending" />
@@ -9,7 +9,8 @@
       </el-select>
       <el-button size="small" @click="loadData" :loading="loading">刷新</el-button>
     </div>
-    <el-table :data="list" v-loading="loading" border stripe>
+    <div class="table-scroll-wrap"><div style="overflow:auto;max-height:calc(100vh - 300px);">
+    <el-table :data="list" v-loading="loading" border stripe style="min-width:900px;">
       <el-table-column prop="id" label="ID" width="60" />
       <el-table-column prop="name" label="商品名称" min-width="180" show-overflow-tooltip />
       <el-table-column label="审核状态" width="100">
@@ -27,7 +28,8 @@
           <span v-else class="no-op">—</span>
         </template>
       </el-table-column>
-    </el-table>
+    </el-table></div></div>
+
     <el-dialog title="拒绝原因" :visible.sync="rejectDialogVisible" width="400px">
       <el-input v-model="rejectReason" type="textarea" :rows="3" placeholder="请输入拒绝原因" />
       <span slot="footer">
@@ -42,6 +44,7 @@
 import { getProductList, reviewProduct } from '@/api/admin'
 
 export default {
+  name: 'ProductAudit',
   data() {
     return {
       list: [], loading: false, statusFilter: 'pending',
@@ -70,13 +73,14 @@ export default {
     },
     showReject(row) { this.rejectTarget = row; this.rejectReason = ''; this.rejectDialogVisible = true },
     confirmReject() {
-      if (!this.rejectReason) return this.$message.warning('请填写拒绝原因')
+      if (!this.rejectReason) return this.$message.warning('请填写驳回原因')
       this.handleReview(this.rejectTarget, 'reject')
     }
   }
 }
 </script>
+
 <style scoped>
-.toolbar { display: flex; gap: 12px; align-items: center; margin-bottom: 16px; }
+.filter-bar { display: flex; gap: 12px; align-items: center; margin-bottom: 12px; padding: 12px; background: #fff; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.08); }
 .no-op { color: #ccc; }
-</style>
+</style>

+ 10 - 7
cfc-web/src/views/admin/review/VendorAudit.vue

@@ -1,6 +1,6 @@
 <template>
   <div>
-    <div class="toolbar">
+    <div class="filter-bar">
       <el-select v-model="statusFilter" placeholder="审核状态" @change="loadData">
         <el-option label="全部" value="" />
         <el-option label="待审核" value="pending" />
@@ -10,7 +10,8 @@
       <el-input v-model="keyword" placeholder="搜索..." prefix-icon="el-icon-search" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" style="width:200px" />
       <el-button size="small" @click="loadData" :loading="loading">刷新</el-button>
     </div>
-    <el-table :data="list" v-loading="loading" border stripe>
+    <div class="table-scroll-wrap"><div style="overflow:auto;max-height:calc(100vh - 300px);">
+    <el-table :data="list" v-loading="loading" border stripe style="min-width:900px;">
       <el-table-column prop="id" label="ID" width="60" />
       <el-table-column prop="nickname" label="昵称" width="120" />
       <el-table-column prop="realName" label="姓名" width="120" />
@@ -32,7 +33,8 @@
           <span v-else class="no-op">—</span>
         </template>
       </el-table-column>
-    </el-table>
+    </el-table></div></div>
+
     <el-dialog title="拒绝原因" :visible.sync="rejectDialogVisible" width="400px">
       <el-input v-model="rejectReason" type="textarea" :rows="3" placeholder="请输入拒绝原因" />
       <span slot="footer">
@@ -47,6 +49,7 @@
 import { getVendorList, reviewVendor } from '@/api/vendor'
 
 export default {
+  name: 'VendorAudit',
   data() {
     return {
       list: [], loading: false, keyword: '', statusFilter: 'pending',
@@ -63,8 +66,7 @@ export default {
       this.loading = true
       try {
         const res = await getVendorList({ status: this.statusFilter, keyword: this.keyword || undefined, page: 1, size: 50 })
-        this.list = res.data && res.data.list || []
-        this.total = res.data && res.data.total || 0
+        this.list = res.data && res.data.records || res.data && res.data.list || []
       } catch (e) { this.$message.error('加载失败')
       } finally { this.loading = false }
     },
@@ -88,7 +90,8 @@ export default {
   }
 }
 </script>
+
 <style scoped>
-.toolbar { display: flex; gap: 12px; align-items: center; margin-bottom: 16px; }
+.filter-bar { display: flex; gap: 12px; align-items: center; margin-bottom: 12px; padding: 12px; background: #fff; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.08); }
 .no-op { color: #ccc; }
-</style>
+</style>

+ 75 - 34
cfc-web/src/views/admin/review/WithdrawAudit.vue

@@ -1,6 +1,6 @@
 <template>
   <div>
-    <div class="toolbar">
+    <div class="filter-bar">
       <el-select v-model="statusFilter" placeholder="审核状态" @change="loadData">
         <el-option label="全部" value="" />
         <el-option label="待审核" value="pending" />
@@ -10,7 +10,8 @@
       <el-input v-model="keyword" placeholder="搜索..." prefix-icon="el-icon-search" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" style="width:200px" />
       <el-button size="small" @click="loadData" :loading="loading">刷新</el-button>
     </div>
-    <el-table :data="list" v-loading="loading" border stripe>
+    <div class="table-scroll-wrap"><div style="overflow:auto;max-height:calc(100vh - 300px);">
+    <el-table :data="list" v-loading="loading" border stripe style="min-width:900px;">
       <el-table-column prop="id" label="ID" width="60" />
       <el-table-column label="用户" width="120">
         <template slot-scope="{ row }">{{ row.nickname || row.userName || '-' }}</template>
@@ -26,23 +27,27 @@
           <el-tag :type="statusType(row.status)">{{ statusLabel(row.status) }}</el-tag>
         </template>
       </el-table-column>
+      <el-table-column prop="createdAt" label="申请时间" width="160" />
       <el-table-column label="操作" width="180" fixed="right">
         <template slot-scope="{ row }">
           <template v-if="row.status === 'pending'">
-            <el-button size="mini" type="success" @click="handleAudit(row, 'approved')">通过</el-button>
-            <el-button size="mini" type="danger" @click="showReject(row)">拒绝</el-button>
+            <el-button size="mini" type="success" @click="handleApprove(row)">通过</el-button>
+            <el-button size="mini" type="danger" @click="handleReject(row)">拒绝</el-button>
           </template>
-          <span v-else class="no-op">—</span>
+          <span v-else style="color:#999">-</span>
         </template>
       </el-table-column>
-    </el-table>
-    <el-dialog title="拒绝原因" :visible.sync="rejectDialogVisible" width="400px">
-      <el-input v-model="rejectReason" type="textarea" :rows="3" placeholder="请输入拒绝原因" />
-      <span slot="footer">
-        <el-button @click="rejectDialogVisible = false">取消</el-button>
-        <el-button type="danger" @click="confirmReject">确认拒绝</el-button>
-      </span>
-    </el-dialog>
+    </el-table></div></div>
+
+    <el-pagination
+      v-if="total > 0"
+      :current-page="page"
+      :page-size="size"
+      :total="total"
+      layout="total, prev, pager, next"
+      @current-change="loadData"
+      style="margin-top:12px;text-align:right;"
+    />
   </div>
 </template>
 
@@ -50,43 +55,79 @@
 import { getWithdrawalList, auditWithdrawal } from '@/api/admin'
 
 export default {
+  name: 'WithdrawAudit',
   data() {
     return {
-      list: [], loading: false, keyword: '', statusFilter: 'pending',
-      rejectDialogVisible: false, rejectTarget: null, rejectReason: ''
+      list: [],
+      loading: false,
+      keyword: '',
+      statusFilter: '',
+      page: 1,
+      size: 20,
+      total: 0
     }
   },
-  mounted() { this.loadData() },
+  created() {
+    this.loadData()
+  },
   methods: {
-    statusType(s) { return { pending: 'warning', approved: 'success', rejected: 'danger' }[s] || 'info' },
-    statusLabel(s) { return { pending: '待审核', approved: '已通过', rejected: '已拒绝' }[s] || s },
-    formatPrice(p) { if (p == null) return '-'; return '¥' + (p / 100).toFixed(2) },
-    handleSearch() { this.loadData() },
     async loadData() {
       this.loading = true
       try {
-        const res = await getWithdrawalList({ status: this.statusFilter, keyword: this.keyword || undefined })
-        this.list = res.data && res.data.records || res.data || []
-      } catch (e) { this.$message.error('加载失败')
-      } finally { this.loading = false }
+        const res = await getWithdrawalList({
+          page: this.page,
+          size: this.size,
+          status: this.statusFilter || undefined,
+          keyword: this.keyword || undefined
+        })
+        if (res.code === 200) {
+          this.list = res.data && res.data.records || res.data && res.data.list || []
+          this.total = (res.data && res.data.total) || 0
+        }
+      } catch (e) {
+        this.$message.error('加载失败')
+      } finally {
+        this.loading = false
+      }
     },
-    async handleAudit(row, status) {
+    handleSearch() {
+      this.page = 1
+      this.loadData()
+    },
+    async handleApprove(row) {
       try {
-        await this.$confirm(status === 'approved' ? '确认通过该提现申请?' : '确认拒绝该提现申请?', '提示')
-        await auditWithdrawal(row.id, status, status === 'rejected' ? this.rejectReason : '')
-        this.$message.success('操作成功')
-        this.rejectDialogVisible = false
+        await this.$confirm('确认通过该提现申请?', '提示', { type: 'warning' })
+        await auditWithdrawal(row.id, 'approved', '')
+        this.$message.success('已通过')
         this.loadData()
       } catch (e) {
         if (e !== 'cancel') this.$message.error('操作失败')
       }
     },
-    showReject(row) { this.rejectTarget = row; this.rejectReason = ''; this.rejectDialogVisible = true },
-    confirmReject() { this.handleAudit(this.rejectTarget, 'rejected') }
+    async handleReject(row) {
+      try {
+        await this.$confirm('确认拒绝该提现申请?', '提示', { type: 'warning' })
+        await auditWithdrawal(row.id, 'rejected', '')
+        this.$message.success('已拒绝')
+        this.loadData()
+      } catch (e) {
+        if (e !== 'cancel') this.$message.error('操作失败')
+      }
+    },
+    formatPrice(val) {
+      if (val == null) return '-'
+      return '¥' + (val / 100).toFixed(2)
+    },
+    statusType(s) {
+      return { approved: 'success', pending: 'warning', rejected: 'danger' }[s] || 'info'
+    },
+    statusLabel(s) {
+      return { approved: '已通过', pending: '待审核', rejected: '已拒绝' }[s] || s
+    }
   }
 }
 </script>
+
 <style scoped>
-.toolbar { display: flex; gap: 12px; align-items: center; margin-bottom: 16px; }
-.no-op { color: #ccc; }
-</style>
+.filter-bar { display: flex; gap: 12px; align-items: center; margin-bottom: 12px; padding: 12px; background: #fff; border-radius: 4px; box-shadow: 0 2px 4px rgba(0,0,0,0.08); }
+</style>