新增员工申请须知功能
This commit is contained in:
@ -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/'
|
||||
|
||||
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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>
|
||||
<!-- 今日统计 -->
|
||||
|
@ -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)
|
||||
|
@ -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>
|
@ -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;
|
||||
|
@ -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
|
||||
|
Reference in New Issue
Block a user