北斗导航定位通信综合实践赛服务系统

This commit is contained in:
2026-03-27 17:36:15 +08:00
commit 7b9028e883
59 changed files with 11761 additions and 0 deletions

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>

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

@@ -0,0 +1,113 @@
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: '/main' });
}
});
};
// 添加响应拦截器
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 === 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);
},
}

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

@@ -0,0 +1,41 @@
export default {
// 用于确认哪个网页在进行登录
toLogin: "/authentication/toLogin",
// 退出登录
unLogin: "/authentication/unLogin",
// 用于获取token
getToken: "/authentication/getToken",
// 用于验证token
verify: "/authentication/verify",
// 获取用户基本信息
getUser: "/authentication/getUser",
// 修改用户基本信息
editUser: "/authentication/editUser",
// 修改密码
changePassword: "/authentication/changePassword",
// 系统获取竞赛基本信息
contestMsg: "/bdContest/contestMsg",
// 检查竞赛账号是否已经绑定北斗卡
checkUser: "/bdContest/checkUser",
// 检查北斗卡是否已经被绑定
checkBind: "/bdContest/checkBind",
// 绑定北斗卡
bindCard: "/bdContest/bindCard",
// 解绑北斗卡
unBindCard: "/bdContest/unBindCard",
// 系统获取排名信息
ranking: "/bdContest/ranking",
// 学生抽取/读取题目
topic: "/bdContest/topic",
// 系统读取单选题
radio: "/bdContest/radio",
// 学生获取历史消息
getHistoryMsg: "/bdContest/getHistoryMsg",
// 学生删除历史消息
delHistoryMsg: "/bdContest/delHistoryMsg",
// 学生完成答题
answer: "/bdContest/answer",
// 学生查看成绩信息
grades: "/bdContest/grades",
}

BIN
src/assets/bdjyLogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 KiB

BIN
src/assets/bdqhLogo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

BIN
src/assets/bg.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 760 KiB

BIN
src/assets/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 72 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,125 @@
<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,118 @@
<template>
<!-- 表格 -->
<el-card>
<div class="flex justify-center flex-wrap">
<el-table
ref="userTableRef"
:data="tableData"
style="width: 100%"
table-layout="auto"
scrollbar-always-on
@selection-change="handleSelectionChange"
>
<el-table-column min-width="100" column-key="card_id">
<template #header>
<div>
<div>
<el-text class="mx-1"> 卡号 </el-text>
</div>
</div>
</template>
<template #default="scope">
<div
:style="styleShunt(options, scope.row.encode_type)"
class="flex justify-start items-center text-base"
>
{{ scope.row.card_id }}
</div>
</template>
</el-table-column>
<el-table-column min-width="80" column-key="encode_type">
<template #header>
<div>
<el-text class="mx-1"> 编码类型 </el-text>
</div>
</template>
<template #default="scope">
<div
:style="styleShunt(options, scope.row.encode_type)"
class="flex justify-start items-center text-base"
>
{{ scope.row.encode_type == true ? "代码" : "汉字" }}
</div>
</template>
</el-table-column>
<el-table-column min-width="200" column-key="msg_data">
<template #header>
<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="
scope.row.msg_data
? styleShunt(options, scope.row.encode_type)
: 'color: rgb(240 70 70)'
"
style="font-size: 1rem"
>
{{ scope.row.msg_data ? scope.row.msg_data : "解析错误" }}
</el-text>
</div>
</template>
</el-table-column>
<el-table-column min-width="170" column-key="created_at">
<template #header>
<div>
<el-text class="mx-1"> 接收时间 </el-text>
</div>
</template>
<template #default="scope">
<div
:style="styleShunt(options, scope.row.encode_type)"
class="flex justify-start items-center text-base"
>
{{ scope.row.created_at }}
</div>
</template>
</el-table-column>
</el-table>
<!-- 表格 -->
</div>
</el-card>
</template>
<script setup>
import { ref } from "vue";
const tableData = ref([]);
const options = [
{
value: "0",
label: "汉字",
style: "color: rgb(16, 184, 130)",
},
{
value: "1",
label: "代码",
style: "color: rgb(56, 188, 248)",
},
];
const styleShunt = (options, data) => {
let nowStyle;
options.forEach((item) => {
if (data == item.value) {
nowStyle = item.style;
}
});
return nowStyle;
};
defineExpose({ tableData });
</script>
<style scoped></style>

View File

