最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java Swing實(shí)現(xiàn)自定義可搜索下拉框(附完整代碼)

 更新時(shí)間:2026年06月02日 09:00:01   作者:RainCity  
Swing 原生的?JComboBox?雖然支持下拉選擇,但有明顯的缺點(diǎn),本文將通過(guò)Java Swing實(shí)現(xiàn)自定義可搜索下拉框CusComboBox,有需要的小伙伴可以了解下

一、背景

Swing 原生的 JComboBox 雖然支持下拉選擇,但有兩個(gè)明顯的痛點(diǎn):

  1. 輸入框只能從下拉列表中選擇,不支持輸入篩選
  2. 當(dāng)數(shù)據(jù)量較大時(shí),用戶(hù)需要滾動(dòng)查找,體驗(yàn)較差

CusComboBox 的作用就是:在原生 JComboBox 基礎(chǔ)上增加可搜索功能。用戶(hù)輸入關(guān)鍵字時(shí)自動(dòng)過(guò)濾下拉選項(xiàng),支持鍵盤(pán)導(dǎo)航(上下鍵選擇、回車(chē)確認(rèn)、ESC取消),并提供無(wú)匹配結(jié)果時(shí)的回調(diào)處理。

二、核心設(shè)計(jì)

CusComboBox 繼承 JComboBox,通過(guò) setEditable(true) 啟用可編輯模式,然后:

  • 自定義編輯器(BasicComboBoxEditor),用 JTextField 作為輸入框
  • 監(jiān)聽(tīng)輸入框文本變化,動(dòng)態(tài)過(guò)濾下拉列表
  • 支持鍵盤(pán)導(dǎo)航(上下鍵、回車(chē)、ESC)
  • 支持中文輸入法(處理輸入法組合狀態(tài))
  • 延遲搜索(200ms 防抖),避免頻繁過(guò)濾

三、類(lèi)源碼

import cn.hutool.core.collection.CollectionUtil;

import javax.swing.*;
import javax.swing.event.PopupMenuEvent;
import javax.swing.event.PopupMenuListener;
import javax.swing.plaf.basic.BasicComboBoxEditor;
import java.awt.*;
import java.awt.event.*;
import java.text.AttributedCharacterIterator;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * 自定義下拉選擇器(可搜索)
 * 支持輸入關(guān)鍵字過(guò)濾下拉選項(xiàng)、鍵盤(pán)導(dǎo)航、無(wú)匹配回調(diào)
 *
 * 使用示例:
 * 1. 基本類(lèi)型數(shù)據(jù):
 *    List<String> list = Arrays.asList("蘋(píng)果", "香蕉", "橙子");
 *    CusComboBox<String> comboBox = new CusComboBox<>(list);
 *
 * 2. 對(duì)象數(shù)據(jù),指定顯示字段:
 *    CusComboBox<User> comboBox = new CusComboBox<>(userList, User::getName);
 *
 * 3. 設(shè)置無(wú)匹配回調(diào):
 *    comboBox.setOnNoMatch(text -> System.out.println("無(wú)匹配:" + text));
 */
public class CusComboBox<T> extends JComboBox<T> {
    /** 顯示的字段 */
    private Function<T, String> field;
    /** null項(xiàng)的顯示文本 */
    private String nullText;
    /** 是否保留null項(xiàng) */
    private boolean keepNull = false;
    /** 數(shù)據(jù)集 */
    private Collection<? extends T> dataList;
    /** 是否正在輸入(中文輸入法組合狀態(tài)) */
    private static boolean isComposing = false;
    /** 是否正在搜索,用于抑制輸入篩選前的選中事件 */
    private boolean suppressActionEvents = false;
    /** 鍵盤(pán)導(dǎo)航標(biāo)志 */
    private boolean keyboardNavigating = false;
    /** 待確認(rèn)的選擇項(xiàng) */
    private T pendingSelection = null;
    /** 輸入無(wú)匹配項(xiàng)時(shí)的回調(diào) */
    private Consumer<String> onNoMatchCallback;

