前端项目搭建记录

一 项目创建

        1.1  没有nodejs 请安装

https://nodejs.org/zh-cn/

        1.2 查看nodejs版本

node --version

        1.3 查看npm版本

npm --version

 前端页面使用vs code

        1.4 使用vite 创建项目

        vue网站

https://cn.vuejs.org/

                1.4.1 全局安装vite

npm config set registry=https://registry.npmmirror.com 使用国内源
npm  install -g  vite@latest

               1.4.2 通过vite 创建项目

npm create vite@latest demo -- --template vue-ts

                1.4.3 使用vs code 打开终端 运行命令

npm install
npm run dev

                       结果:

二 完善项目

        2.1 vue-router的使用

        2.1.1 安装vue-router

npm install vue-router@4

        2.1.2 src/router/index.ts

                2.1.2.1 先简单在src下创建三个vue界面

        

        2.1.2.2 创建src/router/index.ts
import { createMemoryHistory, createRouter } from 'vue-router'


const routes = [
  { path: '/', component: () => import("../views/product/ListProduct.vue") },
  {
    path: '/product',
    children: [
      {
        path: "lisProduct",
        component: () => import("../views/product/ListProduct.vue")
      },
      {
        path: "addProduct",
        component: () => import("../views/product/AddProduct.vue")
      },
      {
        path: "editProduct",
        component: () => import("../views/product/EditProduct.vue")
      }
    ]
  },
]

const router = createRouter({
  history: createMemoryHistory(),
  routes,
})

export default router;
        2.1.2.3 在main.tx文件中加入路由
import { createApp } from 'vue'
import App from './App.vue'
/* 导入路由 */
import router from './router/index'


const app = createApp(App)
/* 使用路由 */
app.use(router)
app.mount("#app")

        2.1.3 在App.vue中使用

<script setup lang="ts">

</script>

<template>
    <router-link to="/product/listProduct">商品列表界面</router-link>
    <router-link to="/product/addProduct">添加商品界面</router-link>
    <router-link to="/product/editProduct">修改商品界面</router-link>
    <router-view></router-view>
</template>

<style scoped>

</style>

        穿插一个AddProduct.vue

<template >
 <h1>添加商品</h1>
</template>
<script setup lang="ts"></script>

        2.1.4 发现问题

   商品列表信息未正常展示,问题: listProduct 手写错误。注意写代码需要细心,中午少吃个鸡腿

        2.2 使用别名 @直接指向'src'

        安装@types/node

npm install @types/node --save-dev

        @types/node 包允许您在TypeScript项目中使用Node.js的核心模块和API,并提供了对它们的类型检查和智能提示的支持。

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from "path";

export default defineConfig({
  plugins: [vue()],
  server: {
     host: '0.0.0.0', //允许的主机头

     port: 5000, // 你需要定义的端口号

 }
 resolve: {
  // 配置路径别名, @就代表当前项目的绝对路径 
  // __dirname是一个全局变量,表示当前模块所属目录的绝对路径
  // path.resolve返回一个以相对于当前的工作目录(working directory)的绝对路径, 
  // 比如当前工作目录为 D:\205\wms-web 那么 @ 就代表 D:\205\wms-web\src
  alias: {
    '@': path.resolve(__dirname, './src'),
  }
},

})

        问题:使用@标红

        解决:

        在在tsconfig.app.json 添加如下代码.

"baseUrl": ".",
    "paths": {
      "@/*": ["src/*"]
    }

        2.3 导入Elementui-plus

npm install element-plus --save

        2.3.1elementui-plus icons 全局安装图标

npm install @element-plus/icons-vue

        2.3.2 main.ts 使用element-plus

//使用ElementPlus插件
import ElementPlus from 'element-plus'
//引用ElementPlus样式
import 'element-plus/dist/index.css'
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
const app = createApp(App)

//全局导入饿了么图标
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
  app.component(key, component)
}
//使用ElementPlus插件
app.use(ElementPlus)

2.4axios 异步请求 

        2.4.1 安装

npm install axios

        官网:Axios中文文档 | Axios中文网

        2.3.2 统一封装类 src/http/index.ts


import axios from 'axios'
import { ElLoading,ElMessage  } from 'element-plus'


