vue js class 绑定重复元素

vue js class binding on repetitious element

我想做一个简单的游戏来帮助孩子们阅读,实际上它是一种语言学习方法,名为'silent mode',反正这是jsfiddle link

https://jsfiddle.net/raminSafari/b7zhc98q/19/

例如,如果我想让学生阅读 'pen',我先指向 p(1),然后指向 e(2),最后指向 n(3),代码可以很好地处理具有唯一字母的单词,但是当这个词类似于 'dad' 时,它没有按照我想要的方式工作,我希望它显示 d(1)(3), a(2)

这是完整的简化代码(我知道它不可靠)

<template>
    <div class = "container pt-5 mt-5">
            <h1 class="text-center pb-5"><span style="color: red;"> {{ answer }} </span> just to clarify</h1> <!-- just to clarify -->
    
   

        
        <div class="text-center">
        <template id="keyboard" v-for="alphabet in alphabets" >
            <template v-if = "alphabet == word.first ">
                    <span :class="{ 'active': firstActive, alphabet}"> {{ alphabet }} </span>&nbsp;<strong style="color: red; font-size: 10px;">{{ num1 }}</strong>&nbsp;
            </template>
             <template v-else-if = "alphabet == word.second ">
                   <span :class="{ 'active': secondActive, alphabet}"> {{ alphabet }} </span>&nbsp;<strong style="color: red; font-size: 10px;">{{ num2 }}</strong>&nbsp;
            </template>
             <template v-else-if = "alphabet == word.third ">
                   <span :class="{ 'active': thirdActive, alphabet}"> {{ alphabet }} </span>&nbsp;<strong style="color: red; font-size: 10px;">{{ num3 }}</strong>&nbsp;
            </template>
           
             <template v-else-if = "alphabet == word.forth ">
                   <span :class="{ 'active': forthActive, alphabet}"> {{ alphabet }} </span>&nbsp;<strong style="color: red; font-size: 10px;">{{ num4 }}</strong>&nbsp;
            </template>
             <template v-else-if = "alphabet == word.fifth ">
                   <span :class="{ 'active': forthActive, alphabet}"> {{ alphabet }} </span>&nbsp;<strong style="color: red; font-size: 10px;">{{ num5 }}</strong>&nbsp;
            </template>
            <template v-else>
                <span class="alphabet"> {{ alphabet }} </span>&nbsp;
            </template>
        </template>
       

                 <div><button class = "btn btn-info mt-3" @click = "again">again</button></div>
        </div>
           
            
     </div>
</template>

<script>
export default {
     data(){
        return{
         
           alphabets: ["p", "e", "m", "n", "d", "a", "s"],
           firstActive: false,
           secondActive: false,
           thirdActive: false,
           forthActive: false,
           fifthActive: false,
           index: 0,
           words:[
                {
                    first: 'p',
                    second: 'e',
                    third: 'n',
                    forth: '',
                    fifth: '',
                    answer : 'pen'
                },
                {
                    first: 'm',
                    second: 'a',
                    third: 'd',
                    forth: 'e',
                    fifth: '',
                    answer : 'made'
                },
                {
                    first: 'd',
                    second: 'a',
                    third: 'd',
                    forth: '',
                    fifth: '',
                    answer : 'dad'
                },

           ],
           word: [],
            answer: '', 
            myVar1: null,
            myVar2: null,
            myVar3: null,
            myVar4: null,
            myVar5: null,
            num1: '',
            num2: '',
            num3: '',
            num4: '',
            num5: ''
                     
        }
    },

    methods: {

        shuffle(a) {
                for (let i = a.length - 1; i > 0; i--) {
                    const j = Math.floor(Math.random() * (i + 1));
                    [a[i], a[j]] = [a[j], a[i]];
                }
        
                return a;
            },

            getWord(){
                this.word = this.words[this.index];
                this.answer = this.word.answer;
            },

            again(){
                clearTimeout(this.myVar1);
                clearTimeout(this.myVar2);
                clearTimeout(this.myVar3);
                clearTimeout(this.myVar4);
                clearTimeout(this.myVar5);
                this.firstActive = false;
                this.secondActive = false;
                this.thirdActive = false;
                this.forthActive = false;
                this.fifthActive = false;
                this.num1 = '';
            this.num2 = '';
            this.num3 = '';
            this.num4 = '';
            this.num5 = '';
                if(this.index == this.words.length){
                    this.index = 0;
                }else{
                    this.index++;
                }
                this.getWord();
                this.showBorder();
            },


            showBorder(){
                     this.myVar1 =  setTimeout(() => {
                         this.firstActive = true;
                         this.num1 = 1;
                    }, 2000);

                     this.myVar2 =  setTimeout(() => {
                         this.secondActive = true;
                         this.num2 = 2;
                    }, 4000);

                    this.myVar3 =  setTimeout(() => {
                         this.thirdActive = true;
                         this.num3 = 3;
                    }, 6000);

                    this.myVar4 =  setTimeout(() => {
                         this.forthActive = true;
                         this.num4 = 4;
                    }, 8000);

                    this.myVar5 =  setTimeout(() => {
                         this.fifthActive = true;
                         this.num5 = 5;
                    }, 10000);
            }
       


    },
    

   
     created(){
            
         this.words = this.shuffle(this.words);
         this.getWord();
         this.showBorder();
                                
      }

      



}
</script>

