Commit 34e69b2dff4e32ae0006c87adf2d5b5d62484630

Authored by “wangming”
1 parent f84588f8

feat: add column settings dropdown and enhance table display

- Introduced a dropdown for column settings in list view, allowing users to customize visible columns and their order via drag-and-drop.
- Updated table columns to dynamically render based on user-selected visibility, improving the overall user experience.
- Enhanced the display of customer information, including merging customer name and ID, and added tooltips for better clarity.
- Refactored existing table columns to utilize a more flexible structure for rendering based on visibility settings.
antis-ncc-admin/src/views/lqKhxx/index.vue
@@ -148,6 +148,28 @@ @@ -148,6 +148,28 @@
148 </el-button-group> 148 </el-button-group>
149 </div> 149 </div>
150 <div class="toolbar-right"> 150 <div class="toolbar-right">
  151 + <el-dropdown v-if="viewMode === 'list'" trigger="click" hide-on-click="false">
  152 + <el-button size="small" icon="el-icon-setting">
  153 + 列设置<i class="el-icon-arrow-down el-icon--right"></i>
  154 + </el-button>
  155 + <el-dropdown-menu slot="dropdown" class="column-setting-dropdown">
  156 + <div class="column-setting-panel" @click.stop>
  157 + <div class="column-setting-title">显示列(拖拽排序)</div>
  158 + <draggable v-model="tableColumnsOrder"
  159 + :options="{ handle: '.drag-handle', animation: 150, forceFallback: true, ghostClass: 'column-drag-ghost' }"
  160 + class="column-draggable-list" @end="saveColumnConfig">
  161 + <div v-for="colId in tableColumnsOrder" :key="colId"
  162 + class="column-draggable-item">
  163 + <i class="el-icon-rank drag-handle"></i>
  164 + <el-checkbox v-model="tableColumnsVisible[colId]"
  165 + @change="saveColumnConfig">
  166 + {{ (tableColumnsMeta[colId] && tableColumnsMeta[colId].label) || colId }}
  167 + </el-checkbox>
  168 + </div>
  169 + </draggable>
  170 + </div>
  171 + </el-dropdown-menu>
  172 + </el-dropdown>
151 <el-radio-group v-model="viewMode" size="small" @change="switchView"> 173 <el-radio-group v-model="viewMode" size="small" @change="switchView">
152 <el-radio-button label="list"> 174 <el-radio-button label="list">
153 <i class="el-icon-s-grid"></i> 列表 175 <i class="el-icon-s-grid"></i> 列表
@@ -288,27 +310,29 @@ @@ -288,27 +310,29 @@
288 </div> 310 </div>
289 </div> 311 </div>
290 </div> 312 </div>
291 - <NCC-table v-if="viewMode === 'list'" v-loading="listLoading" :data="list"  
292 - @selection-change="handleSelectionChange" 313 + <NCC-table v-if="viewMode === 'list'" v-loading="listLoading" :data="list" has-c
  314 + @selection-change="handleSelectionChange" @sort-change="handleSortChange"
293 :header-cell-style="{ background: '#fafafa', color: '#262626', fontWeight: '600' }"> 315 :header-cell-style="{ background: '#fafafa', color: '#262626', fontWeight: '600' }">
294 - <!-- 客户id -->  
295 - <el-table-column label="客户ID" align="left" width="150px" fixed="left"> 316 + <template v-for="colId in visibleColumns">
  317 + <!-- 客户名称+档案号(合并,不单独显示客户ID) -->
  318 + <el-table-column v-if="colId === 'khmc'" key="khmc" label="客户名称" align="left"
  319 + class-name="column-khmc-full"
  320 + :min-width="tableColumnsMeta.khmc.width" :sortable="tableColumnsMeta.khmc.sortable ? 'custom' : false"
  321 + :prop="tableColumnsMeta.khmc.sidx">
296 <template slot-scope="scope"> 322 <template slot-scope="scope">
297 - <span class="cell-text-plain muted">{{ scope.row.id || '无' }}</span> 323 + <el-tooltip :content="(scope.row.khmc || '无') + (scope.row.dah ? ' (' + scope.row.dah + ')' : '')" placement="top">
  324 + <div class="table-cell-with-icon table-cell-name-full">
  325 + <i class="el-icon-user-solid cell-icon primary"></i>
  326 + <span class="cell-text cell-text-full">{{ scope.row.khmc || '无' }}</span>
  327 + <span v-if="scope.row.dah" class="cell-text-plain muted" style="margin-left:6px">({{ scope.row.dah }})</span>
  328 + </div>
  329 + </el-tooltip>
