1 1 месяц назад
Родитель
Сommit
0e7330cc2c

+ 229 - 0
imwork-windows/imwork-silos/src/main/resources/static/assets/lib/jplus/dropdown/css/dropdown.css

@@ -0,0 +1,229 @@
+.custom-dropdown {
+    position: relative;
+    width: 100%;
+    font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
+}
+
+.custom-dropdown .dropdown-toggle {
+    width: 100%;
+    padding: 10px 15px;
+    border: 1px solid #dcdfe6;
+    border-radius: 4px;
+    background-color: white;
+    text-align: left;
+    font-size: 14px;
+    cursor: pointer;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    transition: border-color 0.2s, box-shadow 0.2s;
+    color: #606266;
+}
+
+.custom-dropdown .dropdown-toggle:hover {
+    border-color: #c0c4cc;
+}
+
+.custom-dropdown .dropdown-toggle.active {
+    border-color: #409eff;
+    box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.1);
+}
+
+.custom-dropdown .dropdown-toggle .placeholder {
+    color: #c0c4cc;
+}
+
+.custom-dropdown .dropdown-toggle .selected-text {
+    color: #303133;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+}
+
+.custom-dropdown .dropdown-toggle .dropdown-arrow {
+    color: #c0c4cc;
+    font-size: 12px;
+    transition: transform 0.2s;
+    margin-left: 5px;
+}
+
+.custom-dropdown .dropdown-toggle.active .dropdown-arrow {
+    transform: rotate(180deg);
+}
+
+.custom-dropdown .dropdown-menu {
+    position: absolute;
+    top: 100%;
+    left: 0;
+    width: 100%;
+    background-color: white;
+    border: 1px solid #e4e7ed;
+    border-radius: 4px;
+    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+    z-index: 2000;
+    display: none;
+    margin-top: 5px;
+    overflow: hidden;
+}
+
+.custom-dropdown .dropdown-header {
+    padding: 10px;
+    border-bottom: 1px solid #e4e7ed;
+    background-color: #f5f7fa;
+}
+
+.custom-dropdown .search-input {
+    width: 100%;
+    padding: 8px 12px;
+    border: 1px solid #dcdfe6;
+    border-radius: 4px;
+    font-size: 13px;
+    transition: border-color 0.2s;
+    color: #606266;
+    background-color: white;
+}
+
+.custom-dropdown .search-input:focus {
+    outline: none;
+    border-color: #409eff;
+    box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.1);
+}
+
+.custom-dropdown .search-input::placeholder {
+    color: #c0c4cc;
+}
+
+.custom-dropdown .dropdown-items {
+    max-height: 300px;
+    overflow-y: auto;
+    padding: 5px 0;
+}
+
+.custom-dropdown .dropdown-item {
+    padding: 10px 15px;
+    cursor: pointer;
+    transition: background-color 0.15s;
+    font-size: 14px;
+    color: #606266;
+}
+
+.custom-dropdown .dropdown-item:hover {
+    background-color: #f5f7fa;
+    color: #409eff;
+}
+
+.custom-dropdown .dropdown-item.selected {
+    background-color: #f0f9ff;
+    color: #409eff;
+    font-weight: 500;
+}
+
+.custom-dropdown .dropdown-item .item-text {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+}
+
+.custom-dropdown .dropdown-footer {
+    padding: 10px;
+    border-top: 1px solid #e4e7ed;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    background-color: #f5f7fa;
+    font-size: 12px;
+}
+
+.custom-dropdown .loading-indicator,
+.custom-dropdown .no-data {
+    padding: 20px;
+    text-align: center;
+    color: #909399;
+    font-size: 14px;
+}
+
+.custom-dropdown .pagination-info {
+    font-size: 12px;
+    color: #909399;
+    display: flex;
+    align-items: center;
+    gap: 5px;
+}
+
+.custom-dropdown .pagination-info .current-page,
+.custom-dropdown .pagination-info .total-pages {
+    color: #409eff;
+    font-weight: 500;
+}
+
+.custom-dropdown .pagination-info .total-info {
+    margin-left: 8px;
+    color: #909399;
+}
+
+.custom-dropdown .pagination-info .total-items {
+    color: #409eff;
+    font-weight: 500;
+}
+
+.custom-dropdown .pagination-controls {
+    display: flex;
+    gap: 5px;
+}
+
+.custom-dropdown .pagination-btn {
+    padding: 4px 8px;
+    background-color: white;
+    border: 1px solid #dcdfe6;
+    border-radius: 3px;
+    cursor: pointer;
+    font-size: 11px;
+    transition: all 0.2s;
+    color: #606266;
+    display: flex;
+    align-items: center;
+    gap: 3px;
+}
+
+.custom-dropdown .pagination-btn:hover:not(:disabled) {
+    background-color: #409eff;
+    color: white;
+    border-color: #409eff;
+}
+
+.custom-dropdown .pagination-btn:disabled {
+    opacity: 0.5;
+    cursor: not-allowed;
+}
+
+.custom-dropdown .pagination-btn i {
+    font-size: 10px;
+}
+
+.custom-dropdown .dropdown-error {
+    padding: 15px;
+    color: #f56c6c;
+    text-align: center;
+    background-color: #fef0f0;
+    border-radius: 4px;
+    margin: 10px;
+    font-size: 13px;
+    border: 1px solid #fbc4c4;
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+    .custom-dropdown .dropdown-menu {
+        position: fixed;
+        top: 50%;
+        left: 50%;
+        transform: translate(-50%, -50%);
+        width: 90%;
+        max-width: 400px;
+        max-height: 80vh;
+    }
+
+    .custom-dropdown .dropdown-items {
+        max-height: 50vh;
+    }
+}

