一 项目创建
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
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 或 统一处理错误请求
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 富文本编辑器
安装
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中文网
安装 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 省市区组件
安装 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)
}
]
};