编辑
2024-11-22
个人项目
00

目录

资源处理
码字修正
字体编辑
技术栈
环境配置
Vite 配置
迁移工作
老项目运行问题
vue eslint 问题
Element 组件库升级
代码编程
在兄弟组件之间通信
样式设计
响应式布局
排版
总结

前段时间冲浪的时候发现了一个五笔字根练习的小项目(yhf7952/WubiGame),感觉还挺有意思,遂抄。它本身只是一个前端小项目,是原作者学习 Vue 的时候写的第一个项目。那我也就顺便用这个项目学习一点 Vue 的知识喽。

资源处理

码字修正

原项目的二级简码表有一些缺漏和错字,我进行了编辑修改。实际上由于不同输入法的实现和码表存在差异,因此二级简码有部分码字是不同的。一些表的部分二级简码对应优先对应词组,一些简码理论上能打出单字却没有对应码字(空码),因而我也无法给出完全正确的二级简码表。最后我综合几个码表,选择了空码最多(也即和其他码表定义冲突最小)的码表。

字体编辑

原项目显示字根采用了特殊字体。通过引用字体中字根对应的字符,即可实现字根显示(和图标字体显示应该是同样的原理)。但是字体中的字根也存在错误,需要修改。在线查看字体,可访问该网站,字体编辑可使用 FontCreator

技术栈

在原仓库的基础上

  • 将前端框架从 Vue2 升级到 Vue3
  • 将组件库从 element 升级到 element-plus
  • 使用 TypeScript 代替 js
  • 使用 Pug 代替 html

环境配置

Vite 配置

ts
import { fileURLToPath, URL } from 'node:url' import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import AutoImport from 'unplugin-auto-import/vite' import Components from 'unplugin-vue-components/vite' import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' import ElementPlus from 'unplugin-element-plus/vite' // https://vite.dev/config/ export default defineConfig({ base: '/c/wubi/', plugins: [ vue(), AutoImport({ resolvers: [ElementPlusResolver()], }), Components({ resolvers: [ElementPlusResolver()], }), ElementPlus({}), ], resolve: { alias: { '@': fileURLToPath(new URL('./src', import.meta.url)) } } })