298 </template> 330 </template>
299 </el-table-column> 331 </el-table-column>
300 - <!-- 客户名称 -->  
301 - <el-table-column label="客户名称" align="left" width="100px" fixed="left">  
302 - <template slot-scope="scope">  
303 - <div class="table-cell-with-icon">  
304 - <i class="el-icon-user-solid cell-icon primary"></i>  
305 - <span class="cell-text">{{ scope.row.khmc || '无' }}</span>  
306 - </div>  
307 - </template>  
308 - </el-table-column>  
309 -  
310 <!-- 手机号 --> 332 <!-- 手机号 -->
311 - <el-table-column label="手机号" align="left" width="130px" fixed="left"> 333 + <el-table-column v-else-if="colId === 'sjh'" key="sjh" label="手机号" align="left"
  334 + :min-width="tableColumnsMeta.sjh.width" :sortable="tableColumnsMeta.sjh.sortable ? 'custom' : false"
  335 + :prop="tableColumnsMeta.sjh.sidx">
312 <template slot-scope="scope"> 336 <template slot-scope="scope">
313 <div class="table-cell-with-icon"> 337 <div class="table-cell-with-icon">
314 <i class="el-icon-mobile-phone cell-icon success"></i> 338 <i class="el-icon-mobile-phone cell-icon success"></i>
@@ -316,17 +340,21 @@ @@ -316,17 +340,21 @@
316 </div> 340 </div>
317 </template> 341 </template>
318 </el-table-column> 342 </el-table-column>
319 -  
320 - <!-- 档案号 -->  
321 - <el-table-column label="档案号" align="left" width="150px"> 343 + <!-- 归属门店 -->
  344 + <el-table-column v-else-if="colId === 'gsmd'" key="gsmd" label="归属门店" align="left"
  345 + :min-width="tableColumnsMeta.gsmd.width" :sortable="tableColumnsMeta.gsmd.sortable ? 'custom' : false"
  346 + :prop="tableColumnsMeta.gsmd.sidx">
322 <template slot-scope="scope"> 347 <template slot-scope="scope">
323 - <span class="cell-text-plain">{{ scope.row.dah || '无' }}</span> 348 + <div class="table-cell-with-icon">
  349 + <i class="el-icon-office-building cell-icon info"></i>
  350 + <span class="cell-text">{{ scope.row.gsmdName || '无' }}</span>
  351 + </div>
324 </template> 352 </template>
325 </el-table-column> 353 </el-table-column>
326 -  
327 -  
328 <!-- 性别 --> 354 <!-- 性别 -->
329 - <el-table-column label="性别" align="center" width="70px"> 355 + <el-table-column v-else-if="colId === 'xb'" key="xb" label="性别" align="center"
  356 + :min-width="tableColumnsMeta.xb.width" :sortable="tableColumnsMeta.xb.sortable ? 'custom' : false"
  357 + :prop="tableColumnsMeta.xb.sidx">
330 <template slot-scope="scope"> 358 <template slot-scope="scope">
331 <el-tag :type="scope.row.xb === '男' ? 'primary' : scope.row.xb === '女' ? 'danger' : 'info'" 359 <el-tag :type="scope.row.xb === '男' ? 'primary' : scope.row.xb === '女' ? 'danger' : 'info'"
332 size="small" effect="plain"> 360 size="small" effect="plain">
@@ -334,56 +362,46 @@ @@ -334,56 +362,46 @@
334 </el-tag> 362 </el-tag>
335 </template> 363 </template>
336 </el-table-column> 364 </el-table-column>
337 -  
338 - <!-- 归属门店 -->  
339 - <el-table-column label="归属门店" align="left" width="120px">  
340 - <template slot-scope="scope">  
341 - <div class="table-cell-with-icon">  
342 - <i class="el-icon-office-building cell-icon info"></i>  
343 - <span class="cell-text">{{ scope.row.gsmdName || '无' }}</span>  
344 - </div>  
345 - </template>  
346 - </el-table-column>  
347 -  
348 <!-- 客户类型 --> 365 <!-- 客户类型 -->
349 - <el-table-column label="客户类型" align="center" width="100px"> 366 + <el-table-column v-else-if="colId === 'khlx'" key="khlx" label="客户类型" align="center"
  367 + :min-width="tableColumnsMeta.khlx.width" :sortable="tableColumnsMeta.khlx.sortable ? 'custom' : false"
  368 + :prop="tableColumnsMeta.khlx.sidx">