+ 733 - 0
imwork-windows/imwork-silos/src/main/resources/static/assets/lib/jplus/dropdown/js/dropdown.js

@@ -0,0 +1,733 @@
+/**
+ * jQuery自定义下拉框插件 - 支持搜索、分页、远程数据
+ * 版本: 1.1.0
+ * 依赖: jQuery 3.7.1+
+ */
+(function($) {
+    'use strict';
+
+    // 默认配置
+    const defaults = {
+        // 数据源配置
+        dataSource: null, // 本地数据数组
+        remoteUrl: null,  // 远程API地址
+        remoteMethod: 'POST', // 请求方法 (支持GET/POST)
+        remoteParams: {}, // 额外请求参数
+
+        // 显示配置
+        placeholder: '请选择',
+        searchPlaceholder: '搜索...',
+        itemsPerPage: 10,
+        minSearchLength: 1, // 最小搜索字符长度
+
+        // 字段映射
+        idField: 'id',
+        textField: 'name',
+        valueField: 'id',
+
+        // 分页字段映射 - 根据提供的参数名调整
+        pageField: 'pageNo',          // 页码字段
+        sizeField: 'pageRows',        // 每页记录数字段
+        searchField: 'keyword',       // 搜索字段
+        totalField: 'totalRows',      // 总记录数字段
+        dataField: 'dataList',        // 数据列表字段
+        pagesField: 'pageCount',      // 总页数字段
+
+        // 事件回调
+        onSelect: null,
+        onLoad: null,
+        onError: null,
+        onBeforeLoad: null, // 新增:加载前回调
+
+        // 其他
+        allowClear: true,
+        cacheRemoteData: true,
+        ajaxTimeout: 10000,
+        debounceDelay: 300, // 防抖延迟(毫秒)
+        showLoadingText: '加载中...',
+        noDataText: '暂无数据',
+        noSearchResultText: '没有找到匹配的数据',
+        errorText: '数据加载失败'
+    };
+
+    // 下拉框类
+    class CustomDropdown {
+        constructor(element, options) {
+            this.$element = $(element);
+            this.options = $.extend({}, defaults, options);
+            this.init();
+        }
+
+        init() {
+            // 创建DOM结构
+            this.createStructure();
+
+            // 初始化状态
+            this.currentPage = 1;
+            this.totalPages = 1;
+            this.totalItems = 0;
+            this.searchTerm = '';
+            this.selectedItem = null;
+            this.cachedData = null;
+            this.isLoading = false;
+            this.hasMore = true;
+            this.lastRequestId = 0; // 用于防止重复请求
+
+            // 绑定事件
+            this.bindEvents();
+
+            // 初始化数据
+            this.initData();
+        }
+
+        createStructure() {
+            // 如果已经初始化过,则跳过
+            if (this.$element.find('.dropdown-toggle').length > 0) {
+                return;
+            }
+
+            // 创建隐藏的input用于存储选中值
+            const name = this.$element.attr('name') || this.$element.data('name') || 'dropdown_value';
+            const initialValue = this.$element.val() || '';
+
+            this.$element.wrap('<div class="custom-dropdown"></div>');
+            this.$dropdown = this.$element.parent();
+
+            // 添加HTML结构
+            this.$dropdown.html(`
+                <input type="hidden" class="dropdown-value" name="${name}" value="${initialValue}">
+                <button type="button" class="dropdown-toggle">
+                    <span class="dropdown-text placeholder">${this.options.placeholder}</span>
+                    <span class="dropdown-arrow">▼</span>
+                </button>
+                <div class="dropdown-menu">
+                    <div class="dropdown-header">
+                        <input type="text" class="search-input" placeholder="${this.options.searchPlaceholder}">
+                    </div>
+                    <div class="dropdown-items"></div>
+                    <div class="dropdown-footer" style="display: none;">
+                        <div class="pagination-info">
+                            第 <span class="current-page">1</span> 页,共 <span class="total-pages">1</span> 页
+                            <span class="total-info">(共 <span class="total-items">0</span> 条)</span>
+                        </div>
+                        <div class="pagination-controls">
+                            <button class="pagination-btn prev-btn" type="button" disabled>
+                                <i class="fas fa-chevron-left"></i> 上一页
+                            </button>
+                            <button class="pagination-btn next-btn" type="button" disabled>
+                                下一页 <i class="fas fa-chevron-right"></i>
+                            </button>
+                        </div>
+                    </div>
+                </div>
+            `);
+
+            // 获取DOM引用
+            this.$toggle = this.$dropdown.find('.dropdown-toggle');
+            this.$text = this.$dropdown.find('.dropdown-text');
+            this.$menu = this.$dropdown.find('.dropdown-menu');
+            this.$search = this.$dropdown.find('.search-input');
+            this.$items = this.$dropdown.find('.dropdown-items');
+            this.$footer = this.$dropdown.find('.dropdown-footer');
+            this.$prevBtn = this.$dropdown.find('.prev-btn');
+            this.$nextBtn = this.$dropdown.find('.next-btn');
+            this.$valueInput = this.$dropdown.find('.dropdown-value');
+            this.$currentPage = this.$dropdown.find('.current-page');
+            this.$totalPages = this.$dropdown.find('.total-pages');
+            this.$totalItems = this.$dropdown.find('.total-items');
+
+            // 隐藏原始select
+            this.$element.hide();
+        }
+
+        bindEvents() {
+            const self = this;
+
+            // 切换下拉菜单
+            this.$toggle.on('click', function(e) {
+                e.stopPropagation();
+                self.toggleDropdown();
+            });
+
+            // 搜索输入(防抖处理)
+            let searchTimeout;
+            this.$search.on('input', function() {
+                clearTimeout(searchTimeout);
+                searchTimeout = setTimeout(() => {
+                    self.handleSearch($(this).val());
+                }, self.options.debounceDelay);
+            });
+
+            // 搜索框回车键
+            this.$search.on('keydown', function(e) {
+                if (e.key === 'Enter') {
+                    self.handleSearch($(this).val());
+                }
+            });
+
+            // 分页按钮
+            this.$prevBtn.on('click', function() {
+                if (self.currentPage > 1) {
+                    self.currentPage--;
+                    self.loadData();
+                }
+            });
+
+            this.$nextBtn.on('click', function() {
+                if (self.currentPage < self.totalPages) {
+                    self.currentPage++;
+                    self.loadData();
+                }
+            });
+
+            // 点击外部关闭
+            $(document).on('click', function(e) {
+                if (!self.$dropdown.is(e.target) && self.$dropdown.has(e.target).length === 0) {
+                    self.closeDropdown();
+                }
+            });
+
+            // 阻止下拉菜单内部点击事件冒泡
+            this.$menu.on('click', function(e) {
+                e.stopPropagation();
+            });
+        }
+
+        initData() {
+            const initialValue = this.$valueInput.val();
+
+            if (initialValue) {
+                this.loadInitialValue(initialValue);
+            }
+
+            // 如果是本地数据源,直接加载
+            if (this.options.dataSource && !this.options.remoteUrl) {
+                this.cachedData = this.options.dataSource;
+                this.totalItems = this.cachedData.length;
+                this.totalPages = Math.ceil(this.totalItems / this.options.itemsPerPage);
+                this.renderItems();
+                this.updatePagination();
+            }
+        }
+
+        loadInitialValue(value) {
+            if (!value) return;
+
+            // 如果是远程数据,需要根据ID获取详情
+            if (this.options.remoteUrl) {
+                this.loadItemById(value);
+            } else if (this.options.dataSource) {
+                // 本地数据中查找
+                const item = this.options.dataSource.find(item =>
+                    String(item[this.options.valueField]) === String(value)
+                );
+
+                if (item) {
+                    this.setSelected(item);
+                }
+            }
+        }
+
+        async loadItemById(id) {
+            try {
+                const params = $.extend({}, this.options.remoteParams, {
+                    [this.options.idField]: id
+                });
+
+                const requestId = ++this.lastRequestId;
+                this.isLoading = true;
+
+                const response = await $.ajax({
+                    url: this.options.remoteUrl,
+                    method: this.options.remoteMethod,
+                    data: this.options.remoteMethod === 'GET' ? params : JSON.stringify(params),
+                    dataType: 'json',
+                    contentType: this.options.remoteMethod === 'POST' ? 'application/json' : 'application/x-www-form-urlencoded',
+                    timeout: this.options.ajaxTimeout,
+                    beforeSend: () => {
+                        // 如果请求已过期,中止
+                        if (requestId !== this.lastRequestId) {
+                            return false;
+                        }
+                    }
+                });
+
+                if (response && response[this.options.dataField]) {
+                    const item = response[this.options.dataField];
+                    if (Array.isArray(item) && item.length > 0) {
+                        this.setSelected(item[0]);
+                    } else if (typeof item === 'object') {
+                        this.setSelected(item);
+                    }
+                } else if (response && typeof response === 'object') {
+                    // 尝试直接使用响应对象
+                    this.setSelected(response);
+                }
+            } catch (error) {
+                console.error('加载选中项失败:', error);
+                if (this.options.onError) {
+                    this.options.onError.call(this, error, 'loadItem');
+                }
+            } finally {
+                this.isLoading = false;
+            }
+        }
+
+        toggleDropdown() {
+            if (this.$menu.is(':visible')) {
+                this.closeDropdown();
+            } else {
+                this.openDropdown();
+            }
+        }
+
+        openDropdown() {
+            this.$menu.show();
+            this.$toggle.addClass('active');
+
+            // 如果还没有加载过数据,或者需要刷新数据
+            if (!this.cachedData || this.options.remoteUrl) {
+                this.currentPage = 1;
+                this.loadData();
+            }
+
+            // 聚焦搜索框
+            setTimeout(() => this.$search.focus(), 10);
+        }
+
+        closeDropdown() {
+            this.$menu.hide();
+            this.$toggle.removeClass('active');
+        }
+
+        handleSearch(keyword) {
+            const newSearchTerm = keyword.trim();
+
+            // 如果搜索词没变化,不执行搜索
+            if (newSearchTerm === this.searchTerm) return;
+
+            this.searchTerm = newSearchTerm;
+            this.currentPage = 1;
+            this.loadData();
+        }
+
+        async loadData() {
+            // 防止重复加载
+            if (this.isLoading) return;
+
+            // 触发加载前回调
+            if (this.options.onBeforeLoad) {
+                const shouldContinue = this.options.onBeforeLoad.call(this, {
+                    page: this.currentPage,
+                    searchTerm: this.searchTerm
+                });
+
+                if (shouldContinue === false) {
+                    return;
+                }
+            }
+
+            this.isLoading = true;
+            this.showLoading();
+
+            const requestId = ++this.lastRequestId;
+
+            try {
+                if (this.options.remoteUrl) {
+                    await this.loadRemoteData(requestId);
+                } else if (this.options.dataSource) {
+                    this.loadLocalData();
+                }
+
+                // 检查请求是否过期
+                if (requestId !== this.lastRequestId) {
+                    return;
+                }
+
+                this.updatePagination();
+
+                if (this.options.onLoad) {
+                    this.options.onLoad.call(this, this.cachedData, {
+                        page: this.currentPage,
+                        total: this.totalItems,
+                        pages: this.totalPages
+                    });
+                }
+            } catch (error) {
+                // 检查请求是否过期
+                if (requestId !== this.lastRequestId) {
+                    return;
+                }
+
+                this.showError(this.options.errorText + ': ' + (error.message || '未知错误'));
+                console.error('加载数据失败:', error);
+
+                if (this.options.onError) {
+                    this.options.onError.call(this, error, 'loadData');
+                }
+            } finally {
+                // 检查请求是否过期
+                if (requestId === this.lastRequestId) {
+                    this.isLoading = false;
+                    this.hideLoading();
+                }
+            }
+        }
+
+        async loadRemoteData(requestId) {
+            // 构建请求参数
+            const params = {
+                [this.options.pageField]: this.currentPage,
+                [this.options.sizeField]: this.options.itemsPerPage
+            };
+
+            // 添加搜索关键词
+            if (this.searchTerm && this.searchTerm.length >= this.options.minSearchLength) {
+                params[this.options.searchField] = this.searchTerm;
+            }
+
+            // 合并额外参数
+            const requestParams = $.extend({}, this.options.remoteParams, params);
+
+            // 准备AJAX配置
+            const ajaxConfig = {
+                url: this.options.remoteUrl,
+                method: this.options.remoteMethod,
+                dataType: 'json',
+                timeout: this.options.ajaxTimeout,
+                beforeSend: () => {
+                    // 如果请求已过期,中止
+                    if (requestId !== this.lastRequestId) {
+                        return false;
+                    }
+                }
+            };
+
+            // 根据请求方法设置数据和内容类型
+            if (this.options.remoteMethod.toUpperCase() === 'GET') {
+                ajaxConfig.data = requestParams;
+            } else {
+                // POST请求
+                ajaxConfig.data = JSON.stringify(requestParams);
+                ajaxConfig.contentType = 'application/json; charset=UTF-8';
+                ajaxConfig.processData = false; // 防止jQuery处理数据
+            }
+
+            const response = await $.ajax(ajaxConfig);
+
+            // 检查请求是否过期
+            if (requestId !== this.lastRequestId) {
+                return;
+            }
+
+            // 处理响应数据
+            if (response) {
+                // 支持不同的响应格式
+                let data, total, pages;
+
+                // 格式1: 标准格式
+                if (response[this.options.dataField] !== undefined) {
+                    data = response[this.options.dataField];
+                    total = response[this.options.totalField];
+                    pages = response[this.options.pagesField];
+                }
+                // 格式2: 常见REST格式
+                else if (response.data !== undefined) {
+                    data = response.data;
+                    total = response.total || response.totalRows || response.totalCount;
+                    pages = response.pages || response.pageCount || response.totalPages;
+                }
+                // 格式3: 直接是数组
+                else if (Array.isArray(response)) {
+                    data = response;
+                    total = response.length;
+                    pages = 1;
+                }
+                // 格式4: 其他格式
+                else {
+                    data = response.items || response.list || response.records || [];
+                    total = response.total || response.totalRows || response.totalCount || data.length;
+                    pages = response.pages || response.pageCount || response.totalPages ||
+                        Math.ceil(total / this.options.itemsPerPage);
+                }
+
+                // 确保数据是数组
+                if (!Array.isArray(data)) {
+                    data = [];
+                }
+
+                this.cachedData = data;
+                this.totalItems = total || data.length;
+                this.totalPages = pages || Math.ceil(this.totalItems / this.options.itemsPerPage) || 1;
+
+                this.renderItems();
+            }
+        }
+
+        loadLocalData() {
+            let filteredData = this.options.dataSource || [];
+
+            // 本地搜索过滤
+            if (this.searchTerm && this.searchTerm.length >= this.options.minSearchLength) {
+                const term = this.searchTerm.toLowerCase();
+                filteredData = filteredData.filter(item => {
+                    const text = String(item[this.options.textField] || '').toLowerCase();
+                    return text.includes(term);
+                });
+            }
+
+            // 分页
+            const startIndex = (this.currentPage - 1) * this.options.itemsPerPage;
+            const endIndex = startIndex + this.options.itemsPerPage;
+            const pageData = filteredData.slice(startIndex, endIndex);
+
+            this.cachedData = pageData;
+            this.totalItems = filteredData.length;
+            this.totalPages = Math.ceil(filteredData.length / this.options.itemsPerPage);
+
+            this.renderItems();
+        }
+
+        renderItems() {
+            this.$items.empty();
+
+            if (!this.cachedData || this.cachedData.length === 0) {
+                this.showNoData();
+                this.$footer.hide();
+                return;
+            }
+
+            // 显示分页
+            if (this.totalPages > 1) {
+                this.$footer.show();
+            } else {
+                this.$footer.hide();
+            }
+
+            // 渲染数据项
+            this.cachedData.forEach(item => {
+                const id = item[this.options.idField] || item.id;
+                const text = item[this.options.textField] || item.name || item.text || '';
+                const value = item[this.options.valueField] || id;
+
+                const isSelected = this.selectedItem &&
+                    String(this.selectedItem[this.options.valueField]) === String(value);
+
+                const $item = $(`
+                    <div class="dropdown-item ${isSelected ? 'selected' : ''}" 
+                         data-id="${id}" 
+                         data-value="${value}">
+                        <div class="item-text">${this.escapeHtml(text)}</div>
+                    </div>
+                `);
+
+                // 绑定点击事件
+                $item.on('click', () => {
+                    this.selectItem(item);
+                });
+
+                this.$items.append($item);
+            });
+        }
+
+        selectItem(item) {
+            this.selectedItem = item;
+
+            // 更新显示文本
+            const displayText = item[this.options.textField] || item.name || item.text || '';
+            this.$text.text(displayText).removeClass('placeholder');
+
+            // 更新隐藏的值
+            const value = item[this.options.valueField] || item[this.options.idField] || item.id;
+            this.$valueInput.val(value);
+
+            // 触发原始select的change事件
+            this.$element.val(value).trigger('change');
+
+            // 关闭下拉菜单
+            this.closeDropdown();
+
+            // 触发回调
+            if (this.options.onSelect) {
+                this.options.onSelect.call(this, item, value);
+            }
+
+            // 重新渲染以更新选中状态
+            this.renderItems();
+        }
+
+        setSelected(item) {
+            this.selectedItem = item;
+
+            // 更新显示文本
+            const displayText = item[this.options.textField] || item.name || item.text || '';
+            this.$text.text(displayText).removeClass('placeholder');
+
+            // 更新隐藏的值
+            const value = item[this.options.valueField] || item[this.options.idField] || item.id;
+            this.$valueInput.val(value);
+            this.$element.val(value);
+
+            // 重新渲染以更新选中状态
+            if (this.cachedData) {
+                this.renderItems();
+            }
+        }
+
+        clearSelection() {
+            this.selectedItem = null;
+            this.$text.text(this.options.placeholder).addClass('placeholder');
+            this.$valueInput.val('');
+            this.$element.val('');
+            this.$element.trigger('change');
+
+            if (this.cachedData) {
+                this.renderItems();
+            }
+        }
+
+        updatePagination() {
+            this.$currentPage.text(this.currentPage);
+            this.$totalPages.text(this.totalPages);
+            this.$totalItems.text(this.totalItems);
+
+            // 更新按钮状态
+            this.$prevBtn.prop('disabled', this.currentPage <= 1);
+            this.$nextBtn.prop('disabled', this.currentPage >= this.totalPages);
+
+            // 隐藏分页如果只有一页
+            if (this.totalPages <= 1) {
+                this.$footer.hide();
+            } else {
+                this.$footer.show();
+            }
+        }
+
+        showLoading() {
+            this.$items.html(`<div class="loading-indicator">${this.options.showLoadingText}</div>`);
+            this.$footer.hide();
+        }
+
+        hideLoading() {
+            // 由renderItems处理
+        }
+
+        showNoData() {
+            const message = this.searchTerm ? this.options.noSearchResultText : this.options.noDataText;
+            this.$items.html(`<div class="no-data">${message}</div>`);
+        }
+
+        showError(message) {
+            this.$items.html(`<div class="dropdown-error">${message}</div>`);
+        }
+
+        escapeHtml(text) {
+            const div = document.createElement('div');
+            div.textContent = text;
+            return div.innerHTML;
+        }
+
+        // 公开方法
+        getValue() {
+            return this.$valueInput.val();
+        }
+
+        getSelectedItem() {
+            return this.selectedItem;
+        }
+
+        refresh() {
+            this.currentPage = 1;
+            this.searchTerm = '';
+            this.$search.val('');
+            this.lastRequestId++; // 使之前的请求过期
+            this.loadData();
+        }
+
+        destroy() {
+            // 移除事件绑定
+            this.$toggle.off('click');
+            this.$search.off('input');
+            this.$prevBtn.off('click');
+            this.$nextBtn.off('click');
+            $(document).off('click');
+            this.$menu.off('click');
+
+            // 恢复原始select
+            this.$element.show().unwrap();
+
+            // 移除插件实例
+            this.$element.removeData('customDropdown');
+        }
+    }
+
+    // jQuery插件定义
+    $.fn.customDropdown = function(options) {
+        return this.each(function() {
+            const $this = $(this);
+
+            // 如果已经初始化,返回实例
+            const instance = $this.data('customDropdown');
+            if (instance) {
+                if (typeof options === 'string') {
+                    return instance[options]();
+                }
+                return instance;
+            }
+
+            // 创建新实例
+            const dropdown = new CustomDropdown(this, options);
+            $this.data('customDropdown', dropdown);
+
+            return dropdown;
+        });
+    };
+
+    // 添加公共方法
+    $.fn.customDropdown.getValue = function() {
+        const instance = $(this).data('customDropdown');
+        return instance ? instance.getValue() : null;
+    };
+
+    $.fn.customDropdown.getSelectedItem = function() {
+        const instance = $(this).data('customDropdown');
+        return instance ? instance.getSelectedItem() : null;
+    };
+
+    $.fn.customDropdown.clearSelection = function() {
+        return this.each(function() {
+            const instance = $(this).data('customDropdown');
+            if (instance) {
+                instance.clearSelection();
+            }
+        });
+    };
+
+    $.fn.customDropdown.refresh = function() {
+        return this.each(function() {
+            const instance = $(this).data('customDropdown');
+            if (instance) {
+                instance.refresh();
+            }
+        });
+    };
+
+    $.fn.customDropdown.setSelected = function(item) {
+        return this.each(function() {
+            const instance = $(this).data('customDropdown');
+            if (instance) {
+                instance.setSelected(item);
+            }
+        });
+    };
+
+    $.fn.customDropdown.destroy = function() {
+        return this.each(function() {
+            const instance = $(this).data('customDropdown');
+            if (instance) {
+                instance.destroy();
+            }
+        });
+    };
+
+})(jQuery);