@@ -0,0 +1,412 @@
<template>
<!-- 表格 -->
<el-card>
<div class="flex justify-center flex-wrap">
<el-table
ref="userTableRef"
:data="tableData"
style="width: 100%"
height="20rem"
table-layout="auto"
scrollbar-always-on
@selection-change="handleSelectionChange"
>
<el-table-column
fixed="left"
type="selection"
width="40"
column-key="selection"
/>
<TabelSelect
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 v-if="!answerMark" min-width="100" column-key="id">
<template #header>
<div>
<div>
<el-text class="mx-1"> 作答 </el-text>
</div>
</div>
</template>
<template #default="scope">
<el-button
:disabled="answerMark"
type="primary"
size="small"
@click="handleId(scope.row)"
>作答</el-button
>
</template>
</el-table-column>
<el-table-column min-width="100" column-key="card_id">
<template #header>
<div>
<SearchKey
ref="SearchCardIdKeyRef"
holderText="输入卡号以搜索相关数据"
@search="historyMsg"
/>
<div>
<el-text class="mx-1"> 卡号 </el-text>
<SortSearch
ref="SortCardIdRef"
activeText="顺序"
inactiveText="倒序"
@search="historyMsg"
/>
</div>
</div>
</template>
<template #default="scope">
<div
:style="styleShunt(options, scope.row.encode_type)"
class="flex justify-start items-center text-base"
>
{{ scope.row.card_id }}
</div>
</template>
</el-table-column>
<el-table-column min-width="80" column-key="encode_type">
<template #header>
<SelectSearch
ref="SelectSearchRef"
holderText="选择编码类型以搜索相关数据"
:options="options"
@search="historyMsg"
/>
<div>
<el-text class="mx-1"> 编码类型 </el-text>
</div>
</template>
<template #default="scope">
<div
:style="styleShunt(options, scope.row.encode_type)"
class="flex justify-start items-center text-base"
>
{{ scope.row.encode_type == true ? "代码" : "汉字" }}
</div>
</template>
</el-table-column>
<el-table-column min-width="200" column-key="msg_data">
<template #header>
<SearchKey
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="
scope.row.msg_data
? styleShunt(options, scope.row.encode_type)
: 'color: rgb(240 70 70)'
"
style="font-size: 1rem"
>
{{ scope.row.msg_data ? scope.row.msg_data : "解析错误" }}
</el-text>
</div>
</template>
</el-table-column>
<el-table-column min-width="170" column-key="created_at">
<template #header>
<DateTimePicker
ref="DateTimePickerRef"
DateTimePicker="historyMsgDateTimePicker"
@search="historyMsg"
/>
<div>
<el-text class="mx-1"> 接收时间 </el-text>
<SortSearch
ref="SortDateTimeRef"
activeText="顺序"
inactiveText="倒序"
@search="historyMsg"
/>
</div>
</template>
<template #default="scope">
<div
:style="styleShunt(options, scope.row.encode_type)"
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>
<PasswordOauth
ref="PasswordOauthRef"
operateLabel="删除历史消息"
@requestOperate="requestOperate"
/>
</template>
<script setup>
import { ref, reactive, watch, onMounted, nextTick } from "vue";
import { ElMessage } from "element-plus";
import TabelSelect from "../operate/tabelSelect.vue";
import SearchKey from "../search/searchKey.vue";
import SortSearch from "../search/sortSearch.vue";
import SelectSearch from "../search/selectSearch.vue";
import DateTimePicker from "../search/dateTimePicker.vue";
import Pagination from "../search/pagination.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 historyMsgActive = historyMsgActiveStore();
const emit = defineEmits(["handleAnswer"]);
const answerMark = ref(false);
const delSelectHold = {
single: "删除",
multiple: "删除选中",
all: "删除所有",
label: "删除历史消息",
};
const DelSelectRef = ref(null);
const SearchCardIdKeyRef = ref(null);
const SortCardIdRef = ref(null);
const SortDateTimeRef = ref(null);
const SelectSearchRef = ref(null);
const SearchDataKeyRef = ref(null);
const DateTimePickerRef = ref(null);
const PaginationRef = ref(null);
const PasswordOauthRef = ref(null);
const userTableRef = ref();
const offsetOff = ref(true);
const pageCount = ref(0);
const searchOptions = reactive({
card_id: "",
encode_type: "",
msg_data: "",
created_at: "",
});
const sortOptions = reactive({
card_id: false,
created_at: false,
});
const tableData = ref([]);
const options = [
{
value: "0",
label: "汉字",
style: "color: rgb(16, 184, 130)",
},
{
value: "1",
label: "代码",
style: "color: rgb(56, 188, 248)",
},
];
let singleData = reactive({});
let delData = reactive({});
let delDataClone = reactive({});
const styleShunt = (options, data) => {
let nowStyle;
options.forEach((item) => {
if (data == item.value) {
nowStyle = item.style;
}
});
return nowStyle;
};
const historyMsg = () => {
judgment(searchOptions, "card_id", SearchCardIdKeyRef.value, "searchMsg");
judgment(searchOptions, "encode_type", SelectSearchRef.value, "searchMsg");
judgment(searchOptions, "msg_data", SearchDataKeyRef.value, "searchMsg");
judgment(searchOptions, "created_at", DateTimePickerRef.value, "searchMsg");
judgment(sortOptions, "card_id", SortCardIdRef.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 handleId = (row) => {
emit("handleAnswer", row);
};
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({ answerMark, tableData });
watch(searchOptions, (value) => {
historyMsgActive.searchOptions = value;
});
watch(sortOptions, (value) => {
historyMsgActive.sortOptions = value;
});
onMounted(() => {
pageCount.value = historyMsgActive.pageCount;
searchOptions.card_id = historyMsgActive.searchOptions.card_id;
searchOptions.encode_type = historyMsgActive.searchOptions.encode_type;
searchOptions.msg_data = historyMsgActive.searchOptions.msg_data;
searchOptions.created_at = historyMsgActive.searchOptions.created_at;
sortOptions.card_id = historyMsgActive.sortOptions.card_id;
sortOptions.created_at = historyMsgActive.sortOptions.created_at;
setTimeout(() => {
historyMsg();
}, 200);
setTimeout(() => {
SearchCardIdKeyRef.value.searchMsg = historyMsgActive.searchOptions.card_id;
SelectSearchRef.value.searchMsg = historyMsgActive.searchOptions.encode_type;
SearchDataKeyRef.value.searchMsg = historyMsgActive.searchOptions.msg_data;
DateTimePickerRef.value.created_at = historyMsgActive.searchOptions.created_at;
SortCardIdRef.value.searchMsg = historyMsgActive.sortOptions.card_id;
SortDateTimeRef.value.searchMsg = historyMsgActive.sortOptions.created_at;
}, 0);
});
</script>
<style scoped></style>

View File

@@ -0,0 +1,186 @@
<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="card_id">
<el-input
minlength="8"
maxlength="8"
clearable
placeholder="请输入卡号"
v-model="ruleForm.card_id"
@input="filter(ruleForm.card_id, 'card_id')"
@blur="serviceCheck(ruleForm.card_id)"
/>
</el-form-item>
<el-form-item class="mx-1 font-bold" label="IMSI 号" prop="card_imsi">
<el-input
minlength="18"
maxlength="18"
clearable
placeholder="请输入 IMSI 号"
v-model="ruleForm.card_imsi"
@input="filter(ruleForm.card_imsi, 'card_imsi')"
/>
</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="bind(ruleFormRef)">
绑定
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { ref, reactive, watch } from "vue";
import { CloseBold } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
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({
card_id: "",
card_imsi: "",
});
const rules = reactive({
card_id: [
{ required: true, message: "请输入卡号", trigger: "blur" },
{ min: 8, max: 8, message: "输入长度应当为 8 位", trigger: "change" },
],
card_imsi: [
{ required: true, message: "请输入 IMSI 号", trigger: "blur" },
{ min: 18, max: 18, message: "输入长度应当为 18 位", trigger: "change" },
],
});
const emit = defineEmits(["bindCard", "connect"]);
const props = defineProps({
card_id: String,
});
const filter = (value, label) => {
let filterValue = value.replace(/\D/g, "");
if (label == "card_id") {
ruleForm.card_id = filterValue;
} else if (label == "card_imsi") {
ruleForm.card_imsi = filterValue;
}
};
const serviceCheck = (card_id) => {
if (card_id !== "" && card_id !== undefined) {
const data = {
card_id,
};
$http.post($url.checkBind, data).then((res) => {
if (res.data.code === 200) {
ElMessage({
message: "可绑定!",
type: "success",
});
} else {
ElMessage.error("此卡号已被绑定!");
}
});
}
};
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 bind = async (formEl) => {
if (!formEl) return;
await formEl.validate((valid) => {
if (valid) {
dialogVisible.value = false;
const data = {
card_id: ruleForm.card_id,
card_imsi: ruleForm.card_imsi,
};
$http.post($url.bindCard, data).then((res) => {
if (res.data.code === 200) {
ElMessage({
message: "绑定成功!",
type: "success",
});
emit("bindCard", ruleForm.card_id);
ruleForm.card_id = "";
ruleForm.card_imsi = "";
emit("connect");
window.location.reload();
} else {
ElMessage.error("绑定失败!");
}
});
} else {
ElMessage.error("提交失败!");
}
});
};
defineExpose({ dialogVisible });
watch(dialogVisible, (value) => {
if (!value) {
resetForm(ruleFormRef.value);
}
});
</script>
<style scoped></style>

View File

@@ -0,0 +1,173 @@
<template>
<el-dialog v-model="dialogVisible" title="密码管理" width="600" center align-center>
<el-form
ref="ruleFormRef"
style="max-width: 600px"
:model="ruleForm"
:rules="rules"
label-width="auto"
size="default"
status-icon
>
<el-form-item 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 label="新密码" prop="newPassword">
<el-text class="mx-1" 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 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 @click="resetForm(ruleFormRef)">取消</el-button>
<el-button type="primary" @click="changePassword(ruleFormRef)">
提交修改
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { ref, reactive } from "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();
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 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) {
localStorage.clear();
window.location.reload();
} else {
ElMessage.error("密码修改失败!");
}
});
} else {
ElMessage.error("提交失败!");
}
});
};
defineExpose({ dialogVisible });
</script>
<style scoped></style>