350 <template slot-scope="scope"> 369 <template slot-scope="scope">
351 <span class="cell-text-plain">{{ scope.row.khlxName || '无' }}</span> 370 <span class="cell-text-plain">{{ scope.row.khlxName || '无' }}</span>
352 </template> 371 </template>
353 </el-table-column> 372 </el-table-column>
354 <!-- 健康师 --> 373 <!-- 健康师 -->
355 - <el-table-column label="健康师" align="left" width="70px"> 374 + <el-table-column v-else-if="colId === 'mainHealthUser'" key="mainHealthUser" label="健康师" align="left" :min-width="tableColumnsMeta.mainHealthUser.width">
356 <template slot-scope="scope"> 375 <template slot-scope="scope">
357 <span class="cell-text-plain">{{ scope.row.mainHealthUserName || '无' }}</span> 376 <span class="cell-text-plain">{{ scope.row.mainHealthUserName || '无' }}</span>
358 </template> 377 </template>
359 </el-table-column> 378 </el-table-column>
360 <!-- 负责顾问 --> 379 <!-- 负责顾问 -->
361 - <el-table-column label="负责顾问" align="left" width="80px"> 380 + <el-table-column v-else-if="colId === 'subHealthUser'" key="subHealthUser" label="负责顾问" align="left" :min-width="tableColumnsMeta.subHealthUser.width">
362 <template slot-scope="scope"> 381 <template slot-scope="scope">
363 <span class="cell-text-plain">{{ scope.row.subHealthUserName || '无' }}</span> 382 <span class="cell-text-plain">{{ scope.row.subHealthUserName || '无' }}</span>
364 </template> 383 </template>
365 </el-table-column> 384 </el-table-column>
366 <!-- 进店渠道 --> 385 <!-- 进店渠道 -->
367 - <el-table-column label="进店渠道" align="left" width="100px"> 386 + <el-table-column v-else-if="colId === 'jdqd'" key="jdqd" label="进店渠道" align="left" :min-width="tableColumnsMeta.jdqd.width">
368 <template slot-scope="scope"> 387 <template slot-scope="scope">
369 <span class="cell-text-plain">{{ scope.row.jdqd || '无' }}</span> 388 <span class="cell-text-plain">{{ scope.row.jdqd || '无' }}</span>
370 </template> 389 </template>
371 </el-table-column> 390 </el-table-column>
372 <!-- 推荐人 --> 391 <!-- 推荐人 -->
373 - <el-table-column label="推荐人" align="left" width="80px"> 392 + <el-table-column v-else-if="colId === 'tjr'" key="tjr" label="推荐人" align="left" :min-width="tableColumnsMeta.tjr.width">
374 <template slot-scope="scope"> 393 <template slot-scope="scope">
375 <span class="cell-text-plain">{{ scope.row.tjrName || '无' }}</span> 394 <span class="cell-text-plain">{{ scope.row.tjrName || '无' }}</span>
376 </template> 395 </template>
377 </el-table-column> 396 </el-table-column>
378 <!-- 拓客人员 --> 397 <!-- 拓客人员 -->
379 - <el-table-column label="拓客人员" align="left" width="80px"> 398 + <el-table-column v-else-if="colId === 'expandUser'" key="expandUser" label="拓客人员" align="left" :min-width="tableColumnsMeta.expandUser.width">
380 <template slot-scope="scope"> 399 <template slot-scope="scope">
381 <span class="cell-text-plain">{{ scope.row.expandUserName || '无' }}</span> 400 <span class="cell-text-plain">{{ scope.row.expandUserName || '无' }}</span>
382 </template> 401 </template>
383 </el-table-column> 402 </el-table-column>
384 -  
385 <!-- 会员类型 --> 403 <!-- 会员类型 -->
386 - <el-table-column label="会员类型" align="center" min-width="130"> 404 + <el-table-column v-else-if="colId === 'memberType'" key="memberType" label="会员类型" align="center" :min-width="tableColumnsMeta.memberType.width">
387 <template slot-scope="scope"> 405 <template slot-scope="scope">
388 <div class="member-tags"> 406 <div class="member-tags">
389 <el-tag v-if="scope.row.isBeautyMember === 1" type="success" size="mini" 407 <el-tag v-if="scope.row.isBeautyMember === 1" type="success" size="mini"
@@ -402,7 +420,9 @@ @@ -402,7 +420,9 @@
402 </el-table-column> 420 </el-table-column>
403 421
404 <!-- 消费等级 --> 422 <!-- 消费等级 -->
405 - <el-table-column label="消费等级" align="center" width="90px"> 423 + <el-table-column v-else-if="colId === 'consumeLevel'" key="consumeLevel" label="消费等级" align="center"
  424 + :min-width="tableColumnsMeta.consumeLevel.width" :sortable="tableColumnsMeta.consumeLevel.sortable ? 'custom' : false"
  425 + :prop="tableColumnsMeta.consumeLevel.sidx">
