# crm解决方案 **Repository Path**: pangzong/crm-solution ## Basic Information - **Project Name**: crm解决方案 - **Description**: 此项目是通过个人的业余时间学习总结出来的一套适用于中小型CRM后台管理项目的解决方案空壳。什么叫做空壳,就是没有集成二次封装的功能组件,只实现最基础的必要方案,例如layout架构、登陆、权限管理等。 用户开箱即用,只需要在现有的代码上,专注于业务与组件的开发。 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-12-07 - **Last Updated**: 2023-02-04 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # CRM中小型后台管理解决方案空壳 # 简介 此项目是通过个人的业余时间学习总结出来的一套适用于中小型CRM后台管理项目的解决方案空壳。什么叫做空壳,就是没有集成二次封装的功能组件,只实现最基础的必要方案,例如layout架构、登陆、权限管理等。 用户开箱即用,只需要在现有的代码上,专注于业务与组件的开发。 提示: - 不对IE以及一些使用老旧内核的浏览器做任何特殊兼容。 - 由于只是提供解决方案,用户体验上的侧重不会很大,需要用户自己根据项目需求完善。 - 目前只是最初的版本,将来会不定期的完善。 演示网址:[http://pangzong.gitee.io/crm-solution](http://pangzong.gitee.io/crm-solution) ## 关于二次封装的功能组件 本工程没有提供二次封装的功能组件,因为个人认为,很多成功的后台解决方案中提供的各式各样的功能、组件等,都不一定会在项目中被使用,而且就算有类似需求也不一定能满足。通常很多用户会费力去把多余的代码删除,所以此项目不提供二次封装的功能组件,单独另起了一个工程: 具体可以参考我的另外工程,欢迎使用:[业务组件二次封装](https://gitee.com/pangzong/pz-business-components) > 目前只是vue2的版本,且暂无使用文档,将来不定期完善后升级。 ## 技术栈 本工程使用到的技术栈如下: [![](https://img.shields.io/badge/vue3-{})]() [![](https://img.shields.io/badge/vuex-{})]() [![](https://img.shields.io/badge/vue_cli-{})]() [![](https://img.shields.io/badge/vue_router-{})]() [![](https://img.shields.io/badge/webpack-{})]() [![](https://img.shields.io/badge/element_plus-{})]() [![](https://img.shields.io/badge/eslint-{})]() [![](https://img.shields.io/badge/prettier-{})]() [![](https://img.shields.io/badge/commitizen-{})]() [![](https://img.shields.io/badge/commitlint-{})]() [![](https://img.shields.io/badge/husky-{})]() [![](https://img.shields.io/badge/jsencrypt-{})]() [![](https://img.shields.io/badge/js_cookie-{})]() [![](https://img.shields.io/badge/md5-{})]() [![](https://img.shields.io/badge/i18n-{})]() [![](https://img.shields.io/badge/fuse.js-{})]() [![](https://img.shields.io/badge/rgb_hex.js-{})]() [![](https://img.shields.io/badge/css_color_function.js-{})]() [![](https://img.shields.io/badge/screenfull-{})]() [![](https://img.shields.io/badge/driver.js-{})]() [![](https://img.shields.io/badge/day.js-{})]() [![](https://img.shields.io/badge/nprogress-{})]() ## 基本指令 以下指令可以使用npm或cnpm。 ``` js // 依赖安装 yarn install // 工程启动 yarn serve // 工程打包 yarn build // Lints and fixes files yarn lint ``` ----- # 代码规范方案 实现过程可以参考我的这篇博客:[【工程化】记录一些vue工程中eslint、prettier以及git提交等规范的知识点](https://blog.csdn.net/pagnzong/article/details/121688379?spm=1001.2014.3001.5501) ------ # svg图标解决方案 全局的svg组件放置在目录`src\components\SvgIcon\index.vue`,内部提供了外部(外网)与内部(本地静态)的svg图标引用。 本地静态icon:在目录`src\icons`文件夹内放置静态svg文件,以及组件全局的挂载,在main.js文件做相应配置。 外部icon:使用的是iconfont,并采用symbol方案(ant design开源设计UI),具体使用可看他们的官网,我这里采用在index.html文件直接获取他们生成的在线js文件(暂不支持独立url,下面有讲)。 还需要loader能让webpack读懂svg文件: ``` npm i --save-dev svg-sprite-loader@6.0.9 ``` 在vue.config.js文件中增加对应配置,重启工程即可。 ## 组件目前问题 在实践之后,发现一个组件中同时处理外部和内部svg图标容易出现一些问题,首先,目前第三方库对组件多个根节点的支持不是很好,其次容易带来一些外部引用时属性注入的问题。所以暂时先把外部独立url图片的支持先注释掉。 ------ # 一些基础配置 ## 配置环境变量 首先使用cli提供的环境变量设置,可以根据设定的开发环境、测试环境、生产环境等匹配不同的baseUrl。 此项目只做开发和生产环境的配置。首先根目录下创建两个对应的文件: 1. .env.development 2. .env.production 在目录`src\utils\request.js`文件axios的封装中做相应的配置,比如baseURL根据环境变量修改,token存储与失效处理等。 ## devServer代理设置 本工程接口请求采用的是easymock在线请求网址提供的能力,在本地环境中使用devServer去代理设置。 因为现在请求到的地址是往工程内部去找的,并没有发送出去,需要借助webpack的devServer去帮我们做转接,这样请求就会先经过devServer,然后再帮我们转发到正确的外网地址中去。 在vue.config.js中做对应的配置。 > 在浏览器网络中看仍然是请求的本地地址,但实际已经替换成外网地址了 生产环境的直接请求easymock。 ----- # 本地缓存 登陆的缓存方案采用localStorage、cookie与vuex。 localStorage:目录`src\utils\storage.js` 用于封装`localStorage`的方法。 cookie:采用第三方js-cookie。具体使用可以看登陆页面`src\views\login\index.vue` --- # 登陆退出方案 具体看我的记录博客:[【场景方案】适用于简单后台管理的登陆退出鉴权解决方案](https://blog.csdn.net/pagnzong/article/details/124936050) ---- # Layout架构 解决方案 组件位置为目录`src\layout`文件夹内,index.vue为框架,Navbar.vue为顶栏组件,AppMain.vue为内容区域组件,Sidebar文件夹负责动态menu菜单,即侧边菜单。 对应需要的样式在`src\styles`文件夹中的: - `variables.scss`:一些全局的常量,可以通过js引用,可用于后续更换主题。 - `mixin.scss`:定义整个工程通用的mixin css。 - `sidebar.scss`:侧边导航栏的样式。 ## 动态menu菜单 需要做的是依据**路由表**,生成**menu菜单**。 在目录`src\router\index.js`中设置私有路由表和公有路由表。 动态的menu组件就根据路由表去配置,但是获取的方式需要细节调整。 想要获取路由表数据,那么有两种方式: 1. `router.options.routes`:初始路由列表(新增的路由无法获取到,因为路由是可以动态配置的) 2. `router.getRoutes()`:获取所有路由记录的完整列表 所以,我们此时使用`router.getRoutes()`,但是通过这个方法获取到的路由表,会把子路由重复的放到外面的一级路由上,所以需要专门对其处理并且剔除不需要展示在menu的路由(`meta && meta.title && meta.icon`表示需要出现在menu中)。 可由目录`src\utils\route.js`专门处理。 知道如何获取menu菜单数据后就可以配置菜单组件了,组件文件夹地址`src\layout\components\Sidebar`。 动态menu菜单还涉及到一些权限处理,下方可进行跳转 [用户管理权限解决方案-页面权限](#页面权限) ### 伸缩功能 先通过`src\store\modules\app.js`里面的文件去处理伸缩状态的控制。 伸缩按钮写在`src\components\Hamburger`中,然后引入在navBar中,并与左侧边栏组件通过开关的变量进行联动。 不要忘了在layout的index.vue中对顶栏的宽度动态控制。 ## 动态面包屑功能 对应组件在`src\components\Breadcrumb`中 同时,加入动画`src\styles\transition.scss`(好像vue3这次动画并不会对包裹在内的所有标签进行动画渲染,只会对内部发生改变的标签进行动画渲染) ---- # 国际化解决方案 ## 简单原理 ``` html ``` ## 实现 需要用到v9版本的i18n库。 使用目录`src\i18n`文件夹下提供的文件,在main.js中进行注册。 然后在vuex中的app.js中写好记录当前语言的全局代码。 在目录`src\components\LangSelect`文件负责提供语言切换组件。 ### 使用 在组件中使用i18n: ``` html // 模板中直接$t('msg.test')的方式去获取显示文字 ``` 在js文件中使用: ``` js import i18n from '@/i18n' const t = i18n.global.t; // 需要这样使用t('msg.login.passwordRule') ``` 注意:有个需要注意的地方,如果是在js文件中使用,导出的应该是个可执行的函数,因为如果语言切换了,t函数是还要执行一遍才能成功切换,例如: ``` js import i18n from '@/i18n' const t = i18n.global.t; export default () => t('msg.yoxi') ``` 在使用的时候: ``` js import { watchSwitchLang } from "@/utils/i18n"; import yoxi from "./yoxi"; export const yoxi = ref(yoxi()); // 监听 语言变化 watchSwitchLang(() => { // 重新获取国际化的值 yoxi.value = yoxi(); // ... }); ``` 因为这个原因,在写e-table的规则时,`message: i18n.t("msg.login.usernameRule")`这种形式的写法会导致出现提示后切换语言,该提示不会自动切换语言的问题。 ## element plus 国际化 当前2021/12/11,不支持i18n的动态国际化切换,只能先使用官方提供的方法(缺点是切换语言后要刷新一下页面才能生效) 在main.js中做相应配置即可。 ## 接口请求国际化 这里用个人中心页面做举例: 组件放在`src\views\profile`。 接口在`src\api\user.js`。 在**后端支持**的情况下把`src\utils\request.js`请求方法中的`Accept-Language`设置语言类型。 然后在页面中通过语言监听函数`watchSwitchLang`重新调用接口。 同时在App.vue中也需要随时更新不同语言的用户信息数据。 ## 相对时间的国际化 相对时间的意思是例如:“一个小时前”,实现手段在`src\filters\index.js`。 然后在模板中就可以直接使用`{{ $filters.relativeTime(row.publicDate) }}` ---- # 动态换肤 原理就是提前写好全局的css变量,通过改变css变量,让绑定这个变量的dom样式发生改变。 换肤下拉框组件放在目录`src\components\ThemeSelect`里。 对应的状态管理在目录`src\store\modules\theme.js`。 ## element plus的主题色更换 原理: 1. 获取当前element-plus的所有样式,一般为一个样式文件。 2. 定义我们要替换之后的样式。 3. 在原样式文件中,利用正则替换新样式成我们定义的样式。 4. 把替换后的样式写入到index.html的`style`标签中(利用内部样式表优先级高于外部样式表实现样式覆盖)。 需要两个库进行辅助(不知道用在哪里没关系,看后面提到的文件中的代码就理解了): 1. rgb-hex:转换RGB(A)颜色为十六进制。 2. css-color-function:在一种CSS中颜色函数的解析器和转换器。 首先建个颜色表`src\constant\formula.json`,具体写法参考https://gist.github.com/benfrain/7545629。 这个表的意思是:因为主题色是有周围色的(比如把主题色加入不同亮度的白色或者黑色),tint方法就是加亮,shade方法就是加暗,primary变量就是主题色,最后通过color方法把每个颜色的色值都算出来。就拿到了一个颜色表。 然后在`src\utils\theme.js`文件中写对主题色的替换处理的方法。里面的`generateNewStyle()`这个方法就是操作步奏的入口,可以到该文件具体进行查看,注释写的应该能看懂,思路挺有意思的。 同时为了防止刷新后主题回复到默认颜色,所以需要在App.vue中调用一下theme.js里的方法。 ## 自写组件的主题变更 分析:初始颜色样式都是从通过目录`src\styles\variables.scss`文件拿取静态的,所以需要在主题更换的时候进行动态变量更新,通过目录`src\store\modules\theme.js`文件这里进行处理。然后在getters中获取最新的css全局变量(variables.scss + element主题样式),即可在组件中绑定到dom节点上。 例子 `menuBg: $menuBg;`: ``` html
``` ``` css .box{ background-color: $menuBg; } ``` 在目录`src\store\modules\theme.js`文件中对css变量menuBg做更新。 ---- # 全屏方案 当我们通过app标签执行`document.getElementById('app').requestFullscreen()`,后是能够全屏展示,但是会出现一些样式问题,比如appMain组件的颜色变成黑色,远没有按浏览器的f11效果好。所以需要使用第三方包screenfull。 安装完后写对应的组件在`src\components\Screenfull\index.vue`。 ---- # 全局页面搜索 也称`headerSearch`,通过搜索页面标题的关键词,获取到页面搜索结果并且能够点击进入。 模糊搜索使用第三方库`fuse.js`。 搜索组件写在`src\components\HeaderSearch\index.vue`。并且通过`src\components\HeaderSearch\FuseData.js`文件处理menu数据成`fuse.js`搜索池的数据格式。 逻辑和注意事项都写在对应文件里了。 ---- # tags标签页 在vuex的目录`src\store\modules\app.js`中,`tagsViewList`维护tags数据,并提供了添加和删除tags的函数。 在`src\utils\tags.js`提供tags的一些工具函数。 在`src\layout\components\AppMain.vue`中监听页面路由,修改tags的数据和国际化处理。 tags组件写在`src\components\TagsView\index.vue`中。 tags的右键菜单写在`src\components\TagsView\ContextMenu.vue`中。 ---- # 页面切换动画 其实vue3已经完整的提供了方案,cv就行了。`src\layout\components\AppMain.vue`标签写在layout里。 对应动画在`src\styles\transition.scss`里。 ---- # 引导功能方案 guide 需要使用到第三方库driver.js(不过在网上这个库评价并不是很好,可以考虑introjs等其他库)。 > 注意,需要高亮的元素他的父级不能有position: fixed;的属性,否则需要高亮的元素会被覆盖。 引导功能的组件在目录`src\components\Guide`。 引导的流程步奏都写在`src\components\Guide\steps.js`中。 ----- # 用户管理权限解决方案 也叫RBAC权限控制体系。 相关页面: - 通过`src\views\role-list`角色列表页面去呈现,对应接口`src\api\role.js`。 - 通过`src\views\permission-list`权限列表页面去呈现,对应接口`src\api\permission.js`。 - 通过`src\views\user-manage`员工管理页面的角色按钮弹窗`src\views\user-manage\components\roles.vue`组件呈现。 角色与权限: 首先是有个权限列表,每个权限代表着一个页面是否能访问或者功能的使用权限。然后,有个角色列表,每个列表都能被选择性的赋予权限列表中的权限。这样,一个角色上就附有了一些权限。当一个员工成为了某一个角色,那么这个角色上的权限就赋予了这个员工身上,也就是他的账户。 ## 页面权限 [跳回到动态menu菜单](#动态menu菜单) 主要实现方式是,通过账户的权限数据,将私有路由表(需要权限的路由配置),通过router的`addRoute API`动态添加路由到**路由表**中。 首先,在路由配置文件夹中,将原来的私有路由表拆分成几个模块,例如在目录`src\router\modules`,并合并在index.js中作为私有路由。 对私有路由表的处理,例如权限的筛选,以及状态管理放在vuex `src\store\modules\permission.js`。 在`src\permission.js`中,通过路由守卫完成把权限过滤好的路由表,动态的筛入路由配置中。 最后,在退出登陆的时候要清空路由表,防止重新登陆其他用户后还是上个用户的路由表(个人感觉没必要,因为每次登陆都会重新拉取权限数据生成新的动态路由表),方法放在`src\router\index.js`中。 ## 功能权限 主要实现方式是,通过账户的权限数据,结合自定义指令,隐藏按钮或功能。 指令实现写在`src\directives\permission.js`中。 使用就直接在dom上`v-permission="['removeUser']"` ------- # 页面跳转进度条 采用第三方库nprogress,然后在路由守卫配置一下就好了。 ---- # 存在问题 - element提供的icon使用方式发生改变 - i18n报出警告 - 当前版本的element的一些组件会在渲染的时候,通过目前的样式表默认给style属性级的样式,导致不能立即换色,需要刷新。 - 换肤功能目前没有考虑到字体颜色,当皮肤颜色和文字颜色对比度低的时候体验很差。 - 目前不同解决方案里对应的方法文件归纳个人还不是很满意,将来会改善一下。 - 是不是一个方案里面的utils函数可以放在对应的vuex里呢,做到方案相关的函数聚合,且方法之间也是没有耦合的。 - 虽然vue3组件支持多个根节点的写法,但是还是推荐写一个根节点,因为多个根节点容易引起外部引用时写入的属性被当成props值而报错。