    public CusComboBox() {
        super();
    }

    /**
     * 基本數(shù)據(jù)類(lèi)型包裝類(lèi)和String的便利構(gòu)造方法
     * @param items 數(shù)據(jù)源
     */
    @SuppressWarnings("unchecked")
    public CusComboBox(Collection<?> items) {
        this.field = Object::toString;
        this.keepNull = false;
        this.dataList = (Collection<T>) items;
        initRenderer();
        addAllItems(this.dataList);
    }

    /**
     * 基本數(shù)據(jù)類(lèi)型包裝類(lèi)和String的便利構(gòu)造方法(包含null項(xiàng))
     * @param items 數(shù)據(jù)源
     * @param nullText null項(xiàng)的顯示文本
     */
    @SuppressWarnings("unchecked")
    public CusComboBox(Collection<?> items, String nullText) {
        this.field = Object::toString;
        this.nullText = nullText;
        this.keepNull = true;
        this.dataList = (Collection<T>) items;
        initRenderer();
        addAllItems(this.dataList);
    }

    /**
     * 泛型構(gòu)造方法
     * @param items 數(shù)據(jù)源
     * @param field 顯示字段提取函數(shù)
     */
    public CusComboBox(Collection<? extends T> items, Function<T, String> field) {
        super();
        this.field = field;
        this.keepNull = false;
        this.dataList = items;
        initRenderer();
        addAllItems(items);
    }

    /**
     * 泛型構(gòu)造方法(包含null項(xiàng))
     * @param items 數(shù)據(jù)源
     * @param field 顯示字段提取函數(shù)
     * @param nullText null項(xiàng)的顯示文本
     */
    public CusComboBox(Collection<? extends T> items, Function<T, String> field, String nullText) {
        super();
        this.field = field;
        this.nullText = nullText;
        this.keepNull = true;
        this.dataList = items;
        initRenderer();
        addAllItems(items);
    }

    /**
     * 初始化渲染器
     */
    @SuppressWarnings("unchecked")
    private void initRenderer() {
        setRenderer(new DefaultListCellRenderer() {
            @Override
            public Component getListCellRendererComponent(JList<?> list, Object value, int index,
                                                          boolean isSelected, boolean cellHasFocus) {
                super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
                setText(value == null ? nullText : field.apply((T) value));
                return this;
            }
        });
    }

    /**
     * 設(shè)置顯示字段
     * @param field 顯示字段提取函數(shù)
     */
    public void setDisplayField(Function<T, String> field) {
        this.field = field;
        this.updateUI();
    }

    /**
     * 添加所有數(shù)據(jù)項(xiàng)
     * @param items 數(shù)據(jù)源
     */
    private void addAllItems(Collection<? extends T> items) {
        if (keepNull) {
            addItem(null);
        }
        items.forEach(this::addItem);
    }

    /**
     * 移除所有項(xiàng)(保護(hù)null項(xiàng))
     */
    @Override
    public void removeAllItems() {
        if (keepNull) {
            super.removeAllItems();
            addItem(null);
        } else {
            super.removeAllItems();
        }
    }

    /**
     * 移除指定索引的項(xiàng)(保護(hù)null項(xiàng))
     * @param index 索引
     */
    @Override
    public void removeItemAt(int index) {
        if (keepNull && index == 0) {
            return;
        }
        super.removeItemAt(index);
    }

    /**
     * 設(shè)置是否可搜索
     * @param editable true=可搜索,false=不可搜索
     */
    @Override
    public void setEditable(boolean editable) {
        super.setEditable(editable);
        if (editable) {
            initEditor();
            handleTextChange();
        }
    }

