硅谷直聘笔记

VUE3通信方式:

1.props

  • 实现父给子通信,props数据仍然是只读的
  • 子组件需要使用defineProps方法接受父组件传过来数据
defineProps(['数据1','数据2']);

2.自定义事件

  • 可以实现子给父通信
  • 在vue2中,@click这种写法为自定义事件,可以通过.native修饰符变为原生DOM事件;vue3中为原生DOM事件
  • 在vue3中原生的DOM事件不管是放在标签上、组件标签上都是原生DOM事件
  • 步骤:
(1)在父组件给子组件绑定自定义事件xxx:实现子给父传数据
        <template>
            <Event2 @xxx="handler"></Event2>
        </template>
        <script setup lang='ts'>
            import Event2 from './Event2 .vue'
            let $emit=defineEmits(['xxx']);
            const handler=(p1,p2)=>{
                console.log(p1,p2)
            }
           
        </script>
(2)在子组件中利用defineEmits方法返回函数触发自定义事件
        <template>
            <button @click='handler'>点我出发自定义事件xxx</button>
        </template>
        <script setup lang='ts'>
            let $emit=defineEmits(['xxx']);
            const handler=()=>{
                //第一个参数:事件类型;第2|3|n个为注入数据
                $emit('xxx','数据1','数据2');
            }
           
        </script>

3.全局事件总线

3.1兄弟间通信(child2发,child1收)

  • 使用插件mitt实现
  • 使用方法:
(1)新建bus/indnex.ts
    import mitt form 'mitt';
    const $bus=mitt();
    export default $bus;
(2)在兄弟child1引入$bus
    <script setup lang='ts'>
        import $bus from '../../bus'
        //引入组合式API
        import {onMounted} from 'vue'
        //组件挂在完毕时,当前组件绑定一个事件,接受将来兄弟组件传递的数据
        onMounted(()=>{
            //第一个参数:事件类型  第二个参数:事件回调
            $bus.on('car',(car)=>{
                console.log(car);
            });

        })
     </script>
(3)在兄弟child2
     <template>
        <button @click='handler'>点我给兄弟送法拉利</button>
     </template>
     <script setup lang='ts'>
        import $bus from '../../bus'
        //引入组合式API
        import {onMounted} form 'vue'
        //组件挂在完毕时,当前组件绑定一个事件,接受将来兄弟组件传递的数据
        const handler=()=>{
            //第一个参数:事件类型  第二个参数:事件回调
            $bus.emit('car',{car:'法拉利'});

        }
     </script>

4.v-model

4.1父子组件通信

  • v-model指令:收集表单数据,实现数据双向绑定
<template>
        <input type='text' v-model='info'></input>
</template>
<script setup lang='ts'>
    //引入组合式API
     import {ref} from 'vue'
    let info=ref('');       
</script>
  • props+自定义事件  实现子给父传数据
(1)父组件定义自定义事件update:modelValue
    <template>
        <h1>v-mdel:钱数{{money}}</h1>
        //父给子传props数据:modelValue      自定义事件update:modelValue
        <Child :modelValue="money" @update:modelValue="handler"></Child >
    </template>
    <script setup lang='ts'>
        import Child from './Child .vue'
        import {ref} from 'vue'
        //父组件的数据money
        let money=ref(1000);
        //自定义事件回调
        const handler=(num)=>{
            money.value=num;
         }    
    </script>
(2)子组件
    <template>
        <h1>v-mdel:钱数{{modelValue}}</h1>
        <button @click='handler'>父子组件数据同步</button>
    </template>
    <script setup lang='ts'>
        //接受props
        let props=definProps(['modelValue']);
 
        let $emit=defineEmits(['update:modelValue']);

        //子组件内部按钮点击回调
        const handler=(num)=>{
            //触发自定义事件
            $emit('update:modelValue',props.modelValie+1000);
         }    
    </script>
  • v-model实现父子组件通信
(1)父组件定义自定义事件update:modelValue
    <template>
        <h1>v-mdel:钱数{{money}}</h1>
        //父给子传props数据:modelValue      自定义事件update:modelValue
        //<Child :modelValue="money" @update:modelValue="handler"></Child >
        <Child v-model="money"></Child >
    </template>
    <script setup lang='ts'>
        import Child from './Child .vue'
        import {ref} from 'vue'
        //父组件的数据money
        let money=ref(1000);
        //自定义事件回调
        const handler=(num)=>{
            money.value=num;
         }    
    </script>
(2)子组件child
    <template>
        <h1>v-mdel:钱数{{modelValue}}</h1>
        <button @click='handler'>父子组件数据同步</button>
    </template>
    <script setup lang='ts'>
        //接受props
        let props=definProps(['modelValue']);
 
        let $emit=defineEmits(['update:modelValue']);

        //子组件内部按钮点击回调
        const handler=(num)=>{
            //触发自定义事件
            $emit('update:modelValue',props.modelValie+1000);
         }    
    </script>

v-model在组件身上使用:

        第一:相当于给子组件传递props[modelValue]=10000

        第二:相当于给子组件绑定自定义事件update:modelValue

  • 绑定多个v-model:
