提交
This commit is contained in:
1062
src/App.vue
Normal file
1062
src/App.vue
Normal file
File diff suppressed because it is too large
Load Diff
19
src/api/index.js
Normal file
19
src/api/index.js
Normal file
@@ -0,0 +1,19 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
//demo
|
||||
export function getDemo() {
|
||||
return request({
|
||||
url: '/demo/test/demo',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 新增岗位
|
||||
export function addPost(data) {
|
||||
return request({
|
||||
url: '/system/post',
|
||||
method: 'post',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
1
src/assets/vue.svg
Normal file
1
src/assets/vue.svg
Normal file
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
|
||||
|
After Width: | Height: | Size: 496 B |
BIN
src/assets/wrj.jpg
Normal file
BIN
src/assets/wrj.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
43
src/components/HelloWorld.vue
Normal file
43
src/components/HelloWorld.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
defineProps({
|
||||
msg: String,
|
||||
})
|
||||
|
||||
const count = ref(0)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1>{{ msg }}</h1>
|
||||
|
||||
<div class="card">
|
||||
<button type="button" @click="count++">count is {{ count }}</button>
|
||||
<p>
|
||||
Edit
|
||||
<code>components/HelloWorld.vue</code> to test HMR
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p>
|
||||
Check out
|
||||
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
|
||||
>create-vue</a
|
||||
>, the official Vue + Vite starter
|
||||
</p>
|
||||
<p>
|
||||
Learn more about IDE Support for Vue in the
|
||||
<a
|
||||
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
|
||||
target="_blank"
|
||||
>Vue Docs Scaling up Guide</a
|
||||
>.
|
||||
</p>
|
||||
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.read-the-docs {
|
||||
color: #888;
|
||||
}
|
||||
</style>
|
||||
8
src/main.js
Normal file
8
src/main.js
Normal file
@@ -0,0 +1,8 @@
|
||||
import { createApp } from 'vue'
|
||||
import './style.css'
|
||||
import ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import App from './App.vue'
|
||||
const app = createApp(App)
|
||||
app.use(ElementPlus)
|
||||
app.mount('#app')
|
||||
60
src/plugins/auth.js
Normal file
60
src/plugins/auth.js
Normal file
@@ -0,0 +1,60 @@
|
||||
import store from '@/store'
|
||||
|
||||
function authPermission(permission) {
|
||||
const all_permission = "*:*:*"
|
||||
const permissions = store.getters && store.getters.permissions
|
||||
if (permission && permission.length > 0) {
|
||||
return permissions.some(v => {
|
||||
return all_permission === v || v === permission
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
function authRole(role) {
|
||||
const super_admin = "admin"
|
||||
const roles = store.getters && store.getters.roles
|
||||
if (role && role.length > 0) {
|
||||
return roles.some(v => {
|
||||
return super_admin === v || v === role
|
||||
})
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
// 验证用户是否具备某权限
|
||||
hasPermi(permission) {
|
||||
return authPermission(permission)
|
||||
},
|
||||
// 验证用户是否含有指定权限,只需包含其中一个
|
||||
hasPermiOr(permissions) {
|
||||
return permissions.some(item => {
|
||||
return authPermission(item)
|
||||
})
|
||||
},
|
||||
// 验证用户是否含有指定权限,必须全部拥有
|
||||
hasPermiAnd(permissions) {
|
||||
return permissions.every(item => {
|
||||
return authPermission(item)
|
||||
})
|
||||
},
|
||||
// 验证用户是否具备某角色
|
||||
hasRole(role) {
|
||||
return authRole(role)
|
||||
},
|
||||
// 验证用户是否含有指定角色,只需包含其中一个
|
||||
hasRoleOr(roles) {
|
||||
return roles.some(item => {
|
||||
return authRole(item)
|
||||
})
|
||||
},
|
||||
// 验证用户是否含有指定角色,必须全部拥有
|
||||
hasRoleAnd(roles) {
|
||||
return roles.every(item => {
|
||||
return authRole(item)
|
||||
})
|
||||
}
|
||||
}
|
||||
79
src/plugins/cache.js
Normal file
79
src/plugins/cache.js
Normal file
@@ -0,0 +1,79 @@
|
||||
const sessionCache = {
|
||||
set (key, value) {
|
||||
if (!sessionStorage) {
|
||||
return
|
||||
}
|
||||
if (key != null && value != null) {
|
||||
sessionStorage.setItem(key, value)
|
||||
}
|
||||
},
|
||||
get (key) {
|
||||
if (!sessionStorage) {
|
||||
return null
|
||||
}
|
||||
if (key == null) {
|
||||
return null
|
||||
}
|
||||
return sessionStorage.getItem(key)
|
||||
},
|
||||
setJSON (key, jsonValue) {
|
||||
if (jsonValue != null) {
|
||||
this.set(key, JSON.stringify(jsonValue))
|
||||
}
|
||||
},
|
||||
getJSON (key) {
|
||||
const value = this.get(key)
|
||||
if (value != null) {
|
||||
return JSON.parse(value)
|
||||
}
|
||||
return null
|
||||
},
|
||||
remove (key) {
|
||||
sessionStorage.removeItem(key)
|
||||
}
|
||||
}
|
||||
const localCache = {
|
||||
set (key, value) {
|
||||
if (!localStorage) {
|
||||
return
|
||||
}
|
||||
if (key != null && value != null) {
|
||||
localStorage.setItem(key, value)
|
||||
}
|
||||
},
|
||||
get (key) {
|
||||
if (!localStorage) {
|
||||
return null
|
||||
}
|
||||
if (key == null) {
|
||||
return null
|
||||
}
|
||||
return localStorage.getItem(key)
|
||||
},
|
||||
setJSON (key, jsonValue) {
|
||||
if (jsonValue != null) {
|
||||
this.set(key, JSON.stringify(jsonValue))
|
||||
}
|
||||
},
|
||||
getJSON (key) {
|
||||
const value = this.get(key)
|
||||
if (value != null) {
|
||||
return JSON.parse(value)
|
||||
}
|
||||
return null
|
||||
},
|
||||
remove (key) {
|
||||
localStorage.removeItem(key)
|
||||
}
|
||||
}
|
||||
|
||||
export default {
|
||||
/**
|
||||
* 会话级缓存
|
||||
*/
|
||||
session: sessionCache,
|
||||
/**
|
||||
* 本地缓存
|
||||
*/
|
||||
local: localCache
|
||||
}
|
||||
79
src/plugins/download.js
Normal file
79
src/plugins/download.js
Normal file
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
20
src/plugins/index.js
Normal file
20
src/plugins/index.js
Normal file
@@ -0,0 +1,20 @@
|
||||
import tab from './tab'
|
||||
import auth from './auth'
|
||||
import cache from './cache'
|
||||
import modal from './modal'
|
||||
import download from './download'
|
||||
|
||||
export default {
|
||||
install(Vue) {
|
||||
// 页签操作
|
||||
Vue.prototype.$tab = tab
|
||||
// 认证对象
|
||||
Vue.prototype.$auth = auth
|
||||
// 缓存对象
|
||||
Vue.prototype.$cache = cache
|
||||
// 模态框对象
|
||||
Vue.prototype.$modal = modal
|
||||
// 下载文件
|
||||
Vue.prototype.$download = download
|
||||
}
|
||||
}
|
||||
83
src/plugins/modal.js
Normal file
83
src/plugins/modal.js
Normal file
@@ -0,0 +1,83 @@
|
||||
import { Message, MessageBox, Notification, Loading } from 'element-ui'
|
||||
|
||||
let loadingInstance
|
||||
|
||||
export default {
|
||||
// 消息提示
|
||||
msg(content) {
|
||||
Message.info(content)
|
||||
},
|
||||
// 错误消息
|
||||
msgError(content) {
|
||||
Message.error(content)
|
||||
},
|
||||
// 成功消息
|
||||
msgSuccess(content) {
|
||||
Message.success(content)
|
||||
},
|
||||
// 警告消息
|
||||
msgWarning(content) {
|
||||
Message.warning(content)
|
||||
},
|
||||
// 弹出提示
|
||||
alert(content) {
|
||||
MessageBox.alert(content, "系统提示")
|
||||
},
|
||||
// 错误提示
|
||||
alertError(content) {
|
||||
MessageBox.alert(content, "系统提示", { type: 'error' })
|
||||
},
|
||||
// 成功提示
|
||||
alertSuccess(content) {
|
||||
MessageBox.alert(content, "系统提示", { type: 'success' })
|
||||
},
|
||||
// 警告提示
|
||||
alertWarning(content) {
|
||||
MessageBox.alert(content, "系统提示", { type: 'warning' })
|
||||
},
|
||||
// 通知提示
|
||||
notify(content) {
|
||||
Notification.info(content)
|
||||
},
|
||||
// 错误通知
|
||||
notifyError(content) {
|
||||
Notification.error(content)
|
||||
},
|
||||
// 成功通知
|
||||
notifySuccess(content) {
|
||||
Notification.success(content)
|
||||
},
|
||||
// 警告通知
|
||||
notifyWarning(content) {
|
||||
Notification.warning(content)
|
||||
},
|
||||
// 确认窗体
|
||||
confirm(content) {
|
||||
return MessageBox.confirm(content, "系统提示", {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: "warning",
|
||||
})
|
||||
},
|
||||
// 提交内容
|
||||
prompt(content) {
|
||||
return MessageBox.prompt(content, "系统提示", {
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: "warning",
|
||||
})
|
||||
},
|
||||
// 打开遮罩层
|
||||
loading(content) {
|
||||
loadingInstance = Loading.service({
|
||||
lock: true,
|
||||
text: content,
|
||||
spinner: "el-icon-loading",
|
||||
background: "rgba(0, 0, 0, 0.7)",
|
||||
})
|
||||
},
|
||||
// 关闭遮罩层
|
||||
closeLoading() {
|
||||
loadingInstance.close()
|
||||
}
|
||||
}
|
||||
71
src/plugins/tab.js
Normal file
71
src/plugins/tab.js
Normal file
@@ -0,0 +1,71 @@
|
||||
import store from '@/store'
|
||||
import router from '@/router'
|
||||
|
||||
export default {
|
||||
// 刷新当前tab页签
|
||||
refreshPage(obj) {
|
||||
const { path, query, matched } = router.currentRoute
|
||||
if (obj === undefined) {
|
||||
matched.forEach((m) => {
|
||||
if (m.components && m.components.default && m.components.default.name) {
|
||||
if (!['Layout', 'ParentView'].includes(m.components.default.name)) {
|
||||
obj = { name: m.components.default.name, path: path, query: query }
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
return store.dispatch('tagsView/delCachedView', obj).then(() => {
|
||||
const { path, query } = obj
|
||||
router.replace({
|
||||
path: '/redirect' + path,
|
||||
query: query
|
||||
})
|
||||
})
|
||||
},
|
||||
// 关闭当前tab页签,打开新页签
|
||||
closeOpenPage(obj) {
|
||||
store.dispatch("tagsView/delView", router.currentRoute)
|
||||
if (obj !== undefined) {
|
||||
return router.push(obj)
|
||||
}
|
||||
},
|
||||
// 关闭指定tab页签
|
||||
closePage(obj) {
|
||||
if (obj === undefined) {
|
||||
return store.dispatch('tagsView/delView', router.currentRoute).then(({ visitedViews }) => {
|
||||
const latestView = visitedViews.slice(-1)[0]
|
||||
if (latestView) {
|
||||
return router.push(latestView.fullPath)
|
||||
}
|
||||
return router.push('/')
|
||||
})
|
||||
}
|
||||
return store.dispatch('tagsView/delView', obj)
|
||||
},
|
||||
// 关闭所有tab页签
|
||||
closeAllPage() {
|
||||
return store.dispatch('tagsView/delAllViews')
|
||||
},
|
||||
// 关闭左侧tab页签
|
||||
closeLeftPage(obj) {
|
||||
return store.dispatch('tagsView/delLeftTags', obj || router.currentRoute)
|
||||
},
|
||||
// 关闭右侧tab页签
|
||||
closeRightPage(obj) {
|
||||
return store.dispatch('tagsView/delRightTags', obj || router.currentRoute)
|
||||
},
|
||||
// 关闭其他tab页签
|
||||
closeOtherPage(obj) {
|
||||
return store.dispatch('tagsView/delOthersViews', obj || router.currentRoute)
|
||||
},
|
||||
// 添加tab页签
|
||||
openPage(title, url, params) {
|
||||
const obj = { path: url, meta: { title: title } }
|
||||
store.dispatch('tagsView/addView', obj)
|
||||
return router.push({ path: url, query: params })
|
||||
},
|
||||
// 修改tab页签
|
||||
updatePage(obj) {
|
||||
return store.dispatch('tagsView/updateVisitedView', obj)
|
||||
}
|
||||
}
|
||||
80
src/style.css
Normal file
80
src/style.css
Normal file
@@ -0,0 +1,80 @@
|
||||
:root {
|
||||
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
||||
line-height: 1.5;
|
||||
font-weight: 400;
|
||||
|
||||
color-scheme: light dark;
|
||||
color: rgba(255, 255, 255, 0.87);
|
||||
background-color: #242424;
|
||||
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
a {
|
||||
font-weight: 500;
|
||||
color: #646cff;
|
||||
text-decoration: inherit;
|
||||
}
|
||||
a:hover {
|
||||
color: #535bf2;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 3.2em;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 8px;
|
||||
border: 1px solid transparent;
|
||||
padding: 0.6em 1.2em;
|
||||
font-size: 1em;
|
||||
font-weight: 500;
|
||||
font-family: inherit;
|
||||
background-color: #1a1a1a;
|
||||
cursor: pointer;
|
||||
transition: border-color 0.25s;
|
||||
}
|
||||
button:hover {
|
||||
border-color: #646cff;
|
||||
}
|
||||
button:focus,
|
||||
button:focus-visible {
|
||||
outline: 4px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
.card {
|
||||
padding: 2em;
|
||||
}
|
||||
|
||||
#app {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
:root {
|
||||
color: #213547;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
a:hover {
|
||||
color: #747bff;
|
||||
}
|
||||
button {
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
}
|
||||
15
src/utils/auth.js
Normal file
15
src/utils/auth.js
Normal file
@@ -0,0 +1,15 @@
|
||||
import Cookies from 'js-cookie'
|
||||
|
||||
const TokenKey = 'Admin-Token'
|
||||
|
||||
export function getToken() {
|
||||
return Cookies.get(TokenKey)
|
||||
}
|
||||
|
||||
export function setToken(token) {
|
||||
return Cookies.set(TokenKey, token)
|
||||
}
|
||||
|
||||
export function removeToken() {
|
||||
return Cookies.remove(TokenKey)
|
||||
}
|
||||
6
src/utils/errorCode.js
Normal file
6
src/utils/errorCode.js
Normal file
@@ -0,0 +1,6 @@
|
||||
export default {
|
||||
'401': '认证失败,无法访问系统资源',
|
||||
'403': '当前操作没有权限',
|
||||
'404': '访问资源不存在',
|
||||
'default': '系统未知错误,请反馈给管理员'
|
||||
}
|
||||
125
src/utils/request.js
Normal file
125
src/utils/request.js
Normal file
@@ -0,0 +1,125 @@
|
||||
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'
|
||||
|
||||
// 是否显示重新登录
|
||||
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 = '/index'
|
||||
})
|
||||
}).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 default service
|
||||
228
src/utils/ruoyi.js
Normal file
228
src/utils/ruoyi.js
Normal file
@@ -0,0 +1,228 @@
|
||||
/**
|
||||
* 通用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 tree = []
|
||||
for (let d of data) {
|
||||
let id = d[config.id]
|
||||
childrenListMap[id] = d
|
||||
if (!d[config.childrenList]) {
|
||||
d[config.childrenList] = []
|
||||
}
|
||||
}
|
||||
|
||||
for (let d of data) {
|
||||
let parentId = d[config.parentId]
|
||||
let parentObj = childrenListMap[parentId]
|
||||
if (!parentObj) {
|
||||
tree.push(d)
|
||||
} else {
|
||||
parentObj[config.childrenList].push(d)
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// 返回项目路径
|
||||
export function getNormalPath(p) {
|
||||
if (p.length === 0 || !p || p == 'undefined') {
|
||||
return p
|
||||
}
|
||||
let res = p.replace('//', '/')
|
||||
if (res[res.length - 1] === '/') {
|
||||
return res.slice(0, res.length - 1)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// 验证是否为blob格式
|
||||
export function blobValidate(data) {
|
||||
return data.type !== 'application/json'
|
||||
}
|
||||
296
src/utils/websocket.js
Normal file
296
src/utils/websocket.js
Normal file
@@ -0,0 +1,296 @@
|
||||
/**
|
||||
* WebSocket工具类
|
||||
* 用于实时获取无人机经纬度数据
|
||||
*/
|
||||
class WebSocketClient {
|
||||
constructor(url, options = {}) {
|
||||
this.url = url
|
||||
this.options = {
|
||||
reconnectInterval: options.reconnectInterval || 3000, // 重连间隔
|
||||
maxReconnectAttempts: options.maxReconnectAttempts || 5, // 最大重连次数
|
||||
heartbeatInterval: options.heartbeatInterval || 30000, // 心跳间隔
|
||||
...options
|
||||
}
|
||||
this.ws = null
|
||||
this.reconnectAttempts = 0
|
||||
this.heartbeatTimer = null
|
||||
this.reconnectTimer = null
|
||||
this.isManualClose = false
|
||||
this.listeners = {
|
||||
open: [],
|
||||
message: [],
|
||||
error: [],
|
||||
close: []
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 连接WebSocket
|
||||
*/
|
||||
connect() {
|
||||
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
||||
console.log('WebSocket已经连接')
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
this.ws = new WebSocket(this.url)
|
||||
this.setupEventHandlers()
|
||||
} catch (error) {
|
||||
console.error('WebSocket连接失败:', error)
|
||||
this.handleReconnect()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置事件处理器
|
||||
*/
|
||||
setupEventHandlers() {
|
||||
this.ws.onopen = (event) => {
|
||||
console.log('WebSocket连接成功')
|
||||
this.reconnectAttempts = 0
|
||||
this.isManualClose = false
|
||||
this.startHeartbeat()
|
||||
this.emit('open', event)
|
||||
}
|
||||
|
||||
this.ws.onmessage = (event) => {
|
||||
try {
|
||||
const data = JSON.parse(event.data)
|
||||
this.emit('message', data)
|
||||
} catch (error) {
|
||||
// 如果不是JSON格式,直接传递原始数据
|
||||
this.emit('message', event.data)
|
||||
}
|
||||
}
|
||||
|
||||
this.ws.onerror = (error) => {
|
||||
console.error('WebSocket错误:', error)
|
||||
this.emit('error', error)
|
||||
}
|
||||
|
||||
this.ws.onclose = (event) => {
|
||||
console.log('WebSocket连接关闭')
|
||||
this.stopHeartbeat()
|
||||
this.emit('close', event)
|
||||
|
||||
if (!this.isManualClose) {
|
||||
this.handleReconnect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
*/
|
||||
send(data) {
|
||||
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
||||
if (typeof data === 'object') {
|
||||
this.ws.send(JSON.stringify(data))
|
||||
} else {
|
||||
this.ws.send(data)
|
||||
}
|
||||
} else {
|
||||
console.warn('WebSocket未连接,无法发送消息')
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭连接
|
||||
*/
|
||||
close() {
|
||||
this.isManualClose = true
|
||||
this.stopHeartbeat()
|
||||
if (this.reconnectTimer) {
|
||||
clearTimeout(this.reconnectTimer)
|
||||
this.reconnectTimer = null
|
||||
}
|
||||
if (this.ws) {
|
||||
this.ws.close()
|
||||
this.ws = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 处理重连
|
||||
*/
|
||||
handleReconnect() {
|
||||
if (this.isManualClose) {
|
||||
return
|
||||
}
|
||||
|
||||
if (this.reconnectAttempts >= this.options.maxReconnectAttempts) {
|
||||
console.error('WebSocket重连次数已达上限')
|
||||
return
|
||||
}
|
||||
|
||||
this.reconnectAttempts++
|
||||
console.log(`尝试重连 (${this.reconnectAttempts}/${this.options.maxReconnectAttempts})...`)
|
||||
|
||||
this.reconnectTimer = setTimeout(() => {
|
||||
this.connect()
|
||||
}, this.options.reconnectInterval)
|
||||
}
|
||||
|
||||
/**
|
||||
* 启动心跳
|
||||
*/
|
||||
startHeartbeat() {
|
||||
this.stopHeartbeat()
|
||||
this.heartbeatTimer = setInterval(() => {
|
||||
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
||||
this.send({ type: 'ping' })
|
||||
}
|
||||
}, this.options.heartbeatInterval)
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止心跳
|
||||
*/
|
||||
stopHeartbeat() {
|
||||
if (this.heartbeatTimer) {
|
||||
clearInterval(this.heartbeatTimer)
|
||||
this.heartbeatTimer = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加事件监听器
|
||||
*/
|
||||
on(event, callback) {
|
||||
if (this.listeners[event]) {
|
||||
this.listeners[event].push(callback)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除事件监听器
|
||||
*/
|
||||
off(event, callback) {
|
||||
if (this.listeners[event]) {
|
||||
this.listeners[event] = this.listeners[event].filter(cb => cb !== callback)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 触发事件
|
||||
*/
|
||||
emit(event, data) {
|
||||
if (this.listeners[event]) {
|
||||
this.listeners[event].forEach(callback => {
|
||||
try {
|
||||
callback(data)
|
||||
} catch (error) {
|
||||
console.error(`事件监听器执行错误 (${event}):`, error)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取连接状态
|
||||
*/
|
||||
getReadyState() {
|
||||
if (!this.ws) {
|
||||
return WebSocket.CONNECTING
|
||||
}
|
||||
return this.ws.readyState
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否已连接
|
||||
*/
|
||||
isConnected() {
|
||||
return this.ws && this.ws.readyState === WebSocket.OPEN
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 模拟WebSocket数据生成器
|
||||
* 用于在没有真实WebSocket服务器时生成模拟的无人机位置数据
|
||||
*/
|
||||
class MockWebSocketDataGenerator {
|
||||
constructor(options = {}) {
|
||||
this.options = {
|
||||
interval: options.interval || 1000, // 数据发送间隔(毫秒)
|
||||
startLat: options.startLat || 39.908823, // 起始纬度(北京天安门)
|
||||
startLng: options.startLng || 116.397470, // 起始经度
|
||||
speed: options.speed || 0.0001, // 移动速度(度/次)
|
||||
...options
|
||||
}
|
||||
this.timer = null
|
||||
this.currentLat = this.options.startLat
|
||||
this.currentLng = this.options.startLng
|
||||
this.direction = Math.random() * 360 // 随机方向(度)
|
||||
this.callbacks = []
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始生成数据
|
||||
*/
|
||||
start(callback) {
|
||||
if (this.timer) {
|
||||
this.stop()
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
this.callbacks.push(callback)
|
||||
}
|
||||
|
||||
this.timer = setInterval(() => {
|
||||
// 随机改变方向(模拟飞行路径)
|
||||
if (Math.random() < 0.1) {
|
||||
this.direction = (this.direction + (Math.random() - 0.5) * 30) % 360
|
||||
}
|
||||
|
||||
// 计算新位置
|
||||
const rad = (this.direction * Math.PI) / 180
|
||||
this.currentLng += Math.cos(rad) * this.options.speed
|
||||
this.currentLat += Math.sin(rad) * this.options.speed
|
||||
|
||||
// 添加一些随机波动
|
||||
const noiseLng = (Math.random() - 0.5) * 0.00001
|
||||
const noiseLat = (Math.random() - 0.5) * 0.00001
|
||||
|
||||
const data = {
|
||||
type: 'drone_position',
|
||||
timestamp: Date.now(),
|
||||
latitude: this.currentLat + noiseLat,
|
||||
longitude: this.currentLng + noiseLng,
|
||||
altitude: 100 + Math.random() * 50, // 高度(米)
|
||||
speed: 10 + Math.random() * 5, // 速度(m/s)
|
||||
heading: this.direction,
|
||||
battery: 80 + Math.random() * 20 // 电量(%)
|
||||
}
|
||||
|
||||
this.callbacks.forEach(cb => {
|
||||
try {
|
||||
cb(data)
|
||||
} catch (error) {
|
||||
console.error('数据回调执行错误:', error)
|
||||
}
|
||||
})
|
||||
}, this.options.interval)
|
||||
}
|
||||
|
||||
/**
|
||||
* 停止生成数据
|
||||
*/
|
||||
stop() {
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer)
|
||||
this.timer = null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 重置位置
|
||||
*/
|
||||
reset(lat, lng) {
|
||||
this.currentLat = lat || this.options.startLat
|
||||
this.currentLng = lng || this.options.startLng
|
||||
}
|
||||
}
|
||||
|
||||
export { WebSocketClient, MockWebSocketDataGenerator }
|
||||
|
||||
Reference in New Issue
Block a user