vue案例

小满三岁啦發表於2024-05-08

任務清單(單檔案)

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>模板</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/axios/1.5.0/axios.js"></script>
    <style>
        table {
            border: 1px solid black;
            border-collapse: collapse;
        }

        tr,
        td {
            border: 1px solid black;
            text-align: center;
            padding: 10px;
        }
    </style>
</head>

<body>
    <div id="app">
        <h3>小滿記事本</h3>
        <p><input type="text" v-model="text" @keyup.enter="addTask"> <button @click="addTask">新增任務</button> <button
                @click="clear" v-show="taskList.length > 0">清空任務</button></p>
        <table v-show="taskList.length > 0">
            <tr>
                <td>任務id</td>
                <td>任務內容</td>
                <td colspan="2">操作</td>
            </tr>
            <tr v-for="(task, index) in taskList" :key="task.id">
                <td>{{index+1}}</td>
                <td>{{task.name}}</td>
                <td><button @click="changeTask(task)">修改任務</button></td>
                <td><button @click="del(task.id)">刪除任務</button></td>
            </tr>
        </table>
        <p v-show="taskList.length > 0">任務數量: {{taskList.length}} </p>
        <br>
        <!-- 修改任務的時候會用到 -->
        <div v-show="isShow">
            <label>請輸入新的任務內容<input type="text" v-model="newText"></label> <button @click="relChange">確認修改</button>
        </div>
    </div>
</body>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            taskList: [
                { "id": 1, "name": "欺負小滿" },
                { "id": 2, "name": "逃課" }
            ],
            text: "",
            newText: "",
            isShow: 0,
            // 需要被修改的物件
            changeOBJ: null,
        },
        methods: {
            // 新增任務
            addTask() {
                if (this.text !== "") {
                    this.taskList.push(
                        { id: this.taskList.length + 1, name: this.text }
                    )
                    this.text = ""
                } else {
                    alert("請輸入任務名稱!")
                }
            },

            // 刪除任務 根據id去刪除
            del(id) {
                this.taskList = this.taskList.filter(item => item.id !== id)
            },

            // 首次修改
            changeTask(obj) {
                this.isShow = 1
                this.changeOBJ = obj
            },

            // 最終修改
            relChange() {
                this.changeOBJ.name = this.newText
                alert("任務修改成功!")
                this.isShow = 0
            },

            // 清空任務
            clear() {
                this.taskList = []
            }
        },
    })
</script>

</html>

效果演示

img

任務清單(元件版)

  1. 父子通訊,遵循單檔案流原則
  2. 自己的資料自己負責,即雖然資料是從父傳給子的,但是修改還是要子傳給父,然後父操作修改或者刪除
  3. 別忘記加scoped,保證樣式互不影響
  4. 持久化儲存 watch ,created裡面,深度監視 deep

持久化儲存更簡單的寫法

list: JSON.parse(localStorage.getItem("data")) || [預設陣列]

NoteDetail.vue

<template>
  <div class="note-detail">
    <!-- 頭部 -->
    <ToDoHeader @addTask="addTask"></ToDoHeader>
    <br>
    <!-- 身體 -->
    <ToDoMain :list="list" @delTask="delTask"></ToDoMain>
    <br>
    <!-- 底部 -->
    <ToDoFooter :list="list" @clearTask="clearTask" v-show="list.length > 0"></ToDoFooter>
  </div>
</template>

