[v0.1.1] 新增功能

- 新增管理员日志上传
This commit is contained in:
Guarp 2025-02-21 19:52:33 +08:00
parent b49f42e1bb
commit 04b2ec88e7
49 changed files with 1321 additions and 69 deletions

View File

@ -9,5 +9,13 @@
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
<!-- 识别单行,行内,\( \)样式的公式 -->
<script>
MathJax = {
tex: {inlineMath: [['$', '$'],['$$', '$$'], ['\\(', '\\)']]},
};
</script>
<script id="MathJax-script" async src="/libs/tex-chtml.js"></script>
</body>
</html>

1176
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -12,6 +12,7 @@
"axios": "^1.7.9",
"js-cookie": "^3.0.5",
"marked": "^15.0.7",
"mermaid": "^11.4.1",
"sweetalert2": "^11.16.0",
"vue": "^3.5.13",
"vue-router": "^4.5.0",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

1
public/libs/tex-chtml.js Normal file

File diff suppressed because one or more lines are too long

View File

@ -1,32 +0,0 @@
## 网站更新日志
---
### 2025/2/18 22:00 - [v0.1.0] 新增功能
- 完成留言板
- 新增小工具GPA计算器
### 2025/2/17 22:30 - [v0.0.3] 测试效果
- 新增留言板 - 基本完成
~~啊啊~~
### 2025/2/13 16:30 - [v0.0.2] 测试效果
- 优化布局
- 优化双端切换逻辑
- 新增项目展示页面 - 测试(未完成)
- 新增在线工具罗列页面 - 测试(未完成)
- 新增管理员上传日志 - 测试(未完成)
### 2025/2/12 22:30 - [v0.0.1] 测试功能
- 制作导航栏手机电脑双端模式
- 新增黑白双色模式
- 新增账户管理页面
- 账号设置 - 基本完成
- 头像系统
- 基本信息显示
- 双色模式切换
- 个人主页设置 - 测试(未完成)
- 稿件管理 - 测试(未完成)
- 草稿箱 - 测试(未完成)
- 博客界面
- 关键词标签
- 关键词搜索

View File

