Initial commit

This commit is contained in:
danshihao
2023-11-14 15:33:22 +08:00
commit 5ef2186398
48 changed files with 22347 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
> 1%
last 2 versions
not dead
+45
View File
@@ -0,0 +1,45 @@
[*]
charset=utf-8
end_of_line=crlf
insert_final_newline=false
indent_style=space
indent_size=2
[{*.ng,*.sht,*.html,*.shtm,*.shtml,*.htm}]
indent_style=space
indent_size=2
[{*.jhm,*.xslt,*.xul,*.rng,*.xsl,*.xsd,*.ant,*.tld,*.fxml,*.jrxml,*.xml,*.jnlp,*.wsdl}]
indent_style=space
indent_size=2
[{.babelrc,.stylelintrc,jest.config,.eslintrc,.prettierrc,*.json,*.jsb3,*.jsb2,*.bowerrc}]
indent_style=space
indent_size=2
[*.svg]
indent_style=space
indent_size=2
[*.js.map]
indent_style=space
indent_size=2
[*.less]
indent_style=space
indent_size=2
[*.vue]
indent_style=space
indent_size=2
[{.analysis_options,*.yml,*.yaml}]
indent_style=space
indent_size=2
[*.{js,jsx,ts,tsx,vue}]
indent_style = space
indent_size = 2
trim_trailing_whitespace = true
insert_final_newline = true
+6
View File
@@ -0,0 +1,6 @@
NODE_ENV=production
VUE_APP_PLATFORM_NAME=JeroBoot 企业级快速开发平台
VUE_APP_SSO=false
VUE_TAG_FLAG=0
VUE_APP_LOGIN_PAGE='/user/login'
VUE_APP_CURRENT_SYSTEM='inner'
+4
View File
@@ -0,0 +1,4 @@
NODE_ENV=development
VUE_APP_TAG_FLAG=0
VUE_APP_LOGIN_PAGE='/user/login'
VUE_APP_CURRENT_SYSTEM='inner'
+4
View File
@@ -0,0 +1,4 @@
NODE_ENV=production
VUE_APP_TAG_FLAG=1
VUE_APP_LOGIN_PAGE='https://iam-uat.sinotruk.com:7011/idp/oauth2/authorize?redirect_uri=http://laws-test.sinotruk.com/dashboard/analysis&state=123&client_id=SRMS&response_type=code'
VUE_APP_CURRENT_SYSTEM='IAM'
+7
View File
@@ -0,0 +1,7 @@
NODE_ENV=production
VUE_APP_API_BASE_URL=http://boot.jero.com:8080/laws-sinotruk
VUE_APP_CAS_BASE_URL=http://cas.example.org:8443/cas
VUE_APP_ONLINE_BASE_URL=http://fileview.jero.com/onlinePreview
VUE_APP_TAG_FLAG=1
VUE_APP_LOGIN_PAGE='/user/login'
VUE_APP_CURRENT_SYSTEM='inner'
+3
View File
@@ -0,0 +1,3 @@
/dist
/public
/node_modules
+21
View File
@@ -0,0 +1,21 @@
module.exports = {
root: true,
env: {
node: true
},
'extends': [
'plugin:vue/essential',
'@vue/standard'
],
parserOptions: {
parser: 'babel-eslint'
},
rules: {
'quotes': [2, 'single', {
'avoidEscape': true,
'allowTemplateLiterals': true
}],
'semi': [2, 'never'],
'prefer-promise-reject-errors': 'warn'
}
}
+23
View File
@@ -0,0 +1,23 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
+24
View File
@@ -0,0 +1,24 @@
# laws_sinotruk_mobile
## Project setup
```
npm install
```
### Compiles and hot-reloads for development
```
npm run serve
```
### Compiles and minifies for production
```
npm run build
```
### Lints and fixes files
```
npm run lint
```
### Customize configuration
See [Configuration Reference](https://cli.vuejs.org/config/).
+5
View File
@@ -0,0 +1,5 @@
module.exports = {
presets: [
'@vue/cli-plugin-babel/preset'
]
}
+19
View File
@@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"module": "esnext",
"baseUrl": "./",
"moduleResolution": "node",
"paths": {
"@/*": [
"src/*"
]
},
"lib": [
"esnext",
"dom",
"dom.iterable",
"scripthost"
]
}
}
+13598
View File
File diff suppressed because it is too large Load Diff
+43
View File
@@ -0,0 +1,43 @@
{
"name": "laws_sinotruk_mobile",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"axios": "^0.21.1",
"core-js": "^3.8.3",
"jsencrypt": "^3.0.0-rc.1",
"md5": "^2.2.1",
"vant": "^2.13.2",
"vue": "^2.6.14",
"vue-i18n": "^8.7.0",
"vue-ls": "^3.2.0",
"vue-router": "^3.5.1",
"vuex": "^3.6.2"
},
"devDependencies": {
"@babel/core": "^7.12.16",
"@babel/eslint-parser": "^7.12.16",
"@vue/cli-plugin-babel": "~5.0.0",
"@vue/cli-plugin-eslint": "~4.2.3",
"@vue/cli-plugin-router": "~5.0.0",
"@vue/cli-plugin-vuex": "~5.0.0",
"@vue/cli-service": "~5.0.0",
"@vue/eslint-config-standard": "^5.1.0",
"babel-eslint": "^7.2.3",
"compression-webpack-plugin": "^10.0.0",
"eslint": "^6.7.2",
"eslint-plugin-import": "^2.20.1",
"eslint-plugin-node": "^11.0.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^4.0.0",
"eslint-plugin-vue": "^6.1.2",
"less": "^3.9.0",
"less-loader": "^4.1.0",
"vue-template-compiler": "^2.6.14"
}
}
+16
View File
@@ -0,0 +1,16 @@
/**
* 防止打包部署时路径被写死
*/
window._CONFIG = {}
window._CONFIG.domianURL = '/laws-sinotruk' // 路由转发 devServer
window._CONFIG.domianWebSocketURL = 'http://localhost:8184/laws-sinotruk'
// 单点登录地址
window._CONFIG.staticDomainURL = window._CONFIG.domianURL + '/sys/common/download'
window._CONFIG.pdfDomainURL = window._CONFIG.domianURL + '/sys/common/pdf/pdfPreviewIframe'
window._CONFIG.casPrefixUrl = 'http://cas.example.org:8443/cas'
// 预览文件地址
window._CONFIG.onlinePreviewDomainURL = 'http://127.0.0.1:8012/onlinePreview'
// onlyOffice前端服务地址
window._CONFIG.onlyOfficeUrl = 'http://127.0.0.1:8080'
// onlyOffice下载文件地址(暂时没用)
window._CONFIG.onlyOfficeDownloadFileUrl = 'http://localhost:8184/sys/common/download'
Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