+ 186 - 0
imwork-windows/imwork-silos/src/main/resources/static/assets/lib/jplus/selectRender/selectRender.js

@@ -0,0 +1,186 @@
+$(document).ready(function() {
+    // 示例数据 - 国家
+    const countries = [];
+    // 初始化下拉框控件
+    function initDropdown(dropdownId, data, selectedIdDisplay, selectedNameDisplay) {
+        const $dropdown = $(`#${dropdownId}`);
+        const $toggle = $dropdown.find('.dropdown-toggle');
+        const $menu = $dropdown.find('.dropdown-menu');
+        const $itemsContainer = $dropdown.find('.dropdown-items');
+        const $searchInput = $dropdown.find('.search-input');
+        const $prevBtn = $dropdown.find('.prev-btn');
+        const $nextBtn = $dropdown.find('.next-btn');
+        const $currentPage = $dropdown.find('.current-page');
+        const $totalPages = $dropdown.find('.total-pages');
+        const $hiddenInput = $dropdown.find('input[type="hidden"]');
+        const $placeholder = $dropdown.find('.placeholder');
+
+        let currentPage = 1;
+        let itemsPerPage = 5;
+        let filteredData = [...data];
+        let searchTerm = '';
+        let selectedItem = null;
+
+        // 渲染选项
+        function renderItems() {
+            $itemsContainer.empty();
+
+            // 计算分页
+            const startIndex = (currentPage - 1) * itemsPerPage;
+            const endIndex = startIndex + itemsPerPage;
+            const pageData = filteredData.slice(startIndex, endIndex);
+
+            // 如果没有数据
+            if (pageData.length === 0) {
+                $itemsContainer.append('<div class="dropdown-item" style="text-align: center; color: #95a5a6;">没有找到匹配的选项</div>');
+                return;
+            }
+
+            // 渲染当前页的选项
+            pageData.forEach(item => {
+                const isSelected = selectedItem && selectedItem.id === item.id;
+                const $item = $(`
+                            <div class="dropdown-item ${isSelected ? 'selected' : ''}" data-id="${item.id}">
+                                ${item.name}
+                            </div>
+                        `);
+
+                $itemsContainer.append($item);
+            });
+
+            // 更新分页信息
+            const totalPages = Math.ceil(filteredData.length / itemsPerPage);
+            $currentPage.text(currentPage);
+            $totalPages.text(totalPages);
+
+            // 更新分页按钮状态
+            $prevBtn.prop('disabled', currentPage === 1);
+            $nextBtn.prop('disabled', currentPage === totalPages || totalPages === 0);
+
+            // 如果没有数据,隐藏分页
+            $dropdown.find('.dropdown-footer').toggle(filteredData.length > 0);
+        }
+
+        // 搜索过滤
+        function filterItems() {
+            searchTerm = $searchInput.val().toLowerCase();
+
+            if (searchTerm) {
+                filteredData = data.filter(item =>
+                    item.name.toLowerCase().includes(searchTerm)
+                );
+            } else {
+                filteredData = [...data];
+            }
+
+            currentPage = 1; // 重置到第一页
+            renderItems();
+        }
+
+        // 选择选项
+        function selectItem(item) {
+            selectedItem = item;
+            $hiddenInput.val(item.id);
+            $placeholder.text(item.name).removeClass('placeholder');
+
+            // 更新显示
+            $(selectedIdDisplay).text(item.id);
+            $(selectedNameDisplay).text(item.name);
+
+            // 关闭下拉菜单
+            $menu.hide();
+            $toggle.removeClass('active');
+
+            // 重新渲染以更新选中状态
+            renderItems();
+        }
+
+        // 清除选择
+        function clearSelection() {
+            selectedItem = null;
+            $hiddenInput.val('');
+            $placeholder.text('请选择一个选项').addClass('placeholder');
+
+            // 更新显示
+            $(selectedIdDisplay).text('未选择');
+            $(selectedNameDisplay).text('未选择');
+
+            // 重新渲染
+            renderItems();
+        }
+
+        // 初始化
+        renderItems();
+
+        // 事件绑定
+        $toggle.on('click', function(e) {
+            e.stopPropagation();
+            $menu.toggle();
+            $toggle.toggleClass('active');
+
+            // 如果展开,聚焦搜索框
+            if ($menu.is(':visible')) {
+                setTimeout(() => $searchInput.focus(), 10);
+            }
+        });
+
+        $searchInput.on('input', function() {
+            filterItems();
+        });
+
+        $itemsContainer.on('click', '.dropdown-item', function() {
+            const id = parseInt($(this).data('id'));
+            const item = data.find(d => d.id === id);
+            if (item) {
+                selectItem(item);
+            }
+        });
+
+        $prevBtn.on('click', function() {
+            if (currentPage > 1) {
+                currentPage--;
+                renderItems();
+            }
+        });
+
+        $nextBtn.on('click', function() {
+            const totalPages = Math.ceil(filteredData.length / itemsPerPage);
+            if (currentPage < totalPages) {
+                currentPage++;
+                renderItems();
+            }
+        });
+
+        // 点击页面其他地方关闭下拉菜单
+        $(document).on('click', function(e) {
+            if (!$dropdown.is(e.target) && $dropdown.has(e.target).length === 0) {
+                $menu.hide();
+                $toggle.removeClass('active');
+            }
+        });
+
+        // 防止下拉菜单内部点击事件冒泡到document
+        $menu.on('click', function(e) {
+            e.stopPropagation();
+        });
+
+        // 返回清除选择的方法,以便外部调用
+        return {
+            clearSelection: clearSelection,
+            setSelection: function(id) {
+                const item = data.find(d => d.id === id);
+                if (item) {
+                    selectItem(item);
+                }
+            }
+        };
+    }
+
+    // 初始化两个下拉框
+    const countryDropdown = initDropdown(
+        'country-dropdown',
+        countries,
+        '#selected-id-display',
+        '#selected-country-name'
+    );
+});

