docs: def num_choice(choices, default='1', valid_keys='', depth=1, icons='', sn_info=None,
                 indent=4, fg_color='green', separator='',
                 with_img=6, img_list=None, img_cache_dir='/tmp', use_cache=False,
                 extra_hints='', clear_previous=False, quit_app=True,
                 ):
      """
          传入数组, 生成待选择列表, 如果启用图片支持, 需要额外传入与数组排序一致的图片列表,
          - 图片在 iterms 中显示速度较慢, 不推荐使用
  
          .. note: 图片在 iterms 中显示速度较慢, 如果数组长度大于10, 不推荐使用
  
          .. code:: python
  
              sn_info = {
                  'align': '-', # 左右对齐
                  'length': 2, # 显示长度
              }
  
      :param use_cache:
      :type use_cache:
      :param default:
      :type default:
      :param indent: ``左侧空白``
      :type indent:
      :param fg_color: ``前景色``
      :type fg_color:
      :param choices: 备选选项
      :type choices: list
      :param depth: ``如果是嵌套数组, 显示当前层级``
      :type depth: int
      :param icons: ``默认展示的icons:  '❶❷❸❹❺❻❼❽❾❿'``
      :type icons: any
      :param sn_info: ``需要展示的序号的信息长度对齐方式, 默认2个字符/右对齐``
      :type sn_info: dict
      :param valid_keys: ``可以输入的有效 key, 使用 ',' 分隔``
      :type valid_keys: str
      :param separator: 分隔符 header/footer, 默认无, 如果不为空, 则显示
      :type separator:
      :param img_cache_dir:  ``图片缓存目录``
      :type img_cache_dir: str
      :param with_img: ``是否使用图片, 如果值大于0, 则以实际值大小来作为终端显示行数``
      :type with_img: int
      :param img_list: ``图片原始 url ``
      :type img_list: list
      :param extra_hints: ``n-next,p-prev,s-skip``
      :type extra_hints: any
      :param clear_previous: ``clear previous output``
      :type clear_previous:
      :return:
      :rtype:
      """
      icons = ICONS if not icons else icons
      if not choices:
          return None
  
      # warn: 这里需要使用 None, 不能 not default 来判断!!!, 会可能传入 0
      if default is not None:
          default = '{}'.format(default)
  
      sn_info = sn_info or {}
      _header, _footer = gen_separator(separator=separator)
  
      with textui.indent(indent, quote=' {}'.format(icons[depth - 1])):
          if _header:
              textui.puts(getattr(textui.colored, fg_color)(_header))
  
          for i, choice in enumerate(choices, start=1):
              if with_img > 0 and img_list:
                  cat_net_img(img_list[i - 1],
                              indent=indent,
                              img_height=with_img,
                              img_cache_dir=img_cache_dir,
                              use_cache=use_cache)
  
              _align = '{}{}'.format(sn_info.get('align', ''), sn_info.get('length', 2))
              # _hint = '%{}s. %s'.format(_align) % (i, choice)
              _hint_num = '%{}s.'.format(_align) % i
              _hint = '[{}]'.format(_hint_num)
              _hint = textui.colored.magenta(_hint)
              _hint += getattr(textui.colored, fg_color)(' %s' % choice)
              textui.puts(_hint)
  
          if _footer:
              textui.puts(getattr(textui.colored, fg_color)(_footer))
  
      _valid = [str(x + 1) for x in range(0, len(choices))]
      default_prompt = 'Your Choice'
      valid_choices = ['q-quit', 'b-back']
      if extra_hints:
          if isinstance(extra_hints, str):
              extra_hints = extra_hints.split(',')
          valid_choices += extra_hints
  
      default_prompt = '{}({})?'.format(default_prompt, '/'.join(valid_choices))
      c = click.prompt(
          # click.style('[Depth: ({})]Your Choice(q-quit/b-back)?', fg='cyan').format(depth),
          click.style(default_prompt, fg='cyan'),
          type=str,
          default=default
      )
  
      if str(c) in 'qQ':
          if quit_app:
              os._exit(0)
          else:
              if clear_previous:
                  click.clear()
              return str(c)
      if valid_keys == 'all':
          return c
      elif str(c) in 'bB':
          if clear_previous:
              click.clear()
          return str(c)
      elif valid_keys and str(c) in valid_keys.split(','):
          return str(c)
      elif c not in _valid:
          textui.puts(textui.colored.red('  😭 ✘ Invalid input[{}]'.format(c)))
          return num_choice(
              choices, default, valid_keys, depth, icons, sn_info, indent, fg_color, separator, with_img, img_list,
              img_cache_dir, use_cache, extra_hints, clear_previous, quit_app,
          )
      else:
          return int(c) - 1