# document **Repository Path**: simon0134/document ## Basic Information - **Project Name**: document - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2020-06-30 - **Last Updated**: 2020-12-19 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 前言 项目目为多个模块的多入口单页面引用,模块根据文件夹区分,每个模块可单独运行及独立部署。 ## 项目技术栈 * Vue.js全家桶: Vue、Vue-router、vue、+axios * UI库:ElementUI * 构建工具Webpack+vue-cli * 文件上传:阿里云:ali-oss、aliyun-upload-sdk * 国际化:vue-i18n * 富文本编辑器:tinymce * 代码格式:eslint ## 项目开发 ### 项目初始化及安装依赖 ``` npm install ``` ### 开发调试 ``` npm run dev:[module]:[env] ``` **命令行参数** 举例:npm run dev:admin 启动运营后台开发环境本地调试 参数 | 说明 | 可选值 :----- | :---- | :---- module | 模块名 | admin:运营后台, client:商家管理后台 env | 接口环境 | dev:开发环境,test:测试环境,uat:预发布环境,prod:生产环境 ### 打包部署 ``` #单独打包部署 npm run build:[module]:[env] #打包全部 npm run build-all ``` ## 项目目录结构 ``` ├── babel.config.js // babel配置 ├── config // 多模块配置 │ └── multi.conf.js ├── dist // 项目打包生成目录 ├── package.json ├── public │ ├── favicon.ico // 图标 │ ├── lib // 第三方依赖 │ └── index.html // 入口文件模板 ├── src │ ├── admin // 运营后台模块 │ ├── client // 商家管理后台模块 │ └── common // 公共资源 │ ├── common-components // 公用组件库 │ ├── common-store // 全局公用store │ ├── js // 全局公用工具类和方法 │ ├── mixins // 公用组件mixin │ └── styles // 全局样式 ├── .eslintrc.js // eslint代码格式配置 ├── i18n // 国际化配置 ├── .env.development // 本地调试环境变量 ├── .env.production // 生产环境变量 ├── .env.production.test // 测试环境变量 ├── .env.production.uat // UAT环境变量 └── vue.config.js // 项目配置 ``` system-admin ## 项目配置 ### 多模块配置 multi.conf.js ```javascript const path = require('path') // 获取src下一级目录 function resolve (dir) { return path.join(__dirname, '..', dir) } function getModuleAlias () { const alias = {} importModules.forEach(({ name }) => { alias[`@${name}`] = resolve(`src/${name}`) }) return alias } // 模块打包配置 class MultiModule { constructor (name, opts) { Object.assign(this, { name, assetsSubDirectory: 'static', // 打包后资源路经 assetsPublicPath: './', // 根路径 port: 8080, // 本地调试端口 host: '0.0.0.0', entry: `./src/${name}/${name}-main.js`, // 模块入口文件 title: opts.title, // 网页标题 alias: resolve(`src/${name}`), // 模块根路径别名 index: path.resolve(__dirname, `../dist/${name}/index.html`), // 网页模板路劲 favicon: path.resolve(__dirname, `../src/${name}/assets/favicon.ico`), // 网页图标路劲 assetsRoot: path.resolve(__dirname, `../dist/${name}/`) // 模块打包路经 }, opts) } } // 获取当前运行模块需要的配值 function getModuleProcess (name) { const mItem = importModules.find(item => item.name === name) return mItem || importModules[0] } // 多模块独立配置 const importModules = [ new MultiModule('admin', { title: '运营后台', assetsPublicPath: './' }), new MultiModule('client', { title: '商家后台', assetsPublicPath: './' }) ] // 获取脚当前正在运行的脚本名称 // [ 'dev', 'admin' ] const lifecycleEvents = String(process.env.npm_lifecycle_event).split(':') // 截取模块名如果没有默认admin const moduleName = lifecycleEvents[1] || 'admin' const multiConfig = { modules: importModules, moduleAlias: getModuleAlias(), // 模块根目录别名,目前项目没有用到 process: getModuleProcess(moduleName), moduleName } module.exports = multiConfig ``` ### 项目配置 vue.config.js ```javascript // 多模块配置 const multiConfig = require('./config/multi.conf') const path = require('path') function resolve (dir) { return path.join(__dirname, dir) } console.log(multiConfig.moduleName) module.exports = { // 部署应用包时的基本 URL publicPath: process.env.NODE_ENV === 'production' ? multiConfig.process.assetsPublicPath : multiConfig.process.assetsPublicPath, outputDir: multiConfig.process.assetsRoot, // 生产环境构建文件的目录 assetsDir: 'static', // 放置生成的静态资源 (js、css、img、fonts) 的 (相对于 outputDir 的) 目录 // 向 CSS 相关的 loader 传递选项 css: { loaderOptions: { sass: { // 所有scss 文件都会导入,包含颜射变量,全局mixin等 prependData: '@import "@/common/styles/variables.scss";' } } }, configureWebpack (config) { // 入口文件 config.entry = multiConfig.process.entry config.resolve = { extensions: ['.js', '.vue', '.json', '.css'], alias: { vue$: 'vue/dist/vue.esm.js', '@': resolve('src') // src别名 } } }, productionSourceMap: false, // 生产环境关闭sourcemap devServer: { open: true, // 本地调试制动打开浏览器 overlay: { errors: false, // 代码格式错误是否显示错误提示图层 warnings: false }, proxy: { // 本地接口代理,跨域处理 '/hcp': { // target: 'http://hcp-server.ieltsbro.com/', target: 'http://test.user-center.ieltsbro.com', changeOrigin: true, ws: true } } }, chainWebpack: config => { // set svg-sprite-loader // 加载当前模块目录下的svg图标并将加载的 svg 图片拼接成 雪碧图,放到页面中, config.module .rule('svg') .exclude.add(resolve(`src/${multiConfig.process.name}/${multiConfig.process.name}-icons`)) .end() config.module .rule('icons') .test(/\.svg$/) .include.add(resolve(`src/${multiConfig.process.name}/${multiConfig.process.name}-icons`)) .end() .use('svg-sprite-loader') .loader('svg-sprite-loader') .options({ symbolId: 'icon-[name]' }) .end() config .plugin('html') .tap(args => { // 网页模板,如果模块没有指定则使用公共模板 args[0].template = multiConfig.process.template || 'public/index.html' // 网页标题 args[0].title = multiConfig.process.title return args }) .end() } } ``` ### 环境变量配置 举例:.env.development ```javascript NODE_ENV=development // 表示本地开发调式环境 VUE_APP_NODE_ENV=dev // 当前模式名 VUE_APP_COLOR=#17B3A3 // 网页主题色 VUE_APP_BASE_URL=http://47.112.100.85:9527/backManager // 接口地址 ``` ## 网络请求 项目的网络请求是基于axios封装的全局方法,文件路径为 src/common/js/request.js,在模块的主入口文件注册了全局方法名 ```javascript import http from '../common/js/request' Vue.prototype.$http = http ``` ### 全局请求拦截 ```javascript // 全局请求拦截 service.interceptors.request.use( config => { // 跨域处理 config.headers[ 'Access-Control-Allow-Origin' ] = '*' //请求token鉴权 config.headers.Authorization = `Bearer ${getToken()}` config.headers.clientCode = '001' // 不同客户端clientCode 后台管理为'001' // form 表单数据提交前处理 if (/^application\/x-www-form-urlencoded/.test(config.headers[ 'content-type' ])) { config.data = qs.stringify(config.data) } return config }, error => { console.log(error) // for debug return Promise.reject(error) }) ``` ### 全局响应拦截 ```javascript service.interceptors.response.use( response => { const res = response.data // 响应数据 if (response.data.code === 1005) { // token过期或token不正确重定向到登录也 showError(response.data.message) removeToken() router.replace(`/login?redirect=${window.location.hash.replace('#', '')}`) return Promise.reject(response.data) } else if (response.data.code !== 0) { // 响应数据不为code不为0是全局统一处理 showError(response.data.message) return Promise.reject(response.data) } return res.data }, error => { // 网络错误提示 if (!error.response) { return showError('网络错误!') } showError(error.response.data.message) return Promise.reject(error) }) ``` ### get请求 ```javascript this.$http.get( url, {...请求参数} ).then(res => { // 响应数据 }).catch(error => { // 错误处理 }) ``` ### post 表单 ```javascript this.$http.post( url, {...请求参数} ).then(res => { // 响应数据 }).catch(error => { // 错误处理 }) ``` ### post json ```javascript this.$http.postJson( url, {...请求参数} ).then(res => { // 响应数据 }).catch(error => { // 错误处理 }) ``` ## 页面布局 页面整体布局是一个产品最外层的框架结构,往包含导航、侧边栏、面包屑以及内容等基础布局 ###Layout ![Layout](/images/layout.png) ``` 对应代码 src/common/common-components/layout ``` 大部分页面都是基于这个 layout 的,除了个别页面如:login , 404, 401 等页面没有使用该layout。 ### app-main ``` 对应代码 src/common/common-components/layout/components/AppMain.vue ``` 这里在 app-main 外部包了一层 keep-alive 主要是为了缓存 的,配合页面的 tags-view 标签导航使用。 其中transition 定义了页面之间切换动画 ## 路由和侧边栏 本项目侧边栏和路由是绑定在一起的,在模块下/router/index.js constantRoutes下面配置对应的路由,或者在权限管理菜单中增加菜单,侧边栏菜单就能动态的生成了 ### 配置项 ```javascript // 当设置 true 的时候该路由不会在侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1 hidden: true // (默认 false) //当设置 noRedirect 的时候该路由在面包屑导航中不可被点击 redirect: 'noRedirect' // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如系统页面 // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面 // 若你想不管路由下面的 children 声明的个数都显示你的根路由 // 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由 alwaysShow: true name: 'router-name' // 设定路由的名字,一定要填写,并且要和菜单配置的url一致,不然使用时会出现各种问题 meta: { title: 'title' // 设置该路由在侧边栏和面包屑中展示的名字 icon: 'svg-name' // 设置该路由的图标,支持 svg-class,也支持 el-icon-x element-ui 的 icon noCache: true // 如果设置为true,则不会被 缓存(默认 false) breadcrumb: false // 如果设置为false,则不会在breadcrumb面包屑中显示(默认 true) affix: true // 若果设置为true,它则会固定在tags-view中(默认 false) // 当路由设置了该属性,则会高亮相对应的侧边栏。 // 这在某些场景非常有用,比如:一个文章的列表页路由为:/article/list // 点击文章进入文章详情页,这时候路由为/article/1,但你想在侧边栏高亮文章列表的路由,就可以进行如下设置 activeMenu: '/article/list' } ``` ### 路由 这里的路由分为两种,constantRoutes 和 asyncRoutes。 constantRoutes: 代表那些不需要动态判断权限的路由,如登录页、404、等通用页面。 asyncRoutes: 代表那些需求动态判断权限并通过 addRoutes 动态添加的页面。 ### 侧边栏 侧边栏是通过读取路由并结合权限判断而动态生成 ``` 对应代码 src/common/common-components/layout/components/Sidebar ``` 在 Sidebar 做了判断,当你一个路由下面的 children 声明的路由大于>1 个时,自动会变成嵌套的模式。如果子路由正好等于一个就会默认将子路由作为根路由显示在侧边栏中,若不想这样,可以通过设置在根路由中设置alwaysShow: true来取消这一特性 ### 面包屑 本项目封装了一个面包屑导航,它也是通过 watch $route 变化动态生成的。可以在路由中声明breadcrumb:false,让其不在 breadcrumb 面包屑显示。 ``` 对应代码 src/common/common-components/Breadcrumb ``` ### 标签栏导航 ``` 对应代码 src/common/common-components/layout/components/TagsView ``` 目前 tags-view 维护了两个数组。 * visitedViews : 用户访问过的页面 就是标签栏导航显示的一个个 tag 数组集合 * cachedViews : 实际 keep-alive 的路由。可以在配置路由的时候通过 meta.noCache 来设置是否需要缓存这个路由 默认都缓存 ## 权限验证 项目中权限的实现方式是:通过获取当前用户的权限去比对路由表,生成当前用户具的权限可访问的路由表,通过 router.addRoutes 动态挂载到 router 上。 ``` 对应代码 [module/[module]-perimission.js] 如:admin/admin-permission.js ``` ### 指令权限 封装了一个指令权限,能简单快速的实现按钮级别的权限判断。 v-permission 使用 ```vue 新增 ``` ## svg 图标 项目对icon采用svg来展示 ``` 对应代码 src/common/common-components/SvgIcon ``` **注意** 因为需要将svg图片拼接成雪碧图,放到页面中,svg需要放在模块下指定目中,如admin模块下放在admin-icons/svg目录下 **使用** ```vue ``` ## 常用共用组件 项目的共用组件在src/common/common-components目录下 ### 表格 BasePaginationPage 基于elementUI el-table 封装的表格组件,组件封装了网络请求和分页逻辑 如果想自己处理请求和分页逻辑,可使用BaseTable组件 **使用** ```vue export default { mixins: [pageTableMixin], data () { return { columns: [ { title: '名称', prop: 'name', align: 'left' }, { title: '图标', prop: 'appId', render: row => { return ( ) } }, { title: '卖家是否可见', prop: 'sellerVisibleFlag', formatter: row => { return row.sellerVisibleFlag === 1 ? '是' : '排序' } }, { title: '排序', prop: 'seq' }, { width: 80, title: '操作', fixed: 'right', actions: [ { name: '编辑', permission: 'goods:category:edit', action: row => { this.editBean = { ...row } this.editCategoryVisible = true } } ] } ] } } ``` **属性参数** 参数 | 说明 | 类型 | 可选值 | 默认值 :----- | :---- | :--- | :---- | :--- highlightCurrentRow | 是否高亮当前现在行 | Boolean | - | true maxHeight | 表格最大高度 | string/number | - | - height | 表格高度 | string/number | - | - indexMethod | 行号计算方法 | Function(index) | - | - indexAble | 是否显示序号 | Boolean | - | false selectable | 是否显示多选 | Boolean | - | false loading | 是否显示loading动画 | Boolean | - | false refreshAbleWhenActivated | 缓存页面actived时是否需要刷新数据 | Boolean | - | false params | 请求参数 | Object | - | - listTable | 是否为不分页表格 | Boolean | - | false dataListUrl | 接口请求地址 | string | - | - method | 接口请求方法 | string | get/post/json | - 其他属性同el-table ### DisabledButton * 使用场景:不可点击按钮,hover是显示提示 ```vue ``` ### NetDictSelect 字典下拉选择 项目大量使用了云端字典值,所以封装了字典选择组件,组件内部对字典值做了缓存处理 ```vue ``` 参数 | 说明 | 类型 | 可选值 | 默认值 :----- | :---- | :--- | :---- | :--- placeholder | 占位符 | string | - | - hiddenValues | 隐藏默写字典值 | Array | - | - code | 字典可以 | string | - | - 其他属性同el-select **字典值显示** ```vue ``` ### ImageUpload 图片上传组件 ```vue ``` 参数 | 说明 | 类型 | 可选值 | 默认值 :----- | :---- | :--- | :---- | :--- preList | 预览图 | Array | - | - disabled | 是否可用 | Boolean | - | false autoUpload | 是否自动上传 | Boolean | - | false multiple | 是否多选 | Boolean | - | false limit | 多选限制图片数 | Number | - | - hideRemove | 隐藏删除按钮 | Number | - | - accept | 可选文件格式 | Number | - | - folderCode | 自定上传的路径编码 | Number | - | - handleExceed | 图片过大回调 | Function | - | - onSuccess | 图片上传成功回调 | Function | - | - onRemove | 图片删除回调 | Function | - | - uploadLimit | 限制图片大小 | Function | - | - ### Tinymce 富文本编辑器 ```vue ``` 参数 | 说明 | 类型 | 可选值 | 默认值 :----- | :---- | :--- | :---- | :--- insertBlank | 题库插入填空工具 | Boolean | - | false doubleBlank | 题库插入双填空工具 | Boolean | - | false uploadVideo | 上传视频工具 | Boolean | - | false editorImage | 上传图片工具 | Boolean | - | false height | 高度 | Number | - | 600 width | 宽度 | Number | - | auto menubar | 是否显示菜单栏 | Number | - | auto isLite | 是否精简模式 | Boolean | - | false isMin | 是否迷你模式 | Boolean | - | false