    /**
     * 初始化可搜索時(shí)的編輯器
     */
    private void initEditor() {
        JTextField searchField = new JTextField();
        JTextField originalField = (JTextField) getEditor().getEditorComponent();
        searchField.setBorder(originalField.getBorder());
        searchField.setBackground(originalField.getBackground());
        searchField.setForeground(originalField.getForeground());
        searchField.setFont(originalField.getFont());
        searchField.setPreferredSize(originalField.getPreferredSize());

        setEditor(new BasicComboBoxEditor() {
            @Override
            public void setItem(Object item) {
                if (item == null) {
                    searchField.setText("");
                } else {
                    @SuppressWarnings("unchecked")
                    T typedItem = (T) item;
                    String text = field.apply(typedItem);
                    searchField.setText(text);
                    searchField.setCaretPosition(text.length());
                }
            }

            @Override
            public Object getItem() {
                return getSelectedItem();
            }

            @Override
            public Component getEditorComponent() {
                return searchField;
            }
        });

        // 下拉面板寬度控制
        addPopupMenuListener(new PopupMenuListener() {
            @Override
            public void popupMenuWillBecomeVisible(PopupMenuEvent e) {
                JPopupMenu popup = (JPopupMenu) getUI().getAccessibleChild(CusComboBox.this, 0);
                if (null != popup) {
                    int dataHeight = 60;
                    if (CollectionUtil.isNotEmpty(dataList)) {
                        int height = Math.min(dataList.size() * 25, 380);
                        dataHeight = Math.max(dataHeight, height);
                    }
                    popup.setPreferredSize(new Dimension(getWidth(), dataHeight));
                }
            }

            @Override
            public void popupMenuWillBecomeInvisible(PopupMenuEvent e) {}

            @Override
            public void popupMenuCanceled(PopupMenuEvent e) {}
        });

        setupKeyboardNavigation(searchField);
    }