let loading:any;
class Http {
    myAxios: any;
    constructor(config: any) {
        this.myAxios = axios.create(config);
        // 添加请求拦截器
        this.myAxios.interceptors.request.use(function (config:any) {
            //显示loading层
             loading = ElLoading.service({
                lock: true,
                text: 'Loading',
                background: 'rgba(0, 0, 0, 0.7)',
              })
            
            return config;
        }, function (error:any) {
           // 对请求错误做些什么
           loading.close();
            return Promise.reject(error);
        });
        // 添加响应拦截器
        this.myAxios.interceptors.response.use(function (response:any) { 
            //关闭loading层
            loading.close();
            const {code,msg,data} = response.data

           if(code === 0){
             return data;
           } else if (code == undefined){
            return response;
           } else if(code != 0){
             ElMessage.error(msg)
             return Promise.reject(msg);
           }

            
        }, function (error:any) {
            // 对响应错误做点什么  
            loading.close();       
            return Promise.reject(error);
        });
    }
    get<T>(url: string, params?: object, data = {}): Promise<any> {
        return this.myAxios.get(url, { params, ...data });
    }

    post<T>(url: string, params?: object, data = {}): Promise<any> {
        return this.myAxios.post(url, params, data);
    }

    put<T>(url: string, params?: object, data = {}): Promise<any> {
        return this.myAxios.put(url, params, data);
    }

    delete<T>(url: string, params?: object, data = {}): Promise<any> {
        return this.myAxios.delete(url, { params, ...data });
    }

}
const config = {
    baseURL: '',
    timeout: 30 * 1000,
    withCredentials: true,
}

export default new Http(config);

        2.3.3Proxy配置

  server: {

    port: 5000, // 你需要定义的端口号

    proxy: {
      "/api": {
        target: "Api地址",
        changeOrigin: true,
     
      },
    
    },
 },

        2.3.4 使用

import http from "@/http/index";

onMounted(()=>{

 http.get(
        "/api/products"
      )
      .then((res: any) => {

        tableData.value = res.data;

      })
      .catch((err: any) => {
        console.log(err);
      });

})

三 其他

        3.1 listProduct 

<template>
  <h1>商品列表</h1>
  <!-- 查询 -->
  <div>
    <el-form :inline="true" :model="formData" class="demo-form-inline">
      <el-form-item label="请输入关键字">
        <el-input v-model="formData.name" placeholder="请输入" clearable />
      </el-form-item>

      <el-form-item>
        <el-button type="primary" @click="searchProduct">查询</el-button>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="add">添加</el-button>
      </el-form-item>
    </el-form>


  </div>
  <div>
    <el-table :data="tableData" style="width: 100%">
      <el-table-column prop="name" label="商品名称" width="120" />
      <el-table-column label="商品图片" width="120">
        <template #default="scope">
          <!-- 假设你的图片路径存储在scope.row.img中 -->
          <img :src="scope.row.img" alt="商品图片" style="width: 100%; height: auto; display: block;">
        </template>
      </el-table-column>
      <el-table-column prop="status" label="商品状态" width="120" />
      <el-table-column prop="lastUpdateBy" label="更新人" width="600" />
      <el-table-column fixed="right" label="操作" min-width="120">
        <template #default="scope">
          <!-- 编辑使用对话框 -->
          <el-button type="primary" @click="editProduct(scope.row.id)">
            编辑
          </el-button>


          <!-- 是否确认删除 -->
          <el-popconfirm confirm-button-text="Yes" cancel-button-text="No" icon-color="#626AEF" title="是否确认删除?"
            @confirm="deleteProductById(scope.row.id)" @cancel="cancelDelete">
            <template #reference>
              <el-button type="danger">删除</el-button>
            </template>
          </el-popconfirm>
        </template>
      </el-table-column>
    </el-table>
  </div>

  <!-- 分页 -->
  <div style="margin-left: 600px;margin-top: 30px">
    <!-- 分页 -->

    <el-pagination size="large" background layout="prev, pager, next" :total="pageData.total" class="mt-4"
      :page-size="pageData.pageSize" :current-page="pageData.pageNum" @current-change="changePage" />
  </div>

  <!-- 弹出框 -->
  <el-dialog v-model="dialogFormVisible" title="编辑商品信息" width="500">
    <el-form :model="formProduct" label-width="auto" style="max-width: 600px">
      <el-form-item label="商品名称">
        <el-input v-model="formProduct.name" :value="formProduct.name" />
      </el-form-item>
      <!-- 图片 -->
      <el-form-item label="商品图片上传">
        <el-upload class="avatar-uploader" action="/api/upload" :show-file-list="false"
          :on-success="handleAvatarSuccess" :before-upload="beforeAvatarUpload">
          <img v-if="imageUrl" :src="imageUrl" class="avatar" style="width: 120px;" />
          <el-icon v-else class="avatar-uploader-icon">
            <Plus />
          </el-icon>
        </el-upload>
      </el-form-item>
      <el-form-item label="状态">
        <el-radio-group v-model="formProduct.status" :value="formProduct.status">
          <el-radio :value="1">上架</el-radio>
          <el-radio :value="2">未上架</el-radio>
        </el-radio-group>
      </el-form-item>
      <el-form-item>
        <el-button type="primary" @click="methodXXX">确认</el-button>
        <el-button type="primary" @click="updateProductQX">取消</el-button>
      </el-form-item>
    </el-form>
  </el-dialog>