+23
View File
@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0, maximum-scale=1, minimum-scale=1, user-scalable=no">
<!-- <link rel="icon" href="<%= BASE_URL %>favicon.ico">-->
<title><%= htmlWebpackPlugin.options.title %></title>
<style>
:root {
font-size: calc(100vw / 10);
}
</style>
<script src="<%= BASE_URL %>api_address_config.js"></script>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
+82
View File
@@ -0,0 +1,82 @@
<template>
<div id="app">
<van-nav-bar
v-if="hasNavBar"
:title="pageTitle"
:left-text="hasBack ? $t('back') : ''"
:left-arrow="hasBack"
:right-text="isChinese ? $t('chinese') : $t('english')"
@click-left="handleBack"
@click-right="handleChangeLang"
/>
<router-view />
</div>
</template>
<script>
import Vue from 'vue'
import { Locale } from 'vant'
import zhCN from 'vant/es/locale/lang/zh-CN'
import enUS from 'vant/es/locale/lang/en-US'
const ZH = 'zh-CN'
const EN = 'en-US'
export default {
name: 'App',
data () {
return {
currentLang: ZH
}
},
created () {
// 默认中文
Vue.ls.set('language', ZH, 7 * 24 * 60 * 60 * 1000)
Locale.use('zh-CN', zhCN)
this.$i18n.locale = ZH
},
computed: {
// 是否中文
isChinese () {
return this.currentLang === ZH
},
// 是否显示导航
hasNavBar () {
return this.$route.meta.hasNavBar
},
// 是否显示返回
hasBack () {
return this.$route.meta.hasBack
},
// 导航标题(国际化)
pageTitle () {
if (!this.$route.meta.pageTitle) {
return ''
}
return this.$t('pageTitle.' + this.$route.meta.pageTitle)
}
},
methods: {
// 返回上一层
handleBack () {
this.$router.go(-1)
},
// 切换语言
handleChangeLang () {
// 如果是中文就切换英文,否则切换中文
const targetLang = this.isChinese ? EN : ZH
const vantLang = this.isChinese ? enUS : zhCN
// 更新响应式
this.currentLang = targetLang
// 更新本地存储
Vue.ls.set('language', targetLang, 7 * 24 * 60 * 60 * 1000)
// 更新vant语言
Locale.use(targetLang, vantLang)
// 更新i18n语言
this.$i18n.locale = targetLang
}
}
}
</script>
<style lang="less">
</style>
+230
View File
@@ -0,0 +1,230 @@
import { getAction, httpAction, postAction, putAction } from '@/api/manage'
import Vue from 'vue'
import { UI_CACHE_DB_DICT_DATA } from '@/store/mutation-types'
// 角色管理
const addRole = (params) => postAction('/sys/role/add', params)
const editRole = (params) => postAction('/sys/role/edit', params)
const checkRoleCode = (params) => getAction('/sys/role/checkRoleCode', params)
const queryall = (params) => getAction('/sys/role/queryall', params)
// 用户管理
const addUser = (params) => postAction('/sys/user/add', params)
const editUser = (params) => postAction('/sys/user/edit', params)
const queryUserRole = (params) => getAction('/sys/user/queryUserRole', params)
const getUserList = (params) => getAction('/sys/user/page', params)
const frozenBatch = (params) => postAction('/sys/user/frozenBatch', params)
// 验证用户是否存在
const checkOnlyUser = (params) => getAction('/sys/user/checkOnlyUser', params)
// 改变密码
const changePassword = (params) => postAction('/sys/user/changePassword', params)
// 单点登录获取用户信息
const getSSOUserInfo = (params) => postAction('/iam/loginByOauth2', params)
// 权限管理
const addPermission = (params) => postAction('/sys/permission/add', params)
const editPermission = (params) => postAction('/sys/permission/edit', params)
const getPermissionList = (params) => getAction('/sys/permission/page', params)
const getSystemMenuList = (params) => getAction('/sys/permission/getSystemMenuList', params)
const getSystemSubmenu = (params) => getAction('/sys/permission/getSystemSubmenu', params)
const getSystemSubmenuBatch = (params) => getAction('/sys/permission/getSystemSubmenuBatch', params)
const queryTreeList = (params) => getAction('/sys/permission/queryTreeList', params)
const queryTreeListForRole = (params) => getAction('/sys/role/queryTreeList', params)
const queryListAsync = (params) => getAction('/sys/permission/queryListAsync', params)
const queryRolePermission = (params) => getAction('/sys/permission/queryRolePermission', params)
const saveRolePermission = (params) => postAction('/sys/permission/saveRolePermission', params)
const queryPermissionsByUser = () => getAction('/sys/permission/getUserPermissionByToken')
const loadAllRoleIds = (params) => getAction('/sys/permission/loadAllRoleIds', params)
const getPermissionRuleList = (params) => getAction('/sys/permission/getPermRuleListByPermId', params)
const queryPermissionRule = (params) => getAction('/sys/permission/queryPermissionRule', params)
// 部门管理
const queryDepartTreeList = (params) => getAction('/sys/sysDepart/queryTreeList', params)
const queryDepartTreeSync = (params) => getAction('/sys/sysDepart/queryDepartTreeSync', params)
const queryIdTree = (params) => getAction('/sys/sysDepart/queryIdTree', params)
const queryParentName = (params) => getAction('/sys/sysDepart/queryParentName', params)
const searchByKeywords = (params) => getAction('/sys/sysDepart/searchBy', params)
const deleteByDepartId = (params) => postAction('/sys/sysDepart/delete', params)
// 二级部门管理
const queryDepartPermission = (params) => getAction('/sys/permission/queryDepartPermission', params)
const saveDepartPermission = (params) => postAction('/sys/permission/saveDepartPermission', params)
const queryTreeListForDeptRole = (params) => getAction('/sys/sysDepartPermission/queryTreeListForDeptRole', params)
const queryDeptRolePermission = (params) => getAction('/sys/sysDepartPermission/queryDeptRolePermission', params)
const queryMyDepartTreeList = (params) => getAction('/sys/sysDepart/queryMyDeptTreeList', params)
// 日志管理
const deleteLog = (params) => postAction('/sys/log/delete', params)
const deleteLogList = (params) => postAction('/sys/log/deleteBatch', params)
// 数据字典
const addDict = (params) => postAction('/sys/dict/add', params)
const editDict = (params) => postAction('/sys/dict/edit', params)
const treeList = (params) => getAction('/sys/dict/treeList', params)
const queryDictName = (params) => getAction('/sys/dict/queryDictName', params)
const addDictItem = (params) => postAction('/sys/dictItem/add', params)
const editDictItem = (params) => postAction('/sys/dictItem/edit', params)
// 获得数据字典的树形格式数据
const getDictTreeList = (params) => getAction('/sys/dictItem/treeList', params)
// 获取能在form中使用的数据字典树形格式数据
const getFormDictTreeList = (params) => getAction('/sys/dictItem/treeListOption', params)
// 通过code值获取字典数据
const getDictByCode = (params) => getAction('/sys/dictItem/listByCodes', params)
// 字典标签专用(通过code获取字典数组)
export const ajaxGetDictItems = (code, params) => getAction(`/sys/dict/getDictItems/${code}`, params)
// 从缓存中获取字典配置
function getDictItemsFromCache (dictCode) {
if (Vue.ls.get(UI_CACHE_DB_DICT_DATA) && Vue.ls.get(UI_CACHE_DB_DICT_DATA)[dictCode] && Vue.ls.get(UI_CACHE_DB_DICT_DATA)[dictCode].length > 0) {
return Vue.ls.get(UI_CACHE_DB_DICT_DATA)[dictCode]
}
}
// 系统通告
const doReleaseData = (params) => getAction('/sys/annountCement/doReleaseData', params)
const doReovkeData = (params) => getAction('/sys/annountCement/doReovkeData', params)
// 获取系统访问量
const getLoginfo = (params) => getAction('/sys/loginfo', params)
const getVisitInfo = (params) => getAction('/sys/visitInfo', params)
// 根据部门主键查询用户信息
const queryUserByDepId = (params) => getAction('/sys/user/queryUserByDepId', params)
// 重复校验
const duplicateCheck = (params) => getAction('/sys/duplicate/check', params)
// 加载分类字典
const loadCategoryData = (params) => getAction('/sys/category/loadAllData', params)
const checkRuleByCode = (params) => getAction('/sys/checkRule/checkByCode', params)
// 加载我的通告信息
const getUserNoticeInfo = (params) => getAction('/sys/sysAnnouncementSend/getMyAnnouncementSend', params)
const getTransitURL = url => `/sys/common/transitRESTful?url=${encodeURIComponent(url)}`
// 中转HTTP请求
export const transitRESTful = {
get: (url, parameter) => getAction(getTransitURL(url), parameter),
post: (url, parameter) => postAction(getTransitURL(url), parameter),
put: (url, parameter) => putAction(getTransitURL(url), parameter),
http: (url, parameter) => httpAction(getTransitURL(url), parameter)
}
// 标签管理- 展示区域查询接口
const getShowAreaData = (params) => getAction('/tag/LawsArea/queryShowArea', params)
// 标签管理-展示区域管理-新增
const addAreaData = (params) => postAction('/tag/LawsArea/add', params)
// 标签管理-展示区域管理-编辑
const editAreaData = (params) => postAction('/tag/LawsArea/edit', params)
// 标签管理- 新增
const addTag = (params) => postAction('/tag/lawsTag/add', params)
// 标签管理- 编辑
const editTag = (params) => postAction('/tag/lawsTag/edit', params)
// 标签内容管理- 新增
const addCategoryItem = (params) => postAction('/sys/category/add', params)
const editCategoryItem = (params) => postAction('/sys/category/edit', params)
// 通过文件的id获取文件的相应信息
const getFileInfo = (params) => getAction('/sys/oss/file/queryById', params)
// 标准选择框,分页查询标准数据
const getStandardList = (params) => getAction('/laws/standard/common/getManyStandardSelectionBox', params)
// 内部工作组,左侧树查询
const getWorkGroupTreeList = (params) => getAction('/sys/lawsWorkingGroup/list', params)
// 企业级别映射管理,获取列表
const getEnterpriseLevelMapList = (params) => getAction('/sys/lawsGrade/list', params)
// ASMS接口管理-新增
const addAsmsInterface = (params) => postAction('/docking/asms/api/lawsAsmsOpenApi/add', params)
// ASMS接口管理-编辑
const editAsmsInterface = (params) => postAction('/docking/asms/api/lawsAsmsOpenApi/edit', params)
// ASMS接口管理-查看
const getAsmsInterfaceById = (params) => getAction('/docking/asms/api/lawsAsmsOpenApi/getInfoById', params)
// ASMS接口管理-同步
const syncAsmsInterface = (params) => getAction('/docking/asms/api/lawsAsmsOpenApi/syncBatch', params)
// ASMS接口管理-删除字典配置项
const batchDelDictItemAsmsInterface = (params) => getAction('/docking/asms/api/lawsAsmsOpenApi/removeDictItemBatch', params)
export {
addRole,
editRole,
checkRoleCode,
addUser,
editUser,
queryUserRole,
getUserList,
queryall,
frozenBatch,
checkOnlyUser,
changePassword,
getPermissionList,
addPermission,
editPermission,
queryTreeList,
queryListAsync,
queryRolePermission,
saveRolePermission,
queryPermissionsByUser,
loadAllRoleIds,
getPermissionRuleList,
queryPermissionRule,
queryDepartTreeList,
queryDepartTreeSync,
queryIdTree,
queryParentName,
searchByKeywords,
deleteByDepartId,
deleteLog,
deleteLogList,
addDict,
editDict,
treeList,
queryDictName,
addDictItem,
editDictItem,
getDictTreeList,
getFormDictTreeList,
getDictByCode,
doReleaseData,
doReovkeData,
getLoginfo,
getVisitInfo,
queryUserByDepId,
duplicateCheck,
queryTreeListForRole,
getSystemMenuList,
getSystemSubmenu,
getSystemSubmenuBatch,
loadCategoryData,
checkRuleByCode,
queryDepartPermission,
saveDepartPermission,
queryTreeListForDeptRole,
queryDeptRolePermission,
queryMyDepartTreeList,
getUserNoticeInfo,
getDictItemsFromCache,
getShowAreaData,
addAreaData,
editAreaData,
getFileInfo,
addTag,
editTag,
addCategoryItem,
editCategoryItem,
getStandardList,
getWorkGroupTreeList,
getEnterpriseLevelMapList,
getSSOUserInfo,
addAsmsInterface,
editAsmsInterface,
getAsmsInterfaceById,
syncAsmsInterface,
batchDelDictItemAsmsInterface
}
+10
View File
@@ -0,0 +1,10 @@
const api = {
Login: '/sys/login',
Logout: '/sys/logout',
ForgePassword: '/auth/forge-password',
Register: '/auth/register',
SendSms: '/account/sms',
// get my info
UserInfo: '/user/info'
}
export default api
+87
View File
@@ -0,0 +1,87 @@
import api from './index'
import { axios } from '@/utils/request'
/**
* login func
* parameter: {
* username: '',
* password: '',
* remember_me: true,
* captcha: '12345'
* }
* @param parameter
* @returns {*}
*/
export function login (parameter) {
return axios({
url: '/sys/login',
method: 'post',
data: parameter
})
}
export function phoneLogin (parameter) {
return axios({
url: '/sys/phoneLogin',
method: 'post',
data: parameter
})
}
export function getSmsCaptcha (parameter) {
return axios({
url: api.SendSms,
method: 'post',
data: parameter
})
}
// export function getInfo() {
// return axios({
// url: '/api/user/info',
// method: 'get',
// headers: {
// 'Content-Type': 'application/json;charset=UTF-8'
// }
// })
// }
export function logout (logoutToken) {
return axios({
url: '/sys/logout',
method: 'post',
headers: {
'Content-Type': 'application/json;charset=UTF-8',
'X-Access-Token': logoutToken
}
})
}
/**
* 第三方登录
* @param token
* @param thirdType
* @returns {*}
*/
export function thirdLogin (token, thirdType) {
return axios({
url: `/sys/thirdLogin/getLoginUser/${token}/${thirdType}`,
method: 'get',
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
})
}
/**
* 强退其他账号
* @returns {*}
* @param parameter
*/
export function forceLogout (parameter) {
return axios({
url: '/sys/online/forceLogout',
method: 'post',
data: parameter
})
}
+228
View File
@@ -0,0 +1,228 @@
import Vue from 'vue'
import { axios } from '@/utils/request'
import { ACCESS_TOKEN } from '@/store/mutation-types'
import signMd5Utils from '@/utils/encryption/signMd5Utils'
const api = {
user: '/mock/api/user',
role: '/mock/api/role',
service: '/mock/api/service',
permission: '/mock/api/permission',
permissionNoPager: '/mock/api/permission/no-pager'
}
export default api
// post
export function postAction (url, parameter) {
const sign = signMd5Utils.getSign(url, parameter)
// 将签名和时间戳,添加在请求接口 Header
const signHeader = { 'X-Sign': sign, 'X-TIMESTAMP': signMd5Utils.getDateTimeToString() }
return axios({
url: url,
method: 'post',
data: parameter,
headers: signHeader
})
}
// post method= {post | put}
export function httpAction (url, parameter, method) {
const sign = signMd5Utils.getSign(url, parameter)
// 将签名和时间戳,添加在请求接口 Header
const signHeader = { 'X-Sign': sign, 'X-TIMESTAMP': signMd5Utils.getDateTimeToString() }
return axios({
url: url,
method: method,
data: parameter,
headers: signHeader
})
}
// put
export function putAction (url, parameter) {
return axios({
url: url,
method: 'put',
data: parameter
})
}
// get
export function getAction (url, parameter) {
const sign = signMd5Utils.getSign(url, parameter)
// 将签名和时间戳,添加在请求接口 Header
const signHeader = { 'X-Sign': sign, 'X-TIMESTAMP': signMd5Utils.getDateTimeToString() }
return axios({
url: url,
method: 'get',
params: parameter,
headers: signHeader
})
}
// deleteAction
export function deleteAction (url, parameter) {
return axios({
url: url,
method: 'delete',
params: parameter
})
}
export function getUserList (parameter) {
return axios({
url: api.user,
method: 'get',
params: parameter
})
}
export function getRoleList (parameter) {
return axios({
url: api.role,
method: 'get',
params: parameter
})
}
export function getServiceList (parameter) {
return axios({
url: api.service,
method: 'get',
params: parameter
})
}
export function getPermissions (parameter) {
return axios({
url: api.permissionNoPager,
method: 'get',
params: parameter
})
}
export function saveService (parameter) {
return axios({
url: api.service,
method: 'post',
data: parameter
})
}
/**
* 下载文件 用于excel导出
* @param url
* @param parameter
* @returns {*}
*/
export function downFile (url, parameter) {
return axios({
url: url,
params: parameter,
method: 'get',
responseType: 'blob'
})
}
/**
* 下载文件 用于excel导出
* @param url
* @param parameter
* @returns {*}
*/
export function postDownFile (url, parameter) {
return axios({
url: url,
data: parameter,
method: 'post',
responseType: 'blob'
})
}
/**
* 下载文件
* @param url 文件路径
* @param fileName 文件名
* @param parameter
* @returns {*}
*/
export function downloadFile (url, fileName, parameter) {
return downFile(url, parameter).then((data) => {
if (!data || data.size === 0) {
Vue.prototype.$message.warning('文件下载失败')
return
}
if (typeof window.navigator.msSaveBlob !== 'undefined') {
window.navigator.msSaveBlob(new Blob([data]), fileName)
} else {
const url = window.URL.createObjectURL(new Blob([data]))
const link = document.createElement('a')
link.style.display = 'none'
link.href = url
link.setAttribute('download', fileName)
document.body.appendChild(link)
link.click()
document.body.removeChild(link) // 下载完成移除元素
window.URL.revokeObjectURL(url) // 释放掉blob对象
}
})
}
/**
* 文件上传 用于富文本上传图片
* @param url
* @param parameter
* @returns {*}
*/
export function uploadAction (url, parameter) {
return axios({
url: url,
data: parameter,
method: 'post',
headers: {
'Content-Type': 'multipart/form-data' // 文件上传
}
})
}
/**
* 获取文件服务访问路径
* @param avatar
* @param subStr
* @returns {*}
*/
export function getFileAccessHttpUrl (avatar, subStr) {
if (!subStr) subStr = 'http'
try {
if (avatar && avatar.startsWith(subStr)) {
return avatar
} else {
if (avatar && avatar.length > 0 && avatar.indexOf('[') === -1) {
const token = Vue.ls.get(ACCESS_TOKEN)
return window._CONFIG.staticDomainURL + '/' + avatar + '?token=' + token
}
}
} catch (err) {
console.log(err)
}
}
/**
* params传参的post
*/
export function postForParams (url, parameter) {
const sign = signMd5Utils.getSign(url, parameter)
// 将签名和时间戳,添加在请求接口 Header
const signHeader = { 'X-Sign': sign, 'X-TIMESTAMP': signMd5Utils.getDateTimeToString() }
return axios({
url: url,
method: 'post',
params: parameter,
headers: signHeader
})
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

+1
View File
@@ -0,0 +1 @@
@primary-color: '#D52C26';
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

+10
View File
@@ -0,0 +1,10 @@
module.exports = {
pageTitle: {
home: 'home'
},
back: 'back',
chinese: 'chinese',
english: 'english',
noMobile: '当前节点不支持手机端操作',
toPc: '请前往PC端进行操作'
}
+10
View File
@@ -0,0 +1,10 @@
module.exports = {
pageTitle: {
home: '首页'
},
back: '返回',
chinese: '中',
english: '英',
noMobile: '当前节点不支持手机端操作',
toPc: '请前往PC端进行操作'
}
+44
View File
@@ -0,0 +1,44 @@
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
import Storage from 'vue-ls'
import Vant from 'vant'
import 'vant/lib/index.css'
import 'vant/lib/index.less'
import VueI18n from 'vue-i18n'
import LangENUS from './common/lang/en-us'
import LangZHCN from './common/lang/zh-cn'
import { ACCESS_TOKEN, USER_INFO } from '@/store/mutation-types'
import './permission'
Vue.config.productionTip = false
Vue.use(Vant)
Vue.use(VueI18n)
const i18n = new VueI18n({
locale: 'zh-CN',
messages: {
'en-US': LangENUS,
'zh-CN': LangZHCN
}
})
Vue.use(Storage, {
namespace: 'pro__', // key prefix
name: 'ls', // name variable Vue.[ls] or this.[$ls],
storage: 'local' // storage name session, local, memory
})
new Vue({
router,
store,
i18n,
mounted () {
store.commit('SET_TOKEN', Vue.ls.get(ACCESS_TOKEN))
store.commit('SET_INFO', Vue.ls.get(USER_INFO))
},
render: h => h(App)
}).$mount('#app')
+52
View File
@@ -0,0 +1,52 @@
import Vue from 'vue'
import router from './router'
import store from './store'
import { ACCESS_TOKEN, UI_CACHE_DB_DICT_DATA, USER_INFO, USER_NAME } from '@/store/mutation-types'
import { welcome } from '@/utils/util'
import { getSSOUserInfo } from './api/api'
const whiteList = ['/user/login'] // no redirect whitelist
router.beforeEach(async (to, from, next) => {
// 单点登录
if (to.query.code) {
const { result } = await getSSOUserInfo({ code: to.query.code })
const userInfo = result.userInfo
Vue.ls.set(ACCESS_TOKEN, result.token, 7 * 24 * 60 * 60 * 1000)
Vue.ls.set(USER_NAME, userInfo.username, 7 * 24 * 60 * 60 * 1000)
Vue.ls.set(USER_INFO, userInfo, 7 * 24 * 60 * 60 * 1000)
Vue.ls.set(UI_CACHE_DB_DICT_DATA, result.sysAllDictItems, 7 * 24 * 60 * 60 * 1000)
store.commit('SET_TOKEN', result.token)
store.commit('SET_INFO', userInfo)
store.commit('SET_NAME', { username: userInfo.username, realname: userInfo.realname, welcome: welcome() })
store.commit('SET_AVATAR', userInfo.avatar)
}
if (Vue.ls.get(ACCESS_TOKEN)) {
/* has token */
if (to.path === '/user/login') {
next('/')
} else {
next()
}
} else {
if (whiteList.indexOf(to.path) !== -1) {
// 在免登录白名单,直接进入
if (to.path === '/user/login' && to.path !== process.env.VUE_APP_LOGIN_PAGE) {
window.location.replace(process.env.VUE_APP_LOGIN_PAGE)
return
}
next()
} else {
const path = '/user/login'
if (path === '/user/login' && path !== process.env.VUE_APP_LOGIN_PAGE) {
window.location.replace(process.env.VUE_APP_LOGIN_PAGE)
return
}
next({ path: path, query: { redirect: to.fullPath } })
}
}
})
router.afterEach(() => {
})
+57
View File
@@ -0,0 +1,57 @@
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{ // 登录页
path: '/user/login',
name: 'login',
component: () => import('@/views/user/Login'),
meta: {
// 是否显示头部导航
hasNavBar: true
}
},
{
path: '/',
name: 'home',
component: () => import('@/views/Home'),
meta: {
// 是否显示头部导航
hasNavBar: true,
// 当前页标题(国际化的标题,对应$t('pageTitle.home')中的home,如果为空则不显示
pageTitle: 'home',
// 是否显示返回
hasBack: false
}
},
{ // 流程中节点不支持的页面
path: '/noMobile',
name: 'NoMobile',
meta: {
hasNavBar: true,
hasBack: true
},
component: () => import('@/views/exception/NoMobile')
},
{
path: '*',
redirect: '/'
}
]
const originalPush = VueRouter.prototype.push
VueRouter.prototype.push = function push (location, onResolve, onReject) {
if (onResolve || onReject) return originalPush.call(this, location, onResolve, onReject)
return originalPush.call(this, location).catch(err => err)
}
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
+36
View File
@@ -0,0 +1,36 @@
import Vue from 'vue'
import { USER_INFO, ENHANCE_PRE } from '@/store/mutation-types'
const getters = {
device: state => state.app.device,
theme: state => state.app.theme,
color: state => state.app.color,
token: state => state.user.token,
avatar: state => {
state.user.avatar = Vue.ls.get(USER_INFO).avatar
return state.user.avatar
},
username: state => state.user.username,
nickname: state => {
state.user.realname = Vue.ls.get(USER_INFO).realname
return state.user.realname
},
welcome: state => state.user.welcome,
permissionList: state => state.user.permissionList,
userInfo: state => {
state.user.info = Vue.ls.get(USER_INFO)
return state.user.info
},
addRouters: state => state.permission.addRouters,
onlAuthFields: state => {
return state.online.authFields
},
enhanceJs: (state) => (code) => {
state.enhance.enhanceJs[code] = Vue.ls.get(ENHANCE_PRE + code)
return state.enhance.enhanceJs[code]
},
isEmbedded: state => state.app.isEmbedded,
defaultHeight: state => state.app.defaultHeight
}
export default getters
+25
View File
@@ -0,0 +1,25 @@
import Vue from 'vue'
import Vuex from 'vuex'
import app from './modules/app'
import user from './modules/user'
import getters from './getters'
Vue.use(Vuex)
export default new Vuex.Store({
modules: {
app,
user
},
state: {
},
mutations: {
},
actions: {
},
getters
})
+134
View File
@@ -0,0 +1,134 @@
import Vue from 'vue'
import {
SIDEBAR_TYPE,
DEFAULT_THEME,
DEFAULT_LAYOUT_MODE,
DEFAULT_COLOR,
DEFAULT_COLOR_WEAK,
DEFAULT_FIXED_HEADER,
DEFAULT_FIXED_SIDEMENU,
DEFAULT_FIXED_HEADER_HIDDEN,
DEFAULT_CONTENT_WIDTH_TYPE,
DEFAULT_MULTI_PAGE
} from '@/store/mutation-types'
const app = {
state: {
sidebar: {
opened: true,
withoutAnimation: false
},
device: 'desktop',
theme: '',
layout: '',
contentWidth: '',
fixedHeader: false,
fixSiderbar: false,
autoHideHeader: false,
color: null,
weak: false,
multipage: true, // 默认多页签模式
isEmbedded: false, // 是否嵌入其他系统
defaultHeight: {} // 包含用户信息的高度、面包屑的高度、单行查询条件的高度、单行操作列的高度
},
mutations: {
SET_SIDEBAR_TYPE: (state, type) => {
state.sidebar.opened = type
Vue.ls.set(SIDEBAR_TYPE, type)
},
CLOSE_SIDEBAR: (state, withoutAnimation) => {
Vue.ls.set(SIDEBAR_TYPE, true)
state.sidebar.opened = false
state.sidebar.withoutAnimation = withoutAnimation
},
TOGGLE_DEVICE: (state, device) => {
state.device = device
},
TOGGLE_THEME: (state, theme) => {
// setStore('_DEFAULT_THEME', theme)
Vue.ls.set(DEFAULT_THEME, theme)
state.theme = theme
},
TOGGLE_LAYOUT_MODE: (state, layout) => {
Vue.ls.set(DEFAULT_LAYOUT_MODE, layout)
state.layout = layout
},
TOGGLE_FIXED_HEADER: (state, fixed) => {
Vue.ls.set(DEFAULT_FIXED_HEADER, fixed)
state.fixedHeader = fixed
},
TOGGLE_FIXED_SIDERBAR: (state, fixed) => {
Vue.ls.set(DEFAULT_FIXED_SIDEMENU, fixed)
state.fixSiderbar = fixed
},
TOGGLE_FIXED_HEADER_HIDDEN: (state, show) => {
Vue.ls.set(DEFAULT_FIXED_HEADER_HIDDEN, show)
state.autoHideHeader = show
},
TOGGLE_CONTENT_WIDTH: (state, type) => {
Vue.ls.set(DEFAULT_CONTENT_WIDTH_TYPE, type)
state.contentWidth = type
},
TOGGLE_COLOR: (state, color) => {
Vue.ls.set(DEFAULT_COLOR, color)
state.color = color
},
TOGGLE_WEAK: (state, flag) => {
Vue.ls.set(DEFAULT_COLOR_WEAK, flag)
state.weak = flag
},
SET_MULTI_PAGE (state, multipageFlag) {
Vue.ls.set(DEFAULT_MULTI_PAGE, multipageFlag)
state.multipage = multipageFlag
},
SET_IS_EMBEDDED (state, isEmbedded) {
state.isEmbedded = isEmbedded
},
SET_DEFAULT_HEIGHT (state, defaultHeight) {
state.defaultHeight = defaultHeight
}
},
actions: {
setSidebar: ({ commit }, type) => {
commit('SET_SIDEBAR_TYPE', type)
},
CloseSidebar ({ commit }, { withoutAnimation }) {
commit('CLOSE_SIDEBAR', withoutAnimation)
},
ToggleDevice ({ commit }, device) {
commit('TOGGLE_DEVICE', device)
},
ToggleTheme ({ commit }, theme) {
commit('TOGGLE_THEME', theme)
},
ToggleLayoutMode ({ commit }, mode) {
commit('TOGGLE_LAYOUT_MODE', mode)
},
ToggleFixedHeader ({ commit }, fixedHeader) {
if (!fixedHeader) {
commit('TOGGLE_FIXED_HEADER_HIDDEN', false)
}
commit('TOGGLE_FIXED_HEADER', fixedHeader)
},
ToggleFixSiderbar ({ commit }, fixSiderbar) {
commit('TOGGLE_FIXED_SIDERBAR', fixSiderbar)
},
ToggleFixedHeaderHidden ({ commit }, show) {
commit('TOGGLE_FIXED_HEADER_HIDDEN', show)
},
ToggleContentWidth ({ commit }, type) {
commit('TOGGLE_CONTENT_WIDTH', type)
},
ToggleColor ({ commit }, color) {
commit('TOGGLE_COLOR', color)
},
ToggleWeak ({ commit }, weakFlag) {
commit('TOGGLE_WEAK', weakFlag)
},
ToggleMultipage ({ commit }, multipageFlag) {
commit('SET_MULTI_PAGE', multipageFlag)
}
}
}
export default app
+218
View File
@@ -0,0 +1,218 @@
import Vue from 'vue'
import { login, logout, phoneLogin, thirdLogin } from '@/api/login'
import { ACCESS_TOKEN, USER_NAME, USER_INFO, USER_AUTH, SYS_BUTTON_AUTH, UI_CACHE_DB_DICT_DATA, TENANT_ID, CACHE_INCLUDED_ROUTES } from '@/store/mutation-types'
import store from '@/store'
import { welcome } from '@/utils/util'
import { queryPermissionsByUser } from '@/api/api'
import { getAction } from '@/api/manage'
const user = {
state: {
token: '',
username: '',
realname: '',
tenantid: '',
welcome: '',
avatar: '',
permissionList: [],
info: {}
},
mutations: {
SET_TOKEN: (state, token) => {
state.token = token
},
SET_NAME: (state, { username, realname, welcome }) => {
state.username = username
state.realname = realname
state.welcome = welcome
},
SET_AVATAR: (state, avatar) => {
state.avatar = avatar
},
SET_PERMISSIONLIST: (state, permissionList) => {
state.permissionList = permissionList
},
SET_INFO: (state, info) => {
state.info = info
},
SET_TENANT: (state, id) => {
state.tenantid = id
}
},
actions: {
// CAS验证登录
ValidateLogin ({ commit }, userInfo) {
return new Promise((resolve, reject) => {
getAction('/sys/cas/client/validateLogin', userInfo).then(response => {
console.log('----cas 登录--------', response)
if (response.success) {
const result = response.result
const userInfo = result.userInfo
Vue.ls.set(ACCESS_TOKEN, result.token, 7 * 24 * 60 * 60 * 1000)
Vue.ls.set(USER_NAME, userInfo.username, 7 * 24 * 60 * 60 * 1000)
Vue.ls.set(USER_INFO, userInfo, 7 * 24 * 60 * 60 * 1000)
commit('SET_TOKEN', result.token)
commit('SET_INFO', userInfo)
commit('SET_NAME', { username: userInfo.username, realname: userInfo.realname, welcome: welcome() })
commit('SET_AVATAR', userInfo.avatar)
resolve(response)
} else {
resolve(response)
}
}).catch(error => {
reject(error)
})
})
},
// 登录
Login ({ commit }, userInfo) {
return new Promise((resolve, reject) => {
login(userInfo).then(response => {
if (response.code + '' === '200') {
const result = response.result
const userInfo = result.userInfo
Vue.ls.set(ACCESS_TOKEN, result.token, 7 * 24 * 60 * 60 * 1000)
Vue.ls.set(USER_NAME, userInfo.username, 7 * 24 * 60 * 60 * 1000)
Vue.ls.set(USER_INFO, userInfo, 7 * 24 * 60 * 60 * 1000)
Vue.ls.set(UI_CACHE_DB_DICT_DATA, result.sysAllDictItems, 7 * 24 * 60 * 60 * 1000)
commit('SET_TOKEN', result.token)
commit('SET_INFO', userInfo)
commit('SET_NAME', { username: userInfo.username, realname: userInfo.realname, welcome: welcome() })
commit('SET_AVATAR', userInfo.avatar)
resolve(response)
} else {
reject(response)
}
}).catch(error => {
reject(error)
})
})
},
// 手机号登录
PhoneLogin ({ commit }, userInfo) {
return new Promise((resolve, reject) => {
phoneLogin(userInfo).then(response => {
if (response.code + '' === '200') {
const result = response.result
const userInfo = result.userInfo
Vue.ls.set(ACCESS_TOKEN, result.token, 7 * 24 * 60 * 60 * 1000)
Vue.ls.set(USER_NAME, userInfo.username, 7 * 24 * 60 * 60 * 1000)
Vue.ls.set(USER_INFO, userInfo, 7 * 24 * 60 * 60 * 1000)
Vue.ls.set(UI_CACHE_DB_DICT_DATA, result.sysAllDictItems, 7 * 24 * 60 * 60 * 1000)
commit('SET_TOKEN', result.token)
commit('SET_INFO', userInfo)
commit('SET_NAME', { username: userInfo.username, realname: userInfo.realname, welcome: welcome() })
commit('SET_AVATAR', userInfo.avatar)
resolve(response)
} else {
reject(response)
}
}).catch(error => {
reject(error)
})
})
},
// 获取用户信息
GetPermissionList ({ commit }) {
return new Promise((resolve, reject) => {
queryPermissionsByUser().then(response => {
const menuData = response.result.menu
const authData = response.result.auth
const allAuthData = response.result.allAuth
// Vue.ls.set(USER_AUTH,authData);
sessionStorage.setItem(USER_AUTH, JSON.stringify(authData))
sessionStorage.setItem(SYS_BUTTON_AUTH, JSON.stringify(allAuthData))
if (menuData && menuData.length > 0) {
// update--begin--autor:qinfeng-----date:20200109------for 一级菜单的子菜单全部是隐藏路由,则一级菜单不显示------
menuData.forEach((item) => {
if (item.children) {
const hasChildrenMenu = item.children.filter((i) => {
return !i.hidden || i.hidden === false
})
if (hasChildrenMenu == null || hasChildrenMenu.length === 0) {
item.hidden = true
}
}
})
// console.log(" menu show json ", menuData)
// update--end--autor:qinfeng-----date:20200109------for 一级菜单的子菜单全部是隐藏路由,则一级菜单不显示------
commit('SET_PERMISSIONLIST', menuData)
} else {
Vue.prototype.$Jmodal.error({
title: '无权限',
content: '该用户无权限,请联系管理员!',
okText: '确认',
mask: false,
onOk: () => {
store.dispatch('Logout')
}
})
reject(new Error('getPermissionList: permissions must be a non-null array !'))
}
resolve(response)
}).catch(error => {
reject(error)
})
})
},
// 登出
Logout ({ commit, state }) {
return new Promise((resolve) => {
const logoutToken = state.token
commit('SET_TOKEN', '')
commit('SET_PERMISSIONLIST', [])
Vue.ls.remove(ACCESS_TOKEN)
Vue.ls.remove(USER_INFO)
Vue.ls.remove(USER_NAME)
Vue.ls.remove(UI_CACHE_DB_DICT_DATA)
Vue.ls.remove(CACHE_INCLUDED_ROUTES)
Vue.ls.remove(TENANT_ID)
// console.log('logoutToken: '+ logoutToken)
logout(logoutToken).then(() => {
if (process.env.VUE_APP_SSO + '' === 'true') {
const sevice = 'http://' + window.location.host + '/'
const serviceUrl = encodeURIComponent(sevice)
window.location.href = process.env.VUE_APP_CAS_BASE_URL + '/logout?service=' + serviceUrl
}
resolve()
window.location.replace(process.env.VUE_APP_LOGIN_PAGE)
}).catch(() => {
resolve()
})
})
},
// 第三方登录
ThirdLogin ({ commit }, param) {
return new Promise((resolve, reject) => {
thirdLogin(param.token, param.thirdType).then(response => {
if (response.code + '' === '200') {
const result = response.result
const userInfo = result.userInfo
Vue.ls.set(ACCESS_TOKEN, result.token, 7 * 24 * 60 * 60 * 1000)
Vue.ls.set(USER_NAME, userInfo.username, 7 * 24 * 60 * 60 * 1000)
Vue.ls.set(USER_INFO, userInfo, 7 * 24 * 60 * 60 * 1000)
commit('SET_TOKEN', result.token)
commit('SET_INFO', userInfo)
commit('SET_NAME', { username: userInfo.username, realname: userInfo.realname, welcome: welcome() })
commit('SET_AVATAR', userInfo.avatar)
resolve(response)
} else {
reject(response)
}
}).catch(error => {
reject(error)
})
})
},
saveTenant ({ commit }, id) {
Vue.ls.set(TENANT_ID, id, 7 * 24 * 60 * 60 * 1000)
commit('SET_TENANT', id)
}
}
}
export default user
+28
View File
@@ -0,0 +1,28 @@
export const ACCESS_TOKEN = 'Access-Token'
export const SIDEBAR_TYPE = 'SIDEBAR_TYPE'
export const DEFAULT_THEME = 'DEFAULT_THEME'
export const DEFAULT_LAYOUT_MODE = 'DEFAULT_LAYOUT_MODE'
export const DEFAULT_COLOR = 'DEFAULT_COLOR'
export const DEFAULT_COLOR_WEAK = 'DEFAULT_COLOR_WEAK'
export const DEFAULT_FIXED_HEADER = 'DEFAULT_FIXED_HEADER'
export const DEFAULT_FIXED_SIDEMENU = 'DEFAULT_FIXED_SIDEMENU'
export const DEFAULT_FIXED_HEADER_HIDDEN = 'DEFAULT_FIXED_HEADER_HIDDEN'
export const DEFAULT_CONTENT_WIDTH_TYPE = 'DEFAULT_CONTENT_WIDTH_TYPE'
export const DEFAULT_MULTI_PAGE = 'DEFAULT_MULTI_PAGE'
export const USER_NAME = 'Login_Username'
export const USER_INFO = 'Login_Userinfo'
export const USER_AUTH = 'LOGIN_USER_BUTTON_AUTH'
export const SYS_BUTTON_AUTH = 'SYS_BUTTON_AUTH'
export const ENCRYPTED_STRING = 'ENCRYPTED_STRING'
export const ENHANCE_PRE = 'enhance_'
export const UI_CACHE_DB_DICT_DATA = 'UI_CACHE_DB_DICT_DATA'
export const INDEX_MAIN_PAGE_PATH = '/dashboard/analysis'
export const OAUTH2_LOGIN_PAGE_PATH = '/oauth2-app/login'
export const TENANT_ID = 'TENANT_ID'
export const ONL_AUTH_FIELDS = 'ONL_AUTH_FIELDS'
// 路由缓存问题,关闭了tab页时再打开就不刷新 #842
export const CACHE_INCLUDED_ROUTES = 'CACHE_INCLUDED_ROUTES'
export const CONTENT_WIDTH_TYPE = {
Fluid: 'Fluid',
Fixed: 'Fixed'
}
+37
View File
@@ -0,0 +1,37 @@
const VueAxios = {
vm: {},
// eslint-disable-next-line no-unused-vars
install (Vue, router = {}, instance) {
if (this.installed) {
return
}
this.installed = true
if (!instance) {
// eslint-disable-next-line no-console
console.error('You have to install axios')
return
}
Vue.axios = instance
Object.defineProperties(Vue.prototype, {
axios: {
get: function get () {
return instance
}
},
$http: {
get: function get () {
return instance
}
}
})
}
}
export {
VueAxios
// eslint-disable-next-line no-undef
// instance as axios
}
+16
View File
@@ -0,0 +1,16 @@
import { axios } from '@/utils/request'
/**
* 获取RSA公钥
* @returns {*}
*/
export function getRSAPublicKey () {
return axios({
url: `/sys/getRSAPublicKey`,
method: 'get',
timeout: 5000,
headers: {
'Content-Type': 'application/json;charset=UTF-8'
}
})
}
File diff suppressed because it is too large Load Diff
+128
View File
@@ -0,0 +1,128 @@
import md5 from 'md5'
// 签名密钥串(前后端要一致,正式发布请自行修改)
const signatureSecret = 'dd05f1c54d63749eda95f9fa6d49v442a'
export default class signMd5Utils {
/**
* json参数升序
* @param jsonObj 发送参数
*/
static sortAsc (jsonObj) {
const arr = []
let num = 0
for (const i in jsonObj) {
arr[num] = i
num++
}
const sortArr = arr.sort()
const sortObj = {}
for (const i in sortArr) {
sortObj[sortArr[i]] = jsonObj[sortArr[i]]
}
return sortObj
}
/**
* @param url 请求的url,应该包含请求参数(url的?后面的参数)
* @param requestParams 请求参数(POST的JSON参数)
* @returns {string} 获取签名
*/
static getSign (url, requestParams) {
const urlParams = this.parseQueryString(url)
const jsonObj = this.mergeObject(urlParams, requestParams)
// console.log("sign jsonObj: ",jsonObj)
const requestBody = this.sortAsc(jsonObj)
console.log('sign requestBody: ', requestBody)
return md5(JSON.stringify(requestBody) + signatureSecret).toUpperCase()
}
/**
* @param url 请求的url
* @returns {{}} 将url中请求参数组装成json对象(url的?后面的参数)
*/
static parseQueryString (url) {
const urlReg = /^[^?]+\?([\w\W]+)$/
const paramReg = /([^&=]+)=([\w\W]*?)(&|$|#)/g
const urlArray = urlReg.exec(url)
const result = {}
// 获取URL上最后带逗号的参数变量 sys/dict/getDictItems/sys_user,realname,username
// 【这边条件没有encode】带条件参数例子:/sys/dict/getDictItems/sys_user,realname,id,username!='admin'%20order%20by%20create_time
let lastpathVariable = url.substring(url.lastIndexOf('/') + 1)
if (lastpathVariable.includes(',')) {
if (lastpathVariable.includes('?')) {
lastpathVariable = lastpathVariable.substring(0, lastpathVariable.indexOf('?'))
}
// 解决Sign 签名校验失败 #2728
result['x-path-variable'] = decodeURIComponent(lastpathVariable)
}
if (urlArray && urlArray[1]) {
const paramString = urlArray[1]; let paramResult
while ((paramResult = paramReg.exec(paramString)) != null) {
// 数字值转为string类型,前后端加密规则保持一致
if (this.myIsNaN(paramResult[2])) {
paramResult[2] = paramResult[2].toString()
}
result[paramResult[1]] = paramResult[2]
}
}
return result
}
/**
* @returns {*} 将两个对象合并成一个
*/
static mergeObject (objectOne, objectTwo) {
if (objectTwo && Object.keys(objectTwo).length > 0) {
for (const key in objectTwo) {
if (Object.prototype.hasOwnProperty.call(objectTwo, key) === true) {
// 数字值转为string类型,前后端加密规则保持一致
if (this.myIsNaN(objectTwo[key])) {
objectTwo[key] = objectTwo[key].toString()
}
objectOne[key] = objectTwo[key]
}
}
}
return objectOne
}
static urlEncode (param, key, encode) {
if (param == null) return ''
let paramStr = ''
const t = typeof (param)
if (t === 'string' || t === 'number' || t === 'boolean') {
paramStr += '&' + key + '=' + ((encode == null || encode) ? encodeURIComponent(param) : param)
} else {
for (const i in param) {
const k = key == null ? i : key + (param instanceof Array ? '[' + i + ']' : '.' + i)
paramStr += this.urlEncode(param[i], k, encode)
}
}
return paramStr
}
static getDateTimeToString () {
const date_ = new Date()
const year = date_.getFullYear()
let month = date_.getMonth() + 1
let day = date_.getDate()
if (month < 10) month = '0' + month
if (day < 10) day = '0' + day
let hours = date_.getHours()
let mins = date_.getMinutes()
let secs = date_.getSeconds()
const msecs = date_.getMilliseconds()
if (hours < 10) hours = '0' + hours
if (mins < 10) mins = '0' + mins
if (secs < 10) secs = '0' + secs
if (msecs < 10) secs = '0' + msecs
return year + '' + month + '' + day + '' + hours + '' + mins + '' + secs
}
// true:数值型的,false:非数值型
static myIsNaN (value) {
return typeof value === 'number' && !isNaN(value)
}
}
+193
View File
@@ -0,0 +1,193 @@
import Vue from 'vue'
import axios from 'axios'
import store from '@/store'
import { VueAxios } from './axios'
import router from '@/router/index'
import { ACCESS_TOKEN, TENANT_ID } from '@/store/mutation-types'
import { Dialog, Toast } from 'vant'
/**
* 【指定 axios的 baseURL】
* 如果手工指定 baseURL: '/laws-sinotruk'
* 则映射后端域名,通过 vue.config.js
* @type {*|string}
*/
const apiBaseUrl = window._CONFIG.domianURL || '/laws-sinotruk'
// console.log("apiBaseUrl= ",apiBaseUrl)
// 创建 axios 实例
const service = axios.create({
// baseURL: '/laws-sinotruk',
baseURL: apiBaseUrl, // api base_url
timeout: 30 * 60 * 1000 // 请求超时时间
})
const err = (error) => {
if (error.response) {
const data = error.response.data
const token = Vue.ls.get(ACCESS_TOKEN)
console.log('------异常响应------', token)
console.log('------异常响应------', error.response.status)
switch (error.response.status) {
case 403:
Toast('系统提示\n拒绝访问')
break
case 500:
console.log('------error.response------', error.response)
// update-begin- --- author:liusq ------ date:20200910 ---- for:处理Blob情况----
if (error.response.request.responseType === 'blob') {
blobToJson(data)
break
}
// update-end- --- author:liusq ------ date:20200910 ---- for:处理Blob情况----
if (data.includes('Token失效')) {
// update-begin- --- author:scott ------ date:20190225 ---- for:Token失效采用弹框模式,不直接跳转----
Dialog.alert({
title: '登录已过期',
message: '很抱歉,登录已过期,请重新登录',
confirmButtonText: '重新登录',
theme: 'round-button'
}).then(() => {
store.dispatch('Logout').then(() => {
Vue.ls.remove(ACCESS_TOKEN)
try {
const path = window.document.location.pathname
console.log('location pathname -> ' + path)
if (path !== '/' && path.indexOf('/user/login') === -1) {
window.location.reload()
}
} catch (e) {
window.location.reload()
}
})
})
// update-end- --- author:scott ------ date:20190225 ---- for:Token失效采用弹框模式,不直接跳转----
}
break
case 404:
Toast('系统提示\n很抱歉,资源未找到!')
break
case 504:
Toast('系统提示\n网络超时')
break
case 401:
Toast('系统提示\n未授权,请重新登录')
if (token) {
store.dispatch('Logout').then(() => {
setTimeout(() => {
window.location.reload()
}, 1500)
})
}
break
default:
Toast(data.message)
break
}
} else if (error.message) {
if (error.message.includes('timeout')) {
Toast('网络超时')
} else {
Toast(error.message)
}
}
return Promise.reject(error)
}
// request interceptor
service.interceptors.request.use(config => {
const token = Vue.ls.get(ACCESS_TOKEN)
if (token) {
config.headers['X-Access-Token'] = token // 让每个请求携带自定义 token 请根据实际情况自行修改
}
// update-begin--author:sunjianlei---date:20200723---for 如果当前在low-app环境,并且携带了appId,就向Header里传递appId
const $route = router.currentRoute
if ($route && $route.name && $route.name.startsWith('low-app') && $route.params.appId) {
config.headers['X-Low-App-ID'] = $route.params.appId
}
// update-end--author:sunjianlei---date:20200723---for 如果当前在low-app环境,并且携带了appId,就向Header里传递appId
// update-begin-author:taoyan date:2020707 for:多租户
let tenantid = Vue.ls.get(TENANT_ID)
if (!tenantid) {
tenantid = 0
}
config.headers['tenant-id'] = tenantid
let language = localStorage.getItem('language') || ''
if (!language) {
language = 'zh-cn'
}
const cut = language === 'zh-cn' ? 'zh' : 'en'
config.headers.Language = cut
// update-end-author:taoyan date:2020707 for:多租户
if (config.method === 'get') {
if (config.url.indexOf('sys/dict/getDictItems') < 0) {
config.params = {
_t: Date.parse(new Date()) / 1000,
...config.params
}
}
}
return config
}, (error) => {
return Promise.reject(error)
})
// response interceptor
service.interceptors.response.use((response) => {
return response.data
}, err)
const installer = {
vm: {},
install (Vue, router = {}) {
Vue.use(VueAxios, router, service)
}
}
/**
* Blob解析
* @param data
*/
function blobToJson (data) {
const fileReader = new FileReader()
const token = Vue.ls.get(ACCESS_TOKEN)
fileReader.onload = function () {
try {
const jsonData = JSON.parse(this.result) // 说明是普通对象数据,后台转换失败
console.log('jsonData', jsonData)
if (jsonData.status === 500) {
console.log('token----------》', token)
if (token && jsonData.message.includes('Token失效')) {
Dialog.alert({
title: '登录已过期',
message: '很抱歉,登录已过期,请重新登录',
confirmButtonText: '重新登录',
theme: 'round-button'
}).then(() => {
store.dispatch('Logout').then(() => {
Vue.ls.remove(ACCESS_TOKEN)
try {
const path = window.document.location.pathname
console.log('location pathname -> ' + path)
if (path !== '/' && path.indexOf('/user/login') === -1) {
window.location.reload()
}
} catch (e) {
window.location.reload()
}
})
})
}
}
} catch (err) {
// 解析成对象失败,说明是正常的文件流
console.log('blob解析fileReader返回err', err)
}
}
fileReader.readAsText(data)
}
export {
installer as VueAxios,
service as axios
}
+526
View File
@@ -0,0 +1,526 @@
import Vue from 'vue'
import * as api from '@/api/api'
import { ACCESS_TOKEN } from '@/store/mutation-types'
//
export function timeFix () {
const time = new Date()
const hour = time.getHours()
return hour < 9 ? '早上好' : (hour <= 11 ? '上午好' : (hour <= 13 ? '中午好' : (hour < 20 ? '下午好' : '晚上好')))
}
export function welcome () {
const arr = ['休息一会儿吧', '准备吃什么呢?', '要不要打一把 DOTA', '我猜你可能累了']
const index = Math.floor((Math.random() * arr.length))
return arr[index]
}
/**
* 触发 window.resize
*/
export function triggerWindowResizeEvent () {
const event = document.createEvent('HTMLEvents')
event.initEvent('resize', true, true)
event.eventType = 'message'
window.dispatchEvent(event)
}
/**
* 过滤对象中为空的属性
* @param obj
* @returns {*}
*/
export function filterObj (obj) {
if (!(typeof obj === 'object')) {
return
}
for (const key in obj) {
if (Object.prototype.hasOwnProperty.call(obj, key) && (obj[key] == null || obj[key] === undefined || obj[key] === '')) {
delete obj[key]
}
}
return obj
}
/**
* 时间格式化
* @param value
* @param fmt
* @returns {*}
*/
export function formatDate (value, fmt) {
const regPos = /^\d+(\.\d+)?$/
if (regPos.test(value)) {
// 如果是数字
const getDate = new Date(value)
const o = {
'M+': getDate.getMonth() + 1,
'd+': getDate.getDate(),
'h+': getDate.getHours(),
'm+': getDate.getMinutes(),
's+': getDate.getSeconds(),
'q+': Math.floor((getDate.getMonth() + 3) / 3),
S: getDate.getMilliseconds()
}
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (getDate.getFullYear() + '').substr(4 - RegExp.$1.length))
}
for (const k in o) {
if (new RegExp('(' + k + ')').test(fmt)) {
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k]).length)))
}
}
return fmt
} else {
// TODO
value = value.trim()
return value.substr(0, fmt.length)
}
}
/**
* 深度克隆对象、数组
* @param obj 被克隆的对象
* @return 克隆后的对象
*/
export function cloneObject (obj) {
return JSON.parse(JSON.stringify(obj))
}
/**
* 随机生成数字
*
* 示例:生成长度为 12 的随机数:randomNumber(12)
* 示例:生成 3~23 之间的随机数:randomNumber(3, 23)
*
* @param1 最小值 | 长度
* @param2 最大值
* @return int 生成后的数字
*/
export function randomNumber () {
// 生成 最小值 到 最大值 区间的随机数
const random = (min, max) => {
return Math.floor(Math.random() * (max - min + 1) + min)
}
if (arguments.length === 1) {
const [length] = arguments
// 生成指定长度的随机数字,首位一定不是 0
const nums = [...Array(length).keys()].map((i) => (i > 0 ? random(0, 9) : random(1, 9)))
return parseInt(nums.join(''))
} else if (arguments.length >= 2) {
const [min, max] = arguments
return random(min, max)
} else {
return Number.NaN
}
}
/**
* 随机生成字符串
* @param length 字符串的长度
* @param chats 可选字符串区间(只会生成传入的字符串中的字符)
* @return string 生成的字符串
*/
export function randomString (length, chats) {
if (!length) length = 1
if (!chats) chats = '0123456789qwertyuioplkjhgfdsazxcvbnm'
let str = ''
for (let i = 0; i < length; i++) {
const num = randomNumber(0, chats.length - 1)
str += chats[num]
}
return str
}
/**
* 随机生成uuid
* @return string 生成的uuid
*/
export function randomUUID () {
const chats = '0123456789abcdef'
return randomString(32, chats)
}
/**
* 下划线转驼峰
* @param string
* @returns {*}
*/
export function underLine2CamelCase (string) {
return string.replace(/_([a-z])/g, function (all, letter) {
return letter.toUpperCase()
})
}
/**
* 判断是否显示办理按钮
* @param bpmStatus
* @returns {*}
*/
export function showDealBtn (bpmStatus) {
return bpmStatus + '' !== '1' && bpmStatus + '' !== '3' && bpmStatus + '' !== '4'
}
/**
* 增强CSS,可以在页面上输出全局css
* @param css 要增强的css
* @param id style标签的id,可以用来清除旧样式
*/
export function cssExpand (css, id) {
const style = document.createElement('style')
style.type = 'text/css'
style.innerHTML = `@charset "UTF-8"; ${css}`
// 清除旧样式
if (id) {
const $style = document.getElementById(id)
if ($style != null) $style.outerHTML = ''
style.id = id
}
// 应用新样式
document.head.appendChild(style)
}
/** 用于js增强事件,运行JS代码,可以传参 */
// options 所需参数:
// 参数名 类型 说明
// vm VueComponent vue实例
// event Object event对象
// jsCode String 待执行的js代码
// errorMessage String 执行出错后的提示(控制台)
export function jsExpand (options = {}) {
// 绑定到window上的keyName
const windowKeyName = 'J_CLICK_EVENT_OPTIONS'
if (typeof window[windowKeyName] !== 'object') {
window[windowKeyName] = {}
}
// 随机生成JS增强的执行id,防止冲突
const id = randomString(16, 'qwertyuioplkjhgfdsazxcvbnm'.toUpperCase())
// 封装按钮点击事件
const code = `
(function (o_${id}) {
try {
(function (globalEvent, vm) {
${options.jsCode}
})(o_${id}.event, o_${id}.vm)
} catch (e) {
o_${id}.error(e)
}
o_${id}.done()
})(window['${windowKeyName}']['EVENT_${id}'])
`
// 创建script标签
const script = document.createElement('script')
// 将需要传递的参数挂载到window对象上
window[windowKeyName]['EVENT_' + id] = {
vm: options.vm,
event: options.event,
// 当执行完成时,无论如何都会调用的回调事件
done () {
// 执行完后删除新增的 script 标签不会撤销执行结果(已产生的结果不会被撤销)
script.outerHTML = ''
delete window[windowKeyName]['EVENT_' + id]
},
// 当js运行出错的时候调用的事件
error (e) {
console.group(`${options.errorMessage || '用户自定义JS增强代码运行出错'}${new Date()}`)
console.error(e)
console.groupEnd()
}
}
// 将事件挂载到document中
script.innerHTML = code
document.body.appendChild(script)
}
/**
* 重复值验证工具方法
*
* 使用示例:
* { validator: (rule, value, callback) => validateDuplicateValue('sys_fill_rule', 'rule_code', value, this.model.id, callback) }
*
* @param tableName 被验证的表名
* @param fieldName 被验证的字段名
* @param fieldVal 被验证的值
* @param dataId 数据ID,可空
* @param callback
*/
export function validateDuplicateValue (tableName, fieldName, fieldVal, dataId, callback) {
if (fieldVal) {
const params = { tableName, fieldName, fieldVal, dataId }
api.duplicateCheck(params).then(res => {
res.success ? callback() : callback(res.message)
}).catch(err => {
callback(err.message || err)
})
} else {
callback()
}
}
/**
* 根据编码校验规则code,校验传入的值是否合法
*
* 使用示例:
* { validator: (rule, value, callback) => validateCheckRule('common', value, callback) }
*
* @param ruleCode 编码校验规则 code
* @param value 被验证的值
* @param callback
*/
export function validateCheckRule (ruleCode, value, callback) {
if (ruleCode && value) {
value = encodeURIComponent(value)
api.checkRuleByCode({ ruleCode, value }).then(res => {
res.success ? callback() : callback(res.message)
}).catch(err => {
callback(err.message || err)
})
} else {
callback()
}
}
/**
* 如果值不存在就 push 进数组,反之不处理
* @param array 要操作的数据
* @param value 要添加的值
* @param key 可空,如果比较的是对象,可能存在地址不一样但值实际上是一样的情况,可以传此字段判断对象中唯一的字段,例如 id。不传则直接比较实际值
* @returns {boolean} 成功 push 返回 true,不处理返回 false
*/
export function pushIfNotExist (array, value, key) {
for (const item of array) {
if (key && (item[key] === value[key])) {
return false
} else if (item === value) {
return false
}
}
array.push(value)
return true
}
/**
* 可用于判断是否成功
* @type {symbol}
*/
export const succeedSymbol = Symbol('')
/**
* 可用于判断是否失败
* @type {symbol}
*/
export const failedSymbol = Symbol('')
/**
* 使 promise 无论如何都会 resolve,除非传入的参数不是一个Promise对象或返回Promise对象的方法
* 一般用在 Promise.all 中
*
* @param promise 可传Promise对象或返回Promise对象的方法
* @returns {Promise<any>}
*/
export function alwaysResolve (promise) {
return new Promise((resolve, reject) => {
let p = promise
if (typeof promise === 'function') {
p = promise()
}
if (p instanceof Promise) {
p.then(data => {
resolve({ type: succeedSymbol, data })
}).catch(error => {
resolve({ type: failedSymbol, error })
})
} else {
reject('alwaysResolve: 传入的参数不是一个Promise对象或返回Promise对象的方法')
}
})
}
/**
* 简单实现防抖方法
*
* 防抖(debounce)函数在第一次触发给定的函数时,不立即执行函数,而是给出一个期限值(delay),比如100ms。
* 如果100ms内再次执行函数,就重新开始计时,直到计时结束后再真正执行函数。
* 这样做的好处是如果短时间内大量触发同一事件,只会执行一次函数。
*
* @param fn 要防抖的函数
* @param delay 防抖的毫秒数
* @returns {Function}
*/
export function simpleDebounce (fn, delay = 100) {
let timer = null
return function () {
const args = arguments
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
fn.apply(this, args)
}, delay)
}
}
/**
* 不用正则的方式替换所有值
* @param text 被替换的字符串
* @param checker 替换前的内容
* @param replacer 替换后的内容
* @returns {String} 替换后的字符串
*/
export function replaceAll (text, checker, replacer) {
const lastText = text
text = text.replace(checker, replacer)
if (lastText !== text) {
return replaceAll(text, checker, replacer)
}
return text
}
/**
* 获取事件冒泡路径,兼容 IE11EdgeChromeFirefoxSafari
* 目前使用的地方:JEditableTable Span模式
*/
export function getEventPath (event) {
const target = event.target
const path = (event.composedPath && event.composedPath()) || event.path
if (path != null) {
return (path.indexOf(window) < 0) ? path.concat(window) : path
}
if (target === window) {
return [window]
}
const getParents = (node, memo) => {
memo = memo || []
const parentNode = node.parentNode
if (!parentNode) {
return memo
} else {
return getParents(parentNode, memo.concat(parentNode))
}
}
return [target].concat(getParents(target), window)
}
/**
* 根据组件名获取父级
* @param vm
* @param name
* @returns {Vue | null|null|Vue}
*/
export function getVmParentByName (vm, name) {
const parent = vm.$parent
if (parent && parent.$options) {
if (parent.$options.name === name) {
return parent
} else {
const res = getVmParentByName(parent, name)
if (res) {
return res
}
}
}
return null
}
/**
* 使一个值永远不会为(null | undefined
*
* @param value 要处理的值
* @param def 默认值,如果value为(null | undefined)则返回的默认值,可不传,默认为''
*/
export function neverNull (value, def) {
return value == null ? (neverNull(def, '')) : value
}
/**
* 根据元素值移除数组中的一个元素
* @param array 数组
* @param prod 属性名
* @param value 属性值
* @returns {string}
*/
export function removeArrayElement (array, prod, value) {
let index = -1
for (let i = 0; i < array.length; i++) {
if (array[i][prod] + '' === value + '') {
index = i
break
}
}
if (index >= 0) {
array.splice(index, 1)
}
}
export function findFirstRoute (data) {
if (data[0]) {
if (data[0] && data[0].children && data[0].children.length > 0) {
return findFirstRoute(data[0].children)
} else {
return data[0].path
}
} else {
return '/'
}
}
/** 判断是否是OAuth2APP环境 */
export function isOAuth2AppEnv () {
return /wxwork|dingtalk/i.test(navigator.userAgent)
}
/**
* 获取积木报表打印地址
* @param url
* @param id
* @param open 是否自动打开
* @returns {*}
*/
export function getReportPrintUrl (url, id, open) {
// URL支持{{ window.xxx }}占位符变量
url = url.replace(/{{([^}]+)?}}/g, (s1, s2) => evil(s2))
if (url.includes('?')) {
url += '&'
} else {
url += '?'
}
url += `id=${id}`
url += `&token=${Vue.ls.get(ACCESS_TOKEN)}`
if (open) {
window.open(url)
}
return url
}
/**
* 解决eval的eslint问题
* @param fn
* @returns {*}
*/
export function evil (fn) {
const Fn = Function // 一个变量指向Function,防止有些前端编译工具报错
return new Fn('return ' + fn)()
}
/**
* 将url的参数拆分成对象
* @param url
* @returns {{id: *, url}}
*/
export function urlToParams (url) {
const commonUrl = window._CONFIG.staticDomainURL
// url截取参数的部分
const paramsStr = url.slice(url.indexOf('?') + 1)
const paramsObj = {
url,
id: url.slice(url.indexOf(commonUrl) + commonUrl.length + 1, url.indexOf('?'))
}
paramsStr.split('&').forEach(item => {
const arr = item.split('=')
paramsObj[arr[0]] = arr[1]
})
return paramsObj
}
+41
View File
@@ -0,0 +1,41 @@
/**
* 邮箱
* @param {*} s
* 之前的正则 /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/
*/
export function isEmail (s) {
return /^\w[-\w.+]*@([A-Za-z0-9][-A-Za-z0-9]+\.)+[A-Za-z]{2,14}/.test(s)
}
/**
* 手机号码
* @param {*} s
*/
export function isMobile (s) {
return /^1[0-9]{10}$/.test(s)
}
/**
* 电话号码
* @param {*} s
*/
export function isPhone (s) {
return /^([0-9]{3,4}-)?[0-9]{7,8}$/.test(s)
}
/**
* URL地址
* @param {*} s
*/
export function isURL (s) {
return /^http[s]?:\/\/.*/.test(s)
}
/**
* 手机号或座机号
*
* /^((13[0-9])|(14[579])|(15([0-3]|[5-9]))|(17[0135678])|(18[0-9])|(19[8|9])|(16[6]))\d{8}$/
* */
export function phoneReg (s) {
return /(^[1][3,456789][0-9]{9}$)|(^0\d{2,3}-?\d{7,8}$)/.test(s)
}
+45
View File
@@ -0,0 +1,45 @@
import Vue from 'vue'
Vue.directive('watermark', {
bind: function (el, binding) {
// 水印文字,父元素,画布宽度,画布高度,字体,文字颜色,画布横坐标
function addWaterMarker (str, parentNode, width, height, font, textColor, fillTextX = '10') {
// 检查父元素是否包含子元素
const elementContains = (parent, child) => parent !== child && parent.contains(child)
const flag = elementContains(parentNode, document.querySelector('canvas'))
// 防止重复创建
if (!flag) {
const can = document.createElement('canvas')
parentNode.appendChild(can)
can.width = width || 300
can.height = height || 140
can.style.display = 'none'
const cans = can.getContext('2d')
cans.rotate(-20 * Math.PI / 180)
cans.font = font || '13px Microsoft Yahei'
cans.fillStyle = textColor || '#DDDDDD'
cans.textAlign = 'left'
cans.textBaseline = 'Middle'
cans.fillText(str, fillTextX, can.height)
// 设置背景图(整个项目中都添加水印建议使用此方法)
// parentNode.style.backgroundImage = "url(" + can.toDataURL("image/png") + ")";
// 创建div 定位覆盖(某个元素,如图片添加水印建议使用此方法)
const div = document.createElement('div')
div.id = str
div.style.pointerEvents = 'none'
div.style.top = '0'
div.style.left = '0'
div.style.position = 'absolute'
div.style.zIndex = '100000'
div.style.width = '100%'
div.style.height = '100%'
div.style.background = 'url(' + can.toDataURL('image/png') + ')'
parentNode.appendChild(div)
}
}
if (binding.value.text) {
addWaterMarker(binding.value.text, el, binding.value.width, binding.value.height, binding.value.font, binding.value.textColor, binding.value.fillTextX)
}
}
})
+15
View File
@@ -0,0 +1,15 @@
<template>
<div class="home">
首页
</div>
</template>
<script>
export default {
name: 'Home'
}
</script>
<style scoped>
</style>
+61
View File
@@ -0,0 +1,61 @@
<template>
<!-- 移动端不支持时显示的节点页面 -->
<div>
<div class="container">
<div class="error">
<img :src="require('@/assets/image/error.png')" alt="error">
</div>
<p>{{ $t('noMobile') }}</p>
<p>{{ $t('toPc') }}</p>
</div>
</div>
</template>
<script>
export default {
name: 'NoMobile'
}
</script>
<style lang="less" scoped>
.container {
font-family: PingFang SC-Medium, PingFang SC, sans-serif;
margin: 20vh auto 0;
text-align: center;
.error {
font-size: 0;
margin: 0 auto;
width: 1.6rem;
img {
width: 100%;
}
}
p {
font-size: 0.427rem;
line-height: 0.607rem;
font-weight: 500;
margin: 0;
padding: 0;
}
.error + p {
margin-top: 0.773rem;
}
}
</style>
<style lang="less">
@font-color: #34383F;
#app > .van-nav-bar {
background-color: #FFFFFF;
.van-nav-bar__title {
color: @font-color;
}
.van-nav-bar__text {
color: @font-color;
}
.van-icon {
color: @font-color;
}
}
</style>
+136
View File
@@ -0,0 +1,136 @@
<template>
<div class="login-box">
<van-field v-model="username" border center class="login-form-item" clearable placeholder="请输入账号"/>
<van-field v-model="password" :autocomplete="false" border center class="login-form-item" placeholder="请输入密码" :type="passwordFieldType"
@paste.native.capture.prevent="handleOperate">
<template #right-icon>
<div @click="switchPasswordType">
<van-icon name="eye" v-if="passwordFieldType!=='password'"/>
<van-icon name="closed-eye" v-else/>
</div>
</template>
</van-field>
<div class="random-image-box">
<van-field v-model="captcha" maxlength="4" :autocomplete="false" border center class="login-form-item" clearable placeholder="请输入验证码"/>
<template>
<img class="random-image" v-if="requestCodeSuccess" :src="randCodeImage" @click="handleChangeCheckCode" alt="code"/>
</template>
</div>
<van-button class="login-btn" size="large" type="info" @click="handleSubmit">登录</van-button>
</div>
</template>
<script>
import { Toast } from 'vant'
import { getRSAPublicKey } from '@/utils/encryption'
import { JSEncrypt } from 'jsencrypt'
import { mapActions } from 'vuex'
import { getAction } from '@api/manage'
export default {
name: 'LoginPage',
data () {
return {
username: '',
password: '',
captcha: '', // 验证码
rsaPublicKey: '', // 公钥
requestCodeSuccess: false, // 请求验证码图片是否成功
randCodeImage: '', // 验证码图片
passwordFieldType: 'password'
}
},
created () {
this.getPublicKey()
this.handleChangeCheckCode()
},
methods: {
...mapActions(['Login']),
// 获取RSA公钥
getPublicKey () {
// 获取公钥
getRSAPublicKey().then(res => {
this.rsaPublicKey = res.result
})
},
handleChangeCheckCode () {
this.currdatetime = new Date().getTime()
getAction(`/sys/randomImage/${this.currdatetime}`).then(res => {
if (res.success) {
this.randCodeImage = res.result
this.requestCodeSuccess = true
} else {
this.requestCodeSuccess = false
}
}).catch(() => {
this.requestCodeSuccess = false
})
},
handleSubmit () {
if (!this.username) {
Toast('请输入账号')
return
}
if (!this.password) {
Toast('请输入密码')
return
}
if (!this.captcha) {
Toast('请输入验证码')
return
}
const loginParams = {
rsaPublicKey: this.rsaPublicKey
}
// 新建JSEncrypt对象
const encrypt = new JSEncrypt()
encrypt.setPublicKey(loginParams.rsaPublicKey)
// 公钥加密
loginParams.username = encrypt.encrypt(this.username)
loginParams.password = encrypt.encrypt(this.password)
loginParams.captcha = this.captcha
loginParams.checkKey = this.currdatetime
this.Login(loginParams).then(res => {
console.log(res, 'res')
this.loginSuccess()
}).catch((err) => {
if (err.code === 500) {
this.$toast(err.message)
// 刷新验证码
this.handleChangeCheckCode()
this.captcha = ''
}
})
},
loginSuccess () {
Toast('登录成功')
this.$router.push({ path: '/' }).catch((res) => {
console.log(res)
})
},
// 密码禁止粘贴
handleOperate () {
return false
},
switchPasswordType () {
this.passwordFieldType = this.passwordFieldType === 'password' ? 'text' : 'password'
}
}
}
</script>
<style scoped lang="less">
.login-box {
height: 100vh;
padding: 20px;
background-color: #ccc;
.login-form-item {
margin: 20px 0;
}
.login-btn {
background-color: #D52C26;
}
}
</style>
+14
View File
@@ -0,0 +1,14 @@
const primaryColor = '#D52C26'
const white = '#FFFFFF'
module.exports = {
// 定制主题:用于覆盖原组件的Less变量
// 注意:每次修改完之后要重启服务才能生效
theme: {
// 导航栏
'nav-bar-icon-color': white,
'nav-bar-text-color': white,
'nav-bar-title-text-color': white,
'nav-bar-background-color': primaryColor,
}
}
+91
View File
@@ -0,0 +1,91 @@
const path = require('path')
const CompressionPlugin = require('compression-webpack-plugin')
const { theme } = require('./vant.config')
function resolve (dir) {
return path.join(__dirname, dir)
}
// vue.config.js
module.exports = {
/*
Vue-cli3:
Crashed when using Webpack `import()` #2463
https://github.com/vuejs/vue-cli/issues/2463
*/
// 如果你不需要生产环境的 source map,可以将其设置为 false 以加速生产环境构建。
productionSourceMap: false,
// 打包app时放开该配置
// publicPath:'./',
configureWebpack: config => {
// 生产环境取消 console.log
if (process.env.NODE_ENV === 'production') {
config.optimization.minimizer[0].options.terserOptions.compress.drop_console = true
}
},
chainWebpack: (config) => {
config.resolve.alias
.set('@$', resolve('src'))
.set('@api', resolve('src/api'))
.set('@assets', resolve('src/assets'))
.set('@comp', resolve('src/components'))
.set('@views', resolve('src/views'))
// 生产环境,开启js\css压缩
if (process.env.NODE_ENV === 'production') {
config.plugin('compressionPlugin').use(new CompressionPlugin({
test: /\.(js|css|less)$/, // 匹配文件名
threshold: 10240, // 对超过10k的数据压缩
deleteOriginalAssets: false // 不删除源文件
}))
}
// 配置 webpack 识别 markdown 为普通的文件
config.module
.rule('markdown')
.test(/\.md$/)
.use()
.loader('file-loader')
.end()
},
css: {
loaderOptions: {
less: {
modifyVars: {
/* less 变量覆盖,用于自定义 ant design 主题 */
'primary-color': '#D52C26',
'link-color': '#D52C26',
'border-radius-base': '4px',
/* vant组件变量覆盖 */
...theme
},
javascriptEnabled: true
}
}
},
devServer: {
port: 3335,
proxy: {
/* '/api': {
target: 'https://mock.ihx.me/mock/5baf3052f7da7e07e04a5116/antd-pro', //mock API接口系统
ws: false,
changeOrigin: true,
pathRewrite: {
'/laws-sinotruk': '' //默认所有请求都加了jero-boot前缀,需要去掉
}
}, */
'/laws-sinotruk': {
target: 'http://localhost:8184',
ws: false,
changeOrigin: true,
pathRewrite: {
// '/laws-sinotruk': '' // 默认所有请求都加了jero-boot前缀,需要去掉
}
}
}
},
lintOnSave: undefined
}