ntrip通信服务平台

This commit is contained in:
2026-05-12 17:43:37 +08:00
commit 769103ab12
90 changed files with 6024 additions and 0 deletions

24
.gitignore vendored Normal file
View File

@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

3
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,3 @@
{
"recommendations": ["Vue.volar"]
}

5
README.md Normal file
View File

@@ -0,0 +1,5 @@
# Vue 3 + Vite
This template should help get you started developing with Vue 3 in Vite. The template uses Vue 3 `<script setup>` SFCs, check out the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
Learn more about IDE Support for Vue in the [Vue Docs Scaling up Guide](https://vuejs.org/guide/scaling-up/tooling.html#ide-support).

52
index.html Normal file
View File

@@ -0,0 +1,52 @@
<!DOCTYPE html>
<html lang="en" class="w-h-full">
<head>
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>ntrip通信服务平台</title>
<style>
/* 当屏幕宽度大于或等于3000px时 */
@media (min-width: 3000px) {
html {
font-size: 28px;
}
}
/* 当屏幕宽度在1920px到2999px之间时 */
@media (min-width: 1920px) and (max-width: 2999px) {
html {
font-size: 20px;
}
}
/* 当屏幕宽度在750px到1919px之间时 */
@media (min-width: 750px) and (max-width: 1919px) {
html {
font-size: 14px;
}
}
/* 当屏幕宽度在600px到749px之间时 */
@media (min-width: 600px) and (max-width: 749px) {
html {
font-size: 14px;
}
}
/* 当屏幕宽度小于600px时 */
@media (max-width: 599px) {
html {
font-size: 12px;
}
}
</style>
</head>
<body class="w-h-full">
<div id="app" class="w-h-full text-white"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>

8
jsconfig.json Normal file
View File

@@ -0,0 +1,8 @@
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
},
"exclude": ["node_modules", "dist"]
}

38
package.json Normal file
View File

@@ -0,0 +1,38 @@
{
"name": "ntrip-server",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite --port=7025",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "^2.3.1",
"axios": "^1.6.8",
"core-js": "^3.37.0",
"crypto-js": "^4.2.0",
"element-plus": "^2.7.8",
"fingerprintjs2": "^2.1.4",
"gbk.js": "^0.3.0",
"moment": "^2.30.1",
"pinia": "^2.1.7",
"pinia-plugin-persist": "^1.0.0",
"terser": "^5.31.5",
"vue": "^3.4.21",
"vue-i18n": "^9.13.1",
"vue-router": "^4.3.0",
"zipson": "^0.2.12"
},
"devDependencies": {
"@vitejs/plugin-legacy": "^5.4.1",
"@vitejs/plugin-vue": "^5.0.4",
"autoprefixer": "^10.4.19",
"postcss": "^8.4.40",
"tailwindcss": "^3.4.7",
"unplugin-auto-import": "^0.17.5",
"unplugin-vue-components": "^0.27.0",
"vite": "^5.4.21"
}
}

6
postcss.config.js Normal file
View File

@@ -0,0 +1,6 @@
export default {
plugins: {
tailwindcss: {},
autoprefixer: {}
}
}

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

11
src/App.vue Normal file
View File

@@ -0,0 +1,11 @@
<script setup>
import { RouterView } from 'vue-router'
</script>
<template>
<RouterView />
</template>
<style scoped>
</style>

118
src/api/index.js Normal file
View File

@@ -0,0 +1,118 @@
import axios from "axios";
import $url from "./url";
import router from "../router";
import { encrypt } from "../utils/aes";
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
if (config.headers) {
config.headers.uid = localStorage.data;
if (localStorage.access_token && localStorage.access_token !== "" && localStorage.access_token !== null && localStorage.access_token !== undefined && localStorage.access_token !== NaN) {
config.headers.authorization = localStorage.access_token;
}
}
return config;
}, function (error) {
// 对请求错误做些什么
return Promise.reject(error);
});
// 转到登录页面
const toLogin = () => {
localStorage.clear();
const data = { origin_url: window.location.href };
axios.post($url.toLogin, data).then((res) => {
if (res.data.code === 200) {
window.location.href = res.data.redirect;
} else {
router.push({ path: '/404page' });
}
});
};
// 添加响应拦截器
let result = true;
axios.interceptors.response.use(function (response) {
// 2xx 范围内的状态码都会触发该函数。
// 对响应数据做点什么
if (response.data.code === 200) {
if ("access_token" in response.data) {
localStorage.setItem("access_token", response.data.access_token);
}
if ("refresh_token" in response.data) {
localStorage.setItem("refresh_token", response.data.refresh_token);
}
if ("data" in response.data) {
localStorage.setItem("data", response.data.data);
}
}
// if (response.data.code === 302) {
// localStorage.clear();
// //转到主页
// router.push({ path: '/main' });
// }
if(response.data.code === 401 && result) {
result = false;
setTimeout(() => {
result = true;
}, 1000);
let getCode = window.location.href.indexOf('?');
if (getCode != '-1') {
let code = window.location.href.substring(getCode + 1);
const data = {
code: code,
type: encrypt('code')
};
axios.post($url.getToken, data).then((res) => {
if (res.data.code === 200) {
localStorage.setItem("data", res.data.data);
localStorage.setItem("access_token", res.data.access_token);
localStorage.setItem("refresh_token", res.data.refresh_token);
window.location.href = res.data.redirect;
} else {
toLogin();
}
});
} else if (localStorage.refresh_token && localStorage.refresh_token !== "" && localStorage.refresh_token !== null && localStorage.refresh_token !== undefined && localStorage.refresh_token !== NaN) {
const data = {
code: localStorage.refresh_token,
type: localStorage.data
};
axios.post($url.getToken, data).then((res) => {
if (res.data.code === 200) {
localStorage.setItem("data", res.data.data);
localStorage.setItem("access_token", res.data.access_token);
localStorage.setItem("refresh_token", res.data.refresh_token);
window.location.reload();
} else {
toLogin();
}
});
} else {
toLogin();
}
}
return response;
}, function (error) {
// 超出 2xx 范围的状态码都会触发该函数。
// 对响应错误做点什么
return Promise.reject(error);
});
export default {
get(url, query = {}) {
return axios.get(url, {
params: query
});
},
post(url, body) {
return axios.post(url, body);
},
put(url, body) {
return axios.put(url, body);
},
del(url) {
return axios.delete(url);
}
};

191
src/api/ntrip.js Normal file
View File

@@ -0,0 +1,191 @@
import axios from 'axios';
export default class NTRIPCaster {
constructor(baseURL) {
this.baseURL = baseURL;
this.client = axios.create({
baseURL,
// 长连接超时时间
timeout: 30000,
// 重要:使用流式响应接收实时数据
responseType: 'stream',
headers: {
'Ntrip-Version': 'Ntrip/2.0',
// 'User-Agent': 'Virtual-NTRIP-Client/1.0'
},
});
this.currentStream = null;
this.sizeStrem = null;
}
/**
* 连接NTRIP Caster获取差分数据流
* @param {string} mountpoint - 挂载点
* @param {Object} options - 额外选项
*/
async connect(mountpoint, options = {}) {
try {
const response = await fetch(this.baseURL + `/${mountpoint}`, {
headers: {
'uid': localStorage.data,
'Authorization': localStorage.access_token,
'Ntrip-Version': 'Ntrip/2.0',
'Client-Type': 'Virtual-NTRIP-Client/1.0',
'User-Agent': 'Virtual-NTRIP-Client/1.0',
'Connection': 'keep-alive',
'Accept': '*/*'
}
});
if (!response.ok) {
throw new Error(response.status);
}
const reader = response.body.getReader();
this.currentStream = reader;
// 返回可读流
return {
stream: reader,
headers: response.headers,
status: response.status
};
} catch (error) {
throw this._handleError(error);
}
}
disconnect() {
if (this.currentStream) {
this.currentStream.cancel();
this.currentStream = null;
}
console.log('NTRIP连接已断开');
}
async _delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
/**
* 获取Caster的源列表(Source Table)
*/
async getSourceTable() {
try {
const response = await fetch(this.baseURL + `/`, {
headers: {
'Ntrip-Version': 'Ntrip/2.0'
}
});
const reader = response.body.getReader();
this.sizeStrem = reader;
const { value } = await reader.read();
const str = new TextDecoder().decode(value);
return this._parseSourceTable(str);
} catch (error) {
throw this._handleError(error);
}
}
async getSize(mountpoint) {
try {
const response = await fetch(this.baseURL + `/${mountpoint}`, {
headers: {
'Ntrip-Version': 'Ntrip/2.0',
'Request-Type': 'SIZE',
'uid': localStorage.data
}
});
const reader = response.body.getReader();
this.sizeStrem = reader;
return {
stream: reader
};
} catch (error) {
throw this._handleError(error);
}
}
sizeDisconnect() {
if (this.sizeStrem) {
this.sizeStrem.cancel();
this.sizeStrem = null;
}
console.log('已挂载客户端数量监测连接已断开');
}
/**
* 解析源表
*/
_parseSourceTable(sourceTable) {
const lines = sourceTable.split('\r\n');
const sources = [];
for (const line of lines) {
if (line.startsWith('STR;')) {
const parts = line.split(';');
if (parts.length >= 10) {
sources.push(`format: ${ parts[1] } | mountpoint: ${ parts[2] } | formatDetails: ${ parts[3] } | carrier: ${ parts[4] } | navSystem: ${ parts[5] } | network: ${ parts[6] } | country: ${ parts[7] }, | latitude: ${ parts[8] } | longitude: ${ parts[9] } | nmea: ${ parts[10] } | sourceID: ${ parts[11] }`);
// sources.push({
// // 格式
// format: parts[1],
// // 挂载点名称
// mountpoint: parts[2],
// // 格式详情
// formatDetails: parts[3],
// // 载体
// carrier: parts[4],
// // 导航系统
// navSystem: parts[5],
// // 网络
// network: parts[6],
// // 国家
// country: parts[7],
// // 纬度
// latitude: parts[8],
// // 经度
// longitude: parts[9],
// // NMEA
// nmea: parts[10],
// // 源ID
// sourceID: parts[11],
// // ... 其他字段
// // 解决方案
// // Soution: '',
// // 发生器
// // Generator: '',
// // 压缩加密
// // ComprEncryp: '',
// // 认证
// // Auth: '',
// // 费用
// // Fee: '',
// // 比特率
// // Bitrate: '',
// });
}
}
}
return sources;
}
_handleError(error) {
if (error.response) {
// 服务器响应了非2xx状态码
const status = error.response.status;
switch (status) {
case 401:
return new Error('认证失败');
case 404:
return new Error('挂载点不存在');
case 403:
return new Error('无访问权限');
case 400:
return new Error('错误的请求');
default:
return new Error(`NTRIP错误: ${status}`);
}
} else if (error.request) {
return new Error('网络连接失败');
} else {
return error;
}
}
}

48
src/api/url.js Normal file
View File

@@ -0,0 +1,48 @@
export default {
// 用于确认哪个网页在进行登录
toLogin: "/authentication/toLogin",
// 退出登录
unLogin: "/authentication/unLogin",
// 用于获取token
getToken: "/authentication/getToken",
// 用于验证token
verify: "/authentication/verify",
// 获取用户基本信息
getUser: "/authentication/getUser",
// 修改用户基本信息
editUser: "/authentication/editUser",
// B端单位管理员编辑用户基本信息
editUserManager: "/authentication/editUserManager",
// 修改密码
changePassword: "/authentication/changePassword",
// 重置密码
resetPassword: "/authentication/resetPassword",
// 保存挂载点
saveMountpoint: "/ntripService/saveMountpoint",
// 修改挂载点密码
editPassword: "/ntripService/editPassword",
// 修改挂载点名称
editMountpoint: "/ntripService/editMountpoint",
// 删除挂载点
delMountpoint: "/ntripService/delMountpoint",
// 获取挂载点
getMountpoint: "/ntripService/getMountpoint",
// 搜索挂载点
searchMountpoint: "/ntripService/searchMountpoint",
// 全局寻获挂载点
findMountpoint: "/ntripService/findMountpoint",
// 查看ntrip-clinet密码
viewPassword: "/ntripService/viewPassword",
// 获取挂载点记录
getRecord: "/ntripService/getRecord",
// 存储消息记录
saveMsg: "/ntripService/saveMsg",
// 设置ntrip-clinet密码
setPassword: "/ntripService/setPassword",
// 获取历史消息
getHistoryMsg: "/ntripService/getHistoryMsg",
// 删除历史消息
delHistoryMsg: "/ntripService/delHistoryMsg",
// 获取单位用户
bsideManager: "/ntripService/bsideManager"
}

BIN
src/assets/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1006 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

BIN
src/assets/img/msg/msg.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

BIN
src/assets/img/xor/xor.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

BIN
src/assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

1
src/assets/logo.svg Normal file
View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

After

Width:  |  Height:  |  Size: 276 B

9
src/assets/main.css Normal file
View File

@@ -0,0 +1,9 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.w-h-full {
@apply w-full h-full m-0 p-0 overflow-hidden;
}
}

BIN
src/assets/qrcode.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -0,0 +1,115 @@
<template>
<div>
<div class="m-2 font-bold">
<el-text class="mx-1 font-bold"> 校验数据 : </el-text>
<el-input
v-model="check"
:style="style"
placeholder="请输入将要校验的数据"
clearable
@input="handleInput"
/>
</div>
<div class="m-2 font-bold">
<el-text> 校验结果(十六进制): {{ resultHex }} </el-text>
</div>
<div class="m-2 font-bold">
<el-text> 校验结果(十进制): {{ resultDec }} </el-text>
</div>
<div class="m-2 font-bold">
<el-text> 校验结果(二进制): {{ resultBin }} </el-text>
</div>
<div class="flex justify-center">
<el-button style="font-weight: bold; width: 25%;" type="primary" size="small" @click="calculation">
校验
</el-button>
<el-button style="font-weight: bold; width: 25%;" type="primary" size="small" @click="clear">
清除
</el-button>
</div>
</div>
</template>
<script setup>
import { ref } from "vue";
import { ElMessage } from "element-plus";
const style = ref("width: 80%;");
const check = ref("");
const resultHex = ref("");
const resultDec = ref("");
const resultBin = ref("");
const clear = () => {
style.value = "width: 80%";
check.value = "";
resultHex.value = "";
resultBin.value = "";
resultDec.value = "";
};
// 判断字符是否为简体汉字
const isChinese = (s) => {
return /[\u4e00-\u9fa5]/.test(s);
};
const inputFilter = (inputData) => {
let infilter = [];
inputData.forEach((item) => {
if (!isChinese(item)) {
infilter.push(item);
}
});
return infilter.join("");
};
const handleInput = () => {
check.value = inputFilter(check.value.split(""));
};
const checkit = (acheck) => {
let checkArr = acheck.split("");
let resultBox;
checkArr.forEach((element) => {
if (resultBox == undefined) {
resultBox = element.charCodeAt();
} else {
resultBox = resultBox ^ element.charCodeAt();
}
});
let toOverHex, toOverDec, toOverBin;
toOverHex = resultBox.toString(16).toUpperCase();
if (resultBox.toString(16).toUpperCase().length < 2) {
toOverHex = "0" + toOverHex;
}
toOverDec = resultBox.toString().toUpperCase();
if (resultBox.toString().toUpperCase().length < 2) {
toOverDec = "0" + toOverDec;
}
toOverBin = resultBox.toString(2);
if (resultBox.toString(2).length < 8) {
for (let i = 0; i < 8 - resultBox.toString(2).length; i++) {
toOverBin = "0" + toOverBin;
}
}
return { hex: toOverHex, dec: toOverDec, bin: toOverBin };
};
const calculation = () => {
resultHex.value = "";
resultBin.value = "";
resultDec.value = "";
if (check.value == "") {
style.value = "width: 80%; border-width: 2px; border-color: rgb(239 68 68);";
ElMessage.error("校验数据不能为空");
} else {
const { hex, dec, bin } = checkit(check.value);
style.value = "width: 80%;";
resultHex.value = hex;
resultDec.value = dec;
resultBin.value = bin;
}
};
</script>
<style scoped></style>

View File