(1)父组件定义自定义事件update:modelValue
    <template>
        <h1>v-mdel:钱数{{money}}-{{pageSize}}-{{pageNo}}</h1>
        //父给子传props数据:modelValue      自定义事件update:modelValue
        //<Child :modelValue="money" @update:modelValue="handler"></Child >
        <Child v-model="money"></Child >
        //给子组件传递props数据:pageNo、pageSize  给子组件绑定两个自定义事件:update:pageNo,update:pageSize
        <Child1 v-model:pageNo='pageNo' v-model:pageSize='pageSize'></Child1 >
    </template>
    <script setup lang='ts'>
        import Child from './Child.vue'
        import Child1 from './Child1.vue'
        import {ref} from 'vue'
        //父组件的数据money
        let money=ref(1000);
        //自定义事件回调
        const handler=(num)=>{
            money.value=num;
         }  

        //父组件数据
        let pageNo=ref(1)
        let pageSize=ref(13)
    </script>
(2)子组件child1
    <template>
        <h1>同时绑定多个v-model</h1>
        <button @click="handler">pageNo:{{pageNo}}</button>
        <button @click="$emit('update:pageSize',props.pageSize+4)">pageSize:{{pageSize}}</button>
    </template>
    <script setup lang='ts'>
        //接受props
        let props=definProps(['pageNo','pageSize']);
 
        let $emit=defineEmits(['update:pageNo','update:pageSize']);

        //子组件内部按钮点击回调
        const handler=(num)=>{
            //触发自定义事件
            $emit('update:pageNo',props.pageNo+3);
         }    
    </script>

5.useAttr方法

  • useAttr方法,可以获取组件身上的属性与事件
(1)父组件定义自定义组件
    <template>
        <HintButton type="primary" size="small" :icon="Edit" title="编辑按钮"></HintButton  >
    </template>
    <script setup lang='ts'>
        import HintButton from  './HintButton .vue'
    </script>
(2)子组件HintButton 
    <template>
        <div :title="$attrs.title">
              <el-button :type='$attrs.type'></el-button>
                //上面等同于
              <el-button :='$attrs'></el-button>
        </div>
      
    </template>
    <script setup lang='ts'>
        //接受父组件的数据
        import {useAttrs} from 'vue'
        let $attrs=useAttrs();
  
    </script>

useAttrs不能与props同时拿同一个数据,props优先级更高

6.ref与$parent

 ref:可以获得真实DOM节点,可以获取到子组价实例VC

$parent:可以在子组件内部获取到父组件的实例

  • ref 
(1)父组件
    <template>
        <h1>我是父亲:{{money}}</h1> 
        <Son ref="son"/>
        <button @click="handler">找儿子借十块</button>
    </template>
    <script setup lang='ts'>
        import {ref} from 'vue'
        import Son from './Son .vue'
        //父亲的钱数
        let money=ref(1000)
        //父组件内部按钮点击回调
        const handler=()=>{
            money.value+=10;
            //让儿子的钱数-10
            son.value.money-=10;

            son.value.fly();
         } 
        //获取子组件实例
        let son=ref()
        
    </script>
(2)子组件son 
    <template>
        <h1>我是子组件:{{money}}</h1> 
    </template>
    <script setup lang='ts'>
        import {ref} from 'vue'
        //儿子的钱数
        let money=ref(666)

        //组件内部数据对外关闭,别人不能访问
        //如果想让外部访问,需要用defineExpose方法对外暴露
        defineExpose({
            money,fly
        })

        const fly=()=>{
            console.log("我可以飞");
         } 
  
    </script>
  •  $parent
(1)父组件
    <template>
        <h1>我是父亲:{{money}}</h1> 
        <Son ref="son"/>
        <button @click="handler">找儿子借十块</button>
    </template>
    <script setup lang='ts'>
        import {ref} from 'vue'
        import Son from './Son.vue'
        //父亲的钱数
        let money=ref(1000)
        //获取子组件实例
        let son=ref()
        //父组件内部按钮点击回调
        const handler=()=>{
            money.value+=10;
            //让儿子的钱数-10
            son.value.money-=10;

            son.value.fly();
         } 
     

         //组件内部数据对外关闭,别人不能访问
        //如果想让外部访问,需要用defineExpose方法对外暴露
        defineExpose({
            money
        })
        
    </script>
(2)子组件son 
    <template>
        <h1>我是子组件:{{money}}</h1> 
        <button @click="handler($parent)">点击我爸爸给我10000</button>
    </template>
    <script setup lang='ts'>
        import {ref} from 'vue'
        //儿子的钱数
        let money=ref(999)
        const handler=($parent)=>{
            money.value+=10000;
            $parent.money-=10000;
         } 
  
    </script>

7.provide与Inject

(1)父组件
    <template>
        <h1>我是父亲{{car }}</h1> 
        <Son />
    </template>
    <script setup lang='ts'>
        import Son from './Son.vue'
        //vue3提供provide(提供)与inject(注入),实现隔辈组件传递数据
        import {ref} from 'vue'
        let car =ref('法拉利')

        //祖先给后代提供数据
        //第一个参数:祖先提供的数据key;第二个参数:数据
        provide('TOKEN',car)

    </script>
(2)子组件son 
    <template>
        <h1>我是子组件1</h1> 
        <GrandChild />
    </template>
    <script setup lang='ts'>
        import GrandChild from './GrandChild .vue'

  
    </script>
(3)孙组件GrandChild 
    <template>
        <h1>我是孙子</h1>
        <p>{{car}}</p> 
        <button @click="updateCar">更新数据</button>
    </template>
    <script setup lang='ts'>
        import {inject} from 'vue'
        //注入祖先组件提供的数据
        //参数:祖先提供的数据key
        let car=inject('TOKEN')

        const updateCar=()=>{
            car.value='自行车';
         } 

    </script>

8.pinia

pinia:集中式管理状态容器,可以实现任意组件之间的通信

核心概念:state、actions、getters

 写法:选择式API、组合式API 

  •  选择式API写法
