文字游戏
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

12 KiB

背包功能 MVP 实现计划

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: 在游戏客户端添加背包展示页面,显示角色背包中的物品,鼠标悬停显示物品名称和基本信息

Architecture: 使用现有后端API,前端本地分页,每页16个物品(4×4网格)

Tech Stack: Vue 3 + TypeScript + Pinia + Element Plus


Task 1: 添加背包 API 模块

Files:

  • Create: Build_God_Game/src/api/bag.ts

  • Step 1: 创建 bag.ts API 模块

import http from './index'

export interface Bag {
  id: number
  name: string
  rarity: number
  capacity: number
  description: string | null
}

export interface CharacterBag {
  id: number
  characterId: number
  bagId: number
  bagName: string | null
  bagCapacity: number
}

export interface BagItem {
  id: number
  characterBagId: number
  itemType: number
  itemId: number
  quantity: number
  itemName: string | null
  itemRarity: number | null
}

export const getCharacterBag = (characterId: number): Promise<CharacterBag | null> => {
  return http.get(`bag/character/${characterId}`)
}

export const getBagItems = (characterBagId: number): Promise<BagItem[]> => {
  return http.get(`bag/${characterBagId}/items`)
}
  • Step 2: 提交代码
git add Build_God_Game/src/api/bag.ts
git commit -m "feat(game): add bag API module"

Task 2: 添加背包 Store

Files:

  • Create: Build_God_Game/src/stores/bag.ts

  • Step 1: 创建 bag.ts Store

import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import { getCharacterBag, getBagItems, type CharacterBag, type BagItem } from '@/api/bag'
import { useCharacterStore } from './character'

const PAGE_SIZE = 16

export const useBagStore = defineStore('bag', () => {
  const characterStore = useCharacterStore()
  
  const characterBag = ref<CharacterBag | null>(null)
  const bagItems = ref<BagItem[]>([])
  const currentPage = ref(1)
  const loading = ref(false)
  const error = ref<string | null>(null)

  const currentCharacterId = computed(() => characterStore.currentCharacter?.id)
  
  const totalItems = computed(() => bagItems.value.length)
  
  const totalPages = computed(() => Math.ceil(totalItems.value / PAGE_SIZE))
  
  const paginatedItems = computed(() => {
    const start = (currentPage.value - 1) * PAGE_SIZE
    const end = start + PAGE_SIZE
    return bagItems.value.slice(start, end)
  })

  const usedCapacity = computed(() => {
    return bagItems.value.reduce((sum, item) => sum + item.quantity, 0)
  })

  const loadBag = async () => {
    if (!currentCharacterId.value) {
      error.value = '请先选择角色'
      return
    }

    try {
      loading.value = true
      error.value = null
      
      characterBag.value = await getCharacterBag(currentCharacterId.value)
      
      if (characterBag.value) {
        bagItems.value = await getBagItems(characterBag.value.id)
      } else {
        bagItems.value = []
      }
      
      currentPage.value = 1
    } catch (e: any) {
      error.value = e.message || '加载背包失败'
      console.error('Load bag error:', e)
    } finally {
      loading.value = false
    }
  }

  const nextPage = () => {
    if (currentPage.value < totalPages.value) {
      currentPage.value++
    }
  }

  const prevPage = () => {
    if (currentPage.value > 1) {
      currentPage.value--
    }
  }

  return {
    characterBag,
    bagItems,
    currentPage,
    totalPages,
    totalItems,
    paginatedItems,
    usedCapacity,
    loading,
    error,
    loadBag,
    nextPage,
    prevPage
  }
})
  • Step 2: 提交代码
git add Build_God_Game/src/stores/bag.ts
git commit -m "feat(game): add bag store"

Task 3: 添加背包页面

Files:

  • Create: Build_God_Game/src/views/BagView.vue

  • Step 1: 创建 BagView.vue