View File

@@ -0,0 +1,149 @@
<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="IMSI 号" prop="card_imsi">
<el-input
minlength="18"
maxlength="18"
clearable
placeholder="请输入 IMSI 号"
v-model="ruleForm.card_imsi"
@input="filter(ruleForm.card_imsi, 'card_imsi')"
/>
</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="unbind(ruleFormRef)">
解绑
</el-button>
</div>
</template>
</el-dialog>
</template>
<script setup>
import { ref, reactive, watch } from "vue";
import { CloseBold } from "@element-plus/icons-vue";
import { ElMessage } from "element-plus";
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({
card_imsi: "",
});
const rules = reactive({
card_imsi: [
{ required: true, message: "请输入 IMSI 号", trigger: "blur" },
{ min: 18, max: 18, message: "输入长度应当为 18 位", trigger: "change" },
],
});
const emit = defineEmits(["unBindCard", "disconnect"]);
const props = defineProps({
card_id: String,
});
const filter = (value, label) => {
let filterValue = value.replace(/\D/g, "");
if (label == "card_imsi") {
ruleForm.card_imsi = filterValue;
}
};
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 unbind = async (formEl) => {
if (!formEl) return;
await formEl.validate((valid) => {
if (valid) {
dialogVisible.value = false;
const data = {
card_id: props.card_id,
card_imsi: ruleForm.card_imsi,
};
$http.post($url.unBindCard, data).then((res) => {
if (res.data.code === 200) {
ElMessage({
message: "解绑成功!",
type: "success",
});
emit("unBindCard");
ruleForm.card_imsi = "";
emit("disconnect");
window.location.reload();
} else {
ElMessage.error("解绑失败!");
}
});
} else {
ElMessage.error("提交失败!");
}
});
};
defineExpose({ dialogVisible });
watch(dialogVisible, (value) => {
if (!value) {
resetForm(ruleFormRef.value);
}
});
</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,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,382 @@
<template>
<div
class="scroll-container h-full bg-slate-400 flex flex-col"
style="min-width: 520px"
>
<div class="flex flex-wrap justify-between items-center">
<el-menu
class="flex flex-wrap font-bold"
style="background-color: rgb(148 163 184 / var(--tw-bg-opacity, 1))"
: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-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
index="5"
:style="activeIndex == '5' ? activeStyle : indexStyle"
class="border-r-4 border-slate-300"
>
任务五
</el-menu-item>
<el-menu-item
index="6"
:style="activeIndex == '6' ? activeStyle : indexStyle"
class="border-r-4 border-slate-300"
>
汉字编码
</el-menu-item>
<el-menu-item
index="7"
:style="activeIndex == '7' ? activeStyle : indexStyle"
class="border-r-4 border-slate-300"
>
异或校验
</el-menu-item>
</el-menu>
<div>
<el-button style="font-weight: bold" type="primary" @click="backMain">
返回主页
</el-button>
<el-button
:disabled="answerMark"
class="mx-6"
style="font-weight: bold"
type="primary"
@click="answer"
>
提交答案
</el-button>
</div>
</div>
<div class="flex-grow bg-white text-black">
<div
v-show="activeIndex === '1'"
class="w-full mb-6 p-4 bg-rose-100"
style="overflow-y: auto"
>
<Topic1 ref="topic1Ref" />
<Answer1 ref="answer1Ref" />
</div>
<div
v-show="activeIndex === '2'"
class="w-full mb-6 p-4 bg-rose-100"
style="overflow-y: auto"
>
<Topic2 ref="topic2Ref" />
<Answer2 ref="answer2Ref" />
</div>
<div
v-show="activeIndex === '3'"
class="w-full mb-6 p-4 bg-rose-100"
style="overflow-y: auto"
>
<Topic3 ref="topic3Ref" />
<Answer3 ref="answer3Ref" />
</div>
<div
v-show="activeIndex === '4'"
class="w-full mb-6 p-4 bg-rose-100"
style="overflow-y: auto"
>
<Topic4 />
<Answer4 ref="answer4Ref" />
</div>
<div
v-show="activeIndex === '5'"
class="w-full mb-6 p-4 bg-rose-100"
style="overflow-y: auto"
>
<Topic5 ref="topic5Ref" />
<Answer5 ref="answer5Ref" />
</div>
<div v-show="activeIndex === '6'" class="w-full h-full mb-6 p-4 bg-rose-100">
<el-button
style="font-weight: bold"
type="primary"
@click="showModel"
class="m-4"
>
转换模式
</el-button>
<CodeHelp />
<Encode v-if="showCode" />
<Decode v-if="!showCode" />
</div>
<div v-show="activeIndex === '7'" class="w-full h-full mb-6 p-4 bg-rose-100">
<CalculationXOR />
</div>
<div v-if="activeIndex != 6 && activeIndex != 7" class="w-full p-4 bg-rose-100">
<HistoryMsg
ref="HistoryMsgRef"
@handleAnswer="handleAnswer"
@answerMark="answerMark"
/>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import Topic1 from "../topic/topic1.vue";
import Answer1 from "../features/answer.vue";
import Topic2 from "../topic/topic2.vue";
import Answer2 from "../features/answer.vue";
import Topic3 from "../topic/topic3.vue";
import Answer3 from "../features/answer.vue";
import Topic4 from "../topic/topic4.vue";
import Answer4 from "../features/answer.vue";
import Topic5 from "../topic/topic5.vue";
import Answer5 from "../features/answer.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 HistoryMsg from "../features/historyMsg.vue";
import moment from "moment";
import $http from "../../api/index";
import $url from "../../api/url";
import answerActiveStore from "../../stores/answer";
const answerActive = answerActiveStore();
const router = useRouter();
const answerMark = ref(false);
const activeIndex = ref("1");
const indexStyle = "background-color: rgb(241 245 249);";
const activeStyle = "background-color: rgb(207 250 254);";
const topic1Ref = ref(null);
const answer1Ref = ref(null);
const topic2Ref = ref(null);
const answer2Ref = ref(null);
const topic3Ref = ref(null);
const answer3Ref = ref(null);
const answer4Ref = ref(null);
const topic5Ref = ref(null);
const answer5Ref = ref(null);
const showCode = ref(true);
const HistoryMsgRef = ref(null);
const showModel = () => {
showCode.value = !showCode.value;
};
const contestMsg = () => {
$http.post($url.contestMsg).then((res) => {
if (res.data.code === 200) {
if (
new Date(res.data.contestMsg.contest_start) > new Date() ||
new Date() > new Date(res.data.contestMsg.contest_end)
) {
router.push("/main");
}
// ElMessage({
// message: "请求成功!",
// type: "success",
// });
} else {
// ElMessage.error("请求失败!");
}
});
};
const handleSelect = (key, keyPath) => {
activeIndex.value = key;
};
const handleAnswer = (answer) => {
switch (activeIndex.value) {
case "1":
answer1Ref.value.tableData[0] = answer;
answerActive.answer.answer1 = answer.id;
break;
case "2":
answer2Ref.value.tableData[0] = answer;
answerActive.answer.answer2 = answer.id;
break;
case "3":
answer3Ref.value.tableData[0] = answer;
answerActive.answer.answer3 = answer.id;
break;
case "4":
answer4Ref.value.tableData[0] = answer;
answerActive.answer.answer4 = answer.id;
break;
case "5":
answer5Ref.value.tableData[0] = answer;
answerActive.answer.answer5 = answer.id;
break;
default:
break;
}
};
const answerTips = (answerCheck, topicName) => {
if (answerCheck) {
ElMessage({
message: `${topicName}答案未提交!`,
type: "warning",
});
}
};
const verify = () => {
let answer1Check = answer1Ref.value.tableData.length == 0,
answer2Check = answer2Ref.value.tableData.length == 0,
answer3Check = answer3Ref.value.tableData.length == 0,
answer4Check = answer4Ref.value.tableData.length == 0,
answer5Check = answer5Ref.value.tableData.length == 0;
if (!answer1Check && !answer2Check && !answer3Check && !answer4Check && !answer5Check) {
return true;
} else {
answerTips(answer1Check, "任务一");
answerTips(answer2Check, "任务二");
answerTips(answer3Check, "任务三");
answerTips(answer4Check, "任务四");
answerTips(answer5Check, "任务五");
return false;
}
};
const backMain = () => {
router.push("/main");
};
const answer = () => {
if (verify()) {
const data = {
answer1: answer1Ref.value.tableData[0].id,
answer2: answer2Ref.value.tableData[0].id,
answer3: answer3Ref.value.tableData[0].id,
answer4: answer4Ref.value.tableData[0].id,
answer5: answer5Ref.value.tableData[0].id,
};
$http.post($url.answer, data).then((res) => {
if (res.data.code === 200) {
// ElMessage({
// message: "请求成功!",
// type: "success",
// });
router.push("/main");
} else {
// ElMessage.error("请求失败!");
}
});
}
};
const topic = () => {
const data = {
answerId: Object.values(answerActive.answer),
};
$http.post($url.topic, data).then((res) => {
if (res.data.code === 200) {
let answerId = [];
if (res.data.examination.updated_at != null) {
answerMark.value = true;
HistoryMsgRef.value.answerMark = true;
answerId = res.data.examination.answer.split(",");
} else {
answerId = Object.values(answerActive.answer);
}
answerId.map((item, index) => {
res.data.shortMsg.forEach((element) => {
if (element.id == item) {
element.origin_time = element.created_at;
element.created_at = moment
.utc(element.created_at)
.add(8, "hours")
.format("YYYY-MM-DD-HH:mm:ss");
switch (index) {
case 0:
answer1Ref.value.tableData[0] = element;
break;
case 1:
answer2Ref.value.tableData[0] = element;
break;
case 2:
answer3Ref.value.tableData[0] = element;
break;
case 3:
answer4Ref.value.tableData[0] = element;
break;
case 4:
answer5Ref.value.tableData[0] = element;
break;
default:
break;
}
}
});
});
topic1Ref.value.topic1 = res.data.examination.topic1;
topic1Ref.value.switchTopic = true;
topic2Ref.value.topic2 = res.data.examination.topic2;
topic3Ref.value.topic3 = res.data.examination.topic3;
topic5Ref.value.topic5 = res.data.examination.topic5;
// ElMessage({
// message: "请求成功!",
// type: "success",
// });
} else if (res.data.code === 333) {
ElMessage({
message: "用户未绑定北斗卡!",
type: "warning",
});
} else {
// ElMessage.error("请求失败!");
}
});
};
onMounted(() => {
contestMsg();
topic();
});
</script>
<style scoped>
.scroll-container {
overflow: auto;
}
.flex-grow {
flex-grow: 1;
}
</style>

