企业管理系统定制开发没有特别的幸运,企业管理系统定制开发那么就特别的努力!!!
vue3 + + ts + vant + axios + sass 移动端h5企业管理系统定制开发搭建新项目
vue3 + vite + ts + vant + axios + sass
企业管理系统定制开发搭建第一个 Vite 项目 (vite + vue + ts)
企业管理系统定制开发兼容性注意
Vite 需要 Node.js 版本 14.18+,16+。然而,企业管理系统定制开发有些模板需要依赖更高的 Node 企业管理系统定制开发版本才能正常运行,当你的包管理器发出警告时,请注意升级你的 Node 版本。
nvm管理node多版本。
// 搭建第一个 Vite 项目 (vite + vue + ts)// npm (本篇采用npm搭建)npm init vite@latest // yarnyarn create vite// pnpmpnpm create vite
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
# npm 6.xnpm create vite@latest vite-vue3 --template vue# npm 7+, extra double-dash is needed:npm create vite@latest vite-vue3 -- --template vue# yarnyarn create vite vite-vue3 --template vue# pnpmpnpm create vite vite-vue3 --template vue
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
项目启动
cd vite-vue3npm installnpm run dev
- 1
- 2
- 3
- 4
- 5
- 6
代码规范 (格式化、提示)
eslint
# 自动生成配置文件并安装下面四个依赖npx eslint --init# 或者手动创建文件# npm i eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin eslint-plugin-vue -D
- 1
- 2
- 3
- 4
- 5
npm i prettier eslint-config-prettier eslint-plugin-prettier -D
- 1
创建prettier文件
// prettier.cjsmodule.exports = { printWidth: 100, tabWidth: 2, useTabs: false, // 是否使用tab进行缩进,默认为false singleQuote: true, // 是否使用单引号代替双引号,默认为false semi: true, // 行尾是否使用分号,默认为true arrowParens: 'always', endOfLine: 'auto', vueIndentScriptAndStyle: true, htmlWhitespaceSensitivity: 'strict',};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
配置eslintrc
// eslintrc.cjsmodule.exports = { root: true, // 停止向上查找父级目录中的配置文件 env: { browser: true, es2021: true, node: true, }, extends: [ 'eslint:recommended', 'plugin:vue/vue3-essential', 'plugin:@typescript-eslint/recommended', 'plugin:prettier/recommended', 'prettier', // eslint-config-prettier 的缩写 ], parser: 'vue-eslint-parser', // 指定要使用的解析器 // 给解析器传入一些其他的配置参数 parserOptions: { ecmaVersion: 'latest', // 支持的es版本 parser: '@typescript-eslint/parser', sourceType: 'module', // 模块类型,默认为script,我们设置为module }, plugins: ['vue', '@typescript-eslint', 'prettier'], // eslint-plugin- 可以省略 rules: { 'vue/multi-word-component-names': 'off', '@typescript-eslint/no-var-requires': 'off', },};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
配置 tsconfig
// tsconfig.json{ "compilerOptions": { "target": "ESNext", "useDefineForClassFields": true, "module": "ESNext", "moduleResolution": "Node", "strict": true, "jsx": "preserve", "sourceMap": true, "resolveJsonModule": true, "isolatedModules": true, "esModuleInterop": true, "lib": ["ESNext", "DOM"], "skipLibCheck": true, // 👆是初始化默认配置 /* 在ts中导入js模块会报错找不到类型声明 解决方法一: 仅设置 "allowJs": true 即可 注:allowJs设置true时,下方include不可以加入'src/**\/*.js',否则报错'无法写入文件xx因为它会覆盖输入文件' 方法二: 仅在 env.d.ts 中加入 declare module '*.js'; 模块定义即可 总结:和 "include": ["src/**\/*.js"] 没有任何关系 */ "allowJs": true, // 允许编译器编译JS,JSX文件 "baseUrl": "./", // "typeRoots": [ // "node_modules/@types" // 默认会从'node_modules/@types'路径去引入声明文件 // ], // "types": ["node"] // 仅引入'node'模块 // "paths"是相对于"baseUrl"进行解析 // 在vite.config里配置了路径别名resolve.alias,为了让编译 ts 时也能够解析对应的路径,我们还需要配置 paths 选项 "paths": { "@/*": ["src/*"], } }, "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], // references属性是 TypeScript 3.0 的新特性,允许将 TypeScript 程序拆分结构化(即拆成多个文件,分别配置不同的部分)。 "references": [{ "path": "./tsconfig.node.json" }]}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
tsconfig.node.json
{ "compilerOptions": { "composite": true, "module": "ESNext", "moduleResolution": "Node", "allowSyntheticDefaultImports": true }, "include": ["vite.config.ts", "config/index.ts"]}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
CSS 预处理器
less安装使用
// npm 安装npm install lessnpm install less-loader// yarn 安装yarn add less less-loader
- 1
- 2
- 3
- 4
- 5
- 6
// 使用< style lang="less" scoped></ style>
- 1
- 2
sass安装使用
// npm 安装npm install -D sass sass-loader// yarn 安装yarn add sass sass-loader
- 1
- 2
- 3
- 4
- 5
<style lang="scss" scoped>.home { background-color: #eee; height: 100vh;}</style>
- 1
- 2
- 3
- 4
- 5
- 6
vant 安装
安装
// npm 安装npm i vant// yarn 安装yarn add vant// 通过 pnpm 安装pnpm add vant
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
常规用法
import { createApp } from 'vue'import './style.css'// 1. 引入你需要的组件import { Button } from 'vant';// 2. 引入组件样式import 'vant/lib/index.css';import App from './App.vue'const app = createApp(App)// 3. 注册你需要的组件app.use(Button);app.mount('#app');
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
按需引入组件样式
// 通过 npm 安装npm i unplugin-vue-components -D// 通过 yarn 安装yarn add unplugin-vue-components -D// 通过 pnpm 安装pnpm add unplugin-vue-components -D
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
配置插件
vite 的项目,在 vite.config.js 文件中配置插件:
import vue from '@vitejs/plugin-vue';import Components from 'unplugin-vue-components/vite';import { VantResolver } from 'unplugin-vue-components/resolvers';export default { plugins: [ vue(), Components({ resolvers: [VantResolver()], }), ],};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
使用组件
<template> <van-button type="primary" /></template>
- 1
- 2
- 3
Rem 布局适配
// npm 安装npm install -D postcss-pxtorem lib-flexible// yarn 安装yarn add postcss-pxtorem lib-flexible
- 1
- 2
- 3
- 4
- 5
根目录下面新建一个 .config.js 文件
// postcss.config.jsmodule.exports = { plugins: { 'postcss-pxtorem': { rootValue: 37.5, propList: ['*'], }, },};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
底部适配 - 对于ios系统
<!-- 在 head 标签中添加 meta 标签,并设置 viewport-fit=cover 值 --><meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, viewport-fit=cover"/><!-- 开启顶部安全区适配 --><van-nav-bar safe-area-inset-top /><!-- 开启底部安全区适配 --><van-number-keyboard safe-area-inset-bottom />
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
vue-router
1.安装
npm i vue-router@4
- 1
2.创建路由
// src/router/index.ts//现在创建router的方式与vue2.x的版本已经很不同了import { createRouter, createWebHashHistory } from "vue-router";import { routes } from "./routes";const router = createRouter({ history: createWebHashHistory(), //替代之前的mode,是必须的 routes,});router.beforeEach((to, from, next) => { document.title = to.meta.title as string || '浙里普法' next()})export default router;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
// src/router/routes.tsimport { RouteRecordRaw } from "vue-router";export const routes: Array<RouteRecordRaw> = [ { path: "/", redirect: "/index", }, { path: "/index", name: "Index", component: () => import("../view/index.vue"), meta: { nav: true, title: '首页' } },];
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
3.挂载路由
// src/main.tsimport { createApp } from 'vue';import App from './App.vue';import router from './router/index'; //引入vue-routerconst app = createApp(App);app.use(router); // 挂载到app上app.mount('#app');
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
4.使用
<template> <router-view /></template>
- 1
- 2
- 3
Axios
1.安装
// npm 安装npm i axios// yarn 安装yarn add axios
- 1
- 2
- 3
- 4
- 5
// src/utils/http/axios.tsimport axios, { AxiosResponse, AxiosRequestConfig, AxiosError } from 'axios';import type { Response } from './types';// import { auth } from '@/utils';import { Toast } from 'vant';import router from '../../router';axios.defaults.baseURL = '/api';axios.defaults.timeout = 1000 * 60;axios.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=UTF-8';// 创建axios实例const service = axios.create({ // 根据不同env设置不同的baseURL baseURL: import.meta.env.VITE_APP_API_BASE_URL,});// axios实例拦截请求service.interceptors.request.use( (config: AxiosRequestConfig) => { config.headers = { ...config.headers, // ...auth.headers(), // 你的自定义headers,如token等 }; return config; }, (error: AxiosError) => { return Promise.reject(error); });// axios实例拦截响应service.interceptors.response.use( // 2xx时触发 (response: AxiosResponse<Response>) => { // response.data就是后端返回的数据,结构根据你们的约定来定义 const { code, message } = response.data; let errMessage = ''; switch (code) { case 0: break; case 1: // token过期 errMessage = 'Token expired'; router.push('/login'); break; case 2: // 无权限 errMessage = 'No permission'; break; // default: // errMessage = message; // break; } if (errMessage) Toast.fail(errMessage); return response; }, // 非2xx时触发 (error: AxiosError) => { Toast.fail('Network Error...'); return Promise.reject(error); });export type { AxiosResponse, AxiosRequestConfig };export default service;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
// src/utils/http/index.tsimport service, { AxiosRequestConfig } from './axios';export * from './types';export const request = <T = any>(config: AxiosRequestConfig): Promise<T> => { return new Promise((resolve, reject) => { service .request(config) .then((res) => { // 一些业务处理 resolve(res.data); }) .catch((err) => { console.log('request fail:', err); }); });};const http = { get<T = any>(url: string, params = {}, config?: AxiosRequestConfig): Promise<T> { return request({ url, params, ...config, method: 'GET' }); }, post<T = any>(url: string, data = {}, config?: AxiosRequestConfig): Promise<T> { return request({ url, data, ...config, method: 'POST' }); }, put<T = any>(url: string, data = {}, config?: AxiosRequestConfig): Promise<T> { return request({ url, data, ...config, method: 'PUT' }); }, delete<T = any>(url: string, data = {}, config?: AxiosRequestConfig): Promise<T> { return request({ url, data, ...config, method: 'DELETE' }); }, // 上传文件,指定 'Content-Type': 'multipart/form-data' upload<T = any>(url: string, data = {}, config?: AxiosRequestConfig): Promise<T> { return request({ url, data, ...config, method: 'POST', headers: { 'Content-Type': 'multipart/form-data' }, }); },};export default http;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
// src/utils/http/types.ts// 和后端约定好接口返回的数据结构export interface Response<T = any> { data: string[]; code: number | string; message: string; result: T;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
示例页面
banner列表页面
<script setup>import ResourceList from '@/components/ResourceList.vue'import { monthlyResourceList } from '@/service/api/common'import { onMounted, ref } from 'vue'import { useRoute, useRouter } from "vue-router";const $route = useRoute()const $router = useRouter()const list = ref([])const loading = ref(false);const finished = ref(false);const refreshing = ref(false);const params = ref({ relationId: $route.query.id, relationType: 'banner', currentPage: 1, pageSize: 10})onMounted(() => { document.title = $route.query.name getColumnResourceList()})const getColumnResourceList = () => monthlyResourceList(params.value).then(res => { loading.value = true if (res.success) { loading.value = false list.value = [...list.value,...res.data] // 如果列表数据条数>=总条数,不再触发滚动加载 if (list.value.length >= res.totalCount) { finished.value = true } }})const onRefresh = () => { params.value.currentPage = 1 finished.value = false; refreshing.value = false list.value = [] getColumnResourceList();};const onLoad1 = () => { params.value.currentPage++ getColumnResourceList()}const toInfo = row => { const { type, resourceSource, resourceId, id: relationId, relationType = 'banner' } = row $router.push({ path: '/detail', query: { type, resourceSource, resourceId, relationId, relationType } })}</script><template> <div class='monthInfo'> <van-pull-refresh v-model="refreshing" @refresh="onRefresh"> <van-list v-model:loading="loading" :finished="finished" finished-text="没有更多了" :immediate-check="false" @load="onLoad1" > <div v-for="(item, i) in list" :key="i"> <ResourceList :info="item" @click="toInfo(item)"></ResourceList> </div> </van-list> </van-pull-refresh> </div></template><style lang='scss' scoped>.monthInfo { padding: 22px 16px;}</style>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
项目地址
为了安全协议:项目地址api 已全部替换(望理解!!!)
vue3 开发
父组件传参
defineProps
父组件
<template> <Children :msg="msg" :list="list"></Children></template><script setup lang="ts">import { ref, reactive } from 'vue'import Children from './Children.vue'const msg = ref('hello 啊,树哥')const list = reactive<number[]>([1, 2, 3])</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
子组件
<template> <div> <p>msg:{{msg}}</p> <p>list:{{list}}</p> </div></template><script setup lang="ts">import { defineProps } from "vue";const { msg, list } = defineProps(['msg', 'list'])</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
withDefaults 定义默认值
<script setup lang="ts">import { defineProps } from "vue";withDefaults( defineProps<{ msg?: (string | number | boolean), title?: string }>(),{ msg:'hello vite', title:'默认标题' });</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
defineEmits
子组件传递
<template> <div> <p>msg:{{msg}}</p> <p>list:{{list}}</p> <button @click="onChangeMsg">改变msg</button> </div></template><script setup lang="ts">type Props = { msg?: string, list?: number[]}withDefaults(defineProps<Props>(), { msg: '张麻子', list: () => [4, 5, 6]})const emits = defineEmits(['changeMsg'])const onChangeMsg = () => {emits('changeMsg','黄四郎')}</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
父组件接收
<template> <Children :msg="msg" :list="list" @changeMsg="changeMsg"></Children></template><script setup lang="ts">import { ref, reactive } from 'vue'import Children from './Children.vue'const msg = ref('hello 啊,树哥')const list = reactive<number[]>([1, 2, 3])const changeMsg = (v: string) => { msg.value = v}</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
ref VS reactive
- reactive返回一个对象的响应式代理。
- ref参数一般接收简单数据类型,若ref接收对象为参数,本质上会转变为reactive方法
- 在JS中访问ref的值需要手动添加.value,访问reactive不需要
- 响应式的底层原理都是Proxy
watch
侦听一个或多个响应式数据源,并在数据源变化时调用所给的回调函数。
监听ref定义的一个响应式数据
<script setup lang="ts">import { ref, watch } from "vue";const str = ref('一个值')//3s后改变str的值setTimeout(() => { str.value = '3s后一个值' }, 3000)watch(str, (newV, oldV) => { console.log(newV, oldV) //3s后一个值 一个值})</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
监听多个ref
<script setup lang="ts">import { ref, watch } from "vue";let name = ref('树哥')let age = ref(18)//3s后改变值setTimeout(() => { name.value = '我叫树哥' age.value = 19}, 3000)watch([name, age], (newV, oldV) => { console.log(newV, oldV) // ['我叫树哥', 19] ['树哥', 18]})</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
监听reactive 定义响应式对象的单一属性
<script setup lang="ts">import { reactive, watch } from "vue";let info = reactive({ name: '张麻子', age: 18, obj: { str: '彼时彼刻,恰如此时此刻' }})//3s后改变s值setTimeout(() => { info.obj.str = 'to be or not to be'}, 3000)// 需要自己开启 deep:true深度监听,不然不发触发 watch 的回调函数watch(() => info.obj, (newV, oldV) => { console.log(newV, oldV)}, { deep: true})</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
watch VS watchEffect
watch只有监听的值发生变化的时候才会执行
watchEffect 立即运行一个函数,同时响应式地追踪其依赖,并在依赖更改时重新执行。
wacthEffect 无法获取到原值,只能得到变化后的值
watchEffect 不用指明监视哪个属性,监视的回调中用到哪个属性就监视哪个属性
生命周期
keep-alive 缓存组件
作用和vue2一致,只是生命周期名称有所更改
<template> <div class="full-screen"> <router-view v-slot="{ Component }"> <keep-alive :include="['Index', 'secondaryPage', 'resource', 'monthInfo', 'collect']"> <component :is="Component" /> </keep-alive> </router-view> </div></template>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
provide/inject
provide 可以在祖先组件中指定我们想要提供给后代组件的数据或方法,而在任何后代组件中,我们都可以使用 inject 来接收 provide 提供的数据或方法。
父组件
<template> <router-view v-if="isRouterView"></router-view></template><script lang="ts" setup>import { ref, provide, nextTick } from "vue";const isRouterView = ref(true);//父组件刷新方法const reload = () => { isRouterView.value = false; nextTick(() => { isRouterView.value = true; })}//provide进行注册provide('reload', reload);</script>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
子/孙组件
<script lang="ts" setup>//子孙组件引入injectimport { ref,inject } from "vue";const reload = inject("reload");//调用方法使用const handleClick = (val: any) => { if (typeof reload == "function") reload();};</script >
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
希望能帮助到大家,同时祝愿大家在开发旅途中愉快!!!
可以运用nvm管理node多版本,其中最常见就是环境依赖问题 (npm 安装报错 npm ERR! Unexpected token ‘.’) 可以参考这篇文章: