index.vue 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. <template>
  2. <div
  3. class="hot-key-input-component"
  4. :class="{ cursor: focus, 'hot-key-input-shark': isShark }"
  5. :style="$props.style"
  6. tabindex="0"
  7. :placeholder="list.length ? '' : placeholder"
  8. @focus="handleFocus"
  9. @blur="focus = false"
  10. @keydown.prevent="handleKeydown"
  11. >
  12. <div v-for="(item, index) in list" :key="index" class="hot-item">
  13. <span class="hot-text">{{ formatItemText(item.text) }} </span>
  14. <i class="icon-close" @click="handleDeleteKey(index)"></i>
  15. </div>
  16. </div>
  17. </template>
  18. <script>
  19. const CODE_NUMBER = Array.from({ length: 10 }, (v, k) => `Digit${k + 1}`)
  20. const CODE_NUMPAD = Array.from({ length: 10 }, (v, k) => `Numpad${k + 1}`)
  21. const CODE_ABC = Array.from(
  22. { length: 26 },
  23. (v, k) => `Key${String.fromCharCode(k + 65).toUpperCase()}`
  24. )
  25. const CODE_FN = Array.from({ length: 12 }, (v, k) => `F${k + 1}`)
  26. const CODE_CONTROL = [
  27. "Shift",
  28. "ShiftLeft",
  29. "ShiftRight",
  30. "Control",
  31. "ControlLeft",
  32. "ControlRight",
  33. "Alt",
  34. "AltLeft",
  35. "AltRight",
  36. ] // ShiftKey Control(Ctrl) Alt
  37. export default {
  38. name: "HotKeyInput",
  39. props: {
  40. type: {
  41. type: String,
  42. // lowser upper
  43. default: ()=> 'defalut'
  44. },
  45. // 默认绑定值
  46. // 传入 ['Ctrl+d'] 格式时会自动处理成 [{ text: 'Ctrl+d', controlKey: { altKey: false, ctrlKey: true, shiftKey: false, key: 'd', code: 'KeyD } }]
  47. hotkey: {
  48. type: [Array , Object],
  49. required: true,
  50. },
  51. // 校验函数 判断是否允许显示快捷键
  52. verify: {
  53. type: Function,
  54. default: () => true,
  55. },
  56. // 无绑定时提示文字
  57. placeholder: {
  58. type: String,
  59. default: "",
  60. },
  61. // 限制最大数量
  62. max: {
  63. type: [String, Number],
  64. default: 0,
  65. },
  66. // 当max时,再次输入快捷键 - true: 清空后输入,false:无操作
  67. reset: {
  68. type: Boolean,
  69. default: false,
  70. },
  71. shake: {
  72. type: Boolean,
  73. default: true,
  74. },
  75. // 快捷键使用范围
  76. range: {
  77. type: Array,
  78. default: () => ["NUMBER", "NUMPAD", "ABC", "FN"],
  79. },
  80. },
  81. data() {
  82. return {
  83. isShark: false,
  84. focus: false,
  85. hotkeyBackups: this.hotkey || '' ,
  86. list: [],
  87. keyRange: [],
  88. }
  89. },
  90. watch: {
  91. list: function (list) {
  92. list.length ? (this.focus = false) : (this.focus = true)
  93. // .sync修饰符
  94. this.$emit(
  95. "update:hotkey",
  96. this.list.map((item) => {
  97. return this.formatItemText(item.text)
  98. // if(item.text && this.type != 'default'){
  99. // return this.type == 'lowser' ? item.text.toLowerCase():item.text.toUpperCase()
  100. // }
  101. // return item.text
  102. })
  103. )
  104. },
  105. hotkeyBackups: {
  106. handler: function (val) {
  107. if (!val.length) return;
  108. const list = [];
  109. val.forEach((item) => {
  110. const arr = item.split("+");
  111. const controlKey = {
  112. altKey: arr.includes("Alt"),
  113. ctrlKey: arr.includes("Control"),
  114. shiftKey: arr.includes("Shift"),
  115. key: arr[arr.length - 1],
  116. code: `Key${arr[arr.length - 1].toUpperCase()}`,
  117. };
  118. list.push({
  119. text: arr.reduce((text, item, i) => {
  120. if (i) text += "+";
  121. if (controlKey.key === item) text += item.toUpperCase();
  122. else text += item;
  123. return text;
  124. }, ""),
  125. controlKey,
  126. });
  127. });
  128. this.list = list;
  129. },
  130. immediate: true,
  131. },
  132. range: {
  133. handler: function (val) {
  134. if(val === null){
  135. this.keyRange = null
  136. return
  137. }
  138. const keyRangeList = {
  139. NUMBER: CODE_NUMBER,
  140. NUMPAD: CODE_NUMPAD,
  141. ABC: CODE_ABC,
  142. FN: CODE_FN,
  143. }
  144. val.forEach((item) => {
  145. this.keyRange = this.keyRange.concat(
  146. keyRangeList[item.toUpperCase()]
  147. )
  148. })
  149. },
  150. immediate: true,
  151. },
  152. },
  153. methods: {
  154. formatItemText(text){
  155. if(text && this.type != 'default'){
  156. return this.type == 'lowser' ? text.toLowerCase() : text.toUpperCase()
  157. }
  158. return text
  159. },
  160. handleFocus() {
  161. if (!this.list.length) this.focus = true
  162. },
  163. handleDeleteKey(index) {
  164. this.list.splice(index, 1)
  165. },
  166. handleKeydown(e) {
  167. console.log('e: ',e)
  168. e.preventDefault()
  169. e.stopPropagation()
  170. const { altKey, ctrlKey, shiftKey, key, code } = e
  171. if (!CODE_CONTROL.includes(key)) {
  172. if (this.keyRange !== null && !this.keyRange.includes(code)){
  173. this.shakeAction()
  174. return
  175. }
  176. let controlKey = ''
  177. let temps = [
  178. { key: altKey, text: "Alt" },
  179. { key: ctrlKey, text: "Ctrl" },
  180. { key: shiftKey, text: "Shift" },
  181. ]
  182. temps.forEach((curKey) => {
  183. if (curKey.key) {
  184. if (controlKey) controlKey += "+"
  185. controlKey += curKey.text
  186. }
  187. })
  188. if (key) {
  189. if (controlKey) controlKey += "+"
  190. controlKey += key.toUpperCase()
  191. }
  192. this.addHotkey({
  193. text: controlKey,
  194. controlKey: { altKey, ctrlKey, shiftKey, key, code },
  195. })
  196. }
  197. },
  198. addHotkey(data) {
  199. if (this.list.length) {
  200. if (this.list.length.toString() >= this.max.toString()) {
  201. if (this.reset) {
  202. this.list = []
  203. } else {
  204. return
  205. }
  206. } else if (this.list.some((item) => data.text === item.text)) {
  207. this.shakeAction()
  208. return
  209. }
  210. }
  211. if (!this.verify(data)) {
  212. this.shakeAction()
  213. return
  214. }
  215. this.list.push(data)
  216. },
  217. shakeAction(){
  218. if(this.shake){
  219. this.isShark = true
  220. setTimeout(()=>{
  221. this.isShark = false
  222. }, 800)
  223. }
  224. }
  225. },
  226. }
  227. </script>
  228. <style lang="less">
  229. @keyframes Blink {
  230. 0% {
  231. opacity: 0;
  232. }
  233. 100% {
  234. opacity: 1;
  235. }
  236. }
  237. @keyframes hot-key-input-shake {
  238. 0% {
  239. transform: scale(1);
  240. }
  241. 10%,
  242. 20% {
  243. transform: scale(0.9) rotate(-1deg);
  244. }
  245. 30%,
  246. 50%,
  247. 70%,
  248. 90% {
  249. transform: scale(1.1) rotate(1deg);
  250. }
  251. 40%,
  252. 60%,
  253. 80% {
  254. transform: scale(1.1) rotate(-1deg);
  255. }
  256. 100% {
  257. transform: scale(1) rotate(0);
  258. }
  259. }
  260. .hot-key-input-shark {
  261. animation: hot-key-input-shake 0.8s 1 ease-in;
  262. }
  263. .hot-key-input-component {
  264. display: flex;
  265. padding: 5px;
  266. border: 1px solid #dcdcdc;
  267. background-color: #fff;
  268. color: #333;
  269. cursor: text;
  270. transition: border-color 0.2s cubic-bezier(0.645, 0.045, 0.355, 1);
  271. }
  272. .hot-key-input-component:before {
  273. content: attr(placeholder);
  274. color: #afafaf;
  275. }
  276. .hot-key-input-component.cursor::after {
  277. content: "|";
  278. animation: Blink 1.2s ease 0s infinite;
  279. position: absolute;
  280. left: 10px;
  281. }
  282. .hot-item {
  283. display: flex;
  284. align-items: center;
  285. background-color: #f4f4f5;
  286. border-color: #e9e9eb;
  287. color: #909399;
  288. padding: 0 5px;
  289. margin-right: 5px;
  290. }
  291. .hot-key-input-component .hot-item .icon-close {
  292. display: block;
  293. content: "";
  294. background: url("data:image/svg+xml,%3Csvg class='icon' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg' width='200' height='200'%3E%3Cpath d='M512 64C264.58 64 64 264.58 64 512s200.58 448 448 448 448-200.58 448-448S759.42 64 512 64zm0 832c-212.08 0-384-171.92-384-384s171.92-384 384-384 384 171.92 384 384-171.92 384-384 384z' fill='%23909399'/%3E%3Cpath d='M625.14 353.61L512 466.75 398.86 353.61a32 32 0 0 0-45.25 45.25L466.75 512 353.61 625.14a32 32 0 0 0 45.25 45.25L512 557.25l113.14 113.14a32 32 0 0 0 45.25-45.25L557.25 512l113.14-113.14a32 32 0 0 0-45.25-45.25z' fill='%23909399'/%3E%3C/svg%3E")
  295. no-repeat center;
  296. background-size: contain;
  297. width: 14px;
  298. height: 14px;
  299. transform: scale(0.9);
  300. opacity: 0.6;
  301. }
  302. .hot-key-input-component .hot-item .icon-close:hover {
  303. cursor: pointer;
  304. opacity: 1;
  305. }
  306. </style>