2025-08-11 21:11:19 +08:00

619 lines
16 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<BasicLayout>
<!-- <template #top>
</template> -->
<view class="mb-5">
<view class="flex-row items-center white-bg-color p-15 r-md">
<!-- 左侧课程图片 -->
<view class="course-image-section" v-if="xkkc.lxtp">
<image
:src="getImageUrl(xkkc.lxtp)"
class="course-image"
mode="aspectFill"
@error="handleImageError"
/>
</view>
<view class="flex-col ml-10 flex-1 course-info">
<view class="course-name">{{ xkkc.kcmc }}</view>
<view class="course-info-item">
<view class="info-label">上课周期:</view>
<view class="info-data">{{ xkkc.skzqmc }}</view>
</view>
<view class="course-info-item">
<view class="info-label">上课时间:</view>
<view class="info-data">{{ formatClassTime(xkkc.skkstime, xkkc.skjstime) }}</view>
</view>
</view>
</view>
</view>
<!-- 四个主要部分 -->
<view class="p-15">
<!-- 第一部分:教师信息 -->
<view class="section-card mb-15">
<view class="section-title">教师信息 <text class="required">*</text></view>
<view class="teacher-info-header">
<!-- 教师头像 -->
<view class="teacher-avatar-section" v-if="teacherHeadPic">
<image
:src="getImageUrl(teacherHeadPic)"
class="teacher-avatar"
mode="aspectFill"
@error="handleTeacherAvatarError"
/>
</view>
<!-- 右侧信息 -->
<view class="teacher-details">
<view class="detail-row">
<view class="detail-item">
<text class="detail-label">开课老师:</text>
<text class="detail-value">{{ js.jsxm }}</text>
</view>
<view class="detail-item">
<text class="detail-label">上课地点:</text>
<text class="detail-value">{{ xkkc.kcdd }}</text>
</view>
<view class="detail-item">
<text class="detail-label">上课人数:</text>
<text class="detail-value">{{ xkkc.hasNum || 0 }} | {{ xkkc.maxNum || 0 }}</text>
</view>
</view>
</view>
</view>
<!-- 老师信息输入框单独一行 -->
<view class="teacher-input-row">
<textarea
v-model="xkkc.kcjsms"
placeholder="请输入教师信息(必填)"
class="form-textarea"
/>
</view>
</view>
<!-- 第二部分:教学理念 -->
<view class="section-card mb-15">
<view class="section-title">教学理念 <text class="required">*</text></view>
<textarea
v-model="xkkc.jxll"
placeholder="请输入教学理念(必填)"
class="form-textarea"
/>
</view>
<!-- 第三部分:教学计划 -->
<view class="section-card mb-15">
<view class="section-header">
<view class="section-title">教学计划 <text class="required">*</text></view>
<view @click="addEducation" class="add-icon">
<BasicIcon type="icon-tianjia" size="25" />
</view>
</view>
<view v-if="education.xl.length > 0">
<template v-for="(item, index) in education.xl" :key="index">
<view class="po-re mb-15">
<BasicForm v-model="item.value" :schema="schema" :formsProps="{ labelWidth: 100 }" />
<view @click="deleteMemberFamily(index as number, item.value)" class="delete-icon mt-5">
<BasicIcon type="clear" size="30" />
</view>
</view>
</template>
</view>
<view v-else class="p-15 flex-row-center color-9 font-13 white-bg-color">教学计划暂无数据</view>
</view>
</view>
<template #bottom>
<view class="white-bg-color py-5">
<view class="flex-row items-center pb-10 pt-5">
<u-button text="返回" class="ml-15 mr-7" :plain="true" @click="navigateBack" />
<u-button text="提交" class="mr-15 mr-7" type="primary" @click="submit" />
</view>
</view>
</template>
</BasicLayout>
</template>
<script lang="ts" setup>
import { navigateBack } from "@/utils/uniapp";
import { useForm } from "@/components/BasicForm/hooks/useForm";
import { cloneDeep, get } from "lodash";
import { useUserStore } from "@/store/modules/user";
import { useDataStore } from "@/store/modules/data";
import { jsdXkkcSaveApi } from "@/api/base/server";
import dayjs from "dayjs";
import { BASE_IMAGE_URL } from "@/config";
const { getJs } = useUserStore();
const { getData } = useDataStore();
const js = computed(() => getJs)
const xkkc = ref<any>({})
// 获取教师头像
const teacherHeadPic = ref<string>('')
// 从localStorage获取教师头像
const getTeacherHeadPic = () => {
try {
console.log('开始获取教师头像...');
const appUserStr = uni.getStorageSync('app-user')
console.log('app-user原始数据:', appUserStr);
// 解析JSON字符串为对象
let appUser;
try {
appUser = typeof appUserStr === 'string' ? JSON.parse(appUserStr) : appUserStr;
} catch (parseError) {
console.error('JSON解析失败:', parseError);
return;
}
console.log('解析后的appUser数据:', appUser);
// 详细检查数据结构
console.log('appUser存在:', !!appUser);
console.log('appUser类型:', typeof appUser);
console.log('appUser的所有键:', Object.keys(appUser));
if (appUser) {
console.log('appUser.userdata存在:', !!appUser.userdata);
console.log('appUser.userdata类型:', typeof appUser.userdata);
if (appUser.userdata) {
console.log('appUser.userdata的所有键:', Object.keys(appUser.userdata));
console.log('appUser.userdata.js存在:', !!appUser.userdata.js);
if (appUser.userdata.js) {
console.log('appUser.userdata.js的所有键:', Object.keys(appUser.userdata.js));
console.log('appUser.userdata.js.headPic存在:', !!appUser.userdata.js.headPic);
console.log('headPic值:', appUser.userdata.js.headPic);
}
}
}
// 直接从app-user获取userdata
if (appUser && appUser.userdata && appUser.userdata.js) {
console.log('userdata.js数据:', appUser.userdata.js);
if (appUser.userdata.js.headPic) {
console.log('找到headPic:', appUser.userdata.js.headPic);
teacherHeadPic.value = appUser.userdata.js.headPic
console.log('设置teacherHeadPic为:', teacherHeadPic.value);
} else {
console.log('userdata.js中没有headPic字段');
}
} else {
console.log('没有找到userdata或js');
}
} catch (error) {
console.error('获取教师头像失败:', error)
}
}
const [register, { getValue, setValue }] = useForm({
schema: [
// 不再需要kcjsms和jxll字段因为已经用原生textarea处理
{ colSlot: 'jxjh' },
],
});
const schema = reactive<FormsSchema[]>([
{
field: "jhjd",
label: "阶段",
component: "BasicInput",
componentProps: {},
},
{
field: "jhsj",
label: "计划时间",
component: "BasicDateTimes",
componentProps: {},
},
{
field: "jhdd",
label: "地址",
component: "BasicInput",
componentProps: {},
},
{
field: "jhnr",
label: "计划内容",
component: "BasicInput",
itemProps: {
labelPosition: "top",
},
componentProps: {
type: "textarea",
},
},
])
const education = reactive<any>(
{
xl: []
}
)
function addEducation() {
education.xl.push({ value: {
jhjd: '',
jhsj: '',
jhdd: '',
jhnr: ''
} })
}
function deleteMemberFamily(index: number, item: any) {
const list = cloneDeep(education.xl)
list.splice(index, 1)
education.xl = list
}
// 格式化上课时间
const formatClassTime = (startTime: string, endTime: string) => {
if (!startTime || !endTime) {
return '';
}
try {
// 尝试解析时间,支持多种格式
let start, end;
// 如果是时间格式HH:mm:ss 或 HH:mm
if (startTime.includes(':') && !startTime.includes('-') && !startTime.includes('/')) {
start = startTime;
end = endTime;
} else {
// 尝试用 dayjs 解析
const startDate = dayjs(startTime);
const endDate = dayjs(endTime);
if (startDate.isValid() && endDate.isValid()) {
start = startDate.format('HH:mm:ss');
end = endDate.format('HH:mm:ss');
} else {
// 如果解析失败,直接返回原始值
return `${startTime}~${endTime}`;
}
}
return `${start}~${end}`;
} catch (error) {
console.error('时间格式化错误:', error);
// 如果出错,返回原始值
return `${startTime}~${endTime}`;
}
};
// 获取完整的图片URL
const getImageUrl = (imagePath: string) => {
console.log('getImageUrl被调用参数:', imagePath);
console.log('BASE_IMAGE_URL:', BASE_IMAGE_URL);
if (!imagePath) {
console.log('图片路径为空,返回空字符串');
return '';
}
// 如果已经是完整URL直接返回
if (imagePath.startsWith('http://') || imagePath.startsWith('https://')) {
console.log('已经是完整URL直接返回:', imagePath);
return imagePath;
}
// 否则拼接BASE_IMAGE_URL
const fullUrl = `${BASE_IMAGE_URL}${imagePath}`;
console.log('拼接后的完整URL:', fullUrl);
return fullUrl;
};
// 处理图片加载错误
const handleImageError = () => {
console.log('课程图片加载失败');
};
// 处理教师头像加载错误
const handleTeacherAvatarError = () => {
console.log('教师头像加载失败');
};
const submit = async () => {
// 必填验证
if (!xkkc.value.kcjsms || xkkc.value.kcjsms.trim() === '') {
uni.showToast({
title: '请输入教师信息',
icon: 'none',
duration: 2000
});
return;
}
if (!xkkc.value.jxll || xkkc.value.jxll.trim() === '') {
uni.showToast({
title: '请输入教学理念',
icon: 'none',
duration: 2000
});
return;
}
if (!education.xl || education.xl.length === 0) {
uni.showToast({
title: '请添加教学计划',
icon: 'none',
duration: 2000
});
return;
}
// 验证教学计划是否完整
for (let i = 0; i < education.xl.length; i++) {
const item = education.xl[i];
if (!item.value.jhjd || item.value.jhjd.trim() === '' ||
!item.value.jhsj || item.value.jhsj.trim() === '' ||
!item.value.jhdd || item.value.jhdd.trim() === '' ||
!item.value.jhnr || item.value.jhnr.trim() === '') {
uni.showToast({
title: `教学计划第${i + 1}项信息不完整`,
icon: 'none',
duration: 2000
});
return;
}
}
// 直接使用v-model绑定的值不需要调用getValue()
// xkkc.value.kcjsms 和 xkkc.value.jxll 已经通过v-model绑定
xkkc.value.jxjh = JSON.stringify(education.xl)
await jsdXkkcSaveApi(xkkc.value).then(res => {
uni.showToast({
title: '提交成功',
icon: 'success',
duration: 2000
})
// 提交成功后延迟返回列表页
setTimeout(() => {
uni.navigateBack({
delta: 1
});
}, 1500); // 延迟1.5秒,让用户看到成功提示
}).catch(err => {
uni.showToast({
title: '提交失败',
icon: 'error',
duration: 2000
})
});
}
// 初始化
onMounted(() => {
console.log('页面初始化开始...');
xkkc.value = getData;
setValue(getData);
// 处理教学计划数据回显
if (xkkc.value.jxjh) {
try {
// 如果jxjh是字符串先解析
let jxjhData = xkkc.value.jxjh;
if (typeof jxjhData === 'string') {
jxjhData = JSON.parse(jxjhData);
}
// 检查数据结构并转换为正确的格式
if (Array.isArray(jxjhData)) {
education.xl = jxjhData.map(item => {
// 确保每个item都有value属性
if (item.value) {
return {
value: {
jhjd: item.value.jhjd || '',
jhsj: item.value.jhsj || '',
jhdd: item.value.jhdd || '',
jhnr: item.value.jhnr || ''
}
};
}
return item;
});
console.log('教学计划数据回显成功:', education.xl);
} else {
education.xl = [];
console.log('教学计划数据格式不正确,初始化为空数组');
}
} catch (error) {
console.error('解析教学计划数据失败:', error);
education.xl = [];
}
} else {
education.xl = [];
console.log('没有教学计划数据,初始化为空数组');
}
console.log('调用getTeacherHeadPic前teacherHeadPic值:', teacherHeadPic.value);
getTeacherHeadPic(); // 调用获取教师头像的函数
console.log('调用getTeacherHeadPic后teacherHeadPic值:', teacherHeadPic.value);
// 添加调试信息
setTimeout(() => {
console.log('延迟检查teacherHeadPic值:', teacherHeadPic.value);
console.log('teacherHeadPic是否存在:', !!teacherHeadPic.value);
console.log('当前教学计划数据:', education.xl);
}, 1000);
})
</script>
<style lang="scss">
.delete-icon {
position: absolute;
right: 0;
top: 0;
z-index: 1;
}
.course-info {
flex: 1;
.course-name {
font-size: 16px;
font-weight: 500;
color: #333;
margin-bottom: 10px;
}
.course-info-item {
display: flex;
margin-bottom: 12px;
font-size: 12px;
align-items: center;
.info-label {
color: #949AA4;
flex: 0 0 auto;
margin-right: 4px;
white-space: nowrap;
}
.info-data {
flex: 0 0 auto;
color: #333;
font-weight: 400;
}
}
}
.course-image-section {
flex: 0 0 100px;
margin-right: 15px;
display: flex;
align-items: center;
justify-content: center;
.course-image {
width: 80px;
height: 80px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
border: 1px solid #f0f0f0;
}
}
.teacher-name {
vertical-align: middle;
}
.teacher-info-header {
display: flex;
align-items: center;
gap: 15px;
margin-bottom: 15px;
}
.teacher-details {
flex: 1;
display: flex;
flex-direction: column;
gap: 10px;
}
.detail-row {
display: flex;
gap: 20px;
flex-wrap: wrap;
}
.detail-item {
display: flex;
align-items: center;
gap: 5px;
white-space: nowrap;
}
.detail-label {
font-size: 12px;
color: #666;
font-weight: 500;
}
.detail-value {
font-size: 14px;
color: #333;
font-weight: 400;
}
.teacher-input-row {
margin-top: 15px;
}
.add-icon {
cursor: pointer;
color: #007aff;
}
.teacher-avatar-section {
flex: 0 0 60px;
display: flex;
align-items: center;
justify-content: center;
.teacher-avatar {
width: 50px;
height: 50px;
border-radius: 50%;
border: 2px solid #e0e0e0;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
}
.teacher-input-section {
flex: 1;
}
.section-card {
background: white;
border-radius: 8px;
padding: 15px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.section-title {
font-size: 16px;
font-weight: 600;
color: #333;
margin-bottom: 15px;
padding-left: 8px;
border-left: 3px solid #007aff;
.required {
color: #f56c6c;
font-weight: 700;
margin-left: 4px;
}
}
.section-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.form-textarea {
width: 100%; /* 恢复为100%宽度 */
min-height: 80px;
padding: 12px 15px; /* 左右padding保持15px */
border: 1px solid #e0e0e0;
border-radius: 6px;
font-size: 14px;
line-height: 1.5;
resize: vertical;
box-sizing: border-box; /* 确保padding和border包含在宽度内 */
&:focus {
outline: none;
border-color: #007aff;
box-shadow: 0 0 0 2px rgba(0, 122, 255, 0.1);
}
&::placeholder {
color: #999;
}
}
</style>