<script>
import ToDoHeader from "@/components/ToDoHeader.vue";
import ToDoMain from "@/components/ToDoMain.vue";
import ToDoFooter from "@/components/ToDoFooter.vue";
export default {
  components: {
    ToDoHeader,
    ToDoMain,
    ToDoFooter,
  },
  data() {
    return {
      baseLsit: [
        { id: 1, name: "按時吃飯" },
        { id: 2, name: "按時摸魚" },
        { id: 3, name: "按時學習" },
      ],
        // 傳遞給子元件的陣列 這裡置空了因為資料從本地讀取
      list: [],
      // 用來控制當資料為空的適合 底部元件是否顯示
      isShow: true
    };
  },
  created() {
    // 如果能從本地讀取到資料,預設的資料就從本地獲取
    // 如果讀取不到 預設資料就從初始化的baseList獲取
    if (localStorage.getItem('data')){
      this.list = JSON.parse(localStorage.getItem('data'))
    }else{
      this.list = this.baseLsit
    }
  },
  methods: {
    // 新增單個任務
    addTask(value){
        this.list.unshift({
            id: this.list.length + 1,
            name: value
        })
    },
    // 刪除單個任務
    delTask(id){
        this.list = this.list.filter(item => item.id !== id)
    },
    // 清空全部任務
    clearTask(value){
        this.list = value
    }
  },
  watch: {
    list: {
      deep: true,
      handler(newValue){
        // 持久化到本地
        localStorage.setItem("data", JSON.stringify(newValue))
      }
    }
  }
};
</script>

<style scoped>
.note-detail {
  margin: 50px;
  width: 500px;
  /* height: 300px; */
  background-color: tomato;
  padding: 50px;
}
</style>

ToDoHeader.vue

<!-- ToDoHeader.vue -->
<template>
  <div class="todo-header">
    <p>
        <label><input type="text" placeholder="請輸入任務名稱" v-model="task" @keyup.13="addTask"></label>
        &emsp;
        <button @click="addTask" class="btn btn-dark">新增任務</button>
    </p>
  </div>
</template>

<script>
export default {
    data() {
        return {
            task: ""
        }
    },
    methods: {
        addTask(){
            if (this.task.trim() === ''){
                alert('不能為空')
                return
            }
            // 新增讓父元件新增
            this.$emit("addTask", this.task)
            // 清空預設的輸入
            this.task = ''
        }
    },
}
</script>

<style scoped>
    .todo-header p {
        margin: auto;
        width: 80%;
    }
</style>

ToDoMain.vue

<!-- ToDoMain.vue -->
<template>
  <div class="todo-main">
    <ul v-for="(item, index) in list" :key="item.id">
      <li>
        <span>{{ index + 1 }} {{ item.name }}</span>
        <span @click="del(item.id)">x</span>
      </li>
      <hr>
    </ul>
  </div>
</template>

<script>
export default {
  props: {
    list: {
      type: Array,
      required: true,
    },
  },
  methods: {
    del(id){
        this.$emit("delTask", id)
    }
  },
};
</script>

<style scoped>
    .todo-main ul {
    list-style: none;
    }
    li {
        /* text-indent: 2em; */
        margin: 0 10px 0 10px;
        display: flex;
        justify-content: space-between
    }
</style>

ToDoFooter.vue

<!-- ToDoFooter.vue -->
<template>
  <div class="todo-footer">
    <div class="inner">
      <span>合計:{{ totalCount }}</span>
      <span><button @click="clearTask" class="btn btn-danger">清空任務</button></span>
    </div>
  </div>
</template>

<script>
export default {
  props: {
    list: {
      type: Array,
      required: true,
      default: () => [],
    },
  },
  computed: {
    totalCount() {
      return this.list.length;
    },
  },
  methods: {
    clearTask(){
        this.$emit("clearTask", [])
    }
  },
};
</script>

<style scoped>
    div.inner {
        display: flex;
        justify-content: space-between;
    }
    span {
        font-weight: 700;
        color: #fff;
    }
</style>

效果演示

img

成績計算