406 <template slot-scope="scope"> 426 <template slot-scope="scope">
407 <el-tag :type="getConsumeLevelType(scope.row.consumeLevel)" size="small" effect="dark"> 427 <el-tag :type="getConsumeLevelType(scope.row.consumeLevel)" size="small" effect="dark">
408 {{ getConsumeLevelName(scope.row.consumeLevel) }} 428 {{ getConsumeLevelName(scope.row.consumeLevel) }}
@@ -411,42 +431,54 @@ @@ -411,42 +431,54 @@
411 </el-table-column> 431 </el-table-column>
412 432
413 <!-- 开卡总金额 --> 433 <!-- 开卡总金额 -->
414 - <el-table-column label="开卡总金额" align="right" width="120"> 434 + <el-table-column v-else-if="colId === 'totalBillingAmount'" key="totalBillingAmount" label="开卡总金额" align="right"
  435 + :min-width="tableColumnsMeta.totalBillingAmount.width" :sortable="tableColumnsMeta.totalBillingAmount.sortable ? 'custom' : false"
  436 + :prop="tableColumnsMeta.totalBillingAmount.sidx">
415 <template slot-scope="scope"> 437 <template slot-scope="scope">
416 <span class="amount-text primary">{{ formatMoney(scope.row.totalBillingAmount) }}</span> 438 <span class="amount-text primary">{{ formatMoney(scope.row.totalBillingAmount) }}</span>
417 </template> 439 </template>
418 </el-table-column> 440 </el-table-column>
419 441
420 - <!-- 剩余权益总金额 -->  
421 - <el-table-column label="剩余权益" align="right" width="120"> 442 + <!-- 剩余权益 -->
  443 + <el-table-column v-else-if="colId === 'remainingRightsAmount'" key="remainingRightsAmount" label="剩余权益" align="right"
  444 + :min-width="tableColumnsMeta.remainingRightsAmount.width" :sortable="tableColumnsMeta.remainingRightsAmount.sortable ? 'custom' : false"
  445 + :prop="tableColumnsMeta.remainingRightsAmount.sidx">
422 <template slot-scope="scope"> 446 <template slot-scope="scope">
423 <span class="amount-text success">{{ formatMoney(scope.row.remainingRightsAmount) }}</span> 447 <span class="amount-text success">{{ formatMoney(scope.row.remainingRightsAmount) }}</span>
424 </template> 448 </template>
425 </el-table-column> 449 </el-table-column>
426 450
427 - <!-- 首次到店时间 -->  
428 - <el-table-column label="首次到店" align="left" width="110"> 451 + <!-- 首次到店 -->
  452 + <el-table-column v-else-if="colId === 'firstVisitTime'" key="firstVisitTime" label="首次到店" align="left"
  453 + :min-width="tableColumnsMeta.firstVisitTime.width" :sortable="tableColumnsMeta.firstVisitTime.sortable ? 'custom' : false"
  454 + :prop="tableColumnsMeta.firstVisitTime.sidx">
429 <template slot-scope="scope"> 455 <template slot-scope="scope">
430 <span class="cell-text-plain muted">{{ formatDate(scope.row.firstVisitTime) || '无' }}</span> 456 <span class="cell-text-plain muted">{{ formatDate(scope.row.firstVisitTime) || '无' }}</span>
431 </template> 457 </template>
432 </el-table-column> 458 </el-table-column>
433 459
434 - <!-- 最后到店时间 -->  
435 - <el-table-column label="最后到店" align="left" width="110"> 460 + <!-- 最后到店 -->
  461 + <el-table-column v-else-if="colId === 'lastVisitTime'" key="lastVisitTime" label="最后到店" align="left"
  462 + :min-width="tableColumnsMeta.lastVisitTime.width" :sortable="tableColumnsMeta.lastVisitTime.sortable ? 'custom' : false"
  463 + :prop="tableColumnsMeta.lastVisitTime.sidx">
