vue-vite-ts 新版
Vue 后台管理系统
一、系统创建
1.1、环境检测
$ node -v
v18.10.0
$ npm -v
9.1.2
## 若没有该命令 需要用 npm install -g pnpm 安装
$ pnpm -v
7.13.6
$ vue -V
@vue/cli 4.5.19
1.2、创建项目
## 利用vite创建项目
$ npm create vite his.vue
Need to install the following packages:
create-vite@3.2.1
Ok to proceed? (y) y
√ Select a framework: » Vue
√ Select a variant: » TypeScript
Scaffolding project in D:\vscode\his.vue...
Done. Now run:
cd his.vue
npm install
npm run dev
## 进入目录
$ cd his.vue
## 安装依赖
$ pnpm install
## 运行
$ pnpm dev
## 用vs code 打开
$ code .
1.3、初始化一些配置信息
1.3.1、构建成功后自动打开浏览器
vite.config.ts
export default defineConfig({
plugins: [vue()],
// 自动打开
server: {
open: true,
},
});
1.3.2、配置端口
package.json
"scripts": {
"dev": "vite --port 9527",
"build": "vue-tsc && vite build",
"preview": "vite preview"
},
1.3.3、配置@
path 模块是 node.js 的内置模块,而 node.js 默认不支持 ts 文件的,所以需要安装 @type/node 依赖包
$ pnpm install @types/node
vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { resolve } from "path";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
// 自动打开
server: {
open: true,
},
resolve: {
alias: {
"@": resolve(__dirname, "./src"),
},
},
});
修改 ts.config.json 文件
{
"compilerOptions": {
"target": "ESNext",
"useDefineForClassFields": true,
"module": "ESNext",
"moduleResolution": "Node",
"strict": true,
"jsx": "preserve",
"resolveJsonModule": true,
"isolatedModules": true,
"esModuleInterop": true,
"lib": ["ESNext", "DOM"],
"skipLibCheck": true,
"noEmit": true,
"baseUrl": "./",
"paths": {
"@/": ["src/*"]
}
},
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
"references": [{ "path": "./tsconfig.node.json" }]
}
使用
<script setup lang="ts">
import HelloWorld from '@/components/HelloWorld.vue'
</script> <template>
<HelloWorld msg="Vite + Vue" />
</template>
1.3.4、修改 css
- .\src\style.css 重命名为.\src\style.scss
- main.ts 修改引入的名称
1.4、安装 sass
$ pnpm install sass
Progress: resolved 0, reused 1, downloaded 0, added 0
Progress: resolved 6, reused 6, downloaded 0, added 0
Progress: resolved 28, reused 27, downloaded 0, added 0
Progress: resolved 54, reused 42, downloaded 0, added 0
Progress: resolved 75, reused 51, downloaded 1, added 0
Progress: resolved 80, reused 51, downloaded 4, added 0
Progress: resolved 83, reused 51, downloaded 7, added 0
Packages: +17 -1
+++++++++++++++++-
Progress: resolved 89, reused 51, downloaded 15, added 15
dependencies:
+ sass 1.56.1
Done in 8.2s
Progress: resolved 89, reused 51, downloaded 16, added 17, done
1.5、Element Plus
- 安装
$ pnpm install element-plus
Progress: resolved 0, reused 1, downloaded 0, added 0
Progress: resolved 7, reused 7, downloaded 0, added 0
Progress: resolved 8, reused 7, downloaded 0, added 0
Progress: resolved 78, reused 62, downloaded 2, added 0
Progress: resolved 104, reused 67, downloaded 3, added 0
Progress: resolved 107, reused 67, downloaded 7, added 0
Progress: resolved 109, reused 67, downloaded 15, added 0
Packages: +21
+++++++++++++++++++++
Progress: resolved 110, reused 67, downloaded 16, added 0
Progress: resolved 110, reused 67, downloaded 18, added 17
Progress: resolved 110, reused 67, downloaded 20, added 19
Progress: resolved 110, reused 67, downloaded 20, added 20
Progress: resolved 110, reused 67, downloaded 21, added 20
Progress: resolved 110, reused 67, downloaded 21, added 21
.../node_modules/vue-demi postinstall$ node ./scripts/postinstall.js
.../node_modules/vue-demi postinstall: Done
dependencies:
+ element-plus 2.2.23
Done in 18.4s
Progress: resolved 110, reused 67, downloaded 21, added 21, done
自动导入(推荐)
$ pnpm install -D unplugin-vue-components unplugin-auto-import
Progress: resolved 0, reused 1, downloaded 0, added 0
Progress: resolved 8, reused 8, downloaded 0, added 0
Progress: resolved 70, reused 68, downloaded 1, added 0
Progress: resolved 82, reused 74, downloaded 4, added 0
Progress: resolved 128, reused 88, downloaded 16, added 0
Progress: resolved 138, reused 88, downloaded 28, added 0
Packages: +32
++++++++++++++++++++++++++++++++
Progress: resolved 142, reused 88, downloaded 31, added 31 devDependencies:
+ unplugin-auto-import 0.11.5
+ unplugin-vue-components 0.22.11 Done in 7.4s
Progress: resolved 142, reused 88, downloaded 32, added 32, done修改 vite.config.ts
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import { resolve } from "path";
import AutoImport from "unplugin-auto-import/vite";
import Components from "unplugin-vue-components/vite";
import { ElementPlusResolver } from "unplugin-vue-components/resolvers";
// https://vitejs.dev/config/
export default defineConfig({
plugins: [
vue(),
// Element Plus自动导入
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
],
// 自动打开
server: {
open: true,
},
resolve: {
alias: {
"@": resolve(__dirname, "./src"),
},
},
});
main.ts 引入 css
import "element-plus/dist/index.css";
1.6、安装 route
安装
$ pnpm install vue-router@next
Progress: resolved 0, reused 1, downloaded 0, added 0
Progress: resolved 10, reused 10, downloaded 0, added 0
Progress: resolved 11, reused 10, downloaded 0, added 0
Progress: resolved 134, reused 109, downloaded 0, added 0
Packages: +2
++
Progress: resolved 144, reused 120, downloaded 2, added 2, done dependencies:
+ vue-router 4.0.13 (4.1.6 is available) Done in 6.3ssrc 目录创建 views 文件夹,新建文件 Login.vue
src 目录下新建文件夹 router,文件夹新建 路由文件 index.ts 写入页面和路由映射关系
import { createRouter, createWebHistory } from "vue-router";
import Login from "@/views/Login.vue"; const router = createRouter({
history: createWebHistory(),
routes: [{ path: "/login", component: Login }],
});
export default router;
在 main.ts 中引入路由
import { createApp } from "vue";
import "./style.scss";
import App from "./App.vue";
import "element-plus/dist/index.css";
import router from "./router";
createApp(App).use(router).mount("#app");
在 App.vue 里面加上路由标签
<template>
<router-view></router-view>
</template>
1.7、安装 pinia
pinia 是一款新的 vue3 的状态管理库,完整的 typescript 支持。
起名规则:userXxxStore ,Xxx 为 id 值
下载
$ pnpm i pinia@next
Packages: +1
+ dependencies:
+ pinia 2.0.0-rc.10 (2.0.26 is available) Progress: resolved 145, reused 122, downloaded 1, added 1, done
Done in 4.9s
设置为全局对象,在 main.js 中引用
import { createPinia } from "pinia";
// 创建pinia实例
const pinia = createPinia();
createApp(App).use(router).use(pinia).mount("#app");
新建文件夹 store,新建文件 index.ts
// store/index.ts
import { defineStore } from "pinia"; // 使用defineStore定义store,第一个参数必须是全局唯一的id,可以使用Symbol
// id:必须,在所有store中唯一
export const useGlobalStore = defineStore("global", {
// 定义状态 返回的对象函数
state: () => ({
count: 10,
title: "医院信息系统",
}),
// 计算属性 computed Getter 是计算属性,也可叫只读属性,因此不可能将任何参数传递给它们
// 都带一个可选参数 state ,建议都带上,不建议用this
getters: {
getUserById: (state) => {
return (userId) => state.users.find((user) => user.id === userId);
},
// 返回值会类型推导
count10(state) {
return state.count + 10;
},
// 若getters使用了this必须手动指定返回值类型,否则类型推导不出来
count11(): number {
return this.count + 10;
},
},
// 相当于组件中的方法,不建议用箭头函数,因为无法用this
actions: {
setCount(n: number) {
this.count = n;
},
// 也可以用批量更新
// this.$patch({...})
// this.$patch(state=>{...})
},
});
import { useOtherStore } from "./other-store";
// 访问其他store的getters
export const useMainStore = defineStore("main", {
state: () => ({
// ...
}),
getters: {
otherGetter(state) {
const otherStore = useOtherStore();
return state.localData + otherStore.data;
},
},
});
使用
<script setup lang="ts"> import { reactive, ref, computed } from 'vue'
// 在setup中使用GlobalStore创建store实例
import { useGlobalStore } from '@/stores';
// 获取父组件传值
defineProps<{ msg: string }>()
let store = useGlobalStore();
// 如果直接取state的值必须使用computed才能实现数据的响应式
// 如果直接取 store.state.a 则不会监听到数据的变化,或者使用getter,就可以不使用computed (这边和vuex是一样的)
let count = computed(() => store.count);
let title = store.$state.title;
const clickAdd = () => {
store.setCount(store.count + 1);
console.log(store.count); };
</script> <template>
{{ msg }}<br />
{{ title }}
<div>Login:{{ count }}</div>
<el-button type="primary" @click="clickAdd">{{ count }}++</el-button>
</template> <style lang="scss" scoped> </style>解构
import { storeToRefs } from "pinia";
import { GlobalStore } from "@/stores";
const store = GlobalStore();
// 响应式的代理 解构成响应式
const { UserName, NickName, Token } = storeToRefs(store);
const login = () => {
// 一次性修改多个数据,建议使用$patch
// store.SettingNickName(user.Name)
// store.SettingUserName(user.UserName)
// store.SettingToken(token)
// 一次性修改多个数据,建议使用$patch,内部做了性能优化
// store.$patch({
// NickName: user.Name,
// UserName: user.UserName,
// Token: token,
// Arr: [...store.Arr, 4]
// })
// 这种是推荐写法,复杂情况用这种,简单数据修改用上边的写法
store.$patch((state) => {
(state.NickName = user.Name),
(state.UserName = user.UserName),
(state.Token = token),
state.Arr.push(6);
});
};
一个 ts 中可以写多个 store,但是 id 必须唯一
export const useTestStore = defineStore({
id: "test",
/**
* 属性
* @returns
*/
state: () => ({
count: 0,
}),
/**
* 计算属性
*/
getters: {},
/**
* 方法
*/
actions: {},
});
另一种写法
export const useTestStore = defineStore("test", {
/**
* 属性
* @returns
*/
state: () => ({
count: 0,
}),
/**
* 计算属性
*/
getters: {},
/**
* 方法
*/
actions: {},
});
对比:
- vuex 两个代码的对比我们可以看出使用 pinia 更加的简洁,轻便。
- pinia 取消了原有的 mutations,合并成了 actions,且我们在取值的时候可以直接点到那个值,而不需要在.state,方法也是如此。
1.8、echarts
安装
$ pnpm install echarts
Progress: resolved 0, reused 1, downloaded 0, added 0
Progress: resolved 11, reused 11, downloaded 0, added 0
Progress: resolved 117, reused 114, downloaded 0, added 0
Packages: +3
+++
Progress: resolved 147, reused 122, downloaded 2, added 1
Progress: resolved 147, reused 122, downloaded 2, added 2 dependencies:
+ echarts 5.4.0 Done in 5.5s
Progress: resolved 147, reused 122, downloaded 3, added 3, done
1.9、axios
安装
$ pnpm install axios
Progress: resolved 0, reused 1, downloaded 0, added 0
Progress: resolved 13, reused 13, downloaded 0, added 0
Progress: resolved 14, reused 13, downloaded 0, added 0
Progress: resolved 116, reused 113, downloaded 1, added 0
Progress: resolved 156, reused 126, downloaded 4, added 0
Packages: +9
+++++++++
Progress: resolved 157, reused 126, downloaded 8, added 8 dependencies:
+ axios 1.2.0 Done in 6.2s
Progress: resolved 157, reused 126, downloaded 9, added 9, donesrc 目录下新建 api 文件夹,新建 index.ts 文件
import axios from "axios";
//需要拦截器的地方使用instance对象, 有自定义返回逻辑的地方沿用axios,在组件内部处理返回结果即可
import instance from "@/api/filter";
const http = "/api"; //获取token,,因为instance开启了withCredentials 所以这里要用axios,因为instance有连接器功效,必须携带token
export const getToken = (name: string, password: string) => {
return axios.get(
http + "/Login/GetToken?name=" + name + "&password=" + password
);
}; //获取列表
export const getMenuDataNew = async (parms: {}) => {
instance.defaults.headers.common["Authorization"] =
"Bearer " + localStorage["token"];
return instance.post(http + "/Menu/GetMenus", parms);
};
在需要使用的组件里导入 http 中的方法即可
import { getToken } from '../../http/index'
//请求后端数据,获取token,并将token放入localStorage
const token = await getToken(form.userName, form.passWord) as any as string
const user: UserInfo = JSON.parse(new Tool().FormatToken(token))
localStorage["token"] = token
localStorage["nickname"] = user.NickName
store.commit("SettingNickName",user.NickName)
store.commit("SettingToken",token)
router.push({ path: '/desktop' });
拦截器:
- 对 api 的返回结果解析,返回统一的格式。
- 对于错误信息,在拦截器中弹窗提示,业务层页面只需关注页面,无需过度关注交互
- 新建 filter.ts 文件
//导入axios
import axios from "axios";
import { ElMessage } from "element-plus";
// 处理 类型“AxiosResponse<any, any>”上不存在属性“errorinfo”。ts(2339) 脑壳疼!关键一步。
declare module "axios" {
interface AxiosResponse<T = any> {
errorinfo: null;
// 这里追加你的参数
}
export function create(config?: AxiosRequestConfig): AxiosInstance;
}
//创建一个axios实例
const instance = axios.create({
headers: {
"content-type": "application/json",
},
// true:在跨域请求时,会携带用户凭证
// false(默认):在跨域请求时,不会携带用户凭证;返回的 response 里也会忽略 cookie
// withCredentials: true,
timeout: 5000, //5秒
});
//http 拦截器
instance.interceptors.response.use(
(response) => {
//拦截请求,统一相应
if (response.data.isSuccess) {
return response.data.result;
} else {
ElMessage.error(response.data.msg);
return response.data.result;
}
},
//error也可以处理
(error) => {
if (error.response) {
switch (error.response.status) {
case 401:
ElMessage.warning("资源没有访问权限!");
break;
case 404:
ElMessage.warning("接口不存在,请检查接口地址是否正确!");
break;
case 500:
ElMessage.warning("内部服务器错误,请联系系统管理员!");
break;
default:
return Promise.reject(error.response.data); // 返回接口返回的错误信息
}
} else {
ElMessage.error("遇到跨域错误,请设置代理或者修改后端允许跨域访问!");
}
}
);
export default instance;
1.10、icons-vue
网址:https://element-plus.gitee.io/zh-CN/component/icon.html#基础用法
安装
$ pnpm install @element-plus/icons-vue
Progress: resolved 0, reused 1, downloaded 0, added 0
Progress: resolved 14, reused 14, downloaded 0, added 0
Progress: resolved 15, reused 14, downloaded 0, added 0
Progress: resolved 122, reused 122, downloaded 0, added 0
Progress: resolved 127, reused 127, downloaded 0, added 0
Progress: resolved 128, reused 127, downloaded 0, added 0
Already up to date dependencies:
+ @element-plus/icons-vue 2.0.10 Done in 9.2s
Progress: resolved 157, reused 135, downloaded 0, added 0, done使用
<script setup lang="ts">
import { CoffeeCup} from '@element-plus/icons-vue'
</script> <template>
<div class="left">
<coffee-cup />
</div>
</template> <style lang="scss" scoped>
.left {
width: 32px;
height: 32px;
}
</style>
1.11、前端解决跨域问题
和后端解决是两种方案,而不是必须前后端都解决跨域问题;
代码 vite.config.ts
// vite.config.ts ,和plugins平级添加 server:{
port:3000,
open:true,
proxy:{
'/api':{
target:'http://localhost:5294/api',
changeOrigin:true,
rewrite:(path) => path.replace(/^\/api/,'')
}
},
}
1.12、 json-viewer(弃用)
安装
# 需要依赖clipboard,先安装clipboard
$ pnpm install clipboard
Progress: resolved 0, reused 1, downloaded 0, added 0
Progress: resolved 40, reused 39, downloaded 0, added 0
Packages: +5
+++++
Progress: resolved 161, reused 134, downloaded 4, added 0 dependencies:
+ clipboard 2.0.11 Done in 2.8s
Progress: resolved 161, reused 134, downloaded 5, added 5, done Administrator@wanghx MINGW64 /d/vsdemo/vs2022/HisApi/vue-vite-ts-admin
# 再安装vue3-json-viewer
$ pnpm i vue3-json-viewer
Progress: resolved 0, reused 1, downloaded 0, added 0
Progress: resolved 30, reused 29, downloaded 0, added 0
Packages: +1
+
Progress: resolved 162, reused 139, downloaded 1, added 1, done dependencies:
+ vue3-json-viewer 2.2.2main.ts 引入
import JsonViewer from "vue3-json-viewer";
import "vue3-json-viewer/dist/index.css";
createApp(App).use(router).use(pinia).use(JsonViewer).mount("#app");
新建文件.\src\declaration.d.ts,防止报错
declare module "vue3-json-viewer" {
const vis: any;
export default vis;
}
新建组件
/**
* @description JsonView组件,格式化JSON用
* @author wanghx
* @date 2022-11-26 12:40:39
*/
<template>
<div class="container">
<json-viewer :value="json" :copyable="{ copyText: '复制代码', copiedText: '复制成功' }" :expand-depth=5 boxed
expanded="true" sort :theme="theme" />
</div>
</template> <script setup lang='ts'>
import { reactive, ref } from 'vue'
let theme = ref("dark"); // light,dark
// 获取父组件传值
defineProps<{ json: string }>()
</script> <style lang='scss' scoped>
.container {
margin: auto;
padding-top: 5px;
width: 70%;
}
</style>使用
<template>
<JsonComp :json="jsonData" />
<HelloWorld msg="Login"></HelloWorld>
</template> <script lang="ts" setup>
import JsonComp from "@/components/JsonComp.vue";
const form = {
jsonData:
'[{"name":"黑子","sex":"男","Age":25,"abc":null,"hobby":["篮球","跑步","看电影","王者荣耀"],"normal":true},{"name":"张三","sex":"男","Age":25,"hobby":["上天","入地"],"normal":false},{"name":"黑子","sex":"男","Age":25,"abc":null,"hobby":["篮球","跑步","看电影","王者荣耀"],"normal":true},{"name":"张三","sex":"男","Age":25,"hobby":["上天","入地"],"normal":false}]',
};
const jsonData = JSON.parse(form.jsonData);
</script> <style lang="scss" scoped></style>
1.13、Json:vue3-ace-editor
下载
$ pnpm i vue3-ace-editor
dependencies:
+ vue3-ace-editor 2.2.2
$ pnpm install file-loader
Packages: +3
+++ dependencies:
+ file-loader 6.2.0
$ pnpm install ace-builds
dependencies:
+ ace-builds 1.13.1
代码
/** * @description vue3-ace-editor * @author wanghx * @date 2022-11-29
17:05:41 * pnpm i vue3-ace-editor * pnpm install file-loader * pnpm install
ace-builds */
<template>
<div class="common-layout">
<el-container>
<el-header>
<el-select
v-model="aceConfig.theme"
class="m-2"
placeholder="Select"
size="large"
>
<el-option
v-for="item in aceConfig.arr"
:key="item"
:label="item"
:value="item"
/>
</el-select>
<el-button
style="width: 120px;"
type="success"
round
size="large"
class="jsonFormat"
@click="jsonFormat"
>
格式化Json
</el-button>
<el-button
style="width: 120px;"
type="primary"
round
size="large"
@click="jsonNoFormat"
>压缩
</el-button>
</el-header>
<el-main>
<v-ace-editor
v-model:value="dataForm.textareashow"
@init="jsonFormat"
lang="json"
:theme="aceConfig.theme"
:options="aceConfig.options"
style="height:300px"
:readonly="aceConfig.readOnly"
class="ace-editor"
/>
</el-main>
</el-container>
</div>
</template> <script setup lang="ts">
import { reactive, ref } from "vue";
import { VAceEditor } from "vue3-ace-editor";
import "ace-builds/webpack-resolver";
import "ace-builds/src-noconflict/mode-json";
import "ace-builds/src-noconflict/theme-ambiance";
import "ace-builds/src-noconflict/theme-chaos";
import "ace-builds/src-noconflict/theme-chrome";
import "ace-builds/src-noconflict/theme-cloud9_day";
import "ace-builds/src-noconflict/theme-cloud9_night";
import "ace-builds/src-noconflict/theme-cloud9_night_low_color";
import "ace-builds/src-noconflict/theme-clouds";
import "ace-builds/src-noconflict/theme-clouds_midnight";
import "ace-builds/src-noconflict/theme-cobalt";
import "ace-builds/src-noconflict/theme-crimson_editor";
import "ace-builds/src-noconflict/theme-dawn";
import "ace-builds/src-noconflict/theme-dracula";
import "ace-builds/src-noconflict/theme-dreamweaver";
import "ace-builds/src-noconflict/theme-eclipse";
import "ace-builds/src-noconflict/theme-github";
import "ace-builds/src-noconflict/theme-gob";
import "ace-builds/src-noconflict/theme-gruvbox";
import "ace-builds/src-noconflict/theme-gruvbox_dark_hard";
import "ace-builds/src-noconflict/theme-gruvbox_light_hard";
import "ace-builds/src-noconflict/theme-idle_fingers";
import "ace-builds/src-noconflict/theme-iplastic";
import "ace-builds/src-noconflict/theme-katzenmilch";
import "ace-builds/src-noconflict/theme-kr_theme";
import "ace-builds/src-noconflict/theme-kuroir";
import "ace-builds/src-noconflict/theme-merbivore";
import "ace-builds/src-noconflict/theme-merbivore_soft";
import "ace-builds/src-noconflict/theme-mono_industrial";
import "ace-builds/src-noconflict/theme-monokai";
import "ace-builds/src-noconflict/theme-one_dark";
import "ace-builds/src-noconflict/theme-pastel_on_dark";
import "ace-builds/src-noconflict/theme-solarized_dark";
import "ace-builds/src-noconflict/theme-solarized_light";
import "ace-builds/src-noconflict/theme-sqlserver";
import "ace-builds/src-noconflict/theme-terminal";
import "ace-builds/src-noconflict/theme-textmate";
import "ace-builds/src-noconflict/theme-tomorrow";
import "ace-builds/src-noconflict/theme-tomorrow_night";
import "ace-builds/src-noconflict/theme-tomorrow_night_blue";
import "ace-builds/src-noconflict/theme-tomorrow_night_bright";
import "ace-builds/src-noconflict/theme-tomorrow_night_eighties";
import "ace-builds/src-noconflict/theme-twilight";
import "ace-builds/src-noconflict/theme-vibrant_ink";
import "ace-builds/src-noconflict/theme-xcode";
import "ace-builds/src-noconflict/ext-language_tools";
//ace编辑器配置
const aceConfig = reactive({
lang: "json", // 解析json
theme: "vibrant_ink", // 主题
arr: [
/*所有主题*/
"ambiance",
"chaos",
"chrome",
"cloud9_day",
"cloud9_night",
"cloud9_night_low_color",
"clouds",
"clouds_midnight",
"cobalt",
"crimson_editor",
"dawn",
"dracula",
"dreamweaver",
"eclipse",
"github",
"gob",
"gruvbox",
"gruvbox_dark_hard",
"gruvbox_light_hard",
"idle_fingers",
"iplastic",
"katzenmilch",
"kr_theme",
"kuroir",
"merbivore",
"merbivore_soft",
"mono_industrial",
"monokai",
"one_dark",
"pastel_on_dark",
"solarized_dark",
"solarized_light",
"sqlserver",
"terminal",
"textmate",
"tomorrow",
"tomorrow_night",
"tomorrow_night_blue",
"tomorrow_night_bright",
"tomorrow_night_eighties",
"twilight",
"vibrant_ink",
"xcode",
],
readOnly: false, //是否只读
options: {
enableBasicAutocompletion: true,
enableSnippets: true,
enableLiveAutocompletion: true,
tabSize: 2,
showPrintMargin: false,
fontSize: 16,
},
});
//form
const dataForm = reactive({
textareashow:
'{"infno": "2001","msgid": "H22240100110202211280916066003","mdtrtarea_admvs": "222401","insuplc_admdvs": "222401","recer_sys_code": "123","dev_no": "","dev_safe_info": "","cainfo": "","signtype": "","infver": "V1.0","opter_type": "1","opter": "YBYYJYL","opter_name": "周树人","inf_time": "2022-11-28 09:16:06","fixmedins_code": "H22240100110","fixmedins_name": "延边大学附属医院(延边医院)","sign_no": "","input": {"data": {"psn_no": "22000020001200084268","insutype": "310","fixmedins_code": "H22240100110","med_type": "21","begntime": "2022-11-28 09:16:06","endtime": "","dise_codg": "","dise_name": "","oprn_oprt_code": "","oprn_oprt_name": "","matn_type": "","birctrl_type": ""}}} ',
}); const jsonError = (e: any) => {
console.log(`JSON字符串错误:${e.message}`);
}; // JSON格式化
const jsonFormat = () => {
try {
dataForm.textareashow = JSON.stringify(
JSON.parse(dataForm.textareashow),
null,
2
);
} catch (e) {
jsonError(e);
}
}; // JSON压缩
const jsonNoFormat = () => {
try {
dataForm.textareashow = JSON.stringify(
JSON.parse(dataForm.textareashow)
);
} catch (e) {
jsonError(e);
}
};
</script> <style lang="scss" scoped>
.common-layout {
width: 100%;
height: 100%;
} .jsonFormat {
margin-left: 10px;
margin-right: 10px;
}
</style>
1.14、git
- git全局设置
git config --global user.name "wanghx"
git config --global user.email "9016814@qq.com"
已有仓库
cd his.api
git remote add origin git@gitee.com:his7/his.vue.git
git push -u origin "master"
新建仓库
$ git init
$ git add .
$ git status
git commit -m "创建项目"
git remote add origin git@gitee.com:his7/his.vue.git
git push -u origin "master"
1.15、js-table2excel
安装
npm install js-table2excel
和pnpm install sortablejs
$ pnpm install js-table2excel dependencies:
+ js-table2excel 1.0.3 WARN Issues with peer dependencies found
.
└─┬ file-loader 6.2.0
└── ✕ missing peer webpack@"^4.0.0 || ^5.0.0"
Peer dependencies that should be installed:
webpack@"^4.0.0 || ^5.0.0" Done in 2.8s $ pnpm install sortablejs
Progress: resolved 0, reused 1, downloaded 0, added 0
Progress: resolved 49, reused 49, downloaded 0, added 0
Packages: +1
+
Progress: resolved 182, reused 160, downloaded 0, added 1, done dependencies:
+ sortablejs 1.15.0 WARN Issues with peer dependencies found
.
└─┬ file-loader 6.2.0
└── ✕ missing peer webpack@"^4.0.0 || ^5.0.0"
Peer dependencies that should be installed:
webpack@"^4.0.0 || ^5.0.0" Done in 2.7s使用
import table2excel from 'js-table2excel' //js 部分
var column = [];
this.$refs['myTable'].$children.forEach(element => { if(element.label && element.label!='操作') {
let temp = {
title: element.label,
key: element.prop, //key值对应表单数据字段名称
type: 'text',
}
if(temp.title=='照片') {
temp.type = 'image';
temp.key = 'photo';
temp.width= 75,
temp.height= 100
}
column.push(temp)
}
});
var datas = this.multipleSelection; //表单数据
//文件名称
const excelName = '学生信息_'+ new Date().toLocaleString()
//生成Excel表格,自动下载
table2excel(column, datas, excelName)
1.17、nprogress
安装
npm i nprogress
在router.js中使用
import Vue from 'vue'
import Router from 'vue-router'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css' Vue.use(Router) const router = new Router({
mode: 'history',
routes: [
]
}) router.beforeEach((to, from, next) => {
NProgress.start()
/// code
})
router.afterEach(() => {
NProgress.done()
})
修改颜色
#nprogress .bar {
background: red !important; //颜色可修改
}
不显示时,nprogress的z-index,假如你的header比nprogress的高,可能看不见进度条,可以采用这个办法实施,其中数字比header高就行,或者,你改header的z-index
#nprogress {
.bar {
z-index: 15031;
}
.spinner {
z-index: 15031;
}
}其它用法
NProgress.start() — 显示进度条
NProgress.set(0.4) —设置百分比
NProgress.inc() — 增加一点点
NProgress.done() — 完成进度条
axios中的写法
// axios请求拦截器
axios.interceptors.request.use(
config => {
NProgress.start() // 设置加载进度条(开始..)
return config
},
error => {
return Promise.reject(error)
}
)
// axios响应拦截器
axios.interceptors.response.use(
function(response) {
NProgress.done() // 设置加载进度条(结束..)
return response
},
function(error) {
return Promise.reject(error)
}
)
二、部署
2.1、下载 Nginx
- 地址:http://nginx.org/en/download.html
- 下载:nginx/Windows-1.22.1
2.2、安装
解压 Nginx 压缩包
尽量在 cmd 窗口启动,不要直接双击 nginx.exe,这样会导致修改配置后重启、停止 nginx 无效,需要手动关闭任务管理器内的所有 nginx 进程,再启动才可以,就很麻烦。
查看任务进程是否存在
$ tasklist /fi "imagename eq nginx.exe"
映像名称 PID 会话名 会话# 内存使用
========================= ======== ================ =========== ============
nginx.exe 820 Console 5 18,484 K
nginx.exe 22132 Console 5 18,784 K
打开浏览器,输入 localhost,看到 Welcome to nginx! 即表示安装成功
若失败,查看 80 端口是否被占用,有 0.0.0.0:80 用任务管理器看后面 pid22132 被哪个进程占用了
netstat -ano | findstr "80"
TCP 0.0.0.0:80 0.0.0.0:0 LISTENING 22132
TCP 0.0.0.0:49666 0.0.0.0:0 LISTENING 1800
TCP 127.0.0.1:80 127.0.0.1:57124 ESTABLISHED 22132
TCP 127.0.0.1:80 127.0.0.1:57125 ESTABLISHED 22132
TCP 127.0.0.1:80 127.0.0.1:57127 ESTABLISHED 22132
nginx 的配置文件是 conf 目录下的 nginx.conf,默认配置的 nginx 监听的端口为 80,如果本地电脑的 80 端口有被占用,如果本地 80 端口已经被使用则修改成其他端口。
常用命令
1、启动: $ start nginx 2、停止: $ nginx.exe -s stop 或 C:\nginx>nginx.exe -s quit 注:stop是快速停止nginx,可能并不保存相关信息;quit是完整有序的停止nginx,并保存相关信息。 执行 nginx.exe -s stop或者quit命令是不是不能删除进程?查看进程开了一堆nignx.exe
还有80端口在Listening,并且浏览器F5刷新还能访问页面,可能nginx.exe版本或系统的原因,用
taskkill /f /im nginx.exe > null 杀死nginx进程 3、重新载入Nginx: $ nginx.exe -s reload 当配置信息修改,需要重新载入这些配置时使用此命令。 4、重新打开日志文件: $ nginx.exe -s reopen 5、查看Nginx版本: $ nginx -v
nginx version: nginx/1.22.1
配置文件 nginx.conf
#user nobody; #==工作进程数,一般设置为cpu核心数
worker_processes 1; #error_log logs/error.log;
#error_log logs/error.log notice;
#error_log logs/error.log info; #pid logs/nginx.pid; events { #==最大连接数,一般设置为cpu*2048
worker_connections 1024;
} http {
include mime.types;
default_type application/octet-stream; #log_format main '$remote_addr - $remote_user [$time_local] "$request" '
# '$status $body_bytes_sent "$http_referer" '
# '"$http_user_agent" "$http_x_forwarded_for"'; #access_log logs/access.log main; sendfile on;
#tcp_nopush on; #keepalive_timeout 0; #==客户端链接超时时间
keepalive_timeout 65; #gzip on; #当配置多个server节点时,默认server names的缓存区大小就不够了,需要手动设置大一点
server_names_hash_bucket_size 512; #server表示虚拟主机可以理解为一个站点,可以配置多个server节点搭建多个站点
#每一个请求进来确定使用哪个server由server_name确定
server {
#站点监听端口
listen 80;
#站点访问域名
server_name localhost; #编码格式,避免url参数乱码
charset utf-8; #access_log logs/host.access.log main; #location用来匹配同一域名下多个URI的访问规则
#比如动态资源如何跳转,静态资源如何跳转等
#location后面跟着的/代表匹配规则
location / {
#站点根目录,可以是相对路径,也可以使绝对路径
root html;
#默认主页
index index.html index.htm; #转发后端站点地址,一般用于做软负载,轮询后端服务器
#proxy_pass http://10.11.12.237:8080; #拒绝请求,返回403,一般用于某些目录禁止访问
#deny all; #允许请求
#allow all; add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
#重新定义或者添加发往后端服务器的请求头
#给请求头中添加客户请求主机名
proxy_set_header Host $host;
#给请求头中添加客户端IP
proxy_set_header X-Real-IP $remote_addr;
#将$remote_addr变量值添加在客户端“X-Forwarded-For”请求头的后面,并以逗号分隔。 如果客户端请求未携带“X-Forwarded-For”请求头,$proxy_add_x_forwarded_for变量值将与$remote_addr变量相同
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
#给请求头中添加客户端的Cookie
proxy_set_header Cookie $http_cookie;
#将使用代理服务器的主域名和端口号来替换。如果端口是80,可以不加。
proxy_redirect off; #浏览器对 Cookie 有很多限制,如果 Cookie 的 Domain 部分与当前页面的 Domain 不匹配就无法写入。
#所以如果请求 A 域名,服务器 proxy_pass 到 B 域名,然后 B 服务器输出 Domian=B 的 Cookie,
#前端的页面依然停留在 A 域名上,于是浏览器就无法将 Cookie 写入。 #不仅是域名,浏览器对 Path 也有限制。我们经常会 proxy_pass 到目标服务器的某个 Path 下,
#不把这个 Path 暴露给浏览器。这时候如果目标服务器的 Cookie 写死了 Path 也会出现 Cookie 无法写入的问题。 #设置“Set-Cookie”响应头中的domain属性的替换文本,其值可以为一个字符串、正则表达式的模式或一个引用的变量
#转发后端服务器如果需要Cookie则需要将cookie domain也进行转换,否则前端域名与后端域名不一致cookie就会无法存取
#配置规则:proxy_cookie_domain serverDomain(后端服务器域) nginxDomain(nginx服务器域)
proxy_cookie_domain localhost .testcaigou800.com; #取消当前配置级别的所有proxy_cookie_domain指令
#proxy_cookie_domain off;
#与后端服务器建立连接的超时时间。一般不可能大于75秒;
proxy_connect_timeout 30;
} #error_page 404 /404.html; # redirect server error pages to the static page /50x.html
#
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
} } #当需要对同一端口监听多个域名时,使用如下配置,端口相同域名不同,server_name也可以使用正则进行配置
#但要注意server过多需要手动扩大server_names_hash_bucket_size缓存区大小
server {
listen 80;
server_name www.tjhis.cn;
charset utf-8;
location / {
proxy_pass http://localhost:10001;
}
}
#server {
# listen 80;
# server_name aaa.abc.com;
# charset utf-8;
# location / {
# proxy_pass http://localhost:20002;
# }
#}
}
将要发布的文件拷贝到目录 html 中或者指定路径,可以是相对路径,也可以使绝对路径
2.3、问题
2.3.1 无法访问子路由
nginx 增加配置即可
try_files $uri $uri/ /index.html;
完整的位置
location / {
root D:\vscode\his.vue\dist;
index index.html index.htm;
try_files $uri $uri/ /index.html;
}
3.4、显示中文
比如分页组件不显示中文的问题
修改app.vue
<template>
<el-config-provider :locale="locale">
<router-view></router-view>
</el-config-provider> </template>
<script lang="ts">
import { ElConfigProvider } from 'element-plus'
// 引入中文包
import zhCn from 'element-plus/lib/locale/lang/zh-cn'
export default {
components: {
[ElConfigProvider.name]: ElConfigProvider
},
setup() {
const lang = () => {// eslint-disable-line no-unused-vars(如果运行时报错,就加上前面这行注释)
location.reload()
}
let locale = zhCn
return {
locale
}
}
}
</script>
三、vue
3.1、组件传值
- 父传子:defineProps
- 子传父:defineEmits
- 子暴露:defineExpose
3.1.1、父传给子
父
<HelloWorld msg="我是父组件" /> <!-- 复杂类型 -->
<script setup lang="ts">
import { reactive } from "vue";
import HelloWorld from "./components/HelloWorld.vue";
let obj = reactive<number[]>([]);
obj = [1, 4, 6];
</script> <template>
<h1>父组件</h1>
<hr />
<HelloWorld :msg="obj" title="测试" />
</template>
子
<script setup lang="ts">
defineProps<{ msg: string }>();
</script> <!-- 复杂类型 -->
<script setup lang="ts">
// 都是必填项
const props = defineProps<{ msg: number[]; title: string }>();
// 如何使用
console.log(props.msg[0]);
</script> <!-- 默认值 -->
<script setup lang="ts">
const props = withDefaults(
defineProps<{ msg: number[]; title: string }>(),
{ title: "123" }
);
</script>
问题
- 父给子传值:单项绑定属性的方法
- defineProps 里面的都是必填,父组件在调用是不填写,会报错。
- 如何变成非必填项,给个默认值即可,使用 withDefaults,没有给默认值的仍然为必填项。
3.1.2、子传给父
子
<script setup lang="ts">
import { ref } from "vue";
// const emit = defineEmits(['on-click', 'on-change'])
const emit = defineEmits<{
(e: "on-click", name: any): void; // 注意这里的类型不要定义成number,否则无法和父组件同时改变值
(e: "on-change", name: string): void;
}>();
const btn = () => {
emit("on-click", count);
emit("on-change", "张三");
};
let count = ref(0);
</script> <template>
<button @click="btn">给父组件传值</button>
<h1>子组件:</h1>
<button @click="count++">count : {{ count }}</button>
</template>
<script setup lang="ts">
import { ref } from "vue";
// const emit = defineEmits(['on-click', 'on-change'])
let count = ref(0);
const btn = () => {
emit("on-click", count);
emit("on-change", "张三");
};
</script>
<template>
<button @click="btn">给父组件传值</button>
<h1>子组件:</h1>
<button @click="count++">count : {{ count }}</button>
</template>
父
<script setup lang="ts">
import { reactive, ref } from "vue";
import HelloWorld from "./components/HelloWorld.vue";
let a = ref(10);
let b = ref("");
const getName = (val: number) => {
a.value = val;
};
const getName2 = (val: string) => {
b.value = val;
};
</script> <template>
<h1>父组件 {{ a }}</h1>
{{ b }}
<hr />
<HelloWorld @on-change="getName2" @on-click="getName" />
</template>
点击 count++按钮,count 会实现父子同时改变
问题
3.1.3、子组件暴露属性和方法给父组件
子
<script setup lang="ts">
const click = () => {
console.log('北京');
}
let title = "中国"
defineExpose({
title,
click
})
</script>
父
<HelloWorld ref="childVal" />
<script setup lang="ts">
import { reactive, ref, onMounted } from 'vue';
import HelloWorld from './components/HelloWorld.vue';
// const childVal = ref<InstanceType<typeof HelloWorld>>()
const childVal = ref()
onMounted(() => {
console.log(childVal.value?.title);
childVal.value?.click()
})
3.2、Route
3.2.1、定义一个路由
- 导入组件
- 定义路由
- 类型是 Array
- component 必传 在 RouteRecordSingleView 中定义
- 第一种写法: 先定义,后引用
{ name: "工作台", path: "/desktop", component: DeskTop }
- 第二种写法,不用定义,直接用:
{ path: "/login", component: () => import("@/views/admin/LoginPage.vue") },
- path 必传 在 _RouteRecordBase 中定义
- 创建路由
- 路由守卫......
- 导出路由
// 1. 导入组件
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
import LoginPage from "@/views/admin/LoginPage.vue";
import Main from "@/views/admin/Main.vue";
import DeskTop from "@/views/admin/DeskTop.vue";
import PersonCenter from "@/views/admin/PersonCenter.vue";
import Json from "@/views/others/JsonView.vue";
import ProductListComVue from "@/components/shoppingCart/ProductListCom.vue";
// 2. 定义路由
const routes: Array<RouteRecordRaw> = [
{
path: "/",
name: "主页",
component: Main,
children: [
{ name: "工作台", path: "/desktop", component: DeskTop },
{ name: "个人信息", path: "/person", component: PersonCenter },
{ name: "Json解析", path: "/json", component: Json },
{ name: "购物车", path: "/product", component: ProductListComVue },
],
},
{ path: "/login", component: () => import("@/views/admin/LoginPage.vue") },
];
// 3. 创建路由
const router = createRouter({
history: createWebHistory(),
routes,
});
// 4.导出路由
export default router;
3.2.2、导航
- createWebHashHistory(),
- createWebHistory(),
- router-link
- 编程式导航
- 字符串模式
- 对象模式
- 命名式路由
- a标签
- 直接通过a href也可以跳转但是会刷新页面
- replace 可以进行页面跳转,但history中其不会重复保存记录,就是无法前进和后退
<!-- 使用 router-link 组件进行导航 -->
<!-- 通过传递 `to` 来指定链接 -->
<!--`<router-link>` 将呈现一个带有正确 `href` 属性的 `<a>` 标签-->
<router-link tag="div" to="/">跳转a</router-link>
<router-link tag="div" to="/register">跳转b</router-link>
<!-- name必须和定义路由的名字一致 -->
<router-link :to="{name:'Login'}">Login</router-link>
<router-link :to="{name:'Reg'}">Reg</router-link>
<script>
import { useRouter } from 'vue-router'
const router = useRouter()
// 1. 字符串模式
const toPage = () => {
router.push('/reg')}
// 2. 对象模式
const toPage = () => {
router.push({
path: '/reg'
})}
// 3. 命名式路由
const toPage = () => {
router.push({
name: 'Reg'
})
</script>
// replace用法
<router-link replace to="/reg">Reg</router-link>
<script>
const toPage = (url: string) => {
router.replace(url)
</script>
3.2.3. 路由传参
区别
- query 传参配置的是 path,而 params 传参配置的是name,在 params中配置 path 无效
- query 在路由配置不需要设置参数,而 params 必须设置
- query 传递的参数会显示在地址栏中
- params传参刷新会无效,但是 query 会保存传递过来的值,刷新不变 ;
query的传参方式
- 发送方
const showClick = (product: IProduct) => {
router.push({
path: '/productDetail',
query: { ...product }
});
}
接收方
<el-form :model="form" label-width="120px">
<el-form-item label="商品ID">
<el-input v-model="form.id" disabled />
</el-form-item>
<el-form-item label="代码">
<el-input v-model="form.itemCode" disabled />
</el-form-item>
<el-form-item label="名称">
<el-input v-model="form.itemName" disabled />
</el-form-item>
<el-form-item>
<el-button type="primary" @click="router.back()">返回</el-button>
</el-form-item>
</el-form>
<script setup lang='ts'>
import { reactive, ref, onMounted } from 'vue'
import { useRoute, useRouter } from "vue-router";
const route = useRoute()
const router = useRouter()
const form = route.query
</script>
编程式导航:params的传参方式,刷新后参数会丢失
const toDetail = (item: Item) => {
router.push({
name: 'Reg',
params: item
})
}
动态路由参数
路径参数 用冒号
:
表示。当一个路由被匹配时,它的 params 的值将在每个组件//动态路由参数
path:"/reg/:id",
name:"Reg",
component:()=> import('../components/reg.vue') // 传参
const toDetail = (item: Item) => {
router.push({
name: 'Reg',
params: {
id: item.id
}
})
}
3.3.4、路由守卫
to: Route, 即将要进入的目标 路由对象;
from: Route,当前导航正要离开的路由;
next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。
next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。
next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。
router.beforeEach((to, form) => {
if (localStorage["nickname"] != undefined) {
const user: UserInfo = JSON.parse(
new Tool().FormatToken(localStorage["token"])
);
const expDate = toolObj.FormatDate(user.exp);
const currDate = toolObj.GetDate();
if (to.path == "/login") {
if (expDate >= currDate) {
return { path: "/desktop" };
} else {
toolObj.ClearLocalStorage();
}
} else {
if (expDate < currDate) {
toolObj.ClearLocalStorage();
return { path: "/login" };
}
}
} else {
//避免无限重定向,因此要做个判断
if (to.path !== "/login") {
return { path: "/login" };
}
}
});
3.3.5、利用守卫做loading效果
定义页面
/**
* @description 页面加载的loading效果
* @author wanghx
* @date 2022-12-02 19:15:34
*/
<template>
<div class="wraps">
<div ref="bar" class="bar"></div>
</div>
</template> <script setup lang='ts'>
import { ref } from 'vue'
let speed = ref<number>(1)
let bar = ref<HTMLElement>()
let timer = ref<number>(0)
const startLoading = () => {
let dom = bar.value as HTMLElement;
speed.value = 1
timer.value = window.requestAnimationFrame(function fn() {
if (speed.value < 90) {
speed.value += 1;
dom.style.width = speed.value + '%'
timer.value = window.requestAnimationFrame(fn)
} else {
speed.value = 1;
window.cancelAnimationFrame(timer.value)
}
}) } const endLoading = () => {
let dom = bar.value as HTMLElement;
setTimeout(() => {
window.requestAnimationFrame(() => {
speed.value = 100;
dom.style.width = speed.value + '%'
})
}, 500) } defineExpose({
startLoading,
endLoading
})
</script> <style scoped lang="scss">
.wraps {
position: fixed;
top: 0;
width: 100%;
height: 2px; .bar {
height: inherit;
width: 0;
background: #F56C6C;
}
}
</style>
在路由的ts中,挂在页面,并在路由守卫中加载
import { createVNode, render } from "vue";
import LoadingBar from "@/components/others/loadingBar.vue";
const Vnode = createVNode(LoadingBar);
render(Vnode, document.body); // 前置路由守卫
router.beforeEach((to, form) => {
// 页面加载效果 开始
Vnode.component?.exposed?.startLoading();
} // 后置路由守卫
router.afterEach((to, from) => {
// 页面加载loading效果结束
Vnode.component?.exposed?.endLoading();
});
3.4.6、路由元数据
通过路由记录的
meta
属性可以定义路由的元信息。使用路由元信息可以在路由中附加自定义的数据,例如:
- 权限校验标识。
- 路由组件的过渡名称。
- 路由组件持久化缓存 (keep-alive) 的相关配置。
- 标题名称
{
name: "登录",
path: "/login",
component: () => import("@/views/admin/LoginPage.vue"),
meta: {
title: "登录",
},
},
declare module 'vue-router' {
interface RouteMeta {
title?: string
}
}
3.4.7、动态路由
登录时加载路由
动态路由必须是相对路径.
因为刷新时路由会丢失,会在路由守卫beforeEach中重新加载,所以components的路径一定要用../打头,且LoginPage,必须在views的根目录,index.ts必须在router的根目录.
直接把‘@/’配置到url中引入,会报错,没法识别地址
比如在LoginPage中加载路由,那么components的路径必须是相对于LoginPage的路径
页面刷新时,路由重新初始化,动态添加的路由此时已不存在,只有一些固定路由(比如登录页面)还在,所以出现了404的情况
所以:在vue项目中采用动态添加路由的方式,第一次进入页面会正常显示,但是点击刷新页面后会导致页面空白
vue3中去掉了addRoutes只能使用addRoute添加路由
{
key: 唯一值id,
title: 名称,
icon: 图标,
children: [{
parentKey: 父id,
key: 唯一值id,
title:名称,
name:路由名称
path: 文件路径,
type: 类型, MENU菜单 BUTTON 具体按钮
hiddle:作为菜单是否隐藏 true false
}]
}
网上的处理办法
// 路由守卫
let registerRouteFresh = true
router.beforeEach(async (to, from, next) => {
let res = await api.parentMenu()
let arr = []
res.data.data.filter((value, index) => {
let child = []
if (value.children && value.children.length) {
value.children.filter((val, i) => {
child.push({
name: val.routeName,
path: val.path,
component: () => import(`@/${val.component}`)
})
})
}
arr.push({
name: value.routeName,
redirect: value.redirect,
path: value.path,
component: () => import(`@/${value.component}`),
children: child
})
})
// 如果首次或者刷新界面,next(...to, replace: true)会循环遍历路由,如果to找不到对应的路由那么他会再执行一次beforeEach((to, from, next))直到找到对应的路由,我们的问题在于页面刷新以后异步获取数据,直接执行next()感觉路由添加了但是在next()之后执行的,所以我们没法导航到相应的界面。这里使用变量registerRouteFresh变量做记录,直到找到相应的路由以后,把值设置为false然后走else执行next(),整个流程就走完了,路由也就添加完了。
if (registerRouteFresh) {
arr.forEach((value, index) => {
router.addRoute(value)
})
next({...to, replace: true})
registerRouteFresh = false
} else {
next()
}
我的办法,当刷新时,路由会重置,那么就把代码写在路由中store/index.ts,使用defineAsyncComponent,Vue 3.x 新增一个辅助函数defineAsyncComponent,用来显示声明异步组件
//创建路由
const router = createRouter({
history: createWebHistory(),
routes,
}); //当前存在用户信息且有效,才会读取动态路由
if (localStorage["nickname"] != undefined) {
const user: UserInfo = JSON.parse(
new Tool().FormatToken(localStorage["token"])
);
const expDate = toolObj.FormatDate(user.exp);
const currDate = toolObj.GetDate();
if (expDate >= currDate) {
//读取webapi,加载用户路由信息
const list: MenuModel[] = (await getRouters()) as any as MenuModel[];
if (list.length > 0) {
router.addRoute({
path: "/",
name: "admin",
//这儿不能使用@
component: () => import("@/views/admin/Main.vue"),
});
list.forEach((v: any) => {
router.addRoute("admin", {
path: v.path,
name: v.name,
//这儿不能使用@
component: () =>
defineAsyncComponent(
() => import(/* @vite-ignore */ v.filePath + ".vue")
),
});
});
}
}
}总结:动态路由,问题太多,直接配置吧
四、TypeScript
4.1、定义接口
interface IProduct {
id: number;
itemCode: string;
itemName: string;
itemSpec: string;
units: string;
price: number;
inventory: number;
}
4.2、定义类型
- 合并 IProduct 类,但是不需要 inventory 属性
type Cart = {
quantity: number;
} & Omit<IProduct, "inventory">;
4.3、展开运算符
- 18 行:
- 问题:TCartProduct 会多一个属性 inventory
state: () => {
return {
ProductList: [] as IProduct[],
CartProducts: [] as TCartProduct[],
};
},
actions: {
addProductToCart(product: IProduct) {
// 1、是否有库存
if (product.inventory < 1) {
ElMessage.error(
`商品:${product.itemName}库存为${product.inventory},已经不足`
);
return;
}
// 2、购物车是否已存在该商品
const cartItem = this.CartProducts.find((item) => item.id === product.id);
// 3、存在则数量加 1
if (cartItem) {
cartItem.quantity++;
product.inventory--;
}
// 4、不存在则新增,且数量为 1
else {
this.CartProducts.push({ ...product, quantity: 1 });
product.inventory--;
}
},
}
五、Element Plus
5.1、table 中行内按钮获取行数据
- 10 行:通过<template #default="scope"> 获取
<el-table :data="productStore.ProductList" border stripe style="width: 100%">
<el-table-column prop="itemName" label="名称" width="300" fixed sortable />
<el-table-column prop="id" label="Id" width="120" align="center" />
<el-table-column prop="itemCode" label="编号" width="200" />
<el-table-column prop="itemSpec" label="规格" width="300" />
<el-table-column prop="units" label="单位" width="120" />
<el-table-column prop="price" label="单价" width="120" align="right" />
<el-table-column prop="inventory" label="库存" width="120" align="right" />
<el-table-column fixed="right" label="操作" width="140" align="center">
<template #default="scope">
<el-button
type="primary"
size="small"
icon="el-icon-delete"
style="width: 100px;"
@click.stop="addClick(scope.row)"
>
<el-icon size="16" color="#ffffff"> <ShoppingCart /> </el-icon
><span style="padding-left: 6px;">加入购物车</span>
</el-button>
</template>
</el-table-column>
</el-table>
5.2、tab的计算列
- html代码
<h3>【{{ useGlobalStore().NickName }}】的购物车</h3>
<el-table :data="cartStore.CartProducts" border stripe style="width: 100%" show-summary sum-text="总计"
:summary-method="getSummaries">
<el-table-column prop="itemName" label="名称" width="300" fixed />
<el-table-column prop="itemSpec" label="规格" width="300" />
<el-table-column prop="units" label="单位" width="160" />
<el-table-column prop="price" label="单价" width="160" align="right" />
<el-table-column fixed="right" prop="quantity" label="数量" width="160" align="right" />
<el-table-column fixed="right" prop="costs" label="小计" width="160" align="right" />
<el-table-column fixed="right" label="操作" width="140">
<template #default="scope">
<el-button type="danger" size="small" style="width: 100px;" @click.stop="delClick(scope.row)">
<el-icon size="16" color="#ffffff">
<Delete />
</el-icon><span style="padding-left: 6px;">删除</span>
</el-button>
</template>
</el-table-column>
</el-table>
- 计算函数
/**
* tab的计算列
*/
const getSummaries = (param: SummaryMethodProps): string[] => {
// 解构出来所有的字段和数据
const { columns, data } = param
// 计算列字段
const sums: string[] = []
columns.forEach((column, index) => {
if (index === 0) {
sums[index] = '总计'
return
}
// 单价不合计
if (column.property === 'price') {
return
}
// 获取所有的值
const values = data.map(item => Number(item[column.property]))
if (!values.every(value => Number.isNaN(value))) {
var temp = ` ${values.reduce((prev, curr) => {
const value = Number(curr)
if (!Number.isNaN(value)) {
return prev + curr
} else {
return prev
}
}, 0)}`;
sums[index] = (Number(temp)).toFixed(2);
} else {
sums[index] = ''
}
})
return sums
}
5.3、动态Icon
全局引入
// 在main.ts注册Icon组件
import * as Icons from "@element-plus/icons-vue";
const app = createApp(App);
// 创建Icon组件
for (const [key, component] of Object.entries(Icons)) {
app.component(key, component);
}
创建通用组件
/**
* @description icon图标
* @author wanghx
* @date 2022-12-03 14:31:36
*/
<template>
<i class="el-icon" :style="setIconSvgStyle">
<component :is="getIconName" />
</i>
</template> <script setup lang='ts'>
import { computed } from 'vue'
// 定义父组件传过来的值
const props = defineProps({
// svg 图标组件名字
name: {
type: String,
},
// svg 大小
size: {
type: Number,
default: () => 1.2,
},
// svg 颜色
color: {
type: String,
},
});
// 获取 icon 图标名称
const getIconName = computed(() => {
return props?.name;
});
// 设置图标样式
const setIconSvgStyle = computed(() => {
return `font-size: ${props.size}em;color: ${props.color};margin-right:0.5em`;
});
</script> <style lang='scss' scoped> </style>使用方法
<script>
import IconCom from '@/components/others/IconCom.vue';
</script> <template>
<IconCom name="box" :size=1 color="red" />
</template>
按钮的图标
<el-button class="log-btn-commit" type="primary" :icon="Select"
@click="submitForm(ruleFormRef)">登录
</el-button>
5.4、消息提示框
消息提示
ElMessage({
message: '删除成功!',
type: 'success',
})
弹出式消息提示
showMessage(msg: string) {
ElMessageBox.alert(msg, "提示信息", {
confirmButtonText: "OK",
icon: msg.includes("成功")
? markRaw(SuccessFilled)
: msg.includes("失败")
? markRaw(CircleCloseFilled)
: markRaw(QuestionFilled),
type: msg.includes("成功")
? "success"
: msg.includes("失败")
? "error"
: "warning",
draggable: true,
// 是否支持html
dangerouslyUseHTMLString: true,
});
},
弹出式带确认框的提示
ElMessageBox.confirm(`你确定要删除id为【${ids}】的${arr.length}条数据吗?`, '提示信息', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
}).then(async () => { // 成功之后的回调函数
const res = await batchDelMenu(ids) as any as boolean
if (res) {
globalStore.showMessage("批量删除成功")
LoadTableData()
}
}).catch(() => { // 取消按钮的回调
console.log("=====");
})
5.5 、Tree V2 虚拟化树形控件
用法
<el-tree-v2 :data="treedata" :props="{ value: 'id', label: 'name', children: 'children' }"
show-checkbox ref="tree" :height="500" />
操作
// 获取选中的节点 通过ref指向
const nodes: [] = tree.value.getCheckedNodes() // 将某些节点设置为选中
watch(() => props.roleId, async (newId, oldIdo) => {
if (newId != undefined) {
defaultCheckedKeys = await GetRoleMenuById(newId) as unknown as number[];
tree.value.setCheckedKeys(defaultCheckedKeys)
}
else { }
})
六、pinia
6.1、计算属性的用法
getters: {
// 获取所有商品的总价格
totalPrice(state) {
return state.CartProducts.reduce((total, item) => {
return total + item.costs;
}, 0);
},
},
七、其他技巧
7.1、元素两边分
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
}
7.2、自动适应
<el-row :gutter="10">
<el-col :span="24" :offset="0" :xs="24" :sm="12" :md="12" :lg="6" :xl="6">
</el-col>
</el-row>
vue-vite-ts 新版的更多相关文章
- Vite+TS带你搭建一个属于自己的Vue3组件库
theme: nico 前言 随着前端技术的发展,业界涌现出了许多的UI组件库.例如我们熟知的ElementUI,Vant,AntDesign等等.但是作为一个前端开发者,你知道一个UI组件库是如何被 ...
- 快速新建并配置一个eslint+prettier+husky+commitlint+vue3+vite+ts+pnpm的项目
前置准备 一台电脑 vscode pnpm vscode插件:ESLint v2.2.6及以上 vscode插件:Prettier - Code formatter v9.5.0及以上 vscode插 ...
- vite+ts+vue3+router4+Pinia+ElmPlus+axios+mock项目基本配置
1.vite+TS+Vue3 npm create vite Project name:... yourProjectName Select a framework:>>Vue Selec ...
- vite + ts 快速搭建 vue3 项目 以及介绍相关特性
博客地址:https://ainyi.com/98 Vue3.0,One Piece 接下来得抽空好好学习了 vite 尤大在 Vue 3.0 beta 直播中推荐了 vite 的工具,强调:针对Vu ...
- vue3.0+vite+ts项目搭建(报错处理)
报错一 warning package.json: No license field$ vue-tsc --noEmit && vite build 解决方案,添加这两行,只添加一个是 ...
- vue3.0+vite+ts项目搭建--vite.config.ts配置(三)
vite.config.ts配置 配置路径处理模块 安装ts的类型声明文件 yarn add @types/node -D 通过配置alias来定义路径的别名 resolve: { alias: { ...
- vue3.0+vite+ts项目搭建--基础配置(二)
集成vue-router 使用yarn yarn add vue-router@next --save 安装完成之后在src目录下创建文件夹router/index.ts,创建完成之后需要在Vue-R ...
- Vue实践TS中的一些常见错误解决方案
mixin报错 import { Component, Prop, Vue ,Mixins} from 'vue-property-decorator' import httpminix from ' ...
- 基于typescript编写vue的ts文件语法模板
1 <template> 2 <div> 3 <input v-model="msg"> 4 <p>prop: {{ propMes ...
- vue3.0+vite+ts项目搭建-分环境打包(四)
分环境打包配置 新建.env.dev(或者.env) VITE_NODE_ENV = 'dev' VITE_HOST = 'http://local.host.com' 执行yarn dev ,控制台 ...
随机推荐
- org.elasticsearch.ElasticsearchException: not all primary shards of [.geoip_databases] index are active解决办法
解决办法 在配置elasticsearch.yml中加上 ingest.geoip.downloader.enabled: false
- Google Earth Engine——基于新的Landsat SR数据集去云处理
根据GEE官方公告,明年原来的Landsat/LT05/C01/T1_SR和Landsat/LC08/C01/T1_SR数据集将停止更新,并提供了新的地表反射率数据,就是LANDSAT/LT05/C0 ...
- 自定义一个JdbcTemplate(增删改数据库中表记录)
需求: 自定义一个JdbcTemplate模板,实现增删改数据库中表记录的功能 1 package demo03; 2 3 import utils.JDBC_DBCP_Utils; 4 5 impo ...
- N63050 第十五周运维作业
第二十九天: 网络文件共享服务 1基于DB数据库文件实现FTP的虚拟用户 2基于MySQL数据库文件实现FTP的虚拟用户 3NFS服务的工作原理 4NFS共享服务实现详解 5实现NFS共享存储的LAM ...
- N63050 第十一周运维作业
第十一周 就业和全程班小伙伴本周学习内容: 二十一.Mysql数据库二 1.MySQL的视图函数存储过程触发器和事件管理(64分钟) 2.MySQL用户和权限管理(40分钟) 3.MySQL架构和存储 ...
- SAP 采购订单行项目客制化字段增强
需求: 在采购订单行项目中新增客制化字段,区分采购的项目中的物料是量产还是研发物料 开发步骤 主要使用二代增强出口:MM06E005 创建增强项目 事务码T-code:CMOD 创建项目ZEMM001 ...
- 多点DLT (Direct Linear Transformation) 算法
阅读前可以先参看上一篇代数视觉博客: 四点DLT (Dierct Linear Transformation) 算法 对于大于4个点的数据点来进行 DLT 算法变换, 如果数据点的标注都十分准确,那么 ...
- KU060板卡设计资料原理图第636篇:基于FMC的KU060高性能 PCIe 载板
基于FMC的KU060高性能 PCIe 载板 一.板卡概述 板卡主控芯片采用Xilinx 公司的 Kintex UltraScale系列FPGA XCKU060-2FFVA1156.板载 2 组 64 ...
- 【Linux】常用
查看端口占用 yum install lsof lsof -i:8080:查看8080端口占用 lsof abc.txt:显示开启文件abc.txt的进程 lsof -c abc:显示abc进程现在打 ...
- gcc_to_use
gcc 目录 gcc 概要 基本指令及功能(以gcc为例) gcc -gdb gcc -cmake 概要 GCC:GNU Compiler Collection(GUN 编译器集合),是GNU项目中符 ...