+ 39 - 0
imwork-windows/imwork-silos/src/main/resources/static/business/cms/book/contents/list.js

@@ -301,8 +301,47 @@ layui.config({
         });
     }
 
+    // 初始化POST方式下拉框
+    function initPostDropdown() {
+        const method = $('input[name="method2"]:checked').val();
+
+        $('#bookInfoId').customDropdown('destroy');
+
+        $('#bookInfoId').customDropdown({
+            remoteUrl: '/cms/book/info/queryPage',
+            remoteMethod: method,
+            placeholder: method === 'POST' ? '请选择国家 (POST)' : '请选择国家 (GET)',
+            searchPlaceholder: method === 'POST' ? '搜索国家 (POST)...' : '搜索国家 (GET)...',
+            itemsPerPage: 8,
+            remoteParams: {
+                source: 'dropdown-demo',
+                category: 'country',
+                timestamp: Date.now()
+            },
+            onSelect: function(item, value) {
+                const result = {
+                    value: value,
+                    item: item,
+                    method: method
+                };
+                $('#post-result').text(JSON.stringify(result, null, 2));
+            },
+            onLoad: function(data, pagination) {
+                console.log(`POST方式加载完成,共${pagination.total}条数据,${pagination.pages}页`);
+            },
+            onError: function(error, type) {
+                console.error(`POST方式加载失败 (${type}):`, error);
+                $('#post-result').text(`加载失败: ${error.message || '未知错误'}`);
+            }
+        });
+    }
+
     // 页面加载完成后初始化
     $(document).ready(function() {
         init();
+        initPostDropdown();
+        $('input[name="method2"]').change(function() {
+            initPostDropdown();
+        });
     });
 });

