半成品的后台管理系统

324 阅读6分钟

vue-ts-cms

一、配置环境

  • 为.vue文件声明一个组件类型

    //env.d.ts
    declare module "*.vue" {
      import { DefineComponent } from "vue";
    
      const component: DefineComponent
      export default component
    }
    

    作用:防止导入的组件都是any类型,有更好的代码提示

  • 当某个东西版本过低时

    • 卸载之后重新安装

      //卸载
      npm uninstall vue-tsc
      //安装
      npm install vue-tsc -D
      
    • 可能还安装不上最新的版本

      原因:npm在本地发现已有的版本

      解决:

      //强制把本地缓存清空
      npm cache clean --force
      //安装
      npm install vue-tsc -D
      

二、代码规范

2.1.集成editorconfig配置

Editorconfig有助于为不同IDE编辑器上处理同一项目的多个开发人员维护一致的编码风格。

//.editorconfig
# http://editorconfig.org

root = true

[*] # 表示所有文件适用
charset = utf-8 # 设置文件字符集为utf-8
indent_style = space # 缩进风格(tab | space)
indent_size = 2 # 缩进大小
end_of_line = lf # 控制换行类型(lf | cr | crlf)
trim_trailing_whitespace = true # 去除行尾的任意空白字符
insert_final_newline = true # 始终在文件末尾插入一个新行

[*.md] # 表示仅md文件适用以下规则
max_line_length = off
trim_trailing_whitespace = false

VSCode需要安装一个插件:Editorconfig for VS Code

2.2.使用prettier工具

Prettier是一款强大的代码格式化工具,支持js,ts,css,scss,jsx,angular,vue,graphql, json,md等语言,基本上前端能用到的文件格式它都能搞定,是当前最流行的代码格式化工具。

1.安装prettier

npm install prettier -D

2.配置.prettier文件

  • useTabs: 使用tab缩进还是空格缩进,选择false;

  • tabWidth:tab是空格的情况下是几个空格,选择2个;

  • printWidth:当前字符的长度,推荐80,也可以100或者120;

  • singleQuote:使用单引号还是双引号,选择true,使用单引号;

  • traillingComma:在多行输入的尾逗号是否添加,设置为none,比如对象类型的最后一个属性是否添加一个逗号;

  • semi:语句末尾是否要加分号,默认值true,选择false表示不加;

  • //.prettier
    {
      "useTabs": false,
      "tabWidth": 2,
      "printWidth": 100,
      "singleQuote": true,
      "trailingComma": "none",
      "bracketSpacing": true,
      "semi": false
    }
    

3.创建.prettierignore忽略文件

/dist/*
.local
.output.js
/node_modules/**

**/*.svg
**/*.sh

/public/*

4.安装VS Code插件Prettier - Code formatter,并且在设置中搜索Editor: Default Formatter选择Prettier,format on save=>勾选

2.3.使用ESlint

1.在前面创建项目的时候,我们选择了ESlint,所以Vue会默认帮助我们配置需要的ESlint环境。

2.安装ESlint的插件

3.解决ESlint和prettier冲突的问题

安装插件:(vue在创建项目时,如果选择prettier,那么这两个插件会默认安装)

npm install eslint-plugin-prettier -D  elsint -config-prettier -D

4.在.eslintrc.cjs中进行配置

  extends: [
    'plugin:vue/vue3-essential',
    'eslint:recommended',
    '@vue/eslint-config-typescript',
    '@vue/eslint-config-prettier/skip-formatting',
      
    'plugin:prettier/recommended'
  ]

三、目录结构及样式重置

3.1.目录结构

3.2.样式重置

1.引入normalize.css文件

npm install normalize.css

2.在main.js中进行导入

import 'normalize.css'

3.自己手写一些重置样式

四、路由配置

4.1.安装路由

npm install vue-route

4.2.使用router

import { createRouter, createWebHashHistory } from 'vue-router'

const router = createRouter({
  history: createWebHashHistory(),
  routes: []
})

export default router

4.3.在main.ts进行挂载

import router from './router'

createApp(App).use(router).mount('#app')

4.4.自行配置路径关系

使用懒加载

const router = createRouter({
  history: createWebHashHistory(),
  routes: [
    {
      path: '/',
      redirect: '/main'
    },
    {
      path: '/login',
      component: () => import('../views/login/Login.vue')
    },
    {
      path: '/main',
      component: () => import('../views/main/Main.vue')
    },
    {
     //未找到的路径
      path: '/:pathMatch(.*)',
      component: () => import('../views/not-found/NotFound.vue')
    }
  ]
})

4.5.在App.vue中进行占位及跳转

    <router-link to="/main">主要</router-link>
    <router-link to="/login">登录</router-link>
    <router-view></router-view>

五、状态管理(pinia)

5.1.安装pinia

npm install pinia

5.2.创建pinia

import { createPinia } from 'pinia'

const pinia = createPinia()

export default pinia

5.3.挂载pinia

import pinia from './store'

createApp(App).use(router).use(pinia).mount('#app')

5.4.创建store

import { defineStore } from 'pinia'

const useCounterStore = defineStore('counter', {
  state: () => ({}),
  getters: {},
  actions: {}
})

export default useCounterStore

5.5.使用store

import useCounterStore from '@/store/counter'

const counterStore = useCounterStore()

六、axios的使用

6.1.安装axios

npm install axios

6.2.用ts二次封装axios

前面已封装

七、Element-Plus集成

7.1.安装Element-Plus

 npm install element-plus --save

7.2.引入方式

  • 完整引入

    // main.ts
    import { createApp } from 'vue'
    import ElementPlus from 'element-plus'
    //引入CSS
    import 'element-plus/dist/index.css'
    import App from './App.vue'
    
    const app = createApp(App)
    
    app.use(ElementPlus)
    app.mount('#app')
    

    缺点:打包后的文件将会变大。

    优点:使用组件时直接导入就行,方便快捷。

  • 自动导入

    首先你需要安装unplugin-vue-componentsunplugin-auto-import这两款插件

    npm install -D unplugin-vue-components unplugin-auto-import
    

    将下列代码插入到你的 ViteWebpack 的配置文件中

    • Vite
      // vite.config.ts
      import { defineConfig } from 'vite'
      import AutoImport from 'unplugin-auto-import/vite'
      import Components from 'unplugin-vue-components/vite'
      import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
      
      export default defineConfig({
        // ...
        plugins: [
          // ...
          AutoImport({
            resolvers: [ElementPlusResolver()],
          }),
          Components({
            resolvers: [ElementPlusResolver()],
          }),
        ],
      })
      
    • Webpack
      // webpack.config.js
      const AutoImport = require('unplugin-auto-import/webpack')
      const Components = require('unplugin-vue-components/webpack')
      const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')
      
      module.exports = {
        // ...
        plugins: [
          AutoImport({
            resolvers: [ElementPlusResolver()],
          }),
          Components({
            resolvers: [ElementPlusResolver()],
          }),
        ],
      }
      
    • 使其生成的两个文件生效

      auto-imports.d.ts "components.d.ts

    //tsconfig.json
      "include": [
        "env.d.ts",
        "src/**/*",
        "src/**/*.vue",
        "auto-imports.d.ts",
        "components.d.ts"
      ]
    

