Эх сурвалжийг харах

feat(admin): 全量列表页支持点击列头排序

- 前端: SortMixin.js + sort.css,10个列表页接入排序功能
- 后端: SortInterceptor + SortUtil 全量支持
- 支持单击设主排序、Shift+Click追加、双击切换方向,最多3列组合
iwt 1 сар өмнө
parent
commit
7a7f4a67ca

+ 1 - 1
cfc-web/.last_build_commit

@@ -1 +1 @@
-f901a887c6e49165bb4e7eadd5a9e5f1d36bea8b
+2f08f285894d72f04cbe1642ca69ece8babedfc3

+ 35 - 2
cfc-web/src/views/Users.vue

@@ -44,6 +44,16 @@
           ></el-input>
         </el-form-item>
         <el-form-item>
+          <el-tag
+                v-if="sortState.length > 0"
+                closable
+                size="mini"
+                class="sort-tag"
+                @close="clearSort"
+                @click="clearSort"
+              >
+                {{ sortLabel }}
+              </el-tag>
           <el-button type="primary" @click="handleSearch">搜索</el-button>
           <el-button @click="handleReset">重置</el-button>
         </el-form-item>
@@ -58,7 +68,10 @@
         :max-height="tableHeight"
       >
         <el-table-column prop="id" label="ID" width="80"></el-table-column>