</template>
<script setup lang="ts">
import { onMounted, reactive, ref } from 'vue';
import { ElMessage } from 'element-plus'
import { Plus } from '@element-plus/icons-vue'
import type { UploadProps } from 'element-plus'

/* 导入异步 */
import productApi from "@/api/productApi";
const tableData = ref<any>([])
/* 分页数据模型 */
const pageData = reactive({
  total: 0,
  pageSize: 5,
  pageNum: 1
})
/* 表单数据 */
const formData = reactive({
  name: ""
})
/* 条件查询 */
const searchProduct = () => {
  callProductApi()
}
/* 查询方法 */
const callProductApi = () => {
  let name = formData.name == '' ? undefined : formData.name
  productApi.select.call({ pageNum: pageData.pageNum, pageSize: pageData.pageSize, name: name }).then((res: any) => {
    console.log(res);
    pageData.total = res.total;
    tableData.value = res.items
  })
}
onMounted(() => {
  callProductApi();
});
const changePage = (pageNum: number) => {
  console.log(pageNum);
  pageData.pageNum = pageNum;
  callProductApi()
}

const cancelDelete = () => {
  ElMessage('删除已取消.')
}

/* 点击删除 */
const deleteProductById = (id: number) => {
  productApi.delete.call({ id: id }).then((res: any) => {
    console.log(res);
    ElMessage({
      message: '删除成功.',
      type: 'success',
    })
    /* 删除成功后刷新界面 再次调用查询方法 */
    callProductApi()

  })
}
/* 根据商品id查询商品信息 */
const selectProductById = async (id: number) => {
  await productApi.selectProductById.call({ id: id }).then((res: any) => {
    console.log(res);
    /* 获取信息进行表单赋值 */
    formProduct.id = res.id
    formProduct.name = res.name
    formProduct.img = res.img
    imageUrl.value = res.img
    formProduct.status = res.status;
    formProduct.lastUpdateBy = res.lastUpdateBy
  })
}
/* 编辑 */
const dialogFormVisible = ref(false)
// do not use same name with ref
const formProduct = reactive({
  name: '',
  id: '',
  img: '',
  status: 1,
  seq: '',
  parentId: '',
  lastUpdateBy: '',
  lastUpdateTime: ''
})
/* 点击编辑按钮 */
const editProduct = async (id: number) => {
  await selectProductById(id);
  dialogFormVisible.value = true
}
const updateProduct = () => {
  console.log(formProduct);
  /* 点击确认修改 */
  productApi.edit.call({ id: formProduct.id, name: formProduct.name, img: formProduct.img, status: formProduct.status }).then((res: any) => {
    ElMessage.success('修改成功')
    console.log(res);

  })
  /* 修改完成 */
  dialogFormVisible.value = false
  formProduct.img = "";
  formProduct.name = '';

  /* 完成修改 刷新视图 */
  callProductApi()
}

/* 修改点击取消 */
const updateProductQX = () => {
  ElMessage.success('取消修改'),
    dialogFormVisible.value = false
}