案例用到的

  1. 渲染功能,用到了v-for,渲染的時候使用的是index,這樣保證序號的準確性,別忘記🔑

  2. 分數判斷,如果大於等於60 應用紅色樣式樣式,否則不使用樣式,使用了 :class

  3. 有兩個tbody,互斥狀態,只能顯示一個,條件判斷 v-if v-else

  4. 刪除資料,點選事件,因為這裡使用的a標籤,又使用了prevent,阻止預設事件,然後methods內部使用了filter邏輯更方便計算。

  5. 計算總分和平均分,使用了計算屬性computed

    1. 計算總分,使用到了陣列.reduce
    2. 計算平均分,使用.toFixed()保留兩位小數
    3. 渲染平均分,使用了三目運算子
  6. 新增資料,在最後一個輸入框(分數)

    1. v-model.number 修飾符
    2. @keyup.13按鍵修飾符
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>模板</title>
        <script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
        <script src="https://cdn.bootcdn.net/ajax/libs/axios/1.5.0/axios.js"></script>
        <style>
            #app {
                margin: 20px;
            }
            table {
                border: 1px solid black;
                border-collapse: collapse;
            }
    
            tr,
            td {
                border: 1px solid black;
                padding: 10px;
                text-align: center;
            }
            td {
                width: 200px;
            }
            .red-color {
                color: red
            }
            a {
                text-decoration: none;
                color: red;
            }
        </style>
    </head>
    
    <body>
        <div id="app">
            <p>科目:<input type="text" v-model.trim="subject" placeholder="請輸入科目" @focus="isShow=false"></p>
            <p>成績:<input type="text" v-model.number="score" placeholder="請輸入分數" @keyup.13="addScore" @focus="isShow=false"></p>
            <button @click="addScore">新增</button>  <span :class="{'red-color':true}" v-show="isShow">{{message}}</span>
    
            <br><br>
    
            <table>
                <!-- 頭 -->
                <thead>
                    <tr>
                        <th>編號</th>
                        <th>科目</th>
                        <th>成績</th>
                        <th>操作</th>
                    </tr>
                </thead>
                <!-- 內容 有資料顯示這個tbody-->
                <tbody v-if="scoreList.length > 0">
                    <tr v-for="(item, index) in scoreList" :key="item.id">
                        <td>{{index+1}}</td>
                        <td>{{item.subject}}</td>
                        <!-- 不及格的成績標紅 -->
                        <td :class="{'red-color': item.score < 60}">{{item.score}}</td>
                        <td><a @click.prevent="del(item.id)" href="#" target="_blank">刪除</a></td>
                    </tr>
                </tbody>
                <!-- 這裡不要使用v-show去控制 那樣需要寫兩遍 -->
                <!-- 沒有資料才顯示這個tbody -->
                <tbody v-else>
                    <tr>
                        <td colspan="4">暫無資料</td>
                    </tr>
                </tbody>
                <!-- 腳 -->
                <tfoot>
                    <tr>
                        <td colspan="4">總分:{{totalScore}}&emsp;&emsp;平均分:{{avgScore}}
                        </td>
                    </tr>
                </tfoot>
            </table>
        </div>
    </body>
    <script>
        const app = new Vue({
            el: "#app",
            data: {
                // 科目
                subject: null,
                // 分數
                score: null,
                // 全部成績陣列
                scoreList: [],
                // 添科目和分數時沒有資料觸發
                message: '',
                isShow: false
            },
            methods: {
                // 新增分數
                addScore() {
                    this.isShow = false
                    if(this.subject && this.score){
                        this.scoreList.unshift({
                        id: this.scoreList.length + 1,
                        subject: this.subject,
                        score: this.score
                    })
                    
                    // 新增後清空輸入框的內容
                    this.subject = ''
                    this.score = ''
    
                    }else{
                        this.isShow = true
                        this.message = "請填寫資料!"
                    }
                    
                },
    
                // 從陣列中刪除一個物件,即表格中的tr
                del(index) {
                    this.scoreList = this.scoreList.filter(item => item.id !== index)
                }
            },
            computed: {
                // 總成績
                totalScore() {
                    return this.scoreList.reduce((sum, item) => sum + item.score, 0)
                },
    
                // 平均分
                avgScore() {
                    let avg = this.totalScore / this.scoreList.length
                    return avg ? avg.toFixed(2) : 0
                }
            }
        })
    </script>
    
    </html>
    

    效果演示

    img

    監聽屬性案例(簡單寫法)

    1. 如果不需要渲染頁面,可以不需要寫到data中,直接透過this.xx操作即可
    2. setTimeout延遲,時間不要太長,使用者體驗不好
    3. 設定了setTimeout記得清掉
    <!DOCTYPE html>
    <html lang="en">
    
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>模板</title>
        <script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
        <script src="https://cdn.bootcdn.net/ajax/libs/axios/1.5.0/axios.js"></script>
        <style>
            #app {
                margin: 20px;
            }
        </style>
    </head>
    
    <body>
        <div id="app">
            <h3>簡單監聽屬性1</h3>
            <input type="text" v-model="words" placeholder="請輸入內容"> -- {{result}}
            <h3>簡單監聽屬性2</h3>
            <input type="text" v-model="obj.words" placeholder="請輸入內容"> -- {{objResult}}
        </div>
    </body>
    <script>
        const app = new Vue({
            el: "#app",
            data: {
                // 簡單監聽屬性
                words: "",
                // 裡面的監聽屬性
                obj: {
                    url: 'https://api.vvhan.com/api/ian/rand',
                    words: ''
                },
                url: 'https://applet-base-api-t.itheima.net/api/translate',
                // 渲染監聽結果 簡單
                result: '',
                // 物件監聽結果
                objResult: '',
                // 延時器
                timer: null
            },
            watch: {
                // 該方法會在資料變化時,觸發執行
                // 這裡的資料屬性名,要與data中定義的保持一致
                // 當上面的資料變化了,就會立即呼叫watch中的方法,就是本方法。
                // 引數分別是 變化後的新值 和 老值
                words(newValue, oldValue) {
                    clearTimeout(this.timer)
                    //一些業務邏輯 或 非同步操作。
                    this.timer = setTimeout(async () => {
                        axios({
                            url: this.url,
                            params: {
                                words: newValue
                            }
                        }).then(response => {
                            this.result = response.data.data
                        })
                            .catch(error => {
                                return error
                            })
                    }, 300) // 設定延遲時間 1秒太長了使用者體驗不好
    
                },
    
                // 如果是物件裡面的子屬性,就會觸發此方法執行,其他同上面一樣
                'obj.words'(newValue, oldValue) {
                    this.timer = setTimeout(async () => {
                        // 一些業務邏輯 或 非同步操作
                        axios({
                            url: this.obj.url,
                            params: {
                                words: newValue
                            }
                        }).then(response => {
                            this.objResult = response.data
                        })
                    }, 300) // 設定延遲時間 1秒太長了使用者體驗不好
                }
    
            }
        })
    </script>