    /**
     * 設(shè)置鍵盤(pán)導(dǎo)航
     * @param searchField 搜索輸入框
     */
    private void setupKeyboardNavigation(JTextField searchField) {
        searchField.addKeyListener(new KeyAdapter() {
            @Override
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ENTER) {
                    // 回車(chē)確認(rèn)選擇
                    if (keyboardNavigating && pendingSelection != null) {
                        suppressActionEvents = false;
                        setSelectedItem(pendingSelection);
                        searchField.setText(field.apply(pendingSelection));
                        keyboardNavigating = false;
                        pendingSelection = null;
                        setPopupVisible(false);
                        e.consume();
                    }
                } else if (e.getKeyCode() == KeyEvent.VK_UP || e.getKeyCode() == KeyEvent.VK_DOWN) {
                    // 上下鍵開(kāi)始鍵盤(pán)導(dǎo)航
                    if (!keyboardNavigating) {
                        keyboardNavigating = true;
                        pendingSelection = getItemAt(0);
                        suppressActionEvents = true;
                    }
                } else if (e.getKeyCode() == KeyEvent.VK_ESCAPE) {
                    // ESC取消導(dǎo)航
                    keyboardNavigating = false;
                    pendingSelection = null;
                    suppressActionEvents = false;
                    setPopupVisible(false);
                    e.consume();
                }
            }
        });

        addItemListener(e -> {
            if (keyboardNavigating && e.getStateChange() == ItemEvent.SELECTED) {
                @SuppressWarnings("unchecked")
                T selected = (T) e.getItem();
                pendingSelection = selected;
            }
        });
    }

    /**
     * 重寫(xiě)fireActionEvent,在搜索時(shí)抑制事件
     */
    @Override
    protected void fireActionEvent() {
        if (!suppressActionEvents) {
            super.fireActionEvent();
        }
    }

    /**
     * 處理輸入框文本變化(搜索過(guò)濾)
     */
    private void handleTextChange() {
        JTextField textField = (JTextField) getEditor().getEditorComponent();

        // 處理中文輸入法組合狀態(tài)
        textField.addInputMethodListener(new InputMethodListener() {
            @Override
            public void inputMethodTextChanged(InputMethodEvent event) {
                AttributedCharacterIterator text = event.getText();
                isComposing = text != null && text.getEndIndex() - text.getBeginIndex() > 0;
            }

            @Override
            public void caretPositionChanged(InputMethodEvent event) {}
        });

        textField.addKeyListener(new KeyAdapter() {
            private Timer searchTimer;

            @Override
            public void keyPressed(KeyEvent e) {
                if (e.getKeyCode() == KeyEvent.VK_ENTER || e.getKeyCode() == KeyEvent.VK_SPACE) {
                    isComposing = false;
                }
            }

            @Override
            public void keyReleased(KeyEvent e) {
                if (isComposing) return;

                // 導(dǎo)航鍵不觸發(fā)搜索
                if (e.getKeyCode() == KeyEvent.VK_UP ||
                    e.getKeyCode() == KeyEvent.VK_DOWN ||
                    e.getKeyCode() == KeyEvent.VK_ENTER ||
                    e.getKeyCode() == KeyEvent.VK_ESCAPE) {
                    return;
                }

                if (null != searchTimer) {
                    searchTimer.stop();
                }

                searchTimer = new Timer(200, evt -> {
                    suppressActionEvents = true;
                    keyboardNavigating = false;
                    pendingSelection = null;

                    String text = textField.getText();
                    Object previouslySelectedItem = getSelectedItem();
                    AtomicBoolean hasMatches = new AtomicBoolean(false);

                    removeAllItems();
                    dataList.forEach(item -> {
                        String fieldValue = field.apply(item);
                        if (fieldValue.toLowerCase().contains(text.toLowerCase())) {
                            addItem(item);
                            hasMatches.set(true);
                        }
                    });

                    if (!hasMatches.get() && !text.isEmpty()) {
                        CallbackProcessor.accept(onNoMatchCallback, text);
                    }

                    setPopupVisible(hasMatches.get() && !text.isEmpty());
                    setSelectedItem(previouslySelectedItem);
                    textField.setText(text);
                    suppressActionEvents = false;
                });
                searchTimer.setRepeats(false);
                searchTimer.start();
            }
        });
    }

    /**
     * 獲取輸入框的文本
     * @return 輸入框文本
     */
    public String getEditorText() {
        if (isEditable() && null != getEditor()) {
            Component editorComponent = getEditor().getEditorComponent();
            if (editorComponent instanceof JTextField) {
                return ((JTextField) editorComponent).getText();
            }
        }
        return null;
    }

    /**
     * 設(shè)置無(wú)匹配結(jié)果的回調(diào)
     * @param callback 回調(diào)函數(shù),參數(shù)為輸入的關(guān)鍵字
     */
    public void setOnNoMatch(Consumer<String> callback) {
        this.onNoMatchCallback = callback;
    }

    /**
     * 靜態(tài)工廠方法
     */
    public static <T> CusComboBox<T> create(Collection<?> items) {
        return new CusComboBox<>(items);
    }

    public static <T> CusComboBox<T> create(Collection<?> items, String nullText) {
        return new CusComboBox<>(items, nullText);
    }

    public static <T> CusComboBox<T> create(Collection<? extends T> items, Function<T, String> field) {
        return new CusComboBox<>(items, field);
    }

    public static <T> CusComboBox<T> create(Collection<? extends T> items, Function<T, String> field, String nullText) {
        return new CusComboBox<>(items, field, nullText);
    }
}

四、核心功能說(shuō)明

可搜索過(guò)濾:

  • 輸入關(guān)鍵字時(shí)自動(dòng)過(guò)濾下拉選項(xiàng)(大小寫(xiě)不敏感)
  • 延遲200ms搜索,避免頻繁過(guò)濾
  • 支持中文輸入法(InputMethodListener 處理組合狀態(tài))

鍵盤(pán)導(dǎo)航:

  •  / :上下移動(dòng)選擇,開(kāi)始鍵盤(pán)導(dǎo)航模式
  • Enter:確認(rèn)當(dāng)前高亮項(xiàng)
  • ESC:取消導(dǎo)航,關(guān)閉下拉面板