/* 商品图片上传 */
const imageUrl = ref('')
const handleAvatarSuccess: UploadProps['onSuccess'] = (
  response,
  uploadFile
) => {
  imageUrl.value = URL.createObjectURL(uploadFile.raw!)
  console.log(response.data);
  formProduct.img = response.data

}
const beforeAvatarUpload: UploadProps['beforeUpload'] = (rawFile) => {
  if (rawFile.type !== 'image/jpeg') {
    ElMessage.error('Avatar picture must be JPG format!')
    return false
  } else if (rawFile.size / 1024 / 1024 > 2) {
    ElMessage.error('Avatar picture size can not exceed 2MB!')
    return false
  }
  return true
}
/* 添加个方法用于中间判断需要进行的是添加还是修改 */
const methodXXX = () => {
  if (formProduct.id == null || formProduct.id == '') {
    addProduct()
  } else {
    updateProduct
  }
}
/* 添加 */
const add = () => {
  /* 复用弹出框 */
  /* 先清空一下表单内容 */
  formProduct.id = '';
  formProduct.img = '';
  imageUrl.value = '';
  formProduct.lastUpdateBy = '';
  formProduct.name = '';
  formProduct.status = 1;
  dialogFormVisible.value = true

}
/* 点击确认添加 */
const addProduct = async () => {
  console.log("添加");

  await productApi.add.call({ name: formProduct.name, img: formProduct.img, status: formProduct.status, parentId: 6, lastUpdateBy: "admin" }).then((res: any) => {
    console.log(res);
    ElMessage.success('添加成功')
  })
  /* 先清空一下表单内容 */
  formProduct.id = '';
  formProduct.img = '';
  formProduct.lastUpdateBy = '';
  formProduct.name = '';
  formProduct.status = 1;
  dialogFormVisible.value = false
}
</script>

<style scoped>
.avatar-uploader .el-upload {
  border: 1px dashed var(--el-border-color);
  border-radius: 6px;
  cursor: pointer;
  position: relative;
  overflow: hidden;
  transition: var(--el-transition-duration-fast);
}

.avatar-uploader .el-upload:hover {
  border-color: var(--el-color-primary);
}

.el-icon.avatar-uploader-icon {
  font-size: 28px;
  color: #8c939d;
  width: 178px;
  height: 178px;
  text-align: center;
}
</style>

        3.2 APP.vue导航页

<script setup lang="ts">

/* 左侧菜单 */

/* 头 */
import { ref } from 'vue'

const activeIndex = ref('1')
const handleSelect = (key: string, keyPath: string[]) => {
  console.log(key, keyPath)
}

/* 导入导航 */
import Menu from './components/Menu.vue';

import { useCounterStore } from "@/store/pinia"
const store = useCounterStore()

</script>

<template>
  <router-link target="_blank" :to="{ path: '/login' }">打开新的标签页</router-link>
  <!--  -->
  <div class="common-layout">
    <el-container>
      <el-header>
        <!-- 页头 -->
        <el-menu :default-active="activeIndex" class="el-menu-demo" mode="horizontal" :ellipsis="false"
          @select="handleSelect">
          <el-menu-item index="0">
            <img style="width: 100px" src="./assets/image.png" alt="Element logo" />
          </el-menu-item>
          <div class="flex-grow" />
          <el-menu-item index="1">{{ store.name }}</el-menu-item>
          <el-sub-menu index="2">
            <template #title>Workspace</template>
            <el-menu-item index="2-1">item one</el-menu-item>
            <el-menu-item index="2-2">item two</el-menu-item>
            <el-menu-item index="2-3">item three</el-menu-item>
            <el-sub-menu index="2-4">
              <template #title>item four</template>
              <el-menu-item index="2-4-1">item one</el-menu-item>
              <el-menu-item index="2-4-2">item two</el-menu-item>
              <el-menu-item index="2-4-3">item three</el-menu-item>
            </el-sub-menu>
          </el-sub-menu>
        </el-menu>
      </el-header>
      <el-container>
        <!-- 左侧菜单 -->
        <Menu></Menu>
        <el-main> <router-view></router-view></el-main>
      </el-container>
    </el-container>
  </div>
</template>

<style scoped>
.flex-grow {
  flex-grow: 1;
}
</style>

3.3 菜单自动生成

