732 lines
16 KiB
Vue
Raw Normal View History

2025-10-11 21:11:16 +08:00
<!-- src/pages/view/routine/yishiyice/addkc.vue -->
<template>
<view class="add-course-page">
<!-- 提交遮罩层 -->
<view v-if="isSubmitting" class="submit-overlay">
<view class="submit-loading">
<view class="loading-spinner"></view>
<text class="loading-text">保存中...</text>
</view>
</view>
<view class="form-container">
<scroll-view scroll-y class="form-scroll">
<!-- 基本信息 -->
<view class="section-card">
<view class="section-title">
<text class="title-text">基本信息</text>
</view>
<!-- 课程名称 -->
<view class="form-item">
<text class="item-label required">课程名称</text>
<input
v-model="formData.kcmc"
class="item-input"
placeholder="请输入课程名称"
maxlength="100"
/>
</view>
<!-- 课程描述 -->
<view class="form-item">
<text class="item-label required">课程描述</text>
<textarea
v-model="formData.kcms"
class="item-textarea"
placeholder="请输入课程描述"
maxlength="500"
:show-count="true"
/>
</view>
<!-- 课程缩略图 -->
<view class="form-item">
<text class="item-label">课程缩略图</text>
<view class="upload-section">
<view class="upload-list">
<view v-if="thumbnailImage" class="upload-item">
<image
:src="thumbnailImage.url ? imagUrl(thumbnailImage.url) : thumbnailImage.tempPath"
mode="aspectFill"
class="upload-image"
@click="previewThumbnail"
/>
<view class="upload-delete" @click="deleteThumbnail">
<text class="delete-icon">×</text>
</view>
</view>
<view
v-if="!thumbnailImage"
class="upload-add"
@click="chooseThumbnail"
>
<text class="add-icon">+</text>
<text class="add-text">添加缩略图</text>
</view>
</view>
<view class="upload-tip">建议尺寸800×600像素</view>
</view>
</view>
</view>
<!-- 教师设置 -->
<view class="section-card">
<view class="section-title">
<text class="title-text">教师设置</text>
</view>
<!-- 课程导师上课老师 -->
<view class="form-item">
<text class="item-label required">课程导师</text>
<BasicJsPicker
:multiple="false"
title="选择课程导师"
placeholder="请选择课程导师"
searchPlaceholder="输入教师名称查询"
@change="onTeacherChange"
:defaultValue="formData.jsId ? [formData.jsId] : []"
/>
</view>
<!-- 班主任 -->
<view class="form-item">
<text class="item-label">班主任</text>
<BasicJsPicker
:multiple="true"
title="选择班主任"
placeholder="请选择班主任"
searchPlaceholder="输入教师名称查询"
@change="onBzrChange"
:defaultValue="selectedBzrIds"
/>
</view>
<!-- 校级联系人 -->
<view class="form-item">
<text class="item-label">校级联系人</text>
<BasicJsPicker
:multiple="false"
title="选择校级联系人"
placeholder="请选择校级联系人"
searchPlaceholder="输入教师名称查询"
@change="onLjlxChange"
:defaultValue="formData.ljlxId ? [formData.ljlxId] : []"
/>
</view>
</view>
<!-- 发布设置 -->
<view class="section-card">
<view class="section-title">
<text class="title-text">发布设置</text>
</view>
<!-- 是否发布 -->
<view class="form-item">
<text class="item-label required">发布状态</text>
<picker
:range="publishOptions"
range-key="label"
:value="getPublishIndex()"
@change="onPublishChange"
class="item-picker"
>
<view class="picker-content">
<text class="picker-text">{{ getPublishLabel() }}</text>
<text class="picker-arrow"></text>
</view>
</picker>
</view>
</view>
<!-- 底部留白避免被按钮遮挡 -->
<view style="height: 100px;"></view>
</scroll-view>
</view>
<!-- 底部操作按钮 -->
<view class="bottom-actions">
<view class="action-buttons">
<button @click="goBack" class="cancel-btn">取消</button>
<button @click="handleSubmit" :disabled="isSubmitting" class="submit-btn">
{{ isSubmitting ? '保存中...' : '保存' }}
</button>
</view>
</view>
</view>
</template>
<script lang="ts" setup>
import { ref, reactive } from 'vue';
import { kcjbSaveApi } from '@/api/base/kcjbApi';
import { attachmentUpload } from '@/api/system/upload';
import { imagUrl } from '@/utils';
import BasicJsPicker from '@/components/BasicForm/components/BasicJsPicker.vue';
// 图片项类型
interface ImageItem {
tempPath?: string;
url?: string;
name?: string;
}
// 表单数据
const formData = reactive({
kcmc: '', // 课程名称
kcms: '', // 课程描述
jsId: '', // 上课老师ID
jsxm: '', // 上课老师姓名
bzrId: '', // 班主任ID逗号分隔
bzrxm: '', // 班主任姓名(逗号分隔)
ljlxId: '', // 校级联系人ID
ljlxxm: '', // 校级联系人姓名
kcjbtp: '', // 课程缩略图
kczt: 1 // 是否发布1已发布0未发布
});
// 缩略图
const thumbnailImage = ref<ImageItem | null>(null);
// 班主任选中的ID数组
const selectedBzrIds = ref<string[]>([]);
// 提交状态
const isSubmitting = ref(false);
// 发布状态选项
const publishOptions = [
{ label: '已发布', value: 1 },
{ label: '未发布', value: 0 }
];
// 获取发布状态索引
const getPublishIndex = () => {
return publishOptions.findIndex(item => item.value === formData.kczt);
};
// 获取发布状态标签
const getPublishLabel = () => {
const option = publishOptions.find(item => item.value === formData.kczt);
return option ? option.label : '已发布';
};
// 发布状态改变
const onPublishChange = (e: any) => {
const index = e.detail.value;
formData.kczt = publishOptions[index].value;
};
// 课程导师改变
const onTeacherChange = (selected: any[]) => {
console.log('课程导师选择:', selected);
if (selected && selected.length > 0) {
const teacher = selected[0];
formData.jsId = teacher.id;
formData.jsxm = teacher.jsxm;
} else {
formData.jsId = '';
formData.jsxm = '';
}
};
// 班主任改变
const onBzrChange = (selected: any[]) => {
console.log('班主任选择:', selected);
if (selected && selected.length > 0) {
selectedBzrIds.value = selected.map(item => item.id);
formData.bzrId = selected.map(item => item.id).join(',');
formData.bzrxm = selected.map(item => item.jsxm).join(',');
} else {
selectedBzrIds.value = [];
formData.bzrId = '';
formData.bzrxm = '';
}
};
// 校级联系人改变
const onLjlxChange = (selected: any[]) => {
console.log('校级联系人选择:', selected);
if (selected && selected.length > 0) {
const contact = selected[0];
formData.ljlxId = contact.id;
formData.ljlxxm = contact.jsxm;
} else {
formData.ljlxId = '';
formData.ljlxxm = '';
}
};
// 选择缩略图
const chooseThumbnail = () => {
uni.chooseImage({
count: 1,
sizeType: ['compressed'],
sourceType: ['album', 'camera'],
success: (res) => {
const tempPath = res.tempFilePaths[0];
thumbnailImage.value = { tempPath };
// 立即上传
uploadThumbnail(tempPath);
},
fail: (err) => {
console.error('选择图片失败:', err);
uni.showToast({
title: '选择图片失败',
icon: 'none'
});
}
});
};
// 上传缩略图
const uploadThumbnail = async (filePath: string) => {
uni.showLoading({ title: '上传中...' });
try {
const result = await attachmentUpload(filePath);
console.log('缩略图上传成功:', result);
if (thumbnailImage.value) {
thumbnailImage.value.url = result.url || result;
formData.kcjbtp = result.url || result;
}
uni.hideLoading();
uni.showToast({
title: '上传成功',
icon: 'success'
});
} catch (error) {
console.error('缩略图上传失败:', error);
uni.hideLoading();
uni.showToast({
title: '上传失败',
icon: 'error'
});
// 上传失败,清除缩略图
thumbnailImage.value = null;
}
};
// 删除缩略图
const deleteThumbnail = () => {
uni.showModal({
title: '提示',
content: '确定删除该缩略图吗?',
success: (res) => {
if (res.confirm) {
thumbnailImage.value = null;
formData.kcjbtp = '';
}
}
});
};
// 预览缩略图
const previewThumbnail = () => {
if (!thumbnailImage.value) return;
const url = thumbnailImage.value.url
? imagUrl(thumbnailImage.value.url)
: thumbnailImage.value.tempPath;
if (url) {
uni.previewImage({
urls: [url],
current: 0
});
}
};
// 表单验证
const validateForm = () => {
if (!formData.kcmc.trim()) {
uni.showToast({
title: '请输入课程名称',
icon: 'none'
});
return false;
}
if (!formData.kcms.trim()) {
uni.showToast({
title: '请输入课程描述',
icon: 'none'
});
return false;
}
if (!formData.jsId) {
uni.showToast({
title: '请选择课程导师',
icon: 'none'
});
return false;
}
return true;
};
// 提交表单
const handleSubmit = async () => {
if (!validateForm()) {
return;
}
isSubmitting.value = true;
try {
console.log('提交课程数据:', formData);
const submitData = {
kcmc: formData.kcmc,
kcms: formData.kcms,
jsId: formData.jsId,
jsxm: formData.jsxm,
bzrId: formData.bzrId,
bzrxm: formData.bzrxm,
ljlxId: formData.ljlxId,
ljlxxm: formData.ljlxxm,
kcjbtp: formData.kcjbtp,
kczt: formData.kczt
};
await kcjbSaveApi(submitData);
uni.showToast({
title: '保存成功',
icon: 'success',
duration: 1500
});
setTimeout(() => {
uni.navigateBack();
}, 1500);
} catch (error) {
console.error('保存课程失败:', error);
uni.showToast({
title: '保存失败,请重试',
icon: 'error'
});
} finally {
isSubmitting.value = false;
}
};
// 返回
const goBack = () => {
uni.navigateBack();
};
</script>
<style lang="scss" scoped>
.add-course-page {
min-height: 100vh;
background-color: #f5f7fa;
padding-bottom: 80px;
}
// 提交遮罩层
.submit-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 9999;
.submit-loading {
background-color: rgba(0, 0, 0, 0.8);
padding: 30px 40px;
border-radius: 12px;
display: flex;
flex-direction: column;
align-items: center;
gap: 15px;
.loading-spinner {
width: 40px;
height: 40px;
border: 3px solid rgba(255, 255, 255, 0.3);
border-top-color: #fff;
border-radius: 50%;
animation: spin 0.8s linear infinite;
}
.loading-text {
color: #fff;
font-size: 14px;
}
}
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
// 表单容器
.form-container {
flex: 1;
.form-scroll {
height: calc(100vh - 80px);
padding: 15px;
}
}
// 区块卡片
.section-card {
background-color: #ffffff;
border-radius: 12px;
padding: 20px;
margin-bottom: 15px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
.section-title {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 20px;
padding-bottom: 12px;
border-bottom: 2px solid #f0f0f0;
.title-text {
font-size: 16px;
font-weight: 600;
color: #2c3e50;
position: relative;
padding-left: 12px;
&::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 4px;
height: 16px;
background: linear-gradient(135deg, #007aff 0%, #0056cc 100%);
border-radius: 2px;
}
}
}
}
// 表单项
.form-item {
margin-bottom: 20px;
&:last-child {
margin-bottom: 0;
}
.item-label {
display: block;
font-size: 14px;
color: #606266;
margin-bottom: 10px;
font-weight: 500;
&.required::before {
content: '*';
color: #f56c6c;
margin-right: 4px;
}
}
.item-input {
width: 100%;
padding: 12px 15px;
background-color: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 8px;
font-size: 14px;
color: #333;
box-sizing: border-box;
&::placeholder {
color: #c0c4cc;
}
}
.item-textarea {
width: 100%;
min-height: 120px;
padding: 12px 15px;
background-color: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 8px;
font-size: 14px;
color: #333;
box-sizing: border-box;
&::placeholder {
color: #c0c4cc;
}
}
.item-picker {
width: 100%;
.picker-content {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 15px;
background-color: #f8f9fa;
border: 1px solid #e9ecef;
border-radius: 8px;
.picker-text {
font-size: 14px;
color: #333;
}
.picker-arrow {
font-size: 12px;
color: #909399;
}
}
}
}
// 上传区域
.upload-section {
.upload-list {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.upload-item {
position: relative;
width: 100px;
height: 100px;
border-radius: 8px;
overflow: hidden;
.upload-image {
width: 100%;
height: 100%;
object-fit: cover;
}
.upload-delete {
position: absolute;
top: 0;
right: 0;
width: 24px;
height: 24px;
background-color: rgba(0, 0, 0, 0.6);
border-radius: 0 8px 0 8px;
display: flex;
align-items: center;
justify-content: center;
.delete-icon {
color: #fff;
font-size: 18px;
font-weight: bold;
}
}
}
.upload-add {
width: 100px;
height: 100px;
border: 2px dashed #dcdfe6;
border-radius: 8px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #fafafa;
cursor: pointer;
.add-icon {
font-size: 32px;
color: #909399;
margin-bottom: 5px;
}
.add-text {
font-size: 12px;
color: #909399;
}
&:active {
background-color: #f0f0f0;
}
}
.upload-tip {
margin-top: 8px;
font-size: 12px;
color: #909399;
}
}
// 底部操作按钮
.bottom-actions {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background-color: #fff;
padding: 15px;
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.1);
z-index: 100;
.action-buttons {
display: flex;
gap: 15px;
button {
flex: 1;
height: 44px;
border-radius: 8px;
font-size: 16px;
font-weight: 500;
border: none;
&::after {
border: none;
}
}
.cancel-btn {
background-color: #f5f7fa;
color: #606266;
&:active {
background-color: #e9ecef;
}
}
.submit-btn {
background: linear-gradient(135deg, #007aff 0%, #0056cc 100%);
color: #fff;
box-shadow: 0 4px 16px rgba(0, 122, 255, 0.3);
&:active {
opacity: 0.9;
}
&:disabled {
opacity: 0.6;
}
}
}
}
</style>