+ 9 - 10
imwork-windows/imwork-silos/src/main/resources/templates/cms/book/contents/list.html

@@ -9,6 +9,8 @@
     <link rel="stylesheet" href="../../../../static/assets/lib/layui/css/layui.css" th:href="@{/assets/lib/layui/css/layui.css}"/>
     <link rel="stylesheet" href="../../../../static/assets/lib/fonts/fontawesome6/css/all.min.css" th:href="@{/assets/lib/fonts/fontawesome6/css/all.min.css}"/>
     <link rel="stylesheet" href="../../../../static/business/cms/book/contents/list.css" th:href="@{/business/cms/book/contents/list.css}"/>
+    <link rel="stylesheet" href="../../../../static/assets/lib/jplus/dropdown/css/dropdown.css" th:href="@{/assets/lib/jplus/dropdown/css/dropdown.css}"/>
+
 </head>
 <body>
 <div class="container">
@@ -30,6 +32,12 @@
         </div>
 
         <div class="filters">
+            <div class="filter-group">
+                <label for="bookInfoId">所属图书</label>
+                <select id="bookInfoId" name="bookInfoId">
+                    <option value="">请选择</option>
+                </select>
+            </div>
             <div class="filter-group">
                 <label>内容类型</label>
                 <select id="contentType">
@@ -64,16 +72,6 @@
                 <label>创建时间</label>
                 <input type="date" id="createDateTime">
             </div>
-
-            <div class="filter-group">
-                <label>图书</label>
-                <select id="bookInfoId">
-                    <option value="">全部</option>
-                    <option value="1">网页设计与开发实战</option>
-                    <option value="2">JavaScript高级程序设计</option>
-                    <option value="3">CSS权威指南</option>
-                </select>
-            </div>
         </div>
     </section>
     <div class="chapters-table" id="chaptersTable">
@@ -92,6 +90,7 @@
 
 <script src="../../../../static/assets/lib/jquery/jquery.js" th:src="@{/assets/lib/jquery/jquery.js}"></script>
 <script src="../../../../static/assets/lib/layui/layui.js" th:src="@{/assets/lib/layui/layui.js}"></script>
+<script src="../../../../static/assets/lib/jplus/dropdown/js/dropdown.js" th:src="@{/assets/lib/jplus/dropdown/js/dropdown.js}"></script>
 <script src="../../../../static/business/cms/book/contents/list.js" th:src="@{/business/cms/book/contents/list.js}"></script>
 </body>
 </html>