2025-10-11 21:11:16 +08:00

732 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.

<!-- 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>