iT邦幫忙

2025 iThome 鐵人賽

DAY 8
0
Vue.js

Vue 新手學習紀錄系列 第 8

Day 8|實作新增資料

  • 分享至 

  • xImage
  •  

今日目標
按下分享經驗後跳出表單,填寫完後按分享經驗按鈕就可以囉

實作分享表單

在 src/components 中新增 NewPostModal.vue

<template>
  <div v-if="visible" class="modal-overlay" @click.self="close">
    <div class="modal-content share-modal">
      <div class="modal-header">
        <h3 class="modal-title">✍ 分享你的推甄經驗</h3>
        <button class="close-btn" @click="close">×</button>
      </div>
      
      <div class="modal-body">
        <form @submit.prevent="submitExperience" class="share-form">
          <div class="form-section">
            <h4 class="section-title">📚 基本資訊</h4>
            <div class="form-grid">
              <div class="form-group">
                <label class="form-label">學校</label>
                <input 
                  v-model="formData.pSchool" 
                  type="text" 
                  class="form-input" 
                  placeholder="例:台灣大學"
                  required
                />
              </div>
              <div class="form-group">
                <label class="form-label">系所</label>
                <input 
                  v-model="formData.pDep" 
                  type="text" 
                  class="form-input" 
                  placeholder="例:資訊管理學系"
                  required
                />
              </div>
              <div class="form-group">
                <label class="form-label">推甄年度</label>
                <input 
                  v-model="formData.pYear" 
                  type="text" 
                  class="form-input" 
                  placeholder="例:114"
                  required
                />
              </div>
              <div class="form-group">
                <label class="form-label">成績排名</label>
                <input 
                  v-model="formData.pScore" 
                  type="text" 
                  class="form-input" 
                  placeholder="例:5% 或 系排第3"
                />
              </div>
              <div class="form-group">
                <label class="form-label">GPA (選填)</label>
                <input 
                  v-model="formData.pGPA" 
                  type="text" 
                  class="form-input" 
                  placeholder="例:4.0/4.0"
                />
              </div>
            </div>
          </div>

          <div class="form-section">
            <h4 class="section-title">🌟 經歷與背景</h4>
            <div class="form-group">
              <label class="form-label">詳細經歷</label>
              <textarea 
                v-model="formData.pExp" 
                class="form-textarea" 
                rows="6"
                placeholder="請分享你的經歷,例如:&#10;- 專題研究&#10;- 實習經驗&#10;- 競賽得獎&#10;- 證照考取&#10;- 社團活動&#10;- 其他特殊經歷"
                required
              ></textarea>
            </div>
          </div>

          <div class="form-section">
            <h4 class="section-title">🎯 推甄結果</h4>
            <div class="form-group">
              <label class="form-label">申請結果</label>
              <textarea 
                v-model="formData.pResult1" 
                class="form-textarea" 
                rows="6"
                placeholder="請分享你的申請結果,例如:&#10;台大資管 一階落榜&#10;政大資管 正取&#10;成大資管 備取5&#10;中央資管 正取"
                required
              ></textarea>
            </div>
          </div>

          <div class="form-section">
            <h4 class="section-title">🔗 來源連結 (選填)</h4>
            <div class="form-group">
              <label class="form-label">原文連結</label>
              <input 
                v-model="formData.pURL" 
                type="url" 
                class="form-input" 
                placeholder="例:https://www.dcard.tw/f/graduate_school/p/..."
              />
            </div>
          </div>

          <div class="form-actions">
            <button type="button" class="btn btn-secondary" @click="close">
              取消
            </button>
            <button type="submit" class="btn btn-primary" :disabled="isSubmitting">
              {{ isSubmitting ? '分享中...' : '✨ 分享經驗' }}
            </button>
          </div>
        </form>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, reactive } from 'vue'

const props = defineProps({
  visible: { type: Boolean, default: false }
})

const emit = defineEmits(['close', 'submit'])

const isSubmitting = ref(false)

