显示员工申请须知+显示课程购买须知

This commit is contained in:
2025-08-16 22:37:28 +08:00
parent 4ec8f8e997
commit b0b985bc53
8 changed files with 409 additions and 63 deletions

BIN
dist---TOP.zip Normal file

Binary file not shown.

BIN
dist160-9092.zip Normal file

Binary file not shown.

BIN
dist8月15日9092.zip Normal file

Binary file not shown.

View File

@ -7,7 +7,7 @@ import router from "../router";
const myAxios = axios.create({ const myAxios = axios.create({
withCredentials: true, withCredentials: true,
// baseURL:'http://localhost:9091' // baseURL:'http://localhost:9091'
// baseURL:'http://localhost:9092' baseURL:'http://localhost:9092'
// baseURL:'http://1.94.237.210:3457' // baseURL:'http://1.94.237.210:3457'
//baseURL:'http://1.94.237.210:8088' //baseURL:'http://1.94.237.210:8088'
//baseURL:'http://27.30.77.229:9091/' //baseURL:'http://27.30.77.229:9091/'
@ -15,7 +15,7 @@ const myAxios = axios.create({
// baseURL:'http://160.202.242.36:9091/' // baseURL:'http://160.202.242.36:9091/'
// baseURL:'http://160.202.242.36:9092/' // baseURL:'http://160.202.242.36:9092/'
// baseURL:'http://160.202.242.36:9092' // baseURL:'http://160.202.242.36:9092'
baseURL:'https://www.chenxinzhi.top' // baseURL:'https://www.chenxinzhi.top'
}); });
myAxios.interceptors.request.use(function (config) { myAxios.interceptors.request.use(function (config) {

View File

@ -7,8 +7,22 @@
</button> </button>
</div> </div>
<!-- 加载状态 -->
<div v-if="loadingBanners" class="loading-container">
<a-spin size="large" />
<p class="loading-text">轮播图加载中...</p>
</div>
<!-- 错误提示 -->
<div v-if="bannerError" class="error-container">
<a-alert type="error" :message="bannerError" show-icon />
<a-button type="primary" @click="fetchBanners" class="retry-button">
重新加载
</a-button>
</div>
<!-- 轮播图展示 --> <!-- 轮播图展示 -->
<a-carousel arrows class="carouselContainer"> <a-carousel v-if="!loadingBanners && !bannerError && banners.length > 0" arrows class="carouselContainer">
<template #prevArrow> <template #prevArrow>
<div class="custom-slick-arrow" style="left: 10px; z-index: 1"> <div class="custom-slick-arrow" style="left: 10px; z-index: 1">
<left-circle-outlined /> <left-circle-outlined />
@ -28,6 +42,13 @@
</div> </div>
</a-carousel> </a-carousel>
<!-- 空状态提示 -->
<div v-if="!loadingBanners && !bannerError && banners.length === 0" class="empty-container">
<a-empty description="暂无轮播图数据">
<a-button type="primary" @click="showAddModal">添加轮播图</a-button>
</a-empty>
</div>
<!-- 新增弹框 --> <!-- 新增弹框 -->
<a-modal <a-modal
v-model:visible="addModalVisible" v-model:visible="addModalVisible"
@ -104,7 +125,7 @@ import {
CloseCircleOutlined, CloseCircleOutlined,
DeleteOutlined DeleteOutlined
} from '@ant-design/icons-vue'; } from '@ant-design/icons-vue';
import { message } from 'ant-design-vue'; import { message, Spin as ASpin, Alert as AAlert, Empty as AEmpty } from 'ant-design-vue';
import type { UploadChangeParam } from 'ant-design-vue'; import type { UploadChangeParam } from 'ant-design-vue';
import myAxios from "../../api/myAxios.ts"; import myAxios from "../../api/myAxios.ts";
@ -129,6 +150,11 @@ const currentBannerUrl = ref('');
const currentBannerId = ref(''); const currentBannerId = ref('');
const currentBannerIndex = ref(-1); const currentBannerIndex = ref(-1);
// 新增:加载状态和错误状态
const loadingBanners = ref(true);
const bannerError = ref('');
// 获取上传URL和请求头 // 获取上传URL和请求头
const uploadUrl = `${myAxios.defaults.baseURL}/file/upload`; const uploadUrl = `${myAxios.defaults.baseURL}/file/upload`;
const uploadHeaders = { const uploadHeaders = {
@ -302,6 +328,8 @@ const handleDeleteBanner = async () => {
// 获取轮播图列表 // 获取轮播图列表
const fetchBanners = async () => { const fetchBanners = async () => {
loadingBanners.value = true;
bannerError.value = '';
try { try {
const response:any = await myAxios.get('/banner/web/list', { const response:any = await myAxios.get('/banner/web/list', {
headers: { headers: {
@ -321,6 +349,8 @@ const fetchBanners = async () => {
} catch (error) { } catch (error) {
console.error('获取轮播图失败:', error); console.error('获取轮播图失败:', error);
message.error('获取轮播图失败'); message.error('获取轮播图失败');
}finally {
loadingBanners.value = false;
} }
}; };
@ -331,6 +361,53 @@ onMounted(() => {
</script> </script>
<style scoped> <style scoped>
/* 加载状态样式 */
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 40px;
height: 300px;
background: rgba(255, 255, 255, 0.8);
border-radius: 8px;
margin: 20px auto;
}
.loading-text {
margin-top: 16px;
font-size: 16px;
color: #1890ff;
}
/* 错误状态样式 */
.error-container {
display: flex;
flex-direction: column;
align-items: center;
padding: 30px;
background: #fff1f0;
border-radius: 8px;
margin: 20px auto;
max-width: 80%;
}
.retry-button {
margin-top: 20px;
}
/* 空状态样式 */
.empty-container {
display: flex;
flex-direction: column;
align-items: center;
padding: 40px;
background: #fafafa;
border-radius: 8px;
margin: 20px auto;
max-width: 80%;
}
/* 按钮样式 */ /* 按钮样式 */
.add-button-container { .add-button-container {
margin-top: 10px; margin-top: 10px;

View File

@ -70,7 +70,7 @@ const editorConfig = {
}) })
if (res.code === 1) { if (res.code === 1) {
// const imageUrl = 'http://160.202.242.36:9092/file/download/' + res.data
const imageUrl = 'https://www.chenxinzhi.top/file/download/' + res.data const imageUrl = 'https://www.chenxinzhi.top/file/download/' + res.data
insertFn(imageUrl) insertFn(imageUrl)
} else { } else {

View File

@ -1,7 +1,16 @@
<template> <template>
<form @submit.prevent="handleSubmit" class="modern-form"> <div class="modern-form">
<h2 class="form-title">课程购买须知</h2> <h2 class="form-title">课程购买须知</h2>
<!-- 只读模式 -->
<div v-if="!editing" class="readonly-container">
<div class="notice-content" v-html="decodedNotice"></div>
<button @click="startEditing" class="edit-button">
<span>修改须知</span>
<div class="button-sparkles"></div>
</button>
</div>
<!-- 编辑模式 -->
<form v-else @submit.prevent="handleSubmit" class="editing-form">
<!-- 富文本编辑器 --> <!-- 富文本编辑器 -->
<div class="rich-text-container"> <div class="rich-text-container">
<div class="rich-text-group"> <div class="rich-text-group">
@ -19,12 +28,17 @@
<div v-if="errorMessage" class="error-message"> <div v-if="errorMessage" class="error-message">
{{ errorMessage }} {{ errorMessage }}
</div> </div>
<div class="button-group">
<button type="button" @click="cancelEditing" class="cancel-button">
取消
</button>
<button type="submit" class="submit-button"> <button type="submit" class="submit-button">
<span>保存须知</span> <span>保存须知</span>
<div class="button-sparkles"></div> <div class="button-sparkles"></div>
</button> </button>
</div>
</form> </form>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -45,6 +59,20 @@ const formData = reactive<NoticeForm>({
const editorRef = ref<InstanceType<typeof RichTextEditor> | null>(null); const editorRef = ref<InstanceType<typeof RichTextEditor> | null>(null);
const errorMessage = ref(''); const errorMessage = ref('');
const editorContent = ref(''); const editorContent = ref('');
const decodedNotice = ref('<p>加载课程须知中...</p>');
const editing = ref(false);
// Base64解码函数
const decode64 = (base64Text: string): string => {
if (!base64Text.trim()) return '';
try {
return decodeURIComponent(escape(atob(base64Text)));
} catch (error) {
console.error('Base64解码失败:', error);
return '内容解析错误';
}
};
// 增强Base64编码方法 // 增强Base64编码方法
const encode64 = (text: string): string => { const encode64 = (text: string): string => {
@ -83,6 +111,46 @@ const isContentEmpty = (html: string): boolean => {
} }
}; };
// 获取申请须知
const fetchNotice = async () => {
try {
const storedToken = localStorage.getItem('token');
const res: any = await myAxios.post(
`/userInfo/query/courseDesc`,
{},
{
headers: {
'Content-Type': 'application/json',
'Authorization': storedToken
}
}
);
if (res.code === 1 && res.data) {
decodedNotice.value = decode64(res.data);
} else {
decodedNotice.value = `<p class="error-text">获取购买须知失败: ${res.message || '未知错误'}</p>`;
}
} catch (error: any) {
console.error('获取须知失败:', error);
decodedNotice.value = `<p class="error-text">网络错误,请稍后重试</p>`;
}
};
// 开始编辑
const startEditing = () => {
editing.value = true;
// 将当前内容加载到编辑器
editorContent.value = decodedNotice.value;
formData.templateString = decodedNotice.value;
};
// 取消编辑
const cancelEditing = () => {
editing.value = false;
errorMessage.value = '';
};
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
await nextTick(); await nextTick();
@ -119,6 +187,9 @@ const handleSubmit = async () => {
); );
console.log(res) console.log(res)
if (res.code === 1) { if (res.code === 1) {
decodedNotice.value = formData.templateString;
editing.value = false;
errorMessage.value = '';
alert('须知保存成功!'); alert('须知保存成功!');
if (editorRef.value) { if (editorRef.value) {
@ -155,12 +226,12 @@ const handleSubmit = async () => {
// 初始化时清空错误信息 // 初始化时清空错误信息
onMounted(() => { onMounted(() => {
fetchNotice();
errorMessage.value = ''; errorMessage.value = '';
}); });
</script> </script>
<style scoped> <style scoped>
.modern-form { .modern-form {
max-width: 90%; max-width: 90%;
margin: 2rem auto; margin: 2rem auto;
@ -180,6 +251,52 @@ onMounted(() => {
font-weight: 600; font-weight: 600;
} }
.readonly-container {
border: 1px solid #e2e8f0;
border-radius: 0.75rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
background-color: #f9fafb;
}
.notice-content {
min-height: 300px;
padding: 1rem;
line-height: 1.6;
}
.notice-content :deep(p) {
margin-bottom: 1rem;
}
.notice-content :deep(.error-text) {
color: #e53e3e;
}
.edit-button {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 1rem;
margin-top: 1.5rem;
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
border: none;
border-radius: 0.75rem;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.edit-button:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(16, 185, 129, 0.3);
}
.label-text { .label-text {
display: block; display: block;
margin-bottom: 0.6rem; margin-bottom: 0.6rem;
@ -200,13 +317,18 @@ onMounted(() => {
white-space: pre-wrap; white-space: pre-wrap;
} }
.button-group {
display: flex;
gap: 1rem;
margin-top: 1.5rem;
}
.submit-button { .submit-button {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 100%; flex: 1;
padding: 1rem; padding: 1rem;
margin-top: 1.5rem;
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
color: white; color: white;
border: none; border: none;
@ -219,6 +341,26 @@ onMounted(() => {
overflow: hidden; overflow: hidden;
} }
.cancel-button {
display: flex;
align-items: center;
justify-content: center;
flex: 1;
padding: 1rem;
background: #e2e8f0;
color: #4a5568;
border: none;
border-radius: 0.75rem;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
}
.cancel-button:hover {
background: #cbd5e0;
}
.submit-button:hover { .submit-button:hover {
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(99, 102, 241, 0.3); box-shadow: 0 8px 20px rgba(99, 102, 241, 0.3);
@ -286,6 +428,8 @@ onMounted(() => {
padding: 1.5rem; padding: 1.5rem;
margin: 1rem; margin: 1rem;
} }
.button-group {
flex-direction: column;
}
} }
</style> </style>

View File

@ -1,30 +1,45 @@
<template> <template>
<form @submit.prevent="handleSubmit" class="modern-form"> <div class="modern-form">
<h2 class="form-title">员工账号申请须知</h2> <h2 class="form-title">员工申请须知</h2>
<!-- 富文本编辑器 --> <!-- 只读模式 -->
<div class="rich-text-container"> <div v-if="!editing" class="readonly-container">
<div class="rich-text-group"> <div class="notice-content" v-html="decodedNotice"></div>
<span class="label-text">须知详情</span> <button @click="startEditing" class="edit-button">
<RichTextEditor <span>修改须知</span>
ref="editorRef" <div class="button-sparkles"></div>
:value="editorContent" </button>
:disable="false" </div>
@content-change="handleEditorChange"
/> <!-- 编辑模式 -->
<form v-else @submit.prevent="handleSubmit" class="editing-form">
<div class="rich-text-container">
<div class="rich-text-group">
<span class="label-text">须知详情</span>
<RichTextEditor
ref="editorRef"
:value="editorContent"
:disable="false"
@content-change="handleEditorChange"
/>
</div>
</div> </div>
</div>
<!-- 错误提示 --> <div v-if="errorMessage" class="error-message">
<div v-if="errorMessage" class="error-message"> {{ errorMessage }}
{{ errorMessage }} </div>
</div>
<button type="submit" class="submit-button"> <div class="button-group">
<span>保存须知</span> <button type="button" @click="cancelEditing" class="cancel-button">
<div class="button-sparkles"></div> 取消
</button> </button>
</form> <button type="submit" class="submit-button">
<span>保存须知</span>
<div class="button-sparkles"></div>
</button>
</div>
</form>
</div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
@ -33,7 +48,6 @@ import RichTextEditor from '../components/RichTextEditor.vue';
import '@vueup/vue-quill/dist/vue-quill.snow.css'; import '@vueup/vue-quill/dist/vue-quill.snow.css';
import myAxios from "../../api/myAxios.ts"; import myAxios from "../../api/myAxios.ts";
interface NoticeForm { interface NoticeForm {
templateString: string; templateString: string;
} }
@ -45,13 +59,26 @@ const formData = reactive<NoticeForm>({
const editorRef = ref<InstanceType<typeof RichTextEditor> | null>(null); const editorRef = ref<InstanceType<typeof RichTextEditor> | null>(null);
const errorMessage = ref(''); const errorMessage = ref('');
const editorContent = ref(''); const editorContent = ref('');
const decodedNotice = ref('<p>加载申请须知中...</p>');
const editing = ref(false);
// 增强Base64编码方法 // Base64解码函数
const decode64 = (base64Text: string): string => {
if (!base64Text.trim()) return '';
try {
return decodeURIComponent(escape(atob(base64Text)));
} catch (error) {
console.error('Base64解码失败:', error);
return '内容解析错误';
}
};
// Base64编码函数
const encode64 = (text: string): string => { const encode64 = (text: string): string => {
if (!text.trim()) return ''; if (!text.trim()) return '';
try { try {
return btoa(unescape(encodeURIComponent(text))); return btoa(unescape(encodeURIComponent(text)));
} catch (error) { } catch (error) {
console.error('Base64编码失败:', error); console.error('Base64编码失败:', error);
@ -59,23 +86,18 @@ const encode64 = (text: string): string => {
} }
}; };
const handleEditorChange = (html: string) => { const handleEditorChange = (html: string) => {
formData.templateString = html; formData.templateString = html;
editorContent.value = html; editorContent.value = html;
}; };
const isContentEmpty = (html: string): boolean => { const isContentEmpty = (html: string): boolean => {
if (!html) return true; if (!html) return true;
try { try {
const tempDiv = document.createElement('div'); const tempDiv = document.createElement('div');
tempDiv.innerHTML = html; tempDiv.innerHTML = html;
const plainText = tempDiv.textContent || ''; const plainText = tempDiv.textContent || '';
return plainText.trim() === ''; return plainText.trim() === '';
} catch (error) { } catch (error) {
console.error('内容解析失败:', error); console.error('内容解析失败:', error);
@ -83,6 +105,46 @@ const isContentEmpty = (html: string): boolean => {
} }
}; };
// 获取申请须知
const fetchNotice = async () => {
try {
const storedToken = localStorage.getItem('token');
const res: any = await myAxios.post(
`/userInfo/query/applyNotice`,
{},
{
headers: {
'Content-Type': 'application/json',
'Authorization': storedToken
}
}
);
if (res.code === 1 && res.data) {
decodedNotice.value = decode64(res.data);
} else {
decodedNotice.value = `<p class="error-text">获取申请须知失败: ${res.message || '未知错误'}</p>`;
}
} catch (error: any) {
console.error('获取须知失败:', error);
decodedNotice.value = `<p class="error-text">网络错误,请稍后重试</p>`;
}
};
// 开始编辑
const startEditing = () => {
editing.value = true;
// 将当前内容加载到编辑器
editorContent.value = decodedNotice.value;
formData.templateString = decodedNotice.value;
};
// 取消编辑
const cancelEditing = () => {
editing.value = false;
errorMessage.value = '';
};
const handleSubmit = async () => { const handleSubmit = async () => {
try { try {
await nextTick(); await nextTick();
@ -95,7 +157,6 @@ const handleSubmit = async () => {
const encryptedContent = encode64(formData.templateString); const encryptedContent = encode64(formData.templateString);
if (!encryptedContent) { if (!encryptedContent) {
errorMessage.value = '内容加密失败,请检查输入内容'; errorMessage.value = '内容加密失败,请检查输入内容';
return; return;
@ -105,9 +166,6 @@ const handleSubmit = async () => {
templateString: encryptedContent templateString: encryptedContent
}; };
console.log('提交内容:', formData.templateString);
console.log('加密内容:', encryptedContent);
const res: any = await myAxios.post( const res: any = await myAxios.post(
`/userInfo/modify/applyNotice`, `/userInfo/modify/applyNotice`,
payload, payload,
@ -118,31 +176,26 @@ const handleSubmit = async () => {
} }
} }
); );
console.log(res)
if (res.code === 1) {
alert('须知保存成功!');
if (editorRef.value) { if (res.code === 1) {
editorRef.value.clearContent(); // 更新本地内容并退出编辑模式
} decodedNotice.value = formData.templateString;
formData.templateString = ''; editing.value = false;
editorContent.value = '';
errorMessage.value = ''; errorMessage.value = '';
alert('须知保存成功!');
} else { } else {
errorMessage.value = `保存失败:${res.message || '服务器错误'} (${res.code})`; errorMessage.value = `保存失败:${res.message || '服务器错误'} (${res.code})`;
} }
} catch (error: any) { } catch (error: any) {
console.error('请求失败:', error); console.error('请求失败:', error);
let detailedError = '提交失败:'; let detailedError = '提交失败:';
if (error.response) {
if (error.response) {
const serverMessage = error.response.data?.message || const serverMessage = error.response.data?.message ||
error.response.data?.error || error.response.data?.error ||
'无详细错误信息'; '无详细错误信息';
detailedError += `服务器响应错误 (${error.response.status}): ${serverMessage}`; detailedError += `服务器响应错误 (${error.response.status}): ${serverMessage}`;
if (error.response.data?.code === 40000) { if (error.response.data?.code === 40000) {
detailedError = '须知内容不能为空'; detailedError = '须知内容不能为空';
} }
@ -156,14 +209,13 @@ const handleSubmit = async () => {
} }
}; };
// 初始化时清空错误信息 // 初始化时获取申请须知
onMounted(() => { onMounted(() => {
errorMessage.value = ''; fetchNotice();
}); });
</script> </script>
<style scoped> <style scoped>
.modern-form { .modern-form {
max-width: 90%; max-width: 90%;
margin: 2rem auto; margin: 2rem auto;
@ -183,6 +235,52 @@ onMounted(() => {
font-weight: 600; font-weight: 600;
} }
.readonly-container {
border: 1px solid #e2e8f0;
border-radius: 0.75rem;
padding: 1.5rem;
margin-bottom: 1.5rem;
background-color: #f9fafb;
}
.notice-content {
min-height: 300px;
padding: 1rem;
line-height: 1.6;
}
.notice-content :deep(p) {
margin-bottom: 1rem;
}
.notice-content :deep(.error-text) {
color: #e53e3e;
}
.edit-button {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
padding: 1rem;
margin-top: 1.5rem;
background: linear-gradient(135deg, #10b981 0%, #059669 100%);
color: white;
border: none;
border-radius: 0.75rem;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.edit-button:hover {
transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(16, 185, 129, 0.3);
}
.label-text { .label-text {
display: block; display: block;
margin-bottom: 0.6rem; margin-bottom: 0.6rem;
@ -203,13 +301,18 @@ onMounted(() => {
white-space: pre-wrap; white-space: pre-wrap;
} }
.button-group {
display: flex;
gap: 1rem;
margin-top: 1.5rem;
}
.submit-button { .submit-button {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
width: 100%; flex: 1;
padding: 1rem; padding: 1rem;
margin-top: 1.5rem;
background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%); background: linear-gradient(135deg, #6366f1 0%, #8b5cf6 100%);
color: white; color: white;
border: none; border: none;
@ -222,6 +325,26 @@ onMounted(() => {
overflow: hidden; overflow: hidden;
} }
.cancel-button {
display: flex;
align-items: center;
justify-content: center;
flex: 1;
padding: 1rem;
background: #e2e8f0;
color: #4a5568;
border: none;
border-radius: 0.75rem;
font-size: 1rem;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
}
.cancel-button:hover {
background: #cbd5e0;
}
.submit-button:hover { .submit-button:hover {
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 8px 20px rgba(99, 102, 241, 0.3); box-shadow: 0 8px 20px rgba(99, 102, 241, 0.3);
@ -289,6 +412,8 @@ onMounted(() => {
padding: 1.5rem; padding: 1.5rem;
margin: 1rem; margin: 1rem;
} }
.button-group {
flex-direction: column;
}
} }
</style> </style>