八、登录界面的开发

8.1.让登录框占满整个屏幕

  • 不推荐
//最终组件都是要在app内,所以要设置app,htmlbody的占比
//index.html
<style>
#app, html, body {
    height: 100%;
}
</style>
  • 推荐
//App.vue
.App {
  width: 100vw;
  height: 100vh;
}

8.2.框

先通过element-plus搭建出基本框架

    <div class="tabs">
      <el-tabs type="border-card" stretch>
        <el-tab-pane label="账号登录">
          <div></div>
          <div></div>
        </el-tab-pane>
        <el-tab-pane label="手机登录">
          <div></div>
          <div></div>
        </el-tab-pane>
      </el-tabs>
    </div>

填充icons

同样需要导入icons

npm install @element-plus/icons-vue
  • 同上有两种导入方式,如果你想像用例一样直接,你需要全局注册组件,才能够直接在项目里使用。

    • 全局注册

      // main.ts
      
      // 如果您正在使用CDN引入,请删除下面一行。
      import * as ElementPlusIconsVue from '@element-plus/icons-vue'
      
      const app = createApp(App)
      for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
        app.component(key, component)
      }
      
    • 自动导入

      使用 unplugin-icons 和 unplugin-auto-import 从 iconify 中自动导入任何图标集
      
  • 全局注册方式

    //main.ts
    import registerIcons from './global/register-icons'
    
    app.use(registerIcons)
    
    //register-icons
    import type { App } from 'vue'
    import * as ElementPlusIconsVue from '@element-plus/icons-vue'
    function registerIcons(app: App<Element>) {
      for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
          //注册全局组件
        app.component(key, component)
      }
    }
    export default registerIcons
    
  • icons使用

        <div class="tabs">
          <el-tabs type="border-card" stretch>
            <el-tab-pane label="账号登录">
              <template #label>
                <div class="label">
                  <span class="label-icon">
                    <el-icon><UserFilled /></el-icon>
                  </span>
                  <span class="text">账号登陆</span>
                </div>
              </template>
            </el-tab-pane>
            <el-tab-pane label="手机登录">
              <template #label>
                <div class="label">
                  <el-icon><Iphone /></el-icon>
                  <span class="text">手机登录</span>
                </div>
              </template>
            </el-tab-pane>
          </el-tabs>
        </div>
    

8.3.底部

<!-- 底部 -->
<div class="cotrols">
    <el-checkbox v-model="checked1" label="记住密码" size="large" />
    <el-link type="primary">忘记密码</el-link>
</div>
<el-button class="login-btn" type="primary" size="large">立即登录</el-button>

8.4.监听登录方式

组件之间提供了v-model和name属性

<el-tabs type="border-card" stretch v-model="activename">
<el-tab-pane label="账号登录" name="account">
<el-tab-pane label="手机登录" name="phone">
    
//监听登录按钮的点击
function loginbtnclcik() {
  if (activename.value === 'account') {
    console.log('用户进行账号登录')
  } else {
    console.log('用户进行密码登录')
  }
}