<script setup lang="ts">
import { onMounted, computed } from 'vue'
import { useRouter } from 'vue-router'
import { useBagStore } from '@/stores/bag'
import { useCharacterStore } from '@/stores/character'
import StarBorder from '@/components/StarBorder/StarBorder.vue'
import Particles from '@/components/Particles/Particles.vue'
import { ArrowLeft } from '@element-plus/icons-vue'

const router = useRouter()
const bagStore = useBagStore()
const characterStore = useCharacterStore()

const characterName = computed(() => characterStore.currentCharacter?.name || '')
const bagName = computed(() => bagStore.characterBag?.bagName || '背包')
const bagCapacity = computed(() => bagStore.characterBag?.bagCapacity || 0)
const usedCapacity = computed(() => bagStore.usedCapacity)

const itemTypeMap: Record<number, string> = {
  1: '装备',
  2: '丹药'
}

const rarityMap: Record<number, string> = {
  1: '普通',
  2: '稀有',
  3: '史诗',
  4: '传说'
}

const getItemTooltip = (item: typeof bagStore.paginatedItems[0]) => {
  const type = itemTypeMap[item.itemType] || '未知'
  const rarity = item.itemRarity ? rarityMap[item.itemRarity] || '' : ''
  return `${item.itemName}\n类型: ${type}${rarity ? ` | 稀有度: ${rarity}` : ''}\n数量: ${item.quantity}`
}

const handleBack = () => {
  router.push('/game')
}

onMounted(() => {
  bagStore.loadBag()
})
</script>

<template>
  <div class="bag-page">
    <Particles :particle-count="30" :particle-colors="['#ffffff', '#cccccc']" class="particles-bg" />
    
    <div class="bag-container">
      <StarBorder as="div" color="#8b5cf6" speed="5s" :thickness="2" class="back-btn" @click="handleBack">
        <div class="back-content">
          <el-icon><ArrowLeft /></el-icon>
          <span>返回</span>
        </div>
      </StarBorder>

      <div class="bag-header">
        <h2>{{ bagName }}</h2>
        <span class="capacity">{{ usedCapacity }} / {{ bagCapacity }}</span>
      </div>

      <div v-if="bagStore.loading" class="loading">
        加载中...
      </div>

      <div v-else-if="bagStore.error" class="error">
        {{ bagStore.error }}
      </div>

      <div v-else-if="bagStore.totalItems === 0" class="empty">
        背包空空如也,快去获取一些物品吧!
      </div>

      <div v-else class="items-grid">
        <el-tooltip
          v-for="item in bagStore.paginatedItems"
          :key="item.id"
          :content="getItemTooltip(item)"
          placement="top"
          :show-after="300"
        >
          <div class="item-cell">
            <div class="item-icon" :class="'rarity-' + (item.itemRarity || 1)">
              {{ item.itemType === 1 ? '⚔️' : '💊' }}
            </div>
            <span class="item-name">{{ item.itemName }}</span>
            <span v-if="item.quantity > 1" class="item-count">{{ item.quantity }}</span>
          </div>
        </el-tooltip>
      </div>

      <div v-if="bagStore.totalPages > 1" class="pagination">
        <button 
          class="page-btn" 
          :disabled="bagStore.currentPage === 1"
          @click="bagStore.prevPage()"
        >
          上一页
        </button>
        <span class="page-info">{{ bagStore.currentPage }} / {{ bagStore.totalPages }}</span>
        <button 
          class="page-btn" 
          :disabled="bagStore.currentPage >= bagStore.totalPages"
          @click="bagStore.nextPage()"
        >
          下一页
        </button>
      </div>
    </div>
  </div>
</template>

<style scoped>
.bag-page {
  min-height: 100vh;
  background: #000000;
  padding: 20px;
  position: relative;
  overflow: hidden;
}

.particles-bg {
  position: fixed !important;
  top: 0;
  left: 0;
  width: 100% !important;
  height: 100% !important;
}