View File

@@ -0,0 +1,12 @@
<template>
<div
class="h-full p-5 flex justify-center items-center text-white font-bold"
style="background-color: #222d2b"
>
技术支持电话:400-166-5286
</div>
</template>
<script setup></script>
<style scoped></style>

View File

@@ -0,0 +1,212 @@
<template>
<div
class="h-full p-5 flex flex-wrap justify-between items-center"
style="background-color: #232d3b"
>
<div class="flex justify-between items-center">
<a href="http://www.hwasmart.com"
><img style="width: 150px" :src="bdqhLogo" alt=""
/></a>
&nbsp;&nbsp; &nbsp;&nbsp;
<a href="http://www.beidouin.com"
><img style="width: 150px" :src="bdjyLogo" alt=""
/></a>
</div>
<div
style="font-size: 2rem"
class="text-center flex-auto font-bold text-gray-800 text-white"
>
北斗导航定位通信综合实践赛服务系统
</div>
<div style="width: 320px" class="flex justify-end">
<el-button
style="font-weight: bold; font-size: 1.5rem; width: 7.5rem; height: 2.5rem"
v-if="!loginMark"
type="primary"
@click="toLogin"
>
登录
</el-button>
</div>
<el-dropdown
style="width: 320px"
class="text-center felx justify-end items-center"
v-if="loginMark"
>
<el-button
style="font-weight: bold; font-size: 1.5rem; width: 12rem; height: 2.5rem"
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="changePassword"
>
<el-link
style="font-weight: bold; font-size: 1.5rem; width: 7.5rem; height: 2.5rem"
: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; font-size: 1.5rem; width: 7.5rem; height: 2.5rem"
:underline="false"
type="danger"
>
退出登录
</el-link>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<PasswordManager ref="passwordManagerRef" />
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import { ArrowDown } from "@element-plus/icons-vue";
import $http from "../../api/index";
import $url from "../../api/url";
const router = useRouter();
import { encrypt } from "../../utils/aes";
import bdqhLogo from "../../assets/bdqhLogo.png";
import bdjyLogo from "../../assets/bdjyLogo.png";
import PasswordManager from "../manager/changePassword.vue";
const loginMark = ref(false);
const userMsg = ref({});
const passwordManagerRef = ref(null);
const toLogin = () => {
localStorage.clear();
const data = {
origin_url: window.location.href,
};
$http.post($url.toLogin, data).then((res) => {
if (res.data.code === 200) {
window.location.href = res.data.redirect;
} else {
router.push({ path: "/main" });
}
});
};
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) {
userMsg.value = res.data.userData;
} else {
ElMessage.error("获取用户信息失败!");
}
});
};
onMounted(() => {
let getCode = window.location.href.indexOf("?");
if (getCode != "-1") {
let code = window.location.href.substring(getCode + 1);
const data = {
code: code,
type: encrypt("code"),
};
$http.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 if (
localStorage.data &&
localStorage.data !== "" &&
localStorage.data !== null &&
localStorage.data !== undefined &&
localStorage.data !== NaN &&
localStorage.access_token &&
localStorage.access_token !== "" &&
localStorage.access_token !== null &&
localStorage.access_token !== undefined &&
localStorage.access_token !== NaN
) {
const data = {
access_token: localStorage.access_token,
data: localStorage.data,
};
$http.post($url.verify, data).then((res) => {
if (res.data.code === 200) {
localStorage.setItem("data", res.data.data);
loginMark.value = true;
resetData();
}
});
} else if (
localStorage.data &&
localStorage.data !== "" &&
localStorage.data !== null &&
localStorage.data !== undefined &&
localStorage.data !== NaN &&
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,
};
$http.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();
}
});
}
});
</script>
<style scoped></style>

