如何在aiogram v3中实现带勾选标记的Telegram机器人多选内联按钮
修复aiogram v3多选内联按钮勾选与保存功能问题
核心问题分析
你的代码存在三个关键错误,导致勾选标记失效、保存按钮不显示:
- 全局
user_choice变量被所有用户共享,无法区分不同用户的选择 select_choco函数中,产品按钮的callback_data错误绑定为列表而非单个产品save_choice_callback中错误覆盖全局变量,且删除消息后非法调用query.message.answer
修复步骤与完整代码
以下是修正后的代码,解决了所有问题并实现需求:
from aiogram import Bot, Dispatcher, types, Router, F from aiogram.filters import Command from aiogram.types import Message, InlineKeyboardButton, InlineKeyboardMarkup import asyncio BOT_TOKEN = 'my-private-token' bot = Bot(token=BOT_TOKEN) dp = Dispatcher() router = Router() # 用字典存储每个用户的选择,key为用户ID,value为选择的产品集合 user_selections = {} @router.message(Command('shop')) async def menu_cmd(m: Message): choice = InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text='🍎Фрукти', callback_data='fruits'), InlineKeyboardButton(text='🍫Шоколад', callback_data='chocolate')], [InlineKeyboardButton(text='Скасувати ❌', callback_data='cancel')], ]) await m.answer('Привіт в бота. Вибери: 👇', reply_markup=choice) @router.callback_query(F.data == 'cancel') async def cancel_callback(query: types.CallbackQuery): await query.message.delete() await query.answer('') async def show_fruits(user_id: int): product_ft = ['🍎Яблуко', '🍌Банан'] buttons = [] # 获取当前用户的选择,不存在则初始化空集合 user_choice = user_selections.get(user_id, set()) for product in product_ft: button_text = f'{product}✅' if product in user_choice else product button = types.InlineKeyboardButton(text=button_text, callback_data=f'fruit_{product}') buttons.append([button]) if user_choice: buttons.append([types.InlineKeyboardButton(text='✔️Зберегти вибір', callback_data='save_choice')]) buttons.append([types.InlineKeyboardButton(text='↩️Назад', callback_data='back')]) choice_fruits = types.InlineKeyboardMarkup(inline_keyboard=buttons) return choice_fruits @router.callback_query(F.data == 'fruits') async def select_fruits(query: types.CallbackQuery): fruits_keyboard = await show_fruits(query.from_user.id) await query.message.edit_text('Вибери фрукт: ', reply_markup=fruits_keyboard) await query.answer() @router.callback_query(F.data.startswith('fruit_')) async def fruits_select(query: types.CallbackQuery): user_id = query.from_user.id # 提取产品名称,去掉前缀 product_fruits = query.data.replace('fruit_', '') # 初始化用户选择集合 if user_id not in user_selections: user_selections[user_id] = set() if product_fruits in user_selections[user_id]: user_selections[user_id].remove(product_fruits) else: user_selections[user_id].add(product_fruits) fruits_keyboard = await show_fruits(user_id) await query.message.edit_text('Вибери фрукт: ', reply_markup=fruits_keyboard) await query.answer() @router.callback_query(F.data == 'save_choice') async def save_choice_callback(query: types.CallbackQuery): user_id = query.from_user.id # 获取当前用户的选择 user_choice = user_selections.get(user_id, set()) # 先发送保存成功消息,再删除原消息 await query.message.answer(f'Вибір збережено. Переглянути його ви можете завдяки команді - /my_choice') await query.message.delete() print(f'Вибір користувача {user_id}: {user_choice} збережено.') @router.callback_query(F.data == 'back') async def back_callback(query: types.CallbackQuery): choice = InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text='🍎Фрукти', callback_data='fruits'), InlineKeyboardButton(text='🍫Шоколад', callback_data='chocolate')], [InlineKeyboardButton(text='Скасувати ❌', callback_data='cancel')], ]) await query.message.edit_text('Привіт в бота. Вибери: 👇', reply_markup=choice) async def select_choco(user_id: int): product_choco = ['🍫Мілка', '🍬Снікерс'] buttons = [] user_choice = user_selections.get(user_id, set()) for product in product_choco: button_text = f'{product}✅' if product in user_choice else product # 修复callback_data错误,使用单个产品而非列表 button = types.InlineKeyboardButton(text=button_text, callback_data=f'choco_{product}') buttons.append([button]) if user_choice: buttons.append([types.InlineKeyboardButton(text='✔️Зберегти вибір', callback_data='save_choice')]) buttons.append([types.InlineKeyboardButton(text='↩️Назад', callback_data='back')]) choice_choco = types.InlineKeyboardMarkup(inline_keyboard=buttons) return choice_choco @router.callback_query(F.data == 'chocolate') async def choco_review(query: types.CallbackQuery): choco_keyboard = await select_choco(query.from_user.id) await query.message.edit_text('Вибери шоколадку: ', reply_markup=choco_keyboard) await query.answer() @router.callback_query(F.data.startswith('choco_')) async def choco_select(query: types.CallbackQuery): user_id = query.from_user.id product_choco = query.data.replace('choco_', '') if user_id not in user_selections: user_selections[user_id] = set() if product_choco in user_selections[user_id]: user_selections[user_id].remove(product_choco) else: user_selections[user_id].add(product_choco) choco_keyboard = await select_choco(user_id) await query.message.edit_text('Виберіть шоколадку: ', reply_markup=choco_keyboard) await query.answer() def go_to_start(): kb_start = InlineKeyboardMarkup( inline_keyboard= [ [InlineKeyboardButton(text='На головне меню', callback_data='go_start')] ]) return kb_start @router.callback_query(F.data == 'go_start') async def go_start_callback(query: types.CallbackQuery): choice = InlineKeyboardMarkup(inline_keyboard=[ [InlineKeyboardButton(text='🍎Фрукти', callback_data='fruits'), InlineKeyboardButton(text='🍫Шоколад', callback_data='chocolate')], [InlineKeyboardButton(text='Скасувати ❌', callback_data='cancel')], ]) await query.message.edit_text('Привіт в бота. Вибери: 👇', reply_markup=choice) @router.message(Command('my_choice')) async def mychoice_cmd(m: types.Message): user_id = m.from_user.id user_choice = user_selections.get(user_id, set()) show_kb_start = go_to_start() if user_choice: await m.reply(f'🧺Ось список товарів у вашому кошику: {', '.join(user_choice)}', reply_markup=show_kb_start) else: await m.reply('❌Ваш кошик ще пустий. Добавити товари можете завдяки команді /shop або просто натисніть на кнопку "Перейти на головне меню"') async def main(): dp.include_router(router) print("Bot is running...") await dp.start_polling(bot) if __name__ == '__main__': asyncio.run(main())
关键改进说明
- 用户选择隔离:用
user_selections字典存储每个用户的选择,避免不同用户之间的选择冲突 - 修复按钮回调数据:为水果和巧克力产品的回调数据添加前缀(
fruit_/choco_),避免产品名称与其他回调数据冲突,同时修复了巧克力按钮的回调数据错误 - 修正保存逻辑:先发送保存成功消息再删除原消息,避免消息已删除后的非法调用,同时正确获取当前用户的选择
- 动态按钮文本:根据当前用户的选择集合动态生成带✅标记的按钮文本,确保勾选状态正确更新
内容的提问来源于stack exchange,提问作者bredlik25
相关产品推荐
相关产品推荐