const formData = reactive({
  pSchool: '',
  pDep: '',
  pYear: '',
  pScore: '',
  pGPA: '',
  pExp: '',
  pResult1: '',
  pURL: ''
})

function close() {
  emit('close')
  resetForm()
}

function resetForm() {
  Object.keys(formData).forEach(key => {
    formData[key] = ''
  })
}

async function submitExperience() {
  if (isSubmitting.value) return
  
  isSubmitting.value = true
  
  try {
    const newExperience = {
      pId: `p_${Date.now()}`,
      pDate: new Date().toLocaleDateString('zh-TW', {
        year: 'numeric',
        month: '2-digit',
        day: '2-digit'
      }).replace(/\//g, '/'),
      pDcardId: null,
      pURL: formData.pURL || null,
      pYear: formData.pYear,
      pContent: `背景:
學校/系所:${formData.pSchool} ${formData.pDep}
${formData.pScore ? `成績排名:${formData.pScore}` : ''}
${formData.pGPA ? `GPA:${formData.pGPA}` : ''}

經歷:
${formData.pExp}

結果:
${formData.pResult1}`,
      pSchool: formData.pSchool,
      pDep: formData.pDep,
      p_sd_sId: "user_submitted",
      p_sd_dId: "user_submitted",
      pScore: formData.pScore || null,
      pGPA: formData.pGPA || null,
      pExp: formData.pExp,
      pResult1: formData.pResult1
    }
    
    emit('submit', newExperience)
    
    alert('感謝你的分享!你的經驗已成功加入資料庫 ✨')
    
    close()
  } catch (error) {
    console.error('提交失敗:', error)
    alert('提交失敗,請稍後再試')
  } finally {
    isSubmitting.value = false
  }
}
</script>

修改 App.vue

<script setup>
import NewPostModal from './components/NewPostModal.vue'
...
const showShareModal = ref(false)
const allPosts = ref([...posts])

function openShareModal() {
  showShareModal.value = true
}

function handleNewExperience(newExperience) {
  allPosts.value.unshift(newExperience)
  showShareModal.value = false
  keyword.value = ''
  currentPage.value = 1
}
...
</script>

<template>
  <Header @share="openShareModal" />
  <main>
      ...
    <NewPostModal :visible="showShareModal" @close="showShareModal=false" @submit="handleNewExperience" />
  </main>
</template>

修改原本的分享經驗按鈕

<template>
  <header class="header">
    <div class="header-content">
      <h1 class="logo">
        ✦ 推甄經驗分享 ✦
      </h1>
      <button class="add-btn" @click="handleShare">
        ✍ 分享經驗
      </button>
    </div>
  </header>
</template>


<script setup>
  const emit = defineEmits(['share'])

  function handleShare() {
    emit('share')
  }
</script>

但是目前還沒接到資料的 API,所以資料只會暫時新增上去

ref 和 reactive

ref

  • 適合單一值,例如: string、number 和 boolean
  • 會回傳一個物件,值放在 .value 裡
  • 在 templat 裡用 ref,則不需要加上 .value
import { ref } from 'vue'

const count = ref(0)     
console.log(count.value) // 讀寫要用 .value
count.value++

<template>
  <button @click="count++"> {{ count }} </button> <!-- 在模板可直接用 -->
</template>

reactive

  • 適合物件、陣列,像是表單、清單等等
  • 會回傳一個 Proxy(代理),屬性存取都被 Vue 追蹤
  • 不需要 .value,直接用屬性就好
import { reactive } from 'vue'

const form = reactive({
  school: '',
  dep: '',
  year: ''
})

form.school = '台大' // 直接改屬性

<template>
  <input v-model="form.school" placeholder="學校"/>
  <p>輸入的學校是:{{ form.school }}</p>
</template>

小結

  • 實作新增資料功能
  • ref 與 reaactive 差異

上一篇
Day 7|分頁 Pagination
下一篇
Day 9|用 vee-validate 做表單驗證
系列文
Vue 新手學習紀錄9
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言