數(shù)據(jù)源適配:

  • 支持 Collection<String> 等基本類(lèi)型
  • 支持任意泛型對(duì)象,通過(guò) Function<T, String> 提取顯示字段
  • 支持保留 null 項(xiàng)(帶占位文本)

其他特性:

  • 無(wú)匹配結(jié)果時(shí)的回調(diào)處理
  • 下拉面板寬度自適應(yīng)
  • 抑制搜索過(guò)程中的 ActionEvent 事件

五、使用示例

5.1 基本類(lèi)型數(shù)據(jù)

List<String> fruits = Arrays.asList("蘋(píng)果", "香蕉", "橙子", "葡萄", "西瓜");
CusComboBox<String> comboBox = new CusComboBox<>(fruits);
comboBox.setEditable(true);  // 開(kāi)啟可搜索
panel.add(comboBox);

5.2 對(duì)象數(shù)據(jù),指定顯示字段

List<User> userList = getUserList();
CusComboBox<User> comboBox = new CusComboBox<>(userList, User::getName);
comboBox.setEditable(true);
panel.add(comboBox);

5.3 包含 null 項(xiàng)

CusComboBox<String> comboBox = new CusComboBox<>(list, "請(qǐng)選擇");
comboBox.setEditable(true);

5.4 設(shè)置無(wú)匹配回調(diào)

comboBox.setOnNoMatch(keyword -> {
    System.out.println("未找到匹配項(xiàng):" + keyword);
    // 可彈窗提示或動(dòng)態(tài)添加選項(xiàng)
});

5.5 獲取選中值和輸入文本

// 獲取選中對(duì)象
User selected = comboBox.getSelectedItem();
// 獲取輸入框文本
String text = comboBox.getEditorText();

5.6 靜態(tài)工廠方法

CusComboBox<String> comboBox = CusComboBox.create(list, "請(qǐng)選擇");
comboBox.setEditable(true);

六、注意事項(xiàng)

  1. 必須調(diào)用 setEditable(true) :只有設(shè)置為可編輯模式,搜索功能才會(huì)生效
  2. 中文輸入法:代碼已通過(guò) InputMethodListener 處理,輸入中文時(shí)不會(huì)觸發(fā)搜索
  3. 數(shù)據(jù)源引用:過(guò)濾時(shí)會(huì)重新構(gòu)建下拉列表,原始 dataList 會(huì)被保留作為數(shù)據(jù)源
  4. null項(xiàng)保護(hù):當(dāng) keepNull = true 時(shí),removeAllItems() 和 removeItemAt(0) 會(huì)被保護(hù)
  5. ActionEvent 抑制:搜索過(guò)程中會(huì)臨時(shí)抑制 ActionEvent,避免重復(fù)觸發(fā)

七、小結(jié)

CusComboBox 在原生 JComboBox 基礎(chǔ)上增加了可搜索功能,核心實(shí)現(xiàn)要點(diǎn):

  • setEditable(true) 啟用可編輯模式
  • 自定義 BasicComboBoxEditor 替代默認(rèn)編輯器
  • 監(jiān)聽(tīng)輸入框文本變化,動(dòng)態(tài)過(guò)濾下拉選項(xiàng)
  • 延遲搜索 + 中文輸入法處理
  • 鍵盤(pán)導(dǎo)航(上下鍵、回車(chē)、ESC)