436 <template slot-scope="scope"> 464 <template slot-scope="scope">
437 <span class="cell-text-plain muted">{{ formatDate(scope.row.lastVisitTime) || '无' }}</span> 465 <span class="cell-text-plain muted">{{ formatDate(scope.row.lastVisitTime) || '无' }}</span>
438 </template> 466 </template>
439 </el-table-column> 467 </el-table-column>
440 468
441 - <!-- 到店天数 -->  
442 - <el-table-column label="到店次数" align="center" width="90px"> 469 + <!-- 到店次数 -->
  470 + <el-table-column v-else-if="colId === 'visitDays'" key="visitDays" label="到店次数" align="center"
  471 + :min-width="tableColumnsMeta.visitDays.width" :sortable="tableColumnsMeta.visitDays.sortable ? 'custom' : false"
  472 + :prop="tableColumnsMeta.visitDays.sidx">
443 <template slot-scope="scope"> 473 <template slot-scope="scope">
444 <el-tag size="small" type="info" effect="plain">{{ scope.row.visitDays || 0 }}次</el-tag> 474 <el-tag size="small" type="info" effect="plain">{{ scope.row.visitDays || 0 }}次</el-tag>
445 </template> 475 </template>
446 </el-table-column> 476 </el-table-column>
447 477
448 <!-- 沉睡天数 --> 478 <!-- 沉睡天数 -->
449 - <el-table-column label="沉睡天数" align="center" width="90px"> 479 + <el-table-column v-else-if="colId === 'sleepDays'" key="sleepDays" label="沉睡天数" align="center"
  480 + :min-width="tableColumnsMeta.sleepDays.width" :sortable="tableColumnsMeta.sleepDays.sortable ? 'custom' : false"
  481 + :prop="tableColumnsMeta.sleepDays.sidx">
450 <template slot-scope="scope"> 482 <template slot-scope="scope">
451 <el-tag size="small" :type="scope.row.sleepDays > 30 ? 'warning' : 'success'" 483 <el-tag size="small" :type="scope.row.sleepDays > 30 ? 'warning' : 'success'"
452 effect="plain"> 484 effect="plain">
@@ -454,27 +486,23 @@ @@ -454,27 +486,23 @@
454 </el-tag> 486 </el-tag>
455 </template> 487 </template>
456 </el-table-column> 488 </el-table-column>
  489 + </template>
457 490
458 - <!-- 操作 -->  
459 - <el-table-column label="操作" width="320" align="left" fixed="right"> 491 + <!-- 操作(下拉,减少宽度) -->
  492 + <el-table-column label="操作" width="80" align="center" fixed="right">
460 <template slot-scope="scope"> 493 <template slot-scope="scope">
461 - <div class="action-buttons">  
462 - <el-button type="text" class="detail-btn" icon="el-icon-view"  
463 - @click="openMemberPortrait(scope.row.id)">  
464 - 详情 494 + <el-dropdown trigger="click" @command="handleOpCommand($event, scope.row)">
  495 + <el-button type="text" size="small">
  496 + 操作<i class="el-icon-arrow-down el-icon--right"></i>
465 </el-button> 497 </el-button>
466 - <el-button type="text" class="edit-btn" @click="showMemberRights(scope.row.id)">  
467 - 会员权益  
468 - </el-button>  
469 - <el-button v-has="'btn_edit'" type="text" icon="el-icon-edit"  
470 - @click="addOrUpdateHandle(scope.row.id)" class="edit-btn">  
471 - 编辑  
472 - </el-button>  
473 - <el-button v-has="'btn_remove'" type="text" icon="el-icon-delete"  
474 - @click="handleDel(scope.row.id)" class="delete-btn">  
475 - 删除  
476 - </el-button>  
477 - </div> 498 + <el-dropdown-menu slot="dropdown">
  499 + <el-dropdown-item command="detail" icon="el-icon-view">详情</el-dropdown-item>
  500 + <el-dropdown-item command="rights" icon="el-icon-folder-opened">会员权益</el-dropdown-item>
  501 + <el-dropdown-item v-has="'btn_edit'" command="edit" icon="el-icon-edit">编辑</el-dropdown-item>
  502 + <el-dropdown-item v-has="'btn_remove'" command="delete" icon="el-icon-delete"
  503 + style="color: #F56C6C;">删除</el-dropdown-item>
  504 + </el-dropdown-menu>
  505 + </el-dropdown>
