完成新增项目的富文本加密+修改项目的富文本加密及解密+新增项目通知的加密+编辑项目通知的加密与解密+修改项目富文本+修改编辑项目样式

This commit is contained in:
2025-06-19 20:09:38 +08:00
parent e64541bf18
commit ee3a0c7853
12 changed files with 459 additions and 216 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -198,6 +198,7 @@
</form>
</template>
<script setup lang="ts">
import { reactive, onMounted, nextTick,ref } from 'vue';
import RichTextEditor from '../components/RichTextEditor.vue';
@ -220,6 +221,7 @@ interface ProjectForm {
}
const fileInput = ref<HTMLInputElement | null>(null);
const fileName = ref('');
const handleFileUpload = async (event: Event) => {
const input = event.target as HTMLInputElement;
const file = input.files?.[0];
@ -227,7 +229,6 @@ const handleFileUpload = async (event: Event) => {
if (!file) return;
try {
// 重命名局部变量避免冲突(修复错误❷)
const uploadFormData = new FormData();
uploadFormData.append('biz', 'project');
uploadFormData.append('file', file);
@ -241,7 +242,6 @@ const handleFileUpload = async (event: Event) => {
});
if (res.code === 1) {
// 正确访问响应式对象
formData.projectImage = res.data;
fileName.value = file.name;
}
@ -251,11 +251,6 @@ const handleFileUpload = async (event: Event) => {
}
};
// 添加文件名响应式变量
const fileName = ref('');
// 响应式表单数据
const formData = reactive<ProjectForm>({
projectName: '',
@ -270,39 +265,90 @@ const formData = reactive<ProjectForm>({
projectImage: ''
});
// Base64编码函数
const encode64 = (text: string): string => {
return btoa(
encodeURIComponent(text).replace(
/%([0-9A-F]{2})/g,
(_, p1) => String.fromCharCode(parseInt(p1, 16)))
)
};
// 生命周期钩子
onMounted(() => {
// 初始处理
nextTick(() => {
// 为手机预览添加初始样式
const phoneScreen = document.querySelector('.phone-screen');
if (phoneScreen) {
// 添加阴影和动画效果
phoneScreen.classList.add('animate-fade-in');
}
});
});
const encryptValue = (value: any, key: string): any => {
// 特定字段不加密
if (key === 'projectStatus') {
return value;
}
// 数值型字段直接返回,不加密
if (key === 'projectSettlementCycle' || key === 'maxPromoterCount' || key === 'projectImage' || key === 'projectName') {
return value;
}
try {
// 将值转换为字符串
const valueString = typeof value === 'object'
? JSON.stringify(value)
: String(value);
// 使用Base64编码数据
return encode64(valueString);
} catch (error) {
console.error('Base64编码失败:', error);
return value; // 编码失败返回原始值
}
};
const handleSubmit = async () => {
try {
// 表单验证
if (!validateForm()) {
alert('请填写所有必填字段');
return;
}
const storedToken = localStorage.getItem('token');
const res:any = await myAxios.post(`/project/add`,
JSON.stringify(formData),
{
// 创建加密后的表单对象 - 保持原始数据结构
const encryptedFormData: Record<string, any> = {};
console.log("===== 字段Base64编码结果 =====");
Object.entries(formData).forEach(([key, value]) => {
// 跳过空值字段
if (value === null || value === undefined || value === '') return;
// 对字段值进行Base64编码
encryptedFormData[key] = encryptValue(value, key);
// 打印编码结果
console.log(`字段: ${key}`);
console.log(`原始值: ${value}`);
console.log(`编码值: ${encryptedFormData[key]}`);
console.log(`类型: ${typeof encryptedFormData[key]}`);
console.log('----------------------');
});
console.log("=======================");
// 发送Base64编码后的数据
const res: any = await myAxios.post(`/project/add`, encryptedFormData, {
headers: {
'Content-Type': 'application/json',
'Authorization': storedToken
}
}
);
});
if (res.code === 1) {
alert('项目创建成功!');
// router.back();
router.push('/project')
// 重置表单
Object.assign(formData, {
projectName: '',
projectSettlementCycle: 2,
@ -315,16 +361,40 @@ const handleSubmit = async () => {
applyPromoCodeDesc: '',
projectImage: ''
});
fileName.value = ''; // 重置文件名
router.push('/project');
} else {
alert(`创建失败:${res.message}`);
}
} catch (error) {
console.error('请求失败:', error);
alert('提交失败,请检查控制台获取详细信息');
}
};
const validateForm = (): boolean => {
const requiredFields: (keyof ProjectForm)[] = [
'projectName',
'projectImage',
'projectSettlementCycle',
'maxPromoterCount',
'projectStatus',
'projectDescription',
'settlementDesc',
'projectDesc',
'projectFlow',
'applyPromoCodeDesc'
];
return requiredFields.every(field => {
const value = formData[field];
return value !== '' && value !== null && value !== undefined;
});
};
</script>
<style scoped>
.modern-form {
max-width: 90%;

View File

@ -86,6 +86,13 @@ import myAxios from "../../api/myAxios.ts";
import RichTextEditor from '../components/RichTextEditor.vue';
import {useRoute} from "vue-router";
function encode64(text: string): string {
// 使用_代替未使用的match参数
return btoa(encodeURIComponent(text).replace(/%([0-9A-F]{2})/g, (_, p1) => {
return String.fromCharCode(parseInt(p1, 16));
}));
}
interface NotificationForm {
notificationTitle: string;
notificationContent: string;
@ -110,9 +117,16 @@ onMounted(() => {
const handleSubmit = async () => {
try {
const storedToken = localStorage.getItem('token');
// 创建要发送的数据副本
const payload = {
...formData,
notificationContent: encode64(formData.notificationContent)
};
const res: any = await myAxios.post(
'/projectNotification/add',
JSON.stringify(formData),
JSON.stringify(payload),
{
headers: {
'Content-Type': 'application/json',
@ -124,11 +138,10 @@ const handleSubmit = async () => {
if (res.code === 1) {
alert('通知发布成功!');
router.back();
Object.assign(formData, {
notificationTitle: '',
notificationContent: '',
projectId: ''
});
// 重置表单
formData.notificationTitle = '';
formData.notificationContent = '';
formData.projectId = '';
} else {
alert(`发布失败:${res.message}`);
}

View File

@ -55,7 +55,24 @@ import myAxios from "../../api/myAxios.ts";
import { NotificationOutlined } from '@ant-design/icons-vue';
import RichTextEditor from '../components/RichTextEditor.vue'; // 引入富文本编辑器
import '@vueup/vue-quill/dist/vue-quill.snow.css'; // 引入富文本编辑器样式
function encode64(text: string): string {
return btoa(encodeURIComponent(text).replace(/%([0-9A-F]{2})/g, (_, p1) => {
return String.fromCharCode(parseInt(p1, 16));
}));
}
function decode64(text: string): string {
try {
return decodeURIComponent(
Array.from(atob(text), char =>
'%' + ('00' + char.charCodeAt(0).toString(16)).slice(-2)
).join('')
);
} catch (error) {
console.error('Base64解码失败:', error);
return text; // 解码失败时返回原始文本
}
}
interface NotificationDetail {
id: string; // 改为 string 类型
notificationTitle: string;
@ -94,44 +111,45 @@ const cancelEdit = () => {
isEditing.value = false;
};
// 保存编辑 - 根据接口要求修改
// 修改保存函数
const saveNotice = async () => {
try {
loading.value = true;
const storedToken = localStorage.getItem('token');
// 创建符合接口要求的JSON对象
// 对通知内容进行Base64编码
const encodedContent = encode64(editableNotice.value.notificationContent || '');
// 创建请求数据
const requestData = {
id: editableNotice.value.id,
notificationTitle: editableNotice.value.notificationTitle,
notificationContent: editableNotice.value.notificationContent,
notificationContent: encodedContent, // 使用编码后的内容
projectId: editableNotice.value.projectId
};
// 发送JSON格式的数据
const res:any = await myAxios.post(
"/projectNotification/update",
requestData, // 直接发送JSON对象
requestData,
{
headers: {
Authorization: storedToken,
'Content-Type': 'application/json' // 关键修改使用JSON格式
'Content-Type': 'application/json'
}
}
);
if (res.code === 1) {
message.success('更新成功');
// 更新本地数据并退出编辑模式
projectNotice.value = { ...editableNotice.value };
// 更新本地数据并退出编辑模式 - 使用解码后的内容
projectNotice.value = {
...editableNotice.value, // 这里包含的是用户编辑的原始内容(未编码)
};
isEditing.value = false;
} else {
message.error(res.message || '更新失败');
}
} catch (error: any) {
console.error('更新通知失败:', error);
// 改进错误处理
const errMsg = error.response?.data?.message ||
error.message ||
'更新通知失败,请检查网络';
@ -140,9 +158,6 @@ const saveNotice = async () => {
loading.value = false;
}
};
const fetchNotificationDetail = async (id: number) => {
try {
loading.value = true;
@ -161,10 +176,13 @@ const fetchNotificationDetail = async (id: number) => {
);
if (res.code === 1 && res.data) {
// 对通知内容进行Base64解码
const decodedContent = decode64(res.data.notificationContent);
projectNotice.value = {
id: res.data.id,
notificationTitle: res.data.notificationTitle,
notificationContent: res.data.notificationContent,
notificationContent: decodedContent,
projectId: res.data.projectId,
createTime: res.data.createTime
};
@ -183,8 +201,6 @@ const fetchNotificationDetail = async (id: number) => {
}
};
const goBack = () => {
router.back();
};

View File

@ -110,10 +110,6 @@
</template>
</template>
</a-table>
</template>
<script lang="ts" setup>

View File

@ -3,7 +3,7 @@ import {useRoute, useRouter} from "vue-router";
import { onMounted, ref } from "vue";
import myAxios from "../../api/myAxios";
import { message } from "ant-design-vue";
import { QuillEditor } from '@vueup/vue-quill';
import RichTextEditor from '../components/RichTextEditor.vue';
import {Form} from 'ant-design-vue'
import {downLoadImage} from "../../api/ImageUrl.ts";
@ -36,10 +36,53 @@ const projectData = ref<ProjectDetail>({
projectFlow: '',
applyPromoCodeDesc: ''
});
const editData = ref<ProjectDetail | null>(null);
const projectId = ref<string | null>(null);
const isEditing = ref(false);
const fileInput = ref<HTMLInputElement | null>(null);
function encode64(text: string): string {
return btoa(encodeURIComponent(text).replace(/%([0-9A-F]{2})/g, (_, p1) => {
return String.fromCharCode(parseInt(p1, 16));
}));
}
function decode64(text: string): string {
return decodeURIComponent(
Array.from(atob(text), char =>
'%' + ('00' + char.charCodeAt(0).toString(16)).slice(-2)
).join('')
);
}
const decryptProjectData = (data: ProjectDetail): ProjectDetail => {
const decryptedData: ProjectDetail = { ...data };
const fieldsToDecrypt: (keyof ProjectDetail)[] = [
'projectName',
'projectDescription',
'settlementDesc',
'projectDesc',
'projectFlow',
'applyPromoCodeDesc'
];
fieldsToDecrypt.forEach(field => {
const value = decryptedData[field];
if (typeof value === 'string' && value) {
try {
(decryptedData as Record<keyof ProjectDetail, any>)[field] = decode64(value);
} catch (error) {
console.error(`Base64解码失败 (${field}):`, error);
(decryptedData as Record<keyof ProjectDetail, any>)[field] = value;
}
}
});
return decryptedData;
};
// 图片上传处理逻辑
const handleFileUpload = async (event: Event) => {
@ -52,7 +95,6 @@ const handleFileUpload = async (event: Event) => {
// 本地预览
const reader = new FileReader();
reader.onload = (e) => {
// 仅设置临时预览,不覆盖实际值
previewImage.value = e.target?.result as string;
};
reader.readAsDataURL(file);
@ -71,8 +113,9 @@ const handleFileUpload = async (event: Event) => {
});
if (res.code === 1 && res.data) {
// 核心修改将返回的文件标识符赋值给projectImage
projectData.value.projectImage = res.data;
if (editData.value) {
editData.value.projectImage = res.data;
}
message.success('图片上传成功');
} else {
throw new Error(res.message || '上传失败');
@ -81,12 +124,15 @@ const handleFileUpload = async (event: Event) => {
console.error('上传失败:', error);
message.error('文件上传失败');
previewImage.value = '';
} finally {
// 重置文件输入,允许重复上传同一文件
if (input) input.value = '';
}
};
const previewImage = ref('');
const formRef = ref<typeof Form>(); // 添加表单引用
const formRef = ref<typeof Form>();
// 添加数字输入校验方法
const handleNumberInput = (e: KeyboardEvent) => {
@ -105,24 +151,38 @@ const handlePaste = (e: ClipboardEvent) => {
}
};
// 更新项目时提交处理
const finishEditing = async () => {
//isEditing.value = false;
try {
if (!editData.value) {
message.error('编辑数据不存在');
return;
}
const storedToken = localStorage.getItem('token');
if (!storedToken) throw new Error('未找到登录信息');
if (!storedToken) {
message.error('未找到登录信息');
return;
}
// 转换富文本内容为字符串
const payload = {
...projectData.value,
settlementDesc: String(projectData.value.settlementDesc),
projectDesc: String(projectData.value.projectDesc),
projectFlow: String(projectData.value.projectFlow),
applyPromoCodeDesc: String(projectData.value.applyPromoCodeDesc)
};
const payload: Record<string, any> = { ...editData.value };
const res:any = await myAxios.post("/project/update",
const fieldsToEncode: (keyof ProjectDetail)[] = [
'projectDescription',
'settlementDesc',
'projectDesc',
'projectFlow',
'applyPromoCodeDesc'
];
fieldsToEncode.forEach(field => {
const value = payload[field];
if (typeof value === 'string' && value) {
payload[field] = encode64(value);
}
});
const res: any = await myAxios.post(
"/project/update",
JSON.stringify(payload),
{
headers: {
@ -131,25 +191,24 @@ const finishEditing = async () => {
}
}
);
console.log(res)
if (res.code === 1) {
message.success('项目更新成功');
projectData.value = editData.value;
editData.value = null;
router.push('/project')
} else {
message.error(res.message);
}
} catch (error) {
console.error("更新失败:", error);
message.error('项目更新失败');
return;
} finally {
isEditing.value = false;
previewImage.value = ''; // 清空预览图
}
};
if (typeof route.query.id === 'string') {
projectId.value = route.query.id;
}
@ -160,24 +219,34 @@ onMounted(() => {
}
});
const handleIdSearch = async (id: string) => {
if (!id) return message.warning("请输入项目ID");
if (!/^\d+$/.test(id)) return message.warning("ID必须为数字");
if (!id) {
message.warning("请输入项目ID");
return;
}
if (!/^\d+$/.test(id)) {
message.warning("ID必须为数字");
return;
}
loading.value = true;
try {
const storedToken = localStorage.getItem('token');
if (!storedToken) throw new Error('未找到登录信息');
if (!storedToken) {
message.error('未找到登录信息');
return;
}
const res:any = await myAxios.post("/project/queryById",
const res: any = await myAxios.post(
"/project/queryById",
{ id: parseInt(id) },
{ headers: { Authorization: storedToken } }
);
if (res.code === 1 && res.data) {
console.log('Received project data:', res.data);
projectData.value = res.data;
const decryptedData = decryptProjectData(res.data);
projectData.value = decryptedData;
} else {
message.error(res.message || '查询失败');
}
@ -194,6 +263,9 @@ const updateProject = () => {
message.warning('项目数据尚未加载完成,请稍后再试');
return;
}
// 创建编辑数据的深拷贝副本
editData.value = JSON.parse(JSON.stringify(projectData.value));
previewImage.value = ''; // 进入编辑模式时重置预览图
isEditing.value = true;
};
@ -202,12 +274,17 @@ const router = useRouter();
const goBack = () => {
router.push('/project')
};
</script>
// 触发文件选择
const triggerFileInput = () => {
if (fileInput.value) {
fileInput.value.click();
}
};
</script>
<template>
<div class="project-detail-container">
<!-- 加载中 -->
<div v-if="loading" class="loading">加载中...</div>
@ -265,9 +342,9 @@ const goBack = () => {
</div>
</div>
<!-- 编辑页 - 使用 addProject.vue 结构 -->
<div v-if="isEditing && projectData" class="add-project-container">
<a-form ref="formRef" :model="projectData" layout="vertical" @submit.prevent="finishEditing">
<!-- 编辑页 -->
<div v-if="isEditing && editData" class="add-project-container">
<a-form ref="formRef" :model="editData" layout="vertical" @submit.prevent="finishEditing">
<div class="form-section">
<div class="form-header">
<h2>项目基本信息</h2>
@ -276,12 +353,12 @@ const goBack = () => {
<a-row :gutter="16">
<a-col :span="12">
<a-form-item label="项目名称">
<a-input v-model:value="projectData.projectName" placeholder="请输入项目名称" />
<a-input v-model:value="editData.projectName" placeholder="请输入项目名称" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item label="项目状态">
<a-select v-model:value="projectData.projectStatus" placeholder="请选择状态">
<a-select v-model:value="editData.projectStatus" placeholder="请选择状态">
<a-select-option value="running">运行中</a-select-option>
<a-select-option value="full">人数已满</a-select-option>
<a-select-option value="paused">已暂停</a-select-option>
@ -303,7 +380,7 @@ const goBack = () => {
}]"
>
<a-input-number
v-model:value="projectData.projectSettlementCycle"
v-model:value="editData.projectSettlementCycle"
style="width: 100%"
:min="0"
@keypress="handleNumberInput"
@ -323,7 +400,7 @@ const goBack = () => {
}]"
>
<a-input-number
v-model:value="projectData.maxPromoterCount"
v-model:value="editData.maxPromoterCount"
style="width: 100%"
:min="1"
@keypress="handleNumberInput"
@ -342,12 +419,14 @@ const goBack = () => {
@change="handleFileUpload"
ref="fileInput"
>
<div class="upload-button" @click="fileInput?.click()">
<span v-if="!projectData.projectImage && !previewImage">点击上传图片</span>
<span v-else class="file-name">已选择图片</span>
<div class="upload-area" @click="triggerFileInput">
<div class="upload-button">
<span>点击上传图片</span>
<div class="file-name">支持 JPG/PNG 格式大小不超过 5MB</div>
</div>
<!-- 修改优先显示预览图没有预览图时显示服务器图片 -->
<div v-if="previewImage || projectData.projectImage" class="preview-image">
</div>
<div v-if="previewImage || editData.projectImage" class="preview-image">
<img
v-if="previewImage"
:src="previewImage"
@ -355,7 +434,7 @@ const goBack = () => {
>
<img
v-else
:src="downLoadImage + projectData.projectImage"
:src="downLoadImage + editData.projectImage"
alt="项目封面预览"
>
</div>
@ -363,28 +442,60 @@ const goBack = () => {
</a-form-item>
<a-form-item label="项目描述">
<a-textarea v-model:value="projectData.projectDescription" :rows="4" />
<textarea
v-model="editData.projectDescription"
class="textarea-field"
placeholder="请输入项目简介..."
maxlength="30"
></textarea>
<div class="absolute right-2 bottom-2 text-xs text-gray-500">
{{ editData.projectDescription.length }}/30
</div>
</a-form-item>
</div>
<div class="form-section">
<h2>富文本配置</h2>
<a-form-item label="结算说明">
<QuillEditor v-model:content="projectData.settlementDesc" content-type="html" />
</a-form-item>
<div class="rich-text-container">
<div class="rich-text-columns">
<div class="rich-text-group">
<span class="label-text">结算说明</span>
<RichTextEditor
v-model="editData.settlementDesc"
:disable="false"
@content-change="(html: string) => { if (editData) editData.settlementDesc = html }"
/>
</div>
<a-form-item label="项目说明">
<QuillEditor v-model:content="projectData.projectDesc" content-type="html" />
</a-form-item>
<div class="rich-text-group">
<span class="label-text">项目说明</span>
<RichTextEditor
v-model="editData.projectDesc"
:disable="false"
@content-change="(html: string) => { if (editData) editData.projectDesc = html }"
/>
</div>
<a-form-item label="项目流程">
<QuillEditor v-model:content="projectData.projectFlow" content-type="html" />
</a-form-item>
<div class="rich-text-group">
<span class="label-text">项目流程</span>
<RichTextEditor
v-model="editData.projectFlow"
:disable="false"
@content-change="(html: string) => { if (editData) editData.projectFlow = html }"
/>
</div>
<a-form-item label="申请推广码说明">
<QuillEditor v-model:content="projectData.applyPromoCodeDesc" content-type="html" />
</a-form-item>
<div class="rich-text-group">
<span class="label-text">申请推广码说明</span>
<RichTextEditor
v-model="editData.applyPromoCodeDesc"
:disable="false"
@content-change="(html: string) => { if (editData) editData.applyPromoCodeDesc = html }"
/>
</div>
</div>
</div>
</div>
<div class="form-actions">
@ -403,10 +514,8 @@ const goBack = () => {
margin-bottom: 24px;
}
/* 原有样式保持不变 */
.project-detail-container {
max-width: 1200px;
width: 100%;
margin: 2rem auto;
padding: 0 1rem;
}
@ -436,13 +545,7 @@ const goBack = () => {
font-size: 2rem;
color: #1a1a1a;
margin: 0;
}
.status-tag {
padding: 4px 12px;
border-radius: 16px;
color: white;
font-size: 0.9rem;
flex-grow: 1;
}
.basic-info {
@ -540,31 +643,16 @@ const goBack = () => {
.project-title {
font-size: 1.6rem;
}
.header-section {
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
}
.form-group label {
display: block;
font-weight: bold;
margin-bottom: 0.5rem;
}
.form-group input,
.form-group textarea {
width: 100%;
padding: 0.6rem;
font-size: 1rem;
border: 1px solid #ccc;
border-radius: 6px;
resize: vertical;
}
.form-group textarea {
height: 100px;
}
.add-project-container {
max-width: 960px;
width: 100%;
margin: 2rem auto;
padding: 2rem;
background: #fff;
@ -586,25 +674,16 @@ const goBack = () => {
.form-actions {
text-align: right;
margin-top: 2rem;
}
.ql-container {
min-height: 120px;
}
.preview-image {
margin-top: 1rem;
}
.preview-image img {
max-width: 200px;
height: auto;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
/* 保持原有上传样式 */
.file-upload {
position: relative;
margin-top: 1rem;
}
.file-input {
@ -614,16 +693,22 @@ const goBack = () => {
height: 1px;
}
.upload-area {
cursor: pointer;
}
.upload-button {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 0.8rem 1.5rem;
padding: 1.5rem;
background: #f8fafc;
border: 2px dashed #cbd5e1;
border-radius: 0.75rem;
cursor: pointer;
transition: all 0.3s ease;
text-align: center;
}
.upload-button:hover {
@ -631,23 +716,41 @@ const goBack = () => {
border-color: #94a3b8;
}
.file-name {
color: #64748b;
font-size: 0.9rem;
.upload-button span {
font-weight: 500;
color: #334155;
}
/*橙色按钮*/
.file-name {
color: #64748b;
font-size: 0.85rem;
margin-top: 0.5rem;
}
.preview-image {
margin-top: 1rem;
text-align: center;
}
.preview-image img {
max-width: 100%;
max-height: 300px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
border: 1px solid #eee;
}
.custom-button {
background-color: #ffa940;
border-color: #ffa940;
color: #fff;
font-weight: 500;
}
.custom-button:hover,
.custom-button:focus {
background-color: #ffa940;
border-color: #ffa940;
background-color: #fa8c16;
border-color: #fa8c16;
color: #fff;
}
@ -658,7 +761,6 @@ const goBack = () => {
color: #fff;
}
/* 危险按钮样式 */
.custom-button.ant-btn-dangerous {
background-color: #ff4d4f;
border-color: #ff4d4f;
@ -670,23 +772,89 @@ const goBack = () => {
border-color: #ff7875;
}
.custom-search :deep(.ant-input-search-button) {
background-color: #ffa940;
border-color: #ffa940;
.rich-text-container {
margin-top: 1.5rem;
}
.custom-search :deep(.ant-input-search-button:hover),
.custom-search :deep(.ant-input-search-button:focus) {
background-color: #fa8c16;
border-color: #fa8c16;
.rich-text-columns {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 1rem;
align-items: start;
}
/* 保持输入框原有样式不变 */
.custom-search :deep(.ant-input) {
border-right-color: #ffa940;
.rich-text-group {
display: flex;
flex-direction: column;
gap: 0.5rem;
height: 100%;
border: 2px solid #e2e8f0;
border-radius: 0.75rem;
overflow: hidden;
transition: all 0.3s ease;
min-height: 320px;
flex-grow: 1;
padding: 10px;
background: white;
}
.rich-text-group:focus-within {
border-color: #6366f1;
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
}
.label-text {
display: block;
font-size: 0.95rem;
font-weight: 600;
color: #3b4151;
padding-left: 0.2rem;
transition: color 0.3s ease;
}
.rich-text-group:focus-within .label-text {
color: #6366f1;
}
@media (max-width: 1280px) {
.rich-text-columns {
grid-template-columns: repeat(2, 1fr);
gap: 1.2rem;
}
}
@media (max-width: 768px) {
.rich-text-columns {
grid-template-columns: 1fr;
gap: 1.5rem;
}
}
:deep(.ql-container) {
height: 280px;
border: none !important;
}
.textarea-field {
min-height: 100px;
resize: vertical;
}
.textarea-field:focus {
border-color: #6366f1;
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
outline: none;
}
.textarea-field {
width: 100%;
padding: 0.8rem 1.2rem;
border: 2px solid #e2e8f0;
border-radius: 0.75rem;
transition: all 0.3s ease;
font-size: 1rem;
background: white;
}
</style>

View File

@ -4,6 +4,19 @@ import {useRoute, useRouter} from "vue-router";
import myAxios from "../../api/myAxios";
import {message} from "ant-design-vue";
function decode64(text: string): string {
try {
return decodeURIComponent(
Array.from(atob(text), char =>
'%' + ('00' + char.charCodeAt(0).toString(16)).slice(-2)
).join('')
);
} catch (error) {
console.error('Base64解码失败:', error);
return text; // 解码失败时返回原始文本
}
}
const columns = [
{
title: '项目通知ID',
@ -112,6 +125,7 @@ onMounted(() => {
getNotifications(projectId.value);
}
});
const getNotifications = async (id: string | number) => {
const storedToken = localStorage.getItem('token');
try {
@ -126,10 +140,13 @@ const getNotifications = async (id: string | number) => {
}
}
);
console.log(response)
if (response.code === 1) {
originalTableData.value = response.data;
displayData.value = response.data; // 初始显示所有数据
// 对通知内容进行Base64解码
originalTableData.value = response.data.map((item:any) => ({
...item,
notificationContent: decode64(item.notificationContent)
}));
displayData.value = originalTableData.value;
} else {
error.value = "获取通知列表失败";
originalTableData.value = [];
@ -144,6 +161,7 @@ const getNotifications = async (id: string | number) => {
}
};
onMounted(() => {
if (projectId.value) {
getNotifications(projectId.value);
@ -232,6 +250,7 @@ const goBack = () => {
</a-form-item>
<a-button class="custom-button" @click="goAddProjectNotice">新增项目通知</a-button>
<a-button class="custom-button" @click="reset">重置搜索</a-button>
<a-button @click="goBack" class="custom-button">返回</a-button>
</a-space>
</a-form>
</div>
@ -256,11 +275,6 @@ const goBack = () => {
</a-space>
</template>
</template>
<template #footer>
<div class="table-footer">
<a-button @click="goBack" class="back-button">返回</a-button>
</div>
</template>
</a-table>
</template>
@ -274,29 +288,6 @@ const goBack = () => {
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
}
.error-alert {
padding: 1rem;
background: #ffe3e3;
color: #ff4444;
border-radius: 6px;
display: flex;
align-items: center;
gap: 0.8rem;
margin-top: 1rem;
}
.error-icon {
display: inline-block;
width: 1.2rem;
height: 1.2rem;
border-radius: 50%;
background: #ff4444;
color: white;
text-align: center;
line-height: 1.2rem;
font-weight: bold;
}
:deep(.ant-table-thead) {
background-color: #fafafa !important;
font-weight: 600;
@ -362,17 +353,6 @@ const goBack = () => {
border-right-color: #ffa940;
}
/* 新增表格页脚样式 */
.table-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 0;
}
.back-button {
margin-right: 16px;
}
/* 调整分页器位置 */
:deep(.ant-table-pagination.ant-pagination) {

View File

@ -1 +1 @@
{"root":["./src/main.ts","./src/vite-env.d.ts","./src/api/imageurl.ts","./src/api/myaxios.ts","./src/router/index.ts","./src/router/routes.ts","./src/store/index.ts","./src/store/userstore.ts","./src/types/wangeditor.d.ts","./src/app.vue","./src/layout/managelayout.vue","./src/layout/manage/manageheader.vue","./src/layout/manage/managesidebar.vue","./src/view/index.vue","./src/view/login.vue","./src/view/test.vue","./src/view/community/community.vue","./src/view/components/richtexteditor.vue","./src/view/course/linkedcourse.vue","./src/view/course/localcurriculum.vue","./src/view/project/addproject.vue","./src/view/project/addprojectnotice.vue","./src/view/project/moneydetail.vue","./src/view/project/noticedetail.vue","./src/view/project/project.vue","./src/view/project/projectdetail.vue","./src/view/project/projectnotice.vue","./src/view/project/promotioncode.vue","./src/view/settlement/applicationrecord.vue","./src/view/settlement/moneyrecord.vue","./src/view/userlist/userlist.vue","./src/view/work/workdetail.vue","./src/view/work/worklist.vue"],"version":"5.6.3"}
{"root":["./src/main.ts","./src/vite-env.d.ts","./src/api/imageurl.ts","./src/api/myaxios.ts","./src/router/index.ts","./src/router/routes.ts","./src/store/index.ts","./src/store/userstore.ts","./src/types/wangeditor.d.ts","./src/app.vue","./src/layout/managelayout.vue","./src/layout/manage/manageheader.vue","./src/layout/manage/managesidebar.vue","./src/view/index.vue","./src/view/login.vue","./src/view/test.vue","./src/view/community/community.vue","./src/view/components/richtexteditor.vue","./src/view/course/linkedcourse.vue","./src/view/course/localcurriculum.vue","./src/view/project/addproject.vue","./src/view/project/addprojectnotice.vue","./src/view/project/moneydetail.vue","./src/view/project/noticedetail.vue","./src/view/project/project.vue","./src/view/project/projectdetail.vue","./src/view/project/projectnotice.vue","./src/view/project/promotioncode.vue","./src/view/settlement/applicationrecord.vue","./src/view/settlement/moneyrecord.vue","./src/view/settlement/withdrawalapplicationrecord.vue","./src/view/userlist/userlist.vue","./src/view/work/workdetail.vue","./src/view/work/worklist.vue"],"version":"5.6.3"}