前段时间冲浪的时候发现了一个五笔字根练习的小项目(yhf7952/WubiGame),感觉还挺有意思,遂抄。它本身只是一个前端小项目,是原作者学习 Vue 的时候写的第一个项目。那我也就顺便用这个项目学习一点 Vue 的知识喽。
原项目的二级简码表有一些缺漏和错字,我进行了编辑修改。实际上由于不同输入法的实现和码表存在差异,因此二级简码有部分码字是不同的。一些表的部分二级简码对应优先对应词组,一些简码理论上能打出单字却没有对应码字(空码),因而我也无法给出完全正确的二级简码表。最后我综合几个码表,选择了空码最多(也即和其他码表定义冲突最小)的码表。
原项目显示字根采用了特殊字体。通过引用字体中字根对应的字符,即可实现字根显示(和图标字体显示应该是同样的原理)。但是字体中的字根也存在错误,需要修改。在线查看字体,可访问该网站,字体编辑可使用 FontCreator。
在原仓库的基础上
Vue2 升级到 Vue3element 升级到 element-plus。TypeScript 代替 jsPug 代替 html。tsimport { 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-components 和 unplugin-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。
使用 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
库的变化
要引入新的组件库,参考:https://element-plus.org/zh-CN/guide/quickstart.html
兄弟组件之间通信一般采用全局组件通信的方案,并不推荐借助父组件中转的通信。
常用的全局组件通信方法包括:
由于本项目是一个很小的 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),当尺寸在不同大小时,展示不同的卡片数量:
pugdiv //- 根据大小屏幕显示不同的 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 还是需要引入文件:
tsimport '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-container 的 display 是 flex ,故可以
css.el-container {
   min-height: 100vh;
}
目前项目已归档,不会再做更改
草草写了一篇博文进度记录,细致度和上一篇差很多。中间拖了太久,甚至后面那个 next.js 项目也完成了,导致遗忘了不少细节,希望下次能边学习边记录吧。
本文作者:Zerol Acqua
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!