8.5.表单的功能实现

账号登录表单

使用插件提供的表单验证

  • //内容实现
    <template>
      <div class="panel-account">
        <el-form
          :model="account"
          :rules="accountRules"
          label-width="60px"
          size="large"
        >
          <el-form-item label="账号" prop="name">
            <el-input v-model="account.name" />
          </el-form-item>
          <el-form-item label="密码" prop="password">
            <el-input v-model="account.password" show-password />
          </el-form-item>
        </el-form>
      </div>
    </template>
    
    //rules实现
    const accountRules: FormRules = {
      name: [
        { required: true, message: '必须输入账号信息~', trigger: 'blur' },
        {
          pattern: /^[a-z0-9]{6,20}$/,
          message: '必须是6~20位数字或字母组成',
          trigger: 'change'
        }
      ],
      password: [
        { required: true, message: '必须输入密码信息~', trigger: 'blur' },
        {
          pattern: /^[a-z0-9]{3}/,
          message: '必须是3位以上数字或密码组成',
          trigger: 'change'
        }
      ]
    }
    //要想使其能够输入需要变量来记录绑定,所以要在item上进行绑定
    const account = reactive({
      name: '',
      password: ''
    })
    
    //message表示未满足时的信息
    //trigger表示触发时机,change表示改变时触发,blur表示失去焦点时触发
    
    //要想让其生效
    //要将accountRules绑定到form大表单上,再在item上绑定prop="属性"
    

8.6.将账号密码传递给父组件

  • 在子组件中定义一个方法并且将其暴露出去

    • //子组件
      function loginAction() {
        console.log(account.name, account.password)
      }
      defineExpose({
        loginAction
      })
      
      //父组件
      //绑定一个Ref
      <panel-account ref="accountRef" />
      
      //因为PanelAccount相当于一个类,所创建的组件都是实例对象,但PanelAccount是一个值不能作为类型,所以要进行类型的转换 
      //typeof PanelAccount拿到构造器
      const accountRef = ref<InstanceType<typeof PanelAccount>>()
      
  • 点击登录登录判断是否符合规则

    <el-form ref="formRef">
    import type { FormRules, ElForm } from 'element-plus'
    
    const formRef = ref<InstanceType<typeof ElForm>>()
     //组件内部提供的方法                                
    function loginAction() {
      formRef.value?.validate((valid) => {
        if (valid) {
          console.log('验证成功')
        } else {
          console.log('验证失败')
        }
      })
    }
    
    • 验证失败时弹出弹窗

        formRef.value?.validate((valid) => {
          if (valid) {
            console.log('验证成功')
          } else {
            ElMessage({
              showClose: true,
              message: 'Oops, 请您输入正确格式',
              type: 'error'
            })
          }
        })
      
      //未显示原因是未引入ElMessage的样式
      //解决一
      //main.ts
      //全局引入样式
      import 'element-plus/dist/index.css
      
      //解决二
      //针对ElMessage和ElLoading进行引入
      import 'element-plus/theme-chalk/el-message.css'
      
      //解决三
      //使用插件
      //1.安装插件
      npm i -D vite-plugin-style-import
      npm install consola -D(依赖包)
      
      //vite.config.ts
      import {
        createStyleImportPlugin,
        ElementPlusResolve
      } from 'vite-plugin-style-import'
      //设置plugin
      createStyleImportPlugin({
            resolves: [ElementPlusResolve()],
            libs: [
              {
                libraryName: 'element-plus',
                esModule: true,
                resolveStyle: (name: string) => {
                  return `element-plus/theme-chalk/${name}.css`
                }
              }
            ]
          })
      

8.7.用JS实现登录验证

function loginAction() {
  formRef.value?.validate((valid) => {
    if (valid) {
      //1.获取用户输入的账号和密码
      const name = account.name
      const password = account.password
      //2.向服务器发送网络请求(携带账号密码)
      // accountLogin({ name, password }).then((res) => {
      //   console.log(res)
      // })
      if (name === 'icebin' && password == '123456') {
        ;(loginStore.id = '1'), (loginStore.name = 'icebin')
        localCache.setCache('name', name)
        localCache.setCache('password', password)
        router.push('/main')
      }
    } else {
      ElMessage({
        showClose: true,
        message: 'Oops, 请您输入正确格式',
        type: 'error'
      })
    }
  })
}

//自行封装的一个本地存储方式的类
enum CacheType {
  local,
  session
}

class Cache {
  storage: Storage
  constructor(type: CacheType) {
    this.storage = type === CacheType.local ? localStorage : sessionStorage
  }
  setCache(key: string, value: any) {
    if (value) {
      this.storage.setItem(key, JSON.stringify(value))
    }
  }
  getCache(key: string) {
    const value = this.storage.getItem(key)
    if (value) {
      return JSON.parse(value)
    }
  }

  removeCache(key: string) {
    this.storage.removeItem(key)
  }
  clear() {
    this.storage.clear()
  }
}

const localCache = new Cache(CacheType.local)
const sessionCache = new Cache(CacheType.session)

export { localCache, sessionCache }