效果演示

img

完整監聽屬性

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>模板</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/axios/1.5.0/axios.js"></script>
    <style>
        #app {
            margin: 20px;
        }
    </style>
</head>

<body>
    <div id="app">
        <h3>完整監聽屬性</h3>
        <select v-model="obj.lang">
            <option value="english">英語</option>
            <option value="italy">義大利</option>
            <option value="chinese">中文</option>
        </select>
        <input type="text" v-model="obj.words" placeholder="請輸入內容"> -- {{result}}
    </div>
</body>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            // 翻譯api地址
            url: 'https://applet-base-api-t.itheima.net/api/translate',
            // 渲染監聽結果 簡單
            result: '',
            // 物件監聽結果
            timer: null,
            obj: {
                words: '小滿',
                lang: 'italy'
            },
        },
        watch: {
            obj: {
                // watch 完整寫法
                deep: true, // 深度監視
                immediate: true, // 立刻執行, 一進入頁面handler立即執行
                handler(newValue) {
                    clearTimeout(this.timer)
                //一些業務邏輯 或 非同步操作。
                this.timer = setTimeout(async () => {
                    axios({
                        url: this.url,
                        params: newValue
                    }).then(response => {
                        this.result = response.data.data
                    })
                        .catch(error => {
                            return error
                        })
                }, 300) // 設定延遲時間 1秒太長了使用者體驗不好
                }
            }
        }
    })
</script>

</html>

效果演示

img