(1)创建大仓库./store/index.ts
        import {createPinia} from 'pinia'
        //createPinia用于创建大仓库
        let store=createPinia();
        //对外暴露,安装仓库
        export default store;
(2)在入口文件引入大仓库
        //引入大仓库
        import store from './store/index.ts'
        //安装大仓库
        app.ues(store)
(3)创建小仓库./store/modules/info.ts
    //定义info小仓库     defineStore用于创建小仓库
    import {defineStore} from 'pinia'
    //defineStore参数:第一个参数:小仓库名字;第二个:小仓库配置对象
    //defineStore方法执行会返回一个函数,函数作用是让组件可以获取到仓库数据
    let useInfoStore=defineStore('info',{
        //存储数据:state
        state:()=>{
            return {
                count:99,
                arr:[1,2,3]
            }
        },
        actions:{
             //函数没有context上下文对象
            updateNum(){
                //console.log(111)
               //console.log(this)
                this.count++;
            
            }
        },
        getters:{
            total(){
                let result=this.arr.reduce((prev:number,next:number)=>{
                    return prev+next;

                },0)
                return result;
            }

        },
     })
    export default useInfoStore;
(1)父组件
    <template>
        <h1>我是父亲</h1> 
        <Son />
    </template>
    <script setup lang='ts'>
        import Son1 from './Son1.vue'
        import Son2 from './Son2.vue'
       
    </script>
(2)子组件son1 
    <template>
        <h1>我是子组件1</h1> 
        <h1>{{infoStore.count}}-{{infoStore.total}}</h1> 
        <button @click="updateCount">点我修改仓库数据</button>

    </template>
    <script setup lang='ts'>
        import useInfoStore from '../../store/modules/info.vue'
        //获取小仓库数据对象
        let infoStore=useInfoStore();
        //修改仓库数据
        const updateCount=()=>{
            //第一种:
            //infoStore.count;
            //第二种:
           // infoStore.$patch({
             //   count:1111
             //})
            //第三种:调用仓库自身方法修改数据
            infoStore.updateNum();
        }
    </script>
(3)子组件son2
    <template>
        <h1>我是子组件2</h1>
        <p>{{infoStore.count}}</p> 
    </template>
    <script setup lang='ts'>
        import useInfoStore from '../../store/modules/info.vue'
        //获取小仓库数据对象
        let infoStore=useInfoStore();

       
    </script>
  • 组合式API写法
(1)创建大仓库./store/index.ts
        import {createPinia} from 'pinia'
        //createPinia用于创建大仓库
        let store=createPinia();
        //对外暴露,安装仓库
        export default store;
(2)在入口文件引入大仓库
        //引入大仓库
        import store from './store/index.ts'
        //安装大仓库
        app.ues(store)
(3)创建小仓库./store/modules/todo.ts
    //定义todo小仓库     defineStore用于创建小仓库
    import {defineStore} from 'pinia'
    import {ref,computed} from 'vue'

    //defineStore参数:第一个参数:小仓库名字;第二个:小仓库配置对象
    //defineStore方法执行会返回一个函数,函数作用是让组件可以获取到仓库数据
    let useTodoStore=defineStore('todo',()=>{
        //务必要返回一个对象:属性与方法可以提供给组件使用
        let todos=ref([{id:1,title:"吃饭"},{id:2,title:"睡觉"}]);
        let arr=[1,2,3];
        const total=computed(()=>{
            arr.value.reduce((prev:number,next:number)=>{
                    return prev+next;

            },0)
        })
        return {
              todos,arr,total,
            updateTodo(){
                todos.value.push({{id:3,title:"组合式API"}})
            }
        }
     })
    export default useTodoStore;
(1)父组件
    <template>
        <h1>我是父亲</h1> 
        <Son />
    </template>
    <script setup lang='ts'>
        import Son1 from './Son1.vue'
        import Son2 from './Son2.vue'
       
    </script>
(2)子组件son1 
    <template>
        <h1>我是子组件1</h1> 
        <h1>{{infoStore.count}}-{{infoStore.total}}</h1> 
        <button @click="updateCount">点我修改仓库数据</button>

    </template>
    <script setup lang='ts'>
        import useInfoStore from '../../store/modules/info.vue'
        //获取小仓库数据对象
        let infoStore=useInfoStore();
        //修改仓库数据
        const updateCount=()=>{
            //第一种:
            //infoStore.count;
            //第二种:
           // infoStore.$patch({
             //   count:1111
             //})
            //第三种:调用仓库自身方法修改数据
            infoStore.updateNum();
        }
    </script>
(3)子组件son2
    <template>
        <h1>我是子组件2</h1>
        <p>{{infoStore.count}}</p> 
        <p  @click="updateTodo">{{todoStore.todos}}-{{todoStore.total}}</p>
    </template>
    <script setup lang='ts'>
        import useInfoStore from '../../store/modules/info.vue'
        import useTodoStore from '../../store/modules/todo.vue'
        //获取小仓库数据对象
        let infoStore=useInfoStore();
        let todoStore=useTodoStore ();

        const updateTodo=()=>{
            todoStore.updateTodo();
        }
       
    </script>

 9.插槽

插槽:默认插槽、具名插槽、作用域插槽

作用域插槽:可以传递数据的插槽,子组件可以将数据回传给父组件,父组件可以决定这些回传的数据是以何种结构或外观在子组件内部去展示

  •  默认插槽、具名插槽
