新增员工申请须知功能

This commit is contained in:
2025-08-15 00:11:02 +08:00
parent da9feb26c6
commit 09b105b9e5
10 changed files with 167 additions and 187 deletions

View File

@ -1,8 +1,9 @@
// export const downLoadImage = 'http://1.94.237.210:3457/file/download/'
// export const downLoadImage = 'http://27.30.77.229:9092/file/download/'
export const downLoadImage = 'http://160.202.242.36:9092/file/download/'
// export const downLoadImage = 'http://160.202.242.36:9092/file/download/'
// export const downLoadImage = 'http://160.202.242.36:9091/file/download/'
export const downLoadImage = 'https://www.chenxinzhi.top/file/download/'

View File

@ -14,8 +14,8 @@ const myAxios = axios.create({
//baseURL:'http://27.30.77.229:9092/'
// 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:'https://www.chenxinzhi.top'
});
myAxios.interceptors.request.use(function (config) {

View File

@ -88,7 +88,7 @@ onMounted(() => {
<ul>
<li><span>主管数量</span><strong>{{ dashboardData.superCount }}</strong></li>
<li><span>员工数量</span><strong>{{ dashboardData.empCount }}</strong></li>
<li><span>户数量</span><strong>{{ dashboardData.promoCount }}</strong></li>
<li><span>户数量</span><strong>{{ dashboardData.promoCount }}</strong></li>
</ul>
</div>
<!-- 今日统计 -->

View File

@ -71,7 +71,7 @@ const editorConfig = {
if (res.code === 1) {
// 拼接完整 URL 地址再插入到富文本中
const imageUrl = 'http://160.202.242.36:9092/file/download/' + res.data
const imageUrl = 'https://www.chenxinzhi.top/file/download/' + res.data
insertFn(imageUrl)
} else {
console.error('上传失败:', res.message)

View File

@ -2,140 +2,167 @@
<form @submit.prevent="handleSubmit" class="modern-form">
<h2 class="form-title">员工账号申请须知</h2>
<!-- 富文本编辑器区域 - 修改为50%宽度 -->
<!-- 富文本编辑器 -->
<div class="rich-text-container">
<div class="rich-text-columns">
<div class="rich-text-group">
<span class="label-text">须知详情</span>
<RichTextEditor
v-model="formData.detail"
:disable="false"
@content-change="(html:any) => formData.detail = html"
/>
</div>
<div class="rich-text-group">
<span class="label-text">须知详情</span>
<RichTextEditor
ref="editorRef"
:value="editorContent"
:disable="false"
@content-change="handleEditorChange"
/>
</div>
</div>
<!-- 错误提示 -->
<div v-if="errorMessage" class="error-message">
{{ errorMessage }}
</div>
<button type="submit" class="submit-button">
<span>立即创建</span>
<span>保存须知</span>
<div class="button-sparkles"></div>
</button>
</form>
</template>
<script setup lang="ts">
import { reactive, ref } from 'vue';
import { reactive, ref, nextTick, onMounted } from 'vue';
import RichTextEditor from '../components/RichTextEditor.vue';
import '@vueup/vue-quill/dist/vue-quill.snow.css';
import myAxios from "../../api/myAxios.ts";
interface CourseForm {
name: string;
type: string;
image: string;
detail: string;
promoCodeDesc: string;
originPrice: number;
discountPrice: number;
firstLevelRate: number;
secondLevelRate: number;
// 定义表单数据结构
interface NoticeForm {
templateString: string;
}
const fileName = ref('');
// 新增价格错误状态
const priceError = ref('');
const formData = reactive<CourseForm>({
name: '',
type: '',
image: '',
detail: '',
promoCodeDesc: '',
originPrice: 0,
discountPrice: 0,
firstLevelRate: 0,
secondLevelRate: 0
const formData = reactive<NoticeForm>({
templateString: ''
});
const encode64 = (text: string): string => {
return btoa(
encodeURIComponent(text).replace(
/%([0-9A-F]{2})/g,
(_, p1) => String.fromCharCode(parseInt(p1, 16))
))
};
const editorRef = ref<InstanceType<typeof RichTextEditor> | null>(null);
const errorMessage = ref('');
const editorContent = ref(''); // 单独管理编辑器内容
const encryptValue = (value: any, key: string): any => {
// 数值型字段直接返回,不加密
if (key === 'originPrice' || key === 'discountPrice' ||
key === 'image' || key ==='name'||key === 'type') {
return value;
}
// 增强Base64编码方法
const encode64 = (text: string): string => {
if (!text.trim()) return '';
try {
const valueString = typeof value === 'object'
? JSON.stringify(value)
: String(value);
return encode64(valueString);
// 使用更健壮的Base64编码
return btoa(unescape(encodeURIComponent(text)));
} catch (error) {
console.error('Base64编码失败:', error);
return value;
return '';
}
};
// 处理编辑器内容变化
const handleEditorChange = (html: string) => {
formData.templateString = html;
editorContent.value = html;
};
const handleSubmit = async () => {
// 重置错误状态
priceError.value = '';
// 检查内容是否为空去除所有HTML标签
const isContentEmpty = (html: string): boolean => {
if (!html) return true;
try {
// 创建临时元素解析HTML
const tempDiv = document.createElement('div');
tempDiv.innerHTML = html;
const storedToken = localStorage.getItem('token');
const encryptedFormData: Record<string, any> = {};
// 获取纯文本内容
const plainText = tempDiv.textContent || '';
Object.entries(formData).forEach(([key, value]) => {
if (value === null || value === undefined || value === '') return;
encryptedFormData[key] = encryptValue(value, key);
});
const res: any = await myAxios.post(`/course/add`, encryptedFormData, {
headers: {
'Content-Type': 'application/json',
'Authorization': storedToken
}
});
if (res.code === 1) {
alert('须知创建成功!');
Object.assign(formData, {
name: '',
type: '',
image: '',
detail: '',
promoCodeDesc: '',
originPrice: 0,
discountPrice: 0,
firstLevelRate: 0,
secondLevelRate: 0
});
fileName.value = '';
} else {
alert(`创建失败:${res.message}`);
}
// 检查是否有实际内容
return plainText.trim() === '';
} catch (error) {
console.error('请求失败', error);
alert('提交失败,请检查控制台获取详细信息');
console.error('内容解析失败:', error);
return true;
}
};
const handleSubmit = async () => {
try {
await nextTick();
const storedToken = localStorage.getItem('token');
// 检查内容是否为空
if (isContentEmpty(formData.templateString)) {
errorMessage.value = '须知内容不能为空';
return;
}
const encryptedContent = encode64(formData.templateString);
// 检查加密结果是否有效
if (!encryptedContent) {
errorMessage.value = '内容加密失败,请检查输入内容';
return;
}
const payload = {
templateString: encryptedContent
};
console.log('提交内容:', formData.templateString);
console.log('加密内容:', encryptedContent);
const res: any = await myAxios.post(
`/userInfo/modify/applyNotice`,
payload,
{
headers: {
'Content-Type': 'application/json',
'Authorization': storedToken
}
}
);
console.log(res)
if (res.code === 1) {
alert('须知保存成功!');
formData.templateString = '';
editorContent.value = '';
errorMessage.value = '';
} else {
errorMessage.value = `保存失败:${res.message || '服务器错误'} (${res.code})`;
}
} catch (error: any) {
console.error('请求失败:', error);
let detailedError = '提交失败:';
if (error.response) {
// 优先显示后端返回的错误信息
const serverMessage = error.response.data?.message ||
error.response.data?.error ||
'无详细错误信息';
detailedError += `服务器响应错误 (${error.response.status}): ${serverMessage}`;
// 特殊处理40000错误空字符串错误
if (error.response.data?.code === 40000) {
detailedError = '须知内容不能为空';
}
} else if (error.request) {
detailedError += '请求已发出但无响应';
} else {
detailedError += error.message || '未知错误';
}
errorMessage.value = detailedError;
}
};
// 初始化时清空错误信息
onMounted(() => {
errorMessage.value = '';
});
</script>
<style scoped>
/* 精简后的样式 */
.modern-form {
max-width: 90%;
margin: 2rem auto;
@ -144,6 +171,7 @@ const handleSubmit = async () => {
border-radius: 1.5rem;
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
font-family: 'Segoe UI', system-ui, sans-serif;
position: relative;
}
.form-title {
@ -152,39 +180,28 @@ const handleSubmit = async () => {
color: #2c3e50;
margin-bottom: 2rem;
font-weight: 600;
letter-spacing: 0.5px;
}
.label-text {
display: block;
margin-bottom: 0.6rem;
color: #4a5568;
font-size: 0.9rem;
padding: 10px;
color: #6366f1;
font-size: 18px;
font-weight: 500;
}
.select-wrapper:focus-within .select-arrow {
transform: translateY(-50%) rotate(180deg);
.error-message {
color: #e53e3e;
background: #fff5f5;
padding: 0.8rem;
border-radius: 0.5rem;
margin-top: 1rem;
border: 1px solid #fc8181;
font-size: 0.9rem;
white-space: pre-wrap;
}
/* 下拉选项样式 */
.select-field option {
padding: 8px 12px;
background: white;
color: #4a5568;
}
.select-field option:hover {
background: #f1f5f9;
}
.select-field option:checked {
background: #6366f1;
color: white;
}
.submit-button {
display: flex;
align-items: center;
@ -219,21 +236,9 @@ const handleSubmit = async () => {
}
@keyframes sparkle {
0% { transform: scale(0) translate(0,0); }
50% { transform: scale(1) translate(100px, -50px); }
100% { transform: scale(0) translate(200px, -100px); }
}
@media (max-width: 768px) {
.form-grid {
grid-template-columns: 1fr;
}
.modern-form {
padding: 1.5rem;
margin: 1rem;
}
0% { transform: scale(0) translate(0,0); opacity: 1; }
50% { transform: scale(1) translate(100px, -50px); opacity: 0.8; }
100% { transform: scale(0) translate(200px, -100px); opacity: 0; }
}
.rich-text-container {
@ -241,8 +246,6 @@ const handleSubmit = async () => {
width: 100%;
}
.rich-text-group {
width: 100%;
height: 100%;
@ -250,14 +253,7 @@ const handleSubmit = async () => {
border-radius: 0.75rem;
overflow: hidden;
transition: all 0.3s ease;
min-height: 320px;
flex-grow: 1;
}
@media (max-width: 768px) {
.rich-text-columns {
grid-template-columns: 1fr;
}
max-height: 420px;
}
.rich-text-group:focus-within {
@ -265,43 +261,11 @@ const handleSubmit = async () => {
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
}
.rich-text-group .label-text {
font-size: 0.95rem;
font-weight: 600;
color: #3b4151;
padding: 12px 12px 0;
@media (max-width: 768px) {
.modern-form {
padding: 1.5rem;
margin: 1rem;
}
}
.phone-section-content * {
max-width: 100%;
}
.phone-section-content img {
max-width: 100%;
height: auto;
border-radius: 8px;
margin: 10px 0;
}
.phone-section-content h1,
.phone-section-content h2,
.phone-section-content h3 {
color: #2c3e50;
margin-top: 15px;
margin-bottom: 10px;
}
.phone-section-content p {
margin-bottom: 10px;
}
.phone-section-content ul,
.phone-section-content ol {
margin-left: 20px;
margin-bottom: 10px;
}
</style>

View File

@ -313,7 +313,8 @@ const filterNameInput = (e: Event) => {
// 使用正则表达式过滤非中文字符
// value = value.replace(/[^\u4e00-\u9fa5]/g, '');
value = value.replace(/[^a-zA-Z\u4e00-\u9fa5]/g, '');
// value = value.replace(/[^a-zA-Z\u4e00-\u9fa5]/g, '');
value = value.replace(/[^\u4e00-\u9fa50-9]/g, '');
// 更新输入框值
input.value = value;
searchName.value = value;

View File

@ -200,6 +200,19 @@ const handleKeyDown = (e: KeyboardEvent) => {
}
};
// 新增学校名称输入过滤函数
const filterSchoolInput = (e: Event) => {
const input = e.target as HTMLInputElement;
let value = input.value;
// 只允许中文和数字
value = value.replace(/[^\u4e00-\u9fa50-9]/g, '');
// 更新输入框值
input.value = value;
queryParams.nickName = value;
};
</script>
<template>
@ -249,8 +262,9 @@ const handleKeyDown = (e: KeyboardEvent) => {
<Input
v-model:value="queryParams.nickName"
placeholder="用户昵称"
placeholder="学校"
style="width: 150px; margin-right: 10px;"
@input="filterSchoolInput"
/>
<DatePicker.RangePicker