- 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: 下拉刷新 / 上拉加载 - 产品首页: 添加业务联系 - 文档: 清理过时文件,补充缺失规范章节
5.2 KiB
5.2 KiB
title, impact, impactDescription, type, tags
| title | impact | impactDescription | type | tags | |||||
|---|---|---|---|---|---|---|---|---|---|
| Avoid Scoped Styles in Reusable Transition Components | MEDIUM | Scoped styles in transition wrapper components won't apply to slotted content, breaking the transition animation | gotcha |
|
Avoid Scoped Styles in Reusable Transition Components
Impact: MEDIUM - When creating reusable transition wrapper components, using <style scoped> will prevent the transition CSS classes from applying to slotted content. Scoped styles only affect elements directly in the component's template, not content passed through slots. Your transition animations will silently fail.
Task Checklist
- In reusable transition components, use
<style>withoutscoped - Alternatively, use unique class name prefixes to avoid global conflicts
- Or use CSS modules with
:global()for transition classes - Test that transitions work when component is used in different contexts
Problematic Code:
<!-- MyFadeTransition.vue -->
<template>
<Transition name="my-fade">
<slot />
</Transition>
</template>
<!-- BAD: Scoped styles won't apply to slot content! -->
<style scoped>
.my-fade-enter-active,
.my-fade-leave-active {
transition: opacity 0.3s ease;
}
.my-fade-enter-from,
.my-fade-leave-to {
opacity: 0;
}
</style>
<!-- Parent component using the transition -->
<template>
<MyFadeTransition>
<div v-if="show">This won't animate!</div>
</MyFadeTransition>
</template>
<!--
The <div> is slotted content, so .my-fade-* classes
applied by Vue won't match the scoped CSS selectors
-->
Correct Code:
<!-- MyFadeTransition.vue -->
<template>
<Transition name="my-fade">
<slot />
</Transition>
</template>
<!-- GOOD: Unscoped styles apply to any element -->
<style>
.my-fade-enter-active,
.my-fade-leave-active {
transition: opacity 0.3s ease;
}
.my-fade-enter-from,
.my-fade-leave-to {
opacity: 0;
}
</style>
Alternative: Use Unique Prefixed Class Names
To avoid global style conflicts, use distinctive prefixes:
<!-- FadeTransition.vue -->
<template>
<Transition name="v-fade-transition">
<slot />
</Transition>
</template>
<style>
/* Unique prefix reduces collision risk */
.v-fade-transition-enter-active,
.v-fade-transition-leave-active {
transition: opacity 0.3s ease;
}
.v-fade-transition-enter-from,
.v-fade-transition-leave-to {
opacity: 0;
}
</style>
Alternative: CSS Modules with :global()
<!-- FadeTransition.vue -->
<template>
<Transition name="fade">
<slot />
</Transition>
</template>
<style module>
/* Use :global() for transition classes */
:global(.fade-enter-active),
:global(.fade-leave-active) {
transition: opacity 0.3s ease;
}
:global(.fade-enter-from),
:global(.fade-leave-to) {
opacity: 0;
}
</style>
Alternative: Custom Transition Classes
Use the custom class props to apply scoped classes:
<!-- FadeTransition.vue -->
<template>
<Transition
:enter-active-class="$style.enterActive"
:leave-active-class="$style.leaveActive"
:enter-from-class="$style.enterFrom"
:leave-to-class="$style.leaveTo"
>
<slot />
</Transition>
</template>
<style module>
.enterActive,
.leaveActive {
transition: opacity 0.3s ease;
}
.enterFrom,
.leaveTo {
opacity: 0;
}
</style>
Complete Reusable Transition Component Example
<!-- transitions/SlideTransition.vue -->
<template>
<Transition
name="slide"
:mode="mode"
:appear="appear"
@before-enter="$emit('before-enter', $event)"
@enter="$emit('enter', $event)"
@after-enter="$emit('after-enter', $event)"
@before-leave="$emit('before-leave', $event)"
@leave="$emit('leave', $event)"
@after-leave="$emit('after-leave', $event)"
>
<slot />
</Transition>
</template>
<script setup>
defineProps({
mode: {
type: String,
default: 'out-in',
validator: (v) => ['out-in', 'in-out', ''].includes(v)
},
appear: {
type: Boolean,
default: false
}
})
defineEmits([
'before-enter', 'enter', 'after-enter',
'before-leave', 'leave', 'after-leave'
])
</script>
<!-- Unscoped so styles apply to slotted content -->
<style>
.slide-enter-active,
.slide-leave-active {
transition: transform 0.3s ease, opacity 0.3s ease;
}
.slide-enter-from {
opacity: 0;
transform: translateX(-20px);
}
.slide-leave-to {
opacity: 0;
transform: translateX(20px);
}
</style>
Usage:
<template>
<SlideTransition>
<div v-if="show" class="content">
This will properly animate!
</div>
</SlideTransition>
</template>
Why This Happens
Vue's scoped styles work by adding a unique data attribute (e.g., data-v-7ba5bd90) to elements and selectors:
/* What you write */
.my-fade-enter-active { ... }
/* What Vue generates (scoped) */
.my-fade-enter-active[data-v-7ba5bd90] { ... }
Slotted content comes from the parent component and gets the parent's data attribute, not the transition component's attribute. So the selectors never match.