(1)父组件
    <template>
        <h1>我是父亲</h1> 
        <Son>
            <div>
               //给子组件传递
            <h1>xxxxxx</h1>
            </div>
            //具名插槽填充 v-slot指令可以简化为#
             <template v-slot:a>
                    <div>
                        //给子组件传递
                        <h3>填充具名插槽a</h3>
                    </div>
             </template>
            <template v-slot:b>
                    <div>
                        //给子组件传递
                        <h3>填充具名插槽b</h3>
                    </div>
             </template>
             
        </Son >
    </template>
    <script setup lang='ts'>
        import Son from './Son.vue'
       
    </script>
(2)子组件son 
    <template>
        <h1>我是子组件1</h1> 
        //默认插槽
        <slot></slot>
        <h1>具名插槽1</h1>
        <slot name="a"></slot>
        <h1>具名插槽2</h1>
        <slot name="b"></slot>

    </template>
    <script setup lang='ts'>
        
    </script>
  •  作用域插槽
(1)父组件
    <template>
        <h1>我是父亲</h1> 
        <Son :todos="totos">
             //父组件接受
             <template v-slot='{$row,$index}'>
                    <span>{{$row.title}}---{{$index}}</span>
             </template>
           
             
        </Son >
    </template>
    <script setup lang='ts'>
        import Son from './Son.vue'
        import {ref} from 'vue'
        let todos=ref([{id:1,title:"吃饭",done:false},{id:2,title:"睡觉",done:false}])
    </script>
(2)子组件son 
    <template>
        <h1>{{todos}}</h1> 
        <ul>
            <li v-for='(item,index) in todos' :key='item.id'>
                //把数据回传给父组件
                <slot :$row='item' :$index='index'></slot>
            </li>
        </ul>
       
    </template>
    <script setup lang='ts'>
        defineProps(["todos"])
    </script>

 硅谷直聘

1.项目初始化

1.1创建项目

npm create vite@latest

 1.2 安装代码检测工具eslint

  • 安装

 npm i eslint -D

  •  生成配置文件 .eslint.cjs

npx eslint --init

  •  修改.eslint.cjs
  • 新建.eslintignore文件
dist
node_modules
  • 新增运行脚本 
"scripts": {
    "lint": "eslint src",
    "fix": "eslint src --fix"
  },

1.3配置格式化工具prettier

  • 安装依赖

npm i -D eslint-plugin-prettier prettier eslint-config-prettier

  •  新建.prettierrc.json添加规则
{
  "singleQuote": true,
  "semi": false,
  "bracketSpacing": true,
  "htmlWhitespaceSensitivity": "ignore",
  "endOfLine": "auto",
  "trailingComma": "all",
  "tabWidth": 2
}
  • 新建.prettierignore文件
/dist/*
/html/*
.local
/node_modules/**
**/*.svg
**/*.sh
/public/*

1.4配置CSS校验工具stylelint

  • 安装依赖

npm add sass sass-loader stylelint postcss postcss-scss postcss-html stylelint-config-prettier stylelint-config-recess-order stylelint-config-recommended-scss stylelint-config-standard stylelint-config-standard-vue stylelint-scss stylelint-order stylelint-config-standard-scss -D  --legacy-peer-deps

  •  新建.stylelintrc.cjs配置文件
  • 添加运行脚本
"scripts": {
 "format": "prettier --write \"./**/*.{html,vue,ts,js,json,md}\"",
    "lint:eslint": "eslint src/**/*.{ts,vue} --cache --fix",
    "lint:style": "stylelint src/**/*.{css,scss,vue} --cache --fix",
}

1.5配置husky(强制让开发人员按照代码规范来提交)

  • 安装

npm i husky -D --legacy-peer-deps

  •  生成配置文件

git init

npx husky-init

  •  修改pre-commit文件

npm test改为npm run format

1.6配置commitlint(让每个人按照同一标砖来执行commit信息) 

  • 安装

npm add @commitlint/config-conventional @commitlint/cli -D --legacy-peer-deps

  • 添加配置文件,新建`commitlint.config.cjs` 
  • 添加运行脚本
"scripts": {
    "commitlint": "commitlint --config commitlint.config.cjs -e -V"
}
  • 配置husky

npx husky add .husky/commit-msg

  • 在生成的commit-msg文件中添加下面的命令
npm commitlint

1.7强制使用npm包管理器工具

  • 在根目录创建`scritps/preinstall.js`文件,添加下面的内容

if (!/npm/.test(process.env.npm_execpath || '')) {
    console.warn(
        `\u001b[33mThis repository must using npm as the package manager ` +
        ` for scripts to work properly.\u001b[39m\n`,
    )
    process.exit(1)
}
  • 配置命令
"scripts": {
  "preinstall": "node ./scripts/preinstall.js"
}

1.8硅谷甄选运营平台,UI组件库采用的element-plus

  • 安装依赖

npm install element-plus @element-plus/icons-vue --legacy-peer-deps

  •  修改入口文件
import { createApp } from 'vue'
import App from './App.vue'
// 入口文件main.ts全局安装element-plus,element-plus默认支持语言英语设置为中文
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css'

//@ts-ignore忽略当前文件ts类型的检测否则有红色提示(打包会失败)
import zhCn from 'element-plus/dist/locale/zh-cn.mjs'

//获取应用实例
const app = createApp(App);
//将应用挂载到挂载点上
app.mount('#app')
//安装element-plus插件
app.use(ElementPlus, {
    locale: zhCn
})

1.9配置src别名

//vite.config.ts
import {defineConfig} from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
export default defineConfig({
    plugins: [vue()],
    resolve: {
        alias: {
            "@": path.resolve("./src") // 相对路径别名配置,使用 @ 代替 src
        }
    }
})
// tsconfig.json
{
  "compilerOptions": {
    "baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
    "paths": { //路径映射,相对于baseUrl
      "@/*": ["src/*"] 
    }
  }
}

