12451 b553c4e519 feat: API 统一 + 设备详情补全 + H5 导航优化
- API 路由统一: 移除 /api/h5/,统一 /api/pms/ 入口
- 二进制改名: api-server -> pms-api(与服务名一致)
- nginx: 合并 /api/ + /pms/api/ 为 /api/pms/
- 新增 docs/API.md 接口文档
- 后端: CabinetDetail 增加 rssi/pow_fail/address/soc 等字段
- Redis 管道批量读取仓体实时数据(SOC/电压/电流/功率)
- DB: 所有 TIMESTAMP 列改为 DATETIME(修复类型不匹配)
- 用户管理: 新增创建时间/修改时间列,状态改用 Switch
- 角色管理/用户管理: 操作按钮改为图标
- PC 设备详情: 基本信息补全 RSSI/ICCID/电源/地址
- PC 卡片: 通道统计改用 Lucide 图标,颜色统一
- PC 二维码: canvas 渲染,含编号文字,支持右键下载
- H5: 按 uni-app 规范修正导航(navigateTo 替代 reLaunch)
- H5: 详情页加返回导航条
- H5: 扫码支持摄像头(jsQR)+ ?id= 参数
- H5: 底部 tabBar 在所有页面常显
- H5: 首页统计改为 6 项(设备/通道/空闲/充电/充满/故障)
- H5: 下拉刷新 / 上拉加载
- 产品首页: 添加业务联系
- 文档: 清理过时文件,补充缺失规范章节
2026-07-04 04:03:02 +08:00

5.5 KiB

title, impact, impactDescription, type, tags
title impact impactDescription type tags
Colocate Template, Script, and Style in SFCs for Maintainability MEDIUM Separating template, logic, and styles into different files reduces component cohesion and maintainability best-practice
vue3
sfc
architecture
separation-of-concerns
maintainability

Colocate Template, Script, and Style in SFCs for Maintainability

Impact: MEDIUM - Vue SFCs are designed to colocate template, logic, and styles because these concerns are inherently coupled within a component. Separating them into different files based on file type (not concern) makes components harder to understand and maintain.

Task Checklist

  • Keep template, script, and style in the same .vue file by default
  • Only use src imports when files become extremely large (500+ lines)
  • Group related components in folders rather than separating by file type
  • Use component composition to manage complexity instead of file splitting

Not Recommended:

components/
├── UserCard.vue          # Just template
├── UserCard.js           # Logic
├── UserCard.css          # Styles

Recommended:

<!-- components/UserCard.vue - Everything in one file -->
<script setup>
import { computed } from 'vue'
import { useUserStatus } from '@/composables/useUserStatus'

const props = defineProps({
  user: { type: Object, required: true }
})

const { isOnline } = useUserStatus(props.user.id)
const displayName = computed(() =>
  `${props.user.firstName} ${props.user.lastName}`
)
</script>

<template>
  <div class="user-card">
    <img :src="user.avatar" :alt="displayName" class="avatar" />
    <div class="info">
      <h3 class="name">{{ displayName }}</h3>
      <span :class="['status', { online: isOnline }]">
        {{ isOnline ? 'Online' : 'Offline' }}
      </span>
    </div>
  </div>
</template>

<style scoped>
.user-card {
  display: flex;
  padding: 1rem;
  border: 1px solid #e0e0e0;
  border-radius: 8px;
}

.avatar {
  width: 48px;
  height: 48px;
  border-radius: 50%;
}

.info {
  margin-left: 1rem;
}

.name {
  margin: 0 0 0.25rem;
}

.status {
  font-size: 0.875rem;
  color: #666;
}

.status.online {
  color: #22c55e;
}
</style>

Why Colocation is Preferred

1. Coupled Concerns Should Stay Together

Template, logic, and styles within a component are inherently coupled:

  • Template references reactive data from the script
  • Styles target classes used in the template
  • Changes in one often require changes in others
<!-- Everything references each other -->
<script setup>
const isExpanded = ref(false)  // Used by template and affects styling
</script>

<template>
  <!-- Uses isExpanded from script, applies class for styling -->
  <div :class="{ expanded: isExpanded }">...</div>
</template>

<style scoped>
/* Targets class from template, shows visual state from script */
.expanded { max-height: 500px; }
</style>

2. Easier Navigation and Understanding

Single file = single place to look:

<!-- Open one file, understand the whole component -->
<script setup>
// All logic here
</script>

<template>
  <!-- All markup here -->
</template>

<style scoped>
/* All styles here */
</style>

3. Better Refactoring Experience

Renaming a class? Everything is in one file:

<!-- Before: .card-title used in both template and style -->
<!-- After: Rename to .user-title in one file, one commit -->

When Src Imports Are Acceptable

For genuinely large components (rare), use src imports:

<!-- LargeDataTable.vue -->
<script src="./LargeDataTable.ts" lang="ts"></script>
<template src="./LargeDataTable.html"></template>
<style src="./LargeDataTable.scss" scoped lang="scss"></style>

But first, consider if the component should be split into smaller components.

Managing Complexity Without File Splitting

Extract Logic to Composables

// composables/useDataTable.ts
export function useDataTable(initialData: Ref<Item[]>) {
  const sortColumn = ref<string | null>(null)
  const sortDirection = ref<'asc' | 'desc'>('asc')
  const currentPage = ref(1)

  const sortedData = computed(() => { ... })
  const paginatedData = computed(() => { ... })

  function sort(column: string) { ... }
  function goToPage(page: number) { ... }

  return {
    sortColumn,
    sortDirection,
    currentPage,
    sortedData,
    paginatedData,
    sort,
    goToPage
  }
}
<!-- DataTable.vue - Component stays focused -->
<script setup>
import { useDataTable } from '@/composables/useDataTable'

const props = defineProps(['items'])
const { sortedData, sort, goToPage } = useDataTable(toRef(props, 'items'))
</script>

Break Into Smaller Components

<!-- Instead of one 500-line component -->
<template>
  <div class="data-table">
    <DataTableHeader @sort="handleSort" />
    <DataTableBody :rows="visibleRows" />
    <DataTablePagination
      :total="total"
      :current="currentPage"
      @change="handlePageChange"
    />
  </div>
</template>

The Real Separation of Concerns

True separation of concerns in Vue means:

  • Components handle their own template/logic/style (coupled by nature)
  • Composables handle reusable stateful logic
  • Utilities handle pure functions
  • Stores handle global state

This is more maintainable than separating HTML/CSS/JS into different files.

Reference