View File

@@ -0,0 +1,315 @@
<template>
<div class="scroll-container w-h-full">
<div class="w-full p-4 bg-gray-300" style="height: 31rem">
<div class="w-full flex flex-wrap gap-4 justify-start items-center">
<el-text class="mx-1 font-bold" style="font-size: 1.5rem"> 平台号码: </el-text>
<el-text
style="color: rgb(50, 100, 200); font-size: 1.5rem"
class="mx-1 font-bold"
>
15950077
</el-text>
<el-text class="mx-1 font-bold" style="font-size: 1.5rem">
卡号(北斗ID):
</el-text>
<el-text
style="color: rgb(50, 100, 200); font-size: 1.5rem"
class="mx-1 font-bold"
>
{{ cardID ? cardID : "未绑定" }}
</el-text>
<el-button
style="font-weight: bold; font-size: 1.5rem; width: 7.5rem; height: 2.5rem"
type="primary"
@click="seleBind"
>
{{ cardID ? "解绑" : "绑定" }}
</el-button>
</div>
<div
v-if="loadContestMsg"
class="flex flex-col gap-4 justify-center items-center p-4 bg-cyan-400 my-4 text-4xl text-center"
>
<div>
{{ contestData[0].contest_name }}
</div>
<div>竞赛主办方:{{ contestData[0].contest_organizers }}</div>
<div>第{{ contestData[0].contest_term }}届</div>
<div>
{{ contestData[0].status }}
</div>
<div>竞赛开始时间:{{ contestData[0].contest_start }}</div>
<div>竞赛结束时间:{{ contestData[0].contest_end }}</div>
</div>
<div v-if="loadContestMsg" class="w-full flex justify-center items-center">
<el-button
:disabled="contestMark == 1 ? false : true"
style="font-weight: bold; font-size: 1.5rem; width: 7.5rem; height: 2.5rem"
type="primary"
@click="loadContest"
>
{{ contestMarkText }}
</el-button>
</div>
</div>
<div class="w-full" style="height: 22.7rem">
<div
v-if="contestMark == 0 || contestMark == 3"
class="h-full bg-gray-400"
style="font-size: 1.5rem"
>
暂无数据
</div>
<div v-if="contestMark != 0 && contestMark != 3" class="h-full flex">
<div class="w-1/2 p-4 bg-gray-200">
<div class="w-full text-center mb-2">
<el-text class="mx-1 font-bold" style="font-size: 1.5rem">
竞赛排名榜
</el-text>
</div>
<el-table
:data="rankData"
border
style="width: 100%; height: 18rem"
table-layout="auto"
scrollbar-always-on
>
<el-table-column prop="position" label="排名" min-width="20" />
<el-table-column prop="account_name" label="竞赛账号名称" min-width="200" />
<el-table-column prop="score" label="得分" min-width="20" />
<el-table-column prop="unavailable" label="用时" min-width="200" />
</el-table>
</div>
<div
v-if="loadgradesDataMsg"
class="w-1/2 p-4 bg-green-400 text-3xl flex flex-col gap-4"
>
<div>竞赛账号名称:{{ gradesData.account.account_name }}</div>
<div>得分:{{ gradesData.score }}</div>
<div>
得分详情:
任务一
{{ gradesData.score_collection.split(",")[0] == 1 ? "[√]" : "[×]" }},
任务二
{{ gradesData.score_collection.split(",")[1] == 1 ? "[√]" : "[×]" }},
任务三
{{ gradesData.score_collection.split(",")[2] == 1 ? "[√]" : "[×]" }},
任务四
{{ gradesData.score_collection.split(",")[3] == 1 ? "[√]" : "[×]" }},
任务五
{{ gradesData.score_collection.split(",")[4] == 1 ? "[√]" : "[×]" }}
</div>
<div>用时:{{ gradesData.unavailable }}</div>
<div>排名:{{ gradesData.totalRank }}</div>
</div>
</div>
</div>
</div>
<Bind @bindCard="bindCard" ref="bindRef" :card_id="cardID" />
<Unbind @unBindCard="unBindCard" ref="unBindRef" :card_id="cardID" />
</template>
<script setup>
import { ref, onMounted } from "vue";
import { useRouter } from "vue-router";
import { ElMessage } from "element-plus";
import moment from "moment";
import $http from "../../api/index";
import $url from "../../api/url";
const router = useRouter();
const cardID = ref("");
const bindRef = ref(null);
const unBindRef = ref(null);
const loadContestMsg = ref(false);
const contestMarkText = ref("");
const contestData = ref([]);
const contestMark = ref(0);
const rankData = ref([]);
const loadgradesDataMsg = ref(false);
const gradesData = ref({});
const checkUser = () => {
$http.post($url.checkUser).then((res) => {
if (res.data.code === 200) {
cardID.value = res.data.card_id;
// ElMessage({
// message: "请求成功!",
// type: "success",
// });
} else {
// ElMessage.error("请求失败!");
}
});
};
const seleBind = () => {
if (cardID.value == "") {
bindRef.value.dialogVisible = true;
} else {
unBindRef.value.dialogVisible = true;
}
};
const bindCard = (bindCardID) => {
cardID.value = bindCardID;
};
const unBindCard = () => {
cardID.value = "";
};
const loadContest = () => {
router.push("/contest");
};
const contestStatus = {
0: "准备开赛",
1: "正在进行",
2: "已经结赛",
3: "停止举办",
};
const contestMarkStatus = {
0: "竞赛未开始",
1: "进入考试",
2: "竞赛已结束",
3: "竞赛停止举办",
};
const contestMsg = () => {
$http.post($url.contestMsg).then((res) => {
if (res.data.code === 200) {
if (res.data.contestMsg.status == 1) {
contestMark.value = 3;
} else {
if (
new Date(res.data.contestMsg.contest_start) < new Date() &&
new Date() < new Date(res.data.contestMsg.contest_end)
) {
contestMark.value = 1;
} else if (new Date() > new Date(res.data.contestMsg.contest_end)) {
contestMark.value = 2;
}
}
contestMarkText.value = contestMarkStatus[contestMark.value];
contestData.value = [res.data.contestMsg];
contestData.value[0].status = contestStatus[contestMark.value];
contestData.value[0].contest_start = moment
.utc(res.data.contestMsg.contest_start)
.add(8, "hours")
.format("YYYY年MM月DD日HH时");
contestData.value[0].contest_end = moment
.utc(res.data.contestMsg.contest_end)
.add(8, "hours")
.format("YYYY年MM月DD日HH时");
loadContestMsg.value = true;
// ElMessage({
// message: "请求成功!",
// type: "success",
// });
} else {
// ElMessage.error("请求失败!");
}
});
};
// 定义一个函数来计算两个时间之间的差值
const calculateTimeDifference = (startTime, endTime) => {
// 将时间字符串转换为Date对象
const start = new Date(startTime);
const end = new Date(endTime);
// 计算时间差(毫秒)
let difference = end - start;
// 定义时间单位转换
const timeUnits = {
days: 24 * 60 * 60 * 1000,
hours: 60 * 60 * 1000,
minutes: 60 * 1000,
seconds: 1000,
milliseconds: 1,
};
// 初始化结果对象
const result = {
days: 0,
hours: 0,
minutes: 0,
seconds: 0,
milliseconds: 0,
};
// 遍历时间单位,计算各个单位的时间差
for (let unit in timeUnits) {
if (timeUnits.hasOwnProperty(unit)) {
result[unit] = Math.floor(difference / timeUnits[unit]);
difference %= timeUnits[unit];
}
}
// 返回结果对象
return `${result.days}天 ${result.hours}小时 ${result.minutes}分钟 ${result.seconds}秒`;
};
const ranking = () => {
$http.post($url.ranking).then((res) => {
if (res.data.code === 200) {
rankData.value = [];
res.data.rankList.map((item, index) => {
rankData.value.push({
position: index + 1,
account_name: item.account.account_name,
score: item.score,
unavailable: calculateTimeDifference(item.created_at, item.updated_at),
});
});
// ElMessage({
// message: "请求成功!",
// type: "success",
// });
} else {
// ElMessage.error("请求失败!");
}
});
};
const grades = () => {
$http.post($url.grades).then((res) => {
if (res.data.code === 200) {
gradesData.value = res.data.grades.account.account_name;
gradesData.value = res.data.grades;
gradesData.value.unavailable = calculateTimeDifference(
res.data.grades.created_at,
res.data.grades.updated_at
);
loadgradesDataMsg.value = true;
// ElMessage({
// message: "请求成功!",
// type: "success",
// });
} else {
// ElMessage.error("请求失败!");
}
});
};
onMounted(() => {
setTimeout(() => {
checkUser();
contestMsg();
ranking();
grades();
}, 200);
});
</script>
<style scoped>
.scroll-container {
overflow: auto;
}
</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,26 @@
<template>
<el-text style="font-weight: bold; font-size: 24px">
任务一(1分):将系统给定的汉字转换成汉字编码发送到指定的短报文平台,并在收到的答案信息列表中提交答案,本次给定汉字为:
<el-text v-if="timeout" style="font-weight: bold; font-size: 24px; color: red">
{{ topic1 }}
</el-text>
</el-text>
</template>
<script setup>
import { ref, watch } from "vue";
const topic1 = ref();
const switchTopic = ref(false);
const timeout = ref(false);
watch(switchTopic, (value) => {
setTimeout(() => {
timeout.value = value;
}, 200);
});
defineExpose({ topic1, switchTopic });
</script>
<style scoped></style>