水果購物車案例

  1. 渲染資料,使用v-for
  2. 刪除功能,使用陣列.filter
  3. 修改個數,使用++和-- ,需要注意的是,如果數量小於等於1 需要把disable設定為true
  4. 全選和反選
    1. 這裡不需要設定兩個陣列,因為每一個物件裡面都包含一個isChacked的布林值,這裡只需要使用v-model繫結陣列.every的返回結果即可,全部滿足返回true,即v-model對應的多選框為true,反之同理
    2. 用到了計算屬性的完整寫法 get() 和 set()
  5. 統計選中的個數以及總數量,使用了reduce去更方便的統計,不過這裡有一個坑,需要統計選中的數量,邏輯需要重新理一理
  6. 持久化到本地
    1. 這裡使用了監聽屬性,監聽全部,也就是需要用完整的監聽屬性,使用了deep:true
    2. 這裡使用了localStorage,localStorage是window的物件,可以直接引入使用,Vue是一建立的時候,即created,在這裡寫邏輯,優先從本地去拿值。
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>模板</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/axios/1.5.0/axios.js"></script>
    <script src="https://kit.fontawesome.com/0f0be1c400.js" crossorigin="anonymous"></script>
    <!-- bootstrap5 -->
    <link rel="stylesheet" href="E:\vue\css\bootstrap.min.css">
    <style>
        #app {
            margin: 20px;
        }

        table {
            border: 1px solid black;
            border-collapse: collapse;
            width: 800px !important;
        }

        tr,
        td {
            height: 50px;
            width: 50px;
            line-height: 50px;
            padding: 10px;
            border: 1px solid black;
            text-align: center;
        }

        .active {
            background-color: #f0eeee;
        }

        /* 商品總價格 */
        .price-style {
            font-size: 20px;
            font-weight: 700;
            color: tomato;
        }
    </style>
</head>

<body>
    <div id="app">
        <img src="./img/banner.png">
        <p><i class="fa-solid fa-house" style="color:tomato"></i>&emsp;/&emsp;購物車</p>
        <table class="table" v-if="fruitList.length > 0">
            <!-- 頭 -->
            <thead>
                <tr>
                    <th>選中</th>
                    <th>圖片</th>
                    <th>單價</th>
                    <th>個數</th>
                    <th>小計</th>
                    <th>操作</th>
                </tr>
            </thead>
            <!-- 有內容顯示這個 -->
            <tbody>
                <tr v-for="(item, index) in fruitList" :key="item.id" :class="{active: item.isChacked}">
                    <td><input type="checkbox" v-model="item.isChacked"></td>
                    <td><img :src="item.icon" width="40px"></td>
                    <td>{{item.price}}</td>
                    <td>
                        <button @click="item.num--" :disabled="item.num <= 1" class="btn btn-outline-dark">-</button>
                        &nbsp;{{item.num}}&nbsp;
                        <button @click="item.num++" class="btn btn-outline-dark">+</button>
                    </td>
                    <!-- 小計 -->
                    <td>{{(item.price * item.num).toFixed(2)}}</td>
                    <td><button class="btn btn-danger" @click="del(item.id)">刪除</button></td>
                </tr>
            </tbody>

            <!-- 腳 -->
            <tfoot>
                <tr>
                    <td><input type="checkbox" v-model="isChackedAll">全選</td>
                    <td colspan="3"></td>
                    <td>總價:<span :class="{'price-style': true}">{{totalPrice.toFixed(2)}}</span> 元</td>
                    <td><button class="btn btn-success">結算{{totalCount}}</button></td>
                </tr>
            </tfoot>
        </table>

        <!-- 分割線 -->
        <table class="table table-striped" v-else>
            <!-- 沒有選中任何內容選這個 -->
            <tbody>
                <tr>
                    <td colspan="6"><i class="fa-solid fa-cart-shopping"></i>&emsp;/&emsp;空空如也</td>
                </tr>
            </tbody>
        </table>
    </div>