@@ -0,0 +1,453 @@
<template>
<!-- 表格 -->
<el-card>
<div class="flex justify-center flex-wrap">
<TabelTools @showTools="showTools" :dialogObj="bsideManagerActive" />
<el-table
ref="userTableRef"
:data="tableData"
row-key="account_name"
style="width: 100%"
max-height="32rem"
scrollbar-always-on
@selection-change="handleSelectionChange"
>
<el-table-column
v-if="dialogOperationVisible"
fixed="left"
type="selection"
width="40"
column-key="selection"
/>
<TabelSelect
v-if="dialogOperationVisible"
ref="EditSelectRef"
:holderText="editSelectHold"
singleColor="primary"
multipleColor="warning"
allColor="danger"
singleSize="small"
multipleSize="small"
allSize="small"
:singlePlain="true"
:multiplePlain="false"
:allPlain="false"
operateType="edit"
@showComfirmDialog="showComfirmDialog"
/>
<TabelSelect
v-if="dialogOperationVisible"
ref="DefaultSelectRef"
:holderText="defaultSelectHold"
singleColor="danger"
multipleColor="warning"
allColor="danger"
singleSize="small"
multipleSize="small"
allSize="small"
:singlePlain="true"
:multiplePlain="false"
:allPlain="false"
operateType="default"
@showComfirmDialog="showComfirmDialog"
/>
<TabelSelect
v-if="dialogOperationVisible"
ref="ResetSelectRef"
:holderText="resetSelectHold"
singleColor="danger"
multipleColor="warning"
allColor="danger"
singleSize="small"
multipleSize="small"
allSize="small"
:singlePlain="true"
:multiplePlain="false"
:allPlain="false"
operateType="reset"
@showComfirmDialog="showComfirmDialog"
/>
<TabelSelect
v-if="dialogOperationVisible"
ref="unBindSelectRef"
:holderText="bindSelectHold"
singleColor="danger"
multipleColor="warning"
allColor="danger"
singleSize="small"
multipleSize="small"
allSize="small"
:singlePlain="true"
:multiplePlain="false"
:allPlain="false"
operateType="unbind"
@showComfirmDialog="showComfirmDialog"
/>
<el-table-column min-width="100" column-key="account_name">
<template #header>
<SearchKey
v-if="dialogSearchVisible"
ref="SearchAccountKeyRef"
holderText="输入单位用户名以搜索相关数据"
@search="bsideManager"
/>
<div>
<el-text class="mx-1"> 单位用户名 </el-text>
</div>
</template>
<template #default="scope">
<div>{{ scope.row.account_name }}</div>
</template>
</el-table-column>
<el-table-column min-width="80" column-key="user_name">
<template #header>
<SearchKey
v-if="dialogSearchVisible"
ref="SearchUserKeyRef"
holderText="输入用户姓名以搜索相关数据"
@search="bsideManager"
/>
<div>
<el-text class="mx-1"> 用户姓名 </el-text>
</div>
</template>
<template #default="scope">
<div>{{ scope.row.user_name }}</div>
</template>
</el-table-column>
<el-table-column min-width="120" column-key="phone">
<template #header>
<SearchKey
v-if="dialogSearchVisible"
ref="SearchPhoneKeyRef"
holderText="输入用户手机号码以搜索相关数据"
@search="bsideManager"
/>
<div>
<el-text class="mx-1"> 用户手机号码 </el-text>
</div>
</template>
<template #default="scope">
<div>{{ scope.row.phone }}</div>
</template>
</el-table-column>
</el-table>
<!-- 表格 -->
<Pagination
ref="PaginationRef"
:active="bsideManagerActive"
@sizeChangeBack="changePage"
@search="bsideManager"
/>
</div>
</el-card>
<EditBsideUser
ref="editBsideUserRef"
operateLabel="编辑信息"
@requestOperate="requestOperate"
/>
<PasswordOauth
ref="defaultPasswordOauthRef"
operateLabel="重置账号"
@requestOperate="requestOperate"
/>
<PasswordOauth
ref="resetPasswordOauthRef"
operateLabel="重置密码"
@requestOperate="requestOperate"
/>
<PasswordOauth
ref="unBindPasswordOauthRef"
operateLabel="解绑用户"
@requestOperate="requestOperate"
/>
</template>
<script setup>
import { ref, reactive, watch, onMounted, nextTick } from "vue";
import { ElMessage } from "element-plus";
import TabelTools from "../operate/tabelTools.vue";
import TabelSelect from "../operate/tabelSelect.vue";
import SearchKey from "../search/searchKey.vue";
import Pagination from "../search/pagination.vue";
import EditBsideUser from "../operate/editBsideUser.vue";
import PasswordOauth from "../operate/passwordOauth.vue";
import bsideManagerActiveStore from "../../stores/bsideManager";
import $http from "../../api/index";
import $url from "../../api/url";
import { encrypt } from "../../utils/aes";
import { judgment } from "../../utils/judgment.js";
import { hasNullOrEmptyPropertyWithTypeof } from "../../utils/hasNullOrEmptyPropertyWithTypeof.js";
const bsideManagerActive = bsideManagerActiveStore();
const editSelectHold = {
single: "编辑",
label: "编辑信息"
};
const EditSelectRef = ref(null);
const defaultSelectHold = {
single: "重置",
multiple: "重置选中",
all: "重置所有",
label: "重置账号"
};
const DefaultSelectRef = ref(null);
const resetSelectHold = {
single: "重置",
multiple: "重置选中",
all: "重置所有",
label: "重置密码"
};
const ResetSelectRef = ref(null);
const bindSelectHold = {
single: "解绑",
multiple: "解绑选中",
all: "解绑所有",
label: "解绑用户"
};
const unBindSelectRef = ref(null);
const SearchAccountKeyRef = ref(null);
const SearchUserKeyRef = ref(null);
const SearchPhoneKeyRef = ref(null);
const PaginationRef = ref(null);
const editBsideUserRef = ref(null);
const defaultPasswordOauthRef = ref(null);
const resetPasswordOauthRef = ref(null);
const unBindPasswordOauthRef = ref(null);
const dialogSearchVisible = ref(false);
const dialogSortVisible = ref(false);
const dialogOperationVisible = ref(false);
const userTableRef = ref();
const offsetOff = ref(true);
const pageCount = ref(0);
const searchOptions = reactive({
account_name: "",
user_name: "",
phone: ""
});
const tableData = ref([]);
let operateData = reactive({});
let operateDataClone = reactive({});
const operate = ref("unbind");
let singleData = reactive({});
const showTools = (operateType) => {
switch (operateType) {
case "search":
dialogSearchVisible.value = !dialogSearchVisible.value;
bsideManagerActive.dialogSearchVisible = dialogSearchVisible.value;
break;
case "sort":
dialogSortVisible.value = !dialogSortVisible.value;
bsideManagerActive.dialogSortVisible = dialogSortVisible.value;
break;
case "operation":
dialogOperationVisible.value = !dialogOperationVisible.value;
bsideManagerActive.dialogOperationVisible = dialogOperationVisible.value;
break;
default:
break;
}
};
const bsideManager = () => {
judgment(searchOptions, "account_name", SearchAccountKeyRef.value, "searchMsg");
judgment(searchOptions, "user_name", SearchUserKeyRef.value, "searchMsg");
judgment(searchOptions, "phone", SearchPhoneKeyRef.value, "searchMsg");
if (hasNullOrEmptyPropertyWithTypeof(searchOptions)) {
if (PaginationRef.value.offset !== 1 && offsetOff.value) {
PaginationRef.value.handleCurrentChange(1);
offsetOff.value = false;
}
} else {
offsetOff.value = true;
}
const data = {
uid: localStorage.data,
offset: PaginationRef.value.offset,
limit: PaginationRef.value.limit,
searchOptions: searchOptions,
};
$http.post($url.bsideManager, data).then((res) => {
if (res.data.code === 200) {
tableData.value = res.data.bsideUsers;
PaginationRef.value.dataCount = res.data.count;
pageCount.value = Math.ceil(
PaginationRef.value.dataCount / PaginationRef.value.limit
);
bsideManagerActive.dataCount = PaginationRef.value.dataCount;
bsideManagerActive.pageCount = pageCount.value;
if (!operateData[PaginationRef.value.offset]) return;
operateData[PaginationRef.value.offset].forEach((element) => {
tableData.value.forEach((item) => {
if (item.account_name == element.account_name) {
nextTick(() => {
userTableRef.value.toggleRowSelection(item, true);
});
}
});
});
} else {
tableData.value = [];
// ElMessage.error("未查找到相关数据!");
}
});
};
const changePage = (val) => {
pageCount.value = Math.ceil(PaginationRef.value.dataCount / val);
operateData = {};
for (let i = 0; i < pageCount.value; i++) {
operateData[i + 1] = [];
for (const key in operateDataClone) {
operateDataClone[key].forEach((element) => {
// 判断数据在第几页
if (Math.ceil(element.location / PaginationRef.value.limit) == i + 1) {
operateData[i + 1].push(element);
}
});
}
}
};
const handleSelectionChange = (val) => {
operateData[PaginationRef.value.offset] = val;
tableData.value.forEach((item, index) => {
operateData[PaginationRef.value.offset].forEach((element) => {
if (item.account_name == element.account_name) {
// 数据在表格中的位置
element.location =
(PaginationRef.value.offset - 1) * PaginationRef.value.limit + (index + 1);
}
});
});
operateDataClone = JSON.parse(JSON.stringify(operateData));
};
const showComfirmDialog = (singleMsg) => {
singleData = singleMsg.data;
operate.value = singleMsg.operateType;
if (singleMsg.operateType == "edit") {
const { account_name, user_name, phone } = singleMsg.data;
editBsideUserRef.value.ruleForm.account_name = account_name;
editBsideUserRef.value.ruleForm.user_name = user_name;
editBsideUserRef.value.ruleForm.phone = phone;
editBsideUserRef.value.originMsg = { ...singleMsg.data };
editBsideUserRef.value.operateDialogVisible = true;
}
if (singleMsg.operateType == "default") {
defaultPasswordOauthRef.value.operateDialogVisible = true;
}
if (singleMsg.operateType == "reset") {
resetPasswordOauthRef.value.operateDialogVisible = true;
}
if (singleMsg.operateType == "unbind") {
unBindPasswordOauthRef.value.operateDialogVisible = true;
}
};
const requestOperate = () => {
let operateType, url, model, defaultPassword, password;
if (operate.value == "edit") {
operateType = "编辑信息";
url = $url.editUserManager;
model = EditSelectRef.value.model;
} else if (operate.value == "default") {
operateType = "重置账号";
url = $url.resetPassword;
model = DefaultSelectRef.value.model;
defaultPassword = defaultPasswordOauthRef.value.ruleForm.defaultPassword;
password = defaultPasswordOauthRef.value.ruleForm.password;
} else if (operate.value == "reset") {
operateType = "重置密码";
url = $url.resetPassword;
model = ResetSelectRef.value.model;
defaultPassword = resetPasswordOauthRef.value.ruleForm.defaultPassword;
password = resetPasswordOauthRef.value.ruleForm.password;
} else if (operate.value == "unbind") {
operateType = "解绑用户";
url = $url.unBindManager;
model = unBindSelectRef.value.model;
password = unBindPasswordOauthRef.value.ruleForm.password;
}
const data = {
uid: localStorage.data,
model: encrypt(model),
};
if(operate.value == "edit") {
data.editMsg = { ...editBsideUserRef.value.ruleForm };
data.userMsg = editBsideUserRef.value.originMsg;
} else {
data.password = encrypt(password);
data.userMsg = model == "single" ? singleData : model == "multiple" ? operateDataClone : "";
}
if(operate.value == "default" || operate.value == "reset") {
data.defaultPassword = encrypt(defaultPassword);
data.resetType = operate.value;
}
$http.post(url, data).then((res) => {
if (res.data.code === 200) {
userTableRef.value.clearSelection();
operateData = {};
operateDataClone = {};
ElMessage({
message: `${operateType}成功!`,
type: "success",
});
bsideManager();
} else {
ElMessage.error(`${operateType}失败!`);
}
});
};
defineExpose({ bsideManager });
watch(searchOptions, (value) => {
bsideManagerActive.searchOptions = value;
});
onMounted(() => {
pageCount.value = bsideManagerActive.pageCount;
dialogSearchVisible.value = bsideManagerActive.dialogSearchVisible;
dialogSortVisible.value = bsideManagerActive.dialogSortVisible;
dialogOperationVisible.value = bsideManagerActive.dialogOperationVisible;
if (dialogSearchVisible.value) {
searchOptions.account_name = bsideManagerActive.searchOptions.account_name;
searchOptions.user_name = bsideManagerActive.searchOptions.user_name;
searchOptions.phone = bsideManagerActive.searchOptions.phone;
}
if (dialogSortVisible.value) {
}
if (dialogOperationVisible.value) {
}
setTimeout(() => {
bsideManager();
}, 200);
setTimeout(() => {
if (dialogSearchVisible.value) {
SearchAccountKeyRef.value.searchMsg = bsideManagerActive.searchOptions.account_name;
SearchUserKeyRef.value.searchMsg = bsideManagerActive.searchOptions.user_name;
SearchPhoneKeyRef.value.searchMsg = bsideManagerActive.searchOptions.phone;
}
if (dialogSortVisible.value) {
}
if (dialogOperationVisible.value) {
}
}, 0);
});
</script>
<style scoped></style>

View File

@@ -0,0 +1,406 @@
<template>
<!-- 表格 -->
<el-card>
<div class="flex justify-center flex-wrap">
<TabelTools @showTools="showTools" :dialogObj="historyMsgActive" />
<el-table
ref="userTableRef"
:data="tableData"
style="width: 100%"
max-height="32rem"
table-layout="auto"
scrollbar-always-on
@selection-change="handleSelectionChange"
>
<el-table-column
v-if="dialogOperationVisible"
fixed="left"
type="selection"
width="40"
column-key="selection"
/>
<TabelSelect
v-if="dialogOperationVisible"
ref="DelSelectRef"
:holderText="delSelectHold"
singleColor="danger"
multipleColor="warning"
allColor="danger"
singleSize="small"
multipleSize="small"
allSize="small"
:singlePlain="true"
:multiplePlain="false"
:allPlain="false"
operateType="del"
@showComfirmDialog="showComfirmDialog"
/>
<el-table-column min-width="100" column-key="mountpoint">
<template #header>
<div>
<SearchKey
v-if="dialogSearchVisible"
ref="SearchMountpointKeyRef"
holderText="输入挂载点以搜索相关数据"
@search="historyMsg"
/>
<div>
<el-text class="mx-1"> 挂载点 </el-text>
<SortSearch
v-if="dialogSortVisible"
ref="SortMountpointRef"
activeText="顺序"
inactiveText="倒序"
@search="historyMsg"
/>
</div>
</div>
</template>
<template #default="scope">
<div
style="color: rgb(16, 184, 130);"
class="flex justify-start items-center text-base"
>
{{ scope.row.mountpoint }}
</div>
</template>
</el-table-column>
<el-table-column min-width="200" width="720" column-key="original_msg">
<template #header>
<SearchKey
v-if="dialogSearchVisible"
ref="SearchDataKeyRef"
holderText="输入关键字以搜索相关数据"
@search="historyMsg"
/>
<div>
<el-text class="mx-1"> 接收内容 </el-text>
</div>
</template>
<template #default="scope">
<div class="flex justify-between items-center text-base">
<el-text
truncated
style="color: rgb(16, 184, 130);font-size: 1rem;"
>
{{ scope.row.original_msg }}
</el-text>
<el-button
style="font-size: 1rem; font-weight: bold"
size="small"
type="primary"
plain
@click="showAll(scope.row.original_msg)"
>
展示全部内容
</el-button>
</div>
</template>
</el-table-column>
<el-table-column min-width="170" column-key="created_at">
<template #header>
<DateTimePicker
v-if="dialogSearchVisible"
ref="DateTimePickerRef"
DateTimePicker="historyMsgDateTimePicker"
@search="historyMsg"
/>
<div>
<el-text class="mx-1"> 接收时间 </el-text>
<SortSearch
v-if="dialogSortVisible"
ref="SortDateTimeRef"
activeText="顺序"
inactiveText="倒序"
@search="historyMsg"
/>
</div>
</template>
<template #default="scope">
<div
style="color: rgb(16, 184, 130);"
class="flex justify-start items-center text-base"
>
{{ scope.row.created_at }}
</div>
</template>
</el-table-column>
</el-table>
<!-- 表格 -->
<Pagination
ref="PaginationRef"
:active="historyMsgActive"
@sizeChangeBack="changePage"
@search="historyMsg"
/>
</div>
</el-card>
<DataShow ref="DataShowRef" />
<PasswordOauth
ref="PasswordOauthRef"
operateLabel="删除历史消息"
@requestOperate="requestOperate"
/>
</template>
<script setup>
import { ref, reactive, watch, onMounted, nextTick } from "vue";
import { ElMessage } from "element-plus";
import TabelTools from "../operate/tabelTools.vue";
import TabelSelect from "../operate/tabelSelect.vue";
import SearchKey from "../search/searchKey.vue";
import SortSearch from "../search/sortSearch.vue";
import DateTimePicker from "../search/dateTimePicker.vue";
import Pagination from "../search/pagination.vue";
import DataShow from "../operate/dataShow.vue";
import PasswordOauth from "../operate/passwordOauth.vue";
import historyMsgActiveStore from "../../stores/historyMsg";
import moment from "moment";
import $http from "../../api/index";
import $url from "../../api/url";
import { encrypt } from "../../utils/aes";
import { judgment } from "../../utils/judgment.js";
import { hasNullOrEmptyPropertyWithTypeof } from "../../utils/hasNullOrEmptyPropertyWithTypeof.js";
const emit = defineEmits(["getRole"]);
const historyMsgActive = historyMsgActiveStore();
const manager = ref(false);
const delSelectHold = {
single: "删除",
multiple: "删除选中",
all: "删除所有",
label: "删除历史消息",
};
const DelSelectRef = ref(null);
const SearchMountpointKeyRef = ref(null);
const SortMountpointRef = ref(null);
const SortDateTimeRef = ref(null);
const SearchDataKeyRef = ref(null);
const DateTimePickerRef = ref(null);
const PaginationRef = ref(null);
const DataShowRef = ref(null);
const PasswordOauthRef = ref(null);
const dialogSearchVisible = ref(false);
const dialogSortVisible = ref(false);
const dialogOperationVisible = ref(false);
const userTableRef = ref();
const offsetOff = ref(true);
const pageCount = ref(0);
const searchOptions = reactive({
mountpoint: "",
original_msg: "",
created_at: "",
});
const sortOptions = reactive({
mountpoint: false,
created_at: false,
});
const tableData = ref([]);
let singleData = reactive({});
let delData = reactive({});
let delDataClone = reactive({});
const showAll = (msg) => {
DataShowRef.value.dialogMsg = msg;
DataShowRef.value.dialogVisible = true;
};
const showTools = (operateType) => {
switch (operateType) {
case "search":
dialogSearchVisible.value = !dialogSearchVisible.value;
historyMsgActive.dialogSearchVisible = dialogSearchVisible.value;
break;
case "sort":
dialogSortVisible.value = !dialogSortVisible.value;
historyMsgActive.dialogSortVisible = dialogSortVisible.value;
break;
case "operation":
emit("getRole");
if (manager.value) {
dialogOperationVisible.value = !dialogOperationVisible.value;
historyMsgActive.dialogOperationVisible = dialogOperationVisible.value;
}
break;
default:
break;
}
};
const historyMsg = () => {
judgment(searchOptions, "mountpoint", SearchMountpointKeyRef.value, "searchMsg");
judgment(searchOptions, "original_msg", SearchDataKeyRef.value, "searchMsg");
judgment(searchOptions, "created_at", DateTimePickerRef.value, "searchMsg");
judgment(sortOptions, "mountpoint", SortMountpointRef.value, "searchMsg");
judgment(sortOptions, "created_at", SortDateTimeRef.value, "searchMsg");
if (hasNullOrEmptyPropertyWithTypeof(searchOptions)) {
if (PaginationRef.value.offset !== 1 && offsetOff.value) {
PaginationRef.value.handleCurrentChange(1);
offsetOff.value = false;
}
} else {
offsetOff.value = true;
}
const data = {
uid: localStorage.data,
offset: PaginationRef.value.offset,
limit: PaginationRef.value.limit,
searchOptions: searchOptions,
sortOptions: sortOptions,
};
$http.post($url.getHistoryMsg, data).then((res) => {
if (res.data.code === 200) {
tableData.value = res.data.historyMsg;
tableData.value.forEach((item) => {
item.origin_time = item.created_at;
item.created_at = moment
.utc(item.created_at)
.add(8, "hours")
.format("YYYY-MM-DD-HH:mm:ss");
});
PaginationRef.value.dataCount = res.data.count;
historyMsgActive.dataCount = PaginationRef.value.dataCount;
pageCount.value = Math.ceil(
PaginationRef.value.dataCount / PaginationRef.value.limit
);
historyMsgActive.pageCount = pageCount.value;
if (!delData[PaginationRef.value.offset]) return;
delData[PaginationRef.value.offset].forEach((element) => {
tableData.value.forEach((item) => {
if (item.created_at == element.created_at) {
nextTick(() => {
userTableRef.value.toggleRowSelection(item, true);
});
}
});
});
} else {
tableData.value = [];
// ElMessage.error("未查找到相关数据!");
}
});
};
const changePage = (val) => {
pageCount.value = Math.ceil(PaginationRef.value.dataCount / val);
delData = {};
for (let i = 0; i < pageCount.value; i++) {
delData[i + 1] = [];
for (const key in delDataClone) {
delDataClone[key].forEach((element) => {
// 判断数据在第几页
if (Math.ceil(element.location / PaginationRef.value.limit) == i + 1) {
delData[i + 1].push(element);
}
});
}
}
};
const handleSelectionChange = (val) => {
delData[PaginationRef.value.offset] = val;
tableData.value.forEach((item, index) => {
delData[PaginationRef.value.offset].forEach((element) => {
if (item.created_at == element.created_at) {
// 数据在表格中的位置
element.location =
(PaginationRef.value.offset - 1) * PaginationRef.value.limit + (index + 1);
}
});
});
delDataClone = JSON.parse(JSON.stringify(delData));
};
const showComfirmDialog = (singleMsg) => {
singleData = singleMsg.data;
PasswordOauthRef.value.operateDialogVisible = true;
};
const requestOperate = () => {
const data = {
uid: localStorage.data,
model: encrypt(DelSelectRef.value.model),
password: encrypt(PasswordOauthRef.value.ruleForm.password),
userMsg:
DelSelectRef.value.model == "single"
? singleData
: DelSelectRef.value.model == "multiple"
? delDataClone
: "",
};
$http.post($url.delHistoryMsg, data).then((res) => {
if (res.data.code === 200) {
userTableRef.value.clearSelection();
delData = {};
delDataClone = {};
ElMessage({
message: "操作成功!",
type: "success",
});
historyMsg();
} else {
ElMessage.error("操作失败!");
}
});
};
defineExpose({ manager, historyMsg });
watch(searchOptions, (value) => {
historyMsgActive.searchOptions = value;
});
watch(sortOptions, (value) => {
historyMsgActive.sortOptions = value;
});
onMounted(() => {
pageCount.value = historyMsgActive.pageCount;
dialogSearchVisible.value = historyMsgActive.dialogSearchVisible;
dialogSortVisible.value = historyMsgActive.dialogSortVisible;
dialogOperationVisible.value = historyMsgActive.dialogOperationVisible;
if (dialogSearchVisible.value) {
searchOptions.mountpoint = historyMsgActive.searchOptions.mountpoint;
searchOptions.original_msg = historyMsgActive.searchOptions.original_msg;
searchOptions.created_at = historyMsgActive.searchOptions.created_at;
}
if (dialogSortVisible.value) {
sortOptions.mountpoint = historyMsgActive.sortOptions.mountpoint;
sortOptions.created_at = historyMsgActive.sortOptions.created_at;
}
if (dialogOperationVisible.value) {
}
setTimeout(() => {
historyMsg();
}, 200);
setTimeout(() => {
if (dialogSearchVisible.value) {
SearchMountpointKeyRef.value.searchMsg = historyMsgActive.searchOptions.mountpoint;
SearchDataKeyRef.value.searchMsg = historyMsgActive.searchOptions.original_msg;
DateTimePickerRef.value.created_at = historyMsgActive.searchOptions.created_at;
}
if (dialogSortVisible.value) {
SortMountpointRef.value.searchMsg = historyMsgActive.sortOptions.mountpoint;
SortDateTimeRef.value.searchMsg = historyMsgActive.sortOptions.created_at;
}
if (dialogOperationVisible.value) {
}
}, 0);
});
</script>
<style scoped></style>

