Vite+vue3+ts
创建Vue3项目
检查工程文件夹
cmd进入文件夹
npm init vite(初始化工程)
安装依赖
npm install
启动项目
npm run dev
setup语法糖
<script setup lang='ts' name='hello'>
</script>
ref()响应式数据
js要用.value,模板里面不用
ref用于定义基本类型数据,js里面需要.value
reactive用于定义对象类型数据,js里面不需要.value
toRefs()转换
vue3生命周期
创建(steup)
挂载
onMount()
更新
onUpdated()
卸载(销毁)
onUnMounted()
路由
src取别名
安装path模块
npm install --save-dev @types/node
修改 vite.config.ts 文件
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
}
})
修改tsconfig.js
compilerOptions对象添加
"baseUrl": "./", // 解析非相对模块的基地址,默认是当前目录
"paths": { //路径映射,相对于baseUrl
"@/*": ["src/*"]
}
安装最新版路由
npm install vue-router@next --save
或者npm install vue-router@4(vue3 版本)
npm install vue-router@3(vue2 版本)
新建router.index.ts
import { createRouter, createWebHistory} from "vue-router"
//创建路由
const router = createRouter({
history: createWebHistory(),
routes:[
//首页
{
path: '/home',
name: 'home',
component: () => import('@/views/Home.vue'),
},
//测试页面
{
path: '/test',
name: 'test',
component: () => import('@/views/test/index.vue'),
},
]
})
//导出路由
export default router;
main.ts中使用路由
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
//路由
import router from "./router/index.ts"
const app = createApp(App);
//使用路由
app.use(router)
app.mount('#app')
App.vue中修改内容,使用占位
<script setup lang="ts">
</script>
<template>
<RouterView></RouterView>
</template>
<style scoped>
</style>
这样浏览器url展示对应的vue页面
history与hash模式
编程式路由导航
router.push()
存储区
这里使用pinia
安装
npm i pinia
安装依赖
main.ts全局使用
import {createPinia} from 'pinia'
const pinia = createPinia()
use(pinia);
store文件夹下面创建userStore.js
import { defineStore } from "pinia";
export const userStore = defineStore("userStore", {
state: () => {
return {
token:'',
userId: null,
userName: null
};
},
getters: {},
actions: {},
});
使用
<script setup>
import { userStore } from '@/store/userStore'
const user = userStore()
const userId = user.userId
</script>
环境配置
生产、开发、测试环境
.env.development
# 页面标题
VITE_APP_TITLE = 首页
# 开发环境配置
VITE_APP_ENV = 'development'
# 微信公众号/开发环境
VITE_APP_BASE_API = '/dev-api'
.env.production
# 页面标题
VITE_APP_TITLE = 首页
# 生产环境配置
VITE_APP_ENV = 'production'
# 微信公众号/生产环境
VITE_APP_BASE_API = '/prod-api'
# 是否在打包时开启压缩,支持 gzip 和 brotli
VITE_BUILD_COMPRESS = gzip
.env.test
# 页面标题
VITE_APP_TITLE = 首页
# 测试环境配置
VITE_APP_ENV = 'test'
# 微信公众号/开发环境
VITE_APP_BASE_API = '/test-api'
项目启动端口与后端请求地址
vite.config.ts 新增
// vite 相关配置
server: {
port: 8080,
host: true,
open: true,
proxy: {
// https://cn.vitejs.dev/config/#server-proxy
'/dev-api': {
target: 'http://localhost:8081',
changeOrigin: true,
rewrite: (p) => p.replace(/^\/dev-api/, '')
}
}
},
路径变化,页面title发生变化
router文件
import { createRouter, createWebHistory} from "vue-router"
//创建路由
const router = createRouter({
history: createWebHistory(),
routes:[
//首页
{
path: '/index',
name: 'index',
component: () => import('@/views/index.vue'),
meta:{
hidden:true,
title:"首页"
}
},
//测试页面
{
path: '/test',
name: 'test',
component: () => import('@/views/test/index.vue'),
meta:{
hidden:true,
title:"测试页面"
}
},
]
})
// 全局前置守卫
router.beforeEach((to, from, next) => {
// 在这里设置页面的标题
document.title = to.meta.title || '首页';
next();
});
//导出路由
export default router;
svg图标库配置
安装
npm i vite-plugin-svg-icons -D
or
pnpm install vite-plugin-svg-icons -D
安装
pnpm install fast-glob -D
vite.config.ts配置插件
import { createSvgIconsPlugin } from "vite-plugin-svg-icons";
plugins: [
vue(),
createSvgIconsPlugin({
// 指定需要缓存的图标文件夹
iconDirs: [path.resolve(process.cwd(), "src/assets/icons/svg")],
// 指定symbolId格式
symbolId: "icon-[dir]-[name]",
}),
main.ts导入
//svg
import 'virtual:svg-icons-register'
阿里云矢量图标库复制svg代码到存放文件夹
页面使用
<svg style="width: 30px;height: 30px">
<use xlink:href="#icon-yygh"></use> //use 使用yygh.svg图标
</svg>
封装请求方法
安装axios
cnpm install axios
安装vant
cnpm instanll vant
main.ts全局使用
// 全局引入 Vant 所有组件
import Vant from 'vant';
import 'vant/lib/index.css';
app.use(Vant)
新建utils/request.ts
import axios, { AxiosInstance, AxiosResponse }from "axios";
import { showNotify } from 'vant';
//引入store
import {userStore} from '@/store/userStore.ts'
const user = userStore();
// 创建axios实例
export const service:AxiosInstance = axios.create({
//请求地址
baseURL: import.meta.env.VITE_APP_BASE_API,
// 超时
timeout: 10000 * 20,
//请求头
headers: {
'Content-Type': 'application/json;charset=utf-8',
},
});
// request拦截器,请求体加上token
service.interceptors.request.use(
(config: any) => {
// prettier-ignore
config.headers["Authorization"] = "Bearer " + user.token; // 让每个请求携带自定义token 请根据实际情况自行修改
return config;
},
(error) => {
Promise.reject(error);
}
);
//响应拦截
service.interceptors.response.use((res:AxiosResponse) => {
// 未设置状态码则默认成功状态
const code = res.data.code;
// 获取错误信息
// prettier-ignore
const msg = res.data.msg;
// 二进制数据则直接返回
// prettier-ignore
if (res.request.responseType === "blob" || res.request.responseType === "arraybuffer") {
return res.data;
};
//返回状态码判断
if (code === 401) {
// 说明不通过微信公众号调用
showNotify({ type: 'danger', message: '请关注微信公众号使用!' });
// prettier-ignore
return Promise.reject("请关注微信公众号!");
} else if (code === 500) {
showNotify({ type: 'danger', message: msg });
return Promise.reject(new Error(msg));
} else if (code !== 200) {
showNotify({ type: 'danger', message: msg });
return Promise.reject("error");
} else {
return res.data;
}
}, (error) => {
let { message } = error;
if (message == "Network Error") {
message = "后端接口连接异常";
showNotify({ type: 'danger', message: message });
} else if (message.includes("timeout")) {
message = "系统接口请求超时";
showNotify({ type: 'danger', message: message });
} else if (message.includes("Request failed with status code")) {
message = "系统接口" + message.substr(message.length - 3) + "异常";
showNotify({ type: 'danger', message: message });
}
return Promise.reject(error);
});
//对外暴露
export default service;
定义请求方法
import request from "@/utils/request";
/**
* 测试查询
*/
export const getTset = async () => {
return await request({
url: "/api/test",
method: "get",
});
};
测试调用
<script setup lang="ts">
import {getTset} from "@/api/test";
function t(){
getTset().then(response => {
console.log(response);
});
}
t();
</script>
springboot 获得token
@Controller
public class TestController {
@GetMapping("/api/test")
@ResponseBody
public AjaxResult test(@RequestHeader("Authorization") String authHeader){
// 假设token是Authorization头中的值,并且以"Bearer "开头
String token = authHeader.replaceFirst("Bearer ", "");
System.out.println(token+"??");
return AjaxResult.success("xhengg");
}
}
element ui plus 显示中文
import zhCn from 'element-plus/es/locale/lang/zh-cn'
app.use(ElementPlus,{locale:zhCn})
element ui plus 单选框修改选中与不选择样式
/* 覆盖被选中的el-radio样式 */
:deep.el-radio.is-checked .el-radio__inner {
border-color: #05BF9A !important; /* 选中时的边框颜色 */
background-color: #05BF9A !important; /* 选中时的背景颜色 */
}
/* 当el-radio被选中时,改变对勾的样式 */
:deep.el-radio.is-checked .el-radio__label {
color: #05BF9A !important;
}
/* 覆盖被选中的el-radio样式 */
:deep.el-radio.is-disabled .el-radio__inner {
border-color: #b1b3b8 !important; /* 选中时的边框颜色 */
background-color: #b1b3b8 !important; /* 选中时的背景颜色 */
}
c-loading修改样式
css
:deep .el-loading-spinner .path{
stroke: #32b16c;
}
倒计时
初始化
const showDialog =()=>{
dialogVisible.value = true;
countDown.value = 30;
const x = setInterval(updateCountdown, 1000);
}
开始倒计时
const updateCountdown=() =>{
if (countDown.value > 0) {
countDown.value--;
} else {
}
}
CLODOP打印
打印PDF base64文件
1,官网下载CLODOP
2,将安装包下LodopFuncs.js 加入到工程项目
2,打印方法
const pdfbase64 = "要打印的pdf base64编码"
let LODOP=getLodop();
if(LODOP == null){
ElMessageBox.confirm('CLODDP插件加载失败!', '提示', {
confirmButtonText: '确定',
type: 'warning',
confirmButtonClass: 'bt'
})
visiable.value = false;
return;
}
//判断打印机
const counter = LODOP.GET_PRINTER_COUNT() // 获取打印机个数
if(counter == 6){
ElMessageBox.confirm('此电脑没有连接打印机!', '提示', {
confirmButtonText: '确定',
type: 'warning',
confirmButtonClass: 'bt'
})
visiable.value = false;
return;
}
//选初始化
LODOP.PRINT_INIT("门诊报告打印");
//设置打印机
const name = LODOP.GET_PRINTER_NAME(0);
console.log(name)
if(!LODOP.SET_PRINTER_INDEX(0)){
ElMessageBox.confirm('打印机不存在', '提示', {
confirmButtonText: '确定',
type: 'warning',
confirmButtonClass: 'bt'
})
return;
}
//开始打印
LODOP.ADD_PRINT_PDF(0,0,"100%","100%",pdfbase64.value);
//LODOP.PREVIEW();
LODOP.PRINT();
//打印结果判断
LODOP.On_Return=function(TaskID,Value){
if(Value == true){
showDialog();
}else{
ElMessageBox.confirm('打印失败,请联系管理员!', '提示', {
confirmButtonText: '确定',
type: 'warning',
confirmButtonClass: 'bt'
})
}
};
自定义简单
LODOP.ADD_PRINT_TEXT(70,0,"100%","100%","测试文本");//top,left,width,heigh,string
LODOP.SET_PRINT_STYLEA(0,"Fontsize",30);//字体大小
LODOP.SET_PRINT_STYLEA(0,"Alignment",2);//文本框里 内容对于文本框居中,
LODOP.SET_PRINT_STYLEA(0,"Horient",2);//打印项在纸张中水平居中
LODOP.ADD_PRINT_HTM(840,0,1200,"100%","<hr>")//html代码横线
el-date-picker定义时间范围为最近一个月
const end = new Date()
const start = new Date()
start.setTime(start.getTime() - 3600 * 1000 * 24 * 30)
//查询条件
const mzfyQuery=ref({
PatientId:wxUserStore().patientId,
StartDate:null,
EndDate:null,
Date:[start,end],
})
倒计时
//倒计时
const seconds = ref(120);
const startCountdown = () => {
const intervalId = setInterval(() => {
if (seconds.value > 0) {
seconds.value -= 1;
} else {
// 点击确认执行的逻辑
router.push("/index");;
}
}, 1000);
};
//开始倒计时
startCountdown();
页面自适应
vw vh
.centerCard{
width: 80vw;
height: 60vh;
}
el-button 图标大小
<el-icon class="el-icon--right" style="font-size: 3vw" ><CloseBold/></el-icon>
修改字体
深度选择器
el-select
::v-deep .el-select .el-select__placeholder {
font-size: 1.25vw;
el-radio
::v-deep .el-radio-group .el-radio__label {
font-size: 1.25vw; /* 设置你想要的字体大小 */
}
el-form-item label字体
<el-form-item prop="sex">
<template #label>
<span style="font-size: 1.5vw">性 别</span>
</template>
</el-form-item>
el-date-picker一年以内日期
vue3+ts
<el-date-picker
v-model="dateRangeValue"
type="daterange"
format="YYYY-MM-DD"
value-format="YYYY-MM-DD"
range-separator="至"
start-placeholder="开始日期"
end-placeholder="结束日期"
style="width: 20vw;height: 5vh;font-size: 2vw"
/>
// 初始化日期范围
const dateRangeValue = ref(["2020-03-01","2024-07-03"]);
const initQuery = ()=>{
// 创建一个新的Date对象,这将自动设置为当前日期和时间
let currentDate = new Date();
// 获取年份、月份(注意月份是从0开始的,所以需要+1)和日期
let year = currentDate.getFullYear();
let yearLast = currentDate.getFullYear()-1;
let month = String(currentDate.getMonth() + 1).padStart(2, '0'); // padStart用于确保月份为两位数
let day = String(currentDate.getDate()).padStart(2, '0'); // padStart用于确保日期为两位数
// 将它们组合成"YYYY-MM-DD"的格式
let endDate = `${year}-${month}-${day}`;
let startDate = `${yearLast}-${month}-${day}`;
let temp = [startDate,endDate];
dateRangeValue.value = temp;
}
initQuery()
eldialog
文字居中
<span style="font-size: 2vw;display: block;text-align: center;">请将身份证放到如图所示区域</span>
img居中
<div class="img-center">
<img style="width:30vw;height: 10vh;display: block;text-align: center;" src="/src/img/sfzys.png" >
</div>
.img-center {
display: flex;
justify-content: center; /* 水平居中 */
align-items: center; /* 垂直居中 */
height: 100%; /* 确保 div 的高度与其父元素相同 */
}
div 位于页面中央
<script setup lang="ts">
import {ref} from 'vue'
import router from "@/router";
//父组件传来的信息
import { defineProps } from 'vue';
const props = defineProps({
fatherMsg: {
type: String,
required: true,
},
});
//倒计时
const seconds = ref(180);
const startCountdown = () => {
const intervalId = setInterval(() => {
if (seconds.value > 0) {
seconds.value -= 1;
} else {
// 点击确认执行的逻辑
router.push("/index");;
}
}, 1000);
};
//开始倒计时
startCountdown();
//重置倒计时
const resetCountdown = ()=>{
seconds.value = 180;
}
const alterDialogVisible = ref(false);
//返回
const exit = ()=>{
alterDialogVisible.value = true;
}
const cancelExit = () => {
alterDialogVisible.value = false;
}
const confirmExit = () => {
alterDialogVisible.value = false
router.push("/index")
}
</script>
<template>
<div style="width: 90vw;height: 4vw;margin: 0 auto; /* 上下外边距为0,左右外边距自动 */ " >
<div style="width: 45vw;height: 100%;float: left">
<el-row>
<el-button style="width: 15vw;height: 3vw" round @click="exit"><span style="color: red;font-size: 2vw">{{seconds}}s</span ><span style="font-size: 2vw;color: #05BF9A">自动退出</span></el-button>
<el-button style="height:100%;" round @click="resetCountdown"><span style="font-size: 1.5vw;color: #E6A23C">重置</span></el-button>
</el-row>
</div>
<div style="width: 45vw;height: 100%;float: right">
<el-button round style="background-color: #05BF9A;color: white;width: 7vw;height: 3vw;float: right" type="primary" @click="exit"><span style="font-size: 2vw">退出</span></el-button>
</div>
</div>
<el-dialog v-model="alterDialogVisible" width="50vw" center height="15vh" style="margin-top: 50vh" >
<template #title>
<el-icon style="font-size: 3vw;color: #E6A23C"><WarningFilled /></el-icon>
<span style="font-size: 3vw">温馨提示</span>
</template>
<span style="font-size: 2vw;display: block;text-align: center;">
{{props.fatherMsg}}
</span>
<template #footer>
<div class="dialog-footer">
<el-button style="width: 12vw;height: 3vw" @click="cancelExit"><span style="font-size: 2vw;color: #b1b3b8">取消</span></el-button>
<el-button style="background-color: #05BF9A;color: white;width: 10vw;height: 3vw" type="primary" @click="confirmExit"><span style="font-size: 2vw">确定</span></el-button>
</div>
</template>
</el-dialog>
</template>
<style>
.el-dialog {
border-radius: 30px;
}
</style>
子组件往父组件发消息
子组件
//定义
import { defineEmits } from 'vue';
const emit = defineEmits(['childMsg']);
//通过事件提交
emit('childMsg', "1");
父组件获取
//定义获取子组件数据
const childMsg = (value: string) => {
console.log("子组件返回数据"+value)
};
//父组件使用子组件
<childVue @child-msg="childMsg"/>