</body>
<script src="E:\vue\js\bootstrap.js"></script>
<script>
    const app = new Vue({
        el: "#app",
        data: {
            // 預設資料
            baseFruitList: [
                {
                    id: 1,
                    icon: "./img/fruit/檸檬.png",
                    isChacked: true,
                    num: 3,
                    price: 8
                },
                {
                    id: 2,
                    icon: "./img/fruit/香蕉.png",
                    isChacked: true,
                    num: 5,
                    price: 6
                },
                {
                    id: 3,
                    icon: "./img/fruit/橙子.png",
                    isChacked: false,
                    num: 2,
                    price: 7.7
                }
            ],
            // 全部水果陣列 從本地快取讀取 別忘記轉成 json
            fruitList: []
        },
        created(){
            // Vue例項建立之後就觸發
            if (localStorage.getItem('data')) {
                this.fruitList = JSON.parse(localStorage.getItem('data'))
            }else{
                this.fruitList = this.baseFruitList
            }
        },
        computed: {
            // 總價格
            totalPrice() {
                return this.fruitList.reduce((sum, item) => {
                    if (item.isChacked){
                        return sum + item.num * item.price
                    }else{
                        return sum
                    }
                }, 0)
            },
            // 全選 全不選
            // 要使用完整的計算屬性去實現
            isChackedAll: {
                get() {
                    // 必須所有的小選項卡都選中,全選按鈕才會被選中  -- > every
                    return this.fruitList.every(item => item.isChacked)
                },
                set(value) {
                    this.fruitList.forEach(item => item.isChacked = value)
                }
            },
            // 選中的總數量
            totalCount() {
                return this.fruitList.reduce((sum, item) =>{
                    if(item.isChacked){
                        return sum + item.num
                    }else{
                        return sum
                    }
                }, 0
                )
            }
        },
        methods: {
            // 刪除一個tr
            del(id) {
                this.fruitList = this.fruitList.filter(item => item.id !== id)
            }
        },
        watch: {
            fruitList: {
                deep: true,
                handler(newValue){
                    console.log(newValue);
                    // 需要將變化後的資料 也就newValue 儲存到本地 (不要忘記轉json)
                    localStorage.setItem('data', JSON.stringify(newValue))
                }
            }
        }
    })
</script>

</html>

效果演示

img

img

小滿記賬日記

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>模板</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/axios/1.5.0/axios.js"></script>
    <!-- icon圖示 -->
    <script src="https://kit.fontawesome.com/0f0be1c400.js" crossorigin="anonymous"></script>
    <!-- bootstrap5 -->
    <link rel="stylesheet" href="E:\vue\css\bootstrap.min.css">
    <!-- 彈窗 -->
    <script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script>
    <!-- echarts -->
    <script src="E:\vue\js\echarts.js"></script>
    <style>
        .self-style {
            margin-top: 50px;
        }

        .red {
            color: red;
        }

        .price-style {
            font-size: 20px;
            font-weight: 700;
            text-align: left;
        }

        table td,
        table th {
            width: 25%;
            text-align: center;

        }

        /* echarts */
        .echarts-box {
            width: 70%;
            margin: auto;
            height: 400px;
            border: 1px solid black;
            padding: 30px;
        }
    </style>
</head>

<body>
    <div id="app">
        <!-- 表格資料 -->
        <div class="container self-style">
            <p>
                <input type="text" v-model.trim="name" placeholder="消費名稱">
                <input type="text" v-model.number="price" placeholder="消費價格">
                <button class="btn btn-primary" @click="add">新增賬單</button>
            </p>
            <table class="table border-dart">
                <tr>
                    <th>編號</th>
                    <th>消費名稱</th>
                    <th>消費價格</th>
                    <th>操作</th>
                </tr>
                <tr v-for="(item, index) in dataList" :key="item.id">
                    <td>{{index + 1}}</td>
                    <td>{{item.name}}</td>
                    <td :class="{red: item.price >= 500}">{{item.price.toFixed(2)}}</td>
                    <td><button class="btn btn-danger" @click="del(item.id)">刪除</button></td>
                </tr>
                <tr>
                    <td class="price-style" colspan="4">消費總計:{{totalPrice}} 元</td>
                </tr>
            </table>
        </div>

        <!-- echarts圖表 -->
        <div class="echarts-box" id="main"></div>
    </div>