1.10配置环境变量

项目根目录分别添加 开发、生产和测试环境的文件!

```

.env.development

.env.production

.env.test

```

文件内容

```

# 变量必须以 VITE_ 为前缀才能暴露给外部读取

NODE_ENV = 'development'

VITE_APP_TITLE = '硅谷甄选运营平台'

VITE_APP_BASE_API = '/dev-api'

```

```

NODE_ENV = 'production'

VITE_APP_TITLE = '硅谷甄选运营平台'

VITE_APP_BASE_API = '/prod-api'

```

```

# 变量必须以 VITE_ 为前缀才能暴露给外部读取

NODE_ENV = 'test'

VITE_APP_TITLE = '硅谷甄选运营平台'

VITE_APP_BASE_API = '/test-api'

```

配置运行命令:package.json

```

 "scripts": {

    "dev": "vite --open",

    "build:test": "vue-tsc && vite build --mode test",

    "build:pro": "vue-tsc && vite build --mode production",

    "preview": "vite preview"

  },

```

通过import.meta.env获取环境变量

1.11SVG图标配置 

  • 安装依赖

 npm install vite-plugin-svg-icons -D --legacy-peer-deps

  • 在`vite.config.ts`中配置插件 
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
import path from 'path'
export default () => {
  return {
    plugins: [
      createSvgIconsPlugin({
        // Specify the icon folder to be cached
        iconDirs: [path.resolve(process.cwd(), 'src/assets/icons')],
        // Specify symbolId format
        symbolId: 'icon-[dir]-[name]',
      }),
    ],
  }
}
  •  入口文件导入
import 'virtual:svg-icons-register'
  •  使用
<template>
    <div>
        <svg>
            <use xlink:href="#icon-vue"></use>
        </svg>
    </div>
</template>

1.12定义SvgIcon组件

  • 定义 '@/components/SvgIcon/index.vue'
<template>
    <svg :style="{ width, height }">
        <use :xlink:href="prefix + name" :fill="color"></use>
    </svg>
</template>

<script setup lang="ts">
defineProps({
    //xlink:href属性前缀
    prefix: {
        type: String,
        default: '#icon-'

    },
    //提供使用的图标名字
    name: String,
    //接受父组件的颜色
    color: {
        type: String,
        default: ''
    },
    //接受父组件的宽度
    width: {
        type: String,
        default: '30px'
    },
    //接受父组件的高度
    height: {
        type: String,
        default: '30px'
    }
})
</script>

<style scoped></style>
  • 在main.ts注册SvgIcon为全局组件
import SvgIcon from '@/components/SvgIcon/index.vue'
app.component('SvgIcon', SvgIcon)

1.13定义并使用所有全局组件

  • 新建src\components\index.ts 
//@\components\index.ts
//引入项目中的全部全局组件
import SvgIcon from '@/components/SvgIcon/index.vue'
import Pagination from '@/components/Pagination/index.vue'
//全局对象
const allGlobalComponent: any = { SvgIcon, Pagination }
//对外暴露插件对象
export default {
    install(app: any) {
        //注册项目全局组件
        Object.keys(allGlobalComponent).forEach(key => {
            app.component(key, allGlobalComponent[key])
        })
    }
}
  • main.ts引入安装
//引入自定义插件:注册整个项目的全局组件
import globalComponent from '@/components/index';
//安装自定义插件
app.use(globalComponent)

1.14 集成sass

在src/styles目录下创建一个index.scss文件,当然项目中需要用到清除默认样式,因此在index.scss引入reset.scss

```

@import reset.scss

```

在入口文件引入

```

import '@/styles'

```

但是在src/styles/index.scss全局样式文件中没有办法使用$变量.因此需要给项目中引入全局变量$.在style/variable.scss创建一个variable.scss文件!

在vite.config.ts文件配置如下:

```

export default defineConfig((config) => {

  css: {

      preprocessorOptions: {

        scss: {

          javascriptEnabled: true,

          additionalData: '@import "./src/styles/variable.scss";',

        },

      },

    },

  }

}

```

1.15 mock数据

  •  安装依赖

npm install -D vite-plugin-mock@2.9.6 mockjs --legacy-peer-deps

  •  在 vite.config.js 配置文件启用插件。
import { UserConfigExport, ConfigEnv } from 'vite'
import { viteMockServe } from 'vite-plugin-mock'
import vue from '@vitejs/plugin-vue'
export default ({ command })=> {
  return {
    plugins: [
      vue(),
      viteMockServe({
        localEnabled: command === 'serve',
      }),
    ],
  }
}
  • 在根目录创建mock/user.ts文件夹:去创建我们需要mock数据与接口

//用户信息数据
function createUserList() {
    return [
        {
            userId: 1,
            avatar:
                'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
            username: 'admin',
            password: '111111',
            desc: '平台管理员',
            roles: ['平台管理员'],
            buttons: ['cuser.detail'],
            routes: ['home'],
            token: 'Admin Token',
        },
        {
            userId: 2,
            avatar:
                'https://wpimg.wallstcn.com/f778738c-e4f8-4870-b634-56703b4acafe.gif',
            username: 'system',
            password: '111111',
            desc: '系统管理员',
            roles: ['系统管理员'],
            buttons: ['cuser.detail', 'cuser.user'],
            routes: ['home'],
            token: 'System Token',
        },
    ]
}

