使用pg数据库重构

This commit is contained in:
2025-02-16 23:08:25 +08:00
parent 3d729996bb
commit 9af60b0dbe
40 changed files with 314 additions and 695 deletions

View File

@@ -1,6 +1,6 @@
<script setup lang='ts'>
import { request, type BaseResponseData } from '@/lib/request';
import { computed, onMounted, reactive, ref, watch } from 'vue';
import { computed, onMounted, reactive, ref, watch, watchEffect } from 'vue';
import { useRoute } from 'vue-router';
import { timestampToString } from '@/lib/timestampToString';
const model = defineModel();
@@ -39,8 +39,9 @@ const loadComment = async () => {
}
}
watch(model, (newValue, oldValue) => {
if (newValue) {
watchEffect(() => {
if (model.value) {
model.value = false;
loadComment();
}
})
@@ -54,8 +55,10 @@ watch(model, (newValue, oldValue) => {
<div class="my-[10px]" v-for="blogcomment of blogCommentList">
<div class=" text-[#555] dark:text-[#fff]">{{ blogcomment.name }}</div>
<div class="text-[12px] text-[#888] dark:text-[#aaa]">IP属地{{ blogcomment.ip_address }}</div>
<div class="text-[12px] text-[#888] dark:text-[#aaa]">{{ timestampToString(blogcomment.time) }}</div>
<div class="py-[10px] border-b border-b-[#ddd] text-[#333] dark:text-[#fff]">{{ blogcomment.content }}</div>
<div class="text-[12px] text-[#888] dark:text-[#aaa]">{{ new Date(blogcomment.created_at).toLocaleString()
}}</div>
<div class="py-[10px] border-b border-b-[#ddd] text-[#333] dark:text-[#fff] whitespace-pre-wrap">{{
blogcomment.content }}</div>
</div>
<div class="text-[14px] text-[#666] dark:text-[#fff] my-[15px]"> {{ getStatusText }} </div>
</div>

View File

@@ -4,7 +4,6 @@ import { ElMessage, ElMessageBox } from 'element-plus';
import { ref, onMounted, onUnmounted } from 'vue';
import { useRoute } from 'vue-router';
import { request, type BaseResponseData } from '@/lib/request';
import RotationVerification from '../Common/RotationVerification.vue';
const route = useRoute()
const bloguuid = route.params.uuid;
const emit = defineEmits(['comment-success'])
@@ -56,7 +55,7 @@ const commentHandle = () => {
cancelButtonText: '取消',
}).then(({ value }) => {
inputCommentName = value ? value : '';
isCaptchaViewShow.value = true;
submitComment();
}).catch(() => {
ElMessage.info('已取消')
})
@@ -73,6 +72,7 @@ const submitComment = async () => {
})
if (commentRes.code == 0) {
emit('comment-success');
inputComment.value = '';
return ElMessage.success('评论成功~');
} else {
throw new Error(commentRes.message);
@@ -96,6 +96,4 @@ const submitComment = async () => {
</div>
</div>
</transition>
<RotationVerification v-if="isCaptchaViewShow"
@fail="() => { isCaptchaViewShow = false; ElMessage.warning('验证失败') }" @success="submitComment" />
</template>

View File

@@ -17,7 +17,7 @@ let copyTextwithMsg = (text : string) => {
<a href="https://beian.miit.gov.cn/">
<div class="mt-[5px] cursor-pointer dark:text-[#ccc]">备案号渝ICP备2023009516号-1</div>
</a>
<div class="mt-[6px] sm:mt-0 dark:text-[#ccc]">Copyright ©2020-2024 TONE All Rights Reserved.</div>
<div class="mt-[6px] sm:mt-0 dark:text-[#ccc]">Copyright ©2020-{{ new Date().getFullYear() }} TONE All Rights Reserved.</div>
</div>
<div class="mr-0 sm:mr-[25px] flex sm:pb-0 pb-[20px]">
<el-popover trigger="click" placement="top" :width="160">

View File

@@ -1,199 +0,0 @@
<script setup lang='ts'>
/**
* 旋转图像验证码组件
* @event success 验证成功
* @event fail 验证失败
* @requires ServerSDK
* @since 1.0.2
*/
import { request, type BaseResponseData } from '@/lib/request';
import { onBeforeMount, onMounted, ref } from 'vue';
let imageBase64 = ref('');
const emit = defineEmits(['fail', 'success'])
let onVerifying = ref(false);
let onVerifyFail = ref(false);
onBeforeMount(async () => {
try {
let res: BaseResponseData = await request.get('captcha')
localStorage.setItem('captcha-session', res.data.session)
imageBase64.value = res.data.imgPreStr + res.data.img;
} catch (error) {
console.log("获取图像验证码失败:" + error);
emit('fail');
}
})
onMounted(() => {
let slider = document.getElementById('RV-slider');
let imageEle = document.getElementById('RV-image');
let isDragging = false;// 正在移动标志位
let silderMoveable = true;// 是否可以开始移动标识位
let startX: number;
let deltaX: number;// 拖动距离
if(!slider || !imageEle){
throw new Error('element is null')
}
slider.addEventListener('mousedown', function (e) {
if (silderMoveable) {
isDragging = true;
startX = e.clientX;
onVerifyFail.value = false;
}
})
slider.addEventListener('touchstart', function (e) {
if (silderMoveable) {
e.preventDefault();
isDragging = true;
startX = e.touches[0].clientX;
onVerifyFail.value = false;
}
}, { passive: false });
document.addEventListener('mousemove', function (e) {
if (isDragging) {
silderMoveable = false;// 产生了位移,则需等待验证后才可再次移动
deltaX = e.clientX - startX;
// 对位移距离限幅
if (deltaX < 0)
deltaX = 0;
if (deltaX > 200)
deltaX = 200;
// 调整滑块条位置样式
slider.style.transform = ` translateX(${deltaX}px)`;
// 调整图片旋转样式 200 -> 360映射 y = 9/5x
imageEle.style.transform = `rotate(${deltaX * 9 / 5}deg)`
}
});
document.addEventListener('touchmove', function (e) {
if (isDragging) {
e.preventDefault();
silderMoveable = false;
deltaX = e.touches[0].clientX - startX;
if (deltaX < 0) deltaX = 0;
if (deltaX > 200) deltaX = 200;
slider.style.transform = `translateX(${deltaX}px)`;
imageEle.style.transform = `rotate(${deltaX * 9 / 5}deg)`;
}
}, { passive: false });
document.addEventListener('mouseup', async () => {
if (isDragging) {
isDragging = false;
if (!deltaX) {
silderMoveable = true;// 位移距离为0无需验证可以继续移动
} else {
try {
onVerifying.value = true;
let res: BaseResponseData = await request.post('checkCaptcha', {
session: localStorage.getItem('captcha-session'),
rotateDeg: deltaX * 9 / 5
})
switch (res.code) {
case 0:
// 验证成功
emit('success');
break;
case -5002:
// 可以再试一次
onVerifyFail.value = true;
slider.style.transition = "transform 0.6s";
slider.style.transform = "translateX(0px)";
imageEle.style.transition = "transform 0.6s";
imageEle.style.transform = "rotate(0deg)";
setTimeout(() => {
isDragging = false;
silderMoveable = true;
slider.style.transition = "transform 0s";
imageEle.style.transition = "transform 0s";
}, 600);
break;
default:
console.log('验证session过期、不存在、服务器错误')
emit('fail');
break;
}
} catch (error) {
console.log("图像验证码错误:" + error);
emit('fail');
} finally {
onVerifying.value = false;
}
}
}
});
document.addEventListener('touchend', async () => {
if (isDragging) {
isDragging = false;
if (!deltaX) {
silderMoveable = true;// 位移距离为0无需验证可以继续移动
} else {
try {
onVerifying.value = true;
let res: BaseResponseData = await request.post('checkCaptcha', {
session: localStorage.getItem('captcha-session'),
rotateDeg: deltaX * 9 / 5
})
switch (res.code) {
case 0:
// 验证成功
emit('success');
break;
case -5002:
// 可以再试一次
onVerifyFail.value = true;
slider.style.transition = "transform 0.6s";
slider.style.transform = "translateX(0px)";
imageEle.style.transition = "transform 0.6s";
imageEle.style.transform = "rotate(0deg)";
setTimeout(() => {
isDragging = false;
silderMoveable = true;
slider.style.transition = "transform 0s";
imageEle.style.transition = "transform 0s";
}, 600);
break;
default:
console.log('验证session过期、不存在、服务器错误')
emit('fail');
break;
}
} catch (error) {
console.log("图像验证码错误:" + error);
emit('fail');
} finally {
onVerifying.value = false;
}
}
}
});
})
</script>
<template>
<transition name="el-fade-in-linear">
<div class="fixed w-full h-full inset-0 bg-[#00000022] z-[2000] flex justify-center items-center" @click="$emit('fail')">
<div class="w-[300px] h-[400px] bg-white dark:bg-[#222] dark:border-[1px] flex flex-col items-center rounded-[15px]" onclick="event.stopPropagation()">
<div class="mt-[25px] text-[#888] dark:text-white">安全验证</div>
<div class="mt-[8px] dark:text-[#ccc]">{{ onVerifying ? "正在验证,请稍后..." : (onVerifyFail ? "验证失败,请再试一次" : "拖动滑块,使图片角度为水平")
}}
</div>
<div class="mt-[28px] w-[160px] h-[160px] rounded-full flex justify-center items-center overflow-hidden">
<img class="w-[226px] h-[226px] bg-gray-400 object-cover" :src="imageBase64" alt="" id="RV-image">
</div>
<div class="w-[240px] h-[40px] rounded-[20px] bg-slate-200 dark:bg-[#444] mt-[25px] flex items-center">
<div class="w-[45px] h-[45px] leading-[45px] bg-white dark:bg-[#999] rounded-full shadow-md text-center cursor-pointer relative translate-x-0" id="RV-slider">
<svg t="1706696449802" class="absolute left-[14px] top-[14px] dark:fill-white" viewBox="0 0 1024 1024" fill="#666" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="1924" width="18" height="18">
<path
d="M567.32505 547.18536c20.970614-21.479197 20.970614-56.307424 0-77.790714L185.251168 77.115332c-20.971637-21.47715-54.975079-21.47715-75.948763 0-20.973684 21.484314-20.973684 56.30947 0 77.793784l344.188016 353.383446-344.188016 353.384469c-20.973684 21.484314-20.973684 56.311517 0 77.79276 20.971637 21.482267 54.975079 21.482267 75.948763 0l382.072858-392.280337 0.001024-0.004094zM440.60802 154.908092l344.18597 353.383446-344.18597 353.385493c-20.973684 21.484314-20.973684 56.311517 0 77.79276 20.972661 21.482267 54.975079 21.482267 75.949786 0l382.074905-392.281361c20.966521-21.478174 20.966521-56.307424 0-77.790714L516.555759 77.115332c-20.972661-21.47715-54.975079-21.47715-75.949786 0-20.971637 21.48329-20.971637 56.30947 0.002047 77.79276z"
p-id="1925"></path>
</svg>
</div>
</div>
</div>
</div>
</transition>
</template>

View File

@@ -6,11 +6,10 @@ import { timestampToString } from '@/lib/timestampToString';
const tableData: Ref<any[]> = ref([])
const dialogEditFormVisible = ref(false);
type BlogContentData = {
id: string,
uuid: string,
title: string,
description: string,
publish_time: Date,
created_at: Date,
src: string,
access_level: number,
visit_count: number,
@@ -34,11 +33,10 @@ const loadTableData = async () => {
}
}
const editForm: BlogContentData = reactive({
id: '',
uuid: '',
title: '',
description: '',
publish_time: new Date(),
created_at: new Date(),
src: '',
encrypt_p: '',
access_level: 0,
@@ -46,22 +44,20 @@ const editForm: BlogContentData = reactive({
like_count: 0
})
const editHandle = (data: any) => {
editForm.id = data.id;
editForm.uuid = data.uuid;
editForm.title = data.title;
editForm.description = data.description;
editForm.publish_time = new Date(+data.publish_time);
editForm.created_at = new Date(data.created_at);
editForm.src = data.src;
editForm.access_level = data.access_level;
editForm.visit_count = data.visit_count;
dialogEditFormVisible.value = true;
}
const addHandle = () => {
editForm.id = '';
editForm.uuid = '';
editForm.title = '';
editForm.description = '';
editForm.publish_time = new Date();
editForm.created_at = new Date();
editForm.src = '';
editForm.access_level = 10;
editForm.visit_count = 0;
@@ -70,16 +66,15 @@ const addHandle = () => {
}
const saveHandle = async () => {
// 表单验证
if (!editForm.title || !editForm.description || !editForm.publish_time || !editForm.src || !editForm.access_level) {
if (!editForm.title || !editForm.description || !editForm.created_at || !editForm.src || !editForm.access_level) {
return ElMessage.warning('请先完成表单')
}
try {
let res: BaseResponseData = await request.post('/console/saveBlog', {
id: editForm.id,
uuid: editForm.uuid,
title: editForm.title,
description: editForm.description,
publish_time: editForm.publish_time.getTime(),
created_at: editForm.created_at,
src: editForm.src,
access_level: editForm.access_level,
})
@@ -134,10 +129,10 @@ const saveHandle = async () => {
return ElMessage.error(`保存失败 ${error}`);
}
}
const delHandle = async (data: { id: string, [key: string]: any }) => {
let { id } = data;
const delHandle = async (data: { uuid: string, [key: string]: any }) => {
let { uuid } = data;
try {
let res: BaseResponseData = await request.delete('/console/blog?id=' + id);
let res: BaseResponseData = await request.delete('/console/blog?uuid=' + uuid);
if (res.code == 0) {
ElMessage.success('删除成功');
loadTableData();
@@ -149,7 +144,7 @@ const delHandle = async (data: { id: string, [key: string]: any }) => {
}
}
const formatTime = (row: any, _column: any, _cellValue: any, _index: any) => {
return timestampToString(row.publish_time);
return new Date(row.created_at).toLocaleString();
}
</script>
<template>
@@ -159,11 +154,10 @@ const formatTime = (row: any, _column: any, _cellValue: any, _index: any) => {
</div>
<!-- 数据列表 -->
<el-table :data="tableData" border class="w-full">
<el-table-column prop="id" label="id" width="50" />
<el-table-column prop="uuid" label="uuid" width="120" show-overflow-tooltip />
<el-table-column prop="title" label="标题" width="240" />
<el-table-column prop="description" label="描述" width="200" show-overflow-tooltip />
<el-table-column prop="publish_time" label="发布时间" width="160" :formatter="formatTime" />
<el-table-column prop="title" label="标题" width="250" />
<el-table-column prop="description" label="描述" width="300" show-overflow-tooltip />
<el-table-column prop="created_at" label="发布时间" width="160" :formatter="formatTime" />
<el-table-column prop="access_level" label="可访问级别" width="100" />
<el-table-column prop="visit_count" label="访问量" width="80" />
<el-table-column prop="like_count" label="点赞量" width="80" />
@@ -171,7 +165,7 @@ const formatTime = (row: any, _column: any, _cellValue: any, _index: any) => {
<template #default="scope">
<el-text>{{ scope.encrypt_p ? "是" : "否" }}</el-text>
</template>
</el-table-column> -->
</el-table-column> -->
<el-table-column fixed="right" label="操作" min-width="110">
<template #default="scope">
<el-button link type="primary" size="small" @click="editHandle(scope.row)">编辑</el-button>
@@ -182,9 +176,6 @@ const formatTime = (row: any, _column: any, _cellValue: any, _index: any) => {
<!-- 编辑添加博客对话框 -->
<el-dialog v-model="dialogEditFormVisible" title="编辑" width="800">
<el-form :model="editForm" label-width="auto" style="margin: 0 30px;">
<el-form-item label="id">
<el-input v-model="editForm.id" disabled />
</el-form-item>
<el-form-item label="uuid">
<el-input v-model="editForm.uuid" disabled />
</el-form-item>
@@ -195,7 +186,7 @@ const formatTime = (row: any, _column: any, _cellValue: any, _index: any) => {
<el-input v-model="editForm.description" type="textarea" autosize />
</el-form-item>
<el-form-item label="发布时间">
<el-date-picker v-model="editForm.publish_time" type="datetime" placeholder="选择发布时间" />
<el-date-picker v-model="editForm.created_at" type="datetime" placeholder="选择发布时间" />
</el-form-item>
<el-form-item label="文章链接">
<el-input v-model="editForm.src" type="textarea" autosize />

View File

@@ -3,7 +3,7 @@ import { onMounted, reactive, ref, type Ref } from 'vue';
import { request, type BaseResponseData } from '../../lib/request'
import { ElMessage } from 'element-plus';
type ResourceData = {
id: string,
uuid: string,
type: string,
recommand: number,
title: string,
@@ -31,7 +31,7 @@ const loadTableData = async () => {
}
}
const editForm: ResourceData = reactive({
id: '',
uuid: '',
type: '',
recommand: 1,
title: '',
@@ -42,7 +42,7 @@ const editForm: ResourceData = reactive({
})
const openEditFormSrc = () => { window.open(editForm.src); }
const editHandle = (data: ResourceData) => {
editForm.id = data.id;
editForm.uuid = data.uuid;
editForm.type = data.type;
editForm.recommand = +data.recommand;
editForm.title = data.title;
@@ -53,7 +53,7 @@ const editHandle = (data: ResourceData) => {
dialogEditFormVisible.value = true;
}
const addHandle = () => {
editForm.id = '';
editForm.uuid = '';
editForm.type = '';
editForm.recommand = 1;
editForm.title = '';
@@ -70,7 +70,7 @@ const saveHandle = async () => {
}
try {
let res: BaseResponseData = await request.post('/console/saveResource', {
id: editForm.id,
uuid: editForm.uuid,
type: editForm.type,
recommand: editForm.recommand,
title: editForm.title,
@@ -91,9 +91,9 @@ const saveHandle = async () => {
}
}
const delHandle = async (data: { id: string, [key: string]: any }) => {
let { id } = data;
let { uuid } = data;
try {
let res: BaseResponseData = await request.delete('/console/resource?id=' + id);
let res: BaseResponseData = await request.delete('/console/resource?uuid=' + uuid);
if (res.code == 0) {
ElMessage.success('删除成功');
loadTableData();
@@ -104,6 +104,9 @@ const delHandle = async (data: { id: string, [key: string]: any }) => {
return ElMessage.error(`删除失败 ${error}`);
}
}
const formatTime = (row: any, _column: any, _cellValue: any, _index: any) => {
return new Date(row.created_at).toLocaleString();
}
</script>
<template>
<div class="py-[15px] px-[20px]">
@@ -112,7 +115,7 @@ const delHandle = async (data: { id: string, [key: string]: any }) => {
</div>
<!-- 数据列表 -->
<el-table :data="tableData" border class="w-full">
<el-table-column prop="id" label="id" width="50" />
<el-table-column prop="uuid" label="uuid" width="60" show-overflow-tooltip />
<el-table-column prop="type" label="类型" width="80" sortable>
<template #default="scope">
{{ scope.row.type == 'resource' ? '资源' : scope.row.type == 'download' ? '下载' : '未知' }}
@@ -140,12 +143,13 @@ const delHandle = async (data: { id: string, [key: string]: any }) => {
<el-button link type="primary" size="small" @click="delHandle(scope.row)">删除</el-button>
</template>
</el-table-column>
<el-table-column prop="created_at" label="创建时间" width="180" :formatter="formatTime" />
</el-table>
<!-- 编辑添加资源对话框 -->
<el-dialog v-model="dialogEditFormVisible" title="编辑" width="800">
<el-form :model="editForm" label-width="auto" class="mx-[30px]">
<el-form-item label="id">
<el-input v-model="editForm.id" disabled />
<el-form-item label="uuid">
<el-input v-model="editForm.uuid" disabled />
</el-form-item>
<el-form-item label="类型">
<el-select v-model="editForm.type" placeholder="请选择类型">

View File

@@ -4,8 +4,8 @@ type BaseResponseData = {
message: string,
data: any
};
// axios.defaults.baseURL = "http://localhost:23500";
axios.defaults.baseURL = "https://tonesc.cn/apis";
axios.defaults.baseURL = "http://localhost:23500";
// axios.defaults.baseURL = "https://tonesc.cn/apis";
axios.interceptors.response.use((response) => {
if (response.data && response.data.code == -5) {

View File

@@ -1,7 +1,6 @@
<script setup lang='ts'>
import { ElMessage } from 'element-plus';
import { onMounted, reactive, ref } from 'vue';
import RotationVerification from '@/components/Common/RotationVerification.vue';
import { request, type BaseResponseData } from '@/lib/request';
const containerHeight = ref('800px');
@@ -11,21 +10,17 @@ const formData = reactive({
username: '',
password: ''
})
const loginHandle = () => {
const login = async () => {
if (!formData.username || !formData.password) {
return ElMessage.warning('请填写账户名和密码')
}
isCaptchaShow.value = true;
}
const login = async () => {
loginStatus.value = true;
try {
loginStatus.value = true;
let loginRes: BaseResponseData = await request.post('/console/login', {
username: formData.username,
password: formData.password,
session: localStorage.getItem('captcha-session')
})
loginStatus.value = false;
switch (loginRes.code) {
case 0:
// 成功
@@ -50,8 +45,9 @@ const login = async () => {
return ElMessage.error(`未知错误 ${loginRes.message}`)
}
} catch (error) {
loginStatus.value = false;
return ElMessage.error(`未知错误 ${error}`)
} finally {
loginStatus.value = false;
}
}
onMounted(async () => {
@@ -75,12 +71,11 @@ onMounted(async () => {
clearable>
</el-input>
<el-input v-model="formData.password" show-password class="w-full h-[35px] text-[16px] mt-[10px]"
placeholder="密码" @keyup.enter="loginHandle">
placeholder="密码" @keyup.enter="login">
</el-input>
<el-button class="mt-[12px] mb-[120px] w-full h-[35px] font-bold login-button hover:!bg-white hover:!border-gray-300 hover:!text-gray-800" @click="loginHandle"
:loading="loginStatus">登录</el-button>
<el-button
class="mt-[12px] mb-[120px] w-full h-[35px] font-bold login-button hover:!bg-white hover:!border-gray-300 hover:!text-gray-800"
@click="login" :loading="loginStatus">登录</el-button>
</div>
</div>
<RotationVerification v-if="isCaptchaShow" @fail="() => { isCaptchaShow = false; ElMessage.warning('验证失败') }"
@success="() => { isCaptchaShow = false; login() }" />
</template>