<template>
  <el-menu class="el-menu--demo" @open="handleOpen" :router="true" @close="handleClose" :default-active="$route.path"
    :collapse="store.menuIsCollapse">
    <div style="text-align: center;" @click="changeMenu">
      <component is="More" class="icon"></component>
    </div>

    <template v-for="item1 in $router.options.routes " :key="item1">
      <!-- 有二级目录 -->
      <el-sub-menu v-if="item1.children != undefined && item1.meta?.isShow != false" :index="item1.path">
        <template #title>
          <component :is="item1.meta?.icon" class="icon"></component>
          <span>{{ item1.meta?.title }}</span>
        </template>

        <template v-for="item2 in item1.children" :key="item2">
          <el-menu-item :index="item1.path + '/' + item2.path" v-if="item2.meta?.isShow != false">
            <component :is="item2.meta?.icon" class="icon"></component>{{ item2.meta?.title }}
          </el-menu-item>

        </template>
      </el-sub-menu>
      <!-- 没有二级目录 -->
      <el-menu-item :index="item1.path" v-if="item1.children == undefined && item1.meta?.isShow != false">
        <component :is="item1.meta?.icon" class="icon"></component>
        <span>{{ item1.meta?.title }}</span>
      </el-menu-item>
    </template>

  </el-menu>
</template>

<script setup lang="ts">
import { useCounterStore } from "@/store/pinia"
const store = useCounterStore()
/* 左侧菜单 */
const handleOpen = (key: string, keyPath: string[]) => {
  console.log(key, keyPath)
}
const handleClose = (key: string, keyPath: string[]) => {
  console.log(key, keyPath)
}
/* 左侧标题缩放 */
const changeMenu = () => {
  /* 取反 */

  store.menuIsCollapse = !store.menuIsCollapse
}
</script>

<style scoped>
.icon {
  height: 15px;
  width: 15px;
  margin-right: 5px;
}
</style>

 自动生成菜单

<template>
  <el-menu class="el-menu--demo" @open="handleOpen" :router="true" @close="handleClose" :default-active="$route.path"
    :collapse="store.menuIsCollapse">
    <div style="text-align: center;" @click="changeMenu">
      <component is="More" class="icon"></component>
    </div>

    <template v-for="item1 in $router.options.routes " :key="item1">
      <!-- 有二级目录 -->
      <el-sub-menu v-if="item1.children != undefined && item1.meta?.isShow != false" :index="item1.path">
        <template #title>
          <component :is="item1.meta?.icon" class="icon"></component>
          <span>{{ item1.meta?.title }}</span>
        </template>

        <template v-for="item2 in item1.children" :key="item2">
          <el-menu-item :index="item1.path + '/' + item2.path" v-if="item2.meta?.isShow != false">
            <component :is="item2.meta?.icon" class="icon"></component>{{ item2.meta?.title }}
          </el-menu-item>

        </template>
      </el-sub-menu>
      <!-- 没有二级目录 -->
      <el-menu-item :index="item1.path" v-if="item1.children == undefined && item1.meta?.isShow != false">
        <component :is="item1.meta?.icon" class="icon"></component>
        <span>{{ item1.meta?.title }}</span>
      </el-menu-item>
    </template>

  </el-menu>
</template>

<script setup lang="ts">
import { useCounterStore } from "@/store/pinia"
const store = useCounterStore()
/* 左侧菜单 */
const handleOpen = (key: string, keyPath: string[]) => {
  console.log(key, keyPath)
}
const handleClose = (key: string, keyPath: string[]) => {
  console.log(key, keyPath)
}
/* 左侧标题缩放 */
const changeMenu = () => {
  /* 取反 */

  store.menuIsCollapse = !store.menuIsCollapse
}
</script>

<style scoped>
.icon {
  height: 15px;
  width: 15px;
  margin-right: 5px;
}
</style>

3.4 pinia状态管理

        Pinia | The intuitive store for Vue.js

         持久化存储为什么你应该使用该插件? | pinia-plugin-persistedstate

        3.4.1 安装

npm install pinia   
npm install pinia-plugin-persistedstate

       创建文件 store/pinia.ts

//引入pina
import { createPinia,defineStore } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)

export default pinia

 const wmsStore = defineStore('wmsStore', {
  state: () => {
    return {
      name: '游客',
      isMenuCollapse:false
    }
  },
  persist: {
    key: 'wms-store',
    storage: localStorage,
  },
})

export  {wmsStore}

 在main中引入pinia组件

//引入pina
import pinia from '@/store/pinia'

const app = createApp(App)

app.use(pinia)

        使用

 import  { wmsStore } from '@/store/pinia'
 const wmsstore = wmsStore()

