Compare commits
12 Commits
37a27f5c3d
...
806b72542f
Author | SHA1 | Date | |
---|---|---|---|
806b72542f | |||
12d3276679 | |||
9d77f08b83 | |||
dca7dc584d | |||
0d3bcbef48 | |||
8dfbd9b338 | |||
6f6cb1977d | |||
698757df92 | |||
b6f3feac82 | |||
bedba54805 | |||
69b789b546 | |||
6c17f453a8 |
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -50,3 +50,4 @@ pnpm*
|
||||||
#components.d.ts
|
#components.d.ts
|
||||||
stats.html
|
stats.html
|
||||||
ts-out-dir
|
ts-out-dir
|
||||||
|
archive.zip
|
||||||
|
|
125
deploy.js
Normal file
125
deploy.js
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
/**
|
||||||
|
* $description
|
||||||
|
* @author 一七年夏
|
||||||
|
* @since 2022-12-07 14:00
|
||||||
|
*/
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
const childProcess = require('child_process')
|
||||||
|
const SSH2Promise = require('ssh2-promise')
|
||||||
|
const fs = require('fs')
|
||||||
|
const { v4 } = require('uuid')
|
||||||
|
const { zip } = require('compressing')
|
||||||
|
const dialog = require('dialog')
|
||||||
|
const delay = (ms) => new Promise((r) => setTimeout(r, ms))
|
||||||
|
|
||||||
|
const sshConfig = {
|
||||||
|
host: 'clould.lolosia.top',
|
||||||
|
port: 22,
|
||||||
|
username: 'lolosia',
|
||||||
|
password: 'lolo2024.'
|
||||||
|
}
|
||||||
|
const tempDir = '/home/lolosia/nginx/cache'
|
||||||
|
const tempPrefix = 'home'
|
||||||
|
const nginxContentPath = 'home'
|
||||||
|
|
||||||
|
async function localExec(cmd) {
|
||||||
|
return new Promise((r, rj) => {
|
||||||
|
const process1 = childProcess.exec(cmd)
|
||||||
|
process1.on('exit', r)
|
||||||
|
process1.on('error', rj)
|
||||||
|
process1.stdout.pipe(process.stdout)
|
||||||
|
process1.stderr.pipe(process.stderr)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
if (!process.argv.includes('-r')) {
|
||||||
|
console.log('项目构建...')
|
||||||
|
console.log('前端构建耗时较长,你可以去做其他事,稍后本脚本会弹窗提醒完成。')
|
||||||
|
await localExec('.\\node_modules\\.bin\\vite build --mode build')
|
||||||
|
console.log('构建zip...')
|
||||||
|
await zip.compressDir(`./dist`, './archive.zip')
|
||||||
|
console.log('连接至服务器...')
|
||||||
|
} else {
|
||||||
|
console.log('重新推送...')
|
||||||
|
}
|
||||||
|
const ssh2 = new SSH2Promise(sshConfig)
|
||||||
|
|
||||||
|
await ssh2.connect()
|
||||||
|
const ftp = ssh2.sftp()
|
||||||
|
async function exec(cmd) {
|
||||||
|
console.log(`运行 ${cmd} ...`)
|
||||||
|
console.log(await ssh2.exec(cmd))
|
||||||
|
}
|
||||||
|
async function sudo(cmd) {
|
||||||
|
const shell = await ssh2.shell()
|
||||||
|
shell.stdout.pipe(process.stdout)
|
||||||
|
shell.stderr.pipe(process.stderr)
|
||||||
|
process.stdin.pipe(shell.stdin)
|
||||||
|
shell.write(`sudo ${cmd}\n`)
|
||||||
|
await delay(500)
|
||||||
|
shell.write(`${sshConfig.password}\n`)
|
||||||
|
await delay(1000)
|
||||||
|
shell.write('exit\n')
|
||||||
|
await new Promise((resolve) => {
|
||||||
|
shell.on('end', resolve)
|
||||||
|
})
|
||||||
|
process.stdin.unpipe(shell.stdin)
|
||||||
|
}
|
||||||
|
async function write(path, text) {
|
||||||
|
const stream = await ftp.createWriteStream(path, { encoding: 'utf-8' })
|
||||||
|
return new Promise((r) => {
|
||||||
|
stream.write(text)
|
||||||
|
stream.end(r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const dir = tempDir
|
||||||
|
await exec(`mkdir -p ${dir}`)
|
||||||
|
const archivePath = `${dir}/archive.zip`
|
||||||
|
console.log(`删除 ${archivePath} ...`)
|
||||||
|
try {
|
||||||
|
await ftp.unlink(archivePath)
|
||||||
|
// eslint-disable-next-line no-empty
|
||||||
|
} catch (e) {}
|
||||||
|
console.log(`写入 ${archivePath} ...`)
|
||||||
|
const stream = await ftp.createWriteStream(archivePath, {
|
||||||
|
encoding: 'binary'
|
||||||
|
})
|
||||||
|
const file = fs.createReadStream('./archive.zip', {
|
||||||
|
encoding: 'binary',
|
||||||
|
autoClose: true
|
||||||
|
})
|
||||||
|
await new Promise((r, rj) => {
|
||||||
|
// file.on('close', () => stream.end());
|
||||||
|
stream.on('finish', () => r())
|
||||||
|
file.on('end', () => r())
|
||||||
|
file.on('error', rj)
|
||||||
|
stream.on('error', rj)
|
||||||
|
file.pipe(stream)
|
||||||
|
})
|
||||||
|
console.log(`写入 ${archivePath} 完成`)
|
||||||
|
const uuid = v4()
|
||||||
|
const target = `${dir}/${tempPrefix}-${uuid}`
|
||||||
|
//if (process.platform === 'win32') {
|
||||||
|
// await exec(`unzip -O gbk ${archivePath} -d ${target}`)
|
||||||
|
//} else {
|
||||||
|
await exec(`unzip ${archivePath} -d ${target}`)
|
||||||
|
await exec(`mv ${target}/dist ${target}/home`)
|
||||||
|
//}
|
||||||
|
|
||||||
|
const nginxTarget = '/usr/share/nginx/html'
|
||||||
|
|
||||||
|
console.log('移除旧目录 ...')
|
||||||
|
await sudo(`rm -rf ${nginxTarget}/${nginxContentPath}`)
|
||||||
|
|
||||||
|
console.log('复制文件 到指定目录 ...')
|
||||||
|
await sudo(`cp -r -f ${target}/* ${nginxTarget}/home`)
|
||||||
|
await new Promise((r) => {
|
||||||
|
dialog.info(`你现在可以关闭终端,去做其他事情了`, `${nginxContentPath}项目部署完成!`, r)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
.catch((e) => console.error(e))
|
||||||
|
.finally(() => process.exit(0))
|
|
@ -114,6 +114,7 @@
|
||||||
"Ref": true,
|
"Ref": true,
|
||||||
"VNode": true,
|
"VNode": true,
|
||||||
"WritableComputedRef": true,
|
"WritableComputedRef": true,
|
||||||
"toValue": true
|
"toValue": true,
|
||||||
|
"filterRouters": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,6 @@
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<script type="module" src="/src/boot.ts"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "vue3-admin-plus",
|
"name": "lolosia-web",
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "一七年夏",
|
"author": "一七年夏",
|
||||||
|
@ -19,6 +19,7 @@
|
||||||
"vitest": "vitest --ui",
|
"vitest": "vitest --ui",
|
||||||
"tsc-check": "tsc",
|
"tsc-check": "tsc",
|
||||||
"coverage": "vitest run --coverage",
|
"coverage": "vitest run --coverage",
|
||||||
|
"deploy": "node deploy.js",
|
||||||
"preinstall": "only-allow pnpm"
|
"preinstall": "only-allow pnpm"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
@ -33,6 +34,7 @@
|
||||||
"js-error-collection": "^1.0.8",
|
"js-error-collection": "^1.0.8",
|
||||||
"mitt": "3.0.1",
|
"mitt": "3.0.1",
|
||||||
"moment-mini": "2.29.4",
|
"moment-mini": "2.29.4",
|
||||||
|
"mqtt": "^5.5.0",
|
||||||
"nprogress": "0.2.0",
|
"nprogress": "0.2.0",
|
||||||
"only-allow": "^1.2.1",
|
"only-allow": "^1.2.1",
|
||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
|
@ -72,6 +74,8 @@
|
||||||
"@vue/cli-service": "^5.0.8",
|
"@vue/cli-service": "^5.0.8",
|
||||||
"@vue/test-utils": "^2.4.5",
|
"@vue/test-utils": "^2.4.5",
|
||||||
"@vueuse/core": "^10.9.0",
|
"@vueuse/core": "^10.9.0",
|
||||||
|
"compressing": "^1.10.0",
|
||||||
|
"dialog": "^0.3.1",
|
||||||
"ejs": "^3.1.9",
|
"ejs": "^3.1.9",
|
||||||
"eslint": "^8.56.0",
|
"eslint": "^8.56.0",
|
||||||
"eslint-config-prettier": "8.5.0",
|
"eslint-config-prettier": "8.5.0",
|
||||||
|
@ -91,6 +95,7 @@
|
||||||
"resize-observer-polyfill": "^1.5.1",
|
"resize-observer-polyfill": "^1.5.1",
|
||||||
"rollup-plugin-visualizer": "^5.12.0",
|
"rollup-plugin-visualizer": "^5.12.0",
|
||||||
"sass": "^1.72.0",
|
"sass": "^1.72.0",
|
||||||
|
"ssh2-promise": "^1.0.3",
|
||||||
"svg-sprite-loader": "6.0.11",
|
"svg-sprite-loader": "6.0.11",
|
||||||
"terser": "^5.29.2",
|
"terser": "^5.29.2",
|
||||||
"typescript": "^5.4.3",
|
"typescript": "^5.4.3",
|
||||||
|
|
|
@ -12,7 +12,6 @@ import { storeToRefs } from 'pinia/dist/pinia'
|
||||||
import { useRoute } from 'vue-router'
|
import { useRoute } from 'vue-router'
|
||||||
import { useBasicStore } from '@/store/basic'
|
import { useBasicStore } from '@/store/basic'
|
||||||
import { useConfigStore } from '@/store/config'
|
import { useConfigStore } from '@/store/config'
|
||||||
import { useErrorLog } from '@/hooks/use-error-log'
|
|
||||||
|
|
||||||
//reshow default setting
|
//reshow default setting
|
||||||
import { toggleHtmlClass } from '@/theme/utils'
|
import { toggleHtmlClass } from '@/theme/utils'
|
||||||
|
@ -24,10 +23,6 @@ onBeforeMount(() => {
|
||||||
//set tmp token when setting isNeedLogin false
|
//set tmp token when setting isNeedLogin false
|
||||||
if (!settings.value.isNeedLogin) useBasicStore().setToken(settings.value.tmpToken)
|
if (!settings.value.isNeedLogin) useBasicStore().setToken(settings.value.tmpToken)
|
||||||
})
|
})
|
||||||
onMounted(() => {
|
|
||||||
//lanch the errorLog collection
|
|
||||||
useErrorLog()
|
|
||||||
})
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
const { setTheme, theme, setSize, size, setLanguage, language } = useConfigStore()
|
const { setTheme, theme, setSize, size, setLanguage, language } = useConfigStore()
|
||||||
|
|
|
@ -19,6 +19,7 @@ export interface IUserRole {
|
||||||
roleId: number
|
roleId: number
|
||||||
roleName: string
|
roleName: string
|
||||||
roleType: string
|
roleType: string
|
||||||
|
type: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getMyInfo(): Promise<IUser> {
|
export async function getMyInfo(): Promise<IUser> {
|
||||||
|
|
23
src/boot.ts
Normal file
23
src/boot.ts
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
//@ts-ignore
|
||||||
|
import css from '@/styles/init.css?url'
|
||||||
|
//@ts-ignore
|
||||||
|
import logo from '@/icons/common/sidebar-logo.svg?url'
|
||||||
|
import settings from '@/settings'
|
||||||
|
|
||||||
|
document.querySelector('#app')!.innerHTML = `
|
||||||
|
<div class="__init__" style="display: none">
|
||||||
|
<link id="__init__" rel="stylesheet" href="${css}" />
|
||||||
|
<div class="__init__image">
|
||||||
|
<img src="${logo}" alt="" />
|
||||||
|
</div>
|
||||||
|
<div class="__init__title">${settings.title}</div>
|
||||||
|
<div class="__init__process-frame">
|
||||||
|
<div class="__init__progress">
|
||||||
|
<div class="__init__masker"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
`
|
||||||
|
;(async () => {
|
||||||
|
await import('./main')
|
||||||
|
})()
|
|
@ -1,32 +0,0 @@
|
||||||
/*js 错误日志收集*/
|
|
||||||
import { jsErrorCollection } from 'js-error-collection'
|
|
||||||
import pack from '../../package.json'
|
|
||||||
import settings from '@/settings'
|
|
||||||
import bus from '@/utils/bus'
|
|
||||||
import request from '@/utils/request'
|
|
||||||
const reqUrl = '/integration-front/errorCollection/insert'
|
|
||||||
const errorLogReq = (errLog: string) => {
|
|
||||||
request({
|
|
||||||
url: reqUrl,
|
|
||||||
data: {
|
|
||||||
pageUrl: window.location.href,
|
|
||||||
errorLog: errLog,
|
|
||||||
browserType: navigator.userAgent,
|
|
||||||
version: pack.version
|
|
||||||
},
|
|
||||||
method: 'post'
|
|
||||||
}).then(() => {
|
|
||||||
//通知错误列表页面更新数据
|
|
||||||
bus.emit('reloadErrorPage', {})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useErrorLog = () => {
|
|
||||||
//判断该环境是否需要收集错误日志,由settings配置决定
|
|
||||||
if (settings.errorLog?.includes(import.meta.env.VITE_APP_ENV)) {
|
|
||||||
jsErrorCollection({ runtimeError: true, rejectError: true, consoleError: true }, (errLog) => {
|
|
||||||
//判断是否是reqUrl错误,避免死循环
|
|
||||||
if (!errLog.includes(reqUrl)) errorLogReq(errLog)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +1,36 @@
|
||||||
import NProgress from 'nprogress'
|
|
||||||
import type { RouteRecordName } from 'vue-router'
|
|
||||||
import Layout from '@/layout/default/index.vue'
|
|
||||||
import router, { asyncRoutes, constantRoutes } from '@/router'
|
import router, { asyncRoutes, constantRoutes } from '@/router'
|
||||||
import 'nprogress/nprogress.css'
|
|
||||||
import { useBasicStore } from '@/store/basic'
|
import { useBasicStore } from '@/store/basic'
|
||||||
|
import NProgress from 'nprogress'
|
||||||
|
import 'nprogress/nprogress.css'
|
||||||
|
import type { RouteRecordName } from 'vue-router'
|
||||||
import { isArray } from 'xe-utils'
|
import { isArray } from 'xe-utils'
|
||||||
|
import type { RouterTypes } from '~/basic'
|
||||||
|
|
||||||
|
export function filterRouters<
|
||||||
|
T extends {
|
||||||
|
meta?: { roles?: string[] }
|
||||||
|
children?: T[]
|
||||||
|
}
|
||||||
|
>(menuList: T[], role: string) {
|
||||||
|
const out = menuList.filter((it) => it.meta?.roles?.includes(role) ?? true)
|
||||||
|
for (const t of out) {
|
||||||
|
if (t.children) filterRouters(t.children, role)
|
||||||
|
}
|
||||||
|
menuList.length = 0
|
||||||
|
menuList.push(...out)
|
||||||
|
}
|
||||||
|
|
||||||
//从数据库设置文本路由
|
//从数据库设置文本路由
|
||||||
export function setRouterFromDatabase(menuList) {
|
export function setRouterFromDatabase(menuList) {
|
||||||
const basicStore = useBasicStore()
|
const basicStore = useBasicStore()
|
||||||
const accessRoutes = menuList
|
const accessRoutes: RouterTypes = menuList
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const views = import.meta.glob('../views/**/*.vue') as Record<string, () => Promise<Component>>
|
const views = import.meta.glob('../views/**/*.vue') as Record<string, () => Promise<Component>>
|
||||||
|
|
||||||
function scanRouter(items: any[]) {
|
function scanRouter(items: any[]) {
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
if (item.component === 'Layout') item.component = async () => Layout
|
if (item.component === 'Layout') item.component = () => import('@/layout/default/index.vue')
|
||||||
else if (typeof item.component === 'string') {
|
else if (typeof item.component === 'string') {
|
||||||
let url: string = item.component
|
let url: string = item.component
|
||||||
if (url.startsWith('@/')) {
|
if (url.startsWith('@/')) {
|
||||||
|
@ -39,6 +53,7 @@ export function setRouterFromDatabase(menuList) {
|
||||||
}
|
}
|
||||||
|
|
||||||
scanRouter(accessRoutes)
|
scanRouter(accessRoutes)
|
||||||
|
|
||||||
accessRoutes.forEach((route) => router.addRoute(route))
|
accessRoutes.forEach((route) => router.addRoute(route))
|
||||||
asyncRoutes.forEach((item) => router.addRoute(item))
|
asyncRoutes.forEach((item) => router.addRoute(item))
|
||||||
basicStore.setFilterAsyncRoutes(accessRoutes)
|
basicStore.setFilterAsyncRoutes(accessRoutes)
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
import router from '@/router'
|
import router, { roleRoutes } from '@/router'
|
||||||
import { progressClose, progressStart, setRouterFromDatabase } from '@/hooks/use-permission'
|
import { filterRouters, progressClose, progressStart, setRouterFromDatabase } from '@/hooks/use-permission'
|
||||||
import { useBasicStore } from '@/store/basic'
|
import { useBasicStore } from '@/store/basic'
|
||||||
import { getMyInfo, getMyRole } from '@/api/user'
|
import { getMyInfo, getMyRole } from '@/api/user'
|
||||||
import { langTitle } from '@/hooks/use-common'
|
import { langTitle } from '@/hooks/use-common'
|
||||||
import { getRouterList } from '@/api/router'
|
|
||||||
|
|
||||||
//路由进入前拦截
|
//路由进入前拦截
|
||||||
//to:将要进入的页面 vue-router4.0 不推荐使用next()
|
//to:将要进入的页面 vue-router4.0 不推荐使用next()
|
||||||
const whiteList = ['/login', '/404', '/401', '/500'] // no redirect whitelist
|
const whiteList = ['/mqtt', '/login', '/404', '/401', '/500'] // no redirect whitelist
|
||||||
router.beforeEach(async (to) => {
|
router.beforeEach(async (to) => {
|
||||||
progressStart()
|
progressStart()
|
||||||
document.title = langTitle(to.meta?.title) // i18 page title
|
document.title = langTitle(to.meta?.title) // i18 page title
|
||||||
|
@ -15,34 +14,35 @@ router.beforeEach(async (to) => {
|
||||||
if (to.path === '/500') {
|
if (to.path === '/500') {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if (to.path === '/login') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
//1.判断token
|
//1.判断token
|
||||||
if (basicStore.token) {
|
if (basicStore.token) {
|
||||||
if (to.path === '/login') {
|
//2.判断是否获取用户信息
|
||||||
return '/'
|
if (!basicStore.getUserInfo) {
|
||||||
} else {
|
try {
|
||||||
//2.判断是否获取用户信息
|
const [userData, userRole] = await Promise.all([getMyInfo(), getMyRole()])
|
||||||
if (!basicStore.getUserInfo) {
|
//3.保存用户信息到store
|
||||||
try {
|
basicStore.setUserInfo({
|
||||||
const [userData, userRole] = await Promise.all([getMyInfo(), getMyRole()])
|
userInfo: userData,
|
||||||
const routes = await getRouterList({ roleId: userRole.roleId })
|
roles: [userRole.roleType],
|
||||||
//3.动态路由权限筛选
|
codes: [userRole.roleId]
|
||||||
setRouterFromDatabase(routes)
|
})
|
||||||
//4.保存用户信息到store
|
//const routes = await getRouterList({ roleId: userRole.roleId })
|
||||||
basicStore.setUserInfo({
|
const routes = roleRoutes
|
||||||
userInfo: userData,
|
filterRouters(routes as any, userRole.type)
|
||||||
roles: [userRole.roleType],
|
//4.动态路由权限筛选
|
||||||
codes: [userRole.roleId]
|
setRouterFromDatabase(routes)
|
||||||
})
|
//5.再次执行路由跳转
|
||||||
//5.再次执行路由跳转
|
return { ...to, replace: true }
|
||||||
return { ...to, replace: true }
|
} catch (e) {
|
||||||
} catch (e) {
|
console.error(`route permission error${e}`)
|
||||||
console.error(`route permission error${e}`)
|
progressClose()
|
||||||
progressClose()
|
return `/500?redirect=${to.path}`
|
||||||
return `/500?redirect=${to.path}`
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!whiteList.includes(to.path)) {
|
if (!whiteList.includes(to.path)) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { createRouter, createWebHistory } from 'vue-router'
|
import { createRouter, createWebHistory } from 'vue-router'
|
||||||
import type { RouterTypes } from '~/basic'
|
import type { BootstrapIcons, RouterTypes } from '~/basic'
|
||||||
import settings from '@/settings'
|
import settings from '@/settings'
|
||||||
import { userRoute } from '@/router/modules/user'
|
import { userRoute } from '@/router/modules/user'
|
||||||
const Layout = () => import('@/layout/default/index.vue')
|
const Layout = () => import('@/layout/default/index.vue')
|
||||||
|
@ -39,11 +39,18 @@ export const constantRoutes: RouterTypes = [
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/mqtt',
|
||||||
|
name: 'MQTT',
|
||||||
|
component: () => import('@/views/mqtt/index.vue'),
|
||||||
|
//using el svg icon, the elSvgIcon first when at the same time using elSvgIcon and icon
|
||||||
|
meta: { title: 'MQTT', icon: 'list' as BootstrapIcons, affix: true }
|
||||||
|
},
|
||||||
userRoute
|
userRoute
|
||||||
]
|
]
|
||||||
|
|
||||||
//角色和code数组动态路由
|
//角色和code数组动态路由
|
||||||
export const roleCodeRoutes: RouterTypes = []
|
export const roleRoutes: RouterTypes = []
|
||||||
/**
|
/**
|
||||||
* asyncRoutes
|
* asyncRoutes
|
||||||
* the routes that need to be dynamically loaded based on user roles
|
* the routes that need to be dynamically loaded based on user roles
|
||||||
|
|
|
@ -1,100 +0,0 @@
|
||||||
import Layout from '@/layout/default/index.vue'
|
|
||||||
const BasicDemo = {
|
|
||||||
path: '/basic-demo',
|
|
||||||
component: Layout,
|
|
||||||
meta: { title: 'Basic Demo', icon: 'eye-open' },
|
|
||||||
alwaysShow: true,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'hook',
|
|
||||||
component: () => import('@/views/basic-demo/hook/index.vue'),
|
|
||||||
name: 'Hook',
|
|
||||||
meta: { title: 'Hook' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'pinia',
|
|
||||||
component: () => import('@/views/basic-demo/pinia/index.vue'),
|
|
||||||
name: 'Pinia',
|
|
||||||
meta: { title: 'Pinia' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'mock',
|
|
||||||
component: () => import('@/views/basic-demo/mock/index.vue'),
|
|
||||||
name: 'Mock',
|
|
||||||
meta: { title: 'Mock' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'svg-icon',
|
|
||||||
component: () => import('@/views/basic-demo/svg-icon/index.vue'),
|
|
||||||
name: 'SvgIcon',
|
|
||||||
meta: { title: 'Svg Icon' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'parent-children',
|
|
||||||
component: () => import('@/views/basic-demo/parent-children/index.vue'),
|
|
||||||
name: 'Parent',
|
|
||||||
meta: { title: 'Parent Children' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'second-keep-alive',
|
|
||||||
component: () => import('@/views/basic-demo/keep-alive/index.vue'),
|
|
||||||
name: 'SecondKeepAlive',
|
|
||||||
//cachePage: cachePage when page enter, default false
|
|
||||||
//leaveRmCachePage: remove cachePage when page leave, default false
|
|
||||||
meta: { title: 'Second KeepAlive', cachePage: true, leaveRmCachePage: false }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'second-child',
|
|
||||||
name: 'SecondChild',
|
|
||||||
hidden: true,
|
|
||||||
component: () => import('@/views/basic-demo/keep-alive/second-child.vue'),
|
|
||||||
meta: { title: 'SecondChild', cachePage: true, activeMenu: '/basic-demo/second-keep-alive' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'third-child',
|
|
||||||
name: 'ThirdChild',
|
|
||||||
hidden: true,
|
|
||||||
component: () => import('@/views/basic-demo/keep-alive/third-child.vue'),
|
|
||||||
meta: { title: 'ThirdChild', cachePage: true, activeMenu: '/basic-demo/second-keep-alive' }
|
|
||||||
},
|
|
||||||
//tab-keep-alive
|
|
||||||
{
|
|
||||||
path: 'tab-keep-alive',
|
|
||||||
component: () => import('@/views/basic-demo/keep-alive/tab-keep-alive.vue'),
|
|
||||||
name: 'TabKeepAlive',
|
|
||||||
//closeTabRmCache: remove cachePage when tabs close, default false
|
|
||||||
meta: { title: 'Tab KeepAlive', cachePage: true, closeTabRmCache: true }
|
|
||||||
},
|
|
||||||
//third-keep-alive
|
|
||||||
{
|
|
||||||
path: 'third-keep-alive',
|
|
||||||
name: 'ThirdKeepAlive',
|
|
||||||
component: () => import('@/views/basic-demo/keep-alive/third-keep-alive.vue'),
|
|
||||||
//注:移除父容器页面缓存会把子页面一起移除了
|
|
||||||
meta: { title: 'Third KeepAlive', cachePage: true, leaveRmCachePage: false },
|
|
||||||
alwaysShow: true,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'second-children',
|
|
||||||
name: 'SecondChildren',
|
|
||||||
component: () => import('@/views/basic-demo/keep-alive/third-children/SecondChildren.vue'),
|
|
||||||
meta: { title: 'SecondChild', cachePage: true, leaveRmCachePage: true }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'third-children',
|
|
||||||
name: 'ThirdChildren',
|
|
||||||
component: () => import('@/views/basic-demo/keep-alive/third-children/ThirdChildren.vue'),
|
|
||||||
meta: { title: 'ThirdChild', cachePage: true, leaveRmCachePage: false }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'worker',
|
|
||||||
component: () => import('@/views/basic-demo/worker/index.vue'),
|
|
||||||
name: 'Worker',
|
|
||||||
meta: { title: 'Worker' }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
export default BasicDemo
|
|
|
@ -1,42 +0,0 @@
|
||||||
/** When your routing table is too long, you can split it into small modules**/
|
|
||||||
|
|
||||||
import Layout from '@/layout/default/index.vue'
|
|
||||||
|
|
||||||
const chartsRouter = {
|
|
||||||
path: '/charts',
|
|
||||||
component: Layout,
|
|
||||||
redirect: 'noRedirect',
|
|
||||||
name: 'Charts',
|
|
||||||
meta: {
|
|
||||||
title: 'Charts',
|
|
||||||
icon: 'chart'
|
|
||||||
},
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'keyboard',
|
|
||||||
component: () => import('@/views/charts/keyboard.vue'),
|
|
||||||
name: 'KeyboardChart',
|
|
||||||
meta: { title: 'Keyboard Chart', noCache: true }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'line',
|
|
||||||
component: () => import('@/views/charts/line.vue'),
|
|
||||||
name: 'LineChart',
|
|
||||||
meta: { title: 'Line Chart', noCache: true }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'mix-chart',
|
|
||||||
component: () => import('@/views/charts/mix-chart.vue'),
|
|
||||||
name: 'MixChart',
|
|
||||||
meta: { title: 'Mix Chart', noCache: true }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'echarts-demo',
|
|
||||||
component: () => import('@/views/charts/echarts-demo.vue'),
|
|
||||||
name: 'EchartsDemo',
|
|
||||||
meta: { title: 'Echarts Demo', noCache: true }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
export default chartsRouter
|
|
|
@ -1,48 +0,0 @@
|
||||||
import Layout from '@/layout/default/index.vue'
|
|
||||||
|
|
||||||
const directive = {
|
|
||||||
path: '/directive',
|
|
||||||
component: Layout,
|
|
||||||
meta: { title: 'Directive', icon: 'education' },
|
|
||||||
alwaysShow: true,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'copy',
|
|
||||||
component: () => import('@/views/directive/copy.vue'),
|
|
||||||
name: 'copy',
|
|
||||||
meta: { title: 'v-copy' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'debounce',
|
|
||||||
component: () => import('@/views/directive/debounce.vue'),
|
|
||||||
name: 'debounce',
|
|
||||||
meta: { title: 'v-debounce' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'longpress',
|
|
||||||
component: () => import('@/views/directive/longpress.vue'),
|
|
||||||
name: 'longpress',
|
|
||||||
meta: { title: 'v-longpress' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'watermark',
|
|
||||||
component: () => import('@/views/directive/watermark.vue'),
|
|
||||||
name: 'watermark',
|
|
||||||
meta: { title: 'v-watermark' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'waves',
|
|
||||||
component: () => import('@/views/directive/waves.vue'),
|
|
||||||
name: 'waves',
|
|
||||||
meta: { title: 'v-waves' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'clickoutside',
|
|
||||||
component: () => import('@/views/directive/clickoutside.vue'),
|
|
||||||
name: 'clickoutside',
|
|
||||||
meta: { title: 'v-clickoutside' }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
export default directive
|
|
|
@ -1,23 +0,0 @@
|
||||||
import Layout from '@/layout/default/index.vue'
|
|
||||||
const excel = {
|
|
||||||
path: '/excel',
|
|
||||||
component: Layout,
|
|
||||||
meta: { title: 'EXCEL', icon: 'excel' },
|
|
||||||
alwaysShow: true,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'exportExcel',
|
|
||||||
component: () => import('@/views/excel/exportExcel.vue'),
|
|
||||||
name: 'exportExcel',
|
|
||||||
meta: { title: 'exportExcel' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'importExcel',
|
|
||||||
component: () => import('@/views/excel/importExcel.vue'),
|
|
||||||
name: 'importExcel',
|
|
||||||
meta: { title: 'importExcel' }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
export default excel
|
|
|
@ -1,14 +0,0 @@
|
||||||
import Layout from '@/layout/default/index.vue'
|
|
||||||
const guid = {
|
|
||||||
path: '/guide',
|
|
||||||
component: Layout,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'index',
|
|
||||||
component: () => import('@/views/guide/index.vue'),
|
|
||||||
name: 'Guide',
|
|
||||||
meta: { title: 'Guide', icon: 'guide' }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
export default guid
|
|
|
@ -1,35 +0,0 @@
|
||||||
import Layout from '@/layout/default/index.vue'
|
|
||||||
const other = {
|
|
||||||
path: '/other',
|
|
||||||
component: Layout,
|
|
||||||
meta: { title: 'Other', icon: 'eye-open' },
|
|
||||||
alwaysShow: true,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'count-to',
|
|
||||||
component: () => import('@/views/other/count-to.vue'),
|
|
||||||
name: 'CountTo',
|
|
||||||
meta: { title: 'CountTo' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'd3',
|
|
||||||
component: () => import('@/views/other/d3/index.vue'),
|
|
||||||
name: 'd3',
|
|
||||||
meta: { title: 'd3' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'drag-pane',
|
|
||||||
component: () => import('@/views/other/drag-pane.vue'),
|
|
||||||
name: 'DragPane',
|
|
||||||
meta: { title: 'DragPane' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'signboard',
|
|
||||||
component: () => import('@/views/other/signboard/index.vue'),
|
|
||||||
name: 'signboard',
|
|
||||||
meta: { title: 'signboard' }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
export default other
|
|
|
@ -1,17 +0,0 @@
|
||||||
import Layout from '@/layout/default/index.vue'
|
|
||||||
const richText = {
|
|
||||||
path: '/rich-text',
|
|
||||||
component: Layout,
|
|
||||||
meta: { title: 'Rich Text', icon: 'clipboard' },
|
|
||||||
alwaysShow: true,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'tinymce',
|
|
||||||
name: 'Tinymce',
|
|
||||||
component: () => import('@/views/rich-text/TinymceExample.vue'),
|
|
||||||
meta: { title: 'Tinymce', icon: 'nested' }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
export default richText
|
|
|
@ -1,23 +0,0 @@
|
||||||
import Layout from '@/layout/default/index.vue'
|
|
||||||
const table = {
|
|
||||||
path: '/table',
|
|
||||||
component: Layout,
|
|
||||||
meta: { title: 'Table', icon: 'table' },
|
|
||||||
alwaysShow: true,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: 'dynamic-table',
|
|
||||||
name: 'DynamicTable',
|
|
||||||
component: () => import('@/views/table/dynamic-table.vue'),
|
|
||||||
meta: { title: 'Dynamic Table', icon: 'nested' }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'vxe-table',
|
|
||||||
name: 'VxeTable',
|
|
||||||
component: () => import('@/views/table/vxe-table.vue'),
|
|
||||||
meta: { title: 'Vxe Table', icon: 'nested' }
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
export default table
|
|
|
@ -1,7 +1,6 @@
|
||||||
import packageJson from '../package.json'
|
|
||||||
import type { SettingsConfig } from '~/basic'
|
import type { SettingsConfig } from '~/basic'
|
||||||
export const settings: SettingsConfig = {
|
export const settings: SettingsConfig = {
|
||||||
title: packageJson.name,
|
title: '洛洛希雅的小网站',
|
||||||
/**
|
/**
|
||||||
* @type {boolean} true | false
|
* @type {boolean} true | false
|
||||||
* @description Whether show the logo in sidebar
|
* @description Whether show the logo in sidebar
|
||||||
|
@ -87,7 +86,7 @@ export const settings: SettingsConfig = {
|
||||||
/*
|
/*
|
||||||
* vite.config.js base config
|
* vite.config.js base config
|
||||||
* */
|
* */
|
||||||
viteBasePath: '/vue-admin-plus/',
|
viteBasePath: '/home/',
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* i18n setting default language
|
* i18n setting default language
|
||||||
|
|
68
src/styles/init.css
Normal file
68
src/styles/init.css
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
.__init__ {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 100vh;
|
||||||
|
width: 100vw;
|
||||||
|
overflow: hidden;
|
||||||
|
display: flex !important;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.__init__title {
|
||||||
|
font-size: 2.5vmin;
|
||||||
|
padding: 1.5vmin;
|
||||||
|
font-weight: bold;
|
||||||
|
font-family: 'system-ui';
|
||||||
|
}
|
||||||
|
|
||||||
|
.__init__image {
|
||||||
|
height: 20vmin;
|
||||||
|
width: 20vmin;
|
||||||
|
/*background-image: url("/public/loading-logo.svg");*/
|
||||||
|
background-size: 100% 100%;
|
||||||
|
margin-bottom: 4vmin;
|
||||||
|
overflow: hidden;
|
||||||
|
transform: translateY(-1000px);
|
||||||
|
filter: drop-shadow(0px 1000px orange);
|
||||||
|
}
|
||||||
|
|
||||||
|
.__init__image img {
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.__init__process-frame {
|
||||||
|
width: 33%;
|
||||||
|
height: 2.4vmin;
|
||||||
|
border-radius: 1.2vmin;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.__init__progress {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: linear-gradient(-45deg, #0278ee 36%, #7db9e8 28%, #7db9e8 67%, #0278ee 25%);
|
||||||
|
background-size: 4em 2em;
|
||||||
|
-webkit-animation: load 1s;
|
||||||
|
animation: __init__load 1s;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.__init__masker {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
bottom: 0;
|
||||||
|
opacity: 0.8;
|
||||||
|
background: linear-gradient(90deg, #1c7bda 20%, #fff 100%)
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes __init__load {
|
||||||
|
0% {
|
||||||
|
width: 0;
|
||||||
|
}
|
||||||
|
}
|
|
@ -62,10 +62,21 @@ service.interceptors.response.use(
|
||||||
)
|
)
|
||||||
|
|
||||||
export const baseUrl: string = (() => {
|
export const baseUrl: string = (() => {
|
||||||
let port = import.meta.env.VITE_APP_BASE_URL
|
if (window.NGINX_BASE_URL) return window.NGINX_BASE_URL
|
||||||
if (/:\d{1,5}/.test(port)) port = port.match(/(?<port>:\d{1,5})/)?.groups?.port ?? ''
|
|
||||||
else port = `:${location.port}`
|
const mode = import.meta.env.VITE_APP_BASE_MODE
|
||||||
return `${location.protocol}//${location.hostname}${port}`
|
if (mode === 'port') {
|
||||||
|
const port = import.meta.env.VITE_APP_BASE_PORT || location.port
|
||||||
|
return `${location.protocol}//${location.hostname}:${port}`
|
||||||
|
}
|
||||||
|
if (mode === 'url') {
|
||||||
|
let url = import.meta.env.VITE_APP_BASE_URL
|
||||||
|
if (!url) url = `${location.protocol}//${location.host}`
|
||||||
|
return url
|
||||||
|
}
|
||||||
|
|
||||||
|
//local
|
||||||
|
return `${location.protocol}//${location.host}`
|
||||||
})()
|
})()
|
||||||
|
|
||||||
//导出service实例给页面调用 , config->页面的配置
|
//导出service实例给页面调用 , config->页面的配置
|
||||||
|
|
BIN
src/views/login/1712042358737.jpg
Normal file
BIN
src/views/login/1712042358737.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 139 KiB |
|
@ -1,8 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="login-container columnCE">
|
<div class="login-container columnCE">
|
||||||
<div class="login-hero">
|
<!-- <div class="login-hero">-->
|
||||||
<img src="@/assets/layout/login.svg" :alt="settings.title" />
|
<!-- <img src="@/assets/layout/login.svg" :alt="settings.title" />-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
<el-form ref="refLoginForm" class="login-form" :model="subForm" :rules="formRules">
|
<el-form ref="refLoginForm" class="login-form" :model="subForm" :rules="formRules">
|
||||||
<div class="title-container">
|
<div class="title-container">
|
||||||
<h3 class="title text-center">{{ settings.title }}</h3>
|
<h3 class="title text-center">{{ settings.title }}</h3>
|
||||||
|
@ -32,10 +32,15 @@
|
||||||
</span>
|
</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<div class="tip-message">{{ tipMessage }}</div>
|
<div class="tip-message">{{ tipMessage }}</div>
|
||||||
<el-button :loading="subLoading" type="primary" class="login-btn" size="default" @click.prevent="handleLogin">
|
<el-button :loading="subLoading" color="#E3B4D0" class="login-btn" size="default" @click.prevent="handleLogin">
|
||||||
登录
|
登录
|
||||||
</el-button>
|
</el-button>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
<div class="icp">
|
||||||
|
鲁ICP备
|
||||||
|
<s>1145141919号-810</s>
|
||||||
|
</div>
|
||||||
|
<div class="copyright">© 2024 洛洛希雅Lolosia</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -163,14 +168,19 @@ $light_gray: #eee;
|
||||||
overflow-y: hidden;
|
overflow-y: hidden;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
background-color: $bg;
|
background-color: $bg;
|
||||||
|
background-image: url('./1712042358737.jpg');
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
background-position-x: 65%;
|
||||||
|
background-position-y: 75%;
|
||||||
|
|
||||||
.login-form {
|
.login-form {
|
||||||
width: 360px;
|
width: 360px;
|
||||||
padding: 40px 30px;
|
padding: 40px 30px;
|
||||||
background: #fff;
|
background: #fff6;
|
||||||
box-shadow: 0 4px 16px rgba(4, 61, 175, 0.15);
|
box-shadow: 0 4px 16px rgba(4, 61, 175, 0.15);
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
margin-right: 20vw;
|
margin-right: 10vw;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
@media screen and (min-width: 769px) and (max-width: 992px) {
|
@media screen and (min-width: 769px) and (max-width: 992px) {
|
||||||
margin-right: 10vw;
|
margin-right: 10vw;
|
||||||
|
@ -184,12 +194,26 @@ $light_gray: #eee;
|
||||||
.title-container {
|
.title-container {
|
||||||
.title {
|
.title {
|
||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
color: $dark_gray;
|
color: white;
|
||||||
margin: 0 auto 25px auto;
|
margin: 0 auto 25px auto;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icp {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0.5em;
|
||||||
|
left: 2em;
|
||||||
|
color: #fffb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyright {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0.5em;
|
||||||
|
right: 2em;
|
||||||
|
color: #fffb;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-hero {
|
.login-hero {
|
||||||
|
@ -261,7 +285,7 @@ $light_gray: #eee;
|
||||||
|
|
||||||
.el-form-item {
|
.el-form-item {
|
||||||
border: 1px solid #e0e0e0;
|
border: 1px solid #e0e0e0;
|
||||||
background: #fff;
|
background: #fffb;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
color: #999;
|
color: #999;
|
||||||
|
|
||||||
|
|
99
src/views/mqtt/index.vue
Normal file
99
src/views/mqtt/index.vue
Normal file
|
@ -0,0 +1,99 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { MqttClient } from 'mqtt'
|
||||||
|
import mqtt from 'mqtt'
|
||||||
|
import { delay } from '@/utils/common-util'
|
||||||
|
import type { Ref } from 'vue'
|
||||||
|
|
||||||
|
let mqttClient: MqttClient | null = $ref()
|
||||||
|
const jobCancel: Ref<(() => void) | null> = ref() as any
|
||||||
|
|
||||||
|
const topic = $ref('V2X/RSU/R328328/RSM/UP/DAWNLINE')
|
||||||
|
const url = $ref('mqtt://localhost:15675/mqtt')
|
||||||
|
const username = $ref('root')
|
||||||
|
const password = $ref('123456')
|
||||||
|
const clientId = $ref('R328328')
|
||||||
|
const file: File = $ref()
|
||||||
|
let status = $ref(false)
|
||||||
|
|
||||||
|
function connect() {
|
||||||
|
mqttClient = mqtt.connect(url, {
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
clientId
|
||||||
|
})
|
||||||
|
mqttClient.on('connect', (it) => {
|
||||||
|
status = true
|
||||||
|
})
|
||||||
|
|
||||||
|
mqttClient.on('error', (it) => {
|
||||||
|
status = false
|
||||||
|
console.error(it)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function disconnect() {
|
||||||
|
jobCancel.value?.()
|
||||||
|
await mqttClient?.endAsync()
|
||||||
|
mqttClient = null
|
||||||
|
status = false
|
||||||
|
}
|
||||||
|
|
||||||
|
async function publish() {
|
||||||
|
const text = await file.text()
|
||||||
|
const list = JSON.parse(text) as any[]
|
||||||
|
let i = 0
|
||||||
|
const next = () => list[(i = i++ % list.length)]
|
||||||
|
;(async () => {
|
||||||
|
let exit = false
|
||||||
|
jobCancel.value = () => {
|
||||||
|
exit = true
|
||||||
|
jobCancel.value = null
|
||||||
|
}
|
||||||
|
while (!exit) {
|
||||||
|
const item = next()
|
||||||
|
const json = JSON.stringify(item)
|
||||||
|
mqttClient!.publish(topic, json, {})
|
||||||
|
|
||||||
|
await delay(100)
|
||||||
|
}
|
||||||
|
})().catch((e) => console.error(e))
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<el-container>
|
||||||
|
<el-main style="max-width: 1024px; margin: 0 auto">
|
||||||
|
<el-form label-width="auto">
|
||||||
|
<el-form-item label="地址">
|
||||||
|
<el-input v-model="url" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="TOPIC">
|
||||||
|
<el-input v-model="topic" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="ClientID">
|
||||||
|
<el-input v-model="clientId" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="用户名">
|
||||||
|
<el-input v-model="username" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="密码">
|
||||||
|
<el-input v-model="password" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="文件">
|
||||||
|
<input type="file" accept="application/json" @change="file = $event.target?.['files']?.[0]" />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item label="过滤器">
|
||||||
|
<el-table />
|
||||||
|
</el-form-item>
|
||||||
|
<el-form-item>
|
||||||
|
<el-button v-if="!status" type="primary" @click="connect">连接</el-button>
|
||||||
|
<el-button v-else type="danger" @click="disconnect">断开</el-button>
|
||||||
|
<el-button v-if="!jobCancel" type="primary" :disabled="!status" @click="publish">发送</el-button>
|
||||||
|
<el-button v-else type="danger" :disabled="!status" @click="() => jobCancel?.()">停止</el-button>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-main>
|
||||||
|
</el-container>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped lang="scss"></style>
|
4
typings/auto-imports.d.ts
vendored
4
typings/auto-imports.d.ts
vendored
|
@ -27,6 +27,7 @@ declare global {
|
||||||
const elLoading: typeof import('../src/hooks/use-element')['elLoading']
|
const elLoading: typeof import('../src/hooks/use-element')['elLoading']
|
||||||
const elMessage: typeof import('../src/hooks/use-element')['elMessage']
|
const elMessage: typeof import('../src/hooks/use-element')['elMessage']
|
||||||
const elNotify: typeof import('../src/hooks/use-element')['elNotify']
|
const elNotify: typeof import('../src/hooks/use-element')['elNotify']
|
||||||
|
const filterRouters: typeof import('../src/hooks/use-permission')['filterRouters']
|
||||||
const freshRouter: typeof import('../src/hooks/use-permission')['freshRouter']
|
const freshRouter: typeof import('../src/hooks/use-permission')['freshRouter']
|
||||||
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
const getCurrentInstance: typeof import('vue')['getCurrentInstance']
|
||||||
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
const getCurrentScope: typeof import('vue')['getCurrentScope']
|
||||||
|
@ -69,7 +70,7 @@ declare global {
|
||||||
const resetState: typeof import('../src/hooks/use-permission')['resetState']
|
const resetState: typeof import('../src/hooks/use-permission')['resetState']
|
||||||
const resizeHandler: typeof import('../src/hooks/use-layout')['resizeHandler']
|
const resizeHandler: typeof import('../src/hooks/use-layout')['resizeHandler']
|
||||||
const resolveComponent: typeof import('vue')['resolveComponent']
|
const resolveComponent: typeof import('vue')['resolveComponent']
|
||||||
const resolveDirective: typeof import('vue')['resolveDirective']
|
const resolveDirective: (typeof import('vue'))['resolveDirective']
|
||||||
const rolesPermission: typeof import('../src/directives/roles-permission')['default']
|
const rolesPermission: typeof import('../src/directives/roles-permission')['default']
|
||||||
const routeInfo: typeof import('../src/hooks/use-self-router')['routeInfo']
|
const routeInfo: typeof import('../src/hooks/use-self-router')['routeInfo']
|
||||||
const routerBack: typeof import('../src/hooks/use-self-router')['routerBack']
|
const routerBack: typeof import('../src/hooks/use-self-router')['routerBack']
|
||||||
|
@ -94,7 +95,6 @@ declare global {
|
||||||
const useCssVars: typeof import('vue')['useCssVars']
|
const useCssVars: typeof import('vue')['useCssVars']
|
||||||
const useDebuggerStore: typeof import('../src/store/debuger')['useDebuggerStore']
|
const useDebuggerStore: typeof import('../src/store/debuger')['useDebuggerStore']
|
||||||
const useElement: typeof import('../src/hooks/use-element')['useElement']
|
const useElement: typeof import('../src/hooks/use-element')['useElement']
|
||||||
const useErrorLog: typeof import('../src/hooks/use-error-log')['useErrorLog']
|
|
||||||
const useLink: typeof import('vue-router')['useLink']
|
const useLink: typeof import('vue-router')['useLink']
|
||||||
const useRoute: typeof import('vue-router')['useRoute']
|
const useRoute: typeof import('vue-router')['useRoute']
|
||||||
const useRouter: typeof import('vue-router')['useRouter']
|
const useRouter: typeof import('vue-router')['useRouter']
|
||||||
|
|
1
typings/components.d.ts
vendored
1
typings/components.d.ts
vendored
|
@ -8,7 +8,6 @@ export {}
|
||||||
declare module 'vue' {
|
declare module 'vue' {
|
||||||
export interface GlobalComponents {
|
export interface GlobalComponents {
|
||||||
AppPage: typeof import('./../src/components/AppPage.vue')['default']
|
AppPage: typeof import('./../src/components/AppPage.vue')['default']
|
||||||
Container: typeof import('@/components/AppPage.vue')['default']
|
|
||||||
ElSvgIcon: typeof import('./../src/components/ElSvgIcon.vue')['default']
|
ElSvgIcon: typeof import('./../src/components/ElSvgIcon.vue')['default']
|
||||||
IconSelector: typeof import('./../src/components/IconSelector.vue')['default']
|
IconSelector: typeof import('./../src/components/IconSelector.vue')['default']
|
||||||
MenuIcon: typeof import('./../src/components/MenuIcon.vue')['default']
|
MenuIcon: typeof import('./../src/components/MenuIcon.vue')['default']
|
||||||
|
|
4
typings/env.d.ts
vendored
4
typings/env.d.ts
vendored
|
@ -9,5 +9,9 @@ declare global {
|
||||||
interface ImportMeta {
|
interface ImportMeta {
|
||||||
readonly env: ImportMetaEnv
|
readonly env: ImportMetaEnv
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Window {
|
||||||
|
readonly NGINX_BASE_URL: string | undefined
|
||||||
|
}
|
||||||
}
|
}
|
||||||
export {}
|
export {}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { resolve } from 'path'
|
import { resolve } from 'path'
|
||||||
import { ConfigEnv, defineConfig, loadEnv, ServerOptions, UserConfig } from 'vite'
|
import { ConfigEnv, defineConfig, loadEnv, ServerOptions, UserConfig } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue, { parseVueRequest } from '@vitejs/plugin-vue'
|
||||||
import vueJsx from '@vitejs/plugin-vue-jsx'
|
import vueJsx from '@vitejs/plugin-vue-jsx'
|
||||||
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
|
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
|
||||||
import { ViteMockOptions, viteMockServe } from 'vite-plugin-mock'
|
import { ViteMockOptions, viteMockServe } from 'vite-plugin-mock'
|
||||||
|
@ -94,22 +94,42 @@ export default defineConfig(({ command, mode }: ConfigEnv): UserConfig => {
|
||||||
}),
|
}),
|
||||||
|
|
||||||
vitePluginSetupExtend({ inject: { title: setting.title } }),
|
vitePluginSetupExtend({ inject: { title: setting.title } }),
|
||||||
vitePluginVueSetupExtend()
|
vitePluginVueSetupExtend(),
|
||||||
//依赖分析插件
|
//依赖分析插件
|
||||||
// visualizer({
|
// visualizer({
|
||||||
// open: true,
|
// open: true,
|
||||||
// gzipSize: true,
|
// gzipSize: true,
|
||||||
// brotliSize: true
|
// brotliSize: true
|
||||||
// })
|
// })
|
||||||
|
|
||||||
|
{
|
||||||
|
name: 'index-transform',
|
||||||
|
transform(code, id) {
|
||||||
|
const { filename } = parseVueRequest(id)
|
||||||
|
if (!filename.includes('vite/preload-helper')) return code
|
||||||
|
const fn = 'export const __vitePreload'
|
||||||
|
const index = code.indexOf(fn)
|
||||||
|
const head = code
|
||||||
|
.slice(0, index)
|
||||||
|
.split(';')
|
||||||
|
.map((it) => `${it};`)
|
||||||
|
const preload = code.slice(index)
|
||||||
|
const assetsURLIndex = head.findIndex((it) => it.startsWith('const assetsURL'))
|
||||||
|
let assetsURL = head[assetsURLIndex]
|
||||||
|
assetsURL = assetsURL.replace(' return ', " return (window.NGINX_BASE_URL || '') + ")
|
||||||
|
head[assetsURLIndex] = assetsURL
|
||||||
|
return [...head, preload].join('\n')
|
||||||
|
}
|
||||||
|
}
|
||||||
],
|
],
|
||||||
build: {
|
build: {
|
||||||
chunkSizeWarningLimit: 10000, //消除触发警告的 chunk, 默认500k
|
chunkSizeWarningLimit: 10000, //消除触发警告的 chunk, 默认500k
|
||||||
assetsDir: 'static/assets',
|
assetsDir: 'static/assets',
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
output: {
|
output: {
|
||||||
chunkFileNames: 'static/js/[name]-[hash].js',
|
chunkFileNames: 'static/js/chunk-[hash].js',
|
||||||
entryFileNames: 'static/js/[name]-[hash].js',
|
entryFileNames: 'static/js/index.js',
|
||||||
assetFileNames: 'static/[ext]/[name]-[hash].[ext]'
|
assetFileNames: 'static/[ext]/[hash].[ext]'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
Loading…
Reference in New Issue
Block a user