View File

@@ -0,0 +1,18 @@
<template>
<el-text style="font-weight: bold; font-size: 24px">
任务二(1分):将系统给定的数字用代码方式发送到短报文平台,并在收到的答案信息列表中提交答案,本次给定数字为:
<el-text style="font-weight: bold; font-size: 24px; color: red">
{{ topic2 }}
</el-text>
</el-text>
</template>
<script setup>
import { ref } from "vue";
const topic2 = ref();
defineExpose({ topic2 });
</script>
<style scoped></style>

View File

@@ -0,0 +1,72 @@
<template>
<el-text style="font-weight: bold; font-size: 24px">
任务三(3分):将给出的10道北斗专业相关知识选择题的正确答案ABCD转换成汉字编码发送到短报文平台,并在收到的答案信息列表中提交答案,本次给定选择题列表如下:
</el-text>
<div class="h-full bg-slate-200" style="overflow-y: auto">
<div v-for="(item, index) in radioTopic">
<el-text style="font-weight: bold; font-size: 24px; color: red">
{{ index + 1 + ":" + item.topic }}
</el-text>
<br />
<el-text style="font-weight: bold; font-size: 24px">
A:{{ item.options_a }}
</el-text>
<br />
<el-text style="font-weight: bold; font-size: 24px">
B:{{ item.options_b }}
</el-text>
<br />
<el-text style="font-weight: bold; font-size: 24px">
C:{{ item.options_c }}
</el-text>
<br />
<el-text style="font-weight: bold; font-size: 24px">
D:{{ item.options_d }}
</el-text>
</div>
</div>
</template>
<script setup>
import { ref, onMounted } from "vue";
import { ElMessage } from "element-plus";
import $http from "../../api/index";
import $url from "../../api/url";
const topic3 = ref();
const radioTopic = ref([]);
const radio = () => {
const radioArr = topic3.value.split(",").map((item) => Number(item));
const data = {
radioArr,
};
$http.post($url.radio, data).then((res) => {
if (res.data.code === 200) {
res.data.radioTopic.sort((a, b) => {
const aIndex = radioArr.indexOf(a.id);
const bIndex = radioArr.indexOf(b.id);
return aIndex - bIndex;
});
radioTopic.value = res.data.radioTopic;
// ElMessage({
// message: "请求成功!",
// type: "success",
// });
} else {
// ElMessage.error("请求失败!");
}
});
};
defineExpose({ topic3 });
onMounted(() => {
setTimeout(() => {
radio();
}, 500);
});
</script>
<style scoped></style>

