image.png
image.png
image.pngimage.png
image.png

源代码

  1. const EventEmitter = require('events')
  2. const readline = require('readline')
  3. var MuteStream = require('mute-stream') //静默流,字节进入,但它们不会出来
  4. var ms = new MuteStream()
  5. const { fromEvent } = require('rxjs') //它发出来自给定事件目标的特定类型的事件
  6. const option = {
  7. type: 'list',
  8. name: 'name',
  9. message: 'select your name:',
  10. choices: [
  11. {
  12. name: 'vue',
  13. value: 'vue'
  14. },
  15. {
  16. name: 'react',
  17. value: 'react'
  18. },
  19. {
  20. name: 'angular',
  21. value: 'angular'
  22. }
  23. ]
  24. }
  25. function Prompt(option) {
  26. return new Promise((resolve, reject) => {
  27. try {
  28. const list = new List(option)
  29. list.render()
  30. list.on('exit', answer => resolve(answer))
  31. } catch (error) {
  32. reject(error)
  33. }
  34. })
  35. }
  36. class List extends EventEmitter {
  37. constructor(option) {
  38. super()
  39. this.name = option.name
  40. this.message = option.message
  41. this.choices = option.choices
  42. this.input = process.stdin
  43. this.output = ms.pipe(process.stdout)
  44. this.rl = readline.createInterface({
  45. input: this.input,
  46. output: this.output
  47. })
  48. this.selected = 0 //选中第几个
  49. this.height = 0
  50. this.keyPress = fromEvent(this.rl.input, 'keypress').forEach(this.onKeyPress.apply(this))
  51. this.haveSelected = false //表示是否选择完毕
  52. }
  53. //处理按键
  54. onKeyPress(keyMap) {
  55. console.log('keyMap', keyMap)
  56. }
  57. //渲染清屏
  58. render() {
  59. ms.unmute(this.output) //取消静默
  60. this.clean()
  61. this.output.write(this.getContent())
  62. ms.mute(this.output) //继续静默
  63. }
  64. //清屏
  65. clean() {}
  66. //界面显示的值
  67. getContent() {
  68. if (!this.haveSelected) {
  69. let title = '\x1B[32m?\x1B[39m \x1B[1m' + this.message + '\x1B[22m\x1B[0m\x1B[0m\x1B[0m\x1B[2m(Use arrow keys)\x1B[22m\n'
  70. this.choices.forEach((item, index) => {
  71. if (index === this.selected) {
  72. if (index === this.choices.length) {
  73. title += '\x1B[36m> ' + item.name + '\x1B[39m '
  74. } else {
  75. title += '\x1B[36m> ' + item.name + '\x1B[39m \n'
  76. }
  77. } else {
  78. if (index === this.choices.length) {
  79. title += '\x1B[36m ' + item.name + '\x1B[39m '
  80. } else {
  81. title += '\x1B[36m ' + item.name + '\x1B[39m \n'
  82. }
  83. }
  84. })
  85. this.height = this.choices.length + 3 //height 作为之后清屏的行数
  86. return title
  87. }
  88. }
  89. Prompt(option).then(answer => {
  90. console.log('answer', answer)
  91. })

image.png

处理按键

  1. ...
  2. //处理按键
  3. onKeyPress(keyMap) {
  4. const key = keyMap[1]
  5. if (key.name === 'return') {
  6. this.haveSelected = true
  7. this.render()
  8. this.colse()
  9. this.emit('exit', this.choices[this.selected])
  10. } else if (key.name === 'down') {
  11. this.selected++
  12. if (this.selected > this.choices.length - 1) {
  13. this.selected = 0
  14. }
  15. this.render()
  16. } else if (key.name === 'up') {
  17. this.selected--
  18. if (this.selected < 0) {
  19. this.selected = this.choices.length - 1
  20. }
  21. this.render()
  22. }
  23. }
  24. ...

image.png

问题:追加内容,没有清屏

  1. const ansiEscapes = require('ansi-escapes')
  2. ...
  3. //清屏
  4. clean() {
  5. const emptyLines = ansiEscapes.eraseLines(this.height) //擦除整行
  6. this.output.write(emptyLines)
  7. }

image.png

最后处理return时

  1. getContent(){
  2. ...
  3. const name = this.choices[this.selected].name
  4. let title = '\x1B[32m?\x1B[39m \x1B[1m' + this.message + '\x1B[22m\x1B[0m\x1B[0m\x1B[0m\x1B[2m' + name + '\x1B[22m\n'
  5. return title
  6. }

colse关闭静默

  1. ....
  2. colse() {
  3. ms.unmute(this.output)
  4. this.rl.pause()
  5. this.rl.close()
  6. }

完整代码

  1. const EventEmitter = require('events')
  2. const readline = require('readline')
  3. const ansiEscapes = require('ansi-escapes')
  4. var MuteStream = require('mute-stream') //静默流,字节进入,但它们不会出来
  5. var ms = new MuteStream()
  6. const { fromEvent } = require('rxjs') //它发出来自给定事件目标的特定类型的事件
  7. const option = {
  8. type: 'list',
  9. name: 'name',
  10. message: 'select your name:',
  11. choices: [
  12. {
  13. name: 'vue',
  14. value: 'vue'
  15. },
  16. {
  17. name: 'react',
  18. value: 'react'
  19. },
  20. {
  21. name: 'angular',
  22. value: 'angular'
  23. }
  24. ]
  25. }
  26. function Prompt(option) {
  27. return new Promise((resolve, reject) => {
  28. try {
  29. const list = new List(option)
  30. list.render()
  31. list.on('exit', answer => resolve(answer))
  32. } catch (error) {
  33. reject(error)
  34. }
  35. })
  36. }
  37. class List extends EventEmitter {
  38. constructor(option) {
  39. super()
  40. this.name = option.name
  41. this.message = option.message
  42. this.choices = option.choices
  43. this.input = process.stdin
  44. this.output = ms.pipe(process.stdout)
  45. this.rl = readline.createInterface({
  46. input: this.input,
  47. output: this.output
  48. })
  49. this.selected = 0 //选中第几个
  50. this.height = 0
  51. //fromEvent 第二个参数 所关注的事件名称
  52. this.keyPress = fromEvent(this.rl.input, 'keypress').forEach(this.onKeyPress.bind(this))
  53. this.haveSelected = false //表示是否选择完毕
  54. }
  55. //处理按键
  56. onKeyPress(keyMap) {
  57. const key = keyMap[1]
  58. if (key.name === 'return') {
  59. this.haveSelected = true
  60. this.colse()
  61. this.emit('exit', this.choices[this.selected])
  62. this.render()
  63. } else if (key.name === 'down') {
  64. this.selected++
  65. if (this.selected > this.choices.length - 1) {
  66. this.selected = 0
  67. }
  68. this.render()
  69. } else if (key.name === 'up') {
  70. this.selected--
  71. if (this.selected < 0) {
  72. this.selected = this.choices.length - 1
  73. }
  74. this.render()
  75. }
  76. }
  77. //渲染清屏
  78. render() {
  79. ms.unmute(this.output) //取消静默
  80. this.clean()
  81. this.output.write(this.getContent())
  82. ms.mute(this.output) //继续静默
  83. }
  84. //清屏
  85. clean() {
  86. const emptyLines = ansiEscapes.eraseLines(this.height) //擦除整行
  87. this.output.write(emptyLines)
  88. }
  89. colse() {
  90. ms.unmute(this.output)
  91. this.rl.pause()
  92. this.rl.close()
  93. }
  94. //界面显示的值
  95. getContent() {
  96. if (!this.haveSelected) {
  97. let title = '\x1B[32m?\x1B[39m \x1B[1m' + this.message + '\x1B[22m\x1B[0m\x1B[0m\x1B[0m\x1B[2m(Use arrow keys)\x1B[22m\n'
  98. this.choices.forEach((item, index) => {
  99. if (index === this.selected) {
  100. //选择 则前面加>
  101. if (index === this.choices.length) {
  102. //最后一个不换行
  103. title += '\x1B[36m> ' + item.name + '\x1B[39m '
  104. } else {
  105. title += '\x1B[36m> ' + item.name + '\x1B[39m \n'
  106. }
  107. } else {
  108. if (index === this.choices.length) {
  109. title += '\x1B[36m ' + item.name + '\x1B[39m '
  110. } else {
  111. title += '\x1B[36m ' + item.name + '\x1B[39m \n'
  112. }
  113. }
  114. })
  115. this.height = this.choices.length + 3
  116. return title
  117. } else {
  118. const name = this.choices[this.selected].name
  119. let title = '\x1B[32m?\x1B[39m \x1B[1m' + this.message + '\x1B[22m\x1B[0m\x1B[0m\x1B[0m\x1B[2m' + name + '\x1B[22m\n'
  120. return title
  121. }
  122. }
  123. }
  124. Prompt(option).then(answer => {
  125. console.log('answer', answer)
  126. })

总结:感觉学会了一种很高级的思想,以前不敢用class ,现在更加勇了。对代码开发也更有逻辑了