View File

@@ -0,0 +1,102 @@
<template>
<div class="m-2 font-bold">
<el-card class="box-card">
<template #header>
<div class="card-header">
<span>挂载点列表</span>
</div>
</template>
<el-tag
v-for="mountpoint in mountpointList"
:key="mountpoint"
type="info"
class="mountpoint-tag"
:effect="mountpoint === selectedMountpoint ? 'dark' : 'plain'"
@click="selectMountpoint(mountpoint)"
>
{{ mountpoint }}
</el-tag>
</el-card>
</div>
</template>
<script setup>
import { ref, onMounted, nextTick } from "vue";
import { ElCard, ElTag, ElMessage } from "element-plus";
import $http from "../../api/index";
import $url from "../../api/url";
import mountpointActiveStore from "../../stores/mountpoint";
const mountpointActive = mountpointActiveStore();
const emit = defineEmits(["selectMountpoint"]);
const mountpointList = ref([]);
const selectedMountpoint = ref("");
const selectMountpoint = (mountpoint) => {
selectedMountpoint.value = mountpoint;
mountpointActive.selectedMountpoint = mountpoint;
emit("selectMountpoint", mountpoint);
};
const resetSelect = () => {
selectedMountpoint.value = "";
mountpointActive.selectedMountpoint = "";
};
const getMountpoint = () => {
mountpointList.value = [];
nextTick(() => {
$http.get($url.getMountpoint).then((res) => {
if (res.data.code === 200) {
mountpointList.value = res.data.mountpoint;
} else {
// ElMessage.error("未查找到相关数据!");
}
});
});
};
defineExpose({ resetSelect, getMountpoint });
onMounted(() => {
selectedMountpoint.value = mountpointActive.selectedMountpoint;
setTimeout(() => {
getMountpoint();
}, 200);
});
</script>
<style scoped>
.box-card {
flex: 1;
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.card-header {
text-align: center;
font-weight: bold;
}
.mountpoint-tag {
margin: 10px;
cursor: pointer;
}
.mountpoint-tag.effect-dark {
background-color: #409eff;
color: #fff;
border-color: #409eff;
}
.mountpoint-tag.effect-plain:hover {
background-color: #ecf5ff;
color: #409eff;
border-color: #d9ecff;
}
</style>

View File

@@ -0,0 +1,324 @@
<template>
<!-- 表格 -->
<el-card>
<div class="flex justify-center flex-wrap">
<TabelTools @showTools="showTools" :dialogObj="historyMsgActive" />
<el-table
ref="userTableRef"
:data="tableData"
style="width: 100%"
max-height="32rem"
table-layout="auto"
scrollbar-always-on
>
<el-table-column min-width="100" column-key="mountpoint">
<template #header>
<div>
<SearchKey
v-if="dialogSearchVisible"
ref="SearchMountpointKeyRef"
holderText="输入挂载点名称以搜索相关数据"
@search="historyMsg"
/>
<div>
<el-text class="mx-1"> 挂载点名称 </el-text>
<SortSearch
v-if="dialogSortVisible"
ref="SortMountpointRef"
activeText="顺序"
inactiveText="倒序"
@search="historyMsg"
/>
</div>
</div>
</template>
<template #default="scope">
<div
style="color: rgb(16, 184, 130)"
class="flex justify-start items-center text-base"
>
{{ scope.row.mountpoint }}
</div>
</template>
</el-table-column>
<el-table-column min-width="100" column-key="befor_edit">
<template #header>
<div>
<SearchKey
v-if="dialogSearchVisible"
ref="SearchBeforEditKeyRef"
holderText="输入编辑前挂载点名称以搜索相关数据"
@search="historyMsg"
/>
<div>
<el-text class="mx-1"> 编辑前挂载点名称 </el-text>
<SortSearch
v-if="dialogSortVisible"
ref="SortBeforEditRef"
activeText="顺序"
inactiveText="倒序"
@search="historyMsg"
/>
</div>
</div>
</template>
<template #default="scope">
<div
style="color: rgb(16, 184, 130)"
class="flex justify-start items-center text-base"
>
{{ scope.row.befor_edit }}
</div>
</template>
</el-table-column>
<el-table-column min-width="170" column-key="created_at">
<template #header>
<DateTimePicker
v-if="dialogSearchVisible"
ref="CreatedAtPickerRef"
DateTimePicker="historyMsgDateTimePicker"
@search="historyMsg"
/>
<div>
<el-text class="mx-1"> 记录时间 </el-text>
<SortSearch
v-if="dialogSortVisible"
ref="SortCreatedAtRef"
activeText="顺序"
inactiveText="倒序"
@search="historyMsg"
/>
</div>
</template>
<template #default="scope">
<div
style="color: rgb(16, 184, 130)"
class="flex justify-start items-center text-base"
>
{{ scope.row.created_at }}
</div>
</template>
</el-table-column>
<el-table-column min-width="170" column-key="updated_at">
<template #header>
<DateTimePicker
v-if="dialogSearchVisible"
ref="UpdatedAtPickerRef"
DateTimePicker="historyMsgDateTimePicker"
@search="historyMsg"
/>
<div>
<el-text class="mx-1"> 更新时间 </el-text>
<SortSearch
v-if="dialogSortVisible"
ref="SortUpdatedAtRef"
activeText="顺序"
inactiveText="倒序"
@search="historyMsg"
/>
</div>
</template>
<template #default="scope">
<div
style="color: rgb(16, 184, 130)"
class="flex justify-start items-center text-base"
>
{{ scope.row.updated_at }}
</div>
</template>
</el-table-column>
</el-table>
<!-- 表格 -->
<Pagination
ref="PaginationRef"
:active="historyMsgActive"
@search="historyMsg"
/>
</div>
</el-card>
</template>
<script setup>
import { ref, reactive, watch, onMounted, nextTick } from "vue";
import { ElMessage } from "element-plus";
import TabelTools from "../operate/tabelTools.vue";
import SearchKey from "../search/searchKey.vue";
import SortSearch from "../search/sortSearch.vue";
import DateTimePicker from "../search/dateTimePicker.vue";
import Pagination from "../search/pagination.vue";
import historyMsgActiveStore from "../../stores/historyMsg";
import moment from "moment";
import $http from "../../api/index";
import $url from "../../api/url";
import { judgment } from "../../utils/judgment.js";
import { hasNullOrEmptyPropertyWithTypeof } from "../../utils/hasNullOrEmptyPropertyWithTypeof.js";
const emit = defineEmits(["getRole"]);
const historyMsgActive = historyMsgActiveStore();
const manager = ref(false);
const SearchMountpointKeyRef = ref(null);
const SearchBeforEditKeyRef = ref(null);
const SortMountpointRef = ref(null);
const SortBeforEditRef = ref(null);
const SortCreatedAtRef = ref(null);
const SortUpdatedAtRef = ref(null);
const CreatedAtPickerRef = ref(null);
const UpdatedAtPickerRef = ref(null);
const PaginationRef = ref(null);
const dialogSearchVisible = ref(false);
const dialogSortVisible = ref(false);
const dialogOperationVisible = ref(false);
const offsetOff = ref(true);
const pageCount = ref(0);
const searchOptions = reactive({
mountpoint: "",
befor_edit: "",
created_at: "",
updated_at: ""
});
const sortOptions = reactive({
mountpoint: false,
befor_edit: false,
created_at: false,
updated_at: false
});
const tableData = ref([]);
const showTools = (operateType) => {
switch (operateType) {
case "search":
dialogSearchVisible.value = !dialogSearchVisible.value;
historyMsgActive.dialogSearchVisible = dialogSearchVisible.value;
break;
case "sort":
dialogSortVisible.value = !dialogSortVisible.value;
historyMsgActive.dialogSortVisible = dialogSortVisible.value;
break;
case "operation":
emit("getRole");
if (manager.value) {
dialogOperationVisible.value = !dialogOperationVisible.value;
historyMsgActive.dialogOperationVisible = dialogOperationVisible.value;
}
break;
default:
break;
}
};
const historyMsg = () => {
judgment(searchOptions, "mountpoint", SearchMountpointKeyRef.value, "searchMsg");
judgment(searchOptions, "befor_edit", SearchBeforEditKeyRef.value, "searchMsg");
judgment(searchOptions, "created_at", CreatedAtPickerRef.value, "searchMsg");
judgment(searchOptions, "updated_at", UpdatedAtPickerRef.value, "searchMsg");
judgment(sortOptions, "mountpoint", SortMountpointRef.value, "searchMsg");
judgment(sortOptions, "befor_edit", SortBeforEditRef.value, "searchMsg");
judgment(sortOptions, "created_at", SortCreatedAtRef.value, "searchMsg");
judgment(sortOptions, "updated_at", SortUpdatedAtRef.value, "searchMsg");
if (hasNullOrEmptyPropertyWithTypeof(searchOptions)) {
if (PaginationRef.value.offset !== 1 && offsetOff.value) {
PaginationRef.value.handleCurrentChange(1);
offsetOff.value = false;
}
} else {
offsetOff.value = true;
}
const data = {
uid: localStorage.data,
offset: PaginationRef.value.offset,
limit: PaginationRef.value.limit,
searchOptions: searchOptions,
sortOptions: sortOptions,
};
$http.post($url.getRecord, data).then((res) => {
if (res.data.code === 200) {
tableData.value = res.data.recordMsg;
tableData.value.forEach((item) => {
item.created_at = moment
.utc(item.created_at)
.add(8, "hours")
.format("YYYY-MM-DD-HH:mm:ss");
item.updated_at = moment
.utc(item.updated_at)
.add(8, "hours")
.format("YYYY-MM-DD-HH:mm:ss");
});
PaginationRef.value.dataCount = res.data.count;
historyMsgActive.dataCount = PaginationRef.value.dataCount;
pageCount.value = Math.ceil(
PaginationRef.value.dataCount / PaginationRef.value.limit
);
historyMsgActive.pageCount = pageCount.value;
} else {
tableData.value = [];
// ElMessage.error("未查找到相关数据!");
}
});
};
defineExpose({ manager, historyMsg });
watch(searchOptions, (value) => {
historyMsgActive.searchOptions = value;
});
watch(sortOptions, (value) => {
historyMsgActive.sortOptions = value;
});
onMounted(() => {
pageCount.value = historyMsgActive.pageCount;
dialogSearchVisible.value = historyMsgActive.dialogSearchVisible;
dialogSortVisible.value = historyMsgActive.dialogSortVisible;
dialogOperationVisible.value = historyMsgActive.dialogOperationVisible;
if (dialogSearchVisible.value) {
searchOptions.mountpoint = historyMsgActive.searchOptions.mountpoint;
searchOptions.befor_edit = historyMsgActive.searchOptions.befor_edit;
searchOptions.created_at = historyMsgActive.searchOptions.created_at;
searchOptions.updated_at = historyMsgActive.searchOptions.updated_at;
}
if (dialogSortVisible.value) {
sortOptions.mountpoint = historyMsgActive.sortOptions.mountpoint;
sortOptions.befor_edit = historyMsgActive.sortOptions.befor_edit;
sortOptions.created_at = historyMsgActive.sortOptions.created_at;
sortOptions.updated_at = historyMsgActive.sortOptions.updated_at;
}
if (dialogOperationVisible.value) {
}
setTimeout(() => {
historyMsg();
}, 200);
setTimeout(() => {
if (dialogSearchVisible.value) {
SearchMountpointKeyRef.value.searchMsg = historyMsgActive.searchOptions.mountpoint;
SearchBeforEditKeyRef.value.searchMsg = historyMsgActive.searchOptions.befor_edit;
CreatedAtPickerRef.value.created_at = historyMsgActive.searchOptions.created_at;
UpdatedAtPickerRef.value.updated_at = historyMsgActive.searchOptions.updated_at;
}
if (dialogSortVisible.value) {
SortMountpointRef.value.searchMsg = historyMsgActive.sortOptions.mountpoint;
SortBeforEditRef.value.searchMsg = historyMsgActive.sortOptions.befor_edit;
SortCreatedAtRef.value.searchMsg = historyMsgActive.sortOptions.created_at;
SortUpdatedAtRef.value.searchMsg = historyMsgActive.sortOptions.updated_at;
}
if (dialogOperationVisible.value) {
}
}, 0);
});
</script>
<style scoped></style>

View File

@@ -0,0 +1,87 @@
<template>
<div>
常见问题指南:
<br />
<br />
<span class="text-amber-500 font-black">Q:</span
>为什么平台在打开状态下且一段时间无任何操作后会显示<span
class="text-blue-600 font-black"
>未绑定且无连接状态</span
>?
<br />
<span class="text-emerald-500 font-black">A:</span
>这是由于身份信息过期导致的,进行数据搜索的操作后身份信息会自动刷新,数据也会正常显示。
<br />
<br />
<span class="text-amber-500 font-black">Q:</span
>为什么平台在使用中有时候会突然跳转到登录页面然后跳转回来?
<br />
<span class="text-emerald-500 font-black">A:</span
>这是由于身份信息刷新导致的,不会导致数据丢失,可放心使用。
<br />
<br />
<span class="text-amber-500 font-black">Q:</span
>为什么平台在登录和刷新后有时会弹出“<span class="text-red-500 font-black"
>未查找到相关数据!</span
>”的提示?
<br />
<span class="text-emerald-500 font-black">A:</span
>这是由于身份信息正在刷新导致的,刷新完毕后数据会正常显示。
<br />
<br />
<span class="text-amber-500 font-black">Q:</span>为什么当前消息一直显示“<span
class="text-sky-400 font-black"
>连接中...</span
>”?
<br />
<span class="text-emerald-500 font-black">A:</span
>这种情况需要检查您的网络状况或更改连接的网络后重试。
<br />
<br />
<span class="text-amber-500 font-black">Q:</span>表格左上角的<span
class="text-emerald-500 font-black"
>表格工具</span
>是什么?
<br />
<span class="text-emerald-500 font-black">A:</span
><span class="text-amber-500 font-black">表格工具</span
>是用来显示或隐藏用于对表格进行搜索、排序和编辑删除等操作的控件,
<br />
使用时只需要将鼠标移动到<span class="text-amber-500 font-black">表格工具</span
>上,然后选择需要使用的功能并点击即可,附操作图如下:
<div class="border-2 flex flex-wrap justify-center items-center">
<div class="w-1/4">
<span class="text-red-500 font-black">1.&nbsp;&nbsp;</span
>鼠标移入表格工具,弹出下拉框
<img src="../../assets/img/Q&A/tabelTools.png" alt="" />
</div>
<div class="w-1/4 border-l-2">
<span class="text-red-500 font-black">2.&nbsp;&nbsp;</span
>选择需要使用的工具,工具显
<br />
示,显示状态下右边存在眼睛图标
<img src="../../assets/img/Q&A/toolsClick.png" alt="" />
</div>
<div class="w-1/4 border-l-2">
<span class="text-red-500 font-black">3.&nbsp;&nbsp;</span
>可选择多个工具同时进行使用
<img src="../../assets/img/Q&A/toolsSelect.png" alt="" />
</div>
<div class="w-1/4 border-l-2">
<span class="text-red-500 font-black">4.&nbsp;&nbsp;</span
>如果工具使用完毕,可以再次
<br />
点击下拉框中的对应工具进行隐藏
<img src="../../assets/img/Q&A/showType.png" alt="" />
</div>
</div>
</div>
</template>
<script setup>
import tabelTools from "../../assets/img/Q&A/tabelTools.png";
import toolsClick from "../../assets/img/Q&A/toolsClick.png";
import toolsSelect from "../../assets/img/Q&A/toolsSelect.png";
</script>
<style scoped></style>

View File

@@ -0,0 +1,92 @@
<template>
<div>
汉字编码使用指南:
<br />
<br />
<div>
<span class="text-sky-500 font-black">编码科普:</span>
如果您不懂汉字编码,建议您先点击链接“<span class="text-sky-500 font-black"
>编码科普</span
>”以了解汉字编码的基本知识。
<img src="../../assets/img/code/codeScience.png" alt="" />
</div>
<br />
<br />
<div>
<span class="text-emerald-500 font-black">汉字转换为编码:</span>
<br />
<span class="text-slate-500 font-black">灰色框</span>为输入区域, 在此区域输入需要转换为编码的汉字
<br />
<span class="text-amber-500 font-black">黄色框</span>为“<span class="text-amber-500 font-black">显示类型</span>”选择下拉框,共提供两种显示类型
<br />
<span class="text-emerald-500 font-black">绿色框</span>为“<span class="text-emerald-500 font-black">转换汉字</span>”按钮,点击此按钮可将输入框的汉字转换为编码并显示在输出区域
<br />
<span class="text-sky-500 font-black">蓝色框</span>为“<span class="text-sky-500 font-black">清除</span>”按钮,点击此按钮可清除输入框和输出区域的内容
<img src="../../assets/img/code/fontGuide.png" alt="" />
</div>
<br />
<br />
<div>
<span class="text-sky-500 font-black">选择显示类型:</span>显示类型分为<span class="text-amber-500 font-black">单字</span>显示和<span class="text-emerald-500 font-black">连续</span>显示两种模式
<img src="../../assets/img/code/showType.png" alt="" />
</div>
<br />
<br />
<div>
<span class="text-emerald-500 font-black">汉字转换为编码的转换结果:</span>
<img src="../../assets/img/code/transFont.png" alt="" />
</div>
<br />
<br />
<div>
点击左上角的<span class="text-red-500 font-black">转换模式</span>按钮可切换编码模式
<img src="../../assets/img/code/changeMode.png" alt="" />
</div>
<br />
<br />
<div>
<span class="text-emerald-500 font-black">编码转换为汉字:</span>
<br />
<span class="text-slate-500 font-black">灰色框</span>为输入区域, 在此区域输入需要转换为汉字的编码
<br />
<span class="text-amber-500 font-black">黄色框</span>为“<span class="text-amber-500 font-black">解码类型</span>”选择下拉框,共提供三种解码类型
<br />
<span class="text-emerald-500 font-black">绿色框</span>为“<span class="text-emerald-500 font-black">显示类型</span>”选择下拉框,共提供两种显示类型
<br />
<span class="text-red-500 font-black">红色框</span>为“<span class="text-red-500 font-black">转换编码</span>”按钮,点击此按钮可将输入框的编码转换为汉字并显示在输出区域
<br />
<span class="text-sky-500 font-black">蓝色框</span>为“<span class="text-sky-500 font-black">清除</span>”按钮,点击此按钮可清除输入框和输出区域的内容
<img src="../../assets/img/code/codeGuide.png" alt="" />
</div>
<br />
<br />
<div>
<span class="text-amber-500 font-black">选择解码类型:</span>解码类型分为<span class="text-amber-500 font-black">十进制</span>解码,<span class="text-emerald-500 font-black">十六进制</span>解码和<span class="text-red-500 font-black">二进制</span>解码三种模式
<img src="../../assets/img/code/codeType.png" alt="" />
</div>
<br />
<br />
<div>
<span class="text-sky-500 font-black">选择显示类型:</span>显示类型分为<span class="text-amber-500 font-black">单字</span>显示和<span class="text-emerald-500 font-black">连续</span>显示两种模式
<img src="../../assets/img/code/showType.png" alt="" />
</div>
<br />
<br />
<div>
<span class="text-emerald-500 font-black">编码转换为汉字的转换结果:</span>
<img src="../../assets/img/code/transCode.png" alt="" />
</div>
</div>
</template>
<script setup>
import codeGuide from "../../assets/img/code/codeGuide.png";
import codeScience from "../../assets/img/code/codeScience.png";
import codeType from "../../assets/img/code/codeType.png";
import fontGuide from "../../assets/img/code/fontGuide.png";
import showType from "../../assets/img/code/showType.png";
import transCode from "../../assets/img/code/transCode.png";
import transFont from "../../assets/img/code/transFont.png";
</script>
<style scoped></style>

View File

@@ -0,0 +1,128 @@
<template>
<el-dialog
v-model="dialogVisible"
:show-close="false"
style="width: 80%"
center
align-center
:before-close="handleClose"
>
<template #header="{ close, titleId, titleClass }">
<div
style="font-size: 2rem"
class="w-full font-bold"
:id="titleId"
:class="titleClass"
>
北斗短报文通信服务平台使用帮助
</div>
<div
@click="close"
class="bg-gray-400 hover:bg-red-600 text-3xl absolute top-0 right-0 cursor-pointer flex justify-center items-center"
>
<el-icon>
<CloseBold />
</el-icon>
</div>
</template>
<el-menu
class="flex flex-wrap font-bold"
:default-active="activeIndex"
@select="handleSelect"
>
<el-menu-item
index="0"
:style="activeIndex == '0' ? activeStyle : indexStyle"
class="border-x-4 border-slate-300"
>
Q&A
</el-menu-item>
<el-menu-item
index="1"
:style="activeIndex == '1' ? activeStyle : indexStyle"
class="border-r-4 border-slate-300"
>
顶部导航栏
</el-menu-item>
<el-menu-item
index="2"
:style="activeIndex == '2' ? activeStyle : indexStyle"
class="border-r-4 border-slate-300"
>
查看消息
</el-menu-item>
<el-menu-item
index="3"
:style="activeIndex == '3' ? activeStyle : indexStyle"
class="border-r-4 border-slate-300"
>
汉字编码
</el-menu-item>
<el-menu-item
index="4"
:style="activeIndex == '4' ? activeStyle : indexStyle"
class="border-r-4 border-slate-300"
>
异或校验
</el-menu-item>
<el-menu-item
v-if="manager"
index="5"
:style="activeIndex == '5' ? activeStyle : indexStyle"
class="border-r-4 border-slate-300"
>
单位管理
</el-menu-item>
</el-menu>
<el-card>
<div v-show="activeIndex === '0'">
<QA />
</div>
<div v-show="activeIndex === '1'">
<Header />
</div>
<div v-show="activeIndex === '2'">
<Msg />
</div>
<div v-show="activeIndex === '3'">
<Code />
</div>
<div v-show="activeIndex === '4'">
<Xor />
</div>
<div v-show="activeIndex === '5'">
<Manager />
</div>
</el-card>
</el-dialog>
</template>
<script setup>
import { ref, reactive, watch } from "vue";
import { CloseBold } from "@element-plus/icons-vue";
import QA from "./Q&A.vue";
import Header from "./header.vue";
import Msg from "./msg.vue";
import Code from "./code.vue";
import Xor from "./xor.vue";
import Manager from "./manager.vue";
const dialogVisible = ref(false);
const activeIndex = ref("1");
const indexStyle = "background-color: rgb(241 245 249);";
const activeStyle = "background-color: rgb(207 250 254);";
const manager = ref(false);
const handleClose = () => {
dialogVisible.value = false;
};
const handleSelect = (key, keyPath) => {
activeIndex.value = key;
};
defineExpose({ dialogVisible, manager });
</script>
<style scoped></style>

View File

@@ -0,0 +1,29 @@
<template>
<div>
顶部导航栏指南:
<br />
<br />
导航栏组成:
<br />
<br />
<div>
<span class="text-amber-500 font-black">左侧:</span>
展示了平台号码,卡号,绑定/解绑按钮,以及帮助按钮
<img src="../../assets/img/header/headerGuide.png" alt="" />
</div>
<br />
<br />
<div>
<span class="text-emerald-500 font-black">右侧:</span>
内容为账户名的下拉框,鼠标移动到下拉框时,会显示一些功能
<img src="../../assets/img/header/headerSelect.png" alt="" />
</div>
</div>
</template>
<script setup>
import headerGuide from "../../assets/img/header/headerGuide.png";
import headerSelect from "../../assets/img/header/headerSelect.png";
</script>
<style scoped></style>

View File

@@ -0,0 +1,38 @@
<template>
<div>
单位管理指南:
<br />
<br />
<div>
<span class="text-amber-500 font-black">单位管理操作工具:</span>
<br />
单位管理功能中最重要的部分是表格的操作工具
<br />
可以通过表格左上角的下拉框找到操作工具并使其显示
<img src="../../assets/img/manager/operateSelect.png" alt="" />
</div>
<br />
<br />
<div>
<span class="text-emerald-500 font-black">操作工具介绍:</span>
<br />
单位管理功能的表格的操作工具具有四种操作类型
<br />
<span class="text-amber-500 font-black">黄色框</span>内为账号信息的编辑功能,只允许单个账号信息的编辑操作
<br />
<span class="text-red-500 font-black">红色框</span>内为账号状态的重置功能,可以重置账号的密码、解绑账号所绑定的北斗卡、删除账号现有的姓名和手机号
<br />
<span class="text-sky-500 font-black">蓝色框</span>内为账号密码的重置功能,可以重置账号的密码
<br />
<span class="text-emerald-500 font-black">绿色框</span>内为账号与北斗卡的的解绑功能,可以解绑账号所绑定的北斗卡
<img src="../../assets/img/manager/operateType.png" alt="" />
</div>
</div>
</template>
<script setup>
import operateSelect from "../../assets/img/manager/operateSelect.png";
import operateType from "../../assets/img/manager/operateType.png";
</script>
<style scoped></style>

View File

@@ -0,0 +1,72 @@
<template>
<div>
查看消息指南:
<br />
<br />
<div>
<span class="text-emerald-500 font-black">实时短报文消息:</span>
左侧为接收到的实时短报文消息的内容,右侧为接收到实时短报文消息时的时间
<img src="../../assets/img/msg/msg.png" alt="" />
</div>
<br />
<br />
<div>
<span class="text-sky-500 font-black">查看历史消息的全部内容:</span>
消息内容过长时,可以点击右侧的“<span class="text-sky-500 font-black"
>展示全部内容</span
>”按钮来查看消息的全部内容
<img src="../../assets/img/msg/showMsg.png" alt="" />
</div>
<br />
<br />
<div>
<span class="text-amber-500 font-black">删除历史消息:</span>
想要删除历史消息时,首先需要显示出<span class="text-amber-500 font-black"
>表格工具</span
>中的“<span class="text-emerald-500 font-black">操作工具</span>”
<img src="../../assets/img/msg/operateSelect.png" alt="" />
</div>
<br />
<br />
<div>
<span class="text-red-500 font-black">删除历史消息:</span>
<br />
此处存在三种删除操作:
<br />
<span class="text-red-500 font-black">1. </span>下图<span
class="text-red-500 font-black"
>红色框</span
>内的按钮“<span class="text-red-500 font-black">删除</span>”可以删除当前行的数据
<br />
<span class="text-sky-500 font-black">2. </span>下图<span
class="text-sky-500 font-black"
>蓝色框</span
>内的按钮“<span class="text-red-500 font-black">删除选中</span>”可以删除选中行的数据
<br />
<span class="text-slate-500 font-black">2.1 </span>下图<span
class="text-slate-500 font-black"
>灰色框</span
>内的单选框可以全选中或全不选中所有行的数据
<br />
<span class="text-emerald-500 font-black">2.2 </span>下图<span
class="text-emerald-500 font-black"
>绿色框</span
>内的单选框可以选中或不选中当前行的数据
<br />
<span class="text-amber-500 font-black">3. </span>下图<span
class="text-amber-500 font-black"
>黄色框</span
>内的按钮“<span class="text-red-500 font-black">删除所有</span>”可以删除所有行的数据
<img src="../../assets/img/msg/operateType.png" alt="" />
</div>
</div>
</template>
<script setup>
import msg from "../../assets/img/msg/msg.png";
import operateSelect from "../../assets/img/msg/operateSelect.png";
import operateType from "../../assets/img/msg/operateType.png";
import showMsg from "../../assets/img/msg/showMsg.png";
</script>
<style scoped></style>

View File

@@ -0,0 +1,42 @@
<template>
<div>
异或校验指南:
<br />
<br />
<div>
<span class="text-emerald-500 font-black">功能组成:</span>
<br />
<span class="text-sky-500 font-black">蓝色框</span>内为校验数据的输入框
<br />
<span class="text-emerald-500 font-black">绿色区域</span>为校验结果的显示区域
<br />
下方的“<span class="text-amber-500 font-black">校验</span>”按钮用于执行校验
<br />
下方的“<span class="text-red-500 font-black">清除</span
>”按钮用于清除输入框的校验数据和显示区域的校验结果。
<img src="../../assets/img/xor/xorGuide.png" alt="" />
</div>
<br />
<br />
<div>
<span class="text-red-500 font-black">未输入校验数据时的报错示例:</span>
<img src="../../assets/img/xor/input.png" alt="" />
<img src="../../assets/img/xor/errorMsg.png" alt="" />
</div>
<br />
<br />
<div>
<span class="text-amber-500 font-black">校验结果示例:</span>
<img src="../../assets/img/xor/xor.png" alt="" />
</div>
</div>
</template>
<script setup>
import errorMsg from "../../assets/img/xor/errorMsg.png";
import input from "../../assets/img/xor/input.png";
import xor from "../../assets/img/xor/xor.png";
import xorGuide from "../../assets/img/xor/xorGuide.png";
</script>
<style scoped></style>

View File

@@ -0,0 +1,217 @@
<template>
<el-dialog
v-model="dialogVisible"
:show-close="false"
style="width: 80%"
center
align-center
:before-close="handleClose"
>
<template #header="{ close, titleId, titleClass }">
<div
style="font-size: 2rem"
class="w-full font-bold"
:id="titleId"
:class="titleClass"
>
密码管理
</div>
<div
@click="close"
class="bg-gray-400 hover:bg-red-600 text-3xl absolute top-0 right-0 cursor-pointer flex justify-center items-center"
>
<el-icon>
<CloseBold />
</el-icon>
</div>
</template>
<el-form
ref="ruleFormRef"
style="max-width: 600px"
:model="ruleForm"
:rules="rules"
label-width="auto"
size="default"
status-icon
class="mx-auto"
>
<el-form-item class="mx-1 font-bold" label="原密码" prop="oldPassword">
<el-input
clearable
placeholder="请输入原密码"
type="password"
show-password
maxlength="16"
autocomplete="off"
v-model="ruleForm.oldPassword"
@input="filter(ruleForm.oldPassword, 'oldPassword')"
oncut="return false"
onpaste="return false"
oncopy="return false"
/>
</el-form-item>
<el-form-item class="mx-1 font-bold" label="新密码" prop="newPassword">
<el-text
style="color: rgb(240 70 70)"
class="mx-1 font-bold"
size="small"
type="info"
>
密码必须包含数字、大小写字母以及特殊字符(!@#$%^&*.),且长度在8-16位之间
</el-text>
<el-input
clearable
placeholder="请输入新密码"
type="password"
show-password
maxlength="16"
autocomplete="off"
v-model="ruleForm.newPassword"
@input="filter(ruleForm.newPassword, 'newPassword')"
oncut="return false"
onpaste="return false"
oncopy="return false"
/>
</el-form-item>
<el-form-item class="mx-1 font-bold" label="确认新密码" prop="checkPass">
<el-input
clearable
placeholder="请再次输入新密码"
type="checkPass"
show-password
maxlength="16"
autocomplete="off"
v-model="ruleForm.checkPass"
@input="filter(ruleForm.checkPass, 'checkPass')"
oncut="return false"
onpaste="return false"
oncopy="return false"
/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button style="font-weight: bold" @click="resetForm(ruleFormRef)">
取消
</el-button>
<el-button
style="font-weight: bold"
type="primary"
@click="changePassword(ruleFormRef)"
>
提交修改
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { ref, reactive } from "vue";
import { CloseBold } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
import { encrypt } from "../../utils/aes";
import $http from "../../api/index";
import $url from "../../api/url";
const dialogVisible = ref(false);
const ruleFormRef = ref(null);
const formElClone = ref(null);
const ruleForm = reactive({
oldPassword: "",
newPassword: "",
checkPass: "",
});
const filter = (value, label) => {
if (/[=\s"'+-]/.test(value)) {
if (ruleForm[label]) {
ruleForm[label] = value.replace(/[=\s"'+-]/g, "");
}
}
};
const passwordRegex = /^(?=.*[0-9])(?=.*[a-z])(?=.*[A-Z])(?=.*[!@#$%^&*.]).{8,16}$/;
const validatePass = (rule, value, callback) => {
if (value === "") {
callback(new Error("请输入密码"));
} else if (!passwordRegex.test(value)) {
callback( new Error("密码必须包含数字、大小写字母以及特殊字符(!@#$%^&*.),且长度在8-16位之间"));
} else {
if (ruleForm.checkPass !== "") {
if (!ruleFormRef.value) return;
ruleFormRef.value.validateField("checkPass");
}
callback();
}
};
const validateCheck = (rule, value, callback) => {
if (value === "") {
callback(new Error("请再次输入密码"));
} else if (value !== ruleForm.newPassword) {
callback(new Error("两次输入的密码不一致"));
} else {
callback();
}
};
const rules = reactive({
oldPassword: [{ required: true, message: "请输入原密码", trigger: "blur" }],
newPassword: [{ required: true, validator: validatePass, trigger: "blur" }],
checkPass: [{ required: true, validator: validateCheck, trigger: "blur" }],
});
const handleClose = () => {
dialogVisible.value = false;
if (!ruleFormRef) return;
formElClone.value = ruleFormRef.value;
formElClone.value.resetFields();
};
const resetForm = (formEl) => {
dialogVisible.value = false;
if (!formEl) return;
formEl.resetFields();
};
const changePassword = async (formEl) => {
if (!formEl) return;
await formEl.validate((valid) => {
if (valid) {
dialogVisible.value = false;
let encryptForm = {
oldPassword: encrypt(ruleForm.oldPassword),
newPassword: encrypt(ruleForm.newPassword),
};
formEl.resetFields();
const data = {
passwordForm: encryptForm,
uid: localStorage.data,
};
$http.post($url.changePassword, data).then((res) => {
if (res.data.code === 200) {
ElMessage({
message: "密码修改成功!",
type: "success",
});
localStorage.clear();
window.location.reload();
} else {
ElMessage.error("密码修改失败!");
}
});
} else {
ElMessage.error("提交失败!");
}
});
};
defineExpose({ dialogVisible });
</script>
<style scoped></style>

View File

@@ -0,0 +1,351 @@
<template>
<div class="m-2 font-bold flex">
<div style="width: 5%" class="flex justify-center">
<el-text class="mx-1 font-bold"> 保存挂载点 : </el-text>
</div>
<el-input
v-model="mountpoint"
class="flex-1"
placeholder="请输入挂载点名称"
maxlength="100"
clearable
@input="handleInputMountpoint(mountpoint, 'save')"
/>
<div style="width: 1%"></div>
<el-input
v-model="password"
class="flex-1"
placeholder="请输入挂载点密码"
minlentgth="8"
maxlength="16"
clearable
@input="handleInputPassword(password, 'save')"
/>
<div style="width: 20%" class="flex justify-center">
<el-button
style="font-weight: bold; width: 40%; height: 100%"
type="primary"
size="small"
:disabled="save"
@click="calculationSave"
>
确认
</el-button>
<el-button
style="font-weight: bold; width: 40%; height: 100%"
type="primary"
size="small"
@click="clearSave"
>
清除
</el-button>
</div>
</div>
<div class="m-2 font-bold flex justify-center">
<div style="width: 1%"></div>
<el-text class="mx-1 font-bold" style="color: red">
挂载点命名规则 : 挂载点名称最大长度不得超过 100
个字符,大小写敏感,仅支持大小写英文字母:A-Z、a-z,阿拉伯数字:0-9,特殊符号:连字符
- 下划线 _ 句点 . ,禁止使用除此之外的任何字符。</el-text
>
<div class="flex-1"></div>
</div>
<div class="m-2 font-bold flex justify-center">
<div style="width: 1%"></div>
<el-text class="mx-1 font-bold" style="color: red">
密码设置规则 : 密码长度应处于 8-16 个字符之间,仅支持大小写英文字母:A-Z、a-z,阿拉伯数字:0-9,禁止使用除此之外的任何字符。</el-text
>
<div class="flex-1"></div>
</div>
<div class="m-2 font-bold flex">
<div style="width: 6%" class="flex justify-center">
<el-text class="mx-1 font-bold"> 修改挂载点密码 : </el-text>
</div>
<el-input
v-model="beforEdit"
class="flex-1"
placeholder="请从下方的挂载点列表中选择要修改密码的挂载点名称"
clearable
disabled="true"
/>
<div style="width: 1%"></div>
<el-input
v-model="editPassword"
class="flex-1"
placeholder="请输入修改后的挂载点密码"
minlentgth="8"
maxlength="100"
clearable
@input="handleInputPassword(editPassword, 'change')"
/>
<div style="width: 20%" class="flex justify-center">
<el-button
style="font-weight: bold; width: 40%; height: 100%"
type="primary"
size="small"
:disabled="change"
@click="calculationPasswordEdit"
>
确认
</el-button>
<el-button
style="font-weight: bold; width: 40%; height: 100%"
type="primary"
size="small"
@click="clearPasswordEdit"
>
清除
</el-button>
</div>
</div>
<div class="m-2 font-bold flex">
<div style="width: 6%" class="flex justify-center">
<el-text class="mx-1 font-bold"> 修改挂载点名称 : </el-text>
</div>
<el-input
v-model="beforEdit"
class="flex-1"
placeholder="请从下方的挂载点列表中选择要修改的挂载点名称"
clearable
disabled="true"
/>
<div style="width: 1%"></div>
<el-input
v-model="afterEdit"
class="flex-1"
placeholder="请输入修改后的挂载点名称"
maxlength="100"
clearable
@input="handleInputMountpoint(afterEdit, 'edit')"
/>
<div style="width: 20%" class="flex justify-center">
<el-button
style="font-weight: bold; width: 40%; height: 100%"
type="primary"
size="small"
:disabled="edit"
@click="calculationEdit"
>
确认
</el-button>
<el-button
style="font-weight: bold; width: 40%; height: 100%"
type="primary"
size="small"
@click="clearEdit"
>
清除
</el-button>
</div>
</div>
<div class="m-2 font-bold flex">
<div style="width: 5%" class="flex justify-center">
<el-text class="mx-1 font-bold"> 删除挂载点 : </el-text>
</div>
<el-input
v-model="beforEdit"
class="flex-1"
placeholder="请从下方的挂载点列表中选择要删除的挂载点名称"
clearable
disabled="true"
/>
<div style="width: 20%" class="flex justify-center">
<el-button
style="font-weight: bold; width: 40%; height: 100%"
type="primary"
size="small"
:disabled="del"
@click="calculationDel"
>
确认
</el-button>
<el-button
style="font-weight: bold; width: 40%; height: 100%"
type="primary"
size="small"
@click="clearDel"
>
清除
</el-button>
</div>
</div>
<MountpointList ref="MountpointListRef" @selectMountpoint="selectMountpoint" />
<div class="m-2 font-bold">
<el-text class="mx-1 font-bold"> 挂载点覆盖记录 : </el-text>
<MountpointRecord ref="MountpointRecordRef" />
</div>
</template>
<script setup>
import { ref, nextTick, watch } from "vue";
import { ElMessage } from "element-plus";
import $http from "../../api/index";
import $url from "../../api/url";
import MountpointList from "../features/mountpointList.vue";
import MountpointRecord from "../features/mountpointRecord.vue";
const emit = defineEmits(["getMountpoint"]);
const timeout = ref(null);
const mountpoint = ref("");
const password = ref("");
const save = ref(true);
const beforEdit = ref("");
const afterEdit = ref("");
const editPassword = ref("");
const change = ref(true);
const edit = ref(true);
const del = ref(true);
const MountpointListRef = ref(null);
const MountpointRecordRef = ref(null);
const handleInputMountpoint = (name, type) => {
clearTimeout(timeout.value);
timeout.value = setTimeout(() => {
const isValid = /^[a-zA-Z0-9\-_\.]$/.test(name) && name.length <= 100;
if (type == "save") {
save.value = isValid;
} else if (type == "edit") {
edit.value = isValid;
}
}, 100);
};
const handleInputPassword = (name, type) => {
clearTimeout(timeout.value);
timeout.value = setTimeout(() => {
const isValid = /^[a-zA-Z0-9].{8,16}$/.test(name);
if (type == "save") {
save.value = isValid;
} else if (type == "change") {
change.value = isValid;
}
}, 100);
};
const clearSave = () => {
mountpoint.value = '';
password.value = '';
MountpointListRef.value.resetSelect();
};
const calculationSave = () => {
const data = {
mountpoint: mountpoint.value,
password: password.value
};
nextTick(() => {
$http.post($url.saveMountpoint, data).then((res) => {
if (res.data.code === 200) {
clearSave();
ElMessage({
message: "保存挂载点成功!",
type: "success",
});
MountpointListRef.value.getMountpoint();
emit("getMountpoint");
} else {
ElMessage.error("保存挂载点失败");
}
});
});
};
const clearEdit = () => {
beforEdit.value = '';
MountpointListRef.value.resetSelect();
afterEdit.value = '';
};
const calculationEdit = () => {
const data = {
beforEdit: beforEdit.value,
afterEdit: afterEdit.value
};
nextTick(() => {
$http.post($url.editMountpoint, data).then((res) => {
if (res.data.code === 200) {
clearEdit();
ElMessage({
message: "修改挂载点名称成功!",
type: "success",
});
MountpointListRef.value.getMountpoint();
MountpointRecordRef.value.historyMsg();
emit("getMountpoint");
} else {
ElMessage.error("修改挂载点名称失败");
}
});
});
};
const clearPasswordEdit = () => {
beforEdit.value = '';
MountpointListRef.value.resetSelect();
editPassword.value = '';
};
const calculationPasswordEdit = () => {
const data = {
beforEdit: beforEdit.value,
password: editPassword.value
};
nextTick(() => {
$http.post($url.editPassword, data).then((res) => {
if (res.data.code === 200) {
clearPasswordEdit();
ElMessage({
message: "修改挂载点密码成功!",
type: "success",
});
MountpointListRef.value.getMountpoint();
emit("getMountpoint");
} else {
ElMessage.error("修改挂载点密码失败");
}
});
});
};
const clearDel = () => {
beforEdit.value = '';
del.value = true;
MountpointListRef.value.resetSelect();
};
const calculationDel = () => {
const data = { mountpoint: beforEdit.value };
nextTick(() => {
$http.post($url.delMountpoint, data).then((res) => {
if (res.data.code === 200) {
clearDel();
ElMessage({
message: "删除挂载点名称成功!",
type: "success",
});
MountpointListRef.value.getMountpoint();
MountpointRecordRef.value.historyMsg();
emit("getMountpoint");
} else {
ElMessage.error("删除挂载点名称失败");
}
});
});
};
const selectMountpoint = (selectedMountpoint) => {
beforEdit.value = selectedMountpoint;
};
watch(beforEdit, (value) => {
if (value !== '') {
del.value = false;
}
});
</script>
<style scoped></style>

View File

@@ -0,0 +1,344 @@
<template>
<el-dialog
v-model="dialogVisible"
:show-close="false"
style="width: 80%"
center
align-center
:before-close="handleClose"
>
<template #header="{ close, titleId, titleClass }">
<div
style="font-size: 2rem"
class="w-full font-bold"
:id="titleId"
:class="titleClass"
>
账户管理
</div>
<div
@click="close"
class="bg-gray-400 hover:bg-red-600 text-3xl absolute top-0 right-0 cursor-pointer flex justify-center items-center"
>
<el-icon>
<CloseBold />
</el-icon>
</div>
</template>
<el-form
ref="ruleFormRef"
style="max-width: 600px"
:model="ruleForm"
:rules="rules"
label-width="auto"
size="default"
status-icon
class="mx-auto"
>
<el-form-item class="mx-1 font-bold" label="用户名" prop="account_name">
<el-input
:disabled="acabled"
v-model="ruleForm.account_name"
/>
</el-form-item>
<el-form-item class="mx-1 font-bold" label="姓名" prop="user_name">
<el-input
maxlength="20"
clearable
placeholder="请输入姓名"
v-model="ruleForm.user_name"
@input="filter(ruleForm.user_name, 'user_name')"
/>
</el-form-item>
<el-form-item class="mx-1 font-bold" label="手机号码" prop="phone">
<el-input
minlength="11"
maxlength="11"
clearable
placeholder="请输入手机号码"
v-model="ruleForm.phone"
@input="filter(ruleForm.phone, 'phone')"
/>
</el-form-item>
<el-form-item
class="mx-1 font-bold"
v-if="sideMark == 'C'"
label="性别"
prop="gender"
>
<el-radio-group v-model="ruleForm.gender">
<el-radio value="1"> 男 </el-radio>
<el-radio value="0"> 女 </el-radio>
</el-radio-group>
</el-form-item>
<el-form-item class="mx-1 font-bold" v-if="sideMark == 'C'" label="年龄" prop="age">
<el-input
maxlength="2"
clearable
placeholder="请输入年龄"
v-model="ruleForm.age"
@input="filter(ruleForm.age, 'age')"
/>
</el-form-item>
<el-form-item
class="mx-1 font-bold"
v-if="sideMark == 'C'"
label="邮箱号"
prop="email"
>
<el-input
maxlength="254"
clearable
placeholder="请输入邮箱号"
v-model="ruleForm.email"
@input="filter(ruleForm.email, 'email')"
/>
</el-form-item>
<el-form-item
class="mx-1 font-bold"
v-if="sideMark == 'C'"
label="通信地址"
prop="address"
>
<el-input
maxlength="300"
clearable
placeholder="请输入通信地址"
v-model="ruleForm.address"
@input="filter(ruleForm.address, 'address')"
/>
</el-form-item>
<el-form-item
class="mx-1 font-bold"
v-if="sideMark == 'C'"
label="邮政编码"
prop="post_code"
>
<el-input
maxlength="6"
clearable
placeholder="请输入邮政编码"
v-model="ruleForm.post_code"
@input="filter(ruleForm.post_code, 'post_code')"
/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button style="font-weight: bold" @click="resetForm(ruleFormRef)">
取消
</el-button>
<el-button
style="font-weight: bold"
type="primary"
@click="editUser(ruleFormRef)"
>
提交修改
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { ref, reactive, watch, onMounted } from "vue";
import { CloseBold } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
import userManagerActiveStore from "../../stores/userManager";
import { encrypt, decrypt } from "../../utils/aes";
import $http from "../../api/index";
import $url from "../../api/url";
const phoneRegex = /^(?:13\d|14[579]|15\d|16[67]|17[0135678]|18\d|19[89])\d{8}$/;
const emailRegex = /^([a-zA-Z0-9]+[|-|.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[|-|.]?)*[a-zA-Z0-9]+(.[a-zA-Z]{2,3})+$/;
const postCodeRegex = /^(0[1234567]|1[012356]|2[01234567]|3[0123456]|4[01234567]|5[1234567]|6[1234567]|7[012345]|8[013456])\d{4}$/;
const userManagerActive = userManagerActiveStore();
const dialogVisible = ref(false);
const props = defineProps({
msg: Object,
});
let sideMark;
const acabled = ref(true);
const ruleFormRef = ref(null);
const formElClone = ref(null);
const ruleForm = reactive({
account_name: "",
user_name: "",
phone: "",
});
const filter = (value, label) => {
let filterValue;
if (/[=\s"'+-]/.test(value)) {
if (ruleForm[label]) {
filterValue = value.replace(/[=\s"'+-]/g, "");
}
} else {
filterValue = value;
}
if (label == "phone" || label == "age" || label == "post_code") {
ruleForm[label] = filterValue.replace(/\D/g, "");
} else {
ruleForm[label] = filterValue;
}
};
const validatePhone = (rule, value, callback) => {
if (value && !phoneRegex.test(value)) {
callback(new Error("请输入正确的电话号码"));
} else {
callback();
}
};
const validateEmail = (rule, value, callback) => {
if (value && !emailRegex.test(value)) {
callback(new Error("请输入正确的邮箱地址"));
} else {
callback();
}
};
const validateCode = (rule, value, callback) => {
if (value && !postCodeRegex.test(value)) {
callback(new Error("请输入正确的邮政编码"));
} else {
callback();
}
};
const rules = reactive({
account_name: [{ required: false, message: "请输入用户名", trigger: "blur" }],
user_name: [],
phone: [
{ required: false, message: "请输入手机号", trigger: "blur" },
{ min: 11, max: 11, message: "输入长度应当为 11 位", trigger: "change" },
{ validator: validatePhone },
],
});
const handleClose = () => {
dialogVisible.value = false;
if (!ruleFormRef) return;
formElClone.value = ruleFormRef.value;
formElClone.value.resetFields();
};
const resetForm = (formEl) => {
dialogVisible.value = false;
if (!formEl) return;
formEl.resetFields();
};
const emit = defineEmits(["resetData"]);
const reset = () => {
ruleForm.account_name = props.msg.account_name;
ruleForm.user_name = props.msg.user_name;
ruleForm.phone = props.msg.phone;
if (sideMark == "C") {
ruleForm.gender = props.msg.gender ? "1" : "0";
ruleForm.age = props.msg.age;
ruleForm.email = props.msg.email;
ruleForm.address = props.msg.address;
ruleForm.post_code = props.msg.post_code;
}
};
const editUser = async (formEl) => {
if (!formEl) return;
await formEl.validate((valid) => {
if (valid) {
dialogVisible.value = false;
const data = {
userMsg: ruleForm,
uid: localStorage.data,
};
$http.post($url.editUser, data).then((res) => {
if (res.data.code === 200) {
ElMessage({
message: "用户信息修改成功!",
type: "success",
});
emit("resetData");
} else {
ElMessage.error("用户信息修改失败!");
}
});
} else {
ElMessage.error("提交失败!");
}
});
};
defineExpose({ dialogVisible, reset });
watch(ruleForm, (value) => {
if (dialogVisible.value) {
userManagerActive.dialogVisible = dialogVisible.value;
userManagerActive.ruleForm.account_name = encrypt(value.account_name);
userManagerActive.ruleForm.user_name = encrypt(value.user_name);
userManagerActive.ruleForm.phone = encrypt(value.phone);
if (sideMark == "C") {
userManagerActive.ruleForm.gender = encrypt(value.gender);
userManagerActive.ruleForm.age = encrypt(value.age.toString());
userManagerActive.ruleForm.email = encrypt(value.email);
userManagerActive.ruleForm.address = encrypt(value.address);
userManagerActive.ruleForm.post_code = encrypt(value.post_code);
}
}
});
watch(dialogVisible, (value) => {
if (!value) {
userManagerActive.dialogVisible = value;
userManagerActive.ruleForm = {};
}
});
onMounted(() => {
dialogVisible.value = userManagerActive.dialogVisible;
if (localStorage.data) {
let decryptData = decrypt(localStorage.data);
sideMark = decryptData[decryptData.length - 1];
}
if (sideMark == "C") {
acabled.value = false;
ruleForm.gender = "";
ruleForm.age = "";
ruleForm.email = "";
ruleForm.address = "";
ruleForm.post_code = "";
rules.account_name[0].required = true;
rules.phone[0].required = true;
rules.age = [];
rules.email = [{ validator: validateEmail }];
rules.address = [];
rules.post_code = [
{ min: 6, max: 6, message: "输入长度应当为 6 位", trigger: "change" },
{ validator: validateCode },
];
}
if (userManagerActive.dialogVisible) {
ruleForm.account_name = decrypt(userManagerActive.ruleForm.account_name);
ruleForm.user_name = decrypt(userManagerActive.ruleForm.user_name);
ruleForm.phone = decrypt(userManagerActive.ruleForm.phone);
if (sideMark == "C") {
ruleForm.gender = decrypt(userManagerActive.ruleForm.gender);
ruleForm.age = decrypt(userManagerActive.ruleForm.age);
ruleForm.email = decrypt(userManagerActive.ruleForm.email);
ruleForm.address = decrypt(userManagerActive.ruleForm.address);
ruleForm.post_code = decrypt(userManagerActive.ruleForm.post_code);
}
}
});
</script>
<style scoped></style>

View File

@@ -0,0 +1,54 @@
<template>
<el-dialog
v-model="dialogVisible"
:show-close="false"
style="width: 80%"
center
align-center
>
<template #header="{ close, titleId, titleClass }">
<div
style="font-size: 2rem"
class="w-full font-bold"
:id="titleId"
:class="titleClass"
>
数据展示
</div>
<div
@click="close"
class="bg-gray-400 hover:bg-red-600 text-3xl absolute top-0 right-0 cursor-pointer flex justify-center items-center"
>
<el-icon>
<CloseBold />
</el-icon>
</div>
</template>
<el-text class="block text-center">
{{ dialogMsg }}
</el-text>
<template #footer>
<div class="dialog-footer">
<el-button style="font-weight: bold" type="primary" @click="resetForm">
返回
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { ref } from "vue";
import { CloseBold } from "@element-plus/icons-vue";
const dialogMsg = ref();
const dialogVisible = ref(false);
const resetForm = () => {
dialogVisible.value = false;
};
defineExpose({ dialogMsg, dialogVisible });
</script>
<style scoped></style>

View File

@@ -0,0 +1,140 @@
<template>
<el-dialog
v-model="operateDialogVisible"
:show-close="false"
style="width: 80%"
center
align-center
:before-close="handleClose"
>
<template #header="{ close, titleId, titleClass }">
<div
style="font-size: 2rem"
class="w-full font-bold"
:id="titleId"
:class="titleClass"
>
{{ operateLabel }}
</div>
<div
@click="close"
class="bg-gray-400 hover:bg-red-600 text-3xl absolute top-0 right-0 cursor-pointer flex justify-center items-center"
>
<el-icon>
<CloseBold />
</el-icon>
</div>
</template>
<el-form
ref="ruleFormRef"
style="max-width: 600px"
:model="ruleForm"
:rules="rules"
label-width="auto"
size="default"
status-icon
class="mx-auto"
>
<el-form-item class="mx-1 font-bold" label="单位用户名" prop="account_name">
<el-input
disabled="false"
v-model="ruleForm.account_name"
/>
</el-form-item>
<el-form-item class="mx-1 font-bold" label="用户姓名" prop="user_name">
<el-input
clearable
placeholder="请输入用户姓名"
maxlength="6"
autocomplete="off"
v-model="ruleForm.user_name"
@input="filter(ruleForm.user_name, 'user_name')"
/>
</el-form-item>
<el-form-item class="mx-1 font-bold" label="用户手机号码" prop="phone">
<el-input
clearable
placeholder="请输入用户手机号码"
maxlength="11"
autocomplete="off"
v-model="ruleForm.phone"
@input="filter(ruleForm.phone, 'phone')"
/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button style="font-weight: bold" @click="resetCheckForm(ruleFormRef)">
取消
</el-button>
<el-button style="font-weight: bold" type="primary" @click="confirm(ruleFormRef)">
确定
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { ref, reactive } from "vue";
import { CloseBold } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
const operateDialogVisible = ref(false);
const ruleFormRef = ref(null);
const formElClone = ref(null);
const ruleForm = reactive({
account_name: "",
user_name: "",
phone: ""
});
const originMsg = reactive({});
const rules = reactive({});
const props = defineProps({
operateLabel: String,
});
const emit = defineEmits(["requestOperate"]);
const filter = (value, label) => {
if (/[=\s"'+-]/.test(value)) {
if (ruleForm[label]) {
ruleForm[label] = value.replace(/[=\s"'+-]/g, "");
}
}
};
const handleClose = () => {
operateDialogVisible.value = false;
if (!ruleFormRef) return;
formElClone.value = ruleFormRef.value;
formElClone.value.resetFields();
};
const resetCheckForm = (formEl) => {
operateDialogVisible.value = false;
if (!formEl) return;
formEl.resetFields();
};
const confirm = async (formEl) => {
if (!formEl) return;
await formEl.validate((valid) => {
if (valid) {
emit("requestOperate");
operateDialogVisible.value = false;
formEl.resetFields();
} else {
ElMessage.error("提交失败!");
}
});
};
defineExpose({ operateDialogVisible, ruleForm, originMsg });
</script>
<style scoped></style>

View File

@@ -0,0 +1,146 @@
<template>
<el-dialog
v-model="operateDialogVisible"
:show-close="false"
style="width: 80%"
center
align-center
:before-close="handleClose"
>
<template #header="{ close, titleId, titleClass }">
<div
style="font-size: 2rem"
class="w-full font-bold"
:id="titleId"
:class="titleClass"
>
{{ operateLabel }}
</div>
<div
@click="close"
class="bg-gray-400 hover:bg-red-600 text-3xl absolute top-0 right-0 cursor-pointer flex justify-center items-center"
>
<el-icon>
<CloseBold />
</el-icon>
</div>
</template>
<el-form
ref="ruleFormRef"
style="max-width: 600px"
:model="ruleForm"
:rules="rules"
label-width="auto"
size="default"
status-icon
class="mx-auto"
>
<el-form-item
v-if="operateLabel == '重置密码' || operateLabel == '重置账号'"
class="mx-1 font-bold"
label="重置后的默认密码"
prop="defaultPassword"
>
<el-input
clearable
placeholder="请设置重置后的默认密码"
maxlength="16"
autocomplete="off"
v-model="ruleForm.defaultPassword"
@input="filter(ruleForm.defaultPassword, 'defaultPassword')"
oncut="return false"
onpaste="return false"
oncopy="return false"
/>
</el-form-item>
<el-form-item class="mx-1 font-bold" label="密码" prop="password">
<el-input
clearable
placeholder="请输入密码"
type="password"
show-password
maxlength="16"
autocomplete="off"
v-model="ruleForm.password"
@input="filter(ruleForm.password, 'password')"
oncut="return false"
onpaste="return false"
oncopy="return false"
/>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button style="font-weight: bold" @click="resetCheckForm(ruleFormRef)">
取消
</el-button>
<el-button style="font-weight: bold" type="primary" @click="confirm(ruleFormRef)">
确定
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { ref, reactive } from "vue";
import { CloseBold } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
const operateDialogVisible = ref(false);
const ruleFormRef = ref(null);
const formElClone = ref(null);
const ruleForm = reactive({
defaultPassword: "123456",
password: "",
});
const rules = reactive({
password: [{ required: true, message: "请输入密码", trigger: "blur" }],
});
const props = defineProps({
operateLabel: String,
});
const emit = defineEmits(["requestOperate"]);
const filter = (value, label) => {
if (/[=\s"'+-]/.test(value)) {
if (ruleForm[label]) {
ruleForm[label] = value.replace(/[=\s"'+-]/g, "");
}
}
};
const handleClose = () => {
operateDialogVisible.value = false;
if (!ruleFormRef) return;
formElClone.value = ruleFormRef.value;
formElClone.value.resetFields();
};
const resetCheckForm = (formEl) => {
operateDialogVisible.value = false;
if (!formEl) return;
formEl.resetFields();
};
const confirm = async (formEl) => {
if (!formEl) return;
await formEl.validate((valid) => {
if (valid) {
emit("requestOperate");
operateDialogVisible.value = false;
formEl.resetFields();
} else {
ElMessage.error("提交失败!");
}
});
};
defineExpose({ operateDialogVisible, ruleForm });
</script>
<style scoped></style>

View File

@@ -0,0 +1,409 @@
<template>
<div class="m-2 font-bold flex">
<div style="width: 5%" class="flex justify-center items-center">
<el-text class="mx-1 font-bold"> 挂载点名称 : </el-text>
</div>
<el-input
v-model="mountpoint"
class="flex-1"
placeholder="请从下方的挂载点列表中选择要连接的挂载点名称"
clearable
disabled="true"
/>
<div style="width: 1%"></div>
<div style="width: 5%" class="flex justify-center items-center">
<el-text class="mx-1 font-bold"> 挂载点密码 : </el-text>
</div>
<el-input
v-model="password"
class="flex-1"
placeholder="请输入挂载点密码"
minlength="8"
maxlength="16"
clearable
@input="handleInputPassword(password)"
/>
<div style="width: 20%" class="flex justify-center items-center">
<el-button
style="font-weight: bold; width: 40%; height: 100%"
type="primary"
size="small"
:disabled="allowConnect"
@click="connect"
>
设置并监听
</el-button>
<el-button
style="font-weight: bold; width: 40%; height: 100%"
type="primary"
size="small"
@click="clear"
>
清除
</el-button>
</div>
</div>
<div class="m-2 font-bold flex justify-center">
<div style="width: 1%"></div>
<el-text class="mx-1 font-bold" style="color: red">
密码设置规则 : 密码长度应处于 8-16
个字符之间,仅支持大小写英文字母:A-Z、a-z,阿拉伯数字:0-9,禁止使用除此之外的任何字符。</el-text
>
<div class="flex-1"></div>
</div>
<div class="m-2 font-bold flex justify-center">
<div style="width: 1%"></div>
<el-text class="mx-1 font-bold"> 已连接到挂载点 </el-text>
<el-text class="mx-1 font-bold" style="color: #13ce66"> &nbsp;{{ mountpoint == '' ? '-' : mountpoint }}&nbsp; </el-text>
<el-text class="mx-1 font-bold"> 的客户端数量:&nbsp; </el-text>
<el-text class="mx-1 font-bold" style="color: #13ce66"> {{ mountpointSize }} </el-text>
<div class="flex-1"></div>
</div>
<MountpointList ref="MountpointListRef" @selectMountpoint="selectMountpoint" />
<el-card class="box-card">
<template #header>
<div class="card-header mb-4">
<span>消息队列</span>
</div>
<div class="flex justify-center items-center">
<div class="flex-1" style="font-weight: bold; width: 40%; height: 100%">
<el-switch
v-model="dataSwitch"
inline-prompt
size="large"
width="100%"
style="
width: 98%;
--el-switch-on-color: #13ce66;
--el-switch-off-color: #ff4949;
"
active-text="转换数据"
inactive-text="原始数据"
/>
</div>
<el-button
style="font-weight: bold; width: 40%; height: 100%"
class="flex-1"
type="primary"
@click="clearMsg"
>
清除消息队列
</el-button>
<el-button
style="font-weight: bold; width: 40%; height: 100%"
class="flex-1"
type="primary"
@click="disconnect"
>
断开连接
</el-button>
</div>
</template>
<el-scrollbar height="20rem">
<div class="m-2 font-bold">
<el-text
v-for="(data, index) in msgList"
:key="Math.random()"
class="mx-1 font-bold flex justify-between items-center"
:style="
index % 2 == 0
? 'margin-bottom: 0.5rem; background-color: rgba(19,206,102,0.2);'
: 'margin-bottom: 0.5rem; background-color: rgba(255,73,73,0.2);'
"
>
{{ getTime(new Date(data.time)) + " | "}}
{{ dataSwitch ? data.msg.toString() : formatAsExample(data.msg) }}
<el-button
style="font-weight: bold; width: 20%; height: 100%"
type="primary"
@click="saveMsg(data.msg)"
>
保存
</el-button>
</el-text>
</div>
</el-scrollbar>
</el-card>
<el-card class="box-card">
<template #header>
<div class="card-header mb-4">
<span>源列表</span>
</div>
<div class="flex justify-center items-center">
<el-button
style="font-weight: bold; width: 10%; height: 100%"
class="flex-1"
type="primary"
@click="getSources"
>
获取源列表
</el-button>
<el-button
style="font-weight: bold; width: 40%; height: 100%"
class="flex-1"
type="primary"
@click="clearSources"
>
清除源列表
</el-button>
</div>
</template>
<el-scrollbar height="10rem">
<div class="m-2 font-bold">
<el-text
v-for="(data, index) in sourceTable"
:key="Math.random()"
class="mx-1 font-bold flex justify-between items-center"
:style="
index % 2 == 0
? 'margin-bottom: 0.5rem; background-color: rgba(19,206,102,0.2);'
: 'margin-bottom: 0.5rem; background-color: rgba(255,73,73,0.2);'
"
>
{{ data }}
</el-text>
</div>
</el-scrollbar>
</el-card>
</template>
<script setup>
import { ref, watch, onUnmounted } from "vue";
import { ElMessage } from "element-plus";
import NTRIPCaster from "../../api/ntrip";
import moment from "moment";
import $http from "../../api/index";
import $url from "../../api/url";
import MountpointList from "../features/mountpointList.vue";
const timeout = ref(null);
const mountpoint = ref("");
const mountpointSize = ref(0);
const password = ref("");
const allow = ref(false);
const allowConnect = ref(true);
const MountpointListRef = ref(null);
const isConnected = ref(false);
const dataSwitch = ref(false);
const msgList = ref([]);
const sourceTable = ref([]);
const getTime = (time) => {
return moment.utc(time).add(8, "hours").format("YYYY-MM-DD-HH:mm:ss");
};
const handleInputPassword = (name) => {
clearTimeout(timeout.value);
timeout.value = setTimeout(() => {
const isValid = /^[a-zA-Z0-9].{8,16}$/.test(name);
allow.value = !isValid;
}, 100);
};
const formatAsExample = (bytes) => {
// 基本解析
const messageType = ((bytes[3] & 0xf0) << 4) | bytes[4];
const stationId = ((bytes[4] & 0x0f) << 8) | bytes[5];
// 硬编码为示例格式
return `RTCM3:[ MsgID ${messageType} Receiver:RefS ${stationId} SetupID: ${
stationId === 0 ? "0" : "?"
}, |A 0 SN 0 | R Type 7 CHC SAX FW 17 3.0.40.5_95f6ec7c SN ${stationId} 3253016001ET ]`;
};
const clear = () => {
mountpoint.value = "";
MountpointListRef.value.resetSelect();
};
const getMountpoint = () => {
MountpointListRef.value.getMountpoint();
};
const setPassword = () => {
const data = {
mountpoint: mountpoint.value,
password: password.value,
};
$http.post($url.setPassword, data).then((res) => {
if (res.data.code === 200) {
ElMessage({
message: "ntrip-clinet密码设置成功!",
type: "success",
});
} else {
ElMessage.error("ntrip-clinet密码设置失败");
}
});
};
// NTRIP 客户端 (Client)
const caster = new NTRIPCaster("http://127.0.0.1:2101");
const getSources = async () => {
const sources = await caster.getSourceTable();
sourceTable.value = sources;
};
const clearSources = () => {
sourceTable.value = [];
};
const sizeConnect = async () => {
try {
const size = await caster.getSize(mountpoint.value);
while (true) {
const { done, value } = await size.stream.read();
if (done) return;
mountpointSize.value = String.fromCharCode(value);
}
} catch (error) {
setTimeout(() => {
sizeConnect();
}, 3000);
}
};
const sizeDisconnect = async () => {
if (caster) {
mountpointSize.value = 0;
caster.sizeDisconnect();
}
};
const selectMountpoint = async (selectedMountpoint) => {
mountpoint.value = selectedMountpoint;
await sizeDisconnect();
await sizeConnect();
};
const connect = async () => {
isConnected.value = true;
try {
setPassword();
// 连接差分数据流
const connection = await caster.connect(mountpoint.value);
while (true) {
const { done, value } = await connection.stream.read();
if (done) break;
const data = {
msg: value,
time: new Date(),
};
msgList.value.push(data);
}
} catch (error) {
isConnected.value = false;
switch (error.message) {
case "401":
ElMessage.error("NTRIP 连接失败: 认证失败");
ElMessage({
message: "即将刷新页面以重新获取认证凭证",
type: "warning",
});
setTimeout(() => {
window.location.reload();
}, 1000);
return;
case "404":
ElMessage.error("NTRIP 连接失败: 挂载点不存在");
ElMessage({
message: "请联系管理员连接此挂载点数据源",
type: "warning",
});
return;
case "403":
return ElMessage.error("NTRIP 连接失败: 无访问权限");
case "400":
return ElMessage.error("NTRIP 连接失败: 错误的请求");
default:
setTimeout(() => {
connect();
}, 3000);
return ElMessage.error(`NTRIP 连接失败: ${error.message}`);
}
}
};
const disconnect = async () => {
if (caster) {
caster.disconnect();
}
};
const saveMsg = (msg) => {
const data = {
mountpoint: mountpoint.value,
original_msg: msg.toString(),
};
$http.post($url.saveMsg, data).then((res) => {
if (res.data.code === 200) {
ElMessage({
message: "保存成功!",
type: "success",
});
} else {
ElMessage.error("保存失败");
}
});
};
const clearMsg = () => {
msgList.value = [];
};
defineExpose({ getMountpoint });
watch(mountpoint, (value) => {
if (value == "") {
allowConnect.value = true;
} else {
allowConnect.value = false;
clearTimeout(timeout.value);
timeout.value = setTimeout(() => {
const data = { mountpoint: mountpoint.value };
$http.post($url.viewPassword, data).then((res) => {
if (res.data.code === 200) {
const base = atob(res.data.base);
password.value = base.split(":")[1];
ElMessage({
message: "ntrip-clinet密码获取成功!",
type: "success",
});
} else {
ElMessage.error("ntrip-clinet密码获取失败");
}
});
}, 100);
}
});
watch(isConnected, async (value) => {
if (!value) {
// await connect();
}
});
onUnmounted(async () => {
await disconnect();
await sizeDisconnect();
});
</script>
<style scoped>
.box-card {
flex: 1;
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.card-header {
text-align: center;
font-weight: bold;
}
</style>

View File

@@ -0,0 +1,78 @@
<template>
<el-table-column min-width="180" width="180" :column-key="holderText.label">
<template #header>
<div class="flex justify-between items-center">
<el-button
v-if="operateType !== 'edit'"
style="margin-left: 0px;font-weight: bold;"
:type="multipleColor"
:size="multipleSize"
:plain="multiplePlain"
@click="operateModel('multiple')"
>
{{ holderText.multiple }}
</el-button>
<el-button
v-if="operateType !== 'edit'"
style="margin-left: 0px;font-weight: bold;"
:type="allColor"
:size="allSize"
:plain="allPlain"
@click="operateModel('all')"
>
{{ holderText.all }}
</el-button>
</div>
<div>
<el-text class="mx-1"> {{ holderText.label }} </el-text>
</div>
</template>
<template #default="scope">
<el-button
style="font-weight: bold;"
:type="singleColor"
:size="singleSize"
:plain="singlePlain"
@click="operateMsg(scope.$index, scope.row)"
>
{{ holderText.single }}
</el-button>
</template>
</el-table-column>
</template>
<script setup>
import { ref } from "vue";
const model = ref("single");
const props = defineProps({
holderText: Object,
singleColor: String,
multipleColor: String,
allColor: String,
singleSize: String,
multipleSize: String,
allSize: String,
singlePlain: Boolean,
multiplePlain: Boolean,
allPlain: Boolean,
operateType: String
});
const emit = defineEmits(["showComfirmDialog"]);
const operateModel = (modelType) => {
model.value = modelType;
emit("showComfirmDialog", { operateType: props.operateType });
};
const operateMsg = (index, row) => {
model.value = "single";
emit("showComfirmDialog", { data: row, operateType: props.operateType });
};
defineExpose({ model });
</script>
<style scoped></style>

View File

@@ -0,0 +1,113 @@
<template>
<div
class="w-full h-8 flex flex-col justify-center items-center relative top-0 right-0"
>
<div class="absolute top-0 left-0" style="font-size: 1rem">
<el-dropdown>
<el-button style="font-weight: bold" type="warning" size="small" plain>
表格工具
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item :style="searchBgColor" @click="showTools('search')">
<el-link style="font-weight: bold" :underline="false" type="success">
搜索工具
<el-icon v-show="showSearch" class="el-icon--right">
<View />
</el-icon>
</el-link>
</el-dropdown-item>
<el-dropdown-item :style="sortBgColor" @click="showTools('sort')">
<el-link style="font-weight: bold" :underline="false" type="success">
排序工具
<el-icon v-show="showSort" class="el-icon--right">
<View />
</el-icon>
</el-link>
</el-dropdown-item>
<el-dropdown-item :style="operationBgColor" @click="showTools('operation')">
<el-link style="font-weight: bold" :underline="false" type="success">
操作工具
<el-icon v-show="showOperation" class="el-icon--right">
<View />
</el-icon>
</el-link>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
</div>
</template>
<script setup>
import { ref, watch, onMounted } from "vue";
import { ArrowDown, View } from "@element-plus/icons-vue";
const showSearch = ref(false);
const searchBgColor = ref("background-color: rgba(16, 184, 130, 0.1);");
const showSort = ref(false);
const sortBgColor = ref("background-color: rgba(16, 184, 130, 0.1);");
const showOperation = ref(false);
const operationBgColor = ref("background-color: rgba(16, 184, 130, 0.1);");
const props = defineProps({
dialogObj: Object,
});
const emit = defineEmits(["showTools"]);
const showTools = (operateType) => {
switch (operateType) {
case "search":
emit("showTools", "search");
showSearch.value = !showSearch.value;
break;
case "sort":
emit("showTools", "sort");
showSort.value = !showSort.value;
break;
case "operation":
emit("showTools", "operation");
showOperation.value = !showOperation.value;
break;
default:
break;
}
};
watch(showSearch, (value) => {
if (value) {
searchBgColor.value = "background-color: rgba(16, 184, 130, 0.2);";
} else {
searchBgColor.value = "background-color: rgba(16, 184, 130, 0.1);";
}
});
watch(showSort, (value) => {
if (value) {
sortBgColor.value = "background-color: rgba(16, 184, 130, 0.2);";
} else {
sortBgColor.value = "background-color: rgba(16, 184, 130, 0.1);";
}
});
watch(showOperation, (value) => {
if (value) {
operationBgColor.value = "background-color: rgba(16, 184, 130, 0.2);";
} else {
operationBgColor.value = "background-color: rgba(16, 184, 130, 0.1);";
}
});
onMounted(() => {
showSearch.value = props.dialogObj.dialogSearchVisible;
showSort.value = props.dialogObj.dialogSortVisible;
showOperation.value = props.dialogObj.dialogOperationVisible;
});
</script>
<style scoped></style>

View File

@@ -0,0 +1,16 @@
<template>
<div class="w-full flex justify-center items-center flex-wrap">
<el-text class="mx-1 font-bold">
ntrip通信服务平台
</el-text>
&nbsp;&nbsp;
&nbsp;&nbsp;
<el-text class="mx-1 font-bold">
技术支持电话:400-166-5286
</el-text>
</div>
</template>
<script setup></script>
<style scoped></style>

View File

@@ -0,0 +1,151 @@
<template>
<div class="flex grow justify-around items-center">
<el-button style="font-weight: bold" type="warning" @click="guide">
帮助
<el-icon class="el-icon--right">
<QuestionFilled />
</el-icon>
</el-button>
<el-dropdown class="flex flex-auto justify-end items-center">
<el-button style="font-weight: bold" type="success" size="small">
账户名: {{ userMsg.account_name }}
<el-icon class="el-icon--right">
<arrow-down />
</el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
style="background-color: rgba(56, 188, 248, 0.1)"
@click="editUser"
>
<el-link style="font-weight: bold" :underline="false" type="primary">
账户管理
</el-link>
</el-dropdown-item>
<el-dropdown-item
style="background-color: rgba(56, 188, 248, 0.1)"
@click="changePassword"
>
<el-link style="font-weight: bold" :underline="false" type="primary">
密码管理
</el-link>
</el-dropdown-item>
<el-dropdown-item
style="background-color: rgba(240, 70, 70, 0.1)"
@click="unLogin"
>
<el-link style="font-weight: bold" :underline="false" type="danger">
退出登录
</el-link>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<Guide ref="guideRef" />
<UserManager @resetData="resetData" ref="userManagerRef" :msg="userMsg" />
<PasswordManager ref="passwordManagerRef" />
</template>
<script setup>
import { ref, watch, onMounted } from "vue";
import { ElMessage } from "element-plus";
import { QuestionFilled, ArrowDown } from "@element-plus/icons-vue";
import $http from "../../api/index";
import $url from "../../api/url";
import Guide from "../guide/guide.vue";
import UserManager from "../manager/userManager.vue";
import PasswordManager from "../manager/changePassword.vue";
const emit = defineEmits(["allowManager"]);
const manager = ref(false);
const guideRef = ref(null);
const userManagerRef = ref(null);
const userMsg = ref({});
const passwordManagerRef = ref(null);
const guide = () => {
guideRef.value.dialogVisible = true;
};
const editUser = () => {
if (userManagerRef.value) {
userManagerRef.value.dialogVisible = true;
userManagerRef.value.reset();
}
};
const changePassword = () => {
if (passwordManagerRef.value) {
passwordManagerRef.value.dialogVisible = true;
}
};
const unLogin = () => {
const data = {
uid: localStorage.data,
access_token: localStorage.access_token,
refresh_token: localStorage.refresh_token,
};
$http.post($url.unLogin, data).then((res) => {
localStorage.clear();
window.location.reload();
});
};
const resetData = () => {
const data = {
uid: localStorage.data,
};
$http.post($url.getUser, data).then((res) => {
if (res.data.code === 200) {
ElMessage({
message: "获取用户信息成功!",
type: "success",
});
userMsg.value = res.data.userData;
if (res.data.userData.role == 1) {
manager.value = true;
guideRef.value.manager = true;
}
if (res.data.userData.status == 0) {
ElMessage({
showClose: true,
message: "您的账号正在使用默认密码,请尽快进行更改!",
type: "warning",
});
}
if (res.data.userData.status == 2) {
ElMessage({
showClose: true,
message: "您的账号已被封禁,请联系管理员进行申诉!",
type: "error",
});
}
} else {
console.log("404");
ElMessage.error("获取用户信息失败!");
}
});
};
defineExpose({ manager });
watch(manager, (value) => {
if (value) {
emit("allowManager");
}
});
onMounted(() => {
setTimeout(() => {
resetData();
}, 200);
});
</script>
<style scoped></style>

View File

@@ -0,0 +1,148 @@
<template>
<div style="height: 100%">
<div class="flex flex-col justify-center items-center">
<el-text style="font-size: 2.4rem" class="mx-1 font-bold" size="large">
ntrip通信服务平台
</el-text>
</div>
<el-menu
class="flex flex-wrap font-bold"
:default-active="activeIndex"
@select="handleSelect"
>
<el-menu-item
index="1"
:style="activeIndex == '1' ? activeStyle : indexStyle"
class="border-x-4 border-slate-300"
>
实时消息
</el-menu-item>
<el-menu-item
index="2"
:style="activeIndex == '2' ? activeStyle : indexStyle"
class="border-x-4 border-slate-300"
>
查看消息
</el-menu-item>
<el-menu-item
index="3"
:style="activeIndex == '3' ? activeStyle : indexStyle"
class="border-r-4 border-slate-300"
>
汉字编码
</el-menu-item>
<el-menu-item
index="4"
:style="activeIndex == '4' ? activeStyle : indexStyle"
class="border-r-4 border-slate-300"
>
异或校验
</el-menu-item>
<el-menu-item
v-if="manager"
index="5"
:style="activeIndex == '5' ? activeStyle : indexStyle"
class="border-r-4 border-slate-300"
>
单位管理
</el-menu-item>
<el-menu-item
v-if="manager"
index="6"
:style="activeIndex == '6' ? activeStyle : indexStyle"
class="border-r-4 border-slate-300"
>
挂载点设置
</el-menu-item>
</el-menu>
<el-card>
<div v-show="activeIndex === '1'">
<RealTimeMsg ref="realTimeMsgRef" />
</div>
<div v-show="activeIndex === '2'">
<HistoryMsg ref="historyMsgRef" @getRole="getRole" />
</div>
<div v-show="activeIndex === '3'">
<el-button
style="font-weight: bold"
type="primary"
@click="showModel"
class="mr-4"
>
转换模式
</el-button>
<CodeHelp />
<Encode v-if="showCode" />
<Decode v-if="!showCode" />
</div>
<div v-show="activeIndex === '4'">
<CalculationXOR />
</div>
<div v-show="activeIndex === '5'">
<BsideManager ref="bsideManagerRef" />
</div>
<div v-show="activeIndex === '6'">
<SetMountpoint ref="setMountpointRef" @getMountpoint="getMountpoint" />
</div>
</el-card>
</div>
</template>
<script setup>
import { ref, watch, onMounted } from "vue";
import RealTimeMsg from "../operate/realTimeMsg.vue";
import HistoryMsg from "../features/historyMsg.vue";
import CodeHelp from "../transcoding/codeHelp.vue";
import Encode from "../transcoding/encode.vue";
import Decode from "../transcoding/decode.vue";
import CalculationXOR from "../calculationXOR/calculationXOR.vue";
import BsideManager from "../features/bsideManager.vue";
import SetMountpoint from "../manager/setMountpoint.vue";
import historyMsgActiveStore from "../../stores/historyMsg";
const historyMsgActive = historyMsgActiveStore();
const activeIndex = ref("1");
const indexStyle = "background-color: rgb(241 245 249);";
const activeStyle = "background-color: rgb(207 250 254);";
const manager = ref(false);
const showCode = ref(true);
const realTimeMsgRef = ref(null);
const historyMsgRef = ref(null);
const bsideManagerRef = ref(null);
const setMountpointRef = ref(null);
const handleSelect = (key, keyPath) => {
activeIndex.value = key;
};
const showModel = () => {
showCode.value = !showCode.value;
};
const historyMsg = () => {
historyMsgRef.value.historyMsg();
};
const getRole = () => {
historyMsgRef.value.manager = manager.value;
}
const getMountpoint = () => {
realTimeMsgRef.value.getMountpoint();
};
defineExpose({ manager, historyMsg });
watch(activeIndex, (value) => {
historyMsgActive.activeIndex = value;
});
onMounted(() => {
activeIndex.value = historyMsgActive.activeIndex;
});
</script>
<style scoped></style>

View File

@@ -0,0 +1,94 @@
<template>
<div>
<el-date-picker
v-model="searchMsg"
type="datetimerange"
size="small"
range-separator="到"
start-placeholder="开始时间"
end-placeholder="结束时间"
@change="search"
style="width: 100%; font-size: 1rem"
:popper-class="DateTimePicker"
@visible-change="handleVisibleChange"
/>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
const searchMsg = ref(null);
const props = defineProps({
DateTimePicker: String,
});
const emit = defineEmits(["search"]);
const search = () => {
emit("search");
}
const handleVisibleChange = () => {
setTimeout(() => {
let changeNode = window.document.getElementsByClassName(props.DateTimePicker)[0];
let Triangles = changeNode.children[1];
Triangles.style.display = "none";
let elppNode = changeNode.children[0];
elppNode.style.width = "30rem";
elppNode.style.backgroundColor = "#aaffff";
let elppbwNode = elppNode.children[0];
elppbwNode.style.width = "30rem";
let elppfNode = elppNode.children[1];
elppfNode.style.width = "30rem";
elppfNode.style.backgroundColor = "#aaffff";
let elppbNode = elppbwNode.children[0];
elppbNode.style.width = "30rem";
let elppbCNode = elppbNode.children;
elppbCNode[0].style.width = "30rem";
let leftDateSelectNode = elppbCNode[0].children[0].children[1];
let leftTimeSelectNode = leftDateSelectNode.children[0];
leftTimeSelectNode.addEventListener("click", function () {
let leftTimeTableNode = leftDateSelectNode.children[1];
leftTimeTableNode.style.width = "10rem";
leftTimeTableNode.style.fontSize = "1rem";
leftTimeTableNode.style.transform = "translate(-20%, 0)";
});
let rightDateSelectNode = elppbCNode[0].children[2].children[1];
let rightTimeSelectNode = rightDateSelectNode.children[0];
rightTimeSelectNode.addEventListener("click", function () {
let rightTimeTableNode = rightDateSelectNode.children[1];
rightTimeTableNode.style.width = "10rem";
rightTimeTableNode.style.fontSize = "1rem";
rightTimeTableNode.style.transform = "translate(-20%, 0)";
});
elppbCNode[1].style.width = "15rem";
let elppbCFCNode = elppbCNode[1].children;
elppbCFCNode[0].style.fontSize = "1rem";
let elppbCFCCNode = elppbCFCNode[0].children;
elppbCFCCNode[0].style.margin = "0";
elppbCFCCNode[1].style.margin = "0";
elppbCFCCNode[2].style.fontSize = "1rem";
elppbCFCCNode[2].style.margin = "0";
elppbCFCNode[1].style.fontSize = "1rem";
elppbCNode[2].style.width = "15rem";
let elppbCSCNode = elppbCNode[2].children;
elppbCSCNode[0].style.fontSize = "1rem";
let elppbCSCCNode = elppbCSCNode[0].children;
elppbCSCCNode[0].style.margin = "0";
elppbCSCCNode[1].style.margin = "0";
elppbCSCCNode[2].style.fontSize = "1rem";
elppbCSCCNode[2].style.margin = "0";
elppbCSCNode[1].style.fontSize = "1rem";
}, 0);
};
defineExpose({ searchMsg });
onMounted(() => {
handleVisibleChange();
});
</script>
<style scoped></style>

View File

@@ -0,0 +1,57 @@
<template>
<div class="flex justify-center my-2">
<el-pagination
class="flex justify-center flex-wrap"
v-model:current-page="offset"
v-model:page-size="limit"
:page-sizes="pageSizes"
size="small"
background
layout="total, sizes, prev, pager, next, jumper"
:total="dataCount"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
const offset = ref(1);
const limit = ref(10);
const dataCount = ref(0);
const pageSizes = [10, 20, 50, 100];
const props = defineProps({
active: Object,
});
const emit = defineEmits(["search", "sizeChangeBack"]);
const handleSizeChange = (val) => {
offset.value = 1;
props.active.offset = offset.value;
emit("sizeChangeBack", val);
limit.value = val;
props.active.limit = limit.value;
emit("search");
};
const handleCurrentChange = (val) => {
offset.value = val;
props.active.offset = offset.value;
emit("search");
};
defineExpose({ offset, limit, dataCount, handleCurrentChange });
onMounted(() => {
offset.value = props.active.offset;
limit.value = props.active.limit;
dataCount.value = props.active.dataCount;
emit("search");
});
</script>
<style scoped></style>

View File

@@ -0,0 +1,40 @@
<template>
<div>
<el-tooltip
class="box-item"
effect="dark"
content="输入<span class='text-red-500 font-black'>&nbsp;&nbsp;*空&nbsp;&nbsp;</span>可搜索空数据"
raw-content
placement="top"
>
<el-input
size="small"
:placeholder="holderText"
clearable
v-model="searchMsg"
@blur="search"
style="width: 100%; font-size: 1rem"
/>
</el-tooltip>
</div>
</template>
<script setup>
import { ref } from "vue";
const searchMsg = ref(null);
const props = defineProps({
holderText: String,
});
const emit = defineEmits(["search"]);
const search = () => {
emit("search");
};
defineExpose({ searchMsg });
</script>
<style scoped></style>

View File

@@ -0,0 +1,42 @@
<template>
<div>
<el-select
clearable
size="small"
:placeholder="holderText"
v-model="searchMsg"
@change="search"
style="width: 100%; font-size: 1rem;"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
class="text-center"
:style="item.style"
/>
</el-select>
</div>
</template>
<script setup>
import { ref } from "vue";
const searchMsg = ref(null);
const props = defineProps({
holderText: String,
options: Object
});
const emit = defineEmits(["search"]);
const search = () => {
emit("search");
}
defineExpose({ searchMsg });
</script>
<style scoped></style>

View File

@@ -0,0 +1,32 @@
<template>
<el-switch
size="small"
v-model="searchMsg"
inline-prompt
:active-text="activeText"
:inactive-text="inactiveText"
style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949;"
@change="search"
/>
</template>
<script setup>
import { ref } from "vue";
const searchMsg = ref(null);
const props = defineProps({
activeText: String,
inactiveText: String
});
const emit = defineEmits(["search"]);
const search = () => {
emit("search");
}
defineExpose({ searchMsg });
</script>
<style scoped></style>

View File

@@ -0,0 +1,96 @@
<template>
<el-text class="mx-1 font-bold">
注: 键盘输入的空格、回车、制表符以及普通英文、数字和标点,将自动转为对应的全角字符进行编码,使用时请注意
</el-text>
<el-link
style="font-weight: bold"
:underline="false"
type="primary"
@click="dialogVisible = true"
>
编码科普
</el-link>
<el-dialog
v-model="dialogVisible"
:show-close="false"
style="width: 80%"
center
align-center
>
<template #header="{ close, titleId, titleClass }">
<div
style="font-size: 2rem"
class="w-full font-bold"
:id="titleId"
:class="titleClass"
>
编码帮助
</div>
<div
@click="close"
class="bg-gray-400 hover:bg-red-600 text-3xl absolute top-0 right-0 cursor-pointer flex justify-center items-center"
>
<el-icon>
<CloseBold />
</el-icon>
</div>
</template>
<el-text class="mx-1">
<h1 class="font-bold text-xl">GB2312 编码表</h1>
<br />
GB 2312 标准由中国国家标准总局 1980 年发布,GB 即国标,共收录
6763个汉字,其中一级汉字 3755 个,二级汉字 3008 个。
<br />
<br />
<ul class="list-disc">
<li class="ml-10">
GB 2312的出现,基本满足了汉字的计算机处理需要,它所收录的汉字已经覆盖中国大陆
99.75% 的使用频率。
</li>
<li class="ml-10">
对于人名、古汉语等方面出现的罕用字,GB 2312 不能处理,这导致了后来 GBK 及 GB
18030 汉字字符集的出现。
</li>
</ul>
<br />
GB 2312收录了包括拉丁字母、希腊字母、日文平假名及片假名字母、俄语西里尔字母在内的
682 个全角字符。
<br />
GB 2312 兼容 ASCII 码(0 -
127),之后对任意一个图形字符都采用两个字节表示,高位字节和低位字节都大于127。
<br />
<br />
GB 2312 字符集分成 94
个区,每区有94个位,分别对应第一字节和第二字节,这种表示方式也称为区位码。
<br />
<br />
<ul class="list-disc">
<li class="ml-10">01-09 区为特殊符号</li>
<li class="ml-10">10-15 区为用户自定义符号区(未编码)</li>
<li class="ml-10">16-55 区为一级汉字,按拼音排序,共 3755 个</li>
<li class="ml-10">56-87 区为二级汉字,按部首/笔画排序,共 3008 个</li>
<li class="ml-10">88-94 区为用户自定义汉字区(未编码)</li>
</ul>
</el-text>
<template #footer>
<div class="dialog-footer">
<el-button style="font-weight: bold" type="primary" @click="back">
返回
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { ref } from "vue";
import { CloseBold } from "@element-plus/icons-vue";
const dialogVisible = ref(false);
const back = () => {
dialogVisible.value = false;
};
</script>
<style scoped></style>

View File

@@ -0,0 +1,328 @@
<template>
<el-input
placeholder="请输入编码"
v-model="fontcode"
type="textarea"
resize="none"
:rows="10"
style="box-shadow: 0 0px 20px -10px rgb(0 0 0 / 0.5)"
class="mt-4 mb-4"
/>
<div>
<el-select
v-model="decodeType"
clearable
size="small"
placeholder="选择解码类型"
style="width: 106px"
class="mr-4"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-select
v-model="showType"
clearable
size="small"
placeholder="选择显示类型"
style="width: 106px"
class="mr-4"
>
<el-option
v-for="item in showOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-button style="font-weight: bold;" type="primary" @click="transformcode(fontcode)">
转换编码
</el-button>
<el-button style="font-weight: bold;" type="primary" @click="reset">
清除
</el-button>
</div>
<el-card v-show="showType == 1" class="mt-4 mb-4">
<el-text class="mx-1">
编码(十进制): {{ fontcodedec ? fontcodedec.join(" , ") : "" }}
</el-text>
<br />
<el-text class="mx-1">
编码(十六进制): {{ fontcodehex ? fontcodehex.join(" , ") : "" }}
</el-text>
<br />
<el-text class="mx-1">
编码(二进制): {{ fontcodebin ? fontcodebin.join(" , ") : "" }}
</el-text>
<br />
<el-text v-show="decodeType == 'transcode'" class="mx-1">
汉字: {{ transcode }}
</el-text>
<el-text v-show="decodeType == 'transcodehex'" class="mx-1">
汉字: {{ transcodehex }}
</el-text>
<el-text v-show="decodeType == 'transcodebin'" class="mx-1">
汉字: {{ transcodebin }}
</el-text>
</el-card>
<div class="flex flex-wrap justify-start items-center gap-4 mt-4 mb-4">
<el-card
v-for="(item, index) in chinafont"
v-show="showType == 0"
style="width: 300px"
>
<el-text class="mx-1">
编码(十进制): {{ fontcodedec ? fontcodedec[index] : "" }}
</el-text>
<br />
<el-text class="mx-1">
编码(十六进制): {{ fontcodehex ? fontcodehex[index] : "" }}
</el-text>
<br />
<el-text class="mx-1">
编码(二进制): {{ fontcodebin ? fontcodebin[index] : "" }}
</el-text>
<br />
<el-text v-show="decodeType == 'transcode'" class="mx-1">
汉字: {{ transcode[index] }}
</el-text>
<el-text v-show="decodeType == 'transcodehex'" class="mx-1">
汉字: {{ transcodehex[index] }}
</el-text>
<el-text v-show="decodeType == 'transcodebin'" class="mx-1">
汉字: {{ transcodebin[index] }}
</el-text>
</el-card>
</div>
</template>
<script setup>
import { ref, watch, onMounted } from "vue";
import decodeActiveStore from "../../stores/decode";
import { toFullWidth } from "../../utils/toFullWidth";
import GBK from "gbk.js";
const decodeActive = decodeActiveStore();
const decodeType = ref("transcode");
const showType = ref("1");
const chinafont = ref();
const fontcode = ref();
const fontcodedec = ref();
const fontcodehex = ref();
const fontcodebin = ref();
const transcode = ref();
const transcodehex = ref();
const transcodebin = ref();
const options = [
{
value: "transcode",
label: "十进制",
},
{
value: "transcodehex",
label: "十六进制",
},
{
value: "transcodebin",
label: "二进制",
},
];
const showOptions = [
{
value: "0",
label: "单字",
},
{
value: "1",
label: "连续",
},
];
const decToHex = (dec) => {
let hexArr = [];
dec.forEach((element) => {
let hex = parseInt(element, 10).toString(16).toUpperCase();
if (hex.length == 1) {
hex = "0" + hex;
}
hexArr.push(hex);
});
return hexArr.join(" , ");
};
const decToBinary = (dec) => {
let binaryArr = [];
dec.forEach((element) => {
let binaryString = parseInt(element, 10).toString(2);
while (binaryString.length < 8) {
binaryString = "0" + binaryString;
}
binaryArr.push(binaryString);
});
return binaryArr.join(" , ");
};
const BinaryToDec = (binary) => {
let decArr = [];
binary.forEach((element) => {
let dec = parseInt(element, 2);
decArr.push(dec);
});
return decArr.join(" , ");
};
const BinaryToHex = (binary) => {
let hexArr = [];
binary.forEach((element) => {
let hex = parseInt(element, 2).toString(16).toUpperCase();
if (hex.length == 1) {
hex = "0" + hex;
}
hexArr.push(hex);
});
return hexArr.join(" , ");
};
const transformcode = (fontcode) => {
decodeActive.fontcode = fontcode;
let dec = [], hex = [], bin = [];
fontcode.replace(/\s+/g, '').replace(/,/g, ",").split(",").forEach((element) => {
let newArray;
if(element.length >= 4 && decodeType.value == "transcodehex") {
newArray = element.split("").reduce((acc, curr, index) => {
if (index % 2 === 0 && index + 1 < element.split("").length) {
acc.push([curr, element.split("")[index + 1]].join(""));
}
return acc;
}, []);
if(newArray.length > 2) {
newArray.shift();
newArray = newArray.join(",");
}
} else {
newArray = element;
}
hex.push(parseInt(newArray, 16));
dec.push(newArray);
bin.push(parseInt(newArray, 2));
});
transcode.value = "";
decodeActive.transcode = "";
transcodehex.value = "";
decodeActive.transcodehex = "";
transcodebin.value = "";
decodeActive.transcodebin = "";
switch (decodeType.value) {
case "transcode":
chinafont.value = GBK.decode(dec);
transcode.value = GBK.decode(dec);
decodeActive.transcode = transcode.value;
fontcodedec.value = fontcode.split(",").join(" , ");
decodeActive.fontcodedec = fontcode.split(",").join(" , ");
fontcodehex.value = decToHex(fontcode.split(","));
decodeActive.fontcodehex = decToHex(fontcode.split(","));
fontcodebin.value = decToBinary(fontcode.split(","));
decodeActive.fontcodebin = decToBinary(fontcode.split(","));
break;
case "transcodehex":
chinafont.value = GBK.decode(hex);
transcodehex.value = GBK.decode(hex);
decodeActive.transcodehex = transcodehex.value;
fontcodedec.value = hex.join(" , ");
decodeActive.fontcodedec = hex.join(" , ");
fontcodehex.value = fontcode.split(",").join(" , ");
decodeActive.fontcodehex = fontcode.split(",").join(" , ");
fontcodebin.value = decToBinary(hex);
decodeActive.fontcodebin = decToBinary(hex);
break;
case "transcodebin":
chinafont.value = GBK.decode(bin);
transcodebin.value = GBK.decode(bin);
decodeActive.transcodebin = transcodebin.value;
fontcodedec.value = BinaryToDec(fontcode.split(","));
decodeActive.fontcodedec = BinaryToDec(fontcode.split(","));
fontcodehex.value = BinaryToHex(fontcode.split(","));
decodeActive.fontcodehex = BinaryToHex(fontcode.split(","));
fontcodebin.value = fontcode.split(",").join(" , ");
decodeActive.fontcodebin = fontcode.split(",").join(" , ");
break;
default:
break;
}
let transfont = [];
hex = [], bin = [];
//将半角字符转为全角字符
toFullWidth(chinafont.value).split("").forEach((item) => {
transfont.push(GBK.encode(item).join(" , "));
let hexSingle = [], binSingle = [];
GBK.encode(item).forEach((element) => {
hexSingle.push(element.toString(16).toUpperCase().length < 2 ? "0" + element.toString(16).toUpperCase() : element.toString(16).toUpperCase());
binSingle.push(element.toString(2));
});
hex.push(hexSingle.join(""));
bin.push(binSingle.join(" , "));
});
fontcodedec.value = transfont;
decodeActive.fontcodedec = transfont;
fontcodehex.value = hex;
decodeActive.fontcodehex = hex;
fontcodebin.value = bin;
decodeActive.fontcodebin = bin;
};
const reset = () => {
fontcode.value = "";
fontcodedec.value = "";
fontcodehex.value = "";
fontcodebin.value = "";
transcode.value = "";
transcodehex.value = "";
transcodebin.value = "";
chinafont.value = "";
decodeActive.fontcode = "";
decodeActive.fontcodedec = "";
decodeActive.fontcodehex = "";
decodeActive.fontcodebin = "";
decodeActive.transcode = "";
decodeActive.transcodehex = "";
decodeActive.transcodebin = "";
};
watch(fontcode, (value) => {
decodeActive.fontcode = value;
});
watch(decodeType, (value) => {
decodeActive.decodeType = value;
});
watch(showType, (value) => {
decodeActive.showType = value;
});
onMounted(() => {
decodeType.value = decodeActive.decodeType;
showType.value = decodeActive.showType;
fontcode.value = decodeActive.fontcode;
fontcodedec.value = decodeActive.fontcodedec;
fontcodehex.value = decodeActive.fontcodehex;
fontcodebin.value = decodeActive.fontcodebin;
transcode.value = decodeActive.transcode;
transcodehex.value = decodeActive.transcodehex;
transcodebin.value = decodeActive.transcodebin;
chinafont.value = decodeActive[decodeType.value];
});
</script>
<style scoped></style>

View File

@@ -0,0 +1,159 @@
<template>
<el-input
placeholder="请输入汉字"
v-model="inputfont"
type="textarea"
resize="none"
:rows="10"
style="box-shadow: 0 0px 20px -10px rgb(0 0 0 / 0.5);"
class="mt-4 mb-4"
/>
<div>
<el-select
v-model="showType"
clearable
size="small"
placeholder="选择显示类型"
style="width: 106px"
class="mr-4"
>
<el-option
v-for="item in options"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
<el-button style="font-weight: bold;" type="primary" @click="transformfont(inputfont)">
转换汉字
</el-button>
<el-button style="font-weight: bold;" type="primary" @click="reset">
清除
</el-button>
</div>
<el-card v-show="showType == 1" class="mt-4 mb-4">
<el-text class="mx-1">
汉字: {{ chinafont }}
</el-text>
<br />
<el-text class="mx-1">
编码(十进制): {{ transfont ? transfont.join(" , ") : "" }}
</el-text>
<br />
<el-text class="mx-1">
编码(十六进制): {{ transfonthex ? transfonthex.join(" , ") : "" }}
</el-text>
<br />
<el-text class="mx-1">
编码(二进制): {{ transfontbin ? transfontbin.join(" , ") : "" }}
</el-text>
</el-card>
<div class="flex flex-wrap justify-start items-center gap-4 mt-4 mb-4">
<el-card
v-for="(item, index) in chinafont"
v-show="showType == 0"
style="width: 300px"
>
<el-text class="mx-1">
汉字: {{ item }}
</el-text>
<br />
<el-text class="mx-1">
编码(十进制): {{ transfont[index] }}
</el-text>
<br />
<el-text class="mx-1">
编码(十六进制): {{ transfonthex[index] }}
</el-text>
<br />
<el-text class="mx-1">
编码(二进制): {{ transfontbin[index] }}
</el-text>
</el-card>
</div>
</template>
<script setup>
import { ref, watch, onMounted } from "vue";
import encodeActiveStore from "../../stores/encode";
import { toFullWidth } from "../../utils/toFullWidth";
import GBK from "gbk.js";
const encodeActive = encodeActiveStore();
const showType = ref(1);
const inputfont = ref();
const chinafont = ref();
const transfont = ref();
const transfonthex = ref();
const transfontbin = ref();
const options = [
{
value: 0,
label: "单字",
},
{
value: 1,
label: "连续",
},
];
const transformfont = (inputfont) => {
chinafont.value = inputfont;
encodeActive.inputfont = inputfont;
//将半角字符转为全角字符
let inputfontFullWidth = toFullWidth(inputfont);
transfont.value = [];
let hex = [], bin = [];
inputfontFullWidth.split("").forEach((item) => {
transfont.value.push(GBK.encode(item).join(" , "));
let hexSingle = [], binSingle = [];
GBK.encode(item).forEach((element) => {
hexSingle.push(element.toString(16).toUpperCase().length < 2 ? "0" + element.toString(16).toUpperCase() : element.toString(16).toUpperCase());
binSingle.push(element.toString(2));
});
hex.push(hexSingle.join(""));
bin.push(binSingle.join(" , "));
});
encodeActive.transfont = transfont.value;
transfonthex.value = hex;
encodeActive.transfonthex = transfonthex.value;
transfontbin.value = bin;
encodeActive.transfontbin = transfontbin.value;
};
const reset = () => {
inputfont.value = "";
chinafont.value = "";
transfont.value = "";
transfonthex.value = "";
transfontbin.value = "";
encodeActive.inputfont = "";
encodeActive.transfont = "";
encodeActive.transfonthex = "";
encodeActive.transfontbin = "";
};
watch(inputfont, (value) => {
encodeActive.inputfont = value;
});
watch(showType, (value) => {
encodeActive.showType = value;
});
onMounted(() => {
showType.value = encodeActive.showType;
inputfont.value = encodeActive.inputfont;
chinafont.value = encodeActive.inputfont;
transfont.value = encodeActive.transfont;
transfonthex.value = encodeActive.transfonthex;
transfontbin.value = encodeActive.transfontbin;
});
</script>
<style scoped></style>

23
src/main.js Normal file
View File

@@ -0,0 +1,23 @@
import './assets/main.css'
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import piniaPersist from 'pinia-plugin-persist';
import ElementPlus from 'element-plus';
import zhCn from 'element-plus/es/locale/lang/zh-cn';
import 'element-plus/dist/index.css';
import App from './App.vue';
import router from './router';
const app = createApp(App); // 创建 vue 实例
const store = createPinia(); // 创建 pinia 实例
store.use(piniaPersist);
app.use(store);
app.use(ElementPlus, { locale: zhCn });
app.use(router);
app.mount('#app');

31
src/router/index.js Normal file
View File

@@ -0,0 +1,31 @@
import { createRouter, createWebHistory } from 'vue-router';
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/main',
name: 'main',
component: () => import('../views/Main.vue')
},
{
path: '/404page',
name: '404',
component: () => import('../views/404page.vue')
}
]
});
router.beforeEach((to, from, next) => {
if (to.matched.length === 0) {
if (to.path == "/") {
router.push({ path: '/main' });
} else {
router.push({ path: '/404page' });
}
} else {
next();
}
});
export default router;

View File

@@ -0,0 +1,37 @@
import { defineStore } from 'pinia';
import { parse, stringify } from 'zipson';
const bsideManagerActiveStore = defineStore('bsideManager', {
state: () => ({
// 持久化数据
offset: 1,
limit: 10,
dataCount: 0,
pageCount: 0,
searchOptions: {
account_name: "",
user_name: "",
phone: "",
},
dialogVisible: false,
dialogSearchVisible: false,
dialogSortVisible: false,
dialogOperationVisible: false
}),
persist: {
enabled: true, // Store 中数据持久化生效
strategies: [
{
key: 'bsideManagerActive', // 存储的 key
storage: localStorage, // 存储位置
paths: ["offset", "limit", "dataCount", "pageCount", "searchOptions", "dialogVisible", "dialogSearchVisible", "dialogSortVisible", "dialogOperationVisible"], // 需要持久化的字段
serializer: {
deserialize: parse,
serialize: stringify,
},
}
]
},
});
export default bsideManagerActiveStore;

33
src/stores/decode.js Normal file
View File

@@ -0,0 +1,33 @@
import { defineStore } from 'pinia';
import { parse, stringify } from 'zipson';
const decodeActiveStore = defineStore('decode', {
state: () => ({
// 持久化数据
decodeType: "",
showType: "",
fontcode: "",
transcode: "",
transcodehex: "",
transcodebin: "",
fontcodedec: "",
fontcodehex: "",
fontcodebin: "",
}),
persist: {
enabled: true, // Store 中数据持久化生效
strategies: [
{
key: 'decodeActive', // 存储的 key
storage: localStorage, // 存储位置
paths: ["decodeType", "showType", "fontcode", "transcode", "transcodehex", "transcodebin", "fontcodedec", "fontcodehex", "fontcodebin"], // 需要持久化的字段
serializer: {
deserialize: parse,
serialize: stringify,
},
}
],
},
});
export default decodeActiveStore;

29
src/stores/encode.js Normal file
View File

@@ -0,0 +1,29 @@
import { defineStore } from 'pinia';
import { parse, stringify } from 'zipson';
const encodeActiveStore = defineStore('encode', {
state: () => ({
// 持久化数据
showType: "",
inputfont: "",
transfont: "",
transfonthex: "",
transfontbin: "",
}),
persist: {
enabled: true, // Store 中数据持久化生效
strategies: [
{
key: 'encodeActive', // 存储的 key
storage: localStorage, // 存储位置
paths: ["showType", "inputfont", "transfont", "transfonthex", "transfontbin"], // 需要持久化的字段
serializer: {
deserialize: parse,
serialize: stringify,
},
}
]
},
});
export default encodeActiveStore;

41
src/stores/historyMsg.js Normal file
View File

@@ -0,0 +1,41 @@
import { defineStore } from 'pinia';
import { parse, stringify } from 'zipson';
const historyMsgActiveStore = defineStore('historyMsg', {
state: () => ({
// 持久化数据
activeIndex: "1",
offset: 1,
limit: 10,
dataCount: 0,
pageCount: 0,
searchOptions: {
mountpoint: "",
original_msg: "",
created_at: "",
},
sortOptions: {
mountpoint: false,
created_at: false,
},
dialogSearchVisible: false,
dialogSortVisible: false,
dialogOperationVisible: false
}),
persist: {
enabled: true, // Store 中数据持久化生效
strategies: [
{
key: 'historyMsgActive', // 存储的 key
storage: localStorage, // 存储位置
paths: ["activeIndex", "offset", "limit", "dataCount", "pageCount", "searchOptions", "sortOptions", "dialogSearchVisible", "dialogSortVisible", "dialogOperationVisible"], // 需要持久化的字段
serializer: {
deserialize: parse,
serialize: stringify,
},
}
]
},
});
export default historyMsgActiveStore;

25
src/stores/mountpoint.js Normal file
View File

@@ -0,0 +1,25 @@
import { defineStore } from 'pinia';
import { parse, stringify } from 'zipson';
const mountpointActiveStore = defineStore('mountpoint', {
state: () => ({
// 持久化数据
selectedMountpoint: "",
}),
persist: {
enabled: true, // Store 中数据持久化生效
strategies: [
{
key: 'mountpointActive', // 存储的 key
storage: localStorage, // 存储位置
paths: ["selectedMountpoint"], // 需要持久化的字段
serializer: {
deserialize: parse,
serialize: stringify,
},
}
],
},
});
export default mountpointActiveStore;

View File

@@ -0,0 +1,42 @@
import { defineStore } from 'pinia';
import { parse, stringify } from 'zipson';
const recordActiveStore = defineStore('record', {
state: () => ({
// 持久化数据
offset: 1,
limit: 10,
dataCount: 0,
searchOptions: {
mountpoint: "",
befor_edit: "",
created_at: "",
updated_at: ""
},
sortOptions: {
mountpoint: false,
befor_edit: false,
created_at: false,
updated_at: false
},
dialogSearchVisible: false,
dialogSortVisible: false,
dialogOperationVisible: false
}),
persist: {
enabled: true, // Store 中数据持久化生效
strategies: [
{
key: 'recordActive', // 存储的 key
storage: localStorage, // 存储位置
paths: ["offset", "limit", "dataCount", "searchOptions", "sortOptions", "dialogSearchVisible", "dialogSortVisible", "dialogOperationVisible"], // 需要持久化的字段
serializer: {
deserialize: parse,
serialize: stringify,
},
}
]
},
});
export default recordActiveStore;

35
src/stores/userManager.js Normal file
View File

@@ -0,0 +1,35 @@
import { defineStore } from 'pinia';
import { parse, stringify } from 'zipson';
const userManagerActiveStore = defineStore('userManager', {
state: () => ({
// 持久化数据
dialogVisible: false,
ruleForm: {
account_name: "",
user_name: "",
phone: "",
gender: "",
age: "",
email: "",
address: "",
post_code: "",
},
}),
persist: {
enabled: true, // Store 中数据持久化生效
strategies: [
{
key: 'userManagerActive', // 存储的 key
storage: localStorage, // 存储位置
paths: ["dialogVisible", "ruleForm"], // 需要持久化的字段
serializer: {
deserialize: parse,
serialize: stringify,
},
}
]
},
});
export default userManagerActiveStore;

21
src/utils/aes.js Normal file
View File

@@ -0,0 +1,21 @@
import CryptoJS from 'crypto-js';
const KEY = CryptoJS.enc.Utf8.parse('BDQH');
export const encrypt = (plaintext) => {
if(plaintext !== null && plaintext !== undefined && plaintext !== "") {
return CryptoJS.AES.encrypt(plaintext, KEY, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
}).toString();
}
};
export const decrypt = (plaintext) => {
if(plaintext !== null && plaintext !== undefined && !plaintext == "") {
return CryptoJS.AES.decrypt(plaintext, KEY, {
mode: CryptoJS.mode.ECB,
padding: CryptoJS.pad.Pkcs7
}).toString(CryptoJS.enc.Utf8);
}
};

View File

@@ -0,0 +1,9 @@
export const hasNullOrEmptyPropertyWithTypeof = (obj) => {
let hasNullOrEmpty = false;
for (const prop in obj) {
if (!(obj.hasOwnProperty(prop) && ((obj[prop] === null) || obj[prop] === undefined || (typeof obj[prop] === 'string' && obj[prop] === '')))) {
hasNullOrEmpty = true;
}
}
return hasNullOrEmpty;
}

5
src/utils/judgment.js Normal file
View File

@@ -0,0 +1,5 @@
export const judgment = (keyWord, key, value, val) => {
if (value !== null) {
keyWord[key] = value[val];
}
};

7
src/utils/toFullWidth.js Normal file
View File

@@ -0,0 +1,7 @@
//将半角空格,制表符,回车和半角字符转为全角空格,全角制表符,全角回车和全角字符
export const toFullWidth = (str) => {
//全角制表符使用4个全角空格模拟
return str.replace(/ /g, '\u3000').replace(/\t/g, '\u3000\u3000\u3000\u3000').replace(/\n/g, '\u21B5').replace(/[\x21-\x7E]/g, (font) => {
return String.fromCharCode(font.charCodeAt(0) + 0xFEE0);
});
};

21
src/views/404page.vue Normal file
View File

@@ -0,0 +1,21 @@
<template>
<div class="common-layout w-h-full">
<el-text class="mx-1"> 404 not found </el-text>
<br />
<el-text class="mx-1"> 您搜索的页面不存在 </el-text>
<br />
<el-button style="font-weight: bold;" type="primary" @click="backHome">返回首页</el-button>
</div>
</template>
<script setup>
import { useRouter } from "vue-router";
const router = useRouter();
const backHome = () => {
router.push("/main");
};
</script>
<style scoped></style>

182
src/views/Main.vue Normal file
View File

@@ -0,0 +1,182 @@
<template>
<el-container class="w-h-full">
<el-header class="flex flex-wrap" style="height: 80px; background-color: #f0f0f0">
<Header
ref="headerRef"
@allowManager="allowManager"
@connect="connect"
@disconnect="disconnect"
/>
</el-header>
<el-main>
<Main ref="mainRef" />
</el-main>
<el-footer class="flex" style="background-color: #f2f2f9">
<Footer />
</el-footer>
</el-container>
</template>
<script setup>
import { ref, onMounted, onBeforeMount, onBeforeUnmount } from "vue";
// import io from "socket.io-client";
import GBK from "gbk.js";
import moment from "moment";
import $http from "../api/index";
import $url from "../api/url";
import Header from "../components/pageStructure/header.vue";
import Main from "../components/pageStructure/main.vue";
import Footer from "../components/pageStructure/footer.vue";
const headerRef = ref(null);
const mainRef = ref(null);
//开发/测试环境
// const socket = io("http://127.0.0.1:8010", {
// 生产环境
// const socket = io("http://bdrdserver.hwasmart.com:8010", {
// // path: "/bdrdWebSocket/",
// transports: ["websocket", "polling"],
// reconnection: true,
// reconnectionDelay: 1000,
// reconnectionDelayMax: 6000,
// reconnectionAttempts: 10,
// });
const decode = (data) => {
let arr = [];
data.match(/..?/g).forEach((element) => {
arr.push(parseInt(element, 16));
});
return GBK.decode(arr);
};
const getMsg = () => {
// socket.emit("message", headerRef.value.cardID);
// socket.removeListener(headerRef.value.cardID);
// if (socket.listeners(headerRef.value.cardID).length == 0) {
// socket.on(headerRef.value.cardID, (msg) => {
// const { encode_type, msg_data, created_at } = msg;
// mainRef.value.message = Number(encode_type) == 0 ? decode(msg_data) : msg_data;
// shortMsgActive.message = mainRef.value.message;
// mainRef.value.created_at = moment
// .utc(created_at)
// .add(8, "hours")
// .format("YYYY-MM-DD-HH:mm:ss");
// shortMsgActive.created_at = mainRef.value.created_at;
// mainRef.value.historyMsg();
// });
// }
};
const connect = () => {
// socket.connect();
};
const disconnect = () => {
// socket.disconnect();
};
const allowManager = () => {
if (headerRef.value.manager) {
mainRef.value.manager = true;
}
};
onMounted(() => {
mainRef.value.color = "color: rgb(56 188 248);";
mainRef.value.message = "连接中...";
mainRef.value.created_at = moment
.utc(new Date())
.add(8, "hours")
.format("YYYY-MM-DD-HH:mm:ss");
$http.post($url.getCard, { uid: localStorage.data }).then((res) => {
if (res.data.code === 200) {
headerRef.value.cardID = res.data.cardID;
let connectStatus = false,
reconnectStatus = false;
// socket.on("connect", () => {
// connectStatus = true;
// mainRef.value.color = "color: rgb(16 184 130);";
// mainRef.value.message = "连接成功!";
// mainRef.value.created_at = moment
// .utc(new Date())
// .add(8, "hours")
// .format("YYYY-MM-DD-HH:mm:ss");
// getMsg();
// });
// socket.on("disconnect", (reason) => {
// mainRef.value.color = "color: rgb(240 70 70);";
// mainRef.value.message = "连接断开!请检查网络状态并刷新网页重试!";
// mainRef.value.created_at = moment
// .utc(new Date())
// .add(8, "hours")
// .format("YYYY-MM-DD-HH:mm:ss");
// if (reason === "io server disconnect" || reason === "io client disconnect") {
// if (headerRef.value.cardID !== "") {
// mainRef.value.color = "color: rgb(56 188 248);";
// mainRef.value.message = "连接中...";
// mainRef.value.created_at = moment
// .utc(new Date())
// .add(8, "hours")
// .format("YYYY-MM-DD-HH:mm:ss");
// socket.removeListener(headerRef.value.cardID);
// socket.connect();
// reconnectStatus = true;
// }
// }
// });
// socket.on("connect_error", (err) => {
// // 如果连接异常,修改transports传输方式
// socket.io.opts.transports = ["websocket", "polling"];
// if (reconnectStatus) {
// setTimeout(() => {
// reconnectStatus = false;
// mainRef.value.color = "color: rgb(240 70 70);";
// mainRef.value.message = "连接异常!请检查网络状态并刷新网页重试!";
// mainRef.value.created_at = moment
// .utc(new Date())
// .add(8, "hours")
// .format("YYYY-MM-DD-HH:mm:ss");
// }, socket.io.opts.reconnectionAttempts * socket.io.opts.reconnectionDelayMax);
// }
// });
// setTimeout(() => {
// if (!connectStatus) {
// mainRef.value.color = "color: rgb(240 70 70);";
// mainRef.value.message = "连接异常!请检查网络状态并刷新网页重试!";
// mainRef.value.created_at = moment
// .utc(new Date())
// .add(8, "hours")
// .format("YYYY-MM-DD-HH:mm:ss");
// }
// }, socket.io.opts.reconnectionAttempts * socket.io.opts.reconnectionDelayMax);
} else {
// socket.disconnect();
}
});
});
onBeforeMount(() => {
// if (socket) {
// socket.connect();
// }
});
onBeforeUnmount(() => {
// if (socket) {
// socket.disconnect(); // 组件卸载时断开连接
// }
});
</script>
<style scoped></style>

13
tailwind.config.js Normal file
View File

@@ -0,0 +1,13 @@
/** @type {import('tailwindcss').Config} */
export default {
purge: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'],
darkMode: false, // or 'media' or 'class'
content: [],
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [],
}

53
vite.config.js Normal file
View File

@@ -0,0 +1,53 @@
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
import legacy from '@vitejs/plugin-legacy'
// https://vitejs.dev/config/
export default defineConfig({
base: './',
publicPath: './',
plugins: [
vue(),
AutoImport({
resolvers: [ElementPlusResolver()],
}),
Components({
resolvers: [ElementPlusResolver()],
}),
legacy({
target: ['ie>=11'],
additionalLegacyPolyfills: ['regenerator-runtime/runtime'],
}),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
server: {
//用来配置跨域
host: '127.0.0.1',
port: 7035,
proxy: {
'/authentication': {
target: 'http://127.0.0.1:8011',//鉴权服务器地址
changeOrigin: true,
rewrite: (path) => path.replace(/^\/authentication/, '')
},
'/ntripService': {
target: 'http://127.0.0.1:8022',//资源服务器地址
changeOrigin: true,
rewrite: (path) => path.replace(/^\/ntripService/, '')
},
'/ntripCaster': {
target: 'http://127.0.0.1:2101',//资源服务器地址
changeOrigin: true,
rewrite: (path) => path.replace(/^\/ntripCaster/, '')
}
}
}
});