View File

@@ -0,0 +1,9 @@
<template>
<el-text style="font-weight: bold; font-size: 24px">
任务四(3分):通过编程实现北斗定位模块获取经纬度数值的功能,并将获取的数值转换成JJJjjjjjjWWwwwwww的协议格式(小数点后不足位数的进行末尾补零),通过代码方式发送到平台,并在收到的答案信息列表中提交答案
</el-text>
</template>
<script setup></script>
<style scoped></style>

View File

@@ -0,0 +1,18 @@
<template>
<el-text style="font-weight: bold; font-size: 24px">
任务五(2分):将系统给定的传感器名称和对应传感器要求上传数据用规定格式转换成汉字编码发送到短报文平台(按给出的数据格式发送),并在收到的答案信息列表中提交答案,本次给定传感器和数据为:
<el-text style="font-weight: bold; font-size: 24px; color: red">
{{ topic5 }}
</el-text>
</el-text>
</template>
<script setup>
import { ref } from "vue";
const topic5 = ref();
defineExpose({ topic5 });
</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>

22
src/main.js Normal file
View File

@@ -0,0 +1,22 @@
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 pinia = createPinia();
pinia.use(piniaPersist);
const app = createApp(App);
app.use(ElementPlus, { locale: zhCn });
app.use(pinia);
app.use(router);
app.mount('#app');

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

