|
|
@@ -1,1319 +1,1416 @@
|
|
|
-<!DOCTYPE html>
|
|
|
-<html lang="zh-CN">
|
|
|
-<head>
|
|
|
- <meta charset="UTF-8">
|
|
|
- <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
|
|
|
- <title>列表</title>
|
|
|
- <!-- 引入基础依赖(统一由 base.js 动态注入其他依赖) -->
|
|
|
- <script src="/js/mp_base/base.js"></script>
|
|
|
- <style>
|
|
|
- #app {
|
|
|
- background: #f5f5f5;
|
|
|
- min-height: 100vh;
|
|
|
- }
|
|
|
-
|
|
|
- /* 防止Vue模板闪烁 */
|
|
|
- [v-cloak] {
|
|
|
- display: none !important;
|
|
|
- }
|
|
|
-
|
|
|
- /* 搜索筛选区域 */
|
|
|
- .search-filter-container {
|
|
|
- background: #f5f5f5;
|
|
|
- padding: 15px;
|
|
|
- position: sticky;
|
|
|
- top: 0;
|
|
|
- z-index: 100;
|
|
|
- display: flex;
|
|
|
- justify-content: flex-end;
|
|
|
- flex-wrap: wrap;
|
|
|
- gap: 10px;
|
|
|
- }
|
|
|
- .search-filter-container .ss-select-container{
|
|
|
- border: 1px solid #ccc;
|
|
|
- border-radius: 4px;
|
|
|
- padding: 0 10px;
|
|
|
- height: 34px;
|
|
|
- box-sizing: border-box;
|
|
|
- }
|
|
|
-
|
|
|
- /* 加载状态 */
|
|
|
- .loading-container {
|
|
|
- display: flex;
|
|
|
- flex-direction: column;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- height: 200px;
|
|
|
- color: #666;
|
|
|
- }
|
|
|
-
|
|
|
- .loading-spinner {
|
|
|
- width: 40px;
|
|
|
- height: 40px;
|
|
|
- border: 4px solid #f3f3f3;
|
|
|
- border-top: 4px solid #40ac6d;
|
|
|
- border-radius: 50%;
|
|
|
- animation: spin 1s linear infinite;
|
|
|
- margin-bottom: 15px;
|
|
|
- }
|
|
|
-
|
|
|
- @keyframes spin {
|
|
|
- 0% { transform: rotate(0deg); }
|
|
|
- 100% { transform: rotate(360deg); }
|
|
|
- }
|
|
|
-
|
|
|
- .loading-text {
|
|
|
- font-size: 14px;
|
|
|
- }
|
|
|
-
|
|
|
- /* 列表容器 */
|
|
|
- .list-container {
|
|
|
- padding:0 15px;
|
|
|
- }
|
|
|
-
|
|
|
- /* 空状态 */
|
|
|
- .empty-state {
|
|
|
- text-align: center;
|
|
|
- padding: 60px 20px;
|
|
|
- color: #999;
|
|
|
- }
|
|
|
-
|
|
|
- .empty-icon {
|
|
|
- font-size: 48px;
|
|
|
- margin-bottom: 15px;
|
|
|
- }
|
|
|
-
|
|
|
- .empty-text {
|
|
|
- font-size: 16px;
|
|
|
- }
|
|
|
-
|
|
|
- /* 卡片内容样式 - 按照小程序list.vue转换 */
|
|
|
- .card-content .card-header {
|
|
|
- margin-bottom: 10px; /* 20rpx -> 10px */
|
|
|
- }
|
|
|
-
|
|
|
- .card-content .card-title {
|
|
|
- font-size: 16px; /* 32rpx -> 16px */
|
|
|
- font-weight: bold;
|
|
|
- color: #333;
|
|
|
- }
|
|
|
-
|
|
|
- .card-content .card-description {
|
|
|
- font-size: 14px; /* 28rpx -> 14px */
|
|
|
- color: #666;
|
|
|
- margin-bottom: 8px; /* 15rpx -> 8px */
|
|
|
- }
|
|
|
-
|
|
|
- .card-content .attribute-group {
|
|
|
- display: flex;
|
|
|
- flex-wrap: wrap;
|
|
|
- column-gap: 10px; /* 20rpx -> 10px */
|
|
|
- }
|
|
|
-
|
|
|
- .card-content .attribute-item {
|
|
|
- display: flex;
|
|
|
- margin-bottom: 5px; /* 10rpx -> 5px */
|
|
|
- }
|
|
|
-
|
|
|
- .card-content .attr-label {
|
|
|
- font-size: 13px; /* 26rpx -> 13px */
|
|
|
- color: #999;
|
|
|
- }
|
|
|
-
|
|
|
- .card-content .attr-value {
|
|
|
- font-size: 13px; /* 26rpx -> 13px */
|
|
|
- color: #333;
|
|
|
- flex: 1;
|
|
|
- }
|
|
|
-
|
|
|
- /* 状态文本样式 */
|
|
|
- .status-text {
|
|
|
- font-weight: bold;
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- /* 加载更多提示样式 */
|
|
|
- .load-more-container {
|
|
|
- text-align: center;
|
|
|
- padding: 20px;
|
|
|
- color: #999;
|
|
|
- font-size: 14px;
|
|
|
- }
|
|
|
-
|
|
|
- .load-more-loading {
|
|
|
- color: #007aff;
|
|
|
- }
|
|
|
-
|
|
|
- .load-more-end {
|
|
|
- color: #999;
|
|
|
- }
|
|
|
-
|
|
|
- .load-more-tip {
|
|
|
- color: #ccc;
|
|
|
- }
|
|
|
-
|
|
|
- /* 回到顶部按钮 */
|
|
|
- .back-to-top-btn {
|
|
|
- width: 50px;
|
|
|
- height: 50px;
|
|
|
- border-radius: 50%;
|
|
|
- background: rgba(87, 93, 109, 0.5);
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
- position: fixed;
|
|
|
- bottom: 200px;
|
|
|
- right: 15px;
|
|
|
- cursor: pointer;
|
|
|
- transition: all 0.3s ease;
|
|
|
- z-index: 999;
|
|
|
- }
|
|
|
-
|
|
|
- .back-to-top-btn:active {
|
|
|
- transform: scale(0.9);
|
|
|
- }
|
|
|
-
|
|
|
- .back-to-top-inner {
|
|
|
- width: 42px;
|
|
|
- height: 42px;
|
|
|
- border-radius: 50%;
|
|
|
- background: rgba(87, 93, 109, 0.5);
|
|
|
- display: flex;
|
|
|
- justify-content: center;
|
|
|
- align-items: center;
|
|
|
- }
|
|
|
-
|
|
|
- /* 搜索弹窗 */
|
|
|
- .search-modal-mask {
|
|
|
- position: fixed;
|
|
|
- top: 0;
|
|
|
- left: 0;
|
|
|
- right: 0;
|
|
|
- bottom: 0;
|
|
|
- background: rgba(0, 0, 0, 0.5);
|
|
|
- z-index: 1000;
|
|
|
- display: flex;
|
|
|
- align-items: flex-end;
|
|
|
- }
|
|
|
-
|
|
|
- .search-modal-content {
|
|
|
- width: 100%;
|
|
|
- background: white;
|
|
|
- animation: slideUp 0.3s ease-out;
|
|
|
- /* 让键盘弹起时自动上移 */
|
|
|
- position: relative;
|
|
|
- }
|
|
|
-
|
|
|
- @keyframes slideUp {
|
|
|
- from {
|
|
|
- transform: translateY(100%);
|
|
|
- }
|
|
|
- to {
|
|
|
- transform: translateY(0);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- .search-input-container {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- height: 50px;
|
|
|
- background: white;
|
|
|
- border-top: 1px solid #e5e5e5;
|
|
|
- /* 适配iPhone底部安全区域 */
|
|
|
- /* padding-bottom: env(safe-area-inset-bottom); */
|
|
|
- }
|
|
|
-
|
|
|
- .search-input {
|
|
|
- flex: 1;
|
|
|
- height: 100%;
|
|
|
- border: none;
|
|
|
- padding: 0 15px;
|
|
|
- font-size: 16px;
|
|
|
- outline: none;
|
|
|
- background: transparent;
|
|
|
- }
|
|
|
-
|
|
|
- .search-divider {
|
|
|
- width: 1px;
|
|
|
- height: 30px;
|
|
|
- background: #d5d8dc;
|
|
|
- }
|
|
|
-
|
|
|
- .search-icon-btn {
|
|
|
- width: 60px;
|
|
|
- height: 100%;
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- justify-content: center;
|
|
|
- cursor: pointer;
|
|
|
- background: transparent;
|
|
|
- border: none;
|
|
|
- padding: 0;
|
|
|
- }
|
|
|
-
|
|
|
- .search-icon-btn:active {
|
|
|
- background: #f5f5f5;
|
|
|
- }
|
|
|
-
|
|
|
- </style>
|
|
|
-</head>
|
|
|
-<body>
|
|
|
- <div id="app" v-cloak>
|
|
|
- <!-- 搜索和筛选区域 -->
|
|
|
- <div class="search-filter-container">
|
|
|
- <!-- 动态下拉选择器 -->
|
|
|
- <template v-for="(options, fieldName) in filterSelectOptions" :key="fieldName">
|
|
|
- <ss-select
|
|
|
- v-model="selectedFilters[fieldName]"
|
|
|
- :placeholder="`选择${getFieldDesc(fieldName)}`"
|
|
|
- :options="options"
|
|
|
- @change="handleFilterChange"
|
|
|
- >
|
|
|
- </ss-select>
|
|
|
- </template>
|
|
|
- <!-- 完全由buttonList决定的动态按钮组 -->
|
|
|
- <ss-search-button
|
|
|
- v-for="(button, index) in buttonList"
|
|
|
- :key="index"
|
|
|
- :text="getButtonText(button)"
|
|
|
-
|
|
|
- @click="handleButtonClick(button)"
|
|
|
- >
|
|
|
- </ss-search-button>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 加载状态 -->
|
|
|
- <div v-if="loading" class="loading-container">
|
|
|
- <div class="loading-spinner"></div>
|
|
|
- <div class="loading-text">加载中...</div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 列表区域 -->
|
|
|
- <div v-else class="list-container">
|
|
|
- <!-- 空状态 -->
|
|
|
- <div v-if="list.length === 0" class="empty-state">
|
|
|
- <div class="empty-icon">📋</div>
|
|
|
- <div class="empty-text">暂无数据</div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 数据列表 -->
|
|
|
- <div v-else>
|
|
|
- <ss-card
|
|
|
- v-for="(item, index) in list"
|
|
|
- :key="index"
|
|
|
- :item="item"
|
|
|
- @click="handleCardClick(item)"
|
|
|
- @button-click="handleCardAction"
|
|
|
- >
|
|
|
- <!-- 卡片内容 - 按照小程序list.vue的结构,使用API数据 -->
|
|
|
- <div class="card-content">
|
|
|
- <!-- 主标题 (first) -->
|
|
|
- <div class="card-header" v-if="item.firstDisplay">
|
|
|
- <div class="card-title">{{ item.firstDisplay }}</div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 描述 (second) -->
|
|
|
- <div class="card-description" v-if="item.secondDisplay">
|
|
|
- {{ item.secondDisplay }}
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 属性列表 (third) -->
|
|
|
- <div class="card-attributes" v-if="item.thirdDisplay && item.thirdDisplay.length > 0">
|
|
|
- <div
|
|
|
- v-for="(group, groupIndex) in item.thirdDisplay"
|
|
|
- :key="groupIndex"
|
|
|
- class="attribute-group"
|
|
|
- >
|
|
|
- <div
|
|
|
- v-for="(attr, attrIndex) in group"
|
|
|
- :key="attrIndex"
|
|
|
- class="attribute-item"
|
|
|
- >
|
|
|
- <span class="attr-label">{{ attr.field.desc }}:</span>
|
|
|
- <span class="attr-value">{{ attr.displayValue }}</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </ss-card>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 加载更多提示 -->
|
|
|
- <div class="load-more-container" v-if="list.length > 0">
|
|
|
- <div v-if="isLoadingMore" class="load-more-loading">
|
|
|
- <span>正在加载更多...</span>
|
|
|
- </div>
|
|
|
- <div v-else-if="!hasMore" class="load-more-end">
|
|
|
- <span>没有更多数据了</span>
|
|
|
- </div>
|
|
|
- <div v-else class="load-more-tip">
|
|
|
- <span>滚动到底部加载更多</span>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 回到顶部按钮 -->
|
|
|
- <div
|
|
|
- v-if="showBackToTop"
|
|
|
- class="back-to-top-btn"
|
|
|
- @touchstart="handleLongPressStart"
|
|
|
- @touchend="handleLongPressEnd"
|
|
|
- @touchcancel="handleLongPressCancel"
|
|
|
- @click.prevent="handleBackToTopClick"
|
|
|
- >
|
|
|
- <div class="back-to-top-inner">
|
|
|
- <Icon name="icon-huidaodingbu" size="40" color="#fff"></Icon>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- <!-- 搜索弹窗 -->
|
|
|
- <div v-if="showSearchModal" class="search-modal-mask" @click="closeSearchModal">
|
|
|
- <div class="search-modal-content" @click.stop>
|
|
|
- <div class="search-input-container">
|
|
|
- <input
|
|
|
- ref="searchInput"
|
|
|
- v-model="searchKeyword"
|
|
|
- type="search"
|
|
|
- inputmode="search"
|
|
|
- class="search-input"
|
|
|
- placeholder="请输入关键词"
|
|
|
- @keyup.enter="performSearch"
|
|
|
- autocomplete="off"
|
|
|
- />
|
|
|
- <div class="search-divider"></div>
|
|
|
- <button class="search-icon-btn" @click="performSearch">
|
|
|
- <Icon name="icon-chazhao" size="24" color="#575d6d"></Icon>
|
|
|
- </button>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
- </div>
|
|
|
-
|
|
|
- </div>
|
|
|
- <script>
|
|
|
- // 等待SS框架加载完成
|
|
|
- window.SS.ready(function () {
|
|
|
- // 使用SS框架的方式创建Vue实例
|
|
|
- window.SS.dom.initializeFormApp({
|
|
|
- el: '#app',
|
|
|
- data() {
|
|
|
- return {
|
|
|
- // 加载状态
|
|
|
- loading: true,
|
|
|
-
|
|
|
- // 列表数据
|
|
|
- list: [],
|
|
|
- originalList: [], // 原始数据,用于筛选
|
|
|
-
|
|
|
- // 分页相关
|
|
|
- currentPage: 1,
|
|
|
- pageSize: 10,
|
|
|
- hasMore: true,
|
|
|
- isLoadingMore: false,
|
|
|
-
|
|
|
- // 页面参数
|
|
|
- pageParams: {},
|
|
|
- service: '', // init服务名称
|
|
|
- // 功能说明:对接PC两段式接口(init 返回 home/list 服务名) by xu 2026-02-28
|
|
|
- ssSearchPobjHomeServName: '',
|
|
|
- ssSearchPobjListServName: '',
|
|
|
-
|
|
|
- // API返回的动态配置
|
|
|
- buttonList: [],
|
|
|
- fieldsList: [],
|
|
|
- ssPaging: null,
|
|
|
-
|
|
|
- // 动态搜索筛选选项
|
|
|
- filterOptions: [],
|
|
|
- sortOptions: [],
|
|
|
-
|
|
|
- // 下拉选择器数据
|
|
|
- selectedFilters: {}, // 动态筛选条件
|
|
|
- filterSelectOptions: {}, // 各个筛选字段的选项
|
|
|
-
|
|
|
- // 字典缓存
|
|
|
- dictCache: new Map(),
|
|
|
-
|
|
|
- // 显示字段配置 - 根据service动态设置
|
|
|
- displayFields: [],
|
|
|
-
|
|
|
- // 卡片操作按钮
|
|
|
- cardActions: [
|
|
|
- { text: '查看', name: 'view' },
|
|
|
- { text: '编辑', name: 'edit' }
|
|
|
- ],
|
|
|
-
|
|
|
- // 回到顶部按钮
|
|
|
- showBackToTop: false,
|
|
|
-
|
|
|
- // 是否存在关键词搜索
|
|
|
- hasKeyWord:false,
|
|
|
-
|
|
|
- // 搜索相关
|
|
|
- showSearchModal: false,
|
|
|
- searchKeyword: '',
|
|
|
-
|
|
|
- // 长按相关
|
|
|
- longPressTimer: null,
|
|
|
- isLongPress: false,
|
|
|
-
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- mounted() {
|
|
|
- // 页面加载时初始化
|
|
|
- this.initPage();
|
|
|
-
|
|
|
- // 监听页面刷新通知
|
|
|
- this.setupRefreshListener();
|
|
|
-
|
|
|
- // 监听滚动事件,实现滚动加载
|
|
|
- this.setupScrollListener();
|
|
|
-
|
|
|
- // 开发模式:加载Mock数据(测试用)
|
|
|
- // this.loadMockData();
|
|
|
- },
|
|
|
-
|
|
|
- beforeUnmount() {
|
|
|
- // 清理刷新监听器
|
|
|
- if (this.refreshCleanup) {
|
|
|
- this.refreshCleanup();
|
|
|
- }
|
|
|
-
|
|
|
- // 清理滚动监听器
|
|
|
- if (this.scrollCleanup) {
|
|
|
- this.scrollCleanup();
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- methods: {
|
|
|
- // 加载Mock数据(用于开发测试)
|
|
|
- loadMockData() {
|
|
|
- console.log('🎭 加载Mock数据...');
|
|
|
-
|
|
|
- const mockData = [
|
|
|
- {
|
|
|
- firstDisplay: '张三 - 2024级计算机科学与技术1班',
|
|
|
- secondDisplay: '学号:2024001 | 手机:138****1234',
|
|
|
- thirdDisplay: [
|
|
|
- [
|
|
|
- { field: { desc: '性别' }, displayValue: '男' },
|
|
|
- { field: { desc: '年龄' }, displayValue: '20' },
|
|
|
- { field: { desc: '状态' }, displayValue: '在读' }
|
|
|
- ],
|
|
|
- [
|
|
|
- { field: { desc: '入学时间' }, displayValue: '2024-09-01' },
|
|
|
- { field: { desc: '辅导员' }, displayValue: '王老师' }
|
|
|
- ]
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- firstDisplay: '李四 - 2024级软件工程2班',
|
|
|
- secondDisplay: '学号:2024002 | 手机:139****5678',
|
|
|
- thirdDisplay: [
|
|
|
- [
|
|
|
- { field: { desc: '性别' }, displayValue: '女' },
|
|
|
- { field: { desc: '年龄' }, displayValue: '19' },
|
|
|
- { field: { desc: '状态' }, displayValue: '在读' }
|
|
|
- ],
|
|
|
- [
|
|
|
- { field: { desc: '入学时间' }, displayValue: '2024-09-01' },
|
|
|
- { field: { desc: '辅导员' }, displayValue: '李老师' }
|
|
|
- ]
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- firstDisplay: '王五 - 2023级人工智能1班',
|
|
|
- secondDisplay: '学号:2023003 | 手机:136****9012',
|
|
|
- thirdDisplay: [
|
|
|
- [
|
|
|
- { field: { desc: '性别' }, displayValue: '男' },
|
|
|
- { field: { desc: '年龄' }, displayValue: '21' },
|
|
|
- { field: { desc: '状态' }, displayValue: '在读' }
|
|
|
- ],
|
|
|
- [
|
|
|
- { field: { desc: '入学时间' }, displayValue: '2023-09-01' },
|
|
|
- { field: { desc: '辅导员' }, displayValue: '赵老师' }
|
|
|
- ]
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- firstDisplay: '赵六 - 2024级数据科学与大数据技术1班',
|
|
|
- secondDisplay: '学号:2024004 | 手机:137****3456',
|
|
|
- thirdDisplay: [
|
|
|
- [
|
|
|
- { field: { desc: '性别' }, displayValue: '女' },
|
|
|
- { field: { desc: '年龄' }, displayValue: '20' },
|
|
|
- { field: { desc: '状态' }, displayValue: '在读' }
|
|
|
- ],
|
|
|
- [
|
|
|
- { field: { desc: '入学时间' }, displayValue: '2024-09-01' },
|
|
|
- { field: { desc: '辅导员' }, displayValue: '刘老师' }
|
|
|
- ]
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- firstDisplay: '钱七 - 2023级网络工程1班',
|
|
|
- secondDisplay: '学号:2023005 | 手机:135****7890',
|
|
|
- thirdDisplay: [
|
|
|
- [
|
|
|
- { field: { desc: '性别' }, displayValue: '男' },
|
|
|
- { field: { desc: '年龄' }, displayValue: '21' },
|
|
|
- { field: { desc: '状态' }, displayValue: '休学' }
|
|
|
- ],
|
|
|
- [
|
|
|
- { field: { desc: '入学时间' }, displayValue: '2023-09-01' },
|
|
|
- { field: { desc: '辅导员' }, displayValue: '周老师' }
|
|
|
- ]
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- firstDisplay: '孙八 - 2024级信息安全1班',
|
|
|
- secondDisplay: '学号:2024006 | 手机:133****2468',
|
|
|
- thirdDisplay: [
|
|
|
- [
|
|
|
- { field: { desc: '性别' }, displayValue: '女' },
|
|
|
- { field: { desc: '年龄' }, displayValue: '19' },
|
|
|
- { field: { desc: '状态' }, displayValue: '在读' }
|
|
|
- ],
|
|
|
- [
|
|
|
- { field: { desc: '入学时间' }, displayValue: '2024-09-01' },
|
|
|
- { field: { desc: '辅导员' }, displayValue: '吴老师' }
|
|
|
- ]
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- firstDisplay: '周九 - 2023级物联网工程1班',
|
|
|
- secondDisplay: '学号:2023007 | 手机:188****1357',
|
|
|
- thirdDisplay: [
|
|
|
- [
|
|
|
- { field: { desc: '性别' }, displayValue: '男' },
|
|
|
- { field: { desc: '年龄' }, displayValue: '22' },
|
|
|
- { field: { desc: '状态' }, displayValue: '在读' }
|
|
|
- ],
|
|
|
- [
|
|
|
- { field: { desc: '入学时间' }, displayValue: '2023-09-01' },
|
|
|
- { field: { desc: '辅导员' }, displayValue: '郑老师' }
|
|
|
- ]
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- firstDisplay: '吴十 - 2024级云计算1班',
|
|
|
- secondDisplay: '学号:2024008 | 手机:189****2468',
|
|
|
- thirdDisplay: [
|
|
|
- [
|
|
|
- { field: { desc: '性别' }, displayValue: '女' },
|
|
|
- { field: { desc: '年龄' }, displayValue: '20' },
|
|
|
- { field: { desc: '状态' }, displayValue: '在读' }
|
|
|
- ],
|
|
|
- [
|
|
|
- { field: { desc: '入学时间' }, displayValue: '2024-09-01' },
|
|
|
- { field: { desc: '辅导员' }, displayValue: '冯老师' }
|
|
|
- ]
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- firstDisplay: '郑十一 - 2023级区块链工程1班',
|
|
|
- secondDisplay: '学号:2023009 | 手机:180****3691',
|
|
|
- thirdDisplay: [
|
|
|
- [
|
|
|
- { field: { desc: '性别' }, displayValue: '男' },
|
|
|
- { field: { desc: '年龄' }, displayValue: '21' },
|
|
|
- { field: { desc: '状态' }, displayValue: '在读' }
|
|
|
- ],
|
|
|
- [
|
|
|
- { field: { desc: '入学时间' }, displayValue: '2023-09-01' },
|
|
|
- { field: { desc: '辅导员' }, displayValue: '陈老师' }
|
|
|
- ]
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- firstDisplay: '王十二 - 2024级电子商务1班',
|
|
|
- secondDisplay: '学号:2024010 | 手机:181****4802',
|
|
|
- thirdDisplay: [
|
|
|
- [
|
|
|
- { field: { desc: '性别' }, displayValue: '女' },
|
|
|
- { field: { desc: '年龄' }, displayValue: '19' },
|
|
|
- { field: { desc: '状态' }, displayValue: '在读' }
|
|
|
- ],
|
|
|
- [
|
|
|
- { field: { desc: '入学时间' }, displayValue: '2024-09-01' },
|
|
|
- { field: { desc: '辅导员' }, displayValue: '褚老师' }
|
|
|
- ]
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- firstDisplay: '陈十三 - 2023级金融科技1班',
|
|
|
- secondDisplay: '学号:2023011 | 手机:182****5913',
|
|
|
- thirdDisplay: [
|
|
|
- [
|
|
|
- { field: { desc: '性别' }, displayValue: '男' },
|
|
|
- { field: { desc: '年龄' }, displayValue: '22' },
|
|
|
- { field: { desc: '状态' }, displayValue: '毕业' }
|
|
|
- ],
|
|
|
- [
|
|
|
- { field: { desc: '入学时间' }, displayValue: '2023-09-01' },
|
|
|
- { field: { desc: '辅导员' }, displayValue: '卫老师' }
|
|
|
- ]
|
|
|
- ]
|
|
|
- },
|
|
|
- {
|
|
|
- firstDisplay: '刘十四 - 2024级数字媒体技术1班',
|
|
|
- secondDisplay: '学号:2024012 | 手机:183****6024',
|
|
|
- thirdDisplay: [
|
|
|
- [
|
|
|
- { field: { desc: '性别' }, displayValue: '女' },
|
|
|
- { field: { desc: '年龄' }, displayValue: '20' },
|
|
|
- { field: { desc: '状态' }, displayValue: '在读' }
|
|
|
- ],
|
|
|
- [
|
|
|
- { field: { desc: '入学时间' }, displayValue: '2024-09-01' },
|
|
|
- { field: { desc: '辅导员' }, displayValue: '蒋老师' }
|
|
|
- ]
|
|
|
- ]
|
|
|
- }
|
|
|
- ];
|
|
|
-
|
|
|
- // 设置数据
|
|
|
- this.list = mockData;
|
|
|
- this.originalList = [...mockData];
|
|
|
- this.loading = false;
|
|
|
- this.hasMore = false;
|
|
|
-
|
|
|
- console.log('✅ Mock数据加载完成,共', mockData.length, '条');
|
|
|
- },
|
|
|
-
|
|
|
- // 初始化页面
|
|
|
- async initPage() {
|
|
|
- try {
|
|
|
- console.log('🔄 初始化列表页面...');
|
|
|
-
|
|
|
- // 获取URL参数
|
|
|
- this.pageParams = this.getUrlParams();
|
|
|
- this.service = this.pageParams.service || 'default';
|
|
|
-
|
|
|
- console.log('📋 页面参数:', this.pageParams);
|
|
|
-
|
|
|
- // 功能说明:先调 init,再按 init 返回的 home/list 服务继续拉取数据 by xu 2026-02-28
|
|
|
- await this.loadInitAndHomeData();
|
|
|
-
|
|
|
- } catch (error) {
|
|
|
- console.log('❌ 页面初始化失败:', error);
|
|
|
- // this.showToast('页面初始化失败', 'error');
|
|
|
- } finally {
|
|
|
- this.loading = false;
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- // 功能说明:调用 init 接口并解析 ssSearchPobjHomeServName/ssSearchPobjListServName by xu 2026-02-28
|
|
|
- async loadInitAndHomeData() {
|
|
|
- const initService = (this.service || '').trim();
|
|
|
- if (!initService) {
|
|
|
- await this.loadData(1, false);
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- const initParams = {
|
|
|
- pageNo: 1,
|
|
|
- rowNumPer: this.pageSize,
|
|
|
- management: '1',
|
|
|
- isReady: '1'
|
|
|
- };
|
|
|
-
|
|
|
- const initResult = await request.post(
|
|
|
- `/service?ssServ=${initService}&management=1&isReady=1`,
|
|
|
- initParams,
|
|
|
- {
|
|
|
- loading: false,
|
|
|
- formData: true
|
|
|
- }
|
|
|
- );
|
|
|
-
|
|
|
- const initPayload = this.unwrapResponseData(initResult?.data);
|
|
|
- this.ssSearchPobjHomeServName = String(initPayload?.ssSearchPobjHomeServName || '').trim();
|
|
|
- this.ssSearchPobjListServName = String(initPayload?.ssSearchPobjListServName || '').trim();
|
|
|
-
|
|
|
- console.log('✅ init返回服务名:', {
|
|
|
- initService,
|
|
|
- ssSearchPobjHomeServName: this.ssSearchPobjHomeServName,
|
|
|
- ssSearchPobjListServName: this.ssSearchPobjListServName
|
|
|
- });
|
|
|
-
|
|
|
- const homeService = this.ssSearchPobjHomeServName || initService;
|
|
|
- await this.loadDataByService(homeService, 1, false);
|
|
|
- },
|
|
|
-
|
|
|
- // 功能说明:统一处理 /service 返回结构(兼容 {ssData} 与平铺结构) by xu 2026-02-28
|
|
|
- unwrapResponseData(data) {
|
|
|
- if (!data || typeof data !== 'object') {
|
|
|
- return {};
|
|
|
- }
|
|
|
- if (data.ssData && typeof data.ssData === 'object') {
|
|
|
- return data.ssData;
|
|
|
- }
|
|
|
- return data;
|
|
|
- },
|
|
|
-
|
|
|
- // 功能说明:封装按指定 ssServ 拉取列表数据(home/list 共用) by xu 2026-02-28
|
|
|
- async loadDataByService(ssServ, pageNo = 1, isLoadMore = false) {
|
|
|
- const serviceName = String(ssServ || '').trim();
|
|
|
- if (!serviceName) {
|
|
|
- console.warn('⚠️ 缺少服务名,跳过请求');
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // 防止重复加载:只有在非首次加载且正在加载时才阻止
|
|
|
- if (this.loading && !isLoadMore && this.list.length > 0) return;
|
|
|
- if (this.isLoadingMore && isLoadMore) return;
|
|
|
-
|
|
|
- try {
|
|
|
- console.log(`🔄 加载列表数据... 服务: ${serviceName}, 页码: ${pageNo}, 加载更多: ${isLoadMore}`);
|
|
|
-
|
|
|
- if (isLoadMore) {
|
|
|
- this.isLoadingMore = true;
|
|
|
- } else {
|
|
|
- this.loading = true;
|
|
|
- }
|
|
|
-
|
|
|
- const requestParams = {
|
|
|
- pageNo: pageNo,
|
|
|
- rowNumPer: this.pageSize,
|
|
|
- management: '1',
|
|
|
- isReady: '1',
|
|
|
- // 功能说明:请求参数只保留有效筛选项(避免空值/旧值污染查询) by xu 2026-02-28
|
|
|
- ...this.getActiveFilterParams(this.selectedFilters)
|
|
|
- };
|
|
|
-
|
|
|
- const result = await request.post(
|
|
|
- `/service?ssServ=${serviceName}&management=1&isReady=1`,
|
|
|
- requestParams,
|
|
|
- {
|
|
|
- loading: false,
|
|
|
- formData: true
|
|
|
- }
|
|
|
- );
|
|
|
-
|
|
|
- console.log('✅ API响应数据:', result);
|
|
|
-
|
|
|
- if (result && result.data) {
|
|
|
- await this.processApiData(result.data, isLoadMore);
|
|
|
- } else {
|
|
|
- console.warn('⚠️ API返回数据格式异常:', result);
|
|
|
- }
|
|
|
-
|
|
|
- } catch (error) {
|
|
|
- console.error('❌ 数据加载失败:', error);
|
|
|
- } finally {
|
|
|
- if (isLoadMore) {
|
|
|
- this.isLoadingMore = false;
|
|
|
- } else {
|
|
|
- this.loading = false;
|
|
|
- }
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- // 加载数据
|
|
|
- async loadData(pageNo = 1, isLoadMore = false) {
|
|
|
- // 功能说明:翻页/筛选/搜索统一走 list 服务;未返回时回退 home/init by xu 2026-02-28
|
|
|
- const listService = this.ssSearchPobjListServName || this.ssSearchPobjHomeServName || this.service;
|
|
|
- await this.loadDataByService(listService, pageNo, isLoadMore);
|
|
|
- },
|
|
|
-
|
|
|
- // 处理API返回的数据
|
|
|
- async processApiData(data, isLoadMore = false) {
|
|
|
- try {
|
|
|
- const payload = this.unwrapResponseData(data);
|
|
|
- const objectList = Array.isArray(payload.objectList)
|
|
|
- ? payload.objectList
|
|
|
- : Array.isArray(payload.objList)
|
|
|
- ? payload.objList
|
|
|
- : [];
|
|
|
- const draftList = Array.isArray(payload.draftList) ? payload.draftList : [];
|
|
|
- const combinedObjectList = draftList.concat(objectList);
|
|
|
-
|
|
|
- console.log('🔄 处理API数据...', {
|
|
|
- isLoadMore,
|
|
|
- objectListLength: combinedObjectList.length,
|
|
|
- fromObjList: Array.isArray(payload.objList)
|
|
|
- });
|
|
|
-
|
|
|
- // 保存API返回的配置信息
|
|
|
- if (!isLoadMore) {
|
|
|
- // 功能说明:列表接口通常不返回按钮/搜索字段,缺省时保留首屏(home)已加载配置,避免筛选后按钮消失 by xu 2026-02-28
|
|
|
- if (Array.isArray(payload.buttonList) || Array.isArray(payload.rootFuncList)) {
|
|
|
- this.buttonList = payload.buttonList || payload.rootFuncList || [];
|
|
|
- }
|
|
|
- if (Array.isArray(payload.fieldsList) || Array.isArray(payload.searchFieldList)) {
|
|
|
- this.fieldsList = payload.fieldsList || payload.searchFieldList || [];
|
|
|
- }
|
|
|
- }
|
|
|
- // ssPaging信息每次都要更新,因为包含当前页信息
|
|
|
- this.ssPaging = payload.ssPaging || this.ssPaging || null;
|
|
|
- // 功能说明:list接口未返回 hasKeyword 时沿用首屏值,避免长按搜索入口被错误隐藏 by xu 2026-02-28
|
|
|
- if (Object.prototype.hasOwnProperty.call(payload, 'hasKeyWord') || Object.prototype.hasOwnProperty.call(payload, 'hasKeyword')) {
|
|
|
- this.hasKeyWord = payload.hasKeyWord || payload.hasKeyword || false;
|
|
|
- }
|
|
|
- // 处理objectList数据
|
|
|
- if (combinedObjectList.length > 0) {
|
|
|
- // 使用field-formatter.js格式化列表数据
|
|
|
- const formattedList = await window.formatObjectList(combinedObjectList, this.dictCache);
|
|
|
- // 功能说明:把后端 chg/chgRootFuncList 映射成卡片左滑操作按钮,标题取 desc,供 ss-card 左滑动作区使用 by xu 2026-03-06
|
|
|
- const enhancedList = formattedList.map((item, index) => {
|
|
|
- const rawItem = combinedObjectList[index] || {};
|
|
|
- const rawActions = Array.isArray(rawItem.chgRootFuncList) && rawItem.chgRootFuncList.length > 0
|
|
|
- ? rawItem.chgRootFuncList
|
|
|
- : rawItem.chg
|
|
|
- ? [rawItem.chg]
|
|
|
- : [];
|
|
|
-
|
|
|
- return {
|
|
|
- ...item,
|
|
|
- ssObjId: item.ssObjId || rawItem.ssObjId || '',
|
|
|
- ssObjName: item.ssObjName || rawItem.ssObjName || '',
|
|
|
- swipeActions: rawActions.map(action => ({
|
|
|
- ...action,
|
|
|
- title: action.desc || action.title || '操作'
|
|
|
- }))
|
|
|
- };
|
|
|
- });
|
|
|
-
|
|
|
- if (isLoadMore) {
|
|
|
- // 加载更多:追加到现有列表
|
|
|
- this.list = [...this.list, ...enhancedList];
|
|
|
- this.originalList = [...this.originalList, ...enhancedList];
|
|
|
- } else {
|
|
|
- // 首次加载或刷新:替换列表
|
|
|
- this.originalList = enhancedList;
|
|
|
- this.list = [...this.originalList];
|
|
|
- }
|
|
|
-
|
|
|
- console.log('✅ 列表数据处理完成:', this.list.length, '条');
|
|
|
-
|
|
|
- // 更新分页状态
|
|
|
- this.currentPage = isLoadMore ? this.currentPage + 1 : 1;
|
|
|
-
|
|
|
- // 根据ssPaging信息判断是否还有更多数据
|
|
|
- if (this.ssPaging && this.ssPaging.rowNum !== undefined) {
|
|
|
- const totalRecords = this.ssPaging.rowNum;
|
|
|
- const currentRecords = this.list.length;
|
|
|
- this.hasMore = currentRecords < totalRecords;
|
|
|
- console.log('📊 分页信息:', {
|
|
|
- totalRecords,
|
|
|
- currentRecords,
|
|
|
- hasMore: this.hasMore,
|
|
|
- currentPage: this.currentPage
|
|
|
- });
|
|
|
- } else {
|
|
|
- // 降级处理:根据当前页数据量判断
|
|
|
- this.hasMore = formattedList.length >= this.pageSize;
|
|
|
- console.log('⚠️ 使用降级分页判断:', {
|
|
|
- returnedCount: formattedList.length,
|
|
|
- pageSize: this.pageSize,
|
|
|
- hasMore: this.hasMore
|
|
|
- });
|
|
|
- }
|
|
|
- } else {
|
|
|
- // 没有更多数据
|
|
|
- this.hasMore = false;
|
|
|
- console.log('❌ 没有返回数据,设置hasMore为false');
|
|
|
- }
|
|
|
-
|
|
|
- // 根据fieldsList生成筛选选项(只在首次加载时生成)
|
|
|
- if (!isLoadMore) {
|
|
|
- await this.generateFilterOptions();
|
|
|
- }
|
|
|
-
|
|
|
- } catch (error) {
|
|
|
- console.error('❌ 数据处理失败:', error);
|
|
|
- throw error;
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- // 获取URL参数
|
|
|
- getUrlParams() {
|
|
|
- const params = {};
|
|
|
- const urlSearchParams = new URLSearchParams(window.location.search);
|
|
|
- for (const [key, value] of urlSearchParams) {
|
|
|
- params[key] = decodeURIComponent(value);
|
|
|
- }
|
|
|
- return params;
|
|
|
- },
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- // 处理buttonList按钮点击
|
|
|
- handleButtonClick(button) {
|
|
|
- console.log('🔘 按钮点击:', button);
|
|
|
-
|
|
|
- // 直接跳转到目标页面
|
|
|
- const destPage = button.function?.dest || button.dest;
|
|
|
- NavigationManager.goToFromButton(button);
|
|
|
- },
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- // 跳转到目标页面
|
|
|
- navigateToPage(button, destPage) {
|
|
|
- const urlParams = new URLSearchParams(window.location.search);
|
|
|
-
|
|
|
- // 添加按钮相关参数
|
|
|
- if (button.function) {
|
|
|
- urlParams.set('dest', destPage);
|
|
|
- urlParams.set('title', encodeURIComponent(button.function.desc || button.buttonName));
|
|
|
- urlParams.set('service', button.function.servName || button.service || '');
|
|
|
- } else {
|
|
|
- urlParams.set('dest', destPage);
|
|
|
- urlParams.set('title', encodeURIComponent(button.buttonName));
|
|
|
- urlParams.set('service', button.service || '');
|
|
|
- }
|
|
|
- const newUrl = `${destPage}.html?${urlParams.toString()}`;
|
|
|
-
|
|
|
- console.log('� 跳转到:', newUrl);
|
|
|
- window.location.href = newUrl;
|
|
|
- },
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- // 根据fieldsList生成筛选选项
|
|
|
- async generateFilterOptions() {
|
|
|
- if (!this.fieldsList || this.fieldsList.length === 0) {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- for (const field of this.fieldsList) {
|
|
|
- // 如果字段有cbName,生成下拉选项
|
|
|
- if (field.cbName) {
|
|
|
- try {
|
|
|
- const options = await window.getDictOptions(field.cbName, this.dictCache);
|
|
|
- this.filterSelectOptions[field.name] = [
|
|
|
- { n: `全部${field.desc}`, v: '' },
|
|
|
- ...options
|
|
|
- ];
|
|
|
- } catch (error) {
|
|
|
- console.error('获取筛选选项失败:', field.cbName, error);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- console.log('🔽 生成筛选选项:', this.filterSelectOptions);
|
|
|
- },
|
|
|
-
|
|
|
- // 功能说明:统一处理卡片服务跳转,页面文件名按后端 dest 自动补 mp_,但传给业务页/后端的 dest 仍保持原始值 by xu 2026-03-06
|
|
|
- openServicePage(action, item = {}) {
|
|
|
- const target = action && typeof action === 'object' ? action : null;
|
|
|
- if (!target) {
|
|
|
- this.showToast('当前记录缺少操作配置', 'warning');
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- const serviceName = String(target.servName || target.ssServ || '').trim();
|
|
|
- const rawDest = String(target.dest || '').trim();
|
|
|
- const normalizedDest = rawDest && rawDest.startsWith('mp_') ? rawDest : (rawDest ? `mp_${rawDest}` : '');
|
|
|
- const paramName = String(target.param_name || '').trim();
|
|
|
- const paramValue = target.param_value;
|
|
|
- const ssToken = String(target.ssToken || '').trim();
|
|
|
- const paramText = typeof target.parm === 'string' ? target.parm : '';
|
|
|
-
|
|
|
- if (!serviceName) {
|
|
|
- this.showToast('缺少服务名', 'warning');
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- if (!normalizedDest) {
|
|
|
- this.showToast('缺少目标页面', 'warning');
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- NavigationManager.goTo(normalizedDest, {
|
|
|
- title: target.desc || target.title || '处理',
|
|
|
- service: serviceName,
|
|
|
- dest: rawDest,
|
|
|
- ssDest: rawDest,
|
|
|
- param: paramText,
|
|
|
- playParamName: paramName,
|
|
|
- playParamValue: paramValue,
|
|
|
- ssToken: ssToken,
|
|
|
- ssObjId: item.ssObjId || '',
|
|
|
- ssObjName: item.ssObjName || '',
|
|
|
- management: this.pageParams.management || '1',
|
|
|
- [paramName || 'param_value']: paramValue
|
|
|
- });
|
|
|
- },
|
|
|
-
|
|
|
- // 卡片点击 - SsCard组件会自动传递item数据
|
|
|
- handleCardClick(item) {
|
|
|
- console.log('📄 卡片点击事件触发',item);
|
|
|
- // 功能说明:卡片点击统一按 play 参数跳转到通用查看页 mp_objplay(兼容 play / service.play 两种结构) by xu 2026-03-04
|
|
|
- const play = (item && (item.play || (item.service && item.service.play))) || null;
|
|
|
- this.openServicePage(play, item);
|
|
|
- },
|
|
|
-
|
|
|
- // 卡片操作 - SsCard组件的按钮点击事件
|
|
|
- handleCardAction({ button, item, index }) {
|
|
|
- console.log('⚡ 卡片操作:', button, item);
|
|
|
- // 功能说明:左滑操作按钮按服务配置跳转(如 chg=变动),不再弹 Toast 占位 by xu 2026-03-06
|
|
|
- this.openServicePage(button, item);
|
|
|
- },
|
|
|
-
|
|
|
- // 加载更多数据
|
|
|
- async loadMore() {
|
|
|
- if (!this.hasMore || this.isLoadingMore) {
|
|
|
- console.log('🚫 无法加载更多:', { hasMore: this.hasMore, isLoadingMore: this.isLoadingMore });
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- console.log('📄 加载更多数据...');
|
|
|
- await this.loadData(this.currentPage + 1, true);
|
|
|
- },
|
|
|
-
|
|
|
- // 刷新数据
|
|
|
- async refreshData() {
|
|
|
- console.log('🔄 刷新数据...');
|
|
|
- // 重置分页状态
|
|
|
- this.currentPage = 1;
|
|
|
- this.hasMore = true;
|
|
|
- this.list = [];
|
|
|
- this.originalList = [];
|
|
|
-
|
|
|
- try {
|
|
|
- await this.loadData(1, false);
|
|
|
- this.showToast('刷新成功', 'success');
|
|
|
- } catch (error) {
|
|
|
- this.showToast('刷新失败', 'error');
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- // 获取字段描述
|
|
|
- getFieldDesc(fieldName) {
|
|
|
- const field = this.fieldsList.find(f => f.name === fieldName);
|
|
|
- return field ? field.desc : fieldName;
|
|
|
- },
|
|
|
-
|
|
|
- // 功能说明:统一按钮文案映射(兼容 rootFuncList/buttonList 不同字段) by xu 2026-02-28
|
|
|
- getButtonText(button) {
|
|
|
- if (!button || typeof button !== 'object') return '';
|
|
|
- return button.desc || button.title || button.buttonName || button.name || '';
|
|
|
- },
|
|
|
-
|
|
|
- // 功能说明:提取有效筛选参数(过滤空串/null/undefined) by xu 2026-02-28
|
|
|
- getActiveFilterParams(source) {
|
|
|
- const params = {};
|
|
|
- const obj = (source && typeof source === 'object') ? source : {};
|
|
|
- Object.entries(obj).forEach(([key, value]) => {
|
|
|
- if (value === '' || value === null || value === undefined) return;
|
|
|
- params[key] = value;
|
|
|
- });
|
|
|
- return params;
|
|
|
- },
|
|
|
-
|
|
|
- // 筛选选择器变化(立即搜索)
|
|
|
- handleFilterChange(value) {
|
|
|
- console.log('🔽 筛选条件变化,立即搜索:', value);
|
|
|
- // 立即执行搜索
|
|
|
- this.applyDynamicFilters();
|
|
|
- },
|
|
|
-
|
|
|
- // 应用动态筛选(重新调用API)
|
|
|
- async applyDynamicFilters() {
|
|
|
- try {
|
|
|
- console.log('🔍 应用筛选条件:', this.selectedFilters);
|
|
|
-
|
|
|
- // 功能说明:筛选参数先归一化,清理已取消的筛选条件 by xu 2026-02-28
|
|
|
- const filterParams = this.getActiveFilterParams(this.selectedFilters);
|
|
|
-
|
|
|
- // 功能说明:筛选后重置分页并走 list 服务 by xu 2026-02-28
|
|
|
- this.currentPage = 1;
|
|
|
- this.hasMore = true;
|
|
|
- this.list = [];
|
|
|
- this.originalList = [];
|
|
|
- this.selectedFilters = { ...filterParams };
|
|
|
- await this.loadData(1, false);
|
|
|
-
|
|
|
- } catch (error) {
|
|
|
- console.error('❌ 筛选失败:', error);
|
|
|
- this.showToast('筛选失败', 'error');
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- // 生成项目按钮
|
|
|
- generateItemButtons(service) {
|
|
|
- if (!service || !service.play) return [];
|
|
|
-
|
|
|
- return [{
|
|
|
- title: service.play.name || '查看',
|
|
|
- icon: 'icon-chakan',
|
|
|
- onclick: () => {
|
|
|
- console.log('点击查看按钮:', service.play);
|
|
|
- // 这里可以跳转到详情页面
|
|
|
- this.showToast(`查看: ${service.play.title}`, 'info');
|
|
|
- }
|
|
|
- }];
|
|
|
- },
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- // 设置页面刷新监听
|
|
|
- setupRefreshListener() {
|
|
|
- // 监听子页面返回时的刷新通知
|
|
|
- this.refreshCleanup = NavigationManager.onRefreshNotify((refreshData) => {
|
|
|
- console.log('📢 收到刷新通知,重新加载数据');
|
|
|
- this.refreshData();
|
|
|
- });
|
|
|
- },
|
|
|
-
|
|
|
- // 设置滚动监听
|
|
|
- setupScrollListener() {
|
|
|
- let isThrottled = false;
|
|
|
-
|
|
|
- const handleScroll = () => {
|
|
|
- if (isThrottled) return;
|
|
|
-
|
|
|
- isThrottled = true;
|
|
|
- setTimeout(() => {
|
|
|
- isThrottled = false;
|
|
|
- }, 200); // 节流200ms
|
|
|
-
|
|
|
- // 检查是否滚动到底部
|
|
|
- const scrollTop = window.pageYOffset || document.documentElement.scrollTop;
|
|
|
- const windowHeight = window.innerHeight;
|
|
|
- const documentHeight = document.documentElement.scrollHeight;
|
|
|
-
|
|
|
- // 控制回到顶部按钮显示:滚动超过300px时显示
|
|
|
- this.showBackToTop = scrollTop > 300;
|
|
|
-
|
|
|
- // 距离底部50px时触发加载更多
|
|
|
- if (scrollTop + windowHeight >= documentHeight - 50) {
|
|
|
- console.log('📄 滚动到底部,尝试加载更多...');
|
|
|
- this.loadMore();
|
|
|
- }
|
|
|
- };
|
|
|
-
|
|
|
- // 添加滚动监听
|
|
|
- window.addEventListener('scroll', handleScroll);
|
|
|
-
|
|
|
- // 保存清理函数
|
|
|
- this.scrollCleanup = () => {
|
|
|
- window.removeEventListener('scroll', handleScroll);
|
|
|
- };
|
|
|
- },
|
|
|
-
|
|
|
-
|
|
|
-
|
|
|
- // 返回顶部
|
|
|
- scrollToTop() {
|
|
|
- window.scrollTo({
|
|
|
- top: 0,
|
|
|
- behavior: 'smooth'
|
|
|
- });
|
|
|
- },
|
|
|
-
|
|
|
- // 处理回到顶部按钮点击
|
|
|
- handleBackToTopClick() {
|
|
|
- // 如果不是长按触发的搜索,则执行返回顶部
|
|
|
- if (!this.isLongPress) {
|
|
|
- this.scrollToTop();
|
|
|
- }
|
|
|
- // 重置长按标志
|
|
|
- this.isLongPress = false;
|
|
|
- },
|
|
|
-
|
|
|
- // 长按开始
|
|
|
- handleLongPressStart(event) {
|
|
|
- if(this.hasKeyWord){ // 有输入关键字才显示,否则不处理长按
|
|
|
- this.isLongPress = false;
|
|
|
-
|
|
|
- // 设置长按定时器(500ms)
|
|
|
- this.longPressTimer = setTimeout(() => {
|
|
|
- this.isLongPress = true;
|
|
|
- this.openSearchModal();
|
|
|
- // 震动反馈(如果支持)
|
|
|
- if (navigator.vibrate) {
|
|
|
- navigator.vibrate(50);
|
|
|
- }
|
|
|
- }, 500);
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- // 长按结束
|
|
|
- handleLongPressEnd(event) {
|
|
|
- // 清除长按定时器
|
|
|
- if (this.longPressTimer) {
|
|
|
- clearTimeout(this.longPressTimer);
|
|
|
- this.longPressTimer = null;
|
|
|
- }
|
|
|
- },
|
|
|
-
|
|
|
- // 长按取消(手指移出按钮区域)
|
|
|
- handleLongPressCancel(event) {
|
|
|
- // 清除长按定时器
|
|
|
- if (this.longPressTimer) {
|
|
|
- clearTimeout(this.longPressTimer);
|
|
|
- this.longPressTimer = null;
|
|
|
- }
|
|
|
- this.isLongPress = false;
|
|
|
- },
|
|
|
-
|
|
|
- // 打开搜索弹窗
|
|
|
- openSearchModal() {
|
|
|
- this.showSearchModal = true;
|
|
|
- this.searchKeyword = '';
|
|
|
-
|
|
|
- // 延迟聚焦输入框,确保DOM已渲染和动画完成
|
|
|
- this.$nextTick(() => {
|
|
|
- // 使用 setTimeout 确保在移动端也能正常触发键盘
|
|
|
- setTimeout(() => {
|
|
|
- if (this.$refs.searchInput) {
|
|
|
- // 先点击再聚焦,确保移动端键盘弹出
|
|
|
- this.$refs.searchInput.click();
|
|
|
- this.$refs.searchInput.focus();
|
|
|
- console.log('✅ 输入框已聚焦');
|
|
|
- }
|
|
|
- }, 100); // 等待动画完成后再聚焦
|
|
|
- });
|
|
|
- },
|
|
|
-
|
|
|
- // 关闭搜索弹窗
|
|
|
- closeSearchModal() {
|
|
|
- this.showSearchModal = false;
|
|
|
- this.searchKeyword = '';
|
|
|
- },
|
|
|
-
|
|
|
- // 执行搜索
|
|
|
- async performSearch() {
|
|
|
- const keyword = this.searchKeyword.trim();
|
|
|
-
|
|
|
- // 无关键词时,直接关闭弹窗,什么都不做
|
|
|
- if (!keyword) {
|
|
|
- // this.closeSearchModal();
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- console.log('🔍 执行关键词搜索:', keyword);
|
|
|
-
|
|
|
- // 关闭搜索弹窗
|
|
|
- this.closeSearchModal();
|
|
|
-
|
|
|
- // 设置 keyword 到筛选条件中
|
|
|
- this.selectedFilters.ssKeyword = keyword;
|
|
|
-
|
|
|
- // 调用 API 搜索
|
|
|
- await this.applyDynamicFilters();
|
|
|
- },
|
|
|
-
|
|
|
- // 显示提示
|
|
|
- showToast(message, type = 'info') {
|
|
|
- console.log(`${type.toUpperCase()}: ${message}`);
|
|
|
- // 使用浏览器原生alert,后续可以替换为更好的提示组件
|
|
|
- alert(message);
|
|
|
- }
|
|
|
- }
|
|
|
- })
|
|
|
- })
|
|
|
-
|
|
|
- </script>
|
|
|
-</body>
|
|
|
-</html>
|
|
|
+<!DOCTYPE html>
|
|
|
+<html lang="zh-CN">
|
|
|
+ <head>
|
|
|
+ <meta charset="UTF-8" />
|
|
|
+ <meta
|
|
|
+ name="viewport"
|
|
|
+ content="width=device-width, initial-scale=1.0, user-scalable=no"
|
|
|
+ />
|
|
|
+ <title>列表</title>
|
|
|
+ <!-- 引入基础依赖(统一由 base.js 动态注入其他依赖) -->
|
|
|
+ <script src="/js/mp_base/base.js"></script>
|
|
|
+ <style>
|
|
|
+ #app {
|
|
|
+ background: #f5f5f5;
|
|
|
+ min-height: 100vh;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 防止Vue模板闪烁 */
|
|
|
+ [v-cloak] {
|
|
|
+ display: none !important;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 搜索筛选区域 */
|
|
|
+ .search-filter-container {
|
|
|
+ background: #f5f5f5;
|
|
|
+ padding: 15px;
|
|
|
+ position: sticky;
|
|
|
+ top: 0;
|
|
|
+ z-index: 100;
|
|
|
+ display: flex;
|
|
|
+ justify-content: flex-end;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 10px;
|
|
|
+ }
|
|
|
+ .search-filter-container .ss-select-container {
|
|
|
+ border: 1px solid #ccc;
|
|
|
+ border-radius: 4px;
|
|
|
+ padding: 0 10px;
|
|
|
+ height: 34px;
|
|
|
+ box-sizing: border-box;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 加载状态 */
|
|
|
+ .loading-container {
|
|
|
+ display: flex;
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ height: 200px;
|
|
|
+ color: #666;
|
|
|
+ }
|
|
|
+
|
|
|
+ .loading-spinner {
|
|
|
+ width: 40px;
|
|
|
+ height: 40px;
|
|
|
+ border: 4px solid #f3f3f3;
|
|
|
+ border-top: 4px solid #40ac6d;
|
|
|
+ border-radius: 50%;
|
|
|
+ animation: spin 1s linear infinite;
|
|
|
+ margin-bottom: 15px;
|
|
|
+ }
|
|
|
+
|
|
|
+ @keyframes spin {
|
|
|
+ 0% {
|
|
|
+ transform: rotate(0deg);
|
|
|
+ }
|
|
|
+ 100% {
|
|
|
+ transform: rotate(360deg);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .loading-text {
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 列表容器 */
|
|
|
+ .list-container {
|
|
|
+ padding: 0 15px;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 空状态 */
|
|
|
+ .empty-state {
|
|
|
+ text-align: center;
|
|
|
+ padding: 60px 20px;
|
|
|
+ color: #999;
|
|
|
+ }
|
|
|
+
|
|
|
+ .empty-icon {
|
|
|
+ font-size: 48px;
|
|
|
+ margin-bottom: 15px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .empty-text {
|
|
|
+ font-size: 16px;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 卡片内容样式 - 按照小程序list.vue转换 */
|
|
|
+ .card-content .card-header {
|
|
|
+ margin-bottom: 10px; /* 20rpx -> 10px */
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-content .card-title {
|
|
|
+ font-size: 16px; /* 32rpx -> 16px */
|
|
|
+ font-weight: bold;
|
|
|
+ color: #333;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-content .card-description {
|
|
|
+ font-size: 14px; /* 28rpx -> 14px */
|
|
|
+ color: #666;
|
|
|
+ margin-bottom: 8px; /* 15rpx -> 8px */
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-content .attribute-group {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ column-gap: 10px; /* 20rpx -> 10px */
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-content .attribute-item {
|
|
|
+ display: flex;
|
|
|
+ margin-bottom: 5px; /* 10rpx -> 5px */
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-content .attr-label {
|
|
|
+ font-size: 13px; /* 26rpx -> 13px */
|
|
|
+ color: #999;
|
|
|
+ }
|
|
|
+
|
|
|
+ .card-content .attr-value {
|
|
|
+ font-size: 13px; /* 26rpx -> 13px */
|
|
|
+ color: #333;
|
|
|
+ flex: 1;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 状态文本样式 */
|
|
|
+ .status-text {
|
|
|
+ font-weight: bold;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 加载更多提示样式 */
|
|
|
+ .load-more-container {
|
|
|
+ text-align: center;
|
|
|
+ padding: 20px;
|
|
|
+ color: #999;
|
|
|
+ font-size: 14px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .load-more-loading {
|
|
|
+ color: #007aff;
|
|
|
+ }
|
|
|
+
|
|
|
+ .load-more-end {
|
|
|
+ color: #999;
|
|
|
+ }
|
|
|
+
|
|
|
+ .load-more-tip {
|
|
|
+ color: #ccc;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 回到顶部按钮 */
|
|
|
+ .back-to-top-btn {
|
|
|
+ width: 50px;
|
|
|
+ height: 50px;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: rgba(87, 93, 109, 0.5);
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ position: fixed;
|
|
|
+ bottom: 200px;
|
|
|
+ right: 15px;
|
|
|
+ cursor: pointer;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+ z-index: 999;
|
|
|
+ }
|
|
|
+
|
|
|
+ .back-to-top-btn:active {
|
|
|
+ transform: scale(0.9);
|
|
|
+ }
|
|
|
+
|
|
|
+ .back-to-top-inner {
|
|
|
+ width: 42px;
|
|
|
+ height: 42px;
|
|
|
+ border-radius: 50%;
|
|
|
+ background: rgba(87, 93, 109, 0.5);
|
|
|
+ display: flex;
|
|
|
+ justify-content: center;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+
|
|
|
+ /* 搜索弹窗 */
|
|
|
+ .search-modal-mask {
|
|
|
+ position: fixed;
|
|
|
+ top: 0;
|
|
|
+ left: 0;
|
|
|
+ right: 0;
|
|
|
+ bottom: 0;
|
|
|
+ background: rgba(0, 0, 0, 0.5);
|
|
|
+ z-index: 1000;
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-end;
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-modal-content {
|
|
|
+ width: 100%;
|
|
|
+ background: white;
|
|
|
+ animation: slideUp 0.3s ease-out;
|
|
|
+ /* 让键盘弹起时自动上移 */
|
|
|
+ position: relative;
|
|
|
+ }
|
|
|
+
|
|
|
+ @keyframes slideUp {
|
|
|
+ from {
|
|
|
+ transform: translateY(100%);
|
|
|
+ }
|
|
|
+ to {
|
|
|
+ transform: translateY(0);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-input-container {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ height: 50px;
|
|
|
+ background: white;
|
|
|
+ border-top: 1px solid #e5e5e5;
|
|
|
+ /* 适配iPhone底部安全区域 */
|
|
|
+ /* padding-bottom: env(safe-area-inset-bottom); */
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-input {
|
|
|
+ flex: 1;
|
|
|
+ height: 100%;
|
|
|
+ border: none;
|
|
|
+ padding: 0 15px;
|
|
|
+ font-size: 16px;
|
|
|
+ outline: none;
|
|
|
+ background: transparent;
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-divider {
|
|
|
+ width: 1px;
|
|
|
+ height: 30px;
|
|
|
+ background: #d5d8dc;
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-icon-btn {
|
|
|
+ width: 60px;
|
|
|
+ height: 100%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ cursor: pointer;
|
|
|
+ background: transparent;
|
|
|
+ border: none;
|
|
|
+ padding: 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ .search-icon-btn:active {
|
|
|
+ background: #f5f5f5;
|
|
|
+ }
|
|
|
+ </style>
|
|
|
+ </head>
|
|
|
+ <body>
|
|
|
+ <div id="app" v-cloak>
|
|
|
+ <!-- 搜索和筛选区域 -->
|
|
|
+ <div class="search-filter-container">
|
|
|
+ <!-- 动态下拉选择器 -->
|
|
|
+ <template
|
|
|
+ v-for="(options, fieldName) in filterSelectOptions"
|
|
|
+ :key="fieldName"
|
|
|
+ >
|
|
|
+ <ss-select
|
|
|
+ v-model="selectedFilters[fieldName]"
|
|
|
+ :placeholder="`选择${getFieldDesc(fieldName)}`"
|
|
|
+ :options="options"
|
|
|
+ width="132px"
|
|
|
+ inp="true"
|
|
|
+ @change="handleFilterChange"
|
|
|
+ >
|
|
|
+ </ss-select>
|
|
|
+ </template>
|
|
|
+ <!-- 完全由buttonList决定的动态按钮组 -->
|
|
|
+ <ss-search-button
|
|
|
+ v-for="(button, index) in buttonList"
|
|
|
+ :key="index"
|
|
|
+ :text="getButtonText(button)"
|
|
|
+ @click="handleButtonClick(button)"
|
|
|
+ >
|
|
|
+ </ss-search-button>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 加载状态 -->
|
|
|
+ <div v-if="loading" class="loading-container">
|
|
|
+ <div class="loading-spinner"></div>
|
|
|
+ <div class="loading-text">加载中...</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 列表区域 -->
|
|
|
+ <div v-else class="list-container">
|
|
|
+ <!-- 空状态 -->
|
|
|
+ <div v-if="list.length === 0" class="empty-state">
|
|
|
+ <div class="empty-text">暂无数据</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 数据列表 -->
|
|
|
+ <div v-else>
|
|
|
+ <ss-card
|
|
|
+ v-for="(item, index) in list"
|
|
|
+ :key="index"
|
|
|
+ :item="item"
|
|
|
+ @click="handleCardClick(item)"
|
|
|
+ @button-click="handleCardAction"
|
|
|
+ >
|
|
|
+ <!-- 卡片内容 - 按照小程序list.vue的结构,使用API数据 -->
|
|
|
+ <div class="card-content">
|
|
|
+ <!-- 主标题 (first) -->
|
|
|
+ <div class="card-header" v-if="item.firstDisplay">
|
|
|
+ <div class="card-title">{{ item.firstDisplay }}</div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 描述 (second) -->
|
|
|
+ <div class="card-description" v-if="item.secondDisplay">
|
|
|
+ {{ item.secondDisplay }}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 属性列表 (third) -->
|
|
|
+ <div
|
|
|
+ class="card-attributes"
|
|
|
+ v-if="item.thirdDisplay && item.thirdDisplay.length > 0"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ v-for="(group, groupIndex) in item.thirdDisplay"
|
|
|
+ :key="groupIndex"
|
|
|
+ class="attribute-group"
|
|
|
+ >
|
|
|
+ <div
|
|
|
+ v-for="(attr, attrIndex) in group"
|
|
|
+ :key="attrIndex"
|
|
|
+ class="attribute-item"
|
|
|
+ >
|
|
|
+ <span class="attr-label">{{ attr.field.desc }}:</span>
|
|
|
+ <span class="attr-value">{{ attr.displayValue }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </ss-card>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 加载更多提示 -->
|
|
|
+ <div class="load-more-container" v-if="list.length > 0">
|
|
|
+ <div v-if="isLoadingMore" class="load-more-loading">
|
|
|
+ <span>正在加载更多...</span>
|
|
|
+ </div>
|
|
|
+ <div v-else-if="!hasMore" class="load-more-end">
|
|
|
+ <span>没有更多数据了</span>
|
|
|
+ </div>
|
|
|
+ <div v-else class="load-more-tip">
|
|
|
+ <span>滚动到底部加载更多</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 回到顶部按钮 -->
|
|
|
+ <div
|
|
|
+ v-if="showBackToTop"
|
|
|
+ class="back-to-top-btn"
|
|
|
+ @touchstart="handleLongPressStart"
|
|
|
+ @touchend="handleLongPressEnd"
|
|
|
+ @touchcancel="handleLongPressCancel"
|
|
|
+ @click.prevent="handleBackToTopClick"
|
|
|
+ >
|
|
|
+ <div class="back-to-top-inner">
|
|
|
+ <Icon name="icon-huidaodingbu" size="40" color="#fff"></Icon>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 搜索弹窗 -->
|
|
|
+ <div
|
|
|
+ v-if="showSearchModal"
|
|
|
+ class="search-modal-mask"
|
|
|
+ @click="closeSearchModal"
|
|
|
+ >
|
|
|
+ <div class="search-modal-content" @click.stop>
|
|
|
+ <div class="search-input-container">
|
|
|
+ <input
|
|
|
+ ref="searchInput"
|
|
|
+ v-model="searchKeyword"
|
|
|
+ type="search"
|
|
|
+ inputmode="search"
|
|
|
+ class="search-input"
|
|
|
+ placeholder="请输入关键词"
|
|
|
+ @keyup.enter="performSearch"
|
|
|
+ autocomplete="off"
|
|
|
+ />
|
|
|
+ <div class="search-divider"></div>
|
|
|
+ <button class="search-icon-btn" @click="performSearch">
|
|
|
+ <Icon name="icon-chazhao" size="24" color="#575d6d"></Icon>
|
|
|
+ </button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <script>
|
|
|
+ // 等待SS框架加载完成
|
|
|
+ window.SS.ready(function () {
|
|
|
+ // 使用SS框架的方式创建Vue实例
|
|
|
+ window.SS.dom.initializeFormApp({
|
|
|
+ el: "#app",
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ // 加载状态
|
|
|
+ loading: true,
|
|
|
+
|
|
|
+ // 列表数据
|
|
|
+ list: [],
|
|
|
+ originalList: [], // 原始数据,用于筛选
|
|
|
+
|
|
|
+ // 分页相关
|
|
|
+ currentPage: 1,
|
|
|
+ pageSize: 10,
|
|
|
+ hasMore: true,
|
|
|
+ isLoadingMore: false,
|
|
|
+
|
|
|
+ // 页面参数
|
|
|
+ pageParams: {},
|
|
|
+ service: "", // init服务名称
|
|
|
+ // 功能说明:对接PC两段式接口(init 返回 home/list 服务名) by xu 2026-02-28
|
|
|
+ ssSearchPobjHomeServName: "",
|
|
|
+ ssSearchPobjListServName: "",
|
|
|
+
|
|
|
+ // API返回的动态配置
|
|
|
+ buttonList: [],
|
|
|
+ fieldsList: [],
|
|
|
+ ssPaging: null,
|
|
|
+
|
|
|
+ // 动态搜索筛选选项
|
|
|
+ filterOptions: [],
|
|
|
+ sortOptions: [],
|
|
|
+
|
|
|
+ // 下拉选择器数据
|
|
|
+ selectedFilters: {}, // 动态筛选条件
|
|
|
+ filterSelectOptions: {}, // 各个筛选字段的选项
|
|
|
+
|
|
|
+ // 字典缓存
|
|
|
+ dictCache: new Map(),
|
|
|
+
|
|
|
+ // 显示字段配置 - 根据service动态设置
|
|
|
+ displayFields: [],
|
|
|
+
|
|
|
+ // 卡片操作按钮
|
|
|
+ cardActions: [
|
|
|
+ { text: "查看", name: "view" },
|
|
|
+ { text: "编辑", name: "edit" },
|
|
|
+ ],
|
|
|
+
|
|
|
+ // 回到顶部按钮
|
|
|
+ showBackToTop: false,
|
|
|
+
|
|
|
+ // 是否存在关键词搜索
|
|
|
+ hasKeyWord: false,
|
|
|
+
|
|
|
+ // 搜索相关
|
|
|
+ showSearchModal: false,
|
|
|
+ searchKeyword: "",
|
|
|
+
|
|
|
+ // 长按相关
|
|
|
+ longPressTimer: null,
|
|
|
+ isLongPress: false,
|
|
|
+ };
|
|
|
+ },
|
|
|
+
|
|
|
+ mounted() {
|
|
|
+ // 页面加载时初始化
|
|
|
+ this.initPage();
|
|
|
+
|
|
|
+ // 监听页面刷新通知
|
|
|
+ this.setupRefreshListener();
|
|
|
+
|
|
|
+ // 监听滚动事件,实现滚动加载
|
|
|
+ this.setupScrollListener();
|
|
|
+
|
|
|
+ // 开发模式:加载Mock数据(测试用)
|
|
|
+ // this.loadMockData();
|
|
|
+ },
|
|
|
+
|
|
|
+ beforeUnmount() {
|
|
|
+ // 清理刷新监听器
|
|
|
+ if (this.refreshCleanup) {
|
|
|
+ this.refreshCleanup();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 清理滚动监听器
|
|
|
+ if (this.scrollCleanup) {
|
|
|
+ this.scrollCleanup();
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ methods: {
|
|
|
+ // 加载Mock数据(用于开发测试)
|
|
|
+ loadMockData() {
|
|
|
+ console.log("🎭 加载Mock数据...");
|
|
|
+
|
|
|
+ const mockData = [
|
|
|
+ {
|
|
|
+ firstDisplay: "张三 - 2024级计算机科学与技术1班",
|
|
|
+ secondDisplay: "学号:2024001 | 手机:138****1234",
|
|
|
+ thirdDisplay: [
|
|
|
+ [
|
|
|
+ { field: { desc: "性别" }, displayValue: "男" },
|
|
|
+ { field: { desc: "年龄" }, displayValue: "20" },
|
|
|
+ { field: { desc: "状态" }, displayValue: "在读" },
|
|
|
+ ],
|
|
|
+ [
|
|
|
+ {
|
|
|
+ field: { desc: "入学时间" },
|
|
|
+ displayValue: "2024-09-01",
|
|
|
+ },
|
|
|
+ { field: { desc: "辅导员" }, displayValue: "王老师" },
|
|
|
+ ],
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ firstDisplay: "李四 - 2024级软件工程2班",
|
|
|
+ secondDisplay: "学号:2024002 | 手机:139****5678",
|
|
|
+ thirdDisplay: [
|
|
|
+ [
|
|
|
+ { field: { desc: "性别" }, displayValue: "女" },
|
|
|
+ { field: { desc: "年龄" }, displayValue: "19" },
|
|
|
+ { field: { desc: "状态" }, displayValue: "在读" },
|
|
|
+ ],
|
|
|
+ [
|
|
|
+ {
|
|
|
+ field: { desc: "入学时间" },
|
|
|
+ displayValue: "2024-09-01",
|
|
|
+ },
|
|
|
+ { field: { desc: "辅导员" }, displayValue: "李老师" },
|
|
|
+ ],
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ firstDisplay: "王五 - 2023级人工智能1班",
|
|
|
+ secondDisplay: "学号:2023003 | 手机:136****9012",
|
|
|
+ thirdDisplay: [
|
|
|
+ [
|
|
|
+ { field: { desc: "性别" }, displayValue: "男" },
|
|
|
+ { field: { desc: "年龄" }, displayValue: "21" },
|
|
|
+ { field: { desc: "状态" }, displayValue: "在读" },
|
|
|
+ ],
|
|
|
+ [
|
|
|
+ {
|
|
|
+ field: { desc: "入学时间" },
|
|
|
+ displayValue: "2023-09-01",
|
|
|
+ },
|
|
|
+ { field: { desc: "辅导员" }, displayValue: "赵老师" },
|
|
|
+ ],
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ firstDisplay: "赵六 - 2024级数据科学与大数据技术1班",
|
|
|
+ secondDisplay: "学号:2024004 | 手机:137****3456",
|
|
|
+ thirdDisplay: [
|
|
|
+ [
|
|
|
+ { field: { desc: "性别" }, displayValue: "女" },
|
|
|
+ { field: { desc: "年龄" }, displayValue: "20" },
|
|
|
+ { field: { desc: "状态" }, displayValue: "在读" },
|
|
|
+ ],
|
|
|
+ [
|
|
|
+ {
|
|
|
+ field: { desc: "入学时间" },
|
|
|
+ displayValue: "2024-09-01",
|
|
|
+ },
|
|
|
+ { field: { desc: "辅导员" }, displayValue: "刘老师" },
|
|
|
+ ],
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ firstDisplay: "钱七 - 2023级网络工程1班",
|
|
|
+ secondDisplay: "学号:2023005 | 手机:135****7890",
|
|
|
+ thirdDisplay: [
|
|
|
+ [
|
|
|
+ { field: { desc: "性别" }, displayValue: "男" },
|
|
|
+ { field: { desc: "年龄" }, displayValue: "21" },
|
|
|
+ { field: { desc: "状态" }, displayValue: "休学" },
|
|
|
+ ],
|
|
|
+ [
|
|
|
+ {
|
|
|
+ field: { desc: "入学时间" },
|
|
|
+ displayValue: "2023-09-01",
|
|
|
+ },
|
|
|
+ { field: { desc: "辅导员" }, displayValue: "周老师" },
|
|
|
+ ],
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ firstDisplay: "孙八 - 2024级信息安全1班",
|
|
|
+ secondDisplay: "学号:2024006 | 手机:133****2468",
|
|
|
+ thirdDisplay: [
|
|
|
+ [
|
|
|
+ { field: { desc: "性别" }, displayValue: "女" },
|
|
|
+ { field: { desc: "年龄" }, displayValue: "19" },
|
|
|
+ { field: { desc: "状态" }, displayValue: "在读" },
|
|
|
+ ],
|
|
|
+ [
|
|
|
+ {
|
|
|
+ field: { desc: "入学时间" },
|
|
|
+ displayValue: "2024-09-01",
|
|
|
+ },
|
|
|
+ { field: { desc: "辅导员" }, displayValue: "吴老师" },
|
|
|
+ ],
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ firstDisplay: "周九 - 2023级物联网工程1班",
|
|
|
+ secondDisplay: "学号:2023007 | 手机:188****1357",
|
|
|
+ thirdDisplay: [
|
|
|
+ [
|
|
|
+ { field: { desc: "性别" }, displayValue: "男" },
|
|
|
+ { field: { desc: "年龄" }, displayValue: "22" },
|
|
|
+ { field: { desc: "状态" }, displayValue: "在读" },
|
|
|
+ ],
|
|
|
+ [
|
|
|
+ {
|
|
|
+ field: { desc: "入学时间" },
|
|
|
+ displayValue: "2023-09-01",
|
|
|
+ },
|
|
|
+ { field: { desc: "辅导员" }, displayValue: "郑老师" },
|
|
|
+ ],
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ firstDisplay: "吴十 - 2024级云计算1班",
|
|
|
+ secondDisplay: "学号:2024008 | 手机:189****2468",
|
|
|
+ thirdDisplay: [
|
|
|
+ [
|
|
|
+ { field: { desc: "性别" }, displayValue: "女" },
|
|
|
+ { field: { desc: "年龄" }, displayValue: "20" },
|
|
|
+ { field: { desc: "状态" }, displayValue: "在读" },
|
|
|
+ ],
|
|
|
+ [
|
|
|
+ {
|
|
|
+ field: { desc: "入学时间" },
|
|
|
+ displayValue: "2024-09-01",
|
|
|
+ },
|
|
|
+ { field: { desc: "辅导员" }, displayValue: "冯老师" },
|
|
|
+ ],
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ firstDisplay: "郑十一 - 2023级区块链工程1班",
|
|
|
+ secondDisplay: "学号:2023009 | 手机:180****3691",
|
|
|
+ thirdDisplay: [
|
|
|
+ [
|
|
|
+ { field: { desc: "性别" }, displayValue: "男" },
|
|
|
+ { field: { desc: "年龄" }, displayValue: "21" },
|
|
|
+ { field: { desc: "状态" }, displayValue: "在读" },
|
|
|
+ ],
|
|
|
+ [
|
|
|
+ {
|
|
|
+ field: { desc: "入学时间" },
|
|
|
+ displayValue: "2023-09-01",
|
|
|
+ },
|
|
|
+ { field: { desc: "辅导员" }, displayValue: "陈老师" },
|
|
|
+ ],
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ firstDisplay: "王十二 - 2024级电子商务1班",
|
|
|
+ secondDisplay: "学号:2024010 | 手机:181****4802",
|
|
|
+ thirdDisplay: [
|
|
|
+ [
|
|
|
+ { field: { desc: "性别" }, displayValue: "女" },
|
|
|
+ { field: { desc: "年龄" }, displayValue: "19" },
|
|
|
+ { field: { desc: "状态" }, displayValue: "在读" },
|
|
|
+ ],
|
|
|
+ [
|
|
|
+ {
|
|
|
+ field: { desc: "入学时间" },
|
|
|
+ displayValue: "2024-09-01",
|
|
|
+ },
|
|
|
+ { field: { desc: "辅导员" }, displayValue: "褚老师" },
|
|
|
+ ],
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ firstDisplay: "陈十三 - 2023级金融科技1班",
|
|
|
+ secondDisplay: "学号:2023011 | 手机:182****5913",
|
|
|
+ thirdDisplay: [
|
|
|
+ [
|
|
|
+ { field: { desc: "性别" }, displayValue: "男" },
|
|
|
+ { field: { desc: "年龄" }, displayValue: "22" },
|
|
|
+ { field: { desc: "状态" }, displayValue: "毕业" },
|
|
|
+ ],
|
|
|
+ [
|
|
|
+ {
|
|
|
+ field: { desc: "入学时间" },
|
|
|
+ displayValue: "2023-09-01",
|
|
|
+ },
|
|
|
+ { field: { desc: "辅导员" }, displayValue: "卫老师" },
|
|
|
+ ],
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ {
|
|
|
+ firstDisplay: "刘十四 - 2024级数字媒体技术1班",
|
|
|
+ secondDisplay: "学号:2024012 | 手机:183****6024",
|
|
|
+ thirdDisplay: [
|
|
|
+ [
|
|
|
+ { field: { desc: "性别" }, displayValue: "女" },
|
|
|
+ { field: { desc: "年龄" }, displayValue: "20" },
|
|
|
+ { field: { desc: "状态" }, displayValue: "在读" },
|
|
|
+ ],
|
|
|
+ [
|
|
|
+ {
|
|
|
+ field: { desc: "入学时间" },
|
|
|
+ displayValue: "2024-09-01",
|
|
|
+ },
|
|
|
+ { field: { desc: "辅导员" }, displayValue: "蒋老师" },
|
|
|
+ ],
|
|
|
+ ],
|
|
|
+ },
|
|
|
+ ];
|
|
|
+
|
|
|
+ // 设置数据
|
|
|
+ this.list = mockData;
|
|
|
+ this.originalList = [...mockData];
|
|
|
+ this.loading = false;
|
|
|
+ this.hasMore = false;
|
|
|
+
|
|
|
+ console.log("✅ Mock数据加载完成,共", mockData.length, "条");
|
|
|
+ },
|
|
|
+
|
|
|
+ // 初始化页面
|
|
|
+ async initPage() {
|
|
|
+ try {
|
|
|
+ console.log("🔄 初始化列表页面...");
|
|
|
+
|
|
|
+ // 获取URL参数
|
|
|
+ this.pageParams = this.getUrlParams();
|
|
|
+ this.service = this.pageParams.service || "default";
|
|
|
+
|
|
|
+ console.log("📋 页面参数:", this.pageParams);
|
|
|
+
|
|
|
+ // 功能说明:先调 init,再按 init 返回的 home/list 服务继续拉取数据 by xu 2026-02-28
|
|
|
+ await this.loadInitAndHomeData();
|
|
|
+ } catch (error) {
|
|
|
+ console.log("❌ 页面初始化失败:", error);
|
|
|
+ // this.showToast('页面初始化失败', 'error');
|
|
|
+ } finally {
|
|
|
+ this.loading = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 功能说明:调用 init 接口并解析 ssSearchPobjHomeServName/ssSearchPobjListServName by xu 2026-02-28
|
|
|
+ async loadInitAndHomeData() {
|
|
|
+ const initService = (this.service || "").trim();
|
|
|
+ if (!initService) {
|
|
|
+ await this.loadData(1, false);
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const initParams = {
|
|
|
+ pageNo: 1,
|
|
|
+ rowNumPer: this.pageSize,
|
|
|
+ management: "1",
|
|
|
+ isReady: "1",
|
|
|
+ };
|
|
|
+
|
|
|
+ const initResult = await request.post(
|
|
|
+ `/service?ssServ=${initService}&management=1&isReady=1`,
|
|
|
+ initParams,
|
|
|
+ {
|
|
|
+ loading: false,
|
|
|
+ formData: true,
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ const initPayload = this.unwrapResponseData(initResult?.data);
|
|
|
+ this.ssSearchPobjHomeServName = String(
|
|
|
+ initPayload?.ssSearchPobjHomeServName || ""
|
|
|
+ ).trim();
|
|
|
+ this.ssSearchPobjListServName = String(
|
|
|
+ initPayload?.ssSearchPobjListServName || ""
|
|
|
+ ).trim();
|
|
|
+
|
|
|
+ console.log("✅ init返回服务名:", {
|
|
|
+ initService,
|
|
|
+ ssSearchPobjHomeServName: this.ssSearchPobjHomeServName,
|
|
|
+ ssSearchPobjListServName: this.ssSearchPobjListServName,
|
|
|
+ });
|
|
|
+
|
|
|
+ const homeService = this.ssSearchPobjHomeServName || initService;
|
|
|
+ await this.loadDataByService(homeService, 1, false);
|
|
|
+ },
|
|
|
+
|
|
|
+ // 功能说明:统一处理 /service 返回结构(兼容 {ssData} 与平铺结构) by xu 2026-02-28
|
|
|
+ unwrapResponseData(data) {
|
|
|
+ if (!data || typeof data !== "object") {
|
|
|
+ return {};
|
|
|
+ }
|
|
|
+ if (data.ssData && typeof data.ssData === "object") {
|
|
|
+ return data.ssData;
|
|
|
+ }
|
|
|
+ return data;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 功能说明:封装按指定 ssServ 拉取列表数据(home/list 共用) by xu 2026-02-28
|
|
|
+ async loadDataByService(ssServ, pageNo = 1, isLoadMore = false) {
|
|
|
+ const serviceName = String(ssServ || "").trim();
|
|
|
+ if (!serviceName) {
|
|
|
+ console.warn("⚠️ 缺少服务名,跳过请求");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 防止重复加载:只有在非首次加载且正在加载时才阻止
|
|
|
+ if (this.loading && !isLoadMore && this.list.length > 0) return;
|
|
|
+ if (this.isLoadingMore && isLoadMore) return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ console.log(
|
|
|
+ `🔄 加载列表数据... 服务: ${serviceName}, 页码: ${pageNo}, 加载更多: ${isLoadMore}`
|
|
|
+ );
|
|
|
+
|
|
|
+ if (isLoadMore) {
|
|
|
+ this.isLoadingMore = true;
|
|
|
+ } else {
|
|
|
+ this.loading = true;
|
|
|
+ }
|
|
|
+
|
|
|
+ const requestParams = {
|
|
|
+ pageNo: pageNo,
|
|
|
+ rowNumPer: this.pageSize,
|
|
|
+ management: "1",
|
|
|
+ isReady: "1",
|
|
|
+ // 功能说明:请求参数只保留有效筛选项(避免空值/旧值污染查询) by xu 2026-02-28
|
|
|
+ ...this.getActiveFilterParams(this.selectedFilters),
|
|
|
+ };
|
|
|
+
|
|
|
+ const result = await request.post(
|
|
|
+ `/service?ssServ=${serviceName}&management=1&isReady=1`,
|
|
|
+ requestParams,
|
|
|
+ {
|
|
|
+ loading: false,
|
|
|
+ formData: true,
|
|
|
+ }
|
|
|
+ );
|
|
|
+
|
|
|
+ console.log("✅ API响应数据:", result);
|
|
|
+
|
|
|
+ if (result && result.data) {
|
|
|
+ await this.processApiData(result.data, isLoadMore);
|
|
|
+ } else {
|
|
|
+ console.warn("⚠️ API返回数据格式异常:", result);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error("❌ 数据加载失败:", error);
|
|
|
+ } finally {
|
|
|
+ if (isLoadMore) {
|
|
|
+ this.isLoadingMore = false;
|
|
|
+ } else {
|
|
|
+ this.loading = false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 加载数据
|
|
|
+ async loadData(pageNo = 1, isLoadMore = false) {
|
|
|
+ // 功能说明:翻页/筛选/搜索统一走 list 服务;未返回时回退 home/init by xu 2026-02-28
|
|
|
+ const listService =
|
|
|
+ this.ssSearchPobjListServName ||
|
|
|
+ this.ssSearchPobjHomeServName ||
|
|
|
+ this.service;
|
|
|
+ await this.loadDataByService(listService, pageNo, isLoadMore);
|
|
|
+ },
|
|
|
+
|
|
|
+ // 处理API返回的数据
|
|
|
+ async processApiData(data, isLoadMore = false) {
|
|
|
+ try {
|
|
|
+ const payload = this.unwrapResponseData(data);
|
|
|
+ const objectList = Array.isArray(payload.objectList)
|
|
|
+ ? payload.objectList
|
|
|
+ : Array.isArray(payload.objList)
|
|
|
+ ? payload.objList
|
|
|
+ : [];
|
|
|
+ const draftList = Array.isArray(payload.draftList)
|
|
|
+ ? payload.draftList
|
|
|
+ : [];
|
|
|
+ const combinedObjectList = draftList.concat(objectList);
|
|
|
+
|
|
|
+ console.log("🔄 处理API数据...", {
|
|
|
+ isLoadMore,
|
|
|
+ objectListLength: combinedObjectList.length,
|
|
|
+ fromObjList: Array.isArray(payload.objList),
|
|
|
+ });
|
|
|
+
|
|
|
+ // 保存API返回的配置信息
|
|
|
+ if (!isLoadMore) {
|
|
|
+ // 功能说明:列表接口通常不返回按钮/搜索字段,缺省时保留首屏(home)已加载配置,避免筛选后按钮消失 by xu 2026-02-28
|
|
|
+ if (
|
|
|
+ Array.isArray(payload.buttonList) ||
|
|
|
+ Array.isArray(payload.rootFuncList)
|
|
|
+ ) {
|
|
|
+ this.buttonList =
|
|
|
+ payload.buttonList || payload.rootFuncList || [];
|
|
|
+ }
|
|
|
+ if (
|
|
|
+ Array.isArray(payload.fieldsList) ||
|
|
|
+ Array.isArray(payload.searchFieldList)
|
|
|
+ ) {
|
|
|
+ this.fieldsList =
|
|
|
+ payload.fieldsList || payload.searchFieldList || [];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ // ssPaging信息每次都要更新,因为包含当前页信息
|
|
|
+ this.ssPaging = payload.ssPaging || this.ssPaging || null;
|
|
|
+ // 功能说明:list接口未返回 hasKeyword 时沿用首屏值,避免长按搜索入口被错误隐藏 by xu 2026-02-28
|
|
|
+ if (
|
|
|
+ Object.prototype.hasOwnProperty.call(payload, "hasKeyWord") ||
|
|
|
+ Object.prototype.hasOwnProperty.call(payload, "hasKeyword")
|
|
|
+ ) {
|
|
|
+ this.hasKeyWord =
|
|
|
+ payload.hasKeyWord || payload.hasKeyword || false;
|
|
|
+ }
|
|
|
+ // 处理objectList数据
|
|
|
+ if (combinedObjectList.length > 0) {
|
|
|
+ // 使用field-formatter.js格式化列表数据
|
|
|
+ const formattedList = await window.formatObjectList(
|
|
|
+ combinedObjectList,
|
|
|
+ this.dictCache
|
|
|
+ );
|
|
|
+ // 功能说明:把后端 chg/chgRootFuncList 映射成卡片左滑操作按钮,标题取 desc,供 ss-card 左滑动作区使用 by xu 2026-03-06
|
|
|
+ const enhancedList = formattedList.map((item, index) => {
|
|
|
+ const rawItem = combinedObjectList[index] || {};
|
|
|
+ const rawActions =
|
|
|
+ Array.isArray(rawItem.chgRootFuncList) &&
|
|
|
+ rawItem.chgRootFuncList.length > 0
|
|
|
+ ? rawItem.chgRootFuncList
|
|
|
+ : rawItem.chg
|
|
|
+ ? [rawItem.chg]
|
|
|
+ : [];
|
|
|
+
|
|
|
+ return {
|
|
|
+ ...item,
|
|
|
+ ssObjId: item.ssObjId || rawItem.ssObjId || "",
|
|
|
+ ssObjName: item.ssObjName || rawItem.ssObjName || "",
|
|
|
+ swipeActions: rawActions.map((action) => ({
|
|
|
+ ...action,
|
|
|
+ title: action.desc || action.title || "操作",
|
|
|
+ })),
|
|
|
+ };
|
|
|
+ });
|
|
|
+
|
|
|
+ if (isLoadMore) {
|
|
|
+ // 加载更多:追加到现有列表
|
|
|
+ this.list = [...this.list, ...enhancedList];
|
|
|
+ this.originalList = [...this.originalList, ...enhancedList];
|
|
|
+ } else {
|
|
|
+ // 首次加载或刷新:替换列表
|
|
|
+ this.originalList = enhancedList;
|
|
|
+ this.list = [...this.originalList];
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log("✅ 列表数据处理完成:", this.list.length, "条");
|
|
|
+
|
|
|
+ // 更新分页状态
|
|
|
+ this.currentPage = isLoadMore ? this.currentPage + 1 : 1;
|
|
|
+
|
|
|
+ // 根据ssPaging信息判断是否还有更多数据
|
|
|
+ if (this.ssPaging && this.ssPaging.rowNum !== undefined) {
|
|
|
+ const totalRecords = this.ssPaging.rowNum;
|
|
|
+ const currentRecords = this.list.length;
|
|
|
+ this.hasMore = currentRecords < totalRecords;
|
|
|
+ console.log("📊 分页信息:", {
|
|
|
+ totalRecords,
|
|
|
+ currentRecords,
|
|
|
+ hasMore: this.hasMore,
|
|
|
+ currentPage: this.currentPage,
|
|
|
+ });
|
|
|
+ } else {
|
|
|
+ // 降级处理:根据当前页数据量判断
|
|
|
+ this.hasMore = formattedList.length >= this.pageSize;
|
|
|
+ console.log("⚠️ 使用降级分页判断:", {
|
|
|
+ returnedCount: formattedList.length,
|
|
|
+ pageSize: this.pageSize,
|
|
|
+ hasMore: this.hasMore,
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // 没有更多数据
|
|
|
+ this.hasMore = false;
|
|
|
+ console.log("❌ 没有返回数据,设置hasMore为false");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据fieldsList生成筛选选项(只在首次加载时生成)
|
|
|
+ if (!isLoadMore) {
|
|
|
+ await this.generateFilterOptions();
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error("❌ 数据处理失败:", error);
|
|
|
+ throw error;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 获取URL参数
|
|
|
+ getUrlParams() {
|
|
|
+ const params = {};
|
|
|
+ const urlSearchParams = new URLSearchParams(
|
|
|
+ window.location.search
|
|
|
+ );
|
|
|
+ for (const [key, value] of urlSearchParams) {
|
|
|
+ params[key] = decodeURIComponent(value);
|
|
|
+ }
|
|
|
+ return params;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 处理buttonList按钮点击
|
|
|
+ handleButtonClick(button) {
|
|
|
+ console.log("🔘 按钮点击:", button);
|
|
|
+
|
|
|
+ // 直接跳转到目标页面
|
|
|
+ const destPage = button.function?.dest || button.dest;
|
|
|
+ NavigationManager.goToFromButton(button);
|
|
|
+ },
|
|
|
+
|
|
|
+ // 跳转到目标页面
|
|
|
+ navigateToPage(button, destPage) {
|
|
|
+ const urlParams = new URLSearchParams(window.location.search);
|
|
|
+
|
|
|
+ // 添加按钮相关参数
|
|
|
+ if (button.function) {
|
|
|
+ urlParams.set("dest", destPage);
|
|
|
+ urlParams.set(
|
|
|
+ "title",
|
|
|
+ encodeURIComponent(button.function.desc || button.buttonName)
|
|
|
+ );
|
|
|
+ urlParams.set(
|
|
|
+ "service",
|
|
|
+ button.function.servName || button.service || ""
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ urlParams.set("dest", destPage);
|
|
|
+ urlParams.set("title", encodeURIComponent(button.buttonName));
|
|
|
+ urlParams.set("service", button.service || "");
|
|
|
+ }
|
|
|
+ const newUrl = `${destPage}.html?${urlParams.toString()}`;
|
|
|
+
|
|
|
+ console.log("� 跳转到:", newUrl);
|
|
|
+ window.location.href = newUrl;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 根据fieldsList生成筛选选项
|
|
|
+ async generateFilterOptions() {
|
|
|
+ if (!this.fieldsList || this.fieldsList.length === 0) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (const field of this.fieldsList) {
|
|
|
+ // 如果字段有cbName,生成下拉选项
|
|
|
+ if (field.cbName) {
|
|
|
+ try {
|
|
|
+ const options = await window.getDictOptions(
|
|
|
+ field.cbName,
|
|
|
+ this.dictCache
|
|
|
+ );
|
|
|
+ this.filterSelectOptions[field.name] = [
|
|
|
+ { n: "", v: "" },
|
|
|
+ ...options,
|
|
|
+ ];
|
|
|
+ } catch (error) {
|
|
|
+ console.error("获取筛选选项失败:", field.cbName, error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log("🔽 生成筛选选项:", this.filterSelectOptions);
|
|
|
+ },
|
|
|
+
|
|
|
+ // 功能说明:统一处理卡片服务跳转,页面文件名按后端 dest 自动补 mp_,但传给业务页/后端的 dest 仍保持原始值 by xu 2026-03-06
|
|
|
+ openServicePage(action, item = {}) {
|
|
|
+ const target =
|
|
|
+ action && typeof action === "object" ? action : null;
|
|
|
+ if (!target) {
|
|
|
+ this.showToast("当前记录缺少操作配置", "warning");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ const serviceName = String(
|
|
|
+ target.servName || target.ssServ || ""
|
|
|
+ ).trim();
|
|
|
+ const rawDest = String(target.dest || "").trim();
|
|
|
+ const normalizedDest =
|
|
|
+ rawDest && rawDest.startsWith("mp_")
|
|
|
+ ? rawDest
|
|
|
+ : rawDest
|
|
|
+ ? `mp_${rawDest}`
|
|
|
+ : "";
|
|
|
+ const paramName = String(target.param_name || "").trim();
|
|
|
+ const paramValue = target.param_value;
|
|
|
+ const ssToken = String(target.ssToken || "").trim();
|
|
|
+ const paramText =
|
|
|
+ typeof target.parm === "string" ? target.parm : "";
|
|
|
+
|
|
|
+ if (!serviceName) {
|
|
|
+ this.showToast("缺少服务名", "warning");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!normalizedDest) {
|
|
|
+ this.showToast("缺少目标页面", "warning");
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ NavigationManager.goTo(normalizedDest, {
|
|
|
+ title: target.desc || target.title || "处理",
|
|
|
+ service: serviceName,
|
|
|
+ dest: rawDest,
|
|
|
+ ssDest: rawDest,
|
|
|
+ param: paramText,
|
|
|
+ playParamName: paramName,
|
|
|
+ playParamValue: paramValue,
|
|
|
+ ssToken: ssToken,
|
|
|
+ ssObjId: item.ssObjId || "",
|
|
|
+ ssObjName: item.ssObjName || "",
|
|
|
+ management: this.pageParams.management || "1",
|
|
|
+ [paramName || "param_value"]: paramValue,
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 卡片点击 - SsCard组件会自动传递item数据
|
|
|
+ handleCardClick(item) {
|
|
|
+ console.log("📄 卡片点击事件触发", item);
|
|
|
+ // 功能说明:卡片点击统一按 play 参数跳转到通用查看页 mp_objplay(兼容 play / service.play 两种结构) by xu 2026-03-04
|
|
|
+ const play =
|
|
|
+ (item && (item.play || (item.service && item.service.play))) ||
|
|
|
+ null;
|
|
|
+ this.openServicePage(play, item);
|
|
|
+ },
|
|
|
+
|
|
|
+ // 卡片操作 - SsCard组件的按钮点击事件
|
|
|
+ handleCardAction({ button, item, index }) {
|
|
|
+ console.log("⚡ 卡片操作:", button, item);
|
|
|
+ // 功能说明:左滑操作按钮按服务配置跳转(如 chg=变动),不再弹 Toast 占位 by xu 2026-03-06
|
|
|
+ this.openServicePage(button, item);
|
|
|
+ },
|
|
|
+
|
|
|
+ // 加载更多数据
|
|
|
+ async loadMore() {
|
|
|
+ if (!this.hasMore || this.isLoadingMore) {
|
|
|
+ console.log("🚫 无法加载更多:", {
|
|
|
+ hasMore: this.hasMore,
|
|
|
+ isLoadingMore: this.isLoadingMore,
|
|
|
+ });
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log("📄 加载更多数据...");
|
|
|
+ await this.loadData(this.currentPage + 1, true);
|
|
|
+ },
|
|
|
+
|
|
|
+ // 刷新数据
|
|
|
+ async refreshData() {
|
|
|
+ console.log("🔄 刷新数据...");
|
|
|
+ // 重置分页状态
|
|
|
+ this.currentPage = 1;
|
|
|
+ this.hasMore = true;
|
|
|
+ this.list = [];
|
|
|
+ this.originalList = [];
|
|
|
+
|
|
|
+ try {
|
|
|
+ await this.loadData(1, false);
|
|
|
+ this.showToast("刷新成功", "success");
|
|
|
+ } catch (error) {
|
|
|
+ this.showToast("刷新失败", "error");
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 获取字段描述
|
|
|
+ getFieldDesc(fieldName) {
|
|
|
+ const field = this.fieldsList.find((f) => f.name === fieldName);
|
|
|
+ return field ? field.desc : fieldName;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 功能说明:统一按钮文案映射(兼容 rootFuncList/buttonList 不同字段) by xu 2026-02-28
|
|
|
+ getButtonText(button) {
|
|
|
+ if (!button || typeof button !== "object") return "";
|
|
|
+ return (
|
|
|
+ button.desc ||
|
|
|
+ button.title ||
|
|
|
+ button.buttonName ||
|
|
|
+ button.name ||
|
|
|
+ ""
|
|
|
+ );
|
|
|
+ },
|
|
|
+
|
|
|
+ // 功能说明:提取有效筛选参数(过滤空串/null/undefined) by xu 2026-02-28
|
|
|
+ getActiveFilterParams(source) {
|
|
|
+ const params = {};
|
|
|
+ const obj = source && typeof source === "object" ? source : {};
|
|
|
+ Object.entries(obj).forEach(([key, value]) => {
|
|
|
+ if (value === "" || value === null || value === undefined)
|
|
|
+ return;
|
|
|
+ params[key] = value;
|
|
|
+ });
|
|
|
+ return params;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 筛选选择器变化(立即搜索)
|
|
|
+ handleFilterChange(value) {
|
|
|
+ console.log("🔽 筛选条件变化,立即搜索:", value);
|
|
|
+ // 立即执行搜索
|
|
|
+ this.applyDynamicFilters();
|
|
|
+ },
|
|
|
+
|
|
|
+ // 应用动态筛选(重新调用API)
|
|
|
+ async applyDynamicFilters() {
|
|
|
+ try {
|
|
|
+ console.log("🔍 应用筛选条件:", this.selectedFilters);
|
|
|
+
|
|
|
+ // 功能说明:筛选参数先归一化,清理已取消的筛选条件 by xu 2026-02-28
|
|
|
+ const filterParams = this.getActiveFilterParams(
|
|
|
+ this.selectedFilters
|
|
|
+ );
|
|
|
+
|
|
|
+ // 功能说明:筛选后重置分页并走 list 服务 by xu 2026-02-28
|
|
|
+ this.currentPage = 1;
|
|
|
+ this.hasMore = true;
|
|
|
+ this.list = [];
|
|
|
+ this.originalList = [];
|
|
|
+ this.selectedFilters = { ...filterParams };
|
|
|
+ await this.loadData(1, false);
|
|
|
+ } catch (error) {
|
|
|
+ console.error("❌ 筛选失败:", error);
|
|
|
+ this.showToast("筛选失败", "error");
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 生成项目按钮
|
|
|
+ generateItemButtons(service) {
|
|
|
+ if (!service || !service.play) return [];
|
|
|
+
|
|
|
+ return [
|
|
|
+ {
|
|
|
+ title: service.play.name || "查看",
|
|
|
+ icon: "icon-chakan",
|
|
|
+ onclick: () => {
|
|
|
+ console.log("点击查看按钮:", service.play);
|
|
|
+ // 这里可以跳转到详情页面
|
|
|
+ this.showToast(`查看: ${service.play.title}`, "info");
|
|
|
+ },
|
|
|
+ },
|
|
|
+ ];
|
|
|
+ },
|
|
|
+
|
|
|
+ // 设置页面刷新监听
|
|
|
+ setupRefreshListener() {
|
|
|
+ // 监听子页面返回时的刷新通知
|
|
|
+ this.refreshCleanup = NavigationManager.onRefreshNotify(
|
|
|
+ (refreshData) => {
|
|
|
+ console.log("📢 收到刷新通知,重新加载数据");
|
|
|
+ this.refreshData();
|
|
|
+ }
|
|
|
+ );
|
|
|
+ },
|
|
|
+
|
|
|
+ // 设置滚动监听
|
|
|
+ setupScrollListener() {
|
|
|
+ let isThrottled = false;
|
|
|
+
|
|
|
+ const handleScroll = () => {
|
|
|
+ if (isThrottled) return;
|
|
|
+
|
|
|
+ isThrottled = true;
|
|
|
+ setTimeout(() => {
|
|
|
+ isThrottled = false;
|
|
|
+ }, 200); // 节流200ms
|
|
|
+
|
|
|
+ // 检查是否滚动到底部
|
|
|
+ const scrollTop =
|
|
|
+ window.pageYOffset || document.documentElement.scrollTop;
|
|
|
+ const windowHeight = window.innerHeight;
|
|
|
+ const documentHeight = document.documentElement.scrollHeight;
|
|
|
+
|
|
|
+ // 控制回到顶部按钮显示:滚动超过300px时显示
|
|
|
+ this.showBackToTop = scrollTop > 300;
|
|
|
+
|
|
|
+ // 距离底部50px时触发加载更多
|
|
|
+ if (scrollTop + windowHeight >= documentHeight - 50) {
|
|
|
+ console.log("📄 滚动到底部,尝试加载更多...");
|
|
|
+ this.loadMore();
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 添加滚动监听
|
|
|
+ window.addEventListener("scroll", handleScroll);
|
|
|
+
|
|
|
+ // 保存清理函数
|
|
|
+ this.scrollCleanup = () => {
|
|
|
+ window.removeEventListener("scroll", handleScroll);
|
|
|
+ };
|
|
|
+ },
|
|
|
+
|
|
|
+ // 返回顶部
|
|
|
+ scrollToTop() {
|
|
|
+ window.scrollTo({
|
|
|
+ top: 0,
|
|
|
+ behavior: "smooth",
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 处理回到顶部按钮点击
|
|
|
+ handleBackToTopClick() {
|
|
|
+ // 如果不是长按触发的搜索,则执行返回顶部
|
|
|
+ if (!this.isLongPress) {
|
|
|
+ this.scrollToTop();
|
|
|
+ }
|
|
|
+ // 重置长按标志
|
|
|
+ this.isLongPress = false;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 长按开始
|
|
|
+ handleLongPressStart(event) {
|
|
|
+ if (this.hasKeyWord) {
|
|
|
+ // 有输入关键字才显示,否则不处理长按
|
|
|
+ this.isLongPress = false;
|
|
|
+
|
|
|
+ // 设置长按定时器(500ms)
|
|
|
+ this.longPressTimer = setTimeout(() => {
|
|
|
+ this.isLongPress = true;
|
|
|
+ this.openSearchModal();
|
|
|
+ // 震动反馈(如果支持)
|
|
|
+ if (navigator.vibrate) {
|
|
|
+ navigator.vibrate(50);
|
|
|
+ }
|
|
|
+ }, 500);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 长按结束
|
|
|
+ handleLongPressEnd(event) {
|
|
|
+ // 清除长按定时器
|
|
|
+ if (this.longPressTimer) {
|
|
|
+ clearTimeout(this.longPressTimer);
|
|
|
+ this.longPressTimer = null;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ // 长按取消(手指移出按钮区域)
|
|
|
+ handleLongPressCancel(event) {
|
|
|
+ // 清除长按定时器
|
|
|
+ if (this.longPressTimer) {
|
|
|
+ clearTimeout(this.longPressTimer);
|
|
|
+ this.longPressTimer = null;
|
|
|
+ }
|
|
|
+ this.isLongPress = false;
|
|
|
+ },
|
|
|
+
|
|
|
+ // 打开搜索弹窗
|
|
|
+ openSearchModal() {
|
|
|
+ this.showSearchModal = true;
|
|
|
+ this.searchKeyword = "";
|
|
|
+
|
|
|
+ // 延迟聚焦输入框,确保DOM已渲染和动画完成
|
|
|
+ this.$nextTick(() => {
|
|
|
+ // 使用 setTimeout 确保在移动端也能正常触发键盘
|
|
|
+ setTimeout(() => {
|
|
|
+ if (this.$refs.searchInput) {
|
|
|
+ // 先点击再聚焦,确保移动端键盘弹出
|
|
|
+ this.$refs.searchInput.click();
|
|
|
+ this.$refs.searchInput.focus();
|
|
|
+ console.log("✅ 输入框已聚焦");
|
|
|
+ }
|
|
|
+ }, 100); // 等待动画完成后再聚焦
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ // 关闭搜索弹窗
|
|
|
+ closeSearchModal() {
|
|
|
+ this.showSearchModal = false;
|
|
|
+ this.searchKeyword = "";
|
|
|
+ },
|
|
|
+
|
|
|
+ // 执行搜索
|
|
|
+ async performSearch() {
|
|
|
+ const keyword = this.searchKeyword.trim();
|
|
|
+
|
|
|
+ // 无关键词时,直接关闭弹窗,什么都不做
|
|
|
+ if (!keyword) {
|
|
|
+ // this.closeSearchModal();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ console.log("🔍 执行关键词搜索:", keyword);
|
|
|
+
|
|
|
+ // 关闭搜索弹窗
|
|
|
+ this.closeSearchModal();
|
|
|
+
|
|
|
+ // 设置 keyword 到筛选条件中
|
|
|
+ this.selectedFilters.ssKeyword = keyword;
|
|
|
+
|
|
|
+ // 调用 API 搜索
|
|
|
+ await this.applyDynamicFilters();
|
|
|
+ },
|
|
|
+
|
|
|
+ // 显示提示
|
|
|
+ showToast(message, type = "info") {
|
|
|
+ console.log(`${type.toUpperCase()}: ${message}`);
|
|
|
+ // 使用浏览器原生alert,后续可以替换为更好的提示组件
|
|
|
+ alert(message);
|
|
|
+ },
|
|
|
+ },
|
|
|
+ });
|
|
|
+ });
|
|
|
+ </script>
|
|
|
+ </body>
|
|
|
+</html>
|