新增员工申请须知功能
This commit is contained in:
BIN
dist8月12日.zip
Normal file
BIN
dist8月12日.zip
Normal file
Binary file not shown.
BIN
dist9092.zip
Normal file
BIN
dist9092.zip
Normal file
Binary file not shown.
BIN
distTOP.zip
Normal file
BIN
distTOP.zip
Normal file
Binary file not shown.
@ -1,8 +1,9 @@
|
|||||||
// export const downLoadImage = 'http://1.94.237.210:3457/file/download/'
|
// 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://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 = '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://27.30.77.229:9092/'
|
||||||
// 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'
|
||||||
});
|
});
|
||||||
|
|
||||||
myAxios.interceptors.request.use(function (config) {
|
myAxios.interceptors.request.use(function (config) {
|
||||||
|
@ -88,7 +88,7 @@ onMounted(() => {
|
|||||||
<ul>
|
<ul>
|
||||||
<li><span>主管数量</span><strong>{{ dashboardData.superCount }}</strong></li>
|
<li><span>主管数量</span><strong>{{ dashboardData.superCount }}</strong></li>
|
||||||
<li><span>员工数量</span><strong>{{ dashboardData.empCount }}</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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<!-- 今日统计 -->
|
<!-- 今日统计 -->
|
||||||
|
@ -71,7 +71,7 @@ const editorConfig = {
|
|||||||
|
|
||||||
if (res.code === 1) {
|
if (res.code === 1) {
|
||||||
// 拼接完整 URL 地址再插入到富文本中
|
// 拼接完整 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)
|
insertFn(imageUrl)
|
||||||
} else {
|
} else {
|
||||||
console.error('上传失败:', res.message)
|
console.error('上传失败:', res.message)
|
||||||
|
@ -2,140 +2,167 @@
|
|||||||
<form @submit.prevent="handleSubmit" class="modern-form">
|
<form @submit.prevent="handleSubmit" class="modern-form">
|
||||||
<h2 class="form-title">员工账号申请须知</h2>
|
<h2 class="form-title">员工账号申请须知</h2>
|
||||||
|
|
||||||
<!-- 富文本编辑器区域 - 修改为50%宽度 -->
|
<!-- 富文本编辑器 -->
|
||||||
<div class="rich-text-container">
|
<div class="rich-text-container">
|
||||||
<div class="rich-text-columns">
|
<div class="rich-text-group">
|
||||||
<div class="rich-text-group">
|
<span class="label-text">须知详情</span>
|
||||||
<span class="label-text">须知详情</span>
|
<RichTextEditor
|
||||||
<RichTextEditor
|
ref="editorRef"
|
||||||
v-model="formData.detail"
|
:value="editorContent"
|
||||||
:disable="false"
|
:disable="false"
|
||||||
@content-change="(html:any) => formData.detail = html"
|
@content-change="handleEditorChange"
|
||||||
/>
|
/>
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 错误提示 -->
|
||||||
|
<div v-if="errorMessage" class="error-message">
|
||||||
|
{{ errorMessage }}
|
||||||
|
</div>
|
||||||
|
|
||||||
<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>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, ref } from 'vue';
|
import { reactive, ref, nextTick, onMounted } from 'vue';
|
||||||
import RichTextEditor from '../components/RichTextEditor.vue';
|
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 CourseForm {
|
interface NoticeForm {
|
||||||
name: string;
|
templateString: string;
|
||||||
type: string;
|
|
||||||
image: string;
|
|
||||||
detail: string;
|
|
||||||
promoCodeDesc: string;
|
|
||||||
originPrice: number;
|
|
||||||
discountPrice: number;
|
|
||||||
firstLevelRate: number;
|
|
||||||
secondLevelRate: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const fileName = ref('');
|
const formData = reactive<NoticeForm>({
|
||||||
// 新增价格错误状态
|
templateString: ''
|
||||||
const priceError = ref('');
|
|
||||||
|
|
||||||
const formData = reactive<CourseForm>({
|
|
||||||
name: '',
|
|
||||||
type: '',
|
|
||||||
image: '',
|
|
||||||
detail: '',
|
|
||||||
promoCodeDesc: '',
|
|
||||||
originPrice: 0,
|
|
||||||
discountPrice: 0,
|
|
||||||
firstLevelRate: 0,
|
|
||||||
secondLevelRate: 0
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const encode64 = (text: string): string => {
|
const editorRef = ref<InstanceType<typeof RichTextEditor> | null>(null);
|
||||||
return btoa(
|
const errorMessage = ref('');
|
||||||
encodeURIComponent(text).replace(
|
const editorContent = ref(''); // 单独管理编辑器内容
|
||||||
/%([0-9A-F]{2})/g,
|
|
||||||
(_, p1) => String.fromCharCode(parseInt(p1, 16))
|
|
||||||
))
|
|
||||||
};
|
|
||||||
|
|
||||||
const encryptValue = (value: any, key: string): any => {
|
// 增强Base64编码方法
|
||||||
// 数值型字段直接返回,不加密
|
const encode64 = (text: string): string => {
|
||||||
if (key === 'originPrice' || key === 'discountPrice' ||
|
if (!text.trim()) return '';
|
||||||
key === 'image' || key ==='name'||key === 'type') {
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const valueString = typeof value === 'object'
|
// 使用更健壮的Base64编码
|
||||||
? JSON.stringify(value)
|
return btoa(unescape(encodeURIComponent(text)));
|
||||||
: String(value);
|
|
||||||
return encode64(valueString);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Base64编码失败:', error);
|
console.error('Base64编码失败:', error);
|
||||||
return value;
|
return '';
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 处理编辑器内容变化
|
||||||
|
const handleEditorChange = (html: string) => {
|
||||||
|
formData.templateString = html;
|
||||||
|
editorContent.value = html;
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
// 检查内容是否为空(去除所有HTML标签)
|
||||||
// 重置错误状态
|
const isContentEmpty = (html: string): boolean => {
|
||||||
priceError.value = '';
|
if (!html) return true;
|
||||||
|
|
||||||
|
|
||||||
try {
|
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;
|
return plainText.trim() === '';
|
||||||
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}`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('请求失败:', error);
|
console.error('内容解析失败:', error);
|
||||||
alert('提交失败,请检查控制台获取详细信息');
|
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>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
/* 精简后的样式 */
|
||||||
.modern-form {
|
.modern-form {
|
||||||
max-width: 90%;
|
max-width: 90%;
|
||||||
margin: 2rem auto;
|
margin: 2rem auto;
|
||||||
@ -144,6 +171,7 @@ const handleSubmit = async () => {
|
|||||||
border-radius: 1.5rem;
|
border-radius: 1.5rem;
|
||||||
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
|
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
|
||||||
font-family: 'Segoe UI', system-ui, sans-serif;
|
font-family: 'Segoe UI', system-ui, sans-serif;
|
||||||
|
position: relative;
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-title {
|
.form-title {
|
||||||
@ -152,39 +180,28 @@ const handleSubmit = async () => {
|
|||||||
color: #2c3e50;
|
color: #2c3e50;
|
||||||
margin-bottom: 2rem;
|
margin-bottom: 2rem;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
letter-spacing: 0.5px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.label-text {
|
.label-text {
|
||||||
display: block;
|
display: block;
|
||||||
margin-bottom: 0.6rem;
|
margin-bottom: 0.6rem;
|
||||||
color: #4a5568;
|
padding: 10px;
|
||||||
font-size: 0.9rem;
|
color: #6366f1;
|
||||||
|
font-size: 18px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.select-wrapper:focus-within .select-arrow {
|
.error-message {
|
||||||
transform: translateY(-50%) rotate(180deg);
|
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 {
|
.submit-button {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@ -219,21 +236,9 @@ const handleSubmit = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@keyframes sparkle {
|
@keyframes sparkle {
|
||||||
0% { transform: scale(0) translate(0,0); }
|
0% { transform: scale(0) translate(0,0); opacity: 1; }
|
||||||
50% { transform: scale(1) translate(100px, -50px); }
|
50% { transform: scale(1) translate(100px, -50px); opacity: 0.8; }
|
||||||
100% { transform: scale(0) translate(200px, -100px); }
|
100% { transform: scale(0) translate(200px, -100px); opacity: 0; }
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.form-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.modern-form {
|
|
||||||
padding: 1.5rem;
|
|
||||||
margin: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.rich-text-container {
|
.rich-text-container {
|
||||||
@ -241,8 +246,6 @@ const handleSubmit = async () => {
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.rich-text-group {
|
.rich-text-group {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
@ -250,14 +253,7 @@ const handleSubmit = async () => {
|
|||||||
border-radius: 0.75rem;
|
border-radius: 0.75rem;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
transition: all 0.3s ease;
|
transition: all 0.3s ease;
|
||||||
min-height: 320px;
|
max-height: 420px;
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.rich-text-columns {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.rich-text-group:focus-within {
|
.rich-text-group:focus-within {
|
||||||
@ -265,43 +261,11 @@ const handleSubmit = async () => {
|
|||||||
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
|
box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
.rich-text-group .label-text {
|
@media (max-width: 768px) {
|
||||||
font-size: 0.95rem;
|
.modern-form {
|
||||||
font-weight: 600;
|
padding: 1.5rem;
|
||||||
color: #3b4151;
|
margin: 1rem;
|
||||||
padding: 12px 12px 0;
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.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>
|
</style>
|
@ -313,7 +313,8 @@ const filterNameInput = (e: Event) => {
|
|||||||
|
|
||||||
// 使用正则表达式过滤非中文字符
|
// 使用正则表达式过滤非中文字符
|
||||||
// value = value.replace(/[^\u4e00-\u9fa5]/g, '');
|
// 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;
|
input.value = value;
|
||||||
searchName.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>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -249,8 +262,9 @@ const handleKeyDown = (e: KeyboardEvent) => {
|
|||||||
|
|
||||||
<Input
|
<Input
|
||||||
v-model:value="queryParams.nickName"
|
v-model:value="queryParams.nickName"
|
||||||
placeholder="用户昵称"
|
placeholder="学校"
|
||||||
style="width: 150px; margin-right: 10px;"
|
style="width: 150px; margin-right: 10px;"
|
||||||
|
@input="filterSchoolInput"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<DatePicker.RangePicker
|
<DatePicker.RangePicker
|
||||||
|
Reference in New Issue
Block a user