export default [
    // 用户登录接口
    {
        url: '/api/user/login',//请求地址
        method: 'post',//请求方式
        response: ({ body }) => {
            //获取请求体携带过来的用户名与密码
            const { username, password } = body;
            //调用获取用户信息函数,用于判断是否有此用户
            const checkUser = createUserList().find(
                (item) => item.username === username && item.password === password,
            )
            //没有用户返回失败信息
            if (!checkUser) {
                return { code: 201, data: { message: '账号或者密码不正确' } }
            }
            //如果有返回成功信息
            const { token } = checkUser
            return { code: 200, data: { token } }
        },
    },
    // 获取用户信息
    {
        url: '/api/user/info',
        method: 'get',
        response: (request) => {
            //获取请求头携带token
            const token = request.headers.token;
            //查看用户信息是否包含有次token用户
            const checkUser = createUserList().find((item) => item.token === token)
            //没有返回失败的信息
            if (!checkUser) {
                return { code: 201, data: { message: '获取用户信息失败' } }
            }
            //如果有返回成功信息
            return { code: 200, data: {checkUser} }
        },
    },
]

1.16 axios

  • 安装依赖

npm i axios --legacy-peer-deps

  • 二次封装axios 使用他的请求和响应拦截器 新建src\utils\request.ts
// 二次封装axios 使用他的请求和响应拦截器 
import axios from 'axios';
import { ElMessage } from 'element-plus';
// 第一步:利用axios对象的create方法,创建axios实例(可以配置其他配置:请求路径,超时时间)
let request = axios.create({
    //基础路径,基础路径上会携带API
    baseURL: import.meta.env.VITE_APP_BASE_API,
    //超时时间
    timeout: 5000,
});
//第二部:给request实例添加请求与响应拦截器
request.interceptors.request.use((config) => {
    //config配置对象,headers属性请求头,经常给服务器端携带公共参数
    //返回配置对象
    return config
});
//第三步:响应拦截器
request.interceptors.response.use((response) => {
    //成功回调
    //简化数据
    return response.data;
},
    //失败回调:处理http网络错误的
    (error) => {
        //定义一个变量:存储网络错误信息
        let message = '';
        //http状态码
        const status = error.response.status;
        switch (status) {
            case 401:
                message = 'TOKEN过期'
                break
            case 403:
                message = '无权访问'
                break
            case 404:
                message = '请求地址错误'
                break
            case 500:
                message = '服务器出现问题'
                break
            default:
                message = '网络出现问题'
                break
        };
        //提示错误信息
        /* ElMessage({
            type: "error",
            message,
        }); */
        ElMessage({
            type: "error",
            message,
        });
        return Promise.reject(error);
    }
)
//对外暴露
export default request

import { ElMessage } from 'element-plus';会飘红

tsconfing.json文件添加如下代码:
{
          "compilerOptions": {
    // 若TS版本为5.x,此处为"node"
    "moduleResolution": "node",
    "types": ["element-plus/global"]
        }
}

  • 测试request
<script setup lang="ts">
import request from '@/utils/request';
import { onMounted } from 'vue';
import axios from 'axios';

onMounted(() => {
    //第一种 返回数据
    request.post('/user/login', {
        username: 'admin',
        password: '111111'
    }).then(res => {
        console.log(res);
    });
    //第2种 返回promise
    const res2 = request.post('/user/login', {
        username: 'admin',
        password: '111111'
    });
    console.log(res2);
    //第3种 返回promise
    const res1 = axios.post('/api/user/login', {
        username: 'admin',
        password: '111111'
    }).then();
    console.log(res1)

})
</script>

 1.17 API接口统一管理

  • 新建src/api/user/index.ts
//用户接口
import request from "@/utils/request";
import type { loginForm, loginResponseData, userResponseData } from "./type";
//统一管理接口 ,枚举接口
enum API {
    LOGIN_URL = "/user/login",
    USERINFO_URL = "/user/info"
}
// 暴露请求函数
//登录接口方法
export const reqLogin = (data: loginForm) => request.post<any, loginResponseData>(API.LOGIN_URL, data)
// 获取用户信息
export const reqUserInfo = () => request.get<any, userResponseData>(API.USERINFO_URL)
  • 参数类型 新建src/api/user/type.ts
//登录接口需要携带参数ts类型
export interface loginForm {
    username: string,
    password: string
}


//登录接口返回ts类型
interface dataType {
    token: string
}
export interface loginResponseData {
    code: number,
    data: dataType
}

//用户信息接口返回ts类型
interface userInfo {
    userId: number,
    avatar: string,
    username: string,
    password: string,
    desc: string,
    roles: string[],
    buttons: string[],
    routes: string[],
    token: string,
}
interface user {
    checkUser: userInfo
}
export interface userResponseData {
    code: number,
    data: user
}
  • 测试接口
<script setup lang="ts">
import { onMounted } from 'vue';
import { reqLogin } from './api/user'

onMounted(() => {
    reqLogin({
        username: 'admin',
        password: '111111',
    })
})
</script>

2. 模板路由配置

  • 安装路由

npm i vue-router --legacy-peer-deps

  • 新建路由页面:

src\views\login\index.vue         src\views\home\index.vue         src\views\404\index.vue

<template>
    <div>
        登录 一级
    </div>
</template>

<script setup lang="ts">

</script>

<style scoped></style>
  • 创建路由
// src\router\index.ts
//通过vue-router实现路由配置
import { createRouter, createWebHashHistory } from "vue-router";