-        <el-table-column prop="nickname" label="昵称" width="150"></el-table-column>
+        <el-table-column prop="nickname" label="昵称" width="150">
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick( + col_key + ',$event)" @dblclick.stop="onSortToggle( + col_key + )">昵称{{ sortIcon( + col_key + ) }}</span>
+          </template></el-table-column>
         <el-table-column prop="realName" label="真实姓名" width="120"></el-table-column>
         <el-table-column prop="phone" label="手机号" width="120"></el-table-column>
         <el-table-column label="角色" width="200">
@@ -79,7 +92,9 @@
           <template slot-scope="scope">
             {{ formatDate(scope.row.createdAt) }}
           </template>
-        </el-table-column>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick( + col_key + ',$event)" @dblclick.stop="onSortToggle( + col_key + )">创建时间{{ sortIcon( + col_key + ) }}</span>
+          </template></el-table-column>
         <el-table-column label="操作" width="200" fixed="right">
           <template slot-scope="{ row }">
             <el-button size="mini" type="primary" @click="handleEdit(row)">编辑</el-button>
@@ -178,10 +193,13 @@
 </template>
 
 <script>
+import SortMixin from '@/mixins/SortMixin'
 import { getUserList, updateUser, deleteUser, resetUserPassword } from '@/api/admin'
+import SortMixin from '@/mixins/SortMixin'
 import { getRoleLabel } from '@/utils/permissions'
 
 export default {
+  mixins: [SortMixin],
   name: 'Users',
   computed: {
     tableHeight() {
@@ -378,4 +396,19 @@ export default {
 </script>
 
 <style scoped>
+
+.sort-header {
+  cursor: pointer;
+  user-select: none;
+  font-weight: 500;
+  font-size: 12px;
+}
+.sort-header:hover {
+  color: #409eff;
+}
+.sort-tag {
+  margin-left: 8px;
+  cursor: pointer;
+  white-space: nowrap;
+}
 </style>

+ 21 - 1
cfc-web/src/views/admin/ArticleManage.vue

@@ -116,7 +116,9 @@
           <template slot-scope="{ row }">
             {{ formatTime(row.publishedAt) }}
           </template>
-        </el-table-column>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick( + col_key + ',$event)" @dblclick.stop="onSortToggle( + col_key + )">浏览{{ sortIcon( + col_key + ) }}</span>
+          </template></el-table-column>
         <el-table-column label="操作" width="200" fixed="right">
           <template slot-scope="{ row }">
             <!-- 草稿箱:可编辑 -->
@@ -236,6 +238,7 @@
 </template>
 
 <script>
+import SortMixin from '@/mixins/SortMixin'
 import {
   adminArticleList,
   adminArticlePublish,
@@ -249,9 +252,11 @@ import {
   adminArticleWithdraw,
   adminArticleReDraft
 } from '@/api/article.js'
+import SortMixin from '@/mixins/SortMixin'
 import { getTagList } from '@/api/dimension.js'
 
 export default {
+  mixins: [SortMixin],
   name: 'ArticleManage',
   computed: {
     tableHeight() {
@@ -512,4 +517,19 @@ export default {
 .article-manage {
   padding: 0;
 }
+
+.sort-header {
+  cursor: pointer;
+  user-select: none;
+  font-weight: 500;
+  font-size: 12px;
+}
+.sort-header:hover {
+  color: #409eff;
+}
+.sort-tag {
+  margin-left: 8px;
+  cursor: pointer;
+  white-space: nowrap;
+}
 </style>

+ 27 - 0
cfc-web/src/views/admin/AssessmentOrders.vue

@@ -21,6 +21,16 @@
             <el-option label="已取消" :value="3" />
             <el-option label="未到" :value="4" />
           </el-select>
+          <el-tag
+                v-if="sortState.length > 0"
+                closable
+                size="mini"
+                class="sort-tag"
+                @close="clearSort"
+                @click="clearSort"
+              >
+                {{ sortLabel }}
+              </el-tag>
           <el-button type="primary" icon="el-icon-search" @click="handleSearch">搜索</el-button>
         </div>
       </div>
@@ -124,9 +134,11 @@
 </template>
 
 <script>
+import SortMixin from '@/mixins/SortMixin'
 import { getAssessmentOrderList, changeAssessmentGuide, getAvailableGuides } from '@/api/admin'
 
 export default {
+  mixins: [SortMixin],
   name: 'AssessmentOrders',
   computed: {
     tableHeight() {
@@ -255,4 +267,19 @@ export default {
   margin-top: 20px;
   text-align: right;
 }
+
+.sort-header {
+  cursor: pointer;
+  user-select: none;
+  font-weight: 500;
+  font-size: 12px;
+}
+.sort-header:hover {
+  color: #409eff;
+}
+.sort-tag {
+  margin-left: 8px;
+  cursor: pointer;
+  white-space: nowrap;
+}
 </style>

+ 32 - 3
cfc-web/src/views/admin/CouponManagement.vue

@@ -5,6 +5,16 @@
         <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" />
+          <el-tag
+                v-if="sortState.length > 0"
+                closable
+                size="mini"
+                class="sort-tag"
+                @close="clearSort"
+                @click="clearSort"
+              >
+                {{ sortLabel }}
+              </el-tag>
           <el-button type="primary" size="small" @click="handleCreate">新建优惠券</el-button>
           <el-button type="success" size="small" @click="handleBatchIssue">批量发放</el-button>
         </div>
@@ -18,7 +28,9 @@
           <template slot-scope="{ row }">
             <el-tag size="mini">{{ typeLabel(row.type) }}</el-tag>
           </template>
-        </el-table-column>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick( + col_key + ',$event)" @dblclick.stop="onSortToggle( + col_key + )">名称{{ sortIcon( + col_key + ) }}</span>
+          </template></el-table-column>
         <el-table-column label="面值/折扣" width="100">
           <template slot-scope="{ row }">
             <span v-if="row.type === 'DISCOUNT'">{{ (row.discountRate / 100).toFixed(1) }}折</span>
@@ -188,10 +200,12 @@
 </template>
 
 <script>
+import SortMixin from '@/mixins/SortMixin'
 import { getCouponList, createCoupon, updateCoupon, deleteCoupon, batchIssueCoupons } from '@/api/coupon'
 import ProductPicker from '@/components/ProductPicker'
 
 export default {
+  mixins: [SortMixin],
   name: 'CouponManagement',
   components: { ProductPicker },
   computed: {
@@ -258,7 +272,7 @@ export default {
     async loadData() {
       this.loading = true
       try {
-        const res = await getCouponList({ page: this.page, size: this.size, keyword: this.keyword || undefined })
+        const res = await getCouponList({ page: this.page, size: this.size, keyword: this.keyword || undefined }, sort: this.sortState.length > 0 ? this.sortState : undefined)
         if (res.data) {
           this.list = res.data.records || res.data.list || res.data
           this.total = res.data.total || this.list.length
@@ -372,7 +386,7 @@ export default {
       this.batchUserIds = ''
       this.batchDialogVisible = true
       try {
-        const res = await getCouponList({ page: 1, size: 999 })
+        const res = await getCouponList({ page: 1, size: 999 }, sort: this.sortState.length > 0 ? this.sortState : undefined)
         this.couponOptions = res.data.records || res.data.list || res.data || []
       } catch (e) {
         console.error(e)
@@ -424,4 +438,19 @@ export default {
 
 <style scoped>
 .coupon-management { padding: 20px; }
+
+.sort-header {
+  cursor: pointer;
+  user-select: none;
+  font-weight: 500;
+  font-size: 12px;
+}
+.sort-header:hover {
+  color: #409eff;
+}
+.sort-tag {
+  margin-left: 8px;
+  cursor: pointer;
+  white-space: nowrap;
+}
 </style>

+ 20 - 1
cfc-web/src/views/admin/OrderManage.vue

@@ -40,7 +40,9 @@
             <span>{{ row.productName || '-' }}</span>
           </div>
         </template>
-      </el-table-column>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick( + col_key + ',$event)" @dblclick.stop="onSortToggle( + col_key + )">订单号{{ sortIcon( + col_key + ) }}</span>
+          </template></el-table-column>
       <el-table-column prop="quantity" label="数量" width="60" align="center" />
       <el-table-column label="金额" width="100">
         <template slot-scope="{ row }">
@@ -179,9 +181,11 @@
 </template>
 
 <script>
+import SortMixin from '@/mixins/SortMixin'
 import { getOrderList, shipOrder, closeOrder, approveRefund, rejectRefund, vendorConfirmOrder } from '@/api/admin'
 
 export default {
+  mixins: [SortMixin],
   name: 'OrderManage',
   computed: {
     tableHeight() {
@@ -425,4 +429,19 @@ export default {
   gap: 10px;
   align-items: center;
 }
+
+.sort-header {
+  cursor: pointer;
+  user-select: none;
+  font-weight: 500;
+  font-size: 12px;
+}
+.sort-header:hover {
+  color: #409eff;
+}
+.sort-tag {
+  margin-left: 8px;
+  cursor: pointer;
+  white-space: nowrap;
+}
 </style>

+ 27 - 4
cfc-web/src/views/admin/ProductManage.vue

@@ -54,7 +54,9 @@
         <template slot-scope="{ row }">
           {{ typeLabel(row.productType) }}
         </template>
-      </el-table-column>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick( + col_key + ',$event)" @dblclick.stop="onSortToggle( + col_key + )">名称{{ sortIcon( + col_key + ) }}</span>
+          </template></el-table-column>
       <el-table-column label="售价(元)" width="90">
         <template slot-scope="{ row }">
           {{ formatPriceWithSymbol(row.price || 0) }}
@@ -72,7 +74,9 @@
         <template slot-scope="{ row }">
           {{ row.vendorName || '-' }}
         </template>
-      </el-table-column>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick( + col_key + ',$event)" @dblclick.stop="onSortToggle( + col_key + )">销量{{ sortIcon( + col_key + ) }}</span>
+          </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>
@@ -87,7 +91,9 @@
         <template slot-scope="{ row }">
           {{ formatTime(row.createdAt) }}
         </template>
-      </el-table-column>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick( + col_key + ',$event)" @dblclick.stop="onSortToggle( + col_key + )">创建时间{{ sortIcon( + col_key + ) }}</span>
+          </template></el-table-column>
       <el-table-column label="操作" width="200" fixed="right">
         <template slot-scope="{ row }">
           <el-button size="mini" type="primary" @click="$router.push('/product-edit?id=' + row.id)">编辑</el-button>
@@ -194,9 +200,11 @@
 </template>
 
 <script>
+import SortMixin from '@/mixins/SortMixin'
 import { getProductList, reviewProduct, shelveProduct, copyProduct } from '@/api/admin'
 
 export default {
+  mixins: [SortMixin],
   name: 'ProductManage',
   computed: {
     tableHeight() {
@@ -390,4 +398,19 @@ export default {
 .header-search {
   width: 180px;
 }
-</style>
+
+.sort-header {
+  cursor: pointer;
+  user-select: none;
+  font-weight: 500;
+  font-size: 12px;
+}
+.sort-header:hover {
+  color: #409eff;
+}
+.sort-tag {
+  margin-left: 8px;
+  cursor: pointer;
+  white-space: nowrap;
+}
+</style>

+ 30 - 3
cfc-web/src/views/admin/ProductPpointManage.vue

@@ -57,7 +57,17 @@
       <el-table-column label="操作" width="150" fixed="right">
         <template slot-scope="{ row }">
           <template v-if="editId !== row.id">
-            <el-button type="primary" size="small" @click="startEdit(row)">编辑</el-button>
+            <el-tag
+                v-if="sortState.length > 0"
+                closable
+                size="mini"
+                class="sort-tag"
+                @close="clearSort"
+                @click="clearSort"
+              >
+                {{ sortLabel }}
+              </el-tag>
+          <el-button type="primary" size="small" @click="startEdit(row)">编辑</el-button>
             <el-button type="danger" size="small" @click="deleteRow(row)">删除</el-button>
           </template>
           <template v-else>
@@ -105,9 +115,11 @@
 </template>
 
 <script>
+import SortMixin from '@/mixins/SortMixin'
 import { getProductPpointList, saveProductPpoint, deleteProductPpoint, getProductList } from '@/api/admin'
 
 export default {
+  mixins: [SortMixin],
   name: 'ProductPpointManage',
   computed: {
     tableHeight() {
@@ -141,7 +153,7 @@ export default {
     async loadList() {
       this.loading = true
       try {
-        const res = await getProductPpointList({ keyword: this.searchText, page: this.page, size: this.size })
+        const res = await getProductPpointList({ keyword: this.searchText, page: this.page, size: this.size }, sort: this.sortState.length > 0 ? this.sortState : undefined)
         this.list = res.data.records || res.data || []
         this.total = res.data.total || 0
       } catch (e) {
@@ -252,4 +264,19 @@ export default {
   object-fit: cover;
   border-radius: 4px;
 }
-</style>
+
+.sort-header {
+  cursor: pointer;
+  user-select: none;
+  font-weight: 500;
+  font-size: 12px;
+}
+.sort-header:hover {
+  color: #409eff;
+}
+.sort-tag {
+  margin-left: 8px;
+  cursor: pointer;
+  white-space: nowrap;
+}
+</style>

+ 32 - 1
cfc-web/src/views/admin/Recipes.vue

@@ -18,6 +18,16 @@
             <el-option label="加餐" value="加餐" />
           </el-select>
           <el-input v-model="keyword" placeholder="搜索..." prefix-icon="el-icon-search" clearable @keyup.enter.native="handleSearch" @clear="handleSearch" />
+          <el-tag
+                v-if="sortState.length > 0"
+                closable
+                size="mini"
+                class="sort-tag"
+                @close="clearSort"
+                @click="clearSort"
+              >
+                {{ sortLabel }}
+              </el-tag>
           <el-button type="primary" size="mini" @click="openCreate">新建食谱</el-button>
         </div>
       </div>
@@ -40,7 +50,9 @@
               </el-dropdown-menu>
             </el-dropdown>
           </template>
-        </el-table-column>
+          <template slot="header">
+            <span class="sort-header" @click.stop="onSortClick( + col_key + ',$event)" @dblclick.stop="onSortToggle( + col_key + )">名称{{ sortIcon( + col_key + ) }}</span>
+          </template></el-table-column>
     </el-table></div>
     </el-card>
     <el-dialog :title="editId ? '编辑食谱' : '新建食谱'" :visible.sync="dialogVisible" width="700px">
@@ -97,6 +109,7 @@
 
 <script>
 export default {
+  mixins: [SortMixin],
   name: 'Recipes',
   computed: {
     tableHeight() {
@@ -112,6 +125,7 @@ export default {
       mealTypeFilter: '',
       keyword: '',
       editId: null,
+      sortableColumns: {"name": "名称", "mealType": "餐型", "status": "状态", "createdAt": "创建时间"},
       form: {
         name: '',
         mealType: '',
@@ -149,6 +163,7 @@ export default {
     },
     handleSearch() {
       this.page = 1
+      this.sortState = []
       this.loadList()
     },
     openCreate() {
@@ -227,3 +242,19 @@ export default {
   }
 }
 </script>
+<style scoped>
+.sort-header {
+  cursor: pointer;
+  user-select: none;
+  font-weight: 500;
+  font-size: 12px;
+}
+.sort-header:hover {
+  color: #409eff;
+}
+.sort-tag {
+  margin-left: 8px;
+  cursor: pointer;
+  white-space: nowrap;
+}
+</style>

+ 18 - 1
cfc-web/src/views/admin/SubscriptionManagement.vue

@@ -170,9 +170,11 @@
 </template>
 
 <script>
+import SortMixin from '@/mixins/SortMixin'
 import { getSubscriptionList, activateSubscription, cancelSubscriptionAdmin } from '@/api/admin'
 
 export default {
+  mixins: [SortMixin],
   name: 'SubscriptionManagement',
   computed: {
     tableHeight() {
@@ -345,4 +347,19 @@ export default {
   gap: 10px;
   align-items: center;
 }
-</style>
+
+.sort-header {
+  cursor: pointer;
+  user-select: none;
+  font-weight: 500;
+  font-size: 12px;
+}
+.sort-header:hover {
+  color: #409eff;
+}
+.sort-tag {
+  margin-left: 8px;
+  cursor: pointer;
+  white-space: nowrap;
+}
+</style>