onMounted(()=>{
  console.log(wmsstore.name);

  wmsstore.name = "张三"
 
  console.log(wmsstore.name);

 })

        在实际中pinia值的获取一般实在登录中 如下

<script setup lang="ts">
    /* 导入pinia */
import { wmsStore} from '@/store/userInfo';
const userInfo = wmsStore()

const submitForm = (formEl: any) => {

        const md5PassWord = Md5.hashStr(loginForm.password).toUpperCase();
        userApi.login.call({ tel: loginForm.tel, email: loginForm.email, nickname: loginForm.nickname, password: md5PassWord })
          .then((res => {
            /* pinia存储一下登录信息 */
            let code: any = jwtDecode(res)
            console.log(code.roles);
            userInfo.nickname = code.nickname;
            userInfo.id = code.id;
            userInfo.token = res;
            userInfo.roles = code.roles
            ElMessage.success(res.message ? res.message : '成功');
            console.log("成功跳转");
            router.push("/product/listProduct");
          }))
      } 

</script>

3.5 router统一请求管理

        http/index.ts 对请求和响应进行统一处理 如给向后端发送的请求添加token 或 统一处理错误请求

使用 拦截器 | Axios中文文档 | Axios中文网

import axios from "axios"
import { ElLoading, ElMessage } from 'element-plus'
import { createUserInfo } from "@/store/userInfo";
const userInfo = createUserInfo()
let loading: any;
class Http {
  myAxios: any;
  constructor(config: any) {
    this.myAxios = axios.create(config);
    // 添加请求拦截器
    this.myAxios.interceptors.request.use(function (config: any) {
      if (userInfo.token) {
        config.headers.token = userInfo.token
      }
      //显示loading层
      loading = ElLoading.service({
        lock: true,
        text: 'Loading',
        background: 'rgba(0, 0, 0, 0.7)',
      })

      return config;
    }, function (error: any) {
      // 对请求错误做些什么
      loading.close();
      return Promise.reject(error);
    });
    // 添加响应拦截器
    this.myAxios.interceptors.response.use(function (response: any) {
      //关闭loading层
      loading.close();
      const { code, message, data } = response.data

      if (code === 0) {
        return data;
      } else if (code == undefined) {
        return response;
      } else if (code != 0) {
        ElMessage.error(message)
        return Promise.reject(message);
      }


    }, function (error: any) {
      // 对响应错误做点什么  
      loading.close();
      return Promise.reject(error);
    });
  }
  get<_T>(url: string, params?: object, data = {}): Promise<any> {
    return this.myAxios.get(url, { params, ...data });
  }

  post<_T>(url: string, params?: object, data = {}): Promise<any> {
    return this.myAxios.post(url, params, data);
  }

  put<_T>(url: string, params?: object, data = {}): Promise<any> {
    return this.myAxios.put(url, params, data);
  }

  delete<_T>(url: string, params?: object, data = {}): Promise<any> {
    return this.myAxios.delete(url, { params, ...data });
  }

}
const config = {
  baseURL: '',
  timeout: 30 * 1000,
  withCredentials: true,
}

export default new Http(config);

3.6 使用自定义指令 通过权限控制界面的显示与隐藏

        角色信息存储到token-roles中 

        定义一个组件 auth.ts 

import { createUserInfo } from "@/store/userInfo";
// 判断用户是否有某个角色的函数
const hasRoles = (roles: any) => {
  const pinaRoles: any = createUserInfo().roles;
  if (Array.isArray(roles)) {
    return roles.some(role => pinaRoles.includes(role));
  } else if (typeof roles === 'string') {
    return pinaRoles.includes(roles);
  } else {
    return false

  }
}
// 创建自定义指令
export default {
  mounted(el: HTMLElement, binding: any) {
    if (binding.arg === 'role') {
      if (!hasRoles(binding.value)) {
        el.remove()
      }
    }
  },
};

                在pinia中

        修改菜单组件 在每一个目录相应的添加 v-auth:role="item1.meta?.roles"