//创建路由器
let router = createRouter({
    //路由模式hash
    history: createWebHashHistory(),
    routes: [
        {
            //登录
            path: '/login',
            component: () => import('@/views/login/index.vue'),
            name: 'login',
        },
        {
            //登录成功后展示数据的路由
            path: '/',
            component: () => import('@/views/home/index.vue'),
            name: 'layout',
        },
        {
            //404
            path: '/404',
            component: () => import('@/views/404/index.vue'),
            name: '404',
        },
        {
            //任意路由  以上路由都不能匹配
            path: '/:paMatch(.*)*',
            redirect: '/404',
            name: 'any',
        },
    ],
    //滚动行为
    scrollBehavior() {
        return {
            left: 0,
            top: 0
        }
    }
})

export default router;
  • 在APP.vue写路由出口测试
<template>
    <div>
        <router-view></router-view>>
    </div>
</template>

3.登录页面

3.1登录布局

  • el-input需要v-model绑定后才能进行输入删除

import { reactive } from 'vue'
//收集账号与密码数据
let loginForm = reactive({ username: 'admin', password: '111111' })

3.2登录回调

  • 安装仓库pinia

npm i pinia --legacy-peer-deps

  • 新建大仓库src/store/index.ts 
//仓库大仓库
import { createPinia } from 'pinia'
//创建大仓库
let pinia = createPinia();

export default pinia;
  • 在main.js引入并注册 
//引入大仓库
import pinia from './store';
//安装仓库
app.use(pinia)
  • 创建用户小仓库src/store/modules/user.ts 
//创建用户相关小仓库
import { defineStore } from 'pinia'
import { reqLogin } from '@/api/user';
import type { loginForm } from '@/api/user/type';
//创建用户小仓库
let useUserStore = defineStore('User', {
    //小仓库存数据的地方
    state: () => {
        return {
            token: localStorage.getItem('TOKEN'),
        }
    },
    //处理异步逻辑的地方
    actions: {
        //用户登录
        async userLogin(data: loginForm) {
            let result: any = await reqLogin(data);
            if (result.code == 200) {
                //pinia仓库存储一下token
                this.token = result.data.token;
                //由于pinia|vuex存储数据其实利用js对象,则还应本地存储一份
                localStorage.setItem('TOKEN', result.data.token)
                return 'ok'

            } else {
                return Promise.reject(new Error(result.data.message))
            }

        }
    },
    getters: {

    }
})

export default useUserStore;
  •  点击button登录
<script setup lang="ts">
import { reactive, ref } from 'vue'
import { User, Lock } from '@element-plus/icons-vue';
import { useRouter } from 'vue-router';
import { ElNotification } from 'element-plus';
//引入小仓库
import useUserStore from '@/store/modules/user';
let useStore = useUserStore();
//定义变量控制按钮加载效果
let loading = ref(false);
//获取路由器
let $router = useRouter();
//收集账号与密码数据
let loginForm = reactive({ username: 'admin', password: '111111' })
//登录回调
const login = async () => {
    //登录按钮加载效果
    loading.value = true
    //通知仓库发登录请求
    //请求成功->首页展示数据
    //请求失败->弹出失败信息
    try {
        //保证登陆成功
        await useStore.userLogin(loginForm);
        //编程式导航跳转到展示数据首页
        $router.push('/');
        //登录成功提示信息
        ElNotification({
            type: 'success',
            message: '登陆成功'
        })
         //登录成功按钮加载效果消失
         loading.value = false;
    } catch (error) {
        //登录失败按钮加载效果消失
        loading.value = false;
        ElNotification({
            type: 'error',
            message: (error as Error).message
        })
    }

}
</script>

3.3表单校验

<el-form class="login_form" :model="loginForm" :rules="rules"> 
    //表单内容
</el-form>
//自定义校验需要的函数
const validatorUserName = (_: any, value: any, callback: any) => {
    //rule:即为校验规则对象
    //value:即为表单元素文本内容
    //函数:如果符合条件callBack放行通过即为
    //如果不符合条件callBack方法,注入错误提示信息
    if (value.length >= 5) {
        callback();
    } else {
        callback(new Error('账号长度至少五位'));
    }
}
const validatorPassword = (_: any, value: any, callback: any) => {
    //rule:即为校验规则对象
    //value:即为表单元素文本内容
    //函数:如果符合条件callBack放行通过即为
    //如果不符合条件callBack方法,注入错误提示信息
    if (value.length >= 6) {
        callback();
    } else {
        callback(new Error('密码长度至少六位'));
    }
}
//表单校验规则
const rules = {
    //规则对象属性:
    //required,代表这个字段务必要校验的
    //min:文本长度至少多少位
    //max:文本长度最多多少位
    //message:错误的提示信息
    //trigger:触发校验表单的时机 change->文本发生变化触发校验,blur:失去焦点的时候触发校验规则
     // username: [{ required: true, min: 6, max: 10, message: '用户名最少为6位', trigger: 'change' }],
    // password: [{ required: true, min: 6, max: 15, message: '密码最少为6位', trigger: 'change' }],

    // 自定义校验
    username: [{ trigger: 'change', validator: validatorUserName }],
    password: [{ trigger: 'change', validator: validatorPassword }],
}
  • 发请求前保证所有表单校验通过

//登录回调
const login = async () => {
    //发请求前保证所有表单校验通过
    await loginForms.value.validate();
    //登录按钮加载效果
    loading.value = true
    //通知仓库发登录请求
    //请求成功->首页展示数据
    //请求失败->弹出失败信息
    try {
        //保证登陆成功
        await useStore.userLogin(loginForm);
        //编程式导航跳转到展示数据首页
        $router.push('/');
        //登录成功提示信息
        ElNotification({
            type: 'success',
            message: '登陆成功',
            title: `HI,${getTime()}好`
        })
        //登录成功按钮加载效果消失
        loading.value = false;
    } catch (error) {
        //登录失败按钮加载效果消失
        loading.value = false;
        ElNotification({
            type: 'error',
            message: (error as Error).message
        })
    }

}