@@ -0,0 +1,36 @@
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: '/contest',
name: 'contest',
component: () => import('../components/pageStructure/contest.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

31
src/stores/answer.js Normal file
View File

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

12
src/stores/counter.js Normal file
View File

@@ -0,0 +1,12 @@
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})

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;

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

@@ -0,0 +1,39 @@
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: {
card_id: "",
encode_type: "",
msg_data: "",
created_at: "",
},
sortOptions: {
card_id: false,
created_at: false,
},
}),
persist: {
enabled: true, // Store 中数据持久化生效
strategies: [
{
key: 'historyMsgActive', // 存储的 key
storage: localStorage, // 存储位置
paths: ["activeIndex", "offset", "limit", "dataCount", "pageCount", "searchOptions", "sortOptions"], // 需要持久化的字段
serializer: {
deserialize: parse,
serialize: stringify,
},
}
]
},
});
export default historyMsgActiveStore;

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 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>

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

@@ -0,0 +1,23 @@
<template>
<div class="common-layout w-h-full">
<el-container class="w-h-full">
<el-header style="padding: 0" height="136px">
<Header />
</el-header>
<el-main style="padding: 0">
<Main />
</el-main>
<el-footer style="padding: 0" height="66px">
<Footer />
</el-footer>
</el-container>
</div>
</template>
<script setup>
import Header from "../components/pageStructure/header.vue";
import Main from "../components/pageStructure/main.vue";
import Footer from "../components/pageStructure/footer.vue";
</script>
<style scoped></style>