<style>
    span.alphabet{
         display: inline-block;
         width: 70px;
         height: 70px;
        
        font-size: 30px;
        font-weight: 600;
    }
    .active{
        border: 2px solid red;
        border-radius: 50%;
    }
</style>

谢谢

答案不只是 copy-and-paste 代码,而且有点长。不想看的可以直接跳到结论。

我阅读了 jsfiddle,这里有一些解决您面临的问题的建议:

1。将数字视为字符串,而不是数字

之后你就会知道d(1)(3)问题不是显示2个数字,而是how to append the string " 1" 和 "3" 并显示它。

2。 Divide-and-conquer

将您的整个任务分为 3 个部分:

  1. 创建函数来控制应突出显示哪个字母并显示相应的序列。
  2. 创建一个组件,仅用于显示字母表和序列,例如d(1)(3).
  3. 定义触发渲染过程和重置应用程序状态的函数。

App.vue

这是 App.vue

中的数据
data: function() {
    return {
        word: "dad",
        alphabets: ["p", "e", "m", "n", "d", "a", "s"],
        hits: ["", "", "", "", "", "", ""],
        handlers: [],
        delay: 2000,
    }
},

this.hits 将存储命中的相应序列。例如,对于 dad 的情况,点击将 最终 变为:["", "", "", "", "13", "2", ""].

这些是你应该在App.vue

中拥有的功能
  • 循环每个字符。在this.word,并做相应的渲染
renderAnswer: function() {
    var self = this;
    for (var i = 0; i < this.word.length; i++) {
        self.doSetTimeout(i, self);
    }
}
  • 控制何时进行渲染
doSetTimeout: function (i, self) {
    self.handlers.push(setTimeout(function () { 
        self.updateHits(i, self) 
    }, self.delay * i));
}
  • 更新hits数组,这将在每个字母上触发相应的渲染
updateHits: function(i, self) {
    let char = self.word[i];
    let index = self.alphabets.indexOf(char);
    console.debug(char, index)
    self.hits[index] += (i + 1);
    self.hits = self.hits.filter(x => true); // force refresh list for binding
}

将你的大功能分成一些小功能,让每个小功能只做 1 件事。

注意:在这里,您将看到 renderAnswer() 调用 doSetTimeout()doSetTimeout() 调用 updateHits()。为了正确获取数据和函数,我们需要定义var self = this,并在每个函数调用中传递self

Sub-component 字母表

在App.vue的模板中,需要定义一个<alphabet>sub-component。该组件仅负责呈现特定字母表以及与该字母表相关的序列。

<alphabet 
    v-for="(char, index) in alphabets" 
    v-bind:key="char" 
    :alphabet="char" 
    v-bind:hits="hits[index]"
/>

您需要将字母表和相应的命中值传递给 sub-component 才能正确呈现组件。这个sub-component的代码应该是straight-forward所以这里就跳过了。在执行此部分时,您可能需要知道如何从 official documentaion 传递和使用 props

运行 应用

当你设置所有的函数和 sub-component 时,你应该能够在触发 renderAnswer().

时呈现 dad 的情况

重置状态

您可能需要为每个新词重置应用状态。因此,你还应该定义一个reset()函数,将this.word更新为一个新词,并将this.hits中的每一项重置为空字符串。

如果您需要上述步骤的更多详细信息,请阅读 this gist

结论

当您遇到一些编码问题时,您可以尝试re-phasing您遇到的问题。 提出正确的问题 将引导您使用更好的方法解决问题。

另一方面,把问题分解成更小的问题,一个一个解决。在这种情况下,尝试将大功能拆分为一些更小、更简单的功能。并且,尝试创建一个 sub-component 来完成渲染部分,同时将所有逻辑留给它的 parent.

希望你能解决你遇到的问题,帮助更多的孩子:)

你可以将你输入的字母存储在一个数组中,这样你就可以获得输入的字母的索引并显示它们。我建议像这样构造您的代码。这是重构代码https://jsfiddle.net/tk41d0wo/

的fiddle
<div id="app">
  <div>
    Current word is <span style="color: red;">{{ words[currentIndex] }}</span>
  </div>
  <div>
     <button 
       v-for="letter in letters" 
       :key="letter" 
       @click="inputLetter(letter)"
       :style="{ color: (inputs.includes(letter)) ? 'red': 'initial' }"
     >
       {{ letter }} <small>({{ getAllLetterPositions(letter).join(', ') }})</small>
     </button>
  </div>
  <button @click="resetInputs">
    Reset
  </button>
</div>

并在您的组件中...

new Vue({
  el: "#app",
  data: {
    currentIndex: 0,
    words: ['made', 'sad', 'dad'],
    letters: ['p', 'e', 'm', 'n', 'd', 'a', 's'],
    inputs: [],
  },
  methods: {
    inputLetter(letter) {
      this.inputs.push(letter);
        if(this.inputs.join('') == this.words[this.currentIndex]) {
        this.currentIndex++;
        this.resetInputs();
      }
    },
    getAllLetterPositions(letter) {
        return this.inputs.reduce((positions, current, index) => {
        if(current == letter) {
            return positions.concat(index + 1);
        }
        return positions;
      }, []).sort(function(a, b){return a-b})
    },
    resetInputs() {
        this.inputs = [];
    }
  }
})