4.一级路由layout

  • 新建src/layout/index.vue取代 src\views\home\index.vue
<template>
    <div class="layout_container">
        <!-- 左侧菜单 -->
        <div class="layout_slider"></div>
        <!-- 顶部导航 -->
        <div class="layout_tabbar"></div>
        <!-- 内容展示区域 -->
        <div class="layout_main"></div>
    </div>
</template>

4.1封装logo组件

  • 新建src/layout/logo/index.vue
<template>
    <div class="logo" v-if="setting.logoHidden">
        <img :src="setting.logo">
        <p>{{ setting.title }}</p>
    </div>
</template>

<script setup lang="ts">
import setting from '@/setting.ts'
</script>

4.2封装左侧menu组件(根据路由动态生成菜单)

4.2.1左侧菜单静态页面
  • src/layout/index.vue
<template>
    <div class="layout_container">
        <!-- 左侧菜单 -->
        <div class="layout_slider">
            <Logo></Logo>
            <!-- 展示菜单 -->
            <!-- 滚动组件 -->
            <el-scrollbar class="scrollBar">
                <!-- 菜单组件 -->
                <el-menu background-color="#001529" text-color="white">
                    <el-menu-item index="1">首页</el-menu-item>
                    <el-menu-item index="2">数据大屏</el-menu-item>
                    <!-- 折叠菜单 -->
                    <el-sub-menu index="3">
                        <template #title>
                            <span>权限管理</span>
                        </template>
                        <el-menu-item index="3-1">用户管理</el-menu-item>
                        <el-menu-item index="3-2">角色管理</el-menu-item>
                        <el-menu-item index="3-3">菜单管理</el-menu-item>
                    </el-sub-menu>
                </el-menu>
            </el-scrollbar>
        </div>
        <!-- 顶部导航 -->
        <div class="layout_tabbar"></div>
        <!-- 内容展示区域 -->
        <div class="layout_main">

        </div>
    </div>
</template>
4.2.2 创建二级路由

然后将路由数组引入user仓库,供layout组件使用

 {
            //登录成功后展示数据的路由
            path: '/',
            component: () => import('@/layout/index.vue'),
            name: 'layout',
            children: [
                {
                    path: '/home',
                    component: () => import("@/views/home/index.vue")

                }
            ]
},
4.2.3根据路由动态生成菜单
  • src/layout/index.vue传送路由数组
<template>
    <div class="layout_container">
        <!-- 左侧菜单 -->
        <div class="layout_slider">
            <Logo></Logo>
            <!-- 展示菜单 -->
            <!-- 滚动组件 -->
            <el-scrollbar class="scrollBar">
                <!-- 菜单组件 -->
                <el-menu background-color="#001529" text-color="white">
                    <!-- 根据路由动态生成菜单 -->
                    <Menu :menuList="userStore.menuRoutes"></Menu>
                </el-menu>
            </el-scrollbar>
        </div>
        <!-- 顶部导航 -->
        <div class="layout_tabbar"></div>
        <!-- 内容展示区域 -->
        <div class="layout_main">

        </div>
    </div>
</template>
  • src/layout/menu/index.vue接受路由数组并递归生成菜单
<template>
    <template v-for="(item, index) in menuList" :key="item.path">
        <!-- 没有子路由 -->
        <el-menu-item v-if="!item.children" :index="item.path">
            <template #title>
                <span>{{ item.meta.title }}</span>
            </template>
        </el-menu-item>
        <!-- 一个子路由 -->
        <el-menu-item v-if="item.children && item.children.length == 1" :index="item.children[0].path">
            <template #title>
                <span>{{ item.children[0].meta.title }}</span>
            </template>
        </el-menu-item>
        <!-- 多个子路由 -->
        <el-sub-menu v-if="item.children && item.children.length > 1" :index="item.path">
            <template #title>
                <span>{{ item.meta.title }}</span>
            </template>
            <!-- 如果有多级路由则再次进行递归 -->
            <Menu :menuList="item.children"></Menu>
        </el-sub-menu>
    </template>
</template>

<script setup lang="ts">
//获取父组件layout传递的路由数组
defineProps(['menuList']);
</script>
<script lang="ts">
export default {
    name: "Menu",
}
</script>
<style scoped></style>

5.完善左侧菜单栏

5.1新建路由

  • 先新建src/view/screen/index.vue、src/view/acl、src/view/product的页面
  • 然后routes.ts添加路由
  • 最后在layput/index.vue添加路由出口
  • <template>
        <div class="layout_container">
            <!-- 左侧菜单 -->
            <div class="layout_slider">
                <Logo></Logo>
                <!-- 展示菜单 -->
                <!-- 滚动组件 -->
                <el-scrollbar class="scrollBar">
                    <!-- 菜单组件 -->
                    <el-menu background-color="#001529" text-color="white">
                        <!-- 根据路由动态生成菜单 -->
                        <Menu :menuList="userStore.menuRoutes"></Menu>
                    </el-menu>
                </el-scrollbar>
            </div>
            <!-- 顶部导航 -->
            <div class="layout_tabbar"></div>
            <!-- 内容展示区域 -->
            <div class="layout_main">
                <router-view></router-view>
            </div>
        </div>
    </template>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值