478 </template> 506 </template>
479 </el-table-column> 507 </el-table-column>
480 </NCC-table> 508 </NCC-table>
@@ -499,8 +527,9 @@ import MemberRightsDialog from &#39;./member-rights-dialog.vue&#39; @@ -499,8 +527,9 @@ import MemberRightsDialog from &#39;./member-rights-dialog.vue&#39;
499 import DetailDialog from './detail-dialog.vue' 527 import DetailDialog from './detail-dialog.vue'
500 import MemberPortraitDialog from '@/components/member-portrait-dialog.vue' 528 import MemberPortraitDialog from '@/components/member-portrait-dialog.vue'
501 import { previewDataInterface } from '@/api/systemData/dataInterface' 529 import { previewDataInterface } from '@/api/systemData/dataInterface'
  530 +import draggable from 'vuedraggable'
502 export default { 531 export default {
503 - components: { NCCForm, ExportBox, MemberRightsDialog, DetailDialog, MemberPortraitDialog }, 532 + components: { NCCForm, ExportBox, MemberRightsDialog, DetailDialog, MemberPortraitDialog, draggable },
504 data() { 533 data() {
505 return { 534 return {
506 viewMode: 'list', // list or card 535 viewMode: 'list', // list or card
@@ -553,7 +582,30 @@ export default { @@ -553,7 +582,30 @@ export default {
553 currentPage: 1, 582 currentPage: 1,
554 pageSize: 20, 583 pageSize: 20,
555 sort: "desc", 584 sort: "desc",
556 - sidx: "", 585 + sidx: "id",
  586 + },
  587 + // 列配置:顺序与显示(持久化到 localStorage)
  588 + tableColumnsOrder: ['khmc', 'sjh', 'gsmd', 'xb', 'khlx', 'mainHealthUser', 'subHealthUser', 'jdqd', 'tjr', 'expandUser', 'memberType', 'consumeLevel', 'totalBillingAmount', 'remainingRightsAmount', 'firstVisitTime', 'lastVisitTime', 'visitDays', 'sleepDays'],
  589 + tableColumnsVisible: {},
  590 + tableColumnsMeta: {
  591 + khmc: { label: '客户名称', width: '250', sortable: true, sidx: 'khmc' },
  592 + sjh: { label: '手机号', width: '130px', sortable: true, sidx: 'sjh' },
  593 + gsmd: { label: '归属门店', width: '120px', sortable: true, sidx: 'gsmdName' },
  594 + xb: { label: '性别', width: '85px', sortable: true, sidx: 'xb' },
  595 + khlx: { label: '客户类型', width: '100px', sortable: true, sidx: 'khlx' },
  596 + mainHealthUser: { label: '健康师', width: '85px', sortable: false },
  597 + subHealthUser: { label: '负责顾问', width: '95px', sortable: false },
  598 + jdqd: { label: '进店渠道', width: '100px', sortable: false },
  599 + tjr: { label: '推荐人', width: '85px', sortable: false },
  600 + expandUser: { label: '拓客人员', width: '95px', sortable: false },
  601 + memberType: { label: '会员类型', width: '180', sortable: false },
  602 + consumeLevel: { label: '消费等级', width: '100px', sortable: true, sidx: 'consumeLevel' },
  603 + totalBillingAmount: { label: '开卡总金额', width: '130', sortable: true, sidx: 'totalBillingAmount' },
  604 + remainingRightsAmount: { label: '剩余权益', width: '130', sortable: true, sidx: 'remainingRightsAmount' },
  605 + firstVisitTime: { label: '首次到店', width: '120', sortable: true, sidx: 'firstVisitTime' },
  606 + lastVisitTime: { label: '最后到店', width: '120', sortable: true, sidx: 'lastVisitTime' },
  607 + visitDays: { label: '到店次数', width: '100px', sortable: true, sidx: 'visitDays' },
  608 + sleepDays: { label: '沉睡天数', width: '100px', sortable: true, sidx: 'sleepDays' },
557 }, 609 },
558 formVisible: false, 610 formVisible: false,
559 exportBoxVisible: false, 611 exportBoxVisible: false,
@@ -630,10 +682,15 @@ export default { @@ -630,10 +682,15 @@ export default {
630 kdhyOptions: [], 682 kdhyOptions: [],
631 } 683 }
632 }, 684 },
633 - computed: {}, 685 + computed: {
  686 + visibleColumns() {
  687 + return this.tableColumnsOrder.filter(id => this.tableColumnsVisible[id] !== false)
  688 + }
  689 + },
634 created() { 690 created() {
635 // 默认使用列表视图,不恢复本地存储的视图模式 691 // 默认使用列表视图,不恢复本地存储的视图模式
636 this.viewMode = 'list'; 692 this.viewMode = 'list';
  693 + this.loadColumnConfig();
637 this.initData(); 694 this.initData();
638 this.getgsmdOptions(); 695 this.getgsmdOptions();
639 this.getkhlxOptions(); 696 this.getkhlxOptions();
@@ -1095,6 +1152,41 @@ export default { @@ -1095,6 +1152,41 @@ export default {
1095 } else if (command === 'delete') { 1152 } else if (command === 'delete') {
1096 this.handleDel(item.id); 1153 this.handleDel(item.id);
1097 } 1154 }
  1155 + },
  1156 + handleOpCommand(cmd, row) {
  1157 + if (cmd === 'detail') this.openMemberPortrait(row.id)
  1158 + else if (cmd === 'rights') this.showMemberRights(row.id)
  1159 + else if (cmd === 'edit') this.addOrUpdateHandle(row.id)
  1160 + else if (cmd === 'delete') this.handleDel(row.id)
  1161 + },
  1162 + handleColumnCommand() {},
  1163 + loadColumnConfig() {
  1164 + try {
  1165 + const saved = localStorage.getItem('lqKhxx_columnConfig')
  1166 + if (saved) {
  1167 + const { order, visible } = JSON.parse(saved)
  1168 + if (Array.isArray(order)) this.tableColumnsOrder = order
  1169 + if (visible && typeof visible === 'object') this.tableColumnsVisible = { ...this.tableColumnsVisible, ...visible }
  1170 + }
  1171 + } catch (e) { console.warn('loadColumnConfig error:', e) }
  1172 + // 确保所有列都有 visible 默认值
  1173 + this.tableColumnsOrder.forEach(id => {
  1174 + if (this.tableColumnsVisible[id] === undefined) this.$set(this.tableColumnsVisible, id, true)
  1175 + })
  1176 + },
  1177 + saveColumnConfig() {
  1178 + try {
  1179 + localStorage.setItem('lqKhxx_columnConfig', JSON.stringify({
  1180 + order: this.tableColumnsOrder,
  1181 + visible: this.tableColumnsVisible
  1182 + }))
  1183 + } catch (e) { console.warn('saveColumnConfig error:', e) }
  1184 + },
  1185 + handleSortChange({ prop, order }) {
  1186 + if (!prop || !order) return
  1187 + this.listQuery.sidx = prop
  1188 + this.listQuery.sort = order === 'ascending' ? 'asc' : 'desc'
  1189 + this.initData()
1098 } 1190 }
1099 } 1191 }
1100 } 1192 }
@@ -2672,6 +2764,59 @@ export default { @@ -2672,6 +2764,59 @@ export default {
2672 2764
2673 /* 滑动展开筛选面板 - 流畅的向下展开动画 */ 2765 /* 滑动展开筛选面板 - 流畅的向下展开动画 */
2674 2766
  2767 +/* 列设置下拉面板 */
  2768 +.column-setting-dropdown {
  2769 + padding: 0 !important;
  2770 + min-width: 220px;
  2771 +}
  2772 +.column-setting-panel {
  2773 + padding: 12px 16px;
  2774 + max-height: 400px;
  2775 + overflow-y: auto;
  2776 +}
  2777 +.column-setting-title {
  2778 + font-size: 13px;
  2779 + color: #606266;
  2780 + margin-bottom: 12px;
  2781 + font-weight: 500;
  2782 +}
  2783 +.column-draggable-list {
  2784 + display: flex;
  2785 + flex-direction: column;
  2786 + gap: 4px;
  2787 +}
  2788 +.column-drag-ghost {
  2789 + opacity: 0.5;
  2790 + background: #e6f7ff;
  2791 +}
  2792 +.column-draggable-item {
  2793 + display: flex;
  2794 + align-items: center;
  2795 + gap: 8px;
  2796 + padding: 6px 8px;
  2797 + border-radius: 6px;
  2798 + transition: background 0.2s;
  2799 + &:hover {
  2800 + background: #f5f7fa;
  2801 + }
  2802 + .drag-handle {
  2803 + color: #909399;
  2804 + font-size: 14px;
  2805 + cursor: grab;
  2806 + user-select: none;
  2807 + flex-shrink: 0;
  2808 + padding: 2px;
  2809 + &:active {
  2810 + cursor: grabbing;
  2811 + }
  2812 + }
  2813 + .el-checkbox {
  2814 + flex: 1;
  2815 + margin-right: 0;
  2816 + min-width: 0;
  2817 + }
  2818 +}
  2819 +
2675 .toolbar-card { 2820 .toolbar-card {
2676 margin-bottom: 16px; 2821 margin-bottom: 16px;
2677 border-radius: 12px; 2822 border-radius: 12px;
@@ -2743,6 +2888,11 @@ export default { @@ -2743,6 +2888,11 @@ export default {
2743 font-size: 14px; 2888 font-size: 14px;
2744 border-bottom: 2px solid #e8e8e8; 2889 border-bottom: 2px solid #e8e8e8;
2745 padding: 14px 0; 2890 padding: 14px 0;
  2891 + white-space: nowrap;
  2892 +
  2893 + .cell {
  2894 + white-space: nowrap !important;
  2895 + }
2746 } 2896 }
2747 2897
2748 td { 2898 td {
@@ -2755,6 +2905,17 @@ export default { @@ -2755,6 +2905,17 @@ export default {
2755 backface-visibility: hidden; 2905 backface-visibility: hidden;
2756 } 2906 }
2757 2907
  2908 + td.column-khmc-full {
  2909 + overflow: visible !important;
  2910 + text-overflow: unset;
  2911 +
  2912 + .cell {
  2913 + overflow: visible !important;
  2914 + text-overflow: unset;
  2915 + min-width: max-content;
  2916 + }
  2917 + }
  2918 +
2758 tr { 2919 tr {
2759 transition: background-color 0.15s ease; 2920 transition: background-color 0.15s ease;
2760 will-change: background-color; 2921 will-change: background-color;
@@ -2835,6 +2996,17 @@ export default { @@ -2835,6 +2996,17 @@ export default {
2835 text-overflow: ellipsis; 2996 text-overflow: ellipsis;
2836 white-space: nowrap; 2997 white-space: nowrap;
2837 } 2998 }
  2999 +
  3000 + &.table-cell-name-full {
  3001 + overflow: visible;
  3002 + min-width: max-content;
  3003 +
  3004 + .cell-text-full {
  3005 + overflow: visible;
  3006 + text-overflow: unset;
  3007 + white-space: nowrap;
  3008 + }
  3009 + }
2838 } 3010 }
2839 3011
2840 .cell-text-plain { 3012 .cell-text-plain {
@@ -2872,15 +3044,17 @@ export default { @@ -2872,15 +3044,17 @@ export default {
2872 } 3044 }
2873 } 3045 }
2874 3046
2875 -// 会员标签组 3047 +// 会员标签组(列表内不换行)
2876 .member-tags { 3048 .member-tags {
2877 display: flex; 3049 display: flex;
2878 gap: 4px; 3050 gap: 4px;
2879 - flex-wrap: wrap; 3051 + flex-wrap: nowrap;
2880 justify-content: center; 3052 justify-content: center;
  3053 + white-space: nowrap;
2881 3054
2882 .el-tag { 3055 .el-tag {
2883 margin: 0; 3056 margin: 0;
  3057 + flex-shrink: 0;
2884 } 3058 }
2885 } 3059 }
2886 3060
@@ -3108,16 +3282,18 @@ export default { @@ -3108,16 +3282,18 @@ export default {
3108 } 3282 }
3109 } 3283 }
3110 3284
3111 -/* 标签组样式 */ 3285 +/* 标签组样式(表格内会员类型不换行) */
3112 .member-tags { 3286 .member-tags {
3113 display: flex; 3287 display: flex;
3114 gap: 6px; 3288 gap: 6px;
3115 - flex-wrap: wrap; 3289 + flex-wrap: nowrap;
3116 justify-content: center; 3290 justify-content: center;
3117 align-items: center; 3291 align-items: center;
  3292 + white-space: nowrap;
3118 3293
3119 .el-tag { 3294 .el-tag {
3120 margin: 0; 3295 margin: 0;
  3296 + flex-shrink: 0;
3121 } 3297 }
3122 } 3298 }
3123 3299