由于部署的生产环境使用的是子路径( https://vanblog.zerolacqua.top/c/wubi/ ),因此设置了 base 项。

unplugin-vue-componentsunplugin-auto-import 等是用于自动按需导入 Element Plus 组件库的。

迁移工作

老项目运行问题

老项目运行 vue-cli-service serve 时报错

Error: error:0308010C:digital envelope routines::unsupported at new Hash (node:internal/crypto/hash:79:19) at Object.createHash (node:crypto:139:10) at module.exports

原因是项目依赖了过时的 SSL。

解决方案参见:https://stackoverflow.com/questions/69692842/error-message-error0308010cdigital-envelope-routinesunsupported

vue eslint 问题

使用 pug 作为模板时,使用 <script setup> 检查不到 <templete lang="pug"> 中使用的组件和变量,报 no-unused-vars 错误。

  • 解决方式 1:切换到 html 模板

  • 解决方式 2:眼不见心不烦,变量未使用不会有警告

    js
    // eslint.config.js export default [ { rules: { '@typescript-eslint/no-unused-vars': 'off', }, }, ]
    json
    // tsconfig.json { "compilerOptions": { "noUnusedLocals": true, }, }

解决方案参见:https://github.com/vuejs/language-tools/issues/47

Element 组件库升级

库的变化

  • element-plus 中更新了一些元素的 display 属性
  • element-plus 中的一些组件名发生了改变
  • element-plus 一些组件的 Api 发生了改变
    • 如 ElMessage

要引入新的组件库,参考:https://element-plus.org/zh-CN/guide/quickstart.html

代码编程

在兄弟组件之间通信

兄弟组件之间通信一般采用全局组件通信的方案,并不推荐借助父组件中转的通信。

常用的全局组件通信方法包括:

  • EventBus
  • Reactive State
  • Vuex
  • Pinia

由于本项目是一个很小的 demo,我采用了 Reactive State 方案,简单易懂:

ts
// src/store.ts import { reactive } from 'vue' export interface Store { statData: StatData, incrementRightTimes(): void, incrementTotalTimes(): void, setStatData(statData: StatData): void, resetStatData(): void, } export interface StatData { rightTimes: number, totalTimes: number, startDate: Date, } export const store: Store = reactive({ statData: { rightTimes: 0, totalTimes: 0, startDate: new Date(), }, incrementRightTimes() { store.statData.rightTimes++ }, incrementTotalTimes() { store.statData.totalTimes++ }, setStatData(statData: StatData) { store.statData = statData }, resetStatData() { store.statData = { rightTimes: 0, totalTimes: 0, startDate: new Date(), } }, })

src 目录下创建一个状态中心,设置若干需要使用的属性,并添加更新数据的方法。

在需要更改状态的组件中调用更新方法:

ts
// GameComponent.vue // input 事件 function onInput(str: string) { const input = str.toLowerCase() switch (props.gameMode) { case GameMode.zigen: case GameMode.yiji: if (input === d1.value![0]) { getData() retryTimes.value = 0 isRight.value = true store.incrementRightTimes() } else { isRight.value = false retryTimes.value++ if (retryTimes.value >= 3) { ElMessage({ message: `错了哦,正确答案是:${d1.value![0].toUpperCase()}`, grouping: true, type: 'error', }) } } store.incrementTotalTimes() intext.value = '' return case GameMode.erji: if (input === d1.value![1]) { getData() store.incrementRightTimes() isRight.value = true } else { isRight.value = false } store.incrementTotalTimes() intext.value = '' return default: throw new Error('GameMode error') } } function reset() { d1.value = undefined d2.value = undefined d3.value = undefined d4.value = undefined d5.value = undefined getData() store.resetStatData() isRight.value = true document.getElementById('intext')!.focus() }

而后在需要获取状态的组件中进行读取:

ts
// StatComponent.vue const rightTimes = computed(() => store.statData.rightTimes) const totalTimes = computed(() => store.statData.totalTimes) const startDate = computed(() => store.statData.startDate)

不同的通信方案可参考:https://vue3.chengpeiquan.com/communication.html

样式设计

响应式布局

使用基于断点的隐藏类,以及组件的响应式属性(xs、sm、md、lg 和 xl),当尺寸在不同大小时,展示不同的卡片数量:

pug
div //- 根据大小屏幕显示不同的 gutter el-row( :class="{ zigen: gameMode == GameMode.zigen }" class="box" :gutter= 15 style="margin-top: 50px" ) el-col(:xs="3" :sm="3" :md="2" :lg="2" :xl="2") el-col(:xs="6" :sm="6" :md="4" :lg="4" :xl="4") el-card( shadow="always" :class="isRight ? 'boderNormal' : 'boderError'" ) | {{ d1[1] }} el-col(:xs="6" :sm="6" :md="4" :lg="4" :xl="4") el-card(shadow="always") | {{ d2[1] }} el-col(:xs="6" :sm="6" :md="4" :lg="4" :xl="4") el-card(shadow="always") | {{ d3[1] }} el-col.hidden-sm-and-down(:span="4" ) el-card(shadow="always") | {{ d4[1] }} el-col.hidden-sm-and-down(:span="4" ) el-card(shadow="always") | {{ d5[1] }} el-col(:xs="6" :sm="3" :md="2" :lg="2" :xl="2")

尽管采用了自动按需导入 Element Plus 的方案,但若想使用基于断点的隐藏类 hidden-sm-and-down 还是需要引入文件:

ts
import 'element-plus/theme-chalk/display.css'

使用媒体查询,当尺寸缩放过小时,调整字体卡片的大小:

css
/* xs 以上 */ @media screen { .box { font-size: 1cm; text-align: center; line-height: 45px; } } /* xs 以下 */ @media screen and (max-width: 300px) { .box { font-size: 0.6cm; text-align: center; line-height: 15px; } }

排版

  • 菜单栏对齐

    删除菜单栏的 border-bottom 并调整颜色,同时需要注意 <style> 有不同的作用域。

  • 页脚置底

    el-containerdisplayflex ,故可以

    css
    .el-container { min-height: 100vh; }

完成情况

目前项目已归档,不会再做更改

  • 代码编写
    • 迁移至 pug 和 ts
    • 迁移至 SASS
    • 组件拆分
    • 迁移至组合式 API
    • 单页面路由
  • 响应式布局
    • 打字卡片
    • 统计信息
  • 页面
    • 菜单栏对齐
    • 页脚置底
    • 网站图标
    • 二、三级简码表格化

总结

草草写了一篇博文进度记录,细致度和上一篇差很多。中间拖了太久,甚至后面那个 next.js 项目也完成了,导致遗忘了不少细节,希望下次能边学习边记录吧。

本文作者:Zerol Acqua

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!