以上就是Java Swing實(shí)現(xiàn)自定義可搜索下拉框(附完整代碼)的詳細(xì)內(nèi)容,更多關(guān)于Java Swing自定義下拉框的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java實(shí)現(xiàn)Treap樹(shù)的示例代碼

    Java實(shí)現(xiàn)Treap樹(shù)的示例代碼

    本文主要介紹了Java實(shí)現(xiàn)Treap樹(shù)的示例代碼,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-06-06
  • Java鎖機(jī)制完整學(xué)習(xí)筆記(附詳細(xì)代碼)

    Java鎖機(jī)制完整學(xué)習(xí)筆記(附詳細(xì)代碼)

    Java中的鎖機(jī)制是并發(fā)編程的核心工具之一,通過(guò)合理使用鎖、、讀寫(xiě)鎖和自旋鎖,開(kāi)發(fā)者可以確保多線程環(huán)境下的數(shù)據(jù)一致性和程序正確性,這篇文章主要介紹了Java鎖機(jī)制完整學(xué)習(xí)的相關(guān)資料,需要的朋友可以參考下
    2025-12-12
  • Java跨域問(wèn)題分析與解決方法詳解

    Java跨域問(wèn)題分析與解決方法詳解

    這篇文章主要介紹了Java跨域問(wèn)題分析與解決方法,跨域問(wèn)題是在Web應(yīng)用程序中,由于同源策略的限制,導(dǎo)致瀏覽器無(wú)法發(fā)送跨域請(qǐng)求,也無(wú)法獲取跨域響應(yīng)的問(wèn)題,感興趣想要詳細(xì)了解可以參考下文
    2023-05-05
  • SPRING IOC注入方式過(guò)程解析

    SPRING IOC注入方式過(guò)程解析

    這篇文章主要介紹了SPRING IOC注入方式過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-01-01
  • Java如何基于wsimport調(diào)用wcf接口

    Java如何基于wsimport調(diào)用wcf接口

    這篇文章主要介紹了Java如何基于wsimport調(diào)用wcf接口,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-06-06
  • 詳解Java實(shí)現(xiàn)緩存(LRU,FIFO)

    詳解Java實(shí)現(xiàn)緩存(LRU,FIFO)

    本篇文章主要介紹了詳解Java實(shí)現(xiàn)緩存(LRU,FIFO) ,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-04-04
  • mybatis中mapper代理的生成過(guò)程全面分析

    mybatis中mapper代理的生成過(guò)程全面分析

    這篇文章主要為大家介紹了mybatis中mapper代理的生成過(guò)程全面分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • MyBatis3源碼解析之如何獲取數(shù)據(jù)源詳解

    MyBatis3源碼解析之如何獲取數(shù)據(jù)源詳解

    用myBatis3與spring整合的時(shí)候,我們可以通過(guò)多種方式獲取數(shù)據(jù)源,下面這篇文章主要給大家介紹了關(guān)于MyBatis3源碼解析之如何獲取數(shù)據(jù)源的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2022-06-06
  • 帶有@Transactional和@Async的循環(huán)依賴(lài)問(wèn)題的解決

    帶有@Transactional和@Async的循環(huán)依賴(lài)問(wèn)題的解決

    這篇文章主要介紹了帶有@Transactional和@Async的循環(huán)依賴(lài)問(wèn)題的解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-04-04
  • Netty4之如何實(shí)現(xiàn)HTTP請(qǐng)求、響應(yīng)

    Netty4之如何實(shí)現(xiàn)HTTP請(qǐng)求、響應(yīng)

    這篇文章主要介紹了Netty4之如何實(shí)現(xiàn)HTTP請(qǐng)求、響應(yīng)問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-04-04

最新評(píng)論

股票| 开平市| 临高县| 炉霍县| 沛县| 德格县| 天台县| 平邑县| 塔城市| 仪征市| 渝中区| 特克斯县| 湛江市| 翼城县| 肇州县| 新乡市| 无极县| 任丘市| 阿拉善左旗| 阿城市| 七台河市| 宁阳县| 宁津县| 南京市| 定南县| 太康县| 烟台市| 全南县| 平乡县| 昌吉市| 永寿县| 西充县| 武夷山市| 石城县| 承德县| 布尔津县| 伽师县| 眉山市| 佳木斯市| 盘山县| 叙永县|