</body>
<script src="E:\vue\js\bootstrap.js"></script>
<script>
    /*
       功能需求:
       1. 基本渲染 
            (1)立刻傳送請求獲取資料created
             (2) 拿到資料,存到data的響應式資料中
             (3) 結合資料,進行渲染,v-for
             (4) 消費統計 => 計算屬性
            
        2. 新增功能
             (1) 收集表單資料 v-model
             (2) 給新增按鈕註冊點選事件,傳送新增請求
             (3) 需要重新渲染
        3. 刪除功能
            (1) 註冊點選事件,傳參傳入 id
             (2) 根據 id 傳送刪除請求
             (3) 需要重新渲染
        4. 餅圖渲染
             (1) 初始化一個餅圖 echarts.init(dom) mounted鉤子實現
             (2) 根據資料實時更新餅圖 echarts.setOption({...}) 原本已經設定過的,就不需要再次設定了
    */
    const app = new Vue({
        el: "#app",
        data: {
            // 拿到的全部資料
            dataList: [],
            // 消費名稱
            name: '',
            // 消費價格
            price: '',
        },
        created() {
            this.getList()
        },
        computed: {
            // 獲取總價格
            totalPrice() {
                return this.dataList.reduce((sum, item) => sum + item.price, 0)
            }
        },
        mounted() {
            this.myEchart = echarts.init(document.querySelector('#main'))
            let option = {
                // 大標題
                title: {
                    text: '小滿的記賬日記',
                    // 子標題
                    // subtext: 'Fake Data',
                    left: 'center'
                },
                // 提示框
                tooltip: {
                    trigger: 'item'
                },
                // 圖例
                legend: {
                    orient: 'vertical',
                    left: 'left'
                },
                series: [
                    {
                        name: '消費賬單', // 滑鼠懸停顯示的
                        type: 'pie',
                        radius: '70%', // 圓的半徑
                        // radius: ["50%", "80%"],  // 圓的半徑 和內半徑大小
                        // data: [
                        //     { value: 1048, name: 'Search Engine' },
                        // ],
                        emphasis: {
                            itemStyle: {
                                shadowBlur: 10,
                                shadowOffsetX: 0,
                                shadowColor: 'rgba(0, 0, 0, 0.5)'
                            }
                        }
                    }
                ]
            };
            this.myEchart.setOption(option);
        },
        methods: {
            // 渲染資料
            async getList() {
                // await 一定要寫在 async 中 
                const res = await axios('https://applet-base-api-t.itheima.net/bill', {
                    params: {
                        creator: '小滿'
                    }
                })
                this.dataList = res.data.data

                let option = {
                    series: [
                        {
                            // { value: 1, name: 2 }
                            data: this.dataList.map(item => ({ value: item.price, name: item.name }))

                        }
                    ]
                }
                this.myEchart.setOption(option)
            },
            // 新增資料
            async add() {
                if (!this.name) {
                    swal({
                        title: '錯誤!',
                        text: "請輸入正確的消費名稱",
                        icon: "error"
                    })
                    return
                }
                if (typeof this.price !== 'number') {
                    swal({
                        title: "錯誤!",
                        text: '請輸入正確的價格',
                        icon: "error"
                    })
                    return
                }
                const res = await axios.post('https://applet-base-api-t.itheima.net/bill', {
                    creator: '小滿',
                    name: this.name,
                    price: this.price
                })
                swal({
                    text: '更新資料成功',
                    icon: 'success'
                })
                this.getList()

                // 資料新增成功之後,別忘記清空之前的
                this.name = ''
                this.price = ''
            },
            // 刪除記錄
            async del(id) {
                const res = await axios.delete(`https://applet-base-api-t.itheima.net/bill/${id}`)
                swal({
                    text: '刪除成功',
                    icon: "success"
                })
                // 別忘記重新渲染
                this.getList()
            }
        },
    })
</script>

</html>

效果演示

img

image-20240502230657795

相關文章