@ -29,7 +29,7 @@ export default class AuthService {
EMAIL: email,
PASSWORD: password
};
await api.post('/postmessage', data).then(res=>{
await api.post('/sendkey', data).then(res=>{
result = res;
})
return result;

View File

@ -32,9 +32,11 @@ const updateGlobalTheme = (theme) => {
}
// store
onMounted(() => {
if (AuthService.getToken()) {
onMounted(async () => {
if (await AuthService.getToken()) {
AuthService.setSelfInfo();
} else {
AuthService.logout();
}
updateGlobalTheme(store.state.theme)
})

View File

@ -3,10 +3,12 @@
src: url('../../../public/fonts/Netron.ttf') format('truetype');
font-weight: normal;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'huangkaihua';
src: url('../../../public/fonts/huangkaihuaLawyerfont-2.ttf') format('truetype');
font-weight: normal;
font-style: normal;
font-display: swap;
}

View File

@ -0,0 +1,29 @@
<template>
<div>
<textarea v-model="latexInput" placeholder="输入数学公式" rows="5" cols="50"></textarea>
<div v-html="formattedLatex"></div>
</div>
</template>
<script setup>
import {ref, watch, onMounted, nextTick} from 'vue';
const latexInput = ref(''); // LaTeX
const formattedLatex = ref(''); // LaTeX
onMounted(() => {
// MathJax
if (window.MathJax) {
MathJax.typeset();
}
});
// latexInput
watch(latexInput, async (newValue) => {
formattedLatex.value = newValue; //
await nextTick(); // DOM
if (window.MathJax) {
MathJax.typeset(); // MathJax
}
});
</script>

View File

@ -155,7 +155,7 @@ const filterBlogs = () => {
display: flex;
flex-direction: column;
align-items: center;
padding: 20px 0;
padding: 20px;
//margin-top: 70px;
}

View File

@ -56,7 +56,7 @@ const filterDemos = () => {
</script>
<template>
<div class="container" v-if="$route.path === '/demos'">
<div class="container" v-if="/^\/demos\/?$/.test($route.path)">
<div class="filters">
<!-- 将标签的功能合并到搜索栏内 -->
<input

View File

@ -134,7 +134,7 @@ const handleRegister = async () => {
return;
}
store.commit('stopLoading');
swal.window('info', '邮箱验证链接发送成功!请前往邮箱查看', null, 'ok', '好的');
swal.window('info', '邮箱验证链接发送成功!', '请前往邮箱查看', '好的', 'ok');
}
</script>

View File

@ -35,7 +35,7 @@ const filterTools = () => {
</script>
<template>
<div class="container" v-if="$route.path === '/tools'">
<div class="container" v-if="/^\/tools\/?$/.test($route.path)">
<div class="filters">
<div class="categories">
<span

View File

@ -5,10 +5,13 @@ import UserInfo from './Account_userInfo.vue';
import store from "../../store/index.js"; //
const isMobile = ref(false); //
const isMedia = ref(false);
const showMobileMenu = ref(false)
//
const checkWindowSize = () => {
isMobile.value = window.innerWidth < 1200;
isMedia.value = window.innerWidth < 745;
};
onMounted(() => {
@ -20,7 +23,7 @@ onMounted(() => {
<template>
<div class="account-container">
<div class="sidebar">
<ul>
<ul v-if="!isMedia || showMobileMenu">
<li>
<router-link to="/account/setting">账号设置</router-link>
</li>
@ -33,7 +36,7 @@ onMounted(() => {
<li>
<router-link to="/account/draft">草稿箱</router-link>
</li>
<li v-if="store.state.userInfo.role_id >= 1">
<li v-if="store.getters.isAdmin">
<router-link to="/account/upload-log">管理员: 上传日志</router-link>
</li>
@ -41,6 +44,9 @@ onMounted(() => {
<router-link to="/account">用户信息</router-link>
</li>
</ul>
<button v-if="isMedia" @click="showMobileMenu = !showMobileMenu">
{{ showMobileMenu ? '收起' : '展开功能页' }}
</button>
</div>
<!-- 中间视图区 -->
@ -170,6 +176,20 @@ onMounted(() => {
background-color: #ffb74d; /* 黄色点缀 */
}
.sidebar button {
display: flex;
align-content: flex-start;
color: gray;
width: 100%;
border-radius: 5px;
padding: 5px;
transition: background-color 0.2s ease;
}
.sidebar button:hover {
background: rgba(128, 128, 128, 0.09);
}
@media (max-width: 745px) {
.account-container {
flex-direction: column;

View File

@ -270,7 +270,7 @@ button {
resize: none;
background: #1a1a1a;
color: white;
border: gray solid 1px;
//border: gray solid 1px;
}
.theme-light .input-area textarea,.theme-light .middle .input-area input {

View File

@ -6,12 +6,6 @@ import store from "../../store/index.js";
import api from "../../utils/axios.js";
import swal from "../../utils/sweetalert.js";
const permissionLevel = ref({
"0" : "普通用户",
"1" : "管理员"
})
const isAvatarHover = ref(false)
const isEditingUsername = ref(false)
@ -168,7 +162,7 @@ function saveIntro() {
<div class="permission-row">
<label>账号权限</label>
<span>{{ permissionLevel[store.state.userInfo.role_id] }}</span>
<span>{{ store.getters.userRole }}</span>
</div>
<div class="register-date">

View File

@ -147,24 +147,16 @@ onUnmounted(() => {
<div class="sender-tips">
<img :src="store.getters.profileImage" alt="Avatar">
<div class="text">
<div>{{ store.state.userInfo.username }}</div>
<div><{{ (function (){
if (store.state.userInfo.role_id === 1) {
return '管理员';
}
if (store.state.userInfo.role_id === 0) {
return '普通用户';
}
return '游客'
})() }}></div>
<div>{{ store.state.userInfo.username || '请先登录' }}</div>
<div>{{ '&lt' + store.getters.userRole + '&gt' }}</div>
</div>
<div class="refresh-btn">
<button @click="clickRefresh">刷新</button>
</div>
</div>
<div class="sender-input">
<input class="message-input" v-model="userInput" @keydown.enter="sendMessage">
<button class="send-button" @click="sendMessage" :disabled="sendCD !== 0">
<input class="message-input" v-model="userInput" @keydown.enter="sendMessage" :disabled="!store.state.userInfo.uid">
<button class="send-button" @click="sendMessage" :disabled="sendCD !== 0 || !store.state.userInfo.uid">
{{ sendCD === 0 ? '发送' : sendCD === 'wait' ? '稍等' : `${sendCD}` }}
</button>
</div>

View File

@ -38,7 +38,7 @@ async function deleteMessage(id, message) {
<div class="details">
<div class="userinfo">
<div class="name">{{ message.name }}</div>
<div class="role" v-if="message.role_id === 1">[管理员]</div>
<div class="role">{{ }}</div>
</div>
<div class="content">{{ message.content }}</div>
<div class="bottom">
@ -58,7 +58,7 @@ async function deleteMessage(id, message) {
</div>
<div class="right">
<div v-if="String(message.uid) === store.state.userInfo.uid ||
store.state.userInfo.role_id >= 1" class="delete-message" @click="deleteMessage(message.id, message.content)">
store.getters.isAdmin" class="delete-message" @click="deleteMessage(message.id, message.content)">
<div class="delete-ico"/>
<span>删除留言</span>
</div>

16
src/pages/test.vue Normal file
View File

@ -0,0 +1,16 @@
<script setup>
import GeneralEditor from "../components/GeneralEditor.vue";
</script>
<template>
<div class="container">
<h1>测试页面</h1>
<GeneralEditor></GeneralEditor>
</div>
</template>
<style scoped>
</style>

View File

@ -17,6 +17,7 @@ import Board_page from "../pages/demoPages/messageBoard/Board_page.vue";
import Tools_home from "../pages/Tools_home.vue";
import GpaCalculator_page from "../pages/toolPages/gpaCalculator/gpaCalculator_page.vue";
import About from "../pages/About.vue";
import Test from "../pages/test.vue";
const routes = [
{
@ -66,11 +67,15 @@ const routes = [
{path: '', component: Account_userInfo},
{path: 'upload-log', component: Account_admin_uploadLog}
]
}
}, {
path: '/test_page',
name: 'Test',
component: Test
},
];
const router = createRouter({
history: createWebHistory(), // 使用HTML5历史模式
history: createWebHistory(),
routes
});
@ -81,11 +86,11 @@ router.beforeEach((to, from, next) => {
if (to.path === '/login' && store.state.userInfo.uid) {
next('/account');
}
if (to.path === '/account' && !store.state.userInfo.uid) {
if (to.path.includes('/account') && !store.getters.hasUserInfo) {
next('/login');
}
if (to.path === '/account/upload-log' && store.state.userInfo.role_id < 1) {
next('/account')
if (to.path === '/account/upload-log' && !store.getters.isAdmin) {
next('/account');
}
next();
});

View File

@ -53,8 +53,25 @@ const store = createStore({
}else {
return `https://${getDomain()}/data/user/profile/default.jpg`
}
},
userRole: state => {
switch (state.userInfo.role_id) {
case undefined:
return '游客';
case 0:
return '普通用户';
case 1:
return '认证用户';
case 2:
return '管理员';
case 3:
return '超级管理员';
default:
return '未知';
}
},
isAdmin: state => state.userInfo.role_id >= 2,
},
plugins: [createPersistedStatePlugin({
key: 'cyberStorage',
whitelist: ['theme', 'userInfo', 'demos', 'editStore'],

View File

@ -28,6 +28,23 @@ a:hover {
color: #fff;
}
textarea {
padding: 5px;
font-size: 16px;
font-family: 'Arial', sans-serif;
border: 1px solid transparent;
outline: none;
background-color: #333;
color: #e0e0e0;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
transition: all 0.3s ease;
resize: none;
line-height: 1.6;
}
textarea:focus {
padding: 10px;
}
/* 通用容器 */
.container {
width: 90%;