.bag-container {
  max-width: 480px;
  margin: 0 auto;
  position: relative;
  z-index: 10;
}

.back-btn {
  margin-bottom: 20px;
  display: inline-block;
}

.back-content {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 16px;
  cursor: pointer;
  color: #cccccc;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 8px;
}

.bag-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 24px;
  padding: 16px 20px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 12px;
}

.bag-header h2 {
  margin: 0;
  color: #ffffff;
  font-size: 1.25rem;
  font-weight: 500;
}

.capacity {
  color: #888888;
  font-size: 0.9rem;
}

.loading, .error, .empty {
  text-align: center;
  padding: 40px;
  color: #888888;
}

.error {
  color: #ff4444;
}

.items-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 12px;
  margin-bottom: 24px;
}

.item-cell {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 16px 8px;
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-radius: 12px;
  cursor: pointer;
  transition: all 0.2s ease;
}

.item-cell:hover {
  background: rgba(255, 255, 255, 0.06);
  border-color: rgba(255, 255, 255, 0.15);
}

.item-icon {
  font-size: 2rem;
  margin-bottom: 8px;
}

.item-name {
  color: #cccccc;
  font-size: 0.75rem;
  text-align: center;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  width: 100%;
}

.item-count {
  position: absolute;
  top: 4px;
  right: 4px;
  background: rgba(139, 92, 246, 0.8);
  color: #ffffff;
  font-size: 0.65rem;
  padding: 2px 6px;
  border-radius: 8px;
  min-width: 18px;
  text-align: center;
}

.rarity-1 { filter: grayscale(0.3); }
.rarity-2 { filter: hue-rotate(80deg) saturate(1.5); }
.rarity-3 { filter: hue-rotate(200deg) saturate(2); }
.rarity-4 { filter: hue-rotate(-30deg) saturate(2) brightness(1.2); }

.pagination {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 16px;
}

.page-btn {
  padding: 10px 20px;
  background: rgba(255, 255, 255, 0.05);
  border: 1px solid rgba(255, 255, 255, 0.1);
  border-radius: 8px;
  color: #cccccc;
  cursor: pointer;
  transition: all 0.2s ease;
}

.page-btn:hover:not(:disabled) {
  background: rgba(255, 255, 255, 0.1);
}

.page-btn:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

.page-info {
  color: #888888;
  font-size: 0.9rem;
}
</style>
  • Step 2: 提交代码
git add Build_God_Game/src/views/BagView.vue
git commit -m "feat(game): add bag view page"

Task 4: 添加路由

Files:

  • Modify: Build_God_Game/src/router/index.ts:45-53

  • Step 1: 添加背包路由

routes 数组中,在 /scrap 路由后添加:

{
  path: '/bag',
  name: 'bag',
  component: () => import('@/views/BagView.vue'),
  meta: { requiresAuth: true }
}
  • Step 2: 提交代码
git add Build_God_Game/src/router/index.ts
git commit -m "feat(game): add bag route"

Task 5: 更新游戏主页导航菜单

Files:

  • Modify: Build_God_Game/src/views/GameView.vue:41-46

  • Step 1: 在 menuItems 中添加背包入口

找到 const menuItems = computed(() => [...]),在数组中添加:

{ label: '背包', icon: '🎒', useImage: false }
  • Step 2: 添加 navigateTo 跳转逻辑

navigateTo 函数中添加:

} else if (item.label === '背包') {
  router.push('/bag')
}
  • Step 3: 提交代码
git add Build_God_Game/src/views/GameView.vue
git commit -m "feat(game): add bag menu entry in game view"

Task 6: 验证构建

Files:

  • Test: Build_God_Game/ 整个项目

  • Step 1: 运行类型检查

cd Build_God_Game && npm run type-check
  • Step 2: 运行构建
cd Build_God_Game && npm run build
  • Step 3: 提交最终代码
git add -A && git commit -m "feat(game): complete bag feature MVP"