theme | layout | background | highlighter | lineNumbers | info | drawings | colorSchema | |
---|---|---|---|---|---|---|---|---|
default |
cover |
/cover.jpg |
shiki |
false |
## Vitify Admin
Presentation for Vitify Admin and new features in Online Monitor.
|
|
dark |
Presentation for Vitify Admin and new features in Online Monitor
Yue JIN
Vitify 是用于快速创建web后台应用的起始模板。Vite + Vuetify = Vitify
- ⚡️ Vite 3, pnpm, ESBuild - 快得一逼
- 🗂️ Pages - 基于文件的路由
- 📑 布局系统 - 零配置布局
- 🦾 TypeScript - 支持 Vuetify2 模板 intelisense, Powerer by Volar
- 🔥
<script setup>
- 使用 Vue3 语法 - 🍍 状态管理 - Pinia
- 🧪 测试 - Vitest 单元/组件测试 + Cypress E2E 测试
- 🌍 I18n - 国际化开箱即用
- 📦 自动import - 组件自动化加载,API自动导入
- 😃 Icons - SVG图标自动注册
- 📉 数据可视化 - Vue-Echarts
layout: image-right image: https://d33wubrfki0l68.cloudfront.net/b4b145110098a2c031d3ba49551c504aef62b321/b38fa/img/benchmarks/alotta-files.svg
- 安装速度大幅提升
- 大量减少磁盘占用空间
- patch package: 允许直接修改
node_modules
中的文件 - Vue团队的所有项目及多数社区知名项目已经迁移到了pnpm
pnpm prune
- 更快的冷启动
- 超快的热重载
- 通过环境变量更改后端api地址,不再需要请求
config.json
- 基于文件系统生成路由
- 静态路由,支持debug
- 配合
vite-plugin-vue-layouts
自动在路由设置中添加layout组件
src/pages/
├── users/
│ ├── [id].vue
│ └── index.vue
└── users.vue
不用再写一大堆import
啦
unplugin-vue-components
取代vuetify-loader
自动导入 Vuetify 组件unplugin-auto-import
自动import
API
import { computed, ref } from 'vue'
const count = ref(0)
const doubled = computed(() => count.value * 2)
const count = ref(0)
const doubled = computed(() => count.value * 2)
把svg格式的图标放在相应文件夹下,即可在<v-icon>
中使用
import.meta.glob
一次性导入所有svgvite-plugin-vue2-svg
将导入的svg编译成vue component- 根据文件名在
vuetify
中注册同名图标
const svgIcons = Object.fromEntries(
Object.entries(import.meta.globEager('@/assets/icons/*.svg')).map(
([k, v]) => [
k
.split(/(\\|\/)/g)
.pop()!
.replace(/\.[^/.]+$/, ''),
{ component: v.default },
]
)
)
new Vuetify({icon:{values:{...svgIcons}}})
- 模板TypeScript检查
.vue
单组件文件类型- 单组件窗格分离
- 通过
@volar-plugins/vetur
获得vetur
的功能,迁移旧项目及js项目 - 项目已内置 Vuetify 2 type支持
- 为不同环境提供正确的类型(app vs test)
- VSCode中获得正确的intellisense
- 防止意外引入不必要的源文件
更简洁的代码,更好的 ECMAScript 和 TypeScript 支持
<script lang="ts">
import { reactive, defineComponent } from 'vue'
import BtnCount from './BtnCount.vue'
export default defineComponent({
components: { BtnCount },
setup() {
const state = reactive({ count: 0 })
function increment() {
state.count++
}
return {
state,
increment
}
}
})
</script>
<template>
<BtnCount @click="increment">
{{ state.count }}
</BtnCount>
</template>
import type { PropType } from 'vue'
export default defineComponent({
props: {
dataset: {
type: Array as PropType<number[][]>,
required: false,
default: () => [],
},
sym: {
type: Boolean,
default: false,
},
valueFormatter: {
type: Function as PropType<(val: number) => string>,
required: false,
default: (val: number) => val.toFixed(3),
},
}
})
- 支持 options api 和 composition api
- 不再需要写
mutation
- 配合
vue devtools
debug 全局状态
在 Vue2 中使用 Vue3 内置的<Teleport>
- 避免同页面组件间的数据交换
- header
- footer
- 避免
z-index
地狱- dialog
- notification
::right::
vue-i18n v8
+ vue-i18n-bridge
+ unplugin-vue-i18n
+ i18n-Ally
- Messages 预编译
- SFC Custom block
// zh.json
{
"viewLevel": {
"private": "自己",
"group": "组内",
"public": "公开",
"disabledInfo": "不能改变该对象可见级别,请确认你具有权限。"
}
}
<!-- ViewLevel.vue -->
<i18n lang="json" locale="zh">
{
"private": "自己",
"group": "组内",
"public": "公开",
"disabledInfo": "不能改变该对象可见级别,请确认你具有权限。"
}
</i18n>
Apache ECharts component for Vue.js
- Auto resize
<v-chart autoresize />
- Provide/Inject
const vuetify = useVuetify()
const { locale } = useI18n()
provide(
THEME_KEY,
computed(() => (vuetify?.theme.dark ? 'dark' : undefined))
)
provide(
INIT_OPTIONS_KEY,
computed(() => ({ locale: locale.value.toUpperCase() }))
)
echarts.on
➡️ Component Event listener
<v-chart
@mousemove="onMousemove"
@mouseout="onMouseout"
@selectchanged="onSelectChanged"
/>
- 与 Vite 共享设置
- 快如闪电的 watch mode
- Jest expect compatible APIs
::right::
- 黑盒测试
- 避免组件的实现细节
- 组件的内部状态
- 组件的内部方法
- 组件的生命周期方法
- 子组件
::right::
- 处理 DOM 节点而不是组件实例
- 基于
@vue/test-utils
并隐藏了不适用于组件测试规范的方法
it('login correctly', async () => {
const { getBytext, getByLabelText } = renderWithVuetify(loginPage)
getByText('用户登录')
const userInput = getByLabelText('用户名')
await fireEvent.update(userInput, 'admin')
const passwordInput = getByLabelText('密码')
await fireEvent.update(passwordInput, 'admin')
const button = getByText('登录')
await fireEvent.click(button)
const store = useUserStore()
expect(store.login).toBeCalledWith({ username: 'admin', password: 'admin' })
})
- 90%以上的测试不需要用到
@vue/test-utils
it('message should show and disappear after seconds', async () => {
vi.useFakeTimers()
const { getByText, getByTitle, queryByText } = renderWithVuetify(AppMessage)
const store = useMessageStore()
store.messages = [
{ text: '测试消息', type: 'info', show: true, time: new Date(), id: 1 },
]
getByText('没有新的通知')
await flushPromises()
getByText('测试消息')
vi.runAllTimers()
await nextTick()
expect(queryByText('测试消息')).toBeNull()
vi.useRealTimers()
const buttonEmpty = getByTitle('清除所有通知')
await fireEvent.click(buttonEmpty)
getByText('没有新的通知')
})
vi.mock('@/api/monitor', () => {
return {
getLastCorrectData: vi.fn(() => ({
data: [{ time_produced: '2022-04-30T13:00:00', id: 4 }],
})),
}
})
describe('Monitor Store', () => {
beforeEach(() => {
setActivePinia(createPinia())
})
it('Should remove CorrectData more than 24h ago', async () => {
const store = useMonitorStore()
;(store.correctList as any) = [
{ time_produced: '2022-04-20T13:00:00', id: 1 },
{ time_produced: '2022-04-21T13:00:00', id: 2 },
{ time_produced: '2022-04-30T12:00:00', id: 3 },
]
await store.getLatestData()
expect(store.correctList.length).toBe(2)
})
})
E2E测试,模拟真实的生产环境
- Build完之后配合后端一起测试
- Router
- Request handling
- Top-level components
- Browser
- Cypress也可以组件测试
- style
- native DOM events
- cookies, localStorage
- 还不够稳定
- 和 Pinia 一起使用会报错
- 社区还没跟上
- vue-i18n-bridge 不支持 Vue 2.7
- @vue/test-util 不能正常更新dom
还不够稳定社区还没跟上
Coming Soon!
- Do one thing, and do it well
- 思考更优的交互体验
- 注意数组的性能
const weightedDist = computed(() => {
+ const mesh = burnupMesh.value!
return dist.value
? dist.value.map((layer, j) =>
- layer.map((val, i) => (val * burnupMesh.value[j][i]) / activeHeight.value!)
+ layer.map((val, i) => (val * mesh[j][i]) / activeHeight.value!)
)
: undefined
})
- physical experiment ➡ physics test
- tripleAO ➡ AO3
- ladder ➡ trapezoid
- ...
- 新的 Vitiy Admin 模板
- Vite 生态
- Vue3 新语法和全家桶
- 单元、组件、E2E测试
- 在线监测的一些改动
- 前端技术栈及项目架构已达到企业级
Slides can be found on docs.nustarnuclear.com