diff --git a/acupuncture-ui/.env.development b/acupuncture-ui/.env.development new file mode 100644 index 00000000..31490cb7 --- /dev/null +++ b/acupuncture-ui/.env.development @@ -0,0 +1,14 @@ +# 页面标题 +VUE_APP_TITLE = 针灸管理系统 + +# 开发环境配置 +ENV = 'development' + +# 大唐会议管理系统/开发环境 +VUE_APP_BASE_API = '/dev-api' +VUE_APP_IMG_URL = 'https://test.tall.wiki/acupuncture' +VUE_APP_API_QZURL = 'https://test.tall.wiki/' +# 路由懒加载 +VUE_CLI_BABEL_TRANSPILE_MODULES = true +# 访问路径 +VUE_APP_PUBLIC_PATH = '/' diff --git a/acupuncture-ui/.env.production b/acupuncture-ui/.env.production new file mode 100644 index 00000000..c2347fdf --- /dev/null +++ b/acupuncture-ui/.env.production @@ -0,0 +1,11 @@ +# 页面标题 +VUE_APP_TITLE = 针灸管理系统 + +# 生产环境配置 +NODE_ENV = 'production' +# 因孚生产 +VUE_APP_BASE_API = 'https://test.tall.wiki/acupuncture' +VUE_APP_API_QZURL = 'https://test.tall.wiki/' + +# 访问路径 +VUE_APP_PUBLIC_PATH = '/acupunctureClient/' \ No newline at end of file diff --git a/acupuncture-ui/.env.staging b/acupuncture-ui/.env.staging new file mode 100644 index 00000000..6523f1b3 --- /dev/null +++ b/acupuncture-ui/.env.staging @@ -0,0 +1,12 @@ +# 页面标题 +VUE_APP_TITLE = 针灸管理系统 + +BABEL_ENV = production + +NODE_ENV = production + +# 测试环境配置 +ENV = 'staging' + +# 针灸管理系统/测试环境 +VUE_APP_BASE_API = '/stage-api' diff --git a/acupuncture-ui/README.md b/acupuncture-ui/README.md new file mode 100644 index 00000000..00c0ab84 --- /dev/null +++ b/acupuncture-ui/README.md @@ -0,0 +1,30 @@ +## 开发 + +```bash +# 克隆项目 +git clone https://gitee.com/y_project/RuoYi-Vue + +# 进入项目目录 +cd ruoyi-ui + +# 安装依赖 +npm install + +# 建议不要直接使用 cnpm 安装依赖,会有各种诡异的 bug。可以通过如下操作解决 npm 下载速度慢的问题 +npm install --registry=https://registry.npmmirror.com + +# 启动服务 +npm run dev +``` + +浏览器访问 http://localhost:80 + +## 发布 + +```bash +# 构建测试环境 +npm run build:stage + +# 构建生产环境 +npm run build:prod +``` \ No newline at end of file diff --git a/acupuncture-ui/dist.zip b/acupuncture-ui/dist.zip new file mode 100644 index 00000000..215ddd7c Binary files /dev/null and b/acupuncture-ui/dist.zip differ diff --git a/acupuncture-ui/package.json b/acupuncture-ui/package.json new file mode 100644 index 00000000..c688cb1c --- /dev/null +++ b/acupuncture-ui/package.json @@ -0,0 +1,91 @@ +{ + "name": "ruoyi", + "version": "3.8.9", + "description": "针灸管理系统", + "author": "若依", + "license": "MIT", + "scripts": { + "dev": "vue-cli-service serve", + "build:prod": "vue-cli-service build", + "build:stage": "vue-cli-service build --mode staging", + "preview": "node build/index.js --preview", + "lint": "eslint --ext .js,.vue src" + }, + "husky": { + "hooks": { + "pre-commit": "lint-staged" + } + }, + "lint-staged": { + "src/**/*.{js,vue}": [ + "eslint --fix", + "git add" + ] + }, + "keywords": [ + "vue", + "admin", + "dashboard", + "element-ui", + "boilerplate", + "admin-template", + "management-system" + ], + "repository": { + "type": "git", + "url": "https://gitee.com/y_project/RuoYi-Vue.git" + }, + "dependencies": { + "@riophae/vue-treeselect": "0.4.0", + "axios": "0.28.1", + "clipboard": "2.0.8", + "core-js": "3.37.1", + "echarts": "5.4.0", + "element-ui": "^2.15.14", + "file-saver": "2.0.5", + "fuse.js": "6.4.3", + "highlight.js": "9.18.5", + "js-beautify": "1.13.0", + "js-cookie": "3.0.1", + "jsencrypt": "3.0.0-rc.1", + "nprogress": "0.2.0", + "quill": "2.0.2", + "screenfull": "5.0.2", + "sortablejs": "1.10.2", + "splitpanes": "2.4.1", + "vue": "2.6.12", + "vue-count-to": "1.0.13", + "vue-cropper": "0.5.5", + "vue-meta": "2.4.0", + "vue-router": "3.4.9", + "vuedraggable": "2.24.3", + "vuex": "3.6.0" + }, + "devDependencies": { + "@vue/cli-plugin-babel": "4.4.6", + "@vue/cli-plugin-eslint": "4.4.6", + "@vue/cli-service": "4.4.6", + "babel-eslint": "10.1.0", + "babel-plugin-dynamic-import-node": "2.3.3", + "chalk": "4.1.0", + "compression-webpack-plugin": "6.1.2", + "connect": "3.6.6", + "eslint": "7.15.0", + "eslint-plugin-vue": "7.2.0", + "lint-staged": "10.5.3", + "runjs": "4.4.2", + "sass": "1.32.13", + "sass-loader": "10.1.1", + "script-ext-html-webpack-plugin": "2.1.5", + "svg-sprite-loader": "5.1.1", + "vue-template-compiler": "2.6.12" + }, + "engines": { + "node": ">=8.9", + "npm": ">= 3.0.0" + }, + "browserslist": [ + "> 1%", + "last 2 versions" + ] +} diff --git a/acupuncture-ui/src/App.vue b/acupuncture-ui/src/App.vue new file mode 100644 index 00000000..be9682e8 --- /dev/null +++ b/acupuncture-ui/src/App.vue @@ -0,0 +1,70 @@ + + + + + diff --git a/acupuncture-ui/src/api/followupFile.js b/acupuncture-ui/src/api/followupFile.js new file mode 100644 index 00000000..ba1c7084 --- /dev/null +++ b/acupuncture-ui/src/api/followupFile.js @@ -0,0 +1,75 @@ +import request from "@/utils/request"; + +// 随访队列 +export function followupQuery(data) { + return request({ + url: "/followup/query", + method: "post", + data: data, + }); +} +// 新增随访队列 +export function followupAdd(data) { + return request({ + url: "/followup/add", + method: "post", + data: data, + }); +} +// 新增随访队列 +export function followupUpd(data) { + return request({ + url: "/followup/upd", + method: "post", + data: data, + }); +} +// 新增随访队列 +export function followupDel(data) { + return request({ + url: "/followup/del", + method: "post", + data: data, + }); +} +// ----随访对象---- +// 查看随访对象 +export function queryPatient(data) { + return request({ + url: "/followup/queryPatient", + method: "post", + data: data, + }); +} +// 修改随访对象 队列信息 +export function updPatient(data) { + return request({ + url: "/followup/updPatient", + method: "post", + data: data, + }); +} +// 随访工单 +export function queryTask(data) { + return request({ + url: "/followup/queryTask", + method: "post", + data: data, + }); +} +// 失访 +export function updStatus(data) { + return request({ + url: "/followup/updStatus", + method: "post", + data: data, + }); +} +// 患者随访 +export function followPatient(data) { + return request({ + url: "/followup/followPatient", + method: "post", + data: data, + }); +} \ No newline at end of file diff --git a/acupuncture-ui/src/api/login.js b/acupuncture-ui/src/api/login.js new file mode 100644 index 00000000..dc87eebb --- /dev/null +++ b/acupuncture-ui/src/api/login.js @@ -0,0 +1,60 @@ +import request from "@/utils/request"; + +// 登录方法 +export function login(username, password, code, uuid) { + const data = { + username, + password, + code, + uuid, + }; + return request({ + url: "/web/login", + headers: { + isToken: false, + repeatSubmit: false, + }, + method: "post", + data: data, + }); +} + +// 注册方法 +export function register(data) { + return request({ + url: "/register", + headers: { + isToken: false, + }, + method: "post", + data: data, + }); +} + +// 获取用户详细信息 +export function getInfo() { + return request({ + url: "/getInfo", + method: "get", + }); +} + +// 退出方法 +export function logout() { + return request({ + url: "/logout", + method: "post", + }); +} + +// 获取验证码 +export function getCodeImg() { + return request({ + url: "/captchaImage", + headers: { + isToken: false, + }, + method: "get", + timeout: 20000, + }); +} diff --git a/acupuncture-ui/src/api/medicalFile.js b/acupuncture-ui/src/api/medicalFile.js new file mode 100644 index 00000000..ad244b54 --- /dev/null +++ b/acupuncture-ui/src/api/medicalFile.js @@ -0,0 +1,58 @@ +import request from "@/utils/request"; + +// 获取列表 +export function treatmentQuery(data) { + return request({ + url: "/treatment/list", + method: "post", + data: data, + }); +} +// 添加患者档案 +export function treatmentAdd(data) { + return request({ + url: "/treatment/add", + method: "post", + data: data, + }); +} +// 修改患者档案 +export function treatmentUpd(data) { + return request({ + url: "/treatment/upd", + method: "post", + data: data, + }); +} +// 删除患者档案 +export function treatmentDel(data) { + return request({ + url: "/treatment/del", + method: "post", + data: data, + }); +} +// 档案详情 +export function queryRecord(data) { + return request({ + url: "/treatment/queryRecord", + method: "post", + data: data, + }); +}// 档案详情 +export function saveAidRecord(data) { + return request({ + url: "/treatment/saveAidRecord", + method: "post", + data: data, + }); +} +// 诊疗档案绑定随访队列 + +export function queueAdd(data) { + return request({ + url: "/patientQueueRelation/add", + method: "post", + data: data, + }); +} \ No newline at end of file diff --git a/acupuncture-ui/src/api/patientFile.js b/acupuncture-ui/src/api/patientFile.js new file mode 100644 index 00000000..f36a872e --- /dev/null +++ b/acupuncture-ui/src/api/patientFile.js @@ -0,0 +1,34 @@ +import request from "@/utils/request"; + +// 获取列表 +export function queryPatient(data) { + return request({ + url: "/patient/list", + method: "post", + data: data, + }); +} +// 添加患者档案 +export function patientAdd(data) { + return request({ + url: "/patient/add", + method: "post", + data: data, + }); +} +// 修改患者档案 +export function patientUpd(data) { + return request({ + url: "/patient/upd", + method: "post", + data: data, + }); +} +// 删除患者档案 +export function patientDel(data) { + return request({ + url: "/patient/del", + method: "post", + data: data, + }); +} diff --git a/acupuncture-ui/src/api/system/user.js b/acupuncture-ui/src/api/system/user.js new file mode 100644 index 00000000..b5e3edd8 --- /dev/null +++ b/acupuncture-ui/src/api/system/user.js @@ -0,0 +1,136 @@ +import request from '@/utils/request' +import { parseStrEmpty } from "@/utils/ruoyi"; + +// 查询用户列表 +export function listUser(query) { + return request({ + url: '/system/user/list', + method: 'get', + params: query + }) +} + +// 查询用户详细 +export function getUser(userId) { + return request({ + url: '/system/user/' + parseStrEmpty(userId), + method: 'get' + }) +} + +// 新增用户 +export function addUser(data) { + return request({ + url: '/system/user', + method: 'post', + data: data + }) +} + +// 修改用户 +export function updateUser(data) { + return request({ + url: '/system/user', + method: 'put', + data: data + }) +} + +// 删除用户 +export function delUser(userId) { + return request({ + url: '/system/user/' + userId, + method: 'delete' + }) +} + +// 用户密码重置 +export function resetUserPwd(userId, password) { + const data = { + userId, + password + } + return request({ + url: '/system/user/resetPwd', + method: 'put', + data: data + }) +} + +// 用户状态修改 +export function changeUserStatus(userId, status) { + const data = { + userId, + status + } + return request({ + url: '/system/user/changeStatus', + method: 'put', + data: data + }) +} + +// 查询用户个人信息 +export function getUserProfile() { + return request({ + url: '/system/user/profile', + method: 'get' + }) +} + +// 修改用户个人信息 +export function updateUserProfile(data) { + return request({ + url: '/system/user/profile', + method: 'put', + data: data + }) +} + +// 用户密码重置 +export function updateUserPwd(oldPassword, newPassword) { + const data = { + oldPassword, + newPassword + } + return request({ + url: '/system/user/profile/updatePwd', + method: 'put', + data: data + }) +} + +// 用户头像上传 +export function uploadAvatar(data) { + return request({ + url: '/system/user/profile/avatar', + method: 'post', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + data: data + }) +} + +// 查询授权角色 +export function getAuthRole(userId) { + return request({ + url: '/system/user/authRole/' + userId, + method: 'get' + }) +} + +// 保存授权角色 +export function updateAuthRole(data) { + return request({ + url: '/system/user/authRole', + method: 'put', + params: data + }) +} + +// 查询部门下拉树结构 +export function deptTreeSelect() { + return request({ + url: '/system/user/deptTree', + method: 'get' + }) +} diff --git a/acupuncture-ui/src/assets/styles/common.css b/acupuncture-ui/src/assets/styles/common.css new file mode 100644 index 00000000..52688a2d --- /dev/null +++ b/acupuncture-ui/src/assets/styles/common.css @@ -0,0 +1,135 @@ +.popup { + display: flex; + justify-content: center; + align-items: center; +} +.popup >>> .el-dialog { + max-height: 86vh; + /* margin-top: 0 !important; */ + overflow: auto; + border-radius: 6px; +} +>>>.el-dialog__header{ + padding-bottom: 20px; +} +.popup >>> .el-dialog::-webkit-scrollbar { + display: none; +} +.popup >>> .el-dialog::-webkit-scrollbar { + display: none; +} +.popup >>> .el-dialog:not(.is-fullscreen) { + margin-top: 0 !important; +} +.popup >>> .popupAdd { + height: 58px; + font-size: 14px; + line-height: 58px; + display: flex; + margin-bottom: 20px; + margin-top: 20px; + align-items: center; +} +.popup >>> .popupAdd2 { + padding-left: 1px; + + /* background-image: url(../images/addlog.png); */ + background-repeat: no-repeat; +} +.popup >>> .popupAdd3 { + margin-top: 20px; + padding-left: 1px; + /* background-image: url(../images/tj.png); */ +} +.popup .popupbox { + width: 650px; + height: 600px; + /* background-image: url(../images/solid.png); */ +} +/* .popup >>> .popupAdd .popupleft { + font-size: 14px; + position: relative; + z-index: 2; + width: 68px; + height: 62px; + line-height: 62px; + text-align: center; + background-image: url(../images/addlog.png); + background-size: 100% 100%; +} */ +.formStep >>> .el-icon-delete { + font-size: 20px; +} +.popup >>> .popupAdd .popupRight { + width: 100%; + margin-left: -20px; + height: 50px; + margin-top: 1px; + padding-left: 30px; + line-height: 50px; + font-size: 18px; +} +.inner::-webkit-scrollbar { + display: none; +} +>>>.el-dialog__body{ + padding: 0px 20px 0px 20px; +} +/* .popup >>> .popupAdd2 .popupleft { + width: 55px; +} */ +.formStep .el-button { + width: 68px; + height: 32px; + line-height: 32px; + background-size: 100% 100%; + border: none; + padding: 0; + opacity: 0.87; +} +.popup .el-button:hover { + opacity: 1; +} +.popup .popuButbox > div { + display: flex; + justify-content: center; +} + +/* .formStep >>> .el-checkbox__input.is-checked .el-checkbox__inner { + background-color: #67defc; + border-color: #67defc; +} +.formStep >>> .is-checked .el-checkbox__label { + color: #67defc; +} */ +.formStep .formStep_StepBox { + display: flex; +} +.formStep .StepBox { + width: 620px; +} + +.formStep >>> .el-input__inner, +.formStep >>> .el-textarea__inner { + /* color: #fff; */ + background: rgba(0, 0, 0, 0); +} +.formStep >>> .el-textarea__inner { + /* color: rgb(191, 203, 217); */ +} +.formStep >>> .vue-treeselect__single-value { + color: rgb(191, 203, 217); +} +.formStep >>> .el-input__inner::input-placeholder { + color: #a8c9f0; +} +.formStep >>> .el-input--medium .el-input__inner, +.formStep >>> .el-textarea__inner, +.formStep >>> .el-select, +.formStep >>> .el-input-number--medium, +.formStep >>> .el-date-editor.el-input, +.formStep >>> .el-cascader--medium, +.formStep >>> .el-autocomplete { + width: 100% !important; + text-align: left; +} diff --git a/acupuncture-ui/src/assets/styles/ruoyi.scss b/acupuncture-ui/src/assets/styles/ruoyi.scss new file mode 100644 index 00000000..7e44513c --- /dev/null +++ b/acupuncture-ui/src/assets/styles/ruoyi.scss @@ -0,0 +1,296 @@ +/** +* 通用css样式布局处理 +* Copyright (c) 2019 ruoyi +*/ + +/** 基础通用 **/ +.pt5 { + padding-top: 5px; +} + +.pr5 { + padding-right: 5px; +} + +.pb5 { + padding-bottom: 5px; +} + +.mt5 { + margin-top: 5px; +} + +.mr5 { + margin-right: 5px; +} + +.mb5 { + margin-bottom: 5px; +} + +.mb8 { + margin-bottom: 8px; +} + +.ml5 { + margin-left: 5px; +} + +.mt10 { + margin-top: 10px; +} + +.mr10 { + margin-right: 10px; +} + +.mb10 { + margin-bottom: 10px; +} +.ml10 { + margin-left: 10px; +} + +.mt20 { + margin-top: 20px; +} + +.mr20 { + margin-right: 20px; +} + +.mb20 { + margin-bottom: 20px; +} +.ml20 { + margin-left: 20px; +} + +.h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 { + font-family: inherit; + font-weight: 500; + line-height: 1.1; + color: inherit; +} + +.el-message-box__status + .el-message-box__message{ + word-break: break-word; +} + +.el-dialog:not(.is-fullscreen) { + margin-top: 6vh !important; +} + +.el-dialog__wrapper.scrollbar .el-dialog .el-dialog__body { + overflow: auto; + overflow-x: hidden; + max-height: 70vh; + padding: 10px 20px 0; +} + +.el-table { + .el-table__header-wrapper, .el-table__fixed-header-wrapper { + th { + word-break: break-word; + background-color: #f8f8f9; + color: #515a6e; + height: 40px; + font-size: 13px; + } + } + + .el-table__body-wrapper { + .el-button [class*="el-icon-"] + span { + margin-left: 1px; + } + } +} + +/** 表单布局 **/ +.form-header { + font-size: 15px; + color: #6379bb; + border-bottom: 1px solid #ddd; + margin: 8px 10px 25px 10px; + padding-bottom: 5px +} + +/** 表格布局 **/ +.pagination-container { + position: relative; + height: 32px; + margin-bottom: 10px; + margin-top: 15px; + padding: 10px 20px !important; +} + +/* tree border */ +.tree-border { + margin-top: 5px; + border: 1px solid #e5e6e7; + background: #FFFFFF none; + border-radius: 4px; +} + +.pagination-container .el-pagination { + right: 0; + position: absolute; +} + +@media (max-width: 768px) { + .pagination-container .el-pagination > .el-pagination__jump { + display: none !important; + } + .pagination-container .el-pagination > .el-pagination__sizes { + display: none !important; + } +} + +.el-table .fixed-width .el-button--mini { + padding-left: 0; + padding-right: 0; + width: inherit; +} + +/** 表格更多操作下拉样式 */ +.el-table .el-dropdown-link,.el-table .el-dropdown-selfdefine { + cursor: pointer; + margin-left: 5px; +} + +.el-table .el-dropdown, .el-icon-arrow-down { + font-size: 12px; +} + +.el-tree-node__content > .el-checkbox { + margin-right: 8px; +} + +.list-group-striped > .list-group-item { + border-left: 0; + border-right: 0; + border-radius: 0; + padding-left: 0; + padding-right: 0; +} + +.list-group { + padding-left: 0px; + list-style: none; +} + +.list-group-item { + border-bottom: 1px solid #e7eaec; + border-top: 1px solid #e7eaec; + margin-bottom: -1px; + padding: 11px 0px; + font-size: 13px; +} + +.pull-right { + float: right !important; +} + +.el-card__header { + padding: 14px 15px 7px; + min-height: 40px; +} + +.el-card__body { + padding: 15px 20px 20px 20px; +} + +.card-box { + padding-right: 15px; + padding-left: 15px; + margin-bottom: 10px; +} + +/* button color */ +.el-button--cyan.is-active, +.el-button--cyan:active { + background: #20B2AA; + border-color: #20B2AA; + color: #FFFFFF; +} + +.el-button--cyan:focus, +.el-button--cyan:hover { + background: #48D1CC; + border-color: #48D1CC; + color: #FFFFFF; +} + +.el-button--cyan { + background-color: #20B2AA; + border-color: #20B2AA; + color: #FFFFFF; +} + +/* text color */ +.text-navy { + color: #1ab394; +} + +.text-primary { + color: inherit; +} + +.text-success { + color: #1c84c6; +} + +.text-info { + color: #23c6c8; +} + +.text-warning { + color: #f8ac59; +} + +.text-danger { + color: #ed5565; +} + +.text-muted { + color: #888888; +} + +/* image */ +.img-circle { + border-radius: 50%; +} + +.img-lg { + width: 120px; + height: 120px; +} + +.avatar-upload-preview { + position: relative; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + width: 200px; + height: 200px; + border-radius: 50%; + box-shadow: 0 0 4px #ccc; + overflow: hidden; +} + +/* 拖拽列样式 */ +.sortable-ghost { + opacity: .8; + color: #fff !important; + background: #42b983 !important; +} + +.top-right-btn { + position: relative; + float: right; +} + +/* 分割面板样式 */ +.splitpanes.default-theme .splitpanes__pane { + background-color: #fff!important; +} diff --git a/acupuncture-ui/src/components/HeaderSearch/index.vue b/acupuncture-ui/src/components/HeaderSearch/index.vue new file mode 100644 index 00000000..3f806607 --- /dev/null +++ b/acupuncture-ui/src/components/HeaderSearch/index.vue @@ -0,0 +1,216 @@ + + + + + diff --git a/acupuncture-ui/src/components/RuoYi/Doc/index.vue b/acupuncture-ui/src/components/RuoYi/Doc/index.vue new file mode 100644 index 00000000..75fa8641 --- /dev/null +++ b/acupuncture-ui/src/components/RuoYi/Doc/index.vue @@ -0,0 +1,21 @@ + + + \ No newline at end of file diff --git a/acupuncture-ui/src/components/RuoYi/Git/index.vue b/acupuncture-ui/src/components/RuoYi/Git/index.vue new file mode 100644 index 00000000..bdafbaef --- /dev/null +++ b/acupuncture-ui/src/components/RuoYi/Git/index.vue @@ -0,0 +1,21 @@ + + + \ No newline at end of file diff --git a/acupuncture-ui/src/directive/dialog/drag.js b/acupuncture-ui/src/directive/dialog/drag.js new file mode 100644 index 00000000..2e823465 --- /dev/null +++ b/acupuncture-ui/src/directive/dialog/drag.js @@ -0,0 +1,64 @@ +/** +* v-dialogDrag 弹窗拖拽 +* Copyright (c) 2019 ruoyi +*/ + +export default { + bind(el, binding, vnode, oldVnode) { + const value = binding.value + if (value == false) return + // 获取拖拽内容头部 + const dialogHeaderEl = el.querySelector('.el-dialog__header'); + const dragDom = el.querySelector('.el-dialog'); + dialogHeaderEl.style.cursor = 'move'; + // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null); + const sty = dragDom.currentStyle || window.getComputedStyle(dragDom, null); + dragDom.style.position = 'absolute'; + dragDom.style.marginTop = 0; + let width = dragDom.style.width; + if (width.includes('%')) { + width = +document.body.clientWidth * (+width.replace(/\%/g, '') / 100); + } else { + width = +width.replace(/\px/g, ''); + } + dragDom.style.left = `${(document.body.clientWidth - width) / 2}px`; + // 鼠标按下事件 + dialogHeaderEl.onmousedown = (e) => { + // 鼠标按下,计算当前元素距离可视区的距离 (鼠标点击位置距离可视窗口的距离) + const disX = e.clientX - dialogHeaderEl.offsetLeft; + const disY = e.clientY - dialogHeaderEl.offsetTop; + + // 获取到的值带px 正则匹配替换 + let styL, styT; + + // 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px + if (sty.left.includes('%')) { + styL = +document.body.clientWidth * (+sty.left.replace(/\%/g, '') / 100); + styT = +document.body.clientHeight * (+sty.top.replace(/\%/g, '') / 100); + } else { + styL = +sty.left.replace(/\px/g, ''); + styT = +sty.top.replace(/\px/g, ''); + }; + + // 鼠标拖拽事件 + document.onmousemove = function (e) { + // 通过事件委托,计算移动的距离 (开始拖拽至结束拖拽的距离) + const l = e.clientX - disX; + const t = e.clientY - disY; + + let finallyL = l + styL + let finallyT = t + styT + + // 移动当前元素 + dragDom.style.left = `${finallyL}px`; + dragDom.style.top = `${finallyT}px`; + + }; + + document.onmouseup = function (e) { + document.onmousemove = null; + document.onmouseup = null; + }; + } + } +}; \ No newline at end of file diff --git a/acupuncture-ui/src/directive/dialog/dragHeight.js b/acupuncture-ui/src/directive/dialog/dragHeight.js new file mode 100644 index 00000000..97e53054 --- /dev/null +++ b/acupuncture-ui/src/directive/dialog/dragHeight.js @@ -0,0 +1,34 @@ +/** + * v-dialogDragWidth 可拖动弹窗高度(右下角) + * Copyright (c) 2019 ruoyi + */ + +export default { + bind(el) { + const dragDom = el.querySelector('.el-dialog'); + const lineEl = document.createElement('div'); + lineEl.style = 'width: 6px; background: inherit; height: 10px; position: absolute; right: 0; bottom: 0; margin: auto; z-index: 1; cursor: nwse-resize;'; + lineEl.addEventListener('mousedown', + function(e) { + // 鼠标按下,计算当前元素距离可视区的距离 + const disX = e.clientX - el.offsetLeft; + const disY = e.clientY - el.offsetTop; + // 当前宽度 高度 + const curWidth = dragDom.offsetWidth; + const curHeight = dragDom.offsetHeight; + document.onmousemove = function(e) { + e.preventDefault(); // 移动时禁用默认事件 + // 通过事件委托,计算移动的距离 + const xl = e.clientX - disX; + const yl = e.clientY - disY + dragDom.style.width = `${curWidth + xl}px`; + dragDom.style.height = `${curHeight + yl}px`; + }; + document.onmouseup = function(e) { + document.onmousemove = null; + document.onmouseup = null; + }; + }, false); + dragDom.appendChild(lineEl); + } +} diff --git a/acupuncture-ui/src/directive/dialog/dragWidth.js b/acupuncture-ui/src/directive/dialog/dragWidth.js new file mode 100644 index 00000000..2df08673 --- /dev/null +++ b/acupuncture-ui/src/directive/dialog/dragWidth.js @@ -0,0 +1,30 @@ +/** + * v-dialogDragWidth 可拖动弹窗宽度(右侧边) + * Copyright (c) 2019 ruoyi + */ + +export default { + bind(el) { + const dragDom = el.querySelector('.el-dialog'); + const lineEl = document.createElement('div'); + lineEl.style = 'width: 5px; background: inherit; height: 80%; position: absolute; right: 0; top: 0; bottom: 0; margin: auto; z-index: 1; cursor: w-resize;'; + lineEl.addEventListener('mousedown', + function (e) { + // 鼠标按下,计算当前元素距离可视区的距离 + const disX = e.clientX - el.offsetLeft; + // 当前宽度 + const curWidth = dragDom.offsetWidth; + document.onmousemove = function (e) { + e.preventDefault(); // 移动时禁用默认事件 + // 通过事件委托,计算移动的距离 + const l = e.clientX - disX; + dragDom.style.width = `${curWidth + l}px`; + }; + document.onmouseup = function (e) { + document.onmousemove = null; + document.onmouseup = null; + }; + }, false); + dragDom.appendChild(lineEl); + } +} diff --git a/acupuncture-ui/src/directive/module/clipboard.js b/acupuncture-ui/src/directive/module/clipboard.js new file mode 100644 index 00000000..635315a8 --- /dev/null +++ b/acupuncture-ui/src/directive/module/clipboard.js @@ -0,0 +1,54 @@ +/** +* v-clipboard 文字复制剪贴 +* Copyright (c) 2021 ruoyi +*/ + +import Clipboard from 'clipboard' +export default { + bind(el, binding, vnode) { + switch (binding.arg) { + case 'success': + el._vClipBoard_success = binding.value; + break; + case 'error': + el._vClipBoard_error = binding.value; + break; + default: { + const clipboard = new Clipboard(el, { + text: () => binding.value, + action: () => binding.arg === 'cut' ? 'cut' : 'copy' + }); + clipboard.on('success', e => { + const callback = el._vClipBoard_success; + callback && callback(e); + }); + clipboard.on('error', e => { + const callback = el._vClipBoard_error; + callback && callback(e); + }); + el._vClipBoard = clipboard; + } + } + }, + update(el, binding) { + if (binding.arg === 'success') { + el._vClipBoard_success = binding.value; + } else if (binding.arg === 'error') { + el._vClipBoard_error = binding.value; + } else { + el._vClipBoard.text = function () { return binding.value; }; + el._vClipBoard.action = () => binding.arg === 'cut' ? 'cut' : 'copy'; + } + }, + unbind(el, binding) { + if (!el._vClipboard) return + if (binding.arg === 'success') { + delete el._vClipBoard_success; + } else if (binding.arg === 'error') { + delete el._vClipBoard_error; + } else { + el._vClipBoard.destroy(); + delete el._vClipBoard; + } + } +} diff --git a/acupuncture-ui/src/directive/permission/hasPermi.js b/acupuncture-ui/src/directive/permission/hasPermi.js new file mode 100644 index 00000000..7101e3e8 --- /dev/null +++ b/acupuncture-ui/src/directive/permission/hasPermi.js @@ -0,0 +1,28 @@ + /** + * v-hasPermi 操作权限处理 + * Copyright (c) 2019 ruoyi + */ + +import store from '@/store' + +export default { + inserted(el, binding, vnode) { + const { value } = binding + const all_permission = "*:*:*"; + const permissions = store.getters && store.getters.permissions + + if (value && value instanceof Array && value.length > 0) { + const permissionFlag = value + + const hasPermissions = permissions.some(permission => { + return all_permission === permission || permissionFlag.includes(permission) + }) + + if (!hasPermissions) { + el.parentNode && el.parentNode.removeChild(el) + } + } else { + throw new Error(`请设置操作权限标签值`) + } + } +} diff --git a/acupuncture-ui/src/directive/permission/hasRole.js b/acupuncture-ui/src/directive/permission/hasRole.js new file mode 100644 index 00000000..ad9d4d79 --- /dev/null +++ b/acupuncture-ui/src/directive/permission/hasRole.js @@ -0,0 +1,28 @@ + /** + * v-hasRole 角色权限处理 + * Copyright (c) 2019 ruoyi + */ + +import store from '@/store' + +export default { + inserted(el, binding, vnode) { + const { value } = binding + const super_admin = "admin"; + const roles = store.getters && store.getters.roles + + if (value && value instanceof Array && value.length > 0) { + const roleFlag = value + + const hasRole = roles.some(role => { + return super_admin === role || roleFlag.includes(role) + }) + + if (!hasRole) { + el.parentNode && el.parentNode.removeChild(el) + } + } else { + throw new Error(`请设置角色权限标签值"`) + } + } +} diff --git a/acupuncture-ui/src/layout/components/Navbar.vue b/acupuncture-ui/src/layout/components/Navbar.vue new file mode 100644 index 00000000..4a2f9cd4 --- /dev/null +++ b/acupuncture-ui/src/layout/components/Navbar.vue @@ -0,0 +1,209 @@ + + + + + diff --git a/acupuncture-ui/src/main.js b/acupuncture-ui/src/main.js new file mode 100644 index 00000000..13c6cf29 --- /dev/null +++ b/acupuncture-ui/src/main.js @@ -0,0 +1,86 @@ +import Vue from 'vue' + +import Cookies from 'js-cookie' + +import Element from 'element-ui' +import './assets/styles/element-variables.scss' + +import '@/assets/styles/index.scss' // global css +import '@/assets/styles/ruoyi.scss' // ruoyi css +import App from './App' +import store from './store' +import router from './router' +import directive from './directive' // directive +import plugins from './plugins' // plugins +import { download } from '@/utils/request' + +import './assets/icons' // icon +import './permission' // permission control +import { getDicts } from "@/api/system/dict/data"; +import { getConfigKey } from "@/api/system/config"; +import { parseTime, resetForm, addDateRange, selectDictLabel, selectDictLabels, handleTree } from "@/utils/ruoyi"; +// 分页组件 +import Pagination from "@/components/Pagination"; +// 自定义表格工具组件 +import RightToolbar from "@/components/RightToolbar" +// 富文本组件 +import Editor from "@/components/Editor" +// 文件上传组件 +import FileUpload from "@/components/FileUpload" +// 图片上传组件 +import ImageUpload from "@/components/ImageUpload" +// 图片预览组件 +import ImagePreview from "@/components/ImagePreview" +// 字典标签组件 +import DictTag from '@/components/DictTag' +// 头部标签组件 +import VueMeta from 'vue-meta' +// 字典数据组件 +import DictData from '@/components/DictData' + +// 全局方法挂载 +Vue.prototype.getDicts = getDicts +Vue.prototype.getConfigKey = getConfigKey +Vue.prototype.parseTime = parseTime +Vue.prototype.resetForm = resetForm +Vue.prototype.addDateRange = addDateRange +Vue.prototype.selectDictLabel = selectDictLabel +Vue.prototype.selectDictLabels = selectDictLabels +Vue.prototype.download = download +Vue.prototype.handleTree = handleTree + +// 全局组件挂载 +Vue.component('DictTag', DictTag) +Vue.component('Pagination', Pagination) +Vue.component('RightToolbar', RightToolbar) +Vue.component('Editor', Editor) +Vue.component('FileUpload', FileUpload) +Vue.component('ImageUpload', ImageUpload) +Vue.component('ImagePreview', ImagePreview) + +Vue.use(directive) +Vue.use(plugins) +Vue.use(VueMeta) +DictData.install() + +/** + * If you don't want to use mock-server + * you want to use MockJs for mock api + * you can execute: mockXHR() + * + * Currently MockJs will be used in the production environment, + * please remove it before going online! ! ! + */ + +Vue.use(Element, { + size: Cookies.get('size') || 'medium' // set element-ui default size +}) + +Vue.config.productionTip = false + +new Vue({ + el: '#app', + router, + store, + render: h => h(App) +}) diff --git a/acupuncture-ui/src/plugins/download.js b/acupuncture-ui/src/plugins/download.js new file mode 100644 index 00000000..42acd006 --- /dev/null +++ b/acupuncture-ui/src/plugins/download.js @@ -0,0 +1,79 @@ +import axios from 'axios' +import {Loading, Message} from 'element-ui' +import { saveAs } from 'file-saver' +import { getToken } from '@/utils/auth' +import errorCode from '@/utils/errorCode' +import { blobValidate } from "@/utils/ruoyi"; + +const baseURL = process.env.VUE_APP_BASE_API +let downloadLoadingInstance; + +export default { + name(name, isDelete = true) { + var url = baseURL + "/common/download?fileName=" + encodeURIComponent(name) + "&delete=" + isDelete + axios({ + method: 'get', + url: url, + responseType: 'blob', + headers: { 'Authorization': 'Bearer ' + getToken() } + }).then((res) => { + const isBlob = blobValidate(res.data); + if (isBlob) { + const blob = new Blob([res.data]) + this.saveAs(blob, decodeURIComponent(res.headers['download-filename'])) + } else { + this.printErrMsg(res.data); + } + }) + }, + resource(resource) { + var url = baseURL + "/common/download/resource?resource=" + encodeURIComponent(resource); + axios({ + method: 'get', + url: url, + responseType: 'blob', + headers: { 'Authorization': 'Bearer ' + getToken() } + }).then((res) => { + const isBlob = blobValidate(res.data); + if (isBlob) { + const blob = new Blob([res.data]) + this.saveAs(blob, decodeURIComponent(res.headers['download-filename'])) + } else { + this.printErrMsg(res.data); + } + }) + }, + zip(url, name) { + var url = baseURL + url + downloadLoadingInstance = Loading.service({ text: "正在下载数据,请稍候", spinner: "el-icon-loading", background: "rgba(0, 0, 0, 0.7)", }) + axios({ + method: 'get', + url: url, + responseType: 'blob', + headers: { 'Authorization': 'Bearer ' + getToken() } + }).then((res) => { + const isBlob = blobValidate(res.data); + if (isBlob) { + const blob = new Blob([res.data], { type: 'application/zip' }) + this.saveAs(blob, name) + } else { + this.printErrMsg(res.data); + } + downloadLoadingInstance.close(); + }).catch((r) => { + console.error(r) + Message.error('下载文件出现错误,请联系管理员!') + downloadLoadingInstance.close(); + }) + }, + saveAs(text, name, opts) { + saveAs(text, name, opts); + }, + async printErrMsg(data) { + const resText = await data.text(); + const rspObj = JSON.parse(resText); + const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'] + Message.error(errMsg); + } +} + diff --git a/acupuncture-ui/src/router/index.js b/acupuncture-ui/src/router/index.js new file mode 100644 index 00000000..1a862cab --- /dev/null +++ b/acupuncture-ui/src/router/index.js @@ -0,0 +1,236 @@ +import Vue from "vue"; +import Router from "vue-router"; + +Vue.use(Router); + +/* Layout */ +import Layout from "@/layout"; + +/** + * Note: 路由配置项 + * + * hidden: true // 当设置 true 的时候该路由不会再侧边栏出现 如401,login等页面,或者如一些编辑页面/edit/1 + * alwaysShow: true // 当你一个路由下面的 children 声明的路由大于1个时,自动会变成嵌套的模式--如组件页面 + * // 只有一个时,会将那个子路由当做根路由显示在侧边栏--如引导页面 + * // 若你想不管路由下面的 children 声明的个数都显示你的根路由 + * // 你可以设置 alwaysShow: true,这样它就会忽略之前定义的规则,一直显示根路由 + * redirect: noRedirect // 当设置 noRedirect 的时候该路由在面包屑导航中不可被点击 + * name:'router-name' // 设定路由的名字,一定要填写不然使用时会出现各种问题 + * query: '{"id": 1, "name": "ry"}' // 访问路由的默认传递参数 + * roles: ['admin', 'common'] // 访问路由的角色权限 + * permissions: ['a:a:a', 'b:b:b'] // 访问路由的菜单权限 + * meta : { + noCache: true // 如果设置为true,则不会被 缓存(默认 false) + title: 'title' // 设置该路由在侧边栏和面包屑中展示的名字 + icon: 'svg-name' // 设置该路由的图标,对应路径src/assets/icons/svg + breadcrumb: false // 如果设置为false,则不会在breadcrumb面包屑中显示 + activeMenu: '/system/user' // 当路由设置了该属性,则会高亮相对应的侧边栏。 + } + */ + +// 公共路由 +export const constantRoutes = [ + { + path: "/redirect", + component: Layout, + hidden: true, + children: [ + { + path: "/redirect/:path(.*)", + component: () => import("@/views/redirect"), + }, + ], + }, + { + path: "/login", + component: () => import("@/views/login"), + hidden: true, + }, + { + path: "/register", + component: () => import("@/views/register"), + hidden: true, + }, + { + path: "/404", + component: () => import("@/views/error/404"), + hidden: true, + }, + { + path: "/401", + component: () => import("@/views/error/401"), + hidden: true, + }, + { + path: "", + component: Layout, + redirect: "index", + children: [ + { + path: "index", + component: () => import("@/views/index"), + name: "Index", + meta: { title: "首页", icon: "dashboard", affix: true }, + }, + ], + }, + { + path: "/patientFile", + component: Layout, + redirect: "index", + children: [ + { + path: "/patientIndex", + component: () => import("@/views/patientFile/index"), + name: "Index", + meta: { title: "患者档案", icon: "dashboard", }, + }, + ], + }, + { + path: "/medicalFile", + component: Layout, + redirect: "medicalFile", + children: [ + { + path: "/medicalIndex", + component: () => import("@/views/medicalFile/index"), + name: "medicalIndex", + meta: { title: "诊疗档案", icon: "dashboard", }, + }, + ], + }, + { + path: "/followFile", + meta: { title: "随访档案", icon: "dashboard", }, + component: Layout, + redirect: "followFile", + children: [ + { + path: "/followIndex", + component: () => import("@/views/followFile/index"), + name: "followIndex", + meta: { title: "随访队列", icon: "dashboard", }, + }, + { + path: "/followSubjects", + component: () => import("@/views/followFile/subjects"), + name: "followSubjects", + meta: { title: "随访对象", icon: "dashboard", }, + }, + { + path: "/followWork", + component: () => import("@/views/followFile/work"), + name: "followWork", + meta: { title: "随访工单", icon: "dashboard", }, + }, + ], + }, + { + path: "/user", + component: Layout, + hidden: true, + redirect: "noredirect", + children: [ + { + path: "profile", + component: () => import("@/views/system/user/profile/index"), + name: "Profile", + meta: { title: "个人中心", icon: "user" }, + }, + ], + }, +]; + +// 动态路由,基于用户权限动态去加载 +export const dynamicRoutes = [ + { + path: "/system/user-auth", + component: Layout, + hidden: true, + permissions: ["system:user:edit"], + children: [ + { + path: "role/:userId(\\d+)", + component: () => import("@/views/system/user/authRole"), + name: "AuthRole", + meta: { title: "分配角色", activeMenu: "/system/user" }, + }, + ], + }, + { + path: "/system/role-auth", + component: Layout, + hidden: true, + permissions: ["system:role:edit"], + children: [ + { + path: "user/:roleId(\\d+)", + component: () => import("@/views/system/role/authUser"), + name: "AuthUser", + meta: { title: "分配用户", activeMenu: "/system/role" }, + }, + ], + }, + { + path: "/system/dict-data", + component: Layout, + hidden: true, + permissions: ["system:dict:list"], + children: [ + { + path: "index/:dictId(\\d+)", + component: () => import("@/views/system/dict/data"), + name: "Data", + meta: { title: "字典数据", activeMenu: "/system/dict" }, + }, + ], + }, + { + path: "/monitor/job-log", + component: Layout, + hidden: true, + permissions: ["monitor:job:list"], + children: [ + { + path: "index/:jobId(\\d+)", + component: () => import("@/views/monitor/job/log"), + name: "JobLog", + meta: { title: "调度日志", activeMenu: "/monitor/job" }, + }, + ], + }, + { + path: "/tool/gen-edit", + component: Layout, + hidden: true, + permissions: ["tool:gen:edit"], + children: [ + { + path: "index/:tableId(\\d+)", + component: () => import("@/views/tool/gen/editTable"), + name: "GenEdit", + meta: { title: "修改生成配置", activeMenu: "/tool/gen" }, + }, + ], + }, +]; + +// 防止连续点击多次路由报错 +let routerPush = Router.prototype.push; +let routerReplace = Router.prototype.replace; +// push +Router.prototype.push = function push(location) { + return routerPush.call(this, location).catch((err) => err); +}; +// replace +Router.prototype.replace = function push(location) { + return routerReplace.call(this, location).catch((err) => err); +}; + +export default new Router({ + mode: "history", // 去掉url中的# + base: process.env.VUE_APP_PUBLIC_PATH, + scrollBehavior: () => ({ y: 0 }), + routes: constantRoutes, +}); diff --git a/acupuncture-ui/src/store/modules/permission.js b/acupuncture-ui/src/store/modules/permission.js new file mode 100644 index 00000000..fedee44a --- /dev/null +++ b/acupuncture-ui/src/store/modules/permission.js @@ -0,0 +1,137 @@ +import auth from "@/plugins/auth"; +import router, { constantRoutes, dynamicRoutes } from "@/router"; +import { getRouters } from "@/api/menu"; +import Layout from "@/layout/index"; +import ParentView from "@/components/ParentView"; +import InnerLink from "@/layout/components/InnerLink"; + +const permission = { + state: { + routes: [], + addRoutes: [], + defaultRoutes: [], + topbarRouters: [], + sidebarRouters: [], + }, + mutations: { + SET_ROUTES: (state, routes) => { + state.addRoutes = routes; + state.routes = constantRoutes.concat(routes); + }, + SET_DEFAULT_ROUTES: (state, routes) => { + state.defaultRoutes = constantRoutes.concat(routes); + }, + SET_TOPBAR_ROUTES: (state, routes) => { + state.topbarRouters = routes; + }, + SET_SIDEBAR_ROUTERS: (state, routes) => { + state.sidebarRouters = routes; + }, + }, + actions: { + // 生成路由 + GenerateRoutes({ commit }) { + return new Promise((resolve) => { + // 向后端请求路由数据 + getRouters().then((res) => { + const sdata = JSON.parse(JSON.stringify(res.data)); + const rdata = JSON.parse(JSON.stringify(res.data)); + const sidebarRoutes = filterAsyncRouter(sdata); + const rewriteRoutes = filterAsyncRouter(rdata, false, true); + const asyncRoutes = filterDynamicRoutes(dynamicRoutes); + rewriteRoutes.push({ path: "*", redirect: "/404", hidden: true }); + router.addRoutes(asyncRoutes); + commit("SET_ROUTES", rewriteRoutes); + commit("SET_SIDEBAR_ROUTERS", constantRoutes.concat(sidebarRoutes)); + commit("SET_DEFAULT_ROUTES", sidebarRoutes); + commit("SET_TOPBAR_ROUTES", sidebarRoutes); + resolve(rewriteRoutes); + }); + }); + }, + }, +}; + +// 遍历后台传来的路由字符串,转换为组件对象 +function filterAsyncRouter(asyncRouterMap, lastRouter = false, type = false) { + return asyncRouterMap.filter((route) => { + if (type && route.children) { + route.children = filterChildren(route.children); + } + if (route.component) { + // Layout ParentView 组件特殊处理 + if (route.component === "Layout") { + route.component = Layout; + } else if (route.component === "ParentView") { + route.component = ParentView; + } else if (route.component === "InnerLink") { + route.component = InnerLink; + } else { + route.component = loadView(route.component); + } + } + if (route.children != null && route.children && route.children.length) { + route.children = filterAsyncRouter(route.children, route, type); + } else { + delete route["children"]; + delete route["redirect"]; + } + return true; + }); +} + +function filterChildren(childrenMap, lastRouter = false) { + var children = []; + childrenMap.forEach((el, index) => { + if (el.children && el.children.length) { + if (el.component === "ParentView" && !lastRouter) { + el.children.forEach((c) => { + c.path = el.path + "/" + c.path; + if (c.children && c.children.length) { + children = children.concat(filterChildren(c.children, c)); + return; + } + children.push(c); + }); + return; + } + } + if (lastRouter) { + el.path = lastRouter.path + "/" + el.path; + if (el.children && el.children.length) { + children = children.concat(filterChildren(el.children, el)); + return; + } + } + children = children.concat(el); + }); + return children; +} + +// 动态路由遍历,验证是否具备权限 +export function filterDynamicRoutes(routes) { + const res = []; + routes.forEach((route) => { + if (route.permissions) { + if (auth.hasPermiOr(route.permissions)) { + res.push(route); + } + } else if (route.roles) { + if (auth.hasRoleOr(route.roles)) { + res.push(route); + } + } + }); + return res; +} + +export const loadView = (view) => { + if (process.env.NODE_ENV === "development") { + return (resolve) => require([`@/views/${view}`], resolve); + } else { + // 使用 import 实现生产环境的路由懒加载 + return () => import(`@/views/${view}`); + } +}; + +export default permission; diff --git a/acupuncture-ui/src/utils/dict/Dict.js b/acupuncture-ui/src/utils/dict/Dict.js new file mode 100644 index 00000000..104bd6e7 --- /dev/null +++ b/acupuncture-ui/src/utils/dict/Dict.js @@ -0,0 +1,82 @@ +import Vue from 'vue' +import { mergeRecursive } from "@/utils/ruoyi"; +import DictMeta from './DictMeta' +import DictData from './DictData' + +const DEFAULT_DICT_OPTIONS = { + types: [], +} + +/** + * @classdesc 字典 + * @property {Object} label 标签对象,内部属性名为字典类型名称 + * @property {Object} dict 字段数组,内部属性名为字典类型名称 + * @property {Array.} _dictMetas 字典元数据数组 + */ +export default class Dict { + constructor() { + this.owner = null + this.label = {} + this.type = {} + } + + init(options) { + if (options instanceof Array) { + options = { types: options } + } + const opts = mergeRecursive(DEFAULT_DICT_OPTIONS, options) + if (opts.types === undefined) { + throw new Error('need dict types') + } + const ps = [] + this._dictMetas = opts.types.map(t => DictMeta.parse(t)) + this._dictMetas.forEach(dictMeta => { + const type = dictMeta.type + Vue.set(this.label, type, {}) + Vue.set(this.type, type, []) + if (dictMeta.lazy) { + return + } + ps.push(loadDict(this, dictMeta)) + }) + return Promise.all(ps) + } + + /** + * 重新加载字典 + * @param {String} type 字典类型 + */ + reloadDict(type) { + const dictMeta = this._dictMetas.find(e => e.type === type) + if (dictMeta === undefined) { + return Promise.reject(`the dict meta of ${type} was not found`) + } + return loadDict(this, dictMeta) + } +} + +/** + * 加载字典 + * @param {Dict} dict 字典 + * @param {DictMeta} dictMeta 字典元数据 + * @returns {Promise} + */ +function loadDict(dict, dictMeta) { + return dictMeta.request(dictMeta) + .then(response => { + const type = dictMeta.type + let dicts = dictMeta.responseConverter(response, dictMeta) + if (!(dicts instanceof Array)) { + console.error('the return of responseConverter must be Array.') + dicts = [] + } else if (dicts.filter(d => d instanceof DictData).length !== dicts.length) { + console.error('the type of elements in dicts must be DictData') + dicts = [] + } + dict.type[type].splice(0, Number.MAX_SAFE_INTEGER, ...dicts) + dicts.forEach(d => { + Vue.set(dict.label[type], d.value, d.label) + }) + return dicts + }) +} diff --git a/acupuncture-ui/src/utils/dict/DictMeta.js b/acupuncture-ui/src/utils/dict/DictMeta.js new file mode 100644 index 00000000..9779daa4 --- /dev/null +++ b/acupuncture-ui/src/utils/dict/DictMeta.js @@ -0,0 +1,38 @@ +import { mergeRecursive } from "@/utils/ruoyi"; +import DictOptions from './DictOptions' + +/** + * @classdesc 字典元数据 + * @property {String} type 类型 + * @property {Function} request 请求 + * @property {String} label 标签字段 + * @property {String} value 值字段 + */ +export default class DictMeta { + constructor(options) { + this.type = options.type + this.request = options.request + this.responseConverter = options.responseConverter + this.labelField = options.labelField + this.valueField = options.valueField + this.lazy = options.lazy === true + } +} + + +/** + * 解析字典元数据 + * @param {Object} options + * @returns {DictMeta} + */ +DictMeta.parse= function(options) { + let opts = null + if (typeof options === 'string') { + opts = DictOptions.metas[options] || {} + opts.type = options + } else if (typeof options === 'object') { + opts = options + } + opts = mergeRecursive(DictOptions.metas['*'], opts) + return new DictMeta(opts) +} diff --git a/acupuncture-ui/src/utils/dict/DictOptions.js b/acupuncture-ui/src/utils/dict/DictOptions.js new file mode 100644 index 00000000..338a94e1 --- /dev/null +++ b/acupuncture-ui/src/utils/dict/DictOptions.js @@ -0,0 +1,51 @@ +import { mergeRecursive } from "@/utils/ruoyi"; +import dictConverter from './DictConverter' + +export const options = { + metas: { + '*': { + /** + * 字典请求,方法签名为function(dictMeta: DictMeta): Promise + */ + request: (dictMeta) => { + console.log(`load dict ${dictMeta.type}`) + return Promise.resolve([]) + }, + /** + * 字典响应数据转换器,方法签名为function(response: Object, dictMeta: DictMeta): DictData + */ + responseConverter, + labelField: 'label', + valueField: 'value', + }, + }, + /** + * 默认标签字段 + */ + DEFAULT_LABEL_FIELDS: ['label', 'name', 'title'], + /** + * 默认值字段 + */ + DEFAULT_VALUE_FIELDS: ['value', 'id', 'uid', 'key'], +} + +/** + * 映射字典 + * @param {Object} response 字典数据 + * @param {DictMeta} dictMeta 字典元数据 + * @returns {DictData} + */ +function responseConverter(response, dictMeta) { + const dicts = response.content instanceof Array ? response.content : response + if (dicts === undefined) { + console.warn(`no dict data of "${dictMeta.type}" found in the response`) + return [] + } + return dicts.map(d => dictConverter(d, dictMeta)) +} + +export function mergeOptions(src) { + mergeRecursive(options, src) +} + +export default options diff --git a/acupuncture-ui/src/utils/index.js b/acupuncture-ui/src/utils/index.js new file mode 100644 index 00000000..df5db12b --- /dev/null +++ b/acupuncture-ui/src/utils/index.js @@ -0,0 +1,390 @@ +import { parseTime } from './ruoyi' + +/** + * 表格时间格式化 + */ +export function formatDate(cellValue) { + if (cellValue == null || cellValue == "") return ""; + var date = new Date(cellValue) + var year = date.getFullYear() + var month = date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1 + var day = date.getDate() < 10 ? '0' + date.getDate() : date.getDate() + var hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours() + var minutes = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes() + var seconds = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds() + return year + '-' + month + '-' + day + ' ' + hours + ':' + minutes + ':' + seconds +} + +/** + * @param {number} time + * @param {string} option + * @returns {string} + */ +export function formatTime(time, option) { + if (('' + time).length === 10) { + time = parseInt(time) * 1000 + } else { + time = +time + } + const d = new Date(time) + const now = Date.now() + + const diff = (now - d) / 1000 + + if (diff < 30) { + return '刚刚' + } else if (diff < 3600) { + // less 1 hour + return Math.ceil(diff / 60) + '分钟前' + } else if (diff < 3600 * 24) { + return Math.ceil(diff / 3600) + '小时前' + } else if (diff < 3600 * 24 * 2) { + return '1天前' + } + if (option) { + return parseTime(time, option) + } else { + return ( + d.getMonth() + + 1 + + '月' + + d.getDate() + + '日' + + d.getHours() + + '时' + + d.getMinutes() + + '分' + ) + } +} + +/** + * @param {string} url + * @returns {Object} + */ +export function getQueryObject(url) { + url = url == null ? window.location.href : url + const search = url.substring(url.lastIndexOf('?') + 1) + const obj = {} + const reg = /([^?&=]+)=([^?&=]*)/g + search.replace(reg, (rs, $1, $2) => { + const name = decodeURIComponent($1) + let val = decodeURIComponent($2) + val = String(val) + obj[name] = val + return rs + }) + return obj +} + +/** + * @param {string} input value + * @returns {number} output value + */ +export function byteLength(str) { + // returns the byte length of an utf8 string + let s = str.length + for (var i = str.length - 1; i >= 0; i--) { + const code = str.charCodeAt(i) + if (code > 0x7f && code <= 0x7ff) s++ + else if (code > 0x7ff && code <= 0xffff) s += 2 + if (code >= 0xDC00 && code <= 0xDFFF) i-- + } + return s +} + +/** + * @param {Array} actual + * @returns {Array} + */ +export function cleanArray(actual) { + const newArray = [] + for (let i = 0; i < actual.length; i++) { + if (actual[i]) { + newArray.push(actual[i]) + } + } + return newArray +} + +/** + * @param {Object} json + * @returns {Array} + */ +export function param(json) { + if (!json) return '' + return cleanArray( + Object.keys(json).map(key => { + if (json[key] === undefined) return '' + return encodeURIComponent(key) + '=' + encodeURIComponent(json[key]) + }) + ).join('&') +} + +/** + * @param {string} url + * @returns {Object} + */ +export function param2Obj(url) { + const search = decodeURIComponent(url.split('?')[1]).replace(/\+/g, ' ') + if (!search) { + return {} + } + const obj = {} + const searchArr = search.split('&') + searchArr.forEach(v => { + const index = v.indexOf('=') + if (index !== -1) { + const name = v.substring(0, index) + const val = v.substring(index + 1, v.length) + obj[name] = val + } + }) + return obj +} + +/** + * @param {string} val + * @returns {string} + */ +export function html2Text(val) { + const div = document.createElement('div') + div.innerHTML = val + return div.textContent || div.innerText +} + +/** + * Merges two objects, giving the last one precedence + * @param {Object} target + * @param {(Object|Array)} source + * @returns {Object} + */ +export function objectMerge(target, source) { + if (typeof target !== 'object') { + target = {} + } + if (Array.isArray(source)) { + return source.slice() + } + Object.keys(source).forEach(property => { + const sourceProperty = source[property] + if (typeof sourceProperty === 'object') { + target[property] = objectMerge(target[property], sourceProperty) + } else { + target[property] = sourceProperty + } + }) + return target +} + +/** + * @param {HTMLElement} element + * @param {string} className + */ +export function toggleClass(element, className) { + if (!element || !className) { + return + } + let classString = element.className + const nameIndex = classString.indexOf(className) + if (nameIndex === -1) { + classString += '' + className + } else { + classString = + classString.substr(0, nameIndex) + + classString.substr(nameIndex + className.length) + } + element.className = classString +} + +/** + * @param {string} type + * @returns {Date} + */ +export function getTime(type) { + if (type === 'start') { + return new Date().getTime() - 3600 * 1000 * 24 * 90 + } else { + return new Date(new Date().toDateString()) + } +} + +/** + * @param {Function} func + * @param {number} wait + * @param {boolean} immediate + * @return {*} + */ +export function debounce(func, wait, immediate) { + let timeout, args, context, timestamp, result + + const later = function() { + // 据上一次触发时间间隔 + const last = +new Date() - timestamp + + // 上次被包装函数被调用时间间隔 last 小于设定时间间隔 wait + if (last < wait && last > 0) { + timeout = setTimeout(later, wait - last) + } else { + timeout = null + // 如果设定为immediate===true,因为开始边界已经调用过了此处无需调用 + if (!immediate) { + result = func.apply(context, args) + if (!timeout) context = args = null + } + } + } + + return function(...args) { + context = this + timestamp = +new Date() + const callNow = immediate && !timeout + // 如果延时不存在,重新设定延时 + if (!timeout) timeout = setTimeout(later, wait) + if (callNow) { + result = func.apply(context, args) + context = args = null + } + + return result + } +} + +/** + * This is just a simple version of deep copy + * Has a lot of edge cases bug + * If you want to use a perfect deep copy, use lodash's _.cloneDeep + * @param {Object} source + * @returns {Object} + */ +export function deepClone(source) { + if (!source && typeof source !== 'object') { + throw new Error('error arguments', 'deepClone') + } + const targetObj = source.constructor === Array ? [] : {} + Object.keys(source).forEach(keys => { + if (source[keys] && typeof source[keys] === 'object') { + targetObj[keys] = deepClone(source[keys]) + } else { + targetObj[keys] = source[keys] + } + }) + return targetObj +} + +/** + * @param {Array} arr + * @returns {Array} + */ +export function uniqueArr(arr) { + return Array.from(new Set(arr)) +} + +/** + * @returns {string} + */ +export function createUniqueString() { + const timestamp = +new Date() + '' + const randomNum = parseInt((1 + Math.random()) * 65536) + '' + return (+(randomNum + timestamp)).toString(32) +} + +/** + * Check if an element has a class + * @param {HTMLElement} elm + * @param {string} cls + * @returns {boolean} + */ +export function hasClass(ele, cls) { + return !!ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)')) +} + +/** + * Add class to element + * @param {HTMLElement} elm + * @param {string} cls + */ +export function addClass(ele, cls) { + if (!hasClass(ele, cls)) ele.className += ' ' + cls +} + +/** + * Remove class from element + * @param {HTMLElement} elm + * @param {string} cls + */ +export function removeClass(ele, cls) { + if (hasClass(ele, cls)) { + const reg = new RegExp('(\\s|^)' + cls + '(\\s|$)') + ele.className = ele.className.replace(reg, ' ') + } +} + +export function makeMap(str, expectsLowerCase) { + const map = Object.create(null) + const list = str.split(',') + for (let i = 0; i < list.length; i++) { + map[list[i]] = true + } + return expectsLowerCase + ? val => map[val.toLowerCase()] + : val => map[val] +} + +export const exportDefault = 'export default ' + +export const beautifierConf = { + html: { + indent_size: '2', + indent_char: ' ', + max_preserve_newlines: '-1', + preserve_newlines: false, + keep_array_indentation: false, + break_chained_methods: false, + indent_scripts: 'separate', + brace_style: 'end-expand', + space_before_conditional: true, + unescape_strings: false, + jslint_happy: false, + end_with_newline: true, + wrap_line_length: '110', + indent_inner_html: true, + comma_first: false, + e4x: true, + indent_empty_lines: true + }, + js: { + indent_size: '2', + indent_char: ' ', + max_preserve_newlines: '-1', + preserve_newlines: false, + keep_array_indentation: false, + break_chained_methods: false, + indent_scripts: 'normal', + brace_style: 'end-expand', + space_before_conditional: true, + unescape_strings: false, + jslint_happy: true, + end_with_newline: true, + wrap_line_length: '110', + indent_inner_html: true, + comma_first: false, + e4x: true, + indent_empty_lines: true + } +} + +// 首字母大小 +export function titleCase(str) { + return str.replace(/( |^)[a-z]/g, L => L.toUpperCase()) +} + +// 下划转驼峰 +export function camelCase(str) { + return str.replace(/_[a-z]/g, str1 => str1.substr(-1).toUpperCase()) +} + +export function isNumberStr(str) { + return /^[+-]?(0|([1-9]\d*))(\.\d+)?$/g.test(str) +} + diff --git a/acupuncture-ui/src/utils/request.js b/acupuncture-ui/src/utils/request.js new file mode 100644 index 00000000..faa87275 --- /dev/null +++ b/acupuncture-ui/src/utils/request.js @@ -0,0 +1,152 @@ +import axios from 'axios' +import { Notification, MessageBox, Message, Loading } from 'element-ui' +import store from '@/store' +import { getToken } from '@/utils/auth' +import errorCode from '@/utils/errorCode' +import { tansParams, blobValidate } from "@/utils/ruoyi"; +import cache from '@/plugins/cache' +import { saveAs } from 'file-saver' + +let downloadLoadingInstance; +// 是否显示重新登录 +export let isRelogin = { show: false }; + +axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8' +// 创建axios实例 +const service = axios.create({ + // axios中请求配置有baseURL选项,表示请求URL公共部分 + baseURL: process.env.VUE_APP_BASE_API, + // 超时 + timeout: 10000 +}) + +// request拦截器 +service.interceptors.request.use(config => { + // 是否需要设置 token + const isToken = (config.headers || {}).isToken === false + // 是否需要防止数据重复提交 + const isRepeatSubmit = (config.headers || {}).repeatSubmit === false + if (getToken() && !isToken) { + config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改 + } + // get请求映射params参数 + if (config.method === 'get' && config.params) { + let url = config.url + '?' + tansParams(config.params); + url = url.slice(0, -1); + config.params = {}; + config.url = url; + } + if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) { + const requestObj = { + url: config.url, + data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data, + time: new Date().getTime() + } + const requestSize = Object.keys(JSON.stringify(requestObj)).length; // 请求数据大小 + const limitSize = 5 * 1024 * 1024; // 限制存放数据5M + if (requestSize >= limitSize) { + console.warn(`[${config.url}]: ` + '请求数据大小超出允许的5M限制,无法进行防重复提交验证。') + return config; + } + const sessionObj = cache.session.getJSON('sessionObj') + if (sessionObj === undefined || sessionObj === null || sessionObj === '') { + cache.session.setJSON('sessionObj', requestObj) + } else { + const s_url = sessionObj.url; // 请求地址 + const s_data = sessionObj.data; // 请求数据 + const s_time = sessionObj.time; // 请求时间 + const interval = 1000; // 间隔时间(ms),小于此时间视为重复提交 + if (s_data === requestObj.data && requestObj.time - s_time < interval && s_url === requestObj.url) { + const message = '数据正在处理,请勿重复提交'; + console.warn(`[${s_url}]: ` + message) + return Promise.reject(new Error(message)) + } else { + cache.session.setJSON('sessionObj', requestObj) + } + } + } + return config +}, error => { + console.log(error) + Promise.reject(error) +}) + +// 响应拦截器 +service.interceptors.response.use(res => { + // 未设置状态码则默认成功状态 + const code = res.data.code || 200; + // 获取错误信息 + const msg = errorCode[code] || res.data.msg || errorCode['default'] + // 二进制数据则直接返回 + if (res.request.responseType === 'blob' || res.request.responseType === 'arraybuffer') { + return res.data + } + if (code === 401) { + if (!isRelogin.show) { + isRelogin.show = true; + MessageBox.confirm('登录状态已过期,您可以继续留在该页面,或者重新登录', '系统提示', { confirmButtonText: '重新登录', cancelButtonText: '取消', type: 'warning' }).then(() => { + isRelogin.show = false; + store.dispatch('LogOut').then(() => { + location.href = process.env.VUE_APP_PUBLIC_PATH; + }) + }).catch(() => { + isRelogin.show = false; + }); + } + return Promise.reject('无效的会话,或者会话已过期,请重新登录。') + } else if (code === 500) { + Message({ message: msg, type: 'error' }) + return Promise.reject(new Error(msg)) + } else if (code === 601) { + Message({ message: msg, type: 'warning' }) + return Promise.reject('error') + } else if (code !== 200) { + Notification.error({ title: msg }) + return Promise.reject('error') + } else { + return res.data + } + }, + error => { + console.log('err' + error) + let { message } = error; + if (message == "Network Error") { + message = "后端接口连接异常"; + } else if (message.includes("timeout")) { + message = "系统接口请求超时"; + } else if (message.includes("Request failed with status code")) { + message = "系统接口" + message.substr(message.length - 3) + "异常"; + } + Message({ message: message, type: 'error', duration: 5 * 1000 }) + return Promise.reject(error) + } +) + +// 通用下载方法 +export function download(url, params, filename, config) { + downloadLoadingInstance = Loading.service({ text: "正在下载数据,请稍候", spinner: "el-icon-loading", background: "rgba(0, 0, 0, 0.7)", }) + return service.post(url, params, { + transformRequest: [(params) => { return tansParams(params) }], + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + responseType: 'blob', + ...config + }).then(async (data) => { + const isBlob = blobValidate(data); + if (isBlob) { + const blob = new Blob([data]) + saveAs(blob, filename) + } else { + const resText = await data.text(); + const rspObj = JSON.parse(resText); + const errMsg = errorCode[rspObj.code] || rspObj.msg || errorCode['default'] + Message.error(errMsg); + } + downloadLoadingInstance.close(); + }).catch((r) => { + console.error(r) + Message.error('下载文件出现错误,请联系管理员!') + downloadLoadingInstance.close(); + }) +} + +export default service diff --git a/acupuncture-ui/src/utils/ruoyi.js b/acupuncture-ui/src/utils/ruoyi.js new file mode 100644 index 00000000..44bf9c40 --- /dev/null +++ b/acupuncture-ui/src/utils/ruoyi.js @@ -0,0 +1,233 @@ + + +/** + * 通用js方法封装处理 + * Copyright (c) 2019 ruoyi + */ + +// 日期格式化 +export function parseTime(time, pattern) { + if (arguments.length === 0 || !time) { + return null + } + const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}' + let date + if (typeof time === 'object') { + date = time + } else { + if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) { + time = parseInt(time) + } else if (typeof time === 'string') { + time = time.replace(new RegExp(/-/gm), '/').replace('T', ' ').replace(new RegExp(/\.[\d]{3}/gm), ''); + } + if ((typeof time === 'number') && (time.toString().length === 10)) { + time = time * 1000 + } + date = new Date(time) + } + const formatObj = { + y: date.getFullYear(), + m: date.getMonth() + 1, + d: date.getDate(), + h: date.getHours(), + i: date.getMinutes(), + s: date.getSeconds(), + a: date.getDay() + } + const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => { + let value = formatObj[key] + // Note: getDay() returns 0 on Sunday + if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] } + if (result.length > 0 && value < 10) { + value = '0' + value + } + return value || 0 + }) + return time_str +} + +// 表单重置 +export function resetForm(refName) { + if (this.$refs[refName]) { + this.$refs[refName].resetFields(); + } +} + +// 添加日期范围 +export function addDateRange(params, dateRange, propName) { + let search = params; + search.params = typeof (search.params) === 'object' && search.params !== null && !Array.isArray(search.params) ? search.params : {}; + dateRange = Array.isArray(dateRange) ? dateRange : []; + if (typeof (propName) === 'undefined') { + search.params['beginTime'] = dateRange[0]; + search.params['endTime'] = dateRange[1]; + } else { + search.params['begin' + propName] = dateRange[0]; + search.params['end' + propName] = dateRange[1]; + } + return search; +} + +// 回显数据字典 +export function selectDictLabel(datas, value) { + if (value === undefined) { + return ""; + } + var actions = []; + Object.keys(datas).some((key) => { + if (datas[key].value == ('' + value)) { + actions.push(datas[key].label); + return true; + } + }) + if (actions.length === 0) { + actions.push(value); + } + return actions.join(''); +} + +// 回显数据字典(字符串、数组) +export function selectDictLabels(datas, value, separator) { + if (value === undefined || value.length ===0) { + return ""; + } + if (Array.isArray(value)) { + value = value.join(","); + } + var actions = []; + var currentSeparator = undefined === separator ? "," : separator; + var temp = value.split(currentSeparator); + Object.keys(value.split(currentSeparator)).some((val) => { + var match = false; + Object.keys(datas).some((key) => { + if (datas[key].value == ('' + temp[val])) { + actions.push(datas[key].label + currentSeparator); + match = true; + } + }) + if (!match) { + actions.push(temp[val] + currentSeparator); + } + }) + return actions.join('').substring(0, actions.join('').length - 1); +} + +// 字符串格式化(%s ) +export function sprintf(str) { + var args = arguments, flag = true, i = 1; + str = str.replace(/%s/g, function () { + var arg = args[i++]; + if (typeof arg === 'undefined') { + flag = false; + return ''; + } + return arg; + }); + return flag ? str : ''; +} + +// 转换字符串,undefined,null等转化为"" +export function parseStrEmpty(str) { + if (!str || str == "undefined" || str == "null") { + return ""; + } + return str; +} + +// 数据合并 +export function mergeRecursive(source, target) { + for (var p in target) { + try { + if (target[p].constructor == Object) { + source[p] = mergeRecursive(source[p], target[p]); + } else { + source[p] = target[p]; + } + } catch (e) { + source[p] = target[p]; + } + } + return source; +}; + +/** + * 构造树型结构数据 + * @param {*} data 数据源 + * @param {*} id id字段 默认 'id' + * @param {*} parentId 父节点字段 默认 'parentId' + * @param {*} children 孩子节点字段 默认 'children' + */ +export function handleTree(data, id, parentId, children) { + let config = { + id: id || 'id', + parentId: parentId || 'parentId', + childrenList: children || 'children' + }; + + var childrenListMap = {}; + var nodeIds = {}; + var tree = []; + + for (let d of data) { + let parentId = d[config.parentId]; + if (childrenListMap[parentId] == null) { + childrenListMap[parentId] = []; + } + nodeIds[d[config.id]] = d; + childrenListMap[parentId].push(d); + } + + for (let d of data) { + let parentId = d[config.parentId]; + if (nodeIds[parentId] == null) { + tree.push(d); + } + } + + for (let t of tree) { + adaptToChildrenList(t); + } + + function adaptToChildrenList(o) { + if (childrenListMap[o[config.id]] !== null) { + o[config.childrenList] = childrenListMap[o[config.id]]; + } + if (o[config.childrenList]) { + for (let c of o[config.childrenList]) { + adaptToChildrenList(c); + } + } + } + return tree; +} + +/** +* 参数处理 +* @param {*} params 参数 +*/ +export function tansParams(params) { + let result = '' + for (const propName of Object.keys(params)) { + const value = params[propName]; + var part = encodeURIComponent(propName) + "="; + if (value !== null && value !== "" && typeof (value) !== "undefined") { + if (typeof value === 'object') { + for (const key of Object.keys(value)) { + if (value[key] !== null && value[key] !== "" && typeof (value[key]) !== 'undefined') { + let params = propName + '[' + key + ']'; + var subPart = encodeURIComponent(params) + "="; + result += subPart + encodeURIComponent(value[key]) + "&"; + } + } + } else { + result += part + encodeURIComponent(value) + "&"; + } + } + } + return result +} + +// 验证是否为blob格式 +export function blobValidate(data) { + return data.type !== 'application/json' +} diff --git a/acupuncture-ui/src/views/followFile/index.vue b/acupuncture-ui/src/views/followFile/index.vue new file mode 100644 index 00000000..7a6283be --- /dev/null +++ b/acupuncture-ui/src/views/followFile/index.vue @@ -0,0 +1,465 @@ + + + + + + diff --git a/acupuncture-ui/src/views/followFile/subjects.vue b/acupuncture-ui/src/views/followFile/subjects.vue new file mode 100644 index 00000000..c8d83aa5 --- /dev/null +++ b/acupuncture-ui/src/views/followFile/subjects.vue @@ -0,0 +1,398 @@ + + + + + + diff --git a/acupuncture-ui/src/views/followFile/work.vue b/acupuncture-ui/src/views/followFile/work.vue new file mode 100644 index 00000000..def2e10f --- /dev/null +++ b/acupuncture-ui/src/views/followFile/work.vue @@ -0,0 +1,529 @@ + + + + + + diff --git a/acupuncture-ui/src/views/index.vue b/acupuncture-ui/src/views/index.vue new file mode 100644 index 00000000..f5750d24 --- /dev/null +++ b/acupuncture-ui/src/views/index.vue @@ -0,0 +1,1133 @@ + + + + + diff --git a/acupuncture-ui/src/views/login.vue b/acupuncture-ui/src/views/login.vue new file mode 100644 index 00000000..5f898936 --- /dev/null +++ b/acupuncture-ui/src/views/login.vue @@ -0,0 +1,251 @@ + + + + + diff --git a/acupuncture-ui/src/views/medicalFile/index.vue b/acupuncture-ui/src/views/medicalFile/index.vue new file mode 100644 index 00000000..604127b6 --- /dev/null +++ b/acupuncture-ui/src/views/medicalFile/index.vue @@ -0,0 +1,1763 @@ + + + + + + diff --git a/acupuncture-ui/src/views/monitor/job/index.vue b/acupuncture-ui/src/views/monitor/job/index.vue new file mode 100644 index 00000000..892c7275 --- /dev/null +++ b/acupuncture-ui/src/views/monitor/job/index.vue @@ -0,0 +1,513 @@ + + + diff --git a/acupuncture-ui/src/views/patientFile/index.vue b/acupuncture-ui/src/views/patientFile/index.vue new file mode 100644 index 00000000..171e59ef --- /dev/null +++ b/acupuncture-ui/src/views/patientFile/index.vue @@ -0,0 +1,699 @@ + + + + + + diff --git a/acupuncture-ui/src/views/register.vue b/acupuncture-ui/src/views/register.vue new file mode 100644 index 00000000..62b634c1 --- /dev/null +++ b/acupuncture-ui/src/views/register.vue @@ -0,0 +1,263 @@ + + + + + diff --git a/acupuncture-ui/src/views/system/user/index.vue b/acupuncture-ui/src/views/system/user/index.vue new file mode 100644 index 00000000..dbaede04 --- /dev/null +++ b/acupuncture-ui/src/views/system/user/index.vue @@ -0,0 +1,553 @@ + + + diff --git a/acupuncture-ui/src/views/tool/gen/genInfoForm.vue b/acupuncture-ui/src/views/tool/gen/genInfoForm.vue new file mode 100644 index 00000000..98daf6d5 --- /dev/null +++ b/acupuncture-ui/src/views/tool/gen/genInfoForm.vue @@ -0,0 +1,312 @@ + + + diff --git a/acupuncture-ui/src/views/tool/gen/index.vue b/acupuncture-ui/src/views/tool/gen/index.vue new file mode 100644 index 00000000..9237c302 --- /dev/null +++ b/acupuncture-ui/src/views/tool/gen/index.vue @@ -0,0 +1,354 @@ + + + diff --git a/acupuncture-ui/vue.config.js b/acupuncture-ui/vue.config.js new file mode 100644 index 00000000..60af7ce7 --- /dev/null +++ b/acupuncture-ui/vue.config.js @@ -0,0 +1,130 @@ +"use strict"; +const path = require("path"); + +function resolve(dir) { + return path.join(__dirname, dir); +} + +const CompressionPlugin = require("compression-webpack-plugin"); + +const name = process.env.VUE_APP_TITLE || "针灸管理系统"; // 网页标题 + +const port = process.env.port || process.env.npm_config_port || 80; // 端口 + +// vue.config.js 配置说明 +//官方vue.config.js 参考文档 https://cli.vuejs.org/zh/config/#css-loaderoptions +// 这里只列一部分,具体配置参考文档 +module.exports = { + // 部署生产环境和开发环境下的URL。 + // 默认情况下,Vue CLI 会假设你的应用是被部署在一个域名的根路径上 + // 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。 + publicPath: process.env.NODE_ENV === "production" ? "/acupunctureClient/" : "/", + // 在npm run build 或 yarn build 时 ,生成文件的目录名称(要和baseUrl的生产环境路径一致)(默认dist) + outputDir: "dist", + // 用于放置生成的静态资源 (js、css、img、fonts) 的;(项目打包之后,静态资源会放在这个文件夹下) + assetsDir: "static", + // 是否开启eslint保存检测,有效值:ture | false | 'error' + lintOnSave: process.env.NODE_ENV === "development", + // 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。 + productionSourceMap: false, + transpileDependencies: ["quill"], + // webpack-dev-server 相关配置 + devServer: { + host: "0.0.0.0", + port: port, + open: true, + proxy: { + // detail: https://cli.vuejs.org/config/#devserver-proxy + [process.env.VUE_APP_BASE_API]: { + target: `https://test.tall.wiki/acupuncture`, + changeOrigin: true, + pathRewrite: { + ["^" + process.env.VUE_APP_BASE_API]: "", + }, + }, + }, + disableHostCheck: true, + }, + css: { + loaderOptions: { + sass: { + sassOptions: { outputStyle: "expanded" }, + }, + }, + }, + configureWebpack: { + name: name, + resolve: { + alias: { + "@": resolve("src"), + }, + }, + plugins: [ + // http://doc.ruoyi.vip/ruoyi-vue/other/faq.html#使用gzip解压缩静态文件 + new CompressionPlugin({ + cache: false, // 不启用文件缓存 + test: /\.(js|css|html|jpe?g|png|gif|svg)?$/i, // 压缩文件格式 + filename: "[path][base].gz[query]", // 压缩后的文件名 + algorithm: "gzip", // 使用gzip压缩 + minRatio: 0.8, // 压缩比例,小于 80% 的文件不会被压缩 + deleteOriginalAssets: false, // 压缩后删除原文件 + }), + ], + }, + chainWebpack(config) { + config.plugins.delete("preload"); // TODO: need test + config.plugins.delete("prefetch"); // TODO: need test + + // set svg-sprite-loader + config.module.rule("svg").exclude.add(resolve("src/assets/icons")).end(); + config.module + .rule("icons") + .test(/\.svg$/) + .include.add(resolve("src/assets/icons")) + .end() + .use("svg-sprite-loader") + .loader("svg-sprite-loader") + .options({ + symbolId: "icon-[name]", + }) + .end(); + + config.when(process.env.NODE_ENV !== "development", (config) => { + config + .plugin("ScriptExtHtmlWebpackPlugin") + .after("html") + .use("script-ext-html-webpack-plugin", [ + { + // `runtime` must same as runtimeChunk name. default is `runtime` + inline: /runtime\..*\.js$/, + }, + ]) + .end(); + + config.optimization.splitChunks({ + chunks: "all", + cacheGroups: { + libs: { + name: "chunk-libs", + test: /[\\/]node_modules[\\/]/, + priority: 10, + chunks: "initial", // only package third parties that are initially dependent + }, + elementUI: { + name: "chunk-elementUI", // split elementUI into a single package + test: /[\\/]node_modules[\\/]_?element-ui(.*)/, // in order to adapt to cnpm + priority: 20, // the weight needs to be larger than libs and app or it will be packaged into libs or app + }, + commons: { + name: "chunk-commons", + test: resolve("src/components"), // can customize your rules + minChunks: 3, // minimum common number + priority: 5, + reuseExistingChunk: true, + }, + }, + }); + config.optimization.runtimeChunk("single"); + }); + }, +};