<template>
  <el-menu class="el-menu--demo" @open="handleOpen" :router="true" @close="handleClose" :default-active="$route.path"
    :collapse="store.menuIsCollapse">
    <div style="text-align: center;" @click="changeMenu">
      <component is="More" class="icon"></component>
    </div>

    <template v-for="item1 in $router.options.routes " :key="item1">
      <!-- 有二级目录 -->
      <el-sub-menu v-if="item1.children != undefined && item1.meta?.isShow != false" :index="item1.path"
        v-auth:role="item1.meta?.roles">
        <template #title>
          <component :is="item1.meta?.icon" class="icon"></component>
          <span>{{ item1.meta?.title }}</span>
        </template>

        <template v-for="item2 in item1.children" :key="item2">
          <el-menu-item :index="item1.path + '/' + item2.path" v-if="item2.meta?.isShow != false"
            v-auth:role="item2.meta?.roles">
            <component :is="item2.meta?.icon" class="icon"></component>{{ item2.meta?.title }}
          </el-menu-item>
        </template>
      </el-sub-menu>
      <!-- 没有二级目录 -->
      <el-menu-item :index="item1.path" v-if="item1.children == undefined && item1.meta?.isShow != false"
        v-auth:role="item1.meta?.roles">
        <component :is="item1.meta?.icon" class="icon"></component>
        <span>{{ item1.meta?.title }}</span>
      </el-menu-item>
    </template>
  </el-menu>
</template>

最后在main.ts中引入组件:

import auth from "@/directives/auth"
app.directive('auth', auth) 

3.7 MD5 加密

导入md5

npm install --save ts-md5

使用md5加密

import { Md5 } from 'ts-md5';
//md5加密后的密码
const md5Pwd=Md5.hashStr("123456").toUpperCase();

3.8 前端解析token

        安装插件

npm install jwt-decode --save

        引入

 import {jwtDecode} from 'jwt-decode'

const code = jwtDecode(res.data.data.accessToken)
console.log(code)// 就可以解析成功了

3.9 富文本编辑器

        wangEditor

安装

npm install @wangeditor/editor-for-vue@next --save

引入界面

2. 在引用页面加入如下代码
 
<template>
  <div style="border: 1px solid #ccc">
    <Toolbar style="border-bottom: 1px solid #ccc" :editor="editorRef" />
    <Editor style="height:300px; overflow-y: hidden;" v-model="modeValue" @onCreated="handleCreated" />
  </div>
</template>
<script setup lang="ts">
import '@wangeditor/editor/dist/css/style.css' // 引入 css
import { ref, shallowRef, defineModel } from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'

const modeValue = defineModel()
// 编辑器实例,必须用 shallowRef
const editorRef = shallowRef()
// 内容 HTML

const handleCreated = (editor: any) => {
  editorRef.value = editor // 记录 editor 实例,重要!
}

</script>

3.10 日期插件 dayjs   Day.js中文网

 Day.js中文网 

安装 npm install dayjs --save

使用

import * as dayjs from 'dayjs'

dayjs().format() 
// 默认返回的是 ISO8601 格式字符串 '2020-04-02T08:02:17-05:00'

dayjs('2019-01-25').format('[YYYYescape] YYYY-MM-DDTHH:mm:ssZ[Z]') 
// 'YYYYescape 2019-01-25T00:00:00-02:00Z'

dayjs('2019-01-25').format('DD/MM/YYYY') 

 Day.js中文网表格日期格式化:

<el-table-column prop="lastUpdateBy" label="最近操作人" :formatter="formatter" width="380"/>
const formatter = (row: any, column: any, cellValue: any, index: any) => {

    if (column.property === "createTime") {
        return dayjs(cellValue).format('YYYY-MM-DD HH:mm')
    }
    if (column.property === "name") {
        if (cellValue.length > 4) {
            return cellValue.substring(0, 4) + "...";
        } else {
            return cellValue;
        }
    }

}

3.11 省市区组件

element-china-area-data - npm  

安装 npm install element-china-area-data -S

vue <el-cascader :options="pcaTextArr" v-model="formData.PCC"> </el-cascader>

typescript

import {  pcaTextArr } from "element-china-area-data";

  formData.province = formData.PCC[0]
  formData.city = formData.PCC[1]
  formData.county = formData.PCC[2]

3.12 后端bigInt 类型返回前端精度丢失问题

1. 安装json-bigint插件

npm install json-bigint

2. 加入如下transformResponse配置

import JSONBig from 'json-bigint'
  const config = {
    baseURL: "",
    timeout: 30 * 1000,
    withCredentials: true,
    transformResponse:[
      function (data:any) {
        const json = JSONBig({
          storeAsString: